mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
Fix macOS overlay binding and subtitle alignment
This commit is contained in:
@@ -347,6 +347,7 @@ const shouldToggleMouseIgnore = !isLinuxPlatform;
|
||||
const INVISIBLE_POSITION_EDIT_TOGGLE_CODE = "KeyP";
|
||||
const INVISIBLE_POSITION_STEP_PX = 1;
|
||||
const INVISIBLE_POSITION_STEP_FAST_PX = 4;
|
||||
const INVISIBLE_MACOS_VERTICAL_NUDGE_PX = 4;
|
||||
|
||||
let isOverSubtitle = false;
|
||||
let isDragging = false;
|
||||
@@ -1062,6 +1063,14 @@ function applyInvisibleSubtitleLayoutFromMpvMetrics(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isMacOSPlatform && vAlign === 0) {
|
||||
const currentBottom = parseFloat(subtitleContainer.style.bottom);
|
||||
if (Number.isFinite(currentBottom)) {
|
||||
subtitleContainer.style.bottom = `${Math.max(0, currentBottom + INVISIBLE_MACOS_VERTICAL_NUDGE_PX)}px`;
|
||||
}
|
||||
}
|
||||
|
||||
invisibleLayoutBaseLeftPx = parseFloat(subtitleContainer.style.left) || 0;
|
||||
invisibleLayoutBaseBottomPx = Number.isFinite(parseFloat(subtitleContainer.style.bottom))
|
||||
? parseFloat(subtitleContainer.style.bottom)
|
||||
|
||||
@@ -17,11 +17,132 @@
|
||||
*/
|
||||
|
||||
import { execFile } from "child_process";
|
||||
import * as path from "path";
|
||||
import * as fs from "fs";
|
||||
import * as os from "os";
|
||||
import { BaseWindowTracker } from "./base-tracker";
|
||||
import { createLogger } from "../logger";
|
||||
|
||||
const log = createLogger("tracker").child("macos");
|
||||
|
||||
export class MacOSWindowTracker extends BaseWindowTracker {
|
||||
private pollInterval: ReturnType<typeof setInterval> | null = null;
|
||||
private pollInFlight = false;
|
||||
private helperPath: string | null = null;
|
||||
private helperType: "binary" | "swift" | null = null;
|
||||
private lastExecErrorFingerprint: string | null = null;
|
||||
private lastExecErrorLoggedAtMs = 0;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.detectHelper();
|
||||
}
|
||||
|
||||
private materializeAsarHelper(
|
||||
sourcePath: string,
|
||||
helperType: "binary" | "swift",
|
||||
): string | null {
|
||||
if (!sourcePath.includes(".asar")) {
|
||||
return sourcePath;
|
||||
}
|
||||
|
||||
const fileName =
|
||||
helperType === "binary"
|
||||
? "get-mpv-window-macos"
|
||||
: "get-mpv-window-macos.swift";
|
||||
const targetDir = path.join(os.tmpdir(), "subminer", "helpers");
|
||||
const targetPath = path.join(targetDir, fileName);
|
||||
|
||||
try {
|
||||
fs.mkdirSync(targetDir, { recursive: true });
|
||||
fs.copyFileSync(sourcePath, targetPath);
|
||||
fs.chmodSync(targetPath, 0o755);
|
||||
log.info(`Materialized macOS helper from asar: ${targetPath}`);
|
||||
return targetPath;
|
||||
} catch (error) {
|
||||
log.warn(`Failed to materialize helper from asar: ${sourcePath}`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private tryUseHelper(
|
||||
candidatePath: string,
|
||||
helperType: "binary" | "swift",
|
||||
): boolean {
|
||||
if (!fs.existsSync(candidatePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const resolvedPath = this.materializeAsarHelper(candidatePath, helperType);
|
||||
if (!resolvedPath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.helperPath = resolvedPath;
|
||||
this.helperType = helperType;
|
||||
log.info(`Using macOS helper (${helperType}): ${resolvedPath}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
private detectHelper(): void {
|
||||
// Prefer resources path (outside asar) in packaged apps.
|
||||
const resourcesPath = process.resourcesPath;
|
||||
if (resourcesPath) {
|
||||
const resourcesBinaryPath = path.join(
|
||||
resourcesPath,
|
||||
"scripts",
|
||||
"get-mpv-window-macos",
|
||||
);
|
||||
if (this.tryUseHelper(resourcesBinaryPath, "binary")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Dist binary path (development / unpacked installs).
|
||||
const distBinaryPath = path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"..",
|
||||
"scripts",
|
||||
"get-mpv-window-macos",
|
||||
);
|
||||
if (this.tryUseHelper(distBinaryPath, "binary")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fall back to Swift script for development.
|
||||
const swiftPath = path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"..",
|
||||
"scripts",
|
||||
"get-mpv-window-macos.swift",
|
||||
);
|
||||
if (this.tryUseHelper(swiftPath, "swift")) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.warn("macOS window tracking helper not found");
|
||||
}
|
||||
|
||||
private maybeLogExecError(err: Error, stderr: string): void {
|
||||
const now = Date.now();
|
||||
const fingerprint = `${err.message}|${stderr.trim()}`;
|
||||
const shouldLog =
|
||||
this.lastExecErrorFingerprint !== fingerprint ||
|
||||
now - this.lastExecErrorLoggedAtMs >= 5000;
|
||||
if (!shouldLog) {
|
||||
return;
|
||||
}
|
||||
this.lastExecErrorFingerprint = fingerprint;
|
||||
this.lastExecErrorLoggedAtMs = now;
|
||||
log.warn("macOS helper execution failed", {
|
||||
helperPath: this.helperPath,
|
||||
helperType: this.helperType,
|
||||
error: err.message,
|
||||
stderr: stderr.trim(),
|
||||
});
|
||||
}
|
||||
|
||||
start(): void {
|
||||
this.pollInterval = setInterval(() => this.pollGeometry(), 250);
|
||||
@@ -36,42 +157,28 @@ export class MacOSWindowTracker extends BaseWindowTracker {
|
||||
}
|
||||
|
||||
private pollGeometry(): void {
|
||||
if (this.pollInFlight) {
|
||||
if (this.pollInFlight || !this.helperPath || !this.helperType) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pollInFlight = true;
|
||||
|
||||
const script = `
|
||||
set processNames to {"mpv", "MPV", "org.mpv.mpv"}
|
||||
tell application "System Events"
|
||||
repeat with procName in processNames
|
||||
set procList to (every process whose name is procName)
|
||||
repeat with p in procList
|
||||
try
|
||||
if (count of windows of p) > 0 then
|
||||
set targetWindow to window 1 of p
|
||||
set windowPos to position of targetWindow
|
||||
set windowSize to size of targetWindow
|
||||
return (item 1 of windowPos) & "," & (item 2 of windowPos) & "," & (item 1 of windowSize) & "," & (item 2 of windowSize)
|
||||
end if
|
||||
end try
|
||||
end repeat
|
||||
end repeat
|
||||
end tell
|
||||
return "not-found"
|
||||
`;
|
||||
// Use Core Graphics API via Swift helper for reliable window detection
|
||||
// This works with both bundled and unbundled mpv installations
|
||||
const command = this.helperType === "binary" ? this.helperPath : "swift";
|
||||
const args = this.helperType === "binary" ? [] : [this.helperPath];
|
||||
|
||||
execFile(
|
||||
"osascript",
|
||||
["-e", script],
|
||||
command,
|
||||
args,
|
||||
{
|
||||
encoding: "utf-8",
|
||||
timeout: 1000,
|
||||
maxBuffer: 1024 * 1024,
|
||||
},
|
||||
(err, stdout) => {
|
||||
(err, stdout, stderr) => {
|
||||
if (err) {
|
||||
this.maybeLogExecError(err, stderr || "");
|
||||
this.updateGeometry(null);
|
||||
this.pollInFlight = false;
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user