1
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
3
const Clutter = imports.gi.Clutter;
4
const Pango = imports.gi.Pango;
5
const GLib = imports.gi.GLib;
6
const Gio = imports.gi.Gio;
7
const Gtk = imports.gi.Gtk;
8
const Shell = imports.gi.Shell;
9
const Lang = imports.lang;
10
const Signals = imports.signals;
11
const St = imports.gi.St;
12
const Mainloop = imports.mainloop;
14
const AppFavorites = imports.ui.appFavorites;
15
const DND = imports.ui.dnd;
16
const Main = imports.ui.main;
17
const Overview = imports.ui.overview;
18
const PopupMenu = imports.ui.popupMenu;
19
const Search = imports.ui.search;
20
const Tweener = imports.ui.tweener;
21
const Workspace = imports.ui.workspace;
22
const AppDisplay = imports.ui.appDisplay;
23
const AltTab = imports.ui.altTab;
25
const Gettext = imports.gettext.domain('gnome-shell-extensions');
26
const _ = Gettext.gettext;
29
//const autohide_animation_time = 0.3;
31
// Keep enums in sync with GSettings schemas
32
const PositionMode = {
37
const AutoHideEffect = {
44
const DOCK_POSITION = PositionMode.RIGHT;
46
const DOCK_AUTOHIDE = true;
47
const DOCK_EFFECTHIDE = AutoHideEffect.MOVE;
48
const DOCK_AUTOHIDE_ANIMATION_TIME = 0.3;
49
// Do not change anything below this line (it is intentionally duplicate to keep in
50
// sync with master branch)
52
let position = PositionMode.RIGHT;
53
let dockicon_size = 48;
56
let hideEffect = AutoHideEffect.RESIZE;
57
let autohide_animation_time = 0.3;
58
const DND_RAISE_APP_TIMEOUT = 500;
60
/*************************************************************************************/
61
/**** start resize's Dock functions *****************/
62
/*************************************************************************************/
63
function hideDock_size () {
65
let monitor = Main.layoutManager.primaryMonitor
66
let position_x = monitor.x;
67
let height = (this._nicons)*(this._item_size + this._spacing) + 2*this._spacing;
68
let width = this._item_size + 4*this._spacing;
70
Tweener.addTween(this,{
72
time: autohide_animation_time,
73
transition: 'easeOutQuad',
74
onUpdate: function () {
75
height = (this._nicons)*(this._item_size + this._spacing) + 2*this._spacing;
76
width = this._item_size + 4*this._spacing;
78
case PositionMode.LEFT:
79
position_x=monitor.x-2*this._spacing;
81
case PositionMode.RIGHT:
83
position_x = monitor.x + (monitor.width-1-this._item_size-2*this._spacing);
85
this.actor.set_position (position_x,monitor.y+(monitor.height-height)/2);
86
this.actor.set_size(width,height);
93
function showDock_size () {
94
let monitor = Main.layoutManager.primaryMonitor;
95
let height = (this._nicons)*(this._item_size + this._spacing) + 2*this._spacing;
96
let width = this._item_size + 4*this._spacing;
97
let position_x = monitor.x;
99
Tweener.addTween(this,{
100
_item_size: dockicon_size,
101
time: autohide_animation_time,
102
transition: 'easeOutQuad',
103
onUpdate: function () {
104
height = (this._nicons)*(this._item_size + this._spacing) + 2*this._spacing;
105
width = this._item_size + 4*this._spacing;
107
case PositionMode.LEFT:
108
position_x=monitor.x-2*this._spacing;
110
case PositionMode.RIGHT:
112
position_x=monitor.x + (monitor.width-this._item_size-2*this._spacing);
114
this.actor.set_position (position_x, monitor.y+(monitor.height-height)/2);
115
this.actor.set_size(width,height);
121
function initShowDock_size () {
126
function showEffectAddItem_size () {
127
let primary = Main.layoutManager.primaryMonitor;
128
let height = (this._nicons)*(this._item_size + this._spacing) + 2*this._spacing;
129
let width = this._item_size + 4*this._spacing;
131
Tweener.addTween(this.actor, {
132
y: primary.y + (primary.height-height)/2,
135
time: autohide_animation_time,
136
transition: 'easeOutQuad'
140
/**************************************************************************************/
141
/**** start rescale's Dock functions *****************/
142
/**************************************************************************************/
143
function hideDock_scale () {
144
this._item_size = dockicon_size;
145
let monitor = Main.layoutManager.primaryMonitor;
147
let height = this._nicons*(this._item_size + this._spacing) + 2*this._spacing;
148
let width = this._item_size + 4*this._spacing;
151
case PositionMode.LEFT:
154
case PositionMode.RIGHT:
156
cornerX = monitor.x + monitor.width-1;
160
Tweener.addTween(this.actor,{
161
y: monitor.y + (monitor.height-height)/2,
166
time: autohide_animation_time,
167
transition: 'easeOutQuad'
173
function showDock_scale () {
174
this._item_size = dockicon_size;
175
let monitor = Main.layoutManager.primaryMonitor;
176
let position_x = monitor.x;
177
let height = this._nicons*(this._item_size + this._spacing) + 2*this._spacing;
178
let width = this._item_size + 4*this._spacing;
181
case PositionMode.LEFT:
182
position_x=monitor.x-2*this._spacing;
184
case PositionMode.RIGHT:
186
position_x=monitor.x + (monitor.width-this._item_size-2*this._spacing);
188
Tweener.addTween(this.actor, {
189
y: monitor.y + (monitor.height-height)/2,
190
x: monitor.x + position_x,
194
time: autohide_animation_time,
195
transition: 'easeOutQuad'
200
function initShowDock_scale () {
201
let primary = Main.layoutManager.primaryMonitor;
202
let height = this._nicons*(this._item_size + this._spacing) + 2*this._spacing;
203
let width = this._item_size + 4*this._spacing;
205
this.actor.set_scale (0,0);
206
this.actor.set_size (width,height);
208
// set the position of the dock
210
case PositionMode.LEFT:
212
// effect of creation of the dock
213
Tweener.addTween(this.actor, {
214
x: primary.x-2*this._spacing,
215
y: primary.y + (primary.height-height)/2,
216
time: autohide_animation_time * 3,
217
transition: 'easeOutQuad'
220
case PositionMode.RIGHT:
222
this.actor.x = primary.width-1;
223
// effect of creation of the dock
224
Tweener.addTween(this.actor, {
225
x: primary.x + primary.width-this._item_size- 2*this._spacing,
226
y: primary.y + (primary.height-height)/2,
227
time: autohide_animation_time * 3,
228
transition: 'easeOutQuad'
231
Tweener.addTween(this.actor,{
234
time: autohide_animation_time * 3,
235
transition: 'easeOutQuad'
240
function showEffectAddItem_scale () {
241
let monitor = Main.layoutManager.primaryMonitor;
242
let height = this._nicons*(this._item_size + this._spacing) + 2*this._spacing;
243
let width = this._item_size + 4*this._spacing;
245
Tweener.addTween(this.actor, {
246
y: monitor.y + (monitor.height-height)/2,
249
time: autohide_animation_time,
250
transition: 'easeOutQuad'
254
/**************************************************************************************/
255
/**** start move Dock functions *****************/
256
/**************************************************************************************/
257
function hideDock_move () {
258
this._item_size = dockicon_size;
259
let monitor = Main.layoutManager.primaryMonitor;
261
let height = this._nicons*(this._item_size + this._spacing) + 2*this._spacing;
262
let width = this._item_size + 4*this._spacing;
265
case PositionMode.LEFT:
266
cornerX= monitor.x - width + this._spacing;
268
case PositionMode.RIGHT:
270
cornerX = monitor.x + monitor.width - this._spacing;
274
Tweener.addTween(this.actor,{
276
y: monitor.y + (monitor.height - height)/2,
279
time: autohide_animation_time,
280
transition: 'easeOutQuad'
286
function showDock_move () {
287
this._item_size = dockicon_size;
288
let monitor = Main.layoutManager.primaryMonitor;
289
let position_x = monitor.x;
290
let height = this._nicons*(this._item_size + this._spacing) + 2*this._spacing;
291
let width = this._item_size + 4*this._spacing;
294
case PositionMode.LEFT:
295
position_x=monitor.x - 2*this._spacing;
297
case PositionMode.RIGHT:
299
position_x=monitor.x + (monitor.width-this._item_size-2*this._spacing);
301
Tweener.addTween(this.actor, {
303
y: monitor.y + (monitor.height - height)/2,
306
time: autohide_animation_time,
307
transition: 'easeOutQuad'
312
function initShowDock_move () {
316
function showEffectAddItem_move () {
317
let monitor = Main.layoutManager.primaryMonitor;
318
let height = this._nicons*(this._item_size + this._spacing) + 2*this._spacing;
319
let width = this._item_size + 4*this._spacing;
321
Tweener.addTween(this.actor, {
322
y: monitor.y + (monitor.height-height)/2,
325
time: autohide_animation_time,
326
transition: 'easeOutQuad'
336
this._placeholderText = null;
338
this._menuDisplays = [];
340
this._favorites = [];
343
position = DOCK_POSITION;
344
dockicon_size = DOCK_SIZE;
345
hideDock = hideable = DOCK_AUTOHIDE;
346
hideEffect = DOCK_EFFECTHIDE;
347
autohide_animation_time = DOCK_AUTOHIDE_ANIMATION_TIME;
350
this._item_size = dockicon_size;
352
this._selectFunctionsHide ();
354
this.actor = new St.BoxLayout({ name: 'dock', vertical: true, reactive: true });
356
this._grid = new Shell.GenericContainer();
357
this.actor.add(this._grid, { expand: true, y_align: St.Align.START });
358
this.actor.connect('style-changed', Lang.bind(this, this._onStyleChanged));
360
this._grid.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
361
this._grid.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
362
this._grid.connect('allocate', Lang.bind(this, this._allocate));
364
this._workId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplay));
366
this._tracker = Shell.WindowTracker.get_default();
367
this._appSystem = Shell.AppSystem.get_default();
369
this._installedChangedId = this._appSystem.connect('installed-changed', Lang.bind(this, this._queueRedisplay));
370
this._appFavoritesChangedId = AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._queueRedisplay));
371
this._appStateChangedId = this._appSystem.connect('app-state-changed', Lang.bind(this, this._queueRedisplay));
373
this._overviewShowingId = Main.overview.connect('showing', Lang.bind(this, function() {
376
this._overviewHiddenId = Main.overview.connect('hidden', Lang.bind(this, function() {
379
Main.layoutManager.addChrome(this.actor);
381
this._leave_event = this.actor.connect('leave-event', Lang.bind(this, this._hideDock));
382
this._enter_event = this.actor.connect('enter-event', Lang.bind(this, this._showDock));
387
destroy: function() {
388
if (this._installedChangedId) {
389
this._appSystem.disconnect(this._installedChangedId);
390
this._installedChangedId = 0;
393
if (this._appFavoritesChangedId) {
394
AppFavorites.getAppFavorites().disconnect(this._appFavoritesChangedId);
395
this._appFavoritesChangedId = 0;
398
if (this._appStateChangedId) {
399
this._appSystem.disconnect(this._appStateChangedId);
400
this._appStateChangedId = 0;
403
if (this._overviewShowingId) {
404
Main.overview.disconnect(this._overviewShowingId);
405
this._overviewShowingId = 0;
408
if (this._overviewHiddenId) {
409
Main.overview.disconnect(this._overviewHiddenId);
410
this._overviewHiddenId = 0;
413
this.actor.destroy();
415
// Break reference cycles
416
this._appSystem = null;
417
this._tracker = null;
421
_restoreHideDock: function(){
422
hideable = DOCK_AUTOHIDE;
425
_disableHideDock: function (){
429
_selectFunctionsHide: function () {
430
switch (hideEffect) {
431
case AutoHideEffect.RESCALE:
432
this._hideDock = hideDock_scale;
433
this._showDock = showDock_scale;
434
this._initShowDock = initShowDock_scale;
435
this._showEffectAddItem = showEffectAddItem_scale;
437
case AutoHideEffect.MOVE:
438
this._hideDock = hideDock_move;
439
this._showDock = showDock_move;
440
this._initShowDock = initShowDock_move;
441
this._showEffectAddItem = showEffectAddItem_move;
443
case AutoHideEffect.RESIZE:
445
this._hideDock = hideDock_size;
446
this._showDock = showDock_size;
447
this._initShowDock = initShowDock_size;
448
this._showEffectAddItem = showEffectAddItem_size;
452
_appIdListToHash: function(apps) {
454
for (let i = 0; i < apps.length; i++)
455
ids[apps[i].get_id()] = apps[i];
459
_queueRedisplay: function () {
460
Main.queueDeferredWork(this._workId);
463
_redisplay: function () {
466
let favorites = AppFavorites.getAppFavorites().getFavoriteMap();
468
let running = this._appSystem.get_running();
469
let runningIds = this._appIdListToHash(running);
474
for (let id in favorites) {
475
let app = favorites[id];
476
let display = new DockIcon(app,this);
477
this.addItem(display.actor);
482
for (let i = 0; i < running.length; i++) {
483
let app = running[i];
484
if (app.get_id() in favorites)
486
let display = new DockIcon(app,this);
488
this.addItem(display.actor);
492
if (this._placeholderText) {
493
this._placeholderText.destroy();
494
this._placeholderText = null;
497
if (running.length == 0 && nFavorites == 0) {
498
this._placeholderText = new St.Label({ text: _("Drag here to add favorites") });
499
this.actor.add_actor(this._placeholderText);
502
let primary = Main.layoutManager.primaryMonitor;
503
let height = (icons)*(this._item_size + this._spacing) + 2*this._spacing;
504
let width = this._item_size + 4*this._spacing;
506
if (this.actor.y != primary.y) {
507
if (hideable && hideDock) {
510
if (dockicon_size == this._item_size) {
511
// only add/delete icon
512
this._showEffectAddItem ();
519
// effect of creation
520
this._initShowDock ();
524
_getPreferredWidth: function (grid, forHeight, alloc) {
525
alloc.min_size = this._item_size;
526
alloc.natural_size = this._item_size + this._spacing;
529
_getPreferredHeight: function (grid, forWidth, alloc) {
530
let children = this._grid.get_children();
531
let nRows = children.length;
532
let totalSpacing = Math.max(0, nRows - 1) * this._spacing;
533
let height = nRows * this._item_size + totalSpacing;
534
alloc.min_size = height;
535
alloc.natural_size = height;
538
_allocate: function (grid, box, flags) {
539
let children = this._grid.get_children();
541
let x = box.x1 + this._spacing;
542
if (position == PositionMode.LEFT)
543
x = box.x1 + 2*this._spacing;
544
let y = box.y1 + this._spacing;
546
for (let i = 0; i < children.length; i++) {
547
let childBox = new Clutter.ActorBox();
550
childBox.x2 = childBox.x1 + this._item_size;
551
childBox.y2 = childBox.y1 + this._item_size;
552
children[i].allocate(childBox, flags);
553
y += this._item_size + this._spacing;
558
_onStyleChanged: function() {
559
let themeNode = this.actor.get_theme_node();
560
let [success, len] = themeNode.get_length('spacing', false);
563
[success, len] = themeNode.get_length('-shell-grid-item-size', false);
565
this._item_size = len;
566
this._grid.queue_relayout();
569
removeAll: function () {
570
this._grid.get_children().forEach(Lang.bind(this, function (child) {
575
addItem: function(actor) {
576
this._grid.add_actor(actor);
579
Signals.addSignalMethods(Dock.prototype);
581
function DockIcon(app, dock) {
582
this._init(app, dock);
585
DockIcon.prototype = {
586
_init : function(app, dock) {
588
this.actor = new St.Button({ style_class: 'dock-app',
589
button_mask: St.ButtonMask.ONE | St.ButtonMask.TWO,
593
this.actor._delegate = this;
594
this.actor.set_size(dockicon_size, dockicon_size);
596
this._icon = this.app.create_icon_texture(dockicon_size);
597
this.actor.set_child(this._icon);
599
this.actor.connect('clicked', Lang.bind(this, this._onClicked));
602
this._menuManager = new PopupMenu.PopupMenuManager(this);
604
this._has_focus = false;
606
let tracker = Shell.WindowTracker.get_default();
607
tracker.connect('notify::focus-app', Lang.bind(this, this._onStateChanged));
609
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
610
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
611
this.actor.connect('notify::hover', Lang.bind(this, this._hoverChanged));
613
this._menuTimeoutId = 0;
614
this._stateChangedId = this.app.connect('notify::state',
615
Lang.bind(this, this._onStateChanged));
616
this._onStateChanged();
620
_onDestroy: function() {
621
if (this._stateChangedId > 0)
622
this.app.disconnect(this._stateChangedId);
623
this._stateChangedId = 0;
624
this._removeMenuTimeout();
627
_removeMenuTimeout: function() {
628
if (this._menuTimeoutId > 0) {
629
Mainloop.source_remove(this._menuTimeoutId);
630
this._menuTimeoutId = 0;
634
_hoverChanged: function(actor) {
635
if (actor != this.actor)
636
this._has_focus = false;
638
this._has_focus = true;
642
_onStateChanged: function() {
643
let tracker = Shell.WindowTracker.get_default();
644
let focusedApp = tracker.focus_app;
645
if (this.app.state != Shell.AppState.STOPPED) {
646
this.actor.add_style_class_name('running');
647
if (this.app == focusedApp) {
648
this.actor.add_style_class_name('focused');
650
this.actor.remove_style_class_name('focused');
653
this.actor.remove_style_class_name('focused');
654
this.actor.remove_style_class_name('running');
658
_onButtonPress: function(actor, event) {
659
let button = event.get_button();
661
this._removeMenuTimeout();
662
this._menuTimeoutId = Mainloop.timeout_add(AppDisplay.MENU_POPUP_TIMEOUT, Lang.bind(this, function() {
665
} else if (button == 3) {
670
_onClicked: function(actor, button) {
671
this._removeMenuTimeout();
674
this._onActivate(Clutter.get_current_event());
675
} else if (button == 2) {
676
// Last workspace is always empty
677
let launchWorkspace = global.screen.get_workspace_by_index(global.screen.n_workspaces - 1);
678
launchWorkspace.activate(global.get_current_time());
679
this.emit('launching');
680
this.app.open_new_window(-1);
686
return this.app.get_id();
689
popupMenu: function() {
690
this._removeMenuTimeout();
691
this.actor.fake_release();
693
this._dock._disableHideDock();
696
this._menu = new DockIconMenu(this);
697
this._menu.connect('activate-window', Lang.bind(this, function (menu, window) {
698
this.activateWindow(window);
700
this._menu.connect('open-state-changed', Lang.bind(this, function (menu, isPoppedUp) {
702
//Restore value of autohidedock
703
this._dock._restoreHideDock();
704
this._dock._hideDock();
706
this._onMenuPoppedDown();
710
this._menuManager.addMenu(this._menu, true);
718
activateWindow: function(metaWindow) {
720
this._didActivateWindow = true;
721
Main.activateWindow(metaWindow);
725
setSelected: function (isSelected) {
726
this._selected = isSelected;
728
this.actor.add_style_class_name('selected');
730
this.actor.remove_style_class_name('selected');
733
_onMenuPoppedDown: function() {
734
this.actor.sync_hover();
737
_getRunning: function() {
738
return this.app.state != Shell.AppState.STOPPED;
741
_onActivate: function (event) {
742
this.emit('launching');
743
let modifiers = Shell.get_event_state(event);
745
if (modifiers & Clutter.ModifierType.CONTROL_MASK
746
&& this.app.state == Shell.AppState.RUNNING) {
747
let current_workspace = global.screen.get_active_workspace().index();
748
this.app.open_new_window(current_workspace);
750
let tracker = Shell.WindowTracker.get_default();
751
let focusedApp = tracker.focus_app;
753
if (this.app == focusedApp) {
754
let windows = this.app.get_windows();
755
let current_workspace = global.screen.get_active_workspace();
756
for (let i = 0; i < windows.length; i++) {
758
if (w.get_workspace() == current_workspace)
762
this.app.activate(-1);
765
Main.overview.hide();
768
shellWorkspaceLaunch : function() {
769
this.app.open_new_window();
772
Signals.addSignalMethods(DockIcon.prototype);
774
function DockIconMenu(source) {
778
DockIconMenu.prototype = {
779
__proto__: AppDisplay.AppIconMenu.prototype,
781
_init: function(source) {
783
case PositionMode.LEFT:
784
PopupMenu.PopupMenu.prototype._init.call(this, source.actor, St.Align.MIDDLE, St.Side.LEFT, 0);
786
case PositionMode.RIGHT:
788
PopupMenu.PopupMenu.prototype._init.call(this, source.actor, St.Align.MIDDLE, St.Side.RIGHT, 0);
791
this._source = source;
793
this.connect('activate', Lang.bind(this, this._onActivate));
795
this.actor.add_style_class_name('dock-menu');
797
// Chain our visibility and lifecycle to that of the source
798
source.actor.connect('notify::mapped', Lang.bind(this, function () {
799
if (!source.actor.mapped)
802
source.actor.connect('destroy', Lang.bind(this, function () { this.actor.destroy(); }));
804
Main.layoutManager.addChrome(this.actor);
807
_redisplay: function() {
810
let windows = this._source.app.get_windows();
812
// Display the app windows menu items and the separator between windows
813
// of the current desktop and other windows.
814
let activeWorkspace = global.screen.get_active_workspace();
815
let separatorShown = windows.length > 0 && windows[0].get_workspace() != activeWorkspace;
817
for (let i = 0; i < windows.length; i++) {
818
if (!separatorShown && windows[i].get_workspace() != activeWorkspace) {
819
this._appendSeparator();
820
separatorShown = true;
822
let item = this._appendMenuItem(windows[i].title);
823
item._window = windows[i];
826
if (windows.length > 0)
827
this._appendSeparator();
829
let isFavorite = AppFavorites.getAppFavorites().isFavorite(this._source.app.get_id());
831
this._newWindowMenuItem = windows.length > 0 ? this._appendMenuItem(_("New Window")) : null;
833
this._quitAppMenuItem = windows.length >0 ? this._appendMenuItem(_("Quit Application")) : null;
835
if (windows.length > 0)
836
this._appendSeparator();
837
this._toggleFavoriteMenuItem = this._appendMenuItem(isFavorite ?
838
_("Remove from Favorites")
839
: _("Add to Favorites"));
841
this._highlightedItem = null;
844
_onActivate: function (actor, child) {
846
let metaWindow = child._window;
847
this.emit('activate-window', metaWindow);
848
} else if (child == this._newWindowMenuItem) {
849
let current_workspace = global.screen.get_active_workspace().index();
850
this._source.app.open_new_window(current_workspace);
851
this.emit('activate-window', null);
852
} else if (child == this._quitAppMenuItem) {
853
this._source.app.request_quit();
854
} else if (child == this._toggleFavoriteMenuItem) {
855
let favs = AppFavorites.getAppFavorites();
856
let isFavorite = favs.isFavorite(this._source.app.get_id());
858
favs.removeFavorite(this._source.app.get_id());
860
favs.addFavorite(this._source.app.get_id());
866
function init(extensionMeta) {
867
imports.gettext.bindtextdomain('gnome-shell-extensions', GLib.build_filenamev([extensionMeta.path, 'locale']));