.dotfiles/.local/share/gnome-shell/extensions/caffeine@patapon.info/extension.js

1129 lines
40 KiB
JavaScript

/* -*- mode: js2 - indent-tabs-mode: nil - js2-basic-offset: 4 -*- */
/* jshint multistr:true */
/* jshint esnext:true */
/* exported CaffeineExtension */
/**
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
**/
'use strict';
import Gio from 'gi://Gio';
import GObject from 'gi://GObject';
import Shell from 'gi://Shell';
import St from 'gi://St';
import Meta from 'gi://Meta';
import Clutter from 'gi://Clutter';
import GLib from 'gi://GLib';
import { Extension, gettext as _ } from 'resource:///org/gnome/shell/extensions/extension.js';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
import * as QuickSettings from 'resource:///org/gnome/shell/ui/quickSettings.js';
const QuickSettingsMenu = Main.panel.statusArea.quickSettings;
const INHIBIT_APPS_KEY = 'inhibit-apps';
const SHOW_INDICATOR_KEY = 'show-indicator';
const SHOW_NOTIFICATIONS_KEY = 'show-notifications';
const SHOW_TIMER_KEY = 'show-timer';
const DURATION_TIMER_INDEX = 'duration-timer';
const TOGGLE_STATE_KEY = 'toggle-state';
const USER_ENABLED_KEY = 'user-enabled';
const RESTORE_KEY = 'restore-state';
const FULLSCREEN_KEY = 'enable-fullscreen';
const NIGHT_LIGHT_KEY = 'nightlight-control';
const TOGGLE_SHORTCUT = 'toggle-shortcut';
const TIMER_KEY = 'countdown-timer';
const SCREEN_BLANK = 'screen-blank';
const TRIGGER_APPS_MODE = 'trigger-apps-mode';
const INDICATOR_POSITION = 'indicator-position';
const INDICATOR_INDEX = 'indicator-position-index';
const INDICATOR_POS_MAX = 'indicator-position-max';
const ColorInterface = '<node> \
<interface name="org.gnome.SettingsDaemon.Color"> \
<property name="DisabledUntilTomorrow" type="b" access="readwrite"/>\
<property name="NightLightActive" type="b" access="read"/>\
</interface>\
</node>';
const ColorProxy = Gio.DBusProxy.makeProxyWrapper(ColorInterface);
const DBusSessionManagerIface = '<node>\
<interface name="org.gnome.SessionManager">\
<method name="Inhibit">\
<arg type="s" direction="in" />\
<arg type="u" direction="in" />\
<arg type="s" direction="in" />\
<arg type="u" direction="in" />\
<arg type="u" direction="out" />\
</method>\
<method name="Uninhibit">\
<arg type="u" direction="in" />\
</method>\
<method name="GetInhibitors">\
<arg type="ao" direction="out" />\
</method>\
<signal name="InhibitorAdded">\
<arg type="o" direction="out" />\
</signal>\
<signal name="InhibitorRemoved">\
<arg type="o" direction="out" />\
</signal>\
</interface>\
</node>';
const DBusSessionManagerProxy = Gio.DBusProxy.makeProxyWrapper(DBusSessionManagerIface);
const DBusSessionManagerInhibitorIface = '<node>\
<interface name="org.gnome.SessionManager.Inhibitor">\
<method name="GetAppId">\
<arg type="s" direction="out" />\
</method>\
</interface>\
</node>';
const DBusSessionManagerInhibitorProxy = Gio.DBusProxy.makeProxyWrapper(DBusSessionManagerInhibitorIface);
const DisabledIcon = 'my-caffeine-off-symbolic';
const EnabledIcon = 'my-caffeine-on-symbolic';
const TimerMenuIcon = 'stopwatch-symbolic';
const ControlContext = {
NEVER: 0,
ALWAYS: 1,
FOR_APPS: 2
};
const ShowIndicator = {
ONLY_ACTIVE: 0,
ALWAYS: 1,
NEVER: 2
};
const AppsTrigger = {
ON_RUNNING: 0,
ON_FOCUS: 1,
ON_ACTIVE_WORKSPACE: 2
};
const TIMERS = [
[5, 10, 15, 20, 30, 'caffeine-short-timer-symbolic'],
[10, 20, 30, 40, 50, 'caffeine-medium-timer-symbolic'],
[30, 45, 60, 75, 80, 'caffeine-long-timer-symbolic'],
[0, 0, 0, 0, 0, 'caffeine-infinite-timer-symbolic']
];
const CaffeineToggle = GObject.registerClass(
class CaffeineToggle extends QuickSettings.QuickMenuToggle {
_init(settings, path) {
super._init({
'title': _('Caffeine'),
toggleMode: true
});
this._settings = settings;
this._path = path;
// Icons
this.finalTimerMenuIcon = TimerMenuIcon;
let iconTheme = new St.IconTheme();
if (!iconTheme.has_icon(TimerMenuIcon)) {
this.finalTimerMenuIcon =
Gio.icon_new_for_string(`${this._path}/icons/${TimerMenuIcon}.svg`);
}
this._iconActivated = Gio.icon_new_for_string(`${this._path}/icons/${EnabledIcon}.svg`);
this._iconDeactivated = Gio.icon_new_for_string(`${this._path}/icons/${DisabledIcon}.svg`);
this._iconName();
// Menu
this.menu.setHeader(this.finalTimerMenuIcon, _('Caffeine Timer'), null);
// Add elements
this._itemsSection = new PopupMenu.PopupMenuSection();
this.menu.addMenuItem(this._itemsSection);
// Init Timers
this._timerItems = new Map();
this._syncTimers(false);
// Bind signals
this._settings.bind(`${TOGGLE_STATE_KEY}`,
this, 'checked',
Gio.SettingsBindFlags.DEFAULT);
this._settings.connect(`changed::${TOGGLE_STATE_KEY}`, () => {
this._iconName();
});
this._settings.connect(`changed::${TIMER_KEY}`, () => {
this._sync();
});
this._settings.connect(`changed::${DURATION_TIMER_INDEX}`, () => {
this._syncTimers(true);
});
this.connect('destroy', () => {
this._iconActivated = null;
this._iconDeactivated = null;
this.gicon = null;
});
}
_syncTimers(resetDefault) {
this._itemsSection.removeAll();
this._timerItems.clear();
const durationIndex = this._settings.get_int(DURATION_TIMER_INDEX);
for (const timer of TIMERS) {
let label = null;
if (timer[0] === 0) {
label = _('Infinite');
} else {
label = parseInt(timer[durationIndex]) + _(' minutes');
}
if (!label) {
continue;
}
const icon = Gio.icon_new_for_string(`${this._path}/icons/${timer[5]}.svg`);
const item = new PopupMenu.PopupImageMenuItem(label, icon);
item.connect('activate', () => {
this._checkTimer(timer[durationIndex]);
});
this._timerItems.set(timer[durationIndex], item);
this._itemsSection.addMenuItem(item);
}
this.menuEnabled = TIMERS.length > 2;
// Select active duration
if (resetDefault && this._settings.get_int(TIMER_KEY) !== 0) {
// Set default duration to 0
this._settings.set_int(TIMER_KEY, 0);
} else {
this._sync();
}
}
_sync() {
const activeTimerId = this._settings.get_int(TIMER_KEY);
for (const [timerId, item] of this._timerItems) {
item.setOrnament(timerId === activeTimerId
? PopupMenu.Ornament.CHECK
: PopupMenu.Ornament.NONE);
}
}
_checkTimer(timerId) {
this._settings.set_int(TIMER_KEY, timerId);
this._settings.set_boolean(TOGGLE_STATE_KEY, true);
}
_iconName() {
if (this._settings.get_boolean(TOGGLE_STATE_KEY)) {
this.gicon = this._iconActivated;
} else {
this.gicon = this._iconDeactivated;
}
}
});
const Caffeine = GObject.registerClass(
class Caffeine extends QuickSettings.SystemIndicator {
_init(settings, path, name) {
super._init();
this._indicator = this._addIndicator();
this._settings = settings;
this._name = name;
// D-bus
this._proxy = new ColorProxy(
Gio.DBus.session,
'org.gnome.SettingsDaemon.Color',
'/org/gnome/SettingsDaemon/Color',
(proxy, error) => {
if (error) {
log(error.message);
}
}
);
this._sessionManager = new DBusSessionManagerProxy(Gio.DBus.session,
'org.gnome.SessionManager',
'/org/gnome/SessionManager');
// From auto-move-windows@gnome-shell-extensions.gcampax.github.com
this._appSystem = Shell.AppSystem.get_default();
this._activeWorkspace = null;
// Init Apps Signals Id
this._appStateChangedSignalId = 0;
this._appDisplayChangedSignalId = 0;
this._appWorkspaceChangedSignalId = 0;
this._appAddWindowSignalId = 0;
this._appRemoveWindowSignalId = 0;
// ("screen" in global) is false on 3.28, although global.screen exists
if (typeof global.screen !== 'undefined') {
this._screen = global.screen;
this._display = this._screen.get_display();
} else {
this._screen = global.display;
this._display = this._screen;
}
// Add indicator label for the timer
this._timerLabel = new St.Label({
y_expand: true,
y_align: Clutter.ActorAlign.CENTER
});
this._timerLabel.visible = false;
this.add_child(this._timerLabel);
// Icons
this._iconActivated = Gio.icon_new_for_string(`${path}/icons/${EnabledIcon}.svg`);
this._iconDeactivated = Gio.icon_new_for_string(`${path}/icons/${DisabledIcon}.svg`);
this._indicator.gicon = this._iconDeactivated;
// Manage night light
this._nightLight = false;
/* Inhibited flag value
* - 4: Inhibit suspending the session or computer
* - 12: Inhibit the session being marked as idle
*/
this.inhibitFlags = 12;
// Caffeine state
this._state = false;
this._userState = false;
// Store the inhibition requests until processed
this._inhibitionAddedFifo = [];
this._inhibitionRemovedFifo = [];
// Init Timers
this._timeOut = null;
this._timePrint = null;
this._timerEnable = false;
this._timeFullscreen = null;
this._timeWorkspaceAdd = null;
this._timeWorkspaceRemove = null;
this._timeAppUnblock = null;
// Show icon
this._manageShowIndicator();
// Init app list
this._appConfigs = [];
this._appInhibitedData = new Map();
this._updateAppConfigs();
// Enable caffeine when fullscreen app is running
if (this._settings.get_boolean(FULLSCREEN_KEY)) {
this._inFullscreenId = this._screen.connect('in-fullscreen-changed', this.toggleFullscreen.bind(this));
this.toggleFullscreen();
}
// QuickSettings
this._caffeineToggle = new CaffeineToggle(this._settings, path);
this.quickSettingsItems.push(this._caffeineToggle);
this._updateTimerSubtitle();
// Init settings keys and restore user state
this._settings.reset(TOGGLE_STATE_KEY);
if (this._settings.get_boolean(USER_ENABLED_KEY) && this._settings.get_boolean(RESTORE_KEY)) {
this.toggleState();
} else {
// reset user state
this._settings.reset(USER_ENABLED_KEY);
}
// Bind signals
this._inhibitorAddedId = this._sessionManager.connectSignal(
'InhibitorAdded', this._inhibitorAdded.bind(this));
this._inhibitorRemovedId = this._sessionManager.connectSignal(
'InhibitorRemoved', this._inhibitorRemoved.bind(this));
this.inhibitId = this._settings.connect(`changed::${INHIBIT_APPS_KEY}`,
this._updateAppConfigs.bind(this));
this.stateId = this._settings.connect(`changed::${TOGGLE_STATE_KEY}`,
this._updateMainState.bind(this));
this.timerId = this._settings.connect(`changed::${TIMER_KEY}`,
this._startTimer.bind(this));
this.showTimerId = this._settings.connect(`changed::${SHOW_TIMER_KEY}`,
this._showIndicatorLabel.bind(this));
this.indicatorId = this._settings.connect(`changed::${INDICATOR_POSITION}`,
this._updateIndicatorPosition.bind(this));
this.showIndicatorId = this._settings.connect(`changed::${SHOW_INDICATOR_KEY}`, () => {
this._manageShowIndicator();
this._showIndicatorLabel();
});
this.triggerId = this._settings.connect(`changed::${TRIGGER_APPS_MODE}`, () => {
this._resetAppSignalId();
this._updateAppEventMode();
});
// Change user state on icon scroll event
this._indicator.reactive = true;
this._indicator.connect('scroll-event',
(actor, event) => this._handleScrollEvent(event));
// Init position and index of indicator icon
this.indicatorPosition = this._settings.get_int(INDICATOR_POSITION);
this.indicatorIndex = this._settings.get_int(INDICATOR_INDEX);
this.lastIndicatorPosition = this.indicatorPosition;
// Add indicator and toggle
QuickSettingsMenu.addExternalIndicator(this);
QuickSettingsMenu._indicators.remove_actor(this);
QuickSettingsMenu._indicators.insert_child_at_index(this, this.indicatorIndex);
this._updateLastIndicatorPosition();
}
get inFullscreen() {
let nbMonitors = this._screen.get_n_monitors();
let inFullscreen = false;
for (let i = 0; i < nbMonitors; i++) {
if (this._screen.get_monitor_in_fullscreen(i)) {
inFullscreen = true;
break;
}
}
return inFullscreen;
}
toggleFullscreen() {
/* Reset previous FullScreen delay
* This prevent multiple inhibitors to be created in toggleFullscreen()
* if a previous timer is still running.
*/
if (this._timeFullscreen !== null) {
GLib.Source.remove(this._timeFullscreen);
this._timeFullscreen = null;
}
this._manageScreenBlankState(false);
// Add 2 second delay before adding inhibitor
this._timeFullscreen = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 2, () => {
if (this.inFullscreen && !this._appInhibitedData.has('fullscreen')) {
this.addInhibit('fullscreen');
this._manageNightLight(false, false);
}
this._timeFullscreen = null;
return GLib.SOURCE_REMOVE;
});
if (!this.inFullscreen && this._appInhibitedData.has('fullscreen')) {
this.removeInhibit('fullscreen');
this._manageNightLight(true, false);
}
}
toggleState() {
this._manageScreenBlankState(false);
if (this._state) {
this._removeTimer();
this._appInhibitedData.forEach((data, appId) =>
this.removeInhibit(appId)
);
this._manageNightLight(true, false);
} else {
this.addInhibit('user');
this._manageNightLight(false, false);
// Enable timer when duration isn't null
if (this._settings.get_int(TIMER_KEY) !== 0 && !this._timerEnable) {
this._startTimer();
}
}
}
addInhibit(appId) {
this._sessionManager.InhibitRemote(appId,
0, 'Inhibit by %s'.format(this._name), this.inhibitFlags,
(cookie) => {
this._inhibitionAddedFifo.push(appId);
// Init app data
let data = {
cookie,
isToggled: true,
isInhibited: false,
object: ''
};
this._appInhibitedData.set(appId, data);
}
);
}
removeInhibit(appId) {
let appData = this._appInhibitedData.get(appId);
if (appData && appData.isInhibited) {
this._inhibitionRemovedFifo.push(appId);
this._sessionManager.UninhibitRemote(appData.cookie);
appData.isToggled = false;
this._appInhibitedData.set(appId, appData);
}
}
_updateLastIndicatorPosition() {
let pos = -1;
let nbItems = QuickSettingsMenu._indicators.get_n_children();
let targetIndicator = null;
// Count only the visible item in indicator bar
for (let i = 0; i < nbItems; i++) {
targetIndicator = QuickSettingsMenu._indicators.get_child_at_index(i);
if (targetIndicator.is_visible()) {
pos += 1;
}
}
this._settings.set_int(INDICATOR_POS_MAX, pos);
}
_incrementIndicatorPosIndex() {
if (this.lastIndicatorPosition < this.indicatorPosition) {
this.indicatorIndex += 1;
} else {
this.indicatorIndex -= 1;
}
}
_updateIndicatorPosition() {
this._updateLastIndicatorPosition();
const newPosition = this._settings.get_int(INDICATOR_POSITION);
if (this.indicatorPosition !== newPosition) {
this.indicatorPosition = newPosition;
this._incrementIndicatorPosIndex();
// Skip invisible indicator
let targetIndicator =
QuickSettingsMenu._indicators.get_child_at_index(this.indicatorIndex);
let maxIndex = QuickSettingsMenu._indicators.get_n_children();
while (this.indicatorIndex < maxIndex && !targetIndicator.is_visible() && this.indicatorIndex > -1) {
this._incrementIndicatorPosIndex();
targetIndicator =
QuickSettingsMenu._indicators.get_child_at_index(this.indicatorIndex);
}
// Always reset index to 0 on position 0
if (this.indicatorPosition === 0) {
this.indicatorIndex = 0;
}
// Update last position
this.lastIndicatorPosition = newPosition;
// Update indicator index
QuickSettingsMenu._indicators.remove_actor(this);
QuickSettingsMenu._indicators.insert_child_at_index(this, this.indicatorIndex);
this._settings.set_int(INDICATOR_INDEX, this.indicatorIndex);
}
}
_showIndicatorLabel() {
if (this._settings.get_boolean(SHOW_TIMER_KEY) &&
(this._settings.get_enum(SHOW_INDICATOR_KEY) !== ShowIndicator.NEVER) &&
this._timerEnable) {
this._timerLabel.visible = true;
} else {
this._timerLabel.visible = false;
}
}
_startTimer() {
// Reset timer
this._removeTimer();
this._timerEnable = true;
// Get duration
let timerDelay = this._settings.get_int(TIMER_KEY) * 60;
// Execute Timer only if duration isn't set on infinite time
if (timerDelay !== 0) {
let secondLeft = timerDelay;
this._showIndicatorLabel();
this._printTimer(secondLeft);
this._timePrint = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, () => {
secondLeft -= 1;
this._printTimer(secondLeft);
return GLib.SOURCE_CONTINUE;
});
this._timeOut = GLib.timeout_add(GLib.PRIORITY_DEFAULT, timerDelay * 1000, () => {
// Disable Caffeine when timer ended
this._removeTimer();
this._settings.set_boolean(TOGGLE_STATE_KEY, false);
return GLib.SOURCE_REMOVE;
});
}
}
_printTimer(second) {
const min = Math.floor(second / 60);
const minS = Math.floor(second % 60).toLocaleString('en-US', {
minimumIntegerDigits: 2,
useGrouping: false
});
// Print Timer in system Indicator and Toggle menu subLabel
this._updateLabelTimer(min + ':' + minS);
}
_removeTimer() {
// End timer
this._timerEnable = false;
// Flush and hide timer label
this._updateLabelTimer(null);
this._timerLabel.visible = false;
// Remove timer
if ((this._timeOut !== null) || (this._timePrint !== null)) {
GLib.Source.remove(this._timeOut);
GLib.Source.remove(this._timePrint);
this._timeOut = null;
this._timePrint = null;
}
}
_updateLabelTimer(text) {
this._timerLabel.text = text;
this._caffeineToggle.menu.setHeader(this._caffeineToggle.finalTimerMenuIcon, _('Caffeine Timer'), text);
this._caffeineToggle.subtitle = text;
}
_handleScrollEvent(event) {
switch (event.get_scroll_direction()) {
case Clutter.ScrollDirection.UP:
if (!this._state) {
// User state on - UP
this._settings.set_boolean(TOGGLE_STATE_KEY, true);
}
break;
case Clutter.ScrollDirection.DOWN:
if (this._state) {
// Stop timer
this._removeTimer();
// User state off - DOWN
this._settings.set_boolean(TOGGLE_STATE_KEY, false);
}
break;
}
}
_inhibitorAdded(proxy, sender, [object]) {
this._sessionManager.GetInhibitorsRemote(([inhibitors]) => {
// Get the first added request
let requestedId = this._inhibitionAddedFifo.shift();
for (let i of inhibitors) {
let inhibitor = new DBusSessionManagerInhibitorProxy(Gio.DBus.session,
'org.gnome.SessionManager',
i);
inhibitor.GetAppIdRemote((appId) => {
appId = String(appId);
let appData = this._appInhibitedData.get(appId);
if (appId !== '' && requestedId === appId && appData) {
appData.isInhibited = true;
appData.object = object;
this._appInhibitedData.set(appId, appData);
if (appId === 'user') {
this._saveUserState(true);
} else {
this._updateAppSubtitle(appId);
}
// Update state
if (this._state === false) {
this._saveMainState(true);
// Indicator icon
this._manageShowIndicator();
this._indicator.gicon = this._iconActivated;
// Shell OSD notifications
if (this._settings.get_boolean(SHOW_NOTIFICATIONS_KEY) && !this.inFullscreen) {
this._sendOSDNotification(true);
}
}
}
});
}
});
}
/* eslint-disable no-unused-vars */
_inhibitorRemoved(proxy, sender, [object]) {
/* eslint-enable no-unused-vars */
// Get the first removed request
let appId = this._inhibitionRemovedFifo.shift();
if (appId) {
let appData = this._appInhibitedData.get(appId);
if (appData) {
// Remove app from list
this._appInhibitedData.delete(appId);
if (appId === 'user') {
this._saveUserState(false);
} else {
this._updateAppSubtitle(null);
}
// Update state
if (this._appInhibitedData.size === 0) {
this._saveMainState(false);
// Indicator icon
this._manageShowIndicator();
this._indicator.gicon = this._iconDeactivated;
// Shell OSD notifications
if (this._settings.get_boolean(SHOW_NOTIFICATIONS_KEY)) {
this._sendOSDNotification(false);
}
}
}
}
}
_isToggleInhibited(appId) {
let appData = this._appInhibitedData.get(appId);
if (appData && appData.isToggled) {
return true;
} else {
return false;
}
}
_manageShowIndicator() {
if (this._state) {
this._indicator.visible = this._settings.get_enum(SHOW_INDICATOR_KEY) !== ShowIndicator.NEVER;
} else {
this._indicator.visible = this._settings.get_enum(SHOW_INDICATOR_KEY) === ShowIndicator.ALWAYS;
}
}
_manageScreenBlankState(isApp) {
let blankState = this._settings.get_enum(SCREEN_BLANK) === ControlContext.ALWAYS;
if (isApp) {
blankState = this._settings.get_enum(SCREEN_BLANK) > ControlContext.NEVER;
}
if (blankState) {
this.inhibitFlags = 4;
} else {
this.inhibitFlags = 12;
}
}
_manageNightLight(isEnable, isApp) {
let nightLightPref = this._settings.get_enum(NIGHT_LIGHT_KEY) === ControlContext.ALWAYS;
if (isApp) {
nightLightPref = this._settings.get_enum(NIGHT_LIGHT_KEY) > ControlContext.NEVER;
}
if (isEnable && (nightLightPref || this._nightLight && this._proxy.DisabledUntilTomorrow)) {
this._proxy.DisabledUntilTomorrow = false;
this._nightLight = false;
} else if (!isEnable && nightLightPref) {
this._proxy.DisabledUntilTomorrow = true;
this._nightLight = true;
}
}
_sendOSDNotification(state) {
const nightLightPref =
this._settings.get_enum(NIGHT_LIGHT_KEY) !== ControlContext.NEVER;
if (state) {
let message = _('Caffeine enabled');
if (nightLightPref && this._nightLight && this._proxy.NightLightActive) {
message = message + '. ' + _('Night Light paused');
}
Main.osdWindowManager.show(-1, this._iconActivated,
message, null, null);
} else {
let message = _('Caffeine disabled');
if (nightLightPref && !this._nightLight && this._proxy.NightLightActive) {
message = message + '. ' + _('Night Light resumed');
}
Main.osdWindowManager.show(-1, this._iconDeactivated,
message, null, null);
}
}
// Add the name of App as subtitle
_updateAppSubtitle(id) {
const listAppId = this._appInhibitedData.keys();
let appId = id !== null ? id : listAppId.next().value;
if (appId !== undefined) {
let appInfo = Gio.DesktopAppInfo.new(appId);
this._caffeineToggle.subtitle = appInfo !== null
? appInfo.get_display_name()
: null;
}
}
// Add the timer duration selected as subtitle
_updateTimerSubtitle() {
if (!this._settings.get_boolean(TOGGLE_STATE_KEY)) {
const timerDuration = this._settings.get_int(TIMER_KEY);
this._caffeineToggle.subtitle = timerDuration !== 0
? parseInt(timerDuration) + _(' minutes')
: null;
}
}
_updateAppConfigs() {
this._appConfigs.length = 0;
this._settings.get_strv(INHIBIT_APPS_KEY).forEach((appId) => {
// Check if app still exist
const appInfo = Gio.DesktopAppInfo.new(appId);
if (appInfo) {
this._appConfigs.push(appId);
}
});
// Remove inhibited app that are not in the list anymore
let inhibitedAppsToRemove = [...this._appInhibitedData.keys()]
.filter((id) => !this._appConfigs.includes(id));
inhibitedAppsToRemove.forEach((id) => {
this._manageScreenBlankState(true); // Allow blank screen
this._manageNightLight(true, true);
this.removeInhibit(id); // Uninhibit app
});
this._updateAppEventMode();
}
_updateMainState() {
if (this._settings.get_boolean(TOGGLE_STATE_KEY) !== this._state) {
this.toggleState();
}
// Add timer duration as Subtitle when disable
this._updateTimerSubtitle();
}
_saveUserState(state) {
this._userState = state;
this._settings.set_boolean(USER_ENABLED_KEY, state);
}
_saveMainState(state) {
this._state = state;
this._settings.set_boolean(TOGGLE_STATE_KEY, state);
}
_resetAppSignalId() {
if (this._appStateChangedSignalId > 0) {
this._appSystem.disconnect(this._appStateChangedSignalId);
this._appStateChangedSignalId = 0;
}
if (this._appDisplayChangedSignalId > 0) {
global.display.disconnect(this._appDisplayChangedSignalId);
this._appDisplayChangedSignalId = 0;
}
if (this._appWorkspaceChangedSignalId > 0) {
global.workspace_manager.disconnect(this._appWorkspaceChangedSignalId);
this._appWorkspaceChangedSignalId = 0;
}
if (this._appAddWindowSignalId > 0) {
this._activeWorkspace.disconnect(this._appAddWindowSignalId);
this._appAddWindowSignalId = 0;
}
if (this._appRemoveWindowSignalId > 0) {
this._activeWorkspace.disconnect(this._appRemoveWindowSignalId);
this._appRemoveWindowSignalId = 0;
}
}
_updateAppEventMode() {
let appsTriggeredMode = this._settings.get_enum(TRIGGER_APPS_MODE);
if (this._appConfigs.length === 0) {
this._resetAppSignalId();
} else {
switch (appsTriggeredMode) {
// TRIGGER APPS MODE: ON RUNNING
case AppsTrigger.ON_RUNNING:
if (this._appStateChangedSignalId === 0) {
this._appStateChangedSignalId =
this._appSystem.connect('app-state-changed',
this._appStateChanged.bind(this));
}
// Check if currently running App
this._appConfigs.forEach((id) => {
let app = this._appSystem.lookup_app(id);
if (app && app.get_state() !== Shell.AppState.STOPPED) {
this._appStateChanged(this._appSystem, app);
}
});
break;
// TRIGGER APPS MODE: ON FOCUS
case AppsTrigger.ON_FOCUS:
if (this._appDisplayChangedSignalId === 0) {
this._appDisplayChangedSignalId =
global.display.connect('notify::focus-window',
this._appWindowFocusChanged.bind(this));
}
// Check if currently focused App
this._appWindowFocusChanged();
break;
// TRIGGER APPS MODE: ON ACTIVE WORKSPACE
case AppsTrigger.ON_ACTIVE_WORKSPACE:
if (this._appWorkspaceChangedSignalId === 0) {
this._appWorkspaceChangedSignalId =
global.workspace_manager.connect('workspace-switched',
this._appWorkspaceChanged.bind(this));
}
// Check if App is currently on active workspace
this._appWorkspaceChanged();
break;
}
}
}
_toggleWorkspace() {
// Search for triggered apps on active workspace
this._appConfigs.forEach((appId) => {
let app = this._appSystem.lookup_app(appId);
let isOnWorkspace = app.is_on_workspace(this._activeWorkspace);
if (isOnWorkspace && !this._isToggleInhibited(appId)) {
this._manageScreenBlankState(true); // Allow blank screen
this._manageNightLight(false, true);
this.addInhibit(appId); // Inhibit app
} else if (!isOnWorkspace && this._isToggleInhibited(appId)) {
this._manageScreenBlankState(true); // Allow blank screen
this._manageNightLight(true, true);
this.removeInhibit(appId); // Uninhibit app
}
});
}
_appWorkspaceChanged() {
// Reset signal for Add/remove windows on workspace
if (this._appAddWindowSignalId > 0) {
this._activeWorkspace.disconnect(this._appAddWindowSignalId);
this._appAddWindowSignalId = 0;
}
if (this._appRemoveWindowSignalId > 0) {
this._activeWorkspace.disconnect(this._appRemoveWindowSignalId);
this._appRemoveWindowSignalId = 0;
}
// Get active workspace
this._activeWorkspace = global.workspace_manager.get_active_workspace();
// Add signal listener on add/remove windows for the active workspace
this._appAddWindowSignalId =
this._activeWorkspace.connect('window-added', (wkspace, window) => {
const type = window.get_window_type();
// Accept only normal window, ignore all other type (dialog, menu,...)
if (type === 0) {
// Add 100 ms delay to handle window detection
this._timeWorkspaceAdd = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 100, () => {
this._toggleWorkspace();
this._timeWorkspaceAdd = null;
return GLib.SOURCE_REMOVE;
});
}
});
this._appRemoveWindowSignalId =
this._activeWorkspace.connect('window-removed', (wkspace, window) => {
const type = window.get_window_type();
// Accept only normal window, ignore all other type (dialog, menu,...)
if (type === 0) {
// Add 100 ms delay to handle window detection
this._timeWorkspaceRemove = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 100, () => {
this._toggleWorkspace();
this._timeWorkspaceRemove = null;
return GLib.SOURCE_REMOVE;
});
}
});
// Check and toggle Caffeine
this._toggleWorkspace();
}
_appWindowFocusChanged() {
let winTrack = Shell.WindowTracker.get_default();
let appId = null;
let app = winTrack.focus_app;
if (app) {
appId = app.get_id();
}
if (this._appConfigs.includes(appId) && !this._isToggleInhibited(appId)) {
this._manageScreenBlankState(true); // Allow blank screen
this._manageNightLight(false, true);
this.addInhibit(appId); // Inhibit app
// Uninhibit previous focused apps
this._appInhibitedData.forEach((data, id) => {
if (id !== appId && id !== 'user') {
this.removeInhibit(id);
}
});
} else if (!this._appConfigs.includes(appId) && this._appInhibitedData.size !== 0) {
this._manageScreenBlankState(true); // Allow blank screen
this._manageNightLight(true, true);
// Uninhibit all apps
this._appInhibitedData.forEach((data, id) => {
if (id !== 'user') {
this.removeInhibit(id);
}
});
}
}
_appStateChanged(appSys, app) {
let appId = app.get_id();
let appState = app.get_state();
if (this._appConfigs.includes(appId)) {
// Block App state signal
appSys.block_signal_handler(this._appStateChangedSignalId);
// Allow blank screen
this._manageScreenBlankState(true);
if (appState === Shell.AppState.STOPPED && this._isToggleInhibited(appId)) {
this._manageNightLight(true, true);
this.removeInhibit(appId); // Uninhibit app
} else if (appState !== Shell.AppState.STOPPED && !this._isToggleInhibited(appId)) {
this._manageNightLight(false, true);
this.addInhibit(appId); // Inhibit app
}
// Add 200 ms delay before unblock state signal
this._timeAppUnblock = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 200, () => {
appSys.unblock_signal_handler(this._appStateChangedSignalId);
this._timeAppUnblock = null;
return GLib.SOURCE_REMOVE;
});
}
}
destroy() {
// Remove all inhibitors
this._appInhibitedData.forEach((data, appId) => this.removeInhibit(appId));
this._appInhibitedData.clear();
// Remove ToggleMenu
this.quickSettingsItems.forEach((item) => item.destroy());
// Disconnect from signals
if (this._settings.get_boolean(FULLSCREEN_KEY)) {
this._screen.disconnect(this._inFullscreenId);
}
if (this._inhibitorAddedId) {
this._sessionManager.disconnectSignal(this._inhibitorAddedId);
this._inhibitorAddedId = 0;
}
if (this._inhibitorRemovedId) {
this._sessionManager.disconnectSignal(this._inhibitorRemovedId);
this._inhibitorRemovedId = 0;
}
if (this._windowCreatedId) {
this._display.disconnect(this._windowCreatedId);
this._windowCreatedId = 0;
}
if (this._windowDestroyedId) {
global.window_manager.disconnect(this._windowDestroyedId);
this._windowDestroyedId = 0;
}
if (this._timeOut) {
GLib.Source.remove(this._timeOut);
this._timeOut = null;
}
if (this._timePrint) {
GLib.Source.remove(this._timePrint);
this._timePrint = null;
}
if (this._timeFullscreen) {
GLib.Source.remove(this._timeFullscreen);
this._timeFullscreen = null;
}
if (this._timeWorkspaceAdd) {
GLib.Source.remove(this._timeWorkspaceAdd);
this._timeWorkspaceAdd = null;
}
if (this._timeWorkspaceRemove) {
GLib.Source.remove(this._timeWorkspaceRemove);
this._timeWorkspaceRemove = null;
}
if (this._timeAppUnblock) {
GLib.Source.remove(this._timeAppUnblock);
this._timeAppUnblock = null;
}
this._resetAppSignalId();
// Disconnect settings signals
if (this.inhibitId) {
this._settings.disconnect(this.inhibitId);
this.inhibitId = undefined;
}
if (this.stateId) {
this._settings.disconnect(this.stateId);
this.stateId = undefined;
}
if (this.timerId) {
this._settings.disconnect(this.timerId);
this.timerId = undefined;
}
if (this.showTimerId) {
this._settings.disconnect(this.showTimerId);
this.showTimerId = undefined;
}
if (this.indicatorId) {
this._settings.disconnect(this.indicatorId);
this.indicatorId = undefined;
}
if (this.showIndicatorId) {
this._settings.disconnect(this.showIndicatorId);
this.showIndicatorId = undefined;
}
if (this.triggerId) {
this._settings.disconnect(this.triggerId);
this.triggerId = undefined;
}
this._appConfigs.length = 0;
this._settings = null;
super.destroy();
}
});
export default class CaffeineExtension extends Extension {
enable() {
this._settings = this.getSettings();
this._caffeineIndicator = new Caffeine(this._settings, this.path);
// Register shortcut
Main.wm.addKeybinding(TOGGLE_SHORTCUT, this._settings,
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
Shell.ActionMode.ALL, () => {
this._caffeineIndicator.toggleState();
});
}
disable() {
this._caffeineIndicator.destroy();
this._caffeineIndicator = null;
// Unregister shortcut
Main.wm.removeKeybinding(TOGGLE_SHORTCUT);
}
}