Explorar el Código

[gnome] Add auto rotate extensions

Colin Powell hace 1 año
padre
commit
fb9f4f5824

+ 172 - 0
gnome/.local/share/gnome-shell/extensions/screen-rotate@shyzus.github.io/busUtils.js

@@ -0,0 +1,172 @@
+/* busUtils.js
+*
+* Copyright (C) 2022  kosmospredanie, efosmark
+*
+* 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 3 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 <https://www.gnu.org/licenses/>.
+*/
+
+import GLib from 'gi://GLib';
+import Gio from 'gi://Gio';
+
+export const Methods = Object.freeze({
+  'verify': 0,
+  'temporary': 1,
+  'persistent': 2
+});
+
+export const Monitor = class Monitor {
+  constructor(variant) {
+    let unpacked = variant.unpack();
+    this.connector = unpacked[0].unpack()[0].unpack();
+
+    let modes = unpacked[1].unpack();
+    for (let i = 0; i < modes.length; i++) {
+      let mode = modes[i].unpack();
+      let id = mode[0].unpack();
+      let mode_props = mode[6].unpack();
+      if ('is-current' in mode_props) {
+        let is_current = mode_props['is-current'].unpack().get_boolean();
+        if (is_current) {
+          this.current_mode_id = id;
+          break;
+        }
+      }
+    }
+
+    let props = unpacked[2].unpack();
+    if ('is-underscanning' in props) {
+      this.is_underscanning = props['is-underscanning'].unpack().get_boolean();
+    } else {
+      this.is_underscanning = false;
+    }
+    if ('is-builtin' in props) {
+      this.is_builtin = props['is-builtin'].unpack().get_boolean();
+    } else {
+      this.is_builtin = false;
+    }
+  }
+}
+
+export const LogicalMonitor = class LogicalMonitor {
+  constructor(variant) {
+    let unpacked = variant.unpack();
+    this.x = unpacked[0].unpack();
+    this.y = unpacked[1].unpack();
+    this.scale = unpacked[2].unpack();
+    this.transform = unpacked[3].unpack();
+    this.primary = unpacked[4].unpack();
+    // [ [connector, vendor, product, serial]* ]
+    this.monitors = unpacked[5].deep_unpack();
+    this.properties = unpacked[6].unpack();
+    for (let key in this.properties) {
+      this.properties[key] = this.properties[key].unpack().unpack();
+    }
+  }
+}
+
+export const DisplayConfigState = class DisplayConfigState {
+  constructor(result) {
+    let unpacked = result.unpack();
+
+    this.serial = unpacked[0].unpack();
+
+    this.monitors = [];
+    let monitors = unpacked[1].unpack();
+    monitors.forEach(monitor_packed => {
+      let monitor = new Monitor(monitor_packed);
+      this.monitors.push(monitor);
+    });
+
+    this.logical_monitors = [];
+    let logical_monitors = unpacked[2].unpack();
+    logical_monitors.forEach(lmonitor_packed => {
+      let lmonitor = new LogicalMonitor(lmonitor_packed);
+      this.logical_monitors.push(lmonitor);
+    });
+
+    this.properties = unpacked[3].unpack();
+    for (let key in this.properties) {
+      this.properties[key] = this.properties[key].unpack().unpack();
+    }
+  }
+
+  get builtin_monitor() {
+    for (let i = 0; i < this.monitors.length; i++) {
+      let monitor = this.monitors[i];
+      if (monitor.is_builtin) {
+        return monitor;
+      }
+    }
+    return null;
+  }
+
+  get_monitor(connector) {
+    for (let i = 0; i < this.monitors.length; i++) {
+      let monitor = this.monitors[i];
+      if (monitor.connector === connector) {
+        return monitor;
+      }
+    }
+    return null;
+  }
+
+  get_logical_monitor_for(connector) {
+    for (let i = 0; i < this.logical_monitors.length; i++) {
+      let lmonitor = this.logical_monitors[i];
+      for (let j = 0; j < lmonitor.monitors.length; j++) {
+        let lm_connector = lmonitor.monitors[j][0];
+        if (connector === lm_connector) {
+          return lmonitor;
+        }
+      }
+    }
+    return null;
+  }
+
+  pack_to_apply(method) {
+    let packing = [this.serial, method, [], {}];
+    let logical_monitors = packing[2];
+    let properties = packing[3];
+
+    this.logical_monitors.forEach(lmonitor => {
+      let lmonitor_pack = [
+        lmonitor.x,
+        lmonitor.y,
+        lmonitor.scale,
+        lmonitor.transform,
+        lmonitor.primary,
+        []
+      ];
+      let monitors = lmonitor_pack[5];
+      for (let i = 0; i < lmonitor.monitors.length; i++) {
+        let connector = lmonitor.monitors[i][0];
+        let monitor = this.get_monitor(connector);
+        monitors.push([
+          connector,
+          monitor.current_mode_id,
+          {
+            'enable_underscanning': new GLib.Variant('b', monitor.is_underscanning)
+          }
+        ]);
+      }
+      logical_monitors.push(lmonitor_pack);
+    });
+
+    if ('layout-mode' in this.properties) {
+      properties['layout-mode'] = new GLib.Variant('b', this.properties['layout-mode']);
+    }
+
+    return new GLib.Variant('(uua(iiduba(ssa{sv}))a{sv})', packing);
+  }
+}

