Skip to content

Instantly share code, notes, and snippets.

@andrewmackrodt
Last active April 7, 2022 04:07
Show Gist options
  • Save andrewmackrodt/89b6b998848f57b121cee68e580c4eb4 to your computer and use it in GitHub Desktop.
Save andrewmackrodt/89b6b998848f57b121cee68e580c4eb4 to your computer and use it in GitHub Desktop.
Toggles fractional scaling between 100% and 125%
#!./node_modules/.bin/ts-node-dev
/**
* Based on https://askubuntu.com/a/1267328/170380
*/
import { exec } from 'child_process'
import dbus from 'dbus-next'
const sessionBus = dbus.sessionBus()
const main = async () => {
const namespace = 'org.gnome.Mutter.DisplayConfig'
const path = '/org/gnome/Mutter/DisplayConfig'
const proxyObject = await sessionBus.getProxyObject(namespace, path)
const clientInterface = proxyObject.getInterface(namespace)
let currentState = await clientInterface.GetCurrentState()
const [/* state */, connectedMonitors, logicalMonitors] = currentState
const logicalMonitor = logicalMonitors.find((monitor: any) => monitor[4] === true)
if ( ! logicalMonitor) {
throw new Error('Could not determine primary monitor')
}
let [x, y, scale, transform, primary, monitors] = logicalMonitor
const connector = monitors[0][0]
const connectedMonitor = connectedMonitors.find((monitor: any) => monitor[0][0] === connector)
if ( ! connectedMonitor) {
throw new Error('Could not determine connected monitor')
}
let currentMode: string | undefined
const displayModes = connectedMonitor[1]
for (const mode of displayModes) {
if (mode[6]['is-current']?.value) {
currentMode = mode[0]
break
}
}
if ( ! currentMode) {
throw new Error('Could not determine current display mode')
}
const execAsync = (command: string): Promise<string> => {
return new Promise((resolve, reject) => {
exec(command, (err, stdout, stderr) => {
if (stderr.length > 0) console.error(stderr)
if (err) {
reject(err)
} else {
resolve(stdout.replace(/\r?\n$/, ''))
}
})
})
}
const fractionalScalingKey = 'x11-randr-fractional-scaling'
const featuresText = await execAsync('gsettings get org.gnome.mutter experimental-features')
const featuresJson: string[] = JSON.parse(featuresText.replace(/^@as /, '').replace(/'/g, '"'))
if ( ! featuresJson.includes(fractionalScalingKey)) {
console.log('Enabling Mutter Experimental Feature:', fractionalScalingKey)
const value = JSON.stringify(featuresJson.concat(fractionalScalingKey)).replace(/"/g, '\'')
await execAsync(`gsettings set org.gnome.mutter experimental-features "${value}"`)
scale = 1.25
} else {
console.log('Disabling Mutter Experimental Feature:', fractionalScalingKey)
const value = JSON.stringify(featuresJson.filter(v => v !== fractionalScalingKey)).replace(/"/g, '\'')
await execAsync(`gsettings set org.gnome.mutter experimental-features "${value}"`)
scale = 1
}
const config = [[x, y, scale, transform, primary, [[connector, currentMode, {}]]]]
console.log('Applying Config:', JSON.stringify(config))
// refresh current state as changing experimental-features invalidates previous serial
currentState = await clientInterface.GetCurrentState()
const serial = currentState[0]
await clientInterface.ApplyMonitorsConfig(serial, 1, config, {})
}
main().finally(() => {
sessionBus.disconnect()
})
@jonathanbossenger
Copy link

I just stumbled across this from here and it's exactly what I'm looking for. To run this, do I just save the js file, and then call it using node ie node hidpi-switch.ts?

@andrewmackrodt
Copy link
Author

@jonathanbossenger I created a repository over at https://github.com/andrewmackrodt/gnome-scale-switcher

# clone repository and install dependencies
git clone https://github.com/andrewmackrodt/gnome-scale-switcher.git
cd gnome-scale-switcher
npm install

# run the application
npm start

You may want to edit index.ts to match whatever resolution and scaling preferences you need the defaults (lines 9-21) are:

const fractionalModeSet: ModeSet = {
    width: 3840,
    height: 2160,
    refresh: 60,
    scale: 1.5,
}

const nonFractionalModeSet: ModeSet = {
    width: 1920,
    height: 1080,
    refresh: 120,
    scale: 1,
}

Please be warned, I find this to be unstable versus several months ago, I'm not sure if something has changed in the nvidia driver or with gnome/mutter - but sometimes during a resolution switch, the X display does not come back up, requiring a forceful reboot. This seems to happen when turning fractional scaling on/off and then setting the resolution before the display has "refreshed". To try and work around this I've hardcoded a sleep of 10 seconds between these operations.

@jonathanbossenger
Copy link

Thanks, @andrewmackrodt I will check it out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment