Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
aa7aba7
some hacking
petar-v Apr 8, 2025
666b306
Bump version to 25 in metadata and package files
petar-v Sep 24, 2026
26fe390
Add style-trigger option for alternate panel behavior
petar-v Sep 24, 2026
398bf7b
Add style-trigger configuration for enhanced panel behavior
petar-v Sep 24, 2026
ec2cd0f
oops, bumped again lol
petar-v Sep 24, 2026
02f911d
Refactor window event handling to support alternate panel styling
petar-v Sep 24, 2026
74430da
Remove position callback handling from event manager
petar-v Sep 24, 2026
c39d46c
Fix workspace comparison logic in state equality check
petar-v Sep 24, 2026
59ebfe3
Add style trigger handling to gradient toggle logic
petar-v Sep 24, 2026
e000530
Add style trigger selection to behavior settings
petar-v Sep 24, 2026
e8b1a47
Update gradient settings for alternate style in maximized windows
petar-v Sep 24, 2026
3208506
Add MaximizedWindows and ProximityWindows classes for window management
petar-v Sep 24, 2026
fded703
Refactor window event handling to use behavior classes for style trig…
petar-v Sep 24, 2026
bc15cd9
Add proximity distance setting for alternate panel style trigger
petar-v Sep 24, 2026
3dcbbc0
Add proximity distance setting and related functions to config
petar-v Sep 24, 2026
a4a06fc
Add proximity distance setting to behaviors page
petar-v Sep 24, 2026
b58d4e5
Update proximity distance handling in config and ProximityWindows class
petar-v Sep 24, 2026
b17276a
Update README to clarify alternate style trigger distance for windows
petar-v Sep 24, 2026
31967ca
Update package.json scripts for schema validation and README document…
petar-v Sep 25, 2026
df51a13
remove compiled schemas
petar-v Sep 25, 2026
2485ae2
fix async stuff based on shexli
petar-v Sep 25, 2026
c2a3212
Update verify.yml to include schema validation in lint job
petar-v Sep 25, 2026
3878f15
Update shexli command to check for specific rule ID and enhance verif…
petar-v Sep 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
name: Verify

on:
push:
pull_request:

permissions: {}

jobs:
lint:
name: ESLint
name: ESLint and schemas
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
Expand All @@ -20,6 +19,7 @@ jobs:
- run: corepack enable
- run: yarn install --immutable
- run: yarn lint
- run: yarn validate-schemas

shexli:
name: Shexli
Expand All @@ -36,5 +36,6 @@ jobs:
cache: pip
cache-dependency-path: requirements-dev.txt
- run: corepack enable
- run: yarn install --immutable
- run: yarn setup-python
- run: yarn shexli
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ Give GNOME’s top bar a customizable gradient.

- Choose start and end colors of the gradient
- Vertical or horizontal orientation
- Separate style when a window is maximized: keep the gradient, keep the original theme, or apply a custom gradient
- Trigger an alternate style for maximized windows or windows within a configurable distance of the panel
- Keep the gradient, restore the original theme, or apply a custom gradient when triggered
- Simple preferences dialog


Expand Down Expand Up @@ -91,7 +92,7 @@ The project includes several scripts to help with development:

### Building and Installation

- `yarn compile-schemas`: Compiles the GSettings schemas.
- `yarn validate-schemas`: Validates the GSettings schemas.
- `yarn zip`: Creates a zip archive of the extension.
- `yarn zip-extension`: Packs the extension using GNOME's extension tools.
- `yarn local-install`: Compiles schemas, zips the extension, installs it locally, and enables it (all-in-one command for testing changes).
Expand All @@ -112,7 +113,7 @@ The project includes several scripts to help with development:
4. If needed, debug using `yarn pref-debug` or `yarn nested-wayland`.
5. Submit a pull request with your changes.
6. To publish a new version:
1) Run `yarn zip-extension` (or `yarn zip`) to create the zip
1) Run `yarn zip` to create the complete extension zip
2) Run `yarn upload` and follow the browser flow

