Skip to content

Commit

Permalink
Add WindowState active in plugin API (#13718)
Browse files Browse the repository at this point in the history
fixes #13692

contributed on behalf of STMicroelectronics

Signed-off-by: Remi Schnekenburger <rschnekenburger@eclipsesource.com>
  • Loading branch information
rschnekenbu committed May 28, 2024
1 parent b358fd7 commit 5844c0d
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 8 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

## 1.50.0

- [plugin] Support WindowState active API [#13718](https://github.com/eclipse-theia/theia/pull/13718) - contributed on behalf of STMicroelectronics

<a name="breaking_changes_1.50.0">[Breaking Changes:](#breaking_changes_1.50.0)</a>

- [core] Classes implementing the `Saveable` interface no longer need to implement the `autoSave` field. However, a new `onContentChanged` event has been added instead.
Expand Down
3 changes: 2 additions & 1 deletion packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,8 @@ export interface WindowMain {
}

export interface WindowStateExt {
$onWindowStateChanged(focus: boolean): void;
$onDidChangeWindowFocus(focused: boolean): void;
$onDidChangeWindowActive(active: boolean): void;
}

export interface NotificationExt {
Expand Down
96 changes: 96 additions & 0 deletions packages/plugin-ext/src/main/browser/window-activity-tracker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// *****************************************************************************
// Copyright (C) 2024 STMicroelectronics and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { Disposable, Emitter, Event } from '@theia/core';

const CHECK_INACTIVITY_LIMIT = 30;
const CHECK_INACTIVITY_INTERVAL = 1000;

const eventListenerOptions: AddEventListenerOptions = {
passive: true,
capture: true
};
export class WindowActivityTracker implements Disposable {

private inactivityCounter = 0; // number of times inactivity was checked since last reset
private readonly inactivityLimit = CHECK_INACTIVITY_LIMIT; // number of inactivity checks done before sending inactive signal
private readonly checkInactivityInterval = CHECK_INACTIVITY_INTERVAL; // check interval in milliseconds
private interval: NodeJS.Timeout | undefined;

protected readonly onDidChangeActiveStateEmitter = new Emitter<boolean>();
private _activeState: boolean = true;

constructor(readonly win: Window) {
this.initializeListeners(this.win);
}

get onDidChangeActiveState(): Event<boolean> {
return this.onDidChangeActiveStateEmitter.event;
}

private set activeState(newState: boolean) {
if (this._activeState !== newState) {
this._activeState = newState;
this.onDidChangeActiveStateEmitter.fire(this._activeState);
}
}

private initializeListeners(win: Window): void {
// currently assumes activity based on key/mouse/touch pressed, not on mouse move or scrolling.
win.addEventListener('mousedown', this.resetInactivity, eventListenerOptions);
win.addEventListener('keydown', this.resetInactivity, eventListenerOptions);
win.addEventListener('touchstart', this.resetInactivity, eventListenerOptions);
}

dispose(): void {
this.stopTracking();
this.win.removeEventListener('mousedown', this.resetInactivity);
this.win.removeEventListener('keydown', this.resetInactivity);
this.win.removeEventListener('touchstart', this.resetInactivity);

}

// Reset inactivity time
private resetInactivity = (): void => {
this.inactivityCounter = 0;
if (!this.interval) {
// it was not active. Set as active and restart tracking inactivity
this.activeState = true;
this.startTracking();
}
};

// Check inactivity status
private checkInactivity = (): void => {
this.inactivityCounter++;
if (this.inactivityCounter >= this.inactivityLimit) {
this.activeState = false;
this.stopTracking();
}
};

public startTracking(): void {
this.stopTracking();
this.interval = setInterval(this.checkInactivity, this.checkInactivityInterval);
}

public stopTracking(): void {
if (this.interval) {
clearInterval(this.interval);
this.interval = undefined;
}
}
}
11 changes: 10 additions & 1 deletion packages/plugin-ext/src/main/browser/window-state-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { UriComponents } from '../../common/uri-components';
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
import { open, OpenerService } from '@theia/core/lib/browser/opener-service';
import { ExternalUriService } from '@theia/core/lib/browser/external-uri-service';
import { WindowActivityTracker } from './window-activity-tracker';

export class WindowStateMain implements WindowMain, Disposable {

Expand All @@ -46,14 +47,22 @@ export class WindowStateMain implements WindowMain, Disposable {
const fireDidBlur = () => this.onFocusChanged(false);
window.addEventListener('blur', fireDidBlur);
this.toDispose.push(Disposable.create(() => window.removeEventListener('blur', fireDidBlur)));

const tracker = new WindowActivityTracker(window);
this.toDispose.push(tracker.onDidChangeActiveState(isActive => this.onActiveStateChanged(isActive)));
this.toDispose.push(tracker);
}

dispose(): void {
this.toDispose.dispose();
}

private onFocusChanged(focused: boolean): void {
this.proxy.$onWindowStateChanged(focused);
this.proxy.$onDidChangeWindowFocus(focused);
}

private onActiveStateChanged(isActive: boolean): void {
this.proxy.$onDidChangeWindowActive(isActive);
}

async $openUri(uriComponent: UriComponents): Promise<boolean> {
Expand Down
19 changes: 13 additions & 6 deletions packages/plugin-ext/src/plugin/window-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,28 @@ export class WindowStateExtImpl implements WindowStateExt {

constructor(rpc: RPCProtocol) {
this.proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.WINDOW_MAIN);
this.windowStateCached = { focused: true }; // supposed tab is active on start
this.windowStateCached = { focused: true, active: true }; // supposed tab is active on start
}

getWindowState(): WindowState {
return this.windowStateCached;
}

$onWindowStateChanged(focused: boolean): void {
const state = { focused: focused };
if (state === this.windowStateCached) {
$onDidChangeWindowFocus(focused: boolean): void {
this.onDidChangeWindowProperty('focused', focused);
}

$onDidChangeWindowActive(active: boolean): void {
this.onDidChangeWindowProperty('active', active);
}

onDidChangeWindowProperty(property: keyof WindowState, value: boolean): void {
if (value === this.windowStateCached[property]) {
return;
}

this.windowStateCached = state;
this.windowStateChangedEmitter.fire(state);
this.windowStateCached = { ...this.windowStateCached, [property]: value };
this.windowStateChangedEmitter.fire(this.windowStateCached);
}

openUri(uri: URI): Promise<boolean> {
Expand Down
6 changes: 6 additions & 0 deletions packages/plugin/src/theia.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2741,6 +2741,12 @@ export module '@theia/plugin' {
* Whether the current window is focused.
*/
readonly focused: boolean;

/**
* Whether the window has been interacted with recently. This will change
* immediately on activity, or after a short time of user inactivity.
*/
readonly active: boolean;
}

/**
Expand Down

0 comments on commit 5844c0d

Please sign in to comment.