// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- const Clutter = imports.gi.Clutter; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Meta = imports.gi.Meta; const Shell = imports.gi.Shell; const St = imports.gi.St; const Mainloop = imports.mainloop; const Params = imports.misc.params; const Main = imports.ui.main; const Dash = imports.ui.dash; const IconGrid = imports.ui.iconGrid; const Overview = imports.ui.overview; const OverviewControls = imports.ui.overviewControls; const PointerWatcher = imports.ui.pointerWatcher; const Signals = imports.signals; const ViewSelector = imports.ui.viewSelector; const WorkspaceSwitcherPopup= imports.ui.workspaceSwitcherPopup; const Layout = imports.ui.layout; const LayoutManager = imports.ui.main.layoutManager; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); const Utils = Me.imports.utils; const Intellihide = Me.imports.intellihide; const Theming = Me.imports.theming; const MyDash = Me.imports.dash; const LauncherAPI = Me.imports.launcherAPI; const FileManager1API = Me.imports.fileManager1API; const DOCK_DWELL_CHECK_INTERVAL = 100; var State = { HIDDEN: 0, SHOWING: 1, SHOWN: 2, HIDING: 3 }; const scrollAction = { DO_NOTHING: 0, CYCLE_WINDOWS: 1, SWITCH_WORKSPACE: 2 }; /** * A simple St.Widget with one child whose allocation takes into account the * slide out of its child via the _slidex parameter ([0:1]). * * Required since I want to track the input region of this container which is * based on its allocation even if the child overlows the parent actor. By doing * this the region of the dash that is slideout is not steling anymore the input * regions making the extesion usable when the primary monitor is the right one. * * The slidex parameter can be used to directly animate the sliding. The parent * must have a WEST (SOUTH) anchor_point to achieve the sliding to the RIGHT (BOTTOM) * side. */ var DashSlideContainer = GObject.registerClass({ Properties: { 'side': GObject.ParamSpec.enum( 'side', 'side', 'side', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, St.Side, St.Side.LEFT), 'slidex': GObject.ParamSpec.double( 'slidex', 'slidex', 'slidex', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT, 0, 1, 1), } }, class DashToDock_DashSlideContainer extends St.Widget { _init(params = {}) { super._init(params); this._child = null; // slide parameter: 1 = visible, 0 = hidden. this._slidex = params.slidex || 1; this._slideoutSize = 0; // minimum size when slided out } vfunc_allocate(box, flags) { this.set_allocation(box, flags); if (this._child == null) return; let availWidth = box.x2 - box.x1; let availHeight = box.y2 - box.y1; let [, , natChildWidth, natChildHeight] = this._child.get_preferred_size(); let childWidth = natChildWidth; let childHeight = natChildHeight; let childBox = new Clutter.ActorBox(); let slideoutSize = this._slideoutSize; if (this.side == St.Side.LEFT) { childBox.x1 = (this._slidex -1) * (childWidth - slideoutSize); childBox.x2 = slideoutSize + this._slidex*(childWidth - slideoutSize); childBox.y1 = 0; childBox.y2 = childBox.y1 + childHeight; } else if ((this.side == St.Side.RIGHT) || (this.side == St.Side.BOTTOM)) { childBox.x1 = 0; childBox.x2 = childWidth; childBox.y1 = 0; childBox.y2 = childBox.y1 + childHeight; } else if (this.side == St.Side.TOP) { childBox.x1 = 0; childBox.x2 = childWidth; childBox.y1 = (this._slidex -1) * (childHeight - slideoutSize); childBox.y2 = slideoutSize + this._slidex * (childHeight - slideoutSize); } this._child.allocate(childBox, flags); this._child.set_clip(-childBox.x1, -childBox.y1, -childBox.x1+availWidth, -childBox.y1 + availHeight); } /** * Just the child width but taking into account the slided out part */ vfunc_get_preferred_width(forHeight) { let [minWidth, natWidth] = this._child.get_preferred_width(forHeight); if ((this.side == St.Side.LEFT) || (this.side == St.Side.RIGHT)) { minWidth = (minWidth - this._slideoutSize) * this._slidex + this._slideoutSize; natWidth = (natWidth - this._slideoutSize) * this._slidex + this._slideoutSize; } return [minWidth, natWidth]; } /** * Just the child height but taking into account the slided out part */ vfunc_get_preferred_height(forWidth) { let [minHeight, natHeight] = this._child.get_preferred_height(forWidth); if ((this.side == St.Side.TOP) || (this.side == St.Side.BOTTOM)) { minHeight = (minHeight - this._slideoutSize) * this._slidex + this._slideoutSize; natHeight = (natHeight - this._slideoutSize) * this._slidex + this._slideoutSize; } return [minHeight, natHeight]; } /** * I was expecting it to be a virtual function... stil I don't understand * how things work. */ add_child(actor) { // I'm supposed to have only on child if (this._child !== null) this.remove_child(actor); this._child = actor; super.add_child(actor); } set slidex(value) { if (value == this._slidex) return; this._slidex = value; this.notify('slidex'); if (this._child) this._child.queue_relayout(); } get slidex() { return this._slidex; } }); var DockedDash = GObject.registerClass({ Signals: { 'showing': {}, 'hiding': {}, } }, class DashToDock extends St.Bin { _init(remoteModel, monitorIndex) { this._rtl = (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL); // Load settings let settings = DockManager.settings; this._remoteModel = remoteModel; this._monitorIndex = monitorIndex; // Connect global signals this._signalsHandler = new Utils.GlobalSignalsHandler(); this._bindSettingsChanges(); this._position = Utils.getPosition(); this._isHorizontal = ((this._position == St.Side.TOP) || (this._position == St.Side.BOTTOM)); // Temporary ignore hover events linked to autohide for whatever reason this._ignoreHover = false; this._oldignoreHover = null; // This variables are linked to the settings regardles of autohide or intellihide // being temporary disable. Get set by _updateVisibilityMode; this._autohideIsEnabled = null; this._intellihideIsEnabled = null; this._fixedIsEnabled = null; // Create intellihide object to monitor windows overlapping this._intellihide = new Intellihide.Intellihide(this._monitorIndex); // initialize dock state this._dockState = State.HIDDEN; // Put dock on the required monitor this._monitor = Main.layoutManager.monitors[this._monitorIndex]; // this store size and the position where the dash is shown; // used by intellihide module to check window overlap. this.staticBox = new Clutter.ActorBox(); // Initialize pressure barrier variables this._canUsePressure = false; this._pressureBarrier = null; this._barrier = null; this._removeBarrierTimeoutId = 0; // Initialize dwelling system variables this._dockDwelling = false; this._dockWatch = null; this._dockDwellUserTime = 0; this._dockDwellTimeoutId = 0 // Create a new dash object this.dash = new MyDash.MyDash(this._remoteModel, this._monitorIndex); if (!settings.get_boolean('show-show-apps-button')) this.dash.hideShowAppsButton(); // Create the main actor and the containers for sliding in and out and // centering, turn on track hover let positionStyleClass = ['top', 'right', 'bottom', 'left']; // This is the centering actor super._init({ name: 'dashtodockContainer', reactive: false, style_class: positionStyleClass[this._position], x_align: this._isHorizontal?St.Align.MIDDLE:St.Align.START, y_align: this._isHorizontal?St.Align.START:St.Align.MIDDLE }); this._delegate = this; // This is the sliding actor whose allocation is to be tracked for input regions this._slider = new DashSlideContainer({ side: this._position, slidex: 0 }); // This is the actor whose hover status us tracked for autohide this._box = new St.BoxLayout({ name: 'dashtodockBox', reactive: true, track_hover: true }); this._box.connect('notify::hover', this._hoverChanged.bind(this)); // Create and apply height constraint to the dash. It's controlled by this.height this.constrainSize = new Clutter.BindConstraint({ source: this, coordinate: this._isHorizontal?Clutter.BindCoordinate.WIDTH:Clutter.BindCoordinate.HEIGHT }); this.dash.add_constraint(this.constrainSize); this._signalsHandler.add([ Main.overview, 'item-drag-begin', this._onDragStart.bind(this) ], [ Main.overview, 'item-drag-end', this._onDragEnd.bind(this) ], [ Main.overview, 'item-drag-cancelled', this._onDragEnd.bind(this) ], [ // update when workarea changes, for instance if other extensions modify the struts //(like moving th panel at the bottom) global.display, 'workareas-changed', this._resetPosition.bind(this) ], [ Main.overview, 'showing', this._onOverviewShowing.bind(this) ], [ Main.overview, 'hiding', this._onOverviewHiding.bind(this) ], [ // Hide on appview Main.overview.viewSelector, 'page-changed', this._pageChanged.bind(this) ], [ Main.overview.viewSelector, 'page-empty', this._onPageEmpty.bind(this) ], [ // Ensure the ShowAppsButton status is kept in sync Main.overview.viewSelector._showAppsButton, 'notify::checked', this._syncShowAppsButtonToggled.bind(this) ], [ global.display, 'in-fullscreen-changed', this._updateBarrier.bind(this) ], [ // Monitor windows overlapping this._intellihide, 'status-changed', this._updateDashVisibility.bind(this) ], [ // Keep dragged icon consistent in size with this dash this.dash, 'icon-size-changed', () => { Main.overview.dashIconSize = this.dash.iconSize; } ], [ // This duplicate the similar signal which is in owerview.js. // Being connected and thus executed later this effectively // overwrite any attempt to use the size of the default dash //which given the customization is usually much smaller. // I can't easily disconnect the original signal Main.overview._controls.dash, 'icon-size-changed', () => { Main.overview.dashIconSize = this.dash.iconSize; } ], [ // sync hover after a popupmenu is closed this.dash, 'menu-closed', () => { this._box.sync_hover() } ]); this._injectionsHandler = new Utils.InjectionsHandler(); this._themeManager = new Theming.ThemeManager(this); // Since the actor is not a topLevel child and its parent is now not added to the Chrome, // the allocation change of the parent container (slide in and slideout) doesn't trigger // anymore an update of the input regions. Force the update manually. this.connect('notify::allocation', Main.layoutManager._queueUpdateRegions.bind(Main.layoutManager)); this.dash._container.connect('allocation-changed', this._updateStaticBox.bind(this)); this._slider.connect(this._isHorizontal ? 'notify::x' : 'notify::y', this._updateStaticBox.bind(this)); // Load optional features that need to be activated for one dock only if (this._monitorIndex == settings.get_int('preferred-monitor')) this._enableExtraFeatures(); // Load optional features that need to be activated once per dock this._optionalScrollWorkspaceSwitch(); // Delay operations that require the shell to be fully loaded and with // user theme applied. this._paintId = this.connect('paint', this._initialize.bind(this)); // Manage the which is used to reserve space in the overview for the dock // Add and additional dashSpacer positioned according to the dash positioning. // It gets restored on extension unload. this._dashSpacer = new OverviewControls.DashSpacer(); this._dashSpacer.setDashActor(this._box); if (this._position == St.Side.LEFT) Main.overview._controls._group.insert_child_at_index(this._dashSpacer, this._rtl ? -1 : 0); // insert on first else if (this._position == St.Side.RIGHT) Main.overview._controls._group.insert_child_at_index(this._dashSpacer, this._rtl ? 0 : -1); // insert on last else if (this._position == St.Side.TOP) Main.overview._overview.insert_child_at_index(this._dashSpacer, 0); else if (this._position == St.Side.BOTTOM) Main.overview._overview.insert_child_at_index(this._dashSpacer, -1); // Add dash container actor and the container to the Chrome. this.set_child(this._slider); this._slider.add_child(this._box); this._box.add_actor(this.dash); // Add aligning container without tracking it for input region Main.uiGroup.add_child(this); if (settings.get_boolean('dock-fixed')) { // Note: tracking the fullscreen directly on the slider actor causes some hiccups when fullscreening // windows of certain applications Main.layoutManager._trackActor(this, {affectsInputRegion: false, trackFullscreen: true}); Main.layoutManager._trackActor(this._slider, {affectsStruts: true}); } else Main.layoutManager._trackActor(this._slider); // Set initial position this._resetDepth(); this._resetPosition(); this.connect('destroy', this._onDestroy.bind(this)); } _initialize() { if (this._paintId > 0) { this.disconnect(this._paintId); this._paintId=0; } // Apply custome css class according to the settings this._themeManager.updateCustomTheme(); // Since Gnome 3.8 dragging an app without having opened the overview before cause the attemp to //animate a null target since some variables are not initialized when the viewSelector is created if (Main.overview.viewSelector._activePage == null) Main.overview.viewSelector._activePage = Main.overview.viewSelector._workspacesPage; this._updateVisibilityMode(); // In case we are already inside the overview when the extension is loaded, // for instance on unlocking the screen if it was locked with the overview open. if (Main.overview.visibleTarget) { this._onOverviewShowing(); this._pageChanged(); } // Setup pressure barrier (GS38+ only) this._updatePressureBarrier(); this._updateBarrier(); // setup dwelling system if pressure barriers are not available this._setupDockDwellIfNeeded(); } _onDestroy() { // Disconnect global signals this._signalsHandler.destroy(); // The dash, intellihide and themeManager have global signals as well internally this.dash.destroy(); this._intellihide.destroy(); this._themeManager.destroy(); this._injectionsHandler.destroy(); // Remove barrier timeout if (this._removeBarrierTimeoutId > 0) Mainloop.source_remove(this._removeBarrierTimeoutId); // Remove existing barrier this._removeBarrier(); // Remove pointer watcher if (this._dockWatch) { PointerWatcher.getPointerWatcher()._removeWatch(this._dockWatch); this._dockWatch = null; } // Remove the dashSpacer this._dashSpacer.destroy(); } _bindSettingsChanges() { let settings = DockManager.settings; this._signalsHandler.add([ settings, 'changed::scroll-action', () => { this._optionalScrollWorkspaceSwitch(); } ], [ settings, 'changed::dash-max-icon-size', () => { this.dash.setIconSize(settings.get_int('dash-max-icon-size')); } ], [ settings, 'changed::icon-size-fixed', () => { this.dash.setIconSize(settings.get_int('dash-max-icon-size')); } ], [ settings, 'changed::show-favorites', () => { this.dash.resetAppIcons(); } ], [ settings, 'changed::show-trash', () => { this.dash.resetAppIcons(); }, Utils.SignalsHandlerFlags.CONNECT_AFTER, ], [ settings, 'changed::show-mounts', () => { this.dash.resetAppIcons(); }, Utils.SignalsHandlerFlags.CONNECT_AFTER ], [ settings, 'changed::show-running', () => { this.dash.resetAppIcons(); } ], [ settings, 'changed::show-apps-at-top', () => { this.dash.resetAppIcons(); } ], [ settings, 'changed::show-show-apps-button', () => { if (settings.get_boolean('show-show-apps-button')) this.dash.showShowAppsButton(); else this.dash.hideShowAppsButton(); } ], [ settings, 'changed::dock-fixed', () => { if (settings.get_boolean('dock-fixed')) { Main.layoutManager._untrackActor(this); Main.layoutManager._trackActor(this, {affectsInputRegion: false, trackFullscreen: true}); Main.layoutManager._untrackActor(this._slider); Main.layoutManager._trackActor(this._slider, {affectsStruts: true}); } else { Main.layoutManager._untrackActor(this); Main.layoutManager._untrackActor(this._slider); Main.layoutManager._trackActor(this._slider); } this._resetPosition(); // Add or remove barrier depending on if dock-fixed this._updateBarrier(); this._updateVisibilityMode(); } ], [ settings, 'changed::intellihide', this._updateVisibilityMode.bind(this) ], [ settings, 'changed::intellihide-mode', () => { this._intellihide.forceUpdate(); } ], [ settings, 'changed::autohide', () => { this._updateVisibilityMode(); this._updateBarrier(); } ], [ settings, 'changed::autohide-in-fullscreen', this._updateBarrier.bind(this) ], [ settings, 'changed::extend-height', this._resetPosition.bind(this) ], [ settings, 'changed::height-fraction', this._resetPosition.bind(this) ], [ settings, 'changed::require-pressure-to-show', () => { // Remove pointer watcher if (this._dockWatch) { PointerWatcher.getPointerWatcher()._removeWatch(this._dockWatch); this._dockWatch = null; } this._setupDockDwellIfNeeded(); this._updateBarrier(); } ], [ settings, 'changed::pressure-threshold', () => { this._updatePressureBarrier(); this._updateBarrier(); } ]); } /** * This is call when visibility settings change */ _updateVisibilityMode() { let settings = DockManager.settings; if (settings.get_boolean('dock-fixed')) { this._fixedIsEnabled = true; this._autohideIsEnabled = false; this._intellihideIsEnabled = false; } else { this._fixedIsEnabled = false; this._autohideIsEnabled = settings.get_boolean('autohide') this._intellihideIsEnabled = settings.get_boolean('intellihide') } if (this._intellihideIsEnabled) this._intellihide.enable(); else this._intellihide.disable(); this._updateDashVisibility(); } /** * Show/hide dash based on, in order of priority: * overview visibility * fixed mode * intellihide * autohide * overview visibility */ _updateDashVisibility() { if (Main.overview.visibleTarget) return; let settings = DockManager.settings; if (this._fixedIsEnabled) { this._removeAnimations(); this._animateIn(settings.get_double('animation-time'), 0); } else if (this._intellihideIsEnabled) { if (this._intellihide.getOverlapStatus()) { this._ignoreHover = false; // Do not hide if autohide is enabled and mouse is hover if (!this._box.hover || !this._autohideIsEnabled) this._animateOut(settings.get_double('animation-time'), 0); } else { this._ignoreHover = true; this._removeAnimations(); this._animateIn(settings.get_double('animation-time'), 0); } } else { if (this._autohideIsEnabled) { this._ignoreHover = false; global.sync_pointer(); if (this._box.hover) this._animateIn(settings.get_double('animation-time'), 0); else this._animateOut(settings.get_double('animation-time'), 0); } else this._animateOut(settings.get_double('animation-time'), 0); } } _onOverviewShowing() { this._ignoreHover = true; this._intellihide.disable(); this._removeAnimations(); this._animateIn(DockManager.settings.get_double('animation-time'), 0); } _onOverviewHiding() { this._ignoreHover = false; this._intellihide.enable(); this._updateDashVisibility(); } _hoverChanged() { if (!this._ignoreHover) { // Skip if dock is not in autohide mode for instance because it is shown // by intellihide. if (this._autohideIsEnabled) { if (this._box.hover) this._show(); else this._hide(); } } } getDockState() { return this._dockState; } _show() { if ((this._dockState == State.HIDDEN) || (this._dockState == State.HIDING)) { if (this._dockState == State.HIDING) // suppress all potential queued transitions - i.e. added but not started, // always give priority to show this._removeAnimations(); this.emit('showing'); this._animateIn(DockManager.settings.get_double('animation-time'), 0); } } _hide() { // If no hiding animation is running or queued if ((this._dockState == State.SHOWN) || (this._dockState == State.SHOWING)) { let settings = DockManager.settings; let delay; if (this._dockState == State.SHOWING) //if a show already started, let it finish; queue hide without removing the show. // to obtain this I increase the delay to avoid the overlap and interference // between the animations delay = settings.get_double('hide-delay') + settings.get_double('animation-time'); else delay = settings.get_double('hide-delay'); this.emit('hiding'); this._animateOut(settings.get_double('animation-time'), delay); } } _animateIn(time, delay) { this._dockState = State.SHOWING; this._slider.ease_property('slidex', 1, { duration: time * 1000, delay: delay * 1000, mode: Clutter.AnimationMode.EASE_OUT_QUAD, onComplete: () => { this._dockState = State.SHOWN; // Remove barrier so that mouse pointer is released and can access monitors on other side of dock // NOTE: Delay needed to keep mouse from moving past dock and re-hiding dock immediately. This // gives users an opportunity to hover over the dock if (this._removeBarrierTimeoutId > 0) Mainloop.source_remove(this._removeBarrierTimeoutId); this._removeBarrierTimeoutId = Mainloop.timeout_add(100, this._removeBarrier.bind(this)); } }); } _animateOut(time, delay) { this._dockState = State.HIDING; this._slider.ease_property('slidex', 0, { duration: time * 1000, delay: delay * 1000, mode: Clutter.AnimationMode.EASE_OUT_QUAD, onComplete: () => { this._dockState = State.HIDDEN; // Remove queued barried removal if any if (this._removeBarrierTimeoutId > 0) Mainloop.source_remove(this._removeBarrierTimeoutId); this._updateBarrier(); } }); } /** * Dwelling system based on the GNOME Shell 3.14 messageTray code. */ _setupDockDwellIfNeeded() { // If we don't have extended barrier features, then we need // to support the old tray dwelling mechanism. if (!global.display.supports_extended_barriers() || !DockManager.settings.get_boolean('require-pressure-to-show')) { let pointerWatcher = PointerWatcher.getPointerWatcher(); this._dockWatch = pointerWatcher.addWatch(DOCK_DWELL_CHECK_INTERVAL, this._checkDockDwell.bind(this)); this._dockDwelling = false; this._dockDwellUserTime = 0; } } _checkDockDwell(x, y) { let workArea = Main.layoutManager.getWorkAreaForMonitor(this._monitor.index) let shouldDwell; // Check for the correct screen edge, extending the sensitive area to the whole workarea, // minus 1 px to avoid conflicting with other active corners. if (this._position == St.Side.LEFT) shouldDwell = (x == this._monitor.x) && (y > workArea.y) && (y < workArea.y + workArea.height); else if (this._position == St.Side.RIGHT) shouldDwell = (x == this._monitor.x + this._monitor.width - 1) && (y > workArea.y) && (y < workArea.y + workArea.height); else if (this._position == St.Side.TOP) shouldDwell = (y == this._monitor.y) && (x > workArea.x) && (x < workArea.x + workArea.width); else if (this._position == St.Side.BOTTOM) shouldDwell = (y == this._monitor.y + this._monitor.height - 1) && (x > workArea.x) && (x < workArea.x + workArea.width); if (shouldDwell) { // We only set up dwell timeout when the user is not hovering over the dock // already (!this._box.hover). // The _dockDwelling variable is used so that we only try to // fire off one dock dwell - if it fails (because, say, the user has the mouse down), // we don't try again until the user moves the mouse up and down again. if (!this._dockDwelling && !this._box.hover && (this._dockDwellTimeoutId == 0)) { // Save the interaction timestamp so we can detect user input let focusWindow = global.display.focus_window; this._dockDwellUserTime = focusWindow ? focusWindow.user_time : 0; this._dockDwellTimeoutId = Mainloop.timeout_add(DockManager.settings.get_double('show-delay') * 1000, this._dockDwellTimeout.bind(this)); GLib.Source.set_name_by_id(this._dockDwellTimeoutId, '[dash-to-dock] this._dockDwellTimeout'); } this._dockDwelling = true; } else { this._cancelDockDwell(); this._dockDwelling = false; } } _cancelDockDwell() { if (this._dockDwellTimeoutId != 0) { Mainloop.source_remove(this._dockDwellTimeoutId); this._dockDwellTimeoutId = 0; } } _dockDwellTimeout() { this._dockDwellTimeoutId = 0; if (!DockManager.settings.get_boolean('autohide-in-fullscreen') && this._monitor.inFullscreen) return GLib.SOURCE_REMOVE; // We don't want to open the tray when a modal dialog // is up, so we check the modal count for that. When we are in the // overview we have to take the overview's modal push into account if (Main.modalCount > (Main.overview.visible ? 1 : 0)) return GLib.SOURCE_REMOVE; // If the user interacted with the focus window since we started the tray // dwell (by clicking or typing), don't activate the message tray let focusWindow = global.display.focus_window; let currentUserTime = focusWindow ? focusWindow.user_time : 0; if (currentUserTime != this._dockDwellUserTime) return GLib.SOURCE_REMOVE; // Reuse the pressure version function, the logic is the same this._onPressureSensed(); return GLib.SOURCE_REMOVE; } _updatePressureBarrier() { let settings = DockManager.settings; this._canUsePressure = global.display.supports_extended_barriers(); let pressureThreshold = settings.get_double('pressure-threshold'); // Remove existing pressure barrier if (this._pressureBarrier) { this._pressureBarrier.destroy(); this._pressureBarrier = null; } if (this._barrier) { this._barrier.destroy(); this._barrier = null; } // Create new pressure barrier based on pressure threshold setting if (this._canUsePressure) { this._pressureBarrier = new Layout.PressureBarrier(pressureThreshold, settings.get_double('show-delay')*1000, Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW); this._pressureBarrier.connect('trigger', (barrier) => { if (!settings.get_boolean('autohide-in-fullscreen') && this._monitor.inFullscreen) return; this._onPressureSensed(); }); } } /** * handler for mouse pressure sensed */ _onPressureSensed() { if (Main.overview.visibleTarget) return; // In case the mouse move away from the dock area before hovering it, in such case the leave event // would never be triggered and the dock would stay visible forever. let triggerTimeoutId = Mainloop.timeout_add(250, () => { triggerTimeoutId = 0; let [x, y, mods] = global.get_pointer(); let shouldHide = true; switch (this._position) { case St.Side.LEFT: if (x <= this.staticBox.x2 && x >= this._monitor.x && y >= this._monitor.y && y <= this._monitor.y + this._monitor.height) { shouldHide = false; } break; case St.Side.RIGHT: if (x >= this.staticBox.x1 && x <= this._monitor.x + this._monitor.width && y >= this._monitor.y && y <= this._monitor.y + this._monitor.height) { shouldHide = false; } break; case St.Side.TOP: if (x >= this._monitor.x && x <= this._monitor.x + this._monitor.width && y <= this.staticBox.y2 && y >= this._monitor.y) { shouldHide = false; } break; case St.Side.BOTTOM: if (x >= this._monitor.x && x <= this._monitor.x + this._monitor.width && y >= this.staticBox.y1 && y <= this._monitor.y + this._monitor.height) { shouldHide = false; } } if (shouldHide) { this._hoverChanged(); return GLib.SOURCE_REMOVE; } else { return GLib.SOURCE_CONTINUE; } }); this._show(); } /** * Remove pressure barrier */ _removeBarrier() { if (this._barrier) { if (this._pressureBarrier) this._pressureBarrier.removeBarrier(this._barrier); this._barrier.destroy(); this._barrier = null; } this._removeBarrierTimeoutId = 0; return false; } /** * Update pressure barrier size */ _updateBarrier() { // Remove existing barrier this._removeBarrier(); // The barrier needs to be removed in fullscreen with autohide disabled, otherwise the mouse can // get trapped on monitor. if (this._monitor.inFullscreen && !DockManager.settings.get_boolean('autohide-in-fullscreen')) return // Manually reset pressure barrier // This is necessary because we remove the pressure barrier when it is triggered to show the dock if (this._pressureBarrier) { this._pressureBarrier._reset(); this._pressureBarrier._isTriggered = false; } // Create new barrier // The barrier extends to the whole workarea, minus 1 px to avoid conflicting with other active corners // Note: dash in fixed position doesn't use pressure barrier. if (this._canUsePressure && this._autohideIsEnabled && DockManager.settings.get_boolean('require-pressure-to-show')) { let x1, x2, y1, y2, direction; let workArea = Main.layoutManager.getWorkAreaForMonitor(this._monitor.index) if (this._position == St.Side.LEFT) { x1 = this._monitor.x + 1; x2 = x1; y1 = workArea.y + 1; y2 = workArea.y + workArea.height - 1; direction = Meta.BarrierDirection.POSITIVE_X; } else if (this._position == St.Side.RIGHT) { x1 = this._monitor.x + this._monitor.width - 1; x2 = x1; y1 = workArea.y + 1; y2 = workArea.y + workArea.height - 1; direction = Meta.BarrierDirection.NEGATIVE_X; } else if (this._position == St.Side.TOP) { x1 = workArea.x + 1; x2 = workArea.x + workArea.width - 1; y1 = this._monitor.y; y2 = y1; direction = Meta.BarrierDirection.POSITIVE_Y; } else if (this._position == St.Side.BOTTOM) { x1 = workArea.x + 1; x2 = workArea.x + workArea.width - 1; y1 = this._monitor.y + this._monitor.height; y2 = y1; direction = Meta.BarrierDirection.NEGATIVE_Y; } this._barrier = new Meta.Barrier({ display: global.display, x1: x1, x2: x2, y1: y1, y2: y2, directions: direction }); if (this._pressureBarrier) this._pressureBarrier.addBarrier(this._barrier); } } _isPrimaryMonitor() { return (this._monitorIndex == Main.layoutManager.primaryIndex); } _resetPosition() { // Ensure variables linked to settings are updated. this._updateVisibilityMode(); let extendHeight = DockManager.settings.get_boolean('extend-height'); // Note: do not use the workarea coordinates in the direction on which the dock is placed, // to avoid a loop [position change -> workArea change -> position change] with // fixed dock. let workArea = Main.layoutManager.getWorkAreaForMonitor(this._monitorIndex); // Reserve space for the dash on the overview // if the dock is on the primary monitor if (this._isPrimaryMonitor()) this._dashSpacer.show(); else // No space is required in the overview of the dash this._dashSpacer.hide(); let fraction = DockManager.settings.get_double('height-fraction'); if (extendHeight) fraction = 1; else if ((fraction < 0) || (fraction > 1)) fraction = 0.95; let anchor_point; if (this._isHorizontal) { this.width = Math.round(fraction * workArea.width); let pos_y; if (this._position == St.Side.BOTTOM) { pos_y = this._monitor.y + this._monitor.height; anchor_point = Clutter.Gravity.SOUTH_WEST; } else { pos_y = this._monitor.y; anchor_point = Clutter.Gravity.NORTH_WEST; } this.move_anchor_point_from_gravity(anchor_point); this.x = workArea.x + Math.round((1 - fraction) / 2 * workArea.width); this.y = pos_y; if (extendHeight) { this.dash._container.set_width(this.width); this.add_style_class_name('extended'); } else { this.dash._container.set_width(-1); this.remove_style_class_name('extended'); } } else { this.height = Math.round(fraction * workArea.height); let pos_x; if (this._position == St.Side.RIGHT) { pos_x = this._monitor.x + this._monitor.width; anchor_point = Clutter.Gravity.NORTH_EAST; } else { pos_x = this._monitor.x; anchor_point = Clutter.Gravity.NORTH_WEST; } this.move_anchor_point_from_gravity(anchor_point); this.x = pos_x; this.y = workArea.y + Math.round((1 - fraction) / 2 * workArea.height); if (extendHeight) { this.dash._container.set_height(this.height); this.add_style_class_name('extended'); } else { this.dash._container.set_height(-1); this.remove_style_class_name('extended'); } } this._y0 = this.y; } // Set the dash at the correct depth in z _resetDepth() { // Keep the dash below the modalDialogGroup Main.layoutManager.uiGroup.set_child_below_sibling(this, Main.layoutManager.modalDialogGroup); } _updateStaticBox() { this.staticBox.init_rect( this.x + this._slider.x - (this._position == St.Side.RIGHT ? this._box.width : 0), this.y + this._slider.y - (this._position == St.Side.BOTTOM ? this._box.height : 0), this._box.width, this._box.height ); this._intellihide.updateTargetBox(this.staticBox); } _removeAnimations() { this._slider.remove_all_transitions(); } _onDragStart() { // The dash need to be above the top_window_group, otherwise it doesn't // accept dnd of app icons when not in overiew mode. Main.layoutManager.uiGroup.set_child_above_sibling(this, global.top_window_group); this._oldignoreHover = this._ignoreHover; this._ignoreHover = true; this._animateIn(DockManager.settings.get_double('animation-time'), 0); } _onDragEnd() { // Restore drag default dash stack order this._resetDepth(); if (this._oldignoreHover !== null) this._ignoreHover = this._oldignoreHover; this._oldignoreHover = null; this._box.sync_hover(); if (Main.overview._shown) this._pageChanged(); } _pageChanged() { let activePage = Main.overview.viewSelector.getActivePage(); let dashVisible = (activePage == ViewSelector.ViewPage.WINDOWS || activePage == ViewSelector.ViewPage.APPS); if (dashVisible) this._animateIn(DockManager.settings.get_double('animation-time'), 0); else this._animateOut(DockManager.settings.get_double('animation-time'), 0); } _onPageEmpty() { /* The dash spacer is required only in the WINDOWS view if in the default position. * The 'page-empty' signal is emitted in between a change of view, * signalling the spacer can be added and removed without visible effect, * as it's done for the upstream dashSpacer. * * Moreover, hiding the spacer ensure the appGrid allocaton is triggered. * This matter as the appview spring animation is triggered by to first reallocaton of the appGrid, * (See appDisplay.js, line 202 on GNOME Shell 3.14: * this._grid.actor.connect('notify::allocation', ...) * which in turn seems to be triggered by changes in the other actors in the overview. * Normally, as far as I could understand, either the dashSpacer being hidden or the workspacesThumbnails * sliding out would trigger the allocation. However, with no stock dash * and no thumbnails, which happen if the user configured only 1 and static workspace, * the animation out of icons is not played. */ let activePage = Main.overview.viewSelector.getActivePage(); this._dashSpacer.visible = (this._isHorizontal || activePage == ViewSelector.ViewPage.WINDOWS); } /** * Show dock and give key focus to it */ _onAccessibilityFocus() { this._box.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); this._animateIn(DockManager.settings.get_double('animation-time'), 0); } /** * Keep ShowAppsButton status in sync with the overview status */ _syncShowAppsButtonToggled() { let status = Main.overview.viewSelector._showAppsButton.checked; if (this.dash.showAppsButton.checked !== status) this.dash.showAppsButton.checked = status; } // Optional features to be enabled only for the main Dock _enableExtraFeatures() { // Restore dash accessibility Main.ctrlAltTabManager.addGroup( this.dash, _('Dash'), 'user-bookmarks-symbolic', {focusCallback: this._onAccessibilityFocus.bind(this)}); } /** * Switch workspace by scrolling over the dock */ _optionalScrollWorkspaceSwitch() { let label = 'optionalScrollWorkspaceSwitch'; function isEnabled() { return DockManager.settings.get_enum('scroll-action') === scrollAction.SWITCH_WORKSPACE; } DockManager.settings.connect('changed::scroll-action', () => { if (isEnabled.bind(this)()) enable.bind(this)(); else disable.bind(this)(); }); if (isEnabled.bind(this)()) enable.bind(this)(); function enable() { this._signalsHandler.removeWithLabel(label); this._signalsHandler.addWithLabel(label, [ this._box, 'scroll-event', onScrollEvent.bind(this) ]); } function disable() { this._signalsHandler.removeWithLabel(label); if (this._optionalScrollWorkspaceSwitchDeadTimeId) { Mainloop.source_remove(this._optionalScrollWorkspaceSwitchDeadTimeId); this._optionalScrollWorkspaceSwitchDeadTimeId = 0; } } // This was inspired to desktop-scroller@obsidien.github.com function onScrollEvent(actor, event) { // When in overview change workscape only in windows view if (Main.overview.visible && Main.overview.viewSelector.getActivePage() !== ViewSelector.ViewPage.WINDOWS) return false; let activeWs = global.workspace_manager.get_active_workspace(); 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; } if (direction !== null) { // Prevent scroll events from triggering too many workspace switches // by adding a 250ms deadtime between each scroll event. // Usefull on laptops when using a touchpad. // During the deadtime do nothing if (this._optionalScrollWorkspaceSwitchDeadTimeId) return false; else this._optionalScrollWorkspaceSwitchDeadTimeId = Mainloop.timeout_add(250, () => { this._optionalScrollWorkspaceSwitchDeadTimeId = 0; }); let ws; ws = activeWs.get_neighbor(direction) if (Main.wm._workspaceSwitcherPopup == null) Main.wm._workspaceSwitcherPopup = new WorkspaceSwitcherPopup.WorkspaceSwitcherPopup(); // Set the actor non reactive, so that it doesn't prevent the // clicks events from reaching the dash actor. I can't see a reason // why it should be reactive. Main.wm._workspaceSwitcherPopup.actor.reactive = false; Main.wm._workspaceSwitcherPopup.connect('destroy', function() { Main.wm._workspaceSwitcherPopup = null; }); // Do not show wokspaceSwithcer in overview if (!Main.overview.visible) Main.wm._workspaceSwitcherPopup.display(direction, ws.index()); Main.wm.actionMoveWorkspace(ws); return true; } else return false; } } _activateApp(appIndex) { let children = this.dash._box.get_children().filter(function(actor) { return actor.child && actor.child._delegate && actor.child._delegate.app; }); // Apps currently in the dash let apps = children.map(function(actor) { return actor.child._delegate; }); // Activate with button = 1, i.e. same as left click let button = 1; if (appIndex < apps.length) apps[appIndex].activate(button); } }); /* * Handle keybaord shortcuts */ const DashToDock_KeyboardShortcuts_NUM_HOTKEYS = 10; var KeyboardShortcuts = class DashToDock_KeyboardShortcuts { constructor(allDocks){ this._allDocks = allDocks; this._signalsHandler = new Utils.GlobalSignalsHandler(); this._hotKeysEnabled = false; if (DockManager.settings.get_boolean('hot-keys')) this._enableHotKeys(); this._signalsHandler.add([ DockManager.settings, 'changed::hot-keys', () => { if (DockManager.settings.get_boolean('hot-keys')) this._enableHotKeys.bind(this)(); else this._disableHotKeys.bind(this)(); } ]); this._optionalNumberOverlay(); } destroy() { // Remove keybindings this._disableHotKeys(); this._disableExtraShortcut(); this._signalsHandler.destroy(); } _enableHotKeys() { if (this._hotKeysEnabled) return; // Setup keyboard bindings for dash elements let keys = ['app-hotkey-', 'app-shift-hotkey-', 'app-ctrl-hotkey-']; keys.forEach( function(key) { for (let i = 0; i < DashToDock_KeyboardShortcuts_NUM_HOTKEYS; i++) { let appNum = i; Main.wm.addKeybinding(key + (i + 1), DockManager.settings, Meta.KeyBindingFlags.NONE, Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW, () => { this._allDocks[0]._activateApp(appNum); this._showOverlay(); }); } }, this); this._hotKeysEnabled = true; } _disableHotKeys() { if (!this._hotKeysEnabled) return; let keys = ['app-hotkey-', 'app-shift-hotkey-', 'app-ctrl-hotkey-']; keys.forEach( function(key) { for (let i = 0; i < DashToDock_KeyboardShortcuts_NUM_HOTKEYS; i++) Main.wm.removeKeybinding(key + (i + 1)); }, this); this._hotKeysEnabled = false; } _optionalNumberOverlay() { let settings = DockManager.settings; this._shortcutIsSet = false; // Enable extra shortcut if either 'overlay' or 'show-dock' are true if (settings.get_boolean('hot-keys') && (settings.get_boolean('hotkeys-overlay') || settings.get_boolean('hotkeys-show-dock'))) this._enableExtraShortcut(); this._signalsHandler.add([ settings, 'changed::hot-keys', this._checkHotkeysOptions.bind(this) ], [ settings, 'changed::hotkeys-overlay', this._checkHotkeysOptions.bind(this) ], [ settings, 'changed::hotkeys-show-dock', this._checkHotkeysOptions.bind(this) ]); } _checkHotkeysOptions() { let settings = DockManager.settings; if (settings.get_boolean('hot-keys') && (settings.get_boolean('hotkeys-overlay') || settings.get_boolean('hotkeys-show-dock'))) this._enableExtraShortcut(); else this._disableExtraShortcut(); } _enableExtraShortcut() { if (!this._shortcutIsSet) { Main.wm.addKeybinding('shortcut', DockManager.settings, Meta.KeyBindingFlags.NONE, Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW, this._showOverlay.bind(this)); this._shortcutIsSet = true; } } _disableExtraShortcut() { if (this._shortcutIsSet) { Main.wm.removeKeybinding('shortcut'); this._shortcutIsSet = false; } } _showOverlay() { for (let i = 0; i < this._allDocks.length; i++) { let dock = this._allDocks[i]; if (dock._settings.get_boolean('hotkeys-overlay')) dock.dash.toggleNumberOverlay(true); // Restart the counting if the shortcut is pressed again if (dock._numberOverlayTimeoutId) { Mainloop.source_remove(dock._numberOverlayTimeoutId); dock._numberOverlayTimeoutId = 0; } // Hide the overlay/dock after the timeout let timeout = dock._settings.get_double('shortcut-timeout') * 1000; dock._numberOverlayTimeoutId = Mainloop.timeout_add(timeout, () => { dock._numberOverlayTimeoutId = 0; dock.dash.toggleNumberOverlay(false); // Hide the dock again if necessary dock._updateDashVisibility(); }); // Show the dock if it is hidden if (dock._settings.get_boolean('hotkeys-show-dock')) { let showDock = (dock._intellihideIsEnabled || dock._autohideIsEnabled); if (showDock) dock._show(); } } } }; /** * Isolate overview to open new windows for inactive apps * Note: the future implementaion is not fully contained here. Some bits are around in other methods of other classes. * This class just take care of enabling/disabling the option. */ var WorkspaceIsolation = class DashToDock_WorkspaceIsolation { constructor(allDocks) { let settings = DockManager.settings; this._allDocks = allDocks; this._signalsHandler = new Utils.GlobalSignalsHandler(); this._injectionsHandler = new Utils.InjectionsHandler(); this._signalsHandler.add([ settings, 'changed::isolate-workspaces', () => { this._allDocks.forEach(function(dock) { dock.dash.resetAppIcons(); }); if (settings.get_boolean('isolate-workspaces') || settings.get_boolean('isolate-monitors')) this._enable.bind(this)(); else this._disable.bind(this)(); } ],[ settings, 'changed::isolate-monitors', () => { this._allDocks.forEach(function(dock) { dock.dash.resetAppIcons(); }); if (settings.get_boolean('isolate-workspaces') || settings.get_boolean('isolate-monitors')) this._enable.bind(this)(); else this._disable.bind(this)(); } ]); if (settings.get_boolean('isolate-workspaces') || settings.get_boolean('isolate-monitors')) this._enable(); } _enable() { // ensure I never double-register/inject // although it should never happen this._disable(); this._allDocks.forEach(function(dock) { this._signalsHandler.addWithLabel('isolation', [ global.display, 'restacked', dock.dash._queueRedisplay.bind(dock.dash) ], [ global.window_manager, 'switch-workspace', dock.dash._queueRedisplay.bind(dock.dash) ]); // This last signal is only needed for monitor isolation, as windows // might migrate from one monitor to another without triggering 'restacked' if (DockManager.settings.get_boolean('isolate-monitors')) this._signalsHandler.addWithLabel('isolation', [ global.display, 'window-entered-monitor', dock.dash._queueRedisplay.bind(dock.dash) ]); }, this); // here this is the Shell.App function IsolatedOverview() { // These lines take care of Nautilus for icons on Desktop let windows = this.get_windows().filter(function(w) { return w.get_workspace().index() == global.workspace_manager.get_active_workspace_index(); }); if (windows.length == 1) if (windows[0].skip_taskbar) return this.open_new_window(-1); if (this.is_on_workspace(global.workspace_manager.get_active_workspace())) return Main.activateWindow(windows[0]); return this.open_new_window(-1); } this._injectionsHandler.addWithLabel('isolation', [ Shell.App.prototype, 'activate', IsolatedOverview ]); } _disable () { this._signalsHandler.removeWithLabel('isolation'); this._injectionsHandler.removeWithLabel('isolation'); } destroy() { this._signalsHandler.destroy(); this._injectionsHandler.destroy(); } }; var DockManager = class DashToDock_DockManager { constructor() { if (Me.imports.extension.dockManager) throw new Error('DashToDock has been already initialized'); Me.imports.extension.dockManager = this; this._remoteModel = new LauncherAPI.LauncherEntryRemoteModel(); this._settings = ExtensionUtils.getSettings('org.gnome.shell.extensions.dash-to-dock'); this._oldDash = Main.overview._dash; this._ensureFileManagerClient(); /* Array of all the docks created */ this._allDocks = []; this._createDocks(); // status variable: true when the overview is shown through the dash // applications button. this._forcedOverview = false; // Connect relevant signals to the toggling function this._bindSettingsChanges(); } static getDefault() { return Me.imports.extension.dockManager } static get settings() { return DockManager.getDefault()._settings; } get fm1Client() { return this._fm1Client; } _ensureFileManagerClient() { let supportsLocations = ['show-trash', 'show-mounts'].some((s) => { return this._settings.get_boolean(s); }); if (supportsLocations) { if (!this._fm1Client) this._fm1Client = new FileManager1API.FileManager1Client(); } else if (this._fm1Client) { this._fm1Client.destroy(); this._fm1Client = null; } } _toggle() { this._deleteDocks(); this._createDocks(); this.emit('toggled'); } _bindSettingsChanges() { // Connect relevant signals to the toggling function this._signalsHandler = new Utils.GlobalSignalsHandler(); this._signalsHandler.add([ Meta.MonitorManager.get(), 'monitors-changed', this._toggle.bind(this) ], [ this._settings, 'changed::multi-monitor', this._toggle.bind(this) ], [ this._settings, 'changed::preferred-monitor', this._toggle.bind(this) ], [ this._settings, 'changed::dock-position', this._toggle.bind(this) ], [ this._settings, 'changed::extend-height', this._adjustPanelCorners.bind(this) ], [ this._settings, 'changed::dock-fixed', this._adjustPanelCorners.bind(this) ], [ this._settings, 'changed::show-trash', () => this._ensureFileManagerClient() ], [ this._settings, 'changed::show-mounts', () => this._ensureFileManagerClient() ], ); } _createDocks() { // If there are no monitors (headless configurations, but it can also happen temporary while disconnecting // and reconnecting monitors), just do nothing. When a monitor will be connected we we'll be notified and // and thus create the docks. This prevents pointing trying to access monitors throughout the code, were we // are assuming that at least the primary monitor is present. if (Main.layoutManager.monitors.length <= 0) { return; } this._preferredMonitorIndex = this._settings.get_int('preferred-monitor'); // In case of multi-monitor, we consider the dock on the primary monitor to be the preferred (main) one // regardless of the settings // The dock goes on the primary monitor also if the settings are incosistent (e.g. desired monitor not connected). if (this._settings.get_boolean('multi-monitor') || this._preferredMonitorIndex < 0 || this._preferredMonitorIndex > Main.layoutManager.monitors.length - 1 ) { this._preferredMonitorIndex = Main.layoutManager.primaryIndex; } else { // Gdk and shell monitors numbering differ at least under wayland: // While the primary monitor appears to be always index 0 in Gdk, // the shell can assign a different number (Main.layoutManager.primaryMonitor) // This ensure the indexing in the settings (Gdk) and in the shell are matched, // i.e. that we start counting from the primaryMonitorIndex this._preferredMonitorIndex = (Main.layoutManager.primaryIndex + this._preferredMonitorIndex) % Main.layoutManager.monitors.length ; } // First we create the main Dock, to get the extra features to bind to this one let dock = new DockedDash(this._remoteModel, this._preferredMonitorIndex); this._allDocks.push(dock); // connect app icon into the view selector dock.dash.showAppsButton.connect('notify::checked', this._onShowAppsButtonToggled.bind(this)); // Make the necessary changes to Main.overview._dash this._prepareMainDash(); // Adjust corners if necessary this._adjustPanelCorners(); if (this._settings.get_boolean('multi-monitor')) { let nMon = Main.layoutManager.monitors.length; for (let iMon = 0; iMon < nMon; iMon++) { if (iMon == this._preferredMonitorIndex) continue; let dock = new DockedDash(this._remoteModel, iMon); this._allDocks.push(dock); // connect app icon into the view selector dock.dash.showAppsButton.connect('notify::checked', this._onShowAppsButtonToggled.bind(this)); } } // Load optional features. We load *after* the docks are created, since // we need to connect the signals to all dock instances. this._workspaceIsolation = new WorkspaceIsolation(this._allDocks); this._keyboardShortcuts = new KeyboardShortcuts(this._allDocks); } _prepareMainDash() { // Pretend I'm the dash: meant to make appgrd swarm animation come from the // right position of the appShowButton. Main.overview._dash = this._allDocks[0].dash; // set stored icon size to the new dash Main.overview.dashIconSize = this._allDocks[0].dash.iconSize; // Hide usual Dash Main.overview._controls.dash.actor.hide(); // Also set dash width to 1, so it's almost not taken into account by code // calculaing the reserved space in the overview. The reason to keep it at 1 is // to allow its visibility change to trigger an allocaion of the appGrid which // in turn is triggergin the appsIcon spring animation, required when no other // actors has this effect, i.e in horizontal mode and without the workspaceThumnails // 1 static workspace only) Main.overview._controls.dash.actor.set_width(1); } _deleteDocks() { // Remove extra features this._workspaceIsolation.destroy(); this._keyboardShortcuts.destroy(); // Delete all docks let nDocks = this._allDocks.length; for (let i = nDocks-1; i >= 0; i--) { this._allDocks[i].destroy(); this._allDocks.pop(); } } _restoreDash() { Main.overview._controls.dash.actor.show(); Main.overview._controls.dash.actor.set_width(-1); //reset default dash size // This force the recalculation of the icon size Main.overview._controls.dash._maxHeight = -1; // reset stored icon size to the default dash Main.overview.dashIconSize = Main.overview._controls.dash.iconSize; Main.overview._dash = this._oldDash; } _onShowAppsButtonToggled(button) { // Sync the status of the default appButtons. Only if the two statuses are // different, that means the user interacted with the extension provided // application button, cutomize the behaviour. Otherwise the shell has changed the // status (due to the _syncShowAppsButtonToggled function below) and it // has already performed the desired action. let animate = this._settings.get_boolean('animate-show-apps'); let selector = Main.overview.viewSelector; if (selector._showAppsButton.checked !== button.checked) { // find visible view let visibleView; Main.overview.viewSelector.appDisplay._views.every(function(v, index) { if (v.view.actor.visible) { visibleView = index; return false; } else return true; }); if (button.checked) { // force spring animation triggering.By default the animation only // runs if we are already inside the overview. if (!Main.overview._shown) { this._forcedOverview = true; let view = Main.overview.viewSelector.appDisplay._views[visibleView].view; let grid = view._grid; if (animate) { // Animate in the the appview, hide the appGrid to avoiud flashing // Go to the appView before entering the overview, skipping the workspaces. // Do this manually avoiding opacity in transitions so that the setting of the opacity // to 0 doesn't get overwritten. Main.overview.viewSelector._activePage.opacity = 0; Main.overview.viewSelector._activePage.hide(); Main.overview.viewSelector._activePage = Main.overview.viewSelector._appsPage; Main.overview.viewSelector._activePage.show(); grid.opacity = 0; // The animation has to be trigered manually because the AppDisplay.animate // method is waiting for an allocation not happening, as we skip the workspace view // and the appgrid could already be allocated from previous shown. // It has to be triggered after the overview is shown as wrong coordinates are obtained // otherwise. let overviewShownId = Main.overview.connect('shown', () => { Main.overview.disconnect(overviewShownId); Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { grid.opacity = 255; grid.animateSpring(IconGrid.AnimationDirection.IN, this._allDocks[0].dash.showAppsButton); }); }); } else { Main.overview.viewSelector._activePage = Main.overview.viewSelector._appsPage; Main.overview.viewSelector._activePage.show(); grid.opacity = 255; } } // Finally show the overview selector._showAppsButton.checked = true; Main.overview.show(); } else { if (this._forcedOverview) { // force exiting overview if needed if (animate) { // Manually trigger springout animation without activating the // workspaceView to avoid the zoomout animation. Hide the appPage // onComplete to avoid ugly flashing of original icons. let view = Main.overview.viewSelector.appDisplay._views[visibleView].view; view.animate(IconGrid.AnimationDirection.OUT, () => { Main.overview.viewSelector._appsPage.hide(); Main.overview.hide(); selector._showAppsButton.checked = false; this._forcedOverview = false; }); } else { Main.overview.hide(); this._forcedOverview = false; } } else { selector._showAppsButton.checked = false; this._forcedOverview = false; } } } // whenever the button is unactivated even if not by the user still reset the // forcedOverview flag if (button.checked == false) this._forcedOverview = false; } destroy() { this._signalsHandler.destroy(); this._deleteDocks(); this._revertPanelCorners(); this._restoreDash(); if (this._fm1Client) { this._fm1Client.destroy(); this._fm1Client = null; } this._remoteModel.destroy(); this._settings.run_dispose(); this._settings = null; Me.imports.extension.dockManager = null; } /** * Adjust Panel corners */ _adjustPanelCorners() { let position = Utils.getPosition(); let isHorizontal = ((position == St.Side.TOP) || (position == St.Side.BOTTOM)); let extendHeight = this._settings.get_boolean('extend-height'); let fixedIsEnabled = this._settings.get_boolean('dock-fixed'); let dockOnPrimary = this._settings.get_boolean('multi-monitor') || this._preferredMonitorIndex == Main.layoutManager.primaryIndex; if (!isHorizontal && dockOnPrimary && extendHeight && fixedIsEnabled) { Main.panel._rightCorner.actor.hide(); Main.panel._leftCorner.actor.hide(); } else this._revertPanelCorners(); } _revertPanelCorners() { Main.panel._leftCorner.actor.show(); Main.panel._rightCorner.actor.show(); } }; Signals.addSignalMethods(DockManager.prototype);