[gnome] Add auto rotate extensions

This commit is contained in:
2024-02-05 16:13:38 -05:00
parent 62531b0b5f
commit fb9f4f5824
7 changed files with 659 additions and 0 deletions

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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
}

View File

@ -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;
}
}

View File

@ -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);
})
}

View File

@ -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>