feat: Add BrowserWindow option to hide window in Mission Control (macOS) (#36092)

* feat: Add BrowserWindow option to ignore Mission Control (macOS)
* There are many circumstances when app developers may want to hide their
windows from mission control. E.g., full screen overlays, small helper
windows, dialogs, etc.
* This PR adds the functionality, docs, and tests.

* chore:Rename variables

* Update shell/browser/native_window_mac.h

Co-authored-by: Samuel Maddock <samuel.maddock@gmail.com>

Co-authored-by: Samuel Maddock <samuel.maddock@gmail.com>
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
This commit is contained in:
Brad Carter 2022-11-01 16:43:42 -04:00 committed by GitHub
parent 8b430c9d26
commit 15540975ff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 77 additions and 1 deletions

View file

@ -192,6 +192,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
macOS. Default is `false`. macOS. Default is `false`.
* `skipTaskbar` boolean (optional) _macOS_ _Windows_ - Whether to show the window in taskbar. * `skipTaskbar` boolean (optional) _macOS_ _Windows_ - Whether to show the window in taskbar.
Default is `false`. Default is `false`.
* `hiddenInMissionControl` boolean (optional) _macOS_ - Whether window should be hidden when the user toggles into mission control.
* `kiosk` boolean (optional) - Whether the window is in kiosk mode. Default is `false`. * `kiosk` boolean (optional) - Whether the window is in kiosk mode. Default is `false`.
* `title` string (optional) - Default window title. Default is `"Electron"`. If the HTML tag `<title>` is defined in the HTML file loaded by `loadURL()`, this property will be ignored. * `title` string (optional) - Default window title. Default is `"Electron"`. If the HTML tag `<title>` is defined in the HTML file loaded by `loadURL()`, this property will be ignored.
* `icon` ([NativeImage](native-image.md) | string) (optional) - The window icon. On Windows it is * `icon` ([NativeImage](native-image.md) | string) (optional) - The window icon. On Windows it is
@ -1255,6 +1256,16 @@ Returns `boolean` - Whether the window can be manually closed by user.
On Linux always returns `true`. On Linux always returns `true`.
#### `win.setHiddenInMissionControl(hidden)` _macOS_
* `hidden` boolean
Sets whether the window will be hidden when the user toggles into mission control.
#### `win.isHiddenInMissionControl()` _macOS_
Returns `boolean` - Whether the window will be hidden when the user toggles into mission control.
#### `win.setAlwaysOnTop(flag[, level][, relativeLevel])` #### `win.setAlwaysOnTop(flag[, level][, relativeLevel])`
* `flag` boolean * `flag` boolean

View file

@ -881,6 +881,16 @@ gfx::Point BaseWindow::GetTrafficLightPosition() const {
} }
#endif #endif
#if BUILDFLAG(IS_MAC)
bool BaseWindow::IsHiddenInMissionControl() {
return window_->IsHiddenInMissionControl();
}
void BaseWindow::SetHiddenInMissionControl(bool hidden) {
window_->SetHiddenInMissionControl(hidden);
}
#endif
void BaseWindow::SetTouchBar( void BaseWindow::SetTouchBar(
std::vector<gin_helper::PersistentDictionary> items) { std::vector<gin_helper::PersistentDictionary> items) {
window_->SetTouchBar(std::move(items)); window_->SetTouchBar(std::move(items));
@ -1256,6 +1266,14 @@ void BaseWindow::BuildPrototype(v8::Isolate* isolate,
.SetMethod("getTrafficLightPosition", .SetMethod("getTrafficLightPosition",
&BaseWindow::GetTrafficLightPosition) &BaseWindow::GetTrafficLightPosition)
#endif #endif
#if BUILDFLAG(IS_MAC)
.SetMethod("isHiddenInMissionControl",
&BaseWindow::IsHiddenInMissionControl)
.SetMethod("setHiddenInMissionControl",
&BaseWindow::SetHiddenInMissionControl)
#endif
.SetMethod("_setTouchBarItems", &BaseWindow::SetTouchBar) .SetMethod("_setTouchBarItems", &BaseWindow::SetTouchBar)
.SetMethod("_refreshTouchBarItem", &BaseWindow::RefreshTouchBarItem) .SetMethod("_refreshTouchBarItem", &BaseWindow::RefreshTouchBarItem)
.SetMethod("_setEscapeTouchBarItem", &BaseWindow::SetEscapeTouchBarItem) .SetMethod("_setEscapeTouchBarItem", &BaseWindow::SetEscapeTouchBarItem)

View file

@ -198,6 +198,11 @@ class BaseWindow : public gin_helper::TrackableObject<BaseWindow>,
gfx::Point GetTrafficLightPosition() const; gfx::Point GetTrafficLightPosition() const;
#endif #endif
#if BUILDFLAG(IS_MAC)
bool IsHiddenInMissionControl();
void SetHiddenInMissionControl(bool hidden);
#endif
void SetTouchBar(std::vector<gin_helper::PersistentDictionary> items); void SetTouchBar(std::vector<gin_helper::PersistentDictionary> items);
void RefreshTouchBarItem(const std::string& item_id); void RefreshTouchBarItem(const std::string& item_id);
void SetEscapeTouchBarItem(gin_helper::PersistentDictionary item); void SetEscapeTouchBarItem(gin_helper::PersistentDictionary item);

View file

@ -227,6 +227,12 @@ class NativeWindow : public base::SupportsUserData,
virtual void UpdateFrame() = 0; virtual void UpdateFrame() = 0;
#endif #endif
// whether windows should be ignored by mission control
#if BUILDFLAG(IS_MAC)
virtual bool IsHiddenInMissionControl() = 0;
virtual void SetHiddenInMissionControl(bool hidden) = 0;
#endif
// Touchbar API // Touchbar API
virtual void SetTouchBar(std::vector<gin_helper::PersistentDictionary> items); virtual void SetTouchBar(std::vector<gin_helper::PersistentDictionary> items);
virtual void RefreshTouchBarItem(const std::string& item_id); virtual void RefreshTouchBarItem(const std::string& item_id);

View file

@ -103,6 +103,8 @@ class NativeWindowMac : public NativeWindow,
void SetDocumentEdited(bool edited) override; void SetDocumentEdited(bool edited) override;
bool IsDocumentEdited() override; bool IsDocumentEdited() override;
void SetIgnoreMouseEvents(bool ignore, bool forward) override; void SetIgnoreMouseEvents(bool ignore, bool forward) override;
bool IsHiddenInMissionControl() override;
void SetHiddenInMissionControl(bool hidden) override;
void SetContentProtection(bool enable) override; void SetContentProtection(bool enable) override;
void SetFocusable(bool focusable) override; void SetFocusable(bool focusable) override;
bool IsFocusable() override; bool IsFocusable() override;

View file

@ -209,6 +209,9 @@ NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options,
std::string windowType; std::string windowType;
options.Get(options::kType, &windowType); options.Get(options::kType, &windowType);
bool hiddenInMissionControl = false;
options.Get(options::kHiddenInMissionControl, &hiddenInMissionControl);
bool useStandardWindow = true; bool useStandardWindow = true;
// eventually deprecate separate "standardWindow" option in favor of // eventually deprecate separate "standardWindow" option in favor of
// standard / textured window types // standard / textured window types
@ -347,6 +350,8 @@ NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options,
options.Get(options::kDisableAutoHideCursor, &disableAutoHideCursor); options.Get(options::kDisableAutoHideCursor, &disableAutoHideCursor);
[window_ setDisableAutoHideCursor:disableAutoHideCursor]; [window_ setDisableAutoHideCursor:disableAutoHideCursor];
SetHiddenInMissionControl(hiddenInMissionControl);
// Set maximizable state last to ensure zoom button does not get reset // Set maximizable state last to ensure zoom button does not get reset
// by calls to other APIs. // by calls to other APIs.
SetMaximizable(maximizable); SetMaximizable(maximizable);
@ -1085,9 +1090,17 @@ bool NativeWindowMac::IsDocumentEdited() {
return [window_ isDocumentEdited]; return [window_ isDocumentEdited];
} }
bool NativeWindowMac::IsHiddenInMissionControl() {
NSUInteger collectionBehavior = [window_ collectionBehavior];
return collectionBehavior & NSWindowCollectionBehaviorTransient;
}
void NativeWindowMac::SetHiddenInMissionControl(bool hidden) {
SetCollectionBehavior(hidden, NSWindowCollectionBehaviorTransient);
}
void NativeWindowMac::SetIgnoreMouseEvents(bool ignore, bool forward) { void NativeWindowMac::SetIgnoreMouseEvents(bool ignore, bool forward) {
[window_ setIgnoresMouseEvents:ignore]; [window_ setIgnoresMouseEvents:ignore];
if (!ignore) { if (!ignore) {
SetForwardMouseMessages(NO); SetForwardMouseMessages(NO);
} else { } else {

View file

@ -39,6 +39,8 @@ const char kOverlaySymbolColor[] = "symbolColor";
// The custom height for Window Controls Overlay. // The custom height for Window Controls Overlay.
const char kOverlayHeight[] = "height"; const char kOverlayHeight[] = "height";
// whether to keep the window out of mission control
const char kHiddenInMissionControl[] = "hiddenInMissionControl";
// Whether the window should show in taskbar. // Whether the window should show in taskbar.
const char kSkipTaskbar[] = "skipTaskbar"; const char kSkipTaskbar[] = "skipTaskbar";

View file

@ -30,6 +30,7 @@ extern const char kMinimizable[];
extern const char kMaximizable[]; extern const char kMaximizable[];
extern const char kFullScreenable[]; extern const char kFullScreenable[];
extern const char kClosable[]; extern const char kClosable[];
extern const char kHiddenInMissionControl[];
extern const char kFullscreen[]; extern const char kFullscreen[];
extern const char kSkipTaskbar[]; extern const char kSkipTaskbar[];
extern const char kKiosk[]; extern const char kKiosk[];

View file

@ -4784,6 +4784,24 @@ describe('BrowserWindow module', () => {
}); });
}); });
ifdescribe(process.platform === 'darwin')('isHiddenInMissionControl state', () => {
it('with functions', () => {
it('can be set with ignoreMissionControl constructor option', () => {
const w = new BrowserWindow({ show: false, hiddenInMissionControl: true });
expect(w.isHiddenInMissionControl()).to.be.true('isHiddenInMissionControl');
});
it('can be changed', () => {
const w = new BrowserWindow({ show: false });
expect(w.isHiddenInMissionControl()).to.be.false('isHiddenInMissionControl');
w.setHiddenInMissionControl(true);
expect(w.isHiddenInMissionControl()).to.be.true('isHiddenInMissionControl');
w.setHiddenInMissionControl(false);
expect(w.isHiddenInMissionControl()).to.be.false('isHiddenInMissionControl');
});
});
});
// fullscreen events are dispatched eagerly and twiddling things too fast can confuse poor Electron // fullscreen events are dispatched eagerly and twiddling things too fast can confuse poor Electron
ifdescribe(process.platform === 'darwin')('kiosk state', () => { ifdescribe(process.platform === 'darwin')('kiosk state', () => {