Extension working!
This commit is contained in:
parent
4259725632
commit
19c9d8e045
2 changed files with 312 additions and 6 deletions
|
@ -1,5 +1,5 @@
|
||||||
import { Extension } from "resource:///org/gnome/shell/extensions/extension.js"
|
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 {
|
export default class AutoRotate extends Extension {
|
||||||
_listenerId?: number
|
_listenerId?: number
|
||||||
|
@ -13,11 +13,7 @@ export default class AutoRotate extends Extension {
|
||||||
global.display.get_monitor_in_fullscreen(index)
|
global.display.get_monitor_in_fullscreen(index)
|
||||||
) != -1
|
) != -1
|
||||||
|
|
||||||
const { Meta } = imports.gi;
|
setMonitorTransform(isFullscreen ? 1 : 0)
|
||||||
const monitorManager = Meta.MonitorManager.;
|
|
||||||
if (isFullscreen) {
|
|
||||||
} else {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
310
monitorDBusUtils.ts
Normal file
310
monitorDBusUtils.ts
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue