.gitlab
.gitlab-ci
.settings
data
docs
js
dbusServices
extensions
gdm
misc
portalHelper
ui
components
status
accessDialog.js
altTab.js
animation.js
appDisplay.js
appFavorites.js
appMenu.js
audioDeviceSelection.js
background.js
backgroundMenu.js
barLevel.js
boxpointer.js
calendar.js
checkBox.js
closeDialog.js
components.js
ctrlAltTab.js
dash.js
dateMenu.js
dialog.js
dnd.js
edgeDragAction.js
endSessionDialog.js
environment.js
extensionDownloader.js
extensionSystem.js
focusCaretTracker.js
grabHelper.js
ibusCandidatePopup.js
iconGrid.js
inhibitShortcutsDialog.js
init.js
kbdA11yDialog.js
keyboard.js
layout.js
lightbox.js
listModes.js
locatePointer.js
lookingGlass.js
magnifier.js
main.js
messageList.js
messageTray.js
modalDialog.js
mpris.js
notificationDaemon.js
osdMonitorLabeler.js
osdWindow.js
overview.js
overviewControls.js
padOsd.js
pageIndicators.js
panel.js
panelMenu.js
pointerA11yTimeout.js
pointerWatcher.js
popupMenu.js
quickSettings.js
remoteSearch.js
ripples.js
runDialog.js
screenShield.js
screenshot.js
scripting.js
search.js
searchController.js
sessionMode.js
shellDBus.js
shellEntry.js
shellMountOperation.js
slider.js
swipeTracker.js
switchMonitor.js
switcherPopup.js
unlockDialog.js
userWidget.js
welcomeDialog.js
windowAttentionHandler.js
windowManager.js
windowMenu.js
windowPreview.js
workspace.js
workspaceAnimation.js
workspaceSwitcherPopup.js
workspaceThumbnail.js
workspacesView.js
xdndHandler.js
js-resources.gresource.xml
meson.build
portal-resources.gresource.xml
lint
man
meson
po
src
subprojects
tests
tools
.editorconfig
.eslintrc.yml
.gitignore
.gitlab-ci.yml
.gitmodules
.jscheckignore
COPYING
NEWS
README.md
config.h.meson
gnome-shell.doap
meson.build
meson_options.txt

Calling `setApp` after the menu has been destroyed results in error. Moving the method before `super.destroy()` fixes the issue. Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2998>
295 lines
9.3 KiB
JavaScript
295 lines
9.3 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
|
|
import Clutter from 'gi://Clutter';
|
|
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';
|
|
|
|
import * as AppFavorites from './appFavorites.js';
|
|
import * as Main from './main.js';
|
|
import * as ParentalControlsManager from '../misc/parentalControlsManager.js';
|
|
import * as PopupMenu from './popupMenu.js';
|
|
|
|
export 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(_('App 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() {
|
|
this.setApp(null);
|
|
super.destroy();
|
|
}
|
|
|
|
/**
|
|
* @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;
|
|
|
|
const laters = global.compositor.get_laters();
|
|
this._updateWindowsLaterId = laters.add(
|
|
Meta.LaterType.BEFORE_REDRAW, () => {
|
|
this._updateWindowsSection();
|
|
return GLib.SOURCE_REMOVE;
|
|
});
|
|
}
|
|
|
|
_updateWindowsSection() {
|
|
if (this._updateWindowsLaterId) {
|
|
const laters = global.compositor.get_laters();
|
|
laters.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);
|
|
});
|
|
}
|
|
}
|