From 0e3d12c457210fbacc281ea45eafca045523f7a4 Mon Sep 17 00:00:00 2001 From: Evan Hahn <69474926+EvanHahn-Signal@users.noreply.github.com> Date: Fri, 10 Dec 2021 16:53:10 -0600 Subject: [PATCH] Prevent display sleep while on a call --- app/PreventDisplaySleepService.ts | 40 +++++++ app/main.ts | 8 ++ .../app/PreventDisplaySleepService_test.ts | 104 ++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 app/PreventDisplaySleepService.ts create mode 100644 ts/test-node/app/PreventDisplaySleepService_test.ts diff --git a/app/PreventDisplaySleepService.ts b/app/PreventDisplaySleepService.ts new file mode 100644 index 000000000000..f6ad41f1123d --- /dev/null +++ b/app/PreventDisplaySleepService.ts @@ -0,0 +1,40 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import type { PowerSaveBlocker } from 'electron'; +import * as log from '../ts/logging/log'; + +export class PreventDisplaySleepService { + private blockerId: undefined | number; + + constructor(private powerSaveBlocker: PowerSaveBlocker) {} + + setEnabled(isEnabled: boolean): void { + log.info( + `Prevent display sleep service: ${ + isEnabled ? 'preventing' : 'allowing' + } display sleep` + ); + + if (isEnabled) { + this.enable(); + } else { + this.disable(); + } + } + + private enable(): void { + if (this.blockerId !== undefined) { + return; + } + this.blockerId = this.powerSaveBlocker.start('prevent-display-sleep'); + } + + private disable(): void { + if (this.blockerId === undefined) { + return; + } + this.powerSaveBlocker.stop(this.blockerId); + delete this.blockerId; + } +} diff --git a/app/main.ts b/app/main.ts index d0e415bbdbd6..9c6982de6f3c 100644 --- a/app/main.ts +++ b/app/main.ts @@ -19,6 +19,7 @@ import { dialog, ipcMain as ipc, Menu, + powerSaveBlocker, protocol as electronProtocol, screen, shell, @@ -54,6 +55,7 @@ import * as attachments from './attachments'; import * as attachmentChannel from './attachment_channel'; import * as bounce from '../ts/services/bounce'; import * as updater from '../ts/updater/index'; +import { PreventDisplaySleepService } from './PreventDisplaySleepService'; import { SystemTrayService } from './SystemTrayService'; import { SystemTraySettingCache } from './SystemTraySettingCache'; import { @@ -120,6 +122,10 @@ const enableCI = config.get('enableCI'); const sql = new MainSQL(); const heicConverter = getHeicConverter(); +const preventDisplaySleepService = new PreventDisplaySleepService( + powerSaveBlocker +); + let systemTrayService: SystemTrayService | undefined; const systemTraySettingCache = new SystemTraySettingCache( sql, @@ -761,6 +767,8 @@ ipc.on('title-bar-double-click', () => { }); ipc.on('set-is-call-active', (_event, isCallActive) => { + preventDisplaySleepService.setEnabled(isCallActive); + if (!mainWindow) { return; } diff --git a/ts/test-node/app/PreventDisplaySleepService_test.ts b/ts/test-node/app/PreventDisplaySleepService_test.ts new file mode 100644 index 000000000000..1674e6345595 --- /dev/null +++ b/ts/test-node/app/PreventDisplaySleepService_test.ts @@ -0,0 +1,104 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { assert } from 'chai'; +import * as sinon from 'sinon'; +import type { PowerSaveBlocker } from 'electron'; + +import { PreventDisplaySleepService } from '../../../app/PreventDisplaySleepService'; + +describe('PreventDisplaySleepService', () => { + class FakePowerSaveBlocker implements PowerSaveBlocker { + private nextId = 0; + private idsStarted = new Set(); + + isStarted(id: number): boolean { + return this.idsStarted.has(id); + } + + start(type: 'prevent-app-suspension' | 'prevent-display-sleep'): number { + assert.strictEqual(type, 'prevent-display-sleep'); + + const result = this.nextId; + this.nextId += 1; + this.idsStarted.add(result); + return result; + } + + stop(id: number): void { + assert(this.idsStarted.has(id), `${id} was never started`); + this.idsStarted.delete(id); + } + + // This is only for testing. + _idCount(): number { + return this.idsStarted.size; + } + } + + let sandbox: sinon.SinonSandbox; + let powerSaveBlocker: FakePowerSaveBlocker; + let service: PreventDisplaySleepService; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + powerSaveBlocker = new FakePowerSaveBlocker(); + service = new PreventDisplaySleepService(powerSaveBlocker); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('does nothing if disabling when it was already disabled', () => { + const startStub = sandbox.stub(powerSaveBlocker, 'start'); + const stopStub = sandbox.stub(powerSaveBlocker, 'stop'); + + service.setEnabled(false); + + assert.strictEqual(powerSaveBlocker._idCount(), 0); + sinon.assert.notCalled(startStub); + sinon.assert.notCalled(stopStub); + }); + + it('can start power blocking', () => { + service.setEnabled(true); + + assert.strictEqual(powerSaveBlocker._idCount(), 1); + }); + + it('only starts power blocking once', () => { + service.setEnabled(true); + service.setEnabled(true); + service.setEnabled(true); + + assert.strictEqual(powerSaveBlocker._idCount(), 1); + }); + + it('can start and stop power blocking', () => { + const startSpy = sandbox.spy(powerSaveBlocker, 'start'); + const stopStub = sandbox.spy(powerSaveBlocker, 'stop'); + + service.setEnabled(true); + service.setEnabled(false); + + assert.strictEqual(powerSaveBlocker._idCount(), 0); + sinon.assert.calledOnce(startSpy); + sinon.assert.calledOnce(stopStub); + }); + + it('can toggle power blocking several times', () => { + const startSpy = sandbox.spy(powerSaveBlocker, 'start'); + const stopStub = sandbox.spy(powerSaveBlocker, 'stop'); + + service.setEnabled(true); + service.setEnabled(false); + service.setEnabled(true); + service.setEnabled(false); + service.setEnabled(true); + + assert.strictEqual(powerSaveBlocker._idCount(), 1); + sinon.assert.calledThrice(startSpy); + sinon.assert.calledTwice(stopStub); + }); +});