Skip to content

Commit b5efb7c

Browse files
authored
feat: support VSCode API: WindowState.active (#4233)
* feat: add windowstate active plugin * feat: create window-activity-tracker file * feat: update window-actvity file * feat: window-inactivityTimer * feat: modify the TS type * feat: modify window-activity-timer * feat: window-activity-timer defined var * feat: window-activity-timer defined var
1 parent a1b5f89 commit b5efb7c

6 files changed

Lines changed: 144 additions & 4 deletions

File tree

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,44 @@
11
import { Injectable, Optional } from '@opensumi/di';
22
import { IRPCProtocol } from '@opensumi/ide-connection';
3+
import { DisposableCollection } from '@opensumi/ide-core-browser';
34

45
import { ExtHostAPIIdentifier, IExtHostWindowState } from '../../../common/vscode';
56

7+
import { WindowActivityTimer } from './window-activity/window-activity-timer';
8+
69
@Injectable({ multiple: true })
710
export class MainThreadWindowState {
811
private readonly proxy: IExtHostWindowState;
912
private blurHandler;
1013
private focusHandler;
14+
15+
private readonly toDispose = new DisposableCollection();
16+
1117
constructor(@Optional(Symbol()) private rpcProtocol: IRPCProtocol) {
1218
this.proxy = this.rpcProtocol.getProxy(ExtHostAPIIdentifier.ExtHostWindowState);
1319

1420
this.blurHandler = () => {
15-
this.proxy.$setWindowState(false);
21+
this.proxy.$onDidChangeWindowFocus(false);
1622
};
1723
this.focusHandler = () => {
18-
this.proxy.$setWindowState(true);
24+
this.proxy.$onDidChangeWindowFocus(true);
1925
};
2026
window.addEventListener('blur', this.blurHandler);
2127

2228
window.addEventListener('focus', this.focusHandler);
29+
30+
const tracker = new WindowActivityTimer(window);
31+
this.toDispose.push(tracker.onDidChangeActiveState((isActive) => this.onActiveStateChanged(isActive)));
32+
this.toDispose.push(tracker);
33+
}
34+
35+
private onActiveStateChanged(isActive: boolean): void {
36+
this.proxy.$onDidChangeWindowActive(isActive);
2337
}
2438

2539
public dispose() {
2640
window.removeEventListener('blur', this.blurHandler);
2741
window.removeEventListener('focus', this.focusHandler);
42+
this.toDispose.dispose();
2843
}
2944
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { Disposable, Emitter, Event } from '@opensumi/ide-core-browser';
2+
3+
const MAX_INACTIVITY_COUNT = 30;
4+
const INACTIVITY_CHECK_INTERVAL = 1000;
5+
6+
export class WindowActivityTimer extends Disposable {
7+
private activityCount = 0;
8+
private readonly maxInactivityCount: number = MAX_INACTIVITY_COUNT;
9+
private readonly inactivityCheckInterval: number = INACTIVITY_CHECK_INTERVAL;
10+
private inactivityCheckTimer: ReturnType<typeof setTimeout> | undefined;
11+
private isActive = true;
12+
13+
// Event emitter for active state changes
14+
private readonly activeStateChangedEmitter = new Emitter<boolean>();
15+
16+
// Window instance where the timer is active
17+
constructor(private readonly window: Window, inactivityLimit: number = 30, checkInterval: number = 1000) {
18+
super();
19+
this.maxInactivityCount = inactivityLimit;
20+
this.inactivityCheckInterval = checkInterval;
21+
22+
// Set up event listeners for user activity
23+
this.setupActivityListeners();
24+
}
25+
26+
// Getter for event listener
27+
get onDidChangeActiveState(): Event<boolean> {
28+
return this.activeStateChangedEmitter.event;
29+
}
30+
31+
// Set the current activity state
32+
private setActiveState(newState: boolean): void {
33+
if (this.isActive !== newState) {
34+
this.isActive = newState;
35+
this.activeStateChangedEmitter.fire(newState);
36+
}
37+
}
38+
39+
// Set up event listeners for detecting user activity
40+
private setupActivityListeners(): void {
41+
this.window.addEventListener('mousedown', this.resetInactivityTimer, {
42+
passive: true,
43+
capture: true,
44+
} as AddEventListenerOptions);
45+
this.window.addEventListener('keydown', this.resetInactivityTimer, {
46+
passive: true,
47+
capture: true,
48+
} as AddEventListenerOptions);
49+
this.window.addEventListener('touchstart', this.resetInactivityTimer, {
50+
passive: true,
51+
capture: true,
52+
} as AddEventListenerOptions);
53+
}
54+
55+
// Reset the inactivity timer when activity is detected
56+
private resetInactivityTimer = (): void => {
57+
this.activityCount = 0;
58+
this.setActiveState(true);
59+
if (!this.inactivityCheckTimer) {
60+
this.startInactivityCheck();
61+
}
62+
};
63+
64+
// Check if the user is inactive and update the state
65+
private checkInactivity = (): void => {
66+
this.activityCount++;
67+
68+
if (this.activityCount >= this.maxInactivityCount) {
69+
this.setActiveState(false);
70+
this.stopInactivityCheck();
71+
}
72+
};
73+
74+
// Start the inactivity timer
75+
private startInactivityCheck(): void {
76+
this.stopInactivityCheck();
77+
this.inactivityCheckTimer = setInterval(() => {
78+
this.checkInactivity();
79+
}, this.inactivityCheckInterval);
80+
}
81+
82+
// Stop the inactivity timer
83+
private stopInactivityCheck(): void {
84+
if (this.inactivityCheckTimer) {
85+
clearInterval(this.inactivityCheckTimer);
86+
this.inactivityCheckTimer = undefined;
87+
}
88+
}
89+
90+
// Clean up event listeners and stop the timer when disposed
91+
dispose(): void {
92+
this.stopInactivityCheck();
93+
this.activeStateChangedEmitter.dispose();
94+
super.dispose();
95+
this.window.removeEventListener('mousedown', this.resetInactivityTimer, {
96+
passive: true,
97+
capture: true,
98+
} as AddEventListenerOptions);
99+
this.window.removeEventListener('keydown', this.resetInactivityTimer, {
100+
passive: true,
101+
capture: true,
102+
} as AddEventListenerOptions);
103+
this.window.removeEventListener('touchstart', this.resetInactivityTimer, {
104+
passive: true,
105+
capture: true,
106+
} as AddEventListenerOptions);
107+
}
108+
}

packages/extension/src/common/vscode/ext-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1638,6 +1638,7 @@ export interface OutputChannel {
16381638
}
16391639

16401640
export interface WindowState {
1641+
active: boolean;
16411642
focused: boolean;
16421643
}
16431644

packages/extension/src/common/vscode/window.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,8 @@ export interface IExtHostOutput {
204204
}
205205

206206
export interface IExtHostWindowState {
207-
$setWindowState(focused: boolean);
207+
$onDidChangeWindowFocus(focused: boolean);
208+
$onDidChangeWindowActive(active: boolean);
208209

209210
readonly state: types.WindowState;
210211

packages/extension/src/hosted/api/vscode/ext.host.window-state.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,28 @@ export class ExtHostWindowState implements IExtHostWindowState {
1212

1313
public readonly onDidChangeWindowState: Event<types.WindowState> = this._onDidChangeWindowState.event;
1414

15-
public $setWindowState(focused: boolean) {
15+
public $onDidChangeWindowFocus(focused: boolean) {
1616
if (focused !== this.state.focused) {
1717
this.state.focused = focused;
1818
this._onDidChangeWindowState.fire(this.state);
1919
}
2020
}
21+
22+
public $onDidChangeWindowActive(active: boolean): void {
23+
if (active !== this.state.active) {
24+
this.state.active = active;
25+
this._onDidChangeWindowState.fire(this.state);
26+
}
27+
}
2128
}
2229

2330
export class WindowStateImpl implements types.WindowState {
2431
public focused: boolean;
32+
public active: boolean;
2533

2634
constructor() {
2735
// 当插件进程重启时,这里如果默认是 false,会与实际状态不一致,导致依赖该状态的插件处理有问题
2836
this.focused = true;
37+
this.active = true;
2938
}
3039
}

packages/types/vscode/typings/vscode.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,6 +1054,12 @@ declare module 'vscode' {
10541054
* Whether the current window is focused.
10551055
*/
10561056
readonly focused: boolean;
1057+
1058+
/**
1059+
* Whether the window has been interacted with recently. This will change
1060+
* immediately on activity, or after a short time of user inactivity.
1061+
*/
1062+
readonly active: boolean;
10571063
}
10581064

10591065
/**

0 commit comments

Comments
 (0)