From 14c1c5ff572abd98349a5fbc724a4631033dd322 Mon Sep 17 00:00:00 2001 From: Aetf Date: Sat, 27 Oct 2018 15:11:35 -0400 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=20Feature:=20Refactor=20code=20to?= =?UTF-8?q?=20use=20configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 18 +- README.md | 8 +- contents/code/main.js | 386 ++++++++++++++++++++++++++++++++------- contents/config/main.xml | 3 +- contents/ui/config.ui | 7 +- 5 files changed, 349 insertions(+), 73 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bf7786..f2364af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,20 @@ -# 0.1.0 +# Change Log + +## 0.2.0 + +- :bug: Fixed: Window loses focus after being moved to new desktop. +- :sparkles: Feature: Trigger by window maximize +- :sparkles: Feature: Customizable new desktop position +- :sparkles: Feature: Keep non-empty desktop +- :sparkles: Feature: Skip blacklisted windows +- :sparkles: Feature: Configuration UI +- :recycle: Improved: Complete rewrite. + +## 0.1.0 + - :bug: Fixed: No more extra VD after closing fullscreened window directly. - :recycle: Improved: Code refactorings. -# 0.0.1 +## 0.0.1 + - Initial release. Mostly functional. diff --git a/README.md b/README.md index 9d2cc36..a7ba46d 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,11 @@ The config panel ## Feature -* Move window to a newly created virtual desktop when fullscreen. +* Move window to a newly created virtual desktop when fullscreen/maximize. * Move window back to original desktop when restored to normal size or closed. -* Configurable trigger: fullscreen or maximize or _both_. -* Configurable new desktop position: right most or right to current. -* Window blacklist. Skip some windows that should not trigger the script. +* Configurable trigger: `fullscreen` or `maximize` or `both`. +* Configurable new desktop position: `right most` or `next to current desktop` or `next to app`. +* Window blacklist: windows that match the class name will not trigger the script. __Note__: The default behavior is triggered by window **FULLSCREEN**, not the normal maximize. Window fullscreen can be enabled by right clicking on the window decoration -> `More Actions` -> `Fullscreen`. diff --git a/contents/code/main.js b/contents/code/main.js index e0e4af9..bfb6b98 100644 --- a/contents/code/main.js +++ b/contents/code/main.js @@ -1,89 +1,345 @@ -var state = { - savedDesktops: {}, - enabled: true -}; - function log(msg) { print("KWinMax2NewVirtualDesktop: " + msg); } -function moveToNewDesktop(client) { - state.savedDesktops[client.windowId] = client.desktop; +/* + * Enum that should match the config in main.xml + */ +const TriggerValues = { + FullscreenOnly: 0, + MaximizeOnly: 1, + FullscreenAndMaximize: 2 +}; +/* + * Enum that should match the config in main.xml + */ +const NewDesktopPositionValue = { + RightMost: 0, + NextToCurrent: 1, + NextToApp: 2, +}; - var next = workspace.desktops + 1; - workspace.desktops = next; - client.desktop = next; - workspace.currentDesktop = next; - workspace.activateClient = client; +function Config() { } +Config.prototype.trigger = function() { + var v = readConfig('trigger', 'FullscreenOnly'); + return TriggerValues[v]; +}; +Config.prototype.newDesktopPosition = function() { + var v = readConfig("newDesktopPosition", 'RightMost'); + return NewDesktopPositionValue[v]; +}; -function moveBack(client) { - var saved = state.savedDesktops[client.windowId]; - if (saved === undefined) { - log("Ignoring window not previously seen: " + client.caption); - } else { - log("Resotre client desktop to " + saved); - client.desktop = saved; - workspace.currentDesktop = saved; - workspace.activateClient = client; +Config.prototype.keepNonEmptyDesktop = function() { + var v = readConfig("keepNonEmptyDesktop", false); + // convert to primitive value + return Boolean.prototype.valueOf(v); +}; - workspace.desktops -= 1; +Config.prototype.blockWMClass = function() { + var classes = readConfig("blockWMClass", "").toString(); + classes = classes.split(","); + return classes; +}; + +function State() { + this.savedDesktops = {}; + this.enabled = true; + this.config = new Config(); +} + +State.prototype.isTriggeredByFull = function() { + return this.config.trigger() === TriggerValues.FullscreenOnly + || this.config.trigger() === TriggerValues.FullscreenAndMaximize; +} +State.prototype.isTriggeredByMax = function() { + return this.config.trigger() === TriggerValues.MaximizeOnly + || this.config.trigger() === TriggerValues.FullscreenAndMaximize; +} + +State.prototype.isKnownClient = function(client) { + return this.savedDesktops.hasOwnProperty(client.windowId); +} + +State.prototype.isSkippedClient = function (client) { + var idx = this.config.blockWMClass().indexOf(client.resourceClass.toString()); + return idx != -1; +} + +State.prototype.debugDump = function() { + log(''); + log('state: enabled ' + this.enabled); + log('state: triggerFull ' + this.isTriggeredByFull()); + log('state: triggerMax ' + this.isTriggeredByMax()); + log('state: newPosition ' + this.config.newDesktopPosition()); + log('state: blockWMClass ' + this.config.blockWMClass().toString()); + log('state: savedDesktops size ' + Object.keys(this.savedDesktops).length); + for (var client in this.savedDesktops) { + log('state: savedDesktops: ' + client + ' => ' + this.savedDesktops[client]); + } + log(''); +} + +function Main() { + this.state = new State(); + + // signal handler's this will be the global object + var self = this; + + this.handlers = { + fullscreen: function(client, full) { + log('handle fullscreen'); + self.state.debugDump(); + if (!self.state.isTriggeredByFull() || self.state.isSkippedClient(client)) { + log('handle fullscreen return'); + return; + } + if (full) { + self.moveToNewDesktop(client); + } else { + log("moving back: " + client.caption); + self.moveBack(client); + } + self.state.debugDump(); + log('handle fullscreen done'); + }, + + maximize: function(client, h, v) { + log('handle maximize'); + self.state.debugDump(); + if (!self.state.isTriggeredByMax() || self.state.isSkippedClient(client)) { + log('handle maximize return'); + return; + } + if (h && v) { + self.moveToNewDesktop(client); + } else { + self.moveBack(client); + } + self.state.debugDump(); + log('handle maximize done'); + }, + + removed: function(client) { + log('handle remove'); + self.state.debugDump(); + if (!self.state.isKnownClient(client) || self.state.isSkippedClient(client)) { + log('handle remove return'); + return; + } + self.moveBack(client); + self.state.debugDump(); + log('handle remove done'); + }, + + createUserActionsMenu: function(client) { + log("Creating user menu"); + self.state.debugDump(); + return { + text: "Maximize to New Desktop", + items: [ + { + text: "Enabled", + checkable: true, + checked: self.state.enabled, + triggered: function() { + self.state.enabled = !self.state.enabled; + if (self.state.enabled) { + self.install(); + } else { + self.uninstall(); + } + } + }, + { + text: 'FullTriggered', + checkable: true, + checked: self.state.isTriggeredByFull(), + triggered: function () { + return; + } + }, + { + text: 'MaxTriggered', + checkable: true, + checked: self.state.isTriggeredByMax(), + triggered: function () { + return; + } + }, + { + text: 'KeepNonEmpty', + checkable: true, + checked: self.state.config.keepNonEmptyDesktop(), + triggered: function () { + return; + } + }, + ] + } + } + } +} + +Main.prototype.getNextDesktop = function (client) { + switch (this.state.config.newDesktopPosition()) { + case NewDesktopPositionValue.RightMost: + log('RightMost, workspace.desktops is ' + workspace.desktops); + return workspace.desktops + 1; + case NewDesktopPositionValue.NextToCurrent: + log('NextToCurrent, workspace.currentDesktop is ' + workspace.currentDesktop); + return workspace.currentDesktop + 1; + case NewDesktopPositionValue.NextToApp: + log('NextToApp, client.desktop is ' + client.desktop); + return client.desktop + 1; + default: + log('default, workspace.desktops is ' + workspace.desktops); + return workspace.desktops + 1; } } -function fullHandler(client, full, user) { - if (full) { - moveToNewDesktop(client); +// If the desktop at pos can be removed +Main.prototype.shouldRemoveDesktop = function(pos) { + if (pos <= 0) { + return false; + } + + if (!this.state.config.keepNonEmptyDesktop()) { + return true; + } + + // only remove if the desktop is empty + var count = 0; + const clients = workspace.clientList(); + for (var i = 0; i < clients.length; i++) { + if (clients[i].desktop == pos) { + count++; + } + } + return count == 0; +}; + +// shift clients' desktop on desktop [from, to], to direction +Main.prototype.shiftClients = function (direction, from, to) { + to = typeof to !== 'undefined' ? to : workspace.desktops; + + // note that client.desktop is 1-based. + const clients = workspace.clientList(); + for (var i = 0; i < clients.length; i++) { + if (clients[i].desktop >= from && clients[i].desktop <= to) { + clients[i].desktop += direction; + } + } + // also updated saved desktop info + for (var clientId in this.state.savedDesktops) { + if (this.state.savedDesktops[clientId] >= from + && this.state.savedDesktops[clientId] <= to) { + this.saved.savedDesktops[clientId] += direction; + } + } +} + +// Insert a new desktop at pos and switch to it, which is 1-based. +// if pos > workspace.desktops, new desktops will be created +// if pos <= workspace.desktops, new desktops will be created at end +// and clients will be shifted +Main.prototype.insertDesktop = function (pos) { + log("Inserting a desktop at " + pos); + // save old current + var oldCurrent = workspace.currentDesktop; + if (oldCurrent >= pos) { + oldCurrent += 1; + } + + if (pos > workspace.desktops) { + workspace.desktops = pos; } else { - moveBack(client); + // add one desktop and shift clients + workspace.desktops += 1; + this.shiftClients(+1, pos); } + + // switch to new desktop + workspace.currentDesktop = pos; + + return pos; } -function rmHandler(client) { - moveBack(client); +// remove desktop at pos, shifting clients as needed +Main.prototype.popDesktop = function (pos) { + log('popDesktop: ' + pos); + if (pos > workspace.desktops) { + log('popDesktop: pos > workspace.desktops: ' + workspace.desktops); + return; + } + + if (!this.shouldRemoveDesktop(pos)) { + log('popDesktop: should not remove desktop'); + return; + } + + if (pos == workspace.desktops) { + log('popDesktop: removing'); + workspace.desktops -= 1; + } else { + log('popDesktop: shifting'); + this.shiftClients(-1, pos); + log('popDesktop: removing'); + workspace.desktops -= 1; + } +} + +Main.prototype.moveToNewDesktop = function(client) { + this.state.savedDesktops[client.windowId] = client.desktop; + + var next = this.getNextDesktop(client); + this.insertDesktop(next); + client.desktop = next; + + // make sure the client is activated + workspace.activeClient = client; } -function install() { - workspace.clientFullScreenSet.connect(fullHandler); - workspace.clientRemoved.connect(rmHandler); +Main.prototype.moveBack = function(client) { + if (!this.state.isKnownClient(client)) { + log("Ignoring window not previously seen: " + client.caption); + return; + } + + log("inside moving back: " + client.caption); + var saved = this.state.savedDesktops[client.windowId]; + var toRemove = client.desktop; + + log("Resotre client desktop to " + saved); + client.desktop = saved; + workspace.currentDesktop = saved; + workspace.activeClient = client; + delete this.state.savedDesktops[client.windowId]; + + this.popDesktop(toRemove); +} + + +Main.prototype.install = function() { + workspace.clientFullScreenSet.connect(this.handlers.fullscreen); + workspace.clientMaximizeSet.connect(this.handlers.maximize); + workspace.clientRemoved.connect(this.handlers.removed); log("Handler installed"); } -function uninstall() { - workspace.clientFullScreenSet.disconnect(fullHandler); - workspace.clientRemoved.disconnect(rmHandler); +Main.prototype.uninstall = function() { + workspace.clientFullScreenSet.disconnect(this.handlers.fullscreen); + workspace.clientMaximizeSet.disconnect(this.handlers.maximize); + workspace.clientRemoved.disconnect(this.handlers.removed); log("Handler cleared"); } -registerUserActionsMenu(function(client){ - return { - text: "Maximize to New Desktop", - items: [ - { - text: "Enabled", - checkable: true, - checked: state.enabled, - triggered: function() { - state.enabled = !state.enabled; - if (state.enabled) { - install(); - } else { - uninstall(); - } - } - }, - /* - { - text: "Disable for this window", - checkable: true, - checked: false, - triggered: function(act) { - log('Not implemented yet!'); - } - } - */ - ] - }; -}); +Main.prototype.init = function() { + // this.state.debugDump(); + // registerUserActionsMenu(this.handlers.createUserActionsMenu); + // this.state.debugDump(); + this.install(); +}; -install(); +main = new Main(); +main.init(); \ No newline at end of file diff --git a/contents/config/main.xml b/contents/config/main.xml index eb3721c..67ab6ba 100644 --- a/contents/config/main.xml +++ b/contents/config/main.xml @@ -19,7 +19,8 @@ - + + diff --git a/contents/ui/config.ui b/contents/ui/config.ui index 4bd00a2..daafbb8 100644 --- a/contents/ui/config.ui +++ b/contents/ui/config.ui @@ -95,7 +95,12 @@ - Right To Current + Next To Current + + + + + Next To App