fix(linux): enable JS resize for frameless windows; fix scrollbar edge detection (#4415, #4680)#5368
fix(linux): enable JS resize for frameless windows; fix scrollbar edge detection (#4415, #4680)#5368leaanthony wants to merge 3 commits intomasterfrom
Conversation
…e detection Frameless Wails windows on Linux had no resize capability: the drag.ts resize-edge logic was gated behind `!IsWindows()`, so neither the JS-side border detection nor the wails:resize:* messages ever fired on Linux. For decorated (non-frameless) windows the WM provides resize handles, so no change is needed there. Changes: - drag.ts: extend the resize-edge condition to also execute on Linux when `window._wails.flags.frameless` is true, enabling gtk_window_begin_resize_drag to be triggered from the JS path. - drag.ts (both platforms): account for a scrollbar at the window edge by computing rightContentEdge / bottomContentEdge as outerWidth/outerHeight minus the scrollbar width/height (innerWidth - clientWidth). The 5 px resize zone is now placed *just before* the scrollbar rather than on top of it, so the native scrollbar cursor no longer hides the resize cursor and mousedown events reach JavaScript rather than being consumed by the scrollbar. - linux_cgo.go / linux_cgo_gtk4.go: setResizable now calls execJS to keep window._wails.setResizable() in sync (mirrors Windows behaviour). setFrameless propagates window._wails.flags.frameless via execJS for runtime frameless toggles. - webview_window_linux.go: the WindowLoadFinished hook now initialises both flags.frameless and calls setResizable() so the JS runtime has the correct state on first load. Fixes: #4415 Fixes: #4680 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
🚧 Files skipped from review as they are similar to previous changes (4)
WalkthroughThis PR enables frameless window resizing on Linux by coordinating three areas: regenerating the frontend runtime bundle, updating drag/resize detection to be scrollbar-aware and allow frameless resizing on Linux, and syncing frameless/resizable flags from the Go backend to the frontend (including startup injection). ChangesLinux Frameless Resize Implementation
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 golangci-lint (2.12.1)level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies" 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. Comment |
There was a problem hiding this comment.
Pull request overview
This PR fixes window resizing UX in the v3 desktop runtime by enabling JS-driven resize-edge detection for Linux frameless windows (which otherwise lack WM resize handles) and by adjusting the resize hit-test zone to avoid the native scrollbar strip (which can swallow mouse events at the window edge).
Changes:
- Enable resize-edge detection on Linux when the window is frameless (previously effectively Windows-only).
- Adjust right/bottom edge hit-testing to subtract scrollbar dimensions so resize zones remain mouse-accessible.
- Sync Linux native window state changes (
resizable,frameless) into the JS runtime (including initial values on first load) and rebuild the bundled runtime JS.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
v3/pkg/application/webview_window_linux.go |
Injects initial flags.frameless and calls JS setResizable() at WindowLoadFinished to bootstrap correct runtime state. |
v3/pkg/application/linux_cgo.go |
Updates GTK3 setResizable / setFrameless to also notify the JS runtime. |
v3/pkg/application/linux_cgo_gtk4.go |
Mirrors the GTK3 runtime sync changes for the GTK4 implementation. |
v3/internal/runtime/desktop/@wailsio/runtime/src/drag.ts |
Enables Linux frameless resizing and shifts resize hit-test zones away from scrollbar strips. |
v3/internal/assetserver/bundledassets/runtime.js |
Rebuilt bundled runtime output reflecting the TypeScript changes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| C.gtk_window_set_decorated(w.gtkWindow(), gtkBool(!frameless)) | ||
| // TODO: Deal with transparency for the titlebar if possible when !frameless | ||
| // Perhaps we just make it undecorated and add a menu bar inside? | ||
| w.execJS(fmt.Sprintf("window._wails.flags.frameless=%v;", frameless)) |
|
|
||
| func (w *linuxWebviewWindow) setFrameless(frameless bool) { | ||
| C.gtk_window_set_decorated(w.gtkWindow(), gtkBool(!frameless)) | ||
| w.execJS(fmt.Sprintf("window._wails.flags.frameless=%v;", frameless)) |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
v3/internal/runtime/desktop/@wailsio/runtime/src/drag.ts (1)
237-251: ⚡ Quick win
outerWidth/outerHeightas edge base is incorrect when a native menu bar is present.
event.clientX/Yare viewport coordinates (bounded byinnerWidth/innerHeight). Usingwindow.outerWidthandwindow.outerHeightas the base forrightContentEdge/bottomContentEdgeis fine whenouterWidth == innerWidth, but breaks for a frameless window that includes a native menu bar in its GTK vbox: the menu bar's height causesouterHeight > innerHeight. In that casebottomContentEdge = outerHeight - scrollbarHeight > innerHeight, soevent.clientY(<innerHeight) can never satisfy thebottomContentEdge - event.clientY < resizeHandleHeightcondition — the bottom resize zone becomes permanently unreachable.Using
innerWidth/innerHeight(which already equalclientWidth + scrollbarWidth/clientHeight + scrollbarHeight) is semantically correct regardless of GTK chrome:♻️ Proposed fix
- const rightContentEdge = window.outerWidth - scrollbarWidth; - const bottomContentEdge = window.outerHeight - scrollbarHeight; + const rightContentEdge = window.innerWidth - scrollbarWidth; // == document.documentElement.clientWidth + const bottomContentEdge = window.innerHeight - scrollbarHeight; // == document.documentElement.clientHeight🤖 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 `@v3/internal/runtime/desktop/`@wailsio/runtime/src/drag.ts around lines 237 - 251, The edge calculations use window.outerWidth/outerHeight which is incorrect when native chrome (e.g. a GTK menu bar) makes outer* > inner*; change the base for rightContentEdge and bottomContentEdge to window.innerWidth and window.innerHeight respectively so viewport-based event.clientX/Y comparisons work; update any dependent checks (rightBorder, bottomBorder, rightCorner, bottomCorner) that reference rightContentEdge/bottomContentEdge to use the new inner-based values and keep existing scrollbarWidth/scrollbarHeight, resizeHandleWidth/resizeHandleHeight, cornerExtra logic unchanged.
🤖 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 `@v3/internal/runtime/desktop/`@wailsio/runtime/src/drag.ts:
- Around line 237-251: The edge calculations use window.outerWidth/outerHeight
which is incorrect when native chrome (e.g. a GTK menu bar) makes outer* >
inner*; change the base for rightContentEdge and bottomContentEdge to
window.innerWidth and window.innerHeight respectively so viewport-based
event.clientX/Y comparisons work; update any dependent checks (rightBorder,
bottomBorder, rightCorner, bottomCorner) that reference
rightContentEdge/bottomContentEdge to use the new inner-based values and keep
existing scrollbarWidth/scrollbarHeight, resizeHandleWidth/resizeHandleHeight,
cornerExtra logic unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 4682ae93-17cc-44f5-b7bf-b99de005e70e
📒 Files selected for processing (6)
v3/internal/assetserver/bundledassets/runtime.debug.jsv3/internal/assetserver/bundledassets/runtime.jsv3/internal/runtime/desktop/@wailsio/runtime/src/drag.tsv3/pkg/application/linux_cgo.gov3/pkg/application/linux_cgo_gtk4.gov3/pkg/application/webview_window_linux.go
- drag.ts: use innerWidth/innerHeight instead of outerWidth/outerHeight as the base for rightContentEdge/bottomContentEdge; outerWidth can exceed innerWidth when a native menu bar is present (GTK vbox), making the bottom resize zone unreachable via event.clientY - linux_cgo.go/linux_cgo_gtk4.go: guard execJS calls in setResizable and setFrameless with if(window._wails&&...) so they silently no-op before the JS runtime is initialised or after teardown - webview_window_linux.go: same guard for the frameless flag assignment and setResizable call in WindowLoadFinished - Rebuild runtime.js / runtime.debug.js from updated TypeScript Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai>
Summary
!IsWindows())mousedownis never seen by JavaScript)Root cause
drag.tscontains the resize-edge cursor andwails:resize:*message logic. It has an early-exit guard:On Linux the guard always fires, so no
wails:resize:*message is ever sent andgtk_window_begin_resize_dragis never called. For decorated windows the WM provides resize handles; for frameless windows nothing does — they are permanently non-resizable in v3.The second issue (scrollbars) affects both Windows and Linux: the native scrollbar occupies the last ≈ 15–17 px at the window edge. The
rightBordercheck used rawwindow.outerWidth - event.clientX, which fired inside the scrollbar strip. The native scrollbar cursor overrides any CSS cursor, hiding the resize hint, and the scrollbar capturesmousedownsoprimaryDown()is never called andcanResizestays false.Changes
drag.tsdrag.tsrightBorder/bottomBorder/rightCorner/bottomCornernow subtract the scrollbar width/height (innerWidth - clientWidth) so the resize zone sits just before the scrollbar, keeping it mouse-event–accessible.linux_cgo.go/linux_cgo_gtk4.gosetResizablecallsexecJSto keepwindow._wails.setResizable()in sync (matching Windows behaviour).setFramelesspropagateswindow._wails.flags.framelessfor runtime frameless toggles.webview_window_linux.goWindowLoadFinishedhook initialisesflags.framelessand callssetResizable()so the JS runtime has correct state on first load.runtime.js/runtime.debug.jsTest plan
ew-resizecursor; click-drag resizes the window; scrollbar still scrolls normally when not at the very edgewindow.SetResizable(false)at runtime correctly disables JS resize on LinuxPlatform notes
Tested with: Ubuntu 24.04 LTS, GTK3 + webkit2gtk-4.0. The GTK4 path (
linux_cgo_gtk4.go) receives the samesetResizable/setFramelessJS notification changes.CC @leaanthony
Summary by CodeRabbit
Bug Fixes
Chores