mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-15 01:55:52 -07:00
fix(overlay): handle X11 display scaling across monitors (#193)
This commit is contained in:
@@ -20,6 +20,7 @@ import { BaseWindowTracker } from './base-tracker';
|
||||
import { HyprlandWindowTracker } from './hyprland-tracker';
|
||||
import { SwayWindowTracker } from './sway-tracker';
|
||||
import { X11WindowTracker } from './x11-tracker';
|
||||
import type { ScreenToDipPoint } from './x11-tracker';
|
||||
import { MacOSWindowTracker } from './macos-tracker';
|
||||
import { WindowsWindowTracker } from './windows-tracker';
|
||||
import { createLogger } from '../logger';
|
||||
@@ -51,6 +52,7 @@ function normalizeCompositor(value: string): Compositor | null {
|
||||
export function createWindowTracker(
|
||||
override?: string | null,
|
||||
targetMpvSocketPath?: string | null,
|
||||
screenToDipPoint?: ScreenToDipPoint,
|
||||
): BaseWindowTracker | null {
|
||||
let compositor = detectCompositor();
|
||||
|
||||
@@ -70,7 +72,11 @@ export function createWindowTracker(
|
||||
case 'sway':
|
||||
return new SwayWindowTracker(targetMpvSocketPath?.trim() || undefined);
|
||||
case 'x11':
|
||||
return new X11WindowTracker(targetMpvSocketPath?.trim() || undefined);
|
||||
return new X11WindowTracker(
|
||||
targetMpvSocketPath?.trim() || undefined,
|
||||
undefined,
|
||||
screenToDipPoint,
|
||||
);
|
||||
case 'macos':
|
||||
return new MacOSWindowTracker(targetMpvSocketPath?.trim() || undefined);
|
||||
case 'windows':
|
||||
|
||||
@@ -82,6 +82,47 @@ Height: 360`;
|
||||
});
|
||||
});
|
||||
|
||||
test('X11WindowTracker converts both physical rectangle corners to Electron DIP', async () => {
|
||||
const convertedPoints: Array<{ x: number; y: number }> = [];
|
||||
const tracker = new X11WindowTracker(
|
||||
undefined,
|
||||
async (command, args) => {
|
||||
if (command === 'xdotool' && args[0] === 'search') {
|
||||
return '123';
|
||||
}
|
||||
if (command === 'xdotool' && args[0] === 'getactivewindow') {
|
||||
return '123';
|
||||
}
|
||||
if (command === 'xwininfo') {
|
||||
return `Absolute upper-left X: 2000
|
||||
Absolute upper-left Y: 125
|
||||
Width: 1000
|
||||
Height: 750`;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
(point) => {
|
||||
convertedPoints.push(point);
|
||||
if (point.x === 2000) return { x: 1600, y: 100 };
|
||||
return { x: 2400, y: 700 };
|
||||
},
|
||||
);
|
||||
|
||||
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
assert.deepEqual(convertedPoints, [
|
||||
{ x: 2000, y: 125 },
|
||||
{ x: 3000, y: 875 },
|
||||
]);
|
||||
assert.deepEqual(tracker.getGeometry(), {
|
||||
x: 1600,
|
||||
y: 100,
|
||||
width: 800,
|
||||
height: 600,
|
||||
});
|
||||
});
|
||||
|
||||
test('X11WindowTracker updates target focus from active X11 window', async () => {
|
||||
let activeWindowId = '999';
|
||||
const tracker = new X11WindowTracker(undefined, async (command, args) => {
|
||||
|
||||
@@ -20,6 +20,9 @@ import { execFile } from 'child_process';
|
||||
import { BaseWindowTracker } from './base-tracker';
|
||||
|
||||
type CommandRunner = (command: string, args: string[]) => Promise<string>;
|
||||
export type ScreenToDipPoint = (point: { x: number; y: number }) => { x: number; y: number };
|
||||
|
||||
const preservePoint: ScreenToDipPoint = (point) => point;
|
||||
|
||||
function execFileUtf8(command: string, args: string[]): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -87,16 +90,22 @@ export class X11WindowTracker extends BaseWindowTracker {
|
||||
private pollInterval: ReturnType<typeof setInterval> | null = null;
|
||||
private readonly targetMpvSocketPath: string | null;
|
||||
private readonly runCommand: CommandRunner;
|
||||
private readonly screenToDipPoint: ScreenToDipPoint;
|
||||
private targetWindowId: string | null = null;
|
||||
private targetWindowPid: number | null = null;
|
||||
private pollInFlight = false;
|
||||
private currentPollIntervalMs = 750;
|
||||
private readonly stablePollIntervalMs = 250;
|
||||
|
||||
constructor(targetMpvSocketPath?: string, runCommand: CommandRunner = execFileUtf8) {
|
||||
constructor(
|
||||
targetMpvSocketPath?: string,
|
||||
runCommand: CommandRunner = execFileUtf8,
|
||||
screenToDipPoint: ScreenToDipPoint = preservePoint,
|
||||
) {
|
||||
super();
|
||||
this.targetMpvSocketPath = targetMpvSocketPath?.trim() || null;
|
||||
this.runCommand = runCommand;
|
||||
this.screenToDipPoint = screenToDipPoint;
|
||||
}
|
||||
|
||||
start(): void {
|
||||
@@ -196,11 +205,25 @@ export class X11WindowTracker extends BaseWindowTracker {
|
||||
this.targetWindowPid = targetPid;
|
||||
|
||||
const winInfo = await this.runCommand('xwininfo', ['-id', windowId]);
|
||||
const geometry = parseX11WindowGeometry(winInfo);
|
||||
if (!geometry) {
|
||||
const physicalGeometry = parseX11WindowGeometry(winInfo);
|
||||
if (!physicalGeometry) {
|
||||
this.updateGeometry(null);
|
||||
return;
|
||||
}
|
||||
const topLeft = this.screenToDipPoint({
|
||||
x: physicalGeometry.x,
|
||||
y: physicalGeometry.y,
|
||||
});
|
||||
const bottomRight = this.screenToDipPoint({
|
||||
x: physicalGeometry.x + physicalGeometry.width,
|
||||
y: physicalGeometry.y + physicalGeometry.height,
|
||||
});
|
||||
const geometry = {
|
||||
x: topLeft.x,
|
||||
y: topLeft.y,
|
||||
width: bottomRight.x - topLeft.x,
|
||||
height: bottomRight.y - topLeft.y,
|
||||
};
|
||||
|
||||
const focused = await this.isWindowActive(windowId, targetPid);
|
||||
this.updateGeometry(geometry, focused);
|
||||
|
||||
Reference in New Issue
Block a user