diff options
| author | nippy <you@example.com> | 2026-04-18 13:49:56 +0200 |
|---|---|---|
| committer | nippy <you@example.com> | 2026-04-18 13:49:56 +0200 |
| commit | 5d94c0a7d44a2255b81815a52a7056a94a39842d (patch) | |
| tree | 759bdea9645c6a62f9e1e4c001f7d81cccd120d2 /raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/dbus-service/timer.js | |
| parent | e79cdf210b267f21a186255ce1a4d50029439d54 (diff) | |
| download | RaveOS-PKGBUILD-5d94c0a7d44a2255b81815a52a7056a94a39842d.tar.gz RaveOS-PKGBUILD-5d94c0a7d44a2255b81815a52a7056a94a39842d.zip | |
update Raveos themes
Diffstat (limited to 'raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/dbus-service/timer.js')
| -rw-r--r-- | raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/dbus-service/timer.js | 286 |
1 files changed, 286 insertions, 0 deletions
diff --git a/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/dbus-service/timer.js b/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/dbus-service/timer.js new file mode 100644 index 0000000..6c439ab --- /dev/null +++ b/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/dbus-service/timer.js @@ -0,0 +1,286 @@ +// SPDX-FileCopyrightText: 2023 Deminder <tremminder@gmail.com> +// SPDX-License-Identifier: GPL-3.0-or-later + +import GLib from 'gi://GLib'; +import Gio from 'gi://Gio'; +import { + gettext as _, + ngettext as _n, + pgettext as C_, +} from '../modules/translation.js'; + +import { logDebug, throttleTimeout } from '../modules/util.js'; +import * as Control from './control.js'; +import { + longDurationString, + ScheduleInfo, + getShutdownScheduleFromSettings, + getSliderMinutesFromSettings, +} from '../modules/schedule-info.js'; +import { + actionLabel, + Action, + mapLegacyAction, + UnsupportedActionError, +} from './action.js'; + +const Signals = imports.signals; + +export class Timer { + _timerCancellable = null; + _updateScheduleCancel = null; + _action = new Action(); + info = { + externalShutdown: new ScheduleInfo({ + external: true, + }), + externalWake: new ScheduleInfo({ + external: false, + mode: 'wake', + }), + internalShutdown: new ScheduleInfo({ mode: 'PowerOff' }), + }; + + constructor({ settings }) { + this._settings = settings; + + const [updateScheduleThrottled, updateScheduleCancel] = throttleTimeout( + () => this.updateSchedule(), + 20 + ); + this._updateScheduleCancel = updateScheduleCancel; + this._settingsIds = [ + 'root-mode-value', + 'shutdown-timestamp-value', + 'shutdown-mode-value', + ].map(settingName => + settings.connect(`changed::${settingName}`, updateScheduleThrottled) + ); + + this.updateSchedule(); + } + + destroy() { + if (this._settings === null) { + throw new Error('should not destroy twice'); + } + // Disconnect settings + this._settingsIds.forEach(id => this._settings.disconnect(id)); + this._settingsIds = []; + this._settings = null; + + // Cancel schedule updates + if (this._updateScheduleCancel !== null) { + this._updateScheduleCancel(); + this._updateScheduleCancel = null; + } + + // Cancel internal timer + if (this._timerCancellable !== null) { + this._timerCancellable.cancel(); + this._timerCancellable = null; + } + + // External schedules (for 'shutdown' and 'wake') are not stopped + } + + updateSchedule() { + const oldInternal = this.info.internalShutdown; + const internal = getShutdownScheduleFromSettings(this._settings); + this.info.internalShutdown = internal; + logDebug( + `[updateSchedule] internal schedule: ${internal.label} (old internal schedule: ${oldInternal.label})` + ); + this._updateRootModeProtection(oldInternal); + if ( + internal.mode !== oldInternal.mode || + internal.deadline !== oldInternal.deadline + ) { + this.emit('change'); + if (internal.scheduled) { + if (internal.minutes > 0) { + // Show schedule info + this.emit( + 'message', + C_('StartSchedulePopup', '%s in %s').format( + actionLabel(internal.mode), + longDurationString( + internal.minutes, + h => _n('%s hour', '%s hours', h), + m => _n('%s minute', '%s minutes', m) + ) + ) + ); + } else { + logDebug(`[updateSchedule] hidden message for '< 1 minute' schedule`); + } + if (this._timerCancellable !== null) { + this._timerCancellable.cancel(); + this._timerCancellable = null; + } + this.executeActionDelayed() + .then(() => { + logDebug('[executeActionDelayed] done'); + }) + .catch(err => { + console.error('executeActionDelayed', err); + }); + } else { + if (this._timerCancellable !== null) { + this._timerCancellable.cancel(); + this._timerCancellable = null; + } + if (oldInternal.scheduled) { + this.emit('message', _('Shutdown Timer stopped')); + } + } + } + } + + async executeAction() { + const internal = this.info.internalShutdown; + if (!internal.scheduled) { + logDebug(`Refusing to exectute non scheduled action! '${internal.mode}'`); + return; + } + logDebug(`Running '${internal.mode}' timer action...`); + try { + this.emit('change'); + // Refresh root mode protection + await Promise.all([ + this._updateRootModeProtection(), + // Do shutdown + this._action.shutdownAction( + internal.mode, + this._settings.get_boolean('show-end-session-dialog-value') + ), + ]); + } catch (err) { + if (/* destroyed */ this._settings === null) { + throw err; + } + const newInternal = this.info.internalShutdown; + if (newInternal.scheduled && newInternal.secondsLeft > 0) { + logDebug('[timer] Replaced by new schedule.'); + return; + } + if (err instanceof UnsupportedActionError) { + this.emit( + 'message', + _('%s is not supported!').format(actionLabel(internal.mode)) + ); + } + } + await this.toggleShutdown(false, ''); + } + + async executeActionDelayed() { + const internal = this.info.internalShutdown; + const secs = internal.secondsLeft; + if (secs > 0) { + logDebug(`Started delayed action: ${internal.minutes}min remaining`); + try { + this._timerCancellable = new Gio.Cancellable(); + await Control.sleepUntilDeadline( + internal.deadline, + this._timerCancellable + ); + this._timerCancellable = null; + } catch { + logDebug(`Canceled delayed action: ${internal.minutes}min remaining`); + return; + } + } + await this.executeAction(); + } + + async toggleWake(wake) { + try { + logDebug('[toggleWake] wake', wake); + await this._action.wakeAction( + wake, + getSliderMinutesFromSettings(this._settings, 'wake') + ); + this.emit('change-external'); + } catch (err) { + this.emit( + 'message', + C_('Error', '%s\n%s').format(_('Wake action failed!'), err) + ); + this._settings.set_int('shutdown-timestamp-value', -1); + } + } + + async toggleShutdown(shutdown, legacyAction) { + // Update shutdown action + const action = mapLegacyAction(legacyAction); + if (action === undefined) { + this.emit( + 'message', + _('Unknown shutdown action: "%s"!').format(legacyAction) + ); + return; + } + logDebug('[toggleShutdown] shutdown', shutdown, 'action', action); + if (action !== '') { + this._settings.set_string('shutdown-mode-value', action); + } + + // Update shutdown timestamp + this._settings.set_int( + 'shutdown-timestamp-value', + shutdown + ? GLib.DateTime.new_now_utc().to_unix() + + Math.max( + 1, + getSliderMinutesFromSettings(this._settings, 'shutdown') * 60 + ) + : -1 + ); + if (shutdown) { + await this._action.inhibitSuspend(); + } else { + await this._action.uninhibitSuspend(); + } + if (this._settings.get_boolean('auto-wake-value')) { + await this.toggleWake(shutdown); + } + } + + get state() { + return this.info.internalShutdown.scheduled + ? this.info.internalShutdown.secondsLeft > 0 + ? 'active' + : 'action' + : 'inactive'; + } + + /** + * Ensure that shutdown/reboot is executed even if the Timer fails by running + * the `shutdown` command delayed by 1 minute. + */ + async _updateRootModeProtection(oldInternal) { + if (this._settings.get_boolean('root-mode-value')) { + const internal = this.info.internalShutdown; + logDebug('[updateRootModeProtection] mode ', internal.mode); + try { + if (oldInternal?.scheduled && oldInternal.mode !== internal.mode) { + await Control.stopRootModeProtection(oldInternal); + } + if (internal.scheduled) { + await Control.startRootModeProtection(internal); + } else { + await Control.stopRootModeProtection(internal); + } + } catch (err) { + this.emit( + 'message', + C_('Error', '%s\n%s').format(_('Root mode protection failed!'), err) + ); + console.error('[updateRootModeProtection]', err); + } + this.emit('change-external'); + } + } +} +Signals.addSignalMethods(Timer.prototype); |