keyboard-mouse-share
Version:
Share keyboard and mouse between Mac and Windows
189 lines (160 loc) • 5.72 kB
JavaScript
const { screen, mouse } = require('@nut-tree-fork/nut-js');
const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);
class EdgeDetector {
constructor(config, onEdgeCrossed, onEdgeReturn) {
this.screenEdge = config.screenEdge || 'right';
this.threshold = config.edgeThreshold || 5;
this.enabled = config.autoSwitch !== false;
this.onEdgeCrossed = onEdgeCrossed;
this.onEdgeReturn = onEdgeReturn;
this.screenWidth = 0;
this.screenHeight = 0;
this.virtualScreenWidth = 0;
this.virtualScreenHeight = 0;
this.isCrossed = false;
this.checkInterval = null;
this.checkRate = 50; // Check every 50ms
}
async getVirtualScreenDimensions() {
const platform = process.platform;
try {
if (platform === 'darwin') {
// macOS: Use system_profiler to get all displays
const { stdout } = await execPromise('system_profiler SPDisplaysDataType | grep Resolution');
const resolutions = stdout.match(/(\d+) x (\d+)/g);
if (resolutions && resolutions.length > 0) {
let totalWidth = 0;
let maxHeight = 0;
resolutions.forEach(res => {
const [w, h] = res.match(/\d+/g).map(Number);
totalWidth += w;
maxHeight = Math.max(maxHeight, h);
});
return { width: totalWidth, height: maxHeight };
}
} else if (platform === 'win32') {
// Windows: Use wmic to get display info
const { stdout } = await execPromise('wmic path Win32_VideoController get CurrentHorizontalResolution,CurrentVerticalResolution /format:value');
const widths = [...stdout.matchAll(/CurrentHorizontalResolution=(\d+)/g)].map(m => parseInt(m[1]));
const heights = [...stdout.matchAll(/CurrentVerticalResolution=(\d+)/g)].map(m => parseInt(m[1]));
if (widths.length > 0) {
const totalWidth = widths.reduce((a, b) => a + b, 0);
const maxHeight = Math.max(...heights);
return { width: totalWidth, height: maxHeight };
}
} else if (platform === 'linux') {
// Linux: Use xrandr
const { stdout } = await execPromise('xrandr --query | grep " connected"');
const resolutions = stdout.match(/(\d+)x(\d+)/g);
if (resolutions && resolutions.length > 0) {
let totalWidth = 0;
let maxHeight = 0;
resolutions.forEach(res => {
const [w, h] = res.split('x').map(Number);
totalWidth += w;
maxHeight = Math.max(maxHeight, h);
});
return { width: totalWidth, height: maxHeight };
}
}
} catch (error) {
console.warn(`Could not detect virtual screen size: ${error.message}`);
}
// Fallback to nut-js screen size
return {
width: await screen.width(),
height: await screen.height()
};
}
async init() {
// Get primary screen dimensions
this.screenWidth = await screen.width();
this.screenHeight = await screen.height();
// Get virtual desktop dimensions (all screens combined)
const virtualDims = await this.getVirtualScreenDimensions();
this.virtualScreenWidth = virtualDims.width;
this.virtualScreenHeight = virtualDims.height;
console.log(`Primary screen: ${this.screenWidth}x${this.screenHeight}`);
if (this.virtualScreenWidth !== this.screenWidth || this.virtualScreenHeight !== this.screenHeight) {
console.log(`Virtual desktop: ${this.virtualScreenWidth}x${this.virtualScreenHeight}`);
}
console.log(`Edge detection: ${this.screenEdge} (threshold: ${this.threshold}px)`);
}
start() {
if (!this.enabled || this.screenEdge === 'none') {
console.log('Edge detection disabled');
return;
}
this.checkInterval = setInterval(async () => {
await this.checkEdge();
}, this.checkRate);
console.log(`✓ Edge detection started on ${this.screenEdge} edge`);
}
async checkEdge() {
try {
const pos = await mouse.getPosition();
const atEdge = this.isAtEdge(pos.x, pos.y);
// Edge crossed (entering client screen)
if (atEdge && !this.isCrossed) {
this.isCrossed = true;
if (this.onEdgeCrossed) {
this.onEdgeCrossed();
}
}
// Returned from edge (back to master screen)
else if (!atEdge && this.isCrossed) {
this.isCrossed = false;
if (this.onEdgeReturn) {
this.onEdgeReturn();
}
}
} catch (error) {
// Silently ignore errors
}
}
isAtEdge(x, y) {
// Use virtual screen dimensions for multi-monitor support
const width = this.virtualScreenWidth || this.screenWidth;
const height = this.virtualScreenHeight || this.screenHeight;
switch (this.screenEdge) {
case 'right':
return x >= width - this.threshold;
case 'left':
return x <= this.threshold;
case 'top':
return y <= this.threshold;
case 'bottom':
return y >= height - this.threshold;
default:
return false;
}
}
stop() {
if (this.checkInterval) {
clearInterval(this.checkInterval);
this.checkInterval = null;
}
console.log('Edge detection stopped');
}
setEdge(edge) {
this.screenEdge = edge;
console.log(`Edge detection set to: ${edge}`);
}
setThreshold(threshold) {
this.threshold = threshold;
console.log(`Edge threshold set to: ${threshold}px`);
}
enable() {
this.enabled = true;
if (!this.checkInterval) {
this.start();
}
}
disable() {
this.enabled = false;
this.stop();
}
}
module.exports = EdgeDetector;