dsh-clean-desktop-shell 0.1.11 → 0.1.13

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.
@@ -1,122 +1,122 @@
1
- /**
2
- * Desktop shortcut management (Windows .lnk via Electron's shell API).
3
- *
4
- * Used by:
5
- * - first-run prompt in main.js ("create a desktop shortcut?")
6
- * - the tray "create desktop shortcut" item (always available)
7
- *
8
- * Works in both distribution branches:
9
- * - installer (packaged): targets the installed exe, no arguments
10
- * - plugin-market (bare runtime): targets the provisioned electron.exe
11
- * with the plugin's electron/main.js as its argument
12
- *
13
- * Pure dev mode (npm run dev from a checkout) has no stable executable,
14
- * so the feature stays disabled there.
15
- *
16
- * Electron's built-in shortcut API replaced the earlier hand-rolled
17
- * PowerShell + WScript.Shell COM script: fewer moving parts (no spawned
18
- * interpreter to hang or quote-escape), and it composes with the
19
- * Start-menu shortcut path that already used the same API.
20
- */
21
- import { app, shell } from 'electron'
22
- import { existsSync, readdirSync } from 'node:fs'
23
- import { dirname, join } from 'node:path'
24
- import { fileURLToPath } from 'node:url'
25
- import { APP_USER_MODEL_ID } from './aumid.js'
26
-
27
- const isWin = process.platform === 'win32'
28
- const SHORTCUT_NAME = 'DSH Clean Desktop Shell.lnk'
29
- const PKG_ROOT = dirname(dirname(fileURLToPath(import.meta.url)))
30
- const ICON_ICO = join(PKG_ROOT, 'build', 'icon.ico')
31
-
32
- /**
33
- * Plugin-market mode: the host half spawns a bare electron runtime with the
34
- * plugin's electron/main.js as its entry. process.execPath is the runtime
35
- * electron.exe; process.argv[1] is the entry script (packaged installers
36
- * have no such argument).
37
- */
38
- function pluginArgs() {
39
- if (app.isPackaged) return []
40
- const entry = process.argv[1]
41
- if (entry && /\.js$/i.test(entry)) return [entry]
42
- return []
43
- }
44
-
45
- /** Shortcuts need a stable executable — supported when packaged or in
46
- * plugin mode (where the runtime path is fixed under DSH_HOME). */
47
- export function shortcutSupported() {
48
- return isWin && (app.isPackaged || pluginArgs().length > 0)
49
- }
50
-
51
- /**
52
- * The user's real Desktop. app.getPath('desktop') resolves OneDrive
53
- * redirection and roaming profile policies — a hardcoded ~/Desktop misses
54
- * those and writes the shortcut where the user will never see it.
55
- */
56
- function desktopDir() {
57
- try {
58
- return app.getPath('desktop')
59
- } catch {
60
- return join(app.getPath('home'), 'Desktop')
61
- }
62
- }
63
-
64
- /** True when a desktop shortcut already points at this app's exe. */
65
- export function hasDesktopShortcut() {
66
- if (!isWin || !shortcutSupported()) return false
67
- try {
68
- const target = process.execPath.toLowerCase()
69
- for (const entry of readdirSync(desktopDir())) {
70
- if (!entry.toLowerCase().endsWith('.lnk')) continue
71
- try {
72
- // readShortcutLink throws on invalid/non-shortcut files — skip those.
73
- const link = shell.readShortcutLink(join(desktopDir(), entry))
74
- if (String(link.target).toLowerCase() === target) return true
75
- } catch {
76
- // unreadable .lnk — not ours
77
- }
78
- }
79
- } catch {
80
- // unreadable desktop — treat as "not present"
81
- }
82
- return false
83
- }
84
-
85
- /** Create (or overwrite) the desktop shortcut for this app. Returns bool. */
86
- export function createDesktopShortcut() {
87
- if (!shortcutSupported()) return false
88
- const args = pluginArgs()
89
- const ico = existsSync(ICON_ICO) ? ICON_ICO : undefined
90
- // 'overwrite' makes the call idempotent (safe to re-run after updates).
91
- return shell.writeShortcutLink(join(desktopDir(), SHORTCUT_NAME), 'overwrite', {
92
- target: process.execPath,
93
- args: args.length ? args.join(' ') : undefined,
94
- icon: ico,
95
- iconIndex: 0,
96
- appUserModelId: APP_USER_MODEL_ID,
97
- })
98
- }
99
-
100
- /**
101
- * Ensure a Start-menu shortcut carrying the AppUserModelID. This is what
102
- * makes the taskbar button show our icon: Windows matches a running
103
- * window's AUMID to a shortcut's icon (bare runtime electron.exe has no
104
- * icon resource of its own). Idempotent — the shortcut is only created
105
- * when missing.
106
- */
107
- export function ensureStartMenuShortcut() {
108
- if (!isWin || !shortcutSupported()) return false
109
- const lnkDir = join(
110
- process.env.APPDATA || join(app.getPath('home'), 'AppData', 'Roaming'),
111
- 'Microsoft', 'Windows', 'Start Menu', 'Programs',
112
- )
113
- const lnk = join(lnkDir, SHORTCUT_NAME)
114
- const args = pluginArgs()
115
- return shell.writeShortcutLink(lnk, 'create', {
116
- target: process.execPath,
117
- args: args.length ? args.join(' ') : undefined,
118
- icon: existsSync(ICON_ICO) ? ICON_ICO : undefined,
119
- iconIndex: 0,
120
- appUserModelId: APP_USER_MODEL_ID,
121
- })
122
- }
1
+ /**
2
+ * Desktop shortcut management (Windows .lnk via Electron's shell API).
3
+ *
4
+ * Used by:
5
+ * - first-run prompt in main.js ("create a desktop shortcut?")
6
+ * - the tray "create desktop shortcut" item (always available)
7
+ *
8
+ * Works in both distribution branches:
9
+ * - installer (packaged): targets the installed exe, no arguments
10
+ * - plugin-market (bare runtime): targets the provisioned electron.exe
11
+ * with the plugin's electron/main.js as its argument
12
+ *
13
+ * Pure dev mode (npm run dev from a checkout) has no stable executable,
14
+ * so the feature stays disabled there.
15
+ *
16
+ * Electron's built-in shortcut API replaced the earlier hand-rolled
17
+ * PowerShell + WScript.Shell COM script: fewer moving parts (no spawned
18
+ * interpreter to hang or quote-escape), and it composes with the
19
+ * Start-menu shortcut path that already used the same API.
20
+ */
21
+ import { app, shell } from 'electron'
22
+ import { existsSync, readdirSync } from 'node:fs'
23
+ import { dirname, join } from 'node:path'
24
+ import { fileURLToPath } from 'node:url'
25
+ import { APP_USER_MODEL_ID } from './aumid.js'
26
+
27
+ const isWin = process.platform === 'win32'
28
+ const SHORTCUT_NAME = 'DSH Clean Desktop Shell.lnk'
29
+ const PKG_ROOT = dirname(dirname(fileURLToPath(import.meta.url)))
30
+ const ICON_ICO = join(PKG_ROOT, 'build', 'icon.ico')
31
+
32
+ /**
33
+ * Plugin-market mode: the host half spawns a bare electron runtime with the
34
+ * plugin's electron/main.js as its entry. process.execPath is the runtime
35
+ * electron.exe; process.argv[1] is the entry script (packaged installers
36
+ * have no such argument).
37
+ */
38
+ function pluginArgs() {
39
+ if (app.isPackaged) return []
40
+ const entry = process.argv[1]
41
+ if (entry && /\.js$/i.test(entry)) return [entry]
42
+ return []
43
+ }
44
+
45
+ /** Shortcuts need a stable executable — supported when packaged or in
46
+ * plugin mode (where the runtime path is fixed under DSH_HOME). */
47
+ export function shortcutSupported() {
48
+ return isWin && (app.isPackaged || pluginArgs().length > 0)
49
+ }
50
+
51
+ /**
52
+ * The user's real Desktop. app.getPath('desktop') resolves OneDrive
53
+ * redirection and roaming profile policies — a hardcoded ~/Desktop misses
54
+ * those and writes the shortcut where the user will never see it.
55
+ */
56
+ function desktopDir() {
57
+ try {
58
+ return app.getPath('desktop')
59
+ } catch {
60
+ return join(app.getPath('home'), 'Desktop')
61
+ }
62
+ }
63
+
64
+ /** True when a desktop shortcut already points at this app's exe. */
65
+ export function hasDesktopShortcut() {
66
+ if (!isWin || !shortcutSupported()) return false
67
+ try {
68
+ const target = process.execPath.toLowerCase()
69
+ for (const entry of readdirSync(desktopDir())) {
70
+ if (!entry.toLowerCase().endsWith('.lnk')) continue
71
+ try {
72
+ // readShortcutLink throws on invalid/non-shortcut files — skip those.
73
+ const link = shell.readShortcutLink(join(desktopDir(), entry))
74
+ if (String(link.target).toLowerCase() === target) return true
75
+ } catch {
76
+ // unreadable .lnk — not ours
77
+ }
78
+ }
79
+ } catch {
80
+ // unreadable desktop — treat as "not present"
81
+ }
82
+ return false
83
+ }
84
+
85
+ /** Create (or overwrite) the desktop shortcut for this app. Returns bool. */
86
+ export function createDesktopShortcut() {
87
+ if (!shortcutSupported()) return false
88
+ const args = pluginArgs()
89
+ const ico = existsSync(ICON_ICO) ? ICON_ICO : undefined
90
+ // 'overwrite' makes the call idempotent (safe to re-run after updates).
91
+ return shell.writeShortcutLink(join(desktopDir(), SHORTCUT_NAME), 'overwrite', {
92
+ target: process.execPath,
93
+ args: args.length ? args.join(' ') : undefined,
94
+ icon: ico,
95
+ iconIndex: 0,
96
+ appUserModelId: APP_USER_MODEL_ID,
97
+ })
98
+ }
99
+
100
+ /**
101
+ * Ensure a Start-menu shortcut carrying the AppUserModelID. This is what
102
+ * makes the taskbar button show our icon: Windows matches a running
103
+ * window's AUMID to a shortcut's icon (bare runtime electron.exe has no
104
+ * icon resource of its own). Idempotent — the shortcut is only created
105
+ * when missing.
106
+ */
107
+ export function ensureStartMenuShortcut() {
108
+ if (!isWin || !shortcutSupported()) return false
109
+ const lnkDir = join(
110
+ process.env.APPDATA || join(app.getPath('home'), 'AppData', 'Roaming'),
111
+ 'Microsoft', 'Windows', 'Start Menu', 'Programs',
112
+ )
113
+ const lnk = join(lnkDir, SHORTCUT_NAME)
114
+ const args = pluginArgs()
115
+ return shell.writeShortcutLink(lnk, 'create', {
116
+ target: process.execPath,
117
+ args: args.length ? args.join(' ') : undefined,
118
+ icon: existsSync(ICON_ICO) ? ICON_ICO : undefined,
119
+ iconIndex: 0,
120
+ appUserModelId: APP_USER_MODEL_ID,
121
+ })
122
+ }