+ 225 - 0
gnome/.local/share/gnome-shell/extensions/screen-rotate@shyzus.github.io/extension.js

@@ -0,0 +1,225 @@
+/* extension.js
+* Copyright (C) 2023  kosmospredanie, shyzus, Shinigaminai
+*
+* 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 3 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 <https://www.gnu.org/licenses/>.
+*/
+
+import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js';
+
+import Gio from 'gi://Gio';
+
+import * as SystemActions from 'resource:///org/gnome/shell/misc/systemActions.js';
+
+import * as Rotator from './rotator.js'
+
+const ORIENTATION_LOCK_SCHEMA = 'org.gnome.settings-daemon.peripherals.touchscreen';
+const ORIENTATION_LOCK_KEY = 'orientation-lock';
+
+// Orientation names must match those provided by net.hadess.SensorProxy
+const Orientation = Object.freeze({
+  'normal': 0,
+  'left-up': 1,
+  'bottom-up': 2,
+  'right-up': 3
+});
+
+class SensorProxy {
+  constructor(rotate_cb) {
+    this._rotate_cb = rotate_cb;
+    this._proxy = null;
+    this._enabled = false;
+    this._watcher_id = Gio.bus_watch_name(
+      Gio.BusType.SYSTEM,
+      'net.hadess.SensorProxy',
+      Gio.BusNameWatcherFlags.NONE,
+      this.appeared.bind(this),
+      this.vanished.bind(this)
+    );
+  }
+
+  destroy() {
+    Gio.bus_unwatch_name(this._watcher_id);
+    if (this._enabled) this.disable();
+    this._proxy = null;
+  }
+
+  enable() {
+    this._enabled = true;
+    if (this._proxy === null) return;
+    this._proxy.call_sync('ClaimAccelerometer', null, Gio.DBusCallFlags.NONE, -1, null);
+  }
+
+  disable() {
+    this._enabled = false;
+    if (this._proxy === null) return;
+    this._proxy.call_sync('ReleaseAccelerometer', null, Gio.DBusCallFlags.NONE, -1, null);
+  }
+
+  appeared(connection, name, name_owner) {
+    this._proxy = Gio.DBusProxy.new_for_bus_sync(
+      Gio.BusType.SYSTEM, Gio.DBusProxyFlags.NONE, null,
+      'net.hadess.SensorProxy', '/net/hadess/SensorProxy', 'net.hadess.SensorProxy',
+      null);
+    this._proxy.connect('g-properties-changed', this.properties_changed.bind(this));
+    if (this._enabled) {
+      this._proxy.call_sync('ClaimAccelerometer', null, Gio.DBusCallFlags.NONE, -1, null);
+    }
+  }
+
+  vanished(connection, name) {
+    this._proxy = null;
+  }
+
+  properties_changed(proxy, changed, invalidated) {
+    if (!this._enabled) return;
+    let properties = changed.deep_unpack();
+    for (let [name, value] of Object.entries(properties)) {
+      if (name != 'AccelerometerOrientation') continue;
+      let target = value.unpack();
+      this._rotate_cb(target);
+    }
+  }
+}
+
+class ScreenAutorotate {
+  constructor(settings) {
+    this._system_actions = SystemActions.getDefault();
+    this._settings = settings;
+    this._system_actions_backup = null;
+    this._override_system_actions();
+    this._orientation_settings = new Gio.Settings({ schema_id: ORIENTATION_LOCK_SCHEMA });
+    this._orientation_settings.connect('changed::' + ORIENTATION_LOCK_KEY, this._orientation_lock_changed.bind(this));
+
+    this._sensor_proxy = new SensorProxy(this.rotate_to.bind(this));
+
+    this._state = false; // enabled or not
+
+    let locked = this._orientation_settings.get_boolean(ORIENTATION_LOCK_KEY);
+    if (!locked) this.enable();
+  }
+
+  _override_system_actions() {
+    this._system_actions_backup = {
+      '_updateOrientationLock': this._system_actions._updateOrientationLock
+    };
+
+    this._system_actions._updateOrientationLock = function() {
+      this._actions.get('lock-orientation').available = true;
+      this.notify('can-lock-orientation');
+    };
+
+    this._system_actions._updateOrientationLock();
+  }
+
+  _restore_system_actions() {
+    if (this._system_actions_backup === null) return;
+    this._system_actions._updateOrientationLock = this._system_actions_backup['_updateOrientationLock'];
+    this._system_actions._updateOrientationLock();
+    this._system_actions_backup = null;
+  }
+
+  _orientation_lock_changed() {
+    let locked = this._orientation_settings.get_boolean(ORIENTATION_LOCK_KEY);
+    if (this._state == locked) {
+      this.toggle();
+    }
+  }
+
+  destroy() {
+    this._sensor_proxy.destroy();
+    this._orientation_settings = null;
+    this._restore_system_actions();
+  }
+
+  toggle() {
+    if (this._state) {
+      this.disable();
+    } else {
+      this.enable();
+    }
+  }
+  enable() {
+    this._sensor_proxy.enable();
+    this._state = true;
+  }
+
+  disable() {
+    this._sensor_proxy.disable();
+    this._state = false;
+  }
+
+  rotate_to(orientation) {
+    const sensor = Orientation[orientation];
+    const invert_horizontal_direction = this._settings.get_boolean('invert-horizontal-rotation-direction');
+    const invert_vertical_direction = this._settings.get_boolean('invert-vertical-rotation-direction');
+    const offset = this._settings.get_int('orientation-offset');
+    let target = sensor; // Default to sensor output.
+
+    switch (sensor) {
+      case 0:
+        // sensor reports landscape
+        if (invert_horizontal_direction) {
+          target = 2;
+        }
+        break;
+      case 1:
+        // sensor reports portrait
+        if (invert_vertical_direction) {
+          target = 3;
+        }
+        break;
+      case 2:
+        // sensor reports landscape-inverted
+        if (invert_horizontal_direction) {
+          target = 0;
+        }
+        break;
+      case 3:
+        // sensor reports portrait-inverted
+        if (invert_vertical_direction) {
+          target = 1;
+        }
+        break;
+    }
+
+    target = (target + offset) % 4;
+    if (this._settings.get_boolean('debug-logging')) {
+      console.debug(`sensor=${Orientation[orientation]}`);
+      console.debug(`offset=${offset}`);
+      console.debug(`target=${target}`);
+    }
+    Rotator.rotate_to(target);
+  }
+}
+
+export default class ScreenAutoRotateExtension extends Extension {
+  enable() {
+    this._settings = this.getSettings();
+    this._ext = new ScreenAutorotate(this._settings);
+  }
+
+  disable() {
+    /*
+        Comment for unlock-dialog usage:
+        The unlock-dialog sesson-mode is usefull for this extension as it allows
+        the user to rotate their screen or lock rotation after their device may
+        have auto-locked. This provides the ability to log back in regardless of 
+        the orientation of the device in tablet mode.
+    */
+    this._settings = null;
+    this._ext.destroy();
+    this._ext = null;
+  }
+}
+

