@walldock/agent 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.setWallpaperLinux = setWallpaperLinux;
37
+ const node_child_process_1 = require("node:child_process");
38
+ const node_util_1 = require("node:util");
39
+ const fs = __importStar(require("node:fs/promises"));
40
+ const exec = (0, node_util_1.promisify)(node_child_process_1.execFile);
41
+ async function run(cmd, args) {
42
+ await exec(cmd, args);
43
+ }
44
+ async function tryRun(cmd, args) {
45
+ try {
46
+ await run(cmd, args);
47
+ return true;
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ }
53
+ function fileUri(p) {
54
+ return p.startsWith('file://') ? p : `file://${p}`;
55
+ }
56
+ async function gnomeSet(imagePath) {
57
+ const uri = fileUri(imagePath);
58
+ await run('gsettings', ['set', 'org.gnome.desktop.background', 'picture-uri', uri]);
59
+ await tryRun('gsettings', ['set', 'org.gnome.desktop.background', 'picture-uri-dark', uri]);
60
+ }
61
+ async function mateSet(imagePath) {
62
+ await run('gsettings', ['set', 'org.mate.background', 'picture-filename', imagePath]);
63
+ }
64
+ async function cinnamonSet(imagePath) {
65
+ await run('gsettings', ['set', 'org.cinnamon.desktop.background', 'picture-uri', fileUri(imagePath)]);
66
+ }
67
+ async function kdeSet(imagePath, monitorIndex, multiMonitor) {
68
+ if (!multiMonitor) {
69
+ const ok = await tryRun('plasma-apply-wallpaperimage', [imagePath]);
70
+ if (ok)
71
+ return;
72
+ }
73
+ const imgJson = JSON.stringify(fileUri(imagePath));
74
+ const script = `var ds=desktops();if(!ds||ds.length===0){throw "no desktops";}var i=Math.min(${monitorIndex},ds.length-1);var d=ds[i];d.wallpaperPlugin="org.kde.image";d.currentConfigGroup=["Wallpaper","org.kde.image","General"];d.writeConfig("Image",${imgJson});`;
75
+ for (const dbus of ['qdbus6', 'qdbus']) {
76
+ if (await tryRun(dbus, ['org.kde.plasmashell', '/PlasmaShell', 'org.kde.PlasmaShell.evaluateScript', script]))
77
+ return;
78
+ }
79
+ throw new Error('KDE PlasmaShell script failed');
80
+ }
81
+ async function xfceSet(imagePath, monitorIndex) {
82
+ const execPromisified = (0, node_util_1.promisify)(node_child_process_1.execFile);
83
+ const { stdout } = await execPromisified('xfconf-query', ['-c', 'xfce4-desktop', '-l']);
84
+ const keys = stdout.split('\n').map(l => l.trim()).filter(l => l.includes('/last-image')).sort();
85
+ if (keys.length === 0)
86
+ throw new Error('xfce4-desktop: no last-image keys');
87
+ const key = keys[monitorIndex % keys.length];
88
+ const ok = await tryRun('xfconf-query', ['-c', 'xfce4-desktop', '-p', key, '-n', '-t', 'string', '-s', imagePath]);
89
+ if (!ok)
90
+ await run('xfconf-query', ['-c', 'xfce4-desktop', '-p', key, '-s', imagePath, '-t', 'string']);
91
+ }
92
+ async function swaySet(imagePath, monitorIndex) {
93
+ const { stdout } = await exec('swaymsg', ['-t', 'get_outputs']);
94
+ const outputs = JSON.parse(stdout);
95
+ const output = outputs[monitorIndex];
96
+ if (!output)
97
+ throw new Error(`sway: no output at index ${monitorIndex}`);
98
+ await run('swaymsg', ['output', output.name, 'bg', imagePath, 'fill']);
99
+ }
100
+ async function hyprlandSet(imagePath, monitorIndex) {
101
+ const { stdout } = await exec('hyprctl', ['monitors', '-j']);
102
+ const monitors = JSON.parse(stdout);
103
+ const monitor = monitors[monitorIndex];
104
+ if (!monitor)
105
+ throw new Error(`hyprland: no monitor at index ${monitorIndex}`);
106
+ await tryRun('hyprctl', ['hyprpaper', 'preload', imagePath]);
107
+ await run('hyprctl', ['hyprpaper', 'wallpaper', `${monitor.name},${imagePath}`]);
108
+ }
109
+ function detectDesktop() {
110
+ if (process.env['SWAYSOCK'])
111
+ return 'sway';
112
+ if (process.env['HYPRLAND_INSTANCE_SIGNATURE'])
113
+ return 'hyprland';
114
+ const xdg = (process.env['XDG_CURRENT_DESKTOP'] ?? '').toUpperCase();
115
+ for (const token of xdg.split(':')) {
116
+ if (token.includes('XFCE'))
117
+ return 'xfce';
118
+ if (token.includes('KDE'))
119
+ return 'kde';
120
+ if (token.includes('MATE'))
121
+ return 'mate';
122
+ if (token.includes('CINNAMON') || token.includes('X-CINNAMON'))
123
+ return 'cinnamon';
124
+ if (token.includes('GNOME') || token.includes('UNITY') || token.includes('BUDGIE'))
125
+ return 'gnome';
126
+ }
127
+ const session = (process.env['DESKTOP_SESSION'] ?? '').toLowerCase();
128
+ if (session.includes('xfce'))
129
+ return 'xfce';
130
+ if (session.includes('mate'))
131
+ return 'mate';
132
+ if (session.includes('cinnamon'))
133
+ return 'cinnamon';
134
+ return 'unknown';
135
+ }
136
+ async function setWallpaperLinux(imagePath, monitorIndex, totalMonitors) {
137
+ // Verify file exists before attempting
138
+ await fs.access(imagePath);
139
+ const abs = await fs.realpath(imagePath);
140
+ const desktop = detectDesktop();
141
+ const multi = totalMonitors > 1;
142
+ switch (desktop) {
143
+ case 'gnome': return gnomeSet(abs);
144
+ case 'mate': return mateSet(abs);
145
+ case 'cinnamon': return cinnamonSet(abs);
146
+ case 'kde': return kdeSet(abs, monitorIndex, multi);
147
+ case 'xfce': return xfceSet(abs, monitorIndex);
148
+ case 'sway': return swaySet(abs, monitorIndex);
149
+ case 'hyprland': return hyprlandSet(abs, monitorIndex);
150
+ default: throw new Error(`Unsupported desktop environment (XDG_CURRENT_DESKTOP=${process.env['XDG_CURRENT_DESKTOP'] ?? 'unset'})`);
151
+ }
152
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Set wallpaper on macOS.
3
+ * Uses osascript System Events `desktop N` where N = monitorIndex + 1.
4
+ * Matches the order NSScreen.screens returns them (same as our Swift enumeration).
5
+ */
6
+ export declare function setWallpaperMacOS(imagePath: string, monitorIndex: number): Promise<void>;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setWallpaperMacOS = setWallpaperMacOS;
4
+ const node_child_process_1 = require("node:child_process");
5
+ const node_util_1 = require("node:util");
6
+ const exec = (0, node_util_1.promisify)(node_child_process_1.execFile);
7
+ /**
8
+ * Set wallpaper on macOS.
9
+ * Uses osascript System Events `desktop N` where N = monitorIndex + 1.
10
+ * Matches the order NSScreen.screens returns them (same as our Swift enumeration).
11
+ */
12
+ async function setWallpaperMacOS(imagePath, monitorIndex) {
13
+ const desktopN = monitorIndex + 1;
14
+ const script = `
15
+ tell application "System Events"
16
+ set picture of desktop ${desktopN} to POSIX file "${imagePath.replace(/"/g, '\\"')}"
17
+ end tell`.trim();
18
+ await exec('osascript', ['-e', script]);
19
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Set wallpaper on Windows using the IDesktopWallpaper COM interface via inline C# in PowerShell.
3
+ * monitorIndex maps to GetMonitorDevicePathAt(monitorIndex) — same order as our screen enumeration.
4
+ */
5
+ export declare function setWallpaperWindows(imagePath: string, monitorIndex: number): Promise<void>;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setWallpaperWindows = setWallpaperWindows;
4
+ const node_child_process_1 = require("node:child_process");
5
+ const node_util_1 = require("node:util");
6
+ const exec = (0, node_util_1.promisify)(node_child_process_1.execFile);
7
+ /**
8
+ * Set wallpaper on Windows using the IDesktopWallpaper COM interface via inline C# in PowerShell.
9
+ * monitorIndex maps to GetMonitorDevicePathAt(monitorIndex) — same order as our screen enumeration.
10
+ */
11
+ async function setWallpaperWindows(imagePath, monitorIndex) {
12
+ // Escape path for PowerShell string
13
+ const safePath = imagePath.replace(/'/g, "''");
14
+ const script = `
15
+ $ErrorActionPreference = 'Stop'
16
+ Add-Type @"
17
+ using System;
18
+ using System.Runtime.InteropServices;
19
+
20
+ [ComImport, Guid("B92B56A9-8B55-4E14-9A89-0199BBB6F93B"),
21
+ InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
22
+ public interface IDesktopWallpaper {
23
+ void SetWallpaper([MarshalAs(UnmanagedType.LPWStr)] string monitorID,
24
+ [MarshalAs(UnmanagedType.LPWStr)] string wallpaper);
25
+ [return: MarshalAs(UnmanagedType.LPWStr)]
26
+ string GetWallpaper([MarshalAs(UnmanagedType.LPWStr)] string monitorID);
27
+ [return: MarshalAs(UnmanagedType.LPWStr)]
28
+ string GetMonitorDevicePathAt(uint monitorIndex);
29
+ [return: MarshalAs(UnmanagedType.U4)]
30
+ uint GetMonitorDevicePathCount();
31
+ void GetMonitorRECT([MarshalAs(UnmanagedType.LPWStr)] string monitorID,
32
+ out tagRECT displayRect);
33
+ void SetBackgroundColor(uint color);
34
+ uint GetBackgroundColor();
35
+ void SetPosition(int position);
36
+ int GetPosition();
37
+ void SetSlideshow(object items);
38
+ void GetSlideshow(out object items);
39
+ void SetSlideshowOptions(int options, uint tick);
40
+ void GetSlideshowOptions(out int options, out uint tick);
41
+ void AdvanceSlideshow([MarshalAs(UnmanagedType.LPWStr)] string monitorID, int direction);
42
+ int GetStatus([MarshalAs(UnmanagedType.LPWStr)] string monitorID);
43
+ bool Enable(bool enable);
44
+ }
45
+
46
+ [StructLayout(LayoutKind.Sequential)]
47
+ public struct tagRECT { public int left, top, right, bottom; }
48
+
49
+ [ComImport, Guid("C2CF3110-460E-4FC1-B9D0-8A1C0C9CC4BD")]
50
+ public class DesktopWallpaperClass {}
51
+ "@
52
+ $wt = [System.Activator]::CreateInstance([Type]::GetTypeFromCLSID([Guid]'C2CF3110-460E-4FC1-B9D0-8A1C0C9CC4BD'))
53
+ $wp = [IDesktopWallpaper]$wt
54
+ $monPath = $wp.GetMonitorDevicePathAt(${monitorIndex})
55
+ $wp.SetWallpaper($monPath, '${safePath}')
56
+ `.trim();
57
+ await exec('powershell.exe', ['-NoProfile', '-Command', script]);
58
+ }
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@walldock/agent",
3
+ "version": "0.1.0",
4
+ "description": "Walldock desktop agent — sync wallpapers across all your screens",
5
+ "license": "MIT",
6
+ "main": "dist/main.js",
7
+ "bin": {
8
+ "walldock-agent": "dist/main.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "engines": {
14
+ "node": ">=18.0.0"
15
+ },
16
+ "dependencies": {
17
+ "keytar": "^7.9.0"
18
+ },
19
+ "devDependencies": {
20
+ "@types/keytar": "^4.4.2",
21
+ "@types/node": "^20.0.0",
22
+ "typescript": "^5.4.0"
23
+ },
24
+ "scripts": {
25
+ "build": "tsc",
26
+ "start": "node dist/main.js"
27
+ }
28
+ }