# To Do
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "gnome-gradienttopbar",
"title": "Gradient Top Bar",
"description": "Give the top bar a customizable gradient. Choose direction and colors, and optionally use a different style or keep the theme when windows are maximized.",
"version": "24",
"version": "26",
"engines": {
"gnome": ">=49"
},
Expand All @@ -20,14 +20,14 @@
"nested-wayland": "dbus-run-session -- gnome-shell --devkit --wayland",
"pref-debug": "journalctl -f -o cat /usr/bin/gjs",
"open-prefs": "gnome-extensions prefs gradienttopbar@pshow.org",
"local-install": "yarn compile-schemas && yarn zip && gnome-extensions install gradienttopbar@pshow.org.shell-extension.zip --force && gnome-extensions enable gradienttopbar@pshow.org",
"local-install": "yarn validate-schemas && yarn zip && gnome-extensions install gradienttopbar@pshow.org.shell-extension.zip --force && gnome-extensions enable gradienttopbar@pshow.org",
"zip-extension": "gnome-extensions pack src --force",
"compile-schemas": "glib-compile-schemas ./src/schemas/",
"zip": "cd src && zip -r ../gradienttopbar@pshow.org.shell-extension.zip .",
"validate-schemas": "glib-compile-schemas --strict --dry-run ./src/schemas/",
"zip": "cd src && zip -FS -r ../gradienttopbar@pshow.org.shell-extension.zip .",
"prettify": "prettier --write 'src/**/*.{css}' && yarn lint --fix",
"lint": "eslint 'src/**/*.{js,json}'",
"setup-python": "python3 -m venv .venv && .venv/bin/python -m pip install -r requirements-dev.txt",
"shexli": ".venv/bin/shexli --format json \"$PWD/src\" | .venv/bin/python -c \"import json, sys; report = json.load(sys.stdin); print(json.dumps(report, indent=2)); raise SystemExit(report['summary']['finding_count'] != 0)\"",
"shexli": ".venv/bin/shexli --format json \"$PWD/src\" | .venv/bin/python -c \"import json, sys; report = json.load(sys.stdin); print(json.dumps(report, indent=2)); raise SystemExit(any(finding['rule_id'] != 'EGO-M-004' for finding in report['findings']))\"",
"verify": "yarn lint && yarn shexli",
"precommit": "lint-staged",
"upload": "gnome-extensions upload --accept-tos"
Expand Down
42 changes: 36 additions & 6 deletions src/config.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';

import { DEFAULT_PROXIMITY_DISTANCE } from './constants.js';

Gio._promisify(Gio.Subprocess.prototype, 'communicate_utf8_async');

export const SETTINGS_GSCHEMA =
'org.gnome.shell.extensions.org.pshow.gradienttopbar';

const SETTINGS_GSCHEMA_PATH = `/${SETTINGS_GSCHEMA.replaceAll('.', '/')}/`;

