feat: add per-app auto-hide taskbar when active#12
Conversation
Adds a "Hide Taskbar when active" option to each app config that automatically sets the Windows taskbar to auto-hide when the configured borderless app is in the foreground, and restores the original taskbar state when focus moves away or the app exits. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds Windows taskbar auto-hide control: records original state at startup, hides the taskbar while configured borderless windows are active, restores on focus loss or app exit, adds WinAPI helpers, and exposes a "Hide Taskbar when active" option in defaults and per-app settings. Changes
Sequence Diagram(s)sequenceDiagram
participant Main as Main Loop
participant Scanner as Window Scanner
participant Settings as Settings Store
participant WinAPI as Windows API
participant Taskbar as Taskbar Manager
Main->>Scanner: periodic scan
Scanner->>WinAPI: GetForegroundWindow()
WinAPI-->>Scanner: HWND
Scanner->>Settings: lookup app setting for HWND
Settings-->>Scanner: {HideTaskbar?, AutoApply?}
alt app requests hide
Scanner->>Taskbar: shouldHideTaskbar = true
Taskbar->>WinAPI: setTaskbarAutoHide(ABS_AUTOHIDE)
else otherwise
Scanner->>Taskbar: shouldHideTaskbar = false
Taskbar->>WinAPI: setTaskbarAutoHide(0)
end
Note right of Taskbar: saveTaskbarState() on startup\nrestoreTaskbarOnExit() before quit
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
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.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@gui_app.go`:
- Around line 312-315: The restoreTaskbarOnExit() call is only invoked in the
window close intercept, leaving other shutdown paths (notably restartApp() which
currently calls os.Exit(0)) without taskbar restore; refactor by creating a
single shutdown helper (e.g., shutdownAndExit or performShutdown) that invokes
restoreTaskbarOnExit(), performs any necessary cleanup and then quits/exits,
update window.SetCloseIntercept to call this helper instead of calling
fyneApp.Quit() directly, and change restartApp() to route through the same
helper rather than calling os.Exit(0) so all exits restore the taskbar.
In `@winapi.go`:
- Around line 181-208: findWindowByClass currently swallows lookup failures and
getTaskbarAutoHide flattens the ABM_GETSTATE bitmask to a bool, causing loss of
state when restoring; change findWindowByClass to return (uintptr, error) and
treat a zero hwnd as an error, change getTaskbarAutoHide to return (uint32,
error) and propagate findWindowByClass errors, read the full return value from
procSHAppBarMessage for ABM_GETSTATE (not just evaluating ABS_AUTOHIDE) and
return that bitmask, and change setTaskbarAutoHide to accept the original
bitmask (uint32) and write it back using APPBARDATA.lParam and
procSHAppBarMessage with ABM_SETSTATE so you restore the exact flags you read
(preserving ABS_AUTOHIDE) instead of synthesizing ABS_AUTOHIDE/ABS_ALWAYSONTOP.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2bdbeb18-5457-4266-aff5-370e30014520
📒 Files selected for processing (7)
borderless.gogui_app.gogui_appsetting.gogui_defaults.gomain.gosettings.gowinapi.go
| window.SetCloseIntercept(func() { | ||
| if !settings.CloseToTray { | ||
| restoreTaskbarOnExit() | ||
| fyneApp.Quit() |
There was a problem hiding this comment.
Restore the taskbar from every shutdown path, not just window-close.
Line 314 only covers the close intercept. restartApp() in this file still exits via os.Exit(0) without calling restoreTaskbarOnExit(), so update/restart can leave the taskbar stuck in the app-forced state. Please route quit/restart through one shared shutdown helper.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@gui_app.go` around lines 312 - 315, The restoreTaskbarOnExit() call is only
invoked in the window close intercept, leaving other shutdown paths (notably
restartApp() which currently calls os.Exit(0)) without taskbar restore; refactor
by creating a single shutdown helper (e.g., shutdownAndExit or performShutdown)
that invokes restoreTaskbarOnExit(), performs any necessary cleanup and then
quits/exits, update window.SetCloseIntercept to call this helper instead of
calling fyneApp.Quit() directly, and change restartApp() to route through the
same helper rather than calling os.Exit(0) so all exits restore the taskbar.
findWindowByClass now returns an error on lookup failure instead of silently returning zero. getTaskbarAutoHide returns the full ABM_GETSTATE bitmask (uint32) so we no longer lose ABS_ALWAYSONTOP when saving/restoring. setTaskbarAutoHide accepts that bitmask and writes it back via ABM_SETSTATE, restoring the exact original flags. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Changes in this updateProblem
What changed
🤖 Generated with Claude Code |
Remove foreground window check so the taskbar stays hidden as long as the configured app's window exists, rather than restoring immediately when focus moves to another window. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update: Keep taskbar hidden while app is runningChanged the taskbar hide behavior so it persists as long as the configured app's window exists, rather than only while the app is the foreground window. What changed
This applies to both auto-apply apps and non-auto-apply apps that are already borderless. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
main.go (1)
193-203: Consider early exit onceshouldHideTaskbaris true.Once a single app requires the taskbar hidden, continuing to iterate serves no purpose. This is a minor optimization.
♻️ Suggested optimization
// Also check non-auto-apply apps that are already borderless and running + outer: for _, appSetting := range settings.Apps { if !appSetting.HideTaskbar { continue } for _, win := range windowData { if matchWindow(win, appSetting) && isBorderless(win) { shouldHideTaskbar = true + break outer } } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@main.go` around lines 193 - 203, Loop continues after shouldHideTaskbar becomes true; change the nested loops that iterate settings.Apps and windowData (the block referencing settings.Apps, appSetting, windowData, matchWindow and isBorderless) to break out early once shouldHideTaskbar is set to true—e.g., after setting shouldHideTaskbar = true, break the inner loop and then break the outer loop (or return immediately if inside a function) to avoid unnecessary iterations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@main.go`:
- Around line 193-203: Loop continues after shouldHideTaskbar becomes true;
change the nested loops that iterate settings.Apps and windowData (the block
referencing settings.Apps, appSetting, windowData, matchWindow and isBorderless)
to break out early once shouldHideTaskbar is set to true—e.g., after setting
shouldHideTaskbar = true, break the inner loop and then break the outer loop (or
return immediately if inside a function) to avoid unnecessary iterations.
The unbuffered chWindowList channel caused the scan loop to stall when the GUI wasn't actively consuming updates (e.g. minimized to tray), which prevented taskbar hiding and borderless logic from running. Use a buffered channel with non-blocking send to ensure the scan loop always completes regardless of GUI state. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix: Scan loop blocking when minimized to trayProblemWhen GoBorderless was minimized to the system tray, the scan loop would stall at the This caused the taskbar to not hide until the user opened GoBorderless from the tray (which resumed the GUI's channel reads and unblocked the loop). Fix
This ensures borderless application and taskbar hiding always run on every scan iteration, regardless of whether the GUI is active. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
main.go (1)
197-207: Narrow this pass to non-auto-apply apps to match intent.The comment says “non-auto-apply apps,” but auto-apply entries are also scanned here. Restricting scope and short-circuiting once true will reduce redundant work.
Small cleanup to align code with comment and reduce extra scans
- // Also check non-auto-apply apps that are already borderless and running + // Also check non-auto-apply apps that are already borderless and running for _, appSetting := range settings.Apps { - if !appSetting.HideTaskbar { + if appSetting.AutoApply || !appSetting.HideTaskbar { continue } for _, win := range windowData { if matchWindow(win, appSetting) && isBorderless(win) { shouldHideTaskbar = true + break } } + if shouldHideTaskbar { + break + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@main.go` around lines 197 - 207, The loop over settings.Apps is scanning auto-apply entries even though the intent is "non-auto-apply apps" and it also continues scanning after shouldHideTaskbar becomes true; update the loop to skip auto-apply apps (check appSetting.AutoApply and continue) and short-circuit out when shouldHideTaskbar is set (break the inner/outer loops or return as appropriate). Locate the block using settings.Apps, appSetting.HideTaskbar, matchWindow and isBorderless and add the AutoApply check and an early exit once shouldHideTaskbar is true to avoid redundant work.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@main.go`:
- Around line 164-168: The current non-blocking send to chWindowList drops the
new windowData when the channel is full; instead implement a non-blocking
overwrite: try to send windowData to chWindowList, and if that fails (channel
full) perform a receive from chWindowList to discard the stale entry and then
send windowData so the newest snapshot replaces the old one. Locate the send
using chWindowList and windowData and update the select logic to remove one
stale item on failure before delivering the new snapshot.
---
Nitpick comments:
In `@main.go`:
- Around line 197-207: The loop over settings.Apps is scanning auto-apply
entries even though the intent is "non-auto-apply apps" and it also continues
scanning after shouldHideTaskbar becomes true; update the loop to skip
auto-apply apps (check appSetting.AutoApply and continue) and short-circuit out
when shouldHideTaskbar is set (break the inner/outer loops or return as
appropriate). Locate the block using settings.Apps, appSetting.HideTaskbar,
matchWindow and isBorderless and add the AutoApply check and an early exit once
shouldHideTaskbar is true to avoid redundant work.
| // Non-blocking send: drop stale data if GUI isn't consuming | ||
| select { | ||
| case chWindowList <- windowData: | ||
| default: | ||
| } |
There was a problem hiding this comment.
Newest window snapshot is dropped when channel is full.
Line 164 says stale data should be dropped, but this select drops the new windowData and keeps stale data in the channel.
Suggested non-blocking overwrite of stale entry
- // Non-blocking send: drop stale data if GUI isn't consuming
- select {
- case chWindowList <- windowData:
- default:
- }
+ // Non-blocking send: keep latest snapshot, drop stale buffered one if needed.
+ select {
+ case chWindowList <- windowData:
+ default:
+ select {
+ case <-chWindowList:
+ default:
+ }
+ select {
+ case chWindowList <- windowData:
+ default:
+ }
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@main.go` around lines 164 - 168, The current non-blocking send to
chWindowList drops the new windowData when the channel is full; instead
implement a non-blocking overwrite: try to send windowData to chWindowList, and
if that fails (channel full) perform a receive from chWindowList to discard the
stale entry and then send windowData so the newest snapshot replaces the old
one. Locate the send using chWindowList and windowData and update the select
logic to remove one stale item on failure before delivering the new snapshot.
ABM_SETSTATE with ABS_AUTOHIDE only tells Windows to enable auto-hide mode but doesn't force the taskbar to actually retract. Follow up with ABM_ACTIVATE to make the taskbar re-evaluate its state and hide immediately, rather than waiting for a mouse or focus event. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix: Taskbar not hiding until GoBorderless is opened from trayProblemWhen GoBorderless was minimized to the tray and a game launched, the app would correctly go borderless but the taskbar would remain visible. It only hid once the user opened GoBorderless from the system tray. Root cause
FixAdded an |
Summary
Implementation Details
SHAppBarMessageWindows API to programmatically control taskbar auto-hide stateGetForegroundWindowin the existing scan loopABM_GETSTATEreturn value) and restores it exactly viaABM_SETSTATE, so flags likeABS_ALWAYSONTOPare not lostfindWindowByClassreturns(uintptr, error)— treats a zero hwnd as an error instead of silently continuinggetTaskbarAutoHidereturns(uint32, error)— propagates lookup errors and returns the raw bitmask rather than flattening to a boolsetTaskbarAutoHideacceptsuint32and writes the bitmask directly toAPPBARDATA.lParam, avoiding the previous synthesizedABS_AUTOHIDE/ABS_ALWAYSONTOPtoggleTest plan
Shell_TrayWndis not found🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes