convert to typescript

This commit is contained in:
Eon S. Jeon 2018-11-08 00:07:04 +09:00
parent 77f3d34799
commit 4f455d8e46
5 changed files with 338 additions and 209 deletions

View File

@ -18,63 +18,83 @@
// 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;
class KWinDriver
{
engine: TilingEngine;
/*
* Signal handlers
*/
self._onClientAdded = function(client) {
// DEBUG: print("clientAdded " + client + " " + client.resourceClass);
private bindEvents()
{
workspace.clientAdded.connect(this.onClientAdded);
workspace.clientRemoved.connect(this.onClientRemoved);
workspace.numberScreensChanged.connect(this.onNumberScreensChanged);
var engine_arrange = () => { this.engine.arrange(); }
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(???)
}
private onClientAdded = (client: KWin.Client) =>
{
// TODO: check resourceClasses for some windows
if(!engine.clientManage(client))
return;
this.engine.manageClient(client);
client.desktopChanged.connect(engine.arrange);
client.geometryChanged.connect(function() {
client.desktopChanged.connect(this.engine.arrange);
client.geometryChanged.connect(() => {
if(client.move || client.resize) return;
engine.clientArrange(client);
this.engine.arrangeClient(client);
});
client.moveResizedChanged.connect(function() {
client.moveResizedChanged.connect(() => {
if(client.move || client.resize) return;
engine.arrange();
this.engine.arrange();
});
// DEBUG: print(" -> numTiles=" + engine.tiles.length);
};
self._onClientRemoved = function(client) {
// DEBUG: print("clientRemoved " + client);
private onClientRemoved = (client: KWin.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`.
*/
engine.clientUnmanage(client);
// DEBUG: print(" -> numTiles=" + engine.tiles.length);
this.engine.unmanageClient(client);
};
self._onNumberScreensChanged = function(count) {
// DEBUG: print("numberScreenChanged " + count);
while(engine.screens.length < count)
engine.screenAdd(engine.screens.length);
while(engine.screens.length > count)
engine.screenRemove(engine.screens.length - 1);
private onNumberScreensChanged = (count: number) =>
{
while(this.engine.screens.length < count)
this.engine.addScreen(this.engine.screens.length);
while(this.engine.screens.length > count)
this.engine.screenRemove(this.engine.screens.length - 1);
};
/*
* Utils
*/
self.getWorkingArea = function(screenId) {
public getWorkingArea(screenId: number): QRect
{
// TODO: verify: can each desktops have a different placement area?
return workspace.clientArea(
KWin.PlacementArea, screenId, workspace.currentDesktop);
}
self.getClientGeometry = function(client) {
public getClientGeometry(client: KWin.Client): QRect
{
return {
x: client.geometry.x,
y: client.geometry.y,
@ -83,11 +103,18 @@ function KWinDriver() {
};
}
self.setClientGeometry = function(client, x, y, width, height) {
client.geometry = { x:x, y:y, width:width, height:height };
public setClientGeometry(client: KWin.Client, geometry: QRect)
{
client.geometry = {
x: geometry.x,
y: geometry.y,
width: geometry.width,
height: geometry.height
};
}
self.isClientVisible = function(client, screenId) {
public isClientVisible(client: KWin.Client, screenId: number)
{
// TODO: test KWin::Toplevel properties...?
return (
(!client.minimized) &&
@ -101,30 +128,13 @@ function KWinDriver() {
* Main
*/
self.main = function() {
engine = new TilingEngine(self);
public main()
{
this.engine = new TilingEngine(this);
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(???)
self._onNumberScreensChanged(workspace.numScreens);
workspace.clientList().map(self._onClientAdded);
this.bindEvents();
this.onNumberScreensChanged(workspace.numScreens);
workspace.clientList().map(this.onClientAdded);
}
}

View File

@ -1,157 +0,0 @@
// 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.layoutOpts = {}
}
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(driver) {
var self = this;
self.tiles = Array();
self.screens = Array();
self.arrange = function() {
self.screens.forEach(function(screen) {
if(screen.layout === null)
return;
var area = driver.getWorkingArea(screen.id);
var visibles = self.tiles
.filter(function(t) {
try {
return driver.isClientVisible(t.client, screen.id);
} catch(e) {
t.isError = true;
}
return false;
});
// TODO: fullscreen handling
screen.layout(visibles, area.width, area.height, screen.layoutOpts);
visibles.forEach(function(tile) {
driver.setClientGeometry(
tile.client, tile.x, tile.y, tile.width, tile.height);
});
});
}
self.clientArrange = function(client) {
self.tiles.forEach(function(tile) {
if(tile.client != client) return;
var geometry = driver.getClientGeometry(tile.client);
if(geometry.x == tile.x)
if(geometry.y == tile.y)
if(geometry.width == tile.width)
if(geometry.height == tile.height)
return;
driver.setClientGeometry(
tile.client, tile.x, tile.y, tile.width, tile.height);
});
}
self.clientManage = function(client) {
if(client.specialWindow)
return false;
self.tiles.push(new Tile(client));
self.arrange();
return true;
}
self.clientUnmanage = function(client) {
self.tiles = self.tiles
.filter(function(t) {
return t.client != client && !t.isError;
});
self.arrange();
}
self.screenAdd = function(screenId) {
self.screens.push(new Screen(screenId));
}
self.screenRemove = function(screenId) {
self.screens = self.screens
.filter(function(screen) {
return screen.id !== screenId;
});
}
}

177
src/engine.ts Normal file
View File

@ -0,0 +1,177 @@
// 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.
class Screen
{
id: number;
layout: any;
layoutOpts: any;
constructor(id: number)
{
this.id = id;
this.layout = layout_tile;
this.layoutOpts = {};
}
}
class Tile
{
client: KWin.Client;
isNew: boolean;
isError: boolean;
geometry: QRect;
constructor(client: KWin.Client)
{
this.client = client;
this.isNew = true;
this.isError = false;
this.geometry = {
x: 0, y: 0,
width: 0, height: 0
};
}
}
// TODO: declare Layout class (`layout.js`?)
// TODO: layouts in separate file(s)
function layout_tile(tiles: Tile[], areaWidth: number, areaHeight: number, opts: any) {
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].geometry.x = 0;
tiles[i].geometry.y = masterHeight * i;
tiles[i].geometry.width = masterWidth;
tiles[i].geometry.height = masterHeight;
}
for(var i = 0; i < stackCount; i++) {
var j = masterCount + i;
tiles[j].geometry.x = stackX;
tiles[j].geometry.y = stackHeight * i;
tiles[j].geometry.width = stackWidth;
tiles[j].geometry.height = stackHeight;
}
}
class TilingEngine
{
driver: KWinDriver;
tiles: Tile[];
screens: Screen[];
constructor(driver: KWinDriver)
{
this.driver = driver;
this.tiles = Array();
this.screens = Array();
}
public arrange = () =>
{
this.screens.forEach((screen) => {
if(screen.layout === null)
return;
var area = this.driver.getWorkingArea(screen.id);
var visibles = this.tiles.filter((t) => {
try {
return this.driver.isClientVisible(t.client, screen.id);
} catch(e) {
t.isError = true;
}
return false;
});
// TODO: fullscreen handling
screen.layout(visibles, area.width, area.height, screen.layoutOpts);
visibles.forEach((tile) => {
this.driver.setClientGeometry(tile.client, tile.geometry);
});
});
}
public arrangeClient = (client: KWin.Client) => {
this.tiles.forEach((tile) => {
if(tile.client != client) return;
var geometry = this.driver.getClientGeometry(tile.client);
if(geometry.x == tile.geometry.x)
if(geometry.y == tile.geometry.y)
if(geometry.width == tile.geometry.width)
if(geometry.height == tile.geometry.height)
return;
this.driver.setClientGeometry(tile.client, tile.geometry);
});
}
public manageClient = (client: KWin.Client) => {
// TODO: move this to KWinDriver
if(client.specialWindow)
return false;
this.tiles.push(new Tile(client));
this.arrange();
return true;
}
public unmanageClient = (client: KWin.Client) => {
this.tiles = this.tiles.filter(function(t) {
return t.client != client && !t.isError;
});
this.arrange();
}
public addScreen = (screenId: number) => {
this.screens.push(new Screen(screenId));
}
public screenRemove = (screenId: number) => {
this.screens = this.screens.filter(function(screen) {
return screen.id !== screenId;
});
}
}

89
src/kwin.d.ts vendored Normal file
View File

@ -0,0 +1,89 @@
// API Reference:
// https://techbase.kde.org/Development/Tutorials/KWin/Scripting/API_4.9
interface QRect {
x: number;
y: number;
width: number;
height: number;
}
interface QSignal {
connect(callback: any): void;
}
declare namespace KWin {
/* enum ClientAreaOption */
var PlacementArea: number;
function registerShortcut(
title: string,
text: string,
keySequence: string,
callback: any
): boolean;
interface WorkspaceWrapper {
/* read-only */
readonly numScreens: number;
/* read-write */
currentDesktop: number;
/* signals */
clientAdded: QSignal;
clientRemoved: QSignal;
numberScreensChanged: QSignal;
clientMinimized: QSignal;
clientUnminimized: QSignal;
currentDesktopChanged: QSignal;
screenResized: QSignal;
clientMaximizeSet: QSignal;
clientFullScreenSet: QSignal;
currentActivityChanged: QSignal;
activitiesChanged: QSignal;
activityAdded: QSignal;
activityRemoved: QSignal;
numberDesktopsChanged: QSignal;
/* functions */
clientList(): Client[];
clientArea(option: number, screen: number, desktop: number);
}
interface Toplevel {
/* read-only */
readonly screen: number;
readonly resourceName: string;
readonly resourceClass: string;
readonly windowRole: string;
/* signal */
geometryChanged: QSignal;
}
interface Client extends Toplevel {
/* read-only */
readonly caption: string;
readonly move: boolean;
readonly resize: boolean;
readonly specialWindow: boolean;
/* read-write */
desktop: number;
onAllDesktops: boolean;
fullScreen: boolean;
geometry: QRect;
keepAbove: boolean;
keepBelow: boolean;
minimized: boolean;
noBorder: boolean;
/* signals */
desktopChanged: QSignal;
moveResizedChanged: QSignal;
}
}
declare var workspace: KWin.WorkspaceWrapper;

10
tsconfig.json Normal file
View File

@ -0,0 +1,10 @@
{
"compileOnSave": true,
"compilerOptions": {
"target": "es5",
"outFile": "script.js",
"removeComments": true,
"lib": ["es5"],
"alwaysStrict": true
}
}