From 62db8dc16e46185a7dade99cb7c419dd2f338cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Tue, 13 Jun 2023 21:36:20 +0200 Subject: [PATCH] status/backlight: Add new quick toggle Settings no longer exposes a slider for the keyboard brightness, leaving keyboard shortcuts as the only way of adjusting it. This is good enough in most cases, because devices with keyboard backlight usually include corresponding keys on their keyboard. However for devices without those keys, it would be good for the settings to be exposed somewhere again. Quick settings seems like a more appropriate place than "proper" Settings, since it gives quick access that doesn't require a major focus change. https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/6765 Part-of: --- ...rg.gnome.SettingsDaemon.Power.Keyboard.xml | 7 + .../gnome-shell-dbus-interfaces.gresource.xml | 1 + data/gnome-shell-icons.gresource.xml | 3 + .../keyboard-brightness-high-symbolic.svg | 5 + .../keyboard-brightness-low-symbolic.svg | 5 + .../keyboard-brightness-medium-symbolic.svg | 5 + .../widgets/_quick-settings.scss | 10 + js/js-resources.gresource.xml | 1 + js/ui/panel.js | 4 + js/ui/status/backlight.js | 205 ++++++++++++++++++ po/POTFILES.in | 1 + 11 files changed, 247 insertions(+) create mode 100644 data/dbus-interfaces/org.gnome.SettingsDaemon.Power.Keyboard.xml create mode 100644 data/icons/scalable/status/keyboard-brightness-high-symbolic.svg create mode 100644 data/icons/scalable/status/keyboard-brightness-low-symbolic.svg create mode 100644 data/icons/scalable/status/keyboard-brightness-medium-symbolic.svg create mode 100644 js/ui/status/backlight.js diff --git a/data/dbus-interfaces/org.gnome.SettingsDaemon.Power.Keyboard.xml b/data/dbus-interfaces/org.gnome.SettingsDaemon.Power.Keyboard.xml new file mode 100644 index 000000000..75feb35ea --- /dev/null +++ b/data/dbus-interfaces/org.gnome.SettingsDaemon.Power.Keyboard.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/data/gnome-shell-dbus-interfaces.gresource.xml b/data/gnome-shell-dbus-interfaces.gresource.xml index 31d1f1266..c3954ba39 100644 --- a/data/gnome-shell-dbus-interfaces.gresource.xml +++ b/data/gnome-shell-dbus-interfaces.gresource.xml @@ -37,6 +37,7 @@ org.gnome.SessionManager.Presence.xml org.gnome.SessionManager.xml org.gnome.SettingsDaemon.Color.xml + org.gnome.SettingsDaemon.Power.Keyboard.xml org.gnome.SettingsDaemon.Power.Screen.xml org.gnome.SettingsDaemon.Rfkill.xml org.gnome.SettingsDaemon.Wacom.xml diff --git a/data/gnome-shell-icons.gresource.xml b/data/gnome-shell-icons.gresource.xml index a5dd1247b..ceec3b89d 100644 --- a/data/gnome-shell-icons.gresource.xml +++ b/data/gnome-shell-icons.gresource.xml @@ -22,6 +22,9 @@ scalable/actions/screenshot-ui-window-symbolic.svg scalable/actions/screenshot-recorded-symbolic.svg scalable/status/background-app-ghost-symbolic.svg + scalable/status/keyboard-brightness-high-symbolic.svg + scalable/status/keyboard-brightness-low-symbolic.svg + scalable/status/keyboard-brightness-medium-symbolic.svg scalable/status/keyboard-caps-lock-symbolic.svg scalable/status/keyboard-enter-symbolic.svg scalable/status/keyboard-hide-symbolic.svg diff --git a/data/icons/scalable/status/keyboard-brightness-high-symbolic.svg b/data/icons/scalable/status/keyboard-brightness-high-symbolic.svg new file mode 100644 index 000000000..ca654a9c6 --- /dev/null +++ b/data/icons/scalable/status/keyboard-brightness-high-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/data/icons/scalable/status/keyboard-brightness-low-symbolic.svg b/data/icons/scalable/status/keyboard-brightness-low-symbolic.svg new file mode 100644 index 000000000..15a86c99d --- /dev/null +++ b/data/icons/scalable/status/keyboard-brightness-low-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/data/icons/scalable/status/keyboard-brightness-medium-symbolic.svg b/data/icons/scalable/status/keyboard-brightness-medium-symbolic.svg new file mode 100644 index 000000000..bb51f14d1 --- /dev/null +++ b/data/icons/scalable/status/keyboard-brightness-medium-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/data/theme/gnome-shell-sass/widgets/_quick-settings.scss b/data/theme/gnome-shell-sass/widgets/_quick-settings.scss index 88d938409..870dba47d 100644 --- a/data/theme/gnome-shell-sass/widgets/_quick-settings.scss +++ b/data/theme/gnome-shell-sass/widgets/_quick-settings.scss @@ -163,6 +163,16 @@ .device-subtitle { color: transparentize($fg_color, 0.5); } +.keyboard-brightness-item .slider { + min-height: 16px; +} + +.keyboard-brightness-level { + spacing: $base_padding; + + .button:checked { @include button(default, $c:$selected_bg_color); } +} + // background apps .background-apps-quick-toggle { diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml index 1e36ed4a1..e72cd31e1 100644 --- a/js/js-resources.gresource.xml +++ b/js/js-resources.gresource.xml @@ -137,6 +137,7 @@ ui/status/accessibility.js ui/status/autoRotate.js ui/status/backgroundApps.js + ui/status/backlight.js ui/status/brightness.js ui/status/camera.js ui/status/darkMode.js diff --git a/js/ui/panel.js b/js/ui/panel.js index b44635f56..3a99c9e52 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -30,6 +30,7 @@ const SystemStatus = imports.ui.status.system; const LocationStatus = imports.ui.status.location; const NightLightStatus = imports.ui.status.nightLight; const DarkModeStatus = imports.ui.status.darkMode; +const BacklightStatus = imports.ui.status.backlight; const ThunderboltStatus = imports.ui.status.thunderbolt; const AutoRotateStatus = imports.ui.status.autoRotate; const BackgroundAppsStatus = imports.ui.status.backgroundApps; @@ -398,6 +399,7 @@ class QuickSettings extends PanelMenu.Button { this._thunderbolt = new ThunderboltStatus.Indicator(); this._nightLight = new NightLightStatus.Indicator(); this._darkMode = new DarkModeStatus.Indicator(); + this._backlight = new BacklightStatus.Indicator(); this._powerProfiles = new PowerProfileStatus.Indicator(); this._rfkill = new RFKillStatus.Indicator(); this._autoRotate = new AutoRotateStatus.Indicator(); @@ -414,6 +416,7 @@ class QuickSettings extends PanelMenu.Button { if (this._network) this._indicators.add_child(this._network); this._indicators.add_child(this._darkMode); + this._indicators.add_child(this._backlight); this._indicators.add_child(this._powerProfiles); if (this._bluetooth) this._indicators.add_child(this._bluetooth); @@ -439,6 +442,7 @@ class QuickSettings extends PanelMenu.Button { this._addItems(this._powerProfiles.quickSettingsItems); this._addItems(this._nightLight.quickSettingsItems); this._addItems(this._darkMode.quickSettingsItems); + this._addItems(this._backlight.quickSettingsItems); this._addItems(this._rfkill.quickSettingsItems); this._addItems(this._autoRotate.quickSettingsItems); this._addItems(this._unsafeMode.quickSettingsItems); diff --git a/js/ui/status/backlight.js b/js/ui/status/backlight.js new file mode 100644 index 000000000..cb2904ece --- /dev/null +++ b/js/ui/status/backlight.js @@ -0,0 +1,205 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- +/* exported Indicator */ + +const {Clutter, Gio, GObject, St} = imports.gi; + +const {QuickMenuToggle, SystemIndicator} = imports.ui.quickSettings; + +const PopupMenu = imports.ui.popupMenu; +const {Slider} = imports.ui.slider; + +const {loadInterfaceXML} = imports.misc.fileUtils; + +const BUS_NAME = 'org.gnome.SettingsDaemon.Power'; +const OBJECT_PATH = '/org/gnome/SettingsDaemon/Power'; + +const BrightnessInterface = loadInterfaceXML('org.gnome.SettingsDaemon.Power.Keyboard'); +const BrightnessProxy = Gio.DBusProxy.makeProxyWrapper(BrightnessInterface); + +const SliderItem = GObject.registerClass({ + Properties: { + 'value': GObject.ParamSpec.int( + 'value', '', '', + GObject.ParamFlags.READWRITE, + 0, 100, 0), + }, +}, class SliderItem extends PopupMenu.PopupBaseMenuItem { + constructor() { + super({ + activate: false, + style_class: 'keyboard-brightness-item', + }); + + this._slider = new Slider(0); + + this._sliderChangedId = this._slider.connect('notify::value', + () => this.notify('value')); + this._slider.accessible_name = _('Keyboard Brightness'); + + this.add_child(this._slider); + } + + get value() { + return this._slider.value * 100; + } + + set value(value) { + this._slider.block_signal_handler(this._sliderChangedId); + this._slider.value = value / 100; + this._slider.unblock_signal_handler(this._sliderChangedId); + } +}); + +const DiscreteItem = GObject.registerClass({ + Properties: { + 'value': GObject.ParamSpec.int( + 'value', '', '', + GObject.ParamFlags.READWRITE, + 0, 100, 0), + 'n-levels': GObject.ParamSpec.int( + 'n-levels', '', '', + GObject.ParamFlags.READWRITE, + 1, 3, 1), + }, +}, class DiscreteItem extends St.BoxLayout { + constructor() { + super({ + style_class: 'popup-menu-item', + reactive: true, + }); + + this._levelButtons = new Map(); + this._addLevelButton('off', _('Off'), 'keyboard-brightness-low-symbolic'); + this._addLevelButton('low', _('Low'), 'keyboard-brightness-medium-symbolic'); + this._addLevelButton('high', _('High'), 'keyboard-brightness-high-symbolic'); + + this.connect('notify::n-levels', () => this._syncLevels()); + this.connect('notify::value', () => this._syncChecked()); + this._syncLevels(); + } + + _valueToLevel(value) { + const checkedIndex = Math.round(value * (this.nLevels - 1) / 100); + if (checkedIndex === this.nLevels - 1) + return 'high'; + + return [...this._levelButtons.keys()].at(checkedIndex); + } + + _levelToValue(level) { + const keyIndex = [...this._levelButtons.keys()].indexOf(level); + return 100 * Math.min(keyIndex, this.nLevels - 1) / (this.nLevels - 1); + } + + _addLevelButton(key, label, iconName) { + const box = new St.BoxLayout({ + style_class: 'keyboard-brightness-level', + vertical: true, + x_expand: true, + }); + + box.button = new St.Button({ + styleClass: 'icon-button', + canFocus: true, + iconName, + }); + box.add_child(box.button); + + box.button.connect('clicked', () => { + this.value = this._levelToValue(key); + }); + + box.add_child(new St.Label({ + text: label, + x_align: Clutter.ActorAlign.CENTER, + })); + + this.add_child(box); + this._levelButtons.set(key, box); + } + + _syncLevels() { + this._levelButtons.get('off').visible = this.nLevels > 0; + this._levelButtons.get('high').visible = this.nLevels > 1; + this._levelButtons.get('low').visible = this.nLevels > 2; + } + + _syncChecked() { + const checkedKey = this._valueToLevel(this.value); + this._levelButtons.forEach((b, k) => { + b.button.checked = k === checkedKey; + }); + } +}); + +const KeyboardBrightnessToggle = GObject.registerClass( +class KeyboardBrightnessToggle extends QuickMenuToggle { + _init() { + super._init({ + title: _('Keyboard'), + iconName: 'display-brightness-symbolic', + }); + + this._proxy = new BrightnessProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH, + (proxy, error) => { + if (error) + console.error(error.message); + else + this._proxy.connect('g-properties-changed', () => this._sync()); + this._sync(); + }); + + this.connect('clicked', () => { + this._proxy.Brightness = this.checked ? 0 : 100; + }); + + this._sliderItem = new SliderItem(); + this.menu.box.add_child(this._sliderItem); + + this._discreteItem = new DiscreteItem(); + this.menu.box.add_child(this._discreteItem); + + this._sliderItem.bind_property('visible', + this._discreteItem, 'visible', + GObject.BindingFlags.INVERT_BOOLEAN | + GObject.BindingFlags.SYNC_CREATE); + + this._sliderItem.bind_property('value', + this._discreteItem, 'value', + GObject.BindingFlags.SYNC_CREATE); + + this._sliderItem.connect('notify::value', + () => (this._proxy.Brightness = this._sliderItem.value)); + + this._discreteItem.connect('notify::value', + () => (this._proxy.Brightness = this._discreteItem.value)); + } + + _sync() { + const brightness = this._proxy.Brightness; + const visible = Number.isInteger(brightness) && brightness >= 0; + this.visible = visible; + if (!visible) + return; + + this.checked = brightness > 0; + const useSlider = this._proxy.Steps >= 4; + + this._sliderItem.set({ + visible: useSlider, + value: brightness, + }); + + if (!useSlider) + this._discreteItem.nLevels = this._proxy.Steps; + } +}); + +var Indicator = GObject.registerClass( +class Indicator extends SystemIndicator { + _init() { + super._init(); + + this.quickSettingsItems.push(new KeyboardBrightnessToggle()); + } +}); diff --git a/po/POTFILES.in b/po/POTFILES.in index 098142518..74a521202 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -60,6 +60,7 @@ js/ui/shellMountOperation.js js/ui/status/accessibility.js js/ui/status/autoRotate.js js/ui/status/backgroundApps.js +js/ui/status/backlight.js js/ui/status/bluetooth.js js/ui/status/brightness.js js/ui/status/darkMode.js