diff --git a/extension.js b/extension.js index d90c868..cf602e1 100644 --- a/extension.js +++ b/extension.js @@ -1,10 +1,7 @@ -const Gio = imports.gi.Gio; -const St = imports.gi.St; +"use strict"; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); -const Main = imports.ui.main; -const PanelMenu = imports.ui.panelMenu; const State = Me.imports.sources.state; const Renderer = Me.imports.sources.renderer; diff --git a/prefs.js b/prefs.js index d2568f5..034e3ef 100644 --- a/prefs.js +++ b/prefs.js @@ -48,13 +48,21 @@ function fillPreferencesWindow(window) { const keybinds = new Adw.PreferencesPage(); keybinds.set_title("Keybinds"); + + const layoutBindings = new Adw.PreferencesGroup(); + keybinds.add(layoutBindings); + layoutBindings.add(_createKeybind(settings, "set-layout-tiling")); + layoutBindings.add(_createKeybind(settings, "set-layout-monocle")); + layoutBindings.add(_createKeybind(settings, "set-layout-floating")); + + const focusBindings = new Adw.PreferencesGroup(); + keybinds.add(focusBindings); + focusBindings.add(_createKeybind(settings, "cycle-next")); + focusBindings.add(_createKeybind(settings, "cycle-prev")); + focusBindings.add(_createKeybind(settings, "zoom")); + const tileBindings = new Adw.PreferencesGroup(); keybinds.add(tileBindings); - - tileBindings.add(_createKeybind(settings, "set-layout-tiling")); - tileBindings.add(_createKeybind(settings, "set-layout-monocle")); - tileBindings.add(_createKeybind(settings, "set-layout-floating")); - tileBindings.add(_createKeybind(settings, "incrmfact")); tileBindings.add(_createKeybind(settings, "decmfact")); tileBindings.add(_createKeybind(settings, "incrnmaster")); diff --git a/sources/indicator.js b/sources/indicator.js index 84012a3..05121e7 100644 --- a/sources/indicator.js +++ b/sources/indicator.js @@ -1,9 +1,14 @@ "use strict"; +const Gio = imports.gi.Gio; +const St = imports.gi.St; + const GObject = imports.gi.GObject; const Main = imports.ui.main; const Meta = imports.gi.Meta; const Shell = imports.gi.Shell; +const PanelMenu = imports.ui.panelMenu; + const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); @@ -20,12 +25,9 @@ var Indicator = GObject.registerClass( "org.gnome.shell.extensions.fairy" ); - let indicatorName = `${Me.metadata.name} Indicator`; - - this._layoutIndicator = new PanelMenu.Button(0.0, indicatorName, false); - - // Add an icon - let icon = new St.Icon({ + const indicatorName = `${Me.metadata.name} Indicator`; + this._layoutIndicator = new PanelMenu.Button(0.0, indicatorName); + const icon = new St.Icon({ gicon: new Gio.ThemedIcon({ name: "face-laugh-symbolic" }), style_class: "system-status-icon", }); @@ -37,7 +39,6 @@ var Indicator = GObject.registerClass( "visible", Gio.SettingsBindFlags.DEFAULT ); - Main.panel.addToStatusArea(indicatorName, this._layoutIndicator); } diff --git a/sources/keybinds.js b/sources/keybinds.js index 55efeab..3bb59c0 100644 --- a/sources/keybinds.js +++ b/sources/keybinds.js @@ -39,26 +39,48 @@ var KeyboardManager = GObject.registerClass( Main.wm.removeKeybinding(key); } + _switchLayout(mode) { + const mon = global.display.get_current_monitor(); + const state = this._state.monitors[mon]; + const currentLayout = state.layout; + if (state.layout === mode) + state.layout = state.oldLayout; + else + state.layout = mode; + state.oldLayout = currentLayout; + this._renderer.render(mon); + } + enable() { - this._addBinding("set-layout-tiling", () => { + this._addBinding("set-layout-tiling", () => this._switchLayout("tiling")); + this._addBinding("set-layout-monocle", () => this._switchLayout("monocle")); + this._addBinding("set-layout-floating", () => this._switchLayout("floating")); + + + this._addBinding("cycle-next", () => { const mon = global.display.get_current_monitor(); const state = this._state.monitors[mon]; - state.layout = "tiling"; - this._renderer.render(mon); + const idx = this._state.workIndexByHandle(state.focused); + this._state.focus(this._state.workIndex(mon, state.tags, idx + 1)); }); - this._addBinding("set-layout-monocle", () => { + this._addBinding("cycle-prev", () => { const mon = global.display.get_current_monitor(); const state = this._state.monitors[mon]; - state.layout = "monocle"; - this._renderer.render(mon); + const idx = this._state.workIndexByHandle(state.focused); + this._state.focus(this._state.workIndex(mon, state.tags, idx - 1)); }); - this._addBinding("set-layout-floating", () => { + this._addBinding("zoom", () => { const mon = global.display.get_current_monitor(); const state = this._state.monitors[mon]; - state.layout = "floating"; - this._renderer.render(mon); + const idx = this._state.workIndexByHandle(state.focused); + if (this._state.workIndexByHandle(state.focused)) + this._state.focus(this._state.workIndex(mon, state.tags, 0)); + else + this.state.focus(state.beforeZoom); + state.beforeZoom = state.focused; }); + this._addBinding("incrmfact", () => { const mon = global.display.get_current_monitor(); const state = this._state.monitors[mon]; @@ -90,6 +112,10 @@ var KeyboardManager = GObject.registerClass( this._removeBinding("set-layout-monocle"); this._removeBinding("set-layout-floating"); + this._removeBinding("cycle-next"); + this._removeBinding("cycle-prev"); + this._removeBinding("zoom"); + this._removeBinding("incrmfact"); this._removeBinding("decrmfact"); this._removeBinding("incrnmaster"); diff --git a/sources/renderer.js b/sources/renderer.js index e113e8f..b2dcc04 100644 --- a/sources/renderer.js +++ b/sources/renderer.js @@ -123,6 +123,7 @@ var Renderer = GObject.registerClass( global.display.connect("window-created", (_display, window) => this._waitForWindow(window, () => { this.trackWindow(window); + this._state.focus(window); this.renderForWindow(window); }) ), @@ -159,10 +160,18 @@ var Renderer = GObject.registerClass( if (!this._isValidWindow(window)) return; // Add window signals window._signals = [ - window.connect("unmanaging", (window) => { - window._isInvalid = true; - const faWindow = this._state.popByActor(actor); - if (faWindow) this.render(faWindow.monitor, faWindow.tags); + window.connect("unmanaging", (handle) => { + handle._isInvalid = true; + const idx = this._state.workIndexByHandle(handle); + const faWindow = this._state.popByHandle(handle); + if (!faWindow) return; + + const tags = this._state.monitors[faWindow.monitor].tags; + // Since we retrieved the idx, the window as been removed so we don't need to +1. + const newWindow = this._state.workIndex(faWindow.monitor, tags, idx); + if (newWindow) this._state.focus(newWindow.handle); + + this.render(faWindow.monitor, tags); }), window.connect("workspace-changed", (window) => { if (!this._isValidWindow(window)) return; @@ -175,13 +184,6 @@ var Renderer = GObject.registerClass( this._state.monitors[window.get_monitor()].focused = window; }), ]; - const actor = window.get_compositor_private(); - // actor._signals = [ - // actor.connect("destroy", (actor) => { - // const faWindow = this._state.popByActor(actor); - // if (faWindow) this.render(faWindow.monitor, faWindow.tags); - // }), - // ]; this._state.newWindow(window); } diff --git a/sources/state.js b/sources/state.js index fff60b4..0eb983f 100644 --- a/sources/state.js +++ b/sources/state.js @@ -12,8 +12,13 @@ var StateManager = GObject.registerClass( * @type {Meta.Window} focused window's handle */ focused: null, + /** + * @type {Meta.Window} window's handle that was focused just before a zoom + */ + beforeZoom: null, tags: 1, layout: "tiling", + oldLayout: "monocle", nmaster: 1, mfact: 55, })); @@ -46,8 +51,8 @@ var StateManager = GObject.registerClass( * @param {Meta.Window} handle */ newWindow(handle) { - this.windows.push(this._windowFromHandle(handle)); - log("New window on tag", this.windows[this.windows.length - 1].tags); + this.windows.unshift(this._windowFromHandle(handle)); + log("New window on tag", this.windows[0].tags); } /** @@ -61,13 +66,53 @@ var StateManager = GObject.registerClass( return [old, this.windows[i]]; } - popByActor(actor) { - const window = this.windows.find((x) => x.actor === actor); + popByHandle(handle) { + const window = this.windows.find((x) => x.handle === handle); if (!window) return null; this.windows = this.windows.filter((x) => x !== window); return window; } + /** + * @param {number} mon + * @param {number} tags + * @param {number} idx (will loop if over/under flow) + * @returns {FairyWindow} + */ + workIndex(mon, tags, idx) { + const windows = this.windows.filter( + (x) => x.monitor === mon && x.tags & tags + ); + if (idx < 0) idx = windows.length + idx; + return windows[idx % windows.length]; + } + + /** + * @param {Meta.Window} handle + * @returns {number} idx + */ + workIndexByHandle(handle) { + const window = this.windows.find((x) => x.handle === handle); + const windows = this.windows.filter( + (x) => x.monitor === window.monitor && x.tags & window.tags + ); + return windows.findIndex((x) => x.handle === handle); + } + + /** + * @param {Meta.Window} handle + * @param {boolean} internalOnly + */ + focus(handle, internalOnly = false) { + const mon = handle.get_monitor(); + this.monitors[mon].focused = handle; + // This was focused without a zoom, removing the old zoom value. + this.monitors[mon].beforeZoom = null; + + if (!internalOnly) + handle.focus(global.display.get_current_time()); + } + /** * @param {number} mon * @param {number} tags @@ -116,14 +161,14 @@ var StateManager = GObject.registerClass( handle: x.handle, maximized: false, minimized: false, - x: (i < nmaster || nmaster <= 0) ? 0 : mfact, + x: i < nmaster || nmaster <= 0 ? 0 : mfact, y: stackIndex * (100 / stackLength), width: - (windows.length <= nmaster || nmaster <= 0) + windows.length <= nmaster || nmaster <= 0 ? 100 : i < nmaster - ? mfact - : 100 - mfact, + ? mfact + : 100 - mfact, height: 100 / stackLength, }; });