├── LICENSE ├── Makefile ├── README.md ├── assets ├── dots_line.png └── screenshot.png ├── extension.js ├── metadata.json ├── prefs.js ├── schemas ├── gschemas.compiled └── org.gnome.shell.extensions.horizontal-workspace-indicator.gschema.xml └── stylesheet.css /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2022 Roman Suvorov (tty2) and contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ##@ Deploy 2 | zip: compile-schemas ## Make zip file for deploy to https://extensions.gnome.org/upload/. 3 | @echo -e "\033[2m→ Making zip file...\033[0m" 4 | zip horizontal-workspace-indicatortty2.io.zip metadata.json extension.js stylesheet.css prefs.js \ 5 | LICENSE \ 6 | schemas/gschemas.compiled \ 7 | schemas/org.gnome.shell.extensions.horizontal-workspace-indicator.gschema.xml 8 | 9 | compile-schemas: ## Compile all in schemas folder 10 | glib-compile-schemas --strict schemas/ 11 | 12 | install: compile-schemas ## Copy to extensions directory for debug. 13 | cp -r * ~/.local/share/gnome-shell/extensions/horizontal-workspace-indicator@tty2.io 14 | 15 | ##@ Other 16 | #------------------------------------------------------------------------------ 17 | help: ## Display help 18 | @awk 'BEGIN {FS = ":.*##"; printf "Usage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 19 | #------------- -------------- 20 | 21 | .DEFAULT_GOAL := help 22 | .PHONY: help zip compile-schemas install 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | With gnome 45 the gnome community get a new awesome builtin workspace switcher. This extension does not have any reason to exist. 2 | 3 | # Workspace indicator 4 | [](https://extensions.gnome.org/extension/3952/workspace-indicator/) 5 | 6 | 7 | Horizontal workspace indicator shows the amount of opened workspaces and highlights the current one using unicode characters. 8 | You can use it as an indicator only but widget is clickable. Left button click: move to left, right click: move right. Middle click calls overview. 9 | 10 | ## Compatibility 11 | 12 | This extension is written for Wayland. 13 | 14 | 15 | ## How it looks like 16 | 17 | Example with for opened workspaces where the second one is current: 18 | 19 | Circle symbols: 20 | 21 | ○●○○ 22 | 23 | Line symbols: 24 | 25 | ǀ┃ǀǀ 26 | 27 | Screenshot with the same situation: 28 | 29 | ![screenshot](assets/screenshot.png) 30 | 31 | Three dots on the left side of the top bar. 32 | 33 | 34 | ## Known issues 35 | 36 | 1. If your indicator looks different from one on screen shot, install DejaVu Sans or Ubuntu font. 37 | 38 | 2. There could be an error with the extension after update. The solution is to logout and login again. 39 | -------------------------------------------------------------------------------- /assets/dots_line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tty2/horizontal-workspace-indicator/a18fe8668bc279ffe5b157d160b5a820288efdca/assets/dots_line.png -------------------------------------------------------------------------------- /assets/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tty2/horizontal-workspace-indicator/a18fe8668bc279ffe5b157d160b5a820288efdca/assets/screenshot.png -------------------------------------------------------------------------------- /extension.js: -------------------------------------------------------------------------------- 1 | const { Clutter, St, GObject, Pango } = imports.gi; 2 | const Main = imports.ui.main; 3 | const PanelMenu = imports.ui.panelMenu; 4 | 5 | const ExtensionUtils = imports.misc.extensionUtils; 6 | 7 | const symbolsMap = { 8 | circles: { 9 | active: "●", 10 | inactive: "○" 11 | }, 12 | lines: { 13 | active: "┃", 14 | inactive: "ǀ" 15 | } 16 | } 17 | 18 | const leftButton = 1; 19 | const middleButton = 2; 20 | const rightButton = 3; 21 | 22 | let WorkspaceIndicator = GObject.registerClass( 23 | class WorkspaceIndicator extends PanelMenu.Button { 24 | _init() { 25 | super._init(0.0, _('Horizontal workspace indicator')); 26 | 27 | this._settings = ExtensionUtils.getSettings(); 28 | 29 | this._setIcons(); 30 | 31 | this._container = new St.Widget({ 32 | layout_manager: new Clutter.BinLayout(), 33 | x_expand: true, 34 | y_expand: true, 35 | style_class: "widget", 36 | }); 37 | this.add_actor(this._container); 38 | 39 | this._statusLabel = new St.Label({ 40 | style_class: "panel-button-text", 41 | text: this.getWidgetText(), 42 | x_expand: true, 43 | y_expand: true, 44 | y_align: Clutter.ActorAlign.CENTER, 45 | }); 46 | this._statusLabel.clutter_text.line_wrap = true; 47 | this._statusLabel.clutter_text.line_wrap_mode = Pango.WrapMode.CHAR; 48 | this._statusLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; 49 | this._container.add_actor(this._statusLabel); 50 | 51 | let workspaceManager = global.workspace_manager; 52 | this._workspaceManagerSignals = [ 53 | workspaceManager.connect_after('notify::n-workspaces', 54 | this._updateView.bind(this)), 55 | workspaceManager.connect_after('workspace-switched', 56 | this._updateView.bind(this)), 57 | ]; 58 | 59 | this.connect('button-press-event', this._onButtonPress); 60 | } 61 | 62 | _setIcons() { 63 | this._icons = {}; 64 | 65 | switch (this._settings.get_string("icons-style")) { 66 | case "lines": 67 | this._icons.active = symbolsMap.lines.active; 68 | this._icons.inactive = symbolsMap.lines.inactive; 69 | break; 70 | default: 71 | this._icons.active = symbolsMap.circles.active; 72 | this._icons.inactive = symbolsMap.circles.inactive; 73 | } 74 | 75 | switch (this._settings.get_string("widget-orientation")) { 76 | case "vertical": 77 | this._icons.separator = "\n"; 78 | break; 79 | default: 80 | this._icons.separator = ""; 81 | } 82 | } 83 | 84 | destroy() { 85 | for (let i = 0; i < this._workspaceManagerSignals.length; i++) 86 | global.workspace_manager.disconnect(this._workspaceManagerSignals[i]) 87 | super.destroy(); 88 | } 89 | 90 | _updateView() { 91 | this._statusLabel.text = this.getWidgetText(); 92 | } 93 | 94 | _onButtonPress(_, event) { 95 | let workspaceManager = global.workspace_manager; 96 | let activeWorkspaceIndex = workspaceManager.get_active_workspace_index(); 97 | let button = event.get_button(); 98 | 99 | if (button == leftButton) { 100 | if (activeWorkspaceIndex == 0) { 101 | return 102 | } 103 | Main.wm.actionMoveWorkspace(workspaceManager.get_workspace_by_index(activeWorkspaceIndex-1)); 104 | } else if (button == rightButton) { 105 | if (activeWorkspaceIndex == workspaceManager.get_n_workspaces()-1) { 106 | return 107 | } 108 | Main.wm.actionMoveWorkspace(workspaceManager.get_workspace_by_index(activeWorkspaceIndex+1)); 109 | } else if (button == middleButton) { 110 | if (Main.overview.visible) { 111 | Main.overview.hide(); 112 | } else { 113 | Main.overview.show(); 114 | } 115 | } 116 | } 117 | 118 | getWidgetText() { 119 | let items = []; 120 | let numberWorkspaces = global.workspace_manager.get_n_workspaces(); 121 | let currentWorkspaceIndex = global.workspace_manager.get_active_workspace_index(); 122 | 123 | for (let i = 0; i < numberWorkspaces; i++) { 124 | items.push(i == currentWorkspaceIndex ? this._icons.active : this._icons.inactive); 125 | } 126 | return items.join(this._icons.separator) 127 | } 128 | } 129 | ); 130 | 131 | class Extension { 132 | constructor(uuid) { 133 | this._uuid = uuid; 134 | } 135 | 136 | enable() { 137 | this._indicator = new WorkspaceIndicator(); 138 | let widgetPosition = this._indicator._settings.get_value("widget-position").unpack(); 139 | Main.panel.addToStatusArea(this._uuid, this._indicator, getWidgetIndex(widgetPosition), widgetPosition); 140 | } 141 | 142 | disable() { 143 | this._indicator.destroy(); 144 | this._indicator = null; 145 | } 146 | } 147 | 148 | function getWidgetIndex(position) { 149 | if (position === "right") { 150 | return 0 151 | } 152 | 153 | return 1 154 | } 155 | 156 | function init(meta) { 157 | return new Extension(meta.uuid); 158 | } 159 | -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Workspace indicator shows the amount of opened workspaces and the current one", 3 | "name": "Workspace indicator", 4 | "settings-schema": "org.gnome.shell.extensions.horizontal-workspace-indicator", 5 | "shell-version": ["40", "41", "42", "43", "44"], 6 | "url": "https://github.com/tty2/horizontal-workspace-indicator", 7 | "uuid": "horizontal-workspace-indicator@tty2.io", 8 | "version": "0.4.1" 9 | } 10 | -------------------------------------------------------------------------------- /prefs.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Gio = imports.gi.Gio; 4 | const Gtk = imports.gi.Gtk; 5 | 6 | const ExtensionUtils = imports.misc.extensionUtils; 7 | const Extension = ExtensionUtils.getCurrentExtension(); 8 | 9 | function init() {} 10 | 11 | function buildPrefsWidget() { 12 | this.settings = ExtensionUtils.getSettings(); 13 | 14 | const prefsWidget = new Gtk.Grid({ 15 | margin_top: 20, 16 | margin_bottom: 20, 17 | margin_start: 20, 18 | margin_end: 20, 19 | column_spacing: 12, 20 | row_spacing: 12, 21 | visible: true, 22 | halign: Gtk.Align.CENTER, 23 | }); 24 | 25 | let title = new Gtk.Label({ 26 | label: `Preferences`, 27 | halign: Gtk.Align.CENTER, 28 | use_markup: true, 29 | visible: true 30 | }); 31 | prefsWidget.attach(title, 0, 0, 2, 1); 32 | 33 | let note = new Gtk.Label({ 34 | label: `After any changes turn the extension off and on again.`, 35 | halign: Gtk.Align.CENTER, 36 | use_markup: true, 37 | visible: true 38 | }); 39 | prefsWidget.attach(note, 0, 1, 2, 1); 40 | 41 | const widgetPositionLabel = new Gtk.Label({ 42 | label: "Widget position", 43 | halign: Gtk.Align.START, 44 | visible: true, 45 | }); 46 | prefsWidget.attach(widgetPositionLabel, 0, 2, 1, 1); 47 | 48 | const widgetPositionComboBox = new Gtk.ComboBoxText(); 49 | widgetPositionComboBox.append("right", "Right corner | Status area"); 50 | widgetPositionComboBox.append("center", "Center | Date time area"); 51 | widgetPositionComboBox.append("left", "Left corner | Activities area"); 52 | this.settings.bind( 53 | "widget-position", 54 | widgetPositionComboBox, 55 | "active-id", 56 | Gio.SettingsBindFlags.DEFAULT 57 | ); 58 | prefsWidget.attach(widgetPositionComboBox, 1, 2, 1, 1); 59 | 60 | const widgetOrientationLabel = new Gtk.Label({ 61 | label: "Widget orientation", 62 | halign: Gtk.Align.START, 63 | visible: true, 64 | }); 65 | prefsWidget.attach(widgetOrientationLabel, 0, 3, 1, 1); 66 | 67 | const widgetOrientationComboBox = new Gtk.ComboBoxText(); 68 | widgetOrientationComboBox.append("horizontal", "Horizontal"); 69 | widgetOrientationComboBox.append("vertical", "Vertical"); 70 | this.settings.bind( 71 | "widget-orientation", 72 | widgetOrientationComboBox, 73 | "active-id", 74 | Gio.SettingsBindFlags.DEFAULT 75 | ); 76 | prefsWidget.attach(widgetOrientationComboBox, 1, 3, 1, 1); 77 | 78 | const styleLabel = new Gtk.Label({ 79 | label: "Icons Style", 80 | halign: Gtk.Align.START, 81 | visible: true, 82 | }); 83 | prefsWidget.attach(styleLabel, 0, 4, 1, 1); 84 | 85 | const styleComboBox = new Gtk.ComboBoxText(); 86 | styleComboBox.append("circles", "Circles"); 87 | styleComboBox.append("lines", "Lines"); 88 | this.settings.bind( 89 | "icons-style", 90 | styleComboBox, 91 | "active-id", 92 | Gio.SettingsBindFlags.DEFAULT 93 | ); 94 | prefsWidget.attach(styleComboBox, 1, 4, 1, 1); 95 | 96 | return prefsWidget; 97 | } 98 | -------------------------------------------------------------------------------- /schemas/gschemas.compiled: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tty2/horizontal-workspace-indicator/a18fe8668bc279ffe5b157d160b5a820288efdca/schemas/gschemas.compiled -------------------------------------------------------------------------------- /schemas/org.gnome.shell.extensions.horizontal-workspace-indicator.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 'right' 11 | Widget position 12 | This specifies the position of widget on the left, center or right of top panel. 13 | 14 | 15 | 16 | 17 | 18 | 19 | 'horizontal' 20 | Widget orientation 21 | This specifies the orientation of the dots in the widget, horizontal or vertical (for vertical panels). 22 | 23 | 24 | 25 | 26 | 27 | 28 | 'circles' 29 | Icons style 30 | This specifies icons style. 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /stylesheet.css: -------------------------------------------------------------------------------- 1 | .widget { 2 | font-family: DejaVu Sans, DejaVu Serif, Ubuntu; 3 | } --------------------------------------------------------------------------------