From efe12e4dc84ee1a60bbf3e5ec89238d38c72a8ff Mon Sep 17 00:00:00 2001 From: rpm-build Date: Feb 04 2021 06:17:33 +0000 Subject: more-classic-classic-mode.patch patch_name: more-classic-classic-mode.patch present_in_specfile: true location_in_specfile: 7 --- diff --git a/data/classic.json.in b/data/classic.json.in index fdb3762..c1c0544 100644 --- a/data/classic.json.in +++ b/data/classic.json.in @@ -1,8 +1,9 @@ { "parentMode": "user", "stylesheetName": "gnome-classic.css", + "hasOverview": false, "enabledExtensions": [@CLASSIC_EXTENSIONS@], - "panel": { "left": ["activities", "appMenu"], + "panel": { "left": ["appMenu"], "center": [], "right": ["a11y", "keyboard", "dateMenu", "aggregateMenu"] } diff --git a/data/gnome-classic.scss b/data/gnome-classic.scss index 9e23506..9c0e06e 100644 --- a/data/gnome-classic.scss +++ b/data/gnome-classic.scss @@ -32,18 +32,20 @@ $variant: 'light'; font-weight: normal; color: $fg_color; text-shadow: none; + &:hover { + color: lighten($fg_color,10%); + text-shadow: none; + & .system-status-icon { icon-shadow: none; } + } &:active, &:overview, &:focus, &:checked { // Trick due to St limitations. It needs a background to draw // a box-shadow - background-color: $selected_bg_color !important; - color: $selected_fg_color !important; + background-color: $selected_bg_color; + color: $selected_fg_color; box-shadow: none; & > .system-status-icon { icon-shadow: none; } } - &:hover { - text-shadow: none; - & .system-status-icon { icon-shadow: none; } - } + .app-menu-icon { width: 0; height: 0; margin: 0; } // shell's display:none; :D .system-status-icon { diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js index cc399c6..3dbe43f 100644 --- a/extensions/apps-menu/extension.js +++ b/extensions/apps-menu/extension.js @@ -25,22 +25,6 @@ const NAVIGATION_REGION_OVERSHOOT = 50; Gio._promisify(Gio._LocalFilePrototype, 'query_info_async', 'query_info_finish'); Gio._promisify(Gio._LocalFilePrototype, 'set_attributes_async', 'set_attributes_finish'); -class ActivitiesMenuItem extends PopupMenu.PopupBaseMenuItem { - constructor(button) { - super(); - this._button = button; - let label = new St.Label({ text: _('Activities Overview') }); - this.actor.add_child(label); - this.actor.label_actor = label; - } - - activate(event) { - this._button.menu.toggle(); - Main.overview.toggle(); - super.activate(event); - } -} - class ApplicationMenuItem extends PopupMenu.PopupBaseMenuItem { constructor(button, app) { super(); @@ -82,6 +66,8 @@ class ApplicationMenuItem extends PopupMenu.PopupBaseMenuItem { this._button.selectCategory(null); this._button.menu.toggle(); super.activate(event); + + Main.overview.hide(); } setActive(active, params) { @@ -103,7 +89,9 @@ class ApplicationMenuItem extends PopupMenu.PopupBaseMenuItem { } _updateIcon() { - this._iconBin.set_child(this.getDragActor()); + let icon = this.getDragActor(); + icon.style_class = 'icon-dropshadow'; + this._iconBin.set_child(icon); } } @@ -233,28 +221,9 @@ class ApplicationsMenu extends PopupMenu.PopupMenu { return false; } - open(animate) { - this._button.hotCorner.setBarrierSize(0); - if (this._button.hotCorner.actor) // fallback corner - this._button.hotCorner.actor.hide(); - super.open(animate); - } - - close(animate) { - let size = Main.layoutManager.panelBox.height; - this._button.hotCorner.setBarrierSize(size); - if (this._button.hotCorner.actor) // fallback corner - this._button.hotCorner.actor.show(); - super.close(animate); - } - toggle() { - if (this.isOpen) { + if (this.isOpen) this._button.selectCategory(null); - } else { - if (Main.overview.visible) - Main.overview.hide(); - } super.toggle(); } } @@ -381,7 +350,7 @@ Signals.addSignalMethods(DesktopTarget.prototype); let ApplicationsButton = GObject.registerClass( class ApplicationsButton extends PanelMenu.Button { - _init() { + _init(includeIcon) { super._init(1.0, null, false); this.setMenu(new ApplicationsMenu(this, 1.0, St.Side.TOP, this)); @@ -398,7 +367,8 @@ class ApplicationsButton extends PanelMenu.Button { '/usr/share/icons/hicolor/scalable/apps/start-here.svg'); this._icon = new St.Icon({ gicon: new Gio.FileIcon({ file: iconFile }), - style_class: 'panel-logo-icon' + style_class: 'panel-logo-icon', + visible: includeIcon }); hbox.add_actor(this._icon); @@ -414,8 +384,6 @@ class ApplicationsButton extends PanelMenu.Button { this.name = 'panelApplications'; this.label_actor = this._label; - this.connect('captured-event', this._onCapturedEvent.bind(this)); - this._showingId = Main.overview.connect('showing', () => { this.add_accessible_state (Atk.StateType.CHECKED); }); @@ -457,10 +425,6 @@ class ApplicationsButton extends PanelMenu.Button { } } - get hotCorner() { - return Main.layoutManager.hotCorners[Main.layoutManager.primaryIndex]; - } - _createVertSeparator() { let separator = new St.DrawingArea({ style_class: 'calendar-vertical-separator', @@ -487,14 +451,6 @@ class ApplicationsButton extends PanelMenu.Button { this._desktopTarget.destroy(); } - _onCapturedEvent(actor, event) { - if (event.type() == Clutter.EventType.BUTTON_PRESS) { - if (!Main.overview.shouldToggleByCornerOrButton()) - return true; - } - return false; - } - _onMenuKeyPress(actor, event) { let symbol = event.get_key_symbol(); if (symbol == Clutter.KEY_Left || symbol == Clutter.KEY_Right) { @@ -638,14 +594,6 @@ class ApplicationsButton extends PanelMenu.Button { y_align: St.Align.START }); - let activities = new ActivitiesMenuItem(this); - this.leftBox.add(activities.actor, { - expand: false, - x_fill: true, - y_fill: false, - y_align: St.Align.START - }); - this.applicationsBox = new St.BoxLayout({ vertical: true }); this.applicationsScrollBox.add_actor(this.applicationsBox); this.categoriesBox = new St.BoxLayout({ vertical: true }); @@ -757,19 +705,16 @@ class ApplicationsButton extends PanelMenu.Button { }); let appsMenuButton; -let activitiesButton; function enable() { - activitiesButton = Main.panel.statusArea['activities']; - activitiesButton.container.hide(); - appsMenuButton = new ApplicationsButton(); - Main.panel.addToStatusArea('apps-menu', appsMenuButton, 1, 'left'); + let index = Main.sessionMode.panel.left.indexOf('activities') + 1; + appsMenuButton = new ApplicationsButton(index == 0); + Main.panel.addToStatusArea('apps-menu', appsMenuButton, index, 'left'); } function disable() { Main.panel.menuManager.removeMenu(appsMenuButton.menu); appsMenuButton.destroy(); - activitiesButton.container.show(); } function init() { diff --git a/extensions/horizontal-workspaces/extension.js b/extensions/horizontal-workspaces/extension.js new file mode 100644 index 0000000..b3937ce --- /dev/null +++ b/extensions/horizontal-workspaces/extension.js @@ -0,0 +1,18 @@ +/* exported enable disable */ +const { Meta } = imports.gi; + +function enable() { + global.workspace_manager.override_workspace_layout( + Meta.DisplayCorner.TOPLEFT, + false, + 1, + -1); +} + +function disable() { + global.workspace_manager.override_workspace_layout( + Meta.DisplayCorner.TOPLEFT, + false, + -1, + 1); +} diff --git a/extensions/horizontal-workspaces/meson.build b/extensions/horizontal-workspaces/meson.build new file mode 100644 index 0000000..48504f6 --- /dev/null +++ b/extensions/horizontal-workspaces/meson.build @@ -0,0 +1,5 @@ +extension_data += configure_file( + input: metadata_name + '.in', + output: metadata_name, + configuration: metadata_conf +) diff --git a/extensions/horizontal-workspaces/metadata.json.in b/extensions/horizontal-workspaces/metadata.json.in new file mode 100644 index 0000000..f109e06 --- /dev/null +++ b/extensions/horizontal-workspaces/metadata.json.in @@ -0,0 +1,10 @@ +{ +"extension-id": "@extension_id@", +"uuid": "@uuid@", +"settings-schema": "@gschemaname@", +"gettext-domain": "@gettext_domain@", +"name": "Horizontal workspaces", +"description": "Use a horizontal workspace layout", +"shell-version": [ "@shell_current@" ], +"url": "@url@" +} diff --git a/extensions/horizontal-workspaces/stylesheet.css b/extensions/horizontal-workspaces/stylesheet.css new file mode 100644 index 0000000..25134b6 --- /dev/null +++ b/extensions/horizontal-workspaces/stylesheet.css @@ -0,0 +1 @@ +/* This extensions requires no special styling */ diff --git a/extensions/places-menu/extension.js b/extensions/places-menu/extension.js index c477a4a..5c038ae 100644 --- a/extensions/places-menu/extension.js +++ b/extensions/places-menu/extension.js @@ -135,9 +135,9 @@ let _indicator; function enable() { _indicator = new PlacesMenu; - let pos = 1; + let pos = Main.sessionMode.panel.left.indexOf('appMenu'); if ('apps-menu' in Main.panel.statusArea) - pos = 2; + pos++; Main.panel.addToStatusArea('places-menu', _indicator, pos, 'left'); } diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css index f3c44a3..7079d3e 100644 --- a/extensions/window-list/classic.css +++ b/extensions/window-list/classic.css @@ -4,36 +4,39 @@ border-top-width: 1px; border-bottom-width: 0px; height: 2.25em ; + padding: 2px; } - .bottom-panel .window-button > StWidget { - background-gradient-drection: vertical; - background-color: #fff; - background-gradient-start: #fff; - background-gradient-end: #eee; - color: #000; - -st-natural-width: 18.7em; - max-width: 18.75em; + .bottom-panel .window-button > StWidget, + .bottom-panel .window-picker-toggle > StWidget { color: #2e3436; background-color: #eee; - border-radius: 2px; + border-radius: 3px; padding: 3px 6px 1px; - box-shadow: inset -1px -1px 1px rgba(0,0,0,0.5); - text-shadow: 0 0 transparent; + box-shadow: none; + text-shadow: none; + border: 1px solid rgba(0,0,0,0.2); + } + + .bottom-panel .window-button > StWidget { + -st-natural-width: 18.7em; + max-width: 18.75em; } - .bottom-panel .window-button:hover > StWidget { + .bottom-panel .window-button:hover > StWidget, + .bottom-panel .window-picker-toggle:hover > StWidget { background-color: #f9f9f9; } .bottom-panel .window-button:active > StWidget, .bottom-panel .window-button:focus > StWidget { - box-shadow: inset 1px 1px 2px rgba(0,0,0,0.5); + box-shadow: inset 0 1px 3px rgba(0,0,0,0.1); } - .bottom-panel .window-button.focused > StWidget { - background-color: #ddd; - box-shadow: inset 1px 1px 1px rgba(0,0,0,0.5); + .bottom-panel .window-button.focused > StWidget, + .bottom-panel .window-picker-toggle:checked > StWidget { + background-color: #ccc; + box-shadow: inset 0 1px 3px rgba(0,0,0,0.1); } .bottom-panel .window-button.focused:hover > StWidget { @@ -42,5 +45,24 @@ .bottom-panel .window-button.minimized > StWidget { color: #888; - box-shadow: inset -1px -1px 1px rgba(0,0,0,0.5); + box-shadow: none; } + +/* workspace switcher */ +.window-list-workspace-indicator .workspace { + background-color: #ddd; +} + +.window-list-workspace-indicator .workspace.active { + background-color: #ccc; +} + +.window-list-window-preview { + background-color: #ededed; + border: 1px solid #ccc; +} + +.window-list-window-preview.active { + background-color: #f6f5f4; + border: 2px solid #888; +} diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js index e1ea742..1f854aa 100644 --- a/extensions/window-list/extension.js +++ b/extensions/window-list/extension.js @@ -1,13 +1,16 @@ /* exported init */ -const { Clutter, Gio, GLib, GObject, Gtk, Meta, Shell, St } = imports.gi; +const { Clutter, Gio, GLib, Gtk, Meta, Shell, St } = imports.gi; const DND = imports.ui.dnd; const Main = imports.ui.main; -const PanelMenu = imports.ui.panelMenu; +const Overview = imports.ui.overview; const PopupMenu = imports.ui.popupMenu; +const Tweener = imports.ui.tweener; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); +const { WindowPicker, WindowPickerToggle } = Me.imports.windowPicker; +const { WorkspaceIndicator } = Me.imports.workspaceIndicator; const Gettext = imports.gettext.domain('gnome-shell-extensions'); const _ = Gettext.gettext; @@ -644,132 +647,6 @@ class AppButton extends BaseButton { } -let 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(), - x_align: Clutter.ActorAlign.CENTER, - y_align: Clutter.ActorAlign.CENTER - }); - container.add_actor(this.statusLabel); - - this.workspacesItems = []; - - this._workspaceManagerSignals = []; - this._workspaceManagerSignals.push(workspaceManager.connect('notify::n-workspaces', - this._updateMenu.bind(this))); - this._workspaceManagerSignals.push(workspaceManager.connect_after('workspace-switched', - this._updateIndicator.bind(this))); - - this.connect('scroll-event', this._onScrollEvent.bind(this)); - this._updateMenu(); - - this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' }); - this._settingsChangedId = - this._settings.connect('changed::workspace-names', - this._updateMenu.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(); - } - - _updateIndicator() { - this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); - this._currentWorkspace = global.workspace_manager.get_active_workspace().index(); - this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); - - this.statusLabel.set_text(this._getStatusText()); - } - - _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); - } - - _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', (item, _event) => { - 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()); - } - - _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); - } -}); - class WindowList { constructor(perMonitor, monitor) { this._perMonitor = perMonitor; @@ -787,6 +664,12 @@ class WindowList { let box = new St.BoxLayout({ x_expand: true, y_expand: true }); this.actor.add_actor(box); + let toggle = new WindowPickerToggle(); + box.add_actor(toggle); + + toggle.connect('notify::checked', + this._updateWindowListVisibility.bind(this)); + let layout = new Clutter.BoxLayout({ homogeneous: true }); this._windowList = new St.Widget({ style_class: 'window-list', @@ -936,6 +819,19 @@ class WindowList { this._workspaceIndicator.actor.visible = hasWorkspaces && workspacesOnMonitor; } + _updateWindowListVisibility() { + let visible = !Main.windowPicker.visible; + + Tweener.addTween(this._windowList, { + opacity: visible ? 255 : 0, + transition: 'ease-out-quad', + time: Overview.ANIMATION_TIME + }); + + this._windowList.reactive = visible; + this._windowList.get_children().forEach(c => c.reactive = visible); + } + _getPreferredUngroupedWindowListWidth() { if (this._windowList.get_n_children() == 0) return this._windowList.get_preferred_width(-1)[1]; @@ -1206,7 +1102,7 @@ class WindowList { class Extension { constructor() { this._windowLists = null; - this._injections = {}; + this._hideOverviewOrig = Main.overview.hide; } enable() { @@ -1221,6 +1117,13 @@ class Extension { Main.layoutManager.connect('monitors-changed', this._buildWindowLists.bind(this)); + Main.windowPicker = new WindowPicker(); + + Main.overview.hide = () => { + Main.windowPicker.close(); + this._hideOverviewOrig.call(Main.overview); + }; + this._buildWindowLists(); } @@ -1251,6 +1154,11 @@ class Extension { windowList.actor.destroy(); }); this._windowLists = null; + + Main.windowPicker.actor.destroy(); + delete Main.windowPicker; + + Main.overview.hide = this._hideOverviewOrig; } someWindowListContains(actor) { diff --git a/extensions/window-list/meson.build b/extensions/window-list/meson.build index b4aa4db..34d7c3f 100644 --- a/extensions/window-list/meson.build +++ b/extensions/window-list/meson.build @@ -4,7 +4,7 @@ extension_data += configure_file( configuration: metadata_conf ) -extension_sources += files('prefs.js') +extension_sources += files('prefs.js', 'windowPicker.js', 'workspaceIndicator.js') extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') if classic_mode_enabled diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css index f5285cb..79d56ba 100644 --- a/extensions/window-list/stylesheet.css +++ b/extensions/window-list/stylesheet.css @@ -26,9 +26,8 @@ spacing: 4px; } -.window-button > StWidget { - -st-natural-width: 18.75em; - max-width: 18.75em; +.window-button > StWidget, +.window-picker-toggle > StWidget { color: #bbb; background-color: black; border-radius: 4px; @@ -37,7 +36,21 @@ text-shadow: 1px 1px 4px rgba(0,0,0,0.8); } -.window-button:hover > StWidget { +.window-picker-toggle { + padding: 3px; +} + +.window-picker-toggle > StWidet { + border: 1px solid rgba(255,255,255,0.3); +} + +.window-button > StWidget { + -st-natural-width: 18.75em; + max-width: 18.75em; +} + +.window-button:hover > StWidget, +.window-picker-toggle:hover > StWidget { color: white; background-color: #1f1f1f; } @@ -47,12 +60,14 @@ box-shadow: inset 2px 2px 4px rgba(255,255,255,0.5); } -.window-button.focused > StWidget { +.window-button.focused > StWidget, +.window-picker-toggle:checked > StWidget { color: white; box-shadow: inset 1px 1px 4px rgba(255,255,255,0.7); } -.window-button.focused:active > StWidget { +.window-button.focused:active > StWidget, +.window-picker-toggle:checked:active > StWidget { box-shadow: inset 2px 2px 4px rgba(255,255,255,0.7); } @@ -70,13 +85,50 @@ height: 24px; } -.window-list-workspace-indicator { +.window-list-workspace-indicator .status-label-bin { + background-color: rgba(200, 200, 200, .3); + border: 1px solid #cccccc; + padding: 0 3px; + margin: 3px 0; +} + +.window-list-workspace-indicator .workspaces-box { + spacing: 3px; padding: 3px; } -.window-list-workspace-indicator > StWidget { - background-color: rgba(200, 200, 200, .3); +.window-list-workspace-indicator .workspace { border: 1px solid #cccccc; + width: 52px; +} + +.window-list-workspace-indicator .workspace:first-child:last-child:ltr, +.window-list-workspace-indicator .workspace:first-child:last-child:rtl { + border-radius: 4px; +} + +.window-list-workspace-indicator .workspace:first-child:ltr, +.window-list-workspace-indicator .workspace:last-child:rtl { + border-radius: 4px 0 0 4px; +} + +.window-list-workspace-indicator .workspace:first-child:rtl, +.window-list-workspace-indicator .workspace:last-child:ltr { + border-radius: 0 4px 4px 0; +} + +.window-list-workspace-indicator .workspace.active { + background-color: rgba(200, 200, 200, .3); +} + +.window-list-window-preview { + background-color: #252525; + border: 1px solid #ccc; +} + +.window-list-window-preview.active { + background-color: #353535; + border: 2px solid #ccc; } .notification { diff --git a/extensions/window-list/windowPicker.js b/extensions/window-list/windowPicker.js new file mode 100644 index 0000000..12a7627 --- /dev/null +++ b/extensions/window-list/windowPicker.js @@ -0,0 +1,274 @@ +/* exported WindowPicker, WindowPickerToggle */ +const { Clutter, GLib, GObject, Meta, Shell, St } = imports.gi; +const Signals = imports.signals; + +const Layout = imports.ui.layout; +const Main = imports.ui.main; +const Overview = imports.ui.overview; +const { WorkspacesDisplay } = imports.ui.workspacesView; + +let MyWorkspacesDisplay = class extends WorkspacesDisplay { + constructor() { + super(); + + this.actor.add_constraint( + new Layout.MonitorConstraint({ + primary: true, + work_area: true + })); + + this.actor.connect('destroy', this._onDestroy.bind(this)); + + this._workareasChangedId = global.display.connect('workareas-changed', + this._onWorkAreasChanged.bind(this)); + this._onWorkAreasChanged(); + } + + show(...args) { + if (this._scrollEventId == 0) + this._scrollEventId = Main.windowPicker.connect('scroll-event', + this._onScrollEvent.bind(this)); + + super.show(...args); + } + + hide(...args) { + if (this._scrollEventId > 0) + Main.windowPicker.disconnect(this._scrollEventId); + this._scrollEventId = 0; + + super.hide(...args); + } + + _onWorkAreasChanged() { + let { primaryIndex } = Main.layoutManager; + let workarea = Main.layoutManager.getWorkAreaForMonitor(primaryIndex); + this.setWorkspacesFullGeometry(workarea); + } + + _updateWorkspacesViews() { + super._updateWorkspacesViews(); + + this._workspacesViews.forEach(v => { + Main.layoutManager.overviewGroup.remove_actor(v.actor); + Main.windowPicker.actor.add_actor(v.actor); + }); + } + + _onDestroy() { + if (this._workareasChangedId) + global.display.disconnect(this._workareasChangedId); + this._workareasChangedId = 0; + } +}; + +var WindowPicker = class { + constructor() { + this._visible = false; + this._modal = false; + + this._overlayKeyId = 0; + this._stageKeyPressId = 0; + + this.actor = new Clutter.Actor(); + + this.actor.connect('destroy', this._onDestroy.bind(this)); + + global.bind_property('screen-width', + this.actor, 'width', + GObject.BindingFlags.SYNC_CREATE); + global.bind_property('screen-height', + this.actor, 'height', + GObject.BindingFlags.SYNC_CREATE); + + this._backgroundGroup = new Meta.BackgroundGroup({ reactive: true }); + this.actor.add_child(this._backgroundGroup); + + this._backgroundGroup.connect('scroll-event', (a, ev) => { + this.emit('scroll-event', ev); + }); + + // Trick WorkspacesDisplay constructor into adding actions here + let addActionOrig = Main.overview.addAction; + Main.overview.addAction = a => this._backgroundGroup.add_action(a); + + this._workspacesDisplay = new MyWorkspacesDisplay(); + this.actor.add_child(this._workspacesDisplay.actor); + + Main.overview.addAction = addActionOrig; + + this._bgManagers = []; + + this._monitorsChangedId = Main.layoutManager.connect('monitors-changed', + this._updateBackgrounds.bind(this)); + this._updateBackgrounds(); + + Main.uiGroup.insert_child_below(this.actor, global.window_group); + + if (!Main.sessionMode.hasOverview) { + this._overlayKeyId = global.display.connect('overlay-key', () => { + if (!this._visible) + this.open(); + else + this.close(); + }); + } + } + + get visible() { + return this._visible; + } + + open() { + if (this._visible) + return; + + this._visible = true; + + if (!this._syncGrab()) + return; + + this._fakeOverviewVisible(true); + this._shadeBackgrounds(); + this._fakeOverviewAnimation(); + this._workspacesDisplay.show(false); + + this._stageKeyPressId = global.stage.connect('key-press-event', + (a, event) => { + let sym = event.get_key_symbol(); + if (sym == Clutter.KEY_Escape) { + this.close(); + return Clutter.EVENT_STOP; + } + return Clutter.EVENT_PROPAGATE; + }); + + this.emit('open-state-changed', this._visible); + } + + close() { + if (!this._visible) + return; + + this._visible = false; + + if (!this._syncGrab()) + return; + + this._workspacesDisplay.animateFromOverview(false); + this._unshadeBackgrounds(); + this._fakeOverviewAnimation(() => { + this._workspacesDisplay.hide(); + this._fakeOverviewVisible(false); + }); + + global.stage.disconnect(this._stageKeyPressId); + this._stageKeyPressId = 0; + + this.emit('open-state-changed', this._visible); + } + + _fakeOverviewAnimation(onComplete) { + Main.overview.animationInProgress = true; + GLib.timeout_add( + GLib.PRIORITY_DEFAULT, + Overview.ANIMATION_TIME * 1000, + () => { + Main.overview.animationInProgress = false; + if (onComplete) + onComplete(); + }); + } + + _fakeOverviewVisible(visible) { + // Fake overview state for WorkspacesDisplay + Main.overview.visible = visible; + + // Hide real windows + Main.layoutManager._inOverview = visible; + Main.layoutManager._updateVisibility(); + } + + _syncGrab() { + if (this._visible) { + if (this._modal) + return true; + + this._modal = Main.pushModal(this.actor, { + actionMode: Shell.ActionMode.OVERVIEW + }); + + if (!this._modal) { + this.hide(); + return false; + } + } else if (this._modal) { + Main.popModal(this.actor); + this._modal = false; + } + return true; + } + + _onDestroy() { + if (this._monitorsChangedId) + Main.layoutManager.disconnect(this._monitorsChangedId); + this._monitorsChangedId = 0; + + if (this._overlayKeyId) + global.display.disconnect(this._overlayKeyId); + this._overlayKeyId = 0; + + if (this._stageKeyPressId) + global.stage.disconnect(this._stageKeyPressId); + this._stageKeyPressId = 0; + } + + _updateBackgrounds() { + Main.overview._updateBackgrounds.call(this); + } + + _shadeBackgrounds() { + Main.overview._shadeBackgrounds.call(this); + } + + _unshadeBackgrounds() { + Main.overview._unshadeBackgrounds.call(this); + } +}; +Signals.addSignalMethods(WindowPicker.prototype); + +var WindowPickerToggle = GObject.registerClass( +class WindowPickerToggle extends St.Button { + _init() { + let iconBin = new St.Widget({ + layout_manager: new Clutter.BinLayout() + }); + iconBin.add_child(new St.Icon({ + icon_name: 'focus-windows-symbolic', + icon_size: 16, + x_expand: true, + y_expand: true, + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.CENTER + })); + super._init({ + style_class: 'window-picker-toggle', + child: iconBin, + visible: !Main.sessionMode.hasOverview, + x_fill: true, + y_fill: true, + toggle_mode: true + }); + + this.connect('notify::checked', () => { + if (this.checked) + Main.windowPicker.open(); + else + Main.windowPicker.close(); + }); + + Main.windowPicker.connect('open-state-changed', () => { + this.checked = Main.windowPicker.visible; + }); + } +}); diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js new file mode 100644 index 0000000..ca47611 --- /dev/null +++ b/extensions/window-list/workspaceIndicator.js @@ -0,0 +1,403 @@ +/* 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({ + GTypeName: 'WindowListWindowPreview' +}, 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.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({ + GTypeName: 'WindowListWorkspaceThumbnail' +}, 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); + } + + // eslint-disable-next-line camelcase + 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({ + GTypeName: 'WindowListWorkspaceIndicator' +}, 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', (item, _event) => { + 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); + } +}); + diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js index 3be1268..69eef88 100644 --- a/extensions/workspace-indicator/extension.js +++ b/extensions/workspace-indicator/extension.js @@ -2,56 +2,269 @@ /* exported init enable disable */ const { Clutter, Gio, GObject, Meta, St } = imports.gi; + +const DND = imports.ui.dnd; +const ExtensionUtils = imports.misc.extensionUtils; +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; -const Main = imports.ui.main; - -const ExtensionUtils = imports.misc.extensionUtils; - const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences'; const WORKSPACE_KEY = 'workspace-names'; +let WindowPreview = GObject.registerClass({ + GTypeName: 'WorkspaceIndicatorWindowPreview' +}, class WindowPreview extends St.Button { + _init(window) { + super._init({ + style_class: 'workspace-indicator-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.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({ + GTypeName: 'WorkspaceIndicatorWorkspaceThumbnail' +}, 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); + } + + // eslint-disable-next-line camelcase + 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); + } +}); + let WorkspaceIndicator = GObject.registerClass( class WorkspaceIndicator extends PanelMenu.Button { _init() { super._init(0.0, _('Workspace Indicator')); + 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({ + this._currentWorkspace = workspaceManager.get_active_workspace_index(); + this._statusLabel = new St.Label({ + style_class: 'panel-workspace-indicator', y_align: Clutter.ActorAlign.CENTER, text: this._labelText() }); - this.add_actor(this.statusLabel); + container.add_actor(this._statusLabel); + + this._thumbnailsBox = new St.BoxLayout({ + style_class: 'panel-workspace-indicator-box', + y_expand: true, + reactive: true + }); + + container.add_actor(this._thumbnailsBox); - this.workspacesItems = []; + this._workspacesItems = []; this._workspaceSection = new PopupMenu.PopupMenuSection(); this.menu.addMenuItem(this._workspaceSection); - this._workspaceManagerSignals = []; - this._workspaceManagerSignals.push(workspaceManager.connect_after('workspace-added', - this._createWorkspacesSection.bind(this))); - this._workspaceManagerSignals.push(workspaceManager.connect_after('workspace-removed', - this._createWorkspacesSection.bind(this))); - this._workspaceManagerSignals.push(workspaceManager.connect_after('workspace-switched', - this._updateIndicator.bind(this))); + this._workspaceManagerSignals = [ + workspaceManager.connect_after('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._thumbnailsBox.connect('scroll-event', this._onScrollEvent.bind(this)); this._createWorkspacesSection(); - - //styling - this.statusLabel.add_style_class_name('panel-workspace-indicator'); + this._updateThumbnails(); + this._onWorkspaceOrientationChanged(); this._settings = new Gio.Settings({ schema_id: WORKSPACE_SCHEMA }); - this._settingsChangedId = - this._settings.connect(`changed::${WORKSPACE_KEY}`, - this._createWorkspacesSection.bind(this)); + this._settingsChangedId = this._settings.connect( + `changed::${WORKSPACE_KEY}`, + this._updateMenuLabels.bind(this)); } _onDestroy() { @@ -63,15 +276,55 @@ class WorkspaceIndicator extends PanelMenu.Button { this._settingsChangedId = 0; } + Main.panel.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS); + super._onDestroy(); } - _updateIndicator() { - this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); - this._currentWorkspace = global.workspace_manager.get_active_workspace().index(); - this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); + _onWorkspaceOrientationChanged() { + let vertical = global.workspace_manager.layout_rows == -1; + this.reactive = vertical; - this.statusLabel.set_text(this._labelText()); + this._statusLabel.visible = vertical; + this._thumbnailsBox.visible = !vertical; + + // Disable offscreen-redirect when showing the workspace switcher + // so that clip-to-allocation works + Main.panel.set_offscreen_redirect(vertical + ? Clutter.OffscreenRedirect.ALWAYS + : Clutter.OffscreenRedirect.AUTOMATIC_FOR_OPACITY); + } + + _onWorkspaceSwitched() { + this._currentWorkspace = global.workspace_manager.get_active_workspace_index(); + + this._updateMenuOrnament(); + this._updateActiveThumbnail(); + + this._statusLabel.set_text(this._labelText()); + } + + _nWorkspacesChanged() { + this._createWorkspacesSection(); + 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'); + } } _labelText(workspaceIndex) { @@ -82,34 +335,51 @@ class WorkspaceIndicator extends PanelMenu.Button { return Meta.prefs_get_workspace_name(workspaceIndex); } + _updateMenuLabels() { + for (let i = 0; i < this._workspacesItems.length; i++) + this._workspacesItems[i].label.text = this._labelText(i); + } + _createWorkspacesSection() { let workspaceManager = global.workspace_manager; this._workspaceSection.removeAll(); - this.workspacesItems = []; - this._currentWorkspace = workspaceManager.get_active_workspace().index(); + this._workspacesItems = []; + this._currentWorkspace = workspaceManager.get_active_workspace_index(); let i = 0; for (; i < workspaceManager.n_workspaces; i++) { - this.workspacesItems[i] = new PopupMenu.PopupMenuItem(this._labelText(i)); - this._workspaceSection.addMenuItem(this.workspacesItems[i]); - this.workspacesItems[i].workspaceId = i; - this.workspacesItems[i].label_actor = this.statusLabel; - this.workspacesItems[i].connect('activate', (actor, _event) => { + this._workspacesItems[i] = new PopupMenu.PopupMenuItem(this._labelText(i)); + this._workspaceSection.addMenuItem(this._workspacesItems[i]); + this._workspacesItems[i].workspaceId = i; + this._workspacesItems[i].label_actor = this._statusLabel; + this._workspacesItems[i].connect('activate', (actor, _event) => { this._activate(actor.workspaceId); }); if (i == this._currentWorkspace) - this.workspacesItems[i].setOrnament(PopupMenu.Ornament.DOT); + this._workspacesItems[i].setOrnament(PopupMenu.Ornament.DOT); } - this.statusLabel.set_text(this._labelText()); + this._statusLabel.set_text(this._labelText()); + } + + _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) { + if (index >= 0 && index < workspaceManager.n_workspaces) { let metaWorkspace = workspaceManager.get_workspace_by_index(index); metaWorkspace.activate(global.get_current_time()); } @@ -126,7 +396,7 @@ class WorkspaceIndicator extends PanelMenu.Button { return; } - let newIndex = global.workspace_manager.get_active_workspace().index() + diff; + let newIndex = global.workspace_manager.get_active_workspace_index() + diff; this._activate(newIndex); } }); diff --git a/extensions/workspace-indicator/stylesheet.css b/extensions/workspace-indicator/stylesheet.css index 1271f1c..8c101e7 100644 --- a/extensions/workspace-indicator/stylesheet.css +++ b/extensions/workspace-indicator/stylesheet.css @@ -1,5 +1,37 @@ .panel-workspace-indicator { padding: 0 8px; - background-color: rgba(200, 200, 200, .5); border: 1px solid #cccccc; } + +.panel-workspace-indicator-box { + padding: 2px 0; +} + +.panel-workspace-indicator-box .workspace { + border: 1px solid #cccccc; + width: 48px; +} + +.panel-workspace-indicator, +.panel-workspace-indicator-box .workspace.active { + background-color: rgba(200, 200, 200, .5); +} + +.panel-workspace-indicator-box .workspace { + background-color: rgba(200, 200, 200, .3); + border-left-width: 0; +} + +.panel-workspace-indicator-box .workspace:first-child { + border-left-width: 1px; +} + +.workspace-indicator-window-preview { + background-color: #252525; + border: 1px solid #ccc; +} + +.workspace-indicator-window-preview { + background-color: #353535; + border: 2px solid #ccc; +} diff --git a/meson.build b/meson.build index 32743ed..23bd5ad 100644 --- a/meson.build +++ b/meson.build @@ -34,6 +34,7 @@ uuid_suffix = '@gnome-shell-extensions.gcampax.github.com' classic_extensions = [ 'apps-menu', 'desktop-icons', + 'horizontal-workspaces', 'places-menu', 'launch-new-instance', 'top-icons',