// Settings keys
export const MAXIMIZED_BEHAVIOR = 'maximized-behavior';
export const STYLE_TRIGGER = 'style-trigger';
export const PROXIMITY_DISTANCE = 'proximity-distance';
export const GRADIENT_DIRECTION = 'gradient-direction';
export const MAXIMIZED_GRADIENT_DIRECTION = 'maximized-gradient-direction';
export const COLORS = 'colors';
Expand All @@ -21,13 +27,17 @@ export const MAXIMIZED_COLORS = 'maximized-colors';
*/
export const getConfig = settings => {
const maximizedBehavior = settings.get_string(MAXIMIZED_BEHAVIOR);
const styleTrigger = settings.get_string(STYLE_TRIGGER);
const proximityDistance = settings.get_int(PROXIMITY_DISTANCE);
const colors = settings.get_value(COLORS).deep_unpack();
const gradientDirection = settings.get_string(GRADIENT_DIRECTION);
const maximizedColors = settings.get_value(MAXIMIZED_COLORS).deep_unpack();
const maximizedGradientDirection = settings.get_string(MAXIMIZED_GRADIENT_DIRECTION);

return {
maximizedBehavior,
styleTrigger,
proximityDistance,
gradientDirection,
colors: {
start: colors[0],
Expand Down Expand Up @@ -61,6 +71,19 @@ export const setMaximizedBehavior = (settings, value) => {
settings.set_string(MAXIMIZED_BEHAVIOR, value);
};

export const getStyleTrigger = settings => settings.get_string(STYLE_TRIGGER);

export const setStyleTrigger = (settings, value) => {
settings.set_string(STYLE_TRIGGER, value);
};

export const getProximityDistance = settings =>
settings?.get_int(PROXIMITY_DISTANCE) ?? DEFAULT_PROXIMITY_DISTANCE;

export const setProximityDistance = (settings, value) => {
settings.set_int(PROXIMITY_DISTANCE, value);
};

/**
* Gets the gradient direction setting
*
Expand Down Expand Up @@ -153,6 +176,8 @@ export const attachSettingsListeners = (settings, listener) => {
return [
GRADIENT_DIRECTION,
MAXIMIZED_BEHAVIOR,
STYLE_TRIGGER,
PROXIMITY_DISTANCE,
COLORS,
MAXIMIZED_COLORS,
MAXIMIZED_GRADIENT_DIRECTION
Expand All @@ -174,14 +199,19 @@ export const detachSettingsListeners = (settings, handlerIds) => {
*
* @param {Gio.File} file - The file to export settings to
*/
export const exportSettingsToFile = file => {
export const exportSettingsToFile = async file => {
const process = Gio.Subprocess.new(
['dconf', 'dump', SETTINGS_GSCHEMA_PATH],
Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE
);
const [stdout, stderr] = await process.communicate_utf8_async(null, null);

if (!process.get_successful())
throw new Error(stderr.trim() || 'Failed to export settings');

const raw = file.replace(null, false, Gio.FileCreateFlags.NONE, null);
const out = Gio.BufferedOutputStream.new_sized(raw, 4096);

const settings = GLib.spawn_command_line_sync(
`dconf dump ${SETTINGS_GSCHEMA_PATH}`
)[1];
out.write_all(settings, null);
out.write_all(new TextEncoder().encode(stdout), null);
out.close(null);
};

Expand Down
7 changes: 7 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ export const MAXIMIZED_BEHAVIOR = {
APPLY_STYLE: 'apply-style'
};

export const STYLE_TRIGGER = {
MAXIMIZED: 'maximized',
PROXIMITY: 'proximity'
};

export const DEFAULT_PROXIMITY_DISTANCE = 5;

// Gradient directions
export const GRADIENT_DIRECTION = {
VERTICAL: 'vertical',
Expand Down
23 changes: 23 additions & 0 deletions src/events/behaviours/maximizedWindows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Meta from 'gi://Meta';

const { VERTICAL, BOTH } = Meta.MaximizeFlags;

// Detects windows that occupy the full monitor or are vertically maximized.
// This behaviour deliberately avoids layout and position-change dependencies.
export default class MaximizedWindows {
matches(window) {
if (window.is_monitor_sized() || window.is_screen_sized())
return true;

return [BOTH, VERTICAL].includes(window.get_maximize_flags());
}

// Maximization changes window size; moving an unmaximized window is irrelevant.
attachWindowEvents(eventManager, window, updateState) {
eventManager.attachWindowEventOnce('size-changed', window, updateState);
eventManager.attachWindowEventOnce('workspace-changed', window, updateState);
}

// Maximized detection requires no behaviour-specific global events.
attachGlobalEvents() {}
}
37 changes: 37 additions & 0 deletions src/events/behaviours/proximityWindows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { layoutManager, panel } from 'resource:///org/gnome/shell/ui/main.js';

import { getProximityDistance } from '../../config.js';

// Keeps all panel-layout dependencies inside the proximity behaviour.
export default class ProximityWindows {
constructor(settings) {
this.settings = settings;
}

matches(window) {
const { primaryMonitor } = layoutManager;
if (!primaryMonitor)
return false;

// Include a small tolerance below the panel to avoid an exact-edge requirement.
const distance = getProximityDistance(this.settings);
const panelBottom = primaryMonitor.y + panel.get_height() + distance;
return window.get_frame_rect().y <= panelBottom;
}

// Proximity can change when a window moves, resizes, or changes workspace.
attachWindowEvents(eventManager, window, updateState) {
eventManager.attachWindowEventOnce('size-changed', window, updateState);
eventManager.attachWindowEventOnce('position-changed', window, updateState);
eventManager.attachWindowEventOnce('workspace-changed', window, updateState);
}

// Recalculate the boundary whenever GNOME changes the monitor layout.
attachGlobalEvents(eventManager, updateState) {
eventManager.attachGlobalEventOnce(
'monitors-changed',
layoutManager,
() => updateState(true)
);
}
}
6 changes: 3 additions & 3 deletions src/events/states.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ export const areSameState = (state1, state2) => {
return false;
if (state1.inOverview !== state2.inOverview)
return false;
if ([state1.workspace, state2.workspace].includes(undefined))
if ([state1.currentWorkspace, state2.currentWorkspace].includes(undefined))
return false;
if (state1.workspace.index() !== state2.workspace.index())
if (state1.currentWorkspace.index() !== state2.currentWorkspace.index())
return false;

return eqSet(state1.maximizedWindows, state2.maximizedWindows);
return eqSet(state1.triggerWindows, state2.triggerWindows);
};
Loading
Loading