Compare commits

...

4 Commits

Author SHA1 Message Date
Mario Sanchez Prada
043f042cec altTab: Close the list of thumbnails in the switcher when closing an app
This makes sure that no thumbnails list is left open and empty, since both
the app's icon and all its thumbnails will be gone once the app is closed.

https://bugzilla.gnome.org/show_bug.cgi?id=620106
2017-12-01 21:30:26 +00:00
Mario Sanchez Prada
815302b4ac altTab: Don't activate an app it the icon is no longer available
https://bugzilla.gnome.org/show_bug.cgi?id=620106
2017-12-01 21:30:17 +00:00
Mario Sanchez Prada
e608d4f26b altTab: Use arrow functions for callbacks
https://bugzilla.gnome.org/show_bug.cgi?id=620106
2017-12-01 21:29:49 +00:00
Florian Müllner
77c20e3b8e [appSwitcher] Add shortcut to quit application
Allow to quit the currently selected application by hitting Alt-q,
similar to the OS X switcher.

https://bugzilla.gnome.org/show_bug.cgi?id=620106
2017-11-30 16:58:18 +00:00
2 changed files with 103 additions and 28 deletions

View File

@ -156,6 +156,17 @@ var AppSwitcherPopup = new Lang.Class({
this._items[this._selectedIndex].cachedWindows.length);
},
_quitApplication: function(appIndex) {
let appIcon = this._items[appIndex];
if (!appIcon)
return;
// Make sure the list of thumbnails is hidden before quitting the
// application, not to keep an empty list of windows showing up.
this._select(appIndex, null, false);
appIcon.app.request_quit();
},
_keyPressHandler: function(keysym, action) {
if (action == Meta.KeyBindingAction.SWITCH_GROUP) {
if (!this._thumbnailsFocused)
@ -175,6 +186,8 @@ var AppSwitcherPopup = new Lang.Class({
this._select(this._selectedIndex, this._nextWindow());
else if (keysym == Clutter.Up)
this._select(this._selectedIndex, null, true);
else if (keysym == Clutter.q)
this._quitApplication(this._selectedIndex);
else
return Clutter.EVENT_PROPAGATE;
} else {
@ -184,6 +197,8 @@ var AppSwitcherPopup = new Lang.Class({
this._select(this._next());
else if (keysym == Clutter.Down)
this._select(this._selectedIndex, 0);
else if (keysym == Clutter.q)
this._quitApplication(this._selectedIndex);
else
return Clutter.EVENT_PROPAGATE;
}
@ -250,11 +265,12 @@ var AppSwitcherPopup = new Lang.Class({
_finish : function(timestamp) {
let appIcon = this._items[this._selectedIndex];
if (this._currentWindow < 0)
appIcon.app.activate_window(appIcon.cachedWindows[0], timestamp);
else
Main.activateWindow(appIcon.cachedWindows[this._currentWindow], timestamp);
if (appIcon) {
if (this._currentWindow < 0)
appIcon.app.activate_window(appIcon.cachedWindows[0], timestamp);
else if (appIcon.cachedWindows[this._currentWindow])
Main.activateWindow(appIcon.cachedWindows[this._currentWindow], timestamp);
}
this.parent();
},
@ -317,8 +333,7 @@ var AppSwitcherPopup = new Lang.Class({
} else if (this._items[this._selectedIndex].cachedWindows.length > 1 &&
!forceAppFocus) {
this._thumbnailTimeoutId = Mainloop.timeout_add (
THUMBNAIL_POPUP_TIME,
Lang.bind(this, this._timeoutPopupThumbnails));
THUMBNAIL_POPUP_TIME, () => { this._timeoutPopupThumbnails(); });
GLib.Source.set_name_by_id(this._thumbnailTimeoutId, '[gnome-shell] this._timeoutPopupThumbnails');
}
},
@ -337,10 +352,11 @@ var AppSwitcherPopup = new Lang.Class({
{ opacity: 0,
time: THUMBNAIL_FADE_TIME,
transition: 'easeOutQuad',
onComplete: Lang.bind(this, function() {
thumbnailsActor.destroy();
this.thumbnailsVisible = false;
})
onComplete: () => {
thumbnailsActor.destroy();
this.thumbnailsVisible = false;
},
onCompleteScope: this
});
this._thumbnails = null;
this._switcherList._items[this._selectedIndex].remove_accessible_state (Atk.StateType.EXPANDED);
@ -348,8 +364,12 @@ var AppSwitcherPopup = new Lang.Class({
_createThumbnails : function() {
this._thumbnails = new ThumbnailList (this._items[this._selectedIndex].cachedWindows);
this._thumbnails.connect('item-activated', Lang.bind(this, this._windowActivated));
this._thumbnails.connect('item-entered', Lang.bind(this, this._windowEntered));
this._thumbnails.connect('item-activated', (thumbnailList, n) => { this._windowActivated(thumbnailList, n); });
this._thumbnails.connect('item-entered', (thumbnailList, n) => { this._windowEntered(thumbnailList, n); });
this._thumbnails.actor.connect('destroy', () => {
this._thumbnails = null;
this._thumbnailsFocused = false;
});
this.actor.add_actor(this._thumbnails.actor);
@ -362,7 +382,8 @@ var AppSwitcherPopup = new Lang.Class({
{ opacity: 255,
time: THUMBNAIL_FADE_TIME,
transition: 'easeOutQuad',
onComplete: Lang.bind(this, function () { this.thumbnailsVisible = true; })
onComplete: () => { this.thumbnailsVisible = true; },
onCompleteScope: this
});
this._switcherList._items[this._selectedIndex].add_accessible_state (Atk.StateType.EXPANDED);
@ -389,9 +410,8 @@ var CyclerHighlight = new Lang.Class({
this.actor.add_constraint(constraint);
this.actor.connect('notify::allocation',
Lang.bind(this, this._onAllocationChanged));
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
this.actor.connect('notify::allocation', () => { this._onAllocationChanged(); });
this.actor.connect('destroy', () => { this._onDestroy(); });
},
set window(w) {
@ -449,7 +469,7 @@ var CyclerPopup = new Lang.Class({
// We don't show an actual popup, so just provide what SwitcherPopup
// expects instead of inheriting from SwitcherList
this._switcherList = { actor: new St.Widget(),
highlight: Lang.bind(this, this._highlightItem),
highlight: (index, justOutline) => { this._highlightItem(index, justOutline); },
connect: function() {} };
},
@ -639,12 +659,15 @@ var AppSwitcher = new Lang.Class({
this._altTabPopup = altTabPopup;
this._mouseTimeOutId = 0;
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
this.actor.connect('destroy', () => { this._onDestroy(); });
},
_onDestroy: function() {
if (this._mouseTimeOutId != 0)
Mainloop.source_remove(this._mouseTimeOutId);
for (let i = 0; i < this.icons.length; i++)
this.icons[i].app.disconnect(this.icons[i]._stateChangedId);
},
_setIconSize: function() {
@ -719,12 +742,11 @@ var AppSwitcher = new Lang.Class({
if (this._mouseTimeOutId != 0)
Mainloop.source_remove(this._mouseTimeOutId);
if (this._altTabPopup.thumbnailsVisible) {
this._mouseTimeOutId = Mainloop.timeout_add(APP_ICON_HOVER_TIMEOUT,
Lang.bind(this, function () {
this._enterItem(index);
this._mouseTimeOutId = 0;
return GLib.SOURCE_REMOVE;
}));
this._mouseTimeOutId = Mainloop.timeout_add(APP_ICON_HOVER_TIMEOUT, () => {
this._enterItem(index);
this._mouseTimeOutId = 0;
return GLib.SOURCE_REMOVE;
});
GLib.Source.set_name_by_id(this._mouseTimeOutId, '[gnome-shell] this._enterItem');
} else
this._itemEntered(index);
@ -767,6 +789,11 @@ var AppSwitcher = new Lang.Class({
this.icons.push(appIcon);
let item = this.addItem(appIcon.actor, appIcon.label);
appIcon._stateChangedId = appIcon.app.connect('notify::state', (app) => {
if (app.state != Shell.AppState.RUNNING)
this._removeIcon(app);
});
let n = this._arrows.length;
let arrow = new St.DrawingArea({ style_class: 'switcher-arrow' });
arrow.connect('repaint', function() { SwitcherPopup.drawArrow(arrow, St.Side.BOTTOM); });
@ -777,7 +804,22 @@ var AppSwitcher = new Lang.Class({
arrow.hide();
else
item.add_accessible_state (Atk.StateType.EXPANDABLE);
}
},
_removeIcon: function(app) {
for (let i = 0; i < this.icons.length; i++)
if (this.icons[i].app == app) {
this.icons.splice(i, 1);
this.removeItem(i);
if (this._curApp == i)
this._curApp = SwitcherPopup.mod(i, this.icons.length);
if (this.icons.length > 0)
this.highlight(this._curApp);
else
this.actor.destroy();
return;
}
},
});
var ThumbnailList = new Lang.Class({
@ -816,6 +858,8 @@ var ThumbnailList = new Lang.Class({
}
}
this.actor.connect('destroy', () => { this._onDestroy(); });
},
addClones : function (availHeight) {
@ -840,12 +884,38 @@ var ThumbnailList = new Lang.Class({
let clone = _createWindowClone(mutterWindow, thumbnailSize);
this._thumbnailBins[i].set_height(binHeight);
this._thumbnailBins[i].add_actor(clone);
clone._destroyId = mutterWindow.connect('destroy', () => { this._removeThumbnail(clone); });
this._clones.push(clone);
}
// Make sure we only do this once
this._thumbnailBins = new Array();
}
},
_removeThumbnail: function(source, clone) {
for (let i = 0; i < this._clones.length; i++)
if (this._clones[i] == clone) {
this._clones.splice(i, 1);
this._windows.splice(i, 1);
this._labels.splice(i, 1);
this.removeItem(i);
if (this._clones.length > 0)
this.highlight(SwitcherPopup.mod(i, this._clones.length));
else
this.actor.destroy();
return;
}
},
_onDestroy: function() {
for (let i = 0; i < this._clones.length; i++) {
if (this._clones[i].source)
this._clones[i].source.disconnect(this._clones[i]._destroyId);
}
},
});
var WindowIcon = new Lang.Class({

View File

@ -418,6 +418,11 @@ var SwitcherList = new Lang.Class({
return bbox;
},
removeItem: function(index) {
let item = this._items.splice(index, 1);
item[0].destroy();
},
_onItemClicked: function (index) {
this._itemActivated(index);
},
@ -432,7 +437,7 @@ var SwitcherList = new Lang.Class({
},
highlight: function(index, justOutline) {
if (this._highlighted != -1) {
if (this._items[this._highlighted]) {
this._items[this._highlighted].remove_style_pseudo_class('outlined');
this._items[this._highlighted].remove_style_pseudo_class('selected');
}