barkclaude 1.0.0 → 1.0.3
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.
- package/main.js +65 -18
- package/overlay.html +27 -1
- package/package.json +1 -1
- package/preload.js +2 -0
package/main.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const { app, BrowserWindow, ipcMain, Tray, Menu, screen } = require('electron');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const { exec } = require('child_process');
|
|
3
|
+
const { exec, execFile } = require('child_process');
|
|
4
4
|
|
|
5
5
|
let overlay = null;
|
|
6
6
|
let tray = null;
|
|
@@ -23,24 +23,48 @@ function getRandomMessage() {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function sendToClaude(message) {
|
|
26
|
-
//
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
// No emoji prefix — conhost with non-UTF8 codepage mangles multi-byte chars on paste.
|
|
27
|
+
const text = message;
|
|
28
|
+
|
|
29
|
+
if (process.platform === 'win32') {
|
|
30
|
+
// Type the text character-by-character via SendKeys (no clipboard, no Ctrl+V).
|
|
31
|
+
// Claude Code's TUI in raw mode doesn't handle Ctrl+V as paste — it just sees
|
|
32
|
+
// a literal 'v'. Synthetic keystrokes, on the other hand, are delivered as
|
|
33
|
+
// normal char input through conhost's stdin and land in the TUI input field.
|
|
34
|
+
// SendKeys special metachars +^%~(){}[] must be wrapped in {} to be literal.
|
|
35
|
+
const textB64 = Buffer.from(text, 'utf8').toString('base64');
|
|
36
|
+
const ps = `
|
|
37
|
+
$text = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${textB64}'))
|
|
38
|
+
$escaped = $text -replace '([+^%~(){}\\[\\]])', '{$1}'
|
|
39
|
+
Add-Type -AssemblyName System.Windows.Forms | Out-Null
|
|
40
|
+
Start-Sleep -Milliseconds 80
|
|
41
|
+
[System.Windows.Forms.SendKeys]::SendWait($escaped)
|
|
42
|
+
Start-Sleep -Milliseconds 120
|
|
43
|
+
[System.Windows.Forms.SendKeys]::SendWait('{ENTER}')
|
|
44
|
+
`;
|
|
45
|
+
const encoded = Buffer.from(ps, 'utf16le').toString('base64');
|
|
46
|
+
exec(`powershell -NoProfile -WindowStyle Hidden -EncodedCommand ${encoded}`, { windowsHide: true });
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
34
50
|
if (process.platform === 'darwin') {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
51
|
+
// Type the text directly via System Events (same rationale as Windows:
|
|
52
|
+
// Cmd+V into a TUI is unreliable; typing chars into the focused terminal is).
|
|
53
|
+
const escaped = text.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
54
|
+
const script = `tell application "System Events"
|
|
55
|
+
keystroke "${escaped}"
|
|
56
|
+
delay 0.1
|
|
57
|
+
key code 36
|
|
58
|
+
end tell`;
|
|
59
|
+
execFile('osascript', ['-e', script], (err) => {
|
|
60
|
+
if (err) console.warn('osascript failed:', err.message);
|
|
61
|
+
});
|
|
62
|
+
return;
|
|
43
63
|
}
|
|
64
|
+
|
|
65
|
+
// Linux: try xdotool
|
|
66
|
+
const escaped = text.replace(/'/g, `'\\''`);
|
|
67
|
+
exec(`xdotool type --delay 0 -- '${escaped}' && xdotool key Return 2>/dev/null || true`);
|
|
44
68
|
}
|
|
45
69
|
|
|
46
70
|
function createOverlay() {
|
|
@@ -57,6 +81,7 @@ function createOverlay() {
|
|
|
57
81
|
skipTaskbar: true,
|
|
58
82
|
resizable: false,
|
|
59
83
|
hasShadow: false,
|
|
84
|
+
focusable: false, // critical: don't steal focus from the user's terminal
|
|
60
85
|
webPreferences: {
|
|
61
86
|
preload: path.join(__dirname, 'preload.js'),
|
|
62
87
|
contextIsolation: true,
|
|
@@ -90,10 +115,32 @@ app.whenReady().then(() => {
|
|
|
90
115
|
|
|
91
116
|
ipcMain.on('bark', (_event, customMsg) => {
|
|
92
117
|
const message = customMsg || getRandomMessage();
|
|
93
|
-
console.log(`🐕 ${message}`);
|
|
94
118
|
sendToClaude(message);
|
|
95
119
|
});
|
|
96
120
|
|
|
121
|
+
// Drag: renderer tells us "drag started", we poll the cursor and reposition
|
|
122
|
+
// the window until the renderer tells us "drag ended". This works with
|
|
123
|
+
// pointer capture in the renderer so we keep tracking even if the cursor
|
|
124
|
+
// momentarily leaves the window while it catches up.
|
|
125
|
+
let dragOffset = null;
|
|
126
|
+
let dragTimer = null;
|
|
127
|
+
ipcMain.on('drag-start', () => {
|
|
128
|
+
if (!overlay) return;
|
|
129
|
+
const cursor = screen.getCursorScreenPoint();
|
|
130
|
+
const bounds = overlay.getBounds();
|
|
131
|
+
dragOffset = { x: cursor.x - bounds.x, y: cursor.y - bounds.y };
|
|
132
|
+
if (dragTimer) clearInterval(dragTimer);
|
|
133
|
+
dragTimer = setInterval(() => {
|
|
134
|
+
if (!dragOffset || !overlay) return;
|
|
135
|
+
const c = screen.getCursorScreenPoint();
|
|
136
|
+
overlay.setPosition(c.x - dragOffset.x, c.y - dragOffset.y);
|
|
137
|
+
}, 16);
|
|
138
|
+
});
|
|
139
|
+
ipcMain.on('drag-end', () => {
|
|
140
|
+
dragOffset = null;
|
|
141
|
+
if (dragTimer) { clearInterval(dragTimer); dragTimer = null; }
|
|
142
|
+
});
|
|
143
|
+
|
|
97
144
|
ipcMain.on('quit', () => app.quit());
|
|
98
145
|
});
|
|
99
146
|
|
package/overlay.html
CHANGED
|
@@ -222,7 +222,33 @@ function doBark() {
|
|
|
222
222
|
window.shiba.bark(msg);
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
|
|
225
|
+
// Click-vs-drag: pointerdown starts a potential drag. If the pointer moves more
|
|
226
|
+
// than a few pixels before release, it's a drag and no bark. If it doesn't
|
|
227
|
+
// move, it's a click and we bark.
|
|
228
|
+
const shibaWrap = document.getElementById('shibaWrap');
|
|
229
|
+
let dragStartPt = null;
|
|
230
|
+
shibaWrap.addEventListener('pointerdown', (e) => {
|
|
231
|
+
if (e.button !== 0) return;
|
|
232
|
+
shibaWrap.setPointerCapture(e.pointerId);
|
|
233
|
+
dragStartPt = { x: e.screenX, y: e.screenY };
|
|
234
|
+
window.shiba.dragStart();
|
|
235
|
+
e.preventDefault();
|
|
236
|
+
});
|
|
237
|
+
shibaWrap.addEventListener('pointerup', (e) => {
|
|
238
|
+
if (!dragStartPt) return;
|
|
239
|
+
try { shibaWrap.releasePointerCapture(e.pointerId); } catch (_) {}
|
|
240
|
+
const dx = e.screenX - dragStartPt.x;
|
|
241
|
+
const dy = e.screenY - dragStartPt.y;
|
|
242
|
+
const moved = (dx * dx + dy * dy) > 16; // >4px in any direction
|
|
243
|
+
dragStartPt = null;
|
|
244
|
+
window.shiba.dragEnd();
|
|
245
|
+
if (!moved) doBark();
|
|
246
|
+
});
|
|
247
|
+
shibaWrap.addEventListener('pointercancel', () => {
|
|
248
|
+
dragStartPt = null;
|
|
249
|
+
window.shiba.dragEnd();
|
|
250
|
+
});
|
|
251
|
+
|
|
226
252
|
window.shiba.onTriggerBark(() => doBark());
|
|
227
253
|
</script>
|
|
228
254
|
</body>
|
package/package.json
CHANGED
package/preload.js
CHANGED
|
@@ -4,4 +4,6 @@ contextBridge.exposeInMainWorld('shiba', {
|
|
|
4
4
|
bark: (msg) => ipcRenderer.send('bark', msg),
|
|
5
5
|
quit: () => ipcRenderer.send('quit'),
|
|
6
6
|
onTriggerBark: (cb) => ipcRenderer.on('trigger-bark', cb),
|
|
7
|
+
dragStart: () => ipcRenderer.send('drag-start'),
|
|
8
|
+
dragEnd: () => ipcRenderer.send('drag-end'),
|
|
7
9
|
});
|