/* This file is part of Caffeine (gnome-shell-extension-caffeine). Caffeine 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 3 of the License, or (at your option) any later version. Caffeine 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 Caffeine. If not, see . Copyright 2022 Pakaoraki // From https://gitlab.com/skrewball/openweather/-/blob/master/src/prefs.js */ /* exported GeneralPage */ 'use strict'; import Adw from 'gi://Adw'; import Gtk from 'gi://Gtk'; import GObject from 'gi://GObject'; import Gdk from 'gi://Gdk'; import { gettext as _ } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'; const genParam = (type, name, ...dflt) => GObject.ParamSpec[type](name, name, name, GObject.ParamFlags.READWRITE, ...dflt); const TIMERS_DURATION = [ '05, 10, 30', '10, 20, 45', '15, 30, 60', '20, 40, 75', '30, 50, 80' ]; export var GeneralPage = GObject.registerClass( class CaffeineGeneralPage extends Adw.PreferencesPage { _init(settings, settingsKey) { super._init({ title: _('General'), icon_name: 'general-symbolic', name: 'GeneralPage' }); this._settings = settings; this._settingsKey = settingsKey; // Behavior group // -------------- let behaviorGroup = new Adw.PreferencesGroup({ title: _('Behavior') }); // Enable / Disable fullscreen apps let disableFullscreenSwitch = new Gtk.Switch({ valign: Gtk.Align.CENTER, active: this._settings.get_boolean(this._settingsKey.FULLSCREEN) }); let disableFullscreenRow = new Adw.ActionRow({ title: _('Enable for fullscreen apps'), subtitle: _('Automatically enable when an app enters fullscreen mode'), activatable_widget: disableFullscreenSwitch }); disableFullscreenRow.add_suffix(disableFullscreenSwitch); // Remember state let rememberStateSwitch = new Gtk.Switch({ valign: Gtk.Align.CENTER, active: this._settings.get_boolean(this._settingsKey.RESTORE) }); let rememberStateRow = new Adw.ActionRow({ title: _('Remember state'), subtitle: _('Remember the last state across sessions and reboots'), activatable_widget: rememberStateSwitch }); rememberStateRow.add_suffix(rememberStateSwitch); // Pause and resume Night Light let pauseNightLight = new Gtk.StringList(); pauseNightLight.append(_('Never')); pauseNightLight.append(_('Always')); pauseNightLight.append(_('For apps on list')); let pauseNightLightRow = new Adw.ComboRow({ title: _('Pause and resume Night Light'), subtitle: _('Toggles the night light together with Caffeine\'s state'), model: pauseNightLight, selected: this._settings.get_enum(this._settingsKey.NIGHT_LIGHT) }); // Allow blank screen let allowBlankScreen = new Gtk.StringList(); allowBlankScreen.append(_('Never')); allowBlankScreen.append(_('Always')); allowBlankScreen.append(_('For apps on list')); let allowBlankScreenRow = new Adw.ComboRow({ title: _('Allow screen blank'), subtitle: _('Allow turning off screen when Caffeine is enabled'), model: allowBlankScreen, selected: this._settings.get_enum(this._settingsKey.SCREEN_BLANK) }); // Add elements behaviorGroup.add(disableFullscreenRow); behaviorGroup.add(rememberStateRow); behaviorGroup.add(pauseNightLightRow); behaviorGroup.add(allowBlankScreenRow); this.add(behaviorGroup); // Timer group // -------------- let timerGroup = new Adw.PreferencesGroup({ title: _('Timer') }); const durationIndex = this._settings.get_int(this._settingsKey.DURATION_TIMER_INDEX); this.timerOptionRow = new Adw.ActionRow({ title: _('Durations'), activatable: true }); let adjustSliderTimer = new Gtk.Adjustment({ lower: 0, upper: 4, step_increment: 0.1, page_increment: 1, value: durationIndex }); this._updateTimerDuration(durationIndex); let sliderTimer = new Gtk.Scale({ valign: 'center', hexpand: true, width_request: '200px', round_digits: false, draw_value: false, orientation: 'horizontal', digits: 0, adjustment: adjustSliderTimer }); sliderTimer.add_mark(0, Gtk.PositionType.BOTTOM, null); sliderTimer.add_mark(1, Gtk.PositionType.BOTTOM, null); sliderTimer.add_mark(2, Gtk.PositionType.BOTTOM, null); sliderTimer.add_mark(3, Gtk.PositionType.BOTTOM, null); sliderTimer.add_mark(4, Gtk.PositionType.BOTTOM, null); this.timerOptionRow.add_suffix(sliderTimer); // Add elements timerGroup.add(this.timerOptionRow); this.add(timerGroup); // Shortcut group // -------------- let deleteShortcutButton = new Gtk.Button({ icon_name: 'edit-delete-symbolic', valign: Gtk.Align.CENTER, css_classes: ['error'], hexpand: false, vexpand: false }); let shortcutGroup = new Adw.PreferencesGroup({ title: _('Shortcut'), header_suffix: deleteShortcutButton }); // Keyboard shortcut this.shortcutKeyBoard = new ShortcutSettingWidget( this._settings, this._settingsKey.TOGGLE_SHORTCUT, _('Toggle shortcut'), _('Use Backspace to clear') ); // Hide/Show delete button if (!this.shortcutKeyBoard.isAcceleratorSet()) { deleteShortcutButton.visible = false; } // Add elements shortcutGroup.add(this.shortcutKeyBoard); this.add(shortcutGroup); // Bind signals // -------------- disableFullscreenSwitch.connect('notify::active', (widget) => { this._settings.set_boolean(this._settingsKey.FULLSCREEN, widget.get_active()); }); rememberStateSwitch.connect('notify::active', (widget) => { this._settings.set_boolean(this._settingsKey.RESTORE, widget.get_active()); }); pauseNightLightRow.connect('notify::selected', (widget) => { this._settings.set_enum(this._settingsKey.NIGHT_LIGHT, widget.selected); }); allowBlankScreenRow.connect('notify::selected', (widget) => { this._settings.set_enum(this._settingsKey.SCREEN_BLANK, widget.selected); }); sliderTimer.connect('change-value', (widget) => this._updateTimerDuration(widget.get_value())); deleteShortcutButton.connect('clicked', this._resetShortcut.bind(this)); this._settings.connect(`changed::${this._settingsKey.TOGGLE_SHORTCUT}`, () => { if (this.shortcutKeyBoard.isAcceleratorSet()) { deleteShortcutButton.visible = true; } else { deleteShortcutButton.visible = false; } }); } _updateTimerDuration(value) { const durationIndex = this._settings.get_int(this._settingsKey.DURATION_TIMER_INDEX); this.timerOptionRow.set_subtitle(_('Set to ') + TIMERS_DURATION[value] + _(' minutes')); if (durationIndex !== value) { this._settings.set_int(this._settingsKey.DURATION_TIMER_INDEX, value); } } _resetShortcut() { this.shortcutKeyBoard.resetAccelerator(); } }); /* * Shortcut Widget */ const ShortcutSettingWidget = class extends Adw.ActionRow { static { GObject.registerClass({ Properties: { shortcut: genParam('string', 'shortcut', '') }, Signals: { changed: { param_types: [GObject.TYPE_STRING] } } }, this); } constructor(settings, key, label, sublabel) { super({ title: label, subtitle: sublabel, activatable: true }); this.shortcutBox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL, halign: Gtk.Align.CENTER, spacing: 5, hexpand: false, vexpand: false }); this._key = key; this._settings = settings; this._description = sublabel; this.add_suffix(this.shortcutBox); this.shortLabel = new Gtk.ShortcutLabel({ disabled_text: _('New accelerator…'), valign: Gtk.Align.CENTER, hexpand: false, vexpand: false }); this.shortcutBox.append(this.shortLabel); // Bind signals this.connect('activated', this._onActivated.bind(this)); this.bind_property('shortcut', this.shortLabel, 'accelerator', GObject.BindingFlags.DEFAULT); [this.shortcut] = this._settings.get_strv(this._key); this.add_suffix(this.shortcutBox); } isAcceleratorSet() { if (this.shortLabel.get_accelerator()) { return true; } else { return false; } } resetAccelerator() { this.saveShortcut(); // Clear shortcut } _onActivated(widget) { let ctl = new Gtk.EventControllerKey(); let content = new Adw.StatusPage({ title: _('New accelerator…'), description: this._description, icon_name: 'preferences-desktop-keyboard-shortcuts-symbolic' }); this._editor = new Adw.Window({ modal: true, hide_on_close: true, transient_for: widget.get_root(), width_request: 480, height_request: 320, content }); this._editor.add_controller(ctl); ctl.connect('key-pressed', this._onKeyPressed.bind(this)); this._editor.present(); } _onKeyPressed(_widget, keyval, keycode, state) { let mask = state & Gtk.accelerator_get_default_mod_mask(); mask &= ~Gdk.ModifierType.LOCK_MASK; if (!mask && keyval === Gdk.KEY_Escape) { this._editor.close(); return Gdk.EVENT_STOP; } if (keyval === Gdk.KEY_BackSpace) { this.saveShortcut(); // Clear shortcut return Gdk.EVENT_STOP; } if (!this.isValidBinding(mask, keycode, keyval) || !this.isValidAccel(mask, keyval)) { return Gdk.EVENT_STOP; } this.saveShortcut(keyval, keycode, mask); return Gdk.EVENT_STOP; } saveShortcut(keyval, keycode, mask) { if (!keyval && !keycode) { this.shortcut = ''; } else { this.shortcut = Gtk.accelerator_name_with_keycode(null, keyval, keycode, mask); } this.emit('changed', this.shortcut); this._settings.set_strv(this._key, [this.shortcut]); this._editor.destroy(); } // Functions from https://gitlab.gnome.org/GNOME/gnome-control-center/-/blob/main/panels/keyboard/keyboard-shortcuts.c keyvalIsForbidden(keyval) { return [ // Navigation keys Gdk.KEY_Home, Gdk.KEY_Left, Gdk.KEY_Up, Gdk.KEY_Right, Gdk.KEY_Down, Gdk.KEY_Page_Up, Gdk.KEY_Page_Down, Gdk.KEY_End, Gdk.KEY_Tab, // Return Gdk.KEY_KP_Enter, Gdk.KEY_Return, Gdk.KEY_Mode_switch ].includes(keyval); } isValidBinding(mask, keycode, keyval) { return !(mask === 0 || mask === Gdk.ModifierType.SHIFT_MASK && keycode !== 0 && ((keyval >= Gdk.KEY_a && keyval <= Gdk.KEY_z) || (keyval >= Gdk.KEY_A && keyval <= Gdk.KEY_Z) || (keyval >= Gdk.KEY_0 && keyval <= Gdk.KEY_9) || (keyval >= Gdk.KEY_kana_fullstop && keyval <= Gdk.KEY_semivoicedsound) || (keyval >= Gdk.KEY_Arabic_comma && keyval <= Gdk.KEY_Arabic_sukun) || (keyval >= Gdk.KEY_Serbian_dje && keyval <= Gdk.KEY_Cyrillic_HARDSIGN) || (keyval >= Gdk.KEY_Greek_ALPHAaccent && keyval <= Gdk.KEY_Greek_omega) || (keyval >= Gdk.KEY_hebrew_doublelowline && keyval <= Gdk.KEY_hebrew_taf) || (keyval >= Gdk.KEY_Thai_kokai && keyval <= Gdk.KEY_Thai_lekkao) || (keyval >= Gdk.KEY_Hangul_Kiyeog && keyval <= Gdk.KEY_Hangul_J_YeorinHieuh) || (keyval === Gdk.KEY_space && mask === 0) || this.keyvalIsForbidden(keyval)) ); } isValidAccel(mask, keyval) { return Gtk.accelerator_valid(keyval, mask) || (keyval === Gdk.KEY_Tab && mask !== 0); } };