Skip to content

Commit a75f3ea

Browse files
committed
Add WindowState active in plugin API
fixes #13692 contributed on behalf of STMicroelectronics Signed-off-by: Remi Schnekenburger <[email protected]>
1 parent 3dc5537 commit a75f3ea

File tree

6 files changed

+128
-8
lines changed

6 files changed

+128
-8
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
## 1.50.0
1414

15+
- [plugin] Support WindowState active API [#13718](https://github.com/eclipse-theia/theia/pull/13718) - contributed on behalf of STMicroelectronics
16+
1517
<a name="breaking_changes_1.50.0">[Breaking Changes:](#breaking_changes_1.50.0)</a>
1618

1719
- [core] Classes implementing the `Saveable` interface no longer need to implement the `autoSave` field. However, a new `onContentChanged` event has been added instead.

packages/plugin-ext/src/common/plugin-api-rpc.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -893,7 +893,8 @@ export interface WindowMain {
893893
}
894894

895895
export interface WindowStateExt {
896-
$onWindowStateChanged(focus: boolean): void;
896+
$onDidChangeWindowFocus(focused: boolean): void;
897+
$onDidChangeWindowActive(active: boolean): void;
897898
}
898899

899900
export interface NotificationExt {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright (C) 2022 STMicroelectronics and others.
2+
//
3+
// This program and the accompanying materials are made available under the
4+
// terms of the Eclipse Public License v. 2.0 which is available at
5+
// http://www.eclipse.org/legal/epl-2.0.
6+
//
7+
// This Source Code may also be made available under the following Secondary
8+
// Licenses when the conditions for such availability set forth in the Eclipse
9+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
10+
// with the GNU Classpath Exception which is available at
11+
// https://www.gnu.org/software/classpath/license.html.
12+
//
13+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
14+
// *****************************************************************************
15+
16+
import { Disposable, Emitter, Event } from '@theia/core';
17+
18+
const CHECK_INACTIVITY_LIMIT = 2;
19+
const CHECK_INACTIVITY_INTERVAL = 10000;
20+
21+
const eventListenerOptions: AddEventListenerOptions = {
22+
passive: true,
23+
capture: true
24+
};
25+
export class WindowActivityTracker implements Disposable {
26+
27+
private inactivityTime = 0; // number of times inactivity was checked since last reset
28+
private readonly inactivityLimit = CHECK_INACTIVITY_LIMIT; // number of inactivity checks done before sending inactive signal
29+
private readonly checkInactivityInterval = CHECK_INACTIVITY_INTERVAL; // check interval in milliseconds
30+
private interval: NodeJS.Timeout | undefined;
31+
32+
protected readonly onDidChangeActiveStateEmitter = new Emitter<boolean>();
33+
private _activeState: boolean = true;
34+
35+
constructor(readonly win: Window) {
36+
this.initializeListeners(this.win);
37+
}
38+
39+
get onDidChangeActiveState(): Event<boolean> {
40+
return this.onDidChangeActiveStateEmitter.event;
41+
}
42+
43+
private set activeState(newState: boolean) {
44+
if (this._activeState !== newState) {
45+
this._activeState = newState;
46+
this.onDidChangeActiveStateEmitter.fire(this._activeState);
47+
}
48+
}
49+
50+
private initializeListeners(win: Window): void {
51+
// currently assumes activity based on key/mouse/touch pressed, not on mouse move or scrolling.
52+
win.addEventListener('mousedown', this.resetInactivity, eventListenerOptions);
53+
win.addEventListener('keydown', this.resetInactivity, eventListenerOptions);
54+
win.addEventListener('touchstart', this.resetInactivity, eventListenerOptions);
55+
}
56+
57+
dispose(): void {
58+
this.stopTracking();
59+
this.win.removeEventListener('mousedown', this.resetInactivity);
60+
this.win.removeEventListener('keydown', this.resetInactivity);
61+
this.win.removeEventListener('touchstart', this.resetInactivity);
62+
63+
}
64+
65+
// Reset inactivity time
66+
private resetInactivity = (): void => {
67+
this.inactivityTime = 0;
68+
if (!this.interval) {
69+
// it was not active. Set as active and restart tracking inactivity
70+
this.activeState = true;
71+
this.startTracking();
72+
}
73+
};
74+
75+
// Check inactivity status
76+
private checkInactivity = (): void => {
77+
this.inactivityTime++;
78+
if (this.inactivityTime >= this.inactivityLimit) {
79+
this.activeState = false;
80+
this.stopTracking();
81+
}
82+
};
83+
84+
public startTracking(): void {
85+
this.stopTracking();
86+
this.interval = setInterval(this.checkInactivity, this.checkInactivityInterval);
87+
}
88+
89+
public stopTracking(): void {
90+
if (this.interval) {
91+
clearInterval(this.interval);
92+
this.interval = undefined;
93+
}
94+
}
95+
}

packages/plugin-ext/src/main/browser/window-state-main.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { UriComponents } from '../../common/uri-components';
2323
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
2424
import { open, OpenerService } from '@theia/core/lib/browser/opener-service';
2525
import { ExternalUriService } from '@theia/core/lib/browser/external-uri-service';
26+
import { WindowActivityTracker } from './window-activity-tracker';
2627

2728
export class WindowStateMain implements WindowMain, Disposable {
2829

@@ -46,14 +47,22 @@ export class WindowStateMain implements WindowMain, Disposable {
4647
const fireDidBlur = () => this.onFocusChanged(false);
4748
window.addEventListener('blur', fireDidBlur);
4849
this.toDispose.push(Disposable.create(() => window.removeEventListener('blur', fireDidBlur)));
50+
51+
const tracker = new WindowActivityTracker(window);
52+
this.toDispose.push(tracker.onDidChangeActiveState(isActive => this.onActiveStateChanged(isActive)));
53+
this.toDispose.push(Disposable.create(() => tracker.dispose()));
4954
}
5055

5156
dispose(): void {
5257
this.toDispose.dispose();
5358
}
5459

5560
private onFocusChanged(focused: boolean): void {
56-
this.proxy.$onWindowStateChanged(focused);
61+
this.proxy.$onDidChangeWindowFocus(focused);
62+
}
63+
64+
private onActiveStateChanged(isActive: boolean): void {
65+
this.proxy.$onDidChangeWindowActive(isActive);
5766
}
5867

5968
async $openUri(uriComponent: UriComponents): Promise<boolean> {

packages/plugin-ext/src/plugin/window-state.ts

+13-6
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,28 @@ export class WindowStateExtImpl implements WindowStateExt {
3131

3232
constructor(rpc: RPCProtocol) {
3333
this.proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.WINDOW_MAIN);
34-
this.windowStateCached = { focused: true }; // supposed tab is active on start
34+
this.windowStateCached = { focused: true, active: true }; // supposed tab is active on start
3535
}
3636

3737
getWindowState(): WindowState {
3838
return this.windowStateCached;
3939
}
4040

41-
$onWindowStateChanged(focused: boolean): void {
42-
const state = { focused: focused };
43-
if (state === this.windowStateCached) {
41+
$onDidChangeWindowFocus(focused: boolean): void {
42+
this.onDidChangeWindowProperty('focused', focused);
43+
}
44+
45+
$onDidChangeWindowActive(active: boolean): void {
46+
this.onDidChangeWindowProperty('active', active);
47+
}
48+
49+
onDidChangeWindowProperty(property: keyof WindowState, value: boolean): void {
50+
if (value === this.windowStateCached[property]) {
4451
return;
4552
}
4653

47-
this.windowStateCached = state;
48-
this.windowStateChangedEmitter.fire(state);
54+
this.windowStateCached = { ...this.windowStateCached, [property]: value };
55+
this.windowStateChangedEmitter.fire(this.windowStateCached);
4956
}
5057

5158
openUri(uri: URI): Promise<boolean> {

packages/plugin/src/theia.d.ts

+6
Original file line numberDiff line numberDiff line change
@@ -2741,6 +2741,12 @@ export module '@theia/plugin' {
27412741
* Whether the current window is focused.
27422742
*/
27432743
readonly focused: boolean;
2744+
2745+
/**
2746+
* Whether the window has been interacted with recently. This will change
2747+
* immediately on activity, or after a short time of user inactivity.
2748+
*/
2749+
readonly active: boolean;
27442750
}
27452751

27462752
/**

0 commit comments

Comments
 (0)