Skip to content

Add explicit application activation on macOS - #2964

Open
Descolada wants to merge 2 commits into
picoe:developfrom
keysharp-org:fix/mac-window-focus
Open

Descolada wants to merge 2 commits into
picoe:developfrom
keysharp-org:fix/mac-window-focus

Conversation

@Descolada

@Descolada Descolada commented Jun 14, 2026 •

Copy link
Copy Markdown
Contributor

On macOS, focusing a window can leave its application in the background. Add an explicit Application.Activate() request so callers can activate the application before focusing their chosen window:

Application.Instance.Activate();
window.Focus();

The native macOS backend implements an optional handler capability, preserving existing handler compatibility; other backends do nothing. Activation remains subject to the OS, with IsActive and IsActiveChanged reporting the observed state. The implementation includes a version-appropriate fallback and preserves the existing automatic-activation policy.

Validation: core (netstandard2.0) and MonoMac (net10.0) builds pass with zero warnings/errors. Native macOS runtime verification remains outstanding.

@cwensley

cwensley commented Jul 3, 2026

Copy link
Copy Markdown
Member

Hey @Descolada, so I've thought about this one, and I think I'd like to see an Application.Instance.Activate() API instead of making macOS try to activate every time you focus a window. Thoughts?

@Descolada

Copy link
Copy Markdown
Contributor Author

@cwensley interesting idea. As far as I understand, currently only macOS distinguishes active app (the overarching currently used program, claims the global menu bar at the top of the screen, owns the Dock icon indicator etc) vs focused window (where keyboard focus is). It's possible to activate an app without focusing a window, but not focusing a window without activating the app (and a window can't have keyboard focus without also being the active app). So, I think Application.Instance.Activate() would have a place to activate the app, but that would likely be a macOS-only feature (I'm not sure how that would work on other platforms - on Windows/Linux what would it activate?). Also, if Window.Focus() didn't automatically call it then the semantics of Window.Focus() would remain incorrect, because it sometimes won't bring the window to focus due to app-level activation status being wrong.

On further thought, it could perhaps be a property instead Application.Instance.Active:

  • on macOS the getter would return application-level focus status (supposing it's possible to query it, I haven't looked into it), the setter would set the app-level focus
  • on Windows/Linux it could answer the question whether the currently focused window belongs to our Application.Instance, but I don't think the setter would have an use

What do you think? If you think this is worth implementing then I can attempt it + change the current PR to do if (!Application.Instance.Active) Application.Instance.Active = true before attempting window focusing.

AkiSakurai added a commit to AkiSakurai/OpenTabletDriver that referenced this pull request Oct 10, 2026
Clicking "Show OpenTabletDriver" in the tray icon did not bring the
window to the front when the form was already shown.

Eto's Form.Show() only calls the handler's Show() the first time the
form is shown (when !Loaded); every later call just sets Visible = true,
so the existing FormHandler.Show() override with
ActivateIgnoringOtherApps() never ran on the re-show path.

Bringing the window forward in BringToFront() was not an option because
it cannot be overridden: Window.BringToFront() is a plain non-virtual
method that forwards to the handler, and the Mac handler's BringToFront()
is non-virtual as well (it only calls NSWindow.orderFront, which
reorders windows within our own app and does not activate the app, so
it would not foreground the window anyway). Visible, on the other hand,
is virtual on the handler and is the single choke point that every
re-show path goes through: the tray menu item, Form.Show() on an
already loaded form, and the hide-to-tray restore in MainForm.

Upstream, picoe/Eto#2964 ("Mac: Bring focused windows to the
foreground") is still open and the maintainer would prefer an
Application.Instance.Activate() API over activating on every window
focus, so it will likely take some time to land. This keeps the
workaround local until then.

References:
- https://github.com/picoe/Eto/blob/2.11.0/src/Eto/Forms/Window.cs
  "public void BringToFront() => Handler.BringToFront();" is not virtual,
  so no subclass or handler override can inject app activation there.
- https://github.com/picoe/Eto/blob/2.11.0/src/Eto/Forms/Form.cs
  Form.Show(): "var loaded = Loaded; if (!loaded) { ... Handler.Show(); }
  else { base.Visible = true; }" — re-showing an already loaded form
  bypasses the handler's Show() entirely and goes through Visible.
- https://github.com/picoe/Eto/blob/2.11.0/src/Eto.Mac/Forms/MacWindow.cs
  Mac handler BringToFront() only does "Control.OrderFront(Control)",
  which cannot activate a background application.
- picoe/Eto#2964
  Open upstream fix for foregrounding windows on Mac; still under
  discussion (maintainer prefers an Application.Instance.Activate()
  API), so it will probably take some time to land.

This PR includes AI generated code or other AI use. As part of this repo's rules, I am disclosing my use of AI in the creation of this PR.

Co-Authored-By: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Add Application.Activate() as an optional handler capability so callers can
request native application activation before focusing their chosen window.
Implement it in the native macOS backend, with a version-appropriate fallback
and the existing automatic-activation policy preserved. Other backends no-op.

Bring the PR branch up to date with develop and replace the original implicit
Window.Focus activation change with the reviewed explicit API.

Validation: core netstandard2.0 and MonoMac net10.0 builds pass without warnings.
@Descolada Descolada changed the title Mac: Bring focused windows to the foreground Add explicit application activation on macOS Oct 27, 2026
@Descolada

Copy link
Copy Markdown
Contributor Author

@cwensley I've switched to explicit activation, as suggested:

form.Show();
Application.Instance.Activate();
form.Focus();

My Application.Instance.Active thought was rejected because IsActive already exists, so Activate() is the only logical option.

Activate() requests native macOS application activation and does nothing on other backends. It returns void because activation may be delayed or declined. Existing IsActive/IsActiveChanged expose the observed state, and an optional handler interface preserves existing handler compatibility.

I also considered activating the frontmost application window on other platforms, or bringing all application windows forward. These approaches introduce policies around modal dialogs, tool windows, minimized windows, and Z-order, potentially surfacing windows the caller did not intend to activate. Frontmost application window activation would be restricted as well, because for example GTK in Wayland doesn't expose window z-order information. So I rejected that broader scope here: the caller chooses the desired window and explicitly calls Activate() followed by Focus().

This also supports OpenTabletDriver’s re-show scenario without coupling activation to every visibility change or Focus() call.

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.

2 participants