diff options
author | Evan Welsh <contact@evanwelsh.com> | 2021-09-17 23:48:04 -0700 |
---|---|---|
committer | Evan Welsh <contact@evanwelsh.com> | 2021-09-23 18:26:20 -0700 |
commit | 91d6953ee6bc25aee662abeb2a6c4162245fef72 (patch) | |
tree | 88ba981dba34fdd68a4fb9cddee7c950bc1ca10c | |
parent | ff88b5ab91452d5f36b5ddb622ebac54d205b959 (diff) | |
download | gnome-shell-ewlsh/esm.tar.gz |
ESM Part 1ewlsh/esm
163 files changed, 4826 insertions, 2909 deletions
diff --git a/HACKING.md b/HACKING.md index bdcb9ba93..612b7fdba 100644 --- a/HACKING.md +++ b/HACKING.md @@ -66,7 +66,7 @@ library. These headers are not installed, distributed or introspected. Use UpperCamelCase when importing modules to distinguish them from ordinary variables, e.g. ```javascript - const GLib = imports.gi.GLib; + import GLib from 'gi://GLib'; ``` Imports should be categorized into one of two places. The top-most import block should contain only "environment imports". These are either modules from @@ -118,7 +118,7 @@ See [What's new in JavaScript 1.7](https://developer.mozilla.org/en/JavaScript/N There are many approaches to classes in JavaScript. We use standard ES6 classes whenever possible, that is when not inheriting from GObjects. ```javascript - var IconLabelMenuItem = class extends PopupMenu.PopupMenuBaseItem { + export class IconLabelMenuItem extends PopupMenu.PopupMenuBaseItem { constructor(icon, label) { super({ reactive: false }); this.actor.add_child(icon); diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js index d4edfb9ef..b67d5b590 100644 --- a/js/gdm/authPrompt.js +++ b/js/gdm/authPrompt.js @@ -1,29 +1,34 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported AuthPrompt */ -const { Clutter, GLib, GObject, Pango, Shell, St } = imports.gi; +const { Clutter, GLib, GObject, Pango, Shell } = imports.gi; -const Animation = imports.ui.animation; -const Batch = imports.gdm.batch; -const GdmUtil = imports.gdm.util; -const OVirt = imports.gdm.oVirt; -const Vmware = imports.gdm.vmware; -const ShellEntry = imports.ui.shellEntry; -const UserWidget = imports.ui.userWidget; -const Util = imports.misc.util; +import St from 'gi://St'; +import Gdm from 'gi://Gdm'; -var DEFAULT_BUTTON_WELL_ICON_SIZE = 16; -var DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1000; -var DEFAULT_BUTTON_WELL_ANIMATION_TIME = 300; +import * as Animation from '../ui/animation.js'; +import * as Batch from './batch.js'; +import * as GdmUtil from './util.js'; +import * as OVirt from './oVirt.js'; +import * as Vmware from './vmware.js'; +import * as ShellEntry from '../ui/shellEntry.js'; +import * as UserWidget from '../ui/userWidget.js'; +import * as Util from '../misc/util.js'; -var MESSAGE_FADE_OUT_ANIMATION_TIME = 500; +export let DEFAULT_BUTTON_WELL_ICON_SIZE = 16; +export let DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1000; +export let DEFAULT_BUTTON_WELL_ANIMATION_TIME = 300; -var AuthPromptMode = { +export let MESSAGE_FADE_OUT_ANIMATION_TIME = 500; + +/** @enum {number} */ +export const AuthPromptMode = { UNLOCK_ONLY: 0, UNLOCK_OR_LOG_IN: 1, }; -var AuthPromptStatus = { +/** @enum {number} */ +export const AuthPromptStatus = { NOT_VERIFYING: 0, VERIFYING: 1, VERIFICATION_FAILED: 2, @@ -32,13 +37,14 @@ var AuthPromptStatus = { VERIFICATION_IN_PROGRESS: 5, }; -var BeginRequestType = { +/** @enum {number} */ +export const BeginRequestType = { PROVIDE_USERNAME: 0, DONT_PROVIDE_USERNAME: 1, REUSE_USERNAME: 2, }; -var AuthPrompt = GObject.registerClass({ +export const AuthPrompt = GObject.registerClass({ Signals: { 'cancelled': {}, 'failed': {}, @@ -47,6 +53,10 @@ var AuthPrompt = GObject.registerClass({ 'reset': { param_types: [GObject.TYPE_UINT] }, }, }, class AuthPrompt extends St.BoxLayout { + /** + * @param {Gdm.Client} gdmClient + * @param {AuthPromptMode} mode + */ _init(gdmClient, mode) { super._init({ style_class: 'login-dialog-prompt-layout', @@ -583,6 +593,9 @@ var AuthPrompt = GObject.registerClass({ this._entry.clutter_text.insert_unichar(unichar); } + /** + * @param {{ userName?: string | null, hold?: Batch.Hold | null }} [params] + */ begin(params = {}) { let { userName = null, hold = null } = params; diff --git a/js/gdm/batch.js b/js/gdm/batch.js index f841f0f8e..901e0d11a 100644 --- a/js/gdm/batch.js +++ b/js/gdm/batch.js @@ -45,10 +45,10 @@ */ /* exported ConcurrentBatch, ConsecutiveBatch */ -const { GObject } = imports.gi; -const Signals = imports.misc.signals; +import GObject from 'gi://GObject'; +import * as Signals from '../misc/signals.js'; -var Task = class extends Signals.EventEmitter { +export class Task extends Signals.EventEmitter { constructor(scope, handler) { super(); @@ -68,7 +68,7 @@ var Task = class extends Signals.EventEmitter { } }; -var Hold = class extends Task { +export class Hold extends Task { constructor() { super(null, () => this); @@ -104,7 +104,7 @@ var Hold = class extends Task { } }; -var Batch = class extends Task { +export class Batch extends Task { constructor(scope, tasks) { super(); @@ -173,7 +173,7 @@ var Batch = class extends Task { } }; -var ConcurrentBatch = class extends Batch { +export class ConcurrentBatch extends Batch { process() { let hold = this.runTask(); @@ -187,7 +187,7 @@ var ConcurrentBatch = class extends Batch { } }; -var ConsecutiveBatch = class extends Batch { +export class ConsecutiveBatch extends Batch { process() { let hold = this.runTask(); diff --git a/js/gdm/credentialManager.js b/js/gdm/credentialManager.js index c53de11df..53bb2ded0 100644 --- a/js/gdm/credentialManager.js +++ b/js/gdm/credentialManager.js @@ -1,9 +1,9 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported CredentialManager */ -const Signals = imports.misc.signals; +import * as Signals from "../misc/signals.js"; -var CredentialManager = class CredentialManager extends Signals.EventEmitter { +export class CredentialManager extends Signals.EventEmitter { constructor(service) { super(); diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js index 760d7e9ef..3eb8e41db 100644 --- a/js/gdm/loginDialog.js +++ b/js/gdm/loginDialog.js @@ -17,28 +17,40 @@ * along with this program; if not, see <http://www.gnu.org/licenses/>. */ -const { AccountsService, Atk, Clutter, Gdm, Gio, - GLib, GObject, Meta, Pango, Shell, St } = imports.gi; - -const AuthPrompt = imports.gdm.authPrompt; -const Batch = imports.gdm.batch; -const BoxPointer = imports.ui.boxpointer; -const CtrlAltTab = imports.ui.ctrlAltTab; -const GdmUtil = imports.gdm.util; -const Layout = imports.ui.layout; -const LoginManager = imports.misc.loginManager; -const Main = imports.ui.main; -const PopupMenu = imports.ui.popupMenu; -const Realmd = imports.gdm.realmd; -const UserWidget = imports.ui.userWidget; +import AccountsService from 'gi://AccountsService'; +import Atk from 'gi://Atk'; +import Clutter from 'gi://Clutter'; +import Gdm from 'gi://Gdm'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import Pango from 'gi://Pango'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; + +import * as AuthPrompt from './authPrompt.js'; +import * as Batch from './batch.js'; +import * as BoxPointer from '../ui/boxpointer.js'; +import * as CtrlAltTab from '../ui/ctrlAltTab.js'; +import * as GdmUtil from './util.js'; +import * as Layout from '../ui/layout.js'; +import * as LoginManager from "../misc/loginManager.js"; +import Main from '../ui/main.js'; +import * as PopupMenu from '../ui/popupMenu.js'; +import * as Realmd from './realmd.js'; +import * as UserWidget from '../ui/userWidget.js'; const _FADE_ANIMATION_TIME = 250; const _SCROLL_ANIMATION_TIME = 500; const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0; -var UserListItem = GObject.registerClass({ +export const UserListItem = GObject.registerClass({ Signals: { 'activate': {} }, }, class UserListItem extends St.Button { + /** + * @param {AccountsService.User} user + */ _init(user) { let layout = new St.BoxLayout({ vertical: true, @@ -152,7 +164,7 @@ var UserListItem = GObject.registerClass({ } }); -var UserList = GObject.registerClass({ +export const UserList = GObject.registerClass({ Signals: { 'activate': { param_types: [UserListItem.$gtype] }, 'item-added': { param_types: [UserListItem.$gtype] }, @@ -304,7 +316,7 @@ var UserList = GObject.registerClass({ } }); -var SessionMenuButton = GObject.registerClass({ +export const SessionMenuButton = GObject.registerClass({ Signals: { 'session-activated': { param_types: [GObject.TYPE_STRING] } }, }, class SessionMenuButton extends St.Bin { _init() { @@ -400,12 +412,15 @@ var SessionMenuButton = GObject.registerClass({ } }); -var LoginDialog = GObject.registerClass({ +export const LoginDialog = GObject.registerClass({ Signals: { 'failed': {}, 'wake-up-screen': {}, }, }, class LoginDialog extends St.Widget { + /** + * @param {Clutter.Actor} parentActor + */ _init(parentActor) { super._init({ style_class: 'login-dialog', visible: false }); diff --git a/js/gdm/oVirt.js b/js/gdm/oVirt.js index 4cf17d99e..6e9a5211f 100644 --- a/js/gdm/oVirt.js +++ b/js/gdm/oVirt.js @@ -1,10 +1,9 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- -/* exported getOVirtCredentialsManager */ -const Gio = imports.gi.Gio; -const Credential = imports.gdm.credentialManager; +import Gio from 'gi://Gio'; +import * as Credential from './credentialManager.js'; -var SERVICE_NAME = 'gdm-ovirtcred'; +export const SERVICE_NAME = 'gdm-ovirtcred'; const OVirtCredentialsIface = ` <node> @@ -17,6 +16,7 @@ const OVirtCredentialsIface = ` const OVirtCredentialsInfo = Gio.DBusInterfaceInfo.new_for_xml(OVirtCredentialsIface); +/** @type {OVirtCredentialsManager | null} */ let _oVirtCredentialsManager = null; function OVirtCredentials() { @@ -30,7 +30,7 @@ function OVirtCredentials() { return self; } -var OVirtCredentialsManager = class OVirtCredentialsManager extends Credential.CredentialManager { +export class OVirtCredentialsManager extends Credential.CredentialManager { constructor() { super(SERVICE_NAME); this._credentials = OVirtCredentials(); @@ -41,7 +41,7 @@ var OVirtCredentialsManager = class OVirtCredentialsManager extends Credential.C } }; -function getOVirtCredentialsManager() { +export function getOVirtCredentialsManager() { if (!_oVirtCredentialsManager) _oVirtCredentialsManager = new OVirtCredentialsManager(); diff --git a/js/gdm/realmd.js b/js/gdm/realmd.js index b7ff5ad54..44335b3dc 100644 --- a/js/gdm/realmd.js +++ b/js/gdm/realmd.js @@ -1,10 +1,10 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Manager */ -const Gio = imports.gi.Gio; -const Signals = imports.misc.signals; +import Gio from 'gi://Gio'; +import * as Signals from '../misc/signals.js'; -const { loadInterfaceXML } = imports.misc.fileUtils; +import { loadInterfaceXML } from '../misc/fileUtilsModule.js'; const ProviderIface = loadInterfaceXML("org.freedesktop.realmd.Provider"); const Provider = Gio.DBusProxy.makeProxyWrapper(ProviderIface); @@ -15,7 +15,7 @@ const Service = Gio.DBusProxy.makeProxyWrapper(ServiceIface); const RealmIface = loadInterfaceXML("org.freedesktop.realmd.Realm"); const Realm = Gio.DBusProxy.makeProxyWrapper(RealmIface); -var Manager = class extends Signals.EventEmitter { +export class Manager extends Signals.EventEmitter { constructor() { super(); diff --git a/js/gdm/util.js b/js/gdm/util.js index 0adb22add..7f4f2715c 100644 --- a/js/gdm/util.js +++ b/js/gdm/util.js @@ -3,15 +3,18 @@ DISABLE_USER_LIST_KEY, fadeInActor, fadeOutActor, cloneAndFadeOutActor, ShellUserVerifier */ -const { Clutter, Gdm, Gio, GLib } = imports.gi; -const Signals = imports.misc.signals; - -const Batch = imports.gdm.batch; -const OVirt = imports.gdm.oVirt; -const Vmware = imports.gdm.vmware; -const Main = imports.ui.main; -const { loadInterfaceXML } = imports.misc.fileUtils; -const SmartcardManager = imports.misc.smartcardManager; +import Clutter from 'gi://Clutter'; +import Gdm from 'gi://Gdm'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import * as Signals from '../misc/signals.js'; + +import * as Batch from './batch.js'; +import * as OVirt from './oVirt.js'; +import * as Vmware from './vmware.js'; +import Main from '../ui/main.js'; +import { loadInterfaceXML } from '../misc/fileUtilsModule.js'; +import * as SmartcardManager from '../misc/smartcardManager.js'; const FprintManagerIface = loadInterfaceXML('net.reactivated.Fprint.Manager'); const FprintManagerProxy = Gio.DBusProxy.makeProxyWrapper(FprintManagerIface); @@ -27,29 +30,29 @@ Gio._promisify(Gdm.UserVerifierProxy.prototype, Gio._promisify(Gdm.UserVerifierProxy.prototype, 'call_begin_verification', 'call_begin_verification_finish'); -var PASSWORD_SERVICE_NAME = 'gdm-password'; -var FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint'; -var SMARTCARD_SERVICE_NAME = 'gdm-smartcard'; -var FADE_ANIMATION_TIME = 160; -var CLONE_FADE_ANIMATION_TIME = 250; +export const PASSWORD_SERVICE_NAME = 'gdm-password'; +export const FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint'; +export const SMARTCARD_SERVICE_NAME = 'gdm-smartcard'; +export const FADE_ANIMATION_TIME = 160; +export const CLONE_FADE_ANIMATION_TIME = 250; -var LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen'; -var PASSWORD_AUTHENTICATION_KEY = 'enable-password-authentication'; -var FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication'; -var SMARTCARD_AUTHENTICATION_KEY = 'enable-smartcard-authentication'; -var BANNER_MESSAGE_KEY = 'banner-message-enable'; -var BANNER_MESSAGE_TEXT_KEY = 'banner-message-text'; -var ALLOWED_FAILURES_KEY = 'allowed-failures'; +export const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen'; +export const PASSWORD_AUTHENTICATION_KEY = 'enable-password-authentication'; +export const FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication'; +export const SMARTCARD_AUTHENTICATION_KEY = 'enable-smartcard-authentication'; +export const BANNER_MESSAGE_KEY = 'banner-message-enable'; +export const BANNER_MESSAGE_TEXT_KEY = 'banner-message-text'; +export const ALLOWED_FAILURES_KEY = 'allowed-failures'; -var LOGO_KEY = 'logo'; -var DISABLE_USER_LIST_KEY = 'disable-user-list'; +export const LOGO_KEY = 'logo'; +export const DISABLE_USER_LIST_KEY = 'disable-user-list'; // Give user 48ms to read each character of a PAM message -var USER_READ_TIME = 48; +export const USER_READ_TIME = 48; const FINGERPRINT_ERROR_TIMEOUT_WAIT = 15; -var MessageType = { - // Keep messages in order by priority +/** @enum {number} */ +export const MessageType = { NONE: 0, HINT: 1, INFO: 2, @@ -62,7 +65,7 @@ const FingerprintReaderType = { SWIPE: 2, }; -function fadeInActor(actor) { +export function fadeInActor(actor) { if (actor.opacity == 255 && actor.visible) return null; @@ -86,7 +89,7 @@ function fadeInActor(actor) { return hold; } -function fadeOutActor(actor) { +export function fadeOutActor(actor) { if (!actor.visible || actor.opacity == 0) { actor.opacity = 0; actor.hide(); @@ -108,7 +111,7 @@ function fadeOutActor(actor) { return hold; } -function cloneAndFadeOutActor(actor) { +export function cloneAndFadeOutActor(actor) { // Immediately hide actor so its sibling can have its space // and position, but leave a non-reactive clone on-screen, // so from the user's point of view it smoothly fades away @@ -136,7 +139,7 @@ function cloneAndFadeOutActor(actor) { return hold; } -var ShellUserVerifier = class extends Signals.EventEmitter { +export class ShellUserVerifier extends Signals.EventEmitter { constructor(client, params = {}) { super(); diff --git a/js/gdm/vmware.js b/js/gdm/vmware.js index 118b8af39..6373d23a4 100644 --- a/js/gdm/vmware.js +++ b/js/gdm/vmware.js @@ -1,13 +1,12 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- -/* exported getVmwareCredentialsManager */ -const Gio = imports.gi.Gio; -const Credential = imports.gdm.credentialManager; +import Gio from 'gi://Gio'; +import * as Credential from './credentialManager.js'; const dbusPath = '/org/vmware/viewagent/Credentials'; const dbusInterface = 'org.vmware.viewagent.Credentials'; -var SERVICE_NAME = 'gdm-vmwcred'; +export const SERVICE_NAME = 'gdm-vmwcred'; const VmwareCredentialsIface = '<node> \ <interface name="' + dbusInterface + '"> \ @@ -20,6 +19,7 @@ const VmwareCredentialsIface = '<node> \ const VmwareCredentialsInfo = Gio.DBusInterfaceInfo.new_for_xml(VmwareCredentialsIface); +/** @type {VmwareCredentialsManager | null} */ let _vmwareCredentialsManager = null; function VmwareCredentials() { @@ -33,7 +33,7 @@ function VmwareCredentials() { return self; } -var VmwareCredentialsManager = class VmwareCredentialsManager extends Credential.CredentialManager { +export class VmwareCredentialsManager extends Credential.CredentialManager { constructor() { super(SERVICE_NAME); this._credentials = VmwareCredentials(); @@ -44,7 +44,7 @@ var VmwareCredentialsManager = class VmwareCredentialsManager extends Credential } }; -function getVmwareCredentialsManager() { +export function getVmwareCredentialsManager() { if (!_vmwareCredentialsManager) _vmwareCredentialsManager = new VmwareCredentialsManager(); diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml index 32d41e11e..9861f183f 100644 --- a/js/js-resources.gresource.xml +++ b/js/js-resources.gresource.xml @@ -13,6 +13,7 @@ <file>misc/config.js</file> <file>misc/extensionUtils.js</file> <file>misc/fileUtils.js</file> + <file>misc/fileUtilsModule.js</file> <file>misc/gnomeSession.js</file> <file>misc/history.js</file> <file>misc/ibusManager.js</file> @@ -120,7 +121,7 @@ <file>ui/workspacesView.js</file> <file>ui/xdndHandler.js</file> - <file>ui/components/__init__.js</file> + <file>ui/components.js</file> <file>ui/components/autorunManager.js</file> <file>ui/components/automountManager.js</file> <file>ui/components/networkAgent.js</file> diff --git a/js/misc/config.d.ts b/js/misc/config.d.ts new file mode 100644 index 000000000..1f1139e26 --- /dev/null +++ b/js/misc/config.d.ts @@ -0,0 +1,19 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +/* The name of this package (not localized) */ +export const PACKAGE_NAME: string; +/* The version of this package */ +export const PACKAGE_VERSION: string; +/* 1 if gnome-bluetooth is available, 0 otherwise */ +export const HAVE_BLUETOOTH: boolean; +/* 1 if networkmanager is available, 0 otherwise */ +export const HAVE_NETWORKMANAGER: boolean; +/* gettext package */ +export const GETTEXT_PACKAGE: string; +/* locale dir */ +export const LOCALEDIR: string; +/* other standard directories */ +export const LIBEXECDIR: string; +export const PKGDATADIR: string; +/* g-i package versions */ +export const LIBMUTTER_API_VERSION: string; diff --git a/js/misc/config.js.in b/js/misc/config.js.in index f9210397a..93d905249 100644 --- a/js/misc/config.js.in +++ b/js/misc/config.js.in @@ -18,4 +18,4 @@ var LOCALEDIR = '@datadir@/locale'; var LIBEXECDIR = '@libexecdir@'; var PKGDATADIR = '@datadir@/@PACKAGE_NAME@'; /* g-i package versions */ -var LIBMUTTER_API_VERSION = '@LIBMUTTER_API_VERSION@' +var LIBMUTTER_API_VERSION = '@LIBMUTTER_API_VERSION@'; diff --git a/js/misc/extensionUtils.js b/js/misc/extensionUtils.js index c86468e4b..67c797499 100644 --- a/js/misc/extensionUtils.js +++ b/js/misc/extensionUtils.js @@ -7,18 +7,19 @@ // Common utils for the extension system and the extension // preferences tool -const { Gio, GLib } = imports.gi; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; -const Gettext = imports.gettext; +import * as Gettext from 'gettext'; const Config = imports.misc.config; -var ExtensionType = { +export const ExtensionType = { SYSTEM: 1, PER_USER: 2, }; -var ExtensionState = { +export const ExtensionState = { ENABLED: 1, DISABLED: 2, ERROR: 3, @@ -41,13 +42,36 @@ const SERIALIZED_PROPERTIES = [ 'canChange', ]; +let extension; +/** @type {typeof import('../ui/main.js').default} */ +let Main; + +export function _setCurrentExtension(currentExtension) { + extension = currentExtension; +} + +/** @param {typeof import('../ui/main.js').default} main */ +export function _setMain(main) { + Main = main; +} + +/** @typedef {object} Extension */ + /** * getCurrentExtension: * - * @returns {?object} - The current extension, or null if not called from + * @returns {Extension} - The current extension, or null if not called from * an extension. */ -function getCurrentExtension() { +export function getCurrentExtension() { + if (extension) { + return extension; + } + + if (!Main) { + throw new Error(`getCurrentExtension cannot be called before ExtensionUtils._setMain`); + } + let stack = new Error().stack.split('\n'); let extensionStackLine; @@ -74,7 +98,6 @@ function getCurrentExtension() { // local import, as the module is used from outside the gnome-shell process // as well (not this function though) - let extensionManager = imports.ui.main.extensionManager; let path = match[1]; let file = Gio.File.new_for_path(path); @@ -82,7 +105,7 @@ function getCurrentExtension() { // Walk up the directory tree, looking for an extension with // the same UUID as a directory name. while (file != null) { - let extension = extensionManager.lookup(file.get_basename()); + let extension = Main.extensionManager.lookup(file.get_basename()); if (extension !== undefined) return extension; file = file.get_parent(); @@ -98,7 +121,7 @@ function getCurrentExtension() { * Initialize Gettext to load translations from extensionsdir/locale. * If @domain is not provided, it will be taken from metadata['gettext-domain'] */ -function initTranslations(domain) { +export function initTranslations(domain) { let extension = getCurrentExtension(); if (!extension) @@ -176,13 +199,13 @@ function callExtensionGettextFunc(func, ...args) { /** * getSettings: * @param {string=} schema - the GSettings schema id - * @returns {Gio.Settings} - a new settings object for @schema + * @returns {import("gi://Gio").Settings} - a new settings object for @schema * * Builds and returns a GSettings schema for @schema, using schema files * in extensionsdir/schemas. If @schema is omitted, it is taken from * metadata['settings-schema']. */ -function getSettings(schema) { +export function getSettings(schema) { let extension = getCurrentExtension(); if (!extension) @@ -198,8 +221,8 @@ function getSettings(schema) { let schemaSource; if (schemaDir.query_exists(null)) { schemaSource = GioSSS.new_from_directory(schemaDir.get_path(), - GioSSS.get_default(), - false); + GioSSS.get_default(), + false); } else { schemaSource = GioSSS.get_default(); } @@ -216,15 +239,14 @@ function getSettings(schema) { * * Open the preference dialog of the current extension */ -function openPrefs() { +export function openPrefs() { const extension = getCurrentExtension(); if (!extension) throw new Error('openPrefs() can only be called from extensions'); try { - const extensionManager = imports.ui.main.extensionManager; - extensionManager.openExtensionPrefs(extension.uuid, '', {}); + Main.extensionManager.openExtensionPrefs(extension.uuid, '', {}); } catch (e) { if (e.name === 'ImportError') throw new Error('openPrefs() cannot be called from preferences'); @@ -232,34 +254,37 @@ function openPrefs() { } } -function isOutOfDate(extension) { +export function isOutOfDate(extension) { const [major] = Config.PACKAGE_VERSION.split('.'); return !extension.metadata['shell-version'].some(v => v.startsWith(major)); } -function serializeExtension(extension) { - let obj = { ...extension.metadata }; +export function serializeExtension(extension) { + let obj = {}; + Object.assign(obj, extension.metadata); SERIALIZED_PROPERTIES.forEach(prop => { obj[prop] = extension[prop]; }); + /** @type {{ [key: string]: GLib.Variant<'b' | 's' | 'd'>}} */ let res = {}; for (let key in obj) { let val = obj[key]; + /** @type {'s' | 'd' | 'b'} */ let type; switch (typeof val) { - case 'string': - type = 's'; - break; - case 'number': - type = 'd'; - break; - case 'boolean': - type = 'b'; - break; - default: - continue; + case 'string': + type = 's'; + break; + case 'number': + type = 'd'; + break; + case 'boolean': + type = 'b'; + break; + default: + continue; } res[key] = GLib.Variant.new(type, val); } @@ -267,7 +292,7 @@ function serializeExtension(extension) { return res; } -function deserializeExtension(variant) { +export function deserializeExtension(variant) { let res = { metadata: {} }; for (let prop in variant) { let val = variant[prop].unpack(); @@ -282,11 +307,4 @@ function deserializeExtension(variant) { return res; } -function installImporter(extension) { - let oldSearchPath = imports.searchPath.slice(); // make a copy - imports.searchPath = [extension.dir.get_parent().get_path()]; - // importing a "subdir" creates a new importer object that doesn't affect - // the global one - extension.imports = imports[extension.uuid]; - imports.searchPath = oldSearchPath; -} +// extension.dir.get_parent().get_path() diff --git a/js/misc/fileUtilsModule.js b/js/misc/fileUtilsModule.js new file mode 100644 index 000000000..b452d3679 --- /dev/null +++ b/js/misc/fileUtilsModule.js @@ -0,0 +1,117 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; + +const Config = imports.misc.config; + +export function collectFromDatadirs(subdir, includeUserDir, processFile) { + let dataDirs = GLib.get_system_data_dirs(); + if (includeUserDir) + dataDirs.unshift(GLib.get_user_data_dir()); + + for (let i = 0; i < dataDirs.length; i++) { + let path = GLib.build_filenamev([dataDirs[i], 'gnome-shell', subdir]); + let dir = Gio.File.new_for_path(path); + + let fileEnum; + try { + fileEnum = dir.enumerate_children('standard::name,standard::type', + Gio.FileQueryInfoFlags.NONE, null); + } catch (e) { + fileEnum = null; + } + if (fileEnum != null) { + let info; + while ((info = fileEnum.next_file(null))) + processFile(fileEnum.get_child(info), info); + } + } +} + +export function recursivelyDeleteDir(dir, deleteParent) { + let children = dir.enumerate_children('standard::name,standard::type', + Gio.FileQueryInfoFlags.NONE, null); + + let info; + while ((info = children.next_file(null)) != null) { + let type = info.get_file_type(); + let child = dir.get_child(info.get_name()); + if (type == Gio.FileType.REGULAR) + child.delete(null); + else if (type == Gio.FileType.DIRECTORY) + recursivelyDeleteDir(child, true); + } + + if (deleteParent) + dir.delete(null); +} + +export function recursivelyMoveDir(srcDir, destDir) { + let children = srcDir.enumerate_children('standard::name,standard::type', + Gio.FileQueryInfoFlags.NONE, null); + + if (!destDir.query_exists(null)) + destDir.make_directory_with_parents(null); + + let info; + while ((info = children.next_file(null)) != null) { + let type = info.get_file_type(); + let srcChild = srcDir.get_child(info.get_name()); + let destChild = destDir.get_child(info.get_name()); + if (type == Gio.FileType.REGULAR) + srcChild.move(destChild, Gio.FileCopyFlags.NONE, null, null); + else if (type == Gio.FileType.DIRECTORY) + recursivelyMoveDir(srcChild, destChild); + } +} + +let _ifaceResource = null; +function ensureIfaceResource() { + if (_ifaceResource) + return; + + // don't use global.datadir so the method is usable from tests/tools + let dir = GLib.getenv('GNOME_SHELL_DATADIR') || Config.PKGDATADIR; + let path = `${dir}/gnome-shell-dbus-interfaces.gresource`; + _ifaceResource = Gio.Resource.load(path); + _ifaceResource._register(); +} + +export function loadInterfaceXML(iface) { + ensureIfaceResource(); + + let uri = `resource:///org/gnome/shell/dbus-interfaces/${iface}.xml`; + let f = Gio.File.new_for_uri(uri); + + try { + let [ok_, bytes] = f.load_contents(null); + return imports.byteArray.toString(bytes); + } catch (e) { + log(`Failed to load D-Bus interface ${iface}`); + } + + return null; +} + +export function loadSubInterfaceXML(iface, ifaceFile) { + let xml = loadInterfaceXML(ifaceFile); + if (!xml) + return null; + + let ifaceStartTag = `<interface name="${iface}">`; + let ifaceStopTag = '</interface>'; + let ifaceStartIndex = xml.indexOf(ifaceStartTag); + let ifaceEndIndex = xml.indexOf(ifaceStopTag, ifaceStartIndex + 1) + ifaceStopTag.length; + + let xmlHeader = '<!DOCTYPE node PUBLIC\n' + + '\'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\'\n' + + '\'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\'>\n' + + '<node>\n'; + let xmlFooter = '</node>'; + + return ( + xmlHeader + + xml.substr(ifaceStartIndex, ifaceEndIndex - ifaceStartIndex) + + xmlFooter); +} diff --git a/js/misc/gnomeSession.js b/js/misc/gnomeSession.js index a77930975..bcfc42459 100644 --- a/js/misc/gnomeSession.js +++ b/js/misc/gnomeSession.js @@ -1,21 +1,22 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported PresenceStatus, Presence, Inhibitor, SessionManager, InhibitFlags */ -const Gio = imports.gi.Gio; +import Gio from 'gi://Gio'; -const { loadInterfaceXML } = imports.misc.fileUtils; +import { loadInterfaceXML } from "../misc/fileUtilsModule.js"; const PresenceIface = loadInterfaceXML('org.gnome.SessionManager.Presence'); -var PresenceStatus = { +/** @enum {number} */ +export const PresenceStatus = { AVAILABLE: 0, INVISIBLE: 1, BUSY: 2, IDLE: 3, }; -var PresenceProxy = Gio.DBusProxy.makeProxyWrapper(PresenceIface); -function Presence(initCallback, cancellable) { +export const PresenceProxy = Gio.DBusProxy.makeProxyWrapper(PresenceIface); +export function Presence(initCallback, cancellable) { return new PresenceProxy(Gio.DBus.session, 'org.gnome.SessionManager', '/org/gnome/SessionManager/Presence', initCallback, cancellable); } @@ -25,18 +26,18 @@ function Presence(initCallback, cancellable) { // of new inhibitors) const InhibitorIface = loadInterfaceXML('org.gnome.SessionManager.Inhibitor'); var InhibitorProxy = Gio.DBusProxy.makeProxyWrapper(InhibitorIface); -function Inhibitor(objectPath, initCallback, cancellable) { +export function Inhibitor(objectPath, initCallback, cancellable) { return InhibitorProxy(Gio.DBus.session, 'org.gnome.SessionManager', objectPath, initCallback, cancellable); } // Not the full interface, only the methods we use const SessionManagerIface = loadInterfaceXML('org.gnome.SessionManager'); var SessionManagerProxy = Gio.DBusProxy.makeProxyWrapper(SessionManagerIface); -function SessionManager(initCallback, cancellable) { +export function SessionManager(initCallback, cancellable) { return SessionManagerProxy(Gio.DBus.session, 'org.gnome.SessionManager', '/org/gnome/SessionManager', initCallback, cancellable); } -var InhibitFlags = { +export const InhibitFlags = { LOGOUT: 1 << 0, SWITCH: 1 << 1, SUSPEND: 1 << 2, diff --git a/js/misc/history.js b/js/misc/history.js index b8b66b7e0..30b694d7c 100644 --- a/js/misc/history.js +++ b/js/misc/history.js @@ -1,12 +1,22 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported HistoryManager */ -const Signals = imports.misc.signals; -const Clutter = imports.gi.Clutter; +import * as Signals from './signals.js'; +import Clutter from 'gi://Clutter'; var DEFAULT_LIMIT = 512; -var HistoryManager = class extends Signals.EventEmitter { +/** + * @typedef {object} HistoryManagerParams + * @property {string | null} gsettingsKey + * @property {number} limit + * @property {Clutter.Text} entry + */ + + export class HistoryManager extends Signals.EventEmitter { + /** + * @param {Partial<HistoryManagerParams>} params + */ constructor(params = {}) { super(); diff --git a/js/misc/ibusManager.js b/js/misc/ibusManager.js index 056bf543f..9e9f707b6 100644 --- a/js/misc/ibusManager.js +++ b/js/misc/ibusManager.js @@ -1,10 +1,12 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- -/* exported getIBusManager */ -const { Gio, GLib, IBus, Meta } = imports.gi; -const Signals = imports.misc.signals; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import IBus from 'gi://IBus'; +import Meta from 'gi://Meta'; +import * as Signals from './signals.js'; -const IBusCandidatePopup = imports.ui.ibusCandidatePopup; +import * as IBusCandidatePopup from '../ui/ibusCandidatePopup.js'; Gio._promisify(IBus.Bus.prototype, 'list_engines_async', 'list_engines_async_finish'); @@ -18,6 +20,7 @@ Gio._promisify(IBus.Bus.prototype, // Ensure runtime version matches _checkIBusVersion(1, 5, 2); +/** @type {IBusManager | null} */ let _ibusManager = null; function _checkIBusVersion(requiredMajor, requiredMinor, requiredMicro) { @@ -32,13 +35,13 @@ function _checkIBusVersion(requiredMajor, requiredMinor, requiredMicro) { requiredMajor, requiredMinor, requiredMicro); } -function getIBusManager() { +export function getIBusManager() { if (_ibusManager == null) _ibusManager = new IBusManager(); return _ibusManager; } -var IBusManager = class extends Signals.EventEmitter { +export class IBusManager extends Signals.EventEmitter { constructor() { super(); diff --git a/js/misc/inputMethod.js b/js/misc/inputMethod.js index 25b02e35b..67e153089 100644 --- a/js/misc/inputMethod.js +++ b/js/misc/inputMethod.js @@ -1,15 +1,19 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported InputMethod */ -const { Clutter, GLib, Gio, GObject, IBus } = imports.gi; +import Clutter from 'gi://Clutter'; +import GLib from 'gi://GLib'; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; +import IBus from 'gi://IBus'; -const Keyboard = imports.ui.status.keyboard; +import * as Keyboard from '../ui/status/keyboard.js'; Gio._promisify(IBus.Bus.prototype, 'create_input_context_async', 'create_input_context_async_finish'); -var HIDE_PANEL_TIME = 50; +export let HIDE_PANEL_TIME = 50; -var InputMethod = GObject.registerClass( +export const InputMethod = GObject.registerClass( class InputMethod extends Clutter.InputMethod { _init() { super._init(); diff --git a/js/misc/introspect.js b/js/misc/introspect.js index 22bd8319c..2a0c7d38a 100644 --- a/js/misc/introspect.js +++ b/js/misc/introspect.js @@ -1,5 +1,9 @@ /* exported IntrospectService */ -const { Gio, GLib, Meta, Shell, St } = imports.gi; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; const APP_ALLOWLIST = [ 'org.freedesktop.impl.portal.desktop.gtk', @@ -8,12 +12,12 @@ const APP_ALLOWLIST = [ const INTROSPECT_DBUS_API_VERSION = 3; -const { loadInterfaceXML } = imports.misc.fileUtils; -const { DBusSenderChecker } = imports.misc.util; +import { loadInterfaceXML } from "./fileUtilsModule.js"; +import { DBusSenderChecker } from "./util.js"; const IntrospectDBusIface = loadInterfaceXML('org.gnome.Shell.Introspect'); -var IntrospectService = class { +export class IntrospectService { constructor() { this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(IntrospectDBusIface, this); @@ -131,6 +135,7 @@ var IntrospectService = class { GetWindowsAsync(params, invocation) { let focusWindow = global.display.get_focus_window(); let apps = this._appSystem.get_running(); + /** @type {{ [key: number]: { [key: string]: GLib.Variant }}} */ let windowsList = {}; try { diff --git a/js/misc/jsParse.js b/js/misc/jsParse.js index c4e077f62..293dd6f5a 100644 --- a/js/misc/jsParse.js +++ b/js/misc/jsParse.js @@ -1,5 +1,4 @@ /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ -/* exported getCompletions, getCommonPrefix, getDeclaredConstants */ // Returns a list of potential completions for text. Completions either // follow a dot (e.g. foo.ba -> bar) or they are picked from globalCompletionList (e.g. fo -> foo) @@ -7,7 +6,7 @@ // consist of global constants that might not carry over from the calling environment. // // This function is likely the one you want to call from external modules -function getCompletions(text, commandHeader, globalCompletionList) { +export function getCompletions(text, commandHeader, globalCompletionList) { let methods = []; let expr_, base; let attrHead = ''; @@ -108,7 +107,7 @@ function findTheBrace(expr, offset, ...braces) { // There is no guarantee of correct javascript syntax between the return // value and offset. This function is meant to take a string like // "foo(Obj.We.Are.Completing" and allow you to extract "Obj.We.Are.Completing" -function getExpressionOffset(expr, offset) { +export function getExpressionOffset(expr, offset) { while (offset >= 0) { let currChar = expr.charAt(offset); @@ -132,7 +131,7 @@ function isValidPropertyName(w) { // To get all properties (enumerable and not), we need to walk // the prototype chain ourselves -function getAllProps(obj) { +export function getAllProps(obj) { if (obj === null || obj === undefined) return []; @@ -144,7 +143,7 @@ function getAllProps(obj) { // e.g., expr="({ foo: null, bar: null, 4: null })" will // return ["foo", "bar", ...] but the list will not include "4", // since methods accessed with '.' notation must star with a letter or _. -function getPropertyNamesFromExpression(expr, commandHeader = '') { +export function getPropertyNamesFromExpression(expr, commandHeader = '') { let obj = {}; if (!isUnsafeExpression(expr)) { try { @@ -170,7 +169,7 @@ function getPropertyNamesFromExpression(expr, commandHeader = '') { } // Given a list of words, returns the longest prefix they all have in common -function getCommonPrefix(words) { +export function getCommonPrefix(words) { let word = words[0]; for (let i = 0; i < word.length; i++) { for (let w = 1; w < words.length; w++) { @@ -221,7 +220,7 @@ function isUnsafeExpression(str) { } // Returns a list of global keywords derived from str -function getDeclaredConstants(str) { +export function getDeclaredConstants(str) { let ret = []; str.split(';').forEach(s => { let base_, keyword; diff --git a/js/misc/keyboardManager.js b/js/misc/keyboardManager.js index 142e2f419..f51224c91 100644 --- a/js/misc/keyboardManager.js +++ b/js/misc/keyboardManager.js @@ -1,42 +1,43 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- -/* exported getKeyboardManager, holdKeyboard, releaseKeyboard */ -const { GLib, GnomeDesktop } = imports.gi; +import GLib from 'gi://GLib'; +import GnomeDesktop from 'gi://GnomeDesktop'; -const Main = imports.ui.main; +import Main from "../ui/main.js"; -var DEFAULT_LOCALE = 'en_US'; -var DEFAULT_LAYOUT = 'us'; -var DEFAULT_VARIANT = ''; +export let DEFAULT_LOCALE = 'en_US'; +export let DEFAULT_LAYOUT = 'us'; +export let DEFAULT_VARIANT = ''; let _xkbInfo = null; -function getXkbInfo() { +export function getXkbInfo() { if (_xkbInfo == null) _xkbInfo = new GnomeDesktop.XkbInfo(); return _xkbInfo; } +/** @type {KeyboardManager | null} */ let _keyboardManager = null; -function getKeyboardManager() { +export function getKeyboardManager() { if (_keyboardManager == null) _keyboardManager = new KeyboardManager(); return _keyboardManager; } -function releaseKeyboard() { +export function releaseKeyboard() { if (Main.modalCount > 0) global.display.unfreeze_keyboard(global.get_current_time()); else global.display.ungrab_keyboard(global.get_current_time()); } -function holdKeyboard() { +export function holdKeyboard() { global.display.freeze_keyboard(global.get_current_time()); } -var KeyboardManager = class { +export class KeyboardManager { constructor() { // The XKB protocol doesn't allow for more that 4 layouts in a // keymap. Wayland doesn't impose this limit and libxkbcommon can diff --git a/js/misc/loginManager.js b/js/misc/loginManager.js index f5ebaf657..7f33763a6 100644 --- a/js/misc/loginManager.js +++ b/js/misc/loginManager.js @@ -1,10 +1,10 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- -/* exported canLock, getLoginManager, registerSessionWithGDM */ -const { GLib, Gio } = imports.gi; -const Signals = imports.misc.signals; +import GLib from 'gi://GLib'; +import Gio from 'gi://Gio'; +import * as Signals from './signals.js'; -const { loadInterfaceXML } = imports.misc.fileUtils; +import { loadInterfaceXML } from "./fileUtilsModule.js"; const SystemdLoginManagerIface = loadInterfaceXML('org.freedesktop.login1.Manager'); const SystemdLoginSessionIface = loadInterfaceXML('org.freedesktop.login1.Session'); @@ -32,7 +32,7 @@ function versionCompare(required, reference) { return true; } -function canLock() { +export function canLock() { try { let params = GLib.Variant.new('(ss)', ['org.gnome.DisplayManager.Manager', 'Version']); let result = Gio.DBus.system.call_sync('org.gnome.DisplayManager', @@ -49,8 +49,7 @@ function canLock() { } } - -async function registerSessionWithGDM() { +export async function registerSessionWithGDM() { log("Registering session with GDM"); try { await Gio.DBus.system.call( @@ -68,6 +67,7 @@ async function registerSessionWithGDM() { } } +/** @type {LoginManagerSystemd | LoginManagerDummy | null} */ let _loginManager = null; /** @@ -76,7 +76,7 @@ let _loginManager = null; * @returns {object} - the LoginManager singleton * */ -function getLoginManager() { +export function getLoginManager() { if (_loginManager == null) { if (haveSystemd()) _loginManager = new LoginManagerSystemd(); @@ -87,7 +87,7 @@ function getLoginManager() { return _loginManager; } -var LoginManagerSystemd = class extends Signals.EventEmitter { +export class LoginManagerSystemd extends Signals.EventEmitter { constructor() { super(); @@ -205,7 +205,7 @@ var LoginManagerSystemd = class extends Signals.EventEmitter { } }; -var LoginManagerDummy = class extends Signals.EventEmitter { +export class LoginManagerDummy extends Signals.EventEmitter { getCurrentSessionProxy(_callback) { // we could return a DummySession object that fakes whatever callers // expect (at the time of writing: connect() and connectSignal() diff --git a/js/misc/modemManager.js b/js/misc/modemManager.js index 912acd349..9c0c3b326 100644 --- a/js/misc/modemManager.js +++ b/js/misc/modemManager.js @@ -1,9 +1,12 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported ModemBase, ModemGsm, ModemCdma, BroadbandModem */ -const { Gio, GObject, NM, NMA } = imports.gi; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; +import NM from 'gi://NM'; +import NMA from 'gi://NMA'; -const { loadInterfaceXML } = imports.misc.fileUtils; +import { loadInterfaceXML } from "./fileUtilsModule.js"; // _getMobileProvidersDatabase: // @@ -98,7 +101,7 @@ const ModemGsmNetworkProxy = Gio.DBusProxy.makeProxyWrapper(ModemGsmNetworkInter const ModemCdmaInterface = loadInterfaceXML('org.freedesktop.ModemManager.Modem.Cdma'); const ModemCdmaProxy = Gio.DBusProxy.makeProxyWrapper(ModemCdmaInterface); -var ModemBase = GObject.registerClass({ +export const ModemBase = GObject.registerClass({ GTypeFlags: GObject.TypeFlags.ABSTRACT, Properties: { 'operator-name': GObject.ParamSpec.string( @@ -111,8 +114,8 @@ var ModemBase = GObject.registerClass({ 0, 100, 0), }, }, class ModemBase extends GObject.Object { - _init() { - super._init(); + _init(...args) { + super._init(...args); this._operatorName = null; this._signalQuality = 0; } @@ -140,7 +143,7 @@ var ModemBase = GObject.registerClass({ } }); -var ModemGsm = GObject.registerClass( +export const ModemGsm = GObject.registerClass( class ModemGsm extends ModemBase { _init(path) { super._init(); @@ -174,7 +177,7 @@ class ModemGsm extends ModemBase { } }); -var ModemCdma = GObject.registerClass( +export const ModemCdma = GObject.registerClass( class ModemCdma extends ModemBase { _init(path) { super._init(); @@ -226,7 +229,7 @@ const BroadbandModem3gppProxy = Gio.DBusProxy.makeProxyWrapper(BroadbandModem3gp const BroadbandModemCdmaInterface = loadInterfaceXML('org.freedesktop.ModemManager1.Modem.ModemCdma'); const BroadbandModemCdmaProxy = Gio.DBusProxy.makeProxyWrapper(BroadbandModemCdmaInterface); -var BroadbandModem = GObject.registerClass({ +export const BroadbandModem = GObject.registerClass({ Properties: { 'capabilities': GObject.ParamSpec.flags( 'capabilities', 'capabilities', 'capabilities', diff --git a/js/misc/objectManager.js b/js/misc/objectManager.js index a69f421ab..b9a4038a6 100644 --- a/js/misc/objectManager.js +++ b/js/misc/objectManager.js @@ -1,8 +1,9 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported ObjectManager */ -const { Gio, GLib } = imports.gi; -const Signals = imports.misc.signals; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import * as Signals from './signals.js'; // Specified in the D-Bus specification here: // http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager @@ -25,7 +26,7 @@ const ObjectManagerIface = ` const ObjectManagerInfo = Gio.DBusInterfaceInfo.new_for_xml(ObjectManagerIface); -var ObjectManager = class extends Signals.EventEmitter { +export class ObjectManager extends Signals.EventEmitter { constructor(params = {}) { super(); @@ -243,6 +244,9 @@ var ObjectManager = class extends Signals.EventEmitter { } } + /** + * @param {string[]} interfaces + */ _registerInterfaces(interfaces) { for (let i = 0; i < interfaces.length; i++) { let info = Gio.DBusInterfaceInfo.new_for_xml(interfaces[i]); diff --git a/js/misc/params.d.ts b/js/misc/params.d.ts new file mode 100644 index 000000000..a1362722f --- /dev/null +++ b/js/misc/params.d.ts @@ -0,0 +1,6 @@ +export type Anyify<D extends { [key: string]: any }> = { [key in keyof D]?: any }; + +export function parse<D extends { [key: string]: any }, P extends { [key: string]: any }>(params: P, defaults: D, allowExtras: true): D & typeof params; +export function parse<D extends { [key: string]: any }, P extends Anyify<D>>(params: P, defaults: D, allowExtras: false): D; +export function parse<D extends { [key: string]: any }, P extends Anyify<D>>(params: P, defaults: D): D; +export function parse<D extends { [key: string]: any }, P extends Anyify<D> | { [key: string]: any }>(params: P, defaults: D, allowExtras: boolean): D | D & typeof params; diff --git a/js/misc/params.js b/js/misc/params.js index 817d66ceb..32ddbf692 100644 --- a/js/misc/params.js +++ b/js/misc/params.js @@ -15,7 +15,7 @@ // // Return value: a new object, containing the merged parameters from // @params and @defaults -function parse(params = {}, defaults, allowExtras) { +export function parse(params = {}, defaults, allowExtras) { if (!allowExtras) { for (let prop in params) { if (!(prop in defaults)) diff --git a/js/misc/parentalControlsManager.js b/js/misc/parentalControlsManager.js index fc1e7ced6..5b42e0715 100644 --- a/js/misc/parentalControlsManager.js +++ b/js/misc/parentalControlsManager.js @@ -21,23 +21,24 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -/* exported getDefault */ +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; +import Shell from 'gi://Shell'; -const { Gio, GObject, Shell } = imports.gi; +import gi from 'gi'; // We require libmalcontent ≥ 0.6.0 -const HAVE_MALCONTENT = imports.package.checkSymbol( - 'Malcontent', '0', 'ManagerGetValueFlags'); +const HAVE_MALCONTENT = true; -var Malcontent = null; +let Malcontent = null; if (HAVE_MALCONTENT) { - Malcontent = imports.gi.Malcontent; + Malcontent = gi.require('Malcontent'); Gio._promisify(Malcontent.Manager.prototype, 'get_app_filter_async', 'get_app_filter_finish'); } let _singleton = null; -function getDefault() { +export function getDefault() { if (_singleton === null) _singleton = new ParentalControlsManager(); @@ -48,7 +49,7 @@ function getDefault() { // parental controls settings. It’s possible for the user’s parental controls // to change at runtime if the Parental Controls application is used by an // administrator from within the user’s session. -var ParentalControlsManager = GObject.registerClass({ +export const ParentalControlsManager = GObject.registerClass({ Signals: { 'app-filter-changed': {}, }, diff --git a/js/misc/permissionStore.js b/js/misc/permissionStore.js index 14fc47f7e..368355406 100644 --- a/js/misc/permissionStore.js +++ b/js/misc/permissionStore.js @@ -1,14 +1,14 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported PermissionStore */ -const Gio = imports.gi.Gio; +import Gio from 'gi://Gio'; -const { loadInterfaceXML } = imports.misc.fileUtils; +import { loadInterfaceXML } from "./fileUtilsModule.js"; const PermissionStoreIface = loadInterfaceXML('org.freedesktop.impl.portal.PermissionStore'); const PermissionStoreProxy = Gio.DBusProxy.makeProxyWrapper(PermissionStoreIface); -function PermissionStore(initCallback, cancellable) { +export function PermissionStore(initCallback, cancellable) { return PermissionStoreProxy(Gio.DBus.session, 'org.freedesktop.impl.portal.PermissionStore', '/org/freedesktop/impl/portal/PermissionStore', diff --git a/js/misc/signals.d.ts b/js/misc/signals.d.ts new file mode 100644 index 000000000..616198048 --- /dev/null +++ b/js/misc/signals.d.ts @@ -0,0 +1,9 @@ +export type EventId = number; + +export class EventEmitter { + connect(event: string, handler: (...args: any[]) => any): EventId; + disconnect(id: EventId); + emit(event: string, ...args: any[]); + disconnectAll(); + signalHandlerIsConnected(event: string); +} diff --git a/js/misc/signals.js b/js/misc/signals.js index 48b3429f3..2eb6d3254 100644 --- a/js/misc/signals.js +++ b/js/misc/signals.js @@ -1,5 +1,6 @@ +/** @type {import("environment").SignalsNamespace} */ const Signals = imports.signals; -var EventEmitter = class EventEmitter {}; +export class EventEmitter {} Signals.addSignalMethods(EventEmitter.prototype); diff --git a/js/misc/smartcardManager.js b/js/misc/smartcardManager.js index 0e03ab86d..75ed354bd 100644 --- a/js/misc/smartcardManager.js +++ b/js/misc/smartcardManager.js @@ -1,10 +1,9 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- -/* exported getSmartcardManager */ -const Gio = imports.gi.Gio; -const Signals = imports.misc.signals; +import Gio from 'gi://Gio'; +import * as Signals from './signals.js'; -const ObjectManager = imports.misc.objectManager; +import * as ObjectManager from "./objectManager.js"; const SmartcardTokenIface = ` <node> @@ -16,16 +15,17 @@ const SmartcardTokenIface = ` </interface> </node>`; +/** @type {SmartcardManager | null} */ let _smartcardManager = null; -function getSmartcardManager() { +export function getSmartcardManager() { if (_smartcardManager == null) _smartcardManager = new SmartcardManager(); return _smartcardManager; } -var SmartcardManager = class extends Signals.EventEmitter { +export class SmartcardManager extends Signals.EventEmitter { constructor() { super(); diff --git a/js/misc/systemActions.js b/js/misc/systemActions.js index 10cab9a9e..fba24c371 100644 --- a/js/misc/systemActions.js +++ b/js/misc/systemActions.js @@ -1,9 +1,17 @@ /* exported getDefault */ -const { AccountsService, Clutter, Gdm, Gio, GLib, GObject, Meta } = imports.gi; +import AccountsService from 'gi://AccountsService'; +import Clutter from 'gi://Clutter'; +import Gdm from 'gi://Gdm'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; -const GnomeSession = imports.misc.gnomeSession; -const LoginManager = imports.misc.loginManager; -const Main = imports.ui.main; + +import * as GnomeSession from "./gnomeSession.js"; +import * as LoginManager from "./loginManager.js"; + +import Main from "../ui/main.js"; const LOCKDOWN_SCHEMA = 'org.gnome.desktop.lockdown'; const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen'; @@ -23,7 +31,7 @@ const LOCK_ORIENTATION_ACTION_ID = 'lock-orientation'; let _singleton = null; -function getDefault() { +export function getDefault() { if (_singleton == null) _singleton = new SystemActions(); @@ -137,7 +145,8 @@ const SystemActions = GObject.registerClass({ this._lockdownSettings = new Gio.Settings({ schema_id: LOCKDOWN_SCHEMA }); this._orientationSettings = new Gio.Settings({ schema_id: 'org.gnome.settings-daemon.peripherals.touchscreen' }); - this._session = new GnomeSession.SessionManager(); + // FIXME + this._session = GnomeSession.SessionManager(); this._loginManager = LoginManager.getLoginManager(); this._monitorManager = Meta.MonitorManager.get(); diff --git a/js/misc/util.js b/js/misc/util.js index dd25eacce..4a54606e4 100644 --- a/js/misc/util.js +++ b/js/misc/util.js @@ -4,12 +4,18 @@ ensureActorVisibleInScrollView, wiggle, lerp, GNOMEversionCompare, DBusSenderChecker */ -const { Clutter, Gio, GLib, Shell, St, GnomeDesktop } = imports.gi; -const Gettext = imports.gettext; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; +import GnomeDesktop from 'gi://GnomeDesktop'; -const Main = imports.ui.main; +import Main from "../ui/main.js"; -var SCROLL_TIME = 100; +import * as Gettext from 'gettext'; + +export let SCROLL_TIME = 100; const WIGGLE_OFFSET = 6; const WIGGLE_DURATION = 65; @@ -52,7 +58,7 @@ let _desktopSettings = null; // the position within @str where the URL was found. // // Return value: the list of match objects, as described above -function findUrls(str) { +export function findUrls(str) { let res = [], match; while ((match = _urlRegexp.exec(str))) res.push({ url: match[2], pos: match.index + match[1].length }); @@ -64,7 +70,7 @@ function findUrls(str) { // // Runs @argv in the background, handling any errors that occur // when trying to start the program. -function spawn(argv) { +export function spawn(argv) { try { trySpawn(argv); } catch (err) { @@ -77,7 +83,7 @@ function spawn(argv) { // // Runs @commandLine in the background, handling any errors that // occur when trying to parse or start the program. -function spawnCommandLine(commandLine) { +export function spawnCommandLine(commandLine) { try { let [success_, argv] = GLib.shell_parse_argv(commandLine); trySpawn(argv); @@ -90,7 +96,7 @@ function spawnCommandLine(commandLine) { // @argv: an argv array // // Runs @argv as if it was an application, handling startup notification -function spawnApp(argv) { +export function spawnApp(argv) { try { let app = Gio.AppInfo.create_from_commandline(argv.join(' '), null, Gio.AppInfoCreateFlags.SUPPORTS_STARTUP_NOTIFICATION); @@ -108,7 +114,7 @@ function spawnApp(argv) { // Runs @argv in the background. If launching @argv fails, // this will throw an error. function trySpawn(argv) { - var success_, pid; + let success_, pid; try { [success_, pid] = GLib.spawn_async(null, argv, null, GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD, @@ -145,7 +151,7 @@ function trySpawn(argv) { // // Runs @commandLine in the background. If launching @commandLine // fails, this will throw an error. -function trySpawnCommandLine(commandLine) { +export function trySpawnCommandLine(commandLine) { let success_, argv; try { @@ -165,7 +171,7 @@ function _handleSpawnError(command, err) { Main.notifyError(title, err.message); } -function formatTimeSpan(date) { +export function formatTimeSpan(date) { let now = GLib.DateTime.new_now_local(); let timespan = now.difference(date); @@ -205,7 +211,17 @@ function formatTimeSpan(date) { "%d years ago", yearsAgo).format(yearsAgo); } -function formatTime(time, params = {}) { +/** + * @typedef {object} FormatTimeProps + * @property {boolean} timeOnly + * @property {boolean} ampm + */ + +/** + * @param {GLib.DateTime | Date} time + * @param {Partial<FormatTimeProps>} params + */ +export function formatTime(time, params) { let date; // HACK: The built-in Date type sucks at timezones, which we need for the // world clock; it's often more convenient though, so allow either @@ -300,7 +316,7 @@ function formatTime(time, params = {}) { return formattedTime.replace(/([:\u2236])/g, '\u200e$1'); } -function createTimeLabel(date, params) { +export function createTimeLabel(date, params) { if (_desktopSettings == null) _desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' }); @@ -356,14 +372,14 @@ function lowerBound(array, val, cmp) { // Inserts @val into @array, preserving the // sorting invariants. // Returns the position at which it was inserted -function insertSorted(array, val, cmp) { +export function insertSorted(array, val, cmp) { let pos = lowerBound(array, val, cmp); array.splice(pos, 0, val); return pos; } -function ensureActorVisibleInScrollView(scrollView, actor) { +export function ensureActorVisibleInScrollView(scrollView, actor) { let adjustment = scrollView.vscroll.adjustment; let [value, lower_, upper, stepIncrement_, pageIncrement_, pageSize] = adjustment.get_values(); @@ -399,7 +415,18 @@ function ensureActorVisibleInScrollView(scrollView, actor) { }); } -function wiggle(actor, params = {}) { +/** + * @typedef {object} WiggleProps + * @property {number} offset + * @property {number} duration + * @property {number} wiggleCount + */ + +/** + * @param {Clutter.Actor} actor + * @param {Partial<WiggleProps>} [params] + */ +export function wiggle(actor, params = {}) { if (!St.Settings.get().enable_animations) return; @@ -436,7 +463,7 @@ function wiggle(actor, params = {}) { }); } -function lerp(start, end, progress) { +export function lerp(start, end, progress) { return start + progress * (end - start); } @@ -462,7 +489,7 @@ function _GNOMEversionToNumber(version) { // // Returns an integer less than, equal to, or greater than // zero, if version1 is older, equal or newer than version2 -function GNOMEversionCompare(version1, version2) { +export function GNOMEversionCompare(version1, version2) { const v1Array = version1.split('.'); const v2Array = version2.split('.'); @@ -478,7 +505,7 @@ function GNOMEversionCompare(version1, version2) { return 0; } -var DBusSenderChecker = class { +export class DBusSenderChecker { /** * @param {string[]} allowList - list of allowed well-known names */ diff --git a/js/misc/weather.js b/js/misc/weather.js index 5d6cca1ea..eca6c3d78 100644 --- a/js/misc/weather.js +++ b/js/misc/weather.js @@ -1,12 +1,16 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported WeatherClient */ -const { Geoclue, Gio, GLib, GWeather, Shell } = imports.gi; -const Signals = imports.misc.signals; +import Geoclue from 'gi://Geoclue'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GWeather from 'gi://GWeather'; +import Shell from 'gi://Shell'; +import * as Signals from './signals.js'; -const PermissionStore = imports.misc.permissionStore; +import * as PermissionStore from "./permissionStore.js"; -const { loadInterfaceXML } = imports.misc.fileUtils; +import { loadInterfaceXML } from "./fileUtilsModule.js"; Gio._promisify(Geoclue.Simple, 'new', 'new_finish'); @@ -18,10 +22,11 @@ const WEATHER_INTEGRATION_IFACE = 'org.gnome.Shell.WeatherIntegration'; const WEATHER_APP_ID = 'org.gnome.Weather.desktop'; +export let UPDATE_THRESHOLD = 10 * GLib.TIME_SPAN_MINUTE; + // Minimum time between updates to show loading indication -var UPDATE_THRESHOLD = 10 * GLib.TIME_SPAN_MINUTE; -var WeatherClient = class extends Signals.EventEmitter { +export class WeatherClient extends Signals.EventEmitter { constructor() { super(); @@ -293,7 +298,7 @@ var WeatherClient = class extends Signals.EventEmitter { } _onLocationsChanged() { - let locations = this._settings.get_value('locations').deep_unpack(); + let locations = /** @type {GLib.Variant<'av'>} */ (this._settings.get_value('locations')).deep_unpack(); let serialized = locations.shift(); let mostRecentLocation = null; diff --git a/js/perf/basic.js b/js/perf/basic.js index 718532f28..9f05c6d76 100644 --- a/js/perf/basic.js +++ b/js/perf/basic.js @@ -5,15 +5,16 @@ */ /* eslint camelcase: ["error", { properties: "never", allow: ["^script_"] }] */ -const { St } = imports.gi; +import St from 'gi://St'; -const Main = imports.ui.main; -const MessageTray = imports.ui.messageTray; -const Scripting = imports.ui.scripting; +import Main from "../ui/main.js"; + +import * as MessageTray from "../ui/messageTray.js"; +import * as Scripting from "../ui/scripting.js"; // This script tests the most important (basic) functionality of the shell. -var METRICS = {}; +export const METRICS = {}; async function run() { /* eslint-disable no-await-in-loop */ diff --git a/js/perf/core.js b/js/perf/core.js index f3f496b03..da5ba378f 100644 --- a/js/perf/core.js +++ b/js/perf/core.js @@ -5,17 +5,17 @@ clutter_stagePaintDone */ /* eslint camelcase: ["error", { properties: "never", allow: ["^script_", "^malloc", "^glx", "^clutter"] }] */ -const System = imports.system; +import System from 'system'; -const Main = imports.ui.main; -const Scripting = imports.ui.scripting; +import Main from "../ui/main.js"; +import * as Scripting from "../ui/scripting.js"; // This performance script measure the most important (core) performance // metrics for the shell. By looking at the output metrics of this script // someone should be able to get an idea of how well the shell is performing // on a particular system. -var METRICS = { +export const METRICS = { overviewLatencyFirst: { description: "Time to first frame after triggering overview, first time", units: "us" }, diff --git a/js/perf/hwtest.js b/js/perf/hwtest.js index 0f396acd3..321e61960 100644 --- a/js/perf/hwtest.js +++ b/js/perf/hwtest.js @@ -7,11 +7,14 @@ script_geditLaunch, script_geditFirstFrame, clutter_stagePaintStart, clutter_paintCompletedTimestamp */ /* eslint camelcase: ["error", { properties: "never", allow: ["^script_", "^clutter"] }] */ -const { Clutter, Gio, Shell } = imports.gi; -const Main = imports.ui.main; -const Scripting = imports.ui.scripting; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import Shell from 'gi://Shell'; -var METRICS = { +import Main from "../ui/main.js"; +import * as Scripting from "../ui/scripting.js"; + +export const METRICS = { timeToDesktop: { description: "Time from starting graphical.target to desktop showing", units: "us" }, diff --git a/js/ui/accessDialog.js b/js/ui/accessDialog.js index 7c8d69b80..dac1ed50d 100644 --- a/js/ui/accessDialog.js +++ b/js/ui/accessDialog.js @@ -1,22 +1,29 @@ /* exported AccessDialogDBus */ -const { Clutter, Gio, GLib, GObject, Shell, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; -const CheckBox = imports.ui.checkBox; -const Dialog = imports.ui.dialog; -const ModalDialog = imports.ui.modalDialog; -const { loadInterfaceXML } = imports.misc.fileUtils; +import * as CheckBox from './checkBox.js'; +import * as Dialog from './dialog.js'; +import * as ModalDialog from './modalDialog.js'; + +import { loadInterfaceXML } from '../misc/fileUtilsModule.js'; const RequestIface = loadInterfaceXML('org.freedesktop.impl.portal.Request'); const AccessIface = loadInterfaceXML('org.freedesktop.impl.portal.Access'); -var DialogResponse = { +/** @enum {number} */ +export const DialogResponse = { OK: 0, CANCEL: 1, CLOSED: 2, }; -var AccessDialog = GObject.registerClass( +export const AccessDialog = GObject.registerClass( class AccessDialog extends ModalDialog.ModalDialog { _init(invocation, handle, title, description, body, options) { super._init({ styleClass: 'access-dialog' }); @@ -100,6 +107,7 @@ class AccessDialog extends ModalDialog.ModalDialog { this._request.unexport(); this._requestExported = false; + /** @type {{ [key: string]: GLib.Variant<'s'> }} */ let results = {}; if (response == DialogResponse.OK) { for (let [id, check] of this._choices) { @@ -117,7 +125,7 @@ class AccessDialog extends ModalDialog.ModalDialog { } }); -var AccessDialogDBus = class { +export class AccessDialogDBus { constructor() { this._accessDialog = null; diff --git a/js/ui/altTab.js b/js/ui/altTab.js index 6af9380ed..f428f3fbb 100644 --- a/js/ui/altTab.js +++ b/js/ui/altTab.js @@ -2,24 +2,33 @@ /* exported AppSwitcherPopup, GroupCyclerPopup, WindowSwitcherPopup, WindowCyclerPopup */ -const { Atk, Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi; +import Atk from 'gi://Atk'; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; -const Main = imports.ui.main; -const SwitcherPopup = imports.ui.switcherPopup; -var APP_ICON_HOVER_TIMEOUT = 200; // milliseconds +import Main from './main.js'; +import * as SwitcherPopup from './switcherPopup.js'; -var THUMBNAIL_DEFAULT_SIZE = 256; -var THUMBNAIL_POPUP_TIME = 500; // milliseconds -var THUMBNAIL_FADE_TIME = 100; // milliseconds +export let APP_ICON_HOVER_TIMEOUT = 200; // milliseconds -var WINDOW_PREVIEW_SIZE = 128; -var APP_ICON_SIZE = 96; -var APP_ICON_SIZE_SMALL = 48; +export let THUMBNAIL_DEFAULT_SIZE = 256; +export let THUMBNAIL_POPUP_TIME = 500; // milliseconds +export let THUMBNAIL_FADE_TIME = 100; // milliseconds + +export let WINDOW_PREVIEW_SIZE = 128; +export let APP_ICON_SIZE = 96; +export let APP_ICON_SIZE_SMALL = 48; const baseIconSizes = [96, 64, 48, 32, 22]; -var AppIconMode = { +/** @enum {number} */ +export const AppIconMode = { THUMBNAIL_ONLY: 1, APP_ICON_ONLY: 2, BOTH: 3, @@ -38,7 +47,7 @@ function _createWindowClone(window, size) { y_expand: true }); } -function getWindows(workspace) { +export function getWindows(workspace) { // We ignore skip-taskbar windows in switchers, but if they are attached // to their parent, their position in the MRU list may be more appropriate // than the parent; so start with the complete list ... @@ -51,7 +60,7 @@ function getWindows(workspace) { }).filter((w, i, a) => !w.skip_taskbar && a.indexOf(w) == i); } -var AppSwitcherPopup = GObject.registerClass( +export const AppSwitcherPopup = GObject.registerClass( class AppSwitcherPopup extends SwitcherPopup.SwitcherPopup { _init() { super._init(); @@ -290,8 +299,8 @@ class AppSwitcherPopup extends SwitcherPopup.SwitcherPopup { /** * _select: * @param {number} app: index of the app to select - * @param {number=} window: index of which of @app's windows to select - * @param {bool} forceAppFocus: optional flag, see below + * @param {number} [window]: index of which of @app's windows to select + * @param {boolean} [forceAppFocus]: optional flag, see below * * Selects the indicated @app, and optional @window, and sets * this._thumbnailsFocused appropriately to indicate whether the @@ -397,13 +406,14 @@ class AppSwitcherPopup extends SwitcherPopup.SwitcherPopup { } }); -var CyclerHighlight = GObject.registerClass( +export const CyclerHighlight = GObject.registerClass( class CyclerHighlight extends St.Widget { _init() { super._init({ layout_manager: new Clutter.BinLayout() }); this._window = null; this._sizeChangedId = 0; + /** @type {Clutter.Clone<Meta.WindowActor>} */ this._clone = new Clutter.Clone(); this.add_actor(this._clone); @@ -465,7 +475,7 @@ class CyclerHighlight extends St.Widget { // We don't show an actual popup, so just provide what SwitcherPopup // expects instead of inheriting from SwitcherList -var CyclerList = GObject.registerClass({ +export const CyclerList = GObject.registerClass({ Signals: { 'item-activated': { param_types: [GObject.TYPE_INT] }, 'item-entered': { param_types: [GObject.TYPE_INT] }, 'item-removed': { param_types: [GObject.TYPE_INT] }, @@ -476,7 +486,7 @@ var CyclerList = GObject.registerClass({ } }); -var CyclerPopup = GObject.registerClass({ +export const CyclerPopup = GObject.registerClass({ GTypeFlags: GObject.TypeFlags.ABSTRACT, }, class CyclerPopup extends SwitcherPopup.SwitcherPopup { _init() { @@ -493,6 +503,13 @@ var CyclerPopup = GObject.registerClass({ }); } + /** + * @returns {Meta.Window[]} + */ + _getWindows() { + throw new GObject.NotImplementedError(`_getWindows in ${this.constructor.name}`); + } + _highlightItem(index, _justOutline) { this._highlight.window = this._items[index]; global.window_group.set_child_above_sibling(this._highlight, null); @@ -532,7 +549,7 @@ var CyclerPopup = GObject.registerClass({ }); -var GroupCyclerPopup = GObject.registerClass( +export const GroupCyclerPopup = GObject.registerClass( class GroupCyclerPopup extends CyclerPopup { _init() { this._settings = new Gio.Settings({ schema_id: 'org.gnome.shell.app-switcher' }); @@ -565,7 +582,7 @@ class GroupCyclerPopup extends CyclerPopup { } }); -var WindowSwitcherPopup = GObject.registerClass( +export const WindowSwitcherPopup = GObject.registerClass( class WindowSwitcherPopup extends SwitcherPopup.SwitcherPopup { _init() { super._init(); @@ -623,7 +640,7 @@ class WindowSwitcherPopup extends SwitcherPopup.SwitcherPopup { } }); -var WindowCyclerPopup = GObject.registerClass( +export const WindowCyclerPopup = GObject.registerClass( class WindowCyclerPopup extends CyclerPopup { _init() { this._settings = new Gio.Settings({ schema_id: 'org.gnome.shell.window-switcher' }); @@ -654,12 +671,15 @@ class WindowCyclerPopup extends CyclerPopup { } }); -var AppIcon = GObject.registerClass( +export const AppIcon = GObject.registerClass( class AppIcon extends St.BoxLayout { _init(app) { super._init({ style_class: 'alt-tab-app', vertical: true }); + /** @type {Meta.Window[]} */ + this.cachedWindows = []; + this.app = app; this.icon = null; this._iconBin = new St.Bin(); @@ -679,7 +699,7 @@ class AppIcon extends St.BoxLayout { } }); -var AppSwitcher = GObject.registerClass( +export const AppSwitcher = GObject.registerClass( class AppSwitcher extends SwitcherPopup.SwitcherList { _init(apps, altTabPopup) { super._init(true); @@ -814,6 +834,8 @@ class AppSwitcher extends SwitcherPopup.SwitcherList { } else { this._itemEntered(index); } + + return false; } _enterItem(index) { @@ -884,7 +906,7 @@ class AppSwitcher extends SwitcherPopup.SwitcherList { } }); -var ThumbnailSwitcher = GObject.registerClass( +export const ThumbnailSwitcher = GObject.registerClass( class ThumbnailSwitcher extends SwitcherPopup.SwitcherList { _init(windows) { super._init(false); @@ -892,6 +914,7 @@ class ThumbnailSwitcher extends SwitcherPopup.SwitcherList { this._labels = []; this._thumbnailBins = []; this._clones = []; + /** @type {Meta.Window[]} */ this._windows = windows; for (let i = 0; i < windows.length; i++) { @@ -979,7 +1002,7 @@ class ThumbnailSwitcher extends SwitcherPopup.SwitcherList { } }); -var WindowIcon = GObject.registerClass( +export const WindowIcon = GObject.registerClass( class WindowIcon extends St.BoxLayout { _init(window, mode) { super._init({ style_class: 'alt-tab-app', @@ -987,6 +1010,8 @@ class WindowIcon extends St.BoxLayout { this.window = window; + this._unmanagedSignalId = -1; + this._icon = new St.Widget({ layout_manager: new Clutter.BinLayout() }); this.add_child(this._icon); @@ -1037,7 +1062,7 @@ class WindowIcon extends St.BoxLayout { } }); -var WindowSwitcher = GObject.registerClass( +export const WindowSwitcher = GObject.registerClass( class WindowSwitcher extends SwitcherPopup.SwitcherList { _init(windows, mode) { super._init(true); @@ -1070,6 +1095,9 @@ class WindowSwitcher extends SwitcherPopup.SwitcherList { }); } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_height(forWidth) { let [minHeight, natHeight] = super.vfunc_get_preferred_height(forWidth); diff --git a/js/ui/animation.js b/js/ui/animation.js index b81eb2b90..f98a13f97 100644 --- a/js/ui/animation.js +++ b/js/ui/animation.js @@ -1,14 +1,26 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Animation, AnimatedIcon, Spinner */ -const { Clutter, GLib, GObject, Gio, St } = imports.gi; - - -var ANIMATED_ICON_UPDATE_TIMEOUT = 16; -var SPINNER_ANIMATION_TIME = 300; -var SPINNER_ANIMATION_DELAY = 1000; - -var Animation = GObject.registerClass( +import Clutter from 'gi://Clutter'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Gio from 'gi://Gio'; +import St from 'gi://St'; + + +export let ANIMATED_ICON_UPDATE_TIMEOUT = 16; +export let SPINNER_ANIMATION_TIME = 300; +export let SPINNER_ANIMATION_DELAY = 1000; + +/** + * @typedef {object} AnimationParams + * @property {Gio.File} file + * @property {number} width + * @property {number} height + * @property {number} speed + */ + +export const Animation = GObject.registerClass( class Animation extends St.Bin { _init(file, width, height, speed) { const themeContext = St.ThemeContext.get_for_stage(global.stage); @@ -129,15 +141,26 @@ class Animation extends St.Bin { } }); -var AnimatedIcon = GObject.registerClass( +export const AnimatedIcon = GObject.registerClass( class AnimatedIcon extends Animation { _init(file, size) { super._init(file, size, size, ANIMATED_ICON_UPDATE_TIMEOUT); } }); -var Spinner = GObject.registerClass( +export const Spinner = GObject.registerClass( class Spinner extends AnimatedIcon { + /** + * @typedef {object} SpinnerParams + * @property {boolean} [animate] + * @property {boolean} [hideOnStop] + */ + + /** + * _init: + * @param {number} size + * @param {SpinnerParams} [params] + */ _init(size, params = {}) { const { animate = false, diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 984844f11..0c2c3c9d8 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -1,39 +1,45 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported AppDisplay, AppSearchProvider */ -const { Clutter, Gio, GLib, GObject, Graphene, Meta, - Pango, Shell, St } = imports.gi; - -const AppFavorites = imports.ui.appFavorites; -const { AppMenu } = imports.ui.appMenu; -const BoxPointer = imports.ui.boxpointer; -const DND = imports.ui.dnd; -const GrabHelper = imports.ui.grabHelper; -const IconGrid = imports.ui.iconGrid; -const Layout = imports.ui.layout; -const Main = imports.ui.main; -const PageIndicators = imports.ui.pageIndicators; -const ParentalControlsManager = imports.misc.parentalControlsManager; -const PopupMenu = imports.ui.popupMenu; -const Search = imports.ui.search; -const SwipeTracker = imports.ui.swipeTracker; -const SystemActions = imports.misc.systemActions; - -var MENU_POPUP_TIMEOUT = 600; -var POPDOWN_DIALOG_TIMEOUT = 500; - -var FOLDER_SUBICON_FRACTION = .4; - -var VIEWS_SWITCH_TIME = 400; -var VIEWS_SWITCH_ANIMATION_DELAY = 100; - -var SCROLL_TIMEOUT_TIME = 150; - -var APP_ICON_SCALE_IN_TIME = 500; -var APP_ICON_SCALE_IN_DELAY = 700; - -var APP_ICON_TITLE_EXPAND_TIME = 200; -var APP_ICON_TITLE_COLLAPSE_TIME = 100; +import Clutter from 'gi://Clutter'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Gio from 'gi://Gio'; + +import Graphene from 'gi://Graphene'; +import Pango from 'gi://Pango'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; + +import * as AppFavorites from './appFavorites.js'; +import * as DND from './dnd.js'; +import * as GrabHelper from './grabHelper.js'; +import * as IconGrid from './iconGrid.js'; +import * as Layout from './layout.js'; +import Main from './main.js'; +import * as PageIndicators from './pageIndicators.js'; +import * as ParentalControlsManager from '../misc/parentalControlsManager.js'; +import * as PopupMenu from './popupMenu.js'; +import * as Search from './search.js'; +import * as SwipeTracker from './swipeTracker.js'; +import * as SystemActions from '../misc/systemActions.js'; + +export const MENU_POPUP_TIMEOUT = 600; +export const POPDOWN_DIALOG_TIMEOUT = 500; + +export const FOLDER_SUBICON_FRACTION = .4; + +export const VIEWS_SWITCH_TIME = 400; +export const VIEWS_SWITCH_ANIMATION_DELAY = 100; + +export const SCROLL_TIMEOUT_TIME = 150; + +export const APP_ICON_SCALE_IN_TIME = 500; +export const APP_ICON_SCALE_IN_DELAY = 700; + +export const APP_ICON_TITLE_EXPAND_TIME = 200; +export const APP_ICON_TITLE_COLLAPSE_TIME = 100; const FOLDER_DIALOG_ANIMATION_TIME = 200; @@ -86,14 +92,9 @@ function _getFolderName(folder) { return name; } -function _getViewFromIcon(icon) { - for (let parent = icon.get_parent(); parent; parent = parent.get_parent()) { - if (parent instanceof BaseAppView) - return parent; - } - return null; -} - +/** + * @param {readonly Shell.App[]} apps + */ function _findBestFolderName(apps) { let appInfos = apps.map(app => app.get_app_info()); @@ -126,7 +127,7 @@ function _findBestFolderName(apps) { return null; } -var BaseAppView = GObject.registerClass({ +export const BaseAppView = GObject.registerClass({ GTypeFlags: GObject.TypeFlags.ABSTRACT, Properties: { 'gesture-modes': GObject.ParamSpec.flags( @@ -283,8 +284,9 @@ var BaseAppView = GObject.registerClass({ this._box.add_child(this._pageIndicators); // Swipe + // FIXME this._swipeTracker = new SwipeTracker.SwipeTracker(this._scrollView, - Clutter.Orientation.HORIZONTAL, this.gestureModes); + Clutter.Orientation.HORIZONTAL); this._swipeTracker.orientation = Clutter.Orientation.HORIZONTAL; this._swipeTracker.connect('begin', this._swipeBegin.bind(this)); this._swipeTracker.connect('update', this._swipeUpdate.bind(this)); @@ -395,7 +397,7 @@ var BaseAppView = GObject.registerClass({ if (hOffset === 0 && vOffset === 0) return; } - + // @ts-expect-error this._scrollView.update_fade_effect( new Clutter.Margin({ left: hOffset, @@ -725,7 +727,7 @@ var BaseAppView = GObject.registerClass({ this._swipeTracker.enabled = true; } - _onDragCancelled() { + _onDragCancelled(overview, source) { // At this point, the positions aren't stored yet, thus _redisplay() // will move all items to their original positions this._redisplay(); @@ -832,6 +834,13 @@ var BaseAppView = GObject.registerClass({ return [page, position]; } + /** + * @returns {AppViewItem["prototype"][]} + */ + _loadApps() { + throw new GObject.NotImplementedError(`_loadApps in ${this.constructor.name}`); + } + _redisplay() { let oldApps = this._orderedItems.slice(); let oldAppIds = oldApps.map(icon => icon.id); @@ -1348,7 +1357,7 @@ var BaseAppView = GObject.registerClass({ } }); -var PageManager = GObject.registerClass({ +export const PageManager = GObject.registerClass({ Signals: { 'layout-changed': {} }, }, class PageManager extends GObject.Object { _init() { @@ -1362,8 +1371,10 @@ var PageManager = GObject.registerClass({ } _loadPages() { + /** @type {GLib.Variant<'aa{sv}'>} */ const layout = global.settings.get_value('app-picker-layout'); - this._pages = layout.recursiveUnpack(); + /** @type { { [key: string]: { [key: string]: number } }[] } */ + this._pages = (layout.recursiveUnpack()); if (!this._updatingPages) this.emit('layout-changed'); } @@ -1385,11 +1396,16 @@ var PageManager = GObject.registerClass({ return [page, position]; } - set pages(p) { + /** + * @param {{ [key: string]: { [key: string]: GLib.Variant<'i'> } }[]} p + */ + updatePages(p) { + /** @type {{ [key: string]: GLib.Variant<'a{sv}'> }[]} */ const packedPages = []; // Pack the icon properties as a GVariant for (const page of p) { + /** @type {{ [key: string]: GLib.Variant<'a{sv}'> }} */ const pageData = {}; for (const [appId, properties] of Object.entries(page)) pageData[appId] = new GLib.Variant('a{sv}', properties); @@ -1409,7 +1425,7 @@ var PageManager = GObject.registerClass({ } }); -var AppDisplay = GObject.registerClass( +export const AppDisplay = GObject.registerClass( class AppDisplay extends BaseAppView { _init() { super._init({ @@ -1493,11 +1509,13 @@ class AppDisplay extends BaseAppView { } _savePages() { + /** @type {{ [key: string]: { [key: string]: GLib.Variant<'i'> } }[]} */ const pages = []; for (let i = 0; i < this._grid.nPages; i++) { const pageItems = this._grid.getItemsAtPage(i).filter(c => c.visible); + /** @type {{ [key: string]: { [key: string]: GLib.Variant<'i'> } }} */ const pageData = {}; pageItems.forEach((item, index) => { @@ -1508,7 +1526,7 @@ class AppDisplay extends BaseAppView { pages.push(pageData); } - this._pageManager.pages = pages; + this._pageManager.updatePages(pages); } _ensurePlaceholder(source) { @@ -1752,8 +1770,8 @@ class AppDisplay extends BaseAppView { super._maybeMoveItem(clonedEvent); } - _onDragBegin(overview, source) { - super._onDragBegin(overview, source); + _onDragBegin(_overview, source) { + super._onDragBegin(); // When dragging from a folder dialog, the dragged app icon doesn't // exist in AppDisplay. We work around that by adding a placeholder @@ -1778,7 +1796,7 @@ class AppDisplay extends BaseAppView { } _onDragCancelled(overview, source) { - const view = _getViewFromIcon(source); + const view = source._getView(); if (view instanceof FolderView) return; @@ -1792,7 +1810,7 @@ class AppDisplay extends BaseAppView { this._savePages(); - let view = _getViewFromIcon(source); + let view = source._getView(); if (view instanceof FolderView) view.removeApp(source.app); @@ -1860,7 +1878,7 @@ class AppDisplay extends BaseAppView { } }); -var AppSearchProvider = class AppSearchProvider { +export class AppSearchProvider { constructor() { this._appSys = Shell.AppSystem.get_default(); this.id = 'applications'; @@ -1953,17 +1971,38 @@ var AppSearchProvider = class AppSearchProvider { } }; -var AppViewItem = GObject.registerClass( +/** + * @typedef {object} ViewParams + * @property {boolean} [isDraggable] + * @property {boolean} [expandTitleOnHover] + */ + +export const AppViewItem = GObject.registerClass( class AppViewItem extends St.Button { - _init(params = {}, isDraggable = true, expandTitleOnHover = true) { + /** + * @param {Partial<St.Button.ConstructorProperties> & ViewParams} [params] + * @param {...any} _args + */ + _init(params = {}, ..._args) { + const { + isDraggable = true, + expandTitleOnHover = true, + ...buttonParams + } = params; + super._init({ pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }), reactive: true, button_mask: St.ButtonMask.ONE | St.ButtonMask.TWO, can_focus: true, - ...params, + ...buttonParams, }); + this._name = ''; + this._id = ''; + /** @type {IconGrid.BaseIcon["prototype"] | null} */ + this.icon = null; + this._delegate = this; if (isDraggable) { @@ -2162,7 +2201,7 @@ class AppViewItem extends St.Button { } }); -var FolderGrid = GObject.registerClass( +export const FolderGrid = GObject.registerClass( class FolderGrid extends IconGrid.IconGrid { _init() { super._init({ @@ -2179,7 +2218,14 @@ class FolderGrid extends IconGrid.IconGrid { } }); -var FolderView = GObject.registerClass( +/** + * @typedef {object} FolderViewParams + * @property {import('gi://Gio').Settings} folder + * @property {string} id + * @property {*} parentView + */ + +export const FolderView = GObject.registerClass( class FolderView extends BaseAppView { _init(folder, id, parentView) { super._init({ @@ -2411,7 +2457,7 @@ class FolderView extends BaseAppView { } }); -var FolderIcon = GObject.registerClass({ +export const FolderIcon = GObject.registerClass({ Signals: { 'apps-changed': {}, }, @@ -2519,7 +2565,7 @@ var FolderIcon = GObject.registerClass({ if (!(source instanceof AppIcon)) return false; - let view = _getViewFromIcon(source); + let view = source._getView(); if (!view || !(view instanceof AppDisplay)) return false; @@ -2590,7 +2636,7 @@ var FolderIcon = GObject.registerClass({ } }); -var AppFolderDialog = GObject.registerClass({ +export const AppFolderDialog = GObject.registerClass({ Signals: { 'open-state-changed': { param_types: [GObject.TYPE_BOOLEAN] }, }, @@ -3079,15 +3125,22 @@ var AppFolderDialog = GObject.registerClass({ } }); -var AppIcon = GObject.registerClass({ +/** + * @typedef {ViewParams} AppIconParams */ + +export const AppIcon = GObject.registerClass({ Signals: { 'menu-state-changed': { param_types: [GObject.TYPE_BOOLEAN] }, 'sync-tooltip': {}, }, }, class AppIcon extends AppViewItem { - _init(app, iconParams = {}) { + /** + * @param {Shell.App} app + * @param {AppIconParams} params + */ + _init(app, params = {}) { // Get the isDraggable property without passing it on to the BaseIcon: - const { isDraggable = true, expandTitleOnHover } = iconParams; + const { isDraggable = true, expandTitleOnHover } = params; super._init({ style_class: 'app-well-app' }, isDraggable, expandTitleOnHover); @@ -3102,8 +3155,11 @@ var AppIcon = GObject.registerClass({ this._folderPreviewId = 0; - iconParams['createIcon'] = this._createIcon.bind(this); - iconParams['setSizeManually'] = true; + const iconParams = { + ...params, + createIcon: this._createIcon.bind(this), + setSizeManually: true, + }; this.icon = new IconGrid.BaseIcon(app.get_name(), iconParams); this._iconContainer.add_child(this.icon); @@ -3292,6 +3348,15 @@ var AppIcon = GObject.registerClass({ this.icon.animateZoomOutAtPos(x, y); } + /** + * @typedef {object} WorkspaceLaunchParams + * @property {number} workspace + * @property {number} timestamp + */ + + /** + * @param {Partial<WorkspaceLaunchParams>} params + */ shellWorkspaceLaunch(params = {}) { let { stack } = new Error(); log('shellWorkspaceLaunch is deprecated, use app.open_new_window() instead\n%s'.format(stack)); @@ -3332,7 +3397,7 @@ var AppIcon = GObject.registerClass({ } _canAccept(source) { - let view = _getViewFromIcon(source); + let view = source._getView(); return source != this && (source instanceof this.constructor) && @@ -3366,12 +3431,20 @@ var AppIcon = GObject.registerClass({ } } + _getView() { + for (let parent = this.get_parent(); parent; parent = parent.get_parent()) { + if (parent instanceof BaseAppView) + return parent; + } + return null; + } + acceptDrop(source, actor, x) { const accepted = super.acceptDrop(source, actor, x); if (!accepted) return false; - let view = _getViewFromIcon(this); + let view = this._getView(); let apps = [this.id, source.id]; return view?.createFolder(apps); diff --git a/js/ui/appFavorites.js b/js/ui/appFavorites.js index 67e74e799..d9f45afbc 100644 --- a/js/ui/appFavorites.js +++ b/js/ui/appFavorites.js @@ -1,11 +1,11 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported getAppFavorites */ -const Shell = imports.gi.Shell; -const ParentalControlsManager = imports.misc.parentalControlsManager; -const Signals = imports.misc.signals; +import Shell from 'gi://Shell'; +import * as ParentalControlsManager from '../misc/parentalControlsManager.js'; +import * as Signals from '../misc/signals.js'; -const Main = imports.ui.main; +import Main from './main.js'; // In alphabetical order const RENAMED_DESKTOP_IDS = { @@ -204,8 +204,8 @@ class AppFavorites extends Signals.EventEmitter { } } -var appFavoritesInstance = null; -function getAppFavorites() { +let appFavoritesInstance = null; +export function getAppFavorites() { if (appFavoritesInstance == null) appFavoritesInstance = new AppFavorites(); return appFavoritesInstance; diff --git a/js/ui/appMenu.js b/js/ui/appMenu.js index 87d2218cd..0ec94adc2 100644 --- a/js/ui/appMenu.js +++ b/js/ui/appMenu.js @@ -2,12 +2,12 @@ /* exported AppMenu */ const { Clutter, Gio, GLib, Meta, Shell, St } = imports.gi; -const AppFavorites = imports.ui.appFavorites; -const Main = imports.ui.main; -const ParentalControlsManager = imports.misc.parentalControlsManager; -const PopupMenu = imports.ui.popupMenu; +import * as AppFavorites from './appFavorites.js'; +import Main from './main.js'; +import * as ParentalControlsManager from '../misc/parentalControlsManager.js'; +import * as PopupMenu from './popupMenu.js'; -var AppMenu = class AppMenu extends PopupMenu.PopupMenu { +export class AppMenu extends PopupMenu.PopupMenu { /** * @param {Clutter.Actor} sourceActor - actor the menu is attached to * @param {St.Side} side - arrow side diff --git a/js/ui/audioDeviceSelection.js b/js/ui/audioDeviceSelection.js index 8660663f9..1371db1ca 100644 --- a/js/ui/audioDeviceSelection.js +++ b/js/ui/audioDeviceSelection.js @@ -1,13 +1,20 @@ /* exported AudioDeviceSelectionDBus */ -const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; -const Dialog = imports.ui.dialog; -const Main = imports.ui.main; -const ModalDialog = imports.ui.modalDialog; -const { loadInterfaceXML } = imports.misc.fileUtils; +import * as Dialog from './dialog.js'; +import Main from './main.js'; +import * as ModalDialog from './modalDialog.js'; -var AudioDevice = { +import { loadInterfaceXML } from '../misc/fileUtilsModule.js'; + +export const AudioDevice = { HEADPHONES: 1 << 0, HEADSET: 1 << 1, MICROPHONE: 1 << 2, @@ -15,14 +22,20 @@ var AudioDevice = { const AudioDeviceSelectionIface = loadInterfaceXML('org.gnome.Shell.AudioDeviceSelection'); -var AudioDeviceSelectionDialog = GObject.registerClass({ +export const AudioDeviceSelectionDialog = GObject.registerClass({ Signals: { 'device-selected': { param_types: [GObject.TYPE_UINT] } }, }, class AudioDeviceSelectionDialog extends ModalDialog.ModalDialog { + /** + * @param {*} devices + */ _init(devices) { super._init({ styleClass: 'audio-device-selection-dialog' }); this._deviceItems = {}; + /** @type {string} */ + this._sender = null; + this._buildLayout(); if (devices & AudioDevice.HEADPHONES) @@ -135,7 +148,7 @@ var AudioDeviceSelectionDialog = GObject.registerClass({ } }); -var AudioDeviceSelectionDBus = class AudioDeviceSelectionDBus { +export class AudioDeviceSelectionDBus { constructor() { this._audioSelectionDialog = null; diff --git a/js/ui/background.js b/js/ui/background.js index f5e7b1291..db6fde567 100644 --- a/js/ui/background.js +++ b/js/ui/background.js @@ -94,15 +94,22 @@ // MetaBackgroundImage MetaBackgroundImage // MetaBackgroundImage MetaBackgroundImage -const { Clutter, GDesktopEnums, Gio, GLib, GObject, GnomeDesktop, Meta } = imports.gi; -const Signals = imports.misc.signals; +import Clutter from 'gi://Clutter'; +import GDesktopEnums from 'gi://GDesktopEnums'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import GnomeDesktop from 'gi://GnomeDesktop'; +import Meta from 'gi://Meta'; -const LoginManager = imports.misc.loginManager; -const Main = imports.ui.main; +import * as Signals from '../misc/signals.js'; + +import * as LoginManager from '../misc/loginManager.js'; +import Main from './main.js'; Gio._promisify(Gio._LocalFilePrototype, 'query_info_async', 'query_info_finish'); -var DEFAULT_BACKGROUND_COLOR = Clutter.Color.from_pixel(0x2e3436ff); +export const DEFAULT_BACKGROUND_COLOR = Clutter.Color.from_pixel(0x2e3436ff); const BACKGROUND_SCHEMA = 'org.gnome.desktop.background'; const PRIMARY_COLOR_KEY = 'primary-color'; @@ -111,14 +118,14 @@ const COLOR_SHADING_TYPE_KEY = 'color-shading-type'; const BACKGROUND_STYLE_KEY = 'picture-options'; const PICTURE_URI_KEY = 'picture-uri'; -var FADE_ANIMATION_TIME = 1000; +export let FADE_ANIMATION_TIME = 1000; -// These parameters affect how often we redraw. -// The first is how different (percent crossfaded) the slide show -// has to look before redrawing and the second is the minimum -// frequency (in seconds) we're willing to wake up -var ANIMATION_OPACITY_STEP_INCREMENT = 4.0; -var ANIMATION_MIN_WAKEUP_INTERVAL = 1.0; + // These parameters affect how often we redraw. + // The first is how different (percent crossfaded) the slide show + // has to look before redrawing and the second is the minimum + // frequency (in seconds) we're willing to wake up +export let ANIMATION_OPACITY_STEP_INCREMENT = 4.0; +export let ANIMATION_MIN_WAKEUP_INTERVAL = 1.0; let _backgroundCache = null; @@ -132,12 +139,13 @@ function _fileEqual0(file1, file2) { return file1.equal(file2); } -var BackgroundCache = class BackgroundCache extends Signals.EventEmitter { +export class BackgroundCache extends Signals.EventEmitter { constructor() { super(); this._fileMonitors = {}; this._backgroundSources = {}; + /** @type {{ [key: string]: Animation["prototype"] }} */ this._animations = {}; } @@ -159,6 +167,16 @@ var BackgroundCache = class BackgroundCache extends Signals.EventEmitter { this._fileMonitors[key] = monitor; } + /** + * @typedef {object} AnimationParams + * @property {Gio.File} file + * @property {string} settingsSchema + * @property {((animation: Animation["prototype"]) => void) | null} onLoaded + */ + + /** + * @param {Partial<AnimationParams>} [params] + */ getAnimation(params = {}) { const { file = null, @@ -219,13 +237,13 @@ var BackgroundCache = class BackgroundCache extends Signals.EventEmitter { } }; -function getBackgroundCache() { +export function getBackgroundCache() { if (!_backgroundCache) _backgroundCache = new BackgroundCache(); return _backgroundCache; } -var Background = GObject.registerClass({ +export const Background = GObject.registerClass({ Signals: { 'loaded': {}, 'bg-changed': {} }, }, class Background extends Meta.Background { _init(params = {}) { @@ -248,6 +266,8 @@ var Background = GObject.registerClass({ this._cancellable = new Gio.Cancellable(); this.isLoaded = false; + this._changedId = -1; + this._clock = new GnomeDesktop.WallClock(); this._timezoneChangedId = this._clock.connect('notify::timezone', () => { @@ -269,6 +289,17 @@ var Background = GObject.registerClass({ this._load(); } + /** + * @param {(background: Background) => void} callback + */ + onNextChange(callback) { + this._changedId = this.connect('bg-changed', (background) => { + background.disconnect(this._changedId); + + callback(background); + }); + } + destroy() { this._cancellable.cancel(); this._removeAnimationTimeout(); @@ -515,7 +546,7 @@ var Background = GObject.registerClass({ let _systemBackground; -var SystemBackground = GObject.registerClass({ +export const SystemBackground = GObject.registerClass({ Signals: { 'loaded': {} }, }, class SystemBackground extends Meta.BackgroundActor { _init() { @@ -538,7 +569,7 @@ var SystemBackground = GObject.registerClass({ } }); -var BackgroundSource = class BackgroundSource { +export class BackgroundSource { constructor(layoutManager, settingsSchema) { // Allow override the background image setting for performance testing this._layoutManager = layoutManager; @@ -601,9 +632,9 @@ var BackgroundSource = class BackgroundSource { style, }); - background._changedId = background.connect('bg-changed', () => { - background.disconnect(background._changedId); - background.destroy(); + background.onNextChange((bg) => { + bg.destroy(); + delete this._backgrounds[monitorIndex]; }); @@ -627,8 +658,11 @@ var BackgroundSource = class BackgroundSource { } }; -var Animation = GObject.registerClass( +export const Animation = GObject.registerClass( class Animation extends GnomeDesktop.BGSlideShow { + /** + * @param {*} params + */ _init(params) { super._init(params); @@ -638,6 +672,16 @@ class Animation extends GnomeDesktop.BGSlideShow { this.loaded = false; } + /** + * @returns {false} + */ + load() { + throw new GObject.NotImplementedError(`Animation.prototype.load is not supported. Use loadAsync.`); + } + + /** + * @param {() => void} callback + */ loadAsync(callback) { this.load_async(null, () => { this.loaded = true; @@ -666,7 +710,7 @@ class Animation extends GnomeDesktop.BGSlideShow { } }); -var BackgroundManager = class BackgroundManager extends Signals.EventEmitter { +export class BackgroundManager extends Signals.EventEmitter { constructor(params = {}) { super(); @@ -746,7 +790,7 @@ var BackgroundManager = class BackgroundManager extends Signals.EventEmitter { const { background } = newBackgroundActor.content; - if (background.isLoaded) { + if (background instanceof Background && background.isLoaded) { this._swapBackgroundActor(); } else { newBackgroundActor.loadedSignalId = background.connect('loaded', diff --git a/js/ui/backgroundMenu.js b/js/ui/backgroundMenu.js index e31e4c1a9..a311de559 100644 --- a/js/ui/backgroundMenu.js +++ b/js/ui/backgroundMenu.js @@ -1,13 +1,14 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported addBackgroundMenu */ -const { Clutter, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import St from 'gi://St'; -const BoxPointer = imports.ui.boxpointer; -const Main = imports.ui.main; -const PopupMenu = imports.ui.popupMenu; +import * as BoxPointer from './boxpointer.js'; +import Main from './main.js'; +import * as PopupMenu from './popupMenu.js'; -var BackgroundMenu = class BackgroundMenu extends PopupMenu.PopupMenu { +export class BackgroundMenu extends PopupMenu.PopupMenu { constructor(layoutManager) { super(layoutManager.dummyCursor, 0, St.Side.TOP); @@ -23,7 +24,7 @@ var BackgroundMenu = class BackgroundMenu extends PopupMenu.PopupMenu { } }; -function addBackgroundMenu(actor, layoutManager) { +export function addBackgroundMenu(actor, layoutManager) { actor.reactive = true; actor._backgroundMenu = new BackgroundMenu(layoutManager); actor._backgroundManager = new PopupMenu.PopupMenuManager(actor); diff --git a/js/ui/barLevel.js b/js/ui/barLevel.js index 25d483528..ebf3171cc 100644 --- a/js/ui/barLevel.js +++ b/js/ui/barLevel.js @@ -1,9 +1,13 @@ /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ /* exported BarLevel */ -const { Atk, Clutter, GObject, St } = imports.gi; +import Atk from 'gi://Atk'; +import Clutter from 'gi://Clutter'; +import GObject from 'gi://GObject'; +import St from 'gi://St'; -var BarLevel = GObject.registerClass({ + +export const BarLevel = GObject.registerClass({ Properties: { 'value': GObject.ParamSpec.double( 'value', 'value', 'value', @@ -19,6 +23,9 @@ var BarLevel = GObject.registerClass({ 1, 2, 1), }, }, class BarLevel extends St.DrawingArea { + /** + * @param {*} params + */ _init(params) { this._maxValue = 1; this._value = 0; diff --git a/js/ui/boxpointer.js b/js/ui/boxpointer.js index be9c57ce0..f10ae9933 100644 --- a/js/ui/boxpointer.js +++ b/js/ui/boxpointer.js @@ -1,18 +1,23 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported BoxPointer */ -const { Clutter, GObject, Meta, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import St from 'gi://St'; -const Main = imports.ui.main; -var PopupAnimation = { +import Main from './main.js'; + +/** @enum {number} */ +export const PopupAnimation = { NONE: 0, SLIDE: 1 << 0, FADE: 1 << 1, FULL: ~0, }; -var POPUP_ANIMATION_TIME = 150; +export let POPUP_ANIMATION_TIME = 150; /** * BoxPointer: @@ -27,9 +32,13 @@ var POPUP_ANIMATION_TIME = 150; * totally inside the monitor workarea if possible. * */ -var BoxPointer = GObject.registerClass({ +export const BoxPointer = GObject.registerClass({ Signals: { 'arrow-side-changed': {} }, }, class BoxPointer extends St.Widget { + /** + * @param {*} arrowSide + * @param {*} binProperties + */ _init(arrowSide, binProperties) { super._init(); @@ -167,6 +176,12 @@ var BoxPointer = GObject.registerClass({ }); } + /** + * @param {boolean} isWidth + * @param {number} minSize + * @param {number} natSize + * @returns {[number, number]} + */ _adjustAllocationForArrow(isWidth, minSize, natSize) { let themeNode = this.get_theme_node(); let borderWidth = themeNode.get_length('-arrow-border-width'); @@ -182,6 +197,9 @@ var BoxPointer = GObject.registerClass({ return [minSize, natSize]; } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_width(forHeight) { let themeNode = this.get_theme_node(); forHeight = themeNode.adjust_for_height(forHeight); diff --git a/js/ui/calendar.js b/js/ui/calendar.js index b3eb21dc8..31eef25bf 100644 --- a/js/ui/calendar.js +++ b/js/ui/calendar.js @@ -1,23 +1,28 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Calendar, CalendarMessageList, DBusEventSource */ -const { Clutter, Gio, GLib, GObject, Shell, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; -const Main = imports.ui.main; -const MessageList = imports.ui.messageList; -const MessageTray = imports.ui.messageTray; -const Mpris = imports.ui.mpris; -const PopupMenu = imports.ui.popupMenu; -const Util = imports.misc.util; +import Main from './main.js'; +import * as MessageList from './messageList.js'; +import * as MessageTray from './messageTray.js'; +import * as Mpris from './mpris.js'; +import * as PopupMenu from './popupMenu.js'; +import * as Util from '../misc/util.js'; -const { loadInterfaceXML } = imports.misc.fileUtils; +import { loadInterfaceXML } from '../misc/fileUtilsModule.js'; -var MSECS_IN_DAY = 24 * 60 * 60 * 1000; -var SHOW_WEEKDATE_KEY = 'show-weekdate'; +const MSECS_IN_DAY = 24 * 60 * 60 * 1000; +const SHOW_WEEKDATE_KEY = 'show-weekdate'; -var MESSAGE_ICON_SIZE = -1; // pick up from CSS +export let MESSAGE_ICON_SIZE = -1; // pick up from CSS -var NC_ = (context, str) => '%s\u0004%s'.format(context, str); +export const NC_ = (context, str) => '%s\u0004%s'.format(context, str); function sameYear(dateA, dateB) { return dateA.getYear() == dateB.getYear(); @@ -81,7 +86,7 @@ function _getCalendarDayAbbreviation(dayNumber) { // Abstraction for an appointment/event in a calendar -var CalendarEvent = class CalendarEvent { +export class CalendarEvent { constructor(id, date, end, summary, allDay) { this.id = id; this.date = date; @@ -94,7 +99,7 @@ var CalendarEvent = class CalendarEvent { // Interface for appointments/events - e.g. the contents of a calendar // -var EventSourceBase = GObject.registerClass({ +export const EventSourceBase = GObject.registerClass({ GTypeFlags: GObject.TypeFlags.ABSTRACT, Properties: { 'has-calendars': GObject.ParamSpec.boolean( @@ -108,10 +113,16 @@ var EventSourceBase = GObject.registerClass({ }, Signals: { 'changed': {} }, }, class EventSourceBase extends GObject.Object { + /** + * @returns {boolean} + */ get isLoading() { throw new GObject.NotImplementedError('isLoading in %s'.format(this.constructor.name)); } + /** + * @returns {boolean} + */ get hasCalendars() { throw new GObject.NotImplementedError('hasCalendars in %s'.format(this.constructor.name)); } @@ -123,16 +134,22 @@ var EventSourceBase = GObject.registerClass({ throw new GObject.NotImplementedError('requestRange in %s'.format(this.constructor.name)); } + /** + * @returns {any[]} + */ getEvents(_begin, _end) { throw new GObject.NotImplementedError('getEvents in %s'.format(this.constructor.name)); } + /** + * @returns {boolean} + */ hasEvents(_day) { throw new GObject.NotImplementedError('hasEvents in %s'.format(this.constructor.name)); } }); -var EmptyEventSource = GObject.registerClass( +export const EmptyEventSource = GObject.registerClass( class EmptyEventSource extends EventSourceBase { get isLoading() { return false; @@ -185,7 +202,7 @@ function _dateIntervalsOverlap(a0, a1, b0, b1) { } // an implementation that reads data from a session bus service -var DBusEventSource = GObject.registerClass( +export const DBusEventSource = GObject.registerClass( class DBusEventSource extends EventSourceBase { _init() { super._init(); @@ -373,9 +390,11 @@ class DBusEventSource extends EventSourceBase { } }); -var Calendar = GObject.registerClass({ +export const Calendar = GObject.registerClass({ Signals: { 'selected-date-changed': { param_types: [GLib.DateTime.$gtype] } }, -}, class Calendar extends St.Widget { +}, +/** @extends {St.Widget<Clutter.GridLayout>} */ +class Calendar extends St.Widget { _init() { this._weekStart = Shell.util_get_week_start(); this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.calendar' }); @@ -719,7 +738,7 @@ var Calendar = GObject.registerClass({ } }); -var NotificationMessage = GObject.registerClass( +export const NotificationMessage = GObject.registerClass( class NotificationMessage extends MessageList.Message { _init(notification) { super._init(notification.title, notification.bannerBodyText); @@ -784,7 +803,7 @@ class NotificationMessage extends MessageList.Message { } }); -var TimeLabel = GObject.registerClass( +export const TimeLabel = GObject.registerClass( class NotificationTimeLabel extends St.Label { _init(datetime) { super._init({ @@ -801,7 +820,7 @@ class NotificationTimeLabel extends St.Label { } }); -var NotificationSection = GObject.registerClass( +export const NotificationSection = GObject.registerClass( class NotificationSection extends MessageList.MessageListSection { _init() { super._init(); @@ -882,7 +901,7 @@ class NotificationSection extends MessageList.MessageListSection { } }); -var Placeholder = GObject.registerClass( +export const Placeholder = GObject.registerClass( class Placeholder extends St.BoxLayout { _init() { super._init({ style_class: 'message-list-placeholder', vertical: true }); @@ -918,7 +937,7 @@ class DoNotDisturbSwitch extends PopupMenu.Switch { } }); -var CalendarMessageList = GObject.registerClass( +export const CalendarMessageList = GObject.registerClass( class CalendarMessageList extends St.Widget { _init() { super._init({ @@ -982,6 +1001,7 @@ class CalendarMessageList extends St.Widget { this._clearButton, 'visible', GObject.BindingFlags.INVERT_BOOLEAN); + /** @type {St.BoxLayout<MessageList.MessageListSection["prototype"]>} */ this._sectionList = new St.BoxLayout({ style_class: 'message-list-sections', vertical: true, x_expand: true, diff --git a/js/ui/checkBox.js b/js/ui/checkBox.js index d64bd0d6c..4e227f618 100644 --- a/js/ui/checkBox.js +++ b/js/ui/checkBox.js @@ -1,7 +1,12 @@ /* exported CheckBox */ -const { Atk, Clutter, GObject, Pango, St } = imports.gi; +import Atk from 'gi://Atk'; +import Clutter from 'gi://Clutter'; +import GObject from 'gi://GObject'; +import Pango from 'gi://Pango'; +import St from 'gi://St'; -var CheckBox = GObject.registerClass( + +export const CheckBox = GObject.registerClass( class CheckBox extends St.Button { _init(label) { let container = new St.BoxLayout({ diff --git a/js/ui/closeDialog.js b/js/ui/closeDialog.js index 63a0bcfcf..8b66edf09 100644 --- a/js/ui/closeDialog.js +++ b/js/ui/closeDialog.js @@ -1,21 +1,30 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported CloseDialog */ -const { Clutter, GLib, GObject, Meta, Shell, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; -const Dialog = imports.ui.dialog; -const Main = imports.ui.main; -var FROZEN_WINDOW_BRIGHTNESS = -0.3; -var DIALOG_TRANSITION_TIME = 150; -var ALIVE_TIMEOUT = 5000; +import * as Dialog from './dialog.js'; +import Main from './main.js'; -var CloseDialog = GObject.registerClass({ +export let FROZEN_WINDOW_BRIGHTNESS = -0.3; +export let DIALOG_TRANSITION_TIME = 150; +export let ALIVE_TIMEOUT = 5000; + +export const CloseDialog = GObject.registerClass({ Implements: [Meta.CloseDialog], Properties: { 'window': GObject.ParamSpec.override('window', Meta.CloseDialog), }, }, class CloseDialog extends GObject.Object { + /** + * @param {Meta.Window} window + */ _init(window) { super._init(); this._window = window; @@ -30,6 +39,9 @@ var CloseDialog = GObject.registerClass({ return this._window; } + /** + * @param {Meta.Window} window + */ set window(window) { this._window = window; } @@ -61,6 +73,7 @@ var CloseDialog = GObject.registerClass({ if (this._dialog) return; + /** @type {Meta.WindowActor} */ let windowActor = this._window.get_compositor_private(); this._dialog = new Dialog.Dialog(windowActor, 'close-dialog'); this._dialog.width = windowActor.width; @@ -86,6 +99,7 @@ var CloseDialog = GObject.registerClass({ // We set the effect on the surface actor, so the dialog itself // (which is a child of the MetaWindowActor) does not get the // effect applied itself. + /** @type {Meta.WindowActor} */ let windowActor = this._window.get_compositor_private(); let surfaceActor = windowActor.get_first_child(); let effect = new Clutter.BrightnessContrastEffect(); @@ -94,15 +108,22 @@ var CloseDialog = GObject.registerClass({ } _removeWindowEffect() { + /** @type {Meta.WindowActor} */ let windowActor = this._window.get_compositor_private(); let surfaceActor = windowActor.get_first_child(); surfaceActor.remove_effect_by_name("gnome-shell-frozen-window"); } + /** + * @this {Meta.CloseDialog} + */ _onWait() { this.response(Meta.CloseDialogResponse.WAIT); } + /** + * @this {Meta.CloseDialog} + */ _onClose() { this.response(Meta.CloseDialogResponse.FORCE_CLOSE); } diff --git a/js/ui/components/__init__.js b/js/ui/components.js index 74300136b..db41db370 100644 --- a/js/ui/components/__init__.js +++ b/js/ui/components.js @@ -1,7 +1,7 @@ /* exported ComponentManager */ -const Main = imports.ui.main; +import Main from './main.js'; -var ComponentManager = class { +export class ComponentManager { constructor() { this._allComponents = {}; this._enabledComponents = []; @@ -10,12 +10,12 @@ var ComponentManager = class { this._sessionUpdated(); } - _sessionUpdated() { + async _sessionUpdated() { let newEnabledComponents = Main.sessionMode.components; - newEnabledComponents + await Promise.all([...newEnabledComponents .filter(name => !this._enabledComponents.includes(name)) - .forEach(name => this._enableComponent(name)); + .map(name => this._enableComponent(name))]); this._enabledComponents .filter(name => !newEnabledComponents.includes(name)) @@ -24,12 +24,12 @@ var ComponentManager = class { this._enabledComponents = newEnabledComponents; } - _importComponent(name) { - let module = imports.ui.components[name]; + async _importComponent(name) { + let module = await import(`./components/${name}.js`); return module.Component; } - _ensureComponent(name) { + async _ensureComponent(name) { let component = this._allComponents[name]; if (component) return component; @@ -37,14 +37,14 @@ var ComponentManager = class { if (Main.sessionMode.isLocked) return null; - let constructor = this._importComponent(name); + let constructor = await this._importComponent(name); component = new constructor(); this._allComponents[name] = component; return component; } - _enableComponent(name) { - let component = this._ensureComponent(name); + async _enableComponent(name) { + let component = await this._ensureComponent(name); if (component) component.enable(); } diff --git a/js/ui/components/automountManager.js b/js/ui/components/automountManager.js index ecf9fa144..7cd273501 100644 --- a/js/ui/components/automountManager.js +++ b/js/ui/components/automountManager.js @@ -1,25 +1,26 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Component */ -const { Gio, GLib } = imports.gi; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; -const GnomeSession = imports.misc.gnomeSession; -const Main = imports.ui.main; -const ShellMountOperation = imports.ui.shellMountOperation; +import * as GnomeSession from '../../misc/gnomeSession.js'; +import Main from './../main.js'; +import * as ShellMountOperation from './../shellMountOperation.js'; -var GNOME_SESSION_AUTOMOUNT_INHIBIT = 16; +export let GNOME_SESSION_AUTOMOUNT_INHIBIT = 16; // GSettings keys const SETTINGS_SCHEMA = 'org.gnome.desktop.media-handling'; const SETTING_ENABLE_AUTOMOUNT = 'automount'; -var AUTORUN_EXPIRE_TIMEOUT_SECS = 10; +export let AUTORUN_EXPIRE_TIMEOUT_SECS = 10; -var AutomountManager = class { +export class AutomountManager { constructor() { this._settings = new Gio.Settings({ schema_id: SETTINGS_SCHEMA }); this._activeOperations = new Map(); - this._session = new GnomeSession.SessionManager(); + this._session = GnomeSession.SessionManager(); this._session.connectSignal('InhibitorAdded', this._InhibitorsChanged.bind(this)); this._session.connectSignal('InhibitorRemoved', @@ -130,6 +131,18 @@ var AutomountManager = class { this._checkAndMountVolume(volume); } + /** + * @typedef {object} VolumeMountProps: An object with the properties: + * @property {boolean} [checkSession] + * @property {boolean} [useMountOp] + * @property {boolean} [allowAutorun] + */ + + /** + * _checkAndMountVolume: + * @param {Gio.Volume} volume + * @param {Partial<VolumeMountProps>} [params] + */ _checkAndMountVolume(volume, params = {}) { const { checkSession = true, @@ -253,4 +266,4 @@ var AutomountManager = class { GLib.Source.set_name_by_id(id, '[gnome-shell] volume.allowAutorun'); } }; -var Component = AutomountManager; +export let Component = AutomountManager; diff --git a/js/ui/components/autorunManager.js b/js/ui/components/autorunManager.js index 16b52cfe3..6b321f3e9 100644 --- a/js/ui/components/autorunManager.js +++ b/js/ui/components/autorunManager.js @@ -1,13 +1,17 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Component */ -const { Clutter, Gio, GObject, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; +import St from 'gi://St'; -const GnomeSession = imports.misc.gnomeSession; -const Main = imports.ui.main; -const MessageTray = imports.ui.messageTray; -const { loadInterfaceXML } = imports.misc.fileUtils; +import * as GnomeSession from '../../misc/gnomeSession.js'; +import Main from './../main.js'; +import * as MessageTray from './../messageTray.js'; + +import { loadInterfaceXML } from '../../misc/fileUtilsModule.js'; // GSettings keys const SETTINGS_SCHEMA = 'org.gnome.desktop.media-handling'; @@ -16,7 +20,8 @@ const SETTING_START_APP = 'autorun-x-content-start-app'; const SETTING_IGNORE = 'autorun-x-content-ignore'; const SETTING_OPEN_FOLDER = 'autorun-x-content-open-folder'; -var AutorunSetting = { +/** @enum {number} */ +export const AutorunSetting = { RUN: 0, IGNORE: 1, FILES: 2, @@ -80,7 +85,7 @@ function HotplugSniffer() { '/org/gnome/Shell/HotplugSniffer'); } -var ContentTypeDiscoverer = class { +export class ContentTypeDiscoverer { constructor(callback) { this._callback = callback; this._settings = new Gio.Settings({ schema_id: SETTINGS_SCHEMA }); @@ -142,9 +147,9 @@ var ContentTypeDiscoverer = class { } }; -var AutorunManager = class { +export class AutorunManager { constructor() { - this._session = new GnomeSession.SessionManager(); + this._session = GnomeSession.SessionManager(); this._volumeMonitor = Gio.VolumeMonitor.get(); this._dispatcher = new AutorunDispatcher(this); @@ -160,7 +165,10 @@ var AutorunManager = class { this._volumeMonitor.disconnect(this._mountRemovedId); } - _onMountAdded(monitor, mount) { + /** + * @param {Gio.Mount} mount + */ + _onMountAdded(_, mount) { // don't do anything if our session is not the currently // active one if (!this._session.SessionIsActive) @@ -177,7 +185,10 @@ var AutorunManager = class { } }; -var AutorunDispatcher = class { +export class AutorunDispatcher { + /** + * @param {AutorunManager} manager + */ constructor(manager) { this._manager = manager; this._sources = []; @@ -212,6 +223,10 @@ var AutorunDispatcher = class { return null; } + /** + * @param {Gio.Mount} mount + * @param {Gio.Application[]} apps + */ _addSource(mount, apps) { // if we already have a source showing for this // mount, return @@ -222,6 +237,10 @@ var AutorunDispatcher = class { this._sources.push(new AutorunSource(this._manager, mount, apps)); } + /** + * @param {Gio.Mount} mount + * @param {Gio.Application[]} apps + */ addMount(mount, apps, contentTypes) { // if autorun is disabled globally, return if (this._settings.get_boolean(SETTING_DISABLE_AUTORUN)) @@ -271,7 +290,14 @@ var AutorunDispatcher = class { } }; -var AutorunSource = GObject.registerClass( +/** + * @typedef {object} AutorunSourceParams + * @property {AutorunManager} manager + * @property {Gio.Mount} mount + * @property {Gio.Application[]} apps + */ + +export const AutorunSource = GObject.registerClass( class AutorunSource extends MessageTray.Source { _init(manager, mount, apps) { super._init(mount.get_name()); @@ -296,8 +322,12 @@ class AutorunSource extends MessageTray.Source { } }); -var AutorunNotification = GObject.registerClass( +export const AutorunNotification = GObject.registerClass( class AutorunNotification extends MessageTray.Notification { + /** + * @param {*} manager + * @param {*} source + */ _init(manager, source) { super._init(source, source.title); @@ -356,4 +386,4 @@ class AutorunNotification extends MessageTray.Notification { } }); -var Component = AutorunManager; +export let Component = AutorunManager; diff --git a/js/ui/components/keyring.js b/js/ui/components/keyring.js index cd7a81e95..08f17a5fa 100644 --- a/js/ui/components/keyring.js +++ b/js/ui/components/keyring.js @@ -1,15 +1,22 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Component */ -const { Clutter, Gcr, Gio, GObject, Pango, Shell, St } = imports.gi; - -const Dialog = imports.ui.dialog; -const ModalDialog = imports.ui.modalDialog; -const ShellEntry = imports.ui.shellEntry; -const CheckBox = imports.ui.checkBox; -const Util = imports.misc.util; - -var KeyringDialog = GObject.registerClass( +import Clutter from 'gi://Clutter'; +import Gcr from 'gi://Gcr'; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; +import Pango from 'gi://Pango'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; + + +import * as Dialog from './../dialog.js'; +import * as ModalDialog from './../modalDialog.js'; +import * as ShellEntry from './../shellEntry.js'; +import * as CheckBox from './../checkBox.js'; +import * as Util from '../../misc/util.js'; + +export const KeyringDialog = GObject.registerClass( class KeyringDialog extends ModalDialog.ModalDialog { _init() { super._init({ styleClass: 'prompt-dialog' }); @@ -178,7 +185,7 @@ class KeyringDialog extends ModalDialog.ModalDialog { } }); -var KeyringDummyDialog = class { +export class KeyringDummyDialog { constructor() { this.prompt = new Shell.KeyringPrompt(); this.prompt.connect('show-password', this._cancelPrompt.bind(this)); @@ -190,7 +197,7 @@ var KeyringDummyDialog = class { } }; -var KeyringPrompter = GObject.registerClass( +export const KeyringPrompter = GObject.registerClass( class KeyringPrompter extends Gcr.SystemPrompter { _init() { super._init(); @@ -226,4 +233,4 @@ class KeyringPrompter extends Gcr.SystemPrompter { } }); -var Component = KeyringPrompter; +export let Component = KeyringPrompter; diff --git a/js/ui/components/networkAgent.js b/js/ui/components/networkAgent.js index 367deb666..5c5000475 100644 --- a/js/ui/components/networkAgent.js +++ b/js/ui/components/networkAgent.js @@ -1,14 +1,21 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Component */ -const { Clutter, Gio, GLib, GObject, NM, Pango, Shell, St } = imports.gi; -const Signals = imports.misc.signals; - -const Dialog = imports.ui.dialog; -const Main = imports.ui.main; -const MessageTray = imports.ui.messageTray; -const ModalDialog = imports.ui.modalDialog; -const ShellEntry = imports.ui.shellEntry; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import NM from 'gi://NM'; +import Pango from 'gi://Pango'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; +import * as Signals from '../../misc/signals.js'; + +import * as Dialog from './../dialog.js'; +import Main from './../main.js'; +import * as MessageTray from './../messageTray.js'; +import * as ModalDialog from './../modalDialog.js'; +import * as ShellEntry from './../shellEntry.js'; Gio._promisify(Shell.NetworkAgent.prototype, 'init_async', 'init_finish'); Gio._promisify(Shell.NetworkAgent.prototype, @@ -16,11 +23,21 @@ Gio._promisify(Shell.NetworkAgent.prototype, const VPN_UI_GROUP = 'VPN Plugin UI'; -var NetworkSecretDialog = GObject.registerClass( +export const NetworkSecretDialog = GObject.registerClass( class NetworkSecretDialog extends ModalDialog.ModalDialog { + /** + * @param {any | Shell.NetworkAgent} agent + * @param {string} requestId + * @param {NM.Connection} connection + * @param {string} settingName + * @param {string[]} hints + * @param {number} flags + * @param {{ title: string, message: string, secrets: string[] }} [contentOverride] + */ _init(agent, requestId, connection, settingName, hints, flags, contentOverride) { super._init({ styleClass: 'prompt-dialog' }); + /** @type {Shell.NetworkAgent} */ this._agent = agent; this._requestId = requestId; this._connection = connection; @@ -213,7 +230,7 @@ class NetworkSecretDialog extends ModalDialog.ModalDialog { case 'none': // static WEP secrets.push({ label: _('Key'), - key: 'wep-key%s'.format(wirelessSecuritySetting.wep_tx_keyidx), + key: 'wep-key%s'.format(wirelessSecuritySetting.wep_tx_keyidx.toFixed(0)), value: wirelessSecuritySetting.get_wep_key(wirelessSecuritySetting.wep_tx_keyidx) || '', wep_key_type: wirelessSecuritySetting.wep_key_type, validate: this._validateStaticWep, @@ -298,7 +315,8 @@ class NetworkSecretDialog extends ModalDialog.ModalDialog { else setting = this._connection.get_setting_by_name(connectionType); secrets.push({ label: _('Password'), key: 'password', - value: setting.value || '', password: true }); + // TODO: Does not exist: setting.value on NM.Setting + value: '', password: true }); } _getContent() { @@ -354,7 +372,7 @@ class NetworkSecretDialog extends ModalDialog.ModalDialog { } }); -var VPNRequestHandler = class extends Signals.EventEmitter { +export class VPNRequestHandler extends Signals.EventEmitter { constructor(agent, requestId, authHelper, serviceType, connection, hints, flags) { super(); @@ -527,7 +545,6 @@ var VPNRequestHandler = class extends Signals.EventEmitter { let keyfile = new GLib.KeyFile(); let data; let contentOverride; - try { data = new GLib.Bytes(this._dataStdout.peek_buffer()); keyfile.load_from_bytes(data, GLib.KeyFileFlags.NONE); @@ -563,7 +580,7 @@ var VPNRequestHandler = class extends Signals.EventEmitter { } } catch (e) { // No output is a valid case it means "both secrets are stored" - if (data.length > 0) { + if (data.get_size() > 0) { logError(e, 'error while reading VPN plugin output keyfile'); this._agent.respond(this._requestId, Shell.NetworkAgentResponse.INTERNAL_ERROR); @@ -604,7 +621,7 @@ var VPNRequestHandler = class extends Signals.EventEmitter { } }; -var NetworkAgent = class { +export class NetworkAgent { constructor() { this._native = new Shell.NetworkAgent({ identifier: 'org.gnome.Shell.NetworkAgent', @@ -807,4 +824,4 @@ var NetworkAgent = class { }; } }; -var Component = NetworkAgent; +export let Component = NetworkAgent; diff --git a/js/ui/components/polkitAgent.js b/js/ui/components/polkitAgent.js index 5127ab924..ad7120678 100644 --- a/js/ui/components/polkitAgent.js +++ b/js/ui/components/polkitAgent.js @@ -1,16 +1,25 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Component */ -const { AccountsService, Clutter, GLib, - GObject, Pango, PolkitAgent, Polkit, Shell, St } = imports.gi; - -const Dialog = imports.ui.dialog; -const Main = imports.ui.main; -const ModalDialog = imports.ui.modalDialog; -const ShellEntry = imports.ui.shellEntry; -const UserWidget = imports.ui.userWidget; -const Util = imports.misc.util; - +import AccountsService from 'gi://AccountsService'; +import Clutter from 'gi://Clutter'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Pango from 'gi://Pango'; +import PolkitAgent from 'gi://PolkitAgent'; +import Polkit from 'gi://Polkit'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; + + +import * as Dialog from './../dialog.js'; +import Main from './../main.js'; +import * as ModalDialog from './../modalDialog.js'; +import * as ShellEntry from './../shellEntry.js'; +import * as UserWidget from './../userWidget.js'; +import * as Util from '../../misc/util.js'; + +/** @enum {number} */ const DialogMode = { AUTH: 0, CONFIRM: 1, @@ -20,9 +29,15 @@ const DIALOG_ICON_SIZE = 64; const DELAYED_RESET_TIMEOUT = 200; -var AuthenticationDialog = GObject.registerClass({ +export const AuthenticationDialog = GObject.registerClass({ Signals: { 'done': { param_types: [GObject.TYPE_BOOLEAN] } }, }, class AuthenticationDialog extends ModalDialog.ModalDialog { + /** + * @param {*} actionId + * @param {*} description + * @param {*} cookie + * @param {*} userNames + */ _init(actionId, description, cookie, userNames) { super._init({ styleClass: 'prompt-dialog' }); @@ -410,7 +425,7 @@ var AuthenticationDialog = GObject.registerClass({ } }); -var AuthenticationAgent = GObject.registerClass( +export const AuthenticationAgent = GObject.registerClass( class AuthenticationAgent extends Shell.PolkitAuthenticationAgent { _init() { super._init(); @@ -473,4 +488,4 @@ class AuthenticationAgent extends Shell.PolkitAuthenticationAgent { } }); -var Component = AuthenticationAgent; +export let Component = AuthenticationAgent; diff --git a/js/ui/components/telepathyClient.js b/js/ui/components/telepathyClient.js index 3d7020041..fc8fb31b6 100644 --- a/js/ui/components/telepathyClient.js +++ b/js/ui/components/telepathyClient.js @@ -1,12 +1,29 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Component */ -const { Clutter, Gio, GLib, GObject, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import St from 'gi://St'; + +import gi from 'gi'; + +import Main from './../main.js'; + +import * as History from '../../misc/history.js'; +import * as MessageList from './../messageList.js'; +import * as MessageTray from './../messageTray.js'; +import * as Util from '../../misc/util.js'; + +/** @type {import('telepathylogger0')} */ +let Tpl = null; +/** @type {import('telepathyglib0')} */ +let Tp = null; -var Tpl = null; -var Tp = null; try { - ({ TelepathyGLib: Tp, TelepathyLogger: Tpl } = imports.gi); + Tp = gi.require('TelepathyGlib'); + Tpl = gi.require('TelepathyLogger'); Gio._promisify(Tp.Channel.prototype, 'close_async', 'close_finish'); Gio._promisify(Tp.TextChannel.prototype, @@ -19,29 +36,23 @@ try { log('Telepathy is not available, chat integration will be disabled.'); } -const History = imports.misc.history; -const Main = imports.ui.main; -const MessageList = imports.ui.messageList; -const MessageTray = imports.ui.messageTray; -const Util = imports.misc.util; - const HAVE_TP = Tp != null && Tpl != null; // See Notification.appendMessage -var SCROLLBACK_IMMEDIATE_TIME = 3 * 60; // 3 minutes -var SCROLLBACK_RECENT_TIME = 15 * 60; // 15 minutes -var SCROLLBACK_RECENT_LENGTH = 20; -var SCROLLBACK_IDLE_LENGTH = 5; +export let SCROLLBACK_IMMEDIATE_TIME = 3 * 60; // 3 minutes +export let SCROLLBACK_RECENT_TIME = 15 * 60; // 15 minutes +export let SCROLLBACK_RECENT_LENGTH = 20; +export let SCROLLBACK_IDLE_LENGTH = 5; // See Source._displayPendingMessages -var SCROLLBACK_HISTORY_LINES = 10; +export let SCROLLBACK_HISTORY_LINES = 10; // See Notification._onEntryChanged -var COMPOSING_STOP_TIMEOUT = 5; +export let COMPOSING_STOP_TIMEOUT = 5; -var CHAT_EXPAND_LINES = 12; +export let CHAT_EXPAND_LINES = 12; -var NotificationDirection = { +export const NotificationDirection = { SENT: 'chat-sent', RECEIVED: 'chat-received', }; @@ -51,8 +62,8 @@ const ChatMessage = HAVE_TP ? GObject.registerClass({ 'message-type': GObject.ParamSpec.int( 'message-type', 'message-type', 'message-type', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, - Math.min(...Object.values(Tp.ChannelTextMessageType)), - Math.max(...Object.values(Tp.ChannelTextMessageType)), + Tp.ChannelTextMessageType.NORMAL, + Tp.ChannelTextMessageType.DELIVERY_REPORT, Tp.ChannelTextMessageType.NORMAL), 'text': GObject.ParamSpec.string( 'text', 'text', 'text', @@ -71,35 +82,52 @@ const ChatMessage = HAVE_TP ? GObject.registerClass({ GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, null), }, -}, class ChatMessageClass extends GObject.Object { - static newFromTpMessage(tpMessage, direction) { - return new ChatMessage({ - 'message-type': tpMessage.get_message_type(), - 'text': tpMessage.to_text()[0], - 'sender': tpMessage.sender.alias, - 'timestamp': direction === NotificationDirection.RECEIVED - ? tpMessage.get_received_timestamp() : tpMessage.get_sent_timestamp(), - direction, - }); - } - - static newFromTplTextEvent(tplTextEvent) { - let direction = - tplTextEvent.get_sender().get_entity_type() === Tpl.EntityType.SELF - ? NotificationDirection.SENT : NotificationDirection.RECEIVED; - - return new ChatMessage({ - 'message-type': tplTextEvent.get_message_type(), - 'text': tplTextEvent.get_message(), - 'sender': tplTextEvent.get_sender().get_alias(), - 'timestamp': tplTextEvent.get_timestamp(), - direction, - }); +}, class ChatMessage extends GObject.Object { + /** + * @param {{ [key: string]: any }} properties + */ + _init(properties) { + super._init(properties); + + /** @type {number} */ + this.messageType; + /** @type {string} */ + this.text; + /** @type {string} */ + this.sender; + /** @type {number} */ + this.timestamp; + /** @type {string} */ + this.direction ; } }) : null; +function newChatMessageFromTpMessage(tpMessage, direction) { + return new ChatMessage({ + 'message-type': tpMessage.get_message_type(), + 'text': tpMessage.to_text()[0], + 'sender': tpMessage.sender.alias, + 'timestamp': direction === NotificationDirection.RECEIVED + ? tpMessage.get_received_timestamp() : tpMessage.get_sent_timestamp(), + direction, + }); +} + +function newChatMessageFromTplTextEvent(tplTextEvent) { + let direction = + tplTextEvent.get_sender().get_entity_type() === Tpl.EntityType.SELF + ? NotificationDirection.SENT : NotificationDirection.RECEIVED; + + return new ChatMessage({ + 'message-type': tplTextEvent.get_message_type(), + 'text': tplTextEvent.get_message(), + 'sender': tplTextEvent.get_sender().get_alias(), + 'timestamp': tplTextEvent.get_timestamp(), + direction, + }); +} -var TelepathyComponent = class { +export class TelepathyComponent { constructor() { this._client = null; @@ -131,7 +159,7 @@ var TelepathyComponent = class { } }; -var TelepathyClient = HAVE_TP ? GObject.registerClass( +export let TelepathyClient = HAVE_TP ? GObject.registerClass( class TelepathyClient extends Tp.BaseClient { _init() { // channel path -> ChatSource @@ -161,11 +189,12 @@ class TelepathyClient extends Tp.BaseClient { uniquify_name: true }); // We only care about single-user text-based chats - let filter = {}; - filter[Tp.PROP_CHANNEL_CHANNEL_TYPE] = Tp.IFACE_CHANNEL_TYPE_TEXT; - filter[Tp.PROP_CHANNEL_TARGET_HANDLE_TYPE] = Tp.HandleType.CONTACT; - + let filter = { + [Tp.PROP_CHANNEL_CHANNEL_TYPE] : Tp.IFACE_CHANNEL_TYPE_TEXT, + [Tp.PROP_CHANNEL_TARGET_HANDLE_TYPE] : Tp.HandleType.CONTACT, + }; this.set_observer_recover(true); + // FIXME this.add_observer_filter(filter); this.add_approver_filter(filter); this.add_handler_filter(filter); @@ -201,7 +230,7 @@ class TelepathyClient extends Tp.BaseClient { if (this._chatSources[channel.get_object_path()]) return; - let source = new ChatSource(account, conn, channel, contact, this); + let source = new ChatSource({ account, connection: conn, channel, contact, client: this }); this._chatSources[channel.get_object_path()] = source; source.connect('destroy', () => { @@ -294,19 +323,37 @@ class TelepathyClient extends Tp.BaseClient { } }) : null; -var ChatSource = HAVE_TP ? GObject.registerClass( +/** + * @typedef {object} ChatSourceParams + * @property {import('telepathyglib0').Account} account + * @property {import('telepathyglib0').Connection} connection + * @property {import('telepathyglib0').TextChannel} channel + * @property {import('telepathyglib0').Contact} contact + * @property {import('telepathyglib0').BaseClient} client + */ + +export let ChatSource = HAVE_TP ? GObject.registerClass( class ChatSource extends MessageTray.Source { - _init(account, conn, channel, contact, client) { + + /** + * @param {import('../messageTray.js').SourceParams & ChatSourceParams} params + */ + _init(params) { + const { account, contact, client, connection, channel, ...sourceParams } = params; + this._account = account; this._contact = contact; this._client = client; - super._init(contact.get_alias()); + super._init({ + title: contact.get_alias(), + ...sourceParams + }); this.isChat = true; this._pendingMessages = []; - this._conn = conn; + this._conn = connection; this._channel = channel; this._closedId = this._channel.connect('invalidated', this._channelClosed.bind(this)); @@ -455,7 +502,7 @@ class ChatSource extends MessageTray.Source { Tpl.EventTypeMask.TEXT, SCROLLBACK_HISTORY_LINES, null); - let logMessages = events.map(e => ChatMessage.newFromTplTextEvent(e)); + let logMessages = events.map(e => newChatMessageFromTplTextEvent(e)); this._ensureNotification(); let pendingTpMessages = this._channel.get_pending_messages(); @@ -467,7 +514,7 @@ class ChatSource extends MessageTray.Source { if (message.get_message_type() == Tp.ChannelTextMessageType.DELIVERY_REPORT) continue; - pendingMessages.push(ChatMessage.newFromTpMessage(message, + pendingMessages.push(newChatMessageFromTpMessage(message, NotificationDirection.RECEIVED)); this._pendingMessages.push(message); @@ -492,7 +539,7 @@ class ChatSource extends MessageTray.Source { if (!isPending) { showTimestamp = true; - this._notification.appendMessage(logMessage, true, ['chat-log-message']); + this._notification.appendMessage(logMessage, true); } } @@ -564,7 +611,7 @@ class ChatSource extends MessageTray.Source { this._pendingMessages.push(message); this.countUpdated(); - message = ChatMessage.newFromTpMessage(message, + message = newChatMessageFromTpMessage(message, NotificationDirection.RECEIVED); this._notification.appendMessage(message); @@ -590,7 +637,7 @@ class ChatSource extends MessageTray.Source { // our client and other clients as well. _messageSent(channel, message, _flags, _token) { this._ensureNotification(); - message = ChatMessage.newFromTpMessage(message, + message = newChatMessageFromTpMessage(message, NotificationDirection.SENT); this._notification.appendMessage(message); } @@ -655,19 +702,25 @@ class ChatSource extends MessageTray.Source { const ChatNotificationMessage = HAVE_TP ? GObject.registerClass( class ChatNotificationMessage extends GObject.Object { + /** + * @param {*} props + */ _init(props = {}) { super._init(); this.set(props); } }) : null; -var ChatNotification = HAVE_TP ? GObject.registerClass({ +export let ChatNotification = HAVE_TP ? GObject.registerClass({ Signals: { 'message-removed': { param_types: [ChatNotificationMessage.$gtype] }, 'message-added': { param_types: [ChatNotificationMessage.$gtype] }, 'timestamp-changed': { param_types: [ChatNotificationMessage.$gtype] }, }, }, class ChatNotification extends MessageTray.Notification { + /** + * @param {*} source + */ _init(source) { super._init(source, source.title, null, { secondaryGIcon: source.getSecondaryIcon() }); @@ -687,18 +740,13 @@ var ChatNotification = HAVE_TP ? GObject.registerClass({ /** * appendMessage: - * @param {Object} message: An object with the properties - * {string} message.text: the body of the message, - * {Tp.ChannelTextMessageType} message.messageType: the type - * {string} message.sender: the name of the sender, - * {number} message.timestamp: the time the message was sent - * {NotificationDirection} message.direction: a #NotificationDirection + * @param {ChatMessage["prototype"]} message: An object with the properties * - * @param {bool} noTimestamp: Whether to add a timestamp. If %true, + * @param {boolean} [noTimestamp]: Whether to add a timestamp. If %true, * no timestamp will be added, regardless of the difference since * the last timestamp */ - appendMessage(message, noTimestamp) { + appendMessage(message, noTimestamp = false) { let messageBody = GLib.markup_escape_text(message.text, -1); let styles = [message.direction]; @@ -714,11 +762,8 @@ var ChatNotification = HAVE_TP ? GObject.registerClass({ bannerMarkup: true }); } - let group = message.direction == NotificationDirection.RECEIVED - ? 'received' : 'sent'; - this._append({ body: messageBody, - group, + group: message.direction == NotificationDirection.RECEIVED ? 'received' : 'sent', styles, timestamp: message.timestamp, noTimestamp }); @@ -749,15 +794,18 @@ var ChatNotification = HAVE_TP ? GObject.registerClass({ } } + /** + * @typedef {object} AppendProps: An object with the properties: + * @property {string?} body: The text of the message. + * @property {('received' | 'sent' | 'meta')?} group: The group of the message, one of: 'received', 'sent', 'meta'. + * @property {string[]} [styles]: Style class names for the message to have. + * @property {number} [timestamp]: The timestamp of the message. + * @property {boolean} [noTimestamp]: suppress timestamp signal? + */ + /** * _append: - * @param {Object} props: An object with the properties: - * {string} props.body: The text of the message. - * {string} props.group: The group of the message, one of: - * 'received', 'sent', 'meta'. - * {string[]} props.styles: Style class names for the message to have. - * {number} props.timestamp: The timestamp of the message. - * {bool} props.noTimestamp: suppress timestamp signal? + * @param {Partial<AppendProps>} props */ _append(props = {}) { let currentTime = Date.now() / 1000; @@ -828,16 +876,22 @@ var ChatNotification = HAVE_TP ? GObject.registerClass({ } }) : null; -var ChatLineBox = GObject.registerClass( +export const ChatLineBox = GObject.registerClass( class ChatLineBox extends St.BoxLayout { + /** + * @returns {[number, number]} + */ vfunc_get_preferred_height(forWidth) { let [, natHeight] = super.vfunc_get_preferred_height(forWidth); return [natHeight, natHeight]; } }); -var ChatNotificationBanner = GObject.registerClass( +export const ChatNotificationBanner = GObject.registerClass( class ChatNotificationBanner extends MessageTray.NotificationBanner { + /** + * @param {*} notification + */ _init(notification) { super._init(notification); @@ -1014,4 +1068,4 @@ class ChatNotificationBanner extends MessageTray.NotificationBanner { } }); -var Component = TelepathyComponent; +export const Component = TelepathyComponent; diff --git a/js/ui/ctrlAltTab.js b/js/ui/ctrlAltTab.js index 6507886a4..3735964f9 100644 --- a/js/ui/ctrlAltTab.js +++ b/js/ui/ctrlAltTab.js @@ -1,20 +1,24 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported CtrlAltTabManager */ -const { Clutter, GObject, Meta, Shell, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; -const Main = imports.ui.main; -const SwitcherPopup = imports.ui.switcherPopup; +import Main from './main.js'; +import * as SwitcherPopup from './switcherPopup.js'; -var POPUP_APPICON_SIZE = 96; +export let POPUP_APPICON_SIZE = 96; -var SortGroup = { +export const SortGroup = { TOP: 0, MIDDLE: 1, BOTTOM: 2, }; -var CtrlAltTabManager = class CtrlAltTabManager { +export class CtrlAltTabManager { constructor() { this._items = []; this.addGroup(global.window_group, _("Windows"), @@ -138,7 +142,7 @@ var CtrlAltTabManager = class CtrlAltTabManager { } }; -var CtrlAltTabPopup = GObject.registerClass( +export const CtrlAltTabPopup = GObject.registerClass( class CtrlAltTabPopup extends SwitcherPopup.SwitcherPopup { _init(items) { super._init(items); @@ -167,7 +171,7 @@ class CtrlAltTabPopup extends SwitcherPopup.SwitcherPopup { } }); -var CtrlAltTabSwitcher = GObject.registerClass( +export const CtrlAltTabSwitcher = GObject.registerClass( class CtrlAltTabSwitcher extends SwitcherPopup.SwitcherList { _init(items) { super._init(true); diff --git a/js/ui/dash.js b/js/ui/dash.js index 08902c436..6a083fad7 100644 --- a/js/ui/dash.js +++ b/js/ui/dash.js @@ -1,29 +1,34 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Dash */ -const { Clutter, GLib, GObject, - Graphene, Meta, Shell, St } = imports.gi; - -const AppDisplay = imports.ui.appDisplay; -const AppFavorites = imports.ui.appFavorites; -const DND = imports.ui.dnd; -const IconGrid = imports.ui.iconGrid; -const Main = imports.ui.main; -const Overview = imports.ui.overview; - -var DASH_ANIMATION_TIME = 200; -var DASH_ITEM_LABEL_SHOW_TIME = 150; -var DASH_ITEM_LABEL_HIDE_TIME = 100; -var DASH_ITEM_HOVER_TIMEOUT = 300; - -function getAppFromSource(source) { +import Clutter from 'gi://Clutter'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Graphene from 'gi://Graphene'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; + +import * as AppDisplay from './appDisplay.js'; +import * as AppFavorites from './appFavorites.js'; +import * as DND from './dnd.js'; +import * as IconGrid from './iconGrid.js'; +import * as Overview from './overview.js'; +import Main from './main.js'; + +export const DASH_ANIMATION_TIME = 200; +export const DASH_ITEM_LABEL_SHOW_TIME = 150; +export const DASH_ITEM_LABEL_HIDE_TIME = 100; +export const DASH_ITEM_HOVER_TIMEOUT = 300; + +export function getAppFromSource(source) { if (source instanceof AppDisplay.AppIcon) return source.app; else return null; } -var DashIcon = GObject.registerClass( +export const DashIcon = GObject.registerClass( class DashIcon extends AppDisplay.AppIcon { _init(app) { super._init(app, { @@ -54,7 +59,7 @@ class DashIcon extends AppDisplay.AppIcon { // A container like StBin, but taking the child's scale into account // when requesting a size -var DashItemContainer = GObject.registerClass( +export const DashItemContainer = GObject.registerClass( class DashItemContainer extends St.Widget { _init() { super._init({ @@ -74,6 +79,7 @@ class DashItemContainer extends St.Widget { Main.layoutManager.addChrome(this.label); this.label_actor = this.label; + /** @type {St.Bin | null} */ this.child = null; this.animatingOut = false; @@ -146,6 +152,9 @@ class DashItemContainer extends St.Widget { }); } + /** + * @param {St.Bin} actor + */ setChild(actor) { if (this.child == actor) return; @@ -191,7 +200,7 @@ class DashItemContainer extends St.Widget { } }); -var ShowAppsIcon = GObject.registerClass( +export const ShowAppsIcon = GObject.registerClass( class ShowAppsIcon extends DashItemContainer { _init() { super._init(); @@ -270,7 +279,7 @@ class ShowAppsIcon extends DashItemContainer { } }); -var DragPlaceholderItem = GObject.registerClass( +export const DragPlaceholderItem = GObject.registerClass( class DragPlaceholderItem extends DashItemContainer { _init() { super._init(); @@ -278,7 +287,7 @@ class DragPlaceholderItem extends DashItemContainer { } }); -var EmptyDropTargetItem = GObject.registerClass( +export const EmptyDropTargetItem = GObject.registerClass( class EmptyDropTargetItem extends DashItemContainer { _init() { super._init(); @@ -294,6 +303,7 @@ class DashIconsLayout extends Clutter.BoxLayout { }); } + /** @returns {[number, number]} */ vfunc_get_preferred_width(container, forHeight) { const [, natWidth] = super.vfunc_get_preferred_width(container, forHeight); return [0, natWidth]; @@ -302,7 +312,7 @@ class DashIconsLayout extends Clutter.BoxLayout { const baseIconSizes = [16, 22, 24, 32, 48, 64]; -var Dash = GObject.registerClass({ +export const Dash = GObject.registerClass({ Signals: { 'icon-size-changed': {} }, }, class Dash extends St.Widget { _init() { @@ -330,6 +340,7 @@ var Dash = GObject.registerClass({ y_expand: true, }); + /** @type {St.Widget<DashIconsLayout["prototype"], Clutter.Content, St.Widget | DashItemContainer["prototype"]>} */ this._box = new St.Widget({ clip_to_allocation: true, layout_manager: new DashIconsLayout(), @@ -507,6 +518,10 @@ var Dash = GObject.registerClass({ }); let item = new DashItemContainer(); + + // TODO: I don't know why this type error is occuring. + // I'm guessing it is an issue with the complicated inheritance. + // @ts-expect-error item.setChild(appIcon); // Override default AppIcon label_actor, now the @@ -574,9 +589,16 @@ var Dash = GObject.registerClass({ // icons (i.e. ignoring drag placeholders) and which are not // animating out (which means they will be destroyed at the end of // the animation) - let iconChildren = this._box.get_children().filter(actor => { - return actor.child && + let iconChildren = this._box.get_children().filter( + /** + * @param {Clutter.Actor} actor + * @returns {actor is DashItemContainer["prototype"]} + */ + (actor) => { + return actor instanceof DashItemContainer && + actor.child && actor.child._delegate && + // @ts-expect-error actor.child._delegate.icon && !actor.animatingOut; }); @@ -598,7 +620,7 @@ var Dash = GObject.registerClass({ let spacing = themeNode.get_length('spacing'); let firstButton = iconChildren[0].child; - let firstIcon = firstButton._delegate.icon; + let firstIcon = /** @type {typeof AppDisplay.AppIcon["prototype"]} */ (firstButton._delegate).icon; // Enforce valid spacings during the size request firstIcon.icon.ensure_style(); @@ -634,7 +656,7 @@ var Dash = GObject.registerClass({ let scale = oldIconSize / newIconSize; for (let i = 0; i < iconChildren.length; i++) { - let icon = iconChildren[i].child._delegate.icon; + let icon = /** @type {typeof AppDisplay.AppIcon["prototype"]} */ (iconChildren[i].child._delegate).icon; // Set the new size immediately, to keep the icons' sizes // in sync with this.iconSize @@ -676,13 +698,20 @@ var Dash = GObject.registerClass({ let running = this._appSystem.get_running(); + // FIXME let children = this._box.get_children().filter(actor => { - return actor.child && - actor.child._delegate && - actor.child._delegate.app; + return 'child' in actor && actor.child && + /** @type {typeof AppDisplay.AppIcon["prototype"]} */ (actor.child._delegate).app && + /** @type {typeof AppDisplay.AppIcon["prototype"]} */ (actor.child._delegate).app; + }); // Apps currently in the dash - let oldApps = children.map(actor => actor.child._delegate.app); + let oldApps = children.map(actor => 'child' in actor ? actor.child._delegate : null).filter( + /** + * @param {unknown} delegate ' + * @returns {delegate is typeof AppDisplay.AppIcon["prototype"]} + */ + delegate => delegate instanceof AppDisplay.AppIcon).map(d => d.app); // Apps supposed to be in the dash let newApps = []; @@ -749,7 +778,8 @@ var Dash = GObject.registerClass({ ? newApps[newIndex + 1] : null; let insertHere = nextApp && nextApp == oldApp; let alreadyRemoved = removedActors.reduce((result, actor) => { - let removedApp = actor.child._delegate.app; + let removedApp = /** @type {typeof AppDisplay.AppIcon["prototype"]} */ (actor.child._delegate).app; + return result || removedApp == newApp; }, false); @@ -773,9 +803,10 @@ var Dash = GObject.registerClass({ for (let i = 0; i < removedActors.length; i++) { let item = removedActors[i]; + // FIXME // Don't animate item removal when the overview is transitioning // or hidden - if (Main.overview.visible && !Main.overview.animationInProgress) + if (Main.overview.visible && !Main.overview.animationInProgress && 'animateOutAndDestroy' in item) item.animateOutAndDestroy(); else item.destroy(); @@ -947,7 +978,7 @@ var Dash = GObject.registerClass({ children[i] == this._dragPlaceholder) continue; - let childId = children[i].child._delegate.app.get_id(); + let childId = /** @type {typeof AppDisplay.AppIcon["prototype"]} */ (children[i].child._delegate).app.get_id(); if (childId == id) continue; if (childId in favorites) diff --git a/js/ui/dateMenu.js b/js/ui/dateMenu.js index d202e8436..488c1ee9d 100644 --- a/js/ui/dateMenu.js +++ b/js/ui/dateMenu.js @@ -1,17 +1,24 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported DateMenuButton */ -const { Clutter, Gio, GLib, GnomeDesktop, - GObject, GWeather, Pango, Shell, St } = imports.gi; - -const Util = imports.misc.util; -const Main = imports.ui.main; -const PanelMenu = imports.ui.panelMenu; -const Calendar = imports.ui.calendar; -const Weather = imports.misc.weather; -const System = imports.system; - -const { loadInterfaceXML } = imports.misc.fileUtils; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GnomeDesktop from 'gi://GnomeDesktop'; +import GObject from 'gi://GObject'; +import GWeather from 'gi://GWeather'; +import Pango from 'gi://Pango'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; + +import * as Util from '../misc/util.js'; +import Main from './main.js'; +import * as PanelMenu from './panelMenu.js'; +import * as Calendar from './calendar.js'; +import * as Weather from '../misc/weather.js'; +import System from 'system'; + +import { loadInterfaceXML } from '../misc/fileUtilsModule.js'; const NC_ = (context, str) => '%s\u0004%s'.format(context, str); const T_ = Shell.util_translate_time_string; @@ -24,7 +31,7 @@ const ClocksProxy = Gio.DBusProxy.makeProxyWrapper(ClocksIntegrationIface); function _isToday(date) { let now = new Date(); - return now.getYear() == date.getYear() && + return now.getFullYear() == date.getFullYear() && now.getMonth() == date.getMonth() && now.getDate() == date.getDate(); } @@ -33,8 +40,11 @@ function _gDateTimeToDate(datetime) { return new Date(datetime.to_unix() * 1000 + datetime.get_microsecond() / 1000); } -var TodayButton = GObject.registerClass( +export const TodayButton = GObject.registerClass( class TodayButton extends St.Button { + /** + * @param {*} calendar + */ _init(calendar) { // Having the ability to go to the current date if the user is already // on the current date can be confusing. So don't make the button reactive @@ -88,7 +98,7 @@ class TodayButton extends St.Button { } }); -var EventsSection = GObject.registerClass( +export const EventsSection = GObject.registerClass( class EventsSection extends St.Button { _init() { super._init({ @@ -126,7 +136,11 @@ class EventsSection extends St.Button { this._appInstalledChanged(); } + /** + * @param {Date} date + */ setDate(date) { + /** @type {[number, number, number]} */ const day = [date.getFullYear(), date.getMonth(), date.getDate()]; this._startDate = new Date(...day); this._endDate = new Date(...day, 23, 59, 59, 999); @@ -158,9 +172,9 @@ class EventsSection extends St.Button { if (this._startDate <= now && now <= this._endDate) this._title.text = _('Today'); - else if (this._endDate < now && now - this._endDate < timeSpanDay) + else if (this._endDate < now && now.getTime() - this._endDate.getTime() < timeSpanDay) this._title.text = _('Yesterday'); - else if (this._startDate > now && this._startDate - now < timeSpanDay) + else if (this._startDate > now && this._startDate.getTime() - now.getTime() < timeSpanDay) this._title.text = _('Tomorrow'); else if (this._startDate.getFullYear() === now.getFullYear()) this._title.text = this._startDate.toLocaleFormat(sameYearFormat); @@ -270,7 +284,7 @@ class EventsSection extends St.Button { } }); -var WorldClocksSection = GObject.registerClass( +export const WorldClocksSection = GObject.registerClass( class WorldClocksSection extends St.Button { _init() { super._init({ @@ -331,7 +345,7 @@ class WorldClocksSection extends St.Button { this._locations = []; let world = GWeather.Location.get_world(); - let clocks = this._settings.get_value('locations').deep_unpack(); + let clocks = /** @type {GLib.Variant<'av'>} */ (this._settings.get_value('locations')).deep_unpack(); for (let i = 0; i < clocks.length; i++) { let l = world.deserialize(clocks[i]); if (l && l.get_timezone() != null) @@ -466,7 +480,7 @@ class WorldClocksSection extends St.Button { } }); -var WeatherSection = GObject.registerClass( +export const WeatherSection = GObject.registerClass( class WeatherSection extends St.Button { _init() { super._init({ @@ -602,6 +616,9 @@ class WeatherSection extends St.Button { layout.attach(label, 0, 0, 1, 1); } + /** + * @param {GWeather.Location} loc + */ _findBestLocationName(loc) { const locName = loc.get_name(); @@ -659,7 +676,7 @@ class WeatherSection extends St.Button { } }); -var MessagesIndicator = GObject.registerClass( +export const MessagesIndicator = GObject.registerClass( class MessagesIndicator extends St.Icon { _init() { super._init({ @@ -720,13 +737,17 @@ class MessagesIndicator extends St.Icon { } }); -var FreezableBinLayout = GObject.registerClass( +export const FreezableBinLayout = GObject.registerClass( class FreezableBinLayout extends Clutter.BinLayout { _init() { super._init(); this._frozen = false; + + /** @type {[number, number]} */ this._savedWidth = [NaN, NaN]; + + /** @type {[number, number]} */ this._savedHeight = [NaN, NaN]; } @@ -739,6 +760,9 @@ class FreezableBinLayout extends Clutter.BinLayout { this.layout_changed(); } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_width(container, forHeight) { if (!this._frozen || this._savedWidth.some(isNaN)) return super.vfunc_get_preferred_width(container, forHeight); @@ -760,8 +784,11 @@ class FreezableBinLayout extends Clutter.BinLayout { } }); -var CalendarColumnLayout = GObject.registerClass( +export const CalendarColumnLayout = GObject.registerClass( class CalendarColumnLayout extends Clutter.BoxLayout { + /** + * @param {*} actors + */ _init(actors) { super._init({ orientation: Clutter.Orientation.VERTICAL }); this._colActors = actors; @@ -779,7 +806,7 @@ class CalendarColumnLayout extends Clutter.BoxLayout { } }); -var DateMenuButton = GObject.registerClass( +export const DateMenuButton = GObject.registerClass( class DateMenuButton extends PanelMenu.Button { _init() { let hbox; diff --git a/js/ui/dialog.js b/js/ui/dialog.js index 9513a8151..0a8e448ea 100644 --- a/js/ui/dialog.js +++ b/js/ui/dialog.js @@ -1,7 +1,13 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Dialog, MessageDialogContent, ListSection, ListSectionItem */ -const { Clutter, GLib, GObject, Meta, Pango, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import Pango from 'gi://Pango'; +import St from 'gi://St'; + function _setLabel(label, value) { label.set({ @@ -10,8 +16,12 @@ function _setLabel(label, value) { }); } -var Dialog = GObject.registerClass( +export const Dialog = GObject.registerClass( class Dialog extends St.Widget { + /** + * @param {*} parentActor + * @param {*} styleClass + */ _init(parentActor, styleClass) { super._init({ layout_manager: new Clutter.BinLayout() }); this.connect('destroy', this._onDestroy.bind(this)); @@ -157,7 +167,7 @@ class Dialog extends St.Widget { } }); -var MessageDialogContent = GObject.registerClass({ +export const MessageDialogContent = GObject.registerClass({ Properties: { 'title': GObject.ParamSpec.string( 'title', 'title', 'title', @@ -171,6 +181,9 @@ var MessageDialogContent = GObject.registerClass({ null), }, }, class MessageDialogContent extends St.BoxLayout { + /** + * @param {*} params + */ _init(params) { this._title = new St.Label({ style_class: 'message-dialog-title' }); this._description = new St.Label({ style_class: 'message-dialog-description' }); @@ -247,7 +260,7 @@ var MessageDialogContent = GObject.registerClass({ } }); -var ListSection = GObject.registerClass({ +export const ListSection = GObject.registerClass({ Properties: { 'title': GObject.ParamSpec.string( 'title', 'title', 'title', @@ -256,6 +269,9 @@ var ListSection = GObject.registerClass({ null), }, }, class ListSection extends St.BoxLayout { + /** + * @param {*} params + */ _init(params) { this._title = new St.Label({ style_class: 'dialog-list-title' }); @@ -292,7 +308,7 @@ var ListSection = GObject.registerClass({ } }); -var ListSectionItem = GObject.registerClass({ +export const ListSectionItem = GObject.registerClass({ Properties: { 'icon-actor': GObject.ParamSpec.object( 'icon-actor', 'icon-actor', 'Icon actor', @@ -310,6 +326,9 @@ var ListSectionItem = GObject.registerClass({ null), }, }, class ListSectionItem extends St.BoxLayout { + /** + * @param {*} params + */ _init(params) { this._iconActorBin = new St.Bin(); diff --git a/js/ui/dnd.js b/js/ui/dnd.js index a659c4fd2..3da06629b 100644 --- a/js/ui/dnd.js +++ b/js/ui/dnd.js @@ -1,43 +1,87 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported addDragMonitor, removeDragMonitor, makeDraggable */ -const { Clutter, GLib, Meta, Shell, St } = imports.gi; -const Signals = imports.misc.signals; +import Clutter from 'gi://Clutter'; +import GLib from 'gi://GLib'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; -const Main = imports.ui.main; +import * as Signals from '../misc/signals.js'; + +import Main from './main.js'; // Time to scale down to maxDragActorSize -var SCALE_ANIMATION_TIME = 250; +export let SCALE_ANIMATION_TIME = 250; // Time to animate to original position on cancel -var SNAP_BACK_ANIMATION_TIME = 250; +export let SNAP_BACK_ANIMATION_TIME = 250; // Time to animate to original position on success -var REVERT_ANIMATION_TIME = 750; +export let REVERT_ANIMATION_TIME = 750; + +/** @typedef {{ getDragActor<T extends Clutter.Actor = Clutter.Actor>(): T }} DragActorContainer */ +/** @typedef {{ getDragActorSource<T extends Clutter.Actor = Clutter.Actor>(): T }} DragActorSourceContainer */ +/** @typedef {{ handleDragOver(source: Clutter.Actor | Signals.EventEmitter, actor: Clutter.Actor, x: number, y: number, time: number): DragMotionResult }} DragOverTarget */ +/** @typedef {{ acceptDrop(source: Clutter.Actor | Signals.EventEmitter, actor: Clutter.Actor, x: number, y: number, time: number): boolean }} DropTarget */ + +/** + * @param {unknown} delegate + * @returns {delegate is DragActorContainer} + */ +function hasDragActor(delegate) { + return !!(/** @type {DragActorContainer} */ (delegate).getDragActor); +} + +/** + * @param {unknown} delegate + * @returns {delegate is DragActorSourceContainer} + */ +function hasDragActorSource(delegate) { + return !!(/** @type {DragActorSourceContainer} */ (delegate).getDragActorSource); +} + +/** + * @param {unknown} delegate + * @returns {delegate is DragOverTarget} + */ +export function handlesDragOver(delegate) { + return !!(/** @type {DragOverTarget} */ (delegate).handleDragOver); +} -var DragMotionResult = { +/** + * @param {unknown} delegate + * @returns {delegate is DropTarget} + */ +function isDropTarget(delegate) { + return !!(/** @type {DropTarget} */ (delegate).acceptDrop); +} + +/** @enum {number} */ +export const DragMotionResult = { NO_DROP: 0, COPY_DROP: 1, MOVE_DROP: 2, CONTINUE: 3, }; -var DragState = { +/** @enum {number} */ +export const DragState = { INIT: 0, DRAGGING: 1, CANCELLED: 2, }; -var DRAG_CURSOR_MAP = { +export const DRAG_CURSOR_MAP = { 0: Meta.Cursor.DND_UNSUPPORTED_TARGET, 1: Meta.Cursor.DND_COPY, 2: Meta.Cursor.DND_MOVE, }; -var DragDropResult = { +export const DragDropResult = { FAILURE: 0, SUCCESS: 1, CONTINUE: 2, }; -var dragMonitors = []; +export let dragMonitors = []; let eventHandlerActor = null; let currentDraggable = null; @@ -64,11 +108,11 @@ function _getRealActorScale(actor) { return scale; } -function addDragMonitor(monitor) { +export function addDragMonitor(monitor) { dragMonitors.push(monitor); } -function removeDragMonitor(monitor) { +export function removeDragMonitor(monitor) { for (let i = 0; i < dragMonitors.length; i++) { if (dragMonitors[i] == monitor) { dragMonitors.splice(i, 1); @@ -77,7 +121,20 @@ function removeDragMonitor(monitor) { } } -var _Draggable = class _Draggable extends Signals.EventEmitter { +// FIXME +/** + * @typedef {object} _DraggableParams + * @property {boolean} [manualMode] + * @property {number} [timeoutThreshold] + * @property {boolean} [restoreOnSuccess] + * @property {number} [dragActorMaxSize] + * @property {number} [dragActorOpacity] + */ +export class _Draggable extends Signals.EventEmitter { + /** + * @param {Clutter.Actor} actor + * @param {Partial<_DraggableParams>} [params] + */ constructor(actor, params = {}) { super(); @@ -358,7 +415,7 @@ var _Draggable = class _Draggable extends Signals.EventEmitter { let scaledWidth, scaledHeight; - if (this.actor._delegate && this.actor._delegate.getDragActor) { + if (this.actor._delegate && hasDragActor(this.actor._delegate)) { this._dragActor = this.actor._delegate.getDragActor(); Main.uiGroup.add_child(this._dragActor); Main.uiGroup.set_child_above_sibling(this._dragActor, null); @@ -367,7 +424,7 @@ var _Draggable = class _Draggable extends Signals.EventEmitter { // Drag actor does not always have to be the same as actor. For example drag actor // can be an image that's part of the actor. So to perform "snap back" correctly we need // to know what was the drag actor source. - if (this.actor._delegate.getDragActorSource) { + if (hasDragActorSource(this.actor._delegate)) { this._dragActorSource = this.actor._delegate.getDragActorSource(); // If the user dragged from the source, then position // the dragActor over it. Otherwise, center it @@ -570,7 +627,7 @@ var _Draggable = class _Draggable extends Signals.EventEmitter { dragEvent.targetActor.disconnect(targetActorDestroyHandlerId); while (target) { - if (target._delegate && target._delegate.handleDragOver) { + if (target._delegate && handlesDragOver(target._delegate)) { let [r_, targX, targY] = target.transform_stage_point(this._dragX, this._dragY); // We currently loop through all parents on drag-over even if one of the children has handled it. // We can check the return value of the function and break the loop if it's true if we don't want @@ -643,7 +700,7 @@ var _Draggable = class _Draggable extends Signals.EventEmitter { this._dragCancellable = false; while (target) { - if (target._delegate && target._delegate.acceptDrop) { + if (target._delegate && isDropTarget(target._delegate)) { let [r_, targX, targY] = target.transform_stage_point(dropX, dropY); let accepted = false; try { @@ -842,6 +899,6 @@ var _Draggable = class _Draggable extends Signals.EventEmitter { * target wants to reuse the actor, it's up to the drop target to * reset these values. */ -function makeDraggable(actor, params) { +export function makeDraggable(actor, params) { return new _Draggable(actor, params); } diff --git a/js/ui/edgeDragAction.js b/js/ui/edgeDragAction.js index 986b658e0..148dda232 100644 --- a/js/ui/edgeDragAction.js +++ b/js/ui/edgeDragAction.js @@ -1,19 +1,26 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported EdgeDragAction */ -const { Clutter, GObject, Meta, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import St from 'gi://St'; -const Main = imports.ui.main; +import Main from './main.js'; -var EDGE_THRESHOLD = 20; -var DRAG_DISTANCE = 80; +export const EDGE_THRESHOLD = 20; +export const DRAG_DISTANCE = 80; -var EdgeDragAction = GObject.registerClass({ +export const EdgeDragAction = GObject.registerClass({ Signals: { 'activated': {}, 'progress': { param_types: [GObject.TYPE_DOUBLE] }, }, }, class EdgeDragAction extends Clutter.GestureAction { + /** + * @param {*} side + * @param {*} allowedModes + */ _init(side, allowedModes) { super._init(); this._side = side; diff --git a/js/ui/endSessionDialog.js b/js/ui/endSessionDialog.js index c2a314206..34a19d52e 100644 --- a/js/ui/endSessionDialog.js +++ b/js/ui/endSessionDialog.js @@ -17,17 +17,26 @@ * along with this program; if not, see <http://www.gnu.org/licenses/>. */ -const { AccountsService, Clutter, Gio, - GLib, GObject, Pango, Polkit, Shell, St, UPowerGlib: UPower } = imports.gi; - -const CheckBox = imports.ui.checkBox; -const Dialog = imports.ui.dialog; -const GnomeSession = imports.misc.gnomeSession; -const LoginManager = imports.misc.loginManager; -const ModalDialog = imports.ui.modalDialog; -const UserWidget = imports.ui.userWidget; - -const { loadInterfaceXML } = imports.misc.fileUtils; +import Clutter from 'gi://Clutter'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Gio from 'gi://Gio'; + +import AccountsService from 'gi://AccountsService'; +import Pango from 'gi://Pango'; +import Polkit from 'gi://Polkit'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; +import UPower from 'gi://UPowerGlib'; + +import * as CheckBox from './checkBox.js'; +import * as Dialog from './dialog.js'; +import * as GnomeSession from '../misc/gnomeSession.js'; +import * as LoginManager from '../misc/loginManager.js'; +import * as ModalDialog from './modalDialog.js'; +import * as UserWidget from './userWidget.js'; + +import { loadInterfaceXML } from '../misc/fileUtilsModule.js'; const _ITEM_ICON_SIZE = 64; @@ -151,7 +160,7 @@ const DialogContent = { 4 /* DialogType.UPGRADE_RESTART */: restartUpgradeDialogContent, }; -var MAX_USERS_IN_SESSION_DIALOG = 5; +export let MAX_USERS_IN_SESSION_DIALOG = 5; const LogindSessionIface = loadInterfaceXML('org.freedesktop.login1.Session'); const LogindSession = Gio.DBusProxy.makeProxyWrapper(LogindSessionIface); @@ -216,14 +225,22 @@ function _setCheckBoxLabel(checkBox, text) { } } -function init() { +export function init() { // This always returns the same singleton object // By instantiating it initially, we register the // bus object, etc. new EndSessionDialog(); } -var EndSessionDialog = GObject.registerClass( +/** @typedef {{ name?: string; version?: string; }} PreparedUpgradeInfo */ +/** @typedef {{ + UpdateTriggered: boolean, + UpdatePrepared: boolean, + UpgradeTriggered: boolean, + PreparedUpgrade: PreparedUpgradeInfo } +} UpdateInfo */ + +export const EndSessionDialog = GObject.registerClass( class EndSessionDialog extends ModalDialog.ModalDialog { _init() { super._init({ styleClass: 'end-session-dialog', @@ -650,7 +667,7 @@ class EndSessionDialog extends ModalDialog.ModalDialog { let n = 0; for (let i = 0; i < result.length; i++) { let [id_, uid_, userName, seat_, sessionPath] = result[i]; - let proxy = new LogindSession(Gio.DBus.system, 'org.freedesktop.login1', sessionPath); + let proxy = LogindSession(Gio.DBus.system, 'org.freedesktop.login1', sessionPath); if (proxy.Class != 'user') continue; @@ -662,7 +679,7 @@ class EndSessionDialog extends ModalDialog.ModalDialog { if (!sessionId) { this._loginManager.getCurrentSessionProxy(currentSessionProxy => { sessionId = currentSessionProxy.Id; - log('endSessionDialog: No XDG_SESSION_ID, fetched from logind: %d'.format(sessionId)); + log('endSessionDialog: No XDG_SESSION_ID, fetched from logind: %s'.format(sessionId)); }); } @@ -706,6 +723,9 @@ class EndSessionDialog extends ModalDialog.ModalDialog { }); } + /** + * @returns {Promise<UpdateInfo>} + */ async _getUpdateInfo() { const connection = this._pkOfflineProxy.get_connection(); const reply = await connection.call( @@ -714,12 +734,14 @@ class EndSessionDialog extends ModalDialog.ModalDialog { 'org.freedesktop.DBus.Properties', 'GetAll', new GLib.Variant('(s)', [this._pkOfflineProxy.g_interface_name]), - null, + new GLib.VariantType('(a{sv})'), Gio.DBusCallFlags.NONE, -1, null); + const [info] = reply.recursiveUnpack(); - return info; + + return /** @type {UpdateInfo} */ (info); } async OpenAsync(parameters, invocation) { @@ -764,7 +786,7 @@ class EndSessionDialog extends ModalDialog.ModalDialog { let dialogContent = DialogContent[this._type]; for (let i = 0; i < inhibitorObjectPaths.length; i++) { - let inhibitor = new GnomeSession.Inhibitor(inhibitorObjectPaths[i], proxy => { + let inhibitor = GnomeSession.Inhibitor(inhibitorObjectPaths[i], proxy => { this._onInhibitorLoaded(proxy); }); diff --git a/js/ui/environment.js b/js/ui/environment.js index ccd7dcdaa..16b389442 100644 --- a/js/ui/environment.js +++ b/js/ui/environment.js @@ -3,27 +3,34 @@ const Config = imports.misc.config; -imports.gi.versions.Clutter = Config.LIBMUTTER_API_VERSION; -imports.gi.versions.Gio = '2.0'; -imports.gi.versions.GdkPixbuf = '2.0'; -imports.gi.versions.Gtk = '3.0'; -imports.gi.versions.Soup = '3.0'; -imports.gi.versions.TelepathyGLib = '0.12'; -imports.gi.versions.TelepathyLogger = '0.2'; +import gi from "gi"; +import "gi://Gio?version=2.0"; +import "gi://GdkPixbuf?version=2.0"; +import "gi://Gtk?version=3.0"; +import "gi://TelepathyGLib?version=0.12"; +import "gi://TelepathyLogger?version=0.2"; + +import Gio from "gi://Gio"; +import GLib from "gi://GLib"; +import GObject from "gi://GObject"; +import Meta from "gi://Meta"; +import Polkit from "gi://Polkit"; +import Shell from "gi://Shell"; +import St from "gi://St"; +import * as Gettext from "gettext"; +import System from "system"; +// import * as ExtensionUtils from '../misc/extensionUtils.js'; try { - if (Config.HAVE_SOUP2) - throw new Error('Soup3 support not enabled'); - const Soup_ = imports.gi.Soup; + if (Config.HAVE_SOUP2) + throw new Error('Soup3 support not enabled'); + const Soup_ = gi.require('Soup', '3.0'); } catch (e) { - imports.gi.versions.Soup = '2.4'; - const { Soup } = imports.gi; - _injectSoup3Compat(Soup); + const Soup = gi.require('Soup', '2.4'); + _injectSoup3Compat(Soup); } -const { Clutter, Gio, GLib, GObject, Meta, Polkit, Shell, St } = imports.gi; -const Gettext = imports.gettext; -const System = imports.system; +const Clutter = gi.require("Clutter", Config.LIBMUTTER_API_VERSION); Gio._promisify(Gio.DataInputStream.prototype, 'fill_async', 'fill_finish'); Gio._promisify(Gio.DataInputStream.prototype, @@ -42,41 +49,37 @@ let _localTimeZone = null; // variable initializations, etc, that depend on init() already having // been run. - // "monkey patch" in some varargs ClutterContainer methods; we need // to do this per-container class since there is no representation // of interfaces in Javascript function _patchContainerClass(containerClass) { - // This one is a straightforward mapping of the C method - containerClass.prototype.child_set = function (actor, props) { - let meta = this.get_child_meta(actor); - for (let prop in props) - meta[prop] = props[prop]; - }; - - // clutter_container_add() actually is a an add-many-actors - // method. We conveniently, but somewhat dubiously, take the - // this opportunity to make it do something more useful. - containerClass.prototype.add = function (actor, props) { - this.add_actor(actor); - if (props) - this.child_set(actor, props); - }; + // This one is a straightforward mapping of the C method + containerClass.prototype.child_set = function (actor, props) { + let meta = this.get_child_meta(actor); + for (let prop in props) meta[prop] = props[prop]; + }; + + // clutter_container_add() actually is a an add-many-actors + // method. We conveniently, but somewhat dubiously, take the + // this opportunity to make it do something more useful. + containerClass.prototype.add = function (actor, props) { + this.add_actor(actor); + if (props) this.child_set(actor, props); + }; } function _patchLayoutClass(layoutClass, styleProps) { - if (styleProps) { - layoutClass.prototype.hookup_style = function (container) { - container.connect('style-changed', () => { - let node = container.get_theme_node(); - for (let prop in styleProps) { - let [found, length] = node.lookup_length(styleProps[prop], false); - if (found) - this[prop] = length; - } - }); - }; - } + if (styleProps) { + layoutClass.prototype.hookup_style = function (container) { + container.connect("style-changed", () => { + let node = container.get_theme_node(); + for (let prop in styleProps) { + let [found, length] = node.lookup_length(styleProps[prop], false); + if (found) this[prop] = length; + } + }); + }; + } } /** @@ -116,331 +119,350 @@ function _injectSoup3Compat(Soup) { } function _makeEaseCallback(params, cleanup) { - let onComplete = params.onComplete; - delete params.onComplete; + let onComplete = params.onComplete; + delete params.onComplete; - let onStopped = params.onStopped; - delete params.onStopped; + let onStopped = params.onStopped; + delete params.onStopped; - return isFinished => { - cleanup(); + return (isFinished) => { + cleanup(); - if (onStopped) - onStopped(isFinished); - if (onComplete && isFinished) - onComplete(); - }; + if (onStopped) onStopped(isFinished); + if (onComplete && isFinished) onComplete(); + }; } function _getPropertyTarget(actor, propName) { - if (!propName.startsWith('@')) - return [actor, propName]; - - let [type, name, prop] = propName.split('.'); - switch (type) { - case '@layout': - return [actor.layout_manager, name]; - case '@actions': - return [actor.get_action(name), prop]; - case '@constraints': - return [actor.get_constraint(name), prop]; - case '@content': - return [actor.content, name]; - case '@effects': - return [actor.get_effect(name), prop]; - } - - throw new Error(`Invalid property name ${propName}`); + if (!propName.startsWith("@")) return [actor, propName]; + + let [type, name, prop] = propName.split("."); + switch (type) { + case "@layout": + return [actor.layout_manager, name]; + case "@actions": + return [actor.get_action(name), prop]; + case "@constraints": + return [actor.get_constraint(name), prop]; + case "@content": + return [actor.content, name]; + case "@effects": + return [actor.get_effect(name), prop]; + } + + throw new Error(`Invalid property name ${propName}`); } function _easeActor(actor, params) { - actor.save_easing_state(); - - if (params.duration != undefined) - actor.set_easing_duration(params.duration); - delete params.duration; - - if (params.delay != undefined) - actor.set_easing_delay(params.delay); - delete params.delay; - - let repeatCount = 0; - if (params.repeatCount != undefined) - repeatCount = params.repeatCount; - delete params.repeatCount; - - let autoReverse = false; - if (params.autoReverse != undefined) - autoReverse = params.autoReverse; - delete params.autoReverse; - - // repeatCount doesn't include the initial iteration - const numIterations = repeatCount + 1; - // whether the transition should finish where it started - const isReversed = autoReverse && numIterations % 2 === 0; - - if (params.mode != undefined) - actor.set_easing_mode(params.mode); - delete params.mode; - - const prepare = () => { - Meta.disable_unredirect_for_display(global.display); - global.begin_work(); - }; - const cleanup = () => { - Meta.enable_unredirect_for_display(global.display); - global.end_work(); - }; - let callback = _makeEaseCallback(params, cleanup); - - // cancel overwritten transitions - let animatedProps = Object.keys(params).map(p => p.replace('_', '-', 'g')); - animatedProps.forEach(p => actor.remove_transition(p)); - - if (actor.get_easing_duration() > 0 || !isReversed) - actor.set(params); - actor.restore_easing_state(); - - let transition = animatedProps.map(p => actor.get_transition(p)) - .find(t => t !== null); - - if (transition && transition.delay) - transition.connect('started', () => prepare()); - else - prepare(); - - if (transition) { - transition.set({ repeatCount, autoReverse }); - transition.connect('stopped', (t, finished) => callback(finished)); - } else { - callback(true); - } + actor.save_easing_state(); + + if (params.duration != undefined) actor.set_easing_duration(params.duration); + delete params.duration; + + if (params.delay != undefined) actor.set_easing_delay(params.delay); + delete params.delay; + + let repeatCount = 0; + if (params.repeatCount != undefined) repeatCount = params.repeatCount; + delete params.repeatCount; + + let autoReverse = false; + if (params.autoReverse != undefined) autoReverse = params.autoReverse; + delete params.autoReverse; + + // repeatCount doesn't include the initial iteration + const numIterations = repeatCount + 1; + // whether the transition should finish where it started + const isReversed = autoReverse && numIterations % 2 === 0; + + if (params.mode != undefined) actor.set_easing_mode(params.mode); + delete params.mode; + + const prepare = () => { + Meta.disable_unredirect_for_display(global.display); + global.begin_work(); + }; + const cleanup = () => { + Meta.enable_unredirect_for_display(global.display); + global.end_work(); + }; + let callback = _makeEaseCallback(params, cleanup); + + // cancel overwritten transitions + let animatedProps = Object.keys(params).map((p) => p.replace(/_/g, "-")); + animatedProps.forEach((p) => actor.remove_transition(p)); + + if (actor.get_easing_duration() > 0 || !isReversed) actor.set(params); + actor.restore_easing_state(); + + let transition = animatedProps.map((p) => actor.get_transition(p)).find((t) => t !== null); + + if (transition && transition.delay) transition.connect("started", () => prepare()); + else prepare(); + + if (transition) { + transition.set({ repeatCount, autoReverse }); + transition.connect("stopped", (t, finished) => callback(finished)); + } else { + callback(true); + } } function _easeActorProperty(actor, propName, target, params) { - // Avoid pointless difference with ease() - if (params.mode) - params.progress_mode = params.mode; - delete params.mode; - - if (params.duration) - params.duration = adjustAnimationTime(params.duration); - let duration = Math.floor(params.duration || 0); - - let repeatCount = 0; - if (params.repeatCount != undefined) - repeatCount = params.repeatCount; - delete params.repeatCount; - - let autoReverse = false; - if (params.autoReverse != undefined) - autoReverse = params.autoReverse; - delete params.autoReverse; - - // repeatCount doesn't include the initial iteration - const numIterations = repeatCount + 1; - // whether the transition should finish where it started - const isReversed = autoReverse && numIterations % 2 === 0; - - // Copy Clutter's behavior for implicit animations, see - // should_skip_implicit_transition() - if (actor instanceof Clutter.Actor && !actor.mapped) - duration = 0; - - const prepare = () => { - Meta.disable_unredirect_for_display(global.display); - global.begin_work(); - }; - const cleanup = () => { - Meta.enable_unredirect_for_display(global.display); - global.end_work(); - }; - let callback = _makeEaseCallback(params, cleanup); - - // cancel overwritten transition - actor.remove_transition(propName); - - if (duration == 0) { - let [obj, prop] = _getPropertyTarget(actor, propName); - - if (!isReversed) - obj[prop] = target; - - prepare(); - callback(true); - - return; - } - - let pspec = actor.find_property(propName); - let transition = new Clutter.PropertyTransition(Object.assign({ + // Avoid pointless difference with ease() + if (params.mode) params.progress_mode = params.mode; + delete params.mode; + + if (params.duration) params.duration = adjustAnimationTime(params.duration); + let duration = Math.floor(params.duration || 0); + + let repeatCount = 0; + if (params.repeatCount != undefined) repeatCount = params.repeatCount; + delete params.repeatCount; + + let autoReverse = false; + if (params.autoReverse != undefined) autoReverse = params.autoReverse; + delete params.autoReverse; + + // repeatCount doesn't include the initial iteration + const numIterations = repeatCount + 1; + // whether the transition should finish where it started + const isReversed = autoReverse && numIterations % 2 === 0; + + // Copy Clutter's behavior for implicit animations, see + // should_skip_implicit_transition() + if (actor instanceof Clutter.Actor && !actor.mapped) duration = 0; + + const prepare = () => { + Meta.disable_unredirect_for_display(global.display); + global.begin_work(); + }; + const cleanup = () => { + Meta.enable_unredirect_for_display(global.display); + global.end_work(); + }; + let callback = _makeEaseCallback(params, cleanup); + + // cancel overwritten transition + actor.remove_transition(propName); + + if (duration == 0) { + let [obj, prop] = _getPropertyTarget(actor, propName); + + if (!isReversed) obj[prop] = target; + + prepare(); + callback(true); + + return; + } + + let pspec = actor.find_property(propName); + let transition = new Clutter.PropertyTransition( + Object.assign( + { property_name: propName, interval: new Clutter.Interval({ value_type: pspec.value_type }), remove_on_complete: true, repeat_count: repeatCount, auto_reverse: autoReverse, - }, params)); - actor.add_transition(propName, transition); + }, + params + ) + ); + actor.add_transition(propName, transition); - transition.set_to(target); + transition.set_to(target); - if (transition.delay) - transition.connect('started', () => prepare()); - else - prepare(); + if (transition.delay) transition.connect("started", () => prepare()); + else prepare(); - transition.connect('stopped', (t, finished) => callback(finished)); + transition.connect("stopped", (t, finished) => callback(finished)); } function _loggingFunc(...args) { - let fields = { 'MESSAGE': args.join(', ') }; - let domain = "GNOME Shell"; - - // If the caller is an extension, add it as metadata - let extension = imports.misc.extensionUtils.getCurrentExtension(); - if (extension != null) { - domain = extension.metadata.name; - fields['GNOME_SHELL_EXTENSION_UUID'] = extension.uuid; - fields['GNOME_SHELL_EXTENSION_NAME'] = extension.metadata.name; - } - - GLib.log_structured(domain, GLib.LogLevelFlags.LEVEL_MESSAGE, fields); + let fields = { MESSAGE: args.join(", ") }; + let domain = "GNOME Shell"; + + // If the caller is an extension, add it as metadata + // let extension = ExtensionUtils.getCurrentExtension(); + // if (extension != null) { + // domain = extension.metadata.name; + // fields['GNOME_SHELL_EXTENSION_UUID'] = extension.uuid; + // fields['GNOME_SHELL_EXTENSION_NAME'] = extension.metadata.name; + // } + + GLib.log_structured(domain, GLib.LogLevelFlags.LEVEL_MESSAGE, fields); } -function init() { - // Add some bindings to the global JS namespace - globalThis.global = Shell.Global.get(); - - globalThis.log = _loggingFunc; - - globalThis._ = Gettext.gettext; - globalThis.C_ = Gettext.pgettext; - globalThis.ngettext = Gettext.ngettext; - globalThis.N_ = s => s; - - GObject.gtypeNameBasedOnJSPath = true; - - // Miscellaneous monkeypatching - _patchContainerClass(St.BoxLayout); - - _patchLayoutClass(Clutter.GridLayout, { row_spacing: 'spacing-rows', - column_spacing: 'spacing-columns' }); - _patchLayoutClass(Clutter.BoxLayout, { spacing: 'spacing' }); - - let origSetEasingDuration = Clutter.Actor.prototype.set_easing_duration; - Clutter.Actor.prototype.set_easing_duration = function (msecs) { - origSetEasingDuration.call(this, adjustAnimationTime(msecs)); - }; - let origSetEasingDelay = Clutter.Actor.prototype.set_easing_delay; - Clutter.Actor.prototype.set_easing_delay = function (msecs) { - origSetEasingDelay.call(this, adjustAnimationTime(msecs)); - }; - - Clutter.Actor.prototype.ease = function (props) { - _easeActor(this, props); - }; - Clutter.Actor.prototype.ease_property = function (propName, target, params) { - _easeActorProperty(this, propName, target, params); - }; - St.Adjustment.prototype.ease = function (target, params) { - // we're not an actor of course, but we implement the same - // transition API as Clutter.Actor, so this works anyway - _easeActorProperty(this, 'value', target, params); - }; - - Clutter.Actor.prototype[Symbol.iterator] = function* () { - for (let c = this.get_first_child(); c; c = c.get_next_sibling()) - yield c; - }; - - Clutter.Actor.prototype.toString = function () { - return St.describe_actor(this); - }; - // Deprecation warning for former JS classes turned into an actor subclass - Object.defineProperty(Clutter.Actor.prototype, 'actor', { - get() { - let klass = this.constructor.name; - let { stack } = new Error(); - log(`Usage of object.actor is deprecated for ${klass}\n${stack}`); - return this; - }, - }); - - Gio._LocalFilePrototype.touch_async = function (callback) { - Shell.util_touch_file_async(this, callback); - }; - Gio._LocalFilePrototype.touch_finish = function (result) { - return Shell.util_touch_file_finish(this, result); - }; - - St.set_slow_down_factor = function (factor) { - let { stack } = new Error(); - log(`St.set_slow_down_factor() is deprecated, use St.Settings.slow_down_factor\n${stack}`); - St.Settings.get().slow_down_factor = factor; - }; +log("initializing..."); +// Add some bindings to the global JS namespace + +// TODO: This errors because the Shell global has an incorrect type for 'stage' +/** @type {Shell.Global & { stage: import('gi://Clutter').Stage; }} */ +globalThis.global = (Shell.Global.get()); + +globalThis.log = _loggingFunc; + +globalThis._ = Gettext.gettext; +globalThis.C_ = Gettext.pgettext; +globalThis.ngettext = Gettext.ngettext; +globalThis.N_ = (s) => s; + +globalThis.assertType = + /** + * @template T + * @param {unknown} obj + * @param {new(...args: any[]) => T} type + * @param {string} [message] + * @returns {asserts obj is T} + */ + (obj, type, message) => { + if (!(obj instanceof type)) { + throw new Error(`${obj} is not an instance of ${type.name}${message ? `: ${message}` : ""}`); + } + }; + +GObject.gtypeNameBasedOnJSPath = true; + +// Miscellaneous monkeypatching +_patchContainerClass(St.BoxLayout); + +_patchLayoutClass(Clutter.GridLayout, { row_spacing: "spacing-rows", column_spacing: "spacing-columns" }); +_patchLayoutClass(Clutter.BoxLayout, { spacing: "spacing" }); + +let origSetEasingDuration = Clutter.Actor.prototype.set_easing_duration; +Clutter.Actor.prototype.set_easing_duration = function (msecs) { + origSetEasingDuration.call(this, adjustAnimationTime(msecs)); +}; +let origSetEasingDelay = Clutter.Actor.prototype.set_easing_delay; +Clutter.Actor.prototype.set_easing_delay = function (msecs) { + origSetEasingDelay.call(this, adjustAnimationTime(msecs)); +}; + +Clutter.Actor.prototype.ease = function (props) { + _easeActor(this, props); +}; +Clutter.Actor.prototype.ease_property = function (propName, target, params) { + _easeActorProperty(this, propName, target, params); +}; +St.Adjustment.prototype.ease = function (target, params) { + // we're not an actor of course, but we implement the same + // transition API as Clutter.Actor, so this works anyway + _easeActorProperty(this, "value", target, params); +}; + +Clutter.Actor.prototype[Symbol.iterator] = function* () { + for (let c = this.get_first_child(); c; c = c.get_next_sibling()) yield c; +}; + +Clutter.Actor.prototype.toString = function () { + return St.describe_actor(this); +}; +// Deprecation warning for former JS classes turned into an actor subclass +Object.defineProperty(Clutter.Actor.prototype, "actor", { + get() { + let klass = this.constructor.name; + let { stack } = new Error(); + log(`Usage of object.actor is deprecated for ${klass}\n${stack}`); + return this; + }, +}); + +Gio._LocalFilePrototype.touch_async = function (callback) { + // TODO + if (!callback) { + return Shell.util_touch_file_async(this); + } + + Shell.util_touch_file_async(this, callback); +}; + +Gio._LocalFilePrototype.touch_finish = + /** + * @this {Gio.File} + * @param {Gio.AsyncResult} result + */ + function (result) { + return Shell.util_touch_file_finish(this, result); + }; + +St.set_slow_down_factor = function (factor) { + let { stack } = new Error(); + log(`St.set_slow_down_factor() is deprecated, use St.Settings.slow_down_factor\n${stack}`); + St.Settings.get().slow_down_factor = factor; +}; - let origToString = Object.prototype.toString; - Object.prototype.toString = function () { - let base = origToString.call(this); - try { - if ('actor' in this && this.actor instanceof Clutter.Actor) - return base.replace(/\]$/, ` delegate for ${this.actor.toString().substring(1)}`); - else - return base; - } catch (e) { - return base; - } - }; +/** + * @param {unknown} obj + * @returns {obj is {actor: unknown}} + */ +function hasActor(obj) { + let actorObj = /** @type {{actor?: unknown}} */ (obj); - // Override to clear our own timezone cache as well - const origClearDateCaches = System.clearDateCaches; - System.clearDateCaches = function () { - _localTimeZone = null; - origClearDateCaches(); - }; + return "actor" in actorObj; +} - // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=508783 - Date.prototype.toLocaleFormat = function (format) { - if (_localTimeZone === null) - _localTimeZone = GLib.TimeZone.new_local(); - - let dt = GLib.DateTime.new(_localTimeZone, - this.getFullYear(), - this.getMonth() + 1, - this.getDate(), - this.getHours(), - this.getMinutes(), - this.getSeconds()); - return dt?.format(format) ?? ''; - }; +let origToString = Object.prototype.toString; +Object.prototype.toString = function () { + let base = origToString.call(this); + try { + if (hasActor(this) && this.actor instanceof Clutter.Actor) + return base.replace(/\]$/, ` delegate for ${this.actor.toString().substring(1)}`); + else return base; + } catch (e) { + return base; + } +}; + +// Override to clear our own timezone cache as well +const origClearDateCaches = System.clearDateCaches; +System.clearDateCaches = function () { + _localTimeZone = null; + origClearDateCaches(); +}; + +// Work around https://bugzilla.mozilla.org/show_bug.cgi?id=508783 +Date.prototype.toLocaleFormat = function (format) { + if (_localTimeZone === null) _localTimeZone = GLib.TimeZone.new_local(); + + let dt = GLib.DateTime.new( + _localTimeZone, + this.getFullYear(), + this.getMonth() + 1, + this.getDate(), + this.getHours(), + this.getMinutes(), + this.getSeconds() + ); + return dt?.format(format) ?? ""; +}; + +let slowdownEnv = GLib.getenv("GNOME_SHELL_SLOWDOWN_FACTOR"); +if (slowdownEnv) { + let factor = parseFloat(slowdownEnv); + if (!isNaN(factor) && factor > 0.0) St.Settings.get().slow_down_factor = factor; +} - let slowdownEnv = GLib.getenv('GNOME_SHELL_SLOWDOWN_FACTOR'); - if (slowdownEnv) { - let factor = parseFloat(slowdownEnv); - if (!isNaN(factor) && factor > 0.0) - St.Settings.get().slow_down_factor = factor; - } +// OK, now things are initialized enough that we can import shell JS +const Format = imports.format; - // OK, now things are initialized enough that we can import shell JS - const Format = imports.format; +String.prototype.format = Format.format; - String.prototype.format = Format.format; +Math.clamp = function (x, lower, upper) { + return Math.min(Math.max(x, lower), upper); +}; - Math.clamp = function (x, lower, upper) { - return Math.min(Math.max(x, lower), upper); - }; -} +log("done initializing..."); // adjustAnimationTime: // @msecs: time in milliseconds // // Adjust @msecs to account for St's enable-animations // and slow-down-factor settings -function adjustAnimationTime(msecs) { - let settings = St.Settings.get(); +export function adjustAnimationTime(msecs) { + let settings = St.Settings.get(); - if (!settings.enable_animations) - return 1; - return settings.slow_down_factor * msecs; + if (!settings.enable_animations) return 1; + return settings.slow_down_factor * msecs; } - diff --git a/js/ui/extensionDownloader.js b/js/ui/extensionDownloader.js index 8bf4646a6..8a6e51633 100644 --- a/js/ui/extensionDownloader.js +++ b/js/ui/extensionDownloader.js @@ -1,14 +1,18 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported init, installExtension, uninstallExtension, checkForUpdates */ -const { Clutter, Gio, GLib, GObject, Soup } = imports.gi; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Soup from 'gi://Soup'; const Config = imports.misc.config; -const Dialog = imports.ui.dialog; -const ExtensionUtils = imports.misc.extensionUtils; -const FileUtils = imports.misc.fileUtils; -const Main = imports.ui.main; -const ModalDialog = imports.ui.modalDialog; +import * as Dialog from './dialog.js'; +import * as ExtensionUtils from '../misc/extensionUtils.js'; +import * as FileUtils from '../misc/fileUtilsModule.js'; +import Main from './main.js'; +import * as ModalDialog from './modalDialog.js'; Gio._promisify(Soup.Session.prototype, 'send_and_read_async', 'send_and_read_finish'); @@ -19,10 +23,10 @@ Gio._promisify(Gio.IOStream.prototype, Gio._promisify(Gio.Subprocess.prototype, 'wait_check_async', 'wait_check_finish'); -var REPOSITORY_URL_DOWNLOAD = 'https://extensions.gnome.org/download-extension/%s.shell-extension.zip'; -var REPOSITORY_URL_INFO = 'https://extensions.gnome.org/extension-info/'; -var REPOSITORY_URL_UPDATE = 'https://extensions.gnome.org/update-info/'; - +export const REPOSITORY_URL_DOWNLOAD = 'https://extensions.gnome.org/download-extension/%s.shell-extension.zip'; +export const REPOSITORY_URL_INFO = 'https://extensions.gnome.org/extension-info/'; +export const REPOSITORY_URL_UPDATE = 'https://extensions.gnome.org/update-info/'; + let _httpSession; /** @@ -60,7 +64,7 @@ async function installExtension(uuid, invocation) { dialog.open(global.get_current_time()); } -function uninstallExtension(uuid) { +export function uninstallExtension(uuid) { let extension = Main.extensionManager.lookup(uuid); if (!extension) return false; @@ -215,7 +219,7 @@ async function checkForUpdates() { } } -var InstallExtensionDialog = GObject.registerClass( +export const InstallExtensionDialog = GObject.registerClass( class InstallExtensionDialog extends ModalDialog.ModalDialog { _init(uuid, info, invocation) { super._init({ styleClass: 'extension-dialog' }); diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js index 3c1508d77..21baa4ab5 100644 --- a/js/ui/extensionSystem.js +++ b/js/ui/extensionSystem.js @@ -1,14 +1,18 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported init connect disconnect ExtensionManager */ -const { GLib, Gio, GObject, Shell, St } = imports.gi; -const Signals = imports.misc.signals; - -const ExtensionDownloader = imports.ui.extensionDownloader; -const ExtensionUtils = imports.misc.extensionUtils; -const FileUtils = imports.misc.fileUtils; -const Main = imports.ui.main; -const MessageTray = imports.ui.messageTray; +import GLib from 'gi://GLib'; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; +import * as Signals from '../misc/signals.js'; + +import * as ExtensionDownloader from './extensionDownloader.js'; +import * as ExtensionUtils from '../misc/extensionUtils.js'; +import * as FileUtils from '../misc/fileUtilsModule.js'; +import Main from './main.js'; +import * as MessageTray from './messageTray.js'; const { ExtensionState, ExtensionType } = ExtensionUtils; @@ -19,7 +23,7 @@ const EXTENSION_DISABLE_VERSION_CHECK_KEY = 'disable-extension-version-validatio const UPDATE_CHECK_TIMEOUT = 24 * 60 * 60; // 1 day in seconds -var ExtensionManager = class extends Signals.EventEmitter { +export class ExtensionManager extends Signals.EventEmitter { constructor() { super(); @@ -418,35 +422,36 @@ var ExtensionManager = class extends Signals.EventEmitter { let extensionModule; let extensionState = null; - ExtensionUtils.installImporter(extension); + // TODO: Fix extension initialization. + // ExtensionUtils.installImporter(extension); try { - extensionModule = extension.imports.extension; + // extensionModule = extension.imports.extension; } catch (e) { this.logExtensionError(uuid, e); return false; } - if (extensionModule.init) { - try { - extensionState = extensionModule.init(extension); - } catch (e) { - this.logExtensionError(uuid, e); - return false; - } - } + // if (extensionModule.init) { + // try { + // extensionState = extensionModule.init(extension); + // } catch (e) { + // this.logExtensionError(uuid, e); + // return false; + // } + // } - if (!extensionState) - extensionState = extensionModule; - extension.stateObj = extensionState; + // if (!extensionState) + // extensionState = extensionModule; + // extension.stateObj = extensionState; - extension.state = ExtensionState.DISABLED; - this.emit('extension-loaded', uuid); - return true; + // extension.state = ExtensionState.DISABLED; + // this.emit('extension-loaded', uuid); + return false; } _getModeExtensions() { - if (Array.isArray(Main.sessionMode.enabledExtensions)) - return Main.sessionMode.enabledExtensions; + // if (Array.isArray(Main.sessionMode.enabledExtensions)) + // return Main.sessionMode.enabledExtensions; return []; } @@ -499,7 +504,7 @@ var ExtensionManager = class extends Signals.EventEmitter { .filter(uuid => !newEnabledExtensions.includes(uuid)) .reverse().forEach(uuid => this._callExtensionDisable(uuid)); - this._enabledExtensions = newEnabledExtensions; + this._enabledExtensions = []; //newEnabledExtensions; } _onSettingsWritableChanged() { @@ -625,10 +630,10 @@ var ExtensionManager = class extends Signals.EventEmitter { // from allowExtensions in the future if (Main.sessionMode.allowExtensions) { // Take care of added or removed sessionMode extensions - this._onEnabledExtensionsChanged(); - this._enableAllExtensions(); + // this._onEnabledExtensionsChanged(); + // this._enableAllExtensions(); } else { - this._disableAllExtensions(); + // this._disableAllExtensions(); } } }; @@ -639,7 +644,7 @@ class ExtensionUpdateSource extends MessageTray.Source { let appSys = Shell.AppSystem.get_default(); this._app = appSys.lookup_app('org.gnome.Extensions.desktop'); - super._init(this._app.get_name()); + super._init({ title: this._app.get_name() }); } getIcon() { diff --git a/js/ui/focusCaretTracker.js b/js/ui/focusCaretTracker.js index 5cfe7a849..456fa7dd0 100644 --- a/js/ui/focusCaretTracker.js +++ b/js/ui/focusCaretTracker.js @@ -22,13 +22,13 @@ */ /* exported FocusCaretTracker */ -const Atspi = imports.gi.Atspi; -const Signals = imports.misc.signals; +import Atspi from 'gi://Atspi'; +import * as Signals from '../misc/signals.js'; const CARETMOVED = 'object:text-caret-moved'; const STATECHANGED = 'object:state-changed'; -var FocusCaretTracker = class FocusCaretTracker extends Signals.EventEmitter { +export class FocusCaretTracker extends Signals.EventEmitter { constructor() { super(); diff --git a/js/ui/grabHelper.js b/js/ui/grabHelper.js index 72c1fec44..84fdc8503 100644 --- a/js/ui/grabHelper.js +++ b/js/ui/grabHelper.js @@ -1,9 +1,11 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported GrabHelper */ -const { Clutter, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import St from 'gi://St'; -const Main = imports.ui.main; + +import Main from './main.js'; let _capturedEventId = 0; let _grabHelperStack = []; @@ -30,6 +32,8 @@ function _popGrabHelper(grabHelper) { } } +/** @typedef {{ actor: St.Widget, focus?: St.Widget, savedFocus?: Clutter.Actor, onUngrab: (isUser: boolean) => void }} Grab */ + // GrabHelper: // @owner: the actor that owns the GrabHelper // @params: optional parameters to pass to Main.pushModal() @@ -41,7 +45,7 @@ function _popGrabHelper(grabHelper) { // your code just needs to deal with it; you shouldn't adjust behavior directly // after you call ungrab(), but instead pass an 'onUngrab' callback when you // call grab(). -var GrabHelper = class GrabHelper { +export class GrabHelper { constructor(owner, params) { if (!(owner instanceof Clutter.Actor)) throw new Error('GrabHelper owner must be a Clutter.Actor'); diff --git a/js/ui/ibusCandidatePopup.js b/js/ui/ibusCandidatePopup.js index 5a9fe7792..129e2c5fd 100644 --- a/js/ui/ibusCandidatePopup.js +++ b/js/ui/ibusCandidatePopup.js @@ -1,17 +1,21 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported CandidatePopup */ -const { Clutter, GObject, IBus, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import GObject from 'gi://GObject'; +import IBus from 'gi://IBus'; +import St from 'gi://St'; -const BoxPointer = imports.ui.boxpointer; -const Main = imports.ui.main; -var MAX_CANDIDATES_PER_PAGE = 16; +import * as BoxPointer from './boxpointer.js'; +import Main from './main.js'; -var DEFAULT_INDEX_LABELS = ['1', '2', '3', '4', '5', '6', '7', '8', +export let MAX_CANDIDATES_PER_PAGE = 16; + +export let DEFAULT_INDEX_LABELS = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f']; -var CandidateArea = GObject.registerClass({ +export const CandidateArea = GObject.registerClass({ Signals: { 'candidate-clicked': { param_types: [GObject.TYPE_UINT, GObject.TYPE_UINT, @@ -98,12 +102,20 @@ var CandidateArea = GObject.registerClass({ this.vertical = false; this.remove_style_class_name('vertical'); this.add_style_class_name('horizontal'); + + assertType(this._previousButton.child, St.Icon); + assertType(this._nextButton.child, St.Icon); + this._previousButton.child.icon_name = 'go-previous-symbolic'; this._nextButton.child.icon_name = 'go-next-symbolic'; } else { // VERTICAL || SYSTEM this.vertical = true; this.add_style_class_name('vertical'); this.remove_style_class_name('horizontal'); + + assertType(this._previousButton.child, St.Icon); + assertType(this._nextButton.child, St.Icon); + this._previousButton.child.icon_name = 'go-up-symbolic'; this._nextButton.child.icon_name = 'go-down-symbolic'; } @@ -139,7 +151,7 @@ var CandidateArea = GObject.registerClass({ } }); -var CandidatePopup = GObject.registerClass( +export const CandidatePopup = GObject.registerClass( class IbusCandidatePopup extends BoxPointer.BoxPointer { _init() { super._init(St.Side.TOP); @@ -199,6 +211,7 @@ class IbusCandidatePopup extends BoxPointer.BoxPointer { panelService.connect('set-cursor-location-relative', (ps, x, y, w, h) => { if (!global.display.focus_window) return; + let window = global.display.focus_window.get_compositor_private(); this._setDummyCursorGeometry(window.x + x, window.y + y, w, h); }); diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js index 4f011fe67..1c84163cd 100644 --- a/js/ui/iconGrid.js +++ b/js/ui/iconGrid.js @@ -1,37 +1,49 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported BaseIcon, IconGrid, IconGridLayout */ -const { Clutter, GLib, GObject, Meta, Shell, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; -const Main = imports.ui.main; +import Main from './main.js'; -var ICON_SIZE = 96; +export let ICON_SIZE = 96; -var ANIMATION_TIME_IN = 350; -var ANIMATION_TIME_OUT = 1 / 2 * ANIMATION_TIME_IN; -var ANIMATION_MAX_DELAY_FOR_ITEM = 2 / 3 * ANIMATION_TIME_IN; -var ANIMATION_MAX_DELAY_OUT_FOR_ITEM = 2 / 3 * ANIMATION_TIME_OUT; -var ANIMATION_FADE_IN_TIME_FOR_ITEM = 1 / 4 * ANIMATION_TIME_IN; +export let ANIMATION_TIME_IN = 350; +export let ANIMATION_TIME_OUT = 1 / 2 * ANIMATION_TIME_IN; +export let ANIMATION_MAX_DELAY_FOR_ITEM = 2 / 3 * ANIMATION_TIME_IN; +export let ANIMATION_MAX_DELAY_OUT_FOR_ITEM = 2 / 3 * ANIMATION_TIME_OUT; +export let ANIMATION_FADE_IN_TIME_FOR_ITEM = 1 / 4 * ANIMATION_TIME_IN; -var PAGE_SWITCH_TIME = 300; +export let PAGE_SWITCH_TIME = 300; -var AnimationDirection = { +/** @typedef {Clutter.Actor & { icon?: typeof BaseIcon["prototype"] }} BaseItem */ + +/** @enum {number} */ +export const AnimationDirection = { IN: 0, OUT: 1, }; -var IconSize = { +/** @enum {number} */ +export const IconSize = { LARGE: 96, MEDIUM: 64, SMALL: 32, TINY: 16, }; -var APPICON_ANIMATION_OUT_SCALE = 3; -var APPICON_ANIMATION_OUT_TIME = 250; +export let APPICON_ANIMATION_OUT_SCALE = 3; +export let APPICON_ANIMATION_OUT_TIME = 250; const ICON_POSITION_DELAY = 10; +/** @typedef {{rows: number, columns: number}} GridMode */ + +/** @type {GridMode[]} */ const defaultGridModes = [ { rows: 8, @@ -51,10 +63,11 @@ const defaultGridModes = [ }, ]; -var LEFT_DIVIDER_LEEWAY = 20; -var RIGHT_DIVIDER_LEEWAY = 20; +export let LEFT_DIVIDER_LEEWAY = 20; +export let RIGHT_DIVIDER_LEEWAY = 20; -var DragLocation = { +/** @enum {number} */ +export const DragLocation = { INVALID: 0, START_EDGE: 1, ON_ICON: 2, @@ -62,7 +75,7 @@ var DragLocation = { EMPTY_SPACE: 4, }; -var BaseIcon = GObject.registerClass( +export const BaseIcon = GObject.registerClass( class BaseIcon extends Shell.SquareBin { _init(label, params = {}) { const { @@ -114,6 +127,10 @@ class BaseIcon extends Shell.SquareBin { // This can be overridden by a subclass, or by the createIcon // parameter to _init() + /** + * @param {number} _size + * @returns {St.Widget} + */ createIcon(_size) { throw new GObject.NotImplementedError(`createIcon in ${this.constructor.name}`); } @@ -186,7 +203,7 @@ class BaseIcon extends Shell.SquareBin { } }); -function zoomOutActor(actor) { +export function zoomOutActor(actor) { let [x, y] = actor.get_transformed_position(); zoomOutActorAtPos(actor, x, y); } @@ -250,7 +267,7 @@ function swap(value, length) { return length - value - 1; } -var IconGridLayout = GObject.registerClass({ +export const IconGridLayout = GObject.registerClass({ Properties: { 'allow-incomplete-pages': GObject.ParamSpec.boolean('allow-incomplete-pages', 'Allow incomplete pages', 'Allow incomplete pages', @@ -319,6 +336,7 @@ var IconGridLayout = GObject.registerClass({ 'pages-changed': {}, }, }, class IconGridLayout extends Clutter.LayoutManager { + _init(params = {}) { this._orientation = params.orientation ?? Clutter.Orientation.VERTICAL; @@ -327,6 +345,29 @@ var IconGridLayout = GObject.registerClass({ if (!this.pagePadding) this.pagePadding = new Clutter.Margin(); + /** @type {number} */ + this.fixedIconSize; + /** @type {number} */ + this.rowSpacing; + /** @type {number} */ + this.columnSpacing; + /** @type {number} */ + this.maxRowSpacing; + /** @type {number} */ + this.maxColumnSpacing; + /** @type {number} */ + this.columnsPerPage; + /** @type {number} */ + this.rowsPerPage; + /** @type {number} */ + this.lastRowAlign; + /** @type {number} */ + this.allowIncompletePages; + /** @type {Clutter.ActorAlign} */ + this.pageHalign; + /** @type {Clutter.ActorAlign} */ + this.pageValign; + this._iconSize = this.fixedIconSize !== -1 ? this.fixedIconSize : IconSize.LARGE; @@ -729,6 +770,9 @@ var IconGridLayout = GObject.registerClass({ this._containerDestroyedId = this._container.connect('destroy', this._onDestroy.bind(this)); } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_width(_container, _forHeight) { let minWidth = -1; let natWidth = -1; @@ -748,6 +792,9 @@ var IconGridLayout = GObject.registerClass({ return [minWidth, natWidth]; } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_height(_container, _forWidth) { let minHeight = -1; let natHeight = -1; @@ -840,8 +887,8 @@ var IconGridLayout = GObject.registerClass({ /** * addItem: * @param {Clutter.Actor} item: item to append to the grid - * @param {int} page: page number - * @param {int} index: position in the page + * @param {number} page: page number + * @param {number} index: position in the page * * Adds @item to the grid. @item must not be part of the grid. * @@ -880,8 +927,8 @@ var IconGridLayout = GObject.registerClass({ /** * moveItem: * @param {Clutter.Actor} item: item to move - * @param {int} newPage: new page of the item - * @param {int} newPosition: new page of the item + * @param {number} newPage: new page of the item + * @param {number} newPosition: new page of the item * * Moves @item to the grid. @item must be part of the grid. */ @@ -916,7 +963,7 @@ var IconGridLayout = GObject.registerClass({ /** * getItemsAtPage: - * @param {int} pageIndex: page index + * @param {number} pageIndex: page index * * Retrieves the children at page @pageIndex. Children may be invisible. * @@ -931,12 +978,12 @@ var IconGridLayout = GObject.registerClass({ /** * getItemPosition: - * @param {BaseIcon} item: the item + * @param {BaseIcon["prototype"]} item: the item * * Retrieves the position of @item is its page, or -1 if @item is not * part of the grid. * - * @returns {[int, int]} the page and position of @item + * @returns {[number, number]} the page and position of @item */ getItemPosition(item) { if (!this._items.has(item)) @@ -950,8 +997,8 @@ var IconGridLayout = GObject.registerClass({ /** * getItemAt: - * @param {int} page: the page - * @param {int} position: the position in page + * @param {number} page: the page + * @param {number} position: the position in page * * Retrieves the item at @page and @position. * @@ -971,11 +1018,11 @@ var IconGridLayout = GObject.registerClass({ /** * getItemPage: - * @param {BaseIcon} item: the item + * @param {BaseIcon["prototype"]} item: the item * * Retrieves the page @item is in, or -1 if @item is not part of the grid. * - * @returns {int} the page where @item is in + * @returns {number} the page where @item is in */ getItemPage(item) { if (!this._items.has(item)) @@ -1023,13 +1070,13 @@ var IconGridLayout = GObject.registerClass({ /** * getDropTarget: - * @param {int} x: position of the horizontal axis - * @param {int} y: position of the vertical axis + * @param {number} x: position of the horizontal axis + * @param {number} y: position of the vertical axis * * Retrieves the item located at (@x, @y), as well as the drag location. * Both @x and @y are relative to the grid. * - * @returns {[Clutter.Actor, DragLocation]} the item and drag location + * @returns {[BaseIcon["prototype"] | null, DragLocation]} the item and drag location * under (@x, @y) */ getDropTarget(x, y) { @@ -1154,12 +1201,17 @@ var IconGridLayout = GObject.registerClass({ } }); -var IconGrid = GObject.registerClass({ +export const IconGrid = GObject.registerClass({ Signals: { 'pages-changed': {}, 'animation-done': {}, }, -}, class IconGrid extends St.Viewport { +}, +/** @extends {St.Viewport<typeof IconGridLayout["prototype"]>} */ +class IconGrid extends St.Viewport { + /** + * @param {*} layoutParams + */ _init(layoutParams = {}) { const iconGridLayoutParams = { allow_incomplete_pages: false, @@ -1238,6 +1290,9 @@ var IconGrid = GObject.registerClass({ this.goToPage(itemPage); } + /** + * @param {number} modeIndex + */ _setGridMode(modeIndex) { if (this._currentMode === modeIndex) return; @@ -1259,17 +1314,16 @@ var IconGrid = GObject.registerClass({ const sizeRatio = width / height; let closestRatio = Infinity; - let bestMode = -1; - - for (let modeIndex in this._gridModes) { - const mode = this._gridModes[modeIndex]; + let bestMode = this._gridModes.findIndex((mode) => { const modeRatio = mode.columns / mode.rows; if (Math.abs(sizeRatio - modeRatio) < Math.abs(sizeRatio - closestRatio)) { closestRatio = modeRatio; - bestMode = modeIndex; + return true; } - } + + return false; + }) ?? -1; this._setGridMode(bestMode); } @@ -1312,9 +1366,9 @@ var IconGrid = GObject.registerClass({ /** * addItem: - * @param {Clutter.Actor} item: item to append to the grid - * @param {int} page: page number - * @param {int} index: position in the page + * @param {BaseItem} item: item to append to the grid + * @param {number} page: page number + * @param {number} index: position in the page * * Adds @item to the grid. @item must not be part of the grid. * @@ -1344,8 +1398,8 @@ var IconGrid = GObject.registerClass({ /** * moveItem: * @param {Clutter.Actor} item: item to move - * @param {int} newPage: new page of the item - * @param {int} newPosition: new page of the item + * @param {number} newPage: new page of the item + * @param {number} newPosition: new page of the item * * Moves @item to the grid. @item must be part of the grid. */ @@ -1369,7 +1423,7 @@ var IconGrid = GObject.registerClass({ /** * goToPage: - * @param {int} pageIndex: page index + * @param {number} pageIndex: page index * @param {boolean} animate: animate the page transition * * Moves the current page to @pageIndex. @pageIndex must be a valid page @@ -1405,11 +1459,11 @@ var IconGrid = GObject.registerClass({ /** * getItemPage: - * @param {BaseIcon} item: the item + * @param {BaseIcon["prototype"]} item: the item * * Retrieves the page @item is in, or -1 if @item is not part of the grid. * - * @returns {int} the page where @item is in + * @returns {number} the page where @item is in */ getItemPage(item) { return this.layout_manager.getItemPage(item); @@ -1417,12 +1471,12 @@ var IconGrid = GObject.registerClass({ /** * getItemPosition: - * @param {BaseIcon} item: the item + * @param {BaseIcon["prototype"]} item: the item * * Retrieves the position of @item is its page, or -1 if @item is not * part of the grid. * - * @returns {[int, int]} the page and position of @item + * @returns {[number, number]} the page and position of @item */ getItemPosition(item) { if (!this.contains(item)) @@ -1434,8 +1488,8 @@ var IconGrid = GObject.registerClass({ /** * getItemAt: - * @param {int} page: the page - * @param {int} position: the position in page + * @param {number} page: the page + * @param {number} position: the position in page * * Retrieves the item at @page and @position. * @@ -1448,7 +1502,7 @@ var IconGrid = GObject.registerClass({ /** * getItemsAtPage: - * @param {int} page: the page index + * @param {number} page: the page index * * Retrieves the children at page @page, including invisible children. * @@ -1553,11 +1607,9 @@ var IconGrid = GObject.registerClass({ duration: ANIMATION_TIME_IN, mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD, delay, + ...(isLastItem ? { onComplete: this._animationDone.bind(this) } : {}) }; - if (isLastItem) - movementParams.onComplete = this._animationDone.bind(this); - fadeParams = { opacity: 255, duration: ANIMATION_FADE_IN_TIME_FOR_ITEM, @@ -1579,11 +1631,9 @@ var IconGrid = GObject.registerClass({ duration: ANIMATION_TIME_OUT, mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD, delay, + ...(isLastItem ? { onComplete: this._animationDone.bind(this) } : {}) }; - if (isLastItem) - movementParams.onComplete = this._animationDone.bind(this); - fadeParams = { opacity: 0, duration: ANIMATION_FADE_IN_TIME_FOR_ITEM, @@ -1597,6 +1647,9 @@ var IconGrid = GObject.registerClass({ }); } + /** + * @param {GridMode[]} modes + */ setGridModes(modes) { this._gridModes = modes ? modes : defaultGridModes; this.queue_relayout(); @@ -1604,7 +1657,7 @@ var IconGrid = GObject.registerClass({ getDropTarget(x, y) { const layoutManager = this.layout_manager; - return layoutManager.getDropTarget(x, y, this._currentPage); + return layoutManager.getDropTarget(x, y); } get itemsPerPage() { diff --git a/js/ui/inhibitShortcutsDialog.js b/js/ui/inhibitShortcutsDialog.js index b20e67285..23734da14 100644 --- a/js/ui/inhibitShortcutsDialog.js +++ b/js/ui/inhibitShortcutsDialog.js @@ -1,9 +1,17 @@ /* exported InhibitShortcutsDialog */ -const { Clutter, Gio, GLib, GObject, Gtk, Meta, Pango, Shell, St } = imports.gi; - -const Dialog = imports.ui.dialog; -const ModalDialog = imports.ui.modalDialog; -const PermissionStore = imports.misc.permissionStore; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Gtk from 'gi://Gtk'; +import Meta from 'gi://Meta'; +import Pango from 'gi://Pango'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; + +import * as Dialog from './dialog.js'; +import * as ModalDialog from './modalDialog.js'; +import * as PermissionStore from '../misc/permissionStore.js'; const WAYLAND_KEYBINDINGS_SCHEMA = 'org.gnome.mutter.wayland.keybindings'; @@ -13,14 +21,17 @@ const APP_PERMISSIONS_ID = 'shortcuts-inhibitor'; const GRANTED = 'GRANTED'; const DENIED = 'DENIED'; -var DialogResponse = Meta.InhibitShortcutsDialogResponse; +export const DialogResponse = Meta.InhibitShortcutsDialogResponse; -var InhibitShortcutsDialog = GObject.registerClass({ +export const InhibitShortcutsDialog = GObject.registerClass({ Implements: [Meta.InhibitShortcutsDialog], Properties: { 'window': GObject.ParamSpec.override('window', Meta.InhibitShortcutsDialog), }, }, class InhibitShortcutsDialog extends GObject.Object { + /** + * @param {*} window + */ _init(window) { super._init(); this._window = window; @@ -59,7 +70,7 @@ var InhibitShortcutsDialog = GObject.registerClass({ let permissions = {}; permissions[this._app.get_id()] = [grant]; - let data = GLib.Variant.new('av', {}); + let data = GLib.Variant.new('av', []); this._permStore.SetRemote(APP_PERMISSIONS_TABLE, true, diff --git a/js/ui/init.js b/js/ui/init.js index a0fe63343..6dfe9ff30 100644 --- a/js/ui/init.js +++ b/js/ui/init.js @@ -2,5 +2,15 @@ import { setConsoleLogDomain } from 'console'; setConsoleLogDomain('GNOME Shell'); -imports.ui.environment.init(); -imports.ui.main.start(); +import "./environment.js"; + +import("./main.js") + .then(({ main }) => { + main.start(); + }) + .catch((error) => { + logError(error); + }) + .finally(() => { + log("Main imported."); + });
\ No newline at end of file diff --git a/js/ui/kbdA11yDialog.js b/js/ui/kbdA11yDialog.js index a45e02443..e32480e9d 100644 --- a/js/ui/kbdA11yDialog.js +++ b/js/ui/kbdA11yDialog.js @@ -1,14 +1,18 @@ /* exported KbdA11yDialog */ -const { Clutter, Gio, GObject } = imports.gi; +import Meta from 'gi://Meta'; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; -const Dialog = imports.ui.dialog; -const ModalDialog = imports.ui.modalDialog; + +import * as Dialog from './dialog.js'; +import * as ModalDialog from './modalDialog.js'; const KEYBOARD_A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard'; const KEY_STICKY_KEYS_ENABLED = 'stickykeys-enable'; const KEY_SLOW_KEYS_ENABLED = 'slowkeys-enable'; -var KbdA11yDialog = GObject.registerClass( +export const KbdA11yDialog = GObject.registerClass( class KbdA11yDialog extends GObject.Object { _init() { super._init(); @@ -25,17 +29,23 @@ class KbdA11yDialog extends GObject.Object { let title, description; let key, enabled; - if (whatChanged & Clutter.KeyboardA11yFlags.SLOW_KEYS_ENABLED) { + const META_A11Y_SLOW_KEYS_ENABLED = 1 << 3; + const META_A11Y_STICKY_KEYS_ENABLED = 1 << 10; + // FIXME: + // This change https://github.com/GNOME/mutter/commit/c3acaeb25127a7520ecc0d3edbb3d0cc53b5634e#diff-8da73b762bc44dbbfb6a00938e37693e5e3fc3266673275502117ea932cf7675R55 + // made Clutter.KeyboardA11yFlags turn into Meta.KeyboardA11yFlags but + // also removed it from introspection. + if (whatChanged & /* Meta.KeyboardA11yFlags */ META_A11Y_SLOW_KEYS_ENABLED) { key = KEY_SLOW_KEYS_ENABLED; - enabled = (newFlags & Clutter.KeyboardA11yFlags.SLOW_KEYS_ENABLED) > 0; + enabled = (newFlags & META_A11Y_SLOW_KEYS_ENABLED) > 0; title = enabled ? _("Slow Keys Turned On") : _("Slow Keys Turned Off"); description = _('You just held down the Shift key for 8 seconds. This is the shortcut ' + 'for the Slow Keys feature, which affects the way your keyboard works.'); - } else if (whatChanged & Clutter.KeyboardA11yFlags.STICKY_KEYS_ENABLED) { + } else if (whatChanged & META_A11Y_STICKY_KEYS_ENABLED) { key = KEY_STICKY_KEYS_ENABLED; - enabled = (newFlags & Clutter.KeyboardA11yFlags.STICKY_KEYS_ENABLED) > 0; + enabled = (newFlags & META_A11Y_STICKY_KEYS_ENABLED) > 0; title = enabled ? _("Sticky Keys Turned On") : _("Sticky Keys Turned Off"); diff --git a/js/ui/keyboard.js b/js/ui/keyboard.js index 3e188a99b..e4d43ecfa 100644 --- a/js/ui/keyboard.js +++ b/js/ui/keyboard.js @@ -1,16 +1,22 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported KeyboardManager */ - -const { Clutter, Gio, GLib, GObject, Graphene, Meta, Shell, St } = imports.gi; -const Signals = imports.misc.signals; - -const EdgeDragAction = imports.ui.edgeDragAction; -const InputSourceManager = imports.ui.status.keyboard; -const IBusManager = imports.misc.ibusManager; -const BoxPointer = imports.ui.boxpointer; -const Main = imports.ui.main; -const PageIndicators = imports.ui.pageIndicators; -const PopupMenu = imports.ui.popupMenu; +import Clutter from 'gi://Clutter'; +import St from 'gi://St'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Shell from 'gi://Shell'; +import Meta from 'gi://Meta'; +import Graphene from 'gi://Graphene'; +import * as Signals from '../misc/signals.js'; + +import * as EdgeDragAction from './edgeDragAction.js'; +import * as InputSourceManager from './status/keyboard.js'; +import * as IBusManager from '../misc/ibusManager.js'; +import * as BoxPointer from './boxpointer.js'; +import Main from './main.js'; +import * as PageIndicators from './pageIndicators.js'; +import * as PopupMenu from './popupMenu.js'; var KEYBOARD_ANIMATION_TIME = 150; var KEYBOARD_REST_TIME = KEYBOARD_ANIMATION_TIME * 2; @@ -50,7 +56,7 @@ const defaultKeysPost = [ [{ action: 'emoji', icon: 'face-smile-symbolic' }, { action: 'languageMenu', extraClassName: 'layout-key', icon: 'keyboard-layout-filled-symbolic' }, { action: 'hide', extraClassName: 'hide-key', icon: 'go-down-symbolic' }]], ]; -var AspectContainer = GObject.registerClass( +export const AspectContainer = GObject.registerClass( class AspectContainer extends St.Widget { _init(params) { super._init(params); @@ -62,6 +68,9 @@ class AspectContainer extends St.Widget { this.queue_relayout(); } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_width(forHeight) { let [min, nat] = super.vfunc_get_preferred_width(forHeight); @@ -71,6 +80,9 @@ class AspectContainer extends St.Widget { return [min, nat]; } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_height(forWidth) { let [min, nat] = super.vfunc_get_preferred_height(forWidth); @@ -102,7 +114,7 @@ class AspectContainer extends St.Widget { } }); -var KeyContainer = GObject.registerClass( +export const KeyContainer = GObject.registerClass( class KeyContainer extends St.Widget { _init() { let gridLayout = new Clutter.GridLayout({ orientation: Clutter.Orientation.HORIZONTAL, @@ -120,6 +132,8 @@ class KeyContainer extends St.Widget { this._currentRow = null; this._rows = []; + + this.shiftKeys = []; } appendRow() { @@ -183,7 +197,7 @@ class KeyContainer extends St.Widget { } }); -var Suggestions = GObject.registerClass( +export const Suggestions = GObject.registerClass( class Suggestions extends St.BoxLayout { _init() { super._init({ @@ -205,7 +219,7 @@ class Suggestions extends St.BoxLayout { } }); -var LanguageSelectionPopup = class extends PopupMenu.PopupMenu { +export class LanguageSelectionPopup extends PopupMenu.PopupMenu { constructor(actor) { super(actor, 0.5, St.Side.BOTTOM); @@ -268,7 +282,7 @@ var LanguageSelectionPopup = class extends PopupMenu.PopupMenu { } }; -var Key = GObject.registerClass({ +export const Key = GObject.registerClass({ Signals: { 'activated': {}, 'long-press': {}, @@ -288,6 +302,7 @@ var Key = GObject.registerClass({ this.add_child(this.keyButton); this.connect('destroy', this._onDestroy.bind(this)); + this._keyvalPress = false; this._extendedKeys = extendedKeys; this._extendedKeyboard = null; this._pressTimeoutId = 0; @@ -516,7 +531,7 @@ var Key = GObject.registerClass({ } }); -var KeyboardModel = class { +export class KeyboardModel { constructor(groupName) { let names = [groupName]; if (groupName.includes('+')) @@ -549,7 +564,7 @@ var KeyboardModel = class { } }; -var FocusTracker = class extends Signals.EventEmitter { +export class FocusTracker extends Signals.EventEmitter { constructor() { super(); @@ -664,7 +679,7 @@ var FocusTracker = class extends Signals.EventEmitter { } }; -var EmojiPager = GObject.registerClass({ +export const EmojiPager = GObject.registerClass({ Properties: { 'delta': GObject.ParamSpec.int( 'delta', 'delta', 'delta', @@ -952,7 +967,7 @@ var EmojiPager = GObject.registerClass({ } }); -var EmojiSelection = GObject.registerClass({ +export const EmojiSelection = GObject.registerClass({ Signals: { 'emoji-selected': { param_types: [GObject.TYPE_STRING] }, 'close-request': {}, @@ -1110,7 +1125,7 @@ var EmojiSelection = GObject.registerClass({ } }); -var Keypad = GObject.registerClass({ +export const Keypad = GObject.registerClass({ Signals: { 'keyval': { param_types: [GObject.TYPE_UINT] }, }, @@ -1162,7 +1177,7 @@ var Keypad = GObject.registerClass({ } }); -var KeyboardManager = class KeyBoardManager { +export class KeyboardManager { constructor() { this._keyboard = null; this._a11yApplicationsSettings = new Gio.Settings({ schema_id: A11Y_APPLICATIONS_SCHEMA }); @@ -1269,7 +1284,7 @@ var KeyboardManager = class KeyBoardManager { } }; -var Keyboard = GObject.registerClass({ +export const Keyboard = GObject.registerClass({ Signals: { 'visibility-changed': {}, }, @@ -1301,7 +1316,7 @@ var Keyboard = GObject.registerClass({ if (!Meta.is_wayland_compositor()) { this._connectSignal(this._focusTracker, 'focus-changed', (_tracker, focused) => { if (focused) - this.open(Main.layoutManager.focusIndex); + this.open(!!Main.layoutManager.focusIndex); else this.close(); }); @@ -1441,7 +1456,7 @@ var Keyboard = GObject.registerClass({ // Showing an extended key popup and clicking a key from the extended keys // will grab focus, but ignore that let extendedKeysWereFocused = this._focusInExtendedKeys; - this._focusInExtendedKeys = focus && (focus._extendedKeys || focus.extendedKey); + this._focusInExtendedKeys = focus && (focus instanceof St.Button && (focus._extendedKeys || focus.extendedKey)); if (this._focusInExtendedKeys || extendedKeysWereFocused) return; @@ -1452,7 +1467,7 @@ var Keyboard = GObject.registerClass({ if (!this._showIdleId) { this._showIdleId = GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { - this.open(Main.layoutManager.focusIndex); + this.open(!!Main.layoutManager.focusIndex); this._showIdleId = 0; return GLib.SOURCE_REMOVE; }); @@ -1473,7 +1488,6 @@ var Keyboard = GObject.registerClass({ let level = i >= 1 && levels.length == 3 ? i + 1 : i; let layout = new KeyContainer(); - layout.shiftKeys = []; this._loadRows(currentLevel, level, levels.length, layout); layers[level] = layout; @@ -1655,6 +1669,12 @@ var Keyboard = GObject.registerClass({ this._loadDefaultKeys(post, layout, numLevels, row.length); } + /** + * @param {*} model + * @param {number} level + * @param {number} numLevels + * @param {KeyContainer["prototype"]} layout + */ _loadRows(model, level, numLevels, layout) { let rows = model.rows; for (let i = 0; i < rows.length; ++i) { @@ -1708,8 +1728,7 @@ var Keyboard = GObject.registerClass({ } _onKeyboardGroupsChanged() { - let nonGroupActors = [this._emojiSelection, this._keypad]; - this._aspectContainer.get_children().filter(c => !nonGroupActors.includes(c)).forEach(c => { + this._aspectContainer.get_children().filter(c => c !== this._emojiSelection && c !== this._keypad).forEach(c => { c.destroy(); }); @@ -1747,7 +1766,7 @@ var Keyboard = GObject.registerClass({ return; if (enabled) - this.open(Main.layoutManager.focusIndex); + this.open(!!Main.layoutManager.focusIndex); else this.close(); } @@ -2037,7 +2056,7 @@ var Keyboard = GObject.registerClass({ } }); -var KeyboardController = class extends Signals.EventEmitter { +export class KeyboardController extends Signals.EventEmitter { constructor() { super(); diff --git a/js/ui/layout.js b/js/ui/layout.js index 39d20e027..c4e25cc3e 100644 --- a/js/ui/layout.js +++ b/js/ui/layout.js @@ -1,21 +1,27 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported MonitorConstraint, LayoutManager */ -const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi; -const Signals = imports.misc.signals; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; +import * as Signals from '../misc/signals.js'; -const Background = imports.ui.background; -const BackgroundMenu = imports.ui.backgroundMenu; +import * as Background from './background.js'; +import * as BackgroundMenu from './backgroundMenu.js'; -const DND = imports.ui.dnd; -const Main = imports.ui.main; -const Ripples = imports.ui.ripples; +import * as DND from './dnd.js'; +import Main from './main.js'; +import * as Ripples from './ripples.js'; -var STARTUP_ANIMATION_TIME = 500; -var BACKGROUND_FADE_ANIMATION_TIME = 1000; +export let STARTUP_ANIMATION_TIME = 500; +export let BACKGROUND_FADE_ANIMATION_TIME = 1000; -var HOT_CORNER_PRESSURE_THRESHOLD = 100; // pixels -var HOT_CORNER_PRESSURE_TIMEOUT = 1000; // ms +export let HOT_CORNER_PRESSURE_THRESHOLD = 100; // pixels +export let HOT_CORNER_PRESSURE_TIMEOUT = 1000; // ms function isPopupMetaWindow(actor) { switch (actor.meta_window.get_window_type()) { @@ -28,7 +34,7 @@ function isPopupMetaWindow(actor) { } } -var MonitorConstraint = GObject.registerClass({ +export const MonitorConstraint = GObject.registerClass({ Properties: { 'primary': GObject.ParamSpec.boolean('primary', 'Primary', 'Track primary monitor', @@ -44,6 +50,9 @@ var MonitorConstraint = GObject.registerClass({ false), }, }, class MonitorConstraint extends Clutter.Constraint { + /** + * @param {*} props + */ _init(props) { this._primary = false; this._index = -1; @@ -145,7 +154,7 @@ var MonitorConstraint = GObject.registerClass({ } }); -var Monitor = class Monitor { +export class Monitor { constructor(index, geometry, geometryScale) { this.index = index; this.x = geometry.x; @@ -162,11 +171,17 @@ var Monitor = class Monitor { const UiActor = GObject.registerClass( class UiActor extends St.Widget { + /** + * @returns {[number, number]} + */ vfunc_get_preferred_width(_forHeight) { let width = global.stage.width; return [width, width]; } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_height(_forWidth) { let height = global.stage.height; return [height, height]; @@ -179,7 +194,7 @@ const defaultParams = { affectsInputRegion: true, }; -var LayoutManager = GObject.registerClass({ +export const LayoutManager = GObject.registerClass({ Signals: { 'hot-corners-changed': {}, 'startup-complete': {}, @@ -244,7 +259,7 @@ var LayoutManager = GObject.registerClass({ trackFullscreen: true }); this.panelBox.connect('notify::allocation', this._panelBoxChanged.bind(this)); - + log('initing?'); this.modalDialogGroup = new St.Widget({ name: 'modalDialogGroup', layout_manager: new Clutter.BinLayout() }); this.uiGroup.add_actor(this.modalDialogGroup); @@ -296,7 +311,7 @@ var LayoutManager = GObject.registerClass({ // This is called by Main after everything else is constructed init() { Main.sessionMode.connect('updated', this._sessionUpdated.bind(this)); - + log('testing?'); this._loadBackground(); } @@ -503,6 +518,7 @@ var LayoutManager = GObject.registerClass({ } _panelBoxChanged() { + log('panel box changed') this._updatePanelBarrier(); let size = this.panelBox.height; @@ -532,6 +548,7 @@ var LayoutManager = GObject.registerClass({ } _monitorsChanged() { + log('monitor?'); this._updateMonitors(); this._updateBoxes(); this._updateHotCorners(); @@ -606,8 +623,9 @@ var LayoutManager = GObject.registerClass({ this._systemBackground.add_constraint(constraint); let signalId = this._systemBackground.connect('loaded', () => { + log('loaded!'); this._systemBackground.disconnect(signalId); - +log('p2'); // We're mostly prepared for the startup animation // now, but since a lot is going on asynchronously // during startup, let's defer the startup animation @@ -615,6 +633,7 @@ var LayoutManager = GObject.registerClass({ // This helps to prevent us from running the animation // when the system is bogged down const id = GLib.idle_add(GLib.PRIORITY_LOW, () => { + log('idling...'); this._systemBackground.show(); global.stage.show(); this._prepareStartupAnimation(); @@ -640,6 +659,7 @@ var LayoutManager = GObject.registerClass({ // of the screen. async _prepareStartupAnimation() { + log('preparing...'); // During the initial transition, add a simple actor to block all events, // so they don't get delivered to X11 windows that have been transformed. this._coverPane = new Clutter.Actor({ opacity: 0, @@ -676,25 +696,31 @@ var LayoutManager = GObject.registerClass({ } global.window_group.set_clip(monitor.x, monitor.y, monitor.width, monitor.height); - +log('update backgrounds?'); await this._updateBackgrounds(); } + log('prepared?'); + this.emit('startup-prepared'); this._startupAnimation(); } _startupAnimation() { - if (Meta.is_restart()) + if (Meta.is_restart()) { + log('testing') this._startupAnimationComplete(); - else if (Main.sessionMode.isGreeter) + } else if (Main.sessionMode.isGreeter) this._startupAnimationGreeter(); - else + else { + log ('test 213'); this._startupAnimationSession(); + } } _startupAnimationGreeter() { + log('greet start'); this.panelBox.ease({ translation_y: 0, duration: STARTUP_ANIMATION_TIME, @@ -704,6 +730,7 @@ var LayoutManager = GObject.registerClass({ } _startupAnimationSession() { + log('greet sesh start'); const onComplete = () => this._startupAnimationComplete(); if (Main.sessionMode.hasOverview) { Main.overview.runStartupAnimation(onComplete); @@ -720,6 +747,7 @@ var LayoutManager = GObject.registerClass({ } _startupAnimationComplete() { + log('Animation compl') this._coverPane.destroy(); this._coverPane = null; @@ -852,14 +880,15 @@ var LayoutManager = GObject.registerClass({ if (this._findActor(actor) != -1) throw new Error('trying to re-track existing chrome actor'); - let actorData = { ...defaultParams, ...params }; - actorData.actor = actor; - actorData.visibleId = actor.connect('notify::visible', - this._queueUpdateRegions.bind(this)); - actorData.allocationId = actor.connect('notify::allocation', - this._queueUpdateRegions.bind(this)); - actorData.destroyId = actor.connect('destroy', - this._untrackActor.bind(this)); + let actorData = { ...defaultParams, ...params, + actor: actor, + isibleId : actor.connect('notify::visible', + this._queueUpdateRegions.bind(this)), + allocationId : actor.connect('notify::allocation', + this._queueUpdateRegions.bind(this)), + destroyId: actor.connect('destroy', + this._untrackActor.bind(this)), + }; // Note that destroying actor will unset its parent, so we don't // need to connect to 'destroy' too. @@ -1069,7 +1098,7 @@ var LayoutManager = GObject.registerClass({ // // This class manages a "hot corner" that can toggle switching to // overview. -var HotCorner = GObject.registerClass( +export const HotCorner = GObject.registerClass( class HotCorner extends Clutter.Actor { _init(layoutManager, monitor, x, y) { super._init(); @@ -1227,7 +1256,7 @@ class HotCorner extends Clutter.Actor { } }); -var PressureBarrier = class PressureBarrier extends Signals.EventEmitter { +export class PressureBarrier extends Signals.EventEmitter { constructor(threshold, timeout, actionMode) { super(); diff --git a/js/ui/lightbox.js b/js/ui/lightbox.js index 9e66ffd58..3fb71ab40 100644 --- a/js/ui/lightbox.js +++ b/js/ui/lightbox.js @@ -1,12 +1,15 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Lightbox */ -const { Clutter, GObject, Shell, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import GObject from 'gi://GObject'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; -var DEFAULT_FADE_FACTOR = 0.4; -var VIGNETTE_BRIGHTNESS = 0.5; -var VIGNETTE_SHARPNESS = 0.7; +export let DEFAULT_FADE_FACTOR = 0.4; +export let VIGNETTE_BRIGHTNESS = 0.5; +export let VIGNETTE_SHARPNESS = 0.7; const VIGNETTE_DECLARATIONS = '\ uniform float brightness;\n\ @@ -21,7 +24,7 @@ t = clamp(t, 0.0, 1.0);\n\ float pixel_brightness = mix(1.0, 1.0 - vignette_sharpness, t);\n\ cogl_color_out.a = cogl_color_out.a * (1 - pixel_brightness * brightness);'; -var RadialShaderEffect = GObject.registerClass({ +export const RadialShaderEffect = GObject.registerClass({ Properties: { 'brightness': GObject.ParamSpec.float( 'brightness', 'brightness', 'brightness', @@ -102,7 +105,7 @@ var RadialShaderEffect = GObject.registerClass({ * @container and will track any changes in its size. You can override * this by passing an explicit width and height in @params. */ -var Lightbox = GObject.registerClass({ +export const Lightbox = GObject.registerClass({ Properties: { 'active': GObject.ParamSpec.boolean( 'active', 'active', 'active', GObject.ParamFlags.READABLE, false), diff --git a/js/ui/locatePointer.js b/js/ui/locatePointer.js index 6ae29419a..41ccf3468 100644 --- a/js/ui/locatePointer.js +++ b/js/ui/locatePointer.js @@ -1,14 +1,14 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported LocatePointer */ -const { Gio } = imports.gi; -const Ripples = imports.ui.ripples; -const Main = imports.ui.main; +import Gio from 'gi://Gio'; +import * as Ripples from './ripples.js'; +import Main from './main.js'; const LOCATE_POINTER_KEY = "locate-pointer"; const LOCATE_POINTER_SCHEMA = "org.gnome.desktop.interface"; -var LocatePointer = class { +export class LocatePointer { constructor() { this._settings = new Gio.Settings({ schema_id: LOCATE_POINTER_SCHEMA }); this._settings.connect(`changed::${LOCATE_POINTER_KEY}`, () => this._syncEnabled()); diff --git a/js/ui/lookingGlass.js b/js/ui/lookingGlass.js index 3a39b7de9..d3798613b 100644 --- a/js/ui/lookingGlass.js +++ b/js/ui/lookingGlass.js @@ -1,16 +1,24 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported LookingGlass */ -const { Clutter, Cogl, Gio, GLib, GObject, - Graphene, Meta, Pango, Shell, St } = imports.gi; -const Signals = imports.misc.signals; -const System = imports.system; - -const History = imports.misc.history; -const ExtensionUtils = imports.misc.extensionUtils; -const ShellEntry = imports.ui.shellEntry; -const Main = imports.ui.main; -const JsParse = imports.misc.jsParse; +import Clutter from 'gi://Clutter'; +import Cogl from 'gi://Cogl'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Graphene from 'gi://Graphene'; +import Meta from 'gi://Meta'; +import Pango from 'gi://Pango'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; +import * as Signals from '../misc/signals.js'; +import System from 'system'; + +import * as History from '../misc/history.js'; +import * as ExtensionUtils from '../misc/extensionUtils.js'; +import * as ShellEntry from './shellEntry.js'; +import Main from './main.js'; +import * as JsParse from '../misc/jsParse.js'; const { ExtensionState } = ExtensionUtils; @@ -18,7 +26,7 @@ const CHEVRON = '>>> '; /* Imports...feel free to add here as needed */ var commandHeader = 'const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi; ' + - 'const Main = imports.ui.main; ' + + 'const Main = getMain(); ' + /* Utility functions...we should probably be able to use these * in the shell core code too. */ 'const stage = global.stage; ' + @@ -29,9 +37,10 @@ var commandHeader = 'const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = im const HISTORY_KEY = 'looking-glass-history'; // Time between tabs for them to count as a double-tab event -var AUTO_COMPLETE_DOUBLE_TAB_DELAY = 500; -var AUTO_COMPLETE_SHOW_COMPLETION_ANIMATION_DURATION = 200; -var AUTO_COMPLETE_GLOBAL_KEYWORDS = _getAutoCompleteGlobalKeywords(); + +export let AUTO_COMPLETE_DOUBLE_TAB_DELAY = 500; +export let AUTO_COMPLETE_SHOW_COMPLETION_ANIMATION_DURATION = 200; +export let AUTO_COMPLETE_GLOBAL_KEYWORDS = _getAutoCompleteGlobalKeywords(); const LG_ANIMATION_TIME = 500; @@ -45,7 +54,7 @@ function _getAutoCompleteGlobalKeywords() { return keywords.concat(windowProperties).concat(headerProperties); } -var AutoComplete = class AutoComplete extends Signals.EventEmitter { +export class AutoComplete extends Signals.EventEmitter { constructor(entry) { super(); @@ -109,8 +118,7 @@ var AutoComplete = class AutoComplete extends Signals.EventEmitter { } }; - -var Notebook = GObject.registerClass({ +export const Notebook = GObject.registerClass({ Signals: { 'selection': { param_types: [Clutter.Actor.$gtype] } }, }, class Notebook extends St.BoxLayout { _init() { @@ -253,8 +261,14 @@ function objectToString(o) { } } -var ObjLink = GObject.registerClass( +export const ObjLink = GObject.registerClass( +/** @extends {St.Button<Clutter.Text>} */ class ObjLink extends St.Button { + /** + * @param {*} lookingGlass + * @param {*} o + * @param {*} title + */ _init(lookingGlass, o, title) { let text; if (title) @@ -281,8 +295,14 @@ class ObjLink extends St.Button { } }); -var Result = GObject.registerClass( +export const Result = GObject.registerClass( class Result extends St.BoxLayout { + /** + * @param {*} lookingGlass + * @param {*} command + * @param {*} o + * @param {*} index + */ _init(lookingGlass, command, o, index) { super._init({ vertical: true }); @@ -304,8 +324,11 @@ class Result extends St.BoxLayout { } }); -var WindowList = GObject.registerClass({ +export const WindowList = GObject.registerClass({ }, class WindowList extends St.BoxLayout { + /** + * @param {*} lookingGlass + */ _init(lookingGlass) { super._init({ name: 'Windows', vertical: true, style: 'spacing: 8px' }); let tracker = Shell.WindowTracker.get_default(); @@ -357,8 +380,11 @@ var WindowList = GObject.registerClass({ } }); -var ObjInspector = GObject.registerClass( +export const ObjInspector = GObject.registerClass( class ObjInspector extends St.ScrollView { + /** + * @param {*} lookingGlass + */ _init(lookingGlass) { super._init({ pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }), @@ -397,7 +423,8 @@ class ObjInspector extends St.ScrollView { text: 'Inspecting: %s: %s'.format(typeof obj, objectToString(obj)), x_expand: true, }); - label.single_line_mode = true; + // TODO: Is this the intended code? + label.clutterText.single_line_mode = true; hbox.add_child(label); let button = new St.Button({ label: 'Insert', style_class: 'lg-obj-inspector-button' }); button.connect('clicked', this._onInsert.bind(this)); @@ -475,7 +502,7 @@ class ObjInspector extends St.ScrollView { } }); -var RedBorderEffect = GObject.registerClass( +export const RedBorderEffect = GObject.registerClass( class RedBorderEffect extends Clutter.Effect { _init() { super._init(); @@ -527,7 +554,7 @@ class RedBorderEffect extends Clutter.Effect { } }); -var Inspector = GObject.registerClass({ +export const Inspector = GObject.registerClass({ Signals: { 'closed': {}, 'target': { param_types: [Clutter.Actor.$gtype, GObject.TYPE_DOUBLE, GObject.TYPE_DOUBLE] } }, }, class Inspector extends Clutter.Actor { @@ -667,7 +694,7 @@ var Inspector = GObject.registerClass({ } }); -var Extensions = GObject.registerClass({ +export const Extensions = GObject.registerClass({ }, class Extensions extends St.BoxLayout { _init(lookingGlass) { super._init({ vertical: true, name: 'lookingGlassExtensions' }); @@ -820,7 +847,7 @@ var Extensions = GObject.registerClass({ }); -var ActorLink = GObject.registerClass({ +export const ActorLink = GObject.registerClass({ Signals: { 'inspect-actor': {}, }, @@ -877,7 +904,7 @@ var ActorLink = GObject.registerClass({ } }); -var ActorTreeViewer = GObject.registerClass( +export const ActorTreeViewer = GObject.registerClass( class ActorTreeViewer extends St.BoxLayout { _init(lookingGlass) { super._init(); @@ -1004,7 +1031,7 @@ class ActorTreeViewer extends St.BoxLayout { } }); -var LookingGlass = GObject.registerClass( +export const LookingGlass = GObject.registerClass( class LookingGlass extends St.BoxLayout { _init() { super._init({ @@ -1093,6 +1120,7 @@ class LookingGlass extends St.BoxLayout { this._evalBox = new St.BoxLayout({ name: 'EvalBox', vertical: true }); notebook.appendPage('Evaluator', this._evalBox); + /** @type {St.BoxLayout<Result["prototype"]>} */ this._resultsArea = new St.BoxLayout({ name: 'ResultsArea', vertical: true, @@ -1270,7 +1298,7 @@ class LookingGlass extends St.BoxLayout { getResult(idx) { try { - return this._resultsArea.get_child_at_index(idx - this._offset).o; + return /** @type {Result["prototype"]} */ (this._resultsArea.get_child_at_index(idx - this._offset)).o; } catch (e) { throw new Error('Unknown result at index %d'.format(idx)); } diff --git a/js/ui/magnifier.js b/js/ui/magnifier.js index 8727dbd67..ff8876a32 100644 --- a/js/ui/magnifier.js +++ b/js/ui/magnifier.js @@ -1,19 +1,28 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Magnifier */ -const { Atspi, Clutter, GDesktopEnums, - Gio, GLib, GObject, Meta, Shell, St } = imports.gi; -const Signals = imports.misc.signals; +import Atspi from 'gi://Atspi'; +import Clutter from 'gi://Clutter'; +import GDesktopEnums from 'gi://GDesktopEnums'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; -const Background = imports.ui.background; -const FocusCaretTracker = imports.ui.focusCaretTracker; -const Main = imports.ui.main; -const PointerWatcher = imports.ui.pointerWatcher; +import * as Signals from '../misc/signals.js'; -var CROSSHAIRS_CLIP_SIZE = [100, 100]; -var NO_CHANGE = 0.0; +import * as Background from './background.js'; +import * as FocusCaretTracker from './focusCaretTracker.js'; +import Main from './main.js'; +import * as PointerWatcher from './pointerWatcher.js'; -var POINTER_REST_TIME = 1000; // milliseconds +/** @type {[number, number]} */ +export const CROSSHAIRS_CLIP_SIZE = [100, 100]; +export const NO_CHANGE = 0.0; + +export const POINTER_REST_TIME = 1000; // milliseconds // Settings const MAGNIFIER_SCHEMA = 'org.gnome.desktop.a11y.magnifier'; @@ -39,7 +48,7 @@ const CROSS_HAIRS_OPACITY_KEY = 'cross-hairs-opacity'; const CROSS_HAIRS_LENGTH_KEY = 'cross-hairs-length'; const CROSS_HAIRS_CLIP_KEY = 'cross-hairs-clip'; -var MouseSpriteContent = GObject.registerClass({ +export const MouseSpriteContent = GObject.registerClass({ Implements: [Clutter.Content], }, class MouseSpriteContent extends GObject.Object { _init() { @@ -72,6 +81,9 @@ var MouseSpriteContent = GObject.registerClass({ return this._texture; } + /** + * @this {this & Clutter.Content} + */ set texture(coglTexture) { if (this._texture == coglTexture) return; @@ -87,7 +99,7 @@ var MouseSpriteContent = GObject.registerClass({ } }); -var Magnifier = class Magnifier extends Signals.EventEmitter { +export class Magnifier extends Signals.EventEmitter { constructor() { super(); @@ -98,8 +110,10 @@ var Magnifier = class Magnifier extends Signals.EventEmitter { let cursorTracker = Meta.CursorTracker.get_for_display(global.display); this._cursorTracker = cursorTracker; - this._mouseSprite = new Clutter.Actor({ request_mode: Clutter.RequestMode.CONTENT_SIZE }); - this._mouseSprite.content = new MouseSpriteContent(); + this._mouseSprite = new Clutter.Actor({ + request_mode: Clutter.RequestMode.CONTENT_SIZE, + content: new MouseSpriteContent() + }); // Create the first ZoomRegion and initialize it according to the // magnification settings. @@ -145,7 +159,7 @@ var Magnifier = class Magnifier extends Signals.EventEmitter { /** * setActive: * Show/hide all the zoom regions. - * @param {bool} activate: Boolean to activate or de-activate the magnifier. + * @param {boolean} activate: Boolean to activate or de-activate the magnifier. */ setActive(activate) { let isActive = this.isActive(); @@ -185,7 +199,7 @@ var Magnifier = class Magnifier extends Signals.EventEmitter { /** * isActive: - * @returns {bool} Whether the magnifier is active. + * @returns {boolean} Whether the magnifier is active. */ isActive() { // Sufficient to check one ZoomRegion since Magnifier's active @@ -220,7 +234,7 @@ var Magnifier = class Magnifier extends Signals.EventEmitter { /** * isTrackingMouse: - * @returns {bool} whether the magnifier is currently tracking the mouse + * @returns {boolean} whether the magnifier is currently tracking the mouse */ isTrackingMouse() { return !!this._pointerWatch; @@ -297,7 +311,7 @@ var Magnifier = class Magnifier extends Signals.EventEmitter { /** * getZoomRegions: * Return a list of ZoomRegion's for this Magnifier. - * @returns {number[]} The Magnifier's zoom region list. + * @returns {ZoomRegion[]} The Magnifier's zoom region list. */ getZoomRegions() { return this._zoomRegions; @@ -345,7 +359,7 @@ var Magnifier = class Magnifier extends Signals.EventEmitter { /** * setCrosshairsVisible: * Show or hide the cross hair. - * @param {bool} visible: Flag that indicates show (true) or hide (false). + * @param {boolean} visible: Flag that indicates show (true) or hide (false). */ setCrosshairsVisible(visible) { if (visible) { @@ -406,17 +420,6 @@ var Magnifier = class Magnifier extends Signals.EventEmitter { } /** - * getCrosshairsOpacity: - * @returns {number} Value between 0.0 (transparent) and 1.0 (fully opaque). - */ - getCrosshairsOpacity() { - if (this._crossHairs) - return this._crossHairs.getOpacity() / 255.0; - else - return 0.0; - } - - /** * setCrosshairsLength: * Set the crosshairs length for all ZoomRegions. * @param {number} length: The length of the vertical and horizontal @@ -445,7 +448,7 @@ var Magnifier = class Magnifier extends Signals.EventEmitter { /** * setCrosshairsClip: * Set whether the crosshairs are clipped at their intersection. - * @param {bool} clip: Flag to indicate whether to clip the crosshairs. + * @param {boolean} clip: Flag to indicate whether to clip the crosshairs. */ setCrosshairsClip(clip) { if (!this._crossHairs) @@ -458,7 +461,7 @@ var Magnifier = class Magnifier extends Signals.EventEmitter { /** * getCrosshairsClip: * Get whether the crosshairs are clipped by the mouse image. - * @returns {bool} Whether the crosshairs are clipped. + * @returns {boolean} Whether the crosshairs are clipped. */ getCrosshairsClip() { if (this._crossHairs) { @@ -577,9 +580,9 @@ var Magnifier = class Magnifier extends Signals.EventEmitter { if (aPref) zoomRegion.setCaretTrackingMode(aPref); - aPref = this._settings.get_boolean(INVERT_LIGHTNESS_KEY); - if (aPref) - zoomRegion.setInvertLightness(aPref); + let aBoolPref = this._settings.get_boolean(INVERT_LIGHTNESS_KEY); + if (aBoolPref) + zoomRegion.setInvertLightness(aBoolPref); aPref = this._settings.get_double(COLOR_SATURATION_KEY); if (aPref) @@ -698,7 +701,7 @@ var Magnifier = class Magnifier extends Signals.EventEmitter { } }; -var ZoomRegion = class ZoomRegion { +export class ZoomRegion { constructor(magnifier, mouseSourceActor) { this._magnifier = magnifier; this._focusCaretTracker = new FocusCaretTracker.FocusCaretTracker(); @@ -749,11 +752,11 @@ var ZoomRegion = class ZoomRegion { this._monitorsChanged.bind(this)); this._signalConnections.push([Main.layoutManager, id]); - id = this._focusCaretTracker.connect('caret-moved', this._updateCaret.bind(this)); - this._signalConnections.push([this._focusCaretTracker, id]); + let focusId = this._focusCaretTracker.connect('caret-moved', this._updateCaret.bind(this)); + this._signalConnections.push([this._focusCaretTracker, focusId]); - id = this._focusCaretTracker.connect('focus-changed', this._updateFocus.bind(this)); - this._signalConnections.push([this._focusCaretTracker, id]); + focusId = this._focusCaretTracker.connect('focus-changed', this._updateFocus.bind(this)); + this._signalConnections.push([this._focusCaretTracker, focusId]); } _disconnectSignals() { @@ -827,7 +830,7 @@ var ZoomRegion = class ZoomRegion { /** * setActive: - * @param {bool} activate: Boolean to show/hide the ZoomRegion. + * @param {boolean} activate: Boolean to show/hide the ZoomRegion. */ setActive(activate) { if (activate == this.isActive()) @@ -854,7 +857,7 @@ var ZoomRegion = class ZoomRegion { /** * isActive: - * @returns {bool} Whether this ZoomRegion is active + * @returns {boolean} Whether this ZoomRegion is active */ isActive() { return this._magView != null; @@ -993,7 +996,7 @@ var ZoomRegion = class ZoomRegion { * setLensMode: * Turn lens mode on/off. In full screen mode, lens mode does nothing since * a lens the size of the screen is pointless. - * @param {bool} lensMode: Whether lensMode should be active + * @param {boolean} lensMode: Whether lensMode should be active */ setLensMode(lensMode) { this._lensMode = lensMode; @@ -1004,7 +1007,7 @@ var ZoomRegion = class ZoomRegion { /** * isLensMode: * Is lens mode on or off? - * @returns {bool} The lens mode state. + * @returns {boolean} The lens mode state. */ isLensMode() { return this._lensMode; @@ -1014,7 +1017,7 @@ var ZoomRegion = class ZoomRegion { * setClampScrollingAtEdges: * Stop vs. allow scrolling of the magnified contents when it scroll beyond * the edges of the screen. - * @param {bool} clamp: Boolean to turn on/off clamping. + * @param {boolean} clamp: Boolean to turn on/off clamping. */ setClampScrollingAtEdges(clamp) { this._clampScrollingAtEdges = clamp; @@ -1140,7 +1143,7 @@ var ZoomRegion = class ZoomRegion { /** * scrollToMousePos: * Set the region of interest based on the position of the system pointer. - * @returns {bool}: Whether the system mouse pointer is over the + * @returns {boolean}: Whether the system mouse pointer is over the * magnified view. */ scrollToMousePos() { @@ -1200,7 +1203,7 @@ var ZoomRegion = class ZoomRegion { /** * addCrosshairs: * Add crosshairs centered on the magnified mouse. - * @param {Crosshairs} crossHairs: Crosshairs instance + * @param {Crosshairs["prototype"]} crossHairs: Crosshairs instance */ addCrosshairs(crossHairs) { this._crossHairs = crossHairs; @@ -1214,7 +1217,7 @@ var ZoomRegion = class ZoomRegion { /** * setInvertLightness: * Set whether to invert the lightness of the magnified view. - * @param {bool} flag: whether brightness should be inverted + * @param {boolean} flag: whether brightness should be inverted */ setInvertLightness(flag) { this._invertLightness = flag; @@ -1225,7 +1228,7 @@ var ZoomRegion = class ZoomRegion { /** * getInvertLightness: * Retrieve whether the lightness is inverted. - * @returns {bool} whether brightness should be inverted + * @returns {boolean} whether brightness should be inverted */ getInvertLightness() { return this._invertLightness; @@ -1650,7 +1653,7 @@ var ZoomRegion = class ZoomRegion { } }; -var Crosshairs = GObject.registerClass( +export const Crosshairs = GObject.registerClass( class Crosshairs extends Clutter.Actor { _init() { // Set the group containing the crosshairs to three times the desktop @@ -1825,7 +1828,7 @@ class Crosshairs extends Clutter.Actor { * setClip: * Set the width and height of the rectangle that clips the crosshairs at * their intersection - * @param {number[]} size: Array of [width, height] defining the size + * @param {[number, number]} size: Array of [width, height] defining the size * of the clip rectangle. */ setClip(size) { @@ -1841,12 +1844,16 @@ class Crosshairs extends Clutter.Actor { } } + getClip() { + return this._clipSize; + } + /** * reCenter: * Reposition the horizontal and vertical hairs such that they cross at * the center of crosshairs group. If called with the dimensions of * the clip rectangle, these are used to update the size of the clip. - * @param {number[]=} clipSize: If present, the clip's [width, height]. + * @param {[number, number]} [clipSize]: If present, the clip's [width, height]. */ reCenter(clipSize) { let [groupWidth, groupHeight] = this.get_size(); @@ -1871,7 +1878,7 @@ class Crosshairs extends Clutter.Actor { } }); -var MagShaderEffects = class MagShaderEffects { +export class MagShaderEffects { constructor(uiGroupClone) { this._inverse = new Shell.InvertLightnessEffect(); this._brightnessContrast = new Clutter.BrightnessContrastEffect(); @@ -1902,7 +1909,7 @@ var MagShaderEffects = class MagShaderEffects { /** * setInvertLightness: * Enable/disable invert lightness effect. - * @param {bool} invertFlag: Enabled flag. + * @param {boolean} invertFlag: Enabled flag. */ setInvertLightness(invertFlag) { this._inverse.set_enabled(invertFlag); diff --git a/js/ui/main.js b/js/ui/main.js index 1069674ae..e848eb070 100644 --- a/js/ui/main.js +++ b/js/ui/main.js @@ -7,47 +7,54 @@ kbdA11yDialog, introspectService, start, pushModal, popModal, activateWindow, createLookingGlass, initializeDeferredWork, getThemeStylesheet, setThemeStylesheet */ - -const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi; - -const AccessDialog = imports.ui.accessDialog; -const AudioDeviceSelection = imports.ui.audioDeviceSelection; -const Components = imports.ui.components; -const CtrlAltTab = imports.ui.ctrlAltTab; -const EndSessionDialog = imports.ui.endSessionDialog; -const ExtensionSystem = imports.ui.extensionSystem; -const ExtensionDownloader = imports.ui.extensionDownloader; -const InputMethod = imports.misc.inputMethod; -const Introspect = imports.misc.introspect; -const Keyboard = imports.ui.keyboard; -const MessageTray = imports.ui.messageTray; -const ModalDialog = imports.ui.modalDialog; -const OsdWindow = imports.ui.osdWindow; -const OsdMonitorLabeler = imports.ui.osdMonitorLabeler; -const Overview = imports.ui.overview; -const PadOsd = imports.ui.padOsd; -const Panel = imports.ui.panel; -const RunDialog = imports.ui.runDialog; -const WelcomeDialog = imports.ui.welcomeDialog; -const Layout = imports.ui.layout; -const LoginManager = imports.misc.loginManager; -const LookingGlass = imports.ui.lookingGlass; -const NotificationDaemon = imports.ui.notificationDaemon; -const WindowAttentionHandler = imports.ui.windowAttentionHandler; -const ScreenShield = imports.ui.screenShield; -const Scripting = imports.ui.scripting; -const SessionMode = imports.ui.sessionMode; -const ShellDBus = imports.ui.shellDBus; -const ShellMountOperation = imports.ui.shellMountOperation; -const WindowManager = imports.ui.windowManager; -const Magnifier = imports.ui.magnifier; -const XdndHandler = imports.ui.xdndHandler; -const KbdA11yDialog = imports.ui.kbdA11yDialog; -const LocatePointer = imports.ui.locatePointer; -const PointerA11yTimeout = imports.ui.pointerA11yTimeout; -const ParentalControlsManager = imports.misc.parentalControlsManager; +// @ts-check + +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; + +import * as AccessDialog from './accessDialog.js'; +import * as AudioDeviceSelection from './audioDeviceSelection.js'; +import * as Components from './components.js'; +import * as CtrlAltTab from './ctrlAltTab.js'; +import * as EndSessionDialog from './endSessionDialog.js'; +import * as InputMethod from '../misc/inputMethod.js'; +import * as Introspect from '../misc/introspect.js'; +import * as Keyboard from './keyboard.js'; +import * as MessageTray from './messageTray.js'; +import * as ModalDialog from './modalDialog.js'; +import * as OsdWindow from './osdWindow.js'; +import * as OsdMonitorLabeler from './osdMonitorLabeler.js'; +import * as Overview from './overview.js'; +import * as PadOsd from './padOsd.js'; +import * as Panel from './panel.js'; +import * as Params from '../misc/params.js'; +import * as RunDialog from './runDialog.js'; +import * as WelcomeDialog from './welcomeDialog.js'; +import * as Layout from './layout.js'; +import * as LoginManager from '../misc/loginManager.js'; +import * as LookingGlass from './lookingGlass.js'; +import * as NotificationDaemon from './notificationDaemon.js'; +import * as WindowAttentionHandler from './windowAttentionHandler.js'; +import * as ScreenShield from './screenShield.js'; +import * as Scripting from './scripting.js'; +import * as SessionMode from './sessionMode.js'; +import * as ShellDBus from './shellDBus.js'; +import * as ShellMountOperation from './shellMountOperation.js'; +import * as WindowManager from './windowManager.js'; +import * as Magnifier from './magnifier.js'; +import * as XdndHandler from './xdndHandler.js'; +import * as KbdA11yDialog from './kbdA11yDialog.js'; +import * as LocatePointer from './locatePointer.js'; +import * as PointerA11yTimeout from './pointerA11yTimeout.js'; +import * as ParentalControlsManager from '../misc/parentalControlsManager.js'; const Config = imports.misc.config; -const Util = imports.misc.util; +import * as Util from '../misc/util.js'; +import * as ExtensionUtils from '../misc/extensionUtils.js'; const WELCOME_DIALOG_LAST_SHOWN_VERSION = 'welcome-dialog-last-shown-version'; // Make sure to mention the point release, otherwise it will show every time @@ -56,777 +63,823 @@ const WELCOME_DIALOG_LAST_TOUR_CHANGE = '40.beta'; const LOG_DOMAIN = 'GNOME Shell'; const GNOMESHELL_STARTED_MESSAGE_ID = 'f3ea493c22934e26811cd62abe8e203a'; -var componentManager = null; -var extensionManager = null; -var panel = null; -var overview = null; -var runDialog = null; -var lookingGlass = null; -var welcomeDialog = null; -var wm = null; -var messageTray = null; -var screenShield = null; -var notificationDaemon = null; -var windowAttentionHandler = null; -var ctrlAltTabManager = null; -var padOsdService = null; -var osdWindowManager = null; -var osdMonitorLabeler = null; -var sessionMode = null; -var shellAccessDialogDBusService = null; -var shellAudioSelectionDBusService = null; -var shellDBusService = null; -var shellMountOpDBusService = null; -var screenSaverDBus = null; -var modalCount = 0; -var actionMode = Shell.ActionMode.NONE; -var modalActorFocusStack = []; -var uiGroup = null; -var magnifier = null; -var xdndHandler = null; -var keyboard = null; -var layoutManager = null; -var kbdA11yDialog = null; -var inputMethod = null; -var introspectService = null; -var locatePointer = null; -let _startDate; -let _defaultCssStylesheet = null; -let _cssStylesheet = null; -let _themeResource = null; -let _oskResource = null; - -Gio._promisify(Gio._LocalFilePrototype, 'delete_async', 'delete_finish'); -Gio._promisify(Gio._LocalFilePrototype, 'touch_async', 'touch_finish'); - -let _remoteAccessInhibited = false; - -function _sessionUpdated() { - if (sessionMode.isPrimary) - _loadDefaultStylesheet(); - - wm.allowKeybinding('overlay-key', Shell.ActionMode.NORMAL | - Shell.ActionMode.OVERVIEW); - - wm.allowKeybinding('locate-pointer-key', Shell.ActionMode.ALL); - - wm.setCustomKeybindingHandler('panel-run-dialog', - Shell.ActionMode.NORMAL | - Shell.ActionMode.OVERVIEW, - sessionMode.hasRunDialog ? openRunDialog : null); - - if (!sessionMode.hasRunDialog) { - if (runDialog) - runDialog.close(); - if (lookingGlass) - lookingGlass.close(); - if (welcomeDialog) - welcomeDialog.close(); - } - - let remoteAccessController = global.backend.get_remote_access_controller(); - if (remoteAccessController) { - if (sessionMode.allowScreencast && _remoteAccessInhibited) { - remoteAccessController.uninhibit_remote_access(); - _remoteAccessInhibited = false; - } else if (!sessionMode.allowScreencast && !_remoteAccessInhibited) { - remoteAccessController.inhibit_remote_access(); - _remoteAccessInhibited = true; +export class Main { + componentManager = null; + extensionManager = null; + panel = null; + overview = null; + runDialog = null; + lookingGlass = null; + welcomeDialog = null; + /** @type {WindowManager.WindowManager} */ + wm = null; + messageTray = null; + screenShield = null; + notificationDaemon = null; + windowAttentionHandler = null; + /** @type {CtrlAltTab.CtrlAltTabManager} */ + ctrlAltTabManager = null; + padOsdService = null; + osdWindowManager = null; + osdMonitorLabeler = null; + sessionMode = null; + shellAccessDialogDBusService = null; + shellAudioSelectionDBusService = null; + shellDBusService = null; + shellMountOpDBusService = null; + screenSaverDBus = null; + modalCount = 0; + actionMode = Shell.ActionMode.NONE; + modalActorFocusStack = []; + uiGroup = null; + /** @type {Magnifier.Magnifier} */ + magnifier = null; + /** @type {XdndHandler.XdndHandler} */ + xdndHandler = null; + keyboard = null; + /** @type {Layout.LayoutManager["prototype"]} */ + layoutManager = null; + kbdA11yDialog = null; + inputMethod = null; + introspectService = null; + locatePointer = null; + _startDate; + _defaultCssStylesheet = null; + _cssStylesheet = null; + _themeResource = null; + _oskResource = null; + + _remoteAccessInhibited = false; + + _sessionUpdated() { + const { wm, sessionMode, welcomeDialog, overview, runDialog, lookingGlass } = this; + + if (sessionMode.isPrimary) + this._loadDefaultStylesheet(); + + + wm.setCustomKeybindingHandler('panel-main-menu', + Shell.ActionMode.NORMAL | + Shell.ActionMode.OVERVIEW, + sessionMode.hasOverview ? overview.toggle.bind(overview) : null); + wm.allowKeybinding('overlay-key', Shell.ActionMode.NORMAL | + Shell.ActionMode.OVERVIEW); + + wm.allowKeybinding('locate-pointer-key', Shell.ActionMode.ALL); + + wm.setCustomKeybindingHandler('panel-run-dialog', + Shell.ActionMode.NORMAL | + Shell.ActionMode.OVERVIEW, + sessionMode.hasRunDialog ? this.openRunDialog : null); + + if (!sessionMode.hasRunDialog) { + if (runDialog) + runDialog.close(); + if (lookingGlass) + lookingGlass.close(); + if (welcomeDialog) + welcomeDialog.close(); + } + + let remoteAccessController = global.backend.get_remote_access_controller(); + if (remoteAccessController) { + if (sessionMode.allowScreencast && this._remoteAccessInhibited) { + remoteAccessController.uninhibit_remote_access(); + this._remoteAccessInhibited = false; + } else if (!sessionMode.allowScreencast && !this._remoteAccessInhibited) { + remoteAccessController.inhibit_remote_access(); + this._remoteAccessInhibited = true; + } } - } -} - -function start() { - // These are here so we don't break compatibility. - global.logError = globalThis.log; - global.log = globalThis.log; - - // Chain up async errors reported from C - global.connect('notify-error', (global, msg, detail) => { - notifyError(msg, detail); - }); - - let currentDesktop = GLib.getenv('XDG_CURRENT_DESKTOP'); - if (!currentDesktop || !currentDesktop.split(':').includes('GNOME')) - Gio.DesktopAppInfo.set_desktop_env('GNOME'); - - sessionMode = new SessionMode.SessionMode(); - sessionMode.connect('updated', _sessionUpdated); - - St.Settings.get().connect('notify::gtk-theme', _loadDefaultStylesheet); - - // Initialize ParentalControlsManager before the UI - ParentalControlsManager.getDefault(); - - _initializeUI(); - - shellAccessDialogDBusService = new AccessDialog.AccessDialogDBus(); - shellAudioSelectionDBusService = new AudioDeviceSelection.AudioDeviceSelectionDBus(); - shellDBusService = new ShellDBus.GnomeShell(); - shellMountOpDBusService = new ShellMountOperation.GnomeShellMountOpHandler(); - - const watchId = Gio.DBus.session.watch_name('org.gnome.Shell.Notifications', - Gio.BusNameWatcherFlags.AUTO_START, - bus => bus.unwatch_name(watchId), - bus => bus.unwatch_name(watchId)); - - _sessionUpdated(); -} - -function _initializeUI() { - // Ensure ShellWindowTracker and ShellAppUsage are initialized; this will - // also initialize ShellAppSystem first. ShellAppSystem - // needs to load all the .desktop files, and ShellWindowTracker - // will use those to associate with windows. Right now - // the Monitor doesn't listen for installed app changes - // and recalculate application associations, so to avoid - // races for now we initialize it here. It's better to - // be predictable anyways. - Shell.WindowTracker.get_default(); - Shell.AppUsage.get_default(); - - reloadThemeResource(); - _loadOskLayouts(); - _loadDefaultStylesheet(); - - new AnimationsSettings(); - - // Setup the stage hierarchy early - layoutManager = new Layout.LayoutManager(); - - // Various parts of the codebase still refer to Main.uiGroup - // instead of using the layoutManager. This keeps that code - // working until it's updated. - uiGroup = layoutManager.uiGroup; - - padOsdService = new PadOsd.PadOsdService(); - xdndHandler = new XdndHandler.XdndHandler(); - ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager(); - osdWindowManager = new OsdWindow.OsdWindowManager(); - osdMonitorLabeler = new OsdMonitorLabeler.OsdMonitorLabeler(); - overview = new Overview.Overview(); - kbdA11yDialog = new KbdA11yDialog.KbdA11yDialog(); - wm = new WindowManager.WindowManager(); - magnifier = new Magnifier.Magnifier(); - locatePointer = new LocatePointer.LocatePointer(); - - if (LoginManager.canLock()) - screenShield = new ScreenShield.ScreenShield(); - - inputMethod = new InputMethod.InputMethod(); - Clutter.get_default_backend().set_input_method(inputMethod); - - messageTray = new MessageTray.MessageTray(); - panel = new Panel.Panel(); - keyboard = new Keyboard.KeyboardManager(); - notificationDaemon = new NotificationDaemon.NotificationDaemon(); - windowAttentionHandler = new WindowAttentionHandler.WindowAttentionHandler(); - componentManager = new Components.ComponentManager(); - - introspectService = new Introspect.IntrospectService(); - - layoutManager.init(); - overview.init(); - - new PointerA11yTimeout.PointerA11yTimeout(); - - global.connect('locate-pointer', () => { - locatePointer.show(); - }); - - global.display.connect('show-restart-message', (display, message) => { - showRestartMessage(message); - return true; - }); - - global.display.connect('restart', () => { - global.reexec_self(); - return true; - }); - - global.display.connect('gl-video-memory-purged', loadTheme); - - // Provide the bus object for gnome-session to - // initiate logouts. - EndSessionDialog.init(); - - // We're ready for the session manager to move to the next phase - GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { - Shell.util_sd_notify(); - global.context.notify_ready(); - return GLib.SOURCE_REMOVE; - }); - - _startDate = new Date(); - - ExtensionDownloader.init(); - extensionManager = new ExtensionSystem.ExtensionManager(); - extensionManager.init(); - if (sessionMode.isGreeter && screenShield) { - layoutManager.connect('startup-prepared', () => { - screenShield.showDialog(); + log('session updated...'); + } + + start() { + // These are here so we don't break compatibility. + global.logError = globalThis.log; + global.log = globalThis.log; + + // Chain up async errors reported from C + global.connect('notify-error', (global, msg, detail) => { + this.notifyError(msg, detail); }); + + let currentDesktop = GLib.getenv('XDG_CURRENT_DESKTOP'); + if (!currentDesktop || !currentDesktop.split(':').includes('GNOME')) + Gio.DesktopAppInfo.set_desktop_env('GNOME'); + + this.sessionMode = new SessionMode.SessionMode(); + this.sessionMode.connect('updated', this._sessionUpdated); + + St.Settings.get().connect('notify::gtk-theme', this._loadDefaultStylesheet); + + // Initialize ParentalControlsManager before the UI + ParentalControlsManager.getDefault(); + + this._initializeUI(); + + this.shellAccessDialogDBusService = new AccessDialog.AccessDialogDBus(); + this.shellAudioSelectionDBusService = new AudioDeviceSelection.AudioDeviceSelectionDBus(); + this.shellDBusService = new ShellDBus.GnomeShell(); + this.shellMountOpDBusService = new ShellMountOperation.GnomeShellMountOpHandler(); + + const watchId = Gio.DBus.session.watch_name('org.gnome.Shell.Notifications', + Gio.BusNameWatcherFlags.AUTO_START, + bus => bus.unwatch_name(watchId), + bus => bus.unwatch_name(watchId)); + + this._sessionUpdated(); + + log("Started..."); } - - layoutManager.connect('startup-complete', () => { - if (actionMode == Shell.ActionMode.NONE) - actionMode = Shell.ActionMode.NORMAL; - - if (screenShield) - screenShield.lockIfWasLocked(); - - if (sessionMode.currentMode != 'gdm' && - sessionMode.currentMode != 'initial-setup') { - GLib.log_structured(LOG_DOMAIN, GLib.LogLevelFlags.LEVEL_MESSAGE, { - 'MESSAGE': 'GNOME Shell started at %s'.format(_startDate), - 'MESSAGE_ID': GNOMESHELL_STARTED_MESSAGE_ID, + + _initializeUI() { + // Ensure ShellWindowTracker and ShellAppUsage are initialized; this will + // also initialize ShellAppSystem first. ShellAppSystem + // needs to load all the .desktop files, and ShellWindowTracker + // will use those to associate with windows. Right now + // the Monitor doesn't listen for installed app changes + // and recalculate application associations, so to avoid + // races for now we initialize it here. It's better to + // be predictable anyways. + Shell.WindowTracker.get_default(); + Shell.AppUsage.get_default(); + + this.reloadThemeResource(); + this._loadOskLayouts(); + this._loadDefaultStylesheet(); + + new AnimationsSettings(); + + // Setup the stage hierarchy early + this.layoutManager = new Layout.LayoutManager(); + + // Various parts of the codebase still refer to Main.uiGroup + // instead of using the layoutManager. This keeps that code + // working until it's updated. + this.uiGroup = this.layoutManager.uiGroup; + + this.padOsdService = new PadOsd.PadOsdService(); + this.xdndHandler = new XdndHandler.XdndHandler(); + this.ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager(); + this.osdWindowManager = new OsdWindow.OsdWindowManager(); + this.osdMonitorLabeler = new OsdMonitorLabeler.OsdMonitorLabeler(); + this.overview = new Overview.Overview(); + this.kbdA11yDialog = new KbdA11yDialog.KbdA11yDialog(); + this.wm = new WindowManager.WindowManager(); + this.magnifier = new Magnifier.Magnifier(); + this.locatePointer = new LocatePointer.LocatePointer(); + + if (LoginManager.canLock()) + this.screenShield = new ScreenShield.ScreenShield(); + + this.inputMethod = new InputMethod.InputMethod(); + Clutter.get_default_backend().set_input_method(this.inputMethod); + + this.messageTray = new MessageTray.MessageTray(); + this.panel = new Panel.Panel(); + this.keyboard = new Keyboard.KeyboardManager(); + this.notificationDaemon = new NotificationDaemon.NotificationDaemon(); + this.windowAttentionHandler = new WindowAttentionHandler.WindowAttentionHandler(); + this.componentManager = new Components.ComponentManager(); + + this.introspectService = new Introspect.IntrospectService(); + + this.layoutManager.init(); + this.overview.init(); + + new PointerA11yTimeout.PointerA11yTimeout(); + + global.connect('locate-pointer', () => { + this.locatePointer.show(); + }); + + global.display.connect('show-restart-message', (display, message) => { + showRestartMessage(message); + return true; + }); + + global.display.connect('restart', () => { + global.reexec_self(); + return true; + }); + + global.display.connect('gl-video-memory-purged', this.loadTheme); + + // Provide the bus object for gnome-session to + // initiate logouts. + EndSessionDialog.init(); + + // We're ready for the session manager to move to the next phase + GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { + log('>>>>'); + Shell.util_sd_notify(); + global.context.notify_ready(); + log('<<<<<') + return GLib.SOURCE_REMOVE; + }); + + this._startDate = new Date(); + + // ExtensionDownloader.init(); + // this.extensionManager = new ExtensionSystem.ExtensionManager(); + // this.extensionManager.init(); + + if (this.sessionMode.isGreeter && this.screenShield) { + this.layoutManager.connect('startup-prepared', () => { + log('Startup Prepared!'); + this.screenShield.showDialog(); }); } - - let credentials = new Gio.Credentials(); - if (credentials.get_unix_user() === 0) { - notify(_('Logged in as a privileged user'), - _('Running a session as a privileged user should be avoided for security reasons. If possible, you should log in as a normal user.')); - } else if (sessionMode.showWelcomeDialog) { - _handleShowWelcomeScreen(); - } - - if (sessionMode.currentMode !== 'gdm' && - sessionMode.currentMode !== 'initial-setup') - _handleLockScreenWarning(); - - LoginManager.registerSessionWithGDM(); - - let perfModuleName = GLib.getenv("SHELL_PERF_MODULE"); - if (perfModuleName) { - let perfOutput = GLib.getenv("SHELL_PERF_OUTPUT"); - let module = eval('imports.perf.%s;'.format(perfModuleName)); - Scripting.runPerfScript(module, perfOutput); + + this.layoutManager.connect('startup-complete', () => { + log('Startup Complete!'); + if (this.actionMode == Shell.ActionMode.NONE) + this.actionMode = Shell.ActionMode.NORMAL; + + if (this.screenShield) + this.screenShield.lockIfWasLocked(); + + if (this.sessionMode.currentMode != 'gdm' && + this.sessionMode.currentMode != 'initial-setup') { + GLib.log_structured(LOG_DOMAIN, GLib.LogLevelFlags.LEVEL_MESSAGE, { + 'MESSAGE': 'GNOME Shell started at %s'.format(this._startDate), + 'MESSAGE_ID': GNOMESHELL_STARTED_MESSAGE_ID, + }); + } + + let credentials = new Gio.Credentials(); + if (credentials.get_unix_user() === 0) { + this.notify(_('Logged in as a privileged user'), + _('Running a session as a privileged user should be avoided for security reasons. If possible, you should log in as a normal user.')); + } else if (this.sessionMode.showWelcomeDialog) { + this._handleShowWelcomeScreen(); + } + + if (this.sessionMode.currentMode !== 'gdm' && + this.sessionMode.currentMode !== 'initial-setup') + this._handleLockScreenWarning(); + + LoginManager.registerSessionWithGDM(); + + let perfModuleName = GLib.getenv("SHELL_PERF_MODULE"); + if (perfModuleName) { + let perfOutput = GLib.getenv("SHELL_PERF_OUTPUT"); + let module = eval('imports.perf.%s;'.format(perfModuleName)); + Scripting.runPerfScript(module, perfOutput); + } + }); + + log('done init...'); + } + + _handleShowWelcomeScreen() { + const lastShownVersion = global.settings.get_string(WELCOME_DIALOG_LAST_SHOWN_VERSION); + if (Util.GNOMEversionCompare(WELCOME_DIALOG_LAST_TOUR_CHANGE, lastShownVersion) > 0) { + this.openWelcomeDialog(); + global.settings.set_string(WELCOME_DIALOG_LAST_SHOWN_VERSION, Config.PACKAGE_VERSION); } - }); -} - -function _handleShowWelcomeScreen() { - const lastShownVersion = global.settings.get_string(WELCOME_DIALOG_LAST_SHOWN_VERSION); - if (Util.GNOMEversionCompare(WELCOME_DIALOG_LAST_TOUR_CHANGE, lastShownVersion) > 0) { - openWelcomeDialog(); - global.settings.set_string(WELCOME_DIALOG_LAST_SHOWN_VERSION, Config.PACKAGE_VERSION); } -} -async function _handleLockScreenWarning() { - const path = '%s/lock-warning-shown'.format(global.userdatadir); - const file = Gio.File.new_for_path(path); - - const hasLockScreen = screenShield !== null; - if (hasLockScreen) { - try { - await file.delete_async(0, null); - } catch (e) { - if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND)) + async _handleLockScreenWarning() { + const path = '%s/lock-warning-shown'.format(global.userdatadir); + const file = Gio.File.new_for_path(path); + + const hasLockScreen = this.screenShield !== null; + if (hasLockScreen) { + try { + await file.delete_async(0, null); + } catch (e) { + if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND)) + logError(e); + } + } else { + try { + if (!await file.touch_async()) + return; + } catch (e) { logError(e); - } - } else { - try { - if (!await file.touch_async()) - return; - } catch (e) { - logError(e); - } + } - notify( - _('Screen Lock disabled'), - _('Screen Locking requires the GNOME display manager.')); + this.notify( + _('Screen Lock disabled'), + _('Screen Locking requires the GNOME display manager.')); + } } -} + _getStylesheet(name) { + let stylesheet; -function _getStylesheet(name) { - let stylesheet; + stylesheet = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/%s'.format(name)); + if (stylesheet.query_exists(null)) + return stylesheet; - stylesheet = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/%s'.format(name)); - if (stylesheet.query_exists(null)) - return stylesheet; + let dataDirs = GLib.get_system_data_dirs(); + for (let i = 0; i < dataDirs.length; i++) { + let path = GLib.build_filenamev([dataDirs[i], 'gnome-shell', 'theme', name]); + stylesheet = Gio.file_new_for_path(path); + if (stylesheet.query_exists(null)) + return stylesheet; + } - let dataDirs = GLib.get_system_data_dirs(); - for (let i = 0; i < dataDirs.length; i++) { - let path = GLib.build_filenamev([dataDirs[i], 'gnome-shell', 'theme', name]); - stylesheet = Gio.file_new_for_path(path); + stylesheet = Gio.File.new_for_path('%s/theme/%s'.format(global.datadir, name)); if (stylesheet.query_exists(null)) return stylesheet; - } - stylesheet = Gio.File.new_for_path('%s/theme/%s'.format(global.datadir, name)); - if (stylesheet.query_exists(null)) - return stylesheet; + return null; + } - return null; -} + _getDefaultStylesheet() { + let stylesheet = null; + let name = this.sessionMode.stylesheetName; -function _getDefaultStylesheet() { - let stylesheet = null; - let name = sessionMode.stylesheetName; + // Look for a high-contrast variant first when using GTK+'s HighContrast + // theme + if (St.Settings.get().gtk_theme == 'HighContrast') + stylesheet = this._getStylesheet(name.replace('.css', '-high-contrast.css')); - // Look for a high-contrast variant first when using GTK+'s HighContrast - // theme - if (St.Settings.get().gtk_theme == 'HighContrast') - stylesheet = _getStylesheet(name.replace('.css', '-high-contrast.css')); + if (stylesheet == null) + stylesheet = this._getStylesheet(this.sessionMode.stylesheetName); - if (stylesheet == null) - stylesheet = _getStylesheet(sessionMode.stylesheetName); + return stylesheet; + } - return stylesheet; -} + _loadDefaultStylesheet() { + let stylesheet = this._getDefaultStylesheet(); + if (this._defaultCssStylesheet && this._defaultCssStylesheet.equal(stylesheet)) + return; -function _loadDefaultStylesheet() { - let stylesheet = _getDefaultStylesheet(); - if (_defaultCssStylesheet && _defaultCssStylesheet.equal(stylesheet)) - return; + this._defaultCssStylesheet = stylesheet; + this.loadTheme(); + } - _defaultCssStylesheet = stylesheet; - loadTheme(); -} + /** + * getThemeStylesheet: + * + * Get the theme CSS file that the shell will load + * + * @returns {?Gio.File}: A #GFile that contains the theme CSS, + * null if using the default + */ + getThemeStylesheet() { + return this._cssStylesheet; + } -/** - * getThemeStylesheet: - * - * Get the theme CSS file that the shell will load - * - * @returns {?Gio.File}: A #GFile that contains the theme CSS, - * null if using the default - */ -function getThemeStylesheet() { - return _cssStylesheet; -} -/** - * setThemeStylesheet: + /** + * setThemeStylesheet: * @param {string=} cssStylesheet: A file path that contains the theme CSS, * set it to null to use the default * * Set the theme CSS file that the shell will load */ -function setThemeStylesheet(cssStylesheet) { - _cssStylesheet = cssStylesheet ? Gio.File.new_for_path(cssStylesheet) : null; -} + setThemeStylesheet(cssStylesheet) { + this._cssStylesheet = cssStylesheet ? Gio.File.new_for_path(cssStylesheet) : null; + } -function reloadThemeResource() { - if (_themeResource) - _themeResource._unregister(); + reloadThemeResource() { + if (this._themeResource) + this._themeResource._unregister(); - _themeResource = Gio.Resource.load('%s/%s'.format(global.datadir, - sessionMode.themeResourceName)); - _themeResource._register(); -} + this._themeResource = Gio.Resource.load('%s/%s'.format(global.datadir, + this.sessionMode.themeResourceName)); + this._themeResource._register(); + } -function _loadOskLayouts() { - _oskResource = Gio.Resource.load('%s/gnome-shell-osk-layouts.gresource'.format(global.datadir)); - _oskResource._register(); -} + _loadOskLayouts() { + this._oskResource = Gio.Resource.load('%s/gnome-shell-osk-layouts.gresource'.format(global.datadir)); + this._oskResource._register(); + } -/** - * loadTheme: - * - * Reloads the theme CSS file - */ -function loadTheme() { - let themeContext = St.ThemeContext.get_for_stage(global.stage); - let previousTheme = themeContext.get_theme(); - let theme = new St.Theme({ - application_stylesheet: _cssStylesheet, - default_stylesheet: _defaultCssStylesheet, - }); + /** + * loadTheme: + * + * Reloads the theme CSS file + */ + loadTheme() { + let themeContext = St.ThemeContext.get_for_stage(global.stage); + let previousTheme = themeContext.get_theme(); - if (theme.default_stylesheet == null) - throw new Error("No valid stylesheet found for '%s'".format(sessionMode.stylesheetName)); + let theme = new St.Theme({ + application_stylesheet: this._cssStylesheet, + default_stylesheet: this._defaultCssStylesheet, + }); - if (previousTheme) { - let customStylesheets = previousTheme.get_custom_stylesheets(); + if (theme.default_stylesheet == null) + throw new Error("No valid stylesheet found for '%s'".format(this.sessionMode.stylesheetName)); - for (let i = 0; i < customStylesheets.length; i++) - theme.load_stylesheet(customStylesheets[i]); - } + if (previousTheme) { + let customStylesheets = previousTheme.get_custom_stylesheets(); - themeContext.set_theme(theme); -} + for (let i = 0; i < customStylesheets.length; i++) + theme.load_stylesheet(customStylesheets[i]); + } -/** - * notify: - * @param {string} msg: A message - * @param {string} details: Additional information - */ -function notify(msg, details) { - let source = new MessageTray.SystemNotificationSource(); - messageTray.add(source); - let notification = new MessageTray.Notification(source, msg, details); - notification.setTransient(true); - source.showNotification(notification); -} + themeContext.set_theme(theme); + } -/** - * notifyError: - * @param {string} msg: An error message - * @param {string} details: Additional information - * - * See shell_global_notify_problem(). - */ -function notifyError(msg, details) { - // Also print to stderr so it's logged somewhere - if (details) - log('error: %s: %s'.format(msg, details)); - else - log('error: %s'.format(msg)); - - notify(msg, details); -} + /** + * notify: + * @param {string} msg: A message + * @param {string} details: Additional information + */ + notify(msg, details) { + let source = new MessageTray.SystemNotificationSource(); + this.messageTray.add(source); + let notification = new MessageTray.Notification(source, msg, details); + notification.setTransient(true); + source.showNotification(notification); + } -function _findModal(actor) { - for (let i = 0; i < modalActorFocusStack.length; i++) { - if (modalActorFocusStack[i].actor == actor) - return i; + /** + * notifyError: + * @param {string} msg: An error message + * @param {string} details: Additional information + * + * See shell_global_notify_problem(). + */ + notifyError(msg, details) { + // Also print to stderr so it's logged somewhere + if (details) + log('error: %s: %s'.format(msg, details)); + else + log('error: %s'.format(msg)); + + this.notify(msg, details); } - return -1; -} -/** - * pushModal: - * @param {Clutter.Actor} actor: actor which will be given keyboard focus - * @param {Object=} params: optional parameters - * - * Ensure we are in a mode where all keyboard and mouse input goes to - * the stage, and focus @actor. Multiple calls to this function act in - * a stacking fashion; the effect will be undone when an equal number - * of popModal() invocations have been made. - * - * Next, record the current Clutter keyboard focus on a stack. If the - * modal stack returns to this actor, reset the focus to the actor - * which was focused at the time pushModal() was invoked. - * - * @params may be used to provide the following parameters: - * - timestamp: used to associate the call with a specific user initiated - * event. If not provided then the value of - * global.get_current_time() is assumed. - * - * - options: Meta.ModalOptions flags to indicate that the pointer is - * already grabbed - * - * - actionMode: used to set the current Shell.ActionMode to filter - * global keybindings; the default of NONE will filter - * out all keybindings - * - * @returns {bool}: true iff we successfully acquired a grab or already had one - */ -function pushModal(actor, params = {}) { - const { - timestamp = global.get_current_time(), - options = 0, - actionMode: modalActionMode = Shell.ActionMode.NONE, - } = params; - - if (modalCount == 0) { - if (!global.begin_modal(timestamp, options)) { - log('pushModal: invocation of begin_modal failed'); - return false; + _findModal(actor) { + for (let i = 0; i < this.modalActorFocusStack.length; i++) { + if (this.modalActorFocusStack[i].actor == actor) + return i; } - Meta.disable_unredirect_for_display(global.display); + return -1; } - modalCount += 1; - let actorDestroyId = actor.connect('destroy', () => { - let index = _findModal(actor); - if (index >= 0) - popModal(actor); - }); + /** + * pushModal: + * @param {Clutter.Actor} actor: actor which will be given keyboard focus + * @param {Object=} params: optional parameters + * + * Ensure we are in a mode where all keyboard and mouse input goes to + * the stage, and focus @actor. Multiple calls to this function act in + * a stacking fashion; the effect will be undone when an equal number + * of popModal() invocations have been made. + * + * Next, record the current Clutter keyboard focus on a stack. If the + * modal stack returns to this actor, reset the focus to the actor + * which was focused at the time pushModal() was invoked. + * + * @params may be used to provide the following parameters: + * - timestamp: used to associate the call with a specific user initiated + * event. If not provided then the value of + * global.get_current_time() is assumed. + * + * - options: Meta.ModalOptions flags to indicate that the pointer is + * already grabbed + * + * - actionMode: used to set the current Shell.ActionMode to filter + * global keybindings; the default of NONE will filter + * out all keybindings + * + * @returns {boolean}: true iff we successfully acquired a grab or already had one + */ + pushModal(actor, params) { + params = Params.parse(params, { + timestamp: global.get_current_time(), + options: 0, + actionMode: Shell.ActionMode.NONE + }); - let prevFocus = global.stage.get_key_focus(); - let prevFocusDestroyId; - if (prevFocus != null) { - prevFocusDestroyId = prevFocus.connect('destroy', () => { - const index = modalActorFocusStack.findIndex( - record => record.prevFocus === prevFocus); + if (this.modalCount == 0) { + if (!global.begin_modal(params.timestamp, params.options)) { + log('pushModal: invocation of begin_modal failed'); + return false; + } + Meta.disable_unredirect_for_display(global.display); + } + this.modalCount += 1; + let actorDestroyId = actor.connect('destroy', () => { + let index = this._findModal(actor); if (index >= 0) - modalActorFocusStack[index].prevFocus = null; + this.popModal(actor); }); - } - modalActorFocusStack.push({ actor, - destroyId: actorDestroyId, - prevFocus, - prevFocusDestroyId, - actionMode }); - - actionMode = modalActionMode; - global.stage.set_key_focus(actor); - return true; -} -/** - * popModal: - * @param {Clutter.Actor} actor: the actor passed to original invocation - * of pushModal() - * @param {number=} timestamp: optional timestamp - * - * Reverse the effect of pushModal(). If this invocation is undoing - * the topmost invocation, then the focus will be restored to the - * previous focus at the time when pushModal() was invoked. - * - * @timestamp is optionally used to associate the call with a specific user - * initiated event. If not provided then the value of - * global.get_current_time() is assumed. - */ -function popModal(actor, timestamp) { - if (timestamp == undefined) - timestamp = global.get_current_time(); + let prevFocus = global.stage.get_key_focus(); + let prevFocusDestroyId; + if (prevFocus != null) { + prevFocusDestroyId = prevFocus.connect('destroy', () => { + const index = this.modalActorFocusStack.findIndex( + record => record.prevFocus === prevFocus); - let focusIndex = _findModal(actor); - if (focusIndex < 0) { - global.stage.set_key_focus(null); - global.end_modal(timestamp); - actionMode = Shell.ActionMode.NORMAL; + if (index >= 0) + this.modalActorFocusStack[index].prevFocus = null; + }); + } + this.modalActorFocusStack.push({ + actor, + destroyId: actorDestroyId, + prevFocus, + prevFocusDestroyId, + actionMode: this.actionMode + }); - throw new Error('incorrect pop'); + this.actionMode = params.actionMode; + global.stage.set_key_focus(actor); + return true; } - modalCount -= 1; - - let record = modalActorFocusStack[focusIndex]; - record.actor.disconnect(record.destroyId); - - if (focusIndex == modalActorFocusStack.length - 1) { - if (record.prevFocus) - record.prevFocus.disconnect(record.prevFocusDestroyId); - actionMode = record.actionMode; - global.stage.set_key_focus(record.prevFocus); - } else { - // If we have: - // global.stage.set_focus(a); - // Main.pushModal(b); - // Main.pushModal(c); - // Main.pushModal(d); - // - // then we have the stack: - // [{ prevFocus: a, actor: b }, - // { prevFocus: b, actor: c }, - // { prevFocus: c, actor: d }] - // - // When actor c is destroyed/popped, if we only simply remove the - // record, then the focus stack will be [a, c], rather than the correct - // [a, b]. Shift the focus stack up before removing the record to ensure - // that we get the correct result. - let t = modalActorFocusStack[modalActorFocusStack.length - 1]; - if (t.prevFocus) - t.prevFocus.disconnect(t.prevFocusDestroyId); - // Remove from the middle, shift the focus chain up - for (let i = modalActorFocusStack.length - 1; i > focusIndex; i--) { - modalActorFocusStack[i].prevFocus = modalActorFocusStack[i - 1].prevFocus; - modalActorFocusStack[i].prevFocusDestroyId = modalActorFocusStack[i - 1].prevFocusDestroyId; - modalActorFocusStack[i].actionMode = modalActorFocusStack[i - 1].actionMode; + /** + * popModal: + * @param {Clutter.Actor} actor: the actor passed to original invocation + * of pushModal() + * @param {number=} timestamp: optional timestamp + * + * Reverse the effect of pushModal(). If this invocation is undoing + * the topmost invocation, then the focus will be restored to the + * previous focus at the time when pushModal() was invoked. + * + * @timestamp is optionally used to associate the call with a specific user + * initiated event. If not provided then the value of + * global.get_current_time() is assumed. + */ + popModal(actor, timestamp) { + if (timestamp == undefined) + timestamp = global.get_current_time(); + + let focusIndex = this._findModal(actor); + if (focusIndex < 0) { + global.stage.set_key_focus(null); + global.end_modal(timestamp); + this.actionMode = Shell.ActionMode.NORMAL; + + throw new Error('incorrect pop'); } - } - modalActorFocusStack.splice(focusIndex, 1); - if (modalCount > 0) - return; + this.modalCount -= 1; + + let record = this.modalActorFocusStack[focusIndex]; + record.actor.disconnect(record.destroyId); + + const { modalActorFocusStack } = this; + + if (focusIndex == this.modalActorFocusStack.length - 1) { + if (record.prevFocus) + record.prevFocus.disconnect(record.prevFocusDestroyId); + this.actionMode = record.actionMode; + global.stage.set_key_focus(record.prevFocus); + } else { + // If we have: + // global.stage.set_focus(a); + // Main.pushModal(b); + // Main.pushModal(c); + // Main.pushModal(d); + // + // then we have the stack: + // [{ prevFocus: a, actor: b }, + // { prevFocus: b, actor: c }, + // { prevFocus: c, actor: d }] + // + // When actor c is destroyed/popped, if we only simply remove the + // record, then the focus stack will be [a, c], rather than the correct + // [a, b]. Shift the focus stack up before removing the record to ensure + // that we get the correct result. + + let t = modalActorFocusStack[modalActorFocusStack.length - 1]; + if (t.prevFocus) + t.prevFocus.disconnect(t.prevFocusDestroyId); + // Remove from the middle, shift the focus chain up + for (let i = modalActorFocusStack.length - 1; i > focusIndex; i--) { + modalActorFocusStack[i].prevFocus = modalActorFocusStack[i - 1].prevFocus; + modalActorFocusStack[i].prevFocusDestroyId = modalActorFocusStack[i - 1].prevFocusDestroyId; + modalActorFocusStack[i].actionMode = modalActorFocusStack[i - 1].actionMode; + } + } + modalActorFocusStack.splice(focusIndex, 1); - layoutManager.modalEnded(); - global.end_modal(timestamp); - Meta.enable_unredirect_for_display(global.display); - actionMode = Shell.ActionMode.NORMAL; -} + if (this.modalCount > 0) + return; -function createLookingGlass() { - if (lookingGlass == null) - lookingGlass = new LookingGlass.LookingGlass(); + this.layoutManager.modalEnded(); + global.end_modal(timestamp); + Meta.enable_unredirect_for_display(global.display); + this.actionMode = Shell.ActionMode.NORMAL; + } - return lookingGlass; -} + createLookingGlass() { + if (this.lookingGlass == null) + this.lookingGlass = new LookingGlass.LookingGlass(); -function openRunDialog() { - if (runDialog == null) - runDialog = new RunDialog.RunDialog(); + return this.lookingGlass; + } - runDialog.open(); -} + openRunDialog() { + if (this.runDialog == null) + this.runDialog = new RunDialog.RunDialog(); -function openWelcomeDialog() { - if (welcomeDialog === null) - welcomeDialog = new WelcomeDialog.WelcomeDialog(); + this.runDialog.open(); + } - welcomeDialog.open(); -} + /** + * activateWindow: + * @param {Meta.Window} window: the window to activate + * @param {number=} time: current event time + * @param {number=} workspaceNum: window's workspace number + * + * Activates @window, switching to its workspace first if necessary, + * and switching out of the overview if it's currently active + */ + activateWindow(window, time, workspaceNum) { + let workspaceManager = global.workspace_manager; + let activeWorkspaceNum = workspaceManager.get_active_workspace_index(); + let windowWorkspaceNum = workspaceNum !== undefined ? workspaceNum : window.get_workspace().index(); + + if (!time) + time = global.get_current_time(); + + if (windowWorkspaceNum != activeWorkspaceNum) { + let workspace = workspaceManager.get_workspace_by_index(windowWorkspaceNum); + workspace.activate_with_focus(window, time); + } else { + window.activate(time); + } -/** - * activateWindow: - * @param {Meta.Window} window: the window to activate - * @param {number=} time: current event time - * @param {number=} workspaceNum: window's workspace number - * - * Activates @window, switching to its workspace first if necessary, - * and switching out of the overview if it's currently active - */ -function activateWindow(window, time, workspaceNum) { - let workspaceManager = global.workspace_manager; - let activeWorkspaceNum = workspaceManager.get_active_workspace_index(); - let windowWorkspaceNum = workspaceNum !== undefined ? workspaceNum : window.get_workspace().index(); - - if (!time) - time = global.get_current_time(); - - if (windowWorkspaceNum != activeWorkspaceNum) { - let workspace = workspaceManager.get_workspace_by_index(windowWorkspaceNum); - workspace.activate_with_focus(window, time); - } else { - window.activate(time); + this.overview.hide(); + this.panel.closeCalendar(); } - overview.hide(); - panel.closeCalendar(); -} + _deferredWorkData = {}; -// TODO - replace this timeout with some system to guess when the user might -// be e.g. just reading the screen and not likely to interact. -var DEFERRED_TIMEOUT_SECONDS = 20; -var _deferredWorkData = {}; -// Work scheduled for some point in the future -var _deferredWorkQueue = []; -// Work we need to process before the next redraw -var _beforeRedrawQueue = []; -// Counter to assign work ids -var _deferredWorkSequence = 0; -var _deferredTimeoutId = 0; - -function _runDeferredWork(workId) { - if (!_deferredWorkData[workId]) - return; - let index = _deferredWorkQueue.indexOf(workId); - if (index < 0) - return; - - _deferredWorkQueue.splice(index, 1); - _deferredWorkData[workId].callback(); - if (_deferredWorkQueue.length == 0 && _deferredTimeoutId > 0) { - GLib.source_remove(_deferredTimeoutId); - _deferredTimeoutId = 0; + // Work scheduled for some point in the future + _deferredWorkQueue = []; + // Work we need to process before the next redraw + _beforeRedrawQueue = []; + // Counter to assign work ids + _deferredWorkSequence = 0; + _deferredTimeoutId = 0; + + _runDeferredWork(workId) { + if (!this._deferredWorkData[workId]) + return; + let index = this._deferredWorkQueue.indexOf(workId); + if (index < 0) + return; + + this._deferredWorkQueue.splice(index, 1); + this._deferredWorkData[workId].callback(); + if (this._deferredWorkQueue.length == 0 && this._deferredTimeoutId > 0) { + GLib.source_remove(this._deferredTimeoutId); + this._deferredTimeoutId = 0; + } } -} -function _runAllDeferredWork() { - while (_deferredWorkQueue.length > 0) - _runDeferredWork(_deferredWorkQueue[0]); -} + _runAllDeferredWork() { + while (this._deferredWorkQueue.length > 0) + this._runDeferredWork(this._deferredWorkQueue[0]); + } -function _runBeforeRedrawQueue() { - for (let i = 0; i < _beforeRedrawQueue.length; i++) { - let workId = _beforeRedrawQueue[i]; - _runDeferredWork(workId); + _runBeforeRedrawQueue() { + for (let i = 0; i < this._beforeRedrawQueue.length; i++) { + let workId = this._beforeRedrawQueue[i]; + this._runDeferredWork(workId); + } + this._beforeRedrawQueue = []; + } + + _queueBeforeRedraw(workId) { + this._beforeRedrawQueue.push(workId); + if (this._beforeRedrawQueue.length == 1) { + Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { + this._runBeforeRedrawQueue(); + return false; + }); + } } - _beforeRedrawQueue = []; -} -function _queueBeforeRedraw(workId) { - _beforeRedrawQueue.push(workId); - if (_beforeRedrawQueue.length == 1) { - Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { - _runBeforeRedrawQueue(); - return false; + /** + * initializeDeferredWork: + * @param {Clutter.Actor} actor: an actor + * @param {(...args: any[]) => any} callback: Function to invoke to perform work + * + * This function sets up a callback to be invoked when either the + * given actor is mapped, or after some period of time when the machine + * is idle. This is useful if your actor isn't always visible on the + * screen (for example, all actors in the overview), and you don't want + * to consume resources updating if the actor isn't actually going to be + * displaying to the user. + * + * Note that queueDeferredWork is called by default immediately on + * initialization as well, under the assumption that new actors + * will need it. + * + * @returns {string}: A string work identifier + */ + initializeDeferredWork(actor, callback) { + // Turn into a string so we can use as an object property + let workId = (++this._deferredWorkSequence).toString(); + this._deferredWorkData[workId] = { + actor, + callback + }; + actor.connect('notify::mapped', () => { + if (!(actor.mapped && this._deferredWorkQueue.includes(workId))) + return; + this._queueBeforeRedraw(workId); }); + actor.connect('destroy', () => { + let index = this._deferredWorkQueue.indexOf(workId); + if (index >= 0) + this._deferredWorkQueue.splice(index, 1); + delete this._deferredWorkData[workId]; + }); + this.queueDeferredWork(workId); + return workId; } -} -/** - * initializeDeferredWork: - * @param {Clutter.Actor} actor: an actor - * @param {callback} callback: Function to invoke to perform work - * - * This function sets up a callback to be invoked when either the - * given actor is mapped, or after some period of time when the machine - * is idle. This is useful if your actor isn't always visible on the - * screen (for example, all actors in the overview), and you don't want - * to consume resources updating if the actor isn't actually going to be - * displaying to the user. - * - * Note that queueDeferredWork is called by default immediately on - * initialization as well, under the assumption that new actors - * will need it. - * - * @returns {string}: A string work identifier - */ -function initializeDeferredWork(actor, callback) { - // Turn into a string so we can use as an object property - let workId = (++_deferredWorkSequence).toString(); - _deferredWorkData[workId] = { actor, - callback }; - actor.connect('notify::mapped', () => { - if (!(actor.mapped && _deferredWorkQueue.includes(workId))) + /** + * queueDeferredWork: + * @param {string} workId: work identifier + * + * Ensure that the work identified by @workId will be + * run on map or timeout. You should call this function + * for example when data being displayed by the actor has + * changed. + */ + queueDeferredWork(workId) { + let data = this._deferredWorkData[workId]; + if (!data) { + let message = 'Invalid work id %s'.format(workId); + logError(new Error(message), message); return; - _queueBeforeRedraw(workId); - }); - actor.connect('destroy', () => { - let index = _deferredWorkQueue.indexOf(workId); - if (index >= 0) - _deferredWorkQueue.splice(index, 1); - delete _deferredWorkData[workId]; - }); - queueDeferredWork(workId); - return workId; -} - -/** - * queueDeferredWork: - * @param {string} workId: work identifier - * - * Ensure that the work identified by @workId will be - * run on map or timeout. You should call this function - * for example when data being displayed by the actor has - * changed. - */ -function queueDeferredWork(workId) { - let data = _deferredWorkData[workId]; - if (!data) { - let message = 'Invalid work id %d'.format(workId); - logError(new Error(message), message); - return; + } + if (!this._deferredWorkQueue.includes(workId)) + this._deferredWorkQueue.push(workId); + if (data.actor.mapped) { + this._queueBeforeRedraw(workId); + } else if (this._deferredTimeoutId == 0) { + this._deferredTimeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, DEFERRED_TIMEOUT_SECONDS, () => { + this._runAllDeferredWork(); + this._deferredTimeoutId = 0; + return GLib.SOURCE_REMOVE; + }); + GLib.Source.set_name_by_id(this._deferredTimeoutId, '[gnome-shell] _runAllDeferredWork'); + } } - if (!_deferredWorkQueue.includes(workId)) - _deferredWorkQueue.push(workId); - if (data.actor.mapped) { - _queueBeforeRedraw(workId); - } else if (_deferredTimeoutId == 0) { - _deferredTimeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, DEFERRED_TIMEOUT_SECONDS, () => { - _runAllDeferredWork(); - _deferredTimeoutId = 0; - return GLib.SOURCE_REMOVE; - }); - GLib.Source.set_name_by_id(_deferredTimeoutId, '[gnome-shell] _runAllDeferredWork'); + + openWelcomeDialog() { + if (this.welcomeDialog === null) + this.welcomeDialog = new WelcomeDialog.WelcomeDialog(); + + this.welcomeDialog.open(); } +}; + +export const main = new Main(); + +ExtensionUtils._setMain(main); + +globalThis.getMain = function getMain() { + return main; } -var RestartMessage = GObject.registerClass( -class RestartMessage extends ModalDialog.ModalDialog { - _init(message) { - super._init({ shellReactive: true, - styleClass: 'restart-message headline', - shouldFadeIn: false, - destroyOnClose: true }); - - let label = new St.Label({ - text: message, - x_align: Clutter.ActorAlign.CENTER, - y_align: Clutter.ActorAlign.CENTER, - }); +export default main; - this.contentLayout.add_child(label); - this.buttonLayout.hide(); - } -}); +Gio._promisify(Gio._LocalFilePrototype, 'delete_async', 'delete_finish'); +Gio._promisify(Gio._LocalFilePrototype, 'touch_async', 'touch_finish'); + +// TODO - replace this timeout with some system to guess when the user might +// be e.g. just reading the screen and not likely to interact. +export const DEFERRED_TIMEOUT_SECONDS = 20; + +export const RestartMessage = GObject.registerClass( + class RestartMessage extends ModalDialog.ModalDialog { + _init(message) { + super._init({ + shellReactive: true, + styleClass: 'restart-message headline', + shouldFadeIn: false, + destroyOnClose: true + }); + + let label = new St.Label({ + text: message, + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.CENTER, + }); + + this.contentLayout.add_child(label); + this.buttonLayout.hide(); + } + }); function showRestartMessage(message) { let restartMessage = new RestartMessage(message); restartMessage.open(); } -var AnimationsSettings = class { +export class AnimationsSettings { constructor() { let backend = global.backend; if (!backend.is_rendering_hardware_accelerated()) { diff --git a/js/ui/messageList.js b/js/ui/messageList.js index c68ab77fd..879fa3716 100644 --- a/js/ui/messageList.js +++ b/js/ui/messageList.js @@ -1,14 +1,22 @@ /* exported MessageListSection */ -const { Atk, Clutter, Gio, GLib, - GObject, Graphene, Meta, Pango, St } = imports.gi; -const Main = imports.ui.main; -const MessageTray = imports.ui.messageTray; +import Atk from 'gi://Atk'; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Graphene from 'gi://Graphene'; +import Meta from 'gi://Meta'; +import Pango from 'gi://Pango'; +import St from 'gi://St'; -const Util = imports.misc.util; +import Main from './main.js'; +import * as MessageTray from './messageTray.js'; -var MESSAGE_ANIMATION_TIME = 100; +import * as Util from '../misc/util.js'; -var DEFAULT_EXPAND_LINES = 6; +export let MESSAGE_ANIMATION_TIME = 100; + +export let DEFAULT_EXPAND_LINES = 6; function _fixMarkup(text, allowMarkup) { if (allowMarkup) { @@ -31,7 +39,7 @@ function _fixMarkup(text, allowMarkup) { return GLib.markup_escape_text(text, -1); } -var URLHighlighter = GObject.registerClass( +export const URLHighlighter = GObject.registerClass( class URLHighlighter extends St.Label { _init(text = '', lineWrap, allowMarkup) { super._init({ @@ -159,7 +167,7 @@ class URLHighlighter extends St.Label { } }); -var ScaleLayout = GObject.registerClass( +export const ScaleLayout = GObject.registerClass( class ScaleLayout extends Clutter.BinLayout { _init(params) { this._container = null; @@ -188,6 +196,9 @@ class ScaleLayout extends Clutter.BinLayout { } } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_width(container, forHeight) { this._connectContainer(container); @@ -196,6 +207,9 @@ class ScaleLayout extends Clutter.BinLayout { Math.floor(nat * container.scale_x)]; } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_height(container, forWidth) { this._connectContainer(container); @@ -205,7 +219,7 @@ class ScaleLayout extends Clutter.BinLayout { } }); -var LabelExpanderLayout = GObject.registerClass({ +export const LabelExpanderLayout = GObject.registerClass({ Properties: { 'expansion': GObject.ParamSpec.double('expansion', 'Expansion', @@ -215,6 +229,9 @@ var LabelExpanderLayout = GObject.registerClass({ 0, 1, 0), }, }, class LabelExpanderLayout extends Clutter.LayoutManager { + /** + * @param {*} params + */ _init(params) { this._expansion = 0; this._expandLines = DEFAULT_EXPAND_LINES; @@ -251,6 +268,9 @@ var LabelExpanderLayout = GObject.registerClass({ this._container = container; } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_width(container, forHeight) { let [min, nat] = [0, 0]; @@ -266,6 +286,9 @@ var LabelExpanderLayout = GObject.registerClass({ return [min, nat]; } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_height(container, forWidth) { let [min, nat] = [0, 0]; @@ -295,13 +318,17 @@ var LabelExpanderLayout = GObject.registerClass({ }); -var Message = GObject.registerClass({ +export const Message = GObject.registerClass({ Signals: { 'close': {}, 'expanded': {}, 'unexpanded': {}, }, }, class Message extends St.Button { + /** + * @param {*} title + * @param {*} body + */ _init(title, body) { super._init({ style_class: 'message', @@ -313,6 +340,7 @@ var Message = GObject.registerClass({ this.expanded = false; this._useBodyMarkup = false; + this.notification = null; let vbox = new St.BoxLayout({ vertical: true, @@ -361,11 +389,11 @@ var Message = GObject.registerClass({ }); titleBox.add_actor(this._closeButton); - this._bodyStack = new St.Widget({ x_expand: true }); - this._bodyStack.layout_manager = new LabelExpanderLayout(); + this._bodyStack = new St.Widget({ x_expand: true, layout_manager: new LabelExpanderLayout() }); + contentBox.add_actor(this._bodyStack); - this.bodyLabel = new URLHighlighter('', false, this._useBodyMarkup); + this.bodyLabel = new URLHighlighter({ text: '', lineWrap: false, allowMarkup: this._useBodyMarkup }); this.bodyLabel.add_style_class_name('message-body'); this._bodyStack.add_actor(this.bodyLabel); this.setBody(body); @@ -532,7 +560,7 @@ var Message = GObject.registerClass({ } }); -var MessageListSection = GObject.registerClass({ +export const MessageListSection = GObject.registerClass({ Properties: { 'can-clear': GObject.ParamSpec.boolean( 'can-clear', 'can-clear', 'can-clear', @@ -557,6 +585,7 @@ var MessageListSection = GObject.registerClass({ x_expand: true, }); + /** @type {St.BoxLayout<St.Bin<Message["prototype"]>>} */ this._list = new St.BoxLayout({ style_class: 'message-list-section-list', vertical: true }); this.add_actor(this._list); diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js index 7596f105f..fb32e52ca 100644 --- a/js/ui/messageTray.js +++ b/js/ui/messageTray.js @@ -3,32 +3,38 @@ NotificationApplicationPolicy, Source, SourceActor, SystemNotificationSource, MessageTray */ -const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi; - -const Calendar = imports.ui.calendar; -const GnomeSession = imports.misc.gnomeSession; -const Layout = imports.ui.layout; -const Main = imports.ui.main; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; + +import * as Calendar from './calendar.js'; +import * as GnomeSession from '../misc/gnomeSession.js'; +import * as Layout from './layout.js'; +import Main from './main.js'; const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings'; -var ANIMATION_TIME = 200; -var NOTIFICATION_TIMEOUT = 4000; +export let ANIMATION_TIME = 200; +export let NOTIFICATION_TIMEOUT = 4000; -var HIDE_TIMEOUT = 200; -var LONGER_HIDE_TIMEOUT = 600; +export let HIDE_TIMEOUT = 200; +export let LONGER_HIDE_TIMEOUT = 600; -var MAX_NOTIFICATIONS_IN_QUEUE = 3; -var MAX_NOTIFICATIONS_PER_SOURCE = 3; -var MAX_NOTIFICATION_BUTTONS = 3; +export let MAX_NOTIFICATIONS_IN_QUEUE = 3; +export let MAX_NOTIFICATIONS_PER_SOURCE = 3; +export let MAX_NOTIFICATION_BUTTONS = 3; // We delay hiding of the tray if the mouse is within MOUSE_LEFT_ACTOR_THRESHOLD // range from the point where it left the tray. -var MOUSE_LEFT_ACTOR_THRESHOLD = 20; +export let MOUSE_LEFT_ACTOR_THRESHOLD = 20; -var IDLE_TIME = 1000; +export let IDLE_TIME = 1000; -var State = { +export const State = { HIDDEN: 0, SHOWING: 1, SHOWN: 2, @@ -42,7 +48,8 @@ var State = { // notifications that were requested to be destroyed by the associated source, // and REPLACED for notifications that were destroyed as a consequence of a // newer version having replaced them. -var NotificationDestroyedReason = { +/** @enum {number} */ +export const NotificationDestroyedReason = { EXPIRED: 1, DISMISSED: 2, SOURCE_CLOSED: 3, @@ -53,7 +60,8 @@ var NotificationDestroyedReason = { // urgency values map to the corresponding values for the notifications received // through the notification daemon. HIGH urgency value is used for chats received // through the Telepathy client. -var Urgency = { +/** @enum {number} */ +export const Urgency = { LOW: 0, NORMAL: 1, HIGH: 2, @@ -66,12 +74,13 @@ var Urgency = { // contain information private to the physical system (for example, battery // status) and hence the same for every user. This affects whether the content // of a notification is shown on the lock screen. -var PrivacyScope = { +/** @enum {number} */ +export const PrivacyScope = { USER: 0, SYSTEM: 1, }; -var FocusGrabber = class FocusGrabber { +export class FocusGrabber { constructor(actor) { this._actor = actor; this._prevKeyFocusActor = null; @@ -132,7 +141,7 @@ var FocusGrabber = class FocusGrabber { // source, such as whether to play sound or honour the critical bit. // // A notification without a policy object will inherit the default one. -var NotificationPolicy = GObject.registerClass({ +export const NotificationPolicy = GObject.registerClass({ GTypeFlags: GObject.TypeFlags.ABSTRACT, Properties: { 'enable': GObject.ParamSpec.boolean( @@ -187,7 +196,7 @@ var NotificationPolicy = GObject.registerClass({ } }); -var NotificationGenericPolicy = GObject.registerClass({ +export const NotificationGenericPolicy = GObject.registerClass({ }, class NotificationGenericPolicy extends NotificationPolicy { _init() { super._init(); @@ -204,6 +213,7 @@ var NotificationGenericPolicy = GObject.registerClass({ } _changed(settings, key) { + // @ts-expect-error this.constructor cannot be statically analyzed. if (this.constructor.find_property(key)) this.notify(key); } @@ -217,7 +227,7 @@ var NotificationGenericPolicy = GObject.registerClass({ } }); -var NotificationApplicationPolicy = GObject.registerClass({ +export const NotificationApplicationPolicy = GObject.registerClass({ }, class NotificationApplicationPolicy extends NotificationPolicy { _init(id) { super._init(); @@ -253,6 +263,8 @@ var NotificationApplicationPolicy = GObject.registerClass({ } _changed(settings, key) { + // FIXME + // @ts-expect-error if (this.constructor.find_property(key)) this.notify(key); } @@ -345,7 +357,7 @@ var NotificationApplicationPolicy = GObject.registerClass({ // @source allows playing sounds). // // [1] https://developer.gnome.org/notification-spec/#markup -var Notification = GObject.registerClass({ +export const Notification = GObject.registerClass({ Properties: { 'acknowledged': GObject.ParamSpec.boolean( 'acknowledged', 'acknowledged', 'acknowledged', @@ -358,6 +370,12 @@ var Notification = GObject.registerClass({ 'updated': { param_types: [GObject.TYPE_BOOLEAN] }, }, }, class Notification extends GObject.Object { + /** + * @param {*} source + * @param {*} title + * @param {*} banner + * @param {*} params + */ _init(source, title, banner, params) { super._init(); @@ -376,6 +394,8 @@ var Notification = GObject.registerClass({ this.actions = []; this.setResident(false); + this.answered = false; + // If called with only one argument we assume the caller // will call .update() later on. This is the case of // NotificationDaemon, which wants to use the same code @@ -507,7 +527,7 @@ var Notification = GObject.registerClass({ } }); -var NotificationBanner = GObject.registerClass({ +export const NotificationBanner = GObject.registerClass({ Signals: { 'done-displaying': {}, 'unfocused': {}, @@ -606,7 +626,7 @@ var NotificationBanner = GObject.registerClass({ } }); -var SourceActor = GObject.registerClass( +export const SourceActor = GObject.registerClass( class SourceActor extends St.Widget { _init(source, size) { super._init(); @@ -645,7 +665,13 @@ class SourceActor extends St.Widget { } }); -var Source = GObject.registerClass({ +/** + * @typedef {object} SourceParams + * @property {string} [title] + * @property {string} [iconName] + */ + +export const Source = GObject.registerClass({ Properties: { 'count': GObject.ParamSpec.int( 'count', 'count', 'count', @@ -707,6 +733,9 @@ var Source = GObject.registerClass({ this.notify('count'); } + /** + * @returns {NotificationPolicy["prototype"]} + */ _createPolicy() { return new NotificationGenericPolicy(); } @@ -737,6 +766,9 @@ var Source = GObject.registerClass({ icon_size: size }); } + /** + * @returns {Gio.Icon} + */ getIcon() { return new Gio.ThemedIcon({ name: this.iconName }); } @@ -808,7 +840,7 @@ var Source = GObject.registerClass({ } }); -var MessageTray = GObject.registerClass({ +export const MessageTray = GObject.registerClass({ Signals: { 'queue-changed': {}, 'source-added': { param_types: [Source.$gtype] }, @@ -822,7 +854,7 @@ var MessageTray = GObject.registerClass({ layout_manager: new Clutter.BinLayout(), }); - this._presence = new GnomeSession.Presence((proxy, _error) => { + this._presence = GnomeSession.Presence((proxy, _error) => { this._onStatusChanged(proxy.status); }); this._busy = false; @@ -1438,10 +1470,10 @@ var MessageTray = GObject.registerClass({ } }); -var SystemNotificationSource = GObject.registerClass( +export const SystemNotificationSource = GObject.registerClass( class SystemNotificationSource extends Source { _init() { - super._init(_("System Information"), 'dialog-information-symbolic'); + super._init({ title: _("System Information"), iconName: 'dialog-information-symbolic' }); } open() { diff --git a/js/ui/modalDialog.js b/js/ui/modalDialog.js index 10fe90310..74b538373 100644 --- a/js/ui/modalDialog.js +++ b/js/ui/modalDialog.js @@ -1,17 +1,22 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported ModalDialog */ -const { Atk, Clutter, GObject, Shell, St } = imports.gi; - -const Dialog = imports.ui.dialog; -const Layout = imports.ui.layout; -const Lightbox = imports.ui.lightbox; -const Main = imports.ui.main; - -var OPEN_AND_CLOSE_TIME = 100; -var FADE_OUT_DIALOG_TIME = 1000; - -var State = { +import Atk from 'gi://Atk'; +import Clutter from 'gi://Clutter'; +import GObject from 'gi://GObject'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; + +import * as Dialog from './dialog.js'; +import * as Layout from './layout.js'; +import * as Lightbox from './lightbox.js'; +import Main from './main.js'; + +export let OPEN_AND_CLOSE_TIME = 100; +export let FADE_OUT_DIALOG_TIME = 1000; + +/** @enum {number} */ +export const State = { OPENED: 0, CLOSED: 1, OPENING: 2, @@ -19,7 +24,17 @@ var State = { FADED_OUT: 4, }; -var ModalDialog = GObject.registerClass({ +/** + * @typedef {object} ModalDialogParams + * @property {boolean} shellReactive + * @property {string | null} styleClass + * @property {Shell.ActionMode} actionMode + * @property {boolean} shouldFadeIn + * @property {boolean} shouldFadeOut + * @property {boolean} destroyOnClose + */ + +export const ModalDialog = GObject.registerClass({ Properties: { 'state': GObject.ParamSpec.int('state', 'Dialog state', 'state', GObject.ParamFlags.READABLE, @@ -29,7 +44,7 @@ var ModalDialog = GObject.registerClass({ }, Signals: { 'opened': {}, 'closed': {} }, }, class ModalDialog extends St.Widget { - _init(params = {}) { + _init(params = {}, ...args) { super._init({ visible: false, x: 0, y: 0, @@ -258,7 +273,7 @@ var ModalDialog = GObject.registerClass({ opacity: 0, duration: FADE_OUT_DIALOG_TIME, mode: Clutter.AnimationMode.EASE_OUT_QUAD, - onComplete: () => (this.state = State.FADED_OUT), + onComplete: () => (this._setState(State.FADED_OUT)), }); } }); diff --git a/js/ui/mpris.js b/js/ui/mpris.js index 6038142c6..98e3b8c0a 100644 --- a/js/ui/mpris.js +++ b/js/ui/mpris.js @@ -1,11 +1,14 @@ /* exported MediaSection */ -const { Gio, GObject, Shell, St } = imports.gi; -const Signals = imports.misc.signals; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; +import * as Signals from '../misc/signals.js'; -const Main = imports.ui.main; -const MessageList = imports.ui.messageList; +import Main from './main.js'; +import * as MessageList from './messageList.js'; -const { loadInterfaceXML } = imports.misc.fileUtils; +import { loadInterfaceXML } from '../misc/fileUtilsModule.js'; const DBusIface = loadInterfaceXML('org.freedesktop.DBus'); const DBusProxy = Gio.DBusProxy.makeProxyWrapper(DBusIface); @@ -18,8 +21,11 @@ const MprisPlayerProxy = Gio.DBusProxy.makeProxyWrapper(MprisPlayerIface); const MPRIS_PLAYER_PREFIX = 'org.mpris.MediaPlayer2.'; -var MediaMessage = GObject.registerClass( +export const MediaMessage = GObject.registerClass( class MediaMessage extends MessageList.Message { + /** + * @param {*} player + */ _init(player) { super._init('', ''); @@ -93,7 +99,7 @@ class MediaMessage extends MessageList.Message { } }); -var MprisPlayer = class MprisPlayer extends Signals.EventEmitter { +export class MprisPlayer extends Signals.EventEmitter { constructor(busName) { super(); @@ -243,7 +249,7 @@ var MprisPlayer = class MprisPlayer extends Signals.EventEmitter { } }; -var MediaSection = GObject.registerClass( +export const MediaSection = GObject.registerClass( class MediaSection extends MessageList.MessageListSection { _init() { super._init(); diff --git a/js/ui/notificationDaemon.js b/js/ui/notificationDaemon.js index e75c2fdc0..e4de618da 100644 --- a/js/ui/notificationDaemon.js +++ b/js/ui/notificationDaemon.js @@ -1,30 +1,37 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported NotificationDaemon */ -const { GdkPixbuf, Gio, GLib, GObject, Shell, St } = imports.gi; +import GdkPixbuf from 'gi://GdkPixbuf'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; const Config = imports.misc.config; -const Main = imports.ui.main; -const MessageTray = imports.ui.messageTray; +import Main from './main.js'; +import * as MessageTray from './messageTray.js'; -const { loadInterfaceXML } = imports.misc.fileUtils; +import { loadInterfaceXML } from '../misc/fileUtilsModule.js'; const FdoNotificationsIface = loadInterfaceXML('org.freedesktop.Notifications'); -var NotificationClosedReason = { +/** @enum {number} */ +export const NotificationClosedReason = { EXPIRED: 1, DISMISSED: 2, APP_CLOSED: 3, UNDEFINED: 4, }; -var Urgency = { +/** @enum {number} */ +export const Urgency = { LOW: 0, NORMAL: 1, CRITICAL: 2, }; -var FdoNotificationDaemon = class FdoNotificationDaemon { +export class FdoNotificationDaemon { constructor() { this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(FdoNotificationsIface, this); this._dbusImpl.export(Gio.DBus.session, '/org/freedesktop/Notifications'); @@ -187,7 +194,8 @@ var FdoNotificationDaemon = class FdoNotificationDaemon { let sender = invocation.get_sender(); let pid = hints['sender-pid']; - let source = this._getSource(appName, pid, ndata, sender, null); + // FIXME + let source = this._getSource(appName, pid, ndata, sender); this._notifyForSource(source, ndata); return invocation.return_value(GLib.Variant.new('(u)', [id])); @@ -342,7 +350,15 @@ var FdoNotificationDaemon = class FdoNotificationDaemon { } }; -var FdoNotificationDaemonSource = GObject.registerClass( +/** + * @typedef {object} FdoNotificationDaemonSourceParams + * @property {string} title + * @property {number} pid + * @property {string} sender + * @property {string} appId + */ + +export const FdoNotificationDaemonSource = GObject.registerClass( class FdoNotificationDaemonSource extends MessageTray.Source { _init(title, pid, sender, appId) { this.pid = pid; @@ -466,8 +482,12 @@ const PRIORITY_URGENCY_MAP = { urgent: MessageTray.Urgency.CRITICAL, }; -var GtkNotificationDaemonNotification = GObject.registerClass( +export const GtkNotificationDaemonNotification = GObject.registerClass( class GtkNotificationDaemonNotification extends MessageTray.Notification { + /** + * @param {*} source + * @param {*} notification + */ _init(source, notification) { super._init(source); this._serialized = GLib.Variant.new('a{sv}', notification); @@ -542,14 +562,19 @@ function objectPathFromAppId(appId) { return '/' + appId.replace(/\./g, '/').replace(/-/g, '_'); } -function getPlatformData() { - let startupId = GLib.Variant.new('s', '_TIME%s'.format(global.get_current_time())); +export function getPlatformData() { + let startupId = GLib.Variant.new('s', '_TIME%d'.format(global.get_current_time())); return { "desktop-startup-id": startupId }; } function InvalidAppError() {} -var GtkNotificationDaemonAppSource = GObject.registerClass( +/** + * @typedef {object} GtkNotificationDaemonAppSourceParams + * @property {string} appId + */ + +export const GtkNotificationDaemonAppSource = GObject.registerClass( class GtkNotificationDaemonAppSource extends MessageTray.Source { _init(appId) { let objectPath = objectPathFromAppId(appId); @@ -651,7 +676,7 @@ class GtkNotificationDaemonAppSource extends MessageTray.Source { const GtkNotificationsIface = loadInterfaceXML('org.gtk.Notifications'); -var GtkNotificationDaemon = class GtkNotificationDaemon { +export class GtkNotificationDaemon { constructor() { this._sources = {}; @@ -756,7 +781,7 @@ var GtkNotificationDaemon = class GtkNotificationDaemon { } }; -var NotificationDaemon = class NotificationDaemon { +export class NotificationDaemon { constructor() { this._fdoNotificationDaemon = new FdoNotificationDaemon(); this._gtkNotificationDaemon = new GtkNotificationDaemon(); diff --git a/js/ui/osdMonitorLabeler.js b/js/ui/osdMonitorLabeler.js index b242ecca1..80099440c 100644 --- a/js/ui/osdMonitorLabeler.js +++ b/js/ui/osdMonitorLabeler.js @@ -1,11 +1,16 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported OsdMonitorLabeler */ -const { Clutter, Gio, GObject, Meta, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import St from 'gi://St'; -const Main = imports.ui.main; -var OsdMonitorLabel = GObject.registerClass( +import Main from './main.js'; + +export const OsdMonitorLabel = GObject.registerClass( class OsdMonitorLabel extends St.Widget { _init(monitor, label) { super._init({ x_expand: true, y_expand: true }); @@ -42,7 +47,7 @@ class OsdMonitorLabel extends St.Widget { } }); -var OsdMonitorLabeler = class { +export class OsdMonitorLabeler { constructor() { this._monitorManager = Meta.MonitorManager.get(); this._client = null; diff --git a/js/ui/osdWindow.js b/js/ui/osdWindow.js index bae4b862a..8f98c549b 100644 --- a/js/ui/osdWindow.js +++ b/js/ui/osdWindow.js @@ -1,17 +1,21 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported OsdWindowManager */ -const { Clutter, GLib, GObject, Meta, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import St from 'gi://St'; -const BarLevel = imports.ui.barLevel; -const Layout = imports.ui.layout; -const Main = imports.ui.main; +import * as BarLevel from './barLevel.js'; +import * as Layout from './layout.js'; +import Main from './main.js'; -var HIDE_TIMEOUT = 1500; -var FADE_TIME = 100; -var LEVEL_ANIMATION_TIME = 100; +export let HIDE_TIMEOUT = 1500; +export let FADE_TIME = 100; +export let LEVEL_ANIMATION_TIME = 100; -var OsdWindowConstraint = GObject.registerClass( +export const OsdWindowConstraint = GObject.registerClass( class OsdWindowConstraint extends Clutter.Constraint { _init(props) { this._minSize = 0; @@ -41,7 +45,7 @@ class OsdWindowConstraint extends Clutter.Constraint { } }); -var OsdWindow = GObject.registerClass( +export const OsdWindow = GObject.registerClass( class OsdWindow extends St.Widget { _init(monitorIndex) { super._init({ @@ -199,7 +203,7 @@ class OsdWindow extends St.Widget { } }); -var OsdWindowManager = class { +export class OsdWindowManager { constructor() { this._osdWindows = []; Main.layoutManager.connect('monitors-changed', diff --git a/js/ui/overview.js b/js/ui/overview.js index 176931c61..31d8f82e2 100644 --- a/js/ui/overview.js +++ b/js/ui/overview.js @@ -1,27 +1,33 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Overview */ -const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi; -const Signals = imports.misc.signals; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; +import * as Signals from '../misc/signals.js'; // Time for initial animation going into Overview mode; // this is defined here to make it available in imports. -var ANIMATION_TIME = 250; +export let ANIMATION_TIME = 250; -const DND = imports.ui.dnd; -const LayoutManager = imports.ui.layout; -const Main = imports.ui.main; -const MessageTray = imports.ui.messageTray; -const OverviewControls = imports.ui.overviewControls; -const SwipeTracker = imports.ui.swipeTracker; -const WindowManager = imports.ui.windowManager; -const WorkspaceThumbnail = imports.ui.workspaceThumbnail; +import * as DND from './dnd.js'; +import * as LayoutManager from './layout.js'; +import Main from './main.js'; +import * as MessageTray from './messageTray.js'; +import * as OverviewControls from './overviewControls.js'; +import * as SwipeTracker from './swipeTracker.js'; +import * as WindowManager from './windowManager.js'; +import * as WorkspaceThumbnail from './workspaceThumbnail.js'; -var DND_WINDOW_SWITCH_TIMEOUT = 750; +export let DND_WINDOW_SWITCH_TIMEOUT = 750; -var OVERVIEW_ACTIVATION_TIMEOUT = 0.5; +export let OVERVIEW_ACTIVATION_TIMEOUT = 0.5; -var ShellInfo = class { +export class ShellInfo { constructor() { this._source = null; } @@ -57,7 +63,7 @@ var ShellInfo = class { } }; -var OverviewActor = GObject.registerClass( +export const OverviewActor = GObject.registerClass( class OverviewActor extends St.BoxLayout { _init() { super._init({ @@ -83,6 +89,7 @@ class OverviewActor extends St.BoxLayout { } runStartupAnimation(callback) { + log('animating...'); this._controls.runStartupAnimation(callback); } @@ -99,7 +106,7 @@ class OverviewActor extends St.BoxLayout { } }); -var Overview = class extends Signals.EventEmitter { +export class Overview extends Signals.EventEmitter { constructor() { super(); @@ -641,6 +648,7 @@ var Overview = class extends Signals.EventEmitter { } runStartupAnimation(callback) { + log('trackin') Main.panel.style = 'transition-duration: 0ms;'; this._shown = true; @@ -654,6 +662,7 @@ var Overview = class extends Signals.EventEmitter { Meta.disable_unredirect_for_display(global.display); this.emit('showing'); + log('SHOWING'); this._overview.runStartupAnimation(() => { if (!this._syncGrab()) { diff --git a/js/ui/overviewControls.js b/js/ui/overviewControls.js index 6cbaa22ba..356e7a653 100644 --- a/js/ui/overviewControls.js +++ b/js/ui/overviewControls.js @@ -1,33 +1,41 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported ControlsManager */ -const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi; - -const AppDisplay = imports.ui.appDisplay; -const Dash = imports.ui.dash; -const Layout = imports.ui.layout; -const Main = imports.ui.main; -const Overview = imports.ui.overview; -const SearchController = imports.ui.searchController; -const Util = imports.misc.util; -const WindowManager = imports.ui.windowManager; -const WorkspaceThumbnail = imports.ui.workspaceThumbnail; -const WorkspacesView = imports.ui.workspacesView; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; + +import * as AppDisplay from './appDisplay.js'; +import * as Dash from './dash.js'; +import * as Layout from './layout.js'; +import Main from './main.js'; +import * as Overview from './overview.js'; +import * as SearchController from './searchController.js'; +import * as Util from '../misc/util.js'; +import * as WindowManager from './windowManager.js'; +import * as WorkspaceThumbnail from './workspaceThumbnail.js'; +import * as WorkspacesView from './workspacesView.js'; const SMALL_WORKSPACE_RATIO = 0.15; const DASH_MAX_HEIGHT_RATIO = 0.15; const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard'; -var SIDE_CONTROLS_ANIMATION_TIME = Overview.ANIMATION_TIME; +// TODO +// import { ANIMATION_TIME as SIDE_CONTROLS_ANIMATION_TIME } from './overview.js'; +// export { ANIMATION_TIME as SIDE_CONTROLS_ANIMATION_TIME } from './overview.js'; +export let SIDE_CONTROLS_ANIMATION_TIME = 250; -var ControlsState = { +export const ControlsState = { HIDDEN: 0, WINDOW_PICKER: 1, APP_GRID: 2, }; -var ControlsManagerLayout = GObject.registerClass( +export const ControlsManagerLayout = GObject.registerClass( class ControlsManagerLayout extends Clutter.BoxLayout { _init(searchEntry, appDisplay, workspacesDisplay, workspacesThumbnails, searchController, dash, stateAdjustment) { @@ -118,11 +126,13 @@ class ControlsManagerLayout extends Clutter.BoxLayout { this.hookup_style(container); } + /** @returns {[number, number]} */ vfunc_get_preferred_width(_container, _forHeight) { // The MonitorConstraint will allocate us a fixed size anyway return [0, 0]; } + /** @returns {[number, number]} */ vfunc_get_preferred_height(_container, _forWidth) { // The MonitorConstraint will allocate us a fixed size anyway return [0, 0]; @@ -241,7 +251,7 @@ class ControlsManagerLayout extends Clutter.BoxLayout { } }); -var OverviewAdjustment = GObject.registerClass({ +export const OverviewAdjustment = GObject.registerClass({ Properties: { 'gesture-in-progress': GObject.ParamSpec.boolean( 'gesture-in-progress', 'Gesture in progress', 'Gesture in progress', @@ -249,6 +259,9 @@ var OverviewAdjustment = GObject.registerClass({ false), }, }, class OverviewAdjustment extends St.Adjustment { + /** @type {boolean} */ + gestureInProgress; + _init(actor) { super._init({ actor, @@ -262,12 +275,14 @@ var OverviewAdjustment = GObject.registerClass({ const currentState = this.value; const transition = this.get_transition('value'); - let initialState = transition + /** @type {number} */ + let initialState = (transition ? transition.get_interval().peek_initial_value() - : currentState; - let finalState = transition + : currentState); + /** @type {number} */ + let finalState = (transition ? transition.get_interval().peek_final_value() - : currentState; + : currentState); if (initialState > finalState) { initialState = Math.ceil(initialState); @@ -292,7 +307,7 @@ var OverviewAdjustment = GObject.registerClass({ } }); -var ControlsManager = GObject.registerClass( +export const ControlsManager = GObject.registerClass( class ControlsManager extends St.Widget { _init() { super._init({ @@ -375,6 +390,7 @@ class ControlsManager extends St.Widget { this.add_child(this._thumbnailsBox); this.add_child(this._workspacesDisplay); + /** @type {ControlsManagerLayout["prototype"]} */ this.layout_manager = new ControlsManagerLayout( this._searchEntryBin, this._appDisplay, @@ -718,7 +734,7 @@ class ControlsManager extends St.Widget { } getWorkspacesBoxForState(state) { - return this.layoutManager.getWorkspacesBoxForState(state); + return this.layout_manager.getWorkspacesBoxForState(state); } gestureBegin(tracker) { diff --git a/js/ui/padOsd.js b/js/ui/padOsd.js index 1f3abe14c..c71b24d59 100644 --- a/js/ui/padOsd.js +++ b/js/ui/padOsd.js @@ -1,15 +1,24 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported PadOsd, PadOsdService */ -const { Atk, Clutter, GDesktopEnums, Gio, - GLib, GObject, Gtk, Meta, Pango, Rsvg, St } = imports.gi; -const Signals = imports.misc.signals; - -const Main = imports.ui.main; -const PopupMenu = imports.ui.popupMenu; -const Layout = imports.ui.layout; - -const { loadInterfaceXML } = imports.misc.fileUtils; +import Atk from 'gi://Atk'; +import Clutter from 'gi://Clutter'; +import GDesktopEnums from 'gi://GDesktopEnums'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Gtk from 'gi://Gtk'; +import Meta from 'gi://Meta'; +import Pango from 'gi://Pango'; +import Rsvg from 'gi://Rsvg'; +import St from 'gi://St'; +import * as Signals from '../misc/signals.js'; + +import Main from './main.js'; +import * as PopupMenu from './popupMenu.js'; +import * as Layout from './layout.js'; + +import { loadInterfaceXML } from '../misc/fileUtilsModule.js'; const ACTIVE_COLOR = "#729fcf"; @@ -22,9 +31,13 @@ const CCW = 1; const UP = 0; const DOWN = 1; -var PadChooser = GObject.registerClass({ +export const PadChooser = GObject.registerClass({ Signals: { 'pad-selected': { param_types: [Clutter.InputDevice.$gtype] } }, }, class PadChooser extends St.Button { + /** + * @param {*} device + * @param {*} groupDevices + */ _init(device, groupDevices) { super._init({ style_class: 'pad-chooser-button', @@ -88,7 +101,7 @@ var PadChooser = GObject.registerClass({ } }); -var KeybindingEntry = GObject.registerClass({ +export const KeybindingEntry = GObject.registerClass({ Signals: { 'keybinding-edited': { param_types: [GObject.TYPE_STRING] } }, }, class KeybindingEntry extends St.Entry { _init() { @@ -109,7 +122,7 @@ var KeybindingEntry = GObject.registerClass({ } }); -var ActionComboBox = GObject.registerClass({ +export const ActionComboBox = GObject.registerClass({ Signals: { 'action-selected': { param_types: [GObject.TYPE_INT] } }, }, class ActionComboBox extends St.Button { _init() { @@ -191,7 +204,7 @@ var ActionComboBox = GObject.registerClass({ } }); -var ActionEditor = GObject.registerClass({ +export const ActionEditor = GObject.registerClass({ Signals: { 'done': {} }, }, class ActionEditor extends St.Widget { _init() { @@ -275,7 +288,7 @@ var ActionEditor = GObject.registerClass({ } }); -var PadDiagram = GObject.registerClass({ +export const PadDiagram = GObject.registerClass({ Properties: { 'left-handed': GObject.ParamSpec.boolean('left-handed', 'left-handed', 'Left handed', @@ -368,7 +381,7 @@ var PadDiagram = GObject.registerClass({ let css = this._css; for (let i = 0; i < this._activeButtons.length; i++) { - let ch = String.fromCharCode('A'.charCodeAt() + this._activeButtons[i]); + let ch = String.fromCharCode('A'.charCodeAt(0) + this._activeButtons[i]); css += '.%s.Leader { stroke: %s !important; }'.format(ch, ACTIVE_COLOR); css += '.%s.Button { stroke: %s !important; fill: %s !important; }'.format(ch, ACTIVE_COLOR, ACTIVE_COLOR); } @@ -387,7 +400,8 @@ var PadDiagram = GObject.registerClass({ svgData += this._wrappingSvgFooter(); let istream = new Gio.MemoryInputStream(); - istream.add_bytes(new GLib.Bytes(svgData)); + // FIXME + istream.add_bytes(imports.byteArray.fromString(svgData)); return Rsvg.Handle.new_from_stream_sync(istream, Gio.File.new_for_path(this._imagePath), 0, null); @@ -495,7 +509,7 @@ var PadDiagram = GObject.registerClass({ } _getButtonLabels(button) { - let ch = String.fromCharCode('A'.charCodeAt() + button); + let ch = String.fromCharCode('A'.charCodeAt(0) + button); let labelName = 'Label%s'.format(ch); let leaderName = 'Leader%s'.format(ch); return [labelName, leaderName]; @@ -618,12 +632,19 @@ var PadDiagram = GObject.registerClass({ } }); -var PadOsd = GObject.registerClass({ +export const PadOsd = GObject.registerClass({ Signals: { 'pad-selected': { param_types: [Clutter.InputDevice.$gtype] }, 'closed': {}, }, }, class PadOsd extends St.BoxLayout { + /** + * @param {*} padDevice + * @param {*} settings + * @param {*} imagePath + * @param {*} editionMode + * @param {*} monitorIndex + */ _init(padDevice, settings, imagePath, editionMode, monitorIndex) { super._init({ style_class: 'pad-osd-window', @@ -894,19 +915,19 @@ var PadOsd = GObject.registerClass({ } _startButtonActionEdition(button) { - let ch = String.fromCharCode('A'.charCodeAt() + button); + let ch = String.fromCharCode('A'.charCodeAt(0) + button); let key = 'button%s'.format(ch); this._startActionEdition(key, Meta.PadActionType.BUTTON, button); } _startRingActionEdition(ring, dir, mode) { - let ch = String.fromCharCode('A'.charCodeAt() + ring); + let ch = String.fromCharCode('A'.charCodeAt(0) + ring); let key = 'ring%s-%s-mode-%d'.format(ch, dir == CCW ? 'ccw' : 'cw', mode); this._startActionEdition(key, Meta.PadActionType.RING, ring, dir, mode); } _startStripActionEdition(strip, dir, mode) { - let ch = String.fromCharCode('A'.charCodeAt() + strip); + let ch = String.fromCharCode('A'.charCodeAt(0) + strip); let key = 'strip%s-%s-mode-%d'.format(ch, dir == UP ? 'up' : 'down', mode); this._startActionEdition(key, Meta.PadActionType.STRIP, strip, dir, mode); } @@ -944,7 +965,7 @@ var PadOsd = GObject.registerClass({ const PadOsdIface = loadInterfaceXML('org.gnome.Shell.Wacom.PadOsd'); -var PadOsdService = class extends Signals.EventEmitter { +export class PadOsdService extends Signals.EventEmitter { constructor() { super(); diff --git a/js/ui/pageIndicators.js b/js/ui/pageIndicators.js index 63a31d679..be2c6f99b 100644 --- a/js/ui/pageIndicators.js +++ b/js/ui/pageIndicators.js @@ -1,14 +1,17 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported PageIndicators */ -const { Clutter, Graphene, GObject, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import Graphene from 'gi://Graphene'; +import GObject from 'gi://GObject'; +import St from 'gi://St'; const INDICATOR_INACTIVE_OPACITY = 128; const INDICATOR_INACTIVE_OPACITY_HOVER = 255; const INDICATOR_INACTIVE_SCALE = 2 / 3; const INDICATOR_INACTIVE_SCALE_PRESSED = 0.5; -var PageIndicators = GObject.registerClass({ +export const PageIndicators = GObject.registerClass({ Signals: { 'page-activated': { param_types: [GObject.TYPE_INT] } }, }, class PageIndicators extends St.BoxLayout { _init(orientation = Clutter.Orientation.VERTICAL) { @@ -29,6 +32,9 @@ var PageIndicators = GObject.registerClass({ this._orientation = orientation; } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_height(forWidth) { // We want to request the natural height of all our children as our // natural height, so we chain up to St.BoxLayout, but we only request 0 diff --git a/js/ui/panel.js b/js/ui/panel.js index dafba690a..5af13d239 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -1,23 +1,29 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Panel */ -const { Atk, Clutter, GLib, GObject, Meta, Shell, St } = imports.gi; -const Cairo = imports.cairo; - -const Animation = imports.ui.animation; -const { AppMenu } = imports.ui.appMenu; +import Atk from 'gi://Atk'; +import Clutter from 'gi://Clutter'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; +import Cairo from 'cairo'; + +import * as Animation from './animation.js'; +import { AppMenu } from './appMenu.js'; const Config = imports.misc.config; -const CtrlAltTab = imports.ui.ctrlAltTab; -const DND = imports.ui.dnd; -const Overview = imports.ui.overview; -const PopupMenu = imports.ui.popupMenu; -const PanelMenu = imports.ui.panelMenu; -const Main = imports.ui.main; +import * as CtrlAltTab from './ctrlAltTab.js'; +import * as DND from './dnd.js'; +import * as Overview from './overview.js'; +import * as PopupMenu from './popupMenu.js'; +import * as PanelMenu from './panelMenu.js'; +import Main from './main.js'; -var PANEL_ICON_SIZE = 16; -var APP_MENU_ICON_MARGIN = 0; +export let PANEL_ICON_SIZE = 16; +export let APP_MENU_ICON_MARGIN = 0; -var BUTTON_DND_ACTIVATION_TIMEOUT = 250; +export let BUTTON_DND_ACTIVATION_TIMEOUT = 250; /** * AppMenuButton: @@ -27,7 +33,7 @@ var BUTTON_DND_ACTIVATION_TIMEOUT = 250; * this menu also handles startup notification for it. So when we * have an active startup notification, we switch modes to display that. */ -var AppMenuButton = GObject.registerClass({ +export const AppMenuButton = GObject.registerClass({ Signals: { 'changed': {} }, }, class AppMenuButton extends PanelMenu.Button { _init(panel) { @@ -258,7 +264,7 @@ var AppMenuButton = GObject.registerClass({ } }); -var ActivitiesButton = GObject.registerClass( +export const ActivitiesButton = GObject.registerClass( class ActivitiesButton extends PanelMenu.Button { _init() { super._init(0.0, null, true); @@ -294,6 +300,8 @@ class ActivitiesButton extends PanelMenu.Button { GLib.source_remove(this._xdndTimeOut); this._xdndTimeOut = GLib.timeout_add(GLib.PRIORITY_DEFAULT, BUTTON_DND_ACTIVATION_TIMEOUT, () => { this._xdndToggleOverview(); + + return false; }); GLib.Source.set_name_by_id(this._xdndTimeOut, '[gnome-shell] this._xdndToggleOverview'); @@ -344,7 +352,7 @@ class ActivitiesButton extends PanelMenu.Button { } }); -var PanelCorner = GObject.registerClass( +export const PanelCorner = GObject.registerClass( class PanelCorner extends St.DrawingArea { _init(side) { this._side = side; @@ -535,6 +543,9 @@ class AggregateLayout extends Clutter.BoxLayout { this.layout_changed(); } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_width(container, forHeight) { let themeNode = container.get_theme_node(); let minWidth = themeNode.get_min_width(); @@ -550,38 +561,44 @@ class AggregateLayout extends Clutter.BoxLayout { } }); -var AggregateMenu = GObject.registerClass( + + +export const AggregateMenu = GObject.registerClass( class AggregateMenu extends PanelMenu.Button { _init() { super._init(0.0, C_("System menu in the top bar", "System"), false); this.menu.actor.add_style_class_name('aggregate-menu'); - let menuLayout = new AggregateLayout(); - this.menu.box.set_layout_manager(menuLayout); + this._menuLayout = new AggregateLayout(); + this.menu.box.set_layout_manager(this._menuLayout); this._indicators = new St.BoxLayout({ style_class: 'panel-status-indicators-box' }); this.add_child(this._indicators); + } + async _asyncInit() { if (Config.HAVE_NETWORKMANAGER) - this._network = new imports.ui.status.network.NMApplet(); + this._network = new ((await import('./status/network.js')).NMApplet)(); else this._network = null; if (Config.HAVE_BLUETOOTH) - this._bluetooth = new imports.ui.status.bluetooth.Indicator(); + this._bluetooth = new ((await import('./status/bluetooth.js')).Indicator)(); else this._bluetooth = null; - this._remoteAccess = new imports.ui.status.remoteAccess.RemoteAccessApplet(); - this._power = new imports.ui.status.power.Indicator(); - this._powerProfiles = new imports.ui.status.powerProfiles.Indicator(); - this._rfkill = new imports.ui.status.rfkill.Indicator(); - this._volume = new imports.ui.status.volume.Indicator(); - this._brightness = new imports.ui.status.brightness.Indicator(); - this._system = new imports.ui.status.system.Indicator(); - this._location = new imports.ui.status.location.Indicator(); - this._nightLight = new imports.ui.status.nightLight.Indicator(); - this._thunderbolt = new imports.ui.status.thunderbolt.Indicator(); + const menuLayout = this._menuLayout; + + this._remoteAccess = new ((await import('./status/remoteAccess.js')).RemoteAccessApplet)(); + this._powerProfiles = new ((await import('./status/powerProfiles.js')).Indicator)(); + this._power = new ((await import('./status/power.js')).Indicator)(); + this._rfkill = new ((await import('./status/rfkill.js')).Indicator)(); + this._volume = new ((await import('./status/volume.js')).Indicator)(); + this._brightness = new ((await import('./status/brightness.js')).Indicator)(); + this._system = new ((await import('./status/system.js')).Indicator)(); + this._location = new ((await import('./status/location.js')).Indicator)(); + this._nightLight = new ((await import('./status/nightLight.js')).Indicator)(); + this._thunderbolt = new ((await import('./status/thunderbolt.js')).Indicator)(); this._unsafeMode = new UnsafeModeIndicator(); this._indicators.add_child(this._remoteAccess); @@ -624,17 +641,22 @@ class AggregateMenu extends PanelMenu.Button { } }); +import {DateMenuButton} from './dateMenu.js'; +import {ATIndicator} from './status/accessibility.js'; +import {InputSourceIndicator} from './status/keyboard.js'; +import {DwellClickIndicator} from './status/dwellClick.js'; + const PANEL_ITEM_IMPLEMENTATIONS = { 'activities': ActivitiesButton, 'aggregateMenu': AggregateMenu, 'appMenu': AppMenuButton, - 'dateMenu': imports.ui.dateMenu.DateMenuButton, - 'a11y': imports.ui.status.accessibility.ATIndicator, - 'keyboard': imports.ui.status.keyboard.InputSourceIndicator, - 'dwellClick': imports.ui.status.dwellClick.DwellClickIndicator, + 'dateMenu': DateMenuButton, + 'a11y': ATIndicator, + 'keyboard': InputSourceIndicator, + 'dwellClick': DwellClickIndicator, }; -var Panel = GObject.registerClass( +export const Panel = GObject.registerClass( class Panel extends St.Widget { _init() { super._init({ name: 'panel', @@ -673,13 +695,26 @@ class Panel extends St.Widget { Main.layoutManager.panelBox.add(this); Main.ctrlAltTabManager.addGroup(this, _("Top Bar"), 'focus-top-bar-symbolic', { sortGroup: CtrlAltTab.SortGroup.TOP }); - - Main.sessionMode.connect('updated', this._updatePanel.bind(this)); + log('updating panel...'); + Main.sessionMode.connect('updated', () => { + this._updatePanel().then(() => { + log('Panel Updated!'); + }).catch(err => { + logError(err) + }); + }); global.display.connect('workareas-changed', () => this.queue_relayout()); - this._updatePanel(); + this._updatePanel().then(() => { + log('Panel Updated! (1)'); + }).catch(err => { + logError(err) + }); } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_width(_forHeight) { let primaryMonitor = Main.layoutManager.primaryMonitor; @@ -863,12 +898,14 @@ class Panel extends St.Widget { return this._leftBox.opacity; } - _updatePanel() { + async _updatePanel() { + try { + log('Updating panel...'); let panel = Main.sessionMode.panel; this._hideIndicators(); - this._updateBox(panel.left, this._leftBox); - this._updateBox(panel.center, this._centerBox); - this._updateBox(panel.right, this._rightBox); + await this._updateBox(panel.left, this._leftBox); + await this._updateBox(panel.center, this._centerBox); + await this._updateBox(panel.right, this._rightBox); if (panel.left.includes('dateMenu')) Main.messageTray.bannerAlignment = Clutter.ActorAlign.START; @@ -892,6 +929,10 @@ class Panel extends St.Widget { this._leftCorner.setStyleParent(this._leftBox); this._rightCorner.setStyleParent(this._rightBox); } + }catch(error) { + log('FAILED TO UPDATE PANEL...'); + logError(error); + } } _hideIndicators() { @@ -903,7 +944,7 @@ class Panel extends St.Widget { } } - _ensureIndicator(role) { + async _ensureIndicator(role) { let indicator = this.statusArea[role]; if (!indicator) { let constructor = PANEL_ITEM_IMPLEMENTATIONS[role]; @@ -912,17 +953,22 @@ class Panel extends St.Widget { return null; } indicator = new constructor(this); + + // TODO + if (indicator._asyncInit) + await indicator._asyncInit(); + this.statusArea[role] = indicator; } return indicator; } - _updateBox(elements, box) { + async _updateBox(elements, box) { let nChildren = box.get_n_children(); for (let i = 0; i < elements.length; i++) { let role = elements[i]; - let indicator = this._ensureIndicator(role); + let indicator = await this._ensureIndicator(role); if (indicator == null) continue; diff --git a/js/ui/panelMenu.js b/js/ui/panelMenu.js index ccfe97613..0736b666f 100644 --- a/js/ui/panelMenu.js +++ b/js/ui/panelMenu.js @@ -1,12 +1,15 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Button, SystemIndicator */ -const { Atk, Clutter, GObject, St } = imports.gi; +import Atk from 'gi://Atk'; +import Clutter from 'gi://Clutter'; +import GObject from 'gi://GObject'; +import St from 'gi://St'; -const Main = imports.ui.main; -const PopupMenu = imports.ui.popupMenu; +import Main from './main.js'; +import * as PopupMenu from './popupMenu.js'; -var ButtonBox = GObject.registerClass( +export const ButtonBox = GObject.registerClass( class ButtonBox extends St.Widget { _init(params) { const { @@ -34,6 +37,9 @@ class ButtonBox extends St.Widget { this._natHPadding = themeNode.get_length('-natural-hpadding'); } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_width(_forHeight) { let child = this.get_first_child(); let minimumSize, naturalSize; @@ -49,6 +55,9 @@ class ButtonBox extends St.Widget { return [minimumSize, naturalSize]; } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_height(_forWidth) { let child = this.get_first_child(); @@ -91,7 +100,7 @@ class ButtonBox extends St.Widget { } }); -var Button = GObject.registerClass({ +export const Button = GObject.registerClass({ Signals: { 'menu-set': {} }, }, class PanelMenuButton extends ButtonBox { _init(menuAlignment, nameText, dontCreateMenu) { @@ -106,7 +115,7 @@ var Button = GObject.registerClass({ if (dontCreateMenu) this.menu = new PopupMenu.PopupDummyMenu(this); else - this.setMenu(new PopupMenu.PopupMenu(this, menuAlignment, St.Side.TOP, 0)); + this.setMenu(new PopupMenu.PopupMenu(this, menuAlignment, St.Side.TOP)); } setSensitive(sensitive) { @@ -180,7 +189,7 @@ var Button = GObject.registerClass({ // measures are in logical pixels, so make sure to consider the scale // factor when computing max-height let maxHeight = Math.round((workArea.height - verticalMargins) / scaleFactor); - this.menu.actor.style = 'max-height: %spx;'.format(maxHeight); + this.menu.actor.style = 'max-height: %spx;'.format(maxHeight.toFixed(0)); } _onDestroy() { @@ -197,7 +206,7 @@ var Button = GObject.registerClass({ * of an icon and a menu section, which will be composed into the * aggregate menu. */ -var SystemIndicator = GObject.registerClass( +export const SystemIndicator = GObject.registerClass( class SystemIndicator extends St.BoxLayout { _init() { super._init({ diff --git a/js/ui/pointerA11yTimeout.js b/js/ui/pointerA11yTimeout.js index 263cc3eaf..40dd0abb5 100644 --- a/js/ui/pointerA11yTimeout.js +++ b/js/ui/pointerA11yTimeout.js @@ -1,11 +1,16 @@ /* exported PointerA11yTimeout */ -const { Clutter, GObject, Meta, St } = imports.gi; -const Main = imports.ui.main; -const Cairo = imports.cairo; +import Clutter from 'gi://Clutter'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import St from 'gi://St'; + +import Main from './main.js'; + +import Cairo from 'gi://cairo'; const SUCCESS_ZOOM_OUT_DURATION = 150; -var PieTimer = GObject.registerClass({ +export const PieTimer = GObject.registerClass({ Properties: { 'angle': GObject.ParamSpec.double( 'angle', 'angle', 'angle', @@ -106,7 +111,7 @@ var PieTimer = GObject.registerClass({ } }); -var PointerA11yTimeout = class PointerA11yTimeout { +export class PointerA11yTimeout { constructor() { let seat = Clutter.get_default_backend().get_default_seat(); diff --git a/js/ui/pointerWatcher.js b/js/ui/pointerWatcher.js index 2af35b617..4ab11599a 100644 --- a/js/ui/pointerWatcher.js +++ b/js/ui/pointerWatcher.js @@ -1,24 +1,24 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported getPointerWatcher */ -const { GLib } = imports.gi; +import GLib from 'gi://GLib'; // We stop polling if the user is idle for more than this amount of time -var IDLE_TIME = 1000; +export const IDLE_TIME = 1000; // This file implements a reasonably efficient system for tracking the position // of the mouse pointer. We simply query the pointer from the X server in a loop, // but we turn off the polling when the user is idle. let _pointerWatcher = null; -function getPointerWatcher() { +export function getPointerWatcher() { if (_pointerWatcher == null) _pointerWatcher = new PointerWatcher(); return _pointerWatcher; } -var PointerWatch = class { +export class PointerWatch { constructor(watcher, interval, callback) { this.watcher = watcher; this.interval = interval; @@ -33,7 +33,7 @@ var PointerWatch = class { } }; -var PointerWatcher = class { +export class PointerWatcher { constructor() { this._idleMonitor = global.backend.get_core_idle_monitor(); this._idleMonitor.add_idle_watch(IDLE_TIME, this._onIdleMonitorBecameIdle.bind(this)); diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js index f4e760bfb..e818083b2 100644 --- a/js/ui/popupMenu.js +++ b/js/ui/popupMenu.js @@ -3,14 +3,21 @@ PopupImageMenuItem, PopupMenu, PopupDummyMenu, PopupSubMenu, PopupMenuSection, PopupSubMenuMenuItem, PopupMenuManager */ -const { Atk, Clutter, Gio, GObject, Graphene, Shell, St } = imports.gi; -const Signals = imports.misc.signals; - -const BoxPointer = imports.ui.boxpointer; -const GrabHelper = imports.ui.grabHelper; -const Main = imports.ui.main; - -var Ornament = { +import Atk from 'gi://Atk'; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; +import Graphene from 'gi://Graphene'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; +import * as Signals from '../misc/signals.js'; + +import * as BoxPointer from './boxpointer.js'; +import * as GrabHelper from './grabHelper.js'; +import Main from './main.js'; + +/** @enum {number} */ +export const Ornament = { NONE: 0, DOT: 1, CHECK: 2, @@ -30,7 +37,7 @@ function isPopupMenuItemVisible(child) { * @param {St.Side} side - Side to which the arrow points. * @returns {St.Icon} a new arrow icon */ -function arrowIcon(side) { +export function arrowIcon(side) { let iconName; switch (side) { case St.Side.TOP: @@ -56,7 +63,9 @@ function arrowIcon(side) { return arrow; } -var PopupBaseMenuItem = GObject.registerClass({ +/** @typedef {{active?: boolean} & Partial<Pick<St.BoxLayout.ConstructorProperties, 'style_class' | 'can_focus' | 'hover' | 'activate' | 'reactive'>>} PopupBaseMenuItemParams */ + +export const PopupBaseMenuItem = GObject.registerClass({ Properties: { 'active': GObject.ParamSpec.boolean('active', 'active', 'active', GObject.ParamFlags.READWRITE, @@ -69,7 +78,7 @@ var PopupBaseMenuItem = GObject.registerClass({ 'activate': { param_types: [Clutter.Event.$gtype] }, }, }, class PopupBaseMenuItem extends St.BoxLayout { - _init(params = {}) { + _init(params = {}, ..._) { const { reactive = true, activate = true, @@ -85,6 +94,11 @@ var PopupBaseMenuItem = GObject.registerClass({ accessible_role: Atk.Role.MENU_ITEM }); this._delegate = this; + /** @type {any} */ + this.prop + /** @type {any} */ + this.radioGroup + this._ornament = Ornament.NONE; this._ornamentLabel = new St.Label({ style_class: 'popup-menu-ornament' }); this.add(this._ornamentLabel); @@ -267,9 +281,18 @@ var PopupBaseMenuItem = GObject.registerClass({ } }); -var PopupMenuItem = GObject.registerClass( +/** + * @typedef {object} PopupMenuItemParams + * @property {string} [text] + * @property {boolean} [active] + */ + +export const PopupMenuItem = GObject.registerClass( class PopupMenuItem extends PopupBaseMenuItem { - _init(text, params) { + /** + * @param {PopupMenuItemParams & PopupBaseMenuItemParams} [params] + */ + _init(text, params) { super._init(params); this.label = new St.Label({ text }); @@ -279,8 +302,11 @@ class PopupMenuItem extends PopupBaseMenuItem { }); -var PopupSeparatorMenuItem = GObject.registerClass( +export const PopupSeparatorMenuItem = GObject.registerClass( class PopupSeparatorMenuItem extends PopupBaseMenuItem { + /** + * @param {string | any} text + */ _init(text) { super._init({ style_class: 'popup-separator-menu-item', @@ -310,7 +336,7 @@ class PopupSeparatorMenuItem extends PopupBaseMenuItem { } }); -var Switch = GObject.registerClass({ +export const Switch = GObject.registerClass({ Properties: { 'state': GObject.ParamSpec.boolean( 'state', 'state', 'state', @@ -350,10 +376,10 @@ var Switch = GObject.registerClass({ } }); -var PopupSwitchMenuItem = GObject.registerClass({ +export const PopupSwitchMenuItem = GObject.registerClass({ Signals: { 'toggled': { param_types: [GObject.TYPE_BOOLEAN] } }, }, class PopupSwitchMenuItem extends PopupBaseMenuItem { - _init(text, active, params) { + _init(text, active, params) { super._init(params); this.label = new St.Label({ text }); @@ -434,8 +460,13 @@ var PopupSwitchMenuItem = GObject.registerClass({ } }); -var PopupImageMenuItem = GObject.registerClass( +export const PopupImageMenuItem = GObject.registerClass( class PopupImageMenuItem extends PopupBaseMenuItem { + /** + * @param {string | any} text + * @param {string | Gio.Icon} [icon] + * @param {PopupBaseMenuItemParams} [params] + */ _init(text, icon, params) { super._init(params); @@ -446,9 +477,14 @@ class PopupImageMenuItem extends PopupBaseMenuItem { this.add_child(this.label); this.label_actor = this.label; - this.setIcon(icon); + if (icon) { + this.setIcon(icon); + } } + /** + * @param {string | Gio.Icon} icon + */ setIcon(icon) { // The 'icon' parameter can be either a Gio.Icon or a string. if (icon instanceof GObject.Object && GObject.type_is_a(icon, Gio.Icon)) @@ -458,7 +494,7 @@ class PopupImageMenuItem extends PopupBaseMenuItem { } }); -var PopupMenuBase = class extends Signals.EventEmitter { +export class PopupMenuBase extends Signals.EventEmitter { constructor(sourceActor, styleClass) { super(); @@ -467,6 +503,10 @@ var PopupMenuBase = class extends Signals.EventEmitter { this.sourceActor = sourceActor; this.focusActor = sourceActor; + + /** @type {St.Widget} */ + this.actor; + this._parent = null; this.box = new St.BoxLayout({ @@ -695,6 +735,10 @@ var PopupMenuBase = class extends Signals.EventEmitter { } } + /** + * @param {PopupMenuBase | PopupBaseMenuItem["prototype"]} menuItem + * @param {number} [position] + */ addMenuItem(menuItem, position) { let beforeItem = null; if (position == undefined) { @@ -774,9 +818,12 @@ var PopupMenuBase = class extends Signals.EventEmitter { } _getMenuItems() { - return this.box.get_children().map(a => a._delegate).filter(item => { - return item instanceof PopupBaseMenuItem || item instanceof PopupMenuSection; - }); + return this.box.get_children().map(a => a._delegate).filter( + /** + * @returns {item is PopupMenuBase | PopupMenuSection} + */ + item => item instanceof PopupBaseMenuItem || item instanceof PopupMenuSection + ); } get firstMenuItem() { @@ -791,6 +838,20 @@ var PopupMenuBase = class extends Signals.EventEmitter { return this._getMenuItems().length; } + /** + * @param {BoxPointer.PopupAnimation} _animation + */ + open(_animation) { + throw new GObject.NotImplementedError(`open in ${this.constructor.name}`); + } + + /** + * @param {BoxPointer.PopupAnimation} [_animation] + */ + close(_animation) { + throw new GObject.NotImplementedError(`close in ${this.constructor.name}`); + } + removeAll() { let children = this._getMenuItems(); for (let i = 0; i < children.length; i++) { @@ -818,7 +879,7 @@ var PopupMenuBase = class extends Signals.EventEmitter { } }; -var PopupMenu = class extends PopupMenuBase { +export class PopupMenu extends PopupMenuBase { constructor(sourceActor, arrowAlignment, arrowSide) { super(sourceActor, 'popup-menu-content'); @@ -969,7 +1030,7 @@ var PopupMenu = class extends PopupMenuBase { } }; -var PopupDummyMenu = class extends Signals.EventEmitter { +export class PopupDummyMenu extends Signals.EventEmitter { constructor(sourceActor) { super(); @@ -1001,7 +1062,7 @@ var PopupDummyMenu = class extends Signals.EventEmitter { } }; -var PopupSubMenu = class extends PopupMenuBase { +export class PopupSubMenu extends PopupMenuBase { constructor(sourceActor, sourceArrow) { super(sourceActor); @@ -1146,7 +1207,7 @@ var PopupSubMenu = class extends PopupMenuBase { * can add it to another menu), but is completely transparent * to the user */ -var PopupMenuSection = class extends PopupMenuBase { +export class PopupMenuSection extends PopupMenuBase { constructor() { super(); @@ -1166,7 +1227,7 @@ var PopupMenuSection = class extends PopupMenuBase { } }; -var PopupSubMenuMenuItem = GObject.registerClass( +export const PopupSubMenuMenuItem = GObject.registerClass( class PopupSubMenuMenuItem extends PopupBaseMenuItem { _init(text, wantIcon) { super._init(); @@ -1287,7 +1348,7 @@ class PopupSubMenuMenuItem extends PopupBaseMenuItem { /* Basic implementation of a menu manager. * Call addMenu to add menus */ -var PopupMenuManager = class { +export class PopupMenuManager { constructor(owner, grabParams = {}) { this._grabHelper = new GrabHelper.GrabHelper(owner, { actionMode: Shell.ActionMode.POPUP, diff --git a/js/ui/remoteSearch.js b/js/ui/remoteSearch.js index 137516bdc..72b227765 100644 --- a/js/ui/remoteSearch.js +++ b/js/ui/remoteSearch.js @@ -1,9 +1,13 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported loadRemoteSearchProviders */ -const { GdkPixbuf, Gio, GLib, Shell, St } = imports.gi; +import GdkPixbuf from 'gi://GdkPixbuf'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; -const FileUtils = imports.misc.fileUtils; +import * as FileUtils from '../misc/fileUtilsModule.js'; const KEY_FILE_GROUP = 'Shell Search Provider'; @@ -57,10 +61,10 @@ const SearchProvider2Iface = ` </interface> </node>`; -var SearchProviderProxyInfo = Gio.DBusInterfaceInfo.new_for_xml(SearchProviderIface); -var SearchProvider2ProxyInfo = Gio.DBusInterfaceInfo.new_for_xml(SearchProvider2Iface); +export const SearchProviderProxyInfo = Gio.DBusInterfaceInfo.new_for_xml(SearchProviderIface); +export const SearchProvider2ProxyInfo = Gio.DBusInterfaceInfo.new_for_xml(SearchProvider2Iface); -function loadRemoteSearchProviders(searchSettings, callback) { +export function loadRemoteSearchProviders(searchSettings, callback) { let objectPaths = {}; let loadedProviders = []; @@ -104,9 +108,9 @@ function loadRemoteSearchProviders(searchSettings, callback) { // ignore error } - let version = '1'; + let version = 1; try { - version = keyfile.get_string(group, 'Version'); + version = Number.parseInt(keyfile.get_string(group, 'Version'), 10); } catch (e) { // ignore error } @@ -187,7 +191,7 @@ function loadRemoteSearchProviders(searchSettings, callback) { callback(loadedProviders); } -var RemoteSearchProvider = class { +export class RemoteSearchProvider { constructor(appInfo, dbusName, dbusPath, autoStart, proxyInfo) { if (!proxyInfo) proxyInfo = SearchProviderProxyInfo; @@ -210,6 +214,7 @@ var RemoteSearchProvider = class { this.id = appInfo.get_id(); this.isRemoteProvider = true; this.canLaunchSearch = false; + this.defaultEnabled = false; } createIcon(size, meta) { @@ -318,7 +323,7 @@ var RemoteSearchProvider = class { } }; -var RemoteSearchProvider2 = class extends RemoteSearchProvider { +export class RemoteSearchProvider2 extends RemoteSearchProvider { constructor(appInfo, dbusName, dbusPath, autoStart) { super(appInfo, dbusName, dbusPath, autoStart, SearchProvider2ProxyInfo); diff --git a/js/ui/ripples.js b/js/ui/ripples.js index f38062228..d9d6b8707 100644 --- a/js/ui/ripples.js +++ b/js/ui/ripples.js @@ -1,10 +1,11 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Ripples */ -const { Clutter, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import St from 'gi://St'; // Shamelessly copied from the layout "hotcorner" ripples implementation -var Ripples = class Ripples { +export class Ripples { constructor(px, py, styleClass) { this._x = 0; this._y = 0; diff --git a/js/ui/runDialog.js b/js/ui/runDialog.js index cf835972e..a3cbf7ab2 100644 --- a/js/ui/runDialog.js +++ b/js/ui/runDialog.js @@ -1,14 +1,20 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported RunDialog */ -const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi; - -const Dialog = imports.ui.dialog; -const Main = imports.ui.main; -const ModalDialog = imports.ui.modalDialog; -const ShellEntry = imports.ui.shellEntry; -const Util = imports.misc.util; -const History = imports.misc.history; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; + +import * as Dialog from './dialog.js'; +import Main from './main.js'; +import * as ModalDialog from './modalDialog.js'; +import * as ShellEntry from './shellEntry.js'; +import * as Util from '../misc/util.js'; +import * as History from '../misc/history.js'; const HISTORY_KEY = 'command-history'; @@ -19,7 +25,7 @@ const TERMINAL_SCHEMA = 'org.gnome.desktop.default-applications.terminal'; const EXEC_KEY = 'exec'; const EXEC_ARG_KEY = 'exec-arg'; -var RunDialog = GObject.registerClass( +export const RunDialog = GObject.registerClass( class RunDialog extends ModalDialog.ModalDialog { _init() { super._init({ @@ -251,6 +257,6 @@ class RunDialog extends ModalDialog.ModalDialog { if (this._lockdownSettings.get_boolean(DISABLE_COMMAND_LINE_KEY)) return; - super.open(); + return super.open(); } }); diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js index 431636463..95ee2c3c0 100644 --- a/js/ui/screenShield.js +++ b/js/ui/screenShield.js @@ -1,21 +1,27 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported ScreenShield */ -const { AccountsService, Clutter, Gio, - GLib, Graphene, Meta, Shell, St } = imports.gi; -const Signals = imports.misc.signals; - -const GnomeSession = imports.misc.gnomeSession; -const OVirt = imports.gdm.oVirt; -const LoginManager = imports.misc.loginManager; -const Lightbox = imports.ui.lightbox; -const Main = imports.ui.main; -const Overview = imports.ui.overview; -const MessageTray = imports.ui.messageTray; -const ShellDBus = imports.ui.shellDBus; -const SmartcardManager = imports.misc.smartcardManager; - -const { adjustAnimationTime } = imports.ui.environment; +import AccountsService from 'gi://AccountsService'; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import Graphene from 'gi://Graphene'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; +import * as Signals from '../misc/signals.js'; + +import * as GnomeSession from '../misc/gnomeSession.js'; +import * as OVirt from '../gdm/oVirt.js'; +import * as LoginManager from '../misc/loginManager.js'; +import * as Lightbox from './lightbox.js'; +import Main from './main.js'; +import * as Overview from './overview.js'; +import * as MessageTray from './messageTray.js'; +import * as ShellDBus from './shellDBus.js'; +import * as SmartcardManager from '../misc/smartcardManager.js'; + +import { adjustAnimationTime } from './environment.js'; const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver'; const LOCK_ENABLED_KEY = 'lock-enabled'; @@ -26,14 +32,15 @@ const DISABLE_LOCK_KEY = 'disable-lock-screen'; const LOCKED_STATE_STR = 'screenShield.locked'; + // ScreenShield animation time // - STANDARD_FADE_TIME is used when the session goes idle // - MANUAL_FADE_TIME is used for lowering the shield when asked by the user, // or when cancelling the dialog // - CURTAIN_SLIDE_TIME is used when raising the shield before unlocking -var STANDARD_FADE_TIME = 10000; -var MANUAL_FADE_TIME = 300; -var CURTAIN_SLIDE_TIME = 300; +export let STANDARD_FADE_TIME = 10000; +export let MANUAL_FADE_TIME = 300; +export let CURTAIN_SLIDE_TIME = 300; /** * If you are setting org.gnome.desktop.session.idle-delay directly in dconf, @@ -43,7 +50,7 @@ var CURTAIN_SLIDE_TIME = 300; * This will ensure that the screen blanks at the right time when it fades out. * https://bugzilla.gnome.org/show_bug.cgi?id=668703 explains the dependency. */ -var ScreenShield = class extends Signals.EventEmitter { +export class ScreenShield extends Signals.EventEmitter { constructor() { super(); @@ -71,7 +78,8 @@ var ScreenShield = class extends Signals.EventEmitter { this.actor.add_actor(this._lockScreenGroup); this.actor.add_actor(this._lockDialogGroup); - this._presence = new GnomeSession.Presence((proxy, error) => { + // FIXME + this._presence = GnomeSession.Presence((proxy, error) => { if (error) { logError(error, 'Error while reading gnome-session presence'); return; diff --git a/js/ui/screenshot.js b/js/ui/screenshot.js index bf537b7d6..3819938da 100644 --- a/js/ui/screenshot.js +++ b/js/ui/screenshot.js @@ -1,11 +1,18 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported ScreenshotService */ -const { Clutter, Gio, GObject, GLib, Meta, Shell, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; +import GLib from 'gi://GLib'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; -const GrabHelper = imports.ui.grabHelper; -const Lightbox = imports.ui.lightbox; -const Main = imports.ui.main; + +import * as GrabHelper from './grabHelper.js'; +import * as Lightbox from './lightbox.js'; +import Main from './main.js'; Gio._promisify(Shell.Screenshot.prototype, 'pick_color', 'pick_color_finish'); Gio._promisify(Shell.Screenshot.prototype, 'screenshot', 'screenshot_finish'); @@ -14,12 +21,12 @@ Gio._promisify(Shell.Screenshot.prototype, Gio._promisify(Shell.Screenshot.prototype, 'screenshot_area', 'screenshot_area_finish'); -const { loadInterfaceXML } = imports.misc.fileUtils; -const { DBusSenderChecker } = imports.misc.util; +import { loadInterfaceXML } from '../misc/fileUtilsModule.js'; +import { DBusSenderChecker } from '../misc/util.js'; const ScreenshotIface = loadInterfaceXML('org.gnome.Shell.Screenshot'); -var ScreenshotService = class { +export class ScreenshotService { constructor() { this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(ScreenshotIface, this); this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell/Screenshot'); @@ -108,10 +115,13 @@ var ScreenshotService = class { for (let idx = 1; ; idx++) { yield Gio.File.new_for_path( - GLib.build_filenamev([path, '%s-%s.png'.format(filename, idx)])); + GLib.build_filenamev([path, '%s-%s.png'.format(filename, idx.toFixed(0))])); } } + /** + * @returns {[Gio.OutputStream, Gio.File | null] | [null, null]} + */ _createStream(filename, invocation) { if (filename == '') return [Gio.MemoryOutputStream.new_resizable(), null]; @@ -182,6 +192,15 @@ var ScreenshotService = class { return [x, y, width, height]; } + // FIXME + /** + * + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + * @returns {[number, number, number, number]} + */ _unscaleArea(x, y, width, height) { let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; x /= scaleFactor; @@ -337,7 +356,7 @@ var ScreenshotService = class { } }; -var SelectArea = GObject.registerClass( +export const SelectArea = GObject.registerClass( class SelectArea extends St.Widget { _init() { this._startX = -1; @@ -438,7 +457,7 @@ class SelectArea extends St.Widget { } }); -var RecolorEffect = GObject.registerClass({ +export const RecolorEffect = GObject.registerClass({ Properties: { color: GObject.ParamSpec.boxed( 'color', 'color', 'replacement color', @@ -567,7 +586,7 @@ var RecolorEffect = GObject.registerClass({ } }); -var PickPixel = GObject.registerClass( +export const PickPixel = GObject.registerClass( class PickPixel extends St.Widget { _init(screenshot) { super._init({ visible: false, reactive: true }); @@ -657,9 +676,9 @@ class PickPixel extends St.Widget { } }); -var FLASHSPOT_ANIMATION_OUT_TIME = 500; // milliseconds +export const FLASHSPOT_ANIMATION_OUT_TIME = 500; // milliseconds -var Flashspot = GObject.registerClass( +export const Flashspot = GObject.registerClass( class Flashspot extends Lightbox.Lightbox { _init(area) { super._init(Main.uiGroup, { diff --git a/js/ui/scripting.js b/js/ui/scripting.js index aa405330c..e5746da61 100644 --- a/js/ui/scripting.js +++ b/js/ui/scripting.js @@ -3,13 +3,17 @@ destroyTestWindows, defineScriptEvent, scriptEvent, collectStatistics, runPerfScript */ -const { Gio, GLib, Meta, Shell } = imports.gi; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; + const Config = imports.misc.config; -const Main = imports.ui.main; -const Util = imports.misc.util; +import Main from './main.js'; +import * as Util from '../misc/util.js'; -const { loadInterfaceXML } = imports.misc.fileUtils; +import { loadInterfaceXML } from '../misc/fileUtilsModule.js'; // This module provides functionality for driving the shell user interface // in an automated fashion. The primary current use case for this is @@ -20,7 +24,7 @@ const { loadInterfaceXML } = imports.misc.fileUtils; // When scripting an automated test we want to make a series of calls // in a linear fashion, but we also want to be able to let the main // loop run so actions can finish. For this reason we write the script -// as an async function that uses await when it wants to let the main +// as an async export function that uses await when it wants to let the main // loop run. // // await Scripting.sleep(1000); @@ -37,7 +41,7 @@ const { loadInterfaceXML } = imports.misc.fileUtils; * current script for the specified amount of time. Use as * 'yield Scripting.sleep(500);' */ -function sleep(milliseconds) { +export function sleep(milliseconds) { return new Promise(resolve => { let id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, milliseconds, () => { resolve(); @@ -55,33 +59,33 @@ function sleep(milliseconds) { * current script until the shell is completely idle. Use as * 'yield Scripting.waitLeisure();' */ -function waitLeisure() { +export function waitLeisure() { return new Promise(resolve => { global.run_at_leisure(resolve); }); } const PerfHelperIface = loadInterfaceXML('org.gnome.Shell.PerfHelper'); -var PerfHelperProxy = Gio.DBusProxy.makeProxyWrapper(PerfHelperIface); -function PerfHelper() { +export const PerfHelperProxy = Gio.DBusProxy.makeProxyWrapper(PerfHelperIface); +export function PerfHelper() { return PerfHelperProxy(Gio.DBus.session, 'org.gnome.Shell.PerfHelper', '/org/gnome/Shell/PerfHelper'); } let _perfHelper = null; -function _getPerfHelper() { +export function _getPerfHelper() { if (_perfHelper == null) _perfHelper = PerfHelper(); return _perfHelper; } -function _spawnPerfHelper() { +export function _spawnPerfHelper() { let path = Config.LIBEXECDIR; let command = `${path}/gnome-shell-perf-helper`; Util.trySpawnCommandLine(command); } -function _callRemote(obj, method, ...args) { +export function _callRemote(obj, method, ...args) { return new Promise((resolve, reject) => { args.push((result, excp) => { if (excp) @@ -99,18 +103,18 @@ function _callRemote(obj, method, ...args) { * @param {Object} params: options for window creation. * {number} [params.width=640] - width of window, in pixels * {number} [params.height=480] - height of window, in pixels - * {bool} [params.alpha=false] - whether the window should have an alpha channel - * {bool} [params.maximized=false] - whether the window should be created maximized - * {bool} [params.redraws=false] - whether the window should continually redraw itself + * {boolean} [params.alpha=false] - whether the window should have an alpha channel + * {boolean} [params.maximized=false] - whether the window should be created maximized + * {boolean} [params.redraws=false] - whether the window should continually redraw itself * @returns {Promise} * * Creates a window using gnome-shell-perf-helper for testing purposes. - * While this function can be used with yield in an automation + * While this export function can be used with yield in an automation * script to pause until the D-Bus call to the helper process returns, * because of the normal X asynchronous mapping process, to actually wait * until the window has been mapped and exposed, use waitTestWindows(). */ -function createTestWindow(params = {}) { +export function createTestWindow(params = {}) { const { width = 640, height = 480, @@ -132,7 +136,7 @@ function createTestWindow(params = {}) { * Used within an automation script to pause until all windows previously * created with createTestWindow have been mapped and exposed. */ -function waitTestWindows() { +export function waitTestWindows() { let perfHelper = _getPerfHelper(); return _callRemote(perfHelper, perfHelper.WaitWindowsRemote); } @@ -142,12 +146,12 @@ function waitTestWindows() { * @returns {Promise} * * Destroys all windows previously created with createTestWindow(). - * While this function can be used with yield in an automation + * While this export function can be used with yield in an automation * script to pause until the D-Bus call to the helper process returns, * this doesn't guarantee that Mutter has actually finished the destroy * process because of normal X asynchronicity. */ -function destroyTestWindows() { +export function destroyTestWindows() { let perfHelper = _getPerfHelper(); return _callRemote(perfHelper, perfHelper.DestroyWindowsRemote); } @@ -161,7 +165,7 @@ function destroyTestWindows() { * within the 'script' namespace that is reserved for events defined locally * within a performance automation script */ -function defineScriptEvent(name, description) { +export function defineScriptEvent(name, description) { Shell.PerfLog.get_default().define_event(`script.${name}`, description, ""); @@ -174,7 +178,7 @@ function defineScriptEvent(name, description) { * Convenience function to record a script-local performance event * previously defined with defineScriptEvent */ -function scriptEvent(name) { +export function scriptEvent(name) { Shell.PerfLog.get_default().event(`script.${name}`); } @@ -183,11 +187,11 @@ function scriptEvent(name) { * * Convenience function to trigger statistics collection */ -function collectStatistics() { +export function collectStatistics() { Shell.PerfLog.get_default().collect_statistics(); } -function _collect(scriptModule, outputFile) { +export function _collect(scriptModule, outputFile) { let eventHandlers = {}; for (let f in scriptModule) { @@ -282,7 +286,7 @@ function _collect(scriptModule, outputFile) { } } -async function _runPerfScript(scriptModule, outputFile) { +export async function _runPerfScript(scriptModule, outputFile) { try { await scriptModule.run(); } catch (err) { @@ -340,7 +344,7 @@ async function _runPerfScript(scriptModule, outputFile) { * After running the script and collecting statistics from the * event log, GNOME Shell will exit. **/ -function runPerfScript(scriptModule, outputFile) { +export function runPerfScript(scriptModule, outputFile) { Shell.PerfLog.get_default().set_enabled(true); _spawnPerfHelper(); diff --git a/js/ui/search.js b/js/ui/search.js index 7300b053e..800fbaed4 100644 --- a/js/ui/search.js +++ b/js/ui/search.js @@ -1,20 +1,26 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported SearchResultsView */ -const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi; - -const AppDisplay = imports.ui.appDisplay; -const IconGrid = imports.ui.iconGrid; -const Main = imports.ui.main; -const ParentalControlsManager = imports.misc.parentalControlsManager; -const RemoteSearch = imports.ui.remoteSearch; -const Util = imports.misc.util; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; + +import * as AppDisplay from './appDisplay.js'; +import * as IconGrid from './iconGrid.js'; +import Main from './main.js'; +import * as ParentalControlsManager from '../misc/parentalControlsManager.js'; +import * as RemoteSearch from './remoteSearch.js'; +import * as Util from '../misc/util.js'; const SEARCH_PROVIDERS_SCHEMA = 'org.gnome.desktop.search-providers'; -var MAX_LIST_SEARCH_RESULTS_ROWS = 5; +export let MAX_LIST_SEARCH_RESULTS_ROWS = 5; -var MaxWidthBox = GObject.registerClass( +export const MaxWidthBox = GObject.registerClass( class MaxWidthBox extends St.BoxLayout { vfunc_allocate(box) { let themeNode = this.get_theme_node(); @@ -32,7 +38,7 @@ class MaxWidthBox extends St.BoxLayout { } }); -var SearchResult = GObject.registerClass( +export const SearchResult = GObject.registerClass( class SearchResult extends St.Button { _init(provider, metaInfo, resultsView) { this.provider = provider; @@ -61,8 +67,13 @@ class SearchResult extends St.Button { } }); -var ListSearchResult = GObject.registerClass( +export const ListSearchResult = GObject.registerClass( class ListSearchResult extends SearchResult { + /** + * @param {*} provider + * @param {*} metaInfo + * @param {*} resultsView + */ _init(provider, metaInfo, resultsView) { super._init(provider, metaInfo, resultsView); @@ -132,7 +143,7 @@ class ListSearchResult extends SearchResult { } }); -var GridSearchResult = GObject.registerClass( +export const GridSearchResult = GObject.registerClass( class GridSearchResult extends SearchResult { _init(provider, metaInfo, resultsView) { super._init(provider, metaInfo, resultsView); @@ -152,7 +163,7 @@ class GridSearchResult extends SearchResult { } }); -var SearchResultsBase = GObject.registerClass({ +export const SearchResultsBase = GObject.registerClass({ GTypeFlags: GObject.TypeFlags.ABSTRACT, Properties: { 'focus-child': GObject.ParamSpec.object( @@ -194,6 +205,24 @@ var SearchResultsBase = GObject.registerClass({ return null; } + _clearResultDisplay() { + throw new GObject.NotImplementedError(`_clearResultDisplay in ${this.constructor.name}`); + } + + /** + * @returns {number} + */ + _getMaxDisplayedResults() { + throw new GObject.NotImplementedError(`_getMaxDisplayedResults in ${this.constructor.name}`); + } + + /** + * @param {*} display + */ + _addItem(display) { + throw new GObject.NotImplementedError(`_addItem in ${this.constructor.name}`); + } + clear() { this._cancellable.cancel(); for (let resultId in this._resultDisplays) @@ -293,7 +322,7 @@ var SearchResultsBase = GObject.registerClass({ } }); -var ListSearchResults = GObject.registerClass( +export const ListSearchResults = GObject.registerClass( class ListSearchResults extends SearchResultsBase { _init(provider, resultsView) { super._init(provider, resultsView); @@ -348,7 +377,7 @@ class ListSearchResults extends SearchResultsBase { } }); -var GridSearchResultsLayout = GObject.registerClass({ +export const GridSearchResultsLayout = GObject.registerClass({ Properties: { 'spacing': GObject.ParamSpec.int('spacing', 'Spacing', 'Spacing', GObject.ParamFlags.READWRITE, 0, GLib.MAXINT32, 0), @@ -363,6 +392,9 @@ var GridSearchResultsLayout = GObject.registerClass({ this._container = container; } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_width(container, forHeight) { let minWidth = 0; let natWidth = 0; @@ -386,6 +418,9 @@ var GridSearchResultsLayout = GObject.registerClass({ return [minWidth, natWidth]; } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_height(container, forWidth) { let minHeight = 0; let natHeight = 0; @@ -467,12 +502,12 @@ var GridSearchResultsLayout = GObject.registerClass({ } }); -var GridSearchResults = GObject.registerClass( +export const GridSearchResults = GObject.registerClass( class GridSearchResults extends SearchResultsBase { _init(provider, resultsView) { super._init(provider, resultsView); - this._grid = new St.Widget({ style_class: 'grid-search-results' }); + this._grid = new St.Widget({ style_class: 'grid-search-results', layout_manager: /** @type {GridSearchResultsLayout["prototype"]} */ (null) }); this._grid.layout_manager = new GridSearchResultsLayout(); this._grid.connect('style-changed', () => { @@ -548,7 +583,7 @@ class GridSearchResults extends SearchResultsBase { } }); -var SearchResultsView = GObject.registerClass({ +export const SearchResultsView = GObject.registerClass({ Signals: { 'terms-changed': {} }, }, class SearchResultsView extends St.BoxLayout { _init() { @@ -901,7 +936,7 @@ var SearchResultsView = GObject.registerClass({ } }); -var ProviderInfo = GObject.registerClass( +export const ProviderInfo = GObject.registerClass( class ProviderInfo extends St.Button { _init(provider) { this.provider = provider; diff --git a/js/ui/searchController.js b/js/ui/searchController.js index 5d25897c7..b4decf69e 100644 --- a/js/ui/searchController.js +++ b/js/ui/searchController.js @@ -1,13 +1,16 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported SearchController */ -const { Clutter, GObject, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import GObject from 'gi://GObject'; +import St from 'gi://St'; -const Main = imports.ui.main; -const Search = imports.ui.search; -const ShellEntry = imports.ui.shellEntry; -var FocusTrap = GObject.registerClass( +import Main from './main.js'; +import * as Search from './search.js'; +import * as ShellEntry from './shellEntry.js'; + +export const FocusTrap = GObject.registerClass( class FocusTrap extends St.Widget { vfunc_navigate_focus(from, direction) { if (direction === St.DirectionType.TAB_FORWARD || @@ -24,7 +27,7 @@ function getTermsForSearchString(searchString) { return searchString.split(/\s+/); } -var SearchController = GObject.registerClass({ +export const SearchController = GObject.registerClass({ Properties: { 'search-active': GObject.ParamSpec.boolean( 'search-active', 'search-active', 'search-active', diff --git a/js/ui/sessionMode.js b/js/ui/sessionMode.js index 96136837a..dd32e9dd0 100644 --- a/js/ui/sessionMode.js +++ b/js/ui/sessionMode.js @@ -1,15 +1,18 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported SessionMode, listModes */ -const GLib = imports.gi.GLib; -const Signals = imports.misc.signals; +import GLib from 'gi://GLib'; +import * as Signals from '../misc/signals.js'; -const FileUtils = imports.misc.fileUtils; +import * as FileUtils from '../misc/fileUtilsModule.js'; const Config = imports.misc.config; const DEFAULT_MODE = 'restrictive'; +import { LoginDialog } from "../gdm/loginDialog.js"; +import { UnlockDialog } from "../ui/unlockDialog.js"; + const _modes = { 'restrictive': { parentMode: null, @@ -44,7 +47,7 @@ const _modes = { hasNotifications: true, isGreeter: true, isPrimary: true, - unlockDialog: imports.gdm.loginDialog.LoginDialog, + unlockDialog: LoginDialog, components: Config.HAVE_NETWORKMANAGER ? ['networkAgent', 'polkitAgent'] : ['polkitAgent'], @@ -82,7 +85,7 @@ const _modes = { hasNotifications: true, isLocked: false, isPrimary: true, - unlockDialog: imports.ui.unlockDialog.UnlockDialog, + unlockDialog: UnlockDialog, components: Config.HAVE_NETWORKMANAGER ? ['networkAgent', 'polkitAgent', 'telepathyClient', 'keyring', 'autorunManager', 'automountManager'] @@ -125,10 +128,12 @@ function _loadMode(file, info) { } function _loadModes() { + log('load modes...') FileUtils.collectFromDatadirs('modes', false, _loadMode); } -function listModes() { +export function listModes() { + log('list modes...') _loadModes(); let loop = new GLib.MainLoop(null, false); let id = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { @@ -138,12 +143,14 @@ function listModes() { print(names[i]); } loop.quit(); + + return false; }); GLib.Source.set_name_by_id(id, '[gnome-shell] listModes'); loop.run(); } -var SessionMode = class extends Signals.EventEmitter { +export class SessionMode extends Signals.EventEmitter { constructor() { super(); diff --git a/js/ui/shellDBus.js b/js/ui/shellDBus.js index a8070eb92..0a0afd81c 100644 --- a/js/ui/shellDBus.js +++ b/js/ui/shellDBus.js @@ -1,22 +1,24 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported GnomeShell, ScreenSaverDBus */ -const { Gio, GLib, Meta } = imports.gi; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import Meta from 'gi://Meta'; -const Config = imports.misc.config; -const ExtensionDownloader = imports.ui.extensionDownloader; -const ExtensionUtils = imports.misc.extensionUtils; -const Main = imports.ui.main; -const Screenshot = imports.ui.screenshot; -const { loadInterfaceXML } = imports.misc.fileUtils; -const { DBusSenderChecker } = imports.misc.util; -const { ControlsState } = imports.ui.overviewControls; +const Config = imports.misc.config; +import * as ExtensionDownloader from './extensionDownloader.js'; +import * as ExtensionUtils from '../misc/extensionUtils.js'; +import Main from './main.js'; +import * as Screenshot from './screenshot.js'; +import { ControlsState } from './overviewControls.js'; +import { DBusSenderChecker } from '../misc/util.js'; +import { loadInterfaceXML } from '../misc/fileUtilsModule.js'; const GnomeShellIface = loadInterfaceXML('org.gnome.Shell'); const ScreenSaverIface = loadInterfaceXML('org.gnome.ScreenSaver'); -var GnomeShell = class { +export class GnomeShell { constructor() { this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GnomeShellIface, this); this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell'); @@ -39,9 +41,9 @@ var GnomeShell = class { this._cachedOverviewVisible = false; Main.overview.connect('showing', - this._checkOverviewVisibleChanged.bind(this)); + this._checkOverviewVisibleChanged.bind(this)); Main.overview.connect('hidden', - this._checkOverviewVisibleChanged.bind(this)); + this._checkOverviewVisibleChanged.bind(this)); } /** @@ -116,10 +118,10 @@ var GnomeShell = class { params[param] = params[param].deep_unpack(); let { connector, - label, - level, - max_level: maxLevel, - icon: serializedIcon } = params; + label, + level, + max_level: maxLevel, + icon: serializedIcon } = params; let monitorIndex = -1; if (connector) { @@ -233,7 +235,7 @@ var GnomeShell = class { let ungrabSucceeded = true; for (let i = 0; i < actions.length; i++) - ungrabSucceeded &= this._ungrabAcceleratorForSender(actions[i], sender); + ungrabSucceeded = ungrabSucceeded && this._ungrabAcceleratorForSender(actions[i], sender); invocation.return_value(GLib.Variant.new('(b)', [ungrabSucceeded])); } @@ -274,7 +276,7 @@ var GnomeShell = class { if (!this._grabbers.has(sender)) { let id = Gio.bus_watch_name(Gio.BusType.SESSION, sender, 0, null, - this._onGrabberBusNameVanished.bind(this)); + this._onGrabberBusNameVanished.bind(this)); this._grabbers.set(sender, id); } @@ -363,7 +365,7 @@ var GnomeShell = class { const GnomeShellExtensionsIface = loadInterfaceXML('org.gnome.Shell.Extensions'); -var GnomeShellExtensions = class { +export class GnomeShellExtensions { constructor() { this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GnomeShellExtensionsIface, this); this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell'); @@ -378,8 +380,8 @@ var GnomeShellExtensions = class { new GLib.Variant('b', this._userExtensionsEnabled)); }); - Main.extensionManager.connect('extension-state-changed', - this._extensionStateChanged.bind(this)); + // Main.extensionManager.connect('extension-state-changed', + // this._extensionStateChanged.bind(this)); } ListExtensions() { @@ -460,11 +462,11 @@ var GnomeShellExtensions = class { new GLib.Variant('(sa{sv})', [newState.uuid, state])); this._dbusImpl.emit_signal('ExtensionStatusChanged', - GLib.Variant.new('(sis)', [newState.uuid, newState.state, newState.error])); + GLib.Variant.new('(sis)', [newState.uuid, newState.state, newState.error])); } }; -var ScreenSaverDBus = class { +export class ScreenSaverDBus { constructor(screenShield) { this._screenShield = screenShield; screenShield.connect('active-changed', shield => { diff --git a/js/ui/shellEntry.js b/js/ui/shellEntry.js index f3e2b63d3..56a40f210 100644 --- a/js/ui/shellEntry.js +++ b/js/ui/shellEntry.js @@ -1,13 +1,18 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported addContextMenu CapsLockWarning */ -const { Clutter, GObject, Pango, Shell, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import GObject from 'gi://GObject'; +import Pango from 'gi://Pango'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; -const BoxPointer = imports.ui.boxpointer; -const Main = imports.ui.main; -const PopupMenu = imports.ui.popupMenu; -var EntryMenu = class extends PopupMenu.PopupMenu { +import * as BoxPointer from './boxpointer.js'; +import Main from './main.js'; +import * as PopupMenu from './popupMenu.js'; + +export class EntryMenu extends PopupMenu.PopupMenu { constructor(entry) { super(entry, 0, St.Side.TOP); @@ -126,7 +131,7 @@ function _onPopup(actor, entry) { entry.menu.open(BoxPointer.PopupAnimation.FULL); } -function addContextMenu(entry, params = {}) { +export function addContextMenu(entry, params = {}) { if (entry.menu) return; @@ -156,7 +161,7 @@ function addContextMenu(entry, params = {}) { }); } -var CapsLockWarning = GObject.registerClass( +export const CapsLockWarning = GObject.registerClass( class CapsLockWarning extends St.Label { _init(params) { let defaultParams = { style_class: 'caps-lock-warning-label' }; diff --git a/js/ui/shellMountOperation.js b/js/ui/shellMountOperation.js index 1101c03b3..0d8bde59e 100644 --- a/js/ui/shellMountOperation.js +++ b/js/ui/shellMountOperation.js @@ -1,21 +1,28 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported ShellMountOperation, GnomeShellMountOpHandler */ -const { Clutter, Gio, GLib, GObject, Pango, Shell, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Pango from 'gi://Pango'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; -const Animation = imports.ui.animation; -const CheckBox = imports.ui.checkBox; -const Dialog = imports.ui.dialog; -const Main = imports.ui.main; -const MessageTray = imports.ui.messageTray; -const ModalDialog = imports.ui.modalDialog; -const ShellEntry = imports.ui.shellEntry; -const { loadInterfaceXML } = imports.misc.fileUtils; -const Util = imports.misc.util; +import * as Animation from './animation.js'; +import * as CheckBox from './checkBox.js'; +import * as Dialog from './dialog.js'; +import Main from './main.js'; +import * as MessageTray from './messageTray.js'; +import * as ModalDialog from './modalDialog.js'; +import * as ShellEntry from './shellEntry.js'; -var LIST_ITEM_ICON_SIZE = 48; -var WORK_SPINNER_ICON_SIZE = 16; +import { loadInterfaceXML } from '../misc/fileUtilsModule.js'; +import * as Util from '../misc/util.js'; + +export let LIST_ITEM_ICON_SIZE = 48; +export let WORK_SPINNER_ICON_SIZE = 16; const REMEMBER_MOUNT_PASSWORD_KEY = 'remember-mount-password'; @@ -48,7 +55,7 @@ function _setLabelsForMessage(content, message) { /* -------------------------------------------------------- */ -var ShellMountOperation = class { +export class ShellMountOperation { constructor(source, params = {}) { const { existingDialog = null } = params; @@ -186,7 +193,7 @@ var ShellMountOperation = class { } }; -var ShellUnmountNotifier = GObject.registerClass( +export const ShellUnmountNotifier = GObject.registerClass( class ShellUnmountNotifier extends MessageTray.Source { _init() { super._init('', 'media-removable'); @@ -224,7 +231,7 @@ class ShellUnmountNotifier extends MessageTray.Source { } }); -var ShellMountQuestionDialog = GObject.registerClass({ +export const ShellMountQuestionDialog = GObject.registerClass({ Signals: { 'response': { param_types: [GObject.TYPE_INT] } }, }, class ShellMountQuestionDialog extends ModalDialog.ModalDialog { _init() { @@ -252,7 +259,7 @@ var ShellMountQuestionDialog = GObject.registerClass({ } }); -var ShellMountPasswordDialog = GObject.registerClass({ +export const ShellMountPasswordDialog = GObject.registerClass({ Signals: { 'response': { param_types: [GObject.TYPE_INT, GObject.TYPE_STRING, GObject.TYPE_BOOLEAN, @@ -415,9 +422,10 @@ var ShellMountPasswordDialog = GObject.registerClass({ _onEntryActivate() { let pim = 0; if (this._pimEntry !== null) { - pim = this._pimEntry.get_text(); + // FIXME + let pim_text = this._pimEntry.get_text(); - if (isNaN(pim)) { + if (Number.isNaN(Number.parseInt(pim_text))) { this._pimEntry.set_text(''); this._errorMessageLabel.text = _('The PIM must be a number or empty.'); this._errorMessageLabel.opacity = 255; @@ -439,7 +447,7 @@ var ShellMountPasswordDialog = GObject.registerClass({ this._hiddenVolume.checked, this._systemVolume && this._systemVolume.checked, - parseInt(pim)); + Number.parseInt(pim.toFixed(0))); } _onKeyfilesCheckboxClicked() { @@ -469,7 +477,7 @@ var ShellMountPasswordDialog = GObject.registerClass({ } }); -var ShellProcessesDialog = GObject.registerClass({ +export const ShellProcessesDialog = GObject.registerClass({ Signals: { 'response': { param_types: [GObject.TYPE_INT] } }, }, class ShellProcessesDialog extends ModalDialog.ModalDialog { _init() { @@ -526,14 +534,15 @@ var ShellProcessesDialog = GObject.registerClass({ const GnomeShellMountOpIface = loadInterfaceXML('org.Gtk.MountOperationHandler'); -var ShellMountOperationType = { +/** @enum {number} */ +export const ShellMountOperationType = { NONE: 0, ASK_PASSWORD: 1, ASK_QUESTION: 2, SHOW_PROCESSES: 3, }; -var GnomeShellMountOpHandler = class { +export class GnomeShellMountOpHandler { constructor() { this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GnomeShellMountOpIface, this); this._dbusImpl.export(Gio.DBus.session, '/org/gtk/MountOperationHandler'); @@ -613,7 +622,7 @@ var GnomeShellMountOpHandler = class { AskPasswordAsync(params, invocation) { let [id, message, iconName_, defaultUser_, defaultDomain_, flags] = params; - if (this._setCurrentRequest(invocation, id, ShellMountOperationType.ASK_PASSWORD)) { + if (this._setCurrentRequest(invocation, id, ShellMountOperationType.ASK_PASSWORD) && this._dialog instanceof ShellMountPasswordDialog) { this._dialog.reaskPassword(); return; } @@ -663,14 +672,15 @@ var GnomeShellMountOpHandler = class { AskQuestionAsync(params, invocation) { let [id, message, iconName_, choices] = params; - if (this._setCurrentRequest(invocation, id, ShellMountOperationType.ASK_QUESTION)) { + if (this._setCurrentRequest(invocation, id, ShellMountOperationType.ASK_QUESTION) && this._dialog instanceof ShellMountQuestionDialog) { this._dialog.update(message, choices); return; } this._closeDialog(); - this._dialog = new ShellMountQuestionDialog(message); + // FIXME + this._dialog = new ShellMountQuestionDialog(); this._dialog.connect('response', (object, choice) => { let response; let details = {}; @@ -710,7 +720,7 @@ var GnomeShellMountOpHandler = class { ShowProcessesAsync(params, invocation) { let [id, message, iconName_, applicationPids, choices] = params; - if (this._setCurrentRequest(invocation, id, ShellMountOperationType.SHOW_PROCESSES)) { + if (this._setCurrentRequest(invocation, id, ShellMountOperationType.SHOW_PROCESSES) && this._dialog instanceof ShellProcessesDialog) { this._dialog.update(message, applicationPids, choices); return; } diff --git a/js/ui/slider.js b/js/ui/slider.js index ba3a233f1..7216b0d0e 100644 --- a/js/ui/slider.js +++ b/js/ui/slider.js @@ -1,13 +1,16 @@ /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ /* exported Slider */ -const { Atk, Clutter, GObject } = imports.gi; +import Atk from 'gi://Atk'; +import Clutter from 'gi://Clutter'; +import GObject from 'gi://GObject'; -const BarLevel = imports.ui.barLevel; -var SLIDER_SCROLL_STEP = 0.02; /* Slider scrolling step in % */ +import * as BarLevel from './barLevel.js'; -var Slider = GObject.registerClass({ +export let SLIDER_SCROLL_STEP = 0.02; /* Slider scrolling step in % */ + +export const Slider = GObject.registerClass({ Signals: { 'drag-begin': {}, 'drag-end': {}, diff --git a/js/ui/status/accessibility.js b/js/ui/status/accessibility.js index a0513f68c..844acbc8b 100644 --- a/js/ui/status/accessibility.js +++ b/js/ui/status/accessibility.js @@ -1,10 +1,14 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported ATIndicator */ -const { Gio, GLib, GObject, St } = imports.gi; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import St from 'gi://St'; -const PanelMenu = imports.ui.panelMenu; -const PopupMenu = imports.ui.popupMenu; + +import * as PanelMenu from '../panelMenu.js'; +import * as PopupMenu from '../popupMenu.js'; const A11Y_SCHEMA = 'org.gnome.desktop.a11y'; const KEY_ALWAYS_SHOW = 'always-show-universal-access-status'; @@ -29,7 +33,7 @@ const KEY_TEXT_SCALING_FACTOR = 'text-scaling-factor'; const HIGH_CONTRAST_THEME = 'HighContrast'; -var ATIndicator = GObject.registerClass( +export const ATIndicator = GObject.registerClass( class ATIndicator extends PanelMenu.Button { _init() { super._init(0.5, _("Accessibility")); @@ -97,6 +101,12 @@ class ATIndicator extends PanelMenu.Button { GLib.Source.set_name_by_id(this._syncMenuVisibilityIdle, '[gnome-shell] this._syncMenuVisibility'); } + /** + * @param {string} string + * @param {boolean} initialValue + * @param {boolean} writable + * @param {(state: boolean) => void} onSet + */ _buildItemExtended(string, initialValue, writable, onSet) { let widget = new PopupMenu.PopupSwitchMenuItem(string, initialValue); if (!writable) { diff --git a/js/ui/status/bluetooth.js b/js/ui/status/bluetooth.js index d4db93ea7..85c240a9b 100644 --- a/js/ui/status/bluetooth.js +++ b/js/ui/status/bluetooth.js @@ -1,13 +1,17 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Indicator */ -const { Gio, GLib, GnomeBluetooth, GObject } = imports.gi; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GnomeBluetooth from 'gi://GnomeBluetooth'; +import GObject from 'gi://GObject'; -const Main = imports.ui.main; -const PanelMenu = imports.ui.panelMenu; -const PopupMenu = imports.ui.popupMenu; -const { loadInterfaceXML } = imports.misc.fileUtils; +import Main from '../main.js'; +import * as PanelMenu from '../panelMenu.js'; +import * as PopupMenu from '../popupMenu.js'; + +import { loadInterfaceXML } from '../../misc/fileUtilsModule.js'; const BUS_NAME = 'org.gnome.SettingsDaemon.Rfkill'; const OBJECT_PATH = '/org/gnome/SettingsDaemon/Rfkill'; @@ -17,7 +21,7 @@ const RfkillManagerProxy = Gio.DBusProxy.makeProxyWrapper(RfkillManagerInterface const HAD_BLUETOOTH_DEVICES_SETUP = 'had-bluetooth-devices-setup'; -var Indicator = GObject.registerClass( +export const Indicator = GObject.registerClass( class Indicator extends PanelMenu.SystemIndicator { _init() { super._init(); @@ -73,10 +77,10 @@ class Indicator extends PanelMenu.SystemIndicator { _getDefaultAdapter() { let [ret, iter] = this._model.get_iter_first(); while (ret) { - let isDefault = this._model.get_value(iter, - GnomeBluetooth.Column.DEFAULT); - let isPowered = this._model.get_value(iter, - GnomeBluetooth.Column.POWERED); + /** @type {boolean} */ + let isDefault = (this._model.get_value(iter, GnomeBluetooth.Column.DEFAULT)); + /** @type {boolean} */ + let isPowered = (this._model.get_value(iter, GnomeBluetooth.Column.POWERED)); if (isDefault && isPowered) return iter; ret = this._model.iter_next(iter); @@ -91,17 +95,19 @@ class Indicator extends PanelMenu.SystemIndicator { let deviceInfos = []; let [ret, iter] = this._model.iter_children(adapter); while (ret) { - const isPaired = this._model.get_value(iter, - GnomeBluetooth.Column.PAIRED); - const isTrusted = this._model.get_value(iter, - GnomeBluetooth.Column.TRUSTED); + /** @type {boolean} */ + const isPaired = (this._model.get_value(iter, + GnomeBluetooth.Column.PAIRED)); + /** @type {boolean} */ + const isTrusted = (this._model.get_value(iter, + GnomeBluetooth.Column.TRUSTED)); if (isPaired || isTrusted) { deviceInfos.push({ - connected: this._model.get_value(iter, - GnomeBluetooth.Column.CONNECTED), - name: this._model.get_value(iter, - GnomeBluetooth.Column.ALIAS), + connected: /** @type {boolean} */ (this._model.get_value(iter, + GnomeBluetooth.Column.CONNECTED)), + name: /** @type {string} */ (this._model.get_value(iter, + GnomeBluetooth.Column.ALIAS)), }); } diff --git a/js/ui/status/brightness.js b/js/ui/status/brightness.js index 2a27e7844..292dcfaac 100644 --- a/js/ui/status/brightness.js +++ b/js/ui/status/brightness.js @@ -1,13 +1,15 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Indicator */ -const { Gio, GObject, St } = imports.gi; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; +import St from 'gi://St'; -const PanelMenu = imports.ui.panelMenu; -const PopupMenu = imports.ui.popupMenu; -const Slider = imports.ui.slider; +import * as PanelMenu from '../panelMenu.js'; +import * as PopupMenu from '../popupMenu.js'; +import * as Slider from '../slider.js'; -const { loadInterfaceXML } = imports.misc.fileUtils; +import { loadInterfaceXML } from '../../misc/fileUtilsModule.js'; const BUS_NAME = 'org.gnome.SettingsDaemon.Power'; const OBJECT_PATH = '/org/gnome/SettingsDaemon/Power'; @@ -15,7 +17,7 @@ const OBJECT_PATH = '/org/gnome/SettingsDaemon/Power'; const BrightnessInterface = loadInterfaceXML('org.gnome.SettingsDaemon.Power.Screen'); const BrightnessProxy = Gio.DBusProxy.makeProxyWrapper(BrightnessInterface); -var Indicator = GObject.registerClass( +export const Indicator = GObject.registerClass( class Indicator extends PanelMenu.SystemIndicator { _init() { super._init(); diff --git a/js/ui/status/dwellClick.js b/js/ui/status/dwellClick.js index 4b1d8a2c1..a3c35fafc 100644 --- a/js/ui/status/dwellClick.js +++ b/js/ui/status/dwellClick.js @@ -1,7 +1,11 @@ /* exported DwellClickIndicator */ -const { Clutter, Gio, GLib, GObject, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import St from 'gi://St'; -const PanelMenu = imports.ui.panelMenu; +import * as PanelMenu from '../panelMenu.js'; const MOUSE_A11Y_SCHEMA = 'org.gnome.desktop.a11y.mouse'; const KEY_DWELL_CLICK_ENABLED = 'dwell-click-enabled'; @@ -30,7 +34,7 @@ const DWELL_CLICK_MODES = { }, }; -var DwellClickIndicator = GObject.registerClass( +export const DwellClickIndicator = GObject.registerClass( class DwellClickIndicator extends PanelMenu.Button { _init() { super._init(0.5, _("Dwell Click")); diff --git a/js/ui/status/keyboard.js b/js/ui/status/keyboard.js index 80a91bdfa..c8ac086e0 100644 --- a/js/ui/status/keyboard.js +++ b/js/ui/status/keyboard.js @@ -1,22 +1,35 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported InputSourceIndicator */ -const { Clutter, Gio, GLib, GObject, IBus, Meta, Shell, St } = imports.gi; -const Gettext = imports.gettext; -const Signals = imports.misc.signals; - -const IBusManager = imports.misc.ibusManager; -const KeyboardManager = imports.misc.keyboardManager; -const Main = imports.ui.main; -const PopupMenu = imports.ui.popupMenu; -const PanelMenu = imports.ui.panelMenu; -const SwitcherPopup = imports.ui.switcherPopup; -const Util = imports.misc.util; - -var INPUT_SOURCE_TYPE_XKB = 'xkb'; -var INPUT_SOURCE_TYPE_IBUS = 'ibus'; - -var LayoutMenuItem = GObject.registerClass( +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import IBus from 'gi://IBus'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; +import * as Gettext from 'gettext'; +import * as Signals from '../../misc/signals.js'; + +import * as IBusManager from '../../misc/ibusManager.js'; +import * as KeyboardManager from '../../misc/keyboardManager.js'; +import Main from '../main.js'; +import * as PopupMenu from '../popupMenu.js'; +import * as PanelMenu from '../panelMenu.js'; +import * as SwitcherPopup from '../switcherPopup.js'; +import * as Util from '../../misc/util.js'; + +export let INPUT_SOURCE_TYPE_XKB = 'xkb'; +export let INPUT_SOURCE_TYPE_IBUS = 'ibus'; + +/** + * @typedef {object} LayoutMenuItemParams + * @property {string} displayName + * @property {string} shortName + */ + +export const LayoutMenuItem = GObject.registerClass( class LayoutMenuItem extends PopupMenu.PopupBaseMenuItem { _init(displayName, shortName) { super._init(); @@ -32,7 +45,7 @@ class LayoutMenuItem extends PopupMenu.PopupBaseMenuItem { } }); -var InputSource = class extends Signals.EventEmitter { +export class InputSource extends Signals.EventEmitter { constructor(type, id, displayName, shortName, index) { super(); @@ -72,8 +85,13 @@ var InputSource = class extends Signals.EventEmitter { } }; -var InputSourcePopup = GObject.registerClass( +export const InputSourcePopup = GObject.registerClass( class InputSourcePopup extends SwitcherPopup.SwitcherPopup { + /** + * @param {*} items + * @param {*} action + * @param {*} actionBackward + */ _init(items, action, actionBackward) { super._init(items); @@ -105,10 +123,13 @@ class InputSourcePopup extends SwitcherPopup.SwitcherPopup { } }); -var InputSourceSwitcher = GObject.registerClass( +export const InputSourceSwitcher = GObject.registerClass( class InputSourceSwitcher extends SwitcherPopup.SwitcherList { + /** + * @param {*} items + */ _init(items) { - super._init(true); + super._init({ squareItems: true }); for (let i = 0; i < items.length; i++) this._addIcon(items[i]); @@ -136,7 +157,7 @@ class InputSourceSwitcher extends SwitcherPopup.SwitcherList { } }); -var InputSourceSettings = class extends Signals.EventEmitter { +export class InputSourceSettings extends Signals.EventEmitter { constructor() { super(); @@ -177,7 +198,7 @@ var InputSourceSettings = class extends Signals.EventEmitter { } }; -var InputSourceSystemSettings = class extends InputSourceSettings { +export class InputSourceSystemSettings extends InputSourceSettings { constructor() { super(); @@ -202,6 +223,7 @@ var InputSourceSystemSettings = class extends InputSourceSettings { } async _reload() { + /** @type {{ [key: string]: GLib.Variant }} */ let props; try { const result = await Gio.DBus.system.call( @@ -211,15 +233,19 @@ var InputSourceSystemSettings = class extends InputSourceSettings { 'GetAll', new GLib.Variant('(s)', [this._BUS_IFACE]), null, Gio.DBusCallFlags.NONE, -1, null); - [props] = result.deep_unpack(); + /** @type {[{ [key: string]: GLib.Variant }]} */ + [props] = (result.deep_unpack()); } catch (e) { log('Could not get properties from %s'.format(this._BUS_NAME)); return; } - const layouts = props['X11Layout'].unpack(); - const variants = props['X11Variant'].unpack(); - const options = props['X11Options'].unpack(); + /** @type {string} */ + const layouts = (props['X11Layout'].unpack()); + /** @type {string} */ + const variants = (props['X11Variant'].unpack()); + /** @type {string} */ + const options = (props['X11Options'].unpack()); if (layouts !== this._layouts || variants !== this._variants) { @@ -252,7 +278,7 @@ var InputSourceSystemSettings = class extends InputSourceSettings { } }; -var InputSourceSessionSettings = class extends InputSourceSettings { +export class InputSourceSessionSettings extends InputSourceSettings { constructor() { super(); @@ -269,12 +295,14 @@ var InputSourceSessionSettings = class extends InputSourceSettings { } _getSourcesList(key) { + /** @type {{ type: string, id: number }[]} */ let sourcesList = []; let sources = this._settings.get_value(key); let nSources = sources.n_children(); for (let i = 0; i < nSources; i++) { - let [type, id] = sources.get_child_value(i).deep_unpack(); + /** @type {[string, number]} */ + let [type, id] = (sources.get_child_value(i).deep_unpack()); sourcesList.push({ type, id }); } return sourcesList; @@ -289,7 +317,8 @@ var InputSourceSessionSettings = class extends InputSourceSettings { } set mruSources(sourcesList) { - let sources = GLib.Variant.new('a(ss)', sourcesList); + let list = sourcesList.map(/** @returns {[string, string]} */ ({type, id}) => [type, id.toFixed(0)]); + let sources = GLib.Variant.new('a(ss)', list); this._settings.set_value(this._KEY_MRU_SOURCES, sources); } @@ -302,7 +331,7 @@ var InputSourceSessionSettings = class extends InputSourceSettings { } }; -var InputSourceManager = class extends Signals.EventEmitter { +export class InputSourceManager extends Signals.EventEmitter { constructor() { super(); @@ -782,16 +811,20 @@ var InputSourceManager = class extends Signals.EventEmitter { } }; +/** @type {InputSourceManager | null} */ let _inputSourceManager = null; -function getInputSourceManager() { +export function getInputSourceManager() { if (_inputSourceManager == null) _inputSourceManager = new InputSourceManager(); return _inputSourceManager; } -var InputSourceIndicatorContainer = GObject.registerClass( +export const InputSourceIndicatorContainer = GObject.registerClass( class InputSourceIndicatorContainer extends St.Widget { + /** + * @returns {[number, number]} + */ vfunc_get_preferred_width(forHeight) { // Here, and in vfunc_get_preferred_height, we need to query // for the height of all children, but we ignore the results @@ -803,6 +836,9 @@ class InputSourceIndicatorContainer extends St.Widget { }, [0, 0]); } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_height(forWidth) { return this.get_children().reduce((maxHeight, child) => { let height = child.get_preferred_height(forWidth); @@ -826,7 +862,7 @@ class InputSourceIndicatorContainer extends St.Widget { } }); -var InputSourceIndicator = GObject.registerClass( +export const InputSourceIndicator = GObject.registerClass( class InputSourceIndicator extends PanelMenu.Button { _init() { super._init(0.5, _("Keyboard")); diff --git a/js/ui/status/location.js b/js/ui/status/location.js index 6b399793c..c672da6dc 100644 --- a/js/ui/status/location.js +++ b/js/ui/status/location.js @@ -1,16 +1,21 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Indicator */ -const { Clutter, Gio, GLib, GObject, Shell, St } = imports.gi; - -const Dialog = imports.ui.dialog; -const Main = imports.ui.main; -const PanelMenu = imports.ui.panelMenu; -const PopupMenu = imports.ui.popupMenu; -const ModalDialog = imports.ui.modalDialog; -const PermissionStore = imports.misc.permissionStore; - -const { loadInterfaceXML } = imports.misc.fileUtils; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; + +import * as Dialog from '../dialog.js'; +import Main from '../main.js'; +import * as PanelMenu from '../panelMenu.js'; +import * as PopupMenu from '../popupMenu.js'; +import * as ModalDialog from '../modalDialog.js'; +import * as PermissionStore from '../../misc/permissionStore.js'; + +import { loadInterfaceXML } from '../../misc/fileUtilsModule.js'; const LOCATION_SCHEMA = 'org.gnome.system.location'; const MAX_ACCURACY_LEVEL = 'max-accuracy-level'; @@ -19,7 +24,8 @@ const ENABLED = 'enabled'; const APP_PERMISSIONS_TABLE = 'location'; const APP_PERMISSIONS_ID = 'location'; -var GeoclueAccuracyLevel = { +/** @enum {number} */ +export const GeoclueAccuracyLevel = { NONE: 0, COUNTRY: 1, CITY: 4, @@ -37,10 +43,10 @@ function accuracyLevelToString(accuracyLevel) { return 'NONE'; } -var GeoclueIface = loadInterfaceXML('org.freedesktop.GeoClue2.Manager'); +export const GeoclueIface = loadInterfaceXML('org.freedesktop.GeoClue2.Manager'); const GeoclueManager = Gio.DBusProxy.makeProxyWrapper(GeoclueIface); -var AgentIface = loadInterfaceXML('org.freedesktop.GeoClue2.Agent'); +export const AgentIface = loadInterfaceXML('org.freedesktop.GeoClue2.Agent'); let _geoclueAgent = null; function _getGeoclueAgent() { @@ -210,7 +216,7 @@ var GeoclueAgent = GObject.registerClass({ } }); -var Indicator = GObject.registerClass( +export const Indicator = GObject.registerClass( class Indicator extends PanelMenu.SystemIndicator { _init() { super._init(); @@ -373,7 +379,8 @@ var AppAuthorizer = class { let dateStr = Math.round(Date.now() / 1000).toString(); this._permissions[this.desktopId] = [levelStr, dateStr]; - let data = GLib.Variant.new('av', {}); + // FIXME + let data = GLib.Variant.new('av', []); this._permStoreProxy.SetRemote(APP_PERMISSIONS_TABLE, true, @@ -387,7 +394,7 @@ var AppAuthorizer = class { } }; -var GeolocationDialog = GObject.registerClass({ +export const GeolocationDialog = GObject.registerClass({ Signals: { 'response': { param_types: [GObject.TYPE_UINT] } }, }, class GeolocationDialog extends ModalDialog.ModalDialog { _init(name, reason, reqAccuracyLevel) { diff --git a/js/ui/status/network.js b/js/ui/status/network.js index 890f9a456..416cbb159 100644 --- a/js/ui/status/network.js +++ b/js/ui/status/network.js @@ -1,25 +1,33 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported NMApplet */ -const { Clutter, Gio, GLib, GObject, Meta, NM, Polkit, St } = imports.gi; -const Signals = imports.misc.signals; - -const Animation = imports.ui.animation; -const Main = imports.ui.main; -const PanelMenu = imports.ui.panelMenu; -const PopupMenu = imports.ui.popupMenu; -const MessageTray = imports.ui.messageTray; -const ModalDialog = imports.ui.modalDialog; -const ModemManager = imports.misc.modemManager; -const Rfkill = imports.ui.status.rfkill; -const Util = imports.misc.util; - -const { loadInterfaceXML } = imports.misc.fileUtils; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import NM from 'gi://NM'; +import St from 'gi://St'; +import Polkit from 'gi://Polkit'; +import Meta from 'gi://Meta'; +import * as Signals from '../../misc/signals.js'; + +import * as Animation from '../animation.js'; +import Main from '../main.js'; +import * as PanelMenu from '../panelMenu.js'; +import * as PopupMenu from '../popupMenu.js'; +import * as MessageTray from '../messageTray.js'; +import * as ModalDialog from '../modalDialog.js'; +import * as ModemManager from '../../misc/modemManager.js'; +import * as Rfkill from './rfkill.js'; +import * as Util from '../../misc/util.js'; + +import { loadInterfaceXML } from '../../misc/fileUtilsModule.js'; Gio._promisify(Gio.DBusConnection.prototype, 'call', 'call_finish'); Gio._promisify(NM.Client, 'new_async', 'new_finish'); Gio._promisify(NM.Client.prototype, 'check_connectivity_async', 'check_connectivity_finish'); +/** @enum {string} */ const NMConnectionCategory = { INVALID: 'invalid', WIRED: 'wired', @@ -28,6 +36,7 @@ const NMConnectionCategory = { VPN: 'vpn', }; +/** @enum {number} */ const NMAccessPointSecurity = { NONE: 1, WEP: 2, @@ -37,14 +46,15 @@ const NMAccessPointSecurity = { WPA2_ENT: 6, }; -var MAX_DEVICE_ITEMS = 4; +export let MAX_DEVICE_ITEMS = 4; // small optimization, to avoid using [] all the time const NM80211Mode = NM['80211Mode']; const NM80211ApFlags = NM['80211ApFlags']; const NM80211ApSecurityFlags = NM['80211ApSecurityFlags']; -var PortalHelperResult = { +/** @enum {number} */ +export const PortalHelperResult = { CANCELLED: 0, COMPLETED: 1, RECHECK: 2, @@ -73,6 +83,9 @@ function ssidToLabel(ssid) { return label; } +/** + * @param {NM.ActiveConnection} active + */ function ensureActiveConnectionProps(active) { if (!active._primaryDevice) { let devices = active.get_devices(); @@ -89,7 +102,7 @@ function launchSettingsPanel(panel, ...args) { [panel, args.map(s => new GLib.Variant('s', s))]); const platformData = { 'desktop-startup-id': new GLib.Variant('s', - '_TIME%s'.format(global.get_current_time())), + '_TIME%s'.format(global.get_current_time().toFixed(0))), }; try { Gio.DBus.session.call( @@ -108,7 +121,7 @@ function launchSettingsPanel(panel, ...args) { } } -var NMConnectionItem = class extends Signals.EventEmitter { +export class NMConnectionItem extends Signals.EventEmitter { constructor(section, connection) { super(); @@ -200,14 +213,14 @@ var NMConnectionItem = class extends Signals.EventEmitter { if (this._activeConnection) { this._activeConnectionChangedId = this._activeConnection.connect('notify::state', - this._connectionStateChanged.bind(this)); + this._connectionStateChanged.bind(this)); } this._sync(); } }; -var NMConnectionSection = class NMConnectionSection extends Signals.EventEmitter { +export class NMConnectionSection extends Signals.EventEmitter { constructor(client) { super(); @@ -253,10 +266,27 @@ var NMConnectionSection = class NMConnectionSection extends Signals.EventEmitter this.item.icon.icon_name = this._getMenuIcon(); } + /** + * @return {string} + */ + _getStatus() { + throw new GObject.NotImplementedError(`_getStatus in ${this.constructor.name}`); + } + + /** + * @return {string} + */ + getIndicatorIcon() { + throw new GObject.NotImplementedError(`getIndicatorIcon in ${this.constructor.name}`); + } + _getMenuIcon() { return this.getIndicatorIcon(); } + /** + * @returns {string} + */ getConnectLabel() { return _("Connect"); } @@ -338,7 +368,12 @@ var NMConnectionSection = class NMConnectionSection extends Signals.EventEmitter } }; -var NMConnectionDevice = class NMConnectionDevice extends NMConnectionSection { + +export class NMConnectionDevice extends NMConnectionSection { + /** + * @param {NM.Client} client + * @param {NM.Device} device + */ constructor(client, device) { super(client); @@ -498,7 +533,11 @@ var NMConnectionDevice = class NMConnectionDevice extends NMConnectionSection { } }; -var NMDeviceWired = class extends NMConnectionDevice { +export class NMDeviceWired extends NMConnectionDevice { + /** + * @param {NM.Client} client + * @param {NM.Device} device + */ constructor(client, device) { super(client, device); @@ -541,7 +580,11 @@ var NMDeviceWired = class extends NMConnectionDevice { } }; -var NMDeviceModem = class extends NMConnectionDevice { +export class NMDeviceModem extends NMConnectionDevice { + /** + * @param {NM.Client} client + * @param {NM.DeviceModem} device + */ constructor(client, device) { super(client, device); @@ -647,10 +690,16 @@ var NMDeviceModem = class extends NMConnectionDevice { } }; -var NMDeviceBluetooth = class extends NMConnectionDevice { +export class NMDeviceBluetooth extends NMConnectionDevice { + /** + * @param {NM.Client} client + * @param {NM.DeviceBt} device + */ constructor(client, device) { super(client, device); + this._device = device; + this.item.menu.addSettingsAction(_("Bluetooth Settings"), 'gnome-network-panel.desktop'); } @@ -681,7 +730,7 @@ var NMDeviceBluetooth = class extends NMConnectionDevice { } }; -var NMWirelessDialogItem = GObject.registerClass({ +export const NMWirelessDialogItem = GObject.registerClass({ Signals: { 'selected': {}, }, @@ -708,7 +757,7 @@ var NMWirelessDialogItem = GObject.registerClass({ this.add_child(this._label); this._selectedIcon = new St.Icon({ style_class: 'nm-dialog-icon', - icon_name: 'object-select-symbolic' }); + icon_name: 'object-select-symbolic' }) this.add(this._selectedIcon); this._icons = new St.BoxLayout({ @@ -755,7 +804,7 @@ var NMWirelessDialogItem = GObject.registerClass({ } }); -var NMWirelessDialog = GObject.registerClass( +export const NMWirelessDialog = GObject.registerClass( class NMWirelessDialog extends ModalDialog.ModalDialog { _init(client, device) { super._init({ styleClass: 'nm-dialog' }); @@ -1155,15 +1204,16 @@ class NMWirelessDialog extends ModalDialog.ModalDialog { this._resortItems(); } else { + const ssid = accessPoint.get_ssid(); network = { - ssid: accessPoint.get_ssid(), + ssid: ssid, + ssidText: ssidToLabel(ssid), mode: accessPoint.mode, security: this._getApSecurityType(accessPoint), connections: [], item: null, accessPoints: [accessPoint], }; - network.ssidText = ssidToLabel(network.ssid); this._checkConnections(network, accessPoint); let newPos = Util.insertSorted(this._networks, network, this._networkSortFunction); @@ -1258,7 +1308,7 @@ class NMWirelessDialog extends ModalDialog.ModalDialog { } }); -var NMDeviceWireless = class extends Signals.EventEmitter { +export class NMDeviceWireless extends Signals.EventEmitter { constructor(client, device) { super(); @@ -1471,7 +1521,7 @@ var NMDeviceWireless = class extends Signals.EventEmitter { } }; -var NMVpnConnectionItem = class extends NMConnectionItem { +export class NMVpnConnectionItem extends NMConnectionItem { isActive() { if (this._activeConnection == null) return false; @@ -1490,6 +1540,9 @@ var NMVpnConnectionItem = class extends NMConnectionItem { _sync() { let isActive = this.isActive(); this.labelItem.label.text = isActive ? _("Turn Off") : this._section.getConnectLabel(); + + assertType(this.radioItem, PopupMenu.PopupSwitchMenuItem); + this.radioItem.setToggleState(isActive); this.radioItem.setStatus(this._getStatus()); this.emit('icon-changed'); @@ -1558,7 +1611,7 @@ var NMVpnConnectionItem = class extends NMConnectionItem { } }; -var NMVpnSection = class extends NMConnectionSection { +export class NMVpnSection extends NMConnectionSection { constructor(client) { super(client); @@ -1632,7 +1685,7 @@ var NMVpnSection = class extends NMConnectionSection { } }; -var DeviceCategory = class extends PopupMenu.PopupMenuSection { +class DeviceCategory extends PopupMenu.PopupMenuSection { constructor(category) { super(); @@ -1693,7 +1746,7 @@ var DeviceCategory = class extends PopupMenu.PopupMenuSection { } }; -var NMApplet = GObject.registerClass( +export const NMApplet = GObject.registerClass( class Indicator extends PanelMenu.SystemIndicator { _init() { super._init(); @@ -1803,7 +1856,7 @@ class Indicator extends PanelMenu.SystemIndicator { try { this._deviceAdded(this._client, devices[i], true); } catch (e) { - log('Failed to add device %s: %s'.format(devices[i], e.toString())); + log('Failed to add device %s: %s'.format(devices[i].toString(), e.toString())); } } this._syncDeviceNames(); diff --git a/js/ui/status/nightLight.js b/js/ui/status/nightLight.js index 7fcbfe5c2..f98b5ab2a 100644 --- a/js/ui/status/nightLight.js +++ b/js/ui/status/nightLight.js @@ -1,13 +1,14 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Indicator */ -const { Gio, GObject } = imports.gi; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; -const Main = imports.ui.main; -const PanelMenu = imports.ui.panelMenu; -const PopupMenu = imports.ui.popupMenu; +import Main from '../main.js'; +import * as PanelMenu from '../panelMenu.js'; +import * as PopupMenu from '../popupMenu.js'; -const { loadInterfaceXML } = imports.misc.fileUtils; +import { loadInterfaceXML } from '../../misc/fileUtilsModule.js'; const BUS_NAME = 'org.gnome.SettingsDaemon.Color'; const OBJECT_PATH = '/org/gnome/SettingsDaemon/Color'; @@ -15,7 +16,7 @@ const OBJECT_PATH = '/org/gnome/SettingsDaemon/Color'; const ColorInterface = loadInterfaceXML('org.gnome.SettingsDaemon.Color'); const ColorProxy = Gio.DBusProxy.makeProxyWrapper(ColorInterface); -var Indicator = GObject.registerClass( +export const Indicator = GObject.registerClass( class Indicator extends PanelMenu.SystemIndicator { _init() { super._init(); diff --git a/js/ui/status/power.js b/js/ui/status/power.js index ed6a46a63..ae5d9f9f7 100644 --- a/js/ui/status/power.js +++ b/js/ui/status/power.js @@ -1,13 +1,17 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Indicator */ -const { Clutter, Gio, GObject, St, UPowerGlib: UPower } = imports.gi; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; +import St from 'gi://St'; +import UPower from 'gi://UPowerGlib'; -const Main = imports.ui.main; -const PanelMenu = imports.ui.panelMenu; -const PopupMenu = imports.ui.popupMenu; +import Main from '../main.js'; +import * as PanelMenu from '../panelMenu.js'; +import * as PopupMenu from '../popupMenu.js'; -const { loadInterfaceXML } = imports.misc.fileUtils; +import { loadInterfaceXML } from '../../misc/fileUtilsModule.js'; const BUS_NAME = 'org.freedesktop.UPower'; const OBJECT_PATH = '/org/freedesktop/UPower/devices/DisplayDevice'; @@ -17,7 +21,7 @@ const PowerManagerProxy = Gio.DBusProxy.makeProxyWrapper(DisplayDeviceInterface) const SHOW_BATTERY_PERCENTAGE = 'show-battery-percentage'; -var Indicator = GObject.registerClass( +export const Indicator = GObject.registerClass( class Indicator extends PanelMenu.SystemIndicator { _init() { super._init(); diff --git a/js/ui/status/powerProfiles.js b/js/ui/status/powerProfiles.js index 61205bbc6..649b7ebfc 100644 --- a/js/ui/status/powerProfiles.js +++ b/js/ui/status/powerProfiles.js @@ -3,11 +3,11 @@ const { Gio, GObject } = imports.gi; -const Main = imports.ui.main; -const PanelMenu = imports.ui.panelMenu; -const PopupMenu = imports.ui.popupMenu; +import Main from '../main.js'; +import * as PanelMenu from '../panelMenu.js'; +import * as PopupMenu from '../popupMenu.js'; -const { loadInterfaceXML } = imports.misc.fileUtils; +import { loadInterfaceXML } from '../../misc/fileUtilsModule.js'; const BUS_NAME = 'net.hadess.PowerProfiles'; const OBJECT_PATH = '/net/hadess/PowerProfiles'; @@ -26,7 +26,7 @@ const PROFILE_ICONS = { 'power-saver': 'power-profile-power-saver-symbolic', }; -var Indicator = GObject.registerClass( +export const Indicator = GObject.registerClass( class Indicator extends PanelMenu.SystemIndicator { _init() { super._init(); diff --git a/js/ui/status/remoteAccess.js b/js/ui/status/remoteAccess.js index 21f6581b6..18d41acc9 100644 --- a/js/ui/status/remoteAccess.js +++ b/js/ui/status/remoteAccess.js @@ -1,12 +1,13 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported RemoteAccessApplet */ -const { GObject, Meta } = imports.gi; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; -const PanelMenu = imports.ui.panelMenu; -const PopupMenu = imports.ui.popupMenu; +import * as PanelMenu from '../panelMenu.js'; +import * as PopupMenu from '../popupMenu.js'; -var RemoteAccessApplet = GObject.registerClass( +export const RemoteAccessApplet = GObject.registerClass( class RemoteAccessApplet extends PanelMenu.SystemIndicator { _init() { super._init(); diff --git a/js/ui/status/rfkill.js b/js/ui/status/rfkill.js index c090ffd00..d296c71f1 100644 --- a/js/ui/status/rfkill.js +++ b/js/ui/status/rfkill.js @@ -1,14 +1,15 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Indicator */ -const { Gio, GObject } = imports.gi; -const Signals = imports.misc.signals; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; +import * as Signals from '../../misc/signals.js'; -const Main = imports.ui.main; -const PanelMenu = imports.ui.panelMenu; -const PopupMenu = imports.ui.popupMenu; +import Main from '../main.js'; +import * as PanelMenu from '../panelMenu.js'; +import * as PopupMenu from '../popupMenu.js'; -const { loadInterfaceXML } = imports.misc.fileUtils; +import { loadInterfaceXML } from '../../misc/fileUtilsModule.js'; const BUS_NAME = 'org.gnome.SettingsDaemon.Rfkill'; const OBJECT_PATH = '/org/gnome/SettingsDaemon/Rfkill'; @@ -54,7 +55,7 @@ var RfkillManager = class extends Signals.EventEmitter { }; var _manager; -function getRfkillManager() { +export function getRfkillManager() { if (_manager != null) return _manager; @@ -62,7 +63,7 @@ function getRfkillManager() { return _manager; } -var Indicator = GObject.registerClass( +export const Indicator = GObject.registerClass( class Indicator extends PanelMenu.SystemIndicator { _init() { super._init(); diff --git a/js/ui/status/system.js b/js/ui/status/system.js index 6f71109c5..7c2547753 100644 --- a/js/ui/status/system.js +++ b/js/ui/status/system.js @@ -1,21 +1,23 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Indicator */ -const { GObject, Shell, St } = imports.gi; +import GObject from 'gi://GObject'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; -const BoxPointer = imports.ui.boxpointer; -const SystemActions = imports.misc.systemActions; -const Main = imports.ui.main; -const PanelMenu = imports.ui.panelMenu; -const PopupMenu = imports.ui.popupMenu; +import * as BoxPointer from '../boxpointer.js'; +import * as SystemActions from '../../misc/systemActions.js'; +import Main from '../main.js'; +import * as PanelMenu from '../panelMenu.js'; +import * as PopupMenu from '../popupMenu.js'; -var Indicator = GObject.registerClass( +export const Indicator = GObject.registerClass( class Indicator extends PanelMenu.SystemIndicator { _init() { super._init(); - this._systemActions = new SystemActions.getDefault(); + this._systemActions = SystemActions.getDefault(); this._createSubMenu(); diff --git a/js/ui/status/thunderbolt.js b/js/ui/status/thunderbolt.js index 12535394e..c0176efa6 100644 --- a/js/ui/status/thunderbolt.js +++ b/js/ui/status/thunderbolt.js @@ -3,14 +3,18 @@ // the following is a modified version of bolt/contrib/js/client.js -const { Gio, GLib, GObject, Polkit, Shell } = imports.gi; -const Signals = imports.misc.signals; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Polkit from 'gi://Polkit'; +import Shell from 'gi://Shell'; +import * as Signals from '../../misc/signals.js'; -const Main = imports.ui.main; -const MessageTray = imports.ui.messageTray; -const PanelMenu = imports.ui.panelMenu; +import Main from '../main.js'; +import * as MessageTray from '../messageTray.js'; +import * as PanelMenu from '../panelMenu.js'; -const { loadInterfaceXML } = imports.misc.fileUtils; +import { loadInterfaceXML } from '../../misc/fileUtilsModule.js'; /* Keep in sync with data/org.freedesktop.bolt.xml */ @@ -21,7 +25,7 @@ const BoltDeviceProxy = Gio.DBusProxy.makeProxyWrapper(BoltDeviceInterface); /* */ -var Status = { +export const Status = { DISCONNECTED: 'disconnected', CONNECTING: 'connecting', CONNECTED: 'connected', @@ -30,17 +34,17 @@ var Status = { AUTHORIZED: 'authorized', }; -var Policy = { +export const Policy = { DEFAULT: 'default', MANUAL: 'manual', AUTO: 'auto', }; -var AuthCtrl = { +export const AuthCtrl = { NONE: 'none', }; -var AuthMode = { +export const AuthMode = { DISABLED: 'disabled', ENABLED: 'enabled', }; @@ -49,7 +53,7 @@ const BOLT_DBUS_CLIENT_IFACE = 'org.freedesktop.bolt1.Manager'; const BOLT_DBUS_NAME = 'org.freedesktop.bolt'; const BOLT_DBUS_PATH = '/org/freedesktop/bolt'; -var Client = class extends Signals.EventEmitter { +export class Client extends Signals.EventEmitter { constructor() { super(); @@ -130,7 +134,7 @@ var Client = class extends Signals.EventEmitter { }; /* helper class to automatically authorize new devices */ -var AuthRobot = class extends Signals.EventEmitter { +export class AuthRobot extends Signals.EventEmitter { constructor(client) { super(); @@ -222,7 +226,7 @@ var AuthRobot = class extends Signals.EventEmitter { /* eof client.js */ -var Indicator = GObject.registerClass( +export const Indicator = GObject.registerClass( class Indicator extends PanelMenu.SystemIndicator { _init() { super._init(); diff --git a/js/ui/status/volume.js b/js/ui/status/volume.js index d71daadc5..9ca7bd90a 100644 --- a/js/ui/status/volume.js +++ b/js/ui/status/volume.js @@ -1,13 +1,18 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Indicator */ -const { Clutter, Gio, GLib, GObject, Gvc, St } = imports.gi; -const Signals = imports.misc.signals; - -const Main = imports.ui.main; -const PanelMenu = imports.ui.panelMenu; -const PopupMenu = imports.ui.popupMenu; -const Slider = imports.ui.slider; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Gvc from 'gi://Gvc'; +import St from 'gi://St'; +import * as Signals from '../../misc/signals.js'; + +import Main from '../main.js'; +import * as PanelMenu from '../panelMenu.js'; +import * as PopupMenu from '../popupMenu.js'; +import * as Slider from '../slider.js'; const ALLOW_AMPLIFIED_VOLUME_KEY = 'allow-volume-above-100-percent'; @@ -19,7 +24,7 @@ const VolumeType = { // Each Gvc.MixerControl is a connection to PulseAudio, // so it's better to make it a singleton let _mixerControl; -function getMixerControl() { +export function getMixerControl() { if (_mixerControl) return _mixerControl; @@ -29,7 +34,7 @@ function getMixerControl() { return _mixerControl; } -var StreamSlider = class extends Signals.EventEmitter { +export class StreamSlider extends Signals.EventEmitter { constructor(control) { super(); @@ -217,7 +222,7 @@ var StreamSlider = class extends Signals.EventEmitter { } }; -var OutputStreamSlider = class extends StreamSlider { +export class OutputStreamSlider extends StreamSlider { constructor(control) { super(control); this._slider.accessible_name = _("Volume"); @@ -272,7 +277,7 @@ var OutputStreamSlider = class extends StreamSlider { } }; -var InputStreamSlider = class extends StreamSlider { +export class InputStreamSlider extends StreamSlider { constructor(control) { super(control); this._slider.accessible_name = _("Microphone"); @@ -317,7 +322,7 @@ var InputStreamSlider = class extends StreamSlider { } }; -var VolumeMenu = class extends PopupMenu.PopupMenuSection { +export class VolumeMenu extends PopupMenu.PopupMenuSection { constructor(control) { super(); @@ -394,7 +399,7 @@ var VolumeMenu = class extends PopupMenu.PopupMenuSection { } }; -var Indicator = GObject.registerClass( +export const Indicator = GObject.registerClass( class Indicator extends PanelMenu.SystemIndicator { _init() { super._init(); diff --git a/js/ui/swipeTracker.js b/js/ui/swipeTracker.js index 8d96c2dd8..975cae318 100644 --- a/js/ui/swipeTracker.js +++ b/js/ui/swipeTracker.js @@ -1,9 +1,12 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported SwipeTracker */ -const { Clutter, Gio, GObject, Meta } = imports.gi; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; -const Main = imports.ui.main; +import Main from './main.js'; // FIXME: ideally these values matches physical touchpad size. We can get the // correct values for gnome-shell specifically, since mutter uses libinput @@ -33,6 +36,7 @@ const EPSILON = 0.005; const GESTURE_FINGER_COUNT = 3; +/** @enum {number} */ const State = { NONE: 0, SCROLLING: 1, @@ -107,6 +111,12 @@ const TouchpadSwipeGesture = GObject.registerClass({ this._state = TouchpadState.NONE; this._cumulativeX = 0; this._cumulativeY = 0; + + /** @type {Clutter.Orientation} */ + this.orientation; + /** @type {boolean} */ + this.enabled; + this._touchpadSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.peripherals.touchpad', }); @@ -232,6 +242,9 @@ const TouchSwipeGesture = GObject.registerClass({ this.set_n_touch_points(nTouchPoints); this.set_threshold_trigger_edge(thresholdTriggerEdge); + /** @type {Clutter.Orientation} */ + this.orientation; + this._allowedModes = allowedModes; this._distance = global.screen_height; @@ -332,6 +345,11 @@ const ScrollGesture = GObject.registerClass({ this._began = false; this._enabled = true; + /** @type {Clutter.Orientation} */ + this.orientation; + /** @type {Clutter.ModifierType} */ + this.scrollModifiers; + actor.connect('scroll-event', this._handleEvent.bind(this)); } @@ -432,8 +450,14 @@ const ScrollGesture = GObject.registerClass({ // NOTE: duration can be 0 in some cases, in this case it should finish // instantly. +/** + * @typedef {object} SwipeTrackerParams + * @property {boolean} allowDrag + * @property {boolean} allowScroll + */ + /** A class for handling swipe gestures */ -var SwipeTracker = GObject.registerClass({ +export const SwipeTracker = GObject.registerClass({ Properties: { 'enabled': GObject.ParamSpec.boolean( 'enabled', 'enabled', 'enabled', @@ -466,6 +490,9 @@ var SwipeTracker = GObject.registerClass({ super._init(); const { allowDrag = true, allowScroll = true } = params; + /** @type {boolean} */ + this.allowLongSwipes + this.orientation = orientation; this._allowedModes = allowedModes; this._enabled = true; @@ -528,7 +555,7 @@ var SwipeTracker = GObject.registerClass({ /** * canHandleScrollEvent: * @param {Clutter.Event} scrollEvent: an event to check - * @returns {bool} whether the event can be handled by the tracker + * @returns {boolean} whether the event can be handled by the tracker * * This function can be used to combine swipe gesture and mouse * scrolling. @@ -631,6 +658,9 @@ var SwipeTracker = GObject.registerClass({ return this._findClosestPoint(pos); } + /** + * @returns {[number, number]} + */ _getBounds(pos) { if (this.allowLongSwipes) return [this._snapPoints[0], this._snapPoints[this._snapPoints.length - 1]]; diff --git a/js/ui/switchMonitor.js b/js/ui/switchMonitor.js index 5ac582522..50bb2098e 100644 --- a/js/ui/switchMonitor.js +++ b/js/ui/switchMonitor.js @@ -1,13 +1,17 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported SwitchMonitorPopup */ -const { Clutter, GObject, Meta, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import St from 'gi://St'; -const SwitcherPopup = imports.ui.switcherPopup; -var APP_ICON_SIZE = 96; +import * as SwitcherPopup from './switcherPopup.js'; -var SwitchMonitorPopup = GObject.registerClass( +export let APP_ICON_SIZE = 96; + +export const SwitchMonitorPopup = GObject.registerClass( class SwitchMonitorPopup extends SwitcherPopup.SwitcherPopup { _init() { let items = [{ icon: 'view-mirror-symbolic', @@ -69,10 +73,13 @@ class SwitchMonitorPopup extends SwitcherPopup.SwitcherPopup { } }); -var SwitchMonitorSwitcher = GObject.registerClass( +export const SwitchMonitorSwitcher = GObject.registerClass( class SwitchMonitorSwitcher extends SwitcherPopup.SwitcherList { + /** + * @param {*} items + */ _init(items) { - super._init(true); + super._init({ squareItems: true }); for (let i = 0; i < items.length; i++) this._addIcon(items[i]); diff --git a/js/ui/switcherPopup.js b/js/ui/switcherPopup.js index b1e273006..209b8507c 100644 --- a/js/ui/switcherPopup.js +++ b/js/ui/switcherPopup.js @@ -1,19 +1,24 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported SwitcherPopup, SwitcherList */ -const { Clutter, GLib, GObject, Meta, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import St from 'gi://St'; -const Main = imports.ui.main; -var POPUP_DELAY_TIMEOUT = 150; // milliseconds +import Main from './main.js'; -var POPUP_SCROLL_TIME = 100; // milliseconds -var POPUP_FADE_OUT_TIME = 100; // milliseconds +export let POPUP_DELAY_TIMEOUT = 150; // milliseconds -var DISABLE_HOVER_TIMEOUT = 500; // milliseconds -var NO_MODS_TIMEOUT = 1500; // milliseconds +export let POPUP_SCROLL_TIME = 100; // milliseconds +export let POPUP_FADE_OUT_TIME = 100; // milliseconds -function mod(a, b) { +export let DISABLE_HOVER_TIMEOUT = 500; // milliseconds +export let NO_MODS_TIMEOUT = 1500; // milliseconds + +export function mod(a, b) { return (a + b) % b; } @@ -29,10 +34,10 @@ function primaryModifier(mask) { return primary; } -var SwitcherPopup = GObject.registerClass({ +export const SwitcherPopup = GObject.registerClass({ GTypeFlags: GObject.TypeFlags.ABSTRACT, }, class SwitcherPopup extends St.Widget { - _init(items) { + _init(items, ..._) { super._init({ style_class: 'switcher-popup', reactive: true, visible: false }); @@ -168,6 +173,11 @@ var SwitcherPopup = GObject.registerClass({ return mod(this._selectedIndex - 1, this._items.length); } + /** + * @param {*} _keysym + * @param {*} _action + * @returns {boolean} + */ _keyPressHandler(_keysym, _action) { throw new GObject.NotImplementedError(`_keyPressHandler in ${this.constructor.name}`); } @@ -353,7 +363,7 @@ var SwitcherPopup = GObject.registerClass({ } }); -var SwitcherButton = GObject.registerClass( +export const SwitcherButton = GObject.registerClass( class SwitcherButton extends St.Button { _init(square) { super._init({ style_class: 'item-box', @@ -370,7 +380,7 @@ class SwitcherButton extends St.Button { } }); -var SwitcherList = GObject.registerClass({ +export const SwitcherList = GObject.registerClass({ Signals: { 'item-activated': { param_types: [GObject.TYPE_INT] }, 'item-entered': { param_types: [GObject.TYPE_INT] }, 'item-removed': { param_types: [GObject.TYPE_INT] } }, @@ -627,7 +637,7 @@ var SwitcherList = GObject.registerClass({ } }); -function drawArrow(area, side) { +export function drawArrow(area, side) { let themeNode = area.get_theme_node(); let borderColor = themeNode.get_border_color(side); let bodyColor = themeNode.get_foreground_color(); diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js index 370385abc..3250878c8 100644 --- a/js/ui/unlockDialog.js +++ b/js/ui/unlockDialog.js @@ -1,16 +1,26 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported UnlockDialog */ -const { AccountsService, Atk, Clutter, Gdm, Gio, - GnomeDesktop, GLib, GObject, Meta, Shell, St } = imports.gi; - -const Background = imports.ui.background; -const Layout = imports.ui.layout; -const Main = imports.ui.main; -const MessageTray = imports.ui.messageTray; -const SwipeTracker = imports.ui.swipeTracker; - -const AuthPrompt = imports.gdm.authPrompt; +import AccountsService from 'gi://AccountsService'; +import Atk from 'gi://Atk'; +import Clutter from 'gi://Clutter'; +import Gdm from 'gi://Gdm'; +import Gio from 'gi://Gio'; +import GnomeDesktop from 'gi://GnomeDesktop'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; + + +import * as Background from './background.js'; +import * as Layout from './layout.js'; +import Main from './main.js'; +import * as MessageTray from './messageTray.js'; +import * as SwipeTracker from './swipeTracker.js'; + +import * as AuthPrompt from '../gdm/authPrompt.js'; // The timeout before going back automatically to the lock screen (in seconds) const IDLE_TIMEOUT = 2 * 60; @@ -27,7 +37,7 @@ const BLUR_SIGMA = 60; const SUMMARY_ICON_SIZE = 32; -var NotificationsBox = GObject.registerClass({ +export const NotificationsBox = GObject.registerClass({ Signals: { 'wake-up-screen': {} }, }, class NotificationsBox extends St.BoxLayout { _init() { @@ -314,7 +324,7 @@ var NotificationsBox = GObject.registerClass({ } }); -var Clock = GObject.registerClass( +export const Clock = GObject.registerClass( class UnlockDialogClock extends St.BoxLayout { _init() { super._init({ style_class: 'unlock-dialog-clock', vertical: true }); @@ -387,8 +397,13 @@ class UnlockDialogClock extends St.BoxLayout { } }); -var UnlockDialogLayout = GObject.registerClass( +export const UnlockDialogLayout = GObject.registerClass( class UnlockDialogLayout extends Clutter.LayoutManager { + /** + * @param {*} stack + * @param {*} notifications + * @param {*} switchUserButton + */ _init(stack, notifications, switchUserButton) { super._init(); @@ -466,12 +481,15 @@ class UnlockDialogLayout extends Clutter.LayoutManager { } }); -var UnlockDialog = GObject.registerClass({ +export const UnlockDialog = GObject.registerClass({ Signals: { 'failed': {}, 'wake-up-screen': {}, }, }, class UnlockDialog extends St.Widget { + /** + * @param {Clutter.Actor} parentActor + */ _init(parentActor) { super._init({ accessible_role: Atk.Role.WINDOW, diff --git a/js/ui/userWidget.js b/js/ui/userWidget.js index bfc91e276..88b0bd8eb 100644 --- a/js/ui/userWidget.js +++ b/js/ui/userWidget.js @@ -3,18 +3,32 @@ // A widget showing the user avatar and name /* exported UserWidget */ -const { Clutter, GLib, GObject, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import St from 'gi://St'; - -var AVATAR_ICON_SIZE = 64; +export let AVATAR_ICON_SIZE = 64; // Adapted from gdm/gui/user-switch-applet/applet.c // // Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>. // Copyright (C) 2008,2009 Red Hat, Inc. -var Avatar = GObject.registerClass( +/** + * @typedef {object} AvatarProps: An object with the properties: + * @property {string} [styleClass] + * @property {boolean} [reactive] + * @property {number} [iconSize] + */ + +export const Avatar = GObject.registerClass( class Avatar extends St.Bin { + /** + * _init: + * @param {import('gi://AccountsService').User} user + * @param {Partial<AvatarProps>} [params] + */ _init(user, params = {}) { let themeContext = St.ThemeContext.get_for_stage(global.stage); const { @@ -103,8 +117,11 @@ class Avatar extends St.Bin { } }); -var UserWidgetLabel = GObject.registerClass( +export const UserWidgetLabel = GObject.registerClass( class UserWidgetLabel extends St.Widget { + /** + * @param {*} user + */ _init(user) { super._init({ layout_manager: new Clutter.BinLayout() }); @@ -186,8 +203,12 @@ class UserWidgetLabel extends St.Widget { } }); -var UserWidget = GObject.registerClass( +export const UserWidget = GObject.registerClass( class UserWidget extends St.BoxLayout { + /** + * @param {*} user + * @param {*} orientation + */ _init(user, orientation = Clutter.Orientation.HORIZONTAL) { // If user is null, that implies a username-based login authorization. this._user = user; diff --git a/js/ui/welcomeDialog.js b/js/ui/welcomeDialog.js index cf6540fe2..24bc9958b 100644 --- a/js/ui/welcomeDialog.js +++ b/js/ui/welcomeDialog.js @@ -1,19 +1,20 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported WelcomeDialog */ -const { Clutter, GObject, Shell, St } = imports.gi; - +import Clutter from 'gi://Clutter'; +import GObject from 'gi://GObject'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; +import * as ModalDialog from './modalDialog.js'; +import Main from './main.js'; const Config = imports.misc.config; -const Dialog = imports.ui.dialog; -const Main = imports.ui.main; -const ModalDialog = imports.ui.modalDialog; - +import * as Dialog from './dialog.js'; var DialogResponse = { NO_THANKS: 0, TAKE_TOUR: 1, }; -var WelcomeDialog = GObject.registerClass( +export const WelcomeDialog = GObject.registerClass( class WelcomeDialog extends ModalDialog.ModalDialog { _init() { super._init({ styleClass: 'welcome-dialog' }); @@ -26,9 +27,9 @@ class WelcomeDialog extends ModalDialog.ModalDialog { open() { if (!this._tourAppInfo) - return; + return false; - super.open(); + return super.open(); } _buildLayout() { diff --git a/js/ui/windowAttentionHandler.js b/js/ui/windowAttentionHandler.js index 346fad88d..ea856d703 100644 --- a/js/ui/windowAttentionHandler.js +++ b/js/ui/windowAttentionHandler.js @@ -1,12 +1,14 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported WindowAttentionHandler */ -const { GObject, Shell } = imports.gi; +import GObject from 'gi://GObject'; +import Shell from 'gi://Shell'; -const Main = imports.ui.main; -const MessageTray = imports.ui.messageTray; -var WindowAttentionHandler = class { +import Main from './main.js'; +import * as MessageTray from './messageTray.js'; + +export class WindowAttentionHandler { constructor() { this._tracker = Shell.WindowTracker.get_default(); this._windowDemandsAttentionId = global.display.connect('window-demands-attention', @@ -34,7 +36,7 @@ var WindowAttentionHandler = class { return; let app = this._tracker.get_window_app(window); - let source = new WindowAttentionSource(app, window); + let source = new WindowAttentionSource({ app, window }); Main.messageTray.add(source); let [title, banner] = this._getTitleAndBanner(app, window); @@ -54,13 +56,24 @@ var WindowAttentionHandler = class { } }; -var WindowAttentionSource = GObject.registerClass( +/** + * @typedef {object} WindowAttentionSourceParams + * @property {Shell.App} app + * @property {import('gi://Meta').Window} window + */ + +export const WindowAttentionSource = GObject.registerClass( class WindowAttentionSource extends MessageTray.Source { - _init(app, window) { + /** + * @param {import('./messageTray.js').SourceParams & WindowAttentionSourceParams} params + */ + _init(params) { + const { window, app } = params; + this._window = window; this._app = app; - super._init(app.get_name()); + super._init({ title: app.get_name() }); this.signalIDs = []; this.signalIDs.push(this._window.connect('notify::demands-attention', diff --git a/js/ui/windowManager.js b/js/ui/windowManager.js index 2e3e125b6..a1ec2fb40 100644 --- a/js/ui/windowManager.js +++ b/js/ui/windowManager.js @@ -1,39 +1,66 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported WindowManager */ -const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi; - -const AltTab = imports.ui.altTab; -const AppFavorites = imports.ui.appFavorites; -const Dialog = imports.ui.dialog; -const WorkspaceSwitcherPopup = imports.ui.workspaceSwitcherPopup; -const InhibitShortcutsDialog = imports.ui.inhibitShortcutsDialog; -const Main = imports.ui.main; -const ModalDialog = imports.ui.modalDialog; -const WindowMenu = imports.ui.windowMenu; -const PadOsd = imports.ui.padOsd; -const EdgeDragAction = imports.ui.edgeDragAction; -const CloseDialog = imports.ui.closeDialog; -const SwitchMonitor = imports.ui.switchMonitor; -const IBusManager = imports.misc.ibusManager; -const WorkspaceAnimation = imports.ui.workspaceAnimation; - -const { loadInterfaceXML } = imports.misc.fileUtils; - -var SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings'; -var MINIMIZE_WINDOW_ANIMATION_TIME = 200; -var SHOW_WINDOW_ANIMATION_TIME = 150; -var DIALOG_SHOW_WINDOW_ANIMATION_TIME = 100; -var DESTROY_WINDOW_ANIMATION_TIME = 150; -var DIALOG_DESTROY_WINDOW_ANIMATION_TIME = 100; -var WINDOW_ANIMATION_TIME = 250; -var SCROLL_TIMEOUT_TIME = 150; -var DIM_BRIGHTNESS = -0.3; -var DIM_TIME = 500; -var UNDIM_TIME = 250; -var APP_MOTION_THRESHOLD = 30; - -var ONE_SECOND = 1000; // in ms +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; + + +import * as AltTab from './altTab.js'; +import * as AppFavorites from './appFavorites.js'; +import * as Dialog from './dialog.js'; +import * as WorkspaceSwitcherPopup from './workspaceSwitcherPopup.js'; +import * as InhibitShortcutsDialog from './inhibitShortcutsDialog.js'; +import Main from './main.js'; +import * as ModalDialog from './modalDialog.js'; +import * as WindowMenu from './windowMenu.js'; +import * as PadOsd from './padOsd.js'; +import * as EdgeDragAction from './edgeDragAction.js'; +import * as CloseDialog from './closeDialog.js'; +import * as SwipeTracker from './swipeTracker.js'; +import * as SwitchMonitor from './switchMonitor.js'; +import * as IBusManager from '../misc/ibusManager.js'; +import * as WorkspaceAnimation from './workspaceAnimation.js'; + +import { loadInterfaceXML } from '../misc/fileUtilsModule.js'; + +export const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings'; +export const MINIMIZE_WINDOW_ANIMATION_TIME = 200; +export const SHOW_WINDOW_ANIMATION_TIME = 150; +export const DIALOG_SHOW_WINDOW_ANIMATION_TIME = 100; +export const DESTROY_WINDOW_ANIMATION_TIME = 150; +export const DIALOG_DESTROY_WINDOW_ANIMATION_TIME = 100; +export const WINDOW_ANIMATION_TIME = 250; +export const SCROLL_TIMEOUT_TIME = 150; +export const DIM_BRIGHTNESS = -0.3; +export const DIM_TIME = 500; +export const UNDIM_TIME = 250; +export const APP_MOTION_THRESHOLD = 30; + +export const ONE_SECOND = 1000; // in ms + +const MOTION_DIRECTIONS = [ + Meta.MotionDirection.UP, + Meta.MotionDirection.DOWN, + Meta.MotionDirection.LEFT, + Meta.MotionDirection.RIGHT, + Meta.MotionDirection.UP_LEFT, + Meta.MotionDirection.UP_RIGHT, + Meta.MotionDirection.DOWN_LEFT, + Meta.MotionDirection.DOWN_RIGHT, +]; + +/* + * When the last window closed on a workspace is a dialog or splash + * screen, we assume that it might be an initial window shown before + * the main window of an application, and give the app a grace period + * where it can map another window before we remove the workspace. + */ +export let LAST_WINDOW_GRACE_TIME = 1000; var MIN_NUM_WORKSPACES = 2; @@ -50,8 +77,12 @@ Gio._promisify(Shell, Gio._promisify(Shell, 'util_stop_systemd_unit', 'util_stop_systemd_unit_finish'); -var DisplayChangeDialog = GObject.registerClass( +export const DisplayChangeDialog = GObject.registerClass( class DisplayChangeDialog extends ModalDialog.ModalDialog { + /** + * + * @param {Shell.WM | any} wm + */ _init(wm) { super._init(); @@ -122,7 +153,7 @@ class DisplayChangeDialog extends ModalDialog.ModalDialog { } }); -var WindowDimmer = GObject.registerClass( +export const WindowDimmer = GObject.registerClass( class WindowDimmer extends Clutter.BrightnessContrastEffect { _init() { super._init({ @@ -159,7 +190,7 @@ class WindowDimmer extends Clutter.BrightnessContrastEffect { } }); -function getWindowDimmer(actor) { +export function getWindowDimmer(actor) { let enabled = Meta.prefs_get_attach_modal_dialogs(); let effect = actor.get_effect(WINDOW_DIMMER_EFFECT_NAME); @@ -172,15 +203,8 @@ function getWindowDimmer(actor) { return effect; } -/* - * When the last window closed on a workspace is a dialog or splash - * screen, we assume that it might be an initial window shown before - * the main window of an application, and give the app a grace period - * where it can map another window before we remove the workspace. - */ -var LAST_WINDOW_GRACE_TIME = 1000; -var WorkspaceTracker = class { +export class WorkspaceTracker { constructor(wm) { this._wm = wm; @@ -385,7 +409,7 @@ var WorkspaceTracker = class { } }; -var TilePreview = GObject.registerClass( +export const TilePreview = GObject.registerClass( class TilePreview extends St.Widget { _init() { super._init(); @@ -471,7 +495,7 @@ class TilePreview extends St.Widget { } }); -var AppSwitchAction = GObject.registerClass({ +export const AppSwitchAction = GObject.registerClass({ Signals: { 'activated': {} }, }, class AppSwitchAction extends Clutter.GestureAction { _init() { @@ -532,7 +556,7 @@ var AppSwitchAction = GObject.registerClass({ } }); -var ResizePopup = GObject.registerClass( +export const ResizePopup = GObject.registerClass( class ResizePopup extends St.Widget { _init() { super._init({ layout_manager: new Clutter.BinLayout() }); @@ -555,7 +579,7 @@ class ResizePopup extends St.Widget { } }); -var WindowManager = class { +export class WindowManager { constructor() { this._shellwm = global.window_manager; @@ -1398,7 +1422,7 @@ var WindowManager = class { } _hasAttachedDialogs(window, ignoreWindow) { - var count = 0; + let count = 0; window.foreach_transient(win => { if (win != ignoreWindow && win.is_attached_dialog() && diff --git a/js/ui/windowMenu.js b/js/ui/windowMenu.js index 27cecdac2..779d8a7d0 100644 --- a/js/ui/windowMenu.js +++ b/js/ui/windowMenu.js @@ -1,13 +1,16 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -* /* exported WindowMenuManager */ -const { GLib, Meta, St } = imports.gi; +import GLib from 'gi://GLib'; +import Meta from 'gi://Meta'; +import St from 'gi://St'; -const BoxPointer = imports.ui.boxpointer; -const Main = imports.ui.main; -const PopupMenu = imports.ui.popupMenu; -var WindowMenu = class extends PopupMenu.PopupMenu { +import * as BoxPointer from './boxpointer.js'; +import Main from './main.js'; +import * as PopupMenu from './popupMenu.js'; + +export class WindowMenu extends PopupMenu.PopupMenu { constructor(window, sourceActor) { super(sourceActor, 0, St.Side.TOP); @@ -192,7 +195,7 @@ var WindowMenu = class extends PopupMenu.PopupMenu { } }; -var WindowMenuManager = class { +export class WindowMenuManager { constructor() { this._manager = new PopupMenu.PopupMenuManager(Main.layoutManager.dummyCursor); diff --git a/js/ui/windowPreview.js b/js/ui/windowPreview.js index e67ec9ec0..d9f82c670 100644 --- a/js/ui/windowPreview.js +++ b/js/ui/windowPreview.js @@ -1,28 +1,35 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported WindowPreview */ -const { Atk, Clutter, GLib, GObject, - Graphene, Meta, Pango, Shell, St } = imports.gi; +import Atk from 'gi://Atk'; +import Clutter from 'gi://Clutter'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Graphene from 'gi://Graphene'; +import Meta from 'gi://Meta'; +import Pango from 'gi://Pango'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; -const DND = imports.ui.dnd; -const OverviewControls = imports.ui.overviewControls; +import * as DND from './dnd.js'; +import * as OverviewControls from './overviewControls.js'; -var WINDOW_DND_SIZE = 256; +export let WINDOW_DND_SIZE = 256; -var WINDOW_OVERLAY_IDLE_HIDE_TIMEOUT = 750; -var WINDOW_OVERLAY_FADE_TIME = 200; +export let WINDOW_OVERLAY_IDLE_HIDE_TIMEOUT = 750; +export let WINDOW_OVERLAY_FADE_TIME = 200; -var WINDOW_SCALE_TIME = 200; -var WINDOW_ACTIVE_SIZE_INC = 5; // in each direction +export let WINDOW_SCALE_TIME = 200; +export let WINDOW_ACTIVE_SIZE_INC = 5; // in each direction -var DRAGGING_WINDOW_OPACITY = 100; +export let DRAGGING_WINDOW_OPACITY = 100; const ICON_SIZE = 64; const ICON_OVERLAP = 0.7; const ICON_TITLE_SPACING = 6; -var WindowPreview = GObject.registerClass({ +export const WindowPreview = GObject.registerClass({ Properties: { 'overlay-enabled': GObject.ParamSpec.boolean( 'overlay-enabled', 'overlay-enabled', 'overlay-enabled', @@ -38,6 +45,17 @@ var WindowPreview = GObject.registerClass({ 'size-changed': {}, }, }, class WindowPreview extends Shell.WindowPreview { + /** @returns {Clutter.Actor<Shell.WindowPreviewLayout>} */ + get window_container() { + // Cast the window_container to the appropriate type... + + return /** @type {Clutter.Actor<Shell.WindowPreviewLayout>} */ (super.window_container); + } + + set window_container(window_container) { + super.window_container = window_container; + } + _init(metaWindow, workspace, overviewAdjustment) { this.metaWindow = metaWindow; this.metaWindow._delegate = this; @@ -54,6 +72,7 @@ var WindowPreview = GObject.registerClass({ const windowContainer = new Clutter.Actor({ pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }), + layout_manager: /** @type {Shell.WindowPreviewLayout} */ (null), }); this.window_container = windowContainer; @@ -628,6 +647,8 @@ var WindowPreview = GObject.registerClass({ let [x, y] = action.get_coords(); action.release(); this._draggable.startDrag(x, y, global.get_current_time(), this._dragTouchSequence, event.get_device()); + + return false; }); } else { this.showOverlay(true); diff --git a/js/ui/workspace.js b/js/ui/workspace.js index 1c3a15710..71cbf1a9c 100644 --- a/js/ui/workspace.js +++ b/js/ui/workspace.js @@ -1,26 +1,31 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Workspace */ -const { Clutter, GLib, GObject, Graphene, Meta, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import St from 'gi://St'; +import Graphene from 'gi://Graphene'; -const Background = imports.ui.background; -const DND = imports.ui.dnd; -const Main = imports.ui.main; -const OverviewControls = imports.ui.overviewControls; -const Util = imports.misc.util; -const { WindowPreview } = imports.ui.windowPreview; +import * as Background from './background.js'; +import * as DND from './dnd.js'; +import Main from './main.js'; +import Meta from 'gi://Meta'; +import * as OverviewControls from './overviewControls.js'; +import * as Util from '../misc/util.js'; +import { WindowPreview } from './windowPreview.js'; -var WINDOW_PREVIEW_MAXIMUM_SCALE = 0.95; +export let WINDOW_PREVIEW_MAXIMUM_SCALE = 0.95; -var WINDOW_REPOSITIONING_DELAY = 750; +export let WINDOW_REPOSITIONING_DELAY = 750; // When calculating a layout, we calculate the scale of windows and the percent // of the available area the new layout uses. If the values for the new layout, // when weighted with the values as below, are worse than the previous layout's, // we stop looking for a new layout and use the previous layout. // Otherwise, we keep looking for a new layout. -var LAYOUT_SCALE_WEIGHT = 1; -var LAYOUT_SPACE_WEIGHT = 0.1; +export let LAYOUT_SCALE_WEIGHT = 1; +export let LAYOUT_SPACE_WEIGHT = 0.1; const BACKGROUND_CORNER_RADIUS_PIXELS = 30; const BACKGROUND_MARGIN = 12; @@ -95,7 +100,7 @@ const BACKGROUND_MARGIN = 12; // each window's "cell" area to be the same, but we shrink the thumbnail // and center it horizontally, and align it to the bottom vertically. -var LayoutStrategy = class { +export class LayoutStrategy { constructor(params = {}) { const { monitor = null, @@ -388,7 +393,7 @@ function animateAllocation(actor, box) { return actor.get_transition('allocation'); } -var WorkspaceLayout = GObject.registerClass({ +export const WorkspaceLayout = GObject.registerClass({ Properties: { 'spacing': GObject.ParamSpec.double( 'spacing', 'Spacing', 'Spacing', @@ -572,8 +577,9 @@ var WorkspaceLayout = GObject.registerClass({ _syncWorkareaTracking() { if (this._container) { - if (this._workAreaChangedId) - return; + // FIXME + // if (this._workAreaChangedId) + // return; this._workarea = Main.layoutManager.getWorkAreaForMonitor(this._monitorIndex); this._workareasChangedId = global.display.connect('workareas-changed', () => { @@ -592,6 +598,9 @@ var WorkspaceLayout = GObject.registerClass({ this._stateAdjustment.actor = container; } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_width(container, forHeight) { const workarea = this._getAdjustedWorkarea(container); if (forHeight === -1) @@ -603,6 +612,9 @@ var WorkspaceLayout = GObject.registerClass({ return [0, widthPreservingAspectRatio]; } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_height(container, forWidth) { const workarea = this._getAdjustedWorkarea(container); if (forWidth === -1) @@ -765,7 +777,7 @@ var WorkspaceLayout = GObject.registerClass({ /** * addWindow: - * @param {WindowPreview} window: the window to add + * @param {WindowPreview["prototype"]} window: the window to add * @param {Meta.Window} metaWindow: the MetaWindow of the window * * Adds @window to the workspace, it will be shown immediately if @@ -806,7 +818,7 @@ var WorkspaceLayout = GObject.registerClass({ /** * removeWindow: - * @param {WindowPreview} window: the window to remove + * @param {WindowPreview["prototype"]} window: the window to remove * * Removes @window from the workspace if @window is a part of the * workspace. If the layout-frozen property is set to true, the @@ -1055,7 +1067,8 @@ class WorkspaceBackground extends St.Widget { /** * @metaWorkspace: a #Meta.Workspace, or null */ -var Workspace = GObject.registerClass( +export const Workspace = GObject.registerClass( +/** @extends {St.Widget<typeof WorkspaceLayout["prototype"]>} */ class Workspace extends St.Widget { _init(metaWorkspace, monitorIndex, overviewAdjustment) { super._init({ @@ -1077,6 +1090,7 @@ class Workspace extends St.Widget { reactive: true, x_expand: true, y_expand: true, + layout_manager: /** @type {WorkspaceLayout["prototype"]} */ (null), }); this._container.layout_manager = layoutManager; this.add_child(this._container); @@ -1216,6 +1230,9 @@ class Workspace extends St.Widget { '[gnome-shell] this._layoutFrozenId'); } + /** + * @param {Meta.Window} metaWin + */ _doAddWindow(metaWin) { let win = metaWin.get_compositor_private(); diff --git a/js/ui/workspaceAnimation.js b/js/ui/workspaceAnimation.js index d240fe156..5bd8db359 100644 --- a/js/ui/workspaceAnimation.js +++ b/js/ui/workspaceAnimation.js @@ -1,12 +1,17 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported WorkspaceAnimationController */ -const { Clutter, GObject, Meta, Shell, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; -const Background = imports.ui.background; -const Layout = imports.ui.layout; -const Main = imports.ui.main; -const SwipeTracker = imports.ui.swipeTracker; +import * as Background from './background.js'; +import * as Layout from './layout.js'; +import * as SwipeTracker from './swipeTracker.js'; + +import Main from './main.js'; const WINDOW_ANIMATION_TIME = 250; const WORKSPACE_SPACING = 100; @@ -267,7 +272,7 @@ const MonitorGroup = GObject.registerClass({ } }); -var WorkspaceAnimationController = class { +export class WorkspaceAnimationController { constructor() { this._movingWindow = null; this._switchData = null; diff --git a/js/ui/workspaceSwitcherPopup.js b/js/ui/workspaceSwitcherPopup.js index 5b65bec8a..a85fb4a35 100644 --- a/js/ui/workspaceSwitcherPopup.js +++ b/js/ui/workspaceSwitcherPopup.js @@ -1,14 +1,19 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported WorkspaceSwitcherPopup */ -const { Clutter, GLib, GObject, Meta, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import St from 'gi://St'; -const Main = imports.ui.main; -var ANIMATION_TIME = 100; -var DISPLAY_TIMEOUT = 600; +import Main from './main.js'; -var WorkspaceSwitcherPopupList = GObject.registerClass( + export let ANIMATION_TIME = 100; + export let DISPLAY_TIMEOUT = 600; + +export const WorkspaceSwitcherPopupList = GObject.registerClass( class WorkspaceSwitcherPopupList extends St.Widget { _init() { super._init({ @@ -63,6 +68,9 @@ class WorkspaceSwitcherPopupList extends St.Widget { } } + /** + * @return {[number, number]} + */ _getSizeForOppositeOrientation() { let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex); @@ -119,7 +127,7 @@ class WorkspaceSwitcherPopupList extends St.Widget { } }); -var WorkspaceSwitcherPopup = GObject.registerClass( +export const WorkspaceSwitcherPopup = GObject.registerClass( class WorkspaceSwitcherPopup extends St.Widget { _init() { super._init({ x: 0, diff --git a/js/ui/workspaceThumbnail.js b/js/ui/workspaceThumbnail.js index 412527c93..c18cb5b67 100644 --- a/js/ui/workspaceThumbnail.js +++ b/js/ui/workspaceThumbnail.js @@ -1,50 +1,69 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported WorkspaceThumbnail, ThumbnailsBox */ -const { Clutter, Gio, GLib, GObject, Graphene, Meta, Shell, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; +import Graphene from 'gi://Graphene'; -const DND = imports.ui.dnd; -const Main = imports.ui.main; -const Util = imports.misc.util; -const Workspace = imports.ui.workspace; + + +import * as DND from './dnd.js'; +import Main from './main.js'; +import * as WindowPreview from './windowPreview.js'; +import * as Util from '../misc/util.js' const NUM_WORKSPACES_THRESHOLD = 2; // The maximum size of a thumbnail is 5% the width and height of the screen -var MAX_THUMBNAIL_SCALE = 0.05; +export const MAX_THUMBNAIL_SCALE = 0.05; -var RESCALE_ANIMATION_TIME = 200; -var SLIDE_ANIMATION_TIME = 200; + +export let RESCALE_ANIMATION_TIME = 200; +export let SLIDE_ANIMATION_TIME = 200; // When we create workspaces by dragging, we add a "cut" into the top and // bottom of each workspace so that the user doesn't have to hit the // placeholder exactly. -var WORKSPACE_CUT_SIZE = 10; +export let WORKSPACE_CUT_SIZE = 10; -var WORKSPACE_KEEP_ALIVE_TIME = 100; +export let WORKSPACE_KEEP_ALIVE_TIME = 100; -var MUTTER_SCHEMA = 'org.gnome.mutter'; +const MUTTER_SCHEMA = 'org.gnome.mutter'; /* A layout manager that requests size only for primary_actor, but then allocates all using a fixed layout */ -var PrimaryActorLayout = GObject.registerClass( +export const PrimaryActorLayout = GObject.registerClass( class PrimaryActorLayout extends Clutter.FixedLayout { + /** + * @param {Clutter.Actor} primaryActor + */ _init(primaryActor) { super._init(); this.primaryActor = primaryActor; } - vfunc_get_preferred_width(container, forHeight) { + /** + * @param {number} forHeight + */ + vfunc_get_preferred_width(_, forHeight) { return this.primaryActor.get_preferred_width(forHeight); } - vfunc_get_preferred_height(container, forWidth) { + /** + * @param {number} forWidth + */ + vfunc_get_preferred_height(_, forWidth) { return this.primaryActor.get_preferred_height(forWidth); } }); -var WindowClone = GObject.registerClass({ +export const WindowClone = GObject.registerClass({ Signals: { 'drag-begin': {}, 'drag-cancelled': {}, @@ -52,6 +71,9 @@ var WindowClone = GObject.registerClass({ 'selected': { param_types: [GObject.TYPE_UINT] }, }, }, class WindowClone extends Clutter.Actor { + /** + * @param {Meta.WindowActor} realWindow + */ _init(realWindow) { let clone = new Clutter.Clone({ source: realWindow }); super._init({ @@ -78,14 +100,15 @@ var WindowClone = GObject.registerClass({ this._draggable = DND.makeDraggable(this, { restoreOnSuccess: true, - dragActorMaxSize: Workspace.WINDOW_DND_SIZE, - dragActorOpacity: Workspace.DRAGGING_WINDOW_OPACITY }); + dragActorMaxSize: WindowPreview.WINDOW_DND_SIZE, + dragActorOpacity: WindowPreview.DRAGGING_WINDOW_OPACITY }); this._draggable.connect('drag-begin', this._onDragBegin.bind(this)); this._draggable.connect('drag-cancelled', this._onDragCancelled.bind(this)); this._draggable.connect('drag-end', this._onDragEnd.bind(this)); this.inDrag = false; let iter = win => { + /** @type {Meta.Window} */ let actor = win.get_compositor_private(); if (!actor) @@ -163,7 +186,8 @@ var WindowClone = GObject.registerClass({ } _disconnectSignals() { - this.get_children().forEach(child => { + /** @type {Clutter.Clone[]} */ + (this.get_children()).forEach(child => { let realWindow = child.source; realWindow.disconnect(child._updateId); @@ -230,7 +254,7 @@ var WindowClone = GObject.registerClass({ }); -var ThumbnailState = { +export const ThumbnailState = { NEW: 0, EXPANDING: 1, EXPANDED: 2, @@ -246,7 +270,7 @@ var ThumbnailState = { /** * @metaWorkspace: a #Meta.Workspace */ -var WorkspaceThumbnail = GObject.registerClass({ +export const WorkspaceThumbnail = GObject.registerClass({ Properties: { 'collapse-fraction': GObject.ParamSpec.double( 'collapse-fraction', 'collapse-fraction', 'collapse-fraction', @@ -607,7 +631,7 @@ var WorkspaceThumbnail = GObject.registerClass({ }); -var ThumbnailsBox = GObject.registerClass({ +export const ThumbnailsBox = GObject.registerClass({ Properties: { 'expand-fraction': GObject.ParamSpec.double( 'expand-fraction', 'expand-fraction', 'expand-fraction', @@ -1258,6 +1282,8 @@ var ThumbnailsBox = GObject.registerClass({ }, }); }); + + return false; } _queueUpdateStates() { @@ -1408,6 +1434,8 @@ var ThumbnailsBox = GObject.registerClass({ Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { this._dropPlaceholder.hide(); + + return false; }); } @@ -1438,6 +1466,8 @@ var ThumbnailsBox = GObject.registerClass({ Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { this._dropPlaceholder.show(); + + return false; }); x += placeholderWidth + spacing; } diff --git a/js/ui/workspacesView.js b/js/ui/workspacesView.js index 6dad2df3f..c2355905d 100644 --- a/js/ui/workspacesView.js +++ b/js/ui/workspacesView.js @@ -1,19 +1,29 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported WorkspacesView, WorkspacesDisplay */ -const { Clutter, Gio, GObject, Meta, Shell, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; -const Layout = imports.ui.layout; -const Main = imports.ui.main; -const OverviewControls = imports.ui.overviewControls; -const SwipeTracker = imports.ui.swipeTracker; -const Util = imports.misc.util; -const Workspace = imports.ui.workspace; -const { ThumbnailsBox, MAX_THUMBNAIL_SCALE } = imports.ui.workspaceThumbnail; -var WORKSPACE_SWITCH_TIME = 250; +import { ThumbnailsBox, MAX_THUMBNAIL_SCALE } from './workspaceThumbnail.js'; -const MUTTER_SCHEMA = 'org.gnome.mutter'; +import * as Util from '../misc/util.js'; +import * as SwipeTracker from './swipeTracker.js'; +import * as Workspace from './workspace.js'; +import * as OverviewControls from './overviewControls.js'; +import * as Layout from './layout.js'; + +import Main from './main.js' + +import { ANIMATION_TIME } from '../ui/overview.js'; + +export var WORKSPACE_SWITCH_TIME = 250; + +export const MUTTER_SCHEMA = 'org.gnome.mutter'; const WORKSPACE_MIN_SPACING = 24; const WORKSPACE_MAX_SPACING = 80; @@ -81,21 +91,27 @@ var WorkspacesViewBase = GObject.registerClass({ child.allocate_available_size(0, 0, box.get_width(), box.get_height()); } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_width() { return [0, 0]; } + /** + * @returns {[number, number]} + */ vfunc_get_preferred_height() { return [0, 0]; } }); -var FitMode = { +export const FitMode = { SINGLE: 0, ALL: 1, }; -var WorkspacesView = GObject.registerClass( +export const WorkspacesView = GObject.registerClass( class WorkspacesView extends WorkspacesViewBase { _init(monitorIndex, controls, scrollAdjustment, fitModeAdjustment, overviewAdjustment) { let workspaceManager = global.workspace_manager; @@ -291,6 +307,9 @@ class WorkspacesView extends WorkspacesViewBase { } } + /** + * @param {Clutter.ActorBox} box + */ _getInitialBoxes(box) { const offsetBox = new Clutter.ActorBox(); offsetBox.set_size(...box.get_size()); @@ -546,7 +565,7 @@ class WorkspacesView extends WorkspacesViewBase { } }); -var ExtraWorkspaceView = GObject.registerClass( +export const ExtraWorkspaceView = GObject.registerClass( class ExtraWorkspaceView extends WorkspacesViewBase { _init(monitorIndex, overviewAdjustment) { super._init(monitorIndex, overviewAdjustment); @@ -828,7 +847,7 @@ class SecondaryMonitorDisplay extends St.Widget { } }); -var WorkspacesDisplay = GObject.registerClass( +export const WorkspacesDisplay = GObject.registerClass( class WorkspacesDisplay extends St.Widget { _init(controls, scrollAdjustment, overviewAdjustment) { super._init({ diff --git a/js/ui/xdndHandler.js b/js/ui/xdndHandler.js index 4154b7f39..d45c9bfcf 100644 --- a/js/ui/xdndHandler.js +++ b/js/ui/xdndHandler.js @@ -1,13 +1,12 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported XdndHandler */ -const { Clutter } = imports.gi; -const Signals = imports.misc.signals; +import Clutter from 'gi://Clutter'; +import Main from './main.js'; +import * as Signals from '../misc/signals.js'; +import * as DND from './dnd.js'; -const DND = imports.ui.dnd; -const Main = imports.ui.main; - -var XdndHandler = class extends Signals.EventEmitter { +export class XdndHandler extends Signals.EventEmitter { constructor() { super(); @@ -104,7 +103,7 @@ var XdndHandler = class extends Signals.EventEmitter { } while (pickedActor) { - if (pickedActor._delegate && pickedActor._delegate.handleDragOver) { + if (pickedActor._delegate && DND.handlesDragOver(pickedActor._delegate)) { let [r_, targX, targY] = pickedActor.transform_stage_point(x, y); let result = pickedActor._delegate.handleDragOver(this, dragEvent.dragActor, diff --git a/src/main.c b/src/main.c index 3cd9e10a5..9a4246152 100644 --- a/src/main.c +++ b/src/main.c @@ -393,8 +393,8 @@ list_modes (const char *option_name, shell_introspection_init (); - script = "imports.ui.environment.init();" - "imports.ui.sessionMode.listModes();"; + script = "" + ""; if (!gjs_context_eval (context, script, -1, "<main>", &status, NULL)) g_message ("Retrieving list of available modes failed."); diff --git a/tests/interactive/background-repeat.js b/tests/interactive/background-repeat.js index 1377f7424..3179a8a08 100644 --- a/tests/interactive/background-repeat.js +++ b/tests/interactive/background-repeat.js @@ -2,7 +2,9 @@ const UI = imports.testcommon.ui; -const { Clutter, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import St from 'gi://St'; + function test() { let stage = new Clutter.Stage({ width: 640, height: 480 }); diff --git a/tests/interactive/background-size.js b/tests/interactive/background-size.js index 8f8738da9..367f47e11 100644 --- a/tests/interactive/background-size.js +++ b/tests/interactive/background-size.js @@ -2,7 +2,11 @@ const UI = imports.testcommon.ui; -const { Cogl, Clutter, Meta, St } = imports.gi; +import Cogl from 'gi://Cogl'; +import Clutter from 'gi://Clutter'; +import Meta from 'gi://Meta'; +import St from 'gi://St'; + function test() { diff --git a/tests/interactive/border-radius.js b/tests/interactive/border-radius.js index 4d26518bd..a5eb6a3a7 100644 --- a/tests/interactive/border-radius.js +++ b/tests/interactive/border-radius.js @@ -2,7 +2,9 @@ const UI = imports.testcommon.ui; -const { Clutter, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import St from 'gi://St'; + function test() { let stage = new Clutter.Stage({ width: 640, height: 480 }); diff --git a/tests/interactive/border-width.js b/tests/interactive/border-width.js index 30c7575a0..4d71d443f 100644 --- a/tests/interactive/border-width.js +++ b/tests/interactive/border-width.js @@ -2,7 +2,9 @@ const UI = imports.testcommon.ui; -const { Clutter, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import St from 'gi://St'; + function test() { let stage = new Clutter.Stage({ width: 640, height: 480 }); diff --git a/tests/interactive/borders.js b/tests/interactive/borders.js index 4812acbfa..aa76720bb 100644 --- a/tests/interactive/borders.js +++ b/tests/interactive/borders.js @@ -2,7 +2,8 @@ const UI = imports.testcommon.ui; -const { Clutter, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import St from 'gi://St'; function test() { let stage = new Clutter.Stage({ width: 640, height: 480 }); diff --git a/tests/interactive/box-layout.js b/tests/interactive/box-layout.js index bb9a5bbc2..ec1550b70 100644 --- a/tests/interactive/box-layout.js +++ b/tests/interactive/box-layout.js @@ -2,7 +2,9 @@ const UI = imports.testcommon.ui; -const { Clutter, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import St from 'gi://St'; + function test() { let stage = new Clutter.Stage(); diff --git a/tests/interactive/box-shadow-animated.js b/tests/interactive/box-shadow-animated.js index cf117a7f3..4d5753029 100644 --- a/tests/interactive/box-shadow-animated.js +++ b/tests/interactive/box-shadow-animated.js @@ -2,7 +2,10 @@ const UI = imports.testcommon.ui; -const { Clutter, GLib, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import GLib from 'gi://GLib'; +import St from 'gi://St'; + const DELAY = 2000; @@ -22,7 +25,7 @@ function resize_animated(label) { } } -function get_css_style(shadow_style) +export function get_css_style(shadow_style) { return 'border: 20px solid black;' + 'border-radius: 20px;' + diff --git a/tests/interactive/box-shadows.js b/tests/interactive/box-shadows.js index c9c677c97..077cf18dd 100644 --- a/tests/interactive/box-shadows.js +++ b/tests/interactive/box-shadows.js @@ -2,7 +2,8 @@ const UI = imports.testcommon.ui; -const { Clutter, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import St from 'gi://St'; function test() { let stage = new Clutter.Stage({ width: 640, height: 480 }); diff --git a/tests/interactive/calendar.js b/tests/interactive/calendar.js index d1d435a52..6098ecbb7 100644 --- a/tests/interactive/calendar.js +++ b/tests/interactive/calendar.js @@ -2,7 +2,9 @@ const UI = imports.testcommon.ui; -const { Clutter, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import St from 'gi://St'; + function test() { let stage = new Clutter.Stage({ width: 400, height: 400 }); diff --git a/tests/interactive/css-fonts.js b/tests/interactive/css-fonts.js index a25769318..c6d581ac1 100644 --- a/tests/interactive/css-fonts.js +++ b/tests/interactive/css-fonts.js @@ -2,7 +2,9 @@ const UI = imports.testcommon.ui; -const { Clutter, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import St from 'gi://St'; + function test() { let stage = new Clutter.Stage(); diff --git a/tests/interactive/entry.js b/tests/interactive/entry.js index 9ae010604..0860aa7ce 100644 --- a/tests/interactive/entry.js +++ b/tests/interactive/entry.js @@ -2,7 +2,10 @@ const UI = imports.testcommon.ui; -const { Clutter, GLib, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import GLib from 'gi://GLib'; +import St from 'gi://St'; + function test() { let stage = new Clutter.Stage({ width: 400, height: 400 }); diff --git a/tests/interactive/gapplication.js b/tests/interactive/gapplication.js index ec38b806f..565b72608 100755 --- a/tests/interactive/gapplication.js +++ b/tests/interactive/gapplication.js @@ -1,8 +1,14 @@ #!/usr/bin/env gjs // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- -imports.gi.versions = { Gdk: '3.0', Gtk: '3.0' }; -const { Gdk, Gio, GLib, Gtk } = imports.gi; +import 'gi://Gtk?version=3.0'; +import 'gi://Gdk?version=3.0'; + +import Gdk from 'gi://Gdk'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import Gtk from 'gi://Gtk'; + function do_action(action, parameter) { print ("Action '" + action.name + "' invoked"); diff --git a/tests/interactive/icons.js b/tests/interactive/icons.js index 65b7f6560..54441d52b 100644 --- a/tests/interactive/icons.js +++ b/tests/interactive/icons.js @@ -2,7 +2,9 @@ const UI = imports.testcommon.ui; -const { Clutter, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import St from 'gi://St'; + function test() { let stage = new Clutter.Stage(); diff --git a/tests/interactive/inline-style.js b/tests/interactive/inline-style.js index 3952c3a98..1439ca44c 100644 --- a/tests/interactive/inline-style.js +++ b/tests/interactive/inline-style.js @@ -2,7 +2,9 @@ const UI = imports.testcommon.ui; -const { Clutter, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import St from 'gi://St'; + function test() { let stage = new Clutter.Stage(); diff --git a/tests/interactive/scroll-view-sizing.js b/tests/interactive/scroll-view-sizing.js index a6c682e54..0b07414f3 100644 --- a/tests/interactive/scroll-view-sizing.js +++ b/tests/interactive/scroll-view-sizing.js @@ -2,7 +2,12 @@ const UI = imports.testcommon.ui; -const { Clutter, GObject, Gtk, Shell, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import GObject from 'gi://GObject'; +import Gtk from 'gi://Gtk'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; + // This is an interactive test of the sizing behavior of StScrollView. It // may be interesting in the future to split out the two classes at the @@ -28,7 +33,7 @@ const BOX_WIDTHS = [ const SPACING = 10; -var FlowedBoxes = GObject.registerClass( +export const FlowedBoxes = GObject.registerClass( class FlowedBoxes extends St.Widget { _init() { super._init(); @@ -117,7 +122,7 @@ class FlowedBoxes extends St.Widget { // // This is currently only written for the case where the child is height-for-width -var SizingIllustrator = GObject.registerClass( +export const SizingIllustrator = GObject.registerClass( class SizingIllustrator extends St.Widget { _init() { super._init(); diff --git a/tests/interactive/scrolling.js b/tests/interactive/scrolling.js index 91951ce10..50d1aaa5a 100644 --- a/tests/interactive/scrolling.js +++ b/tests/interactive/scrolling.js @@ -2,7 +2,10 @@ const UI = imports.testcommon.ui; -const { Clutter, Gtk, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import Gtk from 'gi://Gtk'; +import St from 'gi://St'; + function test() { let stage = new Clutter.Stage(); diff --git a/tests/interactive/test-title.js b/tests/interactive/test-title.js index 0a468ddd5..09e2c62ac 100755 --- a/tests/interactive/test-title.js +++ b/tests/interactive/test-title.js @@ -1,8 +1,9 @@ #!/usr/bin/env gjs -imports.gi.versions.Gtk = '3.0'; +import 'gi://Gtk?version=3.0'; -const { GLib, Gtk } = imports.gi; +import GLib from 'gi://GLib'; +import Gtk from 'gi://Gtk'; function nextTitle() { let length = Math.random() * 20; diff --git a/tests/interactive/transitions.js b/tests/interactive/transitions.js index 7b2eac1c7..a9e39ecfc 100644 --- a/tests/interactive/transitions.js +++ b/tests/interactive/transitions.js @@ -2,7 +2,8 @@ const UI = imports.testcommon.ui; -const { Clutter, St } = imports.gi; +import Clutter from 'gi://Clutter'; +import St from 'gi://St'; function test() { let stage = new Clutter.Stage(); diff --git a/tests/testcommon/ui.js b/tests/testcommon/ui.js index abacea5f7..b98fb4567 100644 --- a/tests/testcommon/ui.js +++ b/tests/testcommon/ui.js @@ -1,12 +1,14 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- -const Config = imports.misc.config; +import 'gi://Clutter?version=9'; +import 'gi://Gtk?version=3.0'; -imports.gi.versions = { Clutter: Config.LIBMUTTER_API_VERSION, Gtk: '3.0' }; +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import St from 'gi://St'; -const { Clutter, Gio, GLib, St } = imports.gi; - -const Environment = imports.ui.environment; +import * as Environment from '../../js/ui/environment.js'; function init(stage) { Environment.init(); diff --git a/tests/unit/markup.js b/tests/unit/markup.js index 603ca8194..352717929 100644 --- a/tests/unit/markup.js +++ b/tests/unit/markup.js @@ -3,12 +3,11 @@ // Test cases for MessageList markup parsing const JsUnit = imports.jsUnit; -const Pango = imports.gi.Pango; +import Pango from 'gi://Pango'; const Environment = imports.ui.environment; Environment.init(); -const Main = imports.ui.main; // unused, but needed to break dependency loop const MessageList = imports.ui.messageList; // Assert that @input, assumed to be markup, gets "fixed" to @output, |