Skip to content

Commit

Permalink
feat: add more functions for NSApp activation state
Browse files Browse the repository at this point in the history
  • Loading branch information
nornagon committed Aug 4, 2022
1 parent bba22ae commit 3f54912
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 4 deletions.
49 changes: 46 additions & 3 deletions docs/api/app.md
Expand Up @@ -153,9 +153,23 @@ Returns:

* `event` Event

Emitted when mac application become active. Difference from `activate` event is
that `did-become-active` is emitted every time the app becomes active, not only
when Dock icon is clicked or application is re-launched.
Emitted when the application becomes active (i.e., the macOS title bar is
showing the name of the app). The difference between this and the `activate`
event is that `did-become-active` is emitted every time the app becomes active,
whereas `activate` is only emitted when the Dock icon is clicked or the
application is re-launched.

See [`-[NSApplicationDelegate applicationDidBecomeActive:]`](https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428577-applicationdidbecomeactive?language=objc)

### Event: 'did-resign-active' _macOS_

Returns:

* `event` Event

Emitted when the application loses active state.

See [`-[NSApplicationDelegate applicationDidResignActive:]`](https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428636-applicationdidresignactive?language=objc)

### Event: 'continue-activity' _macOS_

Expand Down Expand Up @@ -1426,6 +1440,28 @@ details.

**Note:** Enable `Secure Keyboard Entry` only when it is needed and disable it when it is no longer needed.

### `app.isActive()` _macOS_

Returns `boolean` - `true` if the app is currently active (i.e., the macOS
title bar is showing the name of the app).

See [`-[NSApplication active]`](https://developer.apple.com/documentation/appkit/nsapplication/1428493-active?language=objc).

### `app.activate([opts])` _macOS_

* `opts` Object (optional)
* `ignoreOtherApps` boolean - If false, the app is activated only if no other
app is currently active. If true, the app activates regardless. Defaults to
false.

See [`-[NSApplication activateIgnoringOtherApps:]`](https://developer.apple.com/documentation/appkit/nsapplication/1428468-activateignoringotherapps?language=objc).

### `app.deactivate()` _macOS_

Deactivates the app.

See [`-[NSApplication deactivate]`](https://developer.apple.com/documentation/appkit/nsapplication/1428428-deactivate?language=objc).

## Properties

### `app.accessibilitySupportEnabled` _macOS_ _Windows_
Expand All @@ -1438,6 +1474,13 @@ This API must be called after the `ready` event is emitted.

**Note:** Rendering accessibility tree can significantly affect the performance of your app. It should not be enabled by default.

### `app.active` _macOS_ _Readonly_

A `boolean` property, `true` if the app is currently active (i.e., the macOS
title bar is showing the name of the app).

See [`-[NSApplication active]`](https://developer.apple.com/documentation/appkit/nsapplication/1428493-active?language=objc).

### `app.applicationMenu`

A `Menu | null` property that returns [`Menu`](menu.md) if one has been set and `null` otherwise.
Expand Down
8 changes: 8 additions & 0 deletions shell/browser/api/electron_api_app.cc
Expand Up @@ -782,6 +782,10 @@ void App::OnNewWindowForTab() {
void App::OnDidBecomeActive() {
Emit("did-become-active");
}

void App::OnDidResignActive() {
Emit("did-resign-active");
}
#endif

bool App::CanCreateWindow(
Expand Down Expand Up @@ -1742,6 +1746,9 @@ gin::ObjectTemplateBuilder App::GetObjectTemplateBuilder(v8::Isolate* isolate) {
.SetMethod("moveToApplicationsFolder", &App::MoveToApplicationsFolder)
.SetMethod("isInApplicationsFolder", &App::IsInApplicationsFolder)
.SetMethod("setActivationPolicy", &App::SetActivationPolicy)
.SetMethod("isActive", &App::IsActive)
.SetMethod("activate", &App::Activate)
.SetMethod("deactivate", &App::Deactivate)
#endif
.SetMethod("setAboutPanelOptions",
base::BindRepeating(&Browser::SetAboutPanelOptions, browser))
Expand Down Expand Up @@ -1805,6 +1812,7 @@ gin::ObjectTemplateBuilder App::GetObjectTemplateBuilder(v8::Isolate* isolate) {
.SetProperty("dock", &App::GetDockAPI)
.SetProperty("runningUnderRosettaTranslation",
&App::IsRunningUnderRosettaTranslation)
.SetProperty("active", &App::IsActive)
#endif
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
.SetProperty("runningUnderARM64Translation",
Expand Down
4 changes: 4 additions & 0 deletions shell/browser/api/electron_api_app.h
Expand Up @@ -116,6 +116,7 @@ class App : public ElectronBrowserClient::Delegate,
base::Value::Dict user_info) override;
void OnNewWindowForTab() override;
void OnDidBecomeActive() override;
void OnDidResignActive() override;
#endif

// content::ContentBrowserClient:
Expand Down Expand Up @@ -227,6 +228,9 @@ class App : public ElectronBrowserClient::Delegate,
bool IsInApplicationsFolder();
v8::Local<v8::Value> GetDockAPI(v8::Isolate* isolate);
bool IsRunningUnderRosettaTranslation() const;
bool IsActive() const;
void Activate(absl::optional<gin_helper::Dictionary> opts) const;
void Deactivate() const;
v8::Global<v8::Value> dock_;
#endif

Expand Down
15 changes: 15 additions & 0 deletions shell/browser/api/electron_api_app_mac.mm
Expand Up @@ -80,4 +80,19 @@
return proc_translated == 1;
}

bool App::IsActive() const {
return NSApp.active;
}

void App::Activate(absl::optional<gin_helper::Dictionary> opts) const {
bool ignore_other_apps = false;
if (opts)
opts->Get("ignoreOtherApps", &ignore_other_apps);
[NSApp activateIgnoringOtherApps:ignore_other_apps];
}

void App::Deactivate() const {
[NSApp deactivate];
}

} // namespace electron::api
5 changes: 5 additions & 0 deletions shell/browser/browser.cc
Expand Up @@ -285,6 +285,11 @@ void Browser::DidBecomeActive() {
for (BrowserObserver& observer : observers_)
observer.OnDidBecomeActive();
}

void Browser::DidResignActive() {
for (BrowserObserver& observer : observers_)
observer.OnDidResignActive();
}
#endif

} // namespace electron
2 changes: 2 additions & 0 deletions shell/browser/browser.h
Expand Up @@ -278,6 +278,8 @@ class Browser : public WindowListObserver {

// Tell the application that application did become active
void DidBecomeActive();

void DidResignActive();
#endif // BUILDFLAG(IS_MAC)

// Tell the application that application is activated with visible/invisible
Expand Down
2 changes: 2 additions & 0 deletions shell/browser/browser_observer.h
Expand Up @@ -84,6 +84,8 @@ class BrowserObserver : public base::CheckedObserver {

// Browser did become active.
virtual void OnDidBecomeActive() {}

virtual void OnDidResignActive() {}
#endif

protected:
Expand Down
4 changes: 4 additions & 0 deletions shell/browser/mac/electron_application_delegate.mm
Expand Up @@ -92,6 +92,10 @@ - (void)applicationDidBecomeActive:(NSNotification*)notification {
electron::Browser::Get()->DidBecomeActive();
}

- (void)applicationDidResignActive:(NSNotification*)notification {
electron::Browser::Get()->DidResignActive();
}

- (NSMenu*)applicationDockMenu:(NSApplication*)sender {
if (menu_controller_)
return [menu_controller_ menu];
Expand Down
49 changes: 48 additions & 1 deletion spec-main/api-app-spec.ts
Expand Up @@ -9,7 +9,7 @@ import { promisify } from 'util';
import { app, BrowserWindow, Menu, session, net as electronNet } from 'electron/main';
import { emittedOnce } from './events-helpers';
import { closeWindow, closeAllWindows } from './window-helpers';
import { ifdescribe, ifit, waitUntil } from './spec-helpers';
import { defer, ifdescribe, ifit, waitUntil } from './spec-helpers';
import split = require('split')

const fixturesPath = path.resolve(__dirname, '../spec/fixtures');
Expand Down Expand Up @@ -1837,6 +1837,53 @@ describe('app module', () => {
})).to.eventually.be.rejectedWith(/ERR_NAME_NOT_RESOLVED/);
});
});

ifdescribe(process.platform === 'darwin')('active state', () => {
afterEach(closeAllWindows);
it('is inactive after deactivating', async () => {
const b = new BrowserWindow();
// It's not clear what state the app will be in prior to the test, so get
// to a known state first.
if (!app.active) {
app.activate({ ignoreOtherApps: true });
await emittedOnce(app, 'did-become-active');
expect(app.active).to.be.true();
}

// [NSApplication deactivate] confusingly doesn't actually deactivate the
// app. But it does blur the windows? So let's test that.
b.focus();
expect(b.isFocused()).to.be.true();
app.deactivate();
expect(b.isFocused()).to.be.false();
});

it('is active after activating', async () => {
// If the app is active, force it to be inactivated.
// app.deactivate() doesn't actually do that, for unknown reasons. But
// setActivationPolicy('prohibited') works, so let's do that instead.
if (app.active) {
app.setActivationPolicy('prohibited');
defer(() => app.setActivationPolicy('regular'));
await emittedOnce(app, 'did-resign-active');
app.setActivationPolicy('regular');
expect(app.active).to.be.false();
}
app.activate({ ignoreOtherApps: true });
await emittedOnce(app, 'did-become-active');
expect(app.active).to.be.true();
});

it('emits did-resign-active', async () => {
app.activate({ ignoreOtherApps: true });
expect(app.active).to.be.true();
app.setActivationPolicy('prohibited');
defer(() => app.setActivationPolicy('regular'));
await emittedOnce(app, 'did-resign-active');
app.setActivationPolicy('regular');
expect(app.active).to.be.false();
});
});
});

describe('default behavior', () => {
Expand Down

0 comments on commit 3f54912

Please sign in to comment.