diff --git a/extension.js b/extension.js index cf602e1..0eb97b1 100644 --- a/extension.js +++ b/extension.js @@ -11,7 +11,8 @@ const Indicator = Me.imports.sources.indicator; class Extension { constructor() { this._state = new State.StateManager(); - this._renderer = new Renderer.Renderer(this._state); + this._settings = ExtensionUtils.getSettings("org.gnome.shell.extensions.fairy"); + this._renderer = new Renderer.Renderer(this._state, this._settings); this._keybinds = new Keybinds.KeyboardManager(this._state, this._renderer); this._indicator = new Indicator.Indicator(this._state, this._renderer); } diff --git a/prefs.js b/prefs.js index ac87853..cc9a8bd 100644 --- a/prefs.js +++ b/prefs.js @@ -5,7 +5,7 @@ const { Adw, Gio, Gtk } = imports.gi; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); -function init() { } +function init() {} function _createKeybind(settings, keybind) { // TODO: Set the title to the gsettings's title. @@ -16,7 +16,39 @@ function _createKeybind(settings, keybind) { // settings.bind(keybind, listener, ); row.add_suffix(listener); row.activable_widget = listener; - return row + return row; +} + +function _createBool(settings, { title, key }) { + const row = new Adw.ActionRow({ title: title }); + + const toggle = new Gtk.Switch({ + active: settings.get_boolean(key), + valign: Gtk.Align.CENTER, + }); + settings.bind(key, toggle, "active", Gio.SettingsBindFlags.DEFAULT); + + row.add_suffix(toggle); + row.activatable_widget = toggle; + return row; +} + +function _createUint(settings, { title, key }) { + const row = new Adw.ActionRow({ title: title }); + + const input = new Gtk.SpinButton({ + adjustment: new Gtk.Adjustment({ + lower: 0, + upper: 100, + step_increment: 1, + value: settings.get_uint(key), + }), + }); + settings.bind(key, input, "value", Gio.SettingsBindFlags.DEFAULT); + + row.add_suffix(input); + row.activatable_widget = input; + return row; } function fillPreferencesWindow(window) { @@ -26,25 +58,36 @@ function fillPreferencesWindow(window) { const page = new Adw.PreferencesPage(); page.set_title("General"); - const group = new Adw.PreferencesGroup(); - page.add(group); - const row = new Adw.ActionRow({ title: "Show Layout Indicator" }); - group.add(row); - - const toggle = new Gtk.Switch({ - active: settings.get_boolean("show-layout"), - valign: Gtk.Align.CENTER, - }); - settings.bind( - "show-layout", - toggle, - "active", - Gio.SettingsBindFlags.DEFAULT + const general = new Adw.PreferencesGroup(); + page.add(general); + general.add( + _createBool(settings, { + title: "Show Layout Indicator", + key: "show-layout", + }) ); - row.add_suffix(toggle); - row.activatable_widget = toggle; + const gaps = new Adw.PreferencesGroup(); + page.add(gaps); + gaps.add( + _createUint(settings, { + title: "Gap size", + key: "gap-size", + }) + ); + gaps.add( + _createUint(settings, { + title: "Outer gap size", + key: "outer-gap-size", + }) + ); + gaps.add( + _createBool(settings, { + title: "Smart gaps", + key: "smart-gaps", + }) + ); const keybinds = new Adw.PreferencesPage(); keybinds.set_title("Keybinds"); diff --git a/schemas/org.gnome.shell.extensions.fairy.gschema.xml b/schemas/org.gnome.shell.extensions.fairy.gschema.xml index 97a0a66..6e4764d 100644 --- a/schemas/org.gnome.shell.extensions.fairy.gschema.xml +++ b/schemas/org.gnome.shell.extensions.fairy.gschema.xml @@ -1,12 +1,6 @@ - - - 'tiling' - Available: floating, tiling, monocle - - true @@ -24,10 +18,15 @@ - 4 + 10 The size of the gap between windows in the workarea + + 20 + The size of the gap between windows and the edge of the monitor + + true True to hide gaps when there is a single window diff --git a/sources/renderer.js b/sources/renderer.js index 554bbb3..efa26f8 100644 --- a/sources/renderer.js +++ b/sources/renderer.js @@ -10,9 +10,15 @@ const Me = ExtensionUtils.getCurrentExtension(); var Renderer = GObject.registerClass( class Renderer extends GObject.Object { - _init(state) { + _init(state, settings) { super._init(); this._state = state; + this._settings = settings; + this.gaps = { + smart: true, + size: 10, + outerGaps: 20, + }; log("fairy init!"); } @@ -23,6 +29,12 @@ var Renderer = GObject.registerClass( enable() { this._bindSignals(); + this.gaps = { + smart: this._settings.get_boolean("smart-gaps"), + size: this._settings.get_uint("gap-size"), + outerGaps: this._settings.get_uint("outer-gap-size"), + }; + for (const window of global.display.list_all_windows()) this.trackWindow(window); @@ -69,8 +81,7 @@ var Renderer = GObject.registerClass( // Wait until window actor is available let waitForWindowId = GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { // Stop if the window doesn't exist anymore or the parent is gone - if (!window.get_workspace()) - return GLib.SOURCE_REMOVE; + if (!window.get_workspace()) return GLib.SOURCE_REMOVE; // Continue if there's no actor available yet if (!windowActor) { @@ -116,6 +127,8 @@ var Renderer = GObject.registerClass( } } this._state.windows = []; + + this._settings.disconnect("changed"); } _bindSignals() { @@ -123,7 +136,11 @@ var Renderer = GObject.registerClass( global.display.connect("window-created", (_display, window) => this._waitForWindow(window, () => { this.trackWindow(window); - this._state.focus(window); + GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { + this._state.focus(window); + // Do not retrigger this idle. + return false; + }); this.renderForWindow(window); }) ), @@ -135,8 +152,8 @@ var Renderer = GObject.registerClass( global.workspace_manager.connect("active-workspace-changed", () => { // Convert gnome workspaces to fairy's tags const workspace = global.display - .get_workspace_manager() - .get_active_workspace_index(); + .get_workspace_manager() + .get_active_workspace_index(); const tags = 0b1 << workspace; log("Switch to tags", tags); if (Meta.prefs_get_workspaces_only_on_primary()) { @@ -150,6 +167,24 @@ var Renderer = GObject.registerClass( } }), ]; + + this._settings.connect("changed", (_, key) => { + log("Proprety changed", key); + switch (key) { + case "gap-size": + this.gaps.size = this._settings.get_uint(key); + this.renderAll(); + break; + case "outer-gap-size": + this.gaps.outerGaps = this._settings.get_uint(key); + this.renderAll(); + break; + case "smart-gaps": + this.gaps.smart = this._settings.get_boolean(key); + this.renderAll(); + break; + } + }); } /** @@ -215,11 +250,11 @@ var Renderer = GObject.registerClass( const tags = this._state.monitors[mon].tags; // This retrieve the lower tag present in the tags set. - const tag = (tags & ~(tags - 1)); + const tag = tags & ~(tags - 1); if (tags !== tag) return; // Retrieve the gnome workspace for the tag (inverse of 0b1 << tag) const workspace = Math.log2(tag); - console.log("Switching to", tags, tag, workspace) + console.log("Switching to", tags, tag, workspace); global.display .get_workspace_manager() @@ -255,10 +290,10 @@ var Renderer = GObject.registerClass( .get_workspace_manager() .get_active_workspace_index(); - for (const window of this._state.render(mon, tags)) { + const windows = this._state.render(mon, tags); + for (const window of windows) { if (window.handle.get_monitor() !== mon) window.handle.move_to_monitor(mon); - // if (!(window.tags & 0b1 << workIdx) || window.currentWorkspace !== workIdx) { if (window.handle.get_workspace().index() !== workIdx) { // The window is visible because another tag as been bringed // so we need to ask gnome to move windows (temporarly to the current workspace) @@ -282,35 +317,47 @@ var Renderer = GObject.registerClass( window.handle.maximize(Meta.MaximizeFlags.BOTH); // Do not resize if maximizing to keep the overview tiled. continue; - } - else window.handle.unmaximize(Meta.MaximizeFlags.BOTH); + } else window.handle.unmaximize(Meta.MaximizeFlags.BOTH); } - // TODO: Add gaps - log( - "Rezing percent: ", - window.x, - window.y, - window.width, - window.height, - "Real values", - monGeo.x + (window.x * monGeo.width) / 100, - monGeo.y + (window.y * monGeo.height) / 100, - (window.width * monGeo.width) / 100, - (window.height * monGeo.height) / 100 - ); + let size = { + x: (window.x * monGeo.width) / 100, + y: (window.y * monGeo.height) / 100, + width: (window.width * monGeo.width) / 100, + height: (window.height * monGeo.height) / 100, + }; - // let cmpWindow = window.handle.get_compositor_private(); - // cmpWindow.remove_all_transitions(); + if (this._state.monitors[mon].layout !== "monocle" && (windows.length > 1 || !this.gaps.smart)) + size = this.addGaps(size, monGeo); window.handle.move_resize_frame( true, - monGeo.x + (window.x * monGeo.width) / 100, - monGeo.y + (window.y * monGeo.height) / 100, - (window.width * monGeo.width) / 100, - (window.height * monGeo.height) / 100 + monGeo.x + size.x, + monGeo.y + size.y, + size.width, + size.height ); } } + + addGaps(window, monGeo) { + const gapSize = this.gaps.size; + // Inner gaps are applied two times so to make outers the same visual size we 2x + const outerGaps = this.gaps.outerGaps * 2; + + return { + ...window, + x: window.x === 0 ? outerGaps : window.x + gapSize, + y: window.y === 0 ? outerGaps : window.y + gapSize, + width: + window.width - + (window.x === 0 ? outerGaps : gapSize) - + (window.x + window.width === monGeo.width ? outerGaps : gapSize), + height: + window.height - + (window.y === 0 ? outerGaps : gapSize) - + (window.y + window.height === monGeo.height ? outerGaps : gapSize), + }; + } } );