Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,37 @@ Target-specific build commands are also available:

---

## Linux: Hyprland / Omarchy

Recordly's recording HUD, countdown, and source picker are transparent floating windows.
Hyprland decorates them like any other window, so the compositor's blur, shadow, dim, and
opacity rules show up as a grey box around the HUD. Wayland also ignores `alwaysOnTop`, so
the HUD can end up behind other windows or stuck on one workspace.

Add these rules to `~/.config/hypr/hyprland.lua` (Omarchy) and run `hyprctl reload`:

```lua
o.window("^[Rr]ecordly$", { tag = "-default-opacity", opacity = "1 1" })
o.window({ class = "^[Rr]ecordly$", float = true }, {
pin = true,
no_blur = true,
no_shadow = true,
border_size = 0,
no_dim = true,
})
```

Plain `hyprland.conf` equivalent:

```ini
windowrule = opacity 1 1, class:^[Rr]ecordly$
windowrule = pin, class:^[Rr]ecordly$, floating:1
windowrule = noblur, class:^[Rr]ecordly$, floating:1
windowrule = noshadow, class:^[Rr]ecordly$, floating:1
windowrule = nodim, class:^[Rr]ecordly$, floating:1
windowrule = bordersize 0, class:^[Rr]ecordly$, floating:1
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
```

## macOS: "App cannot be opened"

