extension.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. /* extension.js
  2. * Copyright (C) 2024 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 Gio from 'gi://Gio';
  18. import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js';
  19. import * as Main from 'resource:///org/gnome/shell/ui/main.js';
  20. import * as SystemActions from 'resource:///org/gnome/shell/misc/systemActions.js';
  21. import * as Rotator from './rotator.js'
  22. import { Orientation } from './orientation.js';
  23. import { ManualOrientationIndicator } from './manualOrientationIndicator.js';
  24. import { SensorProxy } from './sensorProxy.js';
  25. const ORIENTATION_LOCK_SCHEMA = 'org.gnome.settings-daemon.peripherals.touchscreen';
  26. const ORIENTATION_LOCK_KEY = 'orientation-lock';
  27. const A11Y_APPLICATIONS_SCHEMA = 'org.gnome.desktop.a11y.applications';
  28. const SHOW_KEYBOARD = 'screen-keyboard-enabled';
  29. const PERIPHERAL_TOUCHPAD_SCHEMA = 'org.gnome.desktop.peripherals.touchpad';
  30. const TOUCHPAD_EVENTS = 'send-events';
  31. export default class ScreenAutoRotateExtension extends Extension {
  32. enable() {
  33. this._settings = this.getSettings();
  34. this._system_actions = SystemActions.getDefault();
  35. this._system_actions_backup = null;
  36. this._override_system_actions();
  37. this._a11yApplicationsSettings = new Gio.Settings({ schema_id: A11Y_APPLICATIONS_SCHEMA });
  38. this._peripheralTouchpadSettings = new Gio.Settings({ schema_id: PERIPHERAL_TOUCHPAD_SCHEMA });
  39. this._orientation_settings = new Gio.Settings({ schema_id: ORIENTATION_LOCK_SCHEMA });
  40. this._orientation_settings_handler = this._orientation_settings.connect('changed::' + ORIENTATION_LOCK_KEY, this._orientation_lock_changed.bind(this));
  41. this._sensor_proxy = new SensorProxy(this.rotate_to.bind(this));
  42. this._state = false;
  43. let locked = this._orientation_settings.get_boolean(ORIENTATION_LOCK_KEY);
  44. if (!locked) {
  45. this.toggle_rotation_lock()
  46. }
  47. this._manual_flip_settings_handler = this._settings.connect('changed::manual-flip', (settings, key) => {
  48. if (settings.get_boolean(key)) {
  49. this._add_manual_flip();
  50. } else {
  51. this._remove_manual_flip();
  52. }
  53. });
  54. this._hide_lock_rotate_settings_handler = this._settings.connect('changed::hide-lock-rotate', (settings, key) => {
  55. this._set_hide_lock_rotate(settings.get_boolean(key));
  56. });
  57. if (this._settings.get_boolean('manual-flip')) {
  58. this._add_manual_flip();
  59. } else {
  60. this._remove_manual_flip();
  61. }
  62. /* Timeout needed due to unknown race condition causing 'Auto Rotate'
  63. * Quick Toggle to be undefined for a brief moment.
  64. */
  65. this._timeoutId = setTimeout(() => {
  66. this._set_hide_lock_rotate(this._settings.get_boolean('hide-lock-rotate'));
  67. }, 1000);
  68. }
  69. toggle_rotation_lock() {
  70. if (this._state) {
  71. this._a11yApplicationsSettings.set_boolean(SHOW_KEYBOARD, this._originala11yKeyboardSetting);
  72. this._originala11yKeyboardSetting = null;
  73. this._sensor_proxy.disable();
  74. this._state = false;
  75. } else {
  76. this._originala11yKeyboardSetting = this._a11yApplicationsSettings.get_boolean(SHOW_KEYBOARD);
  77. this._sensor_proxy.enable();
  78. this._state = true;
  79. }
  80. }
  81. _set_hide_lock_rotate(state) {
  82. const autoRotateIndicator = Main.panel.statusArea.quickSettings._autoRotate;
  83. if (state) {
  84. Main.panel.statusArea.quickSettings._indicators.remove_child(autoRotateIndicator);
  85. Main.panel.statusArea.quickSettings.menu._grid.remove_child(autoRotateIndicator.quickSettingsItems[0]);
  86. } else {
  87. Main.panel.statusArea.quickSettings._indicators.add_child(autoRotateIndicator);
  88. Main.panel.statusArea.quickSettings._addItemsBefore(
  89. autoRotateIndicator.quickSettingsItems,
  90. Main.panel.statusArea.quickSettings._rfkill.quickSettingsItems[0]
  91. );
  92. }
  93. }
  94. _add_manual_flip() {
  95. this.flipIndicator = new ManualOrientationIndicator(this);
  96. Main.panel.statusArea.quickSettings.addExternalIndicator(this.flipIndicator);
  97. }
  98. _remove_manual_flip() {
  99. if (this.flipIndicator !== null && this.flipIndicator !== undefined) {
  100. this.flipIndicator.destroy();
  101. this.flipIndicator = null;
  102. }
  103. }
  104. _override_system_actions() {
  105. this._system_actions_backup = {
  106. '_updateOrientationLock': this._system_actions._updateOrientationLock
  107. };
  108. this._system_actions._updateOrientationLock = function() {
  109. this._actions.get('lock-orientation').available = true;
  110. this.notify('can-lock-orientation');
  111. };
  112. this._system_actions._updateOrientationLock();
  113. }
  114. _restore_system_actions() {
  115. if (this._system_actions_backup === null) return;
  116. this._system_actions._updateOrientationLock = this._system_actions_backup['_updateOrientationLock'];
  117. this._system_actions._updateOrientationLock();
  118. this._system_actions_backup = null;
  119. }
  120. _orientation_lock_changed() {
  121. let locked = this._orientation_settings.get_boolean(ORIENTATION_LOCK_KEY);
  122. if (this._state === locked) {
  123. this.toggle_rotation_lock();
  124. }
  125. }
  126. _handle_osk(target) {
  127. const landscapeOsk = this._settings.get_boolean('landscape-osk');
  128. const portraitRightOsk = this._settings.get_boolean('portrait-right-osk');
  129. const portraitLeftOsk = this._settings.get_boolean('portrait-left-osk');
  130. const landscapeFlippedOsk = this._settings.get_boolean('landscape-flipped-osk');
  131. switch (target) {
  132. case 0:
  133. this._a11yApplicationsSettings.set_boolean(SHOW_KEYBOARD, landscapeOsk);
  134. break;
  135. case 1:
  136. this._a11yApplicationsSettings.set_boolean(SHOW_KEYBOARD, portraitLeftOsk);
  137. break;
  138. case 2:
  139. this._a11yApplicationsSettings.set_boolean(SHOW_KEYBOARD, landscapeFlippedOsk);
  140. break;
  141. case 3:
  142. this._a11yApplicationsSettings.set_boolean(SHOW_KEYBOARD, portraitRightOsk);
  143. break;
  144. }
  145. }
  146. _handle_dor_touchpad(target) {
  147. const dorLandscape = this._settings.get_boolean('dor-touchpad-landscape');
  148. const dorPortraitRight = this._settings.get_boolean('dor-touchpad-portrait-right');
  149. const dorPortraitLeft = this._settings.get_boolean('dor-touchpad-portrait-left');
  150. const dorLandscapeFlipped = this._settings.get_boolean('dor-touchpad-landscape-flipped');
  151. let setting_value = undefined;
  152. switch (target) {
  153. case 0:
  154. if (dorLandscape) {
  155. setting_value = "disabled";
  156. } else {
  157. setting_value = "enabled";
  158. }
  159. this._peripheralTouchpadSettings.set_string(TOUCHPAD_EVENTS, setting_value);
  160. break;
  161. case 1:
  162. if (dorPortraitLeft) {
  163. setting_value = "disabled";
  164. } else {
  165. setting_value = "enabled";
  166. }
  167. this._peripheralTouchpadSettings.set_string(TOUCHPAD_EVENTS, setting_value);
  168. break;
  169. case 2:
  170. if (dorLandscapeFlipped) {
  171. setting_value = "disabled";
  172. } else {
  173. setting_value = "enabled";
  174. }
  175. this._peripheralTouchpadSettings.set_string(TOUCHPAD_EVENTS, setting_value);
  176. break;
  177. case 3:
  178. if (dorPortraitRight) {
  179. setting_value = "disabled";
  180. } else {
  181. setting_value = "enabled";
  182. }
  183. this._peripheralTouchpadSettings.set_string(TOUCHPAD_EVENTS, setting_value);
  184. break;
  185. }
  186. }
  187. rotate_to(orientation) {
  188. const sensor = Orientation[orientation];
  189. const invert_horizontal_direction = this._settings.get_boolean('invert-horizontal-rotation-direction');
  190. const invert_vertical_direction = this._settings.get_boolean('invert-vertical-rotation-direction');
  191. const offset = this._settings.get_int('orientation-offset');
  192. let target = sensor; // Default to sensor output.
  193. switch (sensor) {
  194. case 0:
  195. // sensor reports landscape
  196. if (invert_horizontal_direction) {
  197. target = 2;
  198. }
  199. break;
  200. case 1:
  201. // sensor reports portrait
  202. if (invert_vertical_direction) {
  203. target = 3;
  204. }
  205. break;
  206. case 2:
  207. // sensor reports landscape-inverted
  208. if (invert_horizontal_direction) {
  209. target = 0;
  210. }
  211. break;
  212. case 3:
  213. // sensor reports portrait-inverted
  214. if (invert_vertical_direction) {
  215. target = 1;
  216. }
  217. break;
  218. }
  219. target = (target + offset) % 4;
  220. if (this._settings.get_boolean('debug-logging')) {
  221. console.log(`sensor=${Orientation[orientation]}`);
  222. console.log(`offset=${offset}`);
  223. console.log(`target=${target}`);
  224. }
  225. Rotator.rotate_to(target);
  226. this._handle_osk(target);
  227. this._handle_dor_touchpad(target);
  228. }
  229. disable() {
  230. /*
  231. Comment for unlock-dialog usage:
  232. The unlock-dialog session-mode is useful for this extension as it allows
  233. the user to rotate their screen or lock rotation after their device may
  234. have auto-locked. This provides the ability to log back in regardless of
  235. the orientation of the device in tablet mode.
  236. */
  237. this._settings.disconnect(this._manual_flip_settings_handler);
  238. this._settings.disconnect(this._hide_lock_rotate_settings_handler);
  239. this._settings = null;
  240. clearTimeout(this._timeoutId);
  241. this._timeoutId = null;
  242. this._remove_manual_flip();
  243. this._sensor_proxy.destroy();
  244. this._orientation_settings.disconnect(this._orientation_settings_handler);
  245. this._orientation_settings = null;
  246. this._a11yApplicationsSettings = null;
  247. this._restore_system_actions();
  248. }
  249. }