Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 24 additions & 0 deletions window-borders@antoni-acevedo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Window Borders

Adds a customizable colored border around every window in Cinnamon.

## Features

- Colored border around all application windows
- Adjustable width (1px – 20px)
- Adjustable border radius (0px – 30px)
- Custom color picker
- Option to hide borders on maximized windows
- Configurable from System Settings → Extensions

## Screenshot

![Screenshot](screenshot.png)

## Requirements

- Cinnamon 6.6 or newer

## Installation

Install from System Settings → Extensions, or from [cinnamon-spices.linuxmint.com](https://cinnamon-spices.linuxmint.com).
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
const { Gio, GLib, St, Clutter, Meta } = imports.gi;
const Main = imports.ui.main;
const Settings = imports.ui.settings;

let borderManager = null;

function init(metadata) {
borderManager = new BorderManager(metadata.uuid);
}

function enable() {
borderManager.enable();
}

function disable() {
borderManager.disable();
}

class BorderManager {
constructor(uuid) {
this._uuid = uuid;
this._borders = new Map();
this._displaySignals = [];
this._settings = null;
this._workspaceSwitchId = 0;

this.borderColor = '#444444';
this.borderWidth = 3;
this.borderRadius = 12;
this.hideOnMaximized = false;
}

enable() {
this._initSettings();

this._displaySignals.push(
global.display.connect('window-created',
(d, w) => { this._onWindowCreated(w); })
);

this._displaySignals.push(
global.display.connect('notify::focus-window',
() => { this._updateAllBorders(); })
);

this._workspaceSwitchId = global.workspace_manager.connect('workspace-switched',
() => {
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
this._refreshAllBorders();
return GLib.SOURCE_REMOVE;
});
}
);

let actors = Meta.get_window_actors(global.display);
actors.forEach(actor => this._addBorder(actor.meta_window));
}

disable() {
this._removeAllBorders();

if (this._workspaceSwitchId) {
try { global.workspace_manager.disconnect(this._workspaceSwitchId); } catch (e) {}
this._workspaceSwitchId = 0;
}

this._displaySignals.forEach(id => {
try { global.display.disconnect(id); } catch (e) {}
});
this._displaySignals = [];

if (this._settings) {
this._settings.finalize();
this._settings = null;
}
}

_initSettings() {
this._settings = new Settings.ExtensionSettings(this, this._uuid);
this._settings.bind('border-color', 'borderColor', () => this._updateStyles());
this._settings.bind('border-width', 'borderWidth', () => this._updateStyles());
this._settings.bind('border-radius', 'borderRadius', () => this._updateStyles());
this._settings.bind('hide-on-maximized', 'hideOnMaximized', () => this._updateAllBorders());
}

_onWindowCreated(metaWindow) {
let type = metaWindow.get_window_type();
if (type === Meta.WindowType.NORMAL ||
type === Meta.WindowType.DIALOG ||
type === Meta.WindowType.MODAL_DIALOG) {
this._addBorder(metaWindow);
}
}

_addBorder(metaWindow, retryCount = 0) {
if (this._borders.has(metaWindow)) return;

let actor = metaWindow.get_compositor_private();
if (!actor || !actor.get_parent()) {
if (retryCount < 20) {
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 100, () => {
this._addBorder(metaWindow, retryCount + 1);
return GLib.SOURCE_REMOVE;
});
}
return;
}

let frameRect = metaWindow.get_frame_rect();
if (frameRect.width === 0 || frameRect.height === 0) return;

let border = new St.Widget({
reactive: false,
track_hover: false,
x: frameRect.x - this.borderWidth,
y: frameRect.y - this.borderWidth,
width: frameRect.width + this.borderWidth * 2,
height: frameRect.height + this.borderWidth * 2
});

this._setBorderStyle(border);

let parent = actor.get_parent();
parent.insert_child_below(border, actor);

let connections = [
{ obj: metaWindow, id: metaWindow.connect('position-changed',
() => { this._updateBorder(metaWindow); }) },
{ obj: metaWindow, id: metaWindow.connect('size-changed',
() => { this._updateBorder(metaWindow); }) },
{ obj: metaWindow, id: metaWindow.connect('notify::minimized',
() => { this._updateBorder(metaWindow); }) },
{ obj: metaWindow, id: metaWindow.connect('notify::fullscreen',
() => { this._updateBorder(metaWindow); }) },
{ obj: metaWindow, id: metaWindow.connect('notify::workspace',
() => { this._updateBorder(metaWindow); }) },
{ obj: metaWindow, id: metaWindow.connect('unmanaging',
() => { this._destroyBorder(metaWindow); }) }
];

if (typeof actor.connect === 'function') {
connections.push(
{ obj: actor, id: actor.connect('notify::visible',
() => { this._updateBorder(metaWindow); }) }
);
}

