├── .gitignore ├── src ├── schemas │ ├── gschemas.compiled │ └── ftpix.com.transparentbar.gschema.xml ├── metadata.json ├── stylesheet.scss ├── prefs.js └── extension.js ├── Makefile ├── shell.nix ├── README.md └── .github └── workflows └── build.yml /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /build/ 3 | /src/stylesheet.css 4 | /src/stylesheet.css.map 5 | /.sass-cache/ 6 | -------------------------------------------------------------------------------- /src/schemas/gschemas.compiled: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lamarios/gnome-shell-extension-transparent-top-bar/HEAD/src/schemas/gschemas.compiled -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | build: $(wildcard src/*) 3 | rm -Rf build 4 | mkdir -p build/ 5 | sass src/stylesheet.scss src/stylesheet.css 6 | cd src/ && zip -r ../build/transparent-top-bar@com.ftpix.zip . 7 | zip -d build/transparent-top-bar@com.ftpix.zip stylesheet.scss 8 | zip -d build/transparent-top-bar@com.ftpix.zip stylesheet.css.map 9 | 10 | .PHONY: clean 11 | clean: 12 | rm -rf build/ 13 | -------------------------------------------------------------------------------- /src/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "_generated": "Generated by SweetTooth, do not edit", 3 | "description": "Fork of: https://github.com/zhanghai/gnome-shell-extension-transparent-top-bar\n\nBring back the transparent top bar in GNOME Shell with adjustable transparency.\n\nDoes not work well with custom shell themes.", 4 | "name": "Transparent Top Bar (Adjustable transparency)", 5 | "shell-version": [ 6 | "49", 7 | "48", 8 | "47" 9 | ], 10 | "url": "https://github.com/lamarios/gnome-shell-extension-transparent-top-bar", 11 | "uuid": "transparent-top-bar@ftpix.com", 12 | "version": 23 13 | } 14 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | # Using a fixed nix commit for maximum reproducability 2 | { pkgs ? import { 3 | } 4 | }: 5 | 6 | pkgs.mkShell { 7 | buildInputs = with pkgs; [ 8 | git sass zip gnumake 9 | ]; 10 | 11 | shellHook = '' 12 | 13 | ''; 14 | 15 | 16 | 17 | #################################################################### 18 | # Without this, almost everything fails with locale issues when 19 | # using `nix-shell --pure` (at least on NixOS). 20 | # See 21 | # + https://github.com/NixOS/nix/issues/318#issuecomment-52986702 22 | # + http://lists.linuxfromscratch.org/pipermail/lfs-support/2004-June/023900.html 23 | #################################################################### 24 | 25 | LOCALE_ARCHIVE = if pkgs.stdenv.isLinux then "${pkgs.glibcLocales}/lib/locale/locale-archive" else ""; 26 | } 27 | 28 | # vim: set tabstop=2 shiftwidth=2 expandtab: -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GNOME Shell Extension - Transparent Top Bar 2 | 3 | A GNOME Shell extension that brings back the transparent top bar when free-floating in GNOME Shell 3.32. 4 | 5 | This basically comes from the feature 6 | implementation [removed in GNOME Shell 3.32](https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/376/), and I 7 | modified the code a bit to make it an extension. Enjoy! 8 | 9 | ## License 10 | 11 | This program is distributed under the terms of the GNU General Public License, version 2 or later. 12 | 13 | ## Development 14 | 15 | ### Wayland 16 | 17 | Start child shell instance with reloaded extensions 18 | ``` 19 | MUTTER_DEBUG_DUMMY_MODE_SPECS=1920x1080 dbus-run-session -- gnome-shell --nested --wayland 20 | ``` 21 | 22 | ### Xorg 23 | 24 | Reload shell by pressing ALT+F2 type r in the input then enter. 25 | 26 | ### Compile schemas 27 | ``` 28 | cd ~/.local/share/gnome-shell/extensions/transparent-top-bar@ftpix.com 29 | glib-compile-schemas schemas/ 30 | ``` 31 | 32 | -------------------------------------------------------------------------------- /src/schemas/ftpix.com.transparentbar.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 35 6 | Top bar opacity 7 | Top bar opacity 8 | 9 | 10 | true 11 | Dark bar on full screen 12 | If true the bar transparency will be reverted to original state when a window is touching the 13 | bar, false and the bar remains transparent at all time 14 | 15 | 16 | 17 | false 18 | Disable text shadow 19 | If true, text shadows will be disabled in the top bar. If false (default), text will have shadows to improve readability on transparent backgrounds. 20 | 21 | 22 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | name: Build 3 | 4 | # Controls when the workflow will run 5 | on: 6 | # Triggers the workflow on push or pull request events but only for the main branch 7 | push: 8 | branches: [master] 9 | paths-ignore: 10 | - '**/README.md' 11 | pull_request: 12 | branches: [master] 13 | 14 | # Allows you to run this workflow manually from the Actions tab 15 | workflow_dispatch: 16 | 17 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 18 | jobs: 19 | # This workflow contains a single job called "build" 20 | build: 21 | name: Build 22 | # The type of runner that the job will run on 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: cachix/install-nix-action@v22 27 | with: 28 | nix_path: nixpkgs=channel:nixos-23.11 29 | - run: nix-shell --run 'make build' 30 | - name: Archive build artifacts 31 | uses: actions/upload-artifact@v4 32 | with: 33 | name: transparent-top-bar 34 | path: build/transparent-top-bar@com.ftpix.zip -------------------------------------------------------------------------------- /src/stylesheet.scss: -------------------------------------------------------------------------------- 1 | #panel.transparent-top-bar { 2 | transition-duration: 500ms; 3 | } 4 | 5 | #panel.transparent-top-bar:overview { 6 | background-color: transparent; 7 | transition-duration: 250ms; 8 | } 9 | 10 | #panel .panel-corner { 11 | -panel-corner-border-width: 0px; 12 | } 13 | 14 | @for $i from 0 through 100 { 15 | $opacity: $i / 100; 16 | @debug "Opacity: #{$opacity}"; 17 | #panel.transparent-top-bar-#{$i} { 18 | background-color: rgba(0, 0, 0, $opacity); 19 | -panel-corner-border-width: 0px; 20 | -panel-corner-opacity: $opacity; 21 | transition-duration: 500ms; 22 | box-shadow: none; 23 | } 24 | 25 | #panel.transparent-top-bar-#{$i} .panel-corner { 26 | -panel-corner-background-color: rgba(0, 0, 0, $opacity); 27 | } 28 | } 29 | 30 | #panel.transparent-top-bar:overview .panel-corner { 31 | -panel-corner-opacity: 0; 32 | transition-duration: 250ms; 33 | } 34 | 35 | #panel.transparent-top-bar .panel-button { 36 | color: #eee; 37 | text-shadow: 0px 1px 2px rgba(0, 0, 0, 0.9); 38 | transition-duration: 100ms; 39 | } 40 | 41 | #panel.transparent-top-bar .system-status-icon, 42 | #panel.transparent-top-bar .app-menu-icon > StIcon, 43 | #panel.transparent-top-bar .popup-menu-arrow { 44 | icon-shadow: 0px 1px 2px rgba(0, 0, 0, 0.9); 45 | } 46 | 47 | #panel.transparent-top-bar:hover { 48 | text-shadow: 0px 1px 6px black; 49 | } 50 | 51 | #panel.transparent-top-bar:hover .system-status-icon, 52 | #panel.transparent-top-bar:hover .app-menu-icon > StIcon, 53 | #panel.transparent-top-bar:hover .popup-menu-arrow { 54 | icon-shadow: 0px 1px 6px black; 55 | } 56 | 57 | #panel.transparent-top-bar:active .system-status-icon, 58 | #panel.transparent-top-bar:overview .system-status-icon, 59 | #panel.transparent-top-bar:focus .system-status-icon, 60 | #panel.transparent-top-bar:checked .system-status-icon { 61 | icon-shadow: black 0 2px 2px; 62 | } 63 | 64 | #panel.no-text-shadow .panel-button { 65 | text-shadow: none !important; 66 | } 67 | 68 | #panel.no-text-shadow:hover { 69 | text-shadow: none !important; 70 | } 71 | 72 | #panel.no-text-shadow .system-status-icon, 73 | #panel.no-text-shadow .app-menu-icon > StIcon, 74 | #panel.no-text-shadow .popup-menu-arrow { 75 | icon-shadow: none !important; 76 | } 77 | 78 | #panel.no-text-shadow:hover .system-status-icon, 79 | #panel.no-text-shadow:hover .app-menu-icon > StIcon, 80 | #panel.no-text-shadow:hover .popup-menu-arrow { 81 | icon-shadow: none !important; 82 | } 83 | 84 | #panel.no-text-shadow:active .system-status-icon, 85 | #panel.no-text-shadow:overview .system-status-icon, 86 | #panel.no-text-shadow:focus .system-status-icon, 87 | #panel.no-text-shadow:checked .system-status-icon { 88 | icon-shadow: none !important; 89 | } -------------------------------------------------------------------------------- /src/prefs.js: -------------------------------------------------------------------------------- 1 | import Adw from 'gi://Adw'; 2 | import Gtk from 'gi://Gtk'; 3 | import {ExtensionPreferences, gettext as _} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'; 4 | 5 | const TRANSPARENCY = 'transparency'; 6 | const DARK_FULL_SCREEN = 'dark-full-screen'; 7 | const DISABLE_TEXT_SHADOW = 'disable-text-shadow'; 8 | 9 | export default class TransparentTopBarPrefsWidget extends ExtensionPreferences { 10 | 11 | fillPreferencesWindow(window) { 12 | window._settings = this.getSettings('com.ftpix.transparentbar'); 13 | const opacity = window._settings.get_int(TRANSPARENCY); 14 | const darkFullScreen = window._settings.get_boolean(DARK_FULL_SCREEN); 15 | const disableTextShadow = window._settings.get_boolean(DISABLE_TEXT_SHADOW); 16 | 17 | const page = new Adw.PreferencesPage(); 18 | 19 | const group = new Adw.PreferencesGroup({ 20 | title: _('Top bar opacity (%)'), 21 | }); 22 | 23 | // scale 24 | const scale = new Gtk.Scale(); 25 | scale.set_digits(0); 26 | scale.set_round_digits(0); 27 | 28 | scale.set_range(0, 100); 29 | scale.set_draw_value(true); 30 | 31 | // setting value last as it might not work if the scale is not set up properly 32 | scale.set_value(opacity); 33 | scale.connect('value-changed', (scale) => { 34 | window._settings.set_int(TRANSPARENCY, scale.get_value()); 35 | }); 36 | group.add(scale); 37 | 38 | // Switch for full opacity when window touch the bar 39 | const row = new Gtk.Box(); 40 | row.set_orientation(Gtk.HORIZONTAL); 41 | row.set_margin_top(20); 42 | row.set_margin_bottom(20); 43 | //row.set_homogeneous(true); 44 | 45 | const sw = new Gtk.Switch(); 46 | sw.set_active(darkFullScreen); 47 | sw.connect('state-set', (sw) => { 48 | window._settings.set_boolean(DARK_FULL_SCREEN, sw.get_active()); 49 | }); 50 | sw.set_halign(Gtk.Align.END); 51 | 52 | const label = Gtk.Label.new('Opaque top bar when a window touches it'); 53 | label.set_hexpand(true); 54 | label.set_halign(Gtk.Align.START); 55 | 56 | row.append(label); 57 | row.append(sw); 58 | group.add(row); 59 | 60 | const shadowRow = new Gtk.Box(); 61 | shadowRow.set_orientation(Gtk.HORIZONTAL); 62 | shadowRow.set_margin_top(20); 63 | shadowRow.set_margin_bottom(20); 64 | 65 | const shadowSwitch = new Gtk.Switch(); 66 | shadowSwitch.set_active(disableTextShadow); 67 | shadowSwitch.connect('state-set', (sw) => { 68 | window._settings.set_boolean(DISABLE_TEXT_SHADOW, sw.get_active()); 69 | }); 70 | shadowSwitch.set_halign(Gtk.Align.END); 71 | 72 | const shadowLabel = Gtk.Label.new('Disable text shadow'); 73 | shadowLabel.set_hexpand(true); 74 | shadowLabel.set_halign(Gtk.Align.START); 75 | 76 | shadowRow.append(shadowLabel); 77 | shadowRow.append(shadowSwitch); 78 | group.add(shadowRow); 79 | 80 | page.add(group); 81 | 82 | window.add(page); 83 | } 84 | 85 | onValueChanged(scale) { 86 | this.settings.set_int("transparency", this._opacity.get_value()); 87 | } 88 | 89 | onDarkFullScreenChanged(gtkSwitch) { 90 | this.settings.set_boolean("dark-full-screen", this._darkFullScreen.get_active()); 91 | } 92 | } 93 | 94 | -------------------------------------------------------------------------------- /src/extension.js: -------------------------------------------------------------------------------- 1 | import Meta from 'gi://Meta'; 2 | import St from 'gi://St'; 3 | import GLib from 'gi://GLib'; 4 | 5 | import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js'; 6 | import * as Main from 'resource:///org/gnome/shell/ui/main.js'; 7 | 8 | export default class TransparentTopBarWithCustomTransparencyExtension extends Extension { 9 | constructor(metadata) { 10 | super(metadata); 11 | this._actorSignalIds = null; 12 | this._windowSignalIds = null; 13 | this._delayedTimeoutId = null; 14 | this.transparencyChangeDebounce = null; 15 | this.darkFullScreenChangeDebounce = null; 16 | this.textShadowChangeDebounce = null; 17 | } 18 | 19 | enable() { 20 | 21 | this._settings = this.getSettings('com.ftpix.transparentbar'); 22 | this._currentTransparency = this._settings.get_int('transparency'); 23 | this._darkFullScreen = this._settings.get_boolean('dark-full-screen'); 24 | this._disableTextShadow = this._settings.get_boolean('disable-text-shadow'); 25 | 26 | this._actorSignalIds = new Map(); 27 | this._windowSignalIds = new Map(); 28 | this._settings.connect('changed', (settings, key) => this.transparencyChanged(settings, key)); 29 | this._actorSignalIds.set(Main.overview, [ 30 | Main.overview.connect('showing', this._updateTransparent.bind(this)), 31 | Main.overview.connect('hiding', this._updateTransparent.bind(this)) 32 | ]); 33 | 34 | this._actorSignalIds.set(Main.sessionMode, [ 35 | Main.sessionMode.connect('updated', this._updateTransparent.bind(this)) 36 | ]); 37 | 38 | for (const metaWindowActor of global.get_window_actors()) { 39 | this._onWindowActorAdded(metaWindowActor.get_parent(), metaWindowActor); 40 | } 41 | 42 | this._actorSignalIds.set(global.window_group, [ 43 | global.window_group.connect('child-added', this._onWindowActorAdded.bind(this)), 44 | global.window_group.connect('child-removed', this._onWindowActorRemoved.bind(this)) 45 | ]); 46 | 47 | //Use a delayed version of _updateTransparent to let the shell catch up 48 | this._actorSignalIds.set(global.window_manager, [ 49 | global.window_manager.connect('switch-workspace', this._updateTransparentDelayed.bind(this)) 50 | ]); 51 | 52 | this._updateTransparent(); 53 | } 54 | 55 | transparencyChanged(settings, key) { 56 | 57 | if (key === 'transparency') { 58 | GLib.source_remove(this.transparencyChangeDebounce); 59 | this.transparencyChangeDebounce = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 250, () => { 60 | const oldTransparency = this._currentTransparency; 61 | this._currentTransparency = this._settings.get_int('transparency'); 62 | Main.panel.remove_style_class_name('transparent-top-bar-' + oldTransparency); 63 | this._updateTransparent(); 64 | }); 65 | return; 66 | } 67 | 68 | if (key === 'dark-full-screen') { 69 | this._darkFullScreen = this._settings.get_boolean('dark-full-screen'); 70 | GLib.source_remove(this.darkFullScreenChangeDebounce); 71 | this.darkFullScreenChangeDebounce = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 250, () => { 72 | Main.panel.remove_style_class_name('transparent-top-bar-' + this._currentTransparency); 73 | this._updateTransparent(); 74 | }); 75 | return; 76 | } 77 | 78 | if (key === 'disable-text-shadow') { 79 | this._disableTextShadow = this._settings.get_boolean('disable-text-shadow'); 80 | GLib.source_remove(this.textShadowChangeDebounce); 81 | this.textShadowChangeDebounce = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 250, () => { 82 | this._updateTextShadow(); 83 | }); 84 | return; 85 | } 86 | } 87 | 88 | disable() { 89 | GLib.source_remove(this.transparencyChangeDebounce); 90 | GLib.source_remove(this.darkFullScreenChangeDebounce); 91 | GLib.source_remove(this.textShadowChangeDebounce); 92 | 93 | for (const actorSignalIds of [this._actorSignalIds, this._windowSignalIds]) { 94 | for (const [actor, signalIds] of actorSignalIds) { 95 | for (const signalId of signalIds) { 96 | actor.disconnect(signalId); 97 | } 98 | } 99 | } 100 | this._actorSignalIds = null; 101 | this._windowSignalIds = null; 102 | 103 | if (this._delayedTimeoutId != null) { 104 | GLib.Source.remove(this._delayedTimeoutId); 105 | } 106 | this._delayedTimeoutId = null; 107 | 108 | this._setTransparent(false); 109 | Main.panel.remove_style_class_name('no-text-shadow'); 110 | this._settings = null; 111 | } 112 | 113 | _onWindowActorAdded(container, metaWindowActor) { 114 | this._windowSignalIds.set(metaWindowActor, [ 115 | metaWindowActor.connect('notify::allocation', this._updateTransparent.bind(this)), 116 | metaWindowActor.connect('notify::visible', this._updateTransparent.bind(this)) 117 | ]); 118 | } 119 | 120 | _onWindowActorRemoved(container, metaWindowActor) { 121 | for (const signalId of this._windowSignalIds.get(metaWindowActor)) { 122 | metaWindowActor.disconnect(signalId); 123 | } 124 | this._windowSignalIds.delete(metaWindowActor); 125 | this._updateTransparent(); 126 | } 127 | 128 | _updateTransparentDelayed() { 129 | this._delayedTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 100, () => { 130 | this._updateTransparent(); 131 | this._delayedTimeoutId = null; 132 | return GLib.SOURCE_REMOVE; 133 | }); 134 | } 135 | 136 | _updateTextShadow() { 137 | if (this._disableTextShadow) { 138 | Main.panel.add_style_class_name('no-text-shadow'); 139 | } else { 140 | Main.panel.remove_style_class_name('no-text-shadow'); 141 | } 142 | } 143 | 144 | _updateTransparent() { 145 | if (!this._darkFullScreen) { 146 | this._setTransparent(true); 147 | return 148 | } 149 | 150 | if (Main.panel.has_style_pseudo_class('overview') || !Main.sessionMode.hasWindows) { 151 | this._setTransparent(true); 152 | return; 153 | } 154 | 155 | if (!Main.layoutManager.primaryMonitor) { 156 | return; 157 | } 158 | 159 | // Get all the windows in the active workspace that are in the primary monitor and visible. 160 | const workspaceManager = global.workspace_manager; 161 | const activeWorkspace = workspaceManager.get_active_workspace(); 162 | const windows = activeWorkspace.list_windows().filter(metaWindow => { 163 | return metaWindow.is_on_primary_monitor() 164 | && metaWindow.showing_on_its_workspace() 165 | && !metaWindow.is_hidden() 166 | && metaWindow.get_window_type() !== Meta.WindowType.DESKTOP 167 | && !metaWindow.skip_taskbar; 168 | }); 169 | 170 | // Check if at least one window is near enough to the panel. 171 | const panelTop = Main.panel.get_transformed_position()[1]; 172 | const panelBottom = panelTop + Main.panel.get_height(); 173 | const scale = St.ThemeContext.get_for_stage(global.stage).scale_factor; 174 | const isNearEnough = windows.some(metaWindow => { 175 | const verticalPosition = metaWindow.get_frame_rect().y; 176 | return verticalPosition < panelBottom + 5 * scale; 177 | }); 178 | 179 | this._setTransparent(!isNearEnough); 180 | } 181 | 182 | _setTransparent(transparent) { 183 | const transparency = this._settings.get_int("transparency"); 184 | if (transparent) { 185 | Main.panel.add_style_class_name('transparent-top-bar'); 186 | Main.panel.add_style_class_name('transparent-top-bar-' + transparency); 187 | } else { 188 | Main.panel.remove_style_class_name('transparent-top-bar'); 189 | Main.panel.remove_style_class_name('transparent-top-bar-' + transparency); 190 | } 191 | 192 | this._updateTextShadow(); 193 | } 194 | 195 | }; 196 | --------------------------------------------------------------------------------