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 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
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