this._borders.set(metaWindow, { border, connections, actor });
this._updateBorder(metaWindow);
}

_setBorderStyle(border) {
border.set_style(
'background-color: transparent; ' +
'border: ' + this.borderWidth + 'px solid ' + this.borderColor + '; ' +
'border-radius: ' + this.borderRadius + 'px; ' +
'box-shadow: none;'
);
}

_updateBorder(metaWindow) {
let info = this._borders.get(metaWindow);
if (!info) return;

if (metaWindow.minimized || metaWindow.fullscreen ||
(this.hideOnMaximized &&
(metaWindow.maximized_horizontally || metaWindow.maximized_vertically))) {
info.border.hide();
return;
}

if (metaWindow.is_hidden()) {
info.border.hide();
return;
}

let actor = metaWindow.get_compositor_private();
if (!actor || !actor.visible) {
info.border.hide();
return;
}

let frameRect = metaWindow.get_frame_rect();
if (frameRect.width === 0 || frameRect.height === 0) {
info.border.hide();
return;
}

info.border.set_position(
frameRect.x - this.borderWidth,
frameRect.y - this.borderWidth
);
info.border.set_size(
frameRect.width + this.borderWidth * 2,
frameRect.height + this.borderWidth * 2
);
info.border.show();
}

_destroyBorder(metaWindow) {
let info = this._borders.get(metaWindow);
if (!info) return;

info.connections.forEach(c => {
try { c.obj.disconnect(c.id); } catch (e) {}
});

try { info.border.destroy(); } catch (e) {}
this._borders.delete(metaWindow);
}

_refreshAllBorders() {
let actors = Meta.get_window_actors(global.display);
let seen = new Set();

actors.forEach(actor => {
let mw = actor.meta_window;
if (!mw) return;
seen.add(mw);

let info = this._borders.get(mw);
if (info) {
if (actor !== info.actor) {
info.connections = info.connections.filter(c => {
if (c.obj === info.actor) {
try { info.actor.disconnect(c.id); } catch (e) {}
return false;
}
return true;
});
if (typeof actor.connect === 'function') {
info.connections.push(
{ obj: actor, id: actor.connect('notify::visible',
() => { this._updateBorder(mw); }) }
);
}
info.actor = actor;
}

let parent = actor.get_parent();
let curParent = info.border.get_parent();
if (parent && curParent !== parent) {
if (curParent) {
try { curParent.remove_child(info.border); } catch (e) {}
}
try { parent.insert_child_below(info.border, actor); } catch (e) {}
}
this._updateBorder(mw);
} else {
this._addBorder(mw);
}
});

this._borders.forEach((info, mw) => {
if (!seen.has(mw)) {
this._destroyBorder(mw);
}
});
}

_updateStyles() {
this._borders.forEach(info => {
this._setBorderStyle(info.border);
});
this._updateAllBorders();
}

_updateAllBorders() {
this._borders.forEach((info, metaWindow) => {
this._updateBorder(metaWindow);
});
}

_removeAllBorders() {
this._borders.forEach((info, metaWindow) => {
info.connections.forEach(c => {
try { c.obj.disconnect(c.id); } catch (e) {}
});
try { info.border.destroy(); } catch (e) {}
});
this._borders.clear();
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"uuid": "window-borders@antoni-acevedo",
"name": "Window Borders",
"description": "Añade un borde coloreado a todas las ventanas. Grosor, color y redondeo ajustables desde Configuración > Extensiones.",
"version": 1,
"cinnamon-version": ["6.6", "6.7", "6.8"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"head": {
"type": "header",
"description": "Borde de ventanas"
},
"border-color": {
"type": "colorchooser",
"default": "#444444",
"description": "Color del borde"
},
"border-width": {
"type": "spinbutton",
"default": 3,
"min": 1,
"max": 20,
"step": 1,
"units": "px",
"description": "Grosor del borde"
},
"border-radius": {
"type": "spinbutton",
"default": 12,
"min": 0,
"max": 30,
"step": 1,
"units": "px",
"description": "Radio de esquinas redondeadas"
},
"hide-on-maximized": {
"type": "switch",
"default": false,
"description": "Ocultar borde en ventanas maximizadas"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.window-border {
background-color: transparent;
}
7 changes: 7 additions & 0 deletions window-borders@antoni-acevedo/info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"author": "antoni-acevedo",
"original_author": "antoni-acevedo",
"name": "Window Borders",
"description": "Adds a customizable colored border to all windows. Adjust width, color, and corner radius from System Settings > Extensions. Works with all window types (NORMAL, DIALOG, MODAL_DIALOG).",
"url": "https://github.com/antoni-acevedo/cinnamon-window-borders"
}
Binary file added window-borders@antoni-acevedo/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading