mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
feat: add setSuspended and isSuspended to globalShortcut (#50425)
Adds the ability to temporarily suspend and resume global shortcut handling via `globalShortcut.setSuspended()` and query the current state via `globalShortcut.isSuspended()`. When suspended, registered shortcuts stop listening and new registrations are rejected. When resumed, previously registered shortcuts are automatically restored.
This commit is contained in:
@@ -148,3 +148,34 @@ added:
|
||||
-->
|
||||
|
||||
Unregisters all of the global shortcuts.
|
||||
|
||||
### `globalShortcut.setSuspended(suspended)`
|
||||
|
||||
<!--
|
||||
```YAML history
|
||||
added:
|
||||
- pr-url: https://github.com/electron/electron/pull/50425
|
||||
```
|
||||
-->
|
||||
|
||||
* `suspended` boolean - Whether global shortcut handling should be suspended.
|
||||
|
||||
Suspends or resumes global shortcut handling. When suspended, all registered
|
||||
global shortcuts will stop listening for key presses. When resumed, all
|
||||
previously registered shortcuts will begin listening again. New shortcut
|
||||
registrations will fail while handling is suspended.
|
||||
|
||||
This can be useful when you want to temporarily allow the user to press key
|
||||
combinations without your application intercepting them, for example while
|
||||
displaying a UI to rebind shortcuts.
|
||||
|
||||
### `globalShortcut.isSuspended()`
|
||||
|
||||
<!--
|
||||
```YAML history
|
||||
added:
|
||||
- pr-url: https://github.com/electron/electron/pull/50425
|
||||
```
|
||||
-->
|
||||
|
||||
Returns `boolean` - Whether global shortcut handling is currently suspended.
|
||||
|
||||
@@ -232,6 +232,30 @@ void GlobalShortcut::UnregisterAll() {
|
||||
}
|
||||
}
|
||||
|
||||
void GlobalShortcut::SetSuspended(bool suspend) {
|
||||
if (!electron::Browser::Get()->is_ready()) {
|
||||
gin_helper::ErrorThrower(JavascriptEnvironment::GetIsolate())
|
||||
.ThrowError("globalShortcut cannot be used before the app is ready");
|
||||
return;
|
||||
}
|
||||
if (ui::GlobalAcceleratorListener::GetInstance()) {
|
||||
ui::GlobalAcceleratorListener::GetInstance()->SetShortcutHandlingSuspended(
|
||||
suspend);
|
||||
}
|
||||
}
|
||||
|
||||
bool GlobalShortcut::IsSuspended() {
|
||||
if (!electron::Browser::Get()->is_ready()) {
|
||||
gin_helper::ErrorThrower(JavascriptEnvironment::GetIsolate())
|
||||
.ThrowError("globalShortcut cannot be used before the app is ready");
|
||||
return false;
|
||||
}
|
||||
if (ui::GlobalAcceleratorListener::GetInstance())
|
||||
return ui::GlobalAcceleratorListener::GetInstance()
|
||||
->IsShortcutHandlingSuspended();
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
gin_helper::Handle<GlobalShortcut> GlobalShortcut::Create(
|
||||
v8::Isolate* isolate) {
|
||||
@@ -247,7 +271,9 @@ gin::ObjectTemplateBuilder GlobalShortcut::GetObjectTemplateBuilder(
|
||||
.SetMethod("register", &GlobalShortcut::Register)
|
||||
.SetMethod("isRegistered", &GlobalShortcut::IsRegistered)
|
||||
.SetMethod("unregister", &GlobalShortcut::Unregister)
|
||||
.SetMethod("unregisterAll", &GlobalShortcut::UnregisterAll);
|
||||
.SetMethod("unregisterAll", &GlobalShortcut::UnregisterAll)
|
||||
.SetMethod("setSuspended", &GlobalShortcut::SetSuspended)
|
||||
.SetMethod("isSuspended", &GlobalShortcut::IsSuspended);
|
||||
}
|
||||
|
||||
const char* GlobalShortcut::GetTypeName() {
|
||||
|
||||
@@ -55,6 +55,8 @@ class GlobalShortcut final
|
||||
void Unregister(const ui::Accelerator& accelerator);
|
||||
void UnregisterSome(const std::vector<ui::Accelerator>& accelerators);
|
||||
void UnregisterAll();
|
||||
void SetSuspended(bool suspend);
|
||||
bool IsSuspended();
|
||||
|
||||
// GlobalAcceleratorListener::Observer implementation.
|
||||
void OnKeyPressed(const ui::Accelerator& accelerator) override;
|
||||
|
||||
@@ -10,57 +10,180 @@ ifdescribe(process.platform !== 'win32')('globalShortcut module', () => {
|
||||
globalShortcut.unregisterAll();
|
||||
});
|
||||
|
||||
it('can register and unregister single accelerators', () => {
|
||||
const combinations = [...singleModifierCombinations, ...doubleModifierCombinations];
|
||||
afterEach(() => {
|
||||
globalShortcut.unregisterAll();
|
||||
});
|
||||
|
||||
combinations.forEach((accelerator) => {
|
||||
expect(globalShortcut.isRegistered(accelerator)).to.be.false(`Initially registered for ${accelerator}`);
|
||||
describe('register', () => {
|
||||
it('can register and unregister single accelerators', () => {
|
||||
const combinations = [...singleModifierCombinations, ...doubleModifierCombinations];
|
||||
|
||||
globalShortcut.register(accelerator, () => { });
|
||||
expect(globalShortcut.isRegistered(accelerator)).to.be.true(`Registration failed for ${accelerator}`);
|
||||
combinations.forEach((accelerator) => {
|
||||
expect(globalShortcut.isRegistered(accelerator)).to.be.false(`Initially registered for ${accelerator}`);
|
||||
|
||||
globalShortcut.unregister(accelerator);
|
||||
expect(globalShortcut.isRegistered(accelerator)).to.be.false(`Unregistration failed for ${accelerator}`);
|
||||
globalShortcut.register(accelerator, () => { });
|
||||
expect(globalShortcut.isRegistered(accelerator)).to.be.true(`Registration failed for ${accelerator}`);
|
||||
|
||||
globalShortcut.register(accelerator, () => { });
|
||||
expect(globalShortcut.isRegistered(accelerator)).to.be.true(`Re-registration failed for ${accelerator}`);
|
||||
globalShortcut.unregister(accelerator);
|
||||
expect(globalShortcut.isRegistered(accelerator)).to.be.false(`Unregistration failed for ${accelerator}`);
|
||||
|
||||
globalShortcut.unregisterAll();
|
||||
expect(globalShortcut.isRegistered(accelerator)).to.be.false(`Re-unregistration failed for ${accelerator}`);
|
||||
globalShortcut.register(accelerator, () => { });
|
||||
expect(globalShortcut.isRegistered(accelerator)).to.be.true(`Re-registration failed for ${accelerator}`);
|
||||
|
||||
globalShortcut.unregisterAll();
|
||||
expect(globalShortcut.isRegistered(accelerator)).to.be.false(`Re-unregistration failed for ${accelerator}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns true on successful registration', () => {
|
||||
const result = globalShortcut.register('CmdOrCtrl+Q', () => {});
|
||||
expect(result).to.be.true();
|
||||
});
|
||||
|
||||
it('can re-register the same accelerator without error', () => {
|
||||
globalShortcut.register('CmdOrCtrl+Z', () => {});
|
||||
expect(() => {
|
||||
globalShortcut.register('CmdOrCtrl+Z', () => {});
|
||||
}).to.not.throw();
|
||||
expect(globalShortcut.isRegistered('CmdOrCtrl+Z')).to.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
it('can register and unregister multiple accelerators', () => {
|
||||
const accelerators = ['CmdOrCtrl+X', 'CmdOrCtrl+Y'];
|
||||
describe('registerAll', () => {
|
||||
it('can register and unregister multiple accelerators', () => {
|
||||
const accelerators = ['CmdOrCtrl+X', 'CmdOrCtrl+Y'];
|
||||
|
||||
expect(globalShortcut.isRegistered(accelerators[0])).to.be.false('first initially unregistered');
|
||||
expect(globalShortcut.isRegistered(accelerators[1])).to.be.false('second initially unregistered');
|
||||
expect(globalShortcut.isRegistered(accelerators[0])).to.be.false('first initially unregistered');
|
||||
expect(globalShortcut.isRegistered(accelerators[1])).to.be.false('second initially unregistered');
|
||||
|
||||
globalShortcut.registerAll(accelerators, () => {});
|
||||
globalShortcut.registerAll(accelerators, () => {});
|
||||
|
||||
expect(globalShortcut.isRegistered(accelerators[0])).to.be.true('first registration worked');
|
||||
expect(globalShortcut.isRegistered(accelerators[1])).to.be.true('second registration worked');
|
||||
expect(globalShortcut.isRegistered(accelerators[0])).to.be.true('first registration worked');
|
||||
expect(globalShortcut.isRegistered(accelerators[1])).to.be.true('second registration worked');
|
||||
|
||||
globalShortcut.unregisterAll();
|
||||
globalShortcut.unregisterAll();
|
||||
|
||||
expect(globalShortcut.isRegistered(accelerators[0])).to.be.false('first unregistered');
|
||||
expect(globalShortcut.isRegistered(accelerators[1])).to.be.false('second unregistered');
|
||||
expect(globalShortcut.isRegistered(accelerators[0])).to.be.false('first unregistered');
|
||||
expect(globalShortcut.isRegistered(accelerators[1])).to.be.false('second unregistered');
|
||||
});
|
||||
|
||||
it('returns true on successful registration', () => {
|
||||
const result = globalShortcut.registerAll(['CmdOrCtrl+Q', 'CmdOrCtrl+W'], () => {});
|
||||
expect(result).to.be.true();
|
||||
});
|
||||
|
||||
it('does not crash when registering media keys as global shortcuts', () => {
|
||||
const accelerators = [
|
||||
'VolumeUp',
|
||||
'VolumeDown',
|
||||
'VolumeMute',
|
||||
'MediaNextTrack',
|
||||
'MediaPreviousTrack',
|
||||
'MediaStop', 'MediaPlayPause'
|
||||
];
|
||||
|
||||
expect(() => {
|
||||
globalShortcut.registerAll(accelerators, () => {});
|
||||
}).to.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not crash when registering media keys as global shortcuts', () => {
|
||||
const accelerators = [
|
||||
'VolumeUp',
|
||||
'VolumeDown',
|
||||
'VolumeMute',
|
||||
'MediaNextTrack',
|
||||
'MediaPreviousTrack',
|
||||
'MediaStop', 'MediaPlayPause'
|
||||
];
|
||||
describe('isRegistered', () => {
|
||||
it('returns false for an accelerator that was never registered', () => {
|
||||
expect(globalShortcut.isRegistered('CmdOrCtrl+Shift+F9')).to.be.false();
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
globalShortcut.registerAll(accelerators, () => {});
|
||||
}).to.not.throw();
|
||||
it('returns false after the accelerator is unregistered', () => {
|
||||
globalShortcut.register('CmdOrCtrl+J', () => {});
|
||||
globalShortcut.unregister('CmdOrCtrl+J');
|
||||
expect(globalShortcut.isRegistered('CmdOrCtrl+J')).to.be.false();
|
||||
});
|
||||
});
|
||||
|
||||
globalShortcut.unregisterAll();
|
||||
describe('unregister', () => {
|
||||
it('does not throw when unregistering a non-registered accelerator', () => {
|
||||
expect(() => {
|
||||
globalShortcut.unregister('CmdOrCtrl+Shift+F8');
|
||||
}).to.not.throw();
|
||||
});
|
||||
|
||||
it('does not affect other registered shortcuts', () => {
|
||||
globalShortcut.register('CmdOrCtrl+A', () => {});
|
||||
globalShortcut.register('CmdOrCtrl+B', () => {});
|
||||
globalShortcut.register('CmdOrCtrl+C', () => {});
|
||||
|
||||
globalShortcut.unregister('CmdOrCtrl+B');
|
||||
|
||||
expect(globalShortcut.isRegistered('CmdOrCtrl+A')).to.be.true('A should still be registered');
|
||||
expect(globalShortcut.isRegistered('CmdOrCtrl+B')).to.be.false('B should be unregistered');
|
||||
expect(globalShortcut.isRegistered('CmdOrCtrl+C')).to.be.true('C should still be registered');
|
||||
});
|
||||
});
|
||||
|
||||
describe('unregisterAll', () => {
|
||||
it('does not throw when no shortcuts are registered', () => {
|
||||
expect(() => {
|
||||
globalShortcut.unregisterAll();
|
||||
}).to.not.throw();
|
||||
});
|
||||
|
||||
it('unregisters all previously registered shortcuts', () => {
|
||||
globalShortcut.register('CmdOrCtrl+A', () => {});
|
||||
globalShortcut.register('CmdOrCtrl+B', () => {});
|
||||
globalShortcut.register('CmdOrCtrl+C', () => {});
|
||||
|
||||
globalShortcut.unregisterAll();
|
||||
|
||||
expect(globalShortcut.isRegistered('CmdOrCtrl+A')).to.be.false();
|
||||
expect(globalShortcut.isRegistered('CmdOrCtrl+B')).to.be.false();
|
||||
expect(globalShortcut.isRegistered('CmdOrCtrl+C')).to.be.false();
|
||||
});
|
||||
|
||||
it('allows re-registration after clearing all shortcuts', () => {
|
||||
globalShortcut.register('CmdOrCtrl+A', () => {});
|
||||
globalShortcut.unregisterAll();
|
||||
|
||||
const result = globalShortcut.register('CmdOrCtrl+A', () => {});
|
||||
expect(result).to.be.true();
|
||||
expect(globalShortcut.isRegistered('CmdOrCtrl+A')).to.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
describe('setSuspended / isSuspended', () => {
|
||||
afterEach(() => {
|
||||
globalShortcut.setSuspended(false);
|
||||
});
|
||||
|
||||
it('is not suspended by default', () => {
|
||||
expect(globalShortcut.isSuspended()).to.be.false();
|
||||
});
|
||||
|
||||
it('can suspend and resume shortcut handling', () => {
|
||||
globalShortcut.setSuspended(true);
|
||||
expect(globalShortcut.isSuspended()).to.be.true();
|
||||
|
||||
globalShortcut.setSuspended(false);
|
||||
expect(globalShortcut.isSuspended()).to.be.false();
|
||||
});
|
||||
|
||||
it('can be called multiple times with the same value', () => {
|
||||
globalShortcut.setSuspended(true);
|
||||
globalShortcut.setSuspended(true);
|
||||
expect(globalShortcut.isSuspended()).to.be.true();
|
||||
|
||||
globalShortcut.setSuspended(false);
|
||||
globalShortcut.setSuspended(false);
|
||||
expect(globalShortcut.isSuspended()).to.be.false();
|
||||
});
|
||||
|
||||
it('does not affect existing registrations', () => {
|
||||
globalShortcut.register('CmdOrCtrl+A', () => {});
|
||||
|
||||
globalShortcut.setSuspended(true);
|
||||
expect(globalShortcut.isRegistered('CmdOrCtrl+A')).to.be.true();
|
||||
|
||||
globalShortcut.setSuspended(false);
|
||||
expect(globalShortcut.isRegistered('CmdOrCtrl+A')).to.be.true();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user