+ 17 - 0
gnome/.local/share/gnome-shell/extensions/screen-rotate@shyzus.github.io/metadata.json

@@ -0,0 +1,17 @@
+{
+  "_generated": "Generated by SweetTooth, do not edit",
+  "description": "Enable screen rotation regardless of touch mode. Fork of Screen Autorotate by Kosmospredanie.",
+  "gettext-domain": "gnome-shell-extension-screen-rotate",
+  "name": "Screen Rotate",
+  "session-modes": [
+    "unlock-dialog",
+    "user"
+  ],
+  "settings-schema": "org.gnome.shell.extensions.screen-rotate",
+  "shell-version": [
+    "45"
+  ],
+  "url": "https://github.com/shyzus/gnome-shell-extension-screen-autorotate",
+  "uuid": "screen-rotate@shyzus.github.io",
+  "version": 15
+}

+ 124 - 0
gnome/.local/share/gnome-shell/extensions/screen-rotate@shyzus.github.io/prefs.js

@@ -0,0 +1,124 @@
+/* prefs.js
+* Copyright (C) 2023  kosmospredanie, shyzus, Shinigaminai
+*
+* 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 3 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 <https://www.gnu.org/licenses/>.
+*/
+
+import Gtk from 'gi://Gtk';
+import Gio from 'gi://Gio';
+import Adw from 'gi://Adw';
+
+import { ExtensionPreferences } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
+
+export default class MyExtensionPreferences extends ExtensionPreferences {
+
+  fillPreferencesWindow(window) {
+    window._settings = this.getSettings();
+
+    const page = new Adw.PreferencesPage();
+    window.add(page);
+
+    const orientationGroup = new Adw.PreferencesGroup();
+    orientationGroup.set_title('Orientation Settings')
+    page.add(orientationGroup);
+
+    const debugGroup = new Adw.PreferencesGroup();
+    debugGroup.set_title('Debug Settings');
+    page.add(debugGroup);
+
+    const invertHorizontalRow = new Adw.ActionRow({
+      title: 'Invert horizontal rotation'
+    });
+    orientationGroup.add(invertHorizontalRow);
+
+    const invertVerticalRow = new Adw.ActionRow({
+      title: 'Invert vertical rotation'
+    });
+    orientationGroup.add(invertVerticalRow);
+
+    const flipOrientationRow = new Adw.ActionRow({
+      title: 'Flip orientation',
+      subtitle: 'e.g: Landscape to Portrait. Default is Landscape'
+    });
+    orientationGroup.add(flipOrientationRow);
+
+    const setOffsetRow = new Adw.ActionRow({
+      title: 'Set orientation offset',
+      subtitle: 'Valid offset range: 0 to 3. Default is 0\nExperiment with this in case\
+ orientation is incorrect due to the display being mounted in a non-landscape orientation\
+ e.g PineTab2 or GPD Pocket 3'
+    });
+
+    orientationGroup.add(setOffsetRow);
+
+    const toggleLoggingRow = new Adw.ActionRow({
+      title: 'Enable debug logging',
+      subtitle: 'Use "journalctl /usr/bin/gnome-shell -f" to see log output.'
+    });
+    debugGroup.add(toggleLoggingRow);
+
+    const invertHorizontalRotationSwitch = new Gtk.Switch({
+      active: window._settings.get_boolean('invert-horizontal-rotation-direction'),
+      valign: Gtk.Align.CENTER,
+    });
+
+    const invertVerticalRotationSwitch = new Gtk.Switch({
+      active: window._settings.get_boolean('invert-vertical-rotation-direction'),
+      valign: Gtk.Align.CENTER,
+    });
+
+    const flipOrientationSwitch = new Gtk.Switch({
+      active: window._settings.get_boolean('flip-orientation'),
+      valign: Gtk.Align.CENTER,
+    });
+
+    const setOffsetSpinButton = Gtk.SpinButton.new_with_range(0, 3, 1);
+    setOffsetSpinButton.value = window._settings.get_int('orientation-offset');
+
+    const toggleLoggingSwitch = new Gtk.Switch({
+      active: window._settings.get_boolean('debug-logging'),
+      valign: Gtk.Align.CENTER
+    });
+
+    window._settings.bind('invert-horizontal-rotation-direction',
+      invertHorizontalRotationSwitch, 'active', Gio.SettingsBindFlags.DEFAULT);
+
+    window._settings.bind('invert-vertical-rotation-direction',
+      invertVerticalRotationSwitch, 'active', Gio.SettingsBindFlags.DEFAULT);
+
+    window._settings.bind('flip-orientation',
+      flipOrientationSwitch, 'active', Gio.SettingsBindFlags.DEFAULT);
+
+    window._settings.bind('orientation-offset',
+      setOffsetSpinButton, 'value', Gio.SettingsBindFlags.DEFAULT);
+
+    window._settings.bind('debug-logging',
+      toggleLoggingSwitch, 'active', Gio.SettingsBindFlags.DEFAULT);
+
+    invertHorizontalRow.add_suffix(invertHorizontalRotationSwitch);
+    invertHorizontalRow.activatable_widget = invertHorizontalRotationSwitch;
+
+    invertVerticalRow.add_suffix(invertVerticalRotationSwitch);
+    invertVerticalRow.activatable_widget = invertVerticalRotationSwitch;
+
+    flipOrientationRow.add_suffix(flipOrientationSwitch);
+    flipOrientationRow.activatable_widget = flipOrientationSwitch;
+
+    setOffsetRow.add_suffix(setOffsetSpinButton);
+    setOffsetRow.activatable_widget = setOffsetSpinButton;
+
+    toggleLoggingRow.add_suffix(toggleLoggingSwitch);
+    toggleLoggingRow.activatable_widget = toggleLoggingSwitch;
+  }
+}

