Extension working!

This commit is contained in:
Henry Hiles 2025-08-10 22:29:19 -04:00
commit 19c9d8e045
No known key found for this signature in database
2 changed files with 312 additions and 6 deletions

View file

@ -1,5 +1,5 @@
import { Extension } from "resource:///org/gnome/shell/extensions/extension.js"
import Gio from "gi://Gio"
import { setMonitorTransform } from "./monitorDBusUtils.js"
export default class AutoRotate extends Extension {
_listenerId?: number
@ -13,11 +13,7 @@ export default class AutoRotate extends Extension {
global.display.get_monitor_in_fullscreen(index)
) != -1
const { Meta } = imports.gi;
const monitorManager = Meta.MonitorManager.;
if (isFullscreen) {
} else {
}
setMonitorTransform(isFullscreen ? 1 : 0)
}
)
}

310
monitorDBusUtils.ts Normal file
View file

@ -0,0 +1,310 @@
// All credit goes to mityax, code taken from here, thank you: https://github.com/mityax/gnome-extension-touchup/blob/main/src/features/screenRotateUtils/floatingScreenRotateButtonFeature.ts
import Gio from "gi://Gio"
import GLib from "gi://GLib"
export const Methods = Object.freeze({
verify: 0,
temporary: 1,
persistent: 2,
})
export function callDbusMethod(
method: string,
handler: Gio.AsyncReadyCallback<Gio.DBusConnection> | null,
params: GLib.Variant | null = null
): void {
if (handler !== null && handler !== undefined) {
Gio.DBus.session.call(
"org.gnome.Mutter.DisplayConfig",
"/org/gnome/Mutter/DisplayConfig",
"org.gnome.Mutter.DisplayConfig",
method,
params,
null,
Gio.DBusCallFlags.NONE,
-1,
null,
handler
)
} else {
Gio.DBus.session.call(
"org.gnome.Mutter.DisplayConfig",
"/org/gnome/Mutter/DisplayConfig",
"org.gnome.Mutter.DisplayConfig",
method,
params,
null,
Gio.DBusCallFlags.NONE,
-1,
null
)
}
}
export function setMonitorTransform(
transform: LogicalMonitorTransform,
targetMonitor?: Monitor
): void {
DisplayConfigState.getCurrent().then((state) => {
targetMonitor ??= state.builtinMonitor ?? state.monitors[0]
const logicalMonitor = state.getLogicalMonitorFor(
targetMonitor.connector
)
if (logicalMonitor) {
logicalMonitor.transform = transform as any
callDbusMethod(
"ApplyMonitorsConfig",
null,
state.packToApply(Methods.temporary)
)
}
})
}
/**
* Possible transform values:
* - 0: normal
* - 1: 90°
* - 2: 180°
* - 3: 270°
* - 4: flipped
* - 5: 90° flipped
* - 6: 180° flipped
* - 7: 270° flipped
*/
export type LogicalMonitorTransform = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
export class LogicalMonitor {
x: number
y: number
scale: number
transform: LogicalMonitorTransform
primary: boolean
monitors: [string, string, string, string][]
properties: Record<string, any>
constructor(variant: GLib.Variant) {
const unpacked = variant.unpack() as any[]
this.x = unpacked[0].unpack()
this.y = unpacked[1].unpack()
this.scale = unpacked[2].unpack()
this.transform = unpacked[3].unpack()
this.primary = unpacked[4].unpack()
this.monitors = unpacked[5].deep_unpack()
this.properties = unpacked[6].unpack()
for (const key in this.properties) {
this.properties[key] = this.properties[key].unpack().unpack()
}
}
}
export class Monitor {
connector: string
vendorName: string
productName: string
productSerial: string
currentModeId: number | null = null
isUnderscanning: boolean = false
isBuiltin: boolean = false
constructor(variant: GLib.Variant) {
// variant.deepUnpack() yields (in Gnome 48):
// (see for docs: https://gitlab.gnome.org/GNOME/mutter/-/blob/main/data/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml#L385)
// [
// [ // - [0] - meta information
// "LVDS1", // - [0][0] - connector
// "MetaProducts Inc.", // vendor name
// "MetaMonitor", // product name
// "0xC0FFEE-1" // product serial
// ],
// [ // - [1] - "modes"
// [
// "1400x1000@60.000", // - [1][0]
// 1400,
// 1000,
// 60,
// 1,
// [ // - [1][1]
// 1, // - [1][1][0]
// 1.25,
// 1.5037593841552734,
// 1.7543859481811523
// ],
// {
// "is-current": {},
// "is-preferred": {}
// }
// ]
// ],
// { // - [2] - "props"
// "is-builtin": {},
// "display-name": {},
// "is-for-lease": {},
// "color-mode": {},
// "supported-color-modes": {}
// }
// ]
const unpacked = variant.deepUnpack() as any[]
this.connector = unpacked[0][0]
this.vendorName = unpacked[0][1]
this.productName = unpacked[0][2]
this.productSerial = unpacked[0][3]
const modes = unpacked[1]
for (const modeVariant of modes) {
const mode = modeVariant
const id = mode[0]
const modeProps = mode[6]
if ("is-current" in modeProps) {
const isCurrent = modeProps["is-current"].get_boolean()
if (isCurrent) {
this.currentModeId = id
break
}
}
}
const props = unpacked[2]
if ("is-underscanning" in props) {
this.isUnderscanning = props["is-underscanning"].get_boolean()
}
if ("is-builtin" in props) {
this.isBuiltin = props["is-builtin"].get_boolean()
}
}
/**
* This method makes no guarantees about the returned string, except that it will uniquely
* identify this physical monitor, even after disconnecting and reconnecting it.
*
* This is at the moment done by monitor metadata by constructing a tuple of (vendor name,
* product name, serial).
*/
constructMonitorId(): string {
return `${this.vendorName}::${this.productName}::${this.productSerial}`
}
}
export class DisplayConfigState {
serial: number
monitors: Monitor[] = []
logicalMonitors: LogicalMonitor[] = []
properties: Record<string, any>
private constructor(result: GLib.Variant) {
const unpacked = result.unpack() as any[]
this.serial = unpacked[0].unpack()
const monitorVariants = unpacked[1].unpack()
for (const monitorPacked of monitorVariants) {
this.monitors.push(new Monitor(monitorPacked))
}
const logicalMonitorVariants = unpacked[2].unpack()
for (const logicalMonitorPacked of logicalMonitorVariants) {
this.logicalMonitors.push(new LogicalMonitor(logicalMonitorPacked))
}
this.properties = unpacked[3].unpack()
for (const key in this.properties) {
this.properties[key] = this.properties[key].unpack().unpack()
}
}
static async getCurrent(): Promise<DisplayConfigState> {
return new Promise((resolve, reject) => {
callDbusMethod("GetCurrentState", (conn, res) => {
try {
const reply = conn?.call_finish(res)!
const configState = new DisplayConfigState(reply)
resolve(configState)
} catch (err) {
reject(err)
}
})
})
}
get builtinMonitor(): Monitor {
return (
this.monitors.find((monitor) => monitor.isBuiltin) ??
this.monitors[0]
)
}
getMonitor(connector: string): Monitor | null {
return (
this.monitors.find((monitor) => monitor.connector === connector) ||
null
)
}
getLogicalMonitorFor(connector: string): LogicalMonitor | null {
return (
this.logicalMonitors.find((logMonitor) =>
logMonitor.monitors.some(
(lmMonitor) => connector === lmMonitor[0]
)
) || null
)
}
setPrimaryMonitor(monitor: LogicalMonitor) {
this.logicalMonitors.forEach((m) => (m.primary = false))
monitor.primary = true
callDbusMethod(
"ApplyMonitorsConfig",
null,
this.packToApply(Methods.temporary)
)
}
packToApply(method: number): GLib.Variant {
const packing = [this.serial, method, [], {}]
const logicalMonitors = packing[2] as any[]
const properties: Record<string, any> = packing[3]
this.logicalMonitors.forEach((logicalMonitor) => {
const lmonitorPack = [
logicalMonitor.x,
logicalMonitor.y,
logicalMonitor.scale,
logicalMonitor.transform,
logicalMonitor.primary,
[],
]
const monitors = lmonitorPack[5] as any[]
for (const logMonitor of logicalMonitor.monitors) {
const connector = logMonitor[0]
const monitor = this.getMonitor(connector)
if (monitor) {
monitors.push([
connector,
monitor.currentModeId,
{
enable_underscanning: new GLib.Variant(
"b",
monitor.isUnderscanning
),
},
])
}
}
logicalMonitors.push(lmonitorPack)
})
if ("layout-mode" in this.properties) {
properties["layout-mode"] = new GLib.Variant(
"b",
this.properties["layout-mode"]
)
}
return new GLib.Variant("(uua(iiduba(ssa{sv}))a{sv})", packing)
}
}