feat: bury offline mode, ship antergos-layan-theme

remember when i thought an offline installer was a good idea?
a pre-built squashfs that still needed internet to install DEs?
"offline" in name only. it was a lie. a beautiful, naive lie.

so i killed it. ripped out the mode picker, the connectivity check,
the whole AskMode() circus. calamares-next now just shows a notice
saying "yeah that wasnt actually offline" and launches directly
into online mode. no more pretending.

also fixed the QML nav button because having a greyed out "Next"
on the last page is terrible UX. now it says "Done" and quits.
youre welcome.

and since we nuked the offline mode, might as well make the
online mode look fucking spectacular. enter antergos-layan-theme:

- kvantum theme because your apps deserve better than qt dreck
- sddm theme (AntergosDark-Layan) for a sexy login screen
- every plasma plasmoid from the rice (panel colorizer, netspeed,
  shutdown switch, applets, separators, music toolbar, the works)
- yakuake arc-dark skin
- antergos logos in /usr/share/logos and /usr/share/pixmaps
- konsole profile, desktop appletsrc, all that jazz
- kde_settings.conf now points to AntergosDark-Layan sddm theme

also replaced the old antergos-grub-theme with the Layan grub
theme because the old one was a git-sourced abomination. 75 distro
icons, terminal box, selection widgets, antergos wallpaper.
ships to /usr/share/grub/themes/antergos/ so calamares finds it.

