gconf-settings/skel/.local/share/gnome-shell/extensions/dash-to-dockmicxgx.gmail.com/appIcons.js

1208 lines
45 KiB
JavaScript

// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Clutter = imports.gi.Clutter;
const GdkPixbuf = imports.gi.GdkPixbuf
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
const Signals = imports.signals;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Mainloop = imports.mainloop;
// Use __ () and N__() for the extension gettext domain, and reuse
// the shell domain with the default _() and N_()
const Gettext = imports.gettext.domain('dashtodock');
const __ = Gettext.gettext;
const N__ = function(e) { return e };
const AppDisplay = imports.ui.appDisplay;
const AppFavorites = imports.ui.appFavorites;
const Dash = imports.ui.dash;
const DND = imports.ui.dnd;
const IconGrid = imports.ui.iconGrid;
const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu;
const Util = imports.misc.util;
const Workspace = imports.ui.workspace;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const Docking = Me.imports.docking;
const Utils = Me.imports.utils;
const WindowPreview = Me.imports.windowPreview;
const AppIconIndicators = Me.imports.appIconIndicators;
let tracker = Shell.WindowTracker.get_default();
const clickAction = {
SKIP: 0,
MINIMIZE: 1,
LAUNCH: 2,
CYCLE_WINDOWS: 3,
MINIMIZE_OR_OVERVIEW: 4,
PREVIEWS: 5,
MINIMIZE_OR_PREVIEWS: 6,
FOCUS_OR_PREVIEWS: 7,
QUIT: 8,
};
const scrollAction = {
DO_NOTHING: 0,
CYCLE_WINDOWS: 1,
SWITCH_WORKSPACE: 2
};
let recentlyClickedAppLoopId = 0;
let recentlyClickedApp = null;
let recentlyClickedAppWindows = null;
let recentlyClickedAppIndex = 0;
let recentlyClickedAppMonitor = -1;
/**
* Extend AppIcon
*
* - Apply a css class based on the number of windows of each application (#N);
* - Customized indicators for running applications in place of the default "dot" style which is hidden (#N);
* a class of the form "running#N" is applied to the AppWellIcon actor.
* like the original .running one.
* - Add a .focused style to the focused app
* - Customize click actions.
* - Update minimization animation target
* - Update menu if open on windows change
*/
var MyAppIcon = class DashToDock_AppIcon extends AppDisplay.AppIcon {
// settings are required inside.
constructor(remoteModel, app, monitorIndex, iconParams) {
super(app, iconParams);
// a prefix is required to avoid conflicting with the parent class variable
this.monitorIndex = monitorIndex;
this._signalsHandler = new Utils.GlobalSignalsHandler();
this.remoteModel = remoteModel;
this._indicator = null;
let appInfo = app.get_app_info();
this._location = appInfo ? appInfo.get_string('XdtdUri') : null;
this._updateIndicatorStyle();
this._optionalScrollCycleWindows();
// Monitor windows-changes instead of app state.
// Keep using the same Id and function callback (that is extended)
if (this._stateChangedId > 0) {
this.app.disconnect(this._stateChangedId);
this._stateChangedId = 0;
}
this._windowsChangedId = this.app.connect('windows-changed',
this.onWindowsChanged.bind(this));
this._focusAppChangeId = tracker.connect('notify::focus-app',
this._onFocusAppChanged.bind(this));
// In Wayland sessions, this signal is needed to track the state of windows dragged
// from one monitor to another. As this is triggered quite often (whenever a new winow
// of any application opened or moved to a different desktop),
// we restrict this signal to the case when 'isolate-monitors' is true,
// and if there are at least 2 monitors.
if (Docking.DockManager.settings.get_boolean('isolate-monitors') &&
Main.layoutManager.monitors.length > 1) {
this._signalsHandler.removeWithLabel('isolate-monitors');
this._signalsHandler.addWithLabel('isolate-monitors', [
global.display,
'window-entered-monitor',
this._onWindowEntered.bind(this)
]);
}
this._progressOverlayArea = null;
this._progress = 0;
let keys = ['apply-custom-theme',
'running-indicator-style',
];
keys.forEach(function(key) {
this._signalsHandler.add([
Docking.DockManager.settings,
'changed::' + key,
this._updateIndicatorStyle.bind(this)
]);
}, this);
if (this._location) {
this._signalsHandler.add([
Docking.DockManager.getDefault().fm1Client,
'windows-changed',
this.onWindowsChanged.bind(this)
]);
}
this._signalsHandler.add([
Docking.DockManager.settings,
'changed::scroll-action',
this._optionalScrollCycleWindows.bind(this)
]);
this._numberOverlay();
this._previewMenuManager = null;
this._previewMenu = null;
}
_onDestroy() {
super._onDestroy();
// This is necessary due to an upstream bug
// https://bugzilla.gnome.org/show_bug.cgi?id=757556
// It can be safely removed once it get solved upstrea.
if (this._menu)
this._menu.close(false);
// Disconect global signals
if (this._windowsChangedId > 0)
this.app.disconnect(this._windowsChangedId);
this._windowsChangedId = 0;
if (this._focusAppChangeId > 0) {
tracker.disconnect(this._focusAppChangeId);
this._focusAppChangeId = 0;
}
this._signalsHandler.destroy();
if (this._scrollEventHandler)
this.actor.disconnect(this._scrollEventHandler);
}
// TOOD Rename this function
_updateIndicatorStyle() {
if (this._indicator !== null) {
this._indicator.destroy();
this._indicator = null;
}
this._indicator = new AppIconIndicators.AppIconIndicator(this);
this._indicator.update();
}
_onWindowEntered(metaScreen, monitorIndex, metaWin) {
let app = Shell.WindowTracker.get_default().get_window_app(metaWin);
if (app && app.get_id() == this.app.get_id())
this.onWindowsChanged();
}
_optionalScrollCycleWindows() {
if (this._scrollEventHandler) {
this.actor.disconnect(this._scrollEventHandler);
this._scrollEventHandler = 0;
}
let settings = Docking.DockManager.settings;
let isEnabled = settings.get_enum('scroll-action') === scrollAction.CYCLE_WINDOWS;
if (!isEnabled) return;
this._scrollEventHandler = this.actor.connect('scroll-event',
this.onScrollEvent.bind(this));
}
onScrollEvent(actor, event) {
// We only activate windows of running applications, i.e. we never open new windows
// We check if the app is running, and that the # of windows is > 0 in
// case we use workspace isolation,
let appIsRunning = this.app.state == Shell.AppState.RUNNING
&& this.getInterestingWindows().length > 0;
if (!appIsRunning)
return false
if (this._optionalScrollCycleWindowsDeadTimeId)
return false;
else
this._optionalScrollCycleWindowsDeadTimeId = Mainloop.timeout_add(250, () => {
this._optionalScrollCycleWindowsDeadTimeId = 0;
});
let direction = null;
switch (event.get_scroll_direction()) {
case Clutter.ScrollDirection.UP:
direction = Meta.MotionDirection.UP;
break;
case Clutter.ScrollDirection.DOWN:
direction = Meta.MotionDirection.DOWN;
break;
case Clutter.ScrollDirection.SMOOTH:
let [dx, dy] = event.get_scroll_delta();
if (dy < 0)
direction = Meta.MotionDirection.UP;
else if (dy > 0)
direction = Meta.MotionDirection.DOWN;
break;
}
let focusedApp = tracker.focus_app;
if (!Main.overview._shown) {
let reversed = direction === Meta.MotionDirection.UP;
if (this.app == focusedApp)
this._cycleThroughWindows(reversed);
else {
// Activate the first window
let windows = this.getInterestingWindows();
if (windows.length > 0) {
let w = windows[0];
Main.activateWindow(w);
}
}
}
else
this.app.activate();
return true;
}
onWindowsChanged() {
if (this._menu && this._menu.isOpen)
this._menu.update();
this._indicator.update();
this.updateIconGeometry();
}
/**
* Update taraget for minimization animation
*/
updateIconGeometry() {
// If (for unknown reason) the actor is not on the stage the reported size
// and position are random values, which might exceeds the integer range
// resulting in an error when assigned to the a rect. This is a more like
// a workaround to prevent flooding the system with errors.
if (this.actor.get_stage() == null)
return;
let rect = new Meta.Rectangle();
[rect.x, rect.y] = this.actor.get_transformed_position();
[rect.width, rect.height] = this.actor.get_transformed_size();
let windows = this.getWindows();
if (Docking.DockManager.settings.get_boolean('multi-monitor')) {
let monitorIndex = this.monitorIndex;
windows = windows.filter(function(w) {
return w.get_monitor() == monitorIndex;
});
}
windows.forEach(function(w) {
w.set_icon_geometry(rect);
});
}
_updateRunningStyle() {
// The logic originally in this function has been moved to
// AppIconIndicatorBase._updateDefaultDot(). However it cannot be removed as
// it called by the parent constructor.
}
popupMenu() {
this._removeMenuTimeout();
this.actor.fake_release();
this._draggable.fakeRelease();
if (!this._menu) {
this._menu = new MyAppIconMenu(this);
this._menu.connect('activate-window', (menu, window) => {
this.activateWindow(window);
});
this._menu.connect('open-state-changed', (menu, isPoppedUp) => {
if (!isPoppedUp)
this._onMenuPoppedDown();
else {
// Setting the max-height is s useful if part of the menu is
// scrollable so the minimum height is smaller than the natural height.
let monitor_index = Main.layoutManager.findIndexForActor(this.actor);
let workArea = Main.layoutManager.getWorkAreaForMonitor(monitor_index);
let position = Utils.getPosition();
this._isHorizontal = ( position == St.Side.TOP ||
position == St.Side.BOTTOM);
// If horizontal also remove the height of the dash
let fixedDock = Docking.DockManager.settings.get_boolean('dock-fixed');
let additional_margin = this._isHorizontal && !fixedDock ? Main.overview._dash.actor.height : 0;
let verticalMargins = this._menu.actor.margin_top + this._menu.actor.margin_bottom;
// Also set a max width to the menu, so long labels (long windows title) get truncated
this._menu.actor.style = ('max-height: ' + Math.round(workArea.height - additional_margin - verticalMargins) + 'px;' +
'max-width: 400px');
}
});
let id = Main.overview.connect('hiding', () => {
this._menu.close();
});
this._menu.actor.connect('destroy', function() {
Main.overview.disconnect(id);
});
this._menuManager.addMenu(this._menu);
}
this.emit('menu-state-changed', true);
this.actor.set_hover(true);
this._menu.popup();
this._menuManager.ignoreRelease();
this.emit('sync-tooltip');
return false;
}
_onFocusAppChanged() {
this._indicator.update();
}
activate(button) {
let event = Clutter.get_current_event();
let modifiers = event ? event.get_state() : 0;
let focusedApp = tracker.focus_app;
// Only consider SHIFT and CONTROL as modifiers (exclude SUPER, CAPS-LOCK, etc.)
modifiers = modifiers & (Clutter.ModifierType.SHIFT_MASK | Clutter.ModifierType.CONTROL_MASK);
// We don't change the CTRL-click behaviour: in such case we just chain
// up the parent method and return.
if (modifiers & Clutter.ModifierType.CONTROL_MASK) {
// Keep default behaviour: launch new window
// By calling the parent method I make it compatible
// with other extensions tweaking ctrl + click
super.activate(button);
return;
}
// We check what type of click we have and if the modifier SHIFT is
// being used. We then define what buttonAction should be for this
// event.
let buttonAction = 0;
let settings = Docking.DockManager.settings;
if (button && button == 2 ) {
if (modifiers & Clutter.ModifierType.SHIFT_MASK)
buttonAction = settings.get_enum('shift-middle-click-action');
else
buttonAction = settings.get_enum('middle-click-action');
}
else if (button && button == 1) {
if (modifiers & Clutter.ModifierType.SHIFT_MASK)
buttonAction = settings.get_enum('shift-click-action');
else
buttonAction = settings.get_enum('click-action');
}
// We check if the app is running, and that the # of windows is > 0 in
// case we use workspace isolation.
let windows = this.getInterestingWindows();
let appIsRunning = (this.app.state == Shell.AppState.RUNNING || this.isLocation())
&& windows.length > 0;
// Some action modes (e.g. MINIMIZE_OR_OVERVIEW) require overview to remain open
// This variable keeps track of this
let shouldHideOverview = true;
// We customize the action only when the application is already running
if (appIsRunning) {
switch (buttonAction) {
case clickAction.MINIMIZE:
// In overview just activate the app, unless the acion is explicitely
// requested with a keyboard modifier
if (!Main.overview._shown || modifiers){
// If we have button=2 or a modifier, allow minimization even if
// the app is not focused
if (this.app == focusedApp || button == 2 || modifiers & Clutter.ModifierType.SHIFT_MASK) {
// minimize all windows on double click and always in the case of primary click without
// additional modifiers
let click_count = 0;
if (Clutter.EventType.CLUTTER_BUTTON_PRESS)
click_count = event.get_click_count();
let all_windows = (button == 1 && ! modifiers) || click_count > 1;
this._minimizeWindow(all_windows);
}
else
this._activateAllWindows();
}
else {
let w = windows[0];
Main.activateWindow(w);
}
break;
case clickAction.MINIMIZE_OR_OVERVIEW:
// When a single window is present, toggle minimization
// If only one windows is present toggle minimization, but only when trigggered with the
// simple click action (no modifiers, no middle click).
if (windows.length == 1 && !modifiers && button == 1) {
let w = windows[0];
if (this.app == focusedApp) {
// Window is raised, minimize it
this._minimizeWindow(w);
} else {
// Window is minimized, raise it
Main.activateWindow(w);
}
// Launch overview when multiple windows are present
// TODO: only show current app windows when gnome shell API will allow it
} else {
shouldHideOverview = false;
Main.overview.toggle();
}
break;
case clickAction.CYCLE_WINDOWS:
if (!Main.overview._shown){
if (this.app == focusedApp)
this._cycleThroughWindows();
else {
// Activate the first window
let w = windows[0];
Main.activateWindow(w);
}
}
else
this.app.activate();
break;
case clickAction.FOCUS_OR_PREVIEWS:
if (this.app == focusedApp &&
(windows.length > 1 || modifiers || button != 1)) {
this._windowPreviews();
} else {
// Activate the first window
let w = windows[0];
Main.activateWindow(w);
}
break;
case clickAction.LAUNCH:
this.launchNewWindow();
break;
case clickAction.PREVIEWS:
if (!Main.overview._shown) {
// If only one windows is present just switch to it, but only when trigggered with the
// simple click action (no modifiers, no middle click).
if (windows.length == 1 && !modifiers && button == 1) {
let w = windows[0];
Main.activateWindow(w);
} else
this._windowPreviews();
}
else {
this.app.activate();
}
break;
case clickAction.MINIMIZE_OR_PREVIEWS:
// When a single window is present, toggle minimization
// If only one windows is present toggle minimization, but only when trigggered with the
// simple click action (no modifiers, no middle click).
if (!Main.overview._shown){
if (windows.length == 1 && !modifiers && button == 1) {
let w = windows[0];
if (this.app == focusedApp) {
// Window is raised, minimize it
this._minimizeWindow(w);
} else {
// Window is minimized, raise it
Main.activateWindow(w);
}
} else {
// Launch previews when multiple windows are present
this._windowPreviews();
}
} else {
this.app.activate();
}
break;
case clickAction.QUIT:
this.closeAllWindows();
break;
case clickAction.SKIP:
let w = windows[0];
Main.activateWindow(w);
break;
}
}
else {
this.launchNewWindow();
}
// Hide overview except when action mode requires it
if(shouldHideOverview) {
Main.overview.hide();
}
}
shouldShowTooltip() {
return this.actor.hover && (!this._menu || !this._menu.isOpen) &&
(!this._previewMenu || !this._previewMenu.isOpen);
}
_windowPreviews() {
if (!this._previewMenu) {
this._previewMenuManager = new PopupMenu.PopupMenuManager(this.actor);
this._previewMenu = new WindowPreview.WindowPreviewMenu(this);
this._previewMenuManager.addMenu(this._previewMenu);
this._previewMenu.connect('open-state-changed', (menu, isPoppedUp) => {
if (!isPoppedUp)
this._onMenuPoppedDown();
});
let id = Main.overview.connect('hiding', () => {
this._previewMenu.close();
});
this._previewMenu.actor.connect('destroy', function() {
Main.overview.disconnect(id);
});
}
if (this._previewMenu.isOpen)
this._previewMenu.close();
else
this._previewMenu.popup();
return false;
}
// Try to do the right thing when attempting to launch a new window of an app. In
// particular, if the application doens't allow to launch a new window, activate
// the existing window instead.
launchNewWindow(p) {
let appInfo = this.app.get_app_info();
let actions = appInfo.list_actions();
if (this.app.can_open_new_window()) {
this.animateLaunch();
// This is used as a workaround for a bug resulting in no new windows being opened
// for certain running applications when calling open_new_window().
//
// https://bugzilla.gnome.org/show_bug.cgi?id=756844
//
// Similar to what done when generating the popupMenu entries, if the application provides
// a "New Window" action, use it instead of directly requesting a new window with
// open_new_window(), which fails for certain application, notably Nautilus.
if (actions.indexOf('new-window') == -1) {
this.app.open_new_window(-1);
}
else {
let i = actions.indexOf('new-window');
if (i !== -1)
this.app.launch_action(actions[i], global.get_current_time(), -1);
}
}
else {
// Try to manually activate the first window. Otherwise, when the app is activated by
// switching to a different workspace, a launch spinning icon is shown and disappers only
// after a timeout.
let windows = this.getWindows();
if (windows.length > 0)
Main.activateWindow(windows[0])
else
this.app.activate();
}
}
_numberOverlay() {
// Add label for a Hot-Key visual aid
this._numberOverlayLabel = new St.Label();
this._numberOverlayBin = new St.Bin({
child: this._numberOverlayLabel,
x_align: St.Align.START, y_align: St.Align.START,
x_expand: true, y_expand: true
});
this._numberOverlayLabel.add_style_class_name('number-overlay');
this._numberOverlayOrder = -1;
this._numberOverlayBin.hide();
this._iconContainer.add_child(this._numberOverlayBin);
}
updateNumberOverlay() {
// We apply an overall scale factor that might come from a HiDPI monitor.
// Clutter dimensions are in physical pixels, but CSS measures are in logical
// pixels, so make sure to consider the scale.
let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
// Set the font size to something smaller than the whole icon so it is
// still visible. The border radius is large to make the shape circular
let [minWidth, natWidth] = this._iconContainer.get_preferred_width(-1);
let font_size = Math.round(Math.max(12, 0.3*natWidth) / scaleFactor);
let size = Math.round(font_size*1.2);
this._numberOverlayLabel.set_style(
'font-size: ' + font_size + 'px;' +
'border-radius: ' + this.icon.iconSize + 'px;' +
'width: ' + size + 'px; height: ' + size +'px;'
);
}
setNumberOverlay(number) {
this._numberOverlayOrder = number;
this._numberOverlayLabel.set_text(number.toString());
}
toggleNumberOverlay(activate) {
if (activate && this._numberOverlayOrder > -1) {
this.updateNumberOverlay();
this._numberOverlayBin.show();
}
else
this._numberOverlayBin.hide();
}
_minimizeWindow(param) {
// Param true make all app windows minimize
let windows = this.getInterestingWindows();
let current_workspace = global.workspace_manager.get_active_workspace();
for (let i = 0; i < windows.length; i++) {
let w = windows[i];
if (w.get_workspace() == current_workspace && w.showing_on_its_workspace()) {
w.minimize();
// Just minimize one window. By specification it should be the
// focused window on the current workspace.
if(!param)
break;
}
}
}
// By default only non minimized windows are activated.
// This activates all windows in the current workspace.
_activateAllWindows() {
// First activate first window so workspace is switched if needed.
// We don't do this if isolation is on!
if (!Docking.DockManager.settings.get_boolean('isolate-workspaces') &&
!Docking.DockManager.settings.get_boolean('isolate-monitors'))
this.app.activate();
// then activate all other app windows in the current workspace
let windows = this.getInterestingWindows();
let activeWorkspace = global.workspace_manager.get_active_workspace_index();
if (windows.length <= 0)
return;
let activatedWindows = 0;
for (let i = windows.length - 1; i >= 0; i--) {
if (windows[i].get_workspace().index() == activeWorkspace) {
Main.activateWindow(windows[i]);
activatedWindows++;
}
}
}
//This closes all windows of the app.
closeAllWindows() {
let windows = this.getInterestingWindows();
for (let i = 0; i < windows.length; i++)
windows[i].delete(global.get_current_time());
}
_cycleThroughWindows(reversed) {
// Store for a little amount of time last clicked app and its windows
// since the order changes upon window interaction
let MEMORY_TIME=3000;
let app_windows = this.getInterestingWindows();
if (app_windows.length <1)
return
if (recentlyClickedAppLoopId > 0)
Mainloop.source_remove(recentlyClickedAppLoopId);
recentlyClickedAppLoopId = Mainloop.timeout_add(MEMORY_TIME, this._resetRecentlyClickedApp);
// If there isn't already a list of windows for the current app,
// or the stored list is outdated, use the current windows list.
let monitorIsolation = Docking.DockManager.settings.get_boolean('isolate-monitors');
if (!recentlyClickedApp ||
recentlyClickedApp.get_id() != this.app.get_id() ||
recentlyClickedAppWindows.length != app_windows.length ||
(recentlyClickedAppMonitor != this.monitorIndex && monitorIsolation)) {
recentlyClickedApp = this.app;
recentlyClickedAppWindows = app_windows;
recentlyClickedAppMonitor = this.monitorIndex;
recentlyClickedAppIndex = 0;
}
if (reversed) {
recentlyClickedAppIndex--;
if (recentlyClickedAppIndex < 0) recentlyClickedAppIndex = recentlyClickedAppWindows.length - 1;
} else {
recentlyClickedAppIndex++;
}
let index = recentlyClickedAppIndex % recentlyClickedAppWindows.length;
let window = recentlyClickedAppWindows[index];
Main.activateWindow(window);
}
_resetRecentlyClickedApp() {
if (recentlyClickedAppLoopId > 0)
Mainloop.source_remove(recentlyClickedAppLoopId);
recentlyClickedAppLoopId=0;
recentlyClickedApp =null;
recentlyClickedAppWindows = null;
recentlyClickedAppIndex = 0;
recentlyClickedAppMonitor = -1;
return false;
}
getWindows() {
return getWindows(this.app, this._location);
}
// Filter out unnecessary windows, for instance
// nautilus desktop window.
getInterestingWindows() {
return getInterestingWindows(this.app, this.monitorIndex, this._location);
}
// Does the Icon represent a location rather than an App
isLocation() {
return this._location != null;
}
};
/**
* Extend AppIconMenu
*
* - set popup arrow side based on dash orientation
* - Add close windows option based on quitfromdash extension
* (https://github.com/deuill/shell-extension-quitfromdash)
* - Add open windows thumbnails instead of list
* - update menu when application windows change
*/
const MyAppIconMenu = class DashToDock_MyAppIconMenu extends AppDisplay.AppIconMenu {
constructor(source) {
let side = Utils.getPosition();
// Damm it, there has to be a proper way of doing this...
// As I can't call the parent parent constructor (?) passing the side
// parameter, I overwite what I need later
super(source);
// Change the initialized side where required.
this._arrowSide = side;
this._boxPointer._arrowSide = side;
this._boxPointer._userArrowSide = side;
}
_redisplay() {
this.removeAll();
if (Docking.DockManager.settings.get_boolean('show-windows-preview')) {
// Display the app windows menu items and the separator between windows
// of the current desktop and other windows.
this._allWindowsMenuItem = new PopupMenu.PopupSubMenuMenuItem(__('All Windows'), false);
this._allWindowsMenuItem.actor.hide();
this.addMenuItem(this._allWindowsMenuItem);
if (!this._source.app.is_window_backed()) {
this._appendSeparator();
let appInfo = this._source.app.get_app_info();
let actions = appInfo.list_actions();
if (this._source.app.can_open_new_window() &&
actions.indexOf('new-window') == -1) {
this._newWindowMenuItem = this._appendMenuItem(_("New Window"));
this._newWindowMenuItem.connect('activate', () => {
if (this._source.app.state == Shell.AppState.STOPPED)
this._source.animateLaunch();
this._source.app.open_new_window(-1);
this.emit('activate-window', null);
});
this._appendSeparator();
}
if (AppDisplay.discreteGpuAvailable &&
this._source.app.state == Shell.AppState.STOPPED &&
actions.indexOf('activate-discrete-gpu') == -1) {
this._onDiscreteGpuMenuItem = this._appendMenuItem(_("Launch using Dedicated Graphics Card"));
this._onDiscreteGpuMenuItem.connect('activate', () => {
if (this._source.app.state == Shell.AppState.STOPPED)
this._source.animateLaunch();
this._source.app.launch(0, -1, true);
this.emit('activate-window', null);
});
}
for (let i = 0; i < actions.length; i++) {
let action = actions[i];
let item = this._appendMenuItem(appInfo.get_action_name(action));
item.connect('activate', (emitter, event) => {
this._source.app.launch_action(action, event.get_time(), -1);
this.emit('activate-window', null);
});
}
let canFavorite = global.settings.is_writable('favorite-apps') &&
!this._source.isLocation();
if (canFavorite) {
this._appendSeparator();
let isFavorite = AppFavorites.getAppFavorites().isFavorite(this._source.app.get_id());
if (isFavorite) {
let item = this._appendMenuItem(_("Remove from Favorites"));
item.connect('activate', () => {
let favs = AppFavorites.getAppFavorites();
favs.removeFavorite(this._source.app.get_id());
});
} else {
let item = this._appendMenuItem(_("Add to Favorites"));
item.connect('activate', () => {
let favs = AppFavorites.getAppFavorites();
favs.addFavorite(this._source.app.get_id());
});
}
}
if (Shell.AppSystem.get_default().lookup_app('org.gnome.Software.desktop') &&
!this._source.isLocation()) {
this._appendSeparator();
let item = this._appendMenuItem(_("Show Details"));
item.connect('activate', () => {
let id = this._source.app.get_id();
let args = GLib.Variant.new('(ss)', [id, '']);
Gio.DBus.get(Gio.BusType.SESSION, null,
function(o, res) {
let bus = Gio.DBus.get_finish(res);
bus.call('org.gnome.Software',
'/org/gnome/Software',
'org.gtk.Actions', 'Activate',
GLib.Variant.new('(sava{sv})',
['details', [args], null]),
null, 0, -1, null, null);
Main.overview.hide();
});
});
}
}
} else {
super._redisplay();
}
// quit menu
this._appendSeparator();
this._quitfromDashMenuItem = this._appendMenuItem(_("Quit"));
this._quitfromDashMenuItem.connect('activate', () => {
this._source.closeAllWindows();
});
this.update();
}
// update menu content when application windows change. This is desirable as actions
// acting on windows (closing) are performed while the menu is shown.
update() {
if(Docking.DockManager.settings.get_boolean('show-windows-preview')){
let windows = this._source.getInterestingWindows();
// update, show or hide the quit menu
if ( windows.length > 0) {
let quitFromDashMenuText = "";
if (windows.length == 1)
this._quitfromDashMenuItem.label.set_text(_("Quit"));
else
this._quitfromDashMenuItem.label.set_text(_("Quit %d Windows").format(windows.length));
this._quitfromDashMenuItem.actor.show();
} else {
this._quitfromDashMenuItem.actor.hide();
}
// update, show, or hide the allWindows menu
// Check if there are new windows not already displayed. In such case, repopulate the allWindows
// menu. Windows removal is already handled by each preview being connected to the destroy signal
let old_windows = this._allWindowsMenuItem.menu._getMenuItems().map(function(item){
return item._window;
});
let new_windows = windows.filter(function(w) {return old_windows.indexOf(w) < 0;});
if (new_windows.length > 0) {
this._populateAllWindowMenu(windows);
// Try to set the width to that of the submenu.
// TODO: can't get the actual size, getting a bit less.
// Temporary workaround: add 15px to compensate
this._allWindowsMenuItem.actor.width = this._allWindowsMenuItem.menu.actor.width + 15;
}
// The menu is created hidden and never hidded after being shown. Instead, a singlal
// connected to its items destroy will set is insensitive if no more windows preview are shown.
if (windows.length > 0){
this._allWindowsMenuItem.actor.show();
this._allWindowsMenuItem.setSensitive(true);
}
// Update separators
this._getMenuItems().forEach(this._updateSeparatorVisibility.bind(this));
}
}
_populateAllWindowMenu(windows) {
this._allWindowsMenuItem.menu.removeAll();
if (windows.length > 0) {
let activeWorkspace = global.workspace_manager.get_active_workspace();
let separatorShown = windows[0].get_workspace() != activeWorkspace;
for (let i = 0; i < windows.length; i++) {
let window = windows[i];
if (!separatorShown && window.get_workspace() != activeWorkspace) {
this._allWindowsMenuItem.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
separatorShown = true;
}
let item = new WindowPreview.WindowPreviewMenuItem(window);
this._allWindowsMenuItem.menu.addMenuItem(item);
item.connect('activate', () => {
this.emit('activate-window', window);
});
// This is to achieve a more gracefull transition when the last windows is closed.
item.connect('destroy', () => {
if(this._allWindowsMenuItem.menu._getMenuItems().length == 1) // It's still counting the item just going to be destroyed
this._allWindowsMenuItem.setSensitive(false);
});
}
}
}
};
Signals.addSignalMethods(MyAppIconMenu.prototype);
function getWindows(app, location) {
if (location != null && Docking.DockManager.getDefault().fm1Client) {
return Docking.DockManager.getDefault().fm1Client.getWindows(location);
} else {
return app.get_windows();
}
}
// Filter out unnecessary windows, for instance
// nautilus desktop window.
function getInterestingWindows(app, monitorIndex, location) {
let windows = getWindows(app, location).filter(function(w) {
return !w.skip_taskbar;
});
let settings = Docking.DockManager.settings;
// When using workspace isolation, we filter out windows
// that are not in the current workspace
if (settings.get_boolean('isolate-workspaces'))
windows = windows.filter(function(w) {
return w.get_workspace().index() == global.workspace_manager.get_active_workspace_index();
});
if (settings.get_boolean('isolate-monitors'))
windows = windows.filter(function(w) {
return w.get_monitor() == monitorIndex;
});
return windows;
}
/**
* A ShowAppsIcon improved class.
*
* - set label position based on dash orientation (Note, I am reusing most machinery of the appIcon class)
* - implement a popupMenu based on the AppIcon code (Note, I am reusing most machinery of the appIcon class)
*
*/
var MyShowAppsIcon = GObject.registerClass({
Signals: {
'menu-state-changed': { param_types: [GObject.TYPE_BOOLEAN] },
'sync-tooltip': {}
}
}
, class DashToDock_MyShowAppsIcon extends Dash.ShowAppsIcon {
_init() {
super._init();
// Re-use appIcon methods
let appIconPrototype = AppDisplay.AppIcon.prototype;
this.actor.connect('leave-event', appIconPrototype._onLeaveEvent.bind(this));
this.actor.connect('button-press-event', appIconPrototype._onButtonPress.bind(this));
this.actor.connect('touch-event', appIconPrototype._onTouchEvent.bind(this));
this.actor.connect('popup-menu', appIconPrototype._onKeyboardPopupMenu.bind(this));
this.actor.connect('clicked', this._removeMenuTimeout.bind(this));
this._menu = null;
this._menuManager = new PopupMenu.PopupMenuManager(this.actor);
this._menuTimeoutId = 0;
}
get actor() {
/* Until GNOME Shell AppIcon is an actor we need to provide this
* compatibility layer or the shell won't be able to access to the
* actual actor */
return this.toggleButton;
}
showLabel() {
itemShowLabel.call(this);
}
_onMenuPoppedDown() {
AppDisplay.AppIcon.prototype._onMenuPoppedDown.apply(this, arguments);
}
_setPopupTimeout() {
AppDisplay.AppIcon.prototype._onMenuPoppedDown.apply(this, arguments);
}
_removeMenuTimeout() {
AppDisplay.AppIcon.prototype._removeMenuTimeout.apply(this, arguments);
}
popupMenu() {
this._removeMenuTimeout();
this.actor.fake_release();
if (!this._menu) {
this._menu = new MyShowAppsIconMenu(this);
this._menu.connect('open-state-changed', (menu, isPoppedUp) => {
if (!isPoppedUp)
this._onMenuPoppedDown();
});
let id = Main.overview.connect('hiding', () => {
this._menu.close();
});
this._menu.actor.connect('destroy', function() {
Main.overview.disconnect(id);
});
this._menuManager.addMenu(this._menu);
}
this.emit('menu-state-changed', true);
this.actor.set_hover(true);
this._menu.popup();
this._menuManager.ignoreRelease();
this.emit('sync-tooltip');
return false;
}
});
/**
* A menu for the showAppsIcon
*/
var MyShowAppsIconMenu = class DashToDock_MyShowAppsIconMenu extends MyAppIconMenu {
_redisplay() {
this.removeAll();
/* Translators: %s is "Settings", which is automatically translated. You
can also translate the full message if this fits better your language. */
let name = __('Dash to Dock %s').format(_('Settings'))
let item = this._appendMenuItem(name);
item.connect('activate', function () {
Util.spawn(["gnome-shell-extension-prefs", Me.metadata.uuid]);
});
}
};
/**
* This function is used for both extendShowAppsIcon and extendDashItemContainer
*/
function itemShowLabel() {
// Check if the label is still present at all. When switching workpaces, the
// item might have been destroyed in between.
if (!this._labelText || this.label.get_stage() == null)
return;
this.label.set_text(this._labelText);
this.label.opacity = 0;
this.label.show();
let [stageX, stageY] = this.get_transformed_position();
let node = this.label.get_theme_node();
let itemWidth = this.allocation.x2 - this.allocation.x1;
let itemHeight = this.allocation.y2 - this.allocation.y1;
let labelWidth = this.label.get_width();
let labelHeight = this.label.get_height();
let x, y, xOffset, yOffset;
let position = Utils.getPosition();
this._isHorizontal = ((position == St.Side.TOP) || (position == St.Side.BOTTOM));
let labelOffset = node.get_length('-x-offset');
switch (position) {
case St.Side.LEFT:
yOffset = Math.floor((itemHeight - labelHeight) / 2);
y = stageY + yOffset;
xOffset = labelOffset;
x = stageX + this.get_width() + xOffset;
break;
case St.Side.RIGHT:
yOffset = Math.floor((itemHeight - labelHeight) / 2);
y = stageY + yOffset;
xOffset = labelOffset;
x = Math.round(stageX) - labelWidth - xOffset;
break;
case St.Side.TOP:
y = stageY + labelOffset + itemHeight;
xOffset = Math.floor((itemWidth - labelWidth) / 2);
x = stageX + xOffset;
break;
case St.Side.BOTTOM:
yOffset = labelOffset;
y = stageY - labelHeight - yOffset;
xOffset = Math.floor((itemWidth - labelWidth) / 2);
x = stageX + xOffset;
break;
}
// keep the label inside the screen border
// Only needed fot the x coordinate.
// Leave a few pixel gap
let gap = 5;
let monitor = Main.layoutManager.findMonitorForActor(this);
if (x - monitor.x < gap)
x += monitor.x - x + labelOffset;
else if (x + labelWidth > monitor.x + monitor.width - gap)
x -= x + labelWidth - (monitor.x + monitor.width) + gap;
this.label.remove_all_transitions();
this.label.set_position(x, y);
this.label.ease({
opacity: 255,
duration: Dash.DASH_ITEM_LABEL_SHOW_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD
});
}