feat: per-screen app toggling for multi-monitor setups#378
Conversation
Add per-screen app management to allow up to one WTQ app open per screen simultaneously. When toggling an app on one screen, apps on other screens remain open instead of being automatically closed. Key changes: - WtqApp: Add CurrentScreenRect property to track which screen an app is displayed on (set on toggle-on, cleared on toggle-off) - IWtqAppRepo/WtqAppRepo: Add GetAllOpen() and GetOpenOnScreen(Rectangle) methods for per-screen app queries - WtqService: Refactor OnAppToggledEventAsync to use screen-aware logic. In per-screen mode (default), toggling an app only closes other apps on the same screen. Apps on different monitors stay open. - FeatureFlags: Add PerScreenApps flag (default: true) to allow opting back into the legacy single-app-global behavior - Unit tests for screen intersection logic and feature flag defaults The legacy behavior (always close the previous app regardless of screen) is preserved when PerScreenApps is set to false in the feature flags. Co-Authored-By: Craft Agent <agents-noreply@craft.do>
Add a checkbox for the PerScreenApps feature flag in the Behavior section of Global Settings, so users can toggle the per-screen app behavior from the WTQ GUI without editing wtq.jsonc manually. Also adds [Display] and [DefaultValue] attributes to both feature flag properties (PerScreenApps and SharpHook) for proper label rendering in the settings UI. Co-Authored-By: Craft Agent <agents-noreply@craft.do>
When the user's wtq.jsonc doesn't contain a FeatureFlags section, the FeatureFlags property on WtqOptions is null. The PerScreenApps checkbox was using the null-forgiving operator (!), which crashes Blazor's render circuit at runtime and breaks all page navigation. Fix by initializing FeatureFlags to a default instance in Reload() if it's null. Co-Authored-By: Craft Agent <agents-noreply@craft.do>
Move the PerScreenApps checkbox from the Behavior fieldset to the Monitor fieldset where it logically belongs alongside Prefer monitor and Monitor index. Fix awkward line breaks in the PerScreenApps XML doc comment that split sentences mid-phrase in the rendered UI description. Co-Authored-By: Craft Agent <agents-noreply@craft.do>
There was a problem hiding this comment.
Pull request overview
Adds per-monitor (“per-screen”) app toggling so opening an app only closes other WTQ apps on the same screen, improving multi-monitor workflows while keeping a legacy global-single-app fallback via a feature flag.
Changes:
- Introduces
FeatureFlags.PerScreenApps(defaulttrue) and exposes it in Global Settings UI. - Tracks per-app screen placement via
WtqApp.CurrentScreenRectand adds repo querying for open apps by screen (GetOpenOnScreen,GetAllOpen). - Refactors
WtqService.OnAppToggledEventAsyncto close apps per-screen when the flag is enabled, and adds unit tests around the rectangle intersection assumptions.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/20-Services/Wtq.Services.UI/Pages/GlobalSettings/_Index.razor |
Initializes FeatureFlags if missing and adds a UI toggle for per-screen apps. |
src/10-Core/Wtq/WtqService.cs |
Implements per-screen toggle behavior behind PerScreenApps flag and adds new dependencies. |
src/10-Core/Wtq/WtqApp.cs |
Tracks the screen rectangle where the app is currently open via CurrentScreenRect. |
src/10-Core/Wtq/Services/WtqAppRepo.cs |
Adds methods to query all open apps and the open app on a given screen. |
src/10-Core/Wtq/Services/IWtqAppRepo.cs |
Extends repo interface with GetAllOpen() and GetOpenOnScreen(Rectangle). |
src/10-Core/Wtq/Configuration/WtqSharedOptions.cs |
Minor docstring formatting change. |
src/10-Core/Wtq/Configuration/FeatureFlags.cs |
Adds PerScreenApps feature flag (default enabled) and display metadata. |
src/10-Core/Wtq.UnitTest/Services/WtqAppRepoPerScreenTest.cs |
Adds tests asserting Rectangle.IntersectsWith semantics and default flag value. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
1. Use IWtqTargetScreenRectProvider instead of cursor position for per-screen toggle resolution. This respects the app's PreferMonitor setting (Primary, AtIndex, WithCursor) so the 'same screen' determination matches where the app will actually open. 2. Change GetOpenOnScreen to return IEnumerable<WtqApp> instead of WtqApp? and close all matching apps on the same screen. This handles edge cases where multiple apps have stale or missing CurrentScreenRect data. 3. Improve tests: add predicate-level tests for multi-app per-screen behavior and empty-result handling. Clarify that tests validate the intersection assumptions the repo method relies on.
|
@ninjaeon Thank you for sending the PR! I do agree with the feature in general (giving each monitor their own independent "scope"), and I feel that there's also a connection to the other PR, about having different app settings for different monitors. Though, I'd like to think a bit about the implementation, as it's too duct-taped on imho, and I think we can do better, making the concept a proper first class citizen. I'll get back to this soon. |
|
Some initial work that touches this feature has been done here, where multiple apps can be active at the same time: The next step is to have an additional setting value for "Exclusive", such as "WithinMonitor" or something. |
Summary
Allow up to one WTQ app open per screen simultaneously. When activating a WTQ app on a screen, only close apps on that same screen — apps on different screens remain open.
Previously, toggling any app would close whatever app was currently open globally, regardless of which screen it was on. This made multi-monitor workflows frustrating: toggling a terminal on your secondary monitor would dismiss the terminal on your primary monitor.
Changes
Core Logic (
WtqService.cs)OnAppToggledEventAsyncnow checks whetherPerScreenAppsis enabledGetOpenOnScreen(screenRect)instead ofGetOpen(), so only apps on the same screen are closed before opening the new oneScreen Tracking
WtqApp.CurrentScreenRect— set to the screen rectangle when the app opens, cleared when it closesWtqAppToggledEvent.ScreenRect— carried from the hotkey handler (gets screen with cursor) through to the service layerRepository
IWtqAppRepo.GetAllOpen()/GetOpenOnScreen(Rectangle)— new methods on the repo interface and implementationRectangle.IntersectsWithto compare screen rectsFeature Flag
FeatureFlags.PerScreenApps— defaults totrue(new behavior is opt-out)Tests
WtqAppRepoPerScreenTest— unit tests for per-screen open/close behaviorFiles Changed (8 files)
FeatureFlags.csPerScreenAppsflag (defaulttrue)WtqApp.csCurrentScreenRectpropertyIWtqAppRepo.csGetAllOpen()andGetOpenOnScreen()WtqAppRepo.csWtqService.csOnAppToggledEventAsyncfor per-screen logicWtqAppToggledEvent.csScreenRectpropertyWtqHotkeyService.cs_Index.razor(Global)Testing