Skip to content

Commit 7869dde

Browse files
committed
fix(desktop): avoid glibc 2.39 in Linux snapshot helpers
1 parent e455200 commit 7869dde

12 files changed

Lines changed: 159 additions & 75 deletions

‎.github/workflows/release-desktop.yml‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ on:
114114
# electron-builder leaves the default arch (x64) out of the unpacked app's
115115
# directory name.
116116
env:
117+
KDE_RUST_TARGET: ${{ inputs.arch == 'arm64' && 'aarch64-unknown-linux-musl' || 'x86_64-unknown-linux-musl' }}
117118
UNPACKED_DIR: ${{ inputs.platform }}-${{ inputs.arch != 'x64' && format('{0}-', inputs.arch) || '' }}unpacked
118119

119120
permissions:
@@ -206,9 +207,9 @@ jobs:
206207
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
207208
with:
208209
path: |
209-
native/kde-snap-shot/target/${{ inputs.rust_target }}/release/t3-kde-snap-shot
210+
native/kde-snap-shot/target/${{ env.KDE_RUST_TARGET }}/release/t3-kde-snap-shot
210211
native/hyprland-snap-shot/target/${{ inputs.rust_target }}/release/t3-hyprland-snap-shot
211-
key: linux-capture-helpers-${{ inputs.rust_target }}-${{ hashFiles('native/kde-snap-shot/Cargo.lock', 'native/kde-snap-shot/Cargo.toml', 'native/kde-snap-shot/src/**', 'native/hyprland-snap-shot/Cargo.lock', 'native/hyprland-snap-shot/Cargo.toml', 'native/hyprland-snap-shot/src/**', 'native/hyprland-snap-shot/protocols/**') }}
212+
key: linux-capture-helpers-musl-kde-v1-${{ inputs.rust_target }}-${{ hashFiles('native/kde-snap-shot/Cargo.lock', 'native/kde-snap-shot/Cargo.toml', 'native/kde-snap-shot/src/**', 'native/hyprland-snap-shot/Cargo.lock', 'native/hyprland-snap-shot/Cargo.toml', 'native/hyprland-snap-shot/src/**', 'native/hyprland-snap-shot/protocols/**') }}
212213

213214
- name: Ensure Rosetta for macOS x64 bytecode generation
214215
if: inputs.platform == 'mac' && inputs.arch == 'x64'
@@ -223,7 +224,7 @@ jobs:
223224
uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable
224225
with:
225226
toolchain: stable
226-
targets: ${{ inputs.rust_target }}
227+
targets: ${{ inputs.rust_target }}${{ inputs.platform == 'linux' && format(',{0}', env.KDE_RUST_TARGET) || '' }}
227228

228229
- name: Download relay client tracing config
229230
if: inputs.relay_client_tracing

