f3acb27d61
While nautilus removed its desktop support a while ago in favor of an extension, it's still possible that some external X11 desktop icon app is used. As DESKTOP windows cannot be moved between workspaces or stacked, and aren't perceived as regular windows, it doesn't make sense to show them as previews in the workspace switcher. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/93
400 lines
13 KiB
JavaScript
400 lines
13 KiB
JavaScript
/* exported WorkspaceIndicator */
|
|
const { Clutter, Gio, GObject, Meta, St } = imports.gi;
|
|
|
|
const DND = imports.ui.dnd;
|
|
const Main = imports.ui.main;
|
|
const PanelMenu = imports.ui.panelMenu;
|
|
const PopupMenu = imports.ui.popupMenu;
|
|
|
|
const Gettext = imports.gettext.domain('gnome-shell-extensions');
|
|
const _ = Gettext.gettext;
|
|
|
|
let WindowPreview = GObject.registerClass(
|
|
class WindowPreview extends St.Button {
|
|
_init(window) {
|
|
super._init({
|
|
style_class: 'window-list-window-preview',
|
|
});
|
|
|
|
this._delegate = this;
|
|
DND.makeDraggable(this, { restoreOnSuccess: true });
|
|
|
|
this._window = window;
|
|
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
|
|
|
this._sizeChangedId = this._window.connect('size-changed',
|
|
this._relayout.bind(this));
|
|
this._positionChangedId = this._window.connect('position-changed',
|
|
this._relayout.bind(this));
|
|
this._minimizedChangedId = this._window.connect('notify::minimized',
|
|
this._relayout.bind(this));
|
|
this._monitorEnteredId = global.display.connect('window-entered-monitor',
|
|
this._relayout.bind(this));
|
|
this._monitorLeftId = global.display.connect('window-left-monitor',
|
|
this._relayout.bind(this));
|
|
|
|
// Do initial layout when we get a parent
|
|
let id = this.connect('parent-set', () => {
|
|
this.disconnect(id);
|
|
if (!this.get_parent())
|
|
return;
|
|
this._laterId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
|
|
this._laterId = 0;
|
|
this._relayout();
|
|
return false;
|
|
});
|
|
});
|
|
|
|
this._focusChangedId = global.display.connect('notify::focus-window',
|
|
this._onFocusChanged.bind(this));
|
|
this._onFocusChanged();
|
|
}
|
|
|
|
// needed for DND
|
|
get realWindow() {
|
|
return this._window.get_compositor_private();
|
|
}
|
|
|
|
_onDestroy() {
|
|
this._window.disconnect(this._sizeChangedId);
|
|
this._window.disconnect(this._positionChangedId);
|
|
this._window.disconnect(this._minimizedChangedId);
|
|
global.display.disconnect(this._monitorEnteredId);
|
|
global.display.disconnect(this._monitorLeftId);
|
|
global.display.disconnect(this._focusChangedId);
|
|
if (this._laterId)
|
|
Meta.later_remove(this._laterId);
|
|
}
|
|
|
|
_onFocusChanged() {
|
|
if (global.display.focus_window === this._window)
|
|
this.add_style_class_name('active');
|
|
else
|
|
this.remove_style_class_name('active');
|
|
}
|
|
|
|
_relayout() {
|
|
let monitor = Main.layoutManager.findIndexForActor(this);
|
|
this.visible = monitor === this._window.get_monitor() &&
|
|
this._window.window_type !== Meta.WindowType.DESKTOP &&
|
|
this._window.showing_on_its_workspace();
|
|
|
|
if (!this.visible)
|
|
return;
|
|
|
|
let workArea = Main.layoutManager.getWorkAreaForMonitor(monitor);
|
|
let hscale = this.get_parent().allocation.get_width() / workArea.width;
|
|
let vscale = this.get_parent().allocation.get_height() / workArea.height;
|
|
|
|
let frameRect = this._window.get_frame_rect();
|
|
this.set_size(
|
|
Math.round(Math.min(frameRect.width, workArea.width) * hscale),
|
|
Math.round(Math.min(frameRect.height, workArea.height) * vscale));
|
|
this.set_position(
|
|
Math.round(frameRect.x * hscale),
|
|
Math.round(frameRect.y * vscale));
|
|
}
|
|
});
|
|
|
|
let WorkspaceThumbnail = GObject.registerClass(
|
|
class WorkspaceThumbnail extends St.Button {
|
|
_init(index) {
|
|
super._init({
|
|
style_class: 'workspace',
|
|
child: new Clutter.Actor({
|
|
layout_manager: new Clutter.BinLayout(),
|
|
clip_to_allocation: true,
|
|
}),
|
|
x_fill: true,
|
|
y_fill: true,
|
|
});
|
|
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
|
|
|
this._index = index;
|
|
this._delegate = this; // needed for DND
|
|
|
|
this._windowPreviews = new Map();
|
|
|
|
let workspaceManager = global.workspace_manager;
|
|
this._workspace = workspaceManager.get_workspace_by_index(index);
|
|
|
|
this._windowAddedId = this._workspace.connect('window-added',
|
|
(ws, window) => {
|
|
this._addWindow(window);
|
|
});
|
|
this._windowRemovedId = this._workspace.connect('window-removed',
|
|
(ws, window) => {
|
|
this._removeWindow(window);
|
|
});
|
|
this._restackedId = global.display.connect('restacked',
|
|
this._onRestacked.bind(this));
|
|
|
|
this._workspace.list_windows().forEach(w => this._addWindow(w));
|
|
this._onRestacked();
|
|
}
|
|
|
|
acceptDrop(source) {
|
|
if (!source.realWindow)
|
|
return false;
|
|
|
|
let window = source.realWindow.get_meta_window();
|
|
this._moveWindow(window);
|
|
return true;
|
|
}
|
|
|
|
handleDragOver(source) {
|
|
if (source.realWindow)
|
|
return DND.DragMotionResult.MOVE_DROP;
|
|
else
|
|
return DND.DragMotionResult.CONTINUE;
|
|
}
|
|
|
|
_addWindow(window) {
|
|
if (this._windowPreviews.has(window))
|
|
return;
|
|
|
|
let preview = new WindowPreview(window);
|
|
preview.connect('clicked', (a, btn) => this.emit('clicked', btn));
|
|
this._windowPreviews.set(window, preview);
|
|
this.child.add_child(preview);
|
|
}
|
|
|
|
_removeWindow(window) {
|
|
let preview = this._windowPreviews.get(window);
|
|
if (!preview)
|
|
return;
|
|
|
|
this._windowPreviews.delete(window);
|
|
preview.destroy();
|
|
}
|
|
|
|
_onRestacked() {
|
|
let lastPreview = null;
|
|
let windows = global.get_window_actors().map(a => a.meta_window);
|
|
for (let i = 0; i < windows.length; i++) {
|
|
let preview = this._windowPreviews.get(windows[i]);
|
|
if (!preview)
|
|
continue;
|
|
|
|
this.child.set_child_above_sibling(preview, lastPreview);
|
|
lastPreview = preview;
|
|
}
|
|
}
|
|
|
|
_moveWindow(window) {
|
|
let monitorIndex = Main.layoutManager.findIndexForActor(this);
|
|
if (monitorIndex !== window.get_monitor())
|
|
window.move_to_monitor(monitorIndex);
|
|
window.change_workspace_by_index(this._index, false);
|
|
}
|
|
|
|
on_clicked() {
|
|
let ws = global.workspace_manager.get_workspace_by_index(this._index);
|
|
if (ws)
|
|
ws.activate(global.get_current_time());
|
|
}
|
|
|
|
_onDestroy() {
|
|
this._workspace.disconnect(this._windowAddedId);
|
|
this._workspace.disconnect(this._windowRemovedId);
|
|
global.display.disconnect(this._restackedId);
|
|
}
|
|
});
|
|
|
|
var WorkspaceIndicator = GObject.registerClass(
|
|
class WorkspaceIndicator extends PanelMenu.Button {
|
|
_init() {
|
|
super._init(0.0, _('Workspace Indicator'), true);
|
|
this.setMenu(new PopupMenu.PopupMenu(this, 0.0, St.Side.BOTTOM));
|
|
this.add_style_class_name('window-list-workspace-indicator');
|
|
this.menu.actor.remove_style_class_name('panel-menu');
|
|
|
|
let container = new St.Widget({
|
|
layout_manager: new Clutter.BinLayout(),
|
|
x_expand: true,
|
|
y_expand: true,
|
|
});
|
|
this.add_actor(container);
|
|
|
|
let workspaceManager = global.workspace_manager;
|
|
|
|
this._currentWorkspace = workspaceManager.get_active_workspace_index();
|
|
this._statusLabel = new St.Label({ text: this._getStatusText() });
|
|
|
|
this._statusBin = new St.Bin({
|
|
style_class: 'status-label-bin',
|
|
x_expand: true,
|
|
y_expand: true,
|
|
child: this._statusLabel,
|
|
});
|
|
container.add_actor(this._statusBin);
|
|
|
|
this._thumbnailsBox = new St.BoxLayout({
|
|
style_class: 'workspaces-box',
|
|
y_expand: true,
|
|
reactive: true,
|
|
});
|
|
this._thumbnailsBox.connect('scroll-event',
|
|
this._onScrollEvent.bind(this));
|
|
container.add_actor(this._thumbnailsBox);
|
|
|
|
this._workspacesItems = [];
|
|
|
|
this._workspaceManagerSignals = [
|
|
workspaceManager.connect('notify::n-workspaces',
|
|
this._nWorkspacesChanged.bind(this)),
|
|
workspaceManager.connect_after('workspace-switched',
|
|
this._onWorkspaceSwitched.bind(this)),
|
|
workspaceManager.connect('notify::layout-rows',
|
|
this._onWorkspaceOrientationChanged.bind(this)),
|
|
];
|
|
|
|
this.connect('scroll-event', this._onScrollEvent.bind(this));
|
|
this._updateMenu();
|
|
this._updateThumbnails();
|
|
this._onWorkspaceOrientationChanged();
|
|
|
|
this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' });
|
|
this._settingsChangedId = this._settings.connect(
|
|
'changed::workspace-names', this._updateMenuLabels.bind(this));
|
|
}
|
|
|
|
_onDestroy() {
|
|
for (let i = 0; i < this._workspaceManagerSignals.length; i++)
|
|
global.workspace_manager.disconnect(this._workspaceManagerSignals[i]);
|
|
|
|
if (this._settingsChangedId) {
|
|
this._settings.disconnect(this._settingsChangedId);
|
|
this._settingsChangedId = 0;
|
|
}
|
|
|
|
super._onDestroy();
|
|
}
|
|
|
|
_onWorkspaceOrientationChanged() {
|
|
let vertical = global.workspace_manager.layout_rows === -1;
|
|
this.reactive = vertical;
|
|
|
|
this._statusBin.visible = vertical;
|
|
this._thumbnailsBox.visible = !vertical;
|
|
}
|
|
|
|
_onWorkspaceSwitched() {
|
|
let workspaceManager = global.workspace_manager;
|
|
this._currentWorkspace = workspaceManager.get_active_workspace_index();
|
|
|
|
this._updateMenuOrnament();
|
|
this._updateActiveThumbnail();
|
|
|
|
this._statusLabel.set_text(this._getStatusText());
|
|
}
|
|
|
|
_nWorkspacesChanged() {
|
|
this._updateMenu();
|
|
this._updateThumbnails();
|
|
}
|
|
|
|
_updateMenuOrnament() {
|
|
for (let i = 0; i < this._workspacesItems.length; i++) {
|
|
this._workspacesItems[i].setOrnament(i === this._currentWorkspace
|
|
? PopupMenu.Ornament.DOT
|
|
: PopupMenu.Ornament.NONE);
|
|
}
|
|
}
|
|
|
|
_updateActiveThumbnail() {
|
|
let thumbs = this._thumbnailsBox.get_children();
|
|
for (let i = 0; i < thumbs.length; i++) {
|
|
if (i === this._currentWorkspace)
|
|
thumbs[i].add_style_class_name('active');
|
|
else
|
|
thumbs[i].remove_style_class_name('active');
|
|
}
|
|
}
|
|
|
|
_getStatusText() {
|
|
let workspaceManager = global.workspace_manager;
|
|
let current = workspaceManager.get_active_workspace_index();
|
|
let total = workspaceManager.n_workspaces;
|
|
|
|
return '%d / %d'.format(current + 1, total);
|
|
}
|
|
|
|
_updateMenuLabels() {
|
|
for (let i = 0; i < this._workspacesItems.length; i++) {
|
|
let item = this._workspacesItems[i];
|
|
let name = Meta.prefs_get_workspace_name(i);
|
|
item.label.text = name;
|
|
}
|
|
}
|
|
|
|
_updateMenu() {
|
|
let workspaceManager = global.workspace_manager;
|
|
|
|
this.menu.removeAll();
|
|
this._workspacesItems = [];
|
|
this._currentWorkspace = workspaceManager.get_active_workspace_index();
|
|
|
|
for (let i = 0; i < workspaceManager.n_workspaces; i++) {
|
|
let name = Meta.prefs_get_workspace_name(i);
|
|
let item = new PopupMenu.PopupMenuItem(name);
|
|
item.workspaceId = i;
|
|
|
|
item.connect('activate', () => {
|
|
this._activate(item.workspaceId);
|
|
});
|
|
|
|
if (i === this._currentWorkspace)
|
|
item.setOrnament(PopupMenu.Ornament.DOT);
|
|
|
|
this.menu.addMenuItem(item);
|
|
this._workspacesItems[i] = item;
|
|
}
|
|
|
|
this._statusLabel.set_text(this._getStatusText());
|
|
}
|
|
|
|
_updateThumbnails() {
|
|
let workspaceManager = global.workspace_manager;
|
|
|
|
this._thumbnailsBox.destroy_all_children();
|
|
|
|
for (let i = 0; i < workspaceManager.n_workspaces; i++) {
|
|
let thumb = new WorkspaceThumbnail(i);
|
|
this._thumbnailsBox.add_actor(thumb);
|
|
}
|
|
this._updateActiveThumbnail();
|
|
}
|
|
|
|
_activate(index) {
|
|
let workspaceManager = global.workspace_manager;
|
|
|
|
if (index >= 0 && index < workspaceManager.n_workspaces) {
|
|
let metaWorkspace = workspaceManager.get_workspace_by_index(index);
|
|
metaWorkspace.activate(global.get_current_time());
|
|
}
|
|
}
|
|
|
|
_onScrollEvent(actor, event) {
|
|
let direction = event.get_scroll_direction();
|
|
let diff = 0;
|
|
if (direction === Clutter.ScrollDirection.DOWN)
|
|
diff = 1;
|
|
else if (direction === Clutter.ScrollDirection.UP)
|
|
diff = -1;
|
|
else
|
|
return;
|
|
|
|
let newIndex = this._currentWorkspace + diff;
|
|
this._activate(newIndex);
|
|
}
|
|
|
|
_allocate(actor, box, flags) {
|
|
if (actor.get_n_children() > 0)
|
|
actor.get_first_child().allocate(box, flags);
|
|
}
|
|
});
|
|
|