Locally built apps may be quarantined by macOS.
Expand Down
2 changes: 2 additions & 0 deletions electron/electron-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ interface RendererNativeExportCapabilities {
interface Window {
electronAPI: {
hudOverlaySetIgnoreMouse: (ignore: boolean) => void;
hudOverlaySetMenuOpen: (open: boolean) => void;
hudOverlaySetSourceSelectionActive: (active: boolean) => void;
hudOverlayDrag: (phase: "start" | "move" | "end", screenX: number, screenY: number) => void;
hudOverlayHide: () => void;
Expand All @@ -208,6 +209,7 @@ interface Window {
getHudOverlayMousePassthroughSupported: () => Promise<{
success: boolean;
supported: boolean;
resizeAnchor?: "bottom" | "center";
}>;
setHudOverlayCaptureProtection: (
enabled: boolean,
Expand Down
39 changes: 31 additions & 8 deletions electron/hudOverlayBounds.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, expect, it } from "vitest";

import {
getHudOverlayResizeAnchor,
getHudOverlayWindowBounds,
resizeHudOverlayFallbackBounds,
shouldExpandHudOverlayFallback,
Expand Down Expand Up @@ -30,9 +31,9 @@ describe("getHudOverlayWindowBounds", () => {
it("expands the non-passthrough fallback for HUD menus and hover interaction", () => {
expect(getHudOverlayWindowBounds(workArea, false, true)).toEqual({
x: 650,
y: 540,
y: 400,
width: 860,
height: 540,
height: 680,
});
});

Expand Down Expand Up @@ -98,9 +99,9 @@ describe("resizeHudOverlayFallbackBounds", () => {
),
).toEqual({
x: 420,
y: 320,
y: 180,
width: 860,
height: 540,
height: 680,
});
});

Expand All @@ -110,9 +111,9 @@ describe("resizeHudOverlayFallbackBounds", () => {
workArea,
{
x: 420,
y: 320,
y: 180,
width: 860,
height: 540,
height: 680,
},
false,
),
Expand All @@ -138,13 +139,35 @@ describe("resizeHudOverlayFallbackBounds", () => {
),
).toEqual({
x: 1060,
y: 520,
y: 380,
width: 860,
height: 540,
height: 680,
});
});
});

describe("getHudOverlayResizeAnchor", () => {
it("returns bottom on non-linux platforms", () => {
expect(getHudOverlayResizeAnchor("darwin", {})).toBe("bottom");
expect(getHudOverlayResizeAnchor("win32", {})).toBe("bottom");
});

it("returns center on linux under wayland", () => {
expect(
getHudOverlayResizeAnchor("linux", { XDG_SESSION_TYPE: "wayland" }),
).toBe("center");
expect(
getHudOverlayResizeAnchor("linux", { WAYLAND_DISPLAY: "wayland-1" }),
).toBe("center");
});

it("returns bottom on linux under x11", () => {
expect(
getHudOverlayResizeAnchor("linux", { XDG_SESSION_TYPE: "x11" }),
).toBe("bottom");
});
});

describe("shouldExpandHudOverlayFallback", () => {
it("expands while recording only when the floating webcam preview is visible", () => {
expect(
Expand Down
17 changes: 16 additions & 1 deletion electron/hudOverlayBounds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,27 @@ export interface HudOverlayWorkArea {

const NON_PASSTHROUGH_HUD_WIDTH_DIP = 860;
const NON_PASSTHROUGH_HUD_COMPACT_HEIGHT_DIP = 160;
const NON_PASSTHROUGH_HUD_EXPANDED_HEIGHT_DIP = 540;
const NON_PASSTHROUGH_HUD_EXPANDED_HEIGHT_DIP = 680;

function clamp(value: number, min: number, max: number): number {
return Math.min(Math.max(value, min), max);
}

export type HudOverlayResizeAnchor = "bottom" | "center";

// Wayland refuses client-side window placement, so the compositor decides where
// a resized HUD lands. Hyprland keeps floating windows centered while X11 honors
// the bottom-anchored bounds this module computes.
export function getHudOverlayResizeAnchor(
platform: NodeJS.Platform,
env: NodeJS.ProcessEnv,
): HudOverlayResizeAnchor {
if (platform !== "linux") {
return "bottom";
}
return env.XDG_SESSION_TYPE === "wayland" || env.WAYLAND_DISPLAY ? "center" : "bottom";
}

export function getHudOverlayWindowBounds(
workArea: HudOverlayWorkArea,
mousePassthroughSupported: boolean,
Expand Down
3 changes: 3 additions & 0 deletions electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ contextBridge.exposeInMainWorld("electronAPI", {
hudOverlaySetIgnoreMouse: (ignore: boolean) => {
ipcRenderer.send("hud-overlay-set-ignore-mouse", ignore);
},
hudOverlaySetMenuOpen: (open: boolean) => {
ipcRenderer.send("hud-overlay-set-menu-open", open);
},
hudOverlaySetSourceSelectionActive: (active: boolean) => {
ipcRenderer.send("hud-overlay-set-source-selection-active", active);
},
Expand Down
10 changes: 10 additions & 0 deletions electron/windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { app, BrowserWindow, ipcMain } from "electron";
import { supportsHudCaptureProtection } from "../src/lib/hudCaptureProtection";
import { USER_DATA_PATH } from "./appPaths";
import {
getHudOverlayResizeAnchor,
getHudOverlayWindowBounds,
resizeHudOverlayFallbackBounds,
shouldExpandHudOverlayFallback,
Expand Down Expand Up @@ -331,6 +332,14 @@ ipcMain.on("hud-overlay-set-ignore-mouse", (_event, ignore: boolean) => {
setHudOverlayMousePassthrough(Boolean(ignore));
});

// Linux has no hover-driven passthrough, so the compact HUD window only grows
// while a menu is open; growing on hover moves the bar out from under the pointer.
ipcMain.on("hud-overlay-set-menu-open", (_event, open: boolean) => {
if (process.platform === "linux") {
setHudOverlayFallbackExpanded(Boolean(open));
}
});

ipcMain.on("hud-overlay-set-source-selection-active", (_event, active: boolean) => {
hudOverlaySourceSelectionActive = Boolean(active);
if (hudOverlaySourceSelectionActive) {
Expand Down Expand Up @@ -418,6 +427,7 @@ ipcMain.handle("get-hud-overlay-mouse-passthrough-supported", () => {
return {
success: true,
supported: isHudOverlayMousePassthroughSupported(),
resizeAnchor: getHudOverlayResizeAnchor(process.platform, process.env),
};
});

Expand Down
9 changes: 7 additions & 2 deletions src/components/launch/LaunchWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ function LaunchWindowContent() {

const {
hudOverlayMousePassthroughSupported,
hudOverlayResizeAnchor,
platform,
appVersion,
hideHudFromCapture,
Expand Down Expand Up @@ -446,8 +447,12 @@ function LaunchWindowContent() {
value={{ onMouseEnter: handleHudMouseEnter, onMouseLeave: handleHudMouseLeave }}
>
<div
className="w-full flex justify-center bg-transparent overflow-visible items-end pb-5 pointer-events-none"
style={{ height: "100vh" }}
className="w-full flex justify-center bg-transparent overflow-visible items-end pointer-events-none"
style={{
height: "100vh",
paddingBottom:
hudOverlayResizeAnchor === "center" ? "calc(50vh - 60px)" : "1.25rem",
}}
Comment on lines +464 to +470

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in latest commit: derived WAYLAND_CENTER_OFFSET_PX (COMPACT_HUD_HEIGHT_DIP / 2 - STANDARD_HUD_BOTTOM_PADDING_PX = 60px) and documented the Wayland center-anchored geometry.

>
<div
ref={hudContentRef}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export function useLaunchHudInteractionState({
const timeoutRef = useRef<NodeJS.Timeout | null>(null);

useEffect(() => {
window.electronAPI?.hudOverlaySetMenuOpen?.(openId !== null);
if (openId !== null) {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
window.electronAPI?.hudOverlaySetIgnoreMouse?.(false);
Comment on lines 23 to 32

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in latest commit: tracked popoverCloseTimeoutRef to clear active timeouts when openId changes or unmounts, and gated the callback to check openIdRef.current === null.

Expand Down
5 changes: 5 additions & 0 deletions src/components/launch/hooks/useLaunchWindowSystemState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export function useLaunchWindowSystemState(
const [hudOverlayMousePassthroughSupported, setHudOverlayMousePassthroughSupported] = useState<
boolean | null
>(null);
const [hudOverlayResizeAnchor, setHudOverlayResizeAnchor] = useState<"bottom" | "center">(
"bottom",
);
const [platform, setPlatform] = useState<string | null>(null);
const [appVersion, setAppVersion] = useState<string | null>(null);
const [hideHudFromCapture, setHideHudFromCapture] = useState(true);
Expand Down Expand Up @@ -54,6 +57,7 @@ export function useLaunchWindowSystemState(
const result = await window.electronAPI.getHudOverlayMousePassthroughSupported();
if (!cancelled && result.success) {
setHudOverlayMousePassthroughSupported(result.supported);
setHudOverlayResizeAnchor(result.resizeAnchor ?? "bottom");
}
} catch (error) {
console.error("Failed to load HUD overlay mouse passthrough support:", error);
Expand Down Expand Up @@ -132,6 +136,7 @@ export function useLaunchWindowSystemState(
return {
recordingsDirectory,
hudOverlayMousePassthroughSupported,
hudOverlayResizeAnchor,
platform,
appVersion,
hideHudFromCapture,
Expand Down