‎apps/desktop/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"dependencies": {
1313
"@clerk/electron": "catalog:",
1414
"@clerk/electron-passkeys": "catalog:",
15-
"@crowecawcaw/xa11y": "0.13.0",
15+
"@crowecawcaw/xa11y": "0.15.4",
1616
"@effect/platform-node": "catalog:",
1717
"@napi-rs/keyring": "^1.3.0",
1818
"@t3tools/client-runtime": "workspace:*",

‎apps/desktop/src/electron/ElectronWindow.test.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const {
1111
activateWindowsForegroundMock,
1212
appFocusMock,
1313
browserWindowMock,
14+
dipToScreenRectMock,
1415
foregroundWindowMock,
1516
getAllWindowsMock,
1617
getFocusedWindowMock,
@@ -24,6 +25,12 @@ const {
2425
activateWindowsForegroundMock: vi.fn(),
2526
appFocusMock: vi.fn(),
2627
browserWindowMock: vi.fn(function BrowserWindowMock() {}),
28+
dipToScreenRectMock: vi.fn((_window: unknown, bounds: Electron.Rectangle) => ({
29+
x: bounds.x * 1.5,
30+
y: bounds.y * 1.5,
31+
width: bounds.width * 1.5,
32+
height: bounds.height * 1.5,
33+
})),
2734
foregroundWindowMock: vi.fn<() => bigint>(),
2835
getAllWindowsMock: vi.fn(),
2936
getFocusedWindowMock: vi.fn(),
@@ -49,6 +56,7 @@ vi.mock("electron", () => ({
4956
app: {
5057
focus: appFocusMock,
5158
},
59+
screen: { dipToScreenRect: dipToScreenRectMock },
5260
BrowserWindow: Object.assign(browserWindowMock, {
5361
getAllWindows: getAllWindowsMock,
5462
getFocusedWindow: getFocusedWindowMock,
@@ -116,6 +124,10 @@ describe("ElectronWindow", () => {
116124
});
117125
const electronWindow = yield* ElectronWindow.ElectronWindow;
118126
yield* electronWindow.prepareReveal(window as unknown as Electron.BrowserWindow);
127+
assert.deepInclude(windowsForegroundPrepareMock.mock.calls[0]?.[0], {
128+
bounds: { x: 150, y: 75, width: 1800, height: 1200 },
129+
contentBounds: { x: 162, y: 75, width: 1776, height: 1188 },
130+
});
119131
window.getTitle.mockClear();
120132

121133
const revealFiber = yield* electronWindow

‎apps/desktop/src/electron/ElectronWindow.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ function windowsForegroundFocusTarget(window: Electron.BrowserWindow) {
2626
windowId: window.id,
2727
processId: process.pid,
2828
title: window.getTitle(),
29-
bounds: window.getBounds(),
30-
contentBounds: window.getContentBounds(),
29+
bounds: Electron.screen.dipToScreenRect(window, window.getBounds()),
30+
contentBounds: Electron.screen.dipToScreenRect(window, window.getContentBounds()),
3131
};
3232
}
3333

‎apps/desktop/src/electron/WindowsForegroundFocusWorker.ts‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,7 @@ async function findTarget(
3232
const children = await App.byPid(target.processId, { timeout: 0 })
3333
.then((app) => app.children())
3434
.catch(() => []);
35-
return (
36-
children.find((candidate) => matchesTarget(candidate, target)) ??
37-
(await App.list())
38-
.filter((candidate) => candidate.pid === target.processId)
39-
.map((candidate) => candidate.asElement())
40-
.find((candidate) => matchesTarget(candidate, target))
41-
);
35+
return children.find((candidate) => matchesTarget(candidate, target));
4236
}
4337

4438
const cachedElements = new Map<number, Element>();

‎apps/desktop/src/snapShot/DesktopSnapShot.test.ts‎

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -993,27 +993,31 @@ it.effect("matches Windows accessibility windows on a scaled display", () => {
993993
regionCaptureMock.mockReset().mockResolvedValue({ width: 800, height: 600, png });
994994
accessibilityForegroundMock.mockReset().mockResolvedValue({
995995
pid: 123,
996-
asElement: () => ({
997-
role: "window",
998-
name: "Editor",
999-
bounds: dipBounds,
1000-
children: async () => [
1001-
{
1002-
role: "button",
1003-
name: "Save",
1004-
bounds: { x: 105, y: 110, width: 100, height: 50 },
1005-
children: async () => [],
1006-
},
1007-
],
1008-
tree: async () => ({ name: "Editor", value: "Scaled text", children: [] }),
1009-
}),
996+
windows: async () => [
997+
{
998+
active: true,
999+
role: "window",
1000+
name: "Editor",
1001+
bounds: active.bounds,
1002+
children: async () => [
1003+
{
1004+
role: "button",
1005+
name: "Save",
1006+
bounds: { x: 210, y: 220, width: 200, height: 100 },
1007+
children: async () => [],
1008+
},
1009+
],
1010+
tree: async () => ({ name: "Editor", value: "Scaled text", children: [] }),
1011+
},
1012+
],
10101013
});
10111014
let metadata = "";
10121015
return Effect.scoped(
10131016
Effect.gen(function* () {
10141017
const service = yield* DesktopSnapShot.make;
10151018
yield* service.configure(enabledSettings());
10161019
yield* service.capture;
1020+
assert.deepEqual(regionCaptureMock.mock.calls[0]?.[0], active.bounds);
10171021
const saved = yield* decodePendingMetadata(metadata);
10181022
assert.include(saved.source.accessibleText, "Scaled text");
10191023
assert.deepEqual(
@@ -1400,9 +1404,18 @@ it.each(["darwin", "win32", "linux"] as const)(
14001404
],
14011405
};
14021406
accessibilityByPidMock.mockReset().mockResolvedValue({ children: async () => [window] });
1403-
accessibilityForegroundMock
1404-
.mockReset()
1405-
.mockResolvedValue({ pid: 123, asElement: () => window });
1407+
accessibilityForegroundMock.mockReset().mockResolvedValue({
1408+
pid: 123,
1409+
asElement: () => ({ role: "application", name: "Editor" }),
1410+
windows: async () => [
1411+
{
1412+
...window,
1413+
active: false,
1414+
tree: async () => ({ value: "Inactive window", children: [] }),
1415+
},
1416+
{ ...window, active: true },
1417+
],
1418+
});
14061419

14071420
const result = await readAccessibleWindowContext(
14081421
{ title: "Editor", bounds, owner: { processId: 123 } },
@@ -1442,7 +1455,7 @@ it.each(["darwin", "win32"] as const)(
14421455
accessibilityByPidMock.mockReset().mockResolvedValue({ children: async () => [window] });
14431456
accessibilityForegroundMock
14441457
.mockReset()
1445-
.mockResolvedValue({ pid: 123, asElement: () => window });
1458+
.mockResolvedValue({ pid: 123, windows: async () => [{ ...window, active: true }] });
14461459
try {
14471460
assert.isUndefined(
14481461
await readAccessibleWindowText(
@@ -1484,7 +1497,7 @@ it.each([
14841497
accessibilityByPidMock.mockReset().mockResolvedValue({ children: async () => [window] });
14851498
accessibilityForegroundMock
14861499
.mockReset()
1487-
.mockResolvedValue({ pid: 123, asElement: () => window });
1500+
.mockResolvedValue({ pid: 123, windows: async () => [{ ...window, active: true }] });
14881501
try {
14891502
const result = await readAccessibleWindowContext(
14901503
{ title: "Editor", bounds, owner: { processId: 123 } },

‎apps/desktop/src/snapShot/DesktopSnapShot.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,7 @@ async function captureSource({
508508
({ source, png } = await captureRegionWindowSnapshot(
509509
regionSnapShotPool,
510510
active,
511-
snapShotFlashBounds(active, platform),
511+
active.bounds,
512512
snapShotThumbnailSize(active),
513513
));
514514
} else {
@@ -547,7 +547,7 @@ async function captureSource({
547547
}
548548
}
549549
const accessibleIdentity = active
550-
? { ...active, bounds: snapShotFlashBounds(active, platform) }
550+
? active
551551
: linuxWindow?.processId
552552
? {
553553
title: linuxWindow.title,

‎apps/desktop/src/snapShot/SnapShotAccessibility.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ async function readCapturedWindowAccessibility(
125125
const pidWindows =
126126
foreground !== undefined
127127
? foreground.pid === active.owner.processId
128-
? [foreground.asElement()]
128+
? (await foreground.windows()).filter((window) => window.active)
129129
: []
130130
: await windowsForPid(App, active.owner.processId);
131131
const matchMode = isWaylandSession(platform, process.env) ? "wayland" : "screen-bounds";

‎docs/operations/development.md‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -295,25 +295,28 @@ keeps the explicit flush needed when Windows force-kills the backend.
295295
### Linux AppImage prerequisites
296296

297297
Build on Linux because the browser-secret helper links against the host's libsecret. Install
298-
Rust, C/C++ build tools, libsecret development headers, pkg-config, and ImageMagick.
298+
C/C++ build tools, libsecret development headers, pkg-config, and ImageMagick.
299+
For packaging, install Rust through [rustup](https://rustup.rs): the KDE helper uses its
300+
self-contained musl standard library and bundled `rust-lld` linker. Add
301+
`aarch64-unknown-linux-musl` on ARM64 or `x86_64-unknown-linux-musl` on x64 using `rustup target add`.
299302

300303
Ubuntu and Debian:
301304

302305
```sh
303306
sudo apt-get update
304-
sudo apt-get install cargo rustc build-essential libsecret-1-dev pkg-config imagemagick
307+
sudo apt-get install build-essential libsecret-1-dev pkg-config imagemagick
305308
```
306309

307310
Fedora:
308311

309312
```sh
310-
sudo dnf install rust cargo gcc gcc-c++ make libsecret-devel pkgconf-pkg-config ImageMagick
313+
sudo dnf install gcc gcc-c++ make libsecret-devel pkgconf-pkg-config ImageMagick
311314
```
312315

313316
Arch Linux:
314317

315318
```sh
316-
sudo pacman -S rust base-devel libsecret pkgconf imagemagick
319+
sudo pacman -S base-devel libsecret pkgconf imagemagick
317320
```
318321

319322
The C toolchain, pkg-config, and libsecret headers are also needed for Linux desktop development.

‎pnpm-lock.yaml‎

Lines changed: 30 additions & 29 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)