mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-28 06:22:45 -08:00
Fix macOS overlay binding and subtitle alignment
This commit is contained in:
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
id: TASK-13
|
||||||
|
title: Fix macOS native window bounds for overlay binding
|
||||||
|
status: Done
|
||||||
|
assignee:
|
||||||
|
- codex
|
||||||
|
created_date: '2026-02-11 15:45'
|
||||||
|
updated_date: '2026-02-11 16:20'
|
||||||
|
labels:
|
||||||
|
- bug
|
||||||
|
- macos
|
||||||
|
- overlay
|
||||||
|
dependencies: []
|
||||||
|
references:
|
||||||
|
- src/window-trackers/macos-tracker.ts
|
||||||
|
- scripts/get-mpv-window-macos.swift
|
||||||
|
priority: high
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||||
|
Overlay windows on macOS are not properly aligned to the mpv window after switching from AppleScript window discovery to native Swift/CoreGraphics bounds retrieval.
|
||||||
|
|
||||||
|
Implement a robust native bounds strategy that prefers Accessibility window geometry (matching app-window coordinates used previously) and falls back to filtered CoreGraphics windows when Accessibility data is unavailable.
|
||||||
|
<!-- SECTION:DESCRIPTION:END -->
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
<!-- AC:BEGIN -->
|
||||||
|
- [x] #1 Overlay bounds track the active mpv window with correct position and size on macOS.
|
||||||
|
- [x] #2 Helper avoids selecting off-screen/non-primary mpv-related windows.
|
||||||
|
- [x] #3 Build succeeds with the updated macOS helper.
|
||||||
|
<!-- AC:END -->
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
<!-- SECTION:NOTES:BEGIN -->
|
||||||
|
Follow-up in progress after packaged app runtime showed fullscreen fallback behavior:
|
||||||
|
- Added packaged-app helper path resolution in tracker (`process.resourcesPath/scripts/get-mpv-window-macos`).
|
||||||
|
- Added `.asar` helper materialization to temp path so child process execution is possible if candidate path resolves inside asar.
|
||||||
|
- Added throttled tracker logging for helper execution failures to expose runtime errors without log spam.
|
||||||
|
- Updated Electron builder `extraResources` to ship `dist/scripts/get-mpv-window-macos` outside asar at `resources/scripts/get-mpv-window-macos`.
|
||||||
|
- Added macOS-only invisible subtitle vertical nudge (`+4px`) in renderer layout to align interactive subtitles with mpv glyph baseline after bounds fix.
|
||||||
|
<!-- SECTION:NOTES:END -->
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
"description": "All-in-one sentence mining overlay with AnkiConnect and dictionary integration",
|
"description": "All-in-one sentence mining overlay with AnkiConnect and dictionary integration",
|
||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc && cp src/renderer/index.html src/renderer/style.css dist/renderer/",
|
"build": "tsc && cp src/renderer/index.html src/renderer/style.css dist/renderer/ && bash scripts/build-macos-helper.sh",
|
||||||
"check:main-lines": "bash scripts/check-main-lines.sh",
|
"check:main-lines": "bash scripts/check-main-lines.sh",
|
||||||
"check:main-lines:baseline": "bash scripts/check-main-lines.sh 5300",
|
"check:main-lines:baseline": "bash scripts/check-main-lines.sh 5300",
|
||||||
"check:main-lines:gate1": "bash scripts/check-main-lines.sh 4500",
|
"check:main-lines:gate1": "bash scripts/check-main-lines.sh 4500",
|
||||||
@@ -85,7 +85,8 @@
|
|||||||
"dist/**/*",
|
"dist/**/*",
|
||||||
"vendor/texthooker-ui/docs/**/*",
|
"vendor/texthooker-ui/docs/**/*",
|
||||||
"vendor/texthooker-ui/package.json",
|
"vendor/texthooker-ui/package.json",
|
||||||
"package.json"
|
"package.json",
|
||||||
|
"scripts/get-mpv-window-macos.swift"
|
||||||
],
|
],
|
||||||
"extraResources": [
|
"extraResources": [
|
||||||
{
|
{
|
||||||
@@ -95,6 +96,10 @@
|
|||||||
{
|
{
|
||||||
"from": "assets",
|
"from": "assets",
|
||||||
"to": "assets"
|
"to": "assets"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from": "dist/scripts/get-mpv-window-macos",
|
||||||
|
"to": "scripts/get-mpv-window-macos"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
27
scripts/build-macos-helper.sh
Executable file
27
scripts/build-macos-helper.sh
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Build macOS window tracking helper binary
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
SWIFT_SOURCE="$SCRIPT_DIR/get-mpv-window-macos.swift"
|
||||||
|
OUTPUT_DIR="$SCRIPT_DIR/../dist/scripts"
|
||||||
|
OUTPUT_BINARY="$OUTPUT_DIR/get-mpv-window-macos"
|
||||||
|
|
||||||
|
# Only build on macOS
|
||||||
|
if [[ "$(uname)" != "Darwin" ]]; then
|
||||||
|
echo "Skipping macOS helper build (not on macOS)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create output directory
|
||||||
|
mkdir -p "$OUTPUT_DIR"
|
||||||
|
|
||||||
|
# Compile Swift script to binary
|
||||||
|
echo "Compiling macOS window tracking helper..."
|
||||||
|
swiftc -O "$SWIFT_SOURCE" -o "$OUTPUT_BINARY"
|
||||||
|
|
||||||
|
# Make executable
|
||||||
|
chmod +x "$OUTPUT_BINARY"
|
||||||
|
|
||||||
|
echo "✓ Built $OUTPUT_BINARY"
|
||||||
165
scripts/get-mpv-window-macos.swift
Normal file
165
scripts/get-mpv-window-macos.swift
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
#!/usr/bin/env swift
|
||||||
|
//
|
||||||
|
// get-mpv-window-macos.swift
|
||||||
|
// SubMiner - Get mpv window geometry on macOS
|
||||||
|
//
|
||||||
|
// This script uses Core Graphics APIs to find mpv windows system-wide.
|
||||||
|
// It works with both bundled and unbundled mpv installations.
|
||||||
|
//
|
||||||
|
// Usage: swift get-mpv-window-macos.swift
|
||||||
|
// Output: "x,y,width,height" or "not-found"
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
private struct WindowGeometry {
|
||||||
|
let x: Int
|
||||||
|
let y: Int
|
||||||
|
let width: Int
|
||||||
|
let height: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
private func geometryFromRect(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat) -> WindowGeometry {
|
||||||
|
let minX = Int(floor(x))
|
||||||
|
let minY = Int(floor(y))
|
||||||
|
let maxX = Int(ceil(x + width))
|
||||||
|
let maxY = Int(ceil(y + height))
|
||||||
|
return WindowGeometry(
|
||||||
|
x: minX,
|
||||||
|
y: minY,
|
||||||
|
width: max(0, maxX - minX),
|
||||||
|
height: max(0, maxY - minY)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func normalizedMpvName(_ name: String) -> Bool {
|
||||||
|
let normalized = name.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
||||||
|
return normalized == "mpv"
|
||||||
|
}
|
||||||
|
|
||||||
|
private func geometryFromAXWindow(_ axWindow: AXUIElement) -> WindowGeometry? {
|
||||||
|
var positionRef: CFTypeRef?
|
||||||
|
var sizeRef: CFTypeRef?
|
||||||
|
|
||||||
|
let positionStatus = AXUIElementCopyAttributeValue(axWindow, kAXPositionAttribute as CFString, &positionRef)
|
||||||
|
let sizeStatus = AXUIElementCopyAttributeValue(axWindow, kAXSizeAttribute as CFString, &sizeRef)
|
||||||
|
|
||||||
|
guard positionStatus == .success,
|
||||||
|
sizeStatus == .success,
|
||||||
|
let positionRaw = positionRef,
|
||||||
|
let sizeRaw = sizeRef,
|
||||||
|
CFGetTypeID(positionRaw) == AXValueGetTypeID(),
|
||||||
|
CFGetTypeID(sizeRaw) == AXValueGetTypeID() else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let positionValue = positionRaw as! AXValue
|
||||||
|
let sizeValue = sizeRaw as! AXValue
|
||||||
|
|
||||||
|
guard AXValueGetType(positionValue) == .cgPoint,
|
||||||
|
AXValueGetType(sizeValue) == .cgSize else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var position = CGPoint.zero
|
||||||
|
var size = CGSize.zero
|
||||||
|
|
||||||
|
guard AXValueGetValue(positionValue, .cgPoint, &position),
|
||||||
|
AXValueGetValue(sizeValue, .cgSize, &size) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let geometry = geometryFromRect(
|
||||||
|
x: position.x,
|
||||||
|
y: position.y,
|
||||||
|
width: size.width,
|
||||||
|
height: size.height
|
||||||
|
)
|
||||||
|
|
||||||
|
guard geometry.width >= 100, geometry.height >= 100 else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return geometry
|
||||||
|
}
|
||||||
|
|
||||||
|
private func geometryFromAccessibilityAPI() -> WindowGeometry? {
|
||||||
|
let runningApps = NSWorkspace.shared.runningApplications.filter { app in
|
||||||
|
guard let name = app.localizedName else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return normalizedMpvName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
for app in runningApps {
|
||||||
|
let appElement = AXUIElementCreateApplication(app.processIdentifier)
|
||||||
|
var windowsRef: CFTypeRef?
|
||||||
|
let status = AXUIElementCopyAttributeValue(appElement, kAXWindowsAttribute as CFString, &windowsRef)
|
||||||
|
guard status == .success, let windows = windowsRef as? [AXUIElement], !windows.isEmpty else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for window in windows {
|
||||||
|
var minimizedRef: CFTypeRef?
|
||||||
|
let minimizedStatus = AXUIElementCopyAttributeValue(window, kAXMinimizedAttribute as CFString, &minimizedRef)
|
||||||
|
if minimizedStatus == .success, let minimized = minimizedRef as? Bool, minimized {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if let geometry = geometryFromAXWindow(window) {
|
||||||
|
return geometry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func geometryFromCoreGraphics() -> WindowGeometry? {
|
||||||
|
// Keep the CG fallback for environments without Accessibility permissions.
|
||||||
|
// Use on-screen layer-0 windows to avoid off-screen helpers/shadows.
|
||||||
|
let options: CGWindowListOption = [.optionOnScreenOnly, .excludeDesktopElements]
|
||||||
|
let windowList = CGWindowListCopyWindowInfo(options, kCGNullWindowID) as? [[String: Any]] ?? []
|
||||||
|
|
||||||
|
for window in windowList {
|
||||||
|
guard let ownerName = window[kCGWindowOwnerName as String] as? String,
|
||||||
|
normalizedMpvName(ownerName) else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if let layer = window[kCGWindowLayer as String] as? Int, layer != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if let alpha = window[kCGWindowAlpha as String] as? Double, alpha <= 0.01 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if let onScreen = window[kCGWindowIsOnscreen as String] as? Int, onScreen == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let bounds = window[kCGWindowBounds as String] as? [String: CGFloat] else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let geometry = geometryFromRect(
|
||||||
|
x: bounds["X"] ?? 0,
|
||||||
|
y: bounds["Y"] ?? 0,
|
||||||
|
width: bounds["Width"] ?? 0,
|
||||||
|
height: bounds["Height"] ?? 0
|
||||||
|
)
|
||||||
|
|
||||||
|
guard geometry.width >= 100, geometry.height >= 100 else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return geometry
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if let window = geometryFromAccessibilityAPI() ?? geometryFromCoreGraphics() {
|
||||||
|
print("\(window.x),\(window.y),\(window.width),\(window.height)")
|
||||||
|
} else {
|
||||||
|
print("not-found")
|
||||||
|
}
|
||||||
@@ -347,6 +347,7 @@ const shouldToggleMouseIgnore = !isLinuxPlatform;
|
|||||||
const INVISIBLE_POSITION_EDIT_TOGGLE_CODE = "KeyP";
|
const INVISIBLE_POSITION_EDIT_TOGGLE_CODE = "KeyP";
|
||||||
const INVISIBLE_POSITION_STEP_PX = 1;
|
const INVISIBLE_POSITION_STEP_PX = 1;
|
||||||
const INVISIBLE_POSITION_STEP_FAST_PX = 4;
|
const INVISIBLE_POSITION_STEP_FAST_PX = 4;
|
||||||
|
const INVISIBLE_MACOS_VERTICAL_NUDGE_PX = 4;
|
||||||
|
|
||||||
let isOverSubtitle = false;
|
let isOverSubtitle = false;
|
||||||
let isDragging = 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;
|
invisibleLayoutBaseLeftPx = parseFloat(subtitleContainer.style.left) || 0;
|
||||||
invisibleLayoutBaseBottomPx = Number.isFinite(parseFloat(subtitleContainer.style.bottom))
|
invisibleLayoutBaseBottomPx = Number.isFinite(parseFloat(subtitleContainer.style.bottom))
|
||||||
? parseFloat(subtitleContainer.style.bottom)
|
? parseFloat(subtitleContainer.style.bottom)
|
||||||
|
|||||||
@@ -17,11 +17,132 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { execFile } from "child_process";
|
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 { BaseWindowTracker } from "./base-tracker";
|
||||||
|
import { createLogger } from "../logger";
|
||||||
|
|
||||||
|
const log = createLogger("tracker").child("macos");
|
||||||
|
|
||||||
export class MacOSWindowTracker extends BaseWindowTracker {
|
export class MacOSWindowTracker extends BaseWindowTracker {
|
||||||
private pollInterval: ReturnType<typeof setInterval> | null = null;
|
private pollInterval: ReturnType<typeof setInterval> | null = null;
|
||||||
private pollInFlight = false;
|
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 {
|
start(): void {
|
||||||
this.pollInterval = setInterval(() => this.pollGeometry(), 250);
|
this.pollInterval = setInterval(() => this.pollGeometry(), 250);
|
||||||
@@ -36,42 +157,28 @@ export class MacOSWindowTracker extends BaseWindowTracker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private pollGeometry(): void {
|
private pollGeometry(): void {
|
||||||
if (this.pollInFlight) {
|
if (this.pollInFlight || !this.helperPath || !this.helperType) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pollInFlight = true;
|
this.pollInFlight = true;
|
||||||
|
|
||||||
const script = `
|
// Use Core Graphics API via Swift helper for reliable window detection
|
||||||
set processNames to {"mpv", "MPV", "org.mpv.mpv"}
|
// This works with both bundled and unbundled mpv installations
|
||||||
tell application "System Events"
|
const command = this.helperType === "binary" ? this.helperPath : "swift";
|
||||||
repeat with procName in processNames
|
const args = this.helperType === "binary" ? [] : [this.helperPath];
|
||||||
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"
|
|
||||||
`;
|
|
||||||
|
|
||||||
execFile(
|
execFile(
|
||||||
"osascript",
|
command,
|
||||||
["-e", script],
|
args,
|
||||||
{
|
{
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
timeout: 1000,
|
timeout: 1000,
|
||||||
maxBuffer: 1024 * 1024,
|
maxBuffer: 1024 * 1024,
|
||||||
},
|
},
|
||||||
(err, stdout) => {
|
(err, stdout, stderr) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
this.maybeLogExecError(err, stderr || "");
|
||||||
this.updateGeometry(null);
|
this.updateGeometry(null);
|
||||||
this.pollInFlight = false;
|
this.pollInFlight = false;
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user