2025-02-09 23:16:18 +01:00
/ * *
* V - Shell ( Vertical Workspaces )
* dash . js
*
* @ author GdH < G - dH @ github . com >
2025-02-09 23:18:13 +01:00
* @ copyright 2022 - 2024
2025-02-09 23:16:18 +01:00
* @ license GPL - 3.0
2025-02-09 23:18:13 +01:00
* /
2025-02-09 23:16:18 +01:00
'use strict' ;
import Clutter from 'gi://Clutter' ;
import GLib from 'gi://GLib' ;
import Meta from 'gi://Meta' ;
import Shell from 'gi://Shell' ;
import St from 'gi://St' ;
import * as Main from 'resource:///org/gnome/shell/ui/main.js' ;
import * as Dash from 'resource:///org/gnome/shell/ui/dash.js' ;
import * as AppDisplay from 'resource:///org/gnome/shell/ui/appDisplay.js' ;
import * as AppFavorites from 'resource:///org/gnome/shell/ui/appFavorites.js' ;
import * as AppMenu from 'resource:///org/gnome/shell/ui/appMenu.js' ;
import * as BoxPointer from 'resource:///org/gnome/shell/ui/boxpointer.js' ;
import * as DND from 'resource:///org/gnome/shell/ui/dnd.js' ;
import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js' ;
let Me ;
let opt ;
// gettext
let _ ;
let _moduleEnabled ;
let _timeouts ;
// added values to achieve a better ability to scale down according to available space
export const BaseIconSizes = [ 16 , 24 , 32 , 40 , 44 , 48 , 56 , 64 , 72 , 80 , 96 , 112 , 128 ] ;
const DASH _ITEM _LABEL _SHOW _TIME = 150 ;
2025-02-09 23:18:13 +01:00
const shellVersion46 = ! Clutter . Container ; // Container has been removed in 46
2025-02-09 23:16:18 +01:00
export const DashModule = class {
constructor ( me ) {
Me = me ;
opt = Me . opt ;
_ = Me . gettext ;
this . _firstActivation = true ;
this . moduleEnabled = false ;
this . _overrides = null ;
this . _horizontalWorkId = null ;
this . _verticalWorkId = null ;
this . _showAppsIconBtnPressId = 0 ;
}
cleanGlobals ( ) {
Me = null ;
opt = null ;
_ = null ;
}
update ( reset ) {
this . _removeTimeouts ( ) ;
this . moduleEnabled = opt . get ( 'dashModule' ) ;
const conflict = ! ! ( Me . Util . getEnabledExtensions ( 'dash-to-dock' ) . length ||
2025-02-09 23:20:06 +01:00
Me . Util . getEnabledExtensions ( 'dash2dock' ) . length ||
2025-02-09 23:16:18 +01:00
Me . Util . getEnabledExtensions ( 'ubuntu-dock' ) . length ||
Me . Util . getEnabledExtensions ( 'dash-to-panel' ) . length ) ;
if ( conflict && ! reset )
console . warn ( ` [ ${ Me . metadata . name } ] Warning: "Dash" module disabled due to potential conflict with another extension ` ) ;
reset = reset || ! this . moduleEnabled || conflict ;
this . _conflict = conflict ;
// don't touch the original code if module disabled
if ( reset && ! this . _firstActivation ) {
this . _disableModule ( ) ;
} else if ( ! reset ) {
this . _firstActivation = false ;
this . _activateModule ( ) ;
}
if ( reset && this . _firstActivation )
console . debug ( ' DashModule - Keeping untouched' ) ;
}
updateStyle ( dash ) {
if ( opt . DASH _BG _LIGHT )
dash . _background . add _style _class _name ( 'dash-background-light' ) ;
else
dash . _background . remove _style _class _name ( 'dash-background-light' ) ;
dash . _background . opacity = opt . DASH _BG _OPACITY ;
let radius = opt . DASH _BG _RADIUS ;
if ( radius ) {
let style ;
switch ( opt . DASH _POSITION ) {
case 1 :
style = opt . DASH _BG _GS3 _STYLE ? ` border-radius: ${ radius } px 0 0 ${ radius } px; ` : ` border-radius: ${ radius } px; ` ;
break ;
case 3 :
style = opt . DASH _BG _GS3 _STYLE ? ` border-radius: 0 ${ radius } px ${ radius } px 0; ` : ` border-radius: ${ radius } px; ` ;
break ;
default :
style = ` border-radius: ${ radius } px; ` ;
}
dash . _background . set _style ( style ) ;
} else {
dash . _background . set _style ( '' ) ;
}
}
_activateModule ( ) {
_moduleEnabled = true ;
_timeouts = { } ;
const dash = Main . overview . _overview . _controls . layoutManager . _dash ;
if ( ! this . _originalWorkId )
this . _originalWorkId = dash . _workId ;
if ( ! this . _overrides )
this . _overrides = new Me . Util . Overrides ( ) ;
this . _resetStyle ( dash ) ;
this . updateStyle ( dash ) ;
this . _overrides . addOverride ( 'DashItemContainer' , Dash . DashItemContainer . prototype , DashItemContainerCommon ) ;
this . _overrides . addOverride ( 'DashCommon' , Dash . Dash . prototype , DashCommon ) ;
this . _overrides . addOverride ( 'AppIcon' , AppDisplay . AppIcon . prototype , AppIconCommon ) ;
this . _overrides . addOverride ( 'DashIcon' , Dash . DashIcon . prototype , DashIconCommon ) ;
this . _overrides . addOverride ( 'AppMenu' , AppMenu . AppMenu . prototype , AppMenuCommon ) ;
2025-02-09 23:18:13 +01:00
if ( shellVersion46 )
dash . add _style _class _name ( 'dash-46' ) ;
2025-02-09 23:16:18 +01:00
if ( opt . DASH _VERTICAL ) {
// this._overrides.addOverride('Dash', Dash.Dash.prototype, DashVerticalOverride);
2025-02-09 23:18:13 +01:00
dash . add _style _class _name ( shellVersion46
? 'vertical-46'
: 'vertical'
) ;
2025-02-09 23:20:06 +01:00
2025-02-09 23:16:18 +01:00
this . _setOrientation ( Clutter . Orientation . VERTICAL ) ;
} else {
this . _setOrientation ( Clutter . Orientation . HORIZONTAL ) ;
}
2025-02-09 23:18:13 +01:00
if ( opt . DASH _VERTICAL && opt . DASH _BG _GS3 _STYLE ) {
if ( opt . DASH _LEFT ) {
dash . add _style _class _name ( shellVersion46
? 'vertical-46-gs3-left'
: 'vertical-gs3-left' ) ;
} else if ( opt . DASH _RIGHT ) {
dash . add _style _class _name ( shellVersion46
? 'vertical-46-gs3-right'
: 'vertical-gs3-right' ) ;
}
} else {
dash . remove _style _class _name ( 'vertical-gs3-left' ) ;
dash . remove _style _class _name ( 'vertical-gs3-right' ) ;
dash . remove _style _class _name ( 'vertical-46-gs3-left' ) ;
dash . remove _style _class _name ( 'vertical-46-gs3-right' ) ;
}
2025-02-09 23:16:18 +01:00
if ( ! this . _customWorkId )
this . _customWorkId = Main . initializeDeferredWork ( dash . _box , dash . _redisplay . bind ( dash ) ) ;
dash . _workId = this . _customWorkId ;
this . _moveDashAppGridIcon ( ) ;
this . _connectShowAppsIcon ( ) ;
dash . visible = opt . DASH _VISIBLE ;
2025-02-09 23:18:13 +01:00
// dash._background.add_style_class_name('dash-background-reduced');
2025-02-09 23:16:18 +01:00
dash . _queueRedisplay ( ) ;
if ( opt . DASH _ISOLATE _WS && ! this . _wmSwitchWsConId ) {
this . _wmSwitchWsConId = global . windowManager . connect ( 'switch-workspace' , ( ) => dash . _queueRedisplay ( ) ) ;
this . _newWindowConId = global . display . connect _after ( 'window-created' , ( ) => dash . _queueRedisplay ( ) ) ;
}
2025-02-09 23:18:13 +01:00
2025-02-09 23:16:18 +01:00
console . debug ( ' DashModule - Activated' ) ;
}
_disableModule ( ) {
const dash = Main . overview . _overview . _controls . layoutManager . _dash ;
if ( this . _overrides )
this . _overrides . removeAll ( ) ;
this . _overrides = null ;
dash . _workId = this . _originalWorkId ;
if ( this . _wmSwitchWsConId ) {
global . windowManager . disconnect ( this . _wmSwitchWsConId ) ;
this . _wmSwitchWsConId = 0 ;
}
if ( this . _newWindowConId ) {
global . windowManager . disconnect ( this . _newWindowConId ) ;
this . _newWindowConId = 0 ;
}
const reset = true ;
this . _setOrientation ( Clutter . Orientation . HORIZONTAL ) ;
this . _moveDashAppGridIcon ( reset ) ;
this . _connectShowAppsIcon ( reset ) ;
2025-02-09 23:18:13 +01:00
2025-02-09 23:16:18 +01:00
this . _resetStyle ( dash ) ;
dash . visible = ! this . _conflict ;
dash . _background . opacity = 255 ;
_moduleEnabled = false ;
console . debug ( ' DashModule - Disabled' ) ;
}
_resetStyle ( dash ) {
2025-02-09 23:20:06 +01:00
dash . remove _style _class _name ( 'dash-46' ) ;
2025-02-09 23:16:18 +01:00
dash . remove _style _class _name ( 'vertical' ) ;
2025-02-09 23:18:13 +01:00
dash . remove _style _class _name ( 'vertical-46' ) ;
2025-02-09 23:16:18 +01:00
dash . remove _style _class _name ( 'vertical-gs3-left' ) ;
dash . remove _style _class _name ( 'vertical-gs3-right' ) ;
2025-02-09 23:18:13 +01:00
dash . remove _style _class _name ( 'vertical-46-gs3-left' ) ;
dash . remove _style _class _name ( 'vertical-46-gs3-right' ) ;
2025-02-09 23:16:18 +01:00
dash . remove _style _class _name ( 'vertical-left' ) ;
dash . remove _style _class _name ( 'vertical-right' ) ;
dash . _background . remove _style _class _name ( 'dash-background-light' ) ;
dash . _background . remove _style _class _name ( 'dash-background-reduced' ) ;
dash . _background . set _style ( '' ) ;
}
_removeTimeouts ( ) {
if ( _timeouts ) {
Object . values ( _timeouts ) . forEach ( t => {
if ( t )
GLib . source _remove ( t ) ;
} ) ;
_timeouts = null ;
}
}
_setOrientation ( orientation , dash ) {
dash = dash ? ? Main . overview . _overview . _controls . layoutManager . _dash ;
dash . _box . layout _manager . orientation = orientation ;
dash . _dashContainer . layout _manager . orientation = orientation ;
dash . _dashContainer . y _expand = ! orientation ;
dash . _dashContainer . x _expand = ! ! orientation ;
dash . x _align = orientation ? Clutter . ActorAlign . START : Clutter . ActorAlign . CENTER ;
dash . y _align = orientation ? Clutter . ActorAlign . CENTER : Clutter . ActorAlign . FILL ;
let sizerBox = dash . _background . get _children ( ) [ 0 ] ;
sizerBox . clear _constraints ( ) ;
sizerBox . add _constraint ( new Clutter . BindConstraint ( {
source : dash . _showAppsIcon . icon ,
coordinate : orientation ? Clutter . BindCoordinate . WIDTH : Clutter . BindCoordinate . HEIGHT ,
} ) ) ;
sizerBox . add _constraint ( new Clutter . BindConstraint ( {
source : dash . _dashContainer ,
coordinate : orientation ? Clutter . BindCoordinate . HEIGHT : Clutter . BindCoordinate . WIDTH ,
} ) ) ;
dash . _box . remove _all _children ( ) ;
dash . _separator = null ;
dash . _queueRedisplay ( ) ;
dash . _adjustIconSize ( ) ;
}
_moveDashAppGridIcon ( reset = false ) {
// move dash app grid icon to the front
const dash = Main . overview . _overview . _controls . layoutManager . _dash ;
const appIconPosition = opt . get ( 'showAppsIconPosition' ) ;
dash . _showAppsIcon . remove _style _class _name ( 'show-apps-icon-vertical-hide' ) ;
dash . _showAppsIcon . remove _style _class _name ( 'show-apps-icon-horizontal-hide' ) ;
dash . _showAppsIcon . opacity = 255 ;
if ( ! reset && appIconPosition === 0 ) // 0 - start
dash . _dashContainer . set _child _at _index ( dash . _showAppsIcon , 0 ) ;
if ( reset || appIconPosition === 1 ) { // 1 - end
const index = dash . _dashContainer . get _children ( ) . length - 1 ;
dash . _dashContainer . set _child _at _index ( dash . _showAppsIcon , index ) ;
}
if ( ! reset && appIconPosition === 2 ) { // 2 - hide
const style = opt . DASH _VERTICAL ? 'show-apps-icon-vertical-hide' : 'show-apps-icon-horizontal-hide' ;
dash . _showAppsIcon . add _style _class _name ( style ) ;
// for some reason even if the icon height in vertical mode should be set to 0 by the style, it stays visible in full size returning height 1px
dash . _showAppsIcon . opacity = 0 ;
}
}
_connectShowAppsIcon ( reset = false , dash ) {
dash = dash ? ? Main . overview . _overview . _controls . layoutManager . _dash ;
if ( ! reset ) {
if ( this . _showAppsIconBtnPressId || Me . Util . dashIsDashToDock ( ) ) {
// button is already connected || dash is Dash to Dock
return ;
}
dash . _showAppsIcon . reactive = true ;
this . _showAppsIconBtnPressId = dash . _showAppsIcon . connect ( 'button-press-event' , ( actor , event ) => {
const button = event . get _button ( ) ;
if ( button === Clutter . BUTTON _MIDDLE )
Me . Util . openPreferences ( ) ;
else if ( button === Clutter . BUTTON _SECONDARY )
Me . Util . activateSearchProvider ( Me . WSP _PREFIX ) ;
else
return Clutter . EVENT _PROPAGATE ;
return Clutter . EVENT _STOP ;
} ) ;
} else if ( this . _showAppsIconBtnPressId ) {
dash . _showAppsIcon . disconnect ( this . _showAppsIconBtnPressId ) ;
this . _showAppsIconBtnPressId = 0 ;
dash . _showAppsIcon . reactive = false ;
}
}
} ;
function getAppFromSource ( source ) {
if ( source instanceof AppDisplay . AppIcon )
return source . app ;
else
return null ;
}
const DashItemContainerCommon = {
// move labels according dash position
showLabel ( ) {
if ( ! this . _labelText )
return ;
const windows = this . child . app ? . get _windows ( ) ;
const recentWindowTitle = windows && windows . length ? windows [ 0 ] . get _title ( ) : '' ;
const windowCount = this . child . app ? . get _windows ( ) . length ;
let labelSuffix = '' ;
if ( windowCount > 1 )
labelSuffix = ` ( ${ windowCount } ) ` ;
if ( recentWindowTitle && recentWindowTitle !== this . _labelText )
labelSuffix += ` \n ${ recentWindowTitle } ` ;
this . label . set _text ( this . _labelText + labelSuffix ) ;
this . label . opacity = 0 ;
this . label . show ( ) ;
let [ stageX , stageY ] = this . get _transformed _position ( ) ;
const itemWidth = this . allocation . get _width ( ) ;
const itemHeight = this . allocation . get _height ( ) ;
const labelWidth = this . label . get _width ( ) ;
const labelHeight = this . label . get _height ( ) ;
let xOffset = Math . floor ( ( itemWidth - labelWidth ) / 2 ) ;
let x = Math . clamp ( stageX + xOffset , 0 , global . stage . width - labelWidth ) ;
const primaryMonitor = global . display . get _monitor _geometry ( global . display . get _primary _monitor ( ) ) ;
x = Math . clamp ( x , primaryMonitor . x , primaryMonitor . x + primaryMonitor . width - labelWidth ) ;
let node = this . label . get _theme _node ( ) ;
let y ;
if ( opt . DASH _TOP ) {
2025-02-09 23:18:13 +01:00
const yOffset = itemHeight + ( shellVersion46 ? 0 : - 3 ) ;
2025-02-09 23:16:18 +01:00
y = stageY + yOffset ;
} else if ( opt . DASH _BOTTOM ) {
const yOffset = node . get _length ( '-y-offset' ) ;
y = stageY - this . label . height - yOffset ;
} else if ( opt . DASH _RIGHT ) {
const yOffset = Math . floor ( ( itemHeight - labelHeight ) / 2 ) ;
2025-02-09 23:18:13 +01:00
xOffset = shellVersion46 ? 8 : 4 ;
2025-02-09 23:16:18 +01:00
x = stageX - xOffset - this . label . width ;
y = Math . clamp ( stageY + yOffset , 0 , global . stage . height - labelHeight ) ;
} else if ( opt . DASH _LEFT ) {
const yOffset = Math . floor ( ( itemHeight - labelHeight ) / 2 ) ;
2025-02-09 23:18:13 +01:00
xOffset = shellVersion46 ? 8 : 4 ;
2025-02-09 23:16:18 +01:00
x = stageX + this . width + xOffset ;
y = Math . clamp ( stageY + yOffset , 0 , global . stage . height - labelHeight ) ;
}
this . label . set _position ( x , y ) ;
this . label . ease ( {
opacity : 255 ,
duration : DASH _ITEM _LABEL _SHOW _TIME ,
mode : Clutter . AnimationMode . EASE _OUT _QUAD ,
} ) ;
this . label . set _position ( x , y ) ;
this . label . ease ( {
opacity : 255 ,
duration : DASH _ITEM _LABEL _SHOW _TIME ,
mode : Clutter . AnimationMode . EASE _OUT _QUAD ,
} ) ;
} ,
} ;
const DashCommon = {
_redisplay ( ) {
// After disabling V-Shell queueRedisplay() may call this function
// In that case redirect the call to the current _redisplay()
if ( ! _moduleEnabled ) {
this . _redisplay ( ) ;
return ;
}
let favorites = AppFavorites . getAppFavorites ( ) . getFavoriteMap ( ) ;
let running = this . _appSystem . get _running ( ) ;
if ( opt . DASH _ISOLATE _WS ) {
const currentWs = global . workspace _manager . get _active _workspace ( ) ;
running = running . filter ( app => {
return app . get _windows ( ) . filter ( w => w . get _workspace ( ) === currentWs ) . length ;
} ) ;
this . _box . get _children ( ) . forEach ( a => a . child ? . _updateRunningStyle ( ) ) ;
}
let children = this . _box . get _children ( ) . filter ( actor => {
return actor . child &&
actor . child . _delegate &&
actor . child . _delegate . app ;
} ) ;
// Apps currently in the dash
let oldApps = children . map ( actor => actor . child . _delegate . app ) ;
// Apps supposed to be in the dash
let newApps = [ ] ;
for ( let id in favorites )
newApps . push ( favorites [ id ] ) ;
for ( let i = 0 ; i < running . length ; i ++ ) {
let app = running [ i ] ;
if ( app . get _id ( ) in favorites )
continue ;
newApps . push ( app ) ;
}
// Figure out the actual changes to the list of items; we iterate
// over both the list of items currently in the dash and the list
// of items expected there, and collect additions and removals.
// Moves are both an addition and a removal, where the order of
// the operations depends on whether we encounter the position
// where the item has been added first or the one from where it
// was removed.
// There is an assumption that only one item is moved at a given
// time; when moving several items at once, everything will still
// end up at the right position, but there might be additional
// additions/removals (e.g. it might remove all the launchers
// and add them back in the new order even if a smaller set of
// additions and removals is possible).
// If above assumptions turns out to be a problem, we might need
// to use a more sophisticated algorithm, e.g. Longest Common
// Subsequence as used by diff.
let addedItems = [ ] ;
let removedActors = [ ] ;
let newIndex = 0 ;
let oldIndex = 0 ;
while ( newIndex < newApps . length || oldIndex < oldApps . length ) {
let oldApp = oldApps . length > oldIndex ? oldApps [ oldIndex ] : null ;
let newApp = newApps . length > newIndex ? newApps [ newIndex ] : null ;
// No change at oldIndex/newIndex
if ( oldApp === newApp ) {
oldIndex ++ ;
newIndex ++ ;
continue ;
}
// App removed at oldIndex
if ( oldApp && ! newApps . includes ( oldApp ) ) {
removedActors . push ( children [ oldIndex ] ) ;
oldIndex ++ ;
continue ;
}
// App added at newIndex
if ( newApp && ! oldApps . includes ( newApp ) ) {
addedItems . push ( {
app : newApp ,
item : this . _createAppItem ( newApp ) ,
pos : newIndex ,
} ) ;
newIndex ++ ;
continue ;
}
// App moved
let nextApp = newApps . length > newIndex + 1
? newApps [ newIndex + 1 ] : null ;
let insertHere = nextApp && nextApp === oldApp ;
let alreadyRemoved = removedActors . reduce ( ( result , actor ) => {
let removedApp = actor . child . _delegate . app ;
return result || removedApp === newApp ;
} , false ) ;
if ( insertHere || alreadyRemoved ) {
let newItem = this . _createAppItem ( newApp ) ;
addedItems . push ( {
app : newApp ,
item : newItem ,
pos : newIndex + removedActors . length ,
} ) ;
newIndex ++ ;
} else {
removedActors . push ( children [ oldIndex ] ) ;
oldIndex ++ ;
}
}
for ( let i = 0 ; i < addedItems . length ; i ++ ) {
this . _box . insert _child _at _index (
addedItems [ i ] . item ,
addedItems [ i ] . pos ) ;
}
for ( let i = 0 ; i < removedActors . length ; i ++ ) {
let item = removedActors [ i ] ;
// Don't animate item removal when the overview is transitioning
// or hidden
if ( Main . overview . visible && ! Main . overview . animationInProgress )
item . animateOutAndDestroy ( ) ;
else
item . destroy ( ) ;
}
this . _adjustIconSize ( ) ;
// Skip animations on first run when adding the initial set
// of items, to avoid all items zooming in at once
let animate = this . _shownInitially && Main . overview . visible &&
! Main . overview . animationInProgress ;
if ( ! this . _shownInitially )
this . _shownInitially = true ;
for ( let i = 0 ; i < addedItems . length ; i ++ )
addedItems [ i ] . item . show ( animate ) ;
// Update separator
const nFavorites = Object . keys ( favorites ) . length ;
const nIcons = children . length + addedItems . length - removedActors . length ;
if ( nFavorites > 0 && nFavorites < nIcons ) {
// destroy the horizontal separator if it exists.
// this is incredibly janky, but I can't think of a better way atm.
if ( this . _separator && this . _separator . height !== 1 ) {
this . _separator . destroy ( ) ;
this . _separator = null ;
}
if ( ! this . _separator ) {
this . _separator = new St . Widget ( {
style _class : 'dash-separator' ,
x _align : Clutter . ActorAlign . CENTER ,
y _align : Clutter . ActorAlign . CENTER ,
width : opt . DASH _VERTICAL ? this . iconSize : 1 ,
height : opt . DASH _VERTICAL ? 1 : this . iconSize ,
} ) ;
this . _box . add _child ( this . _separator ) ;
}
// FIXME: separator placement is broken (also in original dash)
let pos = nFavorites + this . _animatingPlaceholdersCount ;
if ( this . _dragPlaceholder )
pos ++ ;
this . _box . set _child _at _index ( this . _separator , pos ) ;
} else if ( this . _separator ) {
this . _separator . destroy ( ) ;
this . _separator = null ;
}
// Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=692744
// Without it, StBoxLayout may use a stale size cache
this . _box . queue _relayout ( ) ;
} ,
_createAppItem ( app ) {
let appIcon = new Dash . DashIcon ( app ) ;
let indicator = appIcon . _dot ;
if ( opt . DASH _VERTICAL ) {
indicator . x _align = opt . DASH _LEFT ? Clutter . ActorAlign . START : Clutter . ActorAlign . END ;
indicator . y _align = Clutter . ActorAlign . CENTER ;
} else {
indicator . x _align = Clutter . ActorAlign . CENTER ;
indicator . y _align = Clutter . ActorAlign . END ;
}
appIcon . connect ( 'menu-state-changed' ,
( o , opened ) => {
this . _itemMenuStateChanged ( item , opened ) ;
} ) ;
let item = new Dash . DashItemContainer ( ) ;
item . setChild ( appIcon ) ;
// Override default AppIcon label_actor, now the
// accessible_name is set at DashItemContainer.setLabelText
appIcon . label _actor = null ;
item . setLabelText ( app . get _name ( ) ) ;
appIcon . icon . setIconSize ( this . iconSize ) ;
this . _hookUpLabel ( item , appIcon ) ;
return item ;
} ,
// use custom BaseIconSizes and add support for custom icons
_adjustIconSize ( ) {
// if a user launches multiple apps at once, this function may be called again before the previous call has finished
// as a result, new icons will not reach their full size, or will be missing, if adding a new icon and changing the dash size due to lack of space at the same time
if ( this . _adjustingInProgress )
return ;
// For the icon size, we only consider children which are "proper"
// icons (i.e. ignoring drag placeholders) and which are not
// animating out (which means they will be destroyed at the end of
// the animation)
let iconChildren = this . _box . get _children ( ) . filter ( actor => {
return actor . child &&
actor . child . _delegate &&
actor . child . _delegate . icon &&
! actor . animatingOut ;
} ) ;
// add new custom icons to the list
if ( this . _showAppsIcon . visible )
iconChildren . push ( this . _showAppsIcon ) ;
2025-02-09 23:18:13 +01:00
// showWindowsIcon and extensionsIcon can be provided by the WSP and ESP extensions
2025-02-09 23:16:18 +01:00
if ( this . _showWindowsIcon )
iconChildren . push ( this . _showWindowsIcon ) ;
if ( this . _extensionsIcon )
iconChildren . push ( this . _extensionsIcon ) ;
2025-02-09 23:18:13 +01:00
2025-02-09 23:16:18 +01:00
if ( ! iconChildren . length )
return ;
if ( this . _maxWidth === - 1 || this . _maxHeight === - 1 )
return ;
const dashHorizontal = ! opt . DASH _VERTICAL ;
const themeNode = this . get _theme _node ( ) ;
const maxAllocation = new Clutter . ActorBox ( {
x1 : 0 ,
y1 : 0 ,
x2 : dashHorizontal ? this . _maxWidth : 42 , // not whatever
y2 : dashHorizontal ? 42 : this . _maxHeight ,
} ) ;
let maxContent = themeNode . get _content _box ( maxAllocation ) ;
let spacing = themeNode . get _length ( 'spacing' ) ;
let firstButton = iconChildren [ 0 ] . child ;
let firstIcon = firstButton . _delegate . icon ;
if ( ! firstIcon . icon )
return ;
// Enforce valid spacings during the size request
firstIcon . icon . ensure _style ( ) ;
const [ , , iconWidth , iconHeight ] = firstIcon . icon . get _preferred _size ( ) ;
const [ , , buttonWidth , buttonHeight ] = firstButton . get _preferred _size ( ) ;
let scaleFactor = St . ThemeContext . get _for _stage ( global . stage ) . scale _factor ;
2025-02-09 23:20:06 +01:00
let maxIconSize = opt . MAX _ICON _SIZE ;
if ( ! maxIconSize ) {
maxIconSize = Me . Util . monitorHasLowResolution ( )
? 48
: 64 ;
}
let availWidth , availHeight ;
2025-02-09 23:16:18 +01:00
if ( dashHorizontal ) {
availWidth = maxContent . x2 - maxContent . x1 ;
// Subtract icon padding and box spacing from the available width
availWidth -= iconChildren . length * ( buttonWidth - iconWidth ) +
( iconChildren . length - 1 ) * spacing +
2 * this . _background . get _theme _node ( ) . get _horizontal _padding ( ) ;
availHeight = this . _maxHeight ;
availHeight -= this . margin _top + this . margin _bottom ;
availHeight -= this . _background . get _theme _node ( ) . get _vertical _padding ( ) ;
availHeight -= themeNode . get _vertical _padding ( ) ;
availHeight -= buttonHeight - iconHeight ;
2025-02-09 23:20:06 +01:00
maxIconSize = Math . min ( availWidth / iconChildren . length , availHeight , maxIconSize * scaleFactor ) ;
2025-02-09 23:16:18 +01:00
} else {
availWidth = this . _maxWidth ;
availWidth -= this . _background . get _theme _node ( ) . get _horizontal _padding ( ) ;
availWidth -= themeNode . get _horizontal _padding ( ) ;
availWidth -= buttonWidth - iconWidth ;
availHeight = maxContent . y2 - maxContent . y1 ;
availHeight -= iconChildren . length * ( buttonHeight - iconHeight ) +
( iconChildren . length - 1 ) * spacing +
2 * this . _background . get _theme _node ( ) . get _vertical _padding ( ) ;
2025-02-09 23:20:06 +01:00
maxIconSize = Math . min ( availWidth , availHeight / iconChildren . length , maxIconSize * scaleFactor ) ;
2025-02-09 23:16:18 +01:00
}
let iconSizes = BaseIconSizes . map ( s => s * scaleFactor ) ;
let newIconSize = BaseIconSizes [ 0 ] ;
for ( let i = 0 ; i < iconSizes . length ; i ++ ) {
if ( iconSizes [ i ] <= maxIconSize )
newIconSize = BaseIconSizes [ i ] ;
}
if ( newIconSize === this . iconSize )
return ;
// set the in-progress state here after all the possible cancels
this . _adjustingInProgress = true ;
let oldIconSize = this . iconSize ;
this . iconSize = newIconSize ;
this . emit ( 'icon-size-changed' ) ;
let scale = oldIconSize / newIconSize ;
for ( let i = 0 ; i < iconChildren . length ; i ++ ) {
let icon = iconChildren [ i ] . child . _delegate . icon ;
// Set the new size immediately, to keep the icons' sizes
// in sync with this.iconSize
icon . setIconSize ( this . iconSize ) ;
// Don't animate the icon size change when the overview
// is transitioning, not visible or when initially filling
// the dash
if ( ! Main . overview . visible || Main . overview . animationInProgress ||
! this . _shownInitially )
continue ;
let [ targetWidth , targetHeight ] = icon . icon . get _size ( ) ;
// Scale the icon's texture to the previous size and
// tween to the new size
icon . icon . set _size ( icon . icon . width * scale ,
icon . icon . height * scale ) ;
icon . icon . ease ( {
width : targetWidth ,
height : targetHeight ,
duration : Dash . DASH _ANIMATION _TIME ,
mode : Clutter . AnimationMode . EASE _OUT _QUAD ,
} ) ;
}
if ( this . _separator ) {
this . _separator . ease ( {
width : dashHorizontal ? 1 : this . iconSize ,
height : dashHorizontal ? this . iconSize : 1 ,
duration : Dash . DASH _ANIMATION _TIME ,
mode : Clutter . AnimationMode . EASE _OUT _QUAD ,
} ) ;
}
this . _adjustingInProgress = false ;
} ,
handleDragOver ( source , actor , x , y , _time ) {
let app = getAppFromSource ( source ) ;
// Don't allow favoriting of transient apps
if ( app === null || app . is _window _backed ( ) )
return DND . DragMotionResult . NO _DROP ;
if ( ! global . settings . is _writable ( 'favorite-apps' ) )
return DND . DragMotionResult . NO _DROP ;
let favorites = AppFavorites . getAppFavorites ( ) . getFavorites ( ) ;
let numFavorites = favorites . length ;
let favPos = favorites . indexOf ( app ) ;
let children = this . _box . get _children ( ) ;
let numChildren = children . length ;
let boxSize = opt . DASH _VERTICAL ? this . _box . height : this . _box . width ;
// Keep the placeholder out of the index calculation; assuming that
// the remove target has the same size as "normal" items, we don't
// need to do the same adjustment there.
if ( this . _dragPlaceholder ) {
boxSize -= opt . DASH _VERTICAL ? this . _dragPlaceholder . height : this . _dragPlaceholder . width ;
numChildren -- ;
}
// Same with the separator
if ( this . _separator ) {
boxSize -= opt . DASH _VERTICAL ? this . _separator . height : this . _separator . width ;
numChildren -- ;
}
let pos ;
if ( this . _emptyDropTarget )
pos = 0 ; // always insert at the start when dash is empty
else if ( this . text _direction === Clutter . TextDirection . RTL )
pos = numChildren - Math . floor ( ( opt . DASH _VERTICAL ? y : x ) * numChildren / boxSize ) ;
else
pos = Math . floor ( ( opt . DASH _VERTICAL ? y : x ) * numChildren / boxSize ) ;
// Put the placeholder after the last favorite if we are not
// in the favorites zone
if ( pos > numFavorites )
pos = numFavorites ;
if ( pos !== this . _dragPlaceholderPos && this . _animatingPlaceholdersCount === 0 ) {
this . _dragPlaceholderPos = pos ;
// Don't allow positioning before or after self
if ( favPos !== - 1 && ( pos === favPos || pos === favPos + 1 ) ) {
this . _clearDragPlaceholder ( ) ;
return DND . DragMotionResult . CONTINUE ;
}
// If the placeholder already exists, we just move
// it, but if we are adding it, expand its size in
// an animation
let fadeIn ;
if ( this . _dragPlaceholder ) {
this . _dragPlaceholder . destroy ( ) ;
fadeIn = false ;
} else {
fadeIn = true ;
}
// this._dragPlaceholder = new Dash.DragPlaceholderItem(); // not exported in 45
this . _dragPlaceholder = new Dash . DashItemContainer ( ) ;
this . _dragPlaceholder . setChild ( new St . Bin ( { style _class : 'placeholder' } ) ) ;
this . _dragPlaceholder . child . set _width ( this . iconSize / ( opt . DASH _VERTICAL ? 2 : 1 ) ) ;
this . _dragPlaceholder . child . set _height ( this . iconSize / ( opt . DASH _VERTICAL ? 1 : 2 ) ) ;
this . _box . insert _child _at _index (
this . _dragPlaceholder ,
this . _dragPlaceholderPos ) ;
this . _dragPlaceholder . show ( fadeIn ) ;
}
if ( ! this . _dragPlaceholder )
return DND . DragMotionResult . NO _DROP ;
let srcIsFavorite = favPos !== - 1 ;
if ( srcIsFavorite )
return DND . DragMotionResult . MOVE _DROP ;
return DND . DragMotionResult . COPY _DROP ;
} ,
} ;
2025-02-09 23:20:06 +01:00
const AppIconCommon = {
2025-02-09 23:16:18 +01:00
after _ _init ( ) {
2025-02-09 23:18:13 +01:00
if ( this . _updateRunningDotStyle )
this . _updateRunningDotStyle ( ) ;
2025-02-09 23:16:18 +01:00
} ,
_updateRunningDotStyle ( ) {
if ( opt . RUNNING _DOT _STYLE )
2025-02-09 23:20:06 +01:00
this . _dot . add _style _class _name ( 'app-grid-running-dot-custom' ) ;
2025-02-09 23:16:18 +01:00
else
2025-02-09 23:20:06 +01:00
this . _dot . remove _style _class _name ( 'app-grid-running-dot-custom' ) ;
2025-02-09 23:16:18 +01:00
} ,
activate ( button ) {
const event = Clutter . get _current _event ( ) ;
const state = event ? event . get _state ( ) : 0 ;
const isMiddleButton = button && button === Clutter . BUTTON _MIDDLE ;
const isCtrlPressed = Me . Util . isCtrlPressed ( state ) ;
const isShiftPressed = Me . Util . isShiftPressed ( state ) ;
const currentWS = global . workspace _manager . get _active _workspace ( ) ;
const appRecentWorkspace = this . _getAppRecentWorkspace ( this . app ) ;
// this feature shouldn't affect search results, dash icons don't have labels, so we use them as a condition
const showWidowsBeforeActivation = opt . DASH _CLICK _ACTION === 1 && ! this . icon . label ;
let targetWindowOnCurrentWs = false ;
if ( opt . DASH _FOLLOW _RECENT _WIN ) {
targetWindowOnCurrentWs = appRecentWorkspace === currentWS ;
} else {
this . app . get _windows ( ) . forEach (
w => {
targetWindowOnCurrentWs = targetWindowOnCurrentWs || ( w . get _workspace ( ) === currentWS ) ;
}
) ;
}
const openNewWindow = this . app . can _open _new _window ( ) &&
this . app . state === Shell . AppState . RUNNING &&
( ( ( isCtrlPressed || isMiddleButton ) && ! opt . DASH _CLICK _OPEN _NEW _WIN ) ||
( opt . DASH _CLICK _OPEN _NEW _WIN && ! this . _selectedMetaWin && ! isMiddleButton ) ||
( ( opt . DASH _CLICK _PREFER _WORKSPACE || opt . DASH _ISOLATE _WS ) && ! targetWindowOnCurrentWs ) ) ;
if ( ( this . app . state === Shell . AppState . STOPPED || openNewWindow ) && ! isShiftPressed )
this . animateLaunch ( ) ;
if ( openNewWindow ) {
this . app . open _new _window ( - 1 ) ;
// if DASH_CLICK_ACTION == "SHOW_WINS_BEFORE", the app has more than one window and has no window on the current workspace,
// don't activate the app immediately, only move the overview to the workspace with the app's recent window
} else if ( showWidowsBeforeActivation && ! isShiftPressed && this . app . get _n _windows ( ) > 1 && ! targetWindowOnCurrentWs /* && !(opt.OVERVIEW_MODE && !opt.WORKSPACE_MODE)*/ ) {
Main . wm . actionMoveWorkspace ( appRecentWorkspace ) ;
Main . overview . dash . showAppsButton . checked = false ;
return ;
} else if ( this . _selectedMetaWin ) {
this . _selectedMetaWin . activate ( global . get _current _time ( ) ) ;
} else if ( showWidowsBeforeActivation && opt . OVERVIEW _MODE2 && ! opt . WORKSPACE _MODE && ! isShiftPressed && this . app . get _n _windows ( ) > 1 ) {
// expose windows
Main . overview . _overview . _controls . _thumbnailsBox . _activateThumbnailAtPoint ( 0 , 0 , global . get _current _time ( ) , true ) ;
return ;
} else if ( ( ( opt . DASH _SHIFT _CLICK _MV && isShiftPressed ) || ( ( opt . DASH _CLICK _PREFER _WORKSPACE || opt . DASH _ISOLATE _WS ) && ! openNewWindow ) ) && this . app . get _windows ( ) . length ) {
this . _moveAppToCurrentWorkspace ( ) ;
if ( opt . DASH _ISOLATE _WS ) {
this . app . activate ( ) ;
// hide the overview after the window is re-created
GLib . idle _add ( GLib . PRIORITY _LOW , ( ) => Main . overview . hide ( ) ) ;
}
return ;
} else if ( isShiftPressed ) {
return ;
} else {
this . app . activate ( ) ;
}
Main . overview . hide ( ) ;
} ,
_moveAppToCurrentWorkspace ( ) {
this . app . get _windows ( ) . forEach ( w => w . change _workspace ( global . workspace _manager . get _active _workspace ( ) ) ) ;
} ,
popupMenu ( side = St . Side . LEFT ) {
this . setForcedHighlight ( true ) ;
this . _removeMenuTimeout ( ) ;
this . fake _release ( ) ;
if ( ! this . _getWindowsOnCurrentWs ) {
this . _getWindowsOnCurrentWs = function ( ) {
const winList = [ ] ;
this . app . get _windows ( ) . forEach ( w => {
if ( w . get _workspace ( ) === global . workspace _manager . get _active _workspace ( ) )
winList . push ( w ) ;
} ) ;
return winList ;
} ;
this . _windowsOnOtherWs = function ( ) {
return ( this . app . get _windows ( ) . length - this . _getWindowsOnCurrentWs ( ) . length ) > 0 ;
} ;
}
if ( ! this . _menu ) {
this . _menu = new AppMenu . AppMenu ( this , side , {
favoritesSection : true ,
showSingleWindows : true ,
} ) ;
this . _menu . setApp ( this . app ) ;
this . _openSigId = this . _menu . connect ( 'open-state-changed' , ( menu , isPoppedUp ) => {
if ( ! isPoppedUp )
this . _onMenuPoppedDown ( ) ;
} ) ;
// Main.overview.connectObject('hiding',
this . _hidingSigId = Main . overview . connect ( 'hiding' ,
( ) => this . _menu . close ( ) , this ) ;
2025-02-09 23:18:13 +01:00
Main . uiGroup . add _child ( this . _menu . actor ) ;
2025-02-09 23:16:18 +01:00
this . _menuManager . addMenu ( this . _menu ) ;
}
// once the menu is created, it stays unchanged and we need to modify our items based on current situation
if ( this . _addedMenuItems && this . _addedMenuItems . length )
this . _addedMenuItems . forEach ( i => i . destroy ( ) ) ;
const popupItems = [ ] ;
const separator = new PopupMenu . PopupSeparatorMenuItem ( ) ;
this . _menu . addMenuItem ( separator ) ;
if ( this . app . get _n _windows ( ) ) {
// if (/* opt.APP_MENU_FORCE_QUIT*/true) {}
popupItems . push ( [ _ ( 'Force Quit' ) , ( ) => {
this . app . get _windows ( ) [ 0 ] . kill ( ) ;
} ] ) ;
// if (opt.APP_MENU_CLOSE_WS) {}
const nWin = this . _getWindowsOnCurrentWs ( ) . length ;
if ( nWin ) {
popupItems . push ( [ _ ( ` Close ${ nWin } Windows on Current Workspace ` ) , ( ) => {
const windows = this . _getWindowsOnCurrentWs ( ) ;
let time = global . get _current _time ( ) ;
for ( let win of windows ) {
// increase time by 1 ms for each window to avoid errors from GS
win . delete ( time ++ ) ;
}
} ] ) ;
}
popupItems . push ( [ _ ( 'Move App to Current Workspace ( Shift + Click )' ) , this . _moveAppToCurrentWorkspace ] ) ;
2025-02-09 23:18:13 +01:00
// WTMB (Windows Thumbnails) extension required
if ( global . windowThumbnails ) {
popupItems . push ( [ _ ( 'Create Window Thumbnail/PiP' ) , ( ) => {
global . windowThumbnails ? . createThumbnail ( this . app . get _windows ( ) [ 0 ] ) ;
2025-02-09 23:16:18 +01:00
} ] ) ;
}
}
this . _addedMenuItems = [ ] ;
this . _addedMenuItems . push ( separator ) ;
popupItems . forEach ( i => {
let item = new PopupMenu . PopupMenuItem ( i [ 0 ] ) ;
this . _menu . addMenuItem ( item ) ;
item . connect ( 'activate' , i [ 1 ] . bind ( this ) ) ;
if ( i [ 1 ] === this . _moveAppToCurrentWorkspace && ! this . _windowsOnOtherWs ( ) )
item . setSensitive ( false ) ;
this . _addedMenuItems . push ( item ) ;
} ) ;
this . emit ( 'menu-state-changed' , true ) ;
this . _menu . open ( BoxPointer . PopupAnimation . FULL ) ;
this . _menuManager . ignoreRelease ( ) ;
this . emit ( 'sync-tooltip' ) ;
return false ;
} ,
_getWindowApp ( metaWin ) {
const tracker = Shell . WindowTracker . get _default ( ) ;
return tracker . get _window _app ( metaWin ) ;
} ,
_getAppLastUsedWindow ( app ) {
let recentWin ;
global . display . get _tab _list ( Meta . TabList . NORMAL _ALL , null ) . forEach ( metaWin => {
const winApp = this . _getWindowApp ( metaWin ) ;
if ( ! recentWin && winApp === app )
recentWin = metaWin ;
} ) ;
return recentWin ;
} ,
_getAppRecentWorkspace ( app ) {
const recentWin = this . _getAppLastUsedWindow ( app ) ;
if ( recentWin )
return recentWin . get _workspace ( ) ;
return null ;
} ,
} ;
2025-02-09 23:20:06 +01:00
const DashIconCommon = {
after _ _init ( ) {
if ( opt . DASH _ICON _SCROLL && ! Me . Util . dashNotDefault ( ) ) {
this . _scrollConId = this . connect ( 'scroll-event' , DashExtensions . onScrollEvent . bind ( this ) ) ;
this . _leaveConId = this . connect ( 'leave-event' , DashExtensions . onLeaveEvent . bind ( this ) ) ;
}
} ,
popupMenu ( ) {
const side = opt . DASH _VERTICAL ? St . Side . LEFT : St . Side . BOTTOM ;
AppIconCommon . popupMenu . bind ( this ) ( side ) ;
} ,
_updateRunningDotStyle ( ) {
if ( opt . RUNNING _DOT _STYLE )
this . _dot . add _style _class _name ( 'app-grid-running-dot-custom' ) ;
else
this . _dot . remove _style _class _name ( 'app-grid-running-dot-custom' ) ;
this . _dot . translation _x = 0 ;
// _updateDotStyle() has been added in GS 46.2 to apply translation_y value from the CSS on style change
if ( shellVersion46 && ! this . _updateDotStyle && ! opt . DASH _VERTICAL )
this . _dot . translation _y = 8 ;
// GS 46.0 (Ubuntu) only
if ( opt . DASH _VERTICAL )
this . _dot . translationY = 0 ;
} ,
_updateRunningStyle ( ) {
const currentWs = global . workspace _manager . get _active _workspace ( ) ;
const show = opt . DASH _ISOLATE _WS
? this . app . get _windows ( ) . filter ( w => w . get _workspace ( ) === currentWs ) . length
: this . app . state !== Shell . AppState . STOPPED ;
if ( show )
this . _dot . show ( ) ;
else
this . _dot . hide ( ) ;
} ,
} ;
2025-02-09 23:18:13 +01:00
const DashExtensions = {
onScrollEvent ( source , event ) {
if ( ( this . app && ! opt . DASH _ICON _SCROLL ) || ( this . _isSearchWindowsIcon && ! opt . SEARCH _WINDOWS _ICON _SCROLL ) ) {
if ( this . _scrollConId ) {
this . disconnect ( this . _scrollConId ) ;
this . _scrollConId = 0 ;
}
if ( this . _leaveConId ) {
this . disconnect ( this . _leaveConId ) ;
this . _leaveConId = 0 ;
}
return Clutter . EVENT _PROPAGATE ;
}
2025-02-09 23:16:18 +01:00
2025-02-09 23:18:13 +01:00
if ( Main . overview . _overview . controls . _stateAdjustment . value > 1 )
return Clutter . EVENT _PROPAGATE ;
2025-02-09 23:16:18 +01:00
2025-02-09 23:18:13 +01:00
let direction = Me . Util . getScrollDirection ( event ) ;
if ( direction === Clutter . ScrollDirection . UP )
direction = 1 ;
else if ( direction === Clutter . ScrollDirection . DOWN )
direction = - 1 ;
else
return Clutter . EVENT _STOP ;
2025-02-09 23:16:18 +01:00
2025-02-09 23:18:13 +01:00
// avoid uncontrollable switching if smooth scroll wheel or trackpad is used
if ( this . _lastScroll && Date . now ( ) - this . _lastScroll < 160 )
return Clutter . EVENT _STOP ;
2025-02-09 23:16:18 +01:00
2025-02-09 23:18:13 +01:00
this . _lastScroll = Date . now ( ) ;
DashExtensions . switchWindow . bind ( this ) ( direction ) ;
return Clutter . EVENT _STOP ;
2025-02-09 23:16:18 +01:00
} ,
2025-02-09 23:18:13 +01:00
onLeaveEvent ( ) {
if ( ! this . _selectedMetaWin || this . has _pointer || this . toggleButton ? . has _pointer )
return ;
this . _selectedPreview . _activateSelected = false ;
this . _selectedMetaWin = null ;
this . _scrolledWindows = null ;
DashExtensions . showWindowPreview . bind ( this ) ( null ) ;
2025-02-09 23:16:18 +01:00
} ,
2025-02-09 23:18:13 +01:00
switchWindow ( direction ) {
if ( ! this . _scrolledWindows ) {
this . _initialSelection = true ;
// source is app icon
if ( this . app ) {
this . _scrolledWindows = this . app . get _windows ( ) ;
if ( opt . DASH _ISOLATE _WS ) {
const currentWs = global . workspaceManager . get _active _workspace ( ) ;
this . _scrolledWindows = this . _scrolledWindows . filter ( w => w . get _workspace ( ) === currentWs ) ;
}
2025-02-09 23:16:18 +01:00
2025-02-09 23:18:13 +01:00
const wsList = [ ] ;
this . _scrolledWindows . forEach ( w => {
const ws = w . get _workspace ( ) ;
if ( ! wsList . includes ( ws ) )
wsList . push ( ws ) ;
} ) ;
2025-02-09 23:16:18 +01:00
2025-02-09 23:18:13 +01:00
// sort windows by workspaces in MRU order
this . _scrolledWindows . sort ( ( a , b ) => wsList . indexOf ( a . get _workspace ( ) ) > wsList . indexOf ( b . get _workspace ( ) ) ) ;
// source is Search Windows icon
} else if ( this . _isSearchWindowsIcon ) {
if ( opt . SEARCH _WINDOWS _ICON _SCROLL === 1 ) // all windows
this . _scrolledWindows = Me . Util . getWindows ( null ) ;
else
this . _scrolledWindows = Me . Util . getWindows ( global . workspace _manager . get _active _workspace ( ) ) ;
}
}
2025-02-09 23:16:18 +01:00
2025-02-09 23:18:13 +01:00
let windows = this . _scrolledWindows ;
if ( ! windows . length )
return ;
// if window selection is in the process, the previewed window must be the current one
let currentWin = this . _selectedMetaWin ? this . _selectedMetaWin : windows [ 0 ] ;
const currentIdx = windows . indexOf ( currentWin ) ;
let targetIdx = currentIdx ;
// const focusWindow = Me.Util.getWindows(null)[0]; // incompatible 45
const focusWindow = Me . Util . getWindows ( null ) [ 0 ] ;
const appFocused = this . _scrolledWindows [ 0 ] === focusWindow && this . _scrolledWindows [ 0 ] . get _workspace ( ) === global . workspace _manager . get _active _workspace ( ) ;
// only if the app has focus, immediately switch to the previous window
// otherwise just set the current window above others
if ( ! this . _initialSelection || appFocused )
targetIdx += direction ;
else
this . _initialSelection = false ;
if ( targetIdx > windows . length - 1 )
targetIdx = 0 ;
else if ( targetIdx < 0 )
targetIdx = windows . length - 1 ;
const metaWin = windows [ targetIdx ] ;
DashExtensions . showWindowPreview . bind ( this ) ( metaWin ) ;
this . _selectedMetaWin = metaWin ;
2025-02-09 23:16:18 +01:00
} ,
2025-02-09 23:18:13 +01:00
showWindowPreview ( metaWin ) {
const views = Main . overview . _overview . controls . _workspacesDisplay . _workspacesViews ;
const viewsIter = [ views [ 0 ] ] ;
// secondary monitors use different structure
views . forEach ( v => {
if ( v . _workspacesView )
viewsIter . push ( v . _workspacesView ) ;
2025-02-09 23:16:18 +01:00
} ) ;
2025-02-09 23:18:13 +01:00
viewsIter . forEach ( view => {
// if workspaces are on primary monitor only
if ( ! view || ! view . _workspaces )
return ;
2025-02-09 23:16:18 +01:00
2025-02-09 23:18:13 +01:00
view . _workspaces . forEach ( ws => {
ws . _windows . forEach ( windowPreview => {
// metaWin === null resets opacity
let opacity = metaWin ? 50 : 255 ;
windowPreview . _activateSelected = false ;
2025-02-09 23:16:18 +01:00
2025-02-09 23:18:13 +01:00
// minimized windows are invisible if windows are not exposed (WORKSPACE_MODE === 0)
if ( ! windowPreview . opacity )
windowPreview . opacity = 255 ;
// app windows set to lower opacity, so they can be recognized
if ( this . _scrolledWindows && this . _scrolledWindows . includes ( windowPreview . metaWindow ) ) {
if ( opt . DASH _ICON _SCROLL === 2 )
opacity = 254 ;
}
if ( windowPreview . metaWindow === metaWin ) {
if ( metaWin && metaWin . get _workspace ( ) !== global . workspace _manager . get _active _workspace ( ) ) {
Main . wm . actionMoveWorkspace ( metaWin . get _workspace ( ) ) ;
if ( _timeouts . wsSwitcherAnimation )
GLib . source _remove ( _timeouts . wsSwitcherAnimation ) ;
// setting window preview above siblings before workspace switcher animation has no effect
// we need to set the window above after the ws preview become visible on the screen
// the default switcher animation time is 250, 200 ms delay should be enough
_timeouts . wsSwitcherAnimation = GLib . timeout _add ( 0 , 200 * St . Settings . get ( ) . slow _down _factor , ( ) => {
windowPreview . get _parent ( ) . set _child _above _sibling ( windowPreview , null ) ;
_timeouts . wsSwitcherAnimation = 0 ;
return GLib . SOURCE _REMOVE ;
} ) ;
} else {
windowPreview . get _parent ( ) . set _child _above _sibling ( windowPreview , null ) ;
}
2025-02-09 23:16:18 +01:00
2025-02-09 23:18:13 +01:00
opacity = 255 ;
this . _selectedPreview = windowPreview ;
windowPreview . _activateSelected = true ;
}
// if windows are exposed, highlight selected using opacity
if ( ( opt . OVERVIEW _MODE && opt . WORKSPACE _MODE ) || ! opt . OVERVIEW _MODE ) {
if ( metaWin && opacity === 255 )
windowPreview . showOverlay ( true ) ;
else
windowPreview . hideOverlay ( true ) ;
windowPreview . ease ( {
duration : 200 ,
opacity ,
mode : Clutter . AnimationMode . EASE _OUT _QUAD ,
} ) ;
}
} ) ;
} ) ;
2025-02-09 23:16:18 +01:00
} ) ;
2025-02-09 23:18:13 +01:00
} ,
} ;
2025-02-09 23:16:18 +01:00
const AppMenuCommon = {
_updateWindowsSection ( ) {
if ( global . compositor ) {
if ( this . _updateWindowsLaterId ) {
const laters = global . compositor . get _laters ( ) ;
laters . remove ( this . _updateWindowsLaterId ) ;
}
} else if ( this . _updateWindowsLaterId ) {
Meta . later _remove ( this . _updateWindowsLaterId ) ;
}
this . _updateWindowsLaterId = 0 ;
this . _windowSection . removeAll ( ) ;
this . _openWindowsHeader . hide ( ) ;
if ( ! this . _app )
return ;
const minWindows = this . _showSingleWindows ? 1 : 2 ;
const currentWs = global . workspaceManager . get _active _workspace ( ) ;
const isolateWs = opt . DASH _ISOLATE _WS && ! Main . overview . dash . showAppsButton . checked ;
const windows = this . _app . get _windows ( ) . filter ( w => ! w . skip _taskbar && ( isolateWs ? w . get _workspace ( ) === currentWs : true ) ) ;
if ( windows . length < minWindows )
return ;
this . _openWindowsHeader . show ( ) ;
windows . forEach ( window => {
const title = window . title || this . _app . get _name ( ) ;
const item = this . _windowSection . addAction ( title , event => {
Main . activateWindow ( window , event . get _time ( ) ) ;
} ) ;
window . connectObject ( 'notify::title' , ( ) => {
item . label . text = window . title || this . _app . get _name ( ) ;
} , item ) ;
} ) ;
} ,
} ;