aboutsummaryrefslogtreecommitdiff
path: root/.local/share/gnome-shell/extensions/display-profile-switcher@rbenencia.name/prefs.js
diff options
context:
space:
mode:
Diffstat (limited to '.local/share/gnome-shell/extensions/display-profile-switcher@rbenencia.name/prefs.js')
-rw-r--r--.local/share/gnome-shell/extensions/display-profile-switcher@rbenencia.name/prefs.js157
1 files changed, 157 insertions, 0 deletions
diff --git a/.local/share/gnome-shell/extensions/display-profile-switcher@rbenencia.name/prefs.js b/.local/share/gnome-shell/extensions/display-profile-switcher@rbenencia.name/prefs.js
new file mode 100644
index 0000000..3f44f55
--- /dev/null
+++ b/.local/share/gnome-shell/extensions/display-profile-switcher@rbenencia.name/prefs.js
@@ -0,0 +1,157 @@
+import Adw from 'gi://Adw';
+import Gio from 'gi://Gio';
+import GLib from 'gi://GLib';
+import Gtk from 'gi://Gtk';
+
+import {ExtensionPreferences} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
+
+import {
+ PROFILE_VARIANT_TYPE,
+ ProfileIndex,
+ getTextScalingFactor,
+ setTextScalingFactor,
+} from './display-config.js';
+
+const RULE_FIELDS = [
+ ['connector', 'Connector'],
+ ['vendor', 'Vendor'],
+ ['product', 'Product'],
+ ['serial', 'Serial'],
+];
+
+export default class DisplayProfileSwitcherPreferences extends ExtensionPreferences {
+ fillPreferencesWindow(window) {
+ this._settings = this.getSettings();
+ this._profiles = this._settings.get_value('profiles').deepUnpack();
+
+ const page = new Adw.PreferencesPage({title: 'Display Profiles', icon_name: 'video-display-symbolic'});
+ const behavior = new Adw.PreferencesGroup({title: 'Behavior'});
+ const autoApply = new Adw.SwitchRow({
+ title: 'Apply automatically',
+ subtitle: 'Apply the first matching profile shortly after displays change.',
+ });
+ this._settings.bind('automatically-apply', autoApply, 'active', Gio.SettingsBindFlags.DEFAULT);
+ behavior.add(autoApply);
+ page.add(behavior);
+
+ this._profilesGroup = new Adw.PreferencesGroup({
+ title: 'Saved profiles',
+ description: 'Rules are case-insensitive JavaScript regular expressions. Every rule must match a connected monitor.',
+ });
+ this._profileRows = [];
+ page.add(this._profilesGroup);
+ window.add(page);
+ this._rebuildProfiles();
+
+ window.connect('close-request', () => {
+ this._settings = null;
+ this._profiles = null;
+ this._profilesGroup = null;
+ this._profileRows = null;
+ });
+ }
+
+ _rebuildProfiles() {
+ for (const row of this._profileRows)
+ this._profilesGroup.remove(row);
+ this._profileRows = [];
+
+ if (!this._profiles.length) {
+ const row = new Adw.ActionRow({
+ title: 'No profiles saved',
+ subtitle: 'Open the Displays quick-settings menu and choose “Save current layout as profile”.',
+ });
+ this._profilesGroup.add(row);
+ this._profileRows.push(row);
+ return;
+ }
+
+ this._profiles.forEach((profile, profileIndex) => {
+ const row = this._profileRow(profile, profileIndex);
+ this._profilesGroup.add(row);
+ this._profileRows.push(row);
+ });
+ }
+
+ _profileRow(profile, profileIndex) {
+ const row = new Adw.ExpanderRow({title: profile[ProfileIndex.NAME], expanded: false});
+ const name = new Gtk.Entry({text: profile[ProfileIndex.NAME], hexpand: true, valign: Gtk.Align.CENTER});
+ const nameRow = new Adw.ActionRow({title: 'Name'});
+ nameRow.add_suffix(name);
+ row.add_row(nameRow);
+ name.connect('changed', entry => {
+ this._profiles[profileIndex][ProfileIndex.NAME] = entry.text;
+ row.title = entry.text || 'Unnamed profile';
+ this._saveProfiles();
+ });
+
+ profile[ProfileIndex.RULES].forEach((rule, ruleIndex) => {
+ const ruleRow = new Adw.ExpanderRow({title: `Monitor rule ${ruleIndex + 1}`, expanded: false});
+ for (const [field, label] of RULE_FIELDS) {
+ const entry = new Gtk.Entry({text: rule[field] ?? '', hexpand: true, valign: Gtk.Align.CENTER});
+ const fieldRow = new Adw.ActionRow({title: label});
+ fieldRow.add_suffix(entry);
+ ruleRow.add_row(fieldRow);
+ entry.connect('changed', widget => {
+ this._profiles[profileIndex][ProfileIndex.RULES][ruleIndex][field] = widget.text;
+ this._saveProfiles();
+ });
+ }
+ row.add_row(ruleRow);
+ });
+
+ this._addSavedLayoutRows(row, profile, profileIndex);
+
+ const remove = new Gtk.Button({label: 'Remove', valign: Gtk.Align.CENTER, css_classes: ['destructive-action']});
+ remove.connect('clicked', () => {
+ this._profiles.splice(profileIndex, 1);
+ this._saveProfiles();
+ this._rebuildProfiles();
+ });
+ row.add_suffix(remove);
+ return row;
+ }
+
+ _addSavedLayoutRows(row, profile, profileIndex) {
+ const logicalMonitors = profile[ProfileIndex.LOGICAL_MONITORS];
+ const layoutRow = new Adw.ActionRow({
+ title: 'Saved display layout',
+ subtitle: `${logicalMonitors.length} logical display${logicalMonitors.length === 1 ? '' : 's'}`,
+ });
+ row.add_row(layoutRow);
+
+ logicalMonitors.forEach(([x, y, scale, transform, primary, monitors], index) => {
+ const monitorSummary = monitors.map(([connector, mode]) => `${connector}: ${mode}`).join(', ');
+ const transformName = ['Normal', '90° clockwise', 'Upside-down', '90° counter-clockwise'][transform] ?? `Transform ${transform}`;
+ row.add_row(new Adw.ActionRow({
+ title: `Display ${index + 1}${primary ? ' (primary)' : ''}`,
+ subtitle: `${monitorSummary}; ${Math.round(scale * 100)}% display scale; ${transformName}; position ${x}, ${y}`,
+ }));
+ });
+
+ const savedTextScale = getTextScalingFactor(profile);
+ const textScale = new Gtk.SpinButton({
+ adjustment: new Gtk.Adjustment({lower: 0.5, upper: 3.0, step_increment: 0.05, page_increment: 0.25}),
+ digits: 2,
+ value: savedTextScale ?? 1.0,
+ valign: Gtk.Align.CENTER,
+ });
+ const textScaleRow = new Adw.ActionRow({
+ title: 'Text scale',
+ subtitle: savedTextScale === null
+ ? 'Not captured by this older profile. Set a value to apply it with the layout.'
+ : 'Applied with this display profile.',
+ });
+ textScaleRow.add_suffix(textScale);
+ row.add_row(textScaleRow);
+ textScale.connect('value-changed', widget => {
+ setTextScalingFactor(this._profiles[profileIndex], widget.get_value());
+ textScaleRow.subtitle = 'Applied with this display profile.';
+ this._saveProfiles();
+ });
+ }
+
+ _saveProfiles() {
+ this._settings.set_value('profiles', new GLib.Variant(PROFILE_VARIANT_TYPE, this._profiles));
+ }
+}
nihil fit ex nihilo