diff options
Diffstat (limited to 'js/ui/appMenu.js')
-rw-r--r-- | js/ui/appMenu.js | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/js/ui/appMenu.js b/js/ui/appMenu.js new file mode 100644 index 0000000..010fdb3 --- /dev/null +++ b/js/ui/appMenu.js @@ -0,0 +1,287 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- +/* 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; + +var AppMenu = class AppMenu extends PopupMenu.PopupMenu { + /** + * @param {Clutter.Actor} sourceActor - actor the menu is attached to + * @param {St.Side} side - arrow side + * @param {object} params - options + * @param {bool} params.favoritesSection - show items to add/remove favorite + * @param {bool} params.showSingleWindow - show window section for a single window + */ + constructor(sourceActor, side = St.Side.TOP, params = {}) { + if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL) { + if (side === St.Side.LEFT) + side = St.Side.RIGHT; + else if (side === St.Side.RIGHT) + side = St.Side.LEFT; + } + + super(sourceActor, 0.5, side); + + this.actor.add_style_class_name('app-menu'); + + const { + favoritesSection = false, + showSingleWindows = false, + } = params; + + this._app = null; + this._appSystem = Shell.AppSystem.get_default(); + this._parentalControlsManager = ParentalControlsManager.getDefault(); + this._appFavorites = AppFavorites.getAppFavorites(); + this._enableFavorites = favoritesSection; + this._showSingleWindows = showSingleWindows; + + this._windowsChangedId = 0; + this._updateWindowsLaterId = 0; + + /* Translators: This is the heading of a list of open windows */ + this._openWindowsHeader = new PopupMenu.PopupSeparatorMenuItem(_('Open Windows')); + this.addMenuItem(this._openWindowsHeader); + + this._windowSection = new PopupMenu.PopupMenuSection(); + this.addMenuItem(this._windowSection); + + this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); + + this._newWindowItem = this.addAction(_('New Window'), () => { + this._animateLaunch(); + this._app.open_new_window(-1); + Main.overview.hide(); + }); + + this._actionSection = new PopupMenu.PopupMenuSection(); + this.addMenuItem(this._actionSection); + + this._onGpuMenuItem = this.addAction('', () => { + this._animateLaunch(); + this._app.launch(0, -1, this._getNonDefaultLaunchGpu()); + Main.overview.hide(); + }); + + this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); + + this._toggleFavoriteItem = this.addAction('', () => { + const appId = this._app.get_id(); + if (this._appFavorites.isFavorite(appId)) + this._appFavorites.removeFavorite(appId); + else + this._appFavorites.addFavorite(appId); + }); + + this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); + + this._detailsItem = this.addAction(_('Show Details'), async () => { + const id = this._app.get_id(); + const args = GLib.Variant.new('(ss)', [id, '']); + const bus = await Gio.DBus.get(Gio.BusType.SESSION, null); + bus.call( + 'org.gnome.Software', + '/org/gnome/Software', + 'org.gtk.Actions', 'Activate', + new GLib.Variant('(sava{sv})', ['details', [args], null]), + null, 0, -1, null); + Main.overview.hide(); + }); + + this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); + + this._quitItem = + this.addAction(_('Quit'), () => this._app.request_quit()); + + this._appSystem.connectObject( + 'installed-changed', () => this._updateDetailsVisibility(), + 'app-state-changed', this._onAppStateChanged.bind(this), + this.actor); + + this._parentalControlsManager.connectObject( + 'app-filter-changed', () => this._updateFavoriteItem(), this.actor); + + this._appFavorites.connectObject( + 'changed', () => this._updateFavoriteItem(), this.actor); + + global.settings.connectObject( + 'writable-changed::favorite-apps', () => this._updateFavoriteItem(), + this.actor); + + global.connectObject( + 'notify::switcheroo-control', () => this._updateGpuItem(), + this.actor); + + this._updateQuitItem(); + this._updateFavoriteItem(); + this._updateGpuItem(); + this._updateDetailsVisibility(); + } + + _onAppStateChanged(sys, app) { + if (this._app !== app) + return; + + this._updateQuitItem(); + this._updateNewWindowItem(); + this._updateGpuItem(); + } + + _updateQuitItem() { + this._quitItem.visible = this._app?.state === Shell.AppState.RUNNING; + } + + _updateNewWindowItem() { + const actions = this._app?.appInfo?.list_actions() ?? []; + this._newWindowItem.visible = + this._app?.can_open_new_window() && !actions.includes('new-window'); + } + + _updateFavoriteItem() { + const appInfo = this._app?.app_info; + const canFavorite = appInfo && + this._enableFavorites && + global.settings.is_writable('favorite-apps') && + this._parentalControlsManager.shouldShowApp(appInfo); + + this._toggleFavoriteItem.visible = canFavorite; + + if (!canFavorite) + return; + + const { id } = this._app; + this._toggleFavoriteItem.label.text = this._appFavorites.isFavorite(id) + ? _('Unpin') + : _('Pin to Dash'); + } + + _updateGpuItem() { + const proxy = global.get_switcheroo_control(); + const hasDualGpu = proxy?.get_cached_property('HasDualGpu')?.unpack(); + + const showItem = + this._app?.state === Shell.AppState.STOPPED && hasDualGpu; + + this._onGpuMenuItem.visible = showItem; + + if (!showItem) + return; + + const launchGpu = this._getNonDefaultLaunchGpu(); + this._onGpuMenuItem.label.text = launchGpu === Shell.AppLaunchGpu.DEFAULT + ? _('Launch using Integrated Graphics Card') + : _('Launch using Discrete Graphics Card'); + } + + _updateDetailsVisibility() { + const sw = this._appSystem.lookup_app('org.gnome.Software.desktop'); + this._detailsItem.visible = sw !== null; + } + + _animateLaunch() { + if (this.sourceActor.animateLaunch) + this.sourceActor.animateLaunch(); + } + + _getNonDefaultLaunchGpu() { + return this._app.appInfo.get_boolean('PrefersNonDefaultGPU') + ? Shell.AppLaunchGpu.DEFAULT + : Shell.AppLaunchGpu.DISCRETE; + } + + /** */ + destroy() { + super.destroy(); + + this.setApp(null); + } + + /** + * @returns {bool} - true if the menu is empty + */ + isEmpty() { + if (!this._app) + return true; + return super.isEmpty(); + } + + /** + * @param {Shell.App} app - the app the menu represents + */ + setApp(app) { + if (this._app === app) + return; + + this._app?.disconnectObject(this); + + this._app = app; + + this._app?.connectObject('windows-changed', + () => this._queueUpdateWindowsSection(), this); + + this._updateWindowsSection(); + + const appInfo = app?.app_info; + const actions = appInfo?.list_actions() ?? []; + + this._actionSection.removeAll(); + actions.forEach(action => { + const label = appInfo.get_action_name(action); + this._actionSection.addAction(label, event => { + if (action === 'new-window') + this._animateLaunch(); + + this._app.launch_action(action, event.get_time(), -1); + Main.overview.hide(); + }); + }); + + this._updateQuitItem(); + this._updateNewWindowItem(); + this._updateFavoriteItem(); + this._updateGpuItem(); + } + + _queueUpdateWindowsSection() { + if (this._updateWindowsLaterId) + return; + + this._updateWindowsLaterId = Meta.later_add( + Meta.LaterType.BEFORE_REDRAW, () => { + this._updateWindowsSection(); + return GLib.SOURCE_REMOVE; + }); + } + + _updateWindowsSection() { + if (this._updateWindowsLaterId) + Meta.later_remove(this._updateWindowsLaterId); + this._updateWindowsLaterId = 0; + + this._windowSection.removeAll(); + this._openWindowsHeader.hide(); + + if (!this._app) + return; + + const minWindows = this._showSingleWindows ? 1 : 2; + const windows = this._app.get_windows().filter(w => !w.skip_taskbar); + if (windows.length < minWindows) + return; + + this._openWindowsHeader.show(); + + windows.forEach(window => { + const title = window.title || this._app.get_name(); + const item = this._windowSection.addAction(title, event => { + Main.activateWindow(window, event.get_time()); + }); + window.connectObject('notify::title', () => { + item.label.text = window.title || this._app.get_name(); + }, item); + }); + } +}; |