keyboard-mouse-share
Version:
Share keyboard and mouse between Mac and Windows
168 lines (138 loc) • 4.16 kB
JavaScript
const { GlobalKeyboardListener } = require('node-global-key-listener');
class HotkeyManager {
constructor(hotkeyCombo = 'LEFT CTRL + ENTER', callback = null) {
this.hotkeyCombo = hotkeyCombo;
this.callback = callback;
this.listener = null;
this.registered = false;
this.keysPressed = new Set();
this.debug = false; // Set to true for debugging
this.lastTrigger = 0;
this.debounceMs = 500; // Minimum 500ms between triggers
}
register(callback = null) {
if (callback) {
this.callback = callback;
}
if (!this.callback) {
console.error('No callback provided for hotkey');
return;
}
try {
this.listener = new GlobalKeyboardListener();
// Parse hotkey combination
const keys = this.parseHotkey(this.hotkeyCombo);
if (this.debug) {
console.log('Expected keys:', keys);
}
this.listener.addListener((e, down) => {
const keyName = e.name;
if (this.debug) {
console.log(`Key event: ${keyName}, state: ${e.state}, down:`, down);
}
// Track pressed keys
if (e.state === 'DOWN') {
this.keysPressed.add(keyName);
} else if (e.state === 'UP') {
this.keysPressed.delete(keyName);
}
if (this.debug) {
console.log('Currently pressed:', Array.from(this.keysPressed));
}
// Check if hotkey is pressed on DOWN event
if (e.state === 'DOWN') {
if (this.isHotkeyPressed(keys)) {
if (this.debug) {
console.log('✓ Hotkey matched!');
}
this.onHotkeyPressed();
}
}
});
this.registered = true;
console.log(`✓ Hotkey registered: ${this.hotkeyCombo}`);
console.log(` Press ${this.hotkeyCombo} to toggle capture`);
} catch (error) {
console.error(`Failed to register hotkey: ${error.message}`);
console.log('Note: You may need to run with administrator/root privileges');
}
}
parseHotkey(combo) {
// Parse hotkey combination
return combo
.split('+')
.map(k => k.trim())
.map(k => {
// Keep exact names from node-global-key-listener
// Don't normalize too much
return k;
});
}
isHotkeyPressed(requiredKeys) {
// Check if all required keys are currently pressed
for (const key of requiredKeys) {
let found = false;
for (const pressedKey of this.keysPressed) {
// Case-insensitive match and partial match for modifiers
const keyLower = key.toLowerCase();
const pressedLower = pressedKey.toLowerCase();
if (pressedLower === keyLower ||
pressedLower.includes(keyLower) ||
keyLower.includes(pressedLower)) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
return requiredKeys.length > 0;
}
onHotkeyPressed() {
// Debounce - prevent spam
const now = Date.now();
if (now - this.lastTrigger < this.debounceMs) {
if (this.debug) {
console.log(`Hotkey ignored (debounced, ${now - this.lastTrigger}ms since last trigger)`);
}
return;
}
this.lastTrigger = now;
if (this.debug) {
console.log(`Hotkey triggered: ${this.hotkeyCombo}`);
}
if (this.callback) {
this.callback();
}
}
unregister() {
if (this.registered && this.listener) {
try {
this.listener.kill();
this.registered = false;
console.log(`Hotkey unregistered: ${this.hotkeyCombo}`);
} catch (error) {
console.error(`Failed to unregister hotkey: ${error.message}`);
}
}
}
setHotkey(newCombo) {
const wasRegistered = this.registered;
if (wasRegistered) {
this.unregister();
}
this.hotkeyCombo = newCombo;
if (wasRegistered) {
this.register();
}
}
enableDebug() {
this.debug = true;
console.log('Hotkey debug enabled - all key events will be logged');
}
disableDebug() {
this.debug = false;
}
}
module.exports = HotkeyManager;