Skip to content

Fix: Linux startup panic from unrealized dictate window (#680)#837

Open
PhamBit wants to merge 1 commit into
jamiepine:mainfrom
PhamBit:fix/linux-dictate-window-startup-panic
Open

Fix: Linux startup panic from unrealized dictate window (#680)#837
PhamBit wants to merge 1 commit into
jamiepine:mainfrom
PhamBit:fix/linux-dictate-window-startup-panic

Conversation

@PhamBit

@PhamBit PhamBit commented Jul 1, 2026

Copy link
Copy Markdown

Summary

Fixes a startup crash on Linux where the app opens the main window and then immediately aborts with a non-unwinding panic from tao:

thread 'main' panicked at tao-0.34.5/src/platform_impl/linux/event_loop.rs:449:18:
called `Option::unwrap()` on a `None` value
...
panic in a function that cannot unwind

This is the crash reported in #680.

Root cause

build_dictate_window (in tauri/src-tauri/src/main.rs) constructs the transparent "dictate pill" overlay with .visible(false). On the Linux GTK3 backend, a window that is never shown never has its underlying GDK window realized. Shortly after startup, the first set_ignore_cursor_events() toggle is dispatched over the glib main-context channel and reaches into that unrealized window — tao calls .unwrap() on the None GDK window (event_loop.rs:449) and, because it happens in a non-unwinding context, the panic takes the whole process down before the UI is usable.

Fix

Build the pill visible so GTK realizes the GDK backing, then immediately move it off-screen (-10000, -10000) and hide() it. The pill is still not visible to the user until a speak, so behaviour is unchanged — but the window now actually exists when cursor-ignore is first toggled, so there is no None to unwrap.

The change is confined to build_dictate_window; no public API or other call sites are touched.

Testing

  • Environment: Ubuntu 24.04, x86_64, X11 / GTK3 (WebKitGTK), tao 0.34.5 / tauri 2.9.5 — the same stack as [Linux] run just dev crashed on startup with Option::unwrap() panic in tao event loop #680.
  • Before: app aborted on every launch at event_loop.rs:449.
  • After: app launches and stays up. Verified with xwininfo that both the main window (1200x800) and the dictate pill are realized, with the pill parked off-screen and hidden as intended.
  • cargo check --no-default-features passes on current main (only pre-existing keyboard_layout.rs dead-code warnings).

Happy to add a CHANGELOG.md entry in whatever format you prefer.

Fixes #680

Summary by CodeRabbit

  • Bug Fixes
    • Improved Linux window handling so the dictate window initializes reliably without crashing when cursor-related events are toggled.
    • Kept the window hidden and off-screen until it is ready to be shown, preventing unintended interaction during startup.

On Linux (GTK3 + tao 0.34.5), the transparent dictate pill was built
with .visible(false), which leaves its GDK window unrealized. The first
set_ignore_cursor_events() dispatched over the glib main-context channel
then unwraps a None GDK window and aborts the whole app with a
non-unwinding panic at tao event_loop.rs:449.

Build the pill visible so GTK realizes the GDK backing, then immediately
move it off-screen and hide it. The pill remains invisible to the user
until a speak, so behaviour is unchanged, but the window now exists when
cursor-ignore is first toggled.

Fixes jamiepine#680

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The build_dictate_window function in the Tauri desktop backend was modified to create the dictate window visible at construction (instead of hidden), then explicitly reposition it off-screen and hide it after monitor-based positioning logic runs.

Changes

Dictate Window Realization Fix

Layer / File(s) Summary
Window creation and off-screen parking
tauri/src-tauri/src/main.rs
Window builder now sets .visible(true) at creation (with rationale comments for Linux/GTK realization) instead of .visible(false), and after monitor positioning logic, explicitly calls set_position(-10_000, -10_000) and hide() to park the window off-screen and non-interactive.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant WindowBuilder
  participant GdkWindow

  App->>WindowBuilder: build_dictate_window()
  WindowBuilder->>GdkWindow: create with visible(true)
  GdkWindow-->>WindowBuilder: window realized
  WindowBuilder->>GdkWindow: set_position(-10000, -10000)
  WindowBuilder->>GdkWindow: hide()
  GdkWindow-->>App: parked, non-interactive
Loading

Related issues: #680 — Fixes the Linux startup crash caused by Option::unwrap() panic in the tao event loop by ensuring the dictate window's GDK window is realized before later show/hide logic runs.

Suggested labels: bug, linux, tauri

Suggested reviewers: jamiepine

🐰 A window once hidden, now blinks awake,
Then scurries off-screen, no crash in its wake,
GDK realized, GTK appeased,
Linux devs' startup panics, eased.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: fixing the Linux startup panic caused by the unrealized dictate window.
Linked Issues check ✅ Passed The change addresses #680 by realizing the dictate window on Linux and preventing the tao startup panic while keeping the app usable.
Out of Scope Changes check ✅ Passed The PR is narrowly scoped to the Linux dictate-window startup fix in main.rs with no obvious unrelated changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Tools execution failed with the following error:

Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error)


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tauri/src-tauri/src/main.rs (1)

49-58: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Scope the visibility workaround to Linux only.

The rationale comment explains this is a Linux/GTK3/tao-specific realization issue, but .visible(true) is applied unconditionally on all platforms. macOS/Windows previously built the window hidden with no crash; forcing it visible there now introduces an unnecessary (if brief) on-screen flash/focus-steal of the transparent pill before it's parked off-screen at line 70-71.

Consider gating this behind cfg(target_os = "linux") so macOS/Windows retain the original hidden-at-create behavior while Linux gets the realization fix.

♻️ Suggested platform-gated builder
-    .shadow(false)
-    // On Linux (GTK3 + tao 0.34.5), building the transparent dictate pill with
-    // `.visible(false)` leaves its GDK window unrealized. The first
-    // `set_ignore_cursor_events()` dispatched over the glib main-context channel
-    // then unwraps a `None` GDK window and aborts the whole app with a
-    // non-unwinding panic at tao event_loop.rs:449 (see `#680`). Build the window
-    // visible so GTK realizes the GDK backing, then immediately park it
-    // off-screen and hide it (below). Net user-visible effect is unchanged — the
-    // pill stays hidden until a speak — but the window now actually exists.
-    .visible(true)
+    .shadow(false)
+    // On Linux (GTK3 + tao 0.34.5), building the transparent dictate pill with
+    // `.visible(false)` leaves its GDK window unrealized. The first
+    // `set_ignore_cursor_events()` dispatched over the glib main-context channel
+    // then unwraps a `None` GDK window and aborts the whole app with a
+    // non-unwinding panic at tao event_loop.rs:449 (see `#680`). Build the window
+    // visible so GTK realizes the GDK backing, then immediately park it
+    // off-screen and hide it (below). Net user-visible effect is unchanged — the
+    // pill stays hidden until a speak — but the window now actually exists.
+    // Scoped to Linux since macOS/Windows never hit this GDK realization gap.
+    .visible(cfg!(target_os = "linux"))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tauri/src-tauri/src/main.rs` around lines 49 - 58, Scope the
window-visibility workaround to Linux only in the builder chain around the
transparent pill creation. The current `.visible(true)` change in `main` is
Linux/GTK3/tao-specific, so gate it with `cfg(target_os = "linux")` (or
equivalent platform branching) and keep macOS/Windows on the original
hidden-at-create path. Preserve the later off-screen parking and hide logic, but
ensure only Linux forces realization before `set_ignore_cursor_events()` is
used.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tauri/src-tauri/src/main.rs`:
- Around line 49-58: Scope the window-visibility workaround to Linux only in the
builder chain around the transparent pill creation. The current `.visible(true)`
change in `main` is Linux/GTK3/tao-specific, so gate it with `cfg(target_os =
"linux")` (or equivalent platform branching) and keep macOS/Windows on the
original hidden-at-create path. Preserve the later off-screen parking and hide
logic, but ensure only Linux forces realization before
`set_ignore_cursor_events()` is used.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a65602f3-1134-4249-bf39-5031fbee42e2

📥 Commits

Reviewing files that changed from the base of the PR and between e766c7c and ca242d8.

📒 Files selected for processing (1)
  • tauri/src-tauri/src/main.rs

H-Chris233 added a commit to Open-Less/openless that referenced this pull request Jul 14, 2026
…| fix(linux): 修复胶囊窗口因 GDK 未实例化导致的启动崩溃 (#792)

* fix(linux): prevent startup panic from unrealized capsule GDK window | fix(linux): 修复胶囊窗口因 GDK 未实例化导致的启动崩溃

On Linux X11, when the capsule window is created with `visible: false` +
`transparent: true` in tauri.conf.json, its underlying GDK window is never
realized. The subsequent call to `capsule.set_ignore_cursor_events(true)`
triggers a panic inside tao's Linux event loop (event_loop.rs:457) because
`window.window().unwrap()` returns `None` for an unrealized GDK window.

The fix follows the same approach as voicebox PR#837 (PhamBit):
force GDK window realization before calling set_ignore_cursor_events, by
first moving the window off-screen and calling show(). The window is then
repositioned correctly by the existing position_capsule_bottom_center()
call and hidden as normal.

Refs:
  - jamiepine/voicebox#837
  - tauri-apps/tao#635

在 Linux X11 上,tauri.conf.json 中定义的 capsule 窗口设置了
visible: false + transparent: true,导致其底层 GDK 窗口从未被实例化。
随后调用 capsule.set_ignore_cursor_events(true) 时,tao 的 Linux 事件循环
(event_loop.rs:457) 因 window.window().unwrap() 返回 None 而 panic 崩溃。

修复方法参考 voicebox PR#837:在设置鼠标穿透前先将窗口移出屏幕并
调用 show() 强制 GDK 实例化,后续由原有的 position_capsule_bottom_center()
重新定位并隐藏。

* fix(linux): realize hidden capsule without showing it

---------

Co-authored-by: Chris233 <h-chris233@outlook.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Linux] run just dev crashed on startup with Option::unwrap() panic in tao event loop

1 participant