Initial implementation

This commit is contained in:
Eon S. Jeon 2018-11-06 15:38:22 +09:00
parent fde5461eb7
commit 0a4905baa2
5 changed files with 287 additions and 1 deletions

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2018 Eon S. Jeon
Copyright (c) 2018 Eon S. Jeon <esjeon@hyunmu.am>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

5
README.md Normal file
View File

@ -0,0 +1,5 @@
Kröhnkite
=========
A dynamic tiling extension for KWin.

10
src/combine.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash
set -xe
(
cat engine.js;
cat main.js;
) \
| grep -v '^\s*\/\/' \
> combined.js

179
src/engine.js Normal file
View File

@ -0,0 +1,179 @@
// Copyright (c) 2018 Eon S. Jeon <esjeon@hyunmu.am>
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
function Screen(id) {
this.id = id;
this.layout = layout_tile; //null;
this.opts = {}
}
function Tile(client) {
this.client = client;
this.isNew = true;
this.isError = false;
this.x = 0;
this.y = 0;
this.width = 0;
this.height = 0;
}
// TODO: declare Layout class (`layout.js`?)
// TODO: layouts in separate file(s)
function layout_tile(tiles, areaWidth, areaHeight, opts) {
if(!opts.tile_ratio) opts.tile_ratio = 0.45;
if(!opts.tile_nmaster) opts.tile_nmaster = 1;
var masterCount, masterWidth, masterHeight;
var stackCount , stackWidth, stackHeight, stackX;
if(tiles.length <= opts.tile_nmaster) {
masterCount = tiles.length;
masterWidth = areaWidth;
masterHeight = Math.floor(areaHeight / masterCount);
stackCount = stackWidth = stackHeight = stackX = 0;
} else {
masterCount = opts.tile_nmaster;
masterWidth = Math.floor(areaWidth * (1 - opts.tile_ratio));
masterHeight = Math.floor(areaHeight / masterCount);
stackCount = tiles.length - masterCount;
stackWidth = areaWidth - masterWidth;
stackHeight = Math.floor(areaHeight / stackCount);
stackX = masterWidth + 1;
}
for(var i = 0; i < masterCount; i++) {
tiles[i].x = 0;
tiles[i].y = masterHeight * i;
tiles[i].width = masterWidth;
tiles[i].height = masterHeight;
}
for(var i = 0; i < stackCount; i++) {
var j = masterCount + i;
tiles[j].x = stackX;
tiles[j].y = stackHeight * i;
tiles[j].width = stackWidth;
tiles[j].height = stackHeight;
}
}
function TilingEngine() {
var self = this;
self.tiles = Array();
self.screens = Array();
self.manage = function(client, delayed) {
var delayed = delayed? true: false;
if(client.specialWindow)
return false;
self.tiles.push(new Tile(client));
if(!delayed)
self.arrange();
return true;
}
self.unmanage = function(client) {
self.tiles = self.tiles
.filter(function(t) {
return t.client != client && !t.isError;
});
self.arrange();
}
self.arrange = function() {
self.screens.forEach(function(screen) {
if(screen.layout === null)
return;
var desktop = workspace.currentDesktop;
// TODO: move direct calls to KWin to driver.
var area = workspace.clientArea(KWin.PlacementArea, screen.id, desktop);
var visibles = self.tiles
.filter(function(t) {
var c = t.client
try {
// TODO: test KWin::Toplevel properties...?
return (
(!c.minimized) &&
(c.desktop == desktop || c.desktop == -1) &&
(c.screen == screen.id)
);
} catch(e) {
t.isError = true;
return false;
}
});
// TODO: fullscreen handling
screen.layout(visibles, area.width, area.height, {});
// TODO: move direct calls to KWin to driver.
visibles.forEach(function(tile) {
tile.client.geometry = {
x: tile.x,
y: tile.y,
width: tile.width,
height: tile.height,
}
});
});
}
self.arrangeClient = function(client) {
// TODO: move direct calls to KWin to driver.
self.tiles.forEach(function(tile) {
if(tile.client != client) return;
if(client.geometry.x == tile.x)
if(client.geometry.y == tile.y)
if(client.geometry.width == tile.width)
if(client.geometry.height == tile.height)
return;
tile.client.geometry = {
x: tile.x,
y: tile.y,
width: tile.width,
height: tile.height,
}
});
}
self.addScreen = function(screenId) {
self.screens.push(new Screen(screenId));
}
self.removeScreen = function(screenId) {
self.screens = self.screens
.filter(function(screen) {
return screen.id !== screenId;
});
}
}

92
src/main.js Normal file
View File

@ -0,0 +1,92 @@
// Copyright (c) 2018 Eon S. Jeon <esjeon@hyunmu.am>
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
function KWinDriver() {
var self = this;
var engine = null;
self._onClientAdded = function(client) {
print("clientAdded " + client);
// TODO: check resourceClasses for some windows
if(engine.manage(client)) {
client.desktopChanged.connect(engine.arrange);
}
client.geometryChanged.connect(function() {
if(client.move || client.resize) return;
print("geometryChanged " + client);
engine.arrangeClient(client);
});
client.moveResizedChanged.connect(function() {
if(client.move || client.resize) return;
engine.arrange();
});
print(" -> numTiles=" + engine.tiles.length);
};
self._onClientRemoved = function(client) {
/* XXX: This is merely an attempt to remove the exited client.
* Sometimes, the client is not found in the tile list, and causes an
* exception in `engine.arrange`.
*/
print("clientRemoved " + client);
engine.unmanage(client);
print(" -> numTiles=" + engine.tiles.length);
};
self._onNumberScreensChanged = function(count) {
print("numberScreenChanged " + count);
while(engine.screens.length < count)
engine.addScreen(engine.screens.length);
while(engine.screens.length > count)
engine.removeScreen(engine.screens.length - 1);
};
self.main = function() {
engine = new TilingEngine(self);
workspace.clientAdded.connect(self._onClientAdded);
workspace.clientRemoved.connect(self._onClientRemoved);
workspace.numberScreensChanged.connect(self._onNumberScreensChanged);
workspace.clientMinimized.connect(engine.arrange);
workspace.clientUnminimized.connect(engine.arrange);
workspace.currentDesktopChanged.connect(engine.arrange);
// TODO: store screen size in engine?
workspace.screenResized.connect(engine.arrange);
// TODO: handle workspace.clientMaximizeSet signal
// TODO: handle workspace.clientFullScreenSet signal
// TODO: handle workspace.currentActivityChanged signal
// TODO: handle workspace.activitiesChanged signal
// TODO: handle workspace.activityAdded signal
// TODO: handle workspace.activityRemoved signal
// TODO: handle workspace.numberDesktopsChanged signal(???)
workspace.clientManaging.connect(function(client) {
print("clientManaging " + client);
});
self._onNumberScreensChanged(workspace.numScreens);
workspace.clientList().map(self._onClientAdded);
}
}
(new KWinDriver()).main();