+ 75 - 0
gnome/.local/share/gnome-shell/extensions/screen-rotate@shyzus.github.io/rotator.js

@@ -0,0 +1,75 @@
+/* rotator.js
+*
+* Copyright (C) 2022  kosmospredanie, shyzus
+*
+* 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 3 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 <https://www.gnu.org/licenses/>.
+*/
+import Gio from 'gi://Gio';
+
+import * as BusUtils from './busUtils.js';
+const connection = Gio.DBus.session;
+
+export function call_dbus_method(method, params = null, handler) {
+  if (handler != undefined || handler != null) {
+    connection.call(
+      'org.gnome.Mutter.DisplayConfig',
+      '/org/gnome/Mutter/DisplayConfig',
+      'org.gnome.Mutter.DisplayConfig',
+      method,
+      params,
+      null,
+      Gio.DBusCallFlags.NONE,
+      -1,
+      null, handler);
+  } else {
+    connection.call(
+      'org.gnome.Mutter.DisplayConfig',
+      '/org/gnome/Mutter/DisplayConfig',
+      'org.gnome.Mutter.DisplayConfig',
+      method,
+      params,
+      null,
+      Gio.DBusCallFlags.NONE,
+      -1,
+      null);
+  }
+
+}
+
+export function get_state() {
+  return new Promise((resolve, reject) => {
+    call_dbus_method('GetCurrentState', null, (connection, res) => {
+      try {
+        let reply = connection.call_finish(res);
+        let configState = new BusUtils.DisplayConfigState(reply)
+        resolve(configState);
+      } catch (err) {
+        reject(err);
+      }
+
+    });
+  })
+}
+
+export function rotate_to(transform) {
+  this.get_state().then(state => {
+    let builtin_monitor = state.builtin_monitor;
+    let logical_monitor = state.get_logical_monitor_for(builtin_monitor.connector);
+    logical_monitor.transform = transform;
+    let variant = state.pack_to_apply(BusUtils.Methods['temporary']);
+    call_dbus_method('ApplyMonitorsConfig', variant);
+  }).catch(err => {
+    console.error(err);
+  })
+}

BIN
gnome/.local/share/gnome-shell/extensions/screen-rotate@shyzus.github.io/schemas/gschemas.compiled


+ 46 - 0
gnome/.local/share/gnome-shell/extensions/screen-rotate@shyzus.github.io/schemas/org.gnome.shell.extensions.screen-rotate.gschema.xml

@@ -0,0 +1,46 @@
+<!-- 
+-    org.gnome.shell.extensions.screen-rotate.gschema.xml
+- Copyright (C) 2023 shyzus, Shinigaminai
+-
+- 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 3 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 <https://www.gnu.org/licenses/>.
+-
+-->
+<?xml version="1.0" encoding="UTF-8"?>
+<schemalist>
+  <schema id="org.gnome.shell.extensions.screen-rotate"
+    path="/org/gnome/shell/extensions/screen-rotate/">
+
+    <key type="b" name="invert-horizontal-rotation-direction">
+      <default>false</default>
+      <description>Invert horizontal rotation direction of the screen</description>
+    </key>
+    <key type="b" name="invert-vertical-rotation-direction">
+      <default>false</default>
+      <description>Invert vertical rotation direction of the screen</description>
+    </key>
+    <key type="b" name="flip-orientation">
+      <default>false</default>
+      <description>Alter orientation. (e.g: Landscape to Portrait. Default is Landscape.)</description>
+    </key>
+    <key type="i" name="orientation-offset">
+      <default>0</default>
+      <description>Experiment with this offset in case orientation is incorrect.</description>
+    </key>
+    <key type="b" name="debug-logging">
+      <default>false</default>
+      <description>Toggle debug logging.</description>
+    </key>
+  </schema>
+
+</schemalist>