qdesk 1.0.12 → 1.0.14

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.
@@ -18,6 +18,12 @@ export class KeyBindings {
18
18
  console.log(`[add] Bound ${chord} -> "${info.name}"`);
19
19
  save(this.bindings);
20
20
  }
21
+ reportMissing(chord) {
22
+ const info = this.bindings.get(chord);
23
+ if (info) {
24
+ info.missing = true;
25
+ }
26
+ }
21
27
  delete(chord) {
22
28
  const output = this.bindings.delete(chord);
23
29
  if (output) {
@@ -33,7 +39,7 @@ export class KeyBindings {
33
39
  return this.bindings.size;
34
40
  }
35
41
  entries() {
36
- return this.bindings.entries();
42
+ return [...this.bindings.entries()].sort(([chordA], [chordB]) => chordA.localeCompare(chordB));
37
43
  }
38
44
  }
39
45
  function load() {
package/dist/index.js CHANGED
@@ -93,7 +93,11 @@ startListening({
93
93
  name: binding.name,
94
94
  type: "missing",
95
95
  });
96
- bindings.delete(chord);
96
+ bindings.reportMissing(chord);
97
+ setScreenHue("remove");
98
+ setTimeout(() => {
99
+ setScreenHue("off");
100
+ }, 50);
97
101
  }
98
102
  else if (result === "activated") {
99
103
  logSwitch({
@@ -119,8 +123,9 @@ function printBindings() {
119
123
  console.log(" (none)");
120
124
  }
121
125
  else {
126
+ const maxBindingLength = Math.max(...Array.from(bindings.entries()).map(([chord]) => chord.length));
122
127
  for (const [chord, window] of bindings.entries()) {
123
- console.log(` ${chord} -> "${window.name}"`);
128
+ console.log(` ${chord.padEnd(maxBindingLength)} ->${window.missing ? "❓" : " "} "${window.name}"`);
124
129
  }
125
130
  }
126
131
  console.log("");
@@ -131,7 +136,7 @@ console.log(` Dropping a chord: ${DROP_CHORD}`);
131
136
  console.log(" CLI overrides: -a <combo>, -d <combo>");
132
137
  for (const [chord, window] of bindings.entries()) {
133
138
  if (!checkIfWindowExists(window.handle)) {
134
- bindings.delete(chord);
139
+ bindings.reportMissing(chord);
135
140
  }
136
141
  }
137
142
  if (bindings.size > 0) {
@@ -14,22 +14,104 @@ export const WH_KEYBOARD_LL = 13;
14
14
  export const WM_KEYDOWN = 0x0100;
15
15
  export const WM_SYSKEYDOWN = 0x0104;
16
16
  export const PM_REMOVE = 0x0001;
17
+ const VK_TOKEN_TO_CODE = {
18
+ backspace: 0x08,
19
+ tab: 0x09,
20
+ clear: 0x0c,
21
+ enter: 0x0d,
22
+ return: 0x0d,
23
+ pause: 0x13,
24
+ capslock: 0x14,
25
+ esc: VK_ESCAPE,
26
+ escape: VK_ESCAPE,
27
+ space: 0x20,
28
+ pageup: 0x21,
29
+ pagedown: 0x22,
30
+ end: 0x23,
31
+ home: 0x24,
32
+ left: 0x25,
33
+ up: 0x26,
34
+ right: 0x27,
35
+ down: 0x28,
36
+ printscreen: 0x2c,
37
+ prtsc: 0x2c,
38
+ snapshot: 0x2c,
39
+ insert: 0x2d,
40
+ delete: 0x2e,
41
+ num0: 0x60,
42
+ num1: 0x61,
43
+ num2: 0x62,
44
+ num3: 0x63,
45
+ num4: 0x64,
46
+ num5: 0x65,
47
+ num6: 0x66,
48
+ num7: 0x67,
49
+ num8: 0x68,
50
+ num9: 0x69,
51
+ num_mul: 0x6a,
52
+ num_add: 0x6b,
53
+ num_sub: 0x6d,
54
+ num_dec: 0x6e,
55
+ num_div: 0x6f,
56
+ };
57
+ const VK_CODE_TO_TOKEN = new Map(Object.entries(VK_TOKEN_TO_CODE).map(([token, code]) => [code, token]));
58
+ VK_CODE_TO_TOKEN.set(VK_ESCAPE, "esc");
59
+ VK_CODE_TO_TOKEN.set(0x0d, "enter");
60
+ VK_CODE_TO_TOKEN.set(0x2c, "printscreen");
61
+ const MODIFIER_TOKENS = new Set([
62
+ "ctrl",
63
+ "control",
64
+ "alt",
65
+ "shift",
66
+ "win",
67
+ "meta",
68
+ ]);
69
+ function normalizeKeyToken(rawKey) {
70
+ const key = rawKey.trim().toLowerCase();
71
+ if (!key) {
72
+ return null;
73
+ }
74
+ if (/^[0-9a-z]$/.test(key)) {
75
+ return key;
76
+ }
77
+ if (/^f([1-9]|1[0-9]|2[0-4])$/.test(key)) {
78
+ return key;
79
+ }
80
+ if (VK_TOKEN_TO_CODE[key] !== undefined) {
81
+ return VK_CODE_TO_TOKEN.get(VK_TOKEN_TO_CODE[key]) ?? key;
82
+ }
83
+ return null;
84
+ }
85
+ function keyTokenToVk(key) {
86
+ if (/^[0-9]$/.test(key)) {
87
+ return key.charCodeAt(0);
88
+ }
89
+ if (/^[a-z]$/.test(key)) {
90
+ return key.toUpperCase().charCodeAt(0);
91
+ }
92
+ const functionMatch = /^f([1-9]|1[0-9]|2[0-4])$/.exec(key);
93
+ if (functionMatch) {
94
+ const fn = Number(functionMatch[1]);
95
+ return 0x70 + (fn - 1);
96
+ }
97
+ return VK_TOKEN_TO_CODE[key] ?? -1;
98
+ }
17
99
  export function normalizeChord(chord) {
18
100
  chord = chord.trim().toLowerCase();
19
101
  const parts = chord
20
102
  .split("+")
21
103
  .map((part) => part.trim().toLowerCase())
22
104
  .filter(Boolean);
23
- if (parts.length < 2) {
105
+ if (parts.length === 0) {
24
106
  return null;
25
107
  }
26
- const key = parts[parts.length - 1];
27
- if (!/^[0-9a-z]$/.test(key) && !/^f([1-9]|1[0-9]|2[0-4])$/.test(key)) {
108
+ const key = normalizeKeyToken(parts[parts.length - 1]);
109
+ if (!key) {
28
110
  return null;
29
111
  }
30
112
  const modifiers = new Set(parts.slice(0, -1));
31
113
  for (const mod of modifiers) {
32
- if (!["ctrl", "control", "alt", "shift", "win", "meta"].includes(mod)) {
114
+ if (!MODIFIER_TOKENS.has(mod)) {
33
115
  return null;
34
116
  }
35
117
  }
@@ -46,20 +128,20 @@ export function normalizeChord(chord) {
46
128
  if (modifiers.has("win") || modifiers.has("meta")) {
47
129
  normalizedMods.push("win");
48
130
  }
49
- if (normalizedMods.length === 0) {
50
- return null;
51
- }
52
- return `${normalizedMods.join("+")}+${key}`;
131
+ return normalizedMods.length > 0 ? `${normalizedMods.join("+")}+${key}` : key;
53
132
  }
54
133
  export function parseHotkeyCombo(combo) {
55
134
  const parts = combo
56
135
  .split("+")
57
136
  .map((part) => part.trim().toLowerCase())
58
137
  .filter(Boolean);
59
- if (parts.length < 2) {
138
+ if (parts.length === 0) {
139
+ return null;
140
+ }
141
+ const key = normalizeKeyToken(parts[parts.length - 1]);
142
+ if (!key) {
60
143
  return null;
61
144
  }
62
- const key = parts[parts.length - 1];
63
145
  const modifiers = parts.slice(0, -1);
64
146
  let modMask = 0;
65
147
  const normalizedModifiers = [];
@@ -84,21 +166,8 @@ export function parseHotkeyCombo(combo) {
84
166
  return null;
85
167
  }
86
168
  }
87
- let vk = -1;
88
- if (/^[0-9]$/.test(key)) {
89
- vk = key.charCodeAt(0);
90
- }
91
- else if (/^[a-z]$/.test(key)) {
92
- vk = key.toUpperCase().charCodeAt(0);
93
- }
94
- else {
95
- const functionMatch = /^f([1-9]|1[0-9]|2[0-4])$/.exec(key);
96
- if (functionMatch) {
97
- const fn = Number(functionMatch[1]);
98
- vk = 0x70 + (fn - 1);
99
- }
100
- }
101
- if (vk < 0 || modMask === 0) {
169
+ const vk = keyTokenToVk(key);
170
+ if (vk < 0) {
102
171
  return null;
103
172
  }
104
173
  const canonicalModifiers = ["ctrl", "alt", "shift", "win"].filter((mod) => normalizedModifiers.includes(mod));
@@ -121,6 +190,10 @@ function vkToKeyToken(vkCode) {
121
190
  if (vkCode >= 0x70 && vkCode <= 0x87) {
122
191
  return `f${vkCode - 0x6f}`;
123
192
  }
193
+ const named = VK_CODE_TO_TOKEN.get(vkCode);
194
+ if (named) {
195
+ return named;
196
+ }
124
197
  return null;
125
198
  }
126
199
  export function buildCurrentChord(vkCode) {
@@ -141,8 +214,5 @@ export function buildCurrentChord(vkCode) {
141
214
  if (isPressed(VK_LWIN) || isPressed(VK_RWIN)) {
142
215
  parts.push("win");
143
216
  }
144
- if (parts.length === 0) {
145
- return null;
146
- }
147
- return `${parts.join("+")}+${key}`;
217
+ return parts.length > 0 ? `${parts.join("+")}+${key}` : key;
148
218
  }
@@ -17,7 +17,7 @@ function hasCliFlag(flag) {
17
17
  }
18
18
  function getPackageVersion() {
19
19
  try {
20
- const packageJson = readFileSync(new URL("../package.json", import.meta.url), "utf8");
20
+ const packageJson = readFileSync(new URL("../../package.json", import.meta.url), "utf8");
21
21
  const parsed = JSON.parse(packageJson);
22
22
  return parsed.version ?? "unknown";
23
23
  }
package/package.json CHANGED
@@ -1,34 +1,34 @@
1
- {
2
- "name": "qdesk",
3
- "version": "1.0.12",
4
- "type": "module",
5
- "repository": {
6
- "type": "git",
7
- "url": "git+https://github.com/JakeBeaver/qdesk.git"
8
- },
9
- "bugs": {
10
- "url": "https://github.com/JakeBeaver/qdesk/issues"
11
- },
12
- "homepage": "https://github.com/JakeBeaver/qdesk#readme",
13
- "bin": {
14
- "qdesk": "dist/index.js"
15
- },
16
- "files": [
17
- "dist"
18
- ],
19
- "scripts": {
20
- "dev": "tsx src/index.ts",
21
- "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
22
- "build": "pnpm clean && tsc -p tsconfig.json",
23
- "start": "node dist/index.js",
24
- "prepack": "pnpm build"
25
- },
26
- "dependencies": {
27
- "koffi": "^3.0.2"
28
- },
29
- "devDependencies": {
30
- "@types/node": "^22.15.30",
31
- "tsx": "^4.19.4",
32
- "typescript": "^5.8.3"
33
- }
34
- }
1
+ {
2
+ "name": "qdesk",
3
+ "version": "1.0.14",
4
+ "type": "module",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/JakeBeaver/qdesk.git"
8
+ },
9
+ "bugs": {
10
+ "url": "https://github.com/JakeBeaver/qdesk/issues"
11
+ },
12
+ "homepage": "https://github.com/JakeBeaver/qdesk#readme",
13
+ "bin": {
14
+ "qdesk": "dist/index.js"
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "dev": "tsx src/index.ts",
21
+ "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
22
+ "build": "pnpm clean && tsc -p tsconfig.json",
23
+ "start": "node dist/index.js",
24
+ "prepack": "pnpm build"
25
+ },
26
+ "dependencies": {
27
+ "koffi": "^3.0.2"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^22.15.30",
31
+ "tsx": "^4.19.4",
32
+ "typescript": "^5.8.3"
33
+ }
34
+ }