extension.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. /* extension.js
  2. * Copyright (C) 2023 kosmospredanie, shyzus, Shinigaminai
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation, either version 3 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. */
  17. import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js';
  18. import Gio from 'gi://Gio';
  19. import * as SystemActions from 'resource:///org/gnome/shell/misc/systemActions.js';
  20. import * as Rotator from './rotator.js'
  21. const ORIENTATION_LOCK_SCHEMA = 'org.gnome.settings-daemon.peripherals.touchscreen';
  22. const ORIENTATION_LOCK_KEY = 'orientation-lock';
  23. // Orientation names must match those provided by net.hadess.SensorProxy
  24. const Orientation = Object.freeze({
  25. 'normal': 0,
  26. 'left-up': 1,
  27. 'bottom-up': 2,
  28. 'right-up': 3
  29. });
  30. class SensorProxy {
  31. constructor(rotate_cb) {
  32. this._rotate_cb = rotate_cb;
  33. this._proxy = null;
  34. this._enabled = false;
  35. this._watcher_id = Gio.bus_watch_name(
  36. Gio.BusType.SYSTEM,
  37. 'net.hadess.SensorProxy',
  38. Gio.BusNameWatcherFlags.NONE,
  39. this.appeared.bind(this),
  40. this.vanished.bind(this)
  41. );
  42. }
  43. destroy() {
  44. Gio.bus_unwatch_name(this._watcher_id);
  45. if (this._enabled) this.disable();
  46. this._proxy = null;
  47. }
  48. enable() {
  49. this._enabled = true;
  50. if (this._proxy === null) return;
  51. this._proxy.call_sync('ClaimAccelerometer', null, Gio.DBusCallFlags.NONE, -1, null);
  52. }
  53. disable() {
  54. this._enabled = false;
  55. if (this._proxy === null) return;
  56. this._proxy.call_sync('ReleaseAccelerometer', null, Gio.DBusCallFlags.NONE, -1, null);
  57. }
  58. appeared(connection, name, name_owner) {
  59. this._proxy = Gio.DBusProxy.new_for_bus_sync(
  60. Gio.BusType.SYSTEM, Gio.DBusProxyFlags.NONE, null,
  61. 'net.hadess.SensorProxy', '/net/hadess/SensorProxy', 'net.hadess.SensorProxy',
  62. null);
  63. this._proxy.connect('g-properties-changed', this.properties_changed.bind(this));
  64. if (this._enabled) {
  65. this._proxy.call_sync('ClaimAccelerometer', null, Gio.DBusCallFlags.NONE, -1, null);
  66. }
  67. }
  68. vanished(connection, name) {
  69. this._proxy = null;
  70. }
  71. properties_changed(proxy, changed, invalidated) {
  72. if (!this._enabled) return;
  73. let properties = changed.deep_unpack();
  74. for (let [name, value] of Object.entries(properties)) {
  75. if (name != 'AccelerometerOrientation') continue;
  76. let target = value.unpack();
  77. this._rotate_cb(target);
  78. }
  79. }
  80. }
  81. class ScreenAutorotate {
  82. constructor(settings) {
  83. this._system_actions = SystemActions.getDefault();
  84. this._settings = settings;
  85. this._system_actions_backup = null;
  86. this._override_system_actions();
  87. this._orientation_settings = new Gio.Settings({ schema_id: ORIENTATION_LOCK_SCHEMA });
  88. this._orientation_settings.connect('changed::' + ORIENTATION_LOCK_KEY, this._orientation_lock_changed.bind(this));
  89. this._sensor_proxy = new SensorProxy(this.rotate_to.bind(this));
  90. this._state = false; // enabled or not
  91. let locked = this._orientation_settings.get_boolean(ORIENTATION_LOCK_KEY);
  92. if (!locked) this.enable();
  93. }
  94. _override_system_actions() {
  95. this._system_actions_backup = {
  96. '_updateOrientationLock': this._system_actions._updateOrientationLock
  97. };
  98. this._system_actions._updateOrientationLock = function() {
  99. this._actions.get('lock-orientation').available = true;
  100. this.notify('can-lock-orientation');
  101. };
  102. this._system_actions._updateOrientationLock();
  103. }
  104. _restore_system_actions() {
  105. if (this._system_actions_backup === null) return;
  106. this._system_actions._updateOrientationLock = this._system_actions_backup['_updateOrientationLock'];
  107. this._system_actions._updateOrientationLock();
  108. this._system_actions_backup = null;
  109. }
  110. _orientation_lock_changed() {
  111. let locked = this._orientation_settings.get_boolean(ORIENTATION_LOCK_KEY);
  112. if (this._state == locked) {
  113. this.toggle();
  114. }
  115. }
  116. destroy() {
  117. this._sensor_proxy.destroy();
  118. this._orientation_settings = null;
  119. this._restore_system_actions();
  120. }
  121. toggle() {
  122. if (this._state) {
  123. this.disable();
  124. } else {
  125. this.enable();
  126. }
  127. }
  128. enable() {
  129. this._sensor_proxy.enable();
  130. this._state = true;
  131. }
  132. disable() {
  133. this._sensor_proxy.disable();
  134. this._state = false;
  135. }
  136. rotate_to(orientation) {
  137. const sensor = Orientation[orientation];
  138. const invert_horizontal_direction = this._settings.get_boolean('invert-horizontal-rotation-direction');
  139. const invert_vertical_direction = this._settings.get_boolean('invert-vertical-rotation-direction');
  140. const offset = this._settings.get_int('orientation-offset');
  141. let target = sensor; // Default to sensor output.
  142. switch (sensor) {
  143. case 0:
  144. // sensor reports landscape
  145. if (invert_horizontal_direction) {
  146. target = 2;
  147. }
  148. break;
  149. case 1:
  150. // sensor reports portrait
  151. if (invert_vertical_direction) {
  152. target = 3;
  153. }
  154. break;
  155. case 2:
  156. // sensor reports landscape-inverted
  157. if (invert_horizontal_direction) {
  158. target = 0;
  159. }
  160. break;
  161. case 3:
  162. // sensor reports portrait-inverted
  163. if (invert_vertical_direction) {
  164. target = 1;
  165. }
  166. break;
  167. }
  168. target = (target + offset) % 4;
  169. if (this._settings.get_boolean('debug-logging')) {
  170. console.debug(`sensor=${Orientation[orientation]}`);
  171. console.debug(`offset=${offset}`);
  172. console.debug(`target=${target}`);
  173. }
  174. Rotator.rotate_to(target);
  175. }
  176. }
  177. export default class ScreenAutoRotateExtension extends Extension {
  178. enable() {
  179. this._settings = this.getSettings();
  180. this._ext = new ScreenAutorotate(this._settings);
  181. }
  182. disable() {
  183. /*
  184. Comment for unlock-dialog usage:
  185. The unlock-dialog sesson-mode is usefull for this extension as it allows
  186. the user to rotate their screen or lock rotation after their device may
  187. have auto-locked. This provides the ability to log back in regardless of
  188. the orientation of the device in tablet mode.
  189. */
  190. this._settings = null;
  191. this._ext.destroy();
  192. this._ext = null;
  193. }
  194. }