2025-05-09 09:48:24 +02:00

411 lines
14 KiB
JavaScript

import {gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js';
import Clutter from 'gi://Clutter';
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import St from 'gi://St';
import {AppMenu} from 'resource:///org/gnome/shell/ui/appMenu.js';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
import {ArcMenuManager} from './arcmenuManager.js';
import * as Utils from './utils.js';
const DESKTOP_ICONS_UUIDS = [
'ding@rastersoft.com', 'gtk4-ding@smedius.gitlab.com',
'desktopicons-neo@darkdemon',
];
/**
*
* @param {Actor} child
*/
function isPopupMenuItemVisible(child) {
if (child._delegate instanceof PopupMenu.PopupMenuSection) {
if (child._delegate.isEmpty())
return false;
}
return child.visible;
}
export const AppContextMenu = class ArcMenuAppContextMenu extends AppMenu {
constructor(sourceActor, menuLayout) {
super(sourceActor, St.Side.TOP);
this._menuLayout = menuLayout;
this._menuButton = this._menuLayout.menuButton;
this._pinnedAppData = this.sourceActor.pinnedAppData;
this._enableFavorites = true;
this._showSingleWindows = true;
this.actor.add_style_class_name('arcmenu-menu app-menu');
Main.uiGroup.add_child(this.actor);
this._menuLayout.contextMenuManager.addMenu(this);
this.sourceActor.connect('destroy', () => {
if (this.isOpen)
this.close();
Main.uiGroup.remove_child(this.actor);
this.destroy();
});
this.actor.connect('key-press-event', this._menuKeyPress.bind(this));
this._newWindowItem.connect('activate', () => this.closeMenus());
this._onGpuMenuItem.connect('activate', () => this.closeMenus());
this._detailsItem.connect('activate', () => this.closeMenus());
this._arcMenuPinnedItem = this._createMenuItem(_('Pin to ArcMenu'), 8, () => {
this.close();
if (this._pinnedAppData) {
let sourceSettings;
const isFolder = this.sourceActor.folderSettings;
if (isFolder)
sourceSettings = this.sourceActor.folderSettings;
else
sourceSettings = ArcMenuManager.settings;
const pinnedAppsList = sourceSettings.get_value('pinned-apps').deepUnpack();
for (let i = 0; i < pinnedAppsList.length; i++) {
if (pinnedAppsList[i].id === this._app.get_id()) {
pinnedAppsList.splice(i, 1);
sourceSettings.set_value('pinned-apps', new GLib.Variant('aa{ss}', pinnedAppsList));
break;
}
}
} else {
const pinnedAppsList = ArcMenuManager.settings.get_value('pinned-apps').deepUnpack();
const newPinnedAppData = {
id: this._app.get_id(),
};
pinnedAppsList.push(newPinnedAppData);
ArcMenuManager.settings.set_value('pinned-apps', new GLib.Variant('aa{ss}', pinnedAppsList));
}
});
this._createDesktopShortcutItem = this._createMenuItem(_('Create Desktop Shortcut'), 7, () => {
const [exists, src, dst] = this.getDesktopShortcut();
if (exists && src && dst) {
try {
dst.delete(null);
} catch (e) {
console.log(`Failed to delete shortcut: ${e.message}`);
}
} else if (src && dst) {
try {
src.copy(dst, Gio.FileCopyFlags.OVERWRITE, null, null);
} catch (e) {
console.log(`Failed to copy to desktop: ${e.message}`);
}
}
this.close();
this._updateDesktopShortcutItem();
});
this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem(), 8);
ArcMenuManager.settings.connectObject('changed::pinned-apps', () => this._updateArcMenuPinnedItem(), this.actor);
Main.extensionManager.connectObject('extension-state-changed', (data, changedExtension) => {
if (DESKTOP_ICONS_UUIDS.includes(changedExtension.uuid))
this._updateDesktopShortcutItem();
});
this.connect('active-changed', () => this._activeChanged());
}
_activeChanged() {
if (this._activeMenuItem)
Utils.ensureActorVisibleInScrollView(this._activeMenuItem);
}
open(animate) {
if (this._menuButton.tooltipShowingID) {
GLib.source_remove(this._menuButton.tooltipShowingID);
this._menuButton.tooltipShowingID = null;
this._menuButton.tooltipShowing = false;
}
if (this.sourceActor.tooltip) {
this.sourceActor.tooltip.hide();
this._menuButton.tooltipShowing = false;
}
super.open(animate);
this.sourceActor.add_style_pseudo_class('active');
}
destroy() {
this.destroyed = true;
this._createDesktopShortcutItem = null;
this._arcMenuPinnedItem = null;
this._disconnectSignals();
Main.extensionManager.disconnectObject(this);
this._menuButton = null;
this._pinnedAppData = null;
this._menuLayout = null;
super.destroy();
}
closeMenus() {
this.close();
this._menuLayout.arcMenu.toggle();
}
_createMenuItem(labelText, position, callback) {
const item = new PopupMenu.PopupMenuItem(labelText);
item.connect('activate', () => callback());
this.addMenuItem(item, position);
return item;
}
setApp(app) {
if (this._app === app)
return;
this._app?.disconnectObject(this);
if (this.destroyed)
return;
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);
this.closeMenus();
});
});
this._updateQuitItem();
this._updateNewWindowItem();
this._updateFavoriteItem();
this._updateGpuItem();
this._updateArcMenuPinnedItem();
this._updateDesktopShortcutItem();
}
isDesktopActive() {
let hasActiveDesktop = false;
DESKTOP_ICONS_UUIDS.forEach(uuid => {
const extension = Main.extensionManager.lookup(uuid);
if (extension?.state === Utils.ExtensionState.ACTIVE)
hasActiveDesktop = true;
});
return hasActiveDesktop;
}
getDesktopShortcut() {
if (!this._app)
return [false, null, null];
const fileDestination = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP);
const src = Gio.File.new_for_path(this._app.get_app_info().get_filename());
const dst = Gio.File.new_for_path(GLib.build_filenamev([fileDestination, src.get_basename()]));
const exists = dst.query_exists(null);
return [exists, src, dst];
}
_updateDesktopShortcutItem() {
const isDesktopActive = this.isDesktopActive();
if (!this._app || !isDesktopActive) {
this._createDesktopShortcutItem.visible = false;
return;
}
this._createDesktopShortcutItem.visible = true;
const [exists, src_, dst_] = this.getDesktopShortcut();
this._createDesktopShortcutItem.label.text = exists ? _('Delete Desktop Shortcut')
: _('Create Desktop Shortcut');
}
// For Custom Shortcuts in Pinned Apps category. ie ArcMenu Settings
addUnpinItem(id, folder = null) {
this._disconnectSignals();
this.removeAll();
this._id = id;
this._arcMenuPinnedItem = this._createMenuItem(_('Unpin from ArcMenu'), 0, () => {
this.close();
let sourceSettings;
if (!folder && this.sourceActor.folderSettings)
sourceSettings = this.sourceActor.folderSettings;
else
sourceSettings = ArcMenuManager.settings;
// Unpinned the folder, reset all folder settings keys
if (folder) {
const keys = folder.settings_schema.list_keys();
for (const key of keys)
folder.reset(key);
return;
}
const pinnedAppsList = sourceSettings.get_value('pinned-apps').deepUnpack();
for (let i = 0; i < pinnedAppsList.length; i++) {
if (pinnedAppsList[i].id === this._id) {
pinnedAppsList.splice(i, 1);
sourceSettings.set_value('pinned-apps', new GLib.Variant('aa{ss}', pinnedAppsList));
break;
}
}
});
}
_updateArcMenuPinnedItem() {
if (!this._app) {
this._arcMenuPinnedItem.visible = false;
return;
}
this._arcMenuPinnedItem.visible = this._menuLayout.hasPinnedApps;
this._arcMenuPinnedItem.label.text = this._pinnedAppData ? _('Unpin from ArcMenu') : _('Pin to ArcMenu');
}
_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 => {
this.closeMenus();
Main.activateWindow(window, event.get_time());
});
window.connectObject('notify::title', () => {
item.label.text = window.title || this._app.get_name();
}, item);
});
}
setFolderPath(path) {
this._disconnectSignals();
this.removeAll();
this._openFolderLocationItem = this._createMenuItem(_('Open Folder Location'), 0, () => {
const file = Gio.File.new_for_path(path);
const context = global.create_app_launch_context(Clutter.get_current_event().get_time(), -1);
new Promise((resolve, reject) => {
Gio.AppInfo.launch_default_for_uri_async(file.get_uri(), context, null, (o, res) => {
try {
Gio.AppInfo.launch_default_for_uri_finish(res);
resolve();
} catch (e) {
reject(e);
}
});
});
this.closeMenus();
});
}
addAdditionalAction(name, action) {
if (!this._openFolderLocationItem) {
this._disconnectSignals();
this.removeAll();
} else {
this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
}
this._additionalAction = new PopupMenu.PopupMenuItem(_(name));
this._additionalAction.connect('activate', () => {
this.close();
action();
});
this.addMenuItem(this._additionalAction);
}
isEmpty() {
if (!this._app && !this._openFolderLocationItem && !this._id && !this._additionalAction)
return true;
const hasVisibleChildren = this.box.get_children().some(child => {
if (child._delegate instanceof PopupMenu.PopupSeparatorMenuItem)
return false;
return isPopupMenuItemVisible(child);
});
return !hasVisibleChildren;
}
centerBoxPointerPosition() {
this._boxPointer.setSourceAlignment(.50);
this._arrowAlignment = .5;
this._boxPointer._border.queue_repaint();
}
rightBoxPointerPosition() {
this._arrowSide = St.Side.LEFT;
this._boxPointer._arrowSide = St.Side.LEFT;
this._boxPointer._userArrowSide = St.Side.LEFT;
this._boxPointer.setSourceAlignment(.50);
this._arrowAlignment = .5;
this._boxPointer._border.queue_repaint();
}
_disconnectSignals() {
ArcMenuManager.settings.disconnectObject(this.actor);
this._appSystem.disconnectObject(this.actor);
this._parentalControlsManager.disconnectObject(this.actor);
this._appFavorites.disconnectObject(this.actor);
global.settings.disconnectObject(this.actor);
global.disconnectObject(this.actor);
}
close(animate) {
super.close(animate);
this.sourceActor.remove_style_pseudo_class('active');
this.sourceActor.sync_hover();
}
_menuKeyPress(actor, event) {
const symbol = event.get_key_symbol();
if (symbol === Clutter.KEY_Menu) {
this.toggle();
this.sourceActor.sync_hover();
}
}
_onKeyPress() {
return Clutter.EVENT_PROPAGATE;
}
};