Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request]: Create panel windows (NSPanel) on macOS #31538

Closed
3 tasks done
mitchchn opened this issue Oct 21, 2021 · 6 comments
Closed
3 tasks done

[Feature Request]: Create panel windows (NSPanel) on macOS #31538

mitchchn opened this issue Oct 21, 2021 · 6 comments

Comments

@mitchchn
Copy link
Contributor

mitchchn commented Oct 21, 2021

Preflight Checklist

Problem Description

Panel windows on macOS (NSPanel) are used to create floating command palettes (like Spotlight), menu bar apps, and other ephemeral UIs.

Panels support unique interactions with the OS which are important to these kinds of user interfaces. Most importantly, they can be the key window without becoming the main window, which means they receive keyboard input without otherwise stealing focus. Panels can also float over full screen apps.

It is not possible to create panel windows in Electron or fully emulate these behaviours by other means.

Proposed Solution

An API to create panel windows on macOS, e.g.:

new BrowserWindow({type: "panel"})

That's pretty straightforward. The hard part is creating a renderer within an NSPanel instead of NSWindow. This would likely require a Chromium patch, as proposed by @proxi in this PoC: #26596 (comment). It's a lot of code, but most of it is duplicating functionality shared by the two classes.

Perhaps there is a cleaner way to delegate the shared methods, or a better method altogether. I'd love to know what the API working group thinks.

Alternatives Considered

Previously, libraries could create panel windows by swizzling the NSWindow instance at runtime. This is no longer possible in macOS Big Sur (#26596).

As of Electron 15 and Big Sur, the closest way to approximate a panel window is to create a frameless window that can appear over fullscreen apps on all workspaces:

const panel = new BrowserWindow({frame: false});
panel.setVisibleOnAllWorkspaces({visibleOnFullScreen: true});

But this window grabs focus from other windows, unlike a panel, and visibleOnFullScreen has the additional consequence of hiding the Dock icon (#26350) and creating window management issues when other application windows are open at the same time.

@oltreseba
Copy link

oltreseba commented Nov 1, 2021

Hello @mitchchn, at Akiflow we have built a package to convert nswindow to nspanel: https://github.com/akiflow/electron-panel-window
(we didn't build it from scratch, but we improved the previously existing https://github.com/goabstract/electron-panel-window).

We are currently using it with electron 13 (but it should work on electron 15 as well) and macOS Big Sur (I haven't yet tested it on Monterey yet).

If you have any issue with it feel free to report it.

@nbashkankov
Copy link

nbashkankov commented Nov 22, 2021

@oltreseba electron-panel-window crashed with
Crashing on exception: -[PROPanel saveRestorableState]: unrecognized selector sent to instance
Env: Electron 11, arm64 build, Apple Silicon, Monterey

@mitchchn
Copy link
Contributor Author

mitchchn commented Dec 4, 2021

@oltreseba Unfortunately, your solution for Big Sur is still limited by what is possible with NSWindow. It can't appear over a fullscreen window without hiding the Dock, and it can't appear without activating its own app and showing other windows.

I do not see a solution other than patching Chromium on the Electron side. I don't think such a patch would be accepted upstream, since Chromium has no need to create panel windows.

@oltreseba
Copy link

To us is actually working 100%, you can check our app, and we don't have any of these limitations.

@mitchchn
Copy link
Contributor Author

mitchchn commented Dec 12, 2021

@oltreseba I did check it out! Your solution is pretty close but it's not all the way there. For example, the Akiflow panel still has an invisible titlebar area at the top, and clicking it activates the app.

I've been doing some explorations here with different techniques:
https://github.com/mitchchn/FunWithPanels.

The best combination I've found so far is:

  1. Override the class styleMask property to add .nonActivatingPanel.
  2. Use the private API NSWindow._setPreventsActivation to block the titlebar view from activating.

An alternative to 2) is to remove the titlebar area, but it's not as straightforward or safe.

But after all that experimentation I still believe the best solution is for Electron to create a real NSPanel. Otherwise we are just working with undefined behaviour.

We are tantalizingly close; changing one line of code in Chromium produces flawless panel windows. From:

@interface NativeWidgetMacNSWindow : NSWindow <CommandDispatchingWindow>

to

@interface NativeWidgetMacNSWindow : NSPanel <CommandDispatchingWindow>

But of course this turns every window into panel. If there is a simple way to duplicate the class definition (at runtime?) without creating a giant patch, this would be the way to go.

@clavin
Copy link
Member

clavin commented Feb 26, 2024

I'm closing this as it was implemented in #34388 👍

@clavin clavin closed this as completed Feb 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants