Compare commits
No commits in common. "19c9d8e045a1ce2946fd09254aa90deaee37a823" and "442b0f567daa53d122f2cac5836b7bd10e8f746d" have entirely different histories.
19c9d8e045
...
442b0f567d
5 changed files with 9 additions and 337 deletions
1
.envrc
1
.envrc
|
@ -1 +0,0 @@
|
||||||
use flake
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,2 +1 @@
|
||||||
.direnv/
|
.direnv/
|
||||||
node_modules/
|
|
25
extension.ts
25
extension.ts
|
@ -1,25 +1,14 @@
|
||||||
import { Extension } from "resource:///org/gnome/shell/extensions/extension.js"
|
import { Extension } from "resource:///org/gnome/shell/extensions/extension.js"
|
||||||
import { setMonitorTransform } from "./monitorDBusUtils.js"
|
import * as Main from "resource:///org/gnome/shell/ui/main.js"
|
||||||
|
import * as MessageTray from "resource:///org/gnome/shell/ui/messageTray.js"
|
||||||
|
|
||||||
export default class AutoRotate extends Extension {
|
export default class MyExtension extends Extension {
|
||||||
_listenerId?: number
|
|
||||||
enable() {
|
enable() {
|
||||||
this._listenerId = global.display.connect(
|
Main.notify(
|
||||||
"in-fullscreen-changed",
|
"Simple Notification",
|
||||||
() => {
|
"A notification with a title and body"
|
||||||
const monitors = global.display.get_n_monitors()
|
|
||||||
const isFullscreen =
|
|
||||||
[...new Array(monitors)].findIndex((_, index) =>
|
|
||||||
global.display.get_monitor_in_fullscreen(index)
|
|
||||||
) != -1
|
|
||||||
|
|
||||||
setMonitorTransform(isFullscreen ? 1 : 0)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
disable() {
|
disable() {}
|
||||||
if (this._listenerId != null)
|
|
||||||
global.display.disconnect(this._listenerId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,10 +20,6 @@
|
||||||
in {
|
in {
|
||||||
_module.args.pkgs = import inputs.nixpkgs {inherit system;};
|
_module.args.pkgs = import inputs.nixpkgs {inherit system;};
|
||||||
|
|
||||||
devShells.default = pkgs.mkShell {
|
|
||||||
buildInputs = with pkgs; [nodejs];
|
|
||||||
};
|
|
||||||
|
|
||||||
packages.default = pkgs.buildNpmPackage (finalAttrs: {
|
packages.default = pkgs.buildNpmPackage (finalAttrs: {
|
||||||
pname = name;
|
pname = name;
|
||||||
version = "1.0.0";
|
version = "1.0.0";
|
||||||
|
@ -50,7 +46,7 @@
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
apps.default = {
|
apps.install = {
|
||||||
type = "app";
|
type = "app";
|
||||||
program = let
|
program = let
|
||||||
file = "/tmp/${uuid}.zip";
|
file = "/tmp/${uuid}.zip";
|
||||||
|
@ -58,7 +54,6 @@
|
||||||
pkgs.writeShellScriptBin "build" ''
|
pkgs.writeShellScriptBin "build" ''
|
||||||
env -C ${self'.packages.default}/${extDir} ${lib.getExe pkgs.zip} ${file} -FS9r .
|
env -C ${self'.packages.default}/${extDir} ${lib.getExe pkgs.zip} ${file} -FS9r .
|
||||||
gnome-extensions install --force ${file}
|
gnome-extensions install --force ${file}
|
||||||
dbus-run-session -- gnome-shell --nested --wayland
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,310 +0,0 @@
|
||||||
// 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