oh and a dozen audit fixes:
- kscreenlockerrc wallpaper path fixed
- kdialogrc no longer says some other distro
- 7 stale knsregistry files deleted (dev machine was leaking)
- systemd-inhibit guarded with command -v (dinit doesnt have it)
- plasma restart uses killall not systemctl --user
- "XeroLinux Toolkit" -> "Antergos Toolkit" in appletsrc
- KDE_SESSION_VERSION=5 -> =6 (its 2026)
- mirrorlist script says Artix not Arch
- SDDM Messages.sh removed from build artifacts
This commit is contained in:
2026-07-16 21:57:36 +02:00
parent 716ff74560
commit 82ff06c4da
1302 changed files with 308189 additions and 235 deletions
@@ -0,0 +1,109 @@
/*
This file is part of the KDE project.
SPDX-FileCopyrightText: 2012 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
"use strict";
function sleep(miliseconds) {
var currentTime = new Date().getTime();
while (currentTime + miliseconds >= new Date().getTime()) {
}
}
class MaximizeEffect {
constructor() {
effect.configChanged.connect(this.loadConfig.bind(this));
effects.windowFrameGeometryChanged.connect(
this.onWindowFrameGeometryChanged.bind(this));
effects.windowMaximizedStateChanged.connect(
this.onWindowMaximizedStateChanged.bind(this));
effect.animationEnded.connect(this.restoreForceBlurState.bind(this));
this.loadConfig();
}
loadConfig() {
this.duration = animationTime(250);
}
onWindowMaximizedStateChanged(window) {
if (!window.oldGeometry) {
return;
}
sleep(50); //Skip some frames
window.setData(Effect.WindowForceBlurRole, true);
let oldGeometry = window.oldGeometry;
const newGeometry = window.geometry;
if (oldGeometry.width == newGeometry.width && oldGeometry.height == newGeometry.height)
oldGeometry = window.olderGeometry;
window.olderGeometry = Object.assign({}, window.oldGeometry);
window.oldGeometry = Object.assign({}, newGeometry);
window.maximizeAnimation1 = animate({
window: window,
duration: this.duration,
animations: [{
type: Effect.Size,
to: {
value1: newGeometry.width,
value2: newGeometry.height
},
from: {
value1: oldGeometry.width,
value2: oldGeometry.height
},
curve: QEasingCurve.OutCubic
}, {
type: Effect.Translation,
to: {
value1: 0,
value2: 0
},
from: {
value1: oldGeometry.x - newGeometry.x - (newGeometry.width / 2 - oldGeometry.width / 2),
value2: oldGeometry.y - newGeometry.y - (newGeometry.height / 2 - oldGeometry.height / 2)
},
curve: QEasingCurve.OutCubic
}]
});
if (!window.resize) {
window.maximizeAnimation2 =animate({
window: window,
duration: this.duration,
animations: [{
type: Effect.CrossFadePrevious,
to: 1.0,
from: 0.0,
curve: QEasingCurve.OutCubic
}]
});
}
}
restoreForceBlurState(window) {
window.setData(Effect.WindowForceBlurRole, null);
}
onWindowFrameGeometryChanged(window, oldGeometry) {
if (window.maximizeAnimation1) {
if (window.geometry.width != window.oldGeometry.width ||
window.geometry.height != window.oldGeometry.height) {
cancel(window.maximizeAnimation1);
delete window.maximizeAnimation1;
if (window.maximizeAnimation2) {
cancel(window.maximizeAnimation2);
delete window.maximizeAnimation2;
}
}
}
window.oldGeometry = Object.assign({}, window.geometry);
window.olderGeometry = Object.assign({}, oldGeometry);
}
}
new MaximizeEffect();
@@ -0,0 +1,113 @@
/********************************************************************
This file is part of the KDE project.
Copyright (C) 2012 Martin Gräßlin <mgraesslin@kde.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
"use strict";
function sleep(miliseconds) {
var currentTime = new Date().getTime();
while (currentTime + miliseconds >= new Date().getTime()) {
}
}
var maximizeEffect = {
duration: animationTime(150),
loadConfig: function () {
maximizeEffect.duration = animationTime(150);
},
maximizeChanged: function (window) {
var m = Math.floor((Math.random() * 2) + 1);
sleep(50); //Skip some frames
if (!window.oldGeometry) {
return;
}
window.setData(Effect.WindowForceBlurRole, true);
var oldGeometry, newGeometry;
oldGeometry = window.oldGeometry;
newGeometry = window.geometry;
if (oldGeometry.width == newGeometry.width && oldGeometry.height == newGeometry.height)
oldGeometry = window.olderGeometry;
window.olderGeometry = window.oldGeometry;
window.oldGeometry = newGeometry;
window.maximizeAnimation1 = animate({
window: window,
duration: maximizeEffect.duration,
animations: [{
type: Effect.Size,
to: {
value1: newGeometry.width,
value2: newGeometry.height
},
from: {
value1: oldGeometry.width,
value2: oldGeometry.height
}
}, {
type: Effect.Translation,
to: {
value1: 0,
value2: 0
},
from: {
value1: oldGeometry.x - newGeometry.x - (newGeometry.width / 2 - oldGeometry.width / 2),
value2: oldGeometry.y - newGeometry.y - (newGeometry.height / 2 - oldGeometry.height / 2)
}
}]
});
//if (!window.resize) {
if (1 == 2 ) { // disabled the routine because
// CrossFadePrevious+xrender grabs a garbled texture as the start crossfade point
window.maximizeAnimation2 =animate({
window: window,
duration: maximizeEffect.duration,
animations: [{
delay: animationTime(32),
type: Effect.CrossFadePrevious,
to: 1.0,
from: 0.0
}]
});
}
},
restoreForceBlurState: function(window) {
window.setData(Effect.WindowForceBlurRole, null);
},
geometryChange: function (window, oldGeometry) {
if (window.maximizeAnimation1) {
if (window.geometry.width != window.oldGeometry.width ||
window.geometry.height != window.oldGeometry.height) {
cancel(window.maximizeAnimation1);
delete window.maximizeAnimation1;
if (window.maximizeAnimation2) {
cancel(window.maximizeAnimation2);
delete window.maximizeAnimation2;
}
}
}
window.oldGeometry = window.geometry;
window.olderGeometry = oldGeometry;
},
init: function () {
effect.configChanged.connect(maximizeEffect.loadConfig);
effects.windowGeometryShapeChanged.connect(maximizeEffect.geometryChange);
effects.windowMaximizedStateChanged.connect(maximizeEffect.maximizeChanged);
effect.animationEnded.connect(maximizeEffect.restoreForceBlurState);
}
};
maximizeEffect.init();
@@ -0,0 +1,19 @@
[Desktop Entry]
Comment=Animation for windows going to maximize/restore from maximize, less glitches
Icon=preferences-system-windows-effect-maximize
Name=Maximize antiglitch
Type=Service
X-KDE-ParentApp=
X-KDE-PluginInfo-Author=Martin Gräßlin (changed by Antonio Orefice)
X-KDE-PluginInfo-Category=Appearance
X-KDE-PluginInfo-Email=kokoko3k@gmail.com
X-KDE-PluginInfo-License=GPL
X-KDE-PluginInfo-Name=kwin4_effect_maximize_xrender
X-KDE-PluginInfo-Version=1.1
X-KDE-PluginInfo-Website=
X-KDE-ServiceTypes=KWin/Effect
X-KDE-PluginInfo-EnabledByDefault=false
X-KDE-Ordering=60
X-Plasma-API=javascript
X-Plasma-MainScript=code/maximize-xrender.js
@@ -0,0 +1,111 @@
/********************************************************************
This file is part of the KDE project.
Copyright (C) 2012 Martin Gräßlin <mgraesslin@kde.org>
Copyright (C) 2018 Alex Nemeth <alex.nemeth329@gmail.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
var rubberbandMaximizeEffect = {
duration: animationTime(200),
deviation: 0.05,
loadConfig: function () {
"use strict";
rubberbandMaximizeEffect.duration = animationTime(200);
},
slotWindowMaximizedStateChanged: function (window) {
"use strict";
if (!window.oldGeometry) {
return;
}
window.setData(Effect.WindowForceBackgroundContrastRole, true);
window.setData(Effect.WindowForceBlurRole, true);
var oldGeometry = window.oldGeometry;
var newGeometry = window.geometry;
if (oldGeometry.width == newGeometry.width &&
oldGeometry.height == newGeometry.height) {
oldGeometry = window.olderGeometry;
}
var scale = 0;
if (oldGeometry.width < newGeometry.width) {
scale = 1 - rubberbandMaximizeEffect.deviation;
} else {
scale = 1 + rubberbandMaximizeEffect.deviation;
}
window.olderGeometry = window.oldGeometry;
window.oldGeometry = newGeometry;
window.maximizeAnimation = animate({
window: window,
curve: QEasingCurve.OutSine,
duration: rubberbandMaximizeEffect.duration,
animations: [
{
type: Effect.Size,
delay: animationTime(10),
from: {
value1: newGeometry.width * scale,
value2: newGeometry.height * scale
},
to: {
value1: newGeometry.width,
value2: newGeometry.height
}
},
{
type: Effect.Translation,
from: {
value1: (oldGeometry.x - newGeometry.x - (newGeometry.width - oldGeometry.width) / 2) * rubberbandMaximizeEffect.deviation,
value2: (oldGeometry.y - newGeometry.y - (newGeometry.height - oldGeometry.height) / 2) * rubberbandMaximizeEffect.deviation
},
to: {
value1: 0,
value2: 0
}
}
]
});
},
slotWindowGeometryShapeChanged: function (window, oldGeometry) {
"use strict";
if (window.maximizeAnimation) {
if (window.geometry.width != window.oldGeometry.width ||
window.geometry.height != window.oldGeometry.height) {
cancel(window.maximizeAnimation);
delete window.maximizeAnimation;
}
}
window.oldGeometry = window.geometry;
window.olderGeometry = oldGeometry;
},
slotAnimationEnded: function (window) {
window.setData(Effect.WindowForceBackgroundContrastRole, null);
window.setData(Effect.WindowForceBlurRole, null);
},
init: function () {
"use strict";
effect.configChanged.connect(rubberbandMaximizeEffect.loadConfig);
effect.animationEnded.connect(rubberbandMaximizeEffect.slotAnimationEnded);
effects.windowGeometryShapeChanged.connect(rubberbandMaximizeEffect.slotWindowGeometryShapeChanged);
effects.windowMaximizedStateChanged.connect(rubberbandMaximizeEffect.slotWindowMaximizedStateChanged);
}
};
rubberbandMaximizeEffect.init();
@@ -0,0 +1,18 @@
[Desktop Entry]
Comment=Maximize/Restore window animation similar to Windows 10
Icon=preferences-system-windows-effect-rubberband-maximize
Name=Rubberband Maximize
Type=Service
X-KDE-ParentApp=
X-KDE-PluginInfo-Author=Alex Nemeth
X-KDE-PluginInfo-Category=Appearance
X-KDE-PluginInfo-Email=alex.nemeth329@gmail.com
X-KDE-PluginInfo-License=GPL
X-KDE-PluginInfo-Name=kwin4_effect_rubberband_maximize
X-KDE-PluginInfo-Version=1
X-KDE-PluginInfo-Website=
X-KDE-ServiceTypes=KWin/Effect
X-KDE-PluginInfo-EnabledByDefault=true
X-KDE-Ordering=60
X-Plasma-API=javascript
X-Plasma-MainScript=code/main.js
@@ -0,0 +1,91 @@
import QtQuick 2.0;
import org.kde.plasma.core 2.0 as PlasmaCore;
import org.kde.plasma.components 2.0 as Plasma;
import org.kde.kwin 2.0;
Item {
id: root
PlasmaCore.DataSource {
id: composition
engine: 'executable'
connectedSources: []
property QtObject client
function set(aClient) {
client = aClient
var windowId = client.windowId.toString(16)
composition.connectSource("xprop -id 0x" + windowId + " _NET_WM_BYPASS_COMPOSITOR")
}
function preference(stdout) {
if (clientIsException()) {
return "on"
} else {
var splittedStdout = stdout.split(" ")
var compositionPreference = splittedStdout[2]
if (compositionPreference == "not") {
return "none"
} else if (compositionPreference == 1) {
return "off"
} else if (compositionPreference == 2) {
return "on"
} else {
var error = ": Invalid composition preference: "
console.error(client.resourceClass + error + compositionPreference)
return false
}
}
}
function clientIsException() {
var result = false;
const exceptions = ['plasmashell', 'ksmserver-logout-greeter'];
let index = 0;
while (!result && index < exceptions.length) {
result = (client.resourceClass == exceptions[index]);
index++;
}
return result;
}
onNewData: {
var exitCode = data["exit code"]
var exitStatus = data["exit status"]
var stdout = data["stdout"]
var stderr = data["stderr"]
if (exitCode != 0) {
console.error(stderr)
return false
} else {
let compositionPreference = composition.preference(stdout)
if (client.fullScreen && compositionPreference != "on") {
client.blocksCompositing = true
} else if (!client.fullScreen && compositionPreference != "off") {
client.blocksCompositing = false
}
}
disconnectSource(sourceName)
}
}
Connections {
target: workspace
function onClientAdded(client) {
if (client.fullScreen) {
composition.set(client)
}
}
function onClientFullScreenSet(client) {
composition.set(client)
}
}
}
@@ -0,0 +1,19 @@
[Desktop Entry]
Name=Autocomposer
X-KDE-PluginInfo-Version=4
Comment=Blocks compositing on fullscreen, to improve graphical performance
Icon=preferences-desktop-effects
X-KDE-PluginInfo-License=GPLv3
X-KDE-PluginInfo-Website=https://store.kde.org/p/1502826/
X-KDE-PluginInfo-Name=autocomposer
Type=Service
X-KDE-PluginInfo-Category=Kwin Script
X-KDE-PluginInfo-EnabledByDefault=true
X-KDE-ServiceTypes=KWin/Script
X-Plasma-API=declarativescript
X-Plasma-MainScript=code/main.qml
X-KWin-Border-Activate=true
@@ -0,0 +1 @@
kpackagetool6 --type KWin/Script --install .
@@ -0,0 +1,56 @@
BSD 3Clause License
Copyright (c) 2019, Nikita Sorokovikov
Copyright (c) 2025, MurderFromMars
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
NOTICE — Modifications by MurderFromMars
----------------------------------------------------------------------
This project is a fork of "dynamic_workspaces" by Nikita Sorokovikov (2019),
licensed under the BSD 3Clause License.
A nonexhaustive list of specific changes includes:
- Major rewrite of the compatibility layer to target Plasma 6 exclusively
- Removal of all Plasma 5 logic, legacy APIs, and dualmode behavior
- Introduction of a GNOMEstyle dynamic workspace model
- Automatic removal of all empty desktops except the last
- Complete desktop renumbering system to maintain sequential ordering
- Replacement of the original windowshifting algorithm with simplified reassignment logic
- New startup initialization logic enforcing GNOME workspace semantics
- New startup redirection to ensure consistent initial desktop behavior
- Rewritten eventhandling system for window addition, removal, and desktop changes
- New cleanup system triggered on window removal, desktop switch, and client movement
- Simplified and modernized desktop deletion logic
- New helper functions: renumberDesktops, enforceGnomeModel, desktopIsEmpty, trimToMinimum
- Rewritten onClientAdded and desktopchange handling logic
- Updated logging system and naming conventions
- General restructuring, cleanup, and modernization of the codebase
@@ -0,0 +1,52 @@
# Better Dynamic Workspaces #
---
# Purpose of This Fork
The original script was created for Plasma 5, which used different APIs and session handling rules. Plasma 6 introduced new desktop management behavior, new virtual desktop APIs, and a different approach to session restoration. These changes made the original design harder to maintain and led to issues such as inconsistent numbering, restored clutter from previous sessions, and unpredictable startup desktops.
This fork focuses exclusively on Plasma 6 and rebuilds the concept around its modern architecture. The goal is to deliver a stable, predictable, GNOME style dynamic workspace system that behaves consistently across sessions and window events.
# Key Features
## Plasma 6 Native Codebase
The script uses Plasma 6s virtual desktop API directly. This removes the need for compatibility layers and allows the logic to be simpler, more reliable, and easier to maintain. Window events, desktop creation, and desktop removal all use Plasma 6s native structures.
## GNOME Style Dynamic Workspace Model
The workspace lifecycle follows the GNOME approach. The session always begins with two desktops. The user always starts on Desktop 1. A new desktop is created only when the last one becomes occupied. Empty desktops are removed automatically, and the final desktop is always kept empty. This creates a self maintaining layout that grows and shrinks naturally based on activity.
## Unified Cleanup System
The original project used direction based cleanup and shifted windows to earlier desktops to maintain order. Plasma 6 made this approach less reliable. This fork replaces it with a single cleanup pass that runs after any structural change. It removes empty desktops, ensures the last desktop remains empty, and keeps the layout free of gaps. The cleanup is direction agnostic and does not shift windows, which makes the behavior consistent and predictable.
## Consistent Desktop Renumbering
Desktops are always kept in sequential order. Renumbering happens automatically whenever desktops are added or removed. This keeps the pager, window manager, and internal logic aligned at all times.
## Predictable Startup Behavior
The script ensures that every session begins in a clean state. The user always starts on Desktop 1. The session always begins with exactly two desktops. Plasmas session restore behavior is bypassed so that old desktops are not recreated automatically.
---
# Comparison With the Original Project
| Feature | Original Project (maurges) | This Fork |
|--------|-----------------------------|-----------|
| Plasma 5 support | Yes | No |
| Plasma 6 support | Partial | Full, native |
| Startup desktops | Restores all from last session | Always 2 |
| Startup focus | Last used desktop | Always Desktop 1 |
| Desktop renumbering | No | Yes, always sequential |
| Dynamic behavior | Basic | GNOME like |
| Cleanup model | Direction based shift left logic | Unified GNOME style cleanup |
| Workspace lifecycle | Mixed rules | Strictly append only |
| Code complexity | Higher | Reduced, Plasma 6 only |
| Window shifting | Required | Removed |
| Session consistency | Variable | Deterministic |
# License
This fork maintains the BSD 3 license of the original project with proper attribution.
# Credits
Inspired by and built upon the original dynamic_workspaces by maurges, whose work established dynamic workspaces on KDE.
@@ -0,0 +1,239 @@
// Better Dynamic Workspaces
const MIN_DESKTOPS = 2;
const LOG_LEVEL = 2; // 0 verbose, 1 debug, 2 normal
function log(...args) { print("[better_dynamic_ws]", ...args); }
function debug(...args) { if (LOG_LEVEL <= 1) log(...args); }
function trace(...args) { if (LOG_LEVEL <= 0) log(...args); }
let animationGuard = false;
/******** Plasma 6 Compatibility Layer ********/
const compat = {
addDesktop: () => {
workspace.createDesktop(workspace.desktops.length, undefined);
},
windowAddedSignal: ws => ws.windowAdded,
windowList: ws => ws.windowList(),
desktopChangedSignal: client => client.desktopsChanged,
toDesktop: d => d,
workspaceDesktops: () => workspace.desktops,
lastDesktop: () => workspace.desktops[workspace.desktops.length - 1],
deleteLastDesktop: () => {
try {
animationGuard = true;
const desktops = workspace.desktops;
const last = desktops[desktops.length - 1];
const current = workspace.currentDesktop;
const idx = desktops.indexOf(current);
const fallback =
idx + 1 < desktops.length || idx === -1
? desktops[idx + 1]
: current;
workspace.currentDesktop = fallback;
workspace.removeDesktop(last);
workspace.currentDesktop = current;
} finally {
animationGuard = false;
}
},
findDesktop: (list, d) => list.indexOf(d),
clientDesktops: c => c.desktops,
setClientDesktops: (c, ds) => { c.desktops = ds; },
clientOnDesktop: (c, d) => c.desktops.indexOf(d) !== -1,
desktopAmount: () => workspace.desktops.length,
};
/******** Desktop Renumbering ********/
function renumberDesktops() {
let count = compat.desktopAmount();
for (let i = 0; i < count; i++) {
const desktops = compat.workspaceDesktops();
const current = desktops[i];
if (desktops.indexOf(current) !== i) {
compat.addDesktop();
const newDesk = compat.lastDesktop();
compat.windowList(workspace).forEach(client => {
if (compat.clientOnDesktop(client, current)) {
const newList = compat.clientDesktops(client)
.map(d => (d === current ? newDesk : d));
compat.setClientDesktops(client, newList);
}
});
compat.deleteLastDesktop();
}
}
}
/******** GNOMEAccurate Cleanup ********/
function desktopIsEmpty(idx) {
const d = compat.workspaceDesktops()[idx];
const clients = compat.windowList(workspace);
for (const c of clients) {
if (
compat.clientOnDesktop(c, d) &&
!c.skipPager &&
!c.onAllDesktops
) {
return false;
}
}
return true;
}
function removeDesktop(idx) {
const count = compat.desktopAmount();
if (count - 1 <= idx) return false;
if (count <= MIN_DESKTOPS) return false;
compat.windowList(workspace).forEach(c => {
const cds = compat.clientDesktops(c);
const all = compat.workspaceDesktops();
const updated = cds.map(d => {
const i = all.indexOf(d);
return i > idx ? all[i - 1] : d;
});
compat.setClientDesktops(c, updated);
});
compat.deleteLastDesktop();
renumberDesktops();
return true;
}
/******** Unified GNOME Cleanup ********/
function enforceGnomeModel() {
if (animationGuard) return;
animationGuard = true;
try {
const all = compat.workspaceDesktops();
const lastIdx = all.length - 1;
// Remove all empty desktops except the last one
for (let i = lastIdx - 1; i >= 0; i--) {
if (desktopIsEmpty(i)) {
removeDesktop(i);
}
}
// Ensure last desktop is empty
const newLastIdx = compat.desktopAmount() - 1;
if (!desktopIsEmpty(newLastIdx)) {
compat.addDesktop();
}
renumberDesktops();
} finally {
animationGuard = false;
}
}
/******** Core Behavior ********/
function handleClientDesktopChange(client) {
if (compat.clientOnDesktop(client, compat.lastDesktop())) {
compat.addDesktop();
renumberDesktops();
}
enforceGnomeModel();
}
function onClientAdded(client) {
if (!client || client.skipPager) return;
if (compat.clientOnDesktop(client, compat.lastDesktop())) {
compat.addDesktop();
renumberDesktops();
}
compat.desktopChangedSignal(client).connect(() => {
handleClientDesktopChange(client);
});
enforceGnomeModel();
}
/******** Initialization ********/
function trimToMinimum() {
while (compat.desktopAmount() > MIN_DESKTOPS) {
try {
compat.deleteLastDesktop();
} catch (err) {
break;
}
}
}
trimToMinimum();
(function setupInitialDesktops() {
const ds = compat.workspaceDesktops();
workspace.currentDesktop = ds[0];
while (compat.desktopAmount() > MIN_DESKTOPS) {
try {
compat.deleteLastDesktop();
} catch (err) {
break;
}
}
if (compat.desktopAmount() < MIN_DESKTOPS) {
compat.addDesktop();
}
renumberDesktops();
})();
compat.windowList(workspace).forEach(onClientAdded);
compat.windowAddedSignal(workspace).connect(onClientAdded);
workspace.windowRemoved.connect(() => enforceGnomeModel());
workspace.currentDesktopChanged.connect(() => enforceGnomeModel());
// Startup redirect to enforce GNOME behavior.... might not be needed anymore tbh XD
let initialRedirectDone = false;
workspace.currentDesktopChanged.connect(function () {
if (initialRedirectDone) return;
initialRedirectDone = true;
const all = compat.workspaceDesktops();
const currIdx = compat.findDesktop(all, workspace.currentDesktop);
if (currIdx === 1) {
animationGuard = true;
try {
workspace.currentDesktop = all[0];
} finally {
animationGuard = false;
}
}
});
@@ -0,0 +1,21 @@
{ "KPackageStructure": "KWin/Script"
, "KPlugin":
{ "Authors":
[ { "Name": "Maurges, MurderFromMars" }
]
, "Description": "True Gnome Style Workspace Management"
, "Icon": "preferences-system-windows-script-test"
, "Id": "better_dynamic_workspaces"
, "License": "BSD"
, "Name": "Better Dynamic Workspaces"
, "Version": "1.3"
}
, "X-KDE-PluginKeyword": "workspaces"
, "X-Plasma-API": "javascript"
, "X-Plasma-API-Minimum-Version": "6.0"
, "X-KDE-Library": "kwin/effects/configs/kcm_kwin4_genericscripted"
, "X-Plasma-MainScript": "code/main.js"
}
@@ -0,0 +1,14 @@
function isCSD(client) {
return client.resourceClass == "gedit" || client.resourceClass == "nautilus" || client.resourceClass == "lollypop" || client.resourceClass == "gtg" || client.resourceClass == "totem" || client.resourceClass == "corebird" || client.resourceClass == "gnome-music" || client.resourceClass == "gnome-boxes" || client.resourceClass == "evince" || client.resourceClass == "baobab" || client.resourceClass == "epiphany" || client.resourceClass == "dconf-editor" || client.resourceClass == "file-roller" || client.resourceClass == "five-or-more" || client.resourceClass == "four-in-a-row" || client.resourceClass == "gnome-calculator" || client.resourceClass == "gnome-chess" || client.resourceClass == "gnome-clocks" || client.resourceClass == "gnome-contacts" || client.resourceClass == "gnome-control-center" || client.resourceClass == "gnome-dictionary" || client.resourceClass == "gnome-disks" || client.resourceClass == "gnome-builder" || client.resourceClass == "gnome-font-viewer" || client.resourceClass == "gnome-klotski" || client.resourceClass == "gnome-logs" || client.resourceClass == "gnome-mahjongg" || client.resourceClass == "gnome-mines" || client.resourceClass == "gnome-multi-writer" || client.resourceClass == "gnome-robots" || client.resourceClass == "gnome-sudoku" || client.resourceClass == "gnome-taquin" || client.resourceClass == "gnome-tetravex" || client.resourceClass == "gnome-usage" || client.resourceClass == "hitori" || client.resourceClass == "iagno" || client.resourceClass == "simple-scan" || client.resourceClass == "sysprof" || client.resourceClass == "notes-up" || client.resourceClass == "gnome-mpv" || client.resourceClass == "glade" || client.resourceClass == "eolie" || client.resourceClass == "gnome-calendar" || client.resourceClass == "gnome-todo" || client.resourceClass == "marker" || client.resourceClass == "pitivi" || client.resourceClass == "pamac-manager";
}
function onClientAdded(client) {
if (isCSD(client)) {
client.noBorder = false;
}
}
workspace.clientAdded.connect(onClientAdded);
@@ -0,0 +1,22 @@
[Desktop Entry]
Name=Force Decorations for GTK+ 3
Comment=Adds KWin decorations to GTK's header bars
Icon=preferences-system-windows-script-test
X-Plasma-API=javascript
X-Plasma-MainScript=code/main.js
X-KDE-PluginInfo-Author=Jack Gandy
X-KDE-PluginInfo-Email=scionicspectre@gmail.com
X-KDE-PluginInfo-Name=force-decorations-gtk3
X-KDE-PluginInfo-Version=0.1
X-KDE-PluginInfo-Depends=
X-KDE-PluginInfo-License=GPL
X-KDE-ServiceTypes=KWin/Script,KCModule
X-KDE-Library=kwin/effects/configs/kcm_kwin4_genericscripted
X-KDE-PluginKeyword=force-decoration-gtk3
X-KDE-ParentComponents=force-decoration-gtk3
Type=Service
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 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.
@@ -0,0 +1,67 @@
Force Blur
==========
A KWin script to force-enable KWin Blur effect on user-specified windows.
![](image.png)
This is more useful than shell script hacks, because KWin script receives event
notification and window info from KWin. This can also fix tray-based apps,
which loses blur hints when restored from system tray.
System Requirement
------------------
* Operating System:
- (K)Ubuntu 18.10 or newer
- Fedora 29 or newer
- Arch Linux - kwin 5.14 or newer
* Make sure you're using "Blur" desktop effect, which can be enabled through System Settings.
* This script internally calls `xprop` command, which would be already installed
on your system.
* X11 display server (the script does not support wayland applications)
How to Use
----------
1. Install the script.
2. Run the following in terminal, to enable script configuration:
mkdir -p ~/.local/share/kservices5/
cp ~/.local/share/kwin/scripts/forceblur/metadata.desktop ~/.local/share/kservices5/forceblur.desktop
3. Open `Kwin Scripts` page in `System Settings`.
4. Enable `Force Blur` script by checking the checkbox next to it.
5. Change script settings. Note that the script can only match with window classes.
6. Click `OK` to enable the script.
7. Whenever settings are changed, you must disable and re-enable the script.
(Uncheck -> `Apply` -> Check -> `Apply`)
GTK CSD Shadow
--------------
Some GTK applications render client-side shadows, which look ugly if blurred. To
prevent this, you can enable "**Blur only the content of window**" option.
This works by updating the blur region info whenever a window is resized, so
might have some performance impacts. Use with caution.
Authors
-------
* Eon S. Jeon <esjeon@hyunmu.am> - main author
* Aaron Miller (https://github.com/aaronm-cloudtek) - blacklist mode
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
<kcfgfile name=""/>
<group name="">
<entry name="patterns" type="String">
<label>Comma-separated list of window classes</label>
<default>yakuake
urxvt
keepassxc</default>
</entry>
<entry name="blurMatching" type="Bool">
<default>true</default>
</entry>
<entry name="blurExceptMatching" type="Bool">
<default>false</default>
</entry>
<entry name="blurContent" type="Bool">
<default>false</default>
</entry>
</group>
</kcfg>
@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ForceBlurConfigForm</class>
<widget class="QWidget" name="ForceBlurConfigForm">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>400</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>350</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupMatching">
<property name="title">
<string>Window Matching</string>
</property>
<layout class="QVBoxLayout" name="layoutMatching">
<item>
<widget class="QLabel" name="labelMatching">
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;&amp;lt;&amp;lt;Example&amp;gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;yakuake&lt;/p&gt;&lt;p&gt;keepassxc&lt;/p&gt;&lt;p&gt;urxvt&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enter window class names, &lt;span style=&quot; text-decoration: underline;&quot;&gt;one per line&lt;/span&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="kcfg_patterns">
<property name="toolTip">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Fira Sans Book'; font-size:11pt; font-weight:232; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Enter the class names of the windows you want to enable/disable blurring.&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Class names can be obtained by using &lt;span style=&quot; font-weight:600;&quot;&gt;xprop&lt;/span&gt; utility in terminal. (xprop WM_CLASS)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="layoutMatchingMode">
<item>
<widget class="QRadioButton" name="kcfg_blurMatching">
<property name="text">
<string>Blur only matching</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="kcfg_blurExceptMatching">
<property name="text">
<string>Blur all except matching</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupOptions">
<property name="title">
<string>Options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="kcfg_blurContent">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enabling this option prevents blurring behind the non-content regions of window, like &lt;span style=&quot; font-weight:600;&quot;&gt;CSD shadows in GTK&lt;/span&gt;.&lt;/p&gt;&lt;p&gt;This &lt;span style=&quot; text-decoration: underline;&quot;&gt;might&lt;/span&gt; cause performance issues and flickers, thus users should be careful about turning this on.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Blur only the content of window</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
<buttongroups>
<buttongroup name="matchTypeGroup"/>
</buttongroups>
</ui>
@@ -0,0 +1,100 @@
import QtQuick 2.0
import org.kde.plasma.core 2.0 as PlasmaCore;
import org.kde.plasma.components 2.0 as Plasma;
import org.kde.kwin 2.0;
Item {
id: root
readonly property var patterns: (
KWin.readConfig("patterns", "yakuake\nurxvt\nkeepassxc")
.split("\n")
.map(function(rule) {
return rule.trim().toLowerCase();
})
)
readonly property var blurMatching: (
KWin.readConfig("blurMatching", true)
)
readonly property var blurContent: (
KWin.readConfig("blurContent", false)
)
PlasmaCore.DataSource {
id: shell
engine: 'executable'
connectedSources: []
function run(cmd) {
shell.connectSource(cmd);
}
onNewData: {
shell.disconnectSource(sourceName);
}
}
function onClientAdded(client) {
if (!shell) return;
var cls = client.resourceClass.toString().toLowerCase();
var name = client.resourceName.toString().toLowerCase();
var clsMatches = root.patterns.indexOf(cls) >= 0 || root.patterns.indexOf(name) >= 0;
if (clsMatches == root.blurMatching) {
if (root.blurContent) {
registerHintUpdater(client);
} else {
var wid = "0x" + client.windowId.toString(16);
shell.run("xprop -f _KDE_NET_WM_BLUR_BEHIND_REGION 32c -set _KDE_NET_WM_BLUR_BEHIND_REGION 0 -id " + wid);
}
}
}
function registerHintUpdater(client) {
var prevWidth = client.geometry.width;
var prevHeight = client.geometry.height;
var handler = function() {
try { if (!root) return; } catch(e) {
client.geometryChanged.disconnect(handler);
throw e;
}
root.onClientGeometryChanged(client, prevWidth, prevHeight);
prevWidth = client.geometry.width;
prevHeight = client.geometry.height;
}
client.geometryChanged.connect(handler);
root.onClientGeometryChanged(client, 0, 0);
}
function onClientGeometryChanged(client, prevWidth, prevHeight) {
if (!shell) return;
/* Skip if window *size* isn't really changed */
if (client.geometry.width === prevWidth && client.geometry.height === prevHeight)
return;
var wid = "0x" + client.windowId.toString(16);
var region = "0,0," + client.geometry.width + "," + client.geometry.height;
var cmd = "xprop -id " + wid + " -f _KDE_NET_WM_BLUR_BEHIND_REGION 32c -set _KDE_NET_WM_BLUR_BEHIND_REGION " + region
shell.run(cmd);
}
Component.onCompleted: {
console.log("FORCE-BLUR: starting the script");
console.log(JSON.stringify(root.patterns));
var clients = workspace.clientList();
for (var i = 0; i < clients.length; i++) {
root.onClientAdded(clients[i]);
}
workspace.onClientAdded.connect(root.onClientAdded);
}
}
@@ -0,0 +1,18 @@
[Desktop Entry]
Name=Force Blur
Comment=Force-enable Blur effect for certain windows
Icon=preferences-system-windows-script-test
X-Plasma-API=declarativescript
X-Plasma-MainScript=ui/main.qml
X-KDE-PluginInfo-Author=Eon S. Jeon
X-KDE-PluginInfo-Email=esjeon@hyunmu.am
X-KDE-PluginInfo-Name=forceblur
X-KDE-PluginInfo-Version=0.6
X-KDE-PluginInfo-License=MIT
Type=Service
X-KDE-ServiceTypes=KWin/Script
X-KDE-ConfigModule=kwin/effects/configs/kcm_kwin4_genericscripted
@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 http://www.kde.org/standards/kcfg/1.0/kcfg.xsd">
<kcfgfile name="kwinrc" />
<group name="">
<entry name="gapsOuterTop" type="UInt">
<default>16</default>
</entry>
<entry name="gapsOuterBottom" type="UInt">
<default>16</default>
</entry>
<entry name="gapsOuterLeft" type="UInt">
<default>16</default>
</entry>
<entry name="gapsOuterRight" type="UInt">
<default>16</default>
</entry>
<entry name="gapsInnerHorizontal" type="UInt">
<default>8</default>
</entry>
<entry name="gapsInnerVertical" type="UInt">
<default>8</default>
</entry>
<entry name="stackOffsetX" type="UInt">
<default>8</default>
</entry>
<entry name="stackOffsetY" type="UInt">
<default>32</default>
</entry>
<entry name="manualScrollStep" type="UInt">
<default>200</default>
</entry>
<entry name="presetWidths" type="String">
<default>50%, 100%</default>
</entry>
<entry name="offScreenOpacity" type="UInt">
<default>100</default>
</entry>
<entry name="untileOnDrag" type="Bool">
<default>true</default>
</entry>
<entry name="cursorFollowsFocus" type="Bool">
<default>false</default>
</entry>
<entry name="stackColumnsByDefault" type="Bool">
<default>false</default>
</entry>
<entry name="resizeNeighborColumn" type="Bool">
<default>false</default>
</entry>
<entry name="reMaximize" type="Bool">
<default>false</default>
</entry>
<entry name="skipSwitcher" type="Bool">
<default>false</default>
</entry>
<entry name="scrollingLazy" type="Bool">
<default>true</default>
</entry>
<entry name="scrollingCentered" type="Bool">
<default>false</default>
</entry>
<entry name="scrollingGrouped" type="Bool">
<default>false</default>
</entry>
<entry name="gestureScroll" type="Bool">
<default>false</default>
</entry>
<entry name="gestureScrollInvert" type="Bool">
<default>false</default>
</entry>
<entry name="gestureScrollStep" type="UInt">
<default>1920</default>
</entry>
<entry name="tiledKeepBelow" type="Bool">
<default>true</default>
</entry>
<entry name="floatingKeepAbove" type="Bool">
<default>false</default>
</entry>
<entry name="noLayering" type="Bool">
<default>false</default>
</entry>
<entry name="windowRules" type="String">
<default>[
{
"class": "(org\\.kde\\.)?plasmashell",
"tile": false
},
{
"class": "(org\\.kde\\.)?polkit-kde-authentication-agent-1",
"tile": false
},
{
"class": "(org\\.kde\\.)?kded6",
"tile": false
},
{
"class": "(org\\.kde\\.)?kcalc",
"tile": false
},
{
"class": "(org\\.kde\\.)?kfind",
"tile": true
},
{
"class": "(org\\.kde\\.)?kruler",
"tile": false
},
{
"class": "(org\\.kde\\.)?krunner",
"tile": false
},
{
"class": "(org\\.kde\\.)?yakuake",
"tile": false
},
{
"class": "wl-copy|wl-paste",
"caption": "wl-clipboard",
"tile": false
},
{
"class": "steam",
"caption": "Steam Big Picture Mode",
"tile": false
},
{
"class": "zoom",
"caption": "Zoom Cloud Meetings|zoom|zoom &lt;2&gt;",
"tile": false
},
{
"class": "jetbrains-.*",
"caption": "splash",
"tile": false
},
{
"class": "jetbrains-.*",
"caption": "Unstash Changes|Paths Affected by stash@.*",
"tile": true
}
]</default>
</entry>
<entry name="tiledDesktops" type="String">
<default>.*</default>
</entry>
</group>
</kcfg>
@@ -0,0 +1,510 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>KWin::KarouselConfigForm</class>
<widget class="QWidget" name="KWin::KarouselConfigForm">
<property name="windowTitle">
<string>Karousel</string>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="layout_main">
<item>
<widget class="QTabWidget" name="tabContainer">
<widget class="QWidget" name="tab_behavior">
<attribute name="title">
<string>Behavior</string>
</attribute>
<layout class="QVBoxLayout">
<item>
<widget class="QGroupBox">
<property name="flat">
<bool>true</bool>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QCheckBox" name="kcfg_untileOnDrag">
<property name="text">
<string>Un-tile windows by dragging them</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="kcfg_cursorFollowsFocus">
<property name="text">
<string>Cursor follows focus (experimental)</string>
</property>
<property name="toolTip">
<string>When a window gains focus, move the cursor to it</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="kcfg_stackColumnsByDefault">
<property name="text">
<string>Stack columns by default</string>
</property>
<property name="toolTip">
<string>New columns start in stacked mode (only the active window visible)</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="kcfg_resizeNeighborColumn">
<property name="text">
<string>Resize neighbor column on edge resize</string>
</property>
<property name="toolTip">
<string>When resizing a column by dragging its edge, also inversely resize the column on the other side of the edge</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="kcfg_reMaximize">
<property name="text">
<string>Re-maximize tiled windows</string>
</property>
<property name="toolTip">
<string>Restore maximized and full-screen states of tiled windows on focus</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="kcfg_skipSwitcher">
<property name="text">
<string>Tiled windows skip switcher</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox">
<property name="title">
<string>Scrolling mode</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QRadioButton" name="kcfg_scrollingLazy">
<property name="text">
<string>Only scroll as necessary</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="kcfg_scrollingCentered">
<property name="text">
<string>Center focused column</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="kcfg_scrollingGrouped">
<property name="text">
<string>Center visible columns</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox">
<property name="title">
<string>Touchpad scrolling (Wayland only)</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QCheckBox" name="kcfg_gestureScroll">
<property name="text">
<string>Enable scrolling with touchpad gestures
(please don't forget to disable KDE's workspace switching gestures)</string>
</property>
<property name="toolTip">
<string>Scroll with a three-finger horizontal swipe gesture</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="kcfg_gestureScrollInvert">
<property name="text">
<string>Invert scroll direction (Natural scrolling)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox">
<property name="title">
<string>Layering mode</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QRadioButton" name="kcfg_tiledKeepBelow">
<property name="text">
<string>Set "Keep Below" for tiled windows</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="kcfg_floatingKeepAbove">
<property name="text">
<string>Set "Keep Above" for floating windows</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="kcfg_noLayering">
<property name="text">
<string>No layering</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="spacer_footer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_parameters">
<attribute name="title">
<string>Parameters</string>
</attribute>
<layout class="QFormLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_gapsOuterTop">
<property name="text">
<string>Top margin:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="kcfg_gapsOuterTop">
<property name="suffix">
<string> px</string>
</property>
<property name="maximum">
<number>999</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_gapsOuterBottom">
<property name="text">
<string>Bottom margin:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="kcfg_gapsOuterBottom">
<property name="suffix">
<string> px</string>
</property>
<property name="maximum">
<number>999</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_gapsOuterLeft">
<property name="text">
<string>Left margin:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="kcfg_gapsOuterLeft">
<property name="suffix">
<string> px</string>
</property>
<property name="maximum">
<number>999</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_gapsOuterRight">
<property name="text">
<string>Right margin:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="kcfg_gapsOuterRight">
<property name="suffix">
<string> px</string>
</property>
<property name="maximum">
<number>999</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_gapsInnerHorizontal">
<property name="text">
<string>Horizontal gaps between windows:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="kcfg_gapsInnerHorizontal">
<property name="suffix">
<string> px</string>
</property>
<property name="maximum">
<number>999</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_gapsInnerVertical">
<property name="text">
<string>Vertical gaps between windows:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QSpinBox" name="kcfg_gapsInnerVertical">
<property name="suffix">
<string> px</string>
</property>
<property name="maximum">
<number>999</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_stackOffsetX">
<property name="text">
<string>Horizontal offset for stacked columns:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QSpinBox" name="kcfg_stackOffsetX">
<property name="suffix">
<string> px</string>
</property>
<property name="maximum">
<number>999</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_stackOffsetY">
<property name="text">
<string>Vertical offset for stacked columns:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QSpinBox" name="kcfg_stackOffsetY">
<property name="suffix">
<string> px</string>
</property>
<property name="maximum">
<number>999</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_manualScrollStep">
<property name="text">
<string>Manual scroll step size:</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QSpinBox" name="kcfg_manualScrollStep">
<property name="suffix">
<string> px</string>
</property>
<property name="maximum">
<number>999</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_gestureScrollStep">
<property name="text">
<string>Touchpad gesture scrolling speed:</string>
</property>
<property name="toolTip">
<string>The amount to scroll per edge-to-edge gesture</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QSpinBox" name="kcfg_gestureScrollStep">
<property name="suffix">
<string> px</string>
</property>
<property name="maximum">
<number>10000</number>
</property>
<property name="minimum">
<number>100</number>
</property>
<property name="singleStep">
<number>100</number>
</property>
<property name="value">
<number>1920</number>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="label_presetWidths">
<property name="text">
<string>Preset widths:</string>
</property>
<property name="toolTip">
<string>Widths used for cycling through widths</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QLineEdit" name="kcfg_presetWidths">
<property name="toolTip">
<string>Comma-separated list of widths. Supported units: "px" and "%".</string>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QLabel" name="label_offScreenOpacity">
<property name="text">
<string>Obscured window opacity:</string>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="QSpinBox" name="kcfg_offScreenOpacity">
<property name="suffix">
<string> %</string>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_windowRules">
<attribute name="title">
<string>Window Rules</string>
</attribute>
<layout class="QVBoxLayout">
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="label_desktops">
<property name="text">
<string>Tiled desktops:</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="kcfg_tiledDesktops">
<property name="toolTip">
<string>Regex string to match desktops by desktop name"</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPlainTextEdit" name="kcfg_windowRules">
<property name="tabChangesFocus">
<bool>true</bool>
</property>
<property name="lineWrapMode">
<enum>QPlainTextEdit::NoWrap</enum>
</property>
<property name="font">
<font><family>Monospace</family></font>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Karousel script needs to be restarted for changes to take effect.</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,73 @@
import QtQuick 6.0
import org.kde.kwin 3.0
import org.kde.notification 1.0
import "../code/main.js" as Karousel
Item {
id: qmlBase
property var karouselInstance
Component.onCompleted: {
qmlBase.karouselInstance = Karousel.init();
}
Component.onDestruction: {
qmlBase.karouselInstance.destroy();
}
Notification {
id: notificationInvalidTiledDesktops
componentName: "plasma_workspace"
eventId: "notification"
title: "Karousel"
text: "Your Tiled Desktops regex is malformed, please review your Karousel configuration"
flags: Notification.Persistent
urgency: Notification.HighUrgency
}
Notification {
id: notificationInvalidWindowRules
componentName: "plasma_workspace"
eventId: "notification"
title: "Karousel"
text: "Your Window Rules JSON is malformed, please review your Karousel configuration"
flags: Notification.Persistent
urgency: Notification.HighUrgency
}
Notification {
id: notificationInvalidPresetWidths
componentName: "plasma_workspace"
eventId: "notification"
title: "Karousel"
text: "Your preset widths are malformed, please review your Karousel configuration"
flags: Notification.Persistent
urgency: Notification.HighUrgency
}
SwipeGestureHandler {
direction: SwipeGestureHandler.Direction.Left
fingerCount: 3
onActivated: qmlBase.karouselInstance.gestureScrollFinish()
onCancelled: qmlBase.karouselInstance.gestureScrollFinish()
onProgressChanged: qmlBase.karouselInstance.gestureScroll(-progress)
}
SwipeGestureHandler {
direction: SwipeGestureHandler.Direction.Right
fingerCount: 3
onActivated: qmlBase.karouselInstance.gestureScrollFinish()
onCancelled: qmlBase.karouselInstance.gestureScrollFinish()
onProgressChanged: qmlBase.karouselInstance.gestureScroll(progress)
}
DBusCall {
id: moveCursorToFocus
service: "org.kde.kglobalaccel"
path: "/component/kwin"
method: "invokeShortcut"
arguments: ["MoveMouseToFocus"]
}
}
@@ -0,0 +1,23 @@
{
"KPackageStructure": "KWin/Script",
"KPlugin": {
"Name": "Karousel",
"Description": "Scrollable tiling extension for KWin",
"Icon": "preferences-system-windows",
"Authors": [
{
"Email": "peter.fajdiga@gmail.com",
"Name": "Peter Fajdiga"
}
],
"Id": "karousel",
"Version": "0.16",
"License": "GPLv3",
"Website": "https://github.com/peterfajdiga/karousel",
"BugReportUrl": "https://github.com/peterfajdiga/karousel/issues"
},
"X-Plasma-API": "declarativescript",
"X-Plasma-API-Minimum-Version": "6.0",
"X-Plasma-MainScript": "ui/main.qml",
"X-KDE-ConfigModule": "kwin/effects/configs/kcm_kwin4_genericscripted"
}
@@ -0,0 +1 @@
print("KWin need me. Don't ask why.");
@@ -0,0 +1,448 @@
<?xml version="1.0" encoding="UTF-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
<kcfgfile name=""/>
<group name="">
<entry name="dockOrderLeft" type="Int">
<label>The order of the docked window in the dock</label>
<default>1</default>
</entry>
<entry name="dockOrderTop" type="Int">
<label>The order of the docked window in the dock</label>
<default>2</default>
</entry>
<entry name="dockOrderRight" type="Int">
<label>The order of the docked window in the dock</label>
<default>3</default>
</entry>
<entry name="dockOrderBottom" type="Int">
<label>The order of the docked window in the dock</label>
<default>4</default>
</entry>
<entry name="dockHHeight" type="Int">
<label>The default height of a horizontal docked window in percent</label>
<default>15</default>
</entry>
<entry name="dockHWide" type="Int">
<label>The default width of a horizontal docked window in percent</label>
<default>100</default>
</entry>
<entry name="dockHGap" type="Int">
<label>The default Gap of a horizontal docked window in pixels</label>
<default>0</default>
</entry>
<entry name="dockHEdgeGap" type="Int">
<label>The default Vertical Gap of a horizontal docked window in pixels</label>
<default>0</default>
</entry>
<entry name="dockHAlignment" type="Int">
<label>The default Alignment of a horizontal docked window which can be Center , Left, Right</label>
<default>0</default>
</entry>
<entry name="dockHEdgeAlignment" type="Int">
<label>The default vertical Alignment of a horizontal docked window which can be Edge Screen, Middle, Center Screen</label>
<default>0</default>
</entry>
<entry name="dockVHeight" type="Int">
<label>The default height of a vertical docked window in percent</label>
<default>100</default>
</entry>
<entry name="dockVWide" type="Int">
<label>The default width of a vertical docked window in percent</label>
<default>15</default>
</entry>
<entry name="dockVEdgeGap" type="Int">
<label>The default horizontal Gap of a vertical docked window in pixels</label>
<default>0</default>
</entry>
<entry name="dockVGap" type="Int">
<label>The default Gap of a vertical docked window in pixels</label>
<default>0</default>
</entry>
<entry name="dockVEdgeAlignment" type="Int">
<label>The default horizontal Alignment of a vertical docked window which can be Edge Screen, Middle or Center Screen</label>
<default>0</default>
</entry>
<entry name="dockVAlignment" type="Int">
<label>The default vertical Alignment of a vertical docked window which can be Center, Top, Bottom</label>
<default>0</default>
</entry>
<entry name="dockSurfacesConfig" type="String">
<label>Dock settings change the default parameters for the surface</label>
<default></default>
</entry>
<entry name="dockWindowClassConfig" type="String">
<label>Dock default settings for WindowClass which with high priority.</label>
<default></default>
</entry>
<entry name="tileLayoutOrder" type="Int">
<label> Tile layout order</label>
<default>1</default>
</entry>
<entry name="tileLayoutInitialRotationAngle" type="String">
<label>Tile layout initial rotation angle</label>
<default>0</default>
</entry>
<entry name="monocleLayoutOrder" type="Int">
<label> Monocle layout order</label>
<default>2</default>
</entry>
<entry name="threeColumnLayoutOrder" type="Int">
<label> Three Column layout order</label>
<default>3</default>
</entry>
<entry name="spiralLayoutOrder" type="Int">
<label> Spiral layout order</label>
<default>4</default>
</entry>
<entry name="quarterLayoutOrder" type="Int">
<label> Quarter layout order</label>
<default>5</default>
</entry>
<entry name="quarterLayoutReset" type="Bool">
<label>Reset Quarter layout after closing a window</label>
<default>false</default>
</entry>
<entry name="stackedLayoutOrder" type="Int">
<label> Stacked layout order</label>
<default>6</default>
</entry>
<entry name="columnsLayoutOrder" type="Int">
<label> Columns layout order</label>
<default>7</default>
</entry>
<entry name="enableColumnsLayout" type="Bool">
<label>Enable/disable Columns layout</label>
<default>true</default>
</entry>
<entry name="columnsLayoutInitialRotationAngle" type="String">
<label>Columns layout initial rotation angle</label>
<default>0</default>
</entry>
<entry name="columnsBalanced" type="Bool">
<label>Enable/disable creating a new window in the shortest column</label>
<default>false</default>
</entry>
<entry name="columnsLayerConf" type="String">
<label>Columns layout default configuration.</label>
<default></default>
</entry>
<entry name="spreadLayoutOrder" type="Int">
<label> Spread layout order</label>
<default>8</default>
</entry>
<entry name="floatingLayoutOrder" type="Int">
<label> Floating layout order</label>
<default>9</default>
</entry>
<entry name="stairLayoutOrder" type="Int">
<label> Stair layout order</label>
<default>10</default>
</entry>
<entry name="stairReverse" type="Bool">
<label>Reverse the stair layout's orientation</label>
<default>false</default>
</entry>
<entry name="binaryTreeLayoutOrder" type="Int">
<label> BTree layout order</label>
<default>11</default>
</entry>
<entry name="cascadeLayoutOrder" type="Int">
<label> Cascade layout order</label>
<default>12</default>
</entry>
<entry name="ignoreActivity" type="String">
<label>Do not apply tiling on some activities(comma-separated list of activity names)</label>
<default></default>
</entry>
<entry name="ignoreClass" type="String">
<label>Ignore windows with certain classes(comma-separated list)</label>
<default>krunner,yakuake,spectacle,kded5,xwaylandvideobridge,plasmashell,ksplashqml,org.kde.plasmashell,org.kde.polkit-kde-authentication-agent-1,org.kde.kruler,kruler,kwin_wayland,ksmserver-logout-greeter</default>
</entry>
<entry name="ignoreRole" type="String">
<label>Ignore windows with specific role(comma-separated list)</label>
<default>quake</default>
</entry>
<entry name="ignoreScreen" type="String">
<label>Do not apply tiling on some screens(comma-separated list of screen names)</label>
<default></default>
</entry>
<entry name="ignoreVDesktop" type="String">
<label>Do not apply tiling on some virtual desktops(comma-separated list of virtual desktops names)</label>
<default></default>
</entry>
<entry name="ignoreTitle" type="String">
<label>Ignore windows with certain string in title(comma-separated list)</label>
<default></default>
</entry>
<entry name="screenDefaultLayout" type="String">
<label>Assigning number of layout to screen(output) by default</label>
<default></default>
</entry>
<entry name="tileNothing" type="Bool">
<label>Don't tile any windows by default</label>
<default>false</default>
</entry>
<entry name="tilingClass" type="String">
<label>Tile windows with certain classes(comma-separated list)</label>
<default></default>
</entry>
<entry name="floatingClass" type="String">
<label>Float windows with certain classes(comma-separated list)</label>
<default></default>
</entry>
<entry name="floatingTitle" type="String">
<label>Float windows with certain string in title(comma-separated list)</label>
<default></default>
</entry>
<entry name="floatUtility" type="Bool">
<label>Automatically float utility windows (Dialog, Splash, Utility)</label>
<default>true</default>
</entry>
<entry name="layoutPerActivity" type="Bool">
<label>Use separate layouts per activity.</label>
<default>true</default>
</entry>
<entry name="layoutPerDesktop" type="Bool">
<label>Use separate layouts per desktop.</label>
<default>true</default>
</entry>
<entry name="tiledWindowsLayer" type="Int">
<label>What layer will have tiled windows</label>
<default>0</default>
</entry>
<entry name="floatedWindowsLayer" type="Int">
<label>What layer will have floated windows</label>
<default>1</default>
</entry>
<entry name="soleWindowWidth" type="Int">
<label>The sole window width in percent.</label>
<default>100</default>
</entry>
<entry name="soleWindowHeight" type="Int">
<label>The sole window height in percent.</label>
<default>100</default>
</entry>
<entry name="soleWindowNoBorders" type="Bool">
<label>Do not draw borders around sole window</label>
<default>false</default>
</entry>
<entry name="soleWindowNoGaps" type="Bool">
<label>Do not draw gaps around sole window</label>
<default>false</default>
</entry>
<entry name="monocleMaximize" type="Bool">
<label>Always maximize windows in Monocle layout</label>
<default>true</default>
</entry>
<entry name="monocleMinimizeRest" type="Bool">
<label>Minimize other tiles than the current one in Monocle layout</label>
<default>false</default>
</entry>
<entry name="adjustLayout" type="Bool">
<label>Adjust layout with mouse</label>
<default>true</default>
</entry>
<entry name="adjustLayoutLive" type="Bool">
<label>Adjust as window is being resized</label>
<default>true</default>
</entry>
<entry name="keepTilingOnDrag" type="Bool">
<label>Always preserve the tiling status of a window upon dragging</label>
<default>true</default>
</entry>
<entry name="noTileBorder" type="Bool">
<label>Remove borders of tiled windows</label>
<default>false</default>
</entry>
<entry name="preventMinimize" type="Bool">
<label>Prevent windows from minimizing</label>
<default>false</default>
</entry>
<entry name="preventProtrusion" type="Bool">
<label>Prevent windows from protruding out of screen</label>
<default>true</default>
</entry>
<entry name="notificationDuration" type="Int">
<label>Duration of pop-up notifications</label>
<default>1000</default>
</entry>
<entry name="screenGapLeft" type="Int">
<label>Gap between tiles and screen borders (Left)</label>
<default>0</default>
</entry>
<entry name="screenGapRight" type="Int">
<label>Gap between tiles and screen borders (Right)</label>
<default>0</default>
</entry>
<entry name="screenGapTop" type="Int">
<label>Gap between tiles and screen borders (Top)</label>
<default>0</default>
</entry>
<entry name="screenGapBottom" type="Int">
<label>Gap between tiles and screen borders (Bottom)</label>
<default>0</default>
</entry>
<entry name="screenGapBetween" type="Int">
<label>Gap between tiles in Tile layout</label>
<default>0</default>
</entry>
<entry name="gapsOverrideConfig" type="String">
<label>Override default screen gaps</label>
<default></default>
</entry>
<entry name="floatSkipPagerWindows" type="Bool">
<label>Make the window float if its property: skipPager=true (HACK)</label>
<default>false</default>
</entry>
<entry name="directionalKeyDwm" type="Bool">
<label>Up, Down, Left, and Right shortcut behaves like dwm</label>
<default>false</default>
</entry>
<entry name="directionalKeyFocus" type="Bool">
<label>Up, Down, Left, and Right shortcut moves focus</label>
<default>true</default>
</entry>
<entry name="limitTileWidth" type="Bool">
<label>Limit the width of tiles</label>
<default>false</default>
</entry>
<entry name="limitTileWidthRatio" type="Double">
<label>Ratio of maximum tile width to the height of screen</label>
<default>1.6</default>
</entry>
<entry name="newWindowPosition" type="Int">
<label>The position every new window in the screen window array</label>
<default>0</default>
</entry>
<entry name="logging" type="Bool">
<label>Enable logging</label>
<default>false</default>
</entry>
<entry name="logNewWindows" type="Bool">
<label>Log new windows</label>
<default>false</default>
</entry>
<entry name="logFilteredWindows" type="Bool">
<label>Log filtered windows</label>
<default>false</default>
</entry>
<entry name="logUnmanagedWindows" type="Bool">
<label>Log unmanaged windows</label>
<default>false</default>
</entry>
<entry name="logWorkspaceSignals" type="Bool">
<label>Log signals from workspace</label>
<default>false</default>
</entry>
<entry name="logWorkspaceSignalsSubmodules" type="String">
<label>Specify a comma separated list of numbers workspace signals submodules</label>
<default></default>
</entry>
<entry name="logWindowSignals" type="Bool">
<label>Log signals from windows</label>
<default>false</default>
</entry>
<entry name="logWindowSignalsSubmodules" type="String">
<label>Specify a comma separated list of numbers window signals submodules</label>
<default></default>
</entry>
<entry name="logOther" type="Bool">
<label>Log all other modules</label>
<default>false</default>
</entry>
<entry name="logOtherSubmodules" type="String">
<label>Specify a comma separated list of numbers all other submodules</label>
<default></default>
</entry>
<entry name="logFilter" type="Bool">
<label>Enable log filters</label>
<default>false</default>
</entry>
<entry name="logFilterStr" type="String">
<label>Specify a comma separated list of filters</label>
<default></default>
</entry>
</group>
</kcfg>
@@ -0,0 +1,62 @@
// 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.
import QtQuick 2.15
import org.kde.plasma.core as PlasmaCore;
import org.kde.plasma.components as Plasma;
import org.kde.plasma.plasma5support as Plasma5Support
import org.kde.kwin 3.0;
import org.kde.taskmanager as TaskManager
import "../code/script.js" as K
Item {
id: scriptRoot
TaskManager.ActivityInfo {
id: activityInfo
}
Loader {
id: popupDialog
source: "popup.qml"
function show(text, duration) {
var area = Workspace.clientArea(KWin.FullScreenArea, Workspace.activeScreen, Workspace.currentDesktop);
this.item.show(text, area, duration);
}
}
Component.onCompleted: {
console.log("KROHNKITE: starting the script");
const api = {
"workspace": Workspace,
// "options": Options,
"kwin": KWin,
"shortcuts": shortcutsLoader.item
};
(new K.KWinDriver(api)).main();
}
Loader {
id: shortcutsLoader;
source: "shortcuts.qml";
}
}
@@ -0,0 +1,78 @@
// 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.
import QtQuick 2.15
import QtQuick.Controls 2.15
import org.kde.plasma.core as PlasmaCore;
/*
* Component Documentation
* - PlasmaCore global `theme` object:
* https://techbase.kde.org/Development/Tutorials/Plasma2/QML2/API#Plasma_Themes
* - PlasmaCore.Dialog:
* https://techbase.kde.org/Development/Tutorials/Plasma2/QML2/API#Top_Level_windows
*/
PlasmaCore.Dialog {
id: popupDialog
type: PlasmaCore.Dialog.OnScreenDisplay
flags: Qt.Popup | Qt.WindowStaysOnTopHint
location: PlasmaCore.Types.Floating
outputOnly: true
visible: false
mainItem: Item {
width: messageLabel.implicitWidth
height: messageLabel.implicitHeight
Label {
id: messageLabel
padding: 10
font.pointSize: Math.round(10)
font.weight: Font.Bold
}
/* hides the popup window when triggered */
Timer {
id: hideTimer
repeat: false
onTriggered: {
popupDialog.visible = false;
}
}
}
function show(text, area, duration) {
hideTimer.stop();
messageLabel.text = text;
this.x = (area.x + area.width / 2) - this.width / 2;
this.y = (area.y + area.height / 2) - this.height / 2;
this.visible = true;
hideTimer.interval = duration;
hideTimer.start();
}
}
@@ -0,0 +1,358 @@
import QtQuick;
import org.kde.kwin;
Item {
id: dbus;
function getToggleDock() {
return toggleDock;
}
ShortcutHandler {
id: toggleDock;
name: "KrohnkitetoggleDock";
text: "Krohnkite: Toggle Dock";
sequence: "";
}
function getFocusNext() {
return focusNext;
}
ShortcutHandler {
id: focusNext;
name: "KrohnkiteFocusNext";
text: "Krohnkite: Focus Next";
sequence: "Meta+.";
}
function getFocusPrev() {
return focusPrev;
}
ShortcutHandler {
id: focusPrev;
name: "KrohnkiteFocusPrev";
text: "Krohnkite: Focus Previous";
sequence: "Meta+,";
}
function getFocusDown() {
return focusDown;
}
ShortcutHandler {
id: focusDown;
name: "KrohnkiteFocusDown";
text: "Krohnkite: Focus Down";
sequence: "Meta+J";
}
function getFocusUp() {
return focusUp;
}
ShortcutHandler {
id: focusUp;
name: "KrohnkiteFocusUp";
text: "Krohnkite: Focus Up";
sequence: "Meta+K";
}
function getFocusLeft() {
return focusLeft;
}
ShortcutHandler {
id: focusLeft;
name: "KrohnkiteFocusLeft";
text: "Krohnkite: Focus Left";
sequence: "Meta+H";
}
function getFocusRight() {
return focusRight;
}
ShortcutHandler {
id: focusRight;
name: "KrohnkiteFocusRight";
text: "Krohnkite: Focus Right";
sequence: "Meta+L";
}
function getShiftDown() {
return shiftDown;
}
ShortcutHandler {
id: shiftDown;
name: "KrohnkiteShiftDown";
text: "Krohnkite: Move Down/Next";
sequence: "Meta+Shift+J";
}
function getShiftUp() {
return shiftUp;
}
ShortcutHandler {
id: shiftUp;
name: "KrohnkiteShiftUp";
text: "Krohnkite: Move Up/Prev";
sequence: "Meta+Shift+K";
}
function getShiftLeft() {
return shiftLeft;
}
ShortcutHandler {
id: shiftLeft;
name: "KrohnkiteShiftLeft";
text: "Krohnkite: Move Left";
sequence: "Meta+Shift+H";
}
function getShiftRight() {
return shiftRight;
}
ShortcutHandler {
id: shiftRight;
name: "KrohnkiteShiftRight";
text: "Krohnkite: Move Right";
sequence: "Meta+Shift+L";
}
function getGrowHeight() {
return growHeight;
}
ShortcutHandler {
id: growHeight;
name: "KrohnkiteGrowHeight";
text: "Krohnkite: Grow Height";
sequence: "Meta+Ctrl+J";
}
function getShrinkHeight() {
return shrinkHeight;
}
ShortcutHandler {
id: shrinkHeight;
name: "KrohnkiteShrinkHeight";
text: "Krohnkite: Shrink Height";
sequence: "Meta+Ctrl+K";
}
function getShrinkWidth() {
return shrinkWidth;
}
ShortcutHandler {
id: shrinkWidth;
name: "KrohnkiteShrinkWidth";
text: "Krohnkite: Shrink Width";
sequence: "Meta+Ctrl+H";
}
function getGrowWidth() {
return growWidth;
}
ShortcutHandler {
id: growWidth;
name: "KrohnkitegrowWidth";
text: "Krohnkite: Grow Width";
sequence: "Meta+Ctrl+L";
}
function getIncrease() {
return increase;
}
ShortcutHandler {
id: increase;
name: "KrohnkiteIncrease";
text: "Krohnkite: Increase";
sequence: "Meta+I";
}
function getDecrease() {
return decrease;
}
ShortcutHandler {
id: decrease;
name: "KrohnkiteDecrease";
text: "Krohnkite: Decrease";
sequence: "Meta+D";
}
function getToggleFloat() {
return toggleFloat;
}
ShortcutHandler {
id: toggleFloat;
name: "KrohnkiteToggleFloat";
text: "Krohnkite: Toggle Float";
sequence: "Meta+F";
}
function getFloatAll() {
return floatAll;
}
ShortcutHandler {
id: floatAll;
name: "KrohnkiteFloatAll";
text: "Krohnkite: Toggle Float All";
sequence: "Meta+Shift+F";
}
function getNextLayout() {
return nextLayout;
}
ShortcutHandler {
id: nextLayout;
name: "KrohnkiteNextLayout";
text: "Krohnkite: Next Layout";
sequence: "Meta+\\";
}
function getPreviousLayout() {
return previousLayout;
}
ShortcutHandler {
id: previousLayout;
name: "KrohnkitePreviousLayout";
text: "Krohnkite: Previous Layout";
sequence: "Meta+|";
}
function getRotate() {
return rotate;
}
ShortcutHandler {
id: rotate;
name: "KrohnkiteRotate";
text: "Krohnkite: Rotate";
sequence: "Meta+R";
}
function getRotatePart() {
return rotatePart;
}
ShortcutHandler {
id: rotatePart;
name: "KrohnkiteRotatePart";
text: "Krohnkite: Rotate Part";
sequence: "Meta+Shift+R";
}
function getSetMaster() {
return setMaster;
}
ShortcutHandler {
id: setMaster;
name: "KrohnkiteSetMaster";
text: "Krohnkite: Set master";
sequence: "Meta+Return";
}
function getTileLayout() {
return tileLayout;
}
ShortcutHandler {
id: tileLayout;
name: "KrohnkiteTileLayout";
text: "Krohnkite: Tile Layout";
sequence: "Meta+T";
}
function getMonocleLayout() {
return monocleLayout;
}
ShortcutHandler {
id: monocleLayout;
name: "KrohnkiteMonocleLayout";
text: "Krohnkite: Monocle Layout";
sequence: "Meta+M";
}
function getThreeColumnLayout() {
return treeColumnLayout;
}
ShortcutHandler {
id: treeColumnLayout;
name: "KrohnkiteTreeColumnLayout";
text: "Krohnkite: Three Column Layout";
sequence: "";
}
function getSpreadLayout() {
return spreadLayout;
}
ShortcutHandler {
id: spreadLayout;
name: "KrohnkiteSpreadLayout";
text: "Krohnkite: Spread Layout";
sequence: "";
}
function getStairLayout() {
return stairLayout;
}
ShortcutHandler {
id: stairLayout;
name: "KrohnkiteStairLayout";
text: "Krohnkite: Stair Layout";
sequence: "";
}
function getFloatingLayout() {
return floatingLayout;
}
ShortcutHandler {
id: floatingLayout;
name: "KrohnkiteFloatingLayout";
text: "Krohnkite: Floating Layout";
sequence: "";
}
function getQuarterLayout() {
return quarterLayout;
}
ShortcutHandler {
id: quarterLayout;
name: "KrohnkiteQuarterLayout";
text: "Krohnkite: Quarter Layout";
sequence: "";
}
function getStackedLayout() {
return stackedLayout;
}
ShortcutHandler {
id: stackedLayout;
name: "KrohnkiteStackedLayout";
text: "Krohnkite: Stacked Layout";
sequence: "";
}
function getBTreeLayout() {
return bTreeLayout;
}
ShortcutHandler {
id: bTreeLayout;
name: "KrohnkiteBTreeLayout";
text: "Krohnkite: BTree Layout";
sequence: "";
}
function getSpiralLayout() {
return spiralLayout;
}
ShortcutHandler {
id: spiralLayout;
name: "KrohnkiteSpiralLayout";
text: "Krohnkite: Spiral Layout";
sequence: "";
}
function getColumnsLayout() {
return columnsLayout;
}
ShortcutHandler {
id: columnsLayout;
name: "KrohnkiteColumnsLayout";
text: "Krohnkite: Columns Layout";
sequence: "";
}
}
@@ -0,0 +1,23 @@
{
"KPackageStructure": "KWin/Script",
"KPlugin": {
"Name": "Krohnkite",
"Description": "A dynamic tiling script for KWin (1d7fd74)",
"Icon": "dialog-tile-clones",
"Authors": [
{
"Email": "esjeon@hyunmu.am",
"Name": "Eon S. Jeon"
}
],
"Id": "krohnkite",
"Version": "0.9.9.2",
"License": "MIT",
"Website": "https://github.com/anametologin/krohnkite#readme"
},
"X-Plasma-API": "declarativescript",
"X-Plasma-API-Minimum-Version": "6.0",
"X-Plasma-MainScript": "ui/main.qml",
"X-KDE-ConfigModule": "kwin/effects/configs/kcm_kwin4_genericscripted",
"X-KWin-Config-TranslationDomain": "krohnkite"
}
@@ -0,0 +1,2 @@
// Cannot package without this file due to a bug in kpackagetool6
// This file is empty on purpose, the implementation is done in the ui/main.qml file instead
@@ -0,0 +1,376 @@
<?xml version="1.0" encoding="UTF-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 http://www.kde.org/standards/kcfg/1.0/kcfg.xsd">
<kcfgfile name=""/>
<group name="">
<entry name="defaultTiler" type="Int">
<default>0</default>
</entry>
<entry name="centerInTileMode" type="Int">
<default>0</default>
</entry>
<entry name="rememberTiler" type="Bool">
<default>false</default>
</entry>
<entry name="restoreSize" type="Bool">
<default>false</default>
</entry>
<entry name="allowTransient" type="Bool">
<default>false</default>
</entry>
<entry name="allowModal" type="Bool">
<default>false</default>
</entry>
<entry name="tilerVisibility" type="Int">
<default>0</default>
</entry>
<entry name="revealMargin" type="Int">
<default>200</default>
</entry>
<entry name="displayAs" type="Int">
<default>0</default>
</entry>
<entry name="windowVisibility" type="Int">
<default>0</default>
</entry>
<entry name="theme" type="Int">
<default>0</default>
</entry>
<entry name="tileMargin" type="Int">
<default>0</default>
</entry>
<entry name="screenMargin" type="Int">
<default>0</default>
</entry>
<entry name="defaultInput" type="Int">
<default>0</default>
</entry>
<entry name="showOverlayTextHint" type="Bool">
<default>true</default>
</entry>
<entry name="overlayLayout" type="String">
<default>4x2</default>
</entry>
<entry name="overlayScreenEdgeMargin" type="Int">
<default>0</default>
</entry>
<entry name="overlayPollingRate" type="Int">
<default>100</default>
</entry>
<entry name="rememberAllLayouts" type="Bool">
<default>false</default>
</entry>
<entry name="showTargetTileHint" type="Bool">
<default>true</default>
</entry>
<entry name="showTextHint" type="Bool">
<default>true</default>
</entry>
<entry name="gridTilerOpacity" type="Int">
<default>100</default>
<min>0</min>
<max>100</max>
</entry>
<entry name="popupGridAt" type="Int">
<default>0</default>
</entry>
<entry name="horizontalAlignment" type="Int">
<default>1</default>
</entry>
<entry name="verticalAlignment" type="Int">
<default>1</default>
</entry>
<entry name="gridColumns" type="Int">
<default>3</default>
</entry>
<entry name="gridAllColumns" type="Int">
<default>6</default>
</entry>
<entry name="gridSpacing" type="Int">
<default>10</default>
</entry>
<entry name="gridWidth" type="Int">
<default>130</default>
</entry>
<entry name="gridHeight" type="Int">
<default>70</default>
</entry>
<entry name="popupGridPollingRate" type="Int">
<default>100</default>
</entry>
<entry name="virtualDesktopVisibility" type="Int">
<default>0</default>
</entry>
<entry name="virtualDesktopDropAction" type="Int">
<default>0</default>
</entry>
<entry name="virtualDesktopHoverTime" type="Int">
<default>500</default>
</entry>
<entry name="moveBackOnDrop" type="Bool">
<default>false</default>
</entry>
<entry name="moveBackOnTile" type="Bool">
<default>false</default>
</entry>
<entry name="showAddVirtualDesktopButton" type="Bool">
<default>true</default>
</entry>
<entry name="autoRemoveEmptyVirtualDesktops" type="Bool">
<default>false</default>
</entry>
<entry name="popupLayout" type="String">
<default>1x1
2x1
3x1
SPECIAL_SPLIT_HORIZONTAL
0,0,75,100+25,0,75,100+25,0,50,100
4x1
2x2
SPECIAL_FILL
4x2
SPECIAL_AUTO_TILER_1
SPECIAL_AUTO_TILER_2
SPECIAL_AUTO_TILER_3</default>
</entry>
<entry name="allPopupLayouts" type="String">
<default>SPECIAL_FILL
SPECIAL_SPLIT_VERTICAL
SPECIAL_SPLIT_HORIZONTAL
SPECIAL_MAXIMIZE
SPECIAL_MINIMIZE
SPECIAL_FULLSCREEN
0,0,50,100+50,0,50,50+50,50,50,50
0,0,50,50+0,50,50,50+50,0,50,100
0,0,25,50+0,50,25,50+25,0,50,100+75,0,25,50+75,50,25,50
0,0,25,50+0,50,25,50+25,0,50,50+25,50,50,50+75,0,25,50+75,50,25,50
SPECIAL_KEEP_ABOVE
SPECIAL_KEEP_BELOW
0,0,50,100+50,0,25,50+50,50,25,50+75,0,25,50+75,50,25,50
0,0,25,50+0,50,25,50+25,0,25,50+25,50,25,50+50,0,50,100
0,0,50,50+0,50,50,50+50,0,25,50+50,50,25,50+75,0,25,50+75,50,25,50
0,0,25,50+0,50,25,50+25,0,25,50+25,50,25,50+50,0,50,50+50,50,50,50
SPECIAL_NO_TITLEBAR_AND_FRAME
SPECIAL_CLOSE
1x1
2x1
3x1
4x1
5x1
6x1
1x2
1x3
1x4
1x5
0,0,75,100+25,0,75,100+25,0,50,100
0,0,75,50+25,0,75,50+25,0,50,50+0,50,75,50+25,50,75,50+25,50,50,50
2x2
3x2
4x2
5x2
0,0,67,100+33,0,67,100+17,0,66,100
0,0,67,50+33,0,67,50+17,0,66,50+0,50,67,50+33,50,67,50+17,50,66,50
2x3
3x3
4x3
5x3
1x1+17,0,66,100+33,0,34,100
1x2+17,0,66,50+33,0,34,50+17,50,66,50+33,50,34,50
11,25,26,50+37,25,26,50+63,25,26,50
11,0,26,100+37,0,26,100+63,0,26,100
11,0,26,50+37,0,26,50+63,0,26,50 + 11,50,26,50+37,50,26,50+63,50,26,50
11,0,26,100+37,0,26,100+63,0,26,100 + 11,12,26,76+37,12,26,76+63,12,26,76 + 11,25,26,50+37,25,26,50+63,25,26,50
1x1+12,0,76,100+25,0,50,100+37,0,26,100
1x2+12,0,76,50+25,0,50,50+37,0,26,50+12,50,76,50+25,50,50,50+37,50,26,50
0,25,37,50+37,25,26,50+63,25,37,50
0,0,37,100+37,0,26,100+63,0,37,100
0,0,37,50+37,0,26,50+63,0,37,50 + 0,50,37,50+37,50,26,50+63,50,37,50
0,0,37,100+37,0,26,100+63,0,37,100 + 0,12,37,76+37,12,26,76+63,12,37,76 + 0,25,37,50+37,25,26,50+63,25,37,50
1x1+17,17,66,66+33,33,34,34
1x1+12,12,76,76+25,25,50,50+37,37,26,26
0,0,25,100+75,0,25,100+25,0,50,100
0,0,25,67+75,0,25,67+25,0,50,67+0,67,25,33+75,67,25,33+25,67,50,33
0,0,25,33+75,0,25,33+25,0,50,33+0,33,25,67+75,33,25,67+25,33,50,67
0,0,25,67+75,0,25,67+25,0,50,67+0,33,25,67+75,33,25,67+25,33,50,67
0,0,33,67+33,0,34,67+67,0,33,67+0,67,33,33+33,67,34,33+67,67,33,33
0,0,33,33+33,0,34,33+67,0,33,33+0,33,33,67+33,33,34,67+67,33,33,67
0,0,67,100+67,0,33,100
0,0,33,100+33,0,67,100
0,0,67,100+67,0,33,50+67,50,33,50
0,0,33,50+0,50,33,50+33,0,67,100
0,0,67,50+67,0,33,50+0,50,67,50+67,50,33,50
0,0,33,50+33,0,67,50+0,50,33,50+33,50,67,50
SPECIAL_AUTO_TILER_TOGGLE
SPECIAL_AUTO_TILER_1
SPECIAL_AUTO_TILER_2
SPECIAL_AUTO_TILER_3</default>
</entry>
<entry name="shortcutChangeMode" type="String">
<default>Meta+Ctrl+Space</default>
</entry>
<entry name="shortcutShowAllSpan" type="String">
<default>Ctrl+Space</default>
</entry>
<entry name="shortcutVisibility" type="String">
<default>Meta+Space</default>
</entry>
<entry name="shortcutInputType" type="String">
<default>Ctrl+Alt+I</default>
</entry>
<entry name="shortcutCenterInTile" type="String">
<default>Meta+Ctrl+C</default>
</entry>
<entry name="shortcutMoveOnDrop" type="String">
<default>Meta+Ctrl+V</default>
</entry>
<entry name="shortcutToggleTilingSuggestions" type="String">
<default>Meta+Ctrl+S</default>
</entry>
<entry name="hintChangeMode" type="Bool">
<default>true</default>
</entry>
<entry name="hintShowAllSpan" type="Bool">
<default>true</default>
</entry>
<entry name="hintVisibility" type="Bool">
<default>true</default>
</entry>
<entry name="hintInputType" type="Bool">
<default>true</default>
</entry>
<entry name="hintCenterInTile" type="Bool">
<default>true</default>
</entry>
<entry name="hintMoveOnDrop" type="Bool">
<default>true</default>
</entry>
<entry name="hintToggleTilingSuggestions" type="Bool">
<default>true</default>
</entry>
<entry name="showHintHint" type="Bool">
<default>true</default>
</entry>
<entry name="debugLogs" type="Bool">
<default>false</default>
</entry>
<entry name="showPositionHint" type="Bool">
<default>false</default>
</entry>
<entry name="positionHintFormat" type="Int">
<default>0</default>
</entry>
<entry name="showSizeHint" type="Bool">
<default>false</default>
</entry>
<entry name="sizeHintFormat" type="Int">
<default>0</default>
</entry>
<entry name="autoTileNewWindows" type="Bool">
<default>false</default>
</entry>
<entry name="autoTilerEdgeScroll" type="Bool">
<default>true</default>
</entry>
<entry name="autoTileMinimizedMaximized" type="Bool">
<default>true</default>
</entry>
<entry name="autoTileRestoreSize" type="Bool">
<default>false</default>
</entry>
<entry name="autoTileRestoreSizeAndPosition" type="Bool">
<default>true</default>
</entry>
<entry name="autoTileToggleBehavior" type="Int">
<default>0</default>
</entry>
<entry name="autoTileIds" type="String">
<default></default>
</entry>
<entry name="autoTileBlacklist" type="String">
<default>krunner,kded,polkit,plasmashell,yakuake,spectacle,org.kde.spectacle,kded5,xwaylandvideobridge,ksplashqml,org.kde.plasmashell,org.kde.polkit-kde-authentication-agent-1,org.kde.kruler,kruler,kwin_wayland,kwin,ksmserver-logout-greeter,ksmserver</default>
</entry>
<entry name="autoTileWindowAction" type="Int">
<default>0</default>
</entry>
<entry name="autoTileWindowIndex" type="Int">
<default>0</default>
</entry>
<entry name="autoTileFocusAction" type="Int">
<default>0</default>
</entry>
<entry name="autoTileFocusIndex" type="Int">
<default>0</default>
</entry>
<entry name="autoTileMinimizedFocusAction" type="Int">
<default>0</default>
</entry>
<entry name="autoTileMinimizedFocusIndex" type="Int">
<default>0</default>
</entry>
<entry name="autoTileDragSwapAction" type="Int">
<default>0</default>
</entry>
<entry name="autoTileLayer" type="Int">
<default>2</default>
</entry>
<entry name="maxAutoTileDelay" type="Int">
<default>5</default>
</entry>
<entry name="maxAutoTileDelaySessionStart" type="Int">
<default>10</default>
</entry>
<entry name="autoTilerMode1" type="Int">
<default>8</default>
</entry>
<entry name="autoTilerMode2" type="Int">
<default>1</default>
</entry>
<entry name="autoTilerMode3" type="Int">
<default>12</default>
</entry>
<entry name="autoTilerCustom1" type="String">
<default>0:0,0,100,100
0:0,0,50,100+1:50,0,50,100
0:0,0,50,100+1:50,0,50,50+2:50,50,50,50
0:0,0,50,50+3:0,50,50,50+1:50,0,50,50+2:50,50,50,50</default>
</entry>
<entry name="autoTilerCustom2" type="String">
<default>{"carousel":true}
0:0,0,100,100</default>
</entry>
<entry name="autoTilerCustom3" type="String">
<default>0:0,0,67,100
0:0,0,67,100+1:67,0,33,100
0:0,0,67,100+1:67,0,33,50+2:67,50,33,50</default>
</entry>
<entry name="autoTilerCarousel1" type="Bool">
<default>false</default>
</entry>
<entry name="autoTilerCarousel2" type="Bool">
<default>false</default>
</entry>
<entry name="autoTilerCarousel3" type="Bool">
<default>false</default>
</entry>
<entry name="suggestionsVisibility" type="Int">
<default>0</default>
</entry>
<entry name="suggestionsMode" type="Int">
<default>0</default>
</entry>
<entry name="suggestOtherScreens" type="Bool">
<default>false</default>
</entry>
<entry name="suggestOtherDesktops" type="Bool">
<default>false</default>
</entry>
<entry name="suggestOtherActivities" type="Bool">
<default>false</default>
</entry>
<entry name="suggestAutoTiled" type="Bool">
<default>false</default>
</entry>
<entry name="suggestMinimized" type="Bool">
<default>false</default>
</entry>
</group>
</kcfg>
@@ -0,0 +1,239 @@
import QtQuick
import org.kde.kirigami as Kirigami
Item {
Kirigami.Theme.colorSet: Kirigami.Theme.View
property bool lightTheme: {
return Kirigami.ColorUtils.brightnessForColor(Kirigami.Theme.backgroundColor) === Kirigami.ColorUtils.Light;
}
property var backgroundColor: {
switch (config.theme) {
case 1:
return "#161925";
case 2:
return "#110B00";
case 3:
return "#FFFFFF";
case 4:
return "#110000";
case 5:
return "#020900";
case 6:
return "#0A0010";
case 7:
return "#001100";
case 8:
return "#111100";
case 9:
return "#000710";
default:
return Kirigami.Theme.backgroundColor;
}
}
property var borderColor: {
switch (config.theme) {
case 1:
return "#666666";
case 2:
return "#7F4F00";
case 3:
return "#777777";
case 4:
return "#780000";
case 5:
return "#587C00";
case 6:
return "#4A0076";
case 7:
return "#225522";
case 8:
return "#555500";
case 9:
return "#004093";
default:
if (lightTheme) {
return Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, "black", 0.2);
} else {
return Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, "white", 0.2);
}
}
}
property var textColor: {
switch (config.theme) {
case 1:
return "white";
case 2:
return "white";
case 3:
return "black";
case 4:
return "white";
case 5:
return "white";
case 6:
return "white";
case 7:
return "white";
case 8:
return "white";
case 9:
return "white";
default:
return lightTheme ? "black" : "white";
}
}
property var overlayTextColor: {
switch (config.theme) {
case 1:
return "white";
case 2:
return "white";
case 3:
return "black";
case 4:
return "black";
case 5:
return "black";
case 6:
return "white";
case 7:
return "black";
case 8:
return "black";
case 9:
return "white";
default:
return lightTheme ? "black" : "white";
}
}
property var hintBackgroundColor: {
switch (config.theme) {
case 1:
return "#590099FF";
case 2:
return "#59FF9F00";
case 3:
return "#59FF0000";
case 4:
return "#59FF0000";
case 5:
return "#59B5FF00";
case 6:
return "#599F00FF";
case 7:
return "#5900FF00";
case 8:
return "#59FFFF00";
case 9:
return "#590067FF";
default:
return Kirigami.ColorUtils.tintWithAlpha("transparent", Kirigami.Theme.highlightColor, 0.35);
}
}
property var tileBorderColor: {
switch (config.theme) {
case 1:
return "#0099FF";
case 2:
return "#FF9F00";
case 3:
return "#FF0000";
case 4:
return "#FF0000";
case 5:
return "#B5FF00";
case 6:
return "#9F00FF";
case 7:
return "#00FF00";
case 8:
return "#FFFF00";
case 9:
return "#0067FF";
default:
return Kirigami.Theme.highlightColor;
}
}
property var tileBackgroundColor: {
switch (config.theme) {
case 1:
return "#0C0099FF";
case 2:
return "#0CFF9F00";
case 3:
return "#3CFF0000";
case 4:
return "#3CFF0000";
case 5:
return "#3CB5FF00";
case 6:
return "#3C9F00FF";
case 7:
return "#3C00FF00";
case 8:
return "#3CFFFF00";
case 9:
return "#3C0067FF";
default:
return Kirigami.ColorUtils.tintWithAlpha("transparent", Kirigami.Theme.highlightColor, 0.05);
}
}
property var tileBackgroundColorIntense: {
switch (config.theme) {
case 1:
return "#3C0099FF";
case 2:
return "#3CFF9F00";
case 3:
return "#3CFF0000";
case 4:
return "#3CFF0000";
case 5:
return "#3CB5FF00";
case 6:
return "#3C9F00FF";
case 7:
return "#3C00FF00";
case 8:
return "#3CFFFF00";
case 9:
return "#3C0067FF";
default:
return Kirigami.ColorUtils.tintWithAlpha("transparent", Kirigami.Theme.highlightColor, 0.23);
}
}
property var tileBackgroundColorActive: {
switch (config.theme) {
case 1:
return "#BE0099FF";
case 2:
return "#BEFF9F00";
case 3:
return "#BEFF0000";
case 4:
return "#BEFF0000";
case 5:
return "#BEB5FF00";
case 6:
return "#BE9F00FF";
case 7:
return "#BE00FF00";
case 8:
return "#BEFFFF00";
case 9:
return "#BE0067FF";
default:
return Kirigami.ColorUtils.tintWithAlpha("transparent", Kirigami.Theme.highlightColor, 0.75);
}
}
}
@@ -0,0 +1,182 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import org.kde.kwin
import org.kde.kirigami as Kirigami
// import QtWebView
ApplicationWindow {
property var overrides: {}
property var currentOverrides: {}
property var currentApplications: []
property var currentWindows: []
property var defaultConfig: {}
property var currentApplicationIndex: -1
property var currentWindowIndex: -1
// property var isX11: Qt.platform.pluginName == 'xcb'
property var mouseStartX
property var mouseStartY
property var windowStartX
property var windowStartY
Kirigami.Theme.colorSet: Kirigami.Theme.Window
property var lightTheme: {
return Kirigami.ColorUtils.brightnessForColor(Kirigami.Theme.backgroundColor) === Kirigami.ColorUtils.Light;
}
property var headerColor: {
if (lightTheme) {
return Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, "white", 0.4);
} else {
return Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, "black", 0.3);
}
}
property var headerBorderColor: {
if (lightTheme) {
return Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, "black", 0.2);
} else {
return Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, "white", 0.2);
}
}
id: mainMenuRoot
width: 1000
height: 734
title: "Mouse Tiler Configuration"
color: Kirigami.Theme.backgroundColor
flags: Qt.FramelessWindowHint | Qt.Window | Qt.BypassWindowManagerHint
// flags: ixX11 ? Qt.X11BypassWindowManagerHint : flags
// x: isX11 ? Workspace.virtualScreenSize.width / 2 - width / 2 : x
// y: isX11 ? Workspace.virtualScreenSize.height / 2 - height / 2 : y
x: Workspace.virtualScreenSize.width / 2 - width / 2
y: Workspace.virtualScreenSize.height / 2 - height / 2
function initMainMenu() {
}
Rectangle {
color: headerColor
height: 34
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
border.color: headerBorderColor
border.width: 1
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onPressed: {
mouseStartX = Workspace.cursorPos.x;
mouseStartY = Workspace.cursorPos.y;
windowStartX = mainMenuRoot.x;
windowStartY = mainMenuRoot.y;
}
onPositionChanged: {
mainMenuRoot.x = windowStartX + (Workspace.cursorPos.x - mouseStartX);
mainMenuRoot.y = windowStartY + (Workspace.cursorPos.y - mouseStartY);
}
onReleased: {
mainMenuRoot.x = windowStartX + (Workspace.cursorPos.x - mouseStartX);
mainMenuRoot.y = windowStartY + (Workspace.cursorPos.y - mouseStartY);
}
Label {
text: "Mouse Tiler Configuration"
anchors.centerIn: parent
}
Button {
text: "X"
flat: true
width: 26
height: 26
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 4
onClicked: root.closeMainMenu()
}
}
}
GroupBox {
id: mainGroupBox
anchors.fill: parent
anchors.topMargin: 35
// WebView {
// id: webView
// anchors.fill: parent
// url: "https://rxweb.epizy.com/mousetiler/editor.html"
// }
ColumnLayout {
id: mainChoice
visible: true
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
GroupBox {
spacing: 5
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
Label {
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
text: "This was meant to be the UI designer. However it is a lot of work to implement it, probaly at least 1-2 weeks full time. Sadly I got rent to pay and a 3 year old son to feed and based on my previous project Remember Window Positions, I will not be able to feed us from my contributions to Linux. (After over 3 months full time work I earned $100 total which I apreciate but it's not possible to survive on). I might implement it in the future if the situation changes."
wrapMode: Text.WordWrap
}
// Label {
// Layout.fillWidth: true
// text: "Not implemented yet"
// wrapMode: Text.WordWrap
// }
}
}
// GroupBox {
// spacing: 5
// Layout.fillWidth: true
// ColumnLayout {
// anchors.fill: parent
// Label {
// Layout.fillWidth: true
// horizontalAlignment: Text.AlignHCenter
// text: "."
// wrapMode: Text.WordWrap
// }
// Button {
// id: selectWindowButton
// text: "Select Application/Window"
// Layout.fillWidth: true
// onClicked: selectWindow()
// }
// Button {
// text: "Edit Saved Applications and Windows"
// Layout.fillWidth: true
// onClicked: editSavedAppOrWindow()
// }
// }
// }
}
}
}
@@ -0,0 +1,319 @@
import QtQuick
import org.kde.kwin
import org.kde.plasma.core as PlasmaCore
// Window {
PlasmaCore.Dialog {
id: overlayTiler
property var activeScreen: null
property var clientArea: ({width: 1, height: 1, x: 0, y: 0})
property var tilePadding: 2
property int activeIndex: -1
property int spanFromIndex: -1
property int minSpanX: -1
property int minSpanY: -1
property int maxSpanX: -1
property int maxSpanY: -1
property var convertedOverlay: ([])
// Workaround for plasma 6.6 and earlier - needed by Window and Plasma Dialog - replaced by Qt.WindowTransparentForInput in Plasma 6.7
property bool outputOnly: true
width: clientArea.width - root.config.overlayScreenEdgeMargin * 2
height: clientArea.height - root.config.overlayScreenEdgeMargin * 2
x: clientArea.x + root.config.overlayScreenEdgeMargin
y: clientArea.y + root.config.overlayScreenEdgeMargin
// flags: Qt.Popup | Qt.BypassWindowManagerHint | Qt.FramelessWindowHint
// flags: Qt.Tool | Qt.BypassWindowManagerHint | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.WindowDoesNotAcceptFocus
flags: (root.config.displayAs == 0 ? 0 : Qt.Popup) | (Qt.BypassWindowManagerHint | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.WindowDoesNotAcceptFocus | Qt.WindowTransparentForInput)
// color: "transparent" // Window
visible: false
backgroundHints: PlasmaCore.Types.NoBackground // PlasmaCore.Dialog
// outputOnly: true // PlasmaCore.Dialog
// type: PlasmaCore.Dialog.OnScreenDisplay
location: PlasmaCore.Types.Desktop // PlasmaCore.Dialog
function reset() {
activeScreen = null;
spanFromIndex = -1;
activeIndex = -1;
updateSpan();
}
function startAnimations() {
overlayTiler.opacity = 0;
showOverlayTilerAnimation.start();
}
function updateScreen() {
if (activeScreen != Workspace.activeScreen) {
autoTiler.updateShouldShowScreenEdges();
root.logE('updateScreen ' + Workspace.virtualScreenSize);
reset();
activeScreen = Workspace.activeScreen;
clientArea = Workspace.clientArea(KWin.FullScreenArea, activeScreen, Workspace.currentDesktop);
convertLayoutToScreen();
}
}
function convertLayoutToScreen() {
let layout = root.config.overlay;
let converted = [];
for (let i = 0; i < layout.length; i++) {
let tile = layout[i];
let width = (tile.pxW == undefined ? tile.w / 100 * mainItem.width : tile.pxW);
let height = (tile.pxH == undefined ? tile.h / 100 * mainItem.height : tile.pxH);
converted.push({
pxX: (tile.pxX == undefined ? tile.x / 100 * mainItem.width : tile.pxX) - (tile.aX == undefined ? 0 : tile.aX * width / 100),
pxY: (tile.pxY == undefined ? tile.y / 100 * mainItem.height : tile.pxY) - (tile.aY == undefined ? 0 : tile.aY * height / 100),
pxW: width,
pxH: height
});
}
convertedOverlay = converted;
}
function toggleSpan() {
if (spanFromIndex != activeIndex && spanFromIndex == -1) {
spanFromIndex = activeIndex;
updateSpan();
} else if (spanFromIndex >= 0) {
spanFromIndex = -1;
updateSpan();
}
}
function updateSpan() {
if (activeIndex == -1 || spanFromIndex == -1) {
minSpanX = -1;
minSpanY = -1;
maxSpanX = -1;
maxSpanY = -1;
} else {
let itemActive = tileRepeater.itemAt(activeIndex);
let itemSpan = tileRepeater.itemAt(spanFromIndex);
minSpanX = Math.min(itemActive.x, itemSpan.x);
minSpanY = Math.min(itemActive.y, itemSpan.y);
maxSpanX = Math.max(itemActive.x + itemActive.width, itemSpan.x + itemSpan.width);
maxSpanY = Math.max(itemActive.y + itemActive.height, itemSpan.y + itemSpan.height);
}
root.log('Span activeIndex: ' + activeIndex + ' spanFromIndex: ' + spanFromIndex + ' minSpanX: ' + minSpanX + ' minSpanY: ' + minSpanY + ' maxSpanX: ' + maxSpanX + ' maxSpanY: ' + maxSpanY);
}
function getGeometry() {
if (activeIndex >= 0) {
let x, y, width, height;
if (spanFromIndex >= 0) {
x = clientArea.x + minSpanX;
y = clientArea.y + minSpanY;
width = maxSpanX - minSpanX;
height = maxSpanY - minSpanY;
} else {
let item = tileRepeater.itemAt(activeIndex);
x = clientArea.x + item.x;
y = clientArea.y + item.y;
width = item.width;
height = item.height;
}
if (root.centerInTile) {
return {
x: x + width / 2 - root.currentlyMovedWindow.width / 2,
y: y + height / 2 - root.currentlyMovedWindow.height / 2,
width: root.currentlyMovedWindow.width,
height: root.currentlyMovedWindow.height
};
} else {
return {
x: x,
y: y,
width: width,
height: height
};
}
}
return null;
}
function getActiveVirtualDesktopIndex() {
return -1;
}
Item {
id: mainItem
width: popupTiler.width
height: popupTiler.height
SequentialAnimation {
id: showOverlayTilerAnimation
running: false
NumberAnimation {
target: overlayTiler;
property: "opacity";
from: 0;
to: 0;
duration: 32;
}
NumberAnimation {
target: overlayTiler;
property: "opacity";
from: 1;
to: 1;
duration: 1;
}
onFinished: {
root.updateWindowVisibility();
}
}
Colors {
id: colors
}
Repeater {
id: tileRepeater
model: overlayTiler.convertedOverlay
Item {
id: tile
property bool active: activeIndex == index
property bool spanned: !active && Math.ceil(modelData.pxX) >= Math.floor(minSpanX) && Math.floor(modelData.pxX) + Math.floor(modelData.pxW) <= Math.ceil(maxSpanX) && Math.ceil(modelData.pxY) >= Math.floor(minSpanY) && Math.floor(modelData.pxY) + Math.floor(modelData.pxH) <= Math.ceil(maxSpanY)
property bool spannedFrom: spanFromIndex == index
x: modelData.pxX
y: modelData.pxY
width: modelData.pxW
height: modelData.pxH
Rectangle {
anchors.fill: parent
anchors.margins: tilePadding
border.color: colors.tileBorderColor
border.width: 2
color: "transparent"
radius: 12
Rectangle {
anchors.fill: parent
radius: 12
color: active || spanned ? colors.tileBackgroundColorActive : colors.tileBackgroundColor
}
Text {
anchors.centerIn: parent
color: colors.overlayTextColor
textFormat: Text.StyledText
text: {
let defaultHint = "";
let insertExtraBreak = false;
if (root.config.showHintHint) {
defaultHint += (defaultHint.length > 0 ? "<br>" : "") + "Configure tiler visibility and these hints in settings.";
insertExtraBreak = true;
}
if (root.config.hintShowAllSpan) {
if (insertExtraBreak) {
defaultHint += "<br>";
insertExtraBreak = false;
}
defaultHint += (defaultHint.length > 0 ? "<br>" : "") + (spannedFrom ? "Stop spanning (<b>" + root.config.shortcutShowAllSpan + "</b>)" : "Span from this tile (<b>" + root.config.shortcutShowAllSpan + "</b>)");
insertExtraBreak = true;
}
if (root.config.hintVisibility) {
if (insertExtraBreak) {
defaultHint += "<br>";
insertExtraBreak = false;
}
defaultHint += (defaultHint.length > 0 ? "<br>" : "") + "Toggle visibility (<b>" + root.config.shortcutVisibility + "</b>)";
}
if (root.config.hintInputType) {
if (insertExtraBreak) {
defaultHint += "<br>";
insertExtraBreak = false;
}
defaultHint += (defaultHint.length > 0 ? "<br>" : "") + "Input type: " + (root.useMouseCursor ? "Mouse" : "Window") + " (<b>" + root.config.shortcutInputType + "</b>)";
}
if (root.config.hintCenterInTile) {
if (insertExtraBreak) {
defaultHint += "<br>";
insertExtraBreak = false;
}
defaultHint += (defaultHint.length > 0 ? "<br>" : "") + "Center in tile: " + (root.centerInTile ? "Enabled" : "Disabled") + " (<b>" + root.config.shortcutCenterInTile + "</b>)";
}
if (root.config.hintChangeMode) {
insertExtraBreak = false;
defaultHint += (defaultHint.length > 0 ? "<br><br>" : "") + "Switch mode (<b>" + root.config.shortcutChangeMode + "</b>)";
}
if (root.centerInTile) {
return (spannedFrom ? "<b>Center in spanned tiles</b><br><br>" : "<b>Center in this tile</b><br><br>") + defaultHint;
} else {
return defaultHint;
}
return defaultHint;
}
font.pixelSize: 16
font.family: "Noto Sans"
horizontalAlignment: Text.AlignHCenter
visible: root.config.showOverlayTextHint && (active && spanFromIndex == -1 || spannedFrom)
}
}
}
}
Rectangle {
id: popupWindowCursor
anchors.left: parent.left
anchors.leftMargin: root.getCursorPosition().x - clientArea.x - 6
anchors.top: parent.top
anchors.topMargin: root.getCursorPosition().y - clientArea.y - 6
width: 12
height: 12
border.color: colors.tileBorderColor
border.width: 2
color: colors.tileBackgroundColorActive
radius: 6
visible: !root.useMouseCursor
Rectangle {
anchors.centerIn: parent
width: 2
height: 2
color: colors.textColor
radius: 1
opacity: 0.8
}
}
Timer {
interval: root.config.overlayPollingRate
repeat: true
running: overlayTiler.visible
onTriggered: {
updateScreen();
let localCursorPos = Workspace.activeScreen.mapFromGlobal(root.getCursorPosition());
let x = localCursorPos.x - root.config.overlayScreenEdgeMargin;
let y = localCursorPos.y - root.config.overlayScreenEdgeMargin;
let index = -1;
for (let i = 0; i < tileRepeater.count; i++) {
let item = tileRepeater.itemAt(i);
if (item.x <= x && item.x + item.width >= x && item.y <= y && item.y + item.height >= y) {
index = i;
break;
}
}
if (index != activeIndex) {
activeIndex = index;
updateSpan();
}
}
}
}
}
@@ -0,0 +1,978 @@
import QtQuick
import QtQuick.Layouts
import org.kde.kwin
import org.kde.plasma.core as PlasmaCore
// Window {
PlasmaCore.Dialog {
id: popupTiler
property var activeScreen: null
property var clientArea: ({width: 1, height: 1, x: 0, y: 0})
property int tilePadding: 2
property int borderOffset: 2
property int activeLayoutIndex: -1
property int activeTileIndex: -1
property int positionX: 0
property int positionY: 0
property bool showAll: false
property bool lastShowAll: false
property var hint: null
property bool showPopupDropHint: false
property bool hasValidPopupDropHint: false
property var popupDropHintX: 0
property var popupDropHintY: 0
property var popupDropHintWidth: 0
property var popupDropHintHeight: 0
property bool popupDropHintIsCenterInTile: false
property bool sizeEstablished: false
property var revealBox: null
property bool revealed: false
property bool currentlyHovered: false
property int activeVirtualDesktopIndex: -1
property var activeVirtualDesktopHoverTime: -1
property bool virtualDesktopVisibilityOverride: false
// property bool shouldShowAutoTileScroll: false
// property var scrollLeftTime: 0
// property var scrollRightTime: 0
// Workaround for plasma 6.6 and earlier - needed by Window and Plasma Dialog - replaced by Qt.WindowTransparentForInput in Plasma 6.7
property bool outputOnly: true
width: clientArea.width
height: clientArea.height
x: clientArea.x
y: clientArea.y
//flags: Qt.Popup | Qt.BypassWindowManagerHint | Qt.FramelessWindowHint
// flags: Qt.Tool | Qt.BypassWindowManagerHint | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.WindowDoesNotAcceptFocus
flags: (root.config.displayAs == 0 ? 0 : Qt.Popup) | (Qt.BypassWindowManagerHint | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.WindowDoesNotAcceptFocus | Qt.WindowTransparentForInput)
// color: "transparent" // Window
visible: false
backgroundHints: PlasmaCore.Types.NoBackground // PlasmaCore.Dialog
// outputOnly: true // PlasmaCore.Dialog
// type: PlasmaCore.Dialog.OnScreenDisplay
location: PlasmaCore.Types.Desktop // PlasmaCore.Dialog
function reset() {
activeScreen = null;
activeLayoutIndex = -1;
activeTileIndex = -1;
hint = null;
showPopupDropHint = false;
updateHintContent();
}
function resetShowAll() {
showAll = false;
lastShowAll = false;
}
function startAnimations() {
popupTiler.opacity = 0;
showPopupTilerAnimation.start();
}
function updateScreen(forceUpdate = false) {
if (forceUpdate || activeScreen != Workspace.activeScreen) {
root.logE('updateScreen ' + Workspace.virtualScreenSize);
if (activeScreen != Workspace.activeScreen) {
autoTiler.updateAutoTilersInPopupTiler();
}
autoTiler.updateShouldShowScreenEdges();
// shouldShowAutoTileScroll = autoTiler.shouldShowAutoTileScroll();
activeScreen = Workspace.activeScreen;
clientArea = Workspace.clientArea(KWin.FullScreenArea, Workspace.activeScreen, Workspace.currentDesktop);
let localCursorPos = Workspace.activeScreen.mapFromGlobal(root.getCursorPosition());
let visibleHeight = virtualDesktopVisibilityOverride || root.config.virtualDesktopVisibility != 1 ? layouts.height : 0;
if (root.config.popupGridAt == 0) {
switch (root.config.horizontalAlignment) {
default:
positionX = localCursorPos.x - root.config.gridWidth / 2 - root.config.gridSpacing;
break;
case 1:
positionX = localCursorPos.x - layouts.width / 2;
break;
case 2:
positionX = localCursorPos.x - layouts.width + root.config.gridWidth / 2 + root.config.gridSpacing;
break;
}
if (visibleHeight == 0) {
positionY = localCursorPos.y;
} else {
switch (root.config.verticalAlignment) {
default:
positionY = localCursorPos.y - root.config.gridHeight / 2 - root.config.gridSpacing;
break;
case 1:
positionY = localCursorPos.y - layouts.height / 2;
break;
case 2:
positionY = localCursorPos.y - layouts.height + root.config.gridHeight / 2 + root.config.gridSpacing;
break;
}
}
} else {
switch (root.config.horizontalAlignment) {
default:
positionX = 0;
break;
case 1:
positionX = clientArea.width / 2 - layouts.width / 2;
break;
case 2:
positionX = clientArea.width - layouts.width;
break;
}
switch (root.config.verticalAlignment) {
default:
positionY = 0;
break;
case 1:
positionY = clientArea.height / 2 - visibleHeight / 2;
break;
case 2:
positionY = clientArea.height - visibleHeight;
break;
}
}
// positionX = localCursorPos.x - layouts.width / 2;
// positionY = localCursorPos.y - (layouts.height + popupHint.height + 2) / 2;
if (positionX < 0) {
positionX = 0;
} else if (positionX + layouts.width > popupTiler.width) {
positionX = popupTiler.width - layouts.width;
}
let totalHeight = visibleHeight;
if (root.virtualDesktopVisibile) {
if (totalHeight > 0) {
totalHeight += 2;
}
totalHeight += virtualDesktop.height;
}
if (root.config.showTextHint) {
totalHeight += popupHint.height + 2;
}
if (positionY < 0) {
positionY = 0;
} else if (positionY + totalHeight > popupTiler.height) {
positionY = popupTiler.height - totalHeight;
}
if (root.config.tilerVisibility == 3) {
let triggerLeft = Math.max(positionX - config.revealMargin, 0);
let triggerRight = Math.min(positionX + layouts.width + config.revealMargin, popupTiler.width);
let triggerTop = Math.max(positionY - config.revealMargin, 0);
let triggerBottom = Math.min(positionY + totalHeight + config.revealMargin, popupTiler.height);
let leftTop = Workspace.activeScreen.mapToGlobal(Qt.point(triggerLeft, triggerTop));
let rightBottom = Workspace.activeScreen.mapToGlobal(Qt.point(triggerRight, triggerBottom));
revealBox = { left: leftTop.x, right: rightBottom.x, top: leftTop.y, bottom: rightBottom.y };
updateRevealed(true);
} else {
revealBox = null;
updateRevealed(true);
}
}
}
function getGeometry() {
if (activeLayoutIndex >= 0 && activeTileIndex >= 0) {
if (root.centerInTile && hasValidPopupDropHint) {
return {
x: clientArea.x + popupDropHintX,
y: clientArea.y + popupDropHintY,
width: popupDropHintWidth,
height: popupDropHintHeight
};
} else if (layoutRepeater.model[activeLayoutIndex].special) {
return {
special: layoutRepeater.model[activeLayoutIndex].special,
specialMode: activeTileIndex
};
} else if (!root.centerInTile) {
return {
x: clientArea.x + popupDropHintX,
y: clientArea.y + popupDropHintY,
width: popupDropHintWidth,
height: popupDropHintHeight,
defaultLayouts: !showAll,
layoutIndex: activeLayoutIndex,
tileIndex: activeTileIndex
};
}
}
return null;
}
function getActiveVirtualDesktopIndex() {
return activeVirtualDesktopIndex;
}
function resetVirtualDesktopOverride() {
virtualDesktopVisibilityOverride = false;
}
function toggleShowAll() {
reset();
if (root.config.virtualDesktopVisibility == 1 && !virtualDesktopVisibilityOverride) {
virtualDesktopVisibilityOverride = true;
} else {
showAll = !showAll;
updateHintContent();
}
}
function updateAndShowPopupDropHint() {
let shouldShowPopupDropHint = false;
let special = layoutRepeater.model[activeLayoutIndex].special;
let geometry = null;
switch (special) {
case 'SPECIAL_FILL':
if (root.currentlyMovedWindow != null) {
geometry = root.getFillGeometry(root.currentlyMovedWindow, activeTileIndex == 0);
}
break;
case 'SPECIAL_SPLIT_VERTICAL':
if (root.currentlyMovedWindow != null) {
geometry = splitAndMoveSplitted(root.currentlyMovedWindow, true, activeTileIndex == 0, false);
}
break;
case 'SPECIAL_SPLIT_HORIZONTAL':
if (root.currentlyMovedWindow != null) {
geometry = splitAndMoveSplitted(root.currentlyMovedWindow, false, activeTileIndex == 0, false);
}
break;
case 'SPECIAL_NO_TITLEBAR_AND_FRAME':
case 'SPECIAL_KEEP_ABOVE':
case 'SPECIAL_KEEP_BELOW':
case 'SPECIAL_EMPTY':
case 'SPECIAL_MINIMIZE':
case 'SPECIAL_CLOSE':
case 'SPECIAL_AUTO_TILER_TOGGLE':
break;
case 'SPECIAL_AUTO_TILER_1':
case 'SPECIAL_AUTO_TILER_2':
case 'SPECIAL_AUTO_TILER_3':
if (!layoutRepeater.model[activeLayoutIndex].activeAutoTiler) {
break;
}
default:
let layout = layoutRepeater.model[activeLayoutIndex].tiles[activeTileIndex];
let hintWidth = layout.w == undefined ? layout.pxW : layout.w / 100 * clientArea.width;
let hintHeight = layout.h == undefined ? layout.pxH : layout.h / 100 * clientArea.height;
let hintX = (layout.x == undefined ? layout.pxX : layout.x / 100 * clientArea.width) - (layout.aX == undefined ? 0 : layout.aX * hintWidth / 100);
let hintY = (layout.y == undefined ? layout.pxY : layout.y / 100 * clientArea.height) - (layout.aY == undefined ? 0 : layout.aY * hintHeight / 100);
if (root.centerInTile) {
popupDropHintWidth = root.currentlyMovedWindow.width;
popupDropHintHeight = root.currentlyMovedWindow.height;
popupDropHintX = hintX + hintWidth / 2 - root.currentlyMovedWindow.width / 2;
popupDropHintY = hintY + hintHeight / 2 - root.currentlyMovedWindow.height / 2;
} else {
popupDropHintWidth = hintWidth;
popupDropHintHeight = hintHeight;
popupDropHintX = hintX;
popupDropHintY = hintY;
}
showPopupDropHint = root.config.showTargetTileHint;
hasValidPopupDropHint = true;
return; // Force return to avoid hiding popup
}
if (geometry != null) {
if (root.centerInTile) {
popupDropHintX = (geometry.x - clientArea.x) + geometry.width / 2 - root.currentlyMovedWindow.width / 2;
popupDropHintY = (geometry.y - clientArea.y) + geometry.height / 2 - root.currentlyMovedWindow.height / 2;
popupDropHintWidth = root.currentlyMovedWindow.width;
popupDropHintHeight = root.currentlyMovedWindow.height;
shouldShowPopupDropHint = true;
} else {
popupDropHintX = geometry.x - clientArea.x;
popupDropHintY = geometry.y - clientArea.y;
popupDropHintWidth = geometry.width;
popupDropHintHeight = geometry.height;
shouldShowPopupDropHint = true;
}
}
if (root.config.showTargetTileHint) {
showPopupDropHint = shouldShowPopupDropHint;
} else {
showPopupDropHint = false;
}
hasValidPopupDropHint = shouldShowPopupDropHint;
}
function updateHintContent() {
if (activeVirtualDesktopIndex >= 0) {
hasValidPopupDropHint = false;
showPopupDropHint = false;
if (root.virtualDesktops[activeVirtualDesktopIndex].isAdd) {
switch (config.virtualDesktopDropAction) {
case 0:
if (root.moveToVirtualDesktopOnDrop) {
// hint = '<b>Drop</b> - Switch to a new virtual desktop, and move window';
hint = '<b>Drop</b> - Move window to a new virtual desktop, and switch to it';
} else {
// hint = '<b>Drop</b> - Add new virtual desktop, and move window';
// hint = '<b>Drop</b> - Move window to a new virtual desktop without switching';
hint = '<b>Drop</b> - Move window to a new virtual desktop then go back to ' + root.virtualDesktopAtMoveStart.name;
}
break;
case 1:
if (root.moveToVirtualDesktopOnDrop) {
// hint = '<b>Drop</b> - Switch to a new virtual desktop, and maximize window';
hint = '<b>Drop</b> - Maximize window on a new virtual desktop, and switch to it';
} else {
// hint = '<b>Drop</b> - Add new virtual desktop, and maximize window';
// hint = '<b>Drop</b> - Maximize window on a new virtual desktop without switching';
hint = '<b>Drop</b> - Maximize window on a new virtual desktop then go back to ' + root.virtualDesktopAtMoveStart.name;
}
break;
}
if (root.config.hintMoveOnDrop) {
hint += ' (<b>' + root.config.shortcutMoveOnDrop + '</b>)';
}
} else {
let virtualDesktopName = root.virtualDesktops[activeVirtualDesktopIndex].desktop.name;
let virtualDesktopHoverChanged = activeVirtualDesktopIndex != root.virtualDesktopIndexAtMoveStart;
switch (config.virtualDesktopDropAction) {
case 0:
if (!virtualDesktopHoverChanged) {
hint = '<b>Drop</b> - Cancel move';
} else if (root.moveToVirtualDesktopOnDrop) {
// hint = '<b>Hover</b> - Switch to ' + virtualDesktopName + '<br><b>Drop</b> - Move window, and switch to ' + virtualDesktopName;
hint = '<b>Drop</b> - Move window to ' + virtualDesktopName + ', and switch to it';
} else {
// hint = '<b>Hover</b> - Switch to ' + virtualDesktopName + '<br><b>Drop</b> - Move window to ' + virtualDesktopName;
// hint = '<b>Hover</b> - Switch to ' + virtualDesktopName + '<br><b>Drop</b> - Move window to ' + virtualDesktopName + ' without switching';
hint = '<b>Drop</b> - Move window to ' + virtualDesktopName + ' then go back to ' + root.virtualDesktopAtMoveStart.name;
}
break;
case 1:
if (!virtualDesktopHoverChanged) {
hint = '<b>Drop</b> - Maximize window on current virtual desktop';
} else if (root.moveToVirtualDesktopOnDrop) {
// hint = '<b>Hover</b> - Switch to ' + virtualDesktopName + '<br><b>Drop</b> - Maximize window on, and switch to ' + virtualDesktopName;
hint = '<b>Drop</b> - Maximize window on ' + virtualDesktopName + ', and switch to it';
} else {
// hint = '<b>Hover</b> - Switch to ' + virtualDesktopName + '<br><b>Drop</b> - Maximize window on ' + virtualDesktopName;
// hint = '<b>Hover</b> - Switch to ' + virtualDesktopName + '<br><b>Drop</b> - Maximize window on ' + virtualDesktopName + ' without switching';
hint = '<b>Drop</b> - Maximize window on ' + virtualDesktopName + ' then go back to ' + root.virtualDesktopAtMoveStart.name;
}
break;
}
if (virtualDesktopHoverChanged && root.config.hintMoveOnDrop) {
hint += ' (<b>' + root.config.shortcutMoveOnDrop + '</b>)';
}
if (root.currentVirtualDesktopIndex != activeVirtualDesktopIndex) {
hint += '<br><b>Hover</b> - Switch to ' + virtualDesktopName;
}
}
} else if (!virtualDesktopVisibilityOverride && root.config.virtualDesktopVisibility == 1) {
hint = "Show tiler (<b>" + root.config.shortcutShowAllSpan + "</b>)";
hasValidPopupDropHint = false;
showPopupDropHint = false;
} else if (activeLayoutIndex >= 0 && activeTileIndex >= 0) {
updateAndShowPopupDropHint();
popupDropHintIsCenterInTile = root.centerInTile;
let special = layoutRepeater.model[activeLayoutIndex].special;
let tile = layoutRepeater.model[activeLayoutIndex].tiles[activeTileIndex];
if (root.centerInTile && hasValidPopupDropHint) {
hint = '<b>Center in tile</b> - will not resize the window' + (root.config.hintCenterInTile ? '<br>Toggle with <b>' + root.config.shortcutCenterInTile + '</b>' : '');
} else if (tile.hint) {
switch (special) {
case 'SPECIAL_KEEP_ABOVE':
hint = tile.hint + '<br>Currently <b>' + (root.currentlyMovedWindow.keepAbove ? 'Enabled' : 'Disabled') + '</b>';
break;
case 'SPECIAL_KEEP_BELOW':
hint = tile.hint + '<br>Currently <b>' + (root.currentlyMovedWindow.keepBelow ? 'Enabled' : 'Disabled') + '</b>';
break;
case 'SPECIAL_FILL':
hint = hasValidPopupDropHint ? tile.hint : '<font color="orange">⚠</font> No free space available';
break;
case 'SPECIAL_SPLIT_HORIZONTAL':
case 'SPECIAL_SPLIT_VERTICAL':
hint = hasValidPopupDropHint ? tile.hint : '<font color="orange">⚠</font> No window available for splitting';
break;
case 'SPECIAL_AUTO_TILER_TOGGLE':
hint = root.currentlyMovedWindow.mt_auto == undefined ? '<b>Enable</b> auto-tiling for this window' : '<b>Disable</b> auto-tiling for this window';
break;
default:
hint = tile.hint;
break;
}
} else if (layoutRepeater.model[activeLayoutIndex].activeAutoTiler == true && tile.t != '*') {
hint = 'Auto-tile in this tile<br>Cycle auto-tiled windows with Ctrl+Alt+Left or Right';
} else if (root.virtualDesktopChangedSinceMoveStart) {
if (root.moveToVirtualDesktopOnTile) {
hint = 'Tile window on current virtual dekstop';
} else {
hint = 'Tile window on ' + Workspace.currentDesktop.name + ' then go back to ' + root.virtualDesktopAtMoveStart.name;
}
if (root.config.hintMoveOnDrop) {
hint += ' (<b>' + root.config.shortcutMoveOnDrop + '</b>)';
}
} else if ((root.config.showSizeHint || root.config.showPositionHint) && !special) {
hint = '';
if (root.config.showPositionHint) {
if (root.config.showPositionHintInPixels) {
hint += 'X: <b>' + Math.round(popupDropHintX) + '</b> Y: <b>' + Math.round(popupDropHintY) + '</b>';
} else {
hint += 'X: <b>' + Math.round(100 * popupDropHintX / clientArea.width) + '</b>% Y: <b>' + Math.round(100 * popupDropHintY / clientArea.height) + '</b>%';
}
}
if (root.config.showSizeHint) {
if (hint.length > 0) {
hint += '<br>';
}
if (root.config.showSizeHintInPixels) {
hint += 'W: <b>' + Math.round(popupDropHintWidth) + '</b> H: <b>' + Math.round(popupDropHintHeight) + '</b>';
} else {
hint += 'W: <b>' + Math.round(100 * popupDropHintWidth / clientArea.width) + '</b>% H: <b>' + Math.round(100 * popupDropHintHeight / clientArea.height) + '</b>%';
}
}
} else {
hint = null;
}
} else {
hasValidPopupDropHint = false;
showPopupDropHint = false;
hint = null;
}
if (hint == null) {
let defaultHint = "";
let hasShortcutHint = false;
if (root.config.showHintHint) {
defaultHint += (defaultHint.length > 0 ? " " : "") + "Configure tiler visibility and these hints in settings.<br>";
}
if (root.config.hintShowAllSpan) {
defaultHint += (hasShortcutHint ? " - " : "") + (showAll ? "Show default (<b>" + root.config.shortcutShowAllSpan + "</b>)" : "Show all (<b>" + root.config.shortcutShowAllSpan + "</b>)");
hasShortcutHint = true;
}
if (root.config.hintVisibility) {
defaultHint += (hasShortcutHint ? " - " : "") + "Visibility (<b>" + root.config.shortcutVisibility + "</b>)";
hasShortcutHint = true;
}
if (root.config.hintChangeMode) {
defaultHint += (hasShortcutHint ? " - " : "") + "Mode (<b>" + root.config.shortcutChangeMode + "</b>)";
hasShortcutHint = true;
}
if (root.config.hintInputType) {
defaultHint += (hasShortcutHint ? " - " : "") + "Input type (<b>" + root.config.shortcutInputType + "</b>)";
hasShortcutHint = true;
}
if (root.config.hintCenterInTile) {
defaultHint += (hasShortcutHint ? " - " : "") + "Center in tile (<b>" + root.config.shortcutCenterInTile + "</b>)";
hasShortcutHint = true;
}
if (root.config.hintToggleTilingSuggestions) {
defaultHint += (hasShortcutHint ? " - " : "") + "Suggestions: <b>" + (settings.showTilingSuggestions ? "Enabled": "Disabled") + "</b> (<b>" + root.config.shortcutToggleTilingSuggestions + "</b>)";
hasShortcutHint = true;
}
if (defaultHint.length > 0) {
hint = defaultHint;
}
}
if (root.useAutoTilerPreview) {
if (root.validAutoTilerPreview) {
hint = 'Valid auto tile configuration - good job! :)';
} else {
hint = '<font color="orange">⚠</font> <b>WARNING</b> <font color="orange">⚠</font> INVALID AUTO TILE CONFIGURATION';
}
}
}
function updateRevealed(forceUpdate = false) {
var updatedRevealed;
if (revealBox == null) {
updatedRevealed = true;
} else {
let x = root.getCursorPosition().x;
let y = root.getCursorPosition().y;
updatedRevealed = revealBox.left <= x && revealBox.right >= x && revealBox.top <= y && revealBox.bottom >= y;
}
if (forceUpdate || updatedRevealed != revealed) {
revealed = updatedRevealed;
root.updateWindowVisibility();
}
}
Item {
id: mainItem
width: popupTiler.width
height: popupTiler.height
visible: revealed
SequentialAnimation {
id: showPopupTilerAnimation
running: false
NumberAnimation {
target: popupTiler;
property: "opacity";
from: 0;
to: 0;
duration: 32;
}
NumberAnimation {
target: popupTiler;
property: "opacity";
from: 1;
to: 1;
duration: 1;
}
onFinished: {
root.updateWindowVisibility();
}
}
Colors {
id: colors
}
Rectangle {
id: popupDropHint
anchors.left: parent.left
anchors.leftMargin: popupDropHintX
anchors.top: parent.top
anchors.topMargin: popupDropHintY
width: popupDropHintWidth
height: popupDropHintHeight
border.color: colors.tileBorderColor
border.width: 2
color: "transparent"
radius: 12
visible: showPopupDropHint
Rectangle {
anchors.fill: parent
color: colors.hintBackgroundColor
radius: 12
}
}
Rectangle {
id: layouts
width: layoutGrid.implicitWidth + layoutGrid.columnSpacing * 2
height: layoutGrid.implicitHeight + layoutGrid.rowSpacing * 2
color: root.config.gridTilerOpacity != 1 ? Qt.alpha(colors.backgroundColor, root.config.gridTilerOpacity) : colors.backgroundColor
border.color: colors.borderColor
border.width: 1
radius: 8
visible: virtualDesktopVisibilityOverride || root.config.virtualDesktopVisibility != 1
anchors.left: parent.left
anchors.leftMargin: positionX
anchors.top: parent.top
anchors.topMargin: positionY
GridLayout {
id: layoutGrid
columns: showAll ? root.config.gridAllColumns : root.config.gridColumns
columnSpacing: root.config.gridSpacing
rowSpacing: root.config.gridSpacing
anchors.fill: parent
anchors.margins: root.config.gridSpacing
uniformCellWidths: true
uniformCellHeights: true
Repeater {
id: layoutRepeater
model: showAll ? root.popupGridAllLayouts : root.popupGridLayouts
Rectangle {
id: tiles
width: root.config.gridWidth
height: root.config.gridHeight
color: "transparent"
border.color: modelData.activeAutoTiler ? colors.tileBorderColor : colors.borderColor
border.width: 1
radius: 8
clip: modelData.clip == true
property bool layoutActive: activeLayoutIndex == index
Repeater {
id: tileRepeater
model: modelData.tiles
Item {
id: tile
property bool tileActive: activeTileIndex == index
property bool tileDisabled: modelData.d ? true : false
width: (modelData.w == undefined ? modelData.pxW / clientArea.width : modelData.w / 100) * (tiles.width - borderOffset * 2)
height: (modelData.h == undefined ? modelData.pxH / clientArea.height : modelData.h / 100) * (tiles.height - borderOffset * 2)
x: (modelData.x == undefined ? modelData.pxX / clientArea.width : modelData.x / 100) * (tiles.width - borderOffset * 2) + borderOffset - (modelData.aX == undefined ? 0 : modelData.aX * width / 100)
y: (modelData.y == undefined ? modelData.pxY / clientArea.height : modelData.y / 100) * (tiles.height - borderOffset * 2) + borderOffset - (modelData.aY == undefined ? 0 : modelData.aY * height / 100)
Rectangle {
anchors.fill: parent
anchors.margins: tilePadding
border.color: colors.tileBorderColor
border.width: 1
// color: "#152030"
color: "transparent"
radius: 6
opacity: tileDisabled ? 0.3 : 1
Rectangle {
anchors.fill: parent
color: layoutActive && tileActive ? colors.tileBackgroundColorActive : colors.tileBackgroundColor
radius: 6
}
Text {
anchors.centerIn: parent
color: colors.textColor
textFormat: Text.StyledText
// ·
text: root.centerInTile && layoutActive && tileActive && hasValidPopupDropHint ? "⸰" : (modelData.t && modelData.t.length > 0 ? modelData.t : "")
font.pixelSize: 14
font.family: "Noto Sans"
horizontalAlignment: Text.AlignHCenter
visible: root.centerInTile && layoutActive && tileActive && hasValidPopupDropHint ? true : (modelData.t ? modelData.t : false)
}
}
}
}
}
}
}
}
// Rectangle {
// id: autoTilerScrollLeft
// width: 34 + layoutGrid.columnSpacing * 2
// height: 34 + layoutGrid.columnSpacing * 2
// color: root.config.gridTilerOpacity != 1 ? Qt.alpha(colors.backgroundColor, root.config.gridTilerOpacity) : colors.backgroundColor
// border.color: colors.borderColor
// border.width: 1
// radius: 8
// visible: layouts.visible
// anchors.verticalCenter: layouts.verticalCenter
// anchors.right: layouts.left
// anchors.rightMargin: 2
// Text {
// anchors.centerIn: parent
// color: colors.textColor
// textFormat: Text.StyledText
// text: ""
// font.pixelSize: 14
// font.family: "Noto Sans"
// horizontalAlignment: Text.AlignHCenter
// }
// }
// Rectangle {
// id: autoTilerScrollRight
// width: 34 + layoutGrid.columnSpacing * 2
// height: 34 + layoutGrid.columnSpacing * 2
// color: root.config.gridTilerOpacity != 1 ? Qt.alpha(colors.backgroundColor, root.config.gridTilerOpacity) : colors.backgroundColor
// border.color: colors.borderColor
// border.width: 1
// radius: 8
// visible: layouts.visible
// anchors.verticalCenter: layouts.verticalCenter
// anchors.left: layouts.right
// anchors.leftMargin: 2
// Text {
// anchors.centerIn: parent
// color: colors.textColor
// textFormat: Text.StyledText
// text: ""
// font.pixelSize: 14
// font.family: "Noto Sans"
// horizontalAlignment: Text.AlignHCenter
// }
// }
Rectangle {
id: virtualDesktop
width: layoutGrid.implicitWidth + layoutGrid.columnSpacing * 2
height: virtualDesktopLayout.implicitHeight + layoutGrid.rowSpacing * 2
color: root.config.gridTilerOpacity != 1 ? Qt.alpha(colors.backgroundColor, root.config.gridTilerOpacity) : colors.backgroundColor
border.color: colors.borderColor
border.width: 1
radius: 8
visible: root.virtualDesktopVisibile
anchors.left: parent.left
anchors.leftMargin: positionX
anchors.top: layouts.visible ? layouts.bottom : parent.top
anchors.topMargin: layouts.visible ? 2 : positionY
GridLayout {
id: virtualDesktopLayout
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
rowSpacing: 1
columnSpacing: 1
columns: layoutGrid.implicitWidth / 35
Repeater {
id: virtualDesktopRepeater
model: root.virtualDesktops
Item {
id: virtualDesktop
property bool currentVirtualDesktop: root.currentVirtualDesktopIndex == index
property bool activeVirtualDesktop: activeVirtualDesktopIndex == index
width: 34
height: 34
Rectangle {
anchors.fill: parent
anchors.margins: tilePadding
border.color: colors.tileBorderColor
border.width: 1
color: "transparent"
radius: 6
Rectangle {
anchors.fill: parent
color: activeVirtualDesktop ? colors.tileBackgroundColorActive : colors.tileBackgroundColor
radius: 6
}
Text {
anchors.centerIn: parent
color: colors.textColor
textFormat: Text.StyledText
text: modelData.isAdd ? "+" : "⸰"
font.pixelSize: 14
font.family: "Noto Sans"
horizontalAlignment: Text.AlignHCenter
visible: modelData.isAdd || currentVirtualDesktop
}
}
}
}
}
}
Rectangle {
id: popupHint
width: layoutGrid.implicitWidth + layoutGrid.columnSpacing * 2
height: popupHintText.implicitHeight + layoutGrid.rowSpacing * 2
color: root.config.gridTilerOpacity != 1 ? Qt.alpha(colors.backgroundColor, root.config.gridTilerOpacity) : colors.backgroundColor
border.color: colors.borderColor
border.width: 1
radius: 8
visible: root.config.showTextHint && hint != null
anchors.left: parent.left
anchors.leftMargin: positionX
anchors.top: virtualDesktop.visible ? virtualDesktop.bottom : layouts.bottom
anchors.topMargin: 2
Text {
id: popupHintText
width: parent.width - 4
anchors.centerIn: parent
color: colors.textColor
textFormat: Text.StyledText
text: hint != null ? hint : ""
font.pixelSize: 12
font.family: "Noto Sans"
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
}
}
Rectangle {
id: popupWindowCursor
anchors.left: parent.left
anchors.leftMargin: root.getCursorPosition().x - clientArea.x - 6
anchors.top: parent.top
anchors.topMargin: root.getCursorPosition().y - clientArea.y - 6
width: 12
height: 12
border.color: colors.tileBorderColor
border.width: 2
color: colors.tileBackgroundColorActive
radius: 6
visible: !root.useMouseCursor
Rectangle {
anchors.centerIn: parent
width: 2
height: 2
color: colors.textColor
radius: 1
opacity: 0.8
}
}
Timer {
interval: 1
repeat: false
running: popupTiler.visible && !sizeEstablished
onTriggered: {
sizeEstablished = true;
updateScreen(true);
}
}
Timer {
interval: root.config.popupGridPollingRate
repeat: true
running: popupTiler.visible
onTriggered: {
let forceUpdate = lastShowAll != showAll;
if (forceUpdate) {
if (showAll) {
lastShowAll = true;
} else {
lastShowAll = false;
}
}
updateScreen(forceUpdate);
updateRevealed();
let x = root.getCursorPosition().x;
let y = root.getCursorPosition().y;
let layoutIndex = -1;
let tileIndex = -1;
let virtualDesktopIndex = -1;
if (root.config.windowVisibility == 2) {
var updatedHovered;
let layoutsPosition = layouts.mapToGlobal(Qt.point(0, 0));
let visibleHeight = (layouts.visible ? layouts.height : 0) + (virtualDesktop.visible ? virtualDesktop.height : 0) + (layouts.visible && virtualDesktop.visible ? 2 : 0)
if (layoutsPosition.x <= x && layoutsPosition.x + layouts.width >= x && layoutsPosition.y <= y && layoutsPosition.y + visibleHeight >= y) {
updatedHovered = true;
} else {
updatedHovered = false;
}
if (updatedHovered != currentlyHovered) {
currentlyHovered = updatedHovered;
root.updateWindowVisibility();
}
}
if (layouts.visible) {
for (let i = 0; i < layoutRepeater.count; i++) {
let currentLayout = layoutRepeater.itemAt(i);
let currentLayoutPosition = currentLayout.mapToGlobal(Qt.point(0, 0));
if (currentLayoutPosition.x <= x && currentLayoutPosition.x + currentLayout.width >= x && currentLayoutPosition.y <= y && currentLayoutPosition.y + currentLayout.height >= y) {
layoutIndex = i;
// if (layoutRepeater.model[layoutIndex].special) {
// tileIndex = 0;
// } else {
//for (let j = 0; j < currentLayout.children.length; j++) {
for (let j = currentLayout.children.length - 1; j >= 0; j--) {
let currentTile = currentLayout.children[j];
// if (currentTile.tileDisabled) {
// continue;
// }
let currentTilePosition = currentTile.mapToGlobal(Qt.point(0, 0));
if (currentTilePosition.x <= x && currentTilePosition.x + currentTile.width >= x && currentTilePosition.y <= y && currentTilePosition.y + currentTile.height >= y) {
tileIndex = j;
break;
}
}
// }
break;
}
}
}
if (root.virtualDesktopVisibile && layoutIndex == -1) {
for (let i = 0; i < virtualDesktopRepeater.count; i++) {
let currentVirtualDesktop = virtualDesktopRepeater.itemAt(i);
let currentVirtualDesktopPosition = currentVirtualDesktop.mapToGlobal(Qt.point(0, 0));
if (currentVirtualDesktopPosition.x <= x && currentVirtualDesktopPosition.x + currentVirtualDesktop.width >= x && currentVirtualDesktopPosition.y <= y && currentVirtualDesktopPosition.y + currentVirtualDesktop.height >= y) {
virtualDesktopIndex = i;
}
}
}
let switchVirtualDesktop = virtualDesktopIndex != -1 && !root.virtualDesktops[virtualDesktopIndex].isAdd && virtualDesktopIndex != root.currentVirtualDesktopIndex;
if (activeVirtualDesktopIndex != virtualDesktopIndex) {
activeVirtualDesktopIndex = virtualDesktopIndex;
if (switchVirtualDesktop) {
if (config.virtualDesktopHoverTime == 0) {
autoTiler.virtualDesktopAboutToChange(); // Notify to remove window from current virtual desktop auto-tiler
Workspace.currentDesktop = root.virtualDesktops[activeVirtualDesktopIndex].desktop;
} else {
activeVirtualDesktopHoverTime = Date.now();
}
}
updateHintContent();
} else if (switchVirtualDesktop && config.virtualDesktopHoverTime > 0 && Date.now() - activeVirtualDesktopHoverTime > config.virtualDesktopHoverTime) {
autoTiler.virtualDesktopAboutToChange(); // Notify to remove window from current virtual desktop auto-tiler
Workspace.currentDesktop = root.virtualDesktops[activeVirtualDesktopIndex].desktop;
updateHintContent();
}
// if (layoutIndex == -1) {
// let leftScroll = autoTilerScrollLeft.mapToGlobal(Qt.point(0, 0));
// let rightScroll = autoTilerScrollRight.mapToGlobal(Qt.point(0, 0));
// if (leftScroll.x <= x && leftScroll.x + autoTilerScrollLeft.width >= x && leftScroll.y <= y && leftScroll.y + autoTilerScrollLeft.height >= y) {
// logDev('HOVERING LEFT!!!!');
// let timeNow = Date.now();
// if (scrollLeftTime == 0) {
// scrollLeftTime = timeNow + 1000;
// } else if (timeNow >= scrollLeftTime) {
// autoTiler.modifyPrimaryIndex(-1);
// scrollLeftTime = timeNow + 1000;
// }
// } else if (rightScroll.x <= x && rightScroll.x + autoTilerScrollRight.width >= x && rightScroll.y <= y && rightScroll.y + autoTilerScrollRight.height >= y) {
// logDev('HOVERING RIGHT!!!!');
// let timeNow = Date.now();
// if (scrollLeftTime == 0) {
// scrollLeftTime = timeNow + 1000;
// } else if (timeNow >= scrollLeftTime) {
// autoTiler.modifyPrimaryIndex(-1);
// scrollLeftTime = timeNow + 1000;
// }
// } else {
// scrollLeftTime = 0;
// scrollRightTime = 0;
// }
// } else {
// scrollLeftTime = 0;
// scrollRightTime = 0;
// }
// TODO: Add support for negative values (-)
// TODO: Span more than 1 layout if x < 0 || y < 0 || w > 100 || h > 100 to support SPECIAL_EMPTY
if (layoutIndex != activeLayoutIndex || tileIndex != activeTileIndex) {
activeLayoutIndex = layoutIndex;
activeTileIndex = tileIndex;
updateHintContent();
} else if (popupDropHintIsCenterInTile != root.centerInTile && activeLayoutIndex >= 0 && activeTileIndex >= 0) {
updateHintContent();
popupDropHintIsCenterInTile = root.centerInTile;
}
}
}
}
}
@@ -0,0 +1,517 @@
import QtQuick
import org.kde.kwin
import org.kde.plasma.core as PlasmaCore
import org.kde.kirigami as Kirigami
import org.kde.plasma.components as PlasmaComponents
// Window {
PlasmaCore.Dialog {
id: windowSuggestions
property var activeScreen: null
property var currentDesktop: null
property var currentActivity: null
property bool isDefault: true
property int layoutIndex: 0
property int tileIndex: 0
property var clientArea: ({width: 1, height: 1, x: 0, y: 0})
property var tilePadding: 2
property list<var> convertedOverlay: ([])
property int skipIndex: -1
property int activeIndex: -1
property int suggestionsVisibility: 0
property bool suggestionsInsideTile: true
property bool excludeOtherScreens: true
property bool excludeOtherDesktops: true
property bool excludeOtherActivities: true
property bool excludeAutoTiled: true
property bool excludeMinimized: true
property int sortMode: 2
property int colCount: 1
property int rowCount: 1
property int cellWidth: 0
property int previewHeight: 0
property real cellRatio: 0
property int cellHeight: 0
property int cellBottomMargin: 0
property int textSize: 14
property int totalWidth: 0
property int totalHeight: 0
property int offsetX: 0
property int offsetY: 0
property int gridSpacing: 20
property list<var> validWindows: ([])
width: clientArea.width
height: clientArea.height
x: clientArea.x
y: clientArea.y
flags: Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.WindowDoesNotAcceptFocus | Qt.BypassWindowManagerHint
// flags: Qt.Tool | Qt.BypassWindowManagerHint | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.WindowDoesNotAcceptFocus
// flags: Qt.Tool | Qt.BypassWindowManagerHint | Qt.FramelessWindowHint
// color: "transparent" // PlasmaCore.Dialog
visible: false
// outputOnly: true
backgroundHints: PlasmaCore.Types.NoBackground // PlasmaCore.Dialog
// type: PlasmaCore.Dialog.OnScreenDisplay
location: PlasmaCore.Types.Desktop // PlasmaCore.Dialog
function init() {
suggestionsVisibility = KWin.readConfig("suggestionsVisibility", 0);
suggestionsInsideTile = KWin.readConfig("suggestionsMode", 0) == 0;
excludeOtherScreens = !KWin.readConfig("suggestOtherScreens", false);
excludeOtherDesktops = !KWin.readConfig("suggestOtherDesktops", false);
excludeOtherActivities = !KWin.readConfig("suggestOtherActivities", false);
excludeAutoTiled = !KWin.readConfig("suggestAutoTiled", false);
excludeMinimized = !KWin.readConfig("suggestMinimized", false);
}
function updateValid(addedWindow) {
let valid = [];
for (let i = 0; i < Workspace.stackingOrder.length; i++) {
let window = Workspace.stackingOrder[i];
if (window == addedWindow) continue;
if (!root.isValidWindow(window)) continue;
if (window.fullScreen) continue;
// if (window.maximizeMode > 0) continue;
if (excludeAutoTiled && window.mt_auto) continue;
if (excludeMinimized && window.minimized) continue;
if (excludeOtherScreens && window.output != activeScreen) continue;
if (excludeOtherDesktops && window.desktops.length > 0 && !window.desktops.includes(currentDesktop)) continue;
if (excludeOtherActivities && window.activities.length > 0 && !window.activities.includes(currentActivity)) continue;
valid.push(window);
}
switch (sortMode) {
case 0: // newest focused first
valid.reverse();
break;
case 1: // oldest focused first
break;
case 2: // a-z
valid.sort((a, b) => a.caption.toLowerCase().localeCompare(b.caption.toLowerCase()));
break;
}
validWindows = valid;
}
function updateSizes() {
let count = validWindows.length;
log('Valid count: ' + count);
let usableHeight = suggestionsInsideTile ? convertedOverlay[activeIndex].height : clientArea.height - 180;
let usableWidth = suggestionsInsideTile ? convertedOverlay[activeIndex].width : clientArea.width / 2;
let wantedRatio = 16 / 9;
let ratio = usableWidth / usableHeight;
if (!suggestionsInsideTile) {
if (ratio < wantedRatio) {
usableWidth = parseInt(usableWidth);
usableHeight = parseInt(usableWidth / wantedRatio);
} else {
usableHeight = parseInt(usableHeight);
usableWidth = parseInt(usableHeight * wantedRatio);
}
}
colCount = Math.ceil(Math.sqrt(count));
rowCount = Math.ceil(count / colCount);
cellWidth = (usableWidth - gridSpacing * (colCount + 1)) / Math.max(colCount, 2);
log('CellWidth before: ' + cellWidth);
if (suggestionsInsideTile) {
if (cellWidth < 320) {
colCount = Math.min(count, Math.floor(convertedOverlay[activeIndex].width / 320));
} else {
colCount = Math.min(count, Math.floor(convertedOverlay[activeIndex].width / cellWidth));
}
cellWidth = (convertedOverlay[activeIndex].width - gridSpacing * (colCount + 1)) / Math.max(colCount, 2);
}
log('ColCount: ' + colCount + ' width: ' + cellWidth + ' gridSpacing: ' + gridSpacing + ' width: ' + convertedOverlay[activeIndex].width + ' usable: ' + usableWidth);
cellBottomMargin = Kirigami.Units.iconSizes.large / 4;
previewHeight = (usableHeight - gridSpacing * (rowCount + 1)) / Math.max(colCount, 2);
if (suggestionsInsideTile && previewHeight < 200) {
rowCount = Math.ceil(count / colCount);
previewHeight = 200;
}
cellHeight = previewHeight - cellBottomMargin - textSize;
totalWidth = suggestionsInsideTile ? convertedOverlay[activeIndex].width : cellWidth * Math.max(colCount, 2) + gridSpacing * (colCount + 1);
totalHeight = suggestionsInsideTile ? convertedOverlay[activeIndex].height : previewHeight * rowCount + gridSpacing * (rowCount + 1);
cellRatio = cellWidth / previewHeight;
log('Count: ' + count + ' rowCount: ' + rowCount + ' colCount: ' + colCount + ' width: ' + usableWidth + ' height: ' + usableHeight + ' ratio: ' + ratio + ' wanted ratio: ' + wantedRatio + ' cell width: ' + cellWidth + ' cell height: ' + cellHeight + ' totalHeight: ' + totalHeight);
}
function showSuggestions(window, screen, virtualDesktop, activity, isDefault, layoutIndex, tileIndex) {
log('screen: ' + screen.name + ' virtualDesktop: ' + virtualDesktop.id + ' activity: ' + activity + ' isDefault: ' + isDefault + ' layoutIndex: ' + layoutIndex + ' tileIndex: ' + tileIndex);
activeScreen = screen;
currentDesktop = virtualDesktop;
currentActivity = activity;
windowSuggestions.isDefault = isDefault;
windowSuggestions.layoutIndex = layoutIndex;
windowSuggestions.tileIndex = tileIndex;
clientArea = Workspace.clientArea(KWin.FullScreenArea, activeScreen, currentDesktop);
activeIndex = 0;
skipIndex = tileIndex;
convertLayoutToScreen();
updateValid(window);
if (validWindows.length > 0 && convertedOverlay.length > 0) {
updateSizes();
updateOffsets();
windowSuggestions.visible = true;
}
}
function convertLayoutToScreen() {
let layout = isDefault ? popupGridLayouts[layoutIndex].tiles : popupGridAllLayouts[layoutIndex].tiles;
log('Window suggestions layout to convert: ' + JSON.stringify(layout));
let converted = [];
for (let i = 0; i < layout.length; i++) {
if (i == skipIndex) continue;
let tile = layout[i];
let width = (tile.pxW == undefined ? tile.w / 100 * windowSuggestions.width : tile.pxW);
let height = (tile.pxH == undefined ? tile.h / 100 * windowSuggestions.height : tile.pxH);
// Temporarily add clientArea x/y since it is needed for calculating margins
let geometry = {
x: clientArea.x + ((tile.pxX == undefined ? tile.x / 100 * windowSuggestions.width : tile.pxX) - (tile.aX == undefined ? 0 : tile.aX * width / 100)),
y: clientArea.y + ((tile.pxY == undefined ? tile.y / 100 * windowSuggestions.height : tile.pxY) - (tile.aY == undefined ? 0 : tile.aY * height / 100)),
width: width,
height: height,
tileIndex: i
};
root.addMargins(geometry, true, true, true, true);
geometry.x -= clientArea.x;
geometry.y -= clientArea.y;
converted.push(geometry);
}
windowSuggestions.convertedOverlay = converted;
}
function updateOffsets() {
if (activeIndex < convertedOverlay.length) {
if (suggestionsInsideTile) {
offsetX = convertedOverlay[activeIndex].x;
offsetY = convertedOverlay[activeIndex].y;
} else {
let overlay = convertedOverlay[activeIndex];
let rightSpace = clientArea.width - overlay.x - overlay.width;
if (overlay.x < rightSpace) {
offsetX = Math.min(clientArea.width - rightSpace + 2, clientArea.width - totalWidth - 2);
} else {
offsetX = Math.max(overlay.x - totalWidth - 2, 2);
}
offsetY = overlay.y - ((totalHeight + 80 - overlay.height) / 2);
if (offsetY < 0) {
offsetY = 0;
} else if (offsetY + totalHeight + 80 > clientArea.height) {
offsetY = clientArea.height - totalHeight - 80;
}
}
}
}
function selectSuggestion(index, tileIndex) {
log('Select suggestion: ' + index + ' ' + tileIndex);
activeIndex = index;
if (suggestionsInsideTile) {
updateSizes();
}
updateOffsets();
}
function addWindowToSelected(window) {
log('Selected window caption: ' + window.caption);
if (activeIndex != -1 && validWindows.length > 0) {
let localWindows = [...validWindows];
let overlays = JSON.parse(JSON.stringify(windowSuggestions.convertedOverlay)); // Workaround for a strange bug where wrong item is fetched even if activeIndex is correct
let geometry = overlays.splice(activeIndex, 1)[0];
windowSuggestions.convertedOverlay = [...overlays];
geometry.x += clientArea.x;
geometry.y += clientArea.y;
let index = localWindows.indexOf(window);
if (index != -1) {
localWindows.splice(index, 1);
if (!window.desktops.includes(currentDesktop)) {
window.desktops = [currentDesktop];
}
if (!window.activities.includes(currentActivity)) {
window.activities = [currentActivity];
}
if (window.mt_auto) {
autoTiler.disableAutoTiling(window);
}
if (window.minimized) {
window.minimized = false;
}
if (window.maximizeMode > 0) {
window.setMaximize(false, false);
}
if (!window.mt_originalSize) {
window.mt_originalSize = {x: window.x, y: window.y, width: window.width, height: window.height};
}
root.moveAndResizeWindow(window, geometry);
Workspace.raiseWindow(window);
activeIndex = 0;
if (localWindows.length == 0 || convertedOverlay.length == 0) {
windowSuggestions.visible = false;
validWindows = [];
} else {
validWindows = localWindows;
if (suggestionsInsideTile) {
updateSizes();
}
updateOffsets();
}
}
}
}
function windowClosed(window) {
log('Closed window caption: ' + window.caption);
if (activeIndex != -1 && validWindows.length > 0) {
let localWindows = [...validWindows];
let index = localWindows.indexOf(window);
if (index != -1) {
localWindows.splice(index, 1);
if (localWindows.length == 0) {
validWindows = [];
} else {
validWindows = localWindows;
if (suggestionsInsideTile) {
updateSizes();
}
}
}
}
}
Item {
id: mainItem
width: windowSuggestions.width
height: windowSuggestions.height
TapHandler {
onTapped: windowSuggestions.visible = false;
}
Colors {
id: colors
}
Repeater {
id: tileRepeater
model: windowSuggestions.convertedOverlay
Item {
id: tile
property bool active: activeIndex == index
property int tileIndex: modelData.tileIndex
x: modelData.x
y: modelData.y
width: modelData.width
height: modelData.height
Rectangle {
anchors.fill: parent
anchors.margins: tilePadding
border.color: colors.tileBorderColor
border.width: 2
color: active ? colors.tileBackgroundColorActive : colors.tileBackgroundColorIntense
radius: 12
Text {
anchors.centerIn: parent
color: colors.overlayTextColor
textFormat: Text.StyledText
text: "<b>Select a window to tile here</b>"
font.pixelSize: 16
font.family: "Noto Sans"
horizontalAlignment: Text.AlignHCenter
visible: active && !suggestionsInsideTile
}
TapHandler {
gesturePolicy: TapHandler.ReleaseWithinBounds
onTapped: selectSuggestion(index, tileIndex);
}
}
}
}
Item {
height: suggestionsInsideTile ? totalHeight : totalHeight + 80
width: totalWidth
anchors.top: parent.top
// anchors.topMargin: (clientArea.height - height) / 2
anchors.topMargin: offsetY
anchors.left: parent.left
anchors.leftMargin: offsetX
Rectangle {
id: cellWrapper
width: totalWidth
height: suggestionsInsideTile ? totalHeight : totalHeight + 80
color: suggestionsInsideTile ? "transparent" : "#BB000000"
border.width: 1
border.color: suggestionsInsideTile ? "transparent" : "#80FFFFFF"
radius: 6
GridView {
id: listView
model: validWindows
width: colCount * (windowSuggestions.cellWidth + gridSpacing)
// height: rowCount * (previewHeight + gridSpacing)
height: suggestionsInsideTile ? Math.min((previewHeight + gridSpacing) * rowCount, totalHeight - 80 - gridSpacing * 2) : totalHeight - 80 - gridSpacing * 2
cellWidth: windowSuggestions.cellWidth + gridSpacing
cellHeight: previewHeight + gridSpacing
interactive: suggestionsInsideTile ? true : false
clip: suggestionsInsideTile
anchors.top: parent.top
anchors.topMargin: suggestionsInsideTile ? (cellWrapper.height - height) / 2 : 40 + gridSpacing
anchors.left: parent.left
anchors.leftMargin: suggestionsInsideTile ? (cellWrapper.width + gridSpacing - width) / 2 : gridSpacing
delegate: Rectangle {
width: windowSuggestions.cellWidth
height: previewHeight
color: "transparent"
property real previewRatio: modelData.width / modelData.height
property int previewActualWidth: previewRatio > cellRatio ? windowSuggestions.cellWidth : windowSuggestions.cellHeight * previewRatio
property int previewActualHeight: previewRatio > cellRatio ? windowSuggestions.cellWidth / previewRatio : windowSuggestions.cellHeight
WindowThumbnail {
id: preview
client: modelData
width: previewActualWidth
height: previewActualHeight
anchors.centerIn: parent
anchors.verticalCenterOffset: -((textSize + cellBottomMargin) / 2)
TapHandler {
gesturePolicy: TapHandler.ReleaseWithinBounds
onTapped: addWindowToSelected(modelData);
}
Kirigami.Icon {
id: icon
width: Kirigami.Units.iconSizes.large
height: Kirigami.Units.iconSizes.large
source: modelData.icon
anchors.horizontalCenter: preview.horizontalCenter
anchors.verticalCenter: preview.bottom
anchors.verticalCenterOffset: -Math.round(height / 4)
PlasmaComponents.Label {
id: text
width: windowSuggestions.cellWidth
height: textSize
text: modelData.caption
anchors.top: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
color: "white"
background: Rectangle {
anchors.centerIn: parent
anchors.verticalCenterOffset: 3
height: parent.height + 4
width: parent.contentWidth + 6
color: "#66000000"
radius: 3
}
}
}
}
}
}
}
Rectangle {
anchors.top: parent.top
anchors.topMargin: suggestionsInsideTile ? 4 : 1
anchors.left: parent.left
anchors.leftMargin: suggestionsInsideTile ? 4 : 1
width: suggestionsInsideTile ? totalWidth - 8 : totalWidth - 2
height: 40;
color: "#EE333333"
radius: suggestionsInsideTile ? 12 : 6
Text {
anchors.centerIn: parent
color: "white"
textFormat: Text.StyledText
text: suggestionsInsideTile ? "Select a window to place here" : "Select a window to place in the highlighted tile"
font.pixelSize: 16
font.family: "Noto Sans"
horizontalAlignment: Text.AlignHCenter
}
Text {
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
color: "white"
textFormat: Text.StyledText
text: "✖"
font.pixelSize: 16
font.family: "Noto Sans"
horizontalAlignment: Text.AlignHCenter
}
TapHandler {
gesturePolicy: TapHandler.ReleaseWithinBounds
onTapped: windowSuggestions.visible = false;
}
}
Rectangle {
anchors.bottom: parent.bottom
anchors.bottomMargin: suggestionsInsideTile ? 4 : 1
anchors.left: parent.left
anchors.leftMargin: suggestionsInsideTile ? 4 : 1
width: suggestionsInsideTile ? totalWidth - 8 : totalWidth - 2
height: 40;
color: "#EE333333"
radius: suggestionsInsideTile ? 12 : 6
Text {
anchors.centerIn: parent
color: "white"
textFormat: Text.StyledText
text: "Click a tile to switch to it. Click anywhere else to close suggestions. Disable suggestions (<b>" + config.shortcutToggleTilingSuggestions + "</b>)"
font.pixelSize: 16
font.family: "Noto Sans"
horizontalAlignment: Text.AlignHCenter
}
TapHandler {
gesturePolicy: TapHandler.ReleaseWithinBounds
onTapped: windowSuggestions.visible = false;
}
}
}
}
}
@@ -0,0 +1,20 @@
{
"KPackageStructure": "KWin/Script",
"KPlugin": {
"Authors": [
{
"Name": "rxappdev"
}
],
"Name": "Mouse Tiler",
"Description": "Tile your windows with minimal effort",
"Icon": "preferences-desktop-virtual",
"Id": "mousetiler",
"Version": "6.4.0",
"License": "GPLv3",
"Website": "https://github.com/rxappdev/MouseTiler"
},
"X-Plasma-API": "declarativescript",
"X-Plasma-MainScript": "ui/main.qml",
"X-KDE-ConfigModule": "kwin/effects/configs/kcm_kwin4_genericscripted"
}
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
@@ -0,0 +1,27 @@
// Copyright (C) 2018 Konstantinos Smanis <konstantinos.smanis@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
registerShortcut("MoveWindowToCenter", "Move Window to Center", "Meta+C", function () {
var client = workspace.activeClient;
if (client.moveable) {
var maxArea = workspace.clientArea(KWin.MaximizeArea, client);
client.geometry = {
x: maxArea.x + (maxArea.width - client.width) / 2,
y: maxArea.y + (maxArea.height - client.height) / 2,
width: client.width,
height: client.height
};
}
});
@@ -0,0 +1,18 @@
[Desktop Entry]
Name=Move Window to Center
Comment=Centers the active window in the current screen.
Icon=preferences-system-windows-move
X-Plasma-API=javascript
X-Plasma-MainScript=code/main.js
X-KDE-PluginInfo-Author=Konstantinos Smanis
X-KDE-PluginInfo-Email=konstantinos.smanis@gmail.com
X-KDE-PluginInfo-Name=movewindowtocenter
X-KDE-PluginInfo-Version=1.1
X-KDE-PluginInfo-EnabledByDefault=true
X-KDE-PluginInfo-Depends=
X-KDE-PluginInfo-License=GPL
X-KDE-ServiceTypes=KWin/Script
Type=Service
@@ -0,0 +1,82 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2014 Thomas Lübking <thomas.luebking@gmail.com>
KDE5 revisions 2016 Bob Farmer <kde@bfarmer.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
var snapInnerX = readConfig("InnerX", 10);
var snapOuterX= readConfig("OuterX", 10);
var snapInnerY = readConfig("InnerY", 10);
var snapOuterY= readConfig("OuterY", 10);
// ---------------------------------------------------------
var clientStepUserMovedResized = function(client, rect) {
if (rect == undefined)
rect = client.geometry;
else if (!client.move)
return;
var area = workspace.clientArea(KWin.WorkArea, workspace.activeScreen, workspace.currentDesktop);
var adjust = false;
var diff = rect.x - area.x;
if (diff < snapInnerX && diff > -snapOuterX) {
adjust = true;
var width = rect.width;
rect.x = area.x;
rect.width = width;
} else {
diff = rect.x + rect.width - (area.x + area.width);
if (diff > -snapInnerX && diff < snapOuterX) {
adjust = true;
var width = rect.width;
rect.x = area.x + area.width - width;
rect.width = width;
}
}
diff = rect.y - area.y;
if (diff < snapInnerY && diff > -snapOuterY) {
adjust = true;
var height = rect.height;
rect.y = area.y;
rect.height = height;
} else {
diff = rect.y + rect.height - (area.y + area.height);
if (diff > -snapInnerY && diff < snapOuterY) {
adjust = true;
var height = rect.height;
rect.y = area.y + area.height - height;
rect.height = height;
}
}
if (adjust)
client.geometry = rect;
}
var addClient = function(client) {
client.clientStepUserMovedResized.connect(clientStepUserMovedResized);
client.clientFinishUserMovedResized.connect(clientStepUserMovedResized);
clientStepUserMovedResized(client);
}
options.borderSnapZone = 0;
workspace.clientAdded.connect(addClient);
var clients = workspace.clientList();
for (var i = 0; i < clients.length; ++i) {
addClient(clients[i])
}
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
<kcfgfile name=""/>
<group name="">
<entry name="InnerX" type="int">
<default>10</default>
</entry>
<entry name="OuterX" type="int">
<default>10</default>
</entry>
<entry name="InnerY" type="int">
<default>10</default>
</entry>
<entry name="OuterY" type="int">
<default>10</default>
</entry>
</group>
</kcfg>
@@ -0,0 +1,181 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>KWin::ScreenSnappingConfigForm</class>
<widget class="QWidget" name="KWin::ScreenSnappingConfigForm">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>362</width>
<height>190</height>
</rect>
</property>
<property name="windowTitle">
<string>Screen Snapping</string>
</property>
<layout class="QGridLayout" name="gridLayout" columnstretch="3,1">
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Horizontal Snapping</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Inside</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="kcfg_InnerX">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="suffix">
<string>px</string>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Outside</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="kcfg_OuterX">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="suffix">
<string>px</string>
</property>
<property name="maximum">
<number>5000</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Vertical Snapping</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Inside</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="kcfg_InnerY">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="suffix">
<string>px</string>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>Outside</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="kcfg_OuterY">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="suffix">
<string>px</string>
</property>
<property name="maximum">
<number>5000</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1" rowspan="2">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>42</width>
<height>124</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,21 @@
[Desktop Entry]
Name=Screen Snapping 5
Comment=Snap to deco instead of window content
Icon=preferences-system-windows-script-test
Type=Service
X-Plasma-API=javascript
X-Plasma-MainScript=code/main.js
X-KDE-PluginInfo-Author=Bob Farmer
X-KDE-PluginInfo-Email=kde@bfarmer.net
X-KDE-PluginInfo-Name=screenSnapping5
X-KDE-PluginInfo-Version=1.0
X-KDE-PluginInfo-Depends=
X-KDE-PluginInfo-License=GPL
X-KDE-ServiceTypes=KWin/Script,KCModule
X-KDE-Library=kwin/effects/configs/kcm_kwin4_genericscripted
X-KDE-PluginKeyword=screenSnapping5
X-KDE-ParentComponents=screenSnapping5
@@ -0,0 +1,170 @@
import QtQuick 2.0
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
import org.kde.kquickcontrolsaddons 2.0
import org.kde.kwin 2.0 as KWin
// Reused code from the Grid Thumbnail switcher.
// https://techbase.kde.org/Development/Tutorials/KWin/WindowSwitcher
KWin.Switcher {
id: tabBox
currentIndex: thumbnailListView.currentIndex
PlasmaCore.Dialog {
id: dialog
location: PlasmaCore.Types.Floating
visible: tabBox.visible
flags: Qt.X11BypassWindowManagerHint
x: tabBox.screenGeometry.x + tabBox.screenGeometry.width * 0.5 - dialogMainItem.width * 0.5
y: tabBox.screenGeometry.y + tabBox.screenGeometry.height * 0.5 - dialogMainItem.height * 0.5
mainItem: Item {
id: dialogMainItem
property int maxWidth: tabBox.screenGeometry.width * 0.75
property int maxHeight: tabBox.screenGeometry.height * 0.9
property real screenFactor: tabBox.screenGeometry.width / tabBox.screenGeometry.height
property int listRows: thumbnailListView.count
width: thumbnailListView.width
height: thumbnailListView.height
clip: true
// just to get the margin sizes
PlasmaCore.FrameSvgItem {
id: hoverItem
imagePath: "widgets/viewitem"
prefix: "hover"
visible: false
}
ListView {
id: thumbnailListView
model: tabBox.model
orientation: ListView.Vertical
anchors.fill: parent
property int captionRowHeight: 22
property int thumbnailWidth: dialogMainItem.maxWidth
property int thumbnailHeight: thumbnailWidth * (1.0/dialogMainItem.screenFactor)
width: hoverItem.margins.left + thumbnailWidth + hoverItem.margins.right
height: hoverItem.margins.top + captionRowHeight + thumbnailHeight + hoverItem.margins.bottom
delegate: Item {
width: thumbnailListView.width
height: thumbnailListView.height
MouseArea {
anchors.fill: parent
onClicked: {
parent.select_relative(0)
}
onWheel: {
if (wheel.angleDelta.x < 0) {
parent.select_relative(1)
} else {
parent.select_relative(-1)
}
}
}
function select_relative(x) {
thumbnailListView.currentIndex = (index + thumbnailListView.count + x) % thumbnailListView.count;
thumbnailListView.currentIndexChanged(thumbnailListView.currentIndex);
}
Item {
z: 0
anchors.fill: parent
anchors.leftMargin: hoverItem.margins.left
anchors.topMargin: hoverItem.margins.top
anchors.rightMargin: hoverItem.margins.right
anchors.bottomMargin: hoverItem.margins.bottom
RowLayout {
id: captionRow
anchors.top: parent.top
anchors.right: parent.right
height: thumbnailListView.captionRowHeight
spacing: 4
QIconItem {
id: iconItem
icon: model.icon
width: parent.height
height: parent.height
state: index == thumbnailListView.currentIndex ? QIconItem.ActiveState : QIconItem.DefaultState
}
PlasmaComponents.Label {
text: model.caption
height: parent.height
Layout.fillWidth: true
elide: Text.ElideRight
}
PlasmaComponents.Label {
text: model.desktopName
color: "green"
height: parent.height
Layout.fillWidth: true
elide: Text.ElideRight
}
PlasmaComponents.ToolButton {
visible: model.closeable && typeof tabBox.model.close !== 'undefined' || false
iconSource: 'window-close-symbolic'
onClicked: {
console.log(tabBox.model, index, model.closeable, tabBox.model.close)
tabBox.model.close(index)
}
}
}
// Cannot draw icon on top of thumbnail.
KWin.ThumbnailItem {
wId: windowId
// clip: true
// clipTo: thumbnailListView
clip: true
clipTo: parent
anchors.fill: parent
anchors.topMargin: captionRow.height
}
}
} // GridView.delegate
highlight: PlasmaCore.FrameSvgItem {
id: highlightItem
imagePath: "widgets/viewitem"
prefix: "hover"
width: thumbnailListView.width
height: thumbnailListView.height
}
highlightMoveDuration: 200
// property int selectedIndex: -1
Connections {
target: tabBox
onCurrentIndexChanged: {
thumbnailListView.currentIndex = tabBox.currentIndex
}
}
// keyNavigationEnabled: true // Requires: Qt 5.7 and QtQuick 2.? (2.7 didn't work).
// keyNavigationWraps: true // Requires: Qt 5.7 and QtQuick 2.? (2.7 didn't work).
} // GridView
// This doesn't work, nor does keyboard input work on any other tabbox skin (KDE 5.7.4)
// It does work in the preview however.
} // Dialog.mainItem
} // Dialog
}
@@ -0,0 +1,16 @@
[Desktop Entry]
Name=Preview Reel
Comment=
Type=Service
X-KDE-ServiceTypes=KWin/WindowSwitcher
X-Plasma-API=declarativeappletscript
X-Plasma-MainScript=ui/main.qml
Icon=preferences-system-windows-switcher-big-icons
X-KDE-PluginInfo-Author=Mohamed Helmy
X-KDE-PluginInfo-Email=helmym40@gmail.com
X-KDE-PluginInfo-Name=preview_reel
X-KDE-PluginInfo-Version=0.1
X-KDE-PluginInfo-Depends=