Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Combined KDE 5 and KDE 6 compatibility #8

Open
wants to merge 26 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6d8487e
Start splitting for KDE 5/6
RedBearAK Nov 20, 2023
279f4d7
Merge grouping fix branch changes
RedBearAK Nov 20, 2023
416c5ea
Merge code for KDE 5 and 6 together
RedBearAK Nov 20, 2023
986bab4
Supress output from command checks
RedBearAK Nov 20, 2023
7f8fc8e
Suppress install error before trying upgrade
RedBearAK Nov 20, 2023
fb16bf3
Suppress strange output from reconfigure command
RedBearAK Nov 20, 2023
1f6eb31
Fix for getting desktops on KDE 6
RedBearAK Nov 22, 2023
0183050
Enhance app group debugging output
RedBearAK Nov 22, 2023
27b3c84
Rework install/uninstall scripts
RedBearAK Nov 23, 2023
d3cb807
Version bump in READMEs
RedBearAK Nov 23, 2023
c2fde48
Remove some unneeded files
RedBearAK Nov 23, 2023
067f28d
Add .gitignore file
RedBearAK Nov 23, 2023
043fa3c
Remove unwanted VSCode file
RedBearAK Nov 23, 2023
e55d0ab
Improve code consistency.
RedBearAK Dec 21, 2023
189d582
Add v1.7 kwinscript (zip) file
RedBearAK May 9, 2024
554e3d2
Create kwinscript file like previous version
RedBearAK May 11, 2024
38bd1be
Use correct remove syntax in uninstall script
RedBearAK May 12, 2024
56807b3
Less redundant log messages
RedBearAK May 12, 2024
973a4de
Update KDE version error log message
RedBearAK May 13, 2024
1184593
Unload scripts if possible before removing
RedBearAK May 13, 2024
f3a0c7f
Update D-Bus commands
RedBearAK May 13, 2024
1ed97ac
Parse output from unloadScript D-Bus commands
RedBearAK May 13, 2024
32bb239
Also handle 'false' case from unloadScript
RedBearAK May 13, 2024
7ce44e2
Validate script_name variable parsed from metadata
RedBearAK May 13, 2024
ab12191
Update v1.7 kwinscript file
RedBearAK May 13, 2024
056d393
Prioritize using 'gdbus' over 'qdbus'
RedBearAK Jul 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.vscode
.vscode/settings.json
2 changes: 1 addition & 1 deletion README.bbcode
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Automatically raises all other visible windows of the same application together when activating one of them, effectively creating application groups to task-switch between.

[b]Please make sure to install the most recent version (v1.6) and to not use Discover for installation.[/b] For more information on installation, setup and usage as well as any requests, please visit [url=https://github.com/nclarius/kwin-application-switcher]the GitHub page[/url].
[b]Please make sure to install the most recent version (v1.7) and to not use Discover for installation.[/b] For more information on installation, setup and usage as well as any requests, please visit [url=https://github.com/nclarius/kwin-application-switcher]the GitHub page[/url].

This extension gives rise to an application-centric task switching workflow as known from environments such as GNOME or MacOS, where an application’s windows are treated as a group, and task switching can take place at two levels: one mode for switching applications and one mode for switching between windows of an application.

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Seen in the screencast: Switching from Konsole back to Dolphin also brings the o

### Installation via graphical interface

**Please make sure to select the most recent version (v1.6)** in the installation process.
**Please make sure to select the most recent version (v1.7)** in the installation process.

A [bug](https://bugs.kde.org/show_bug.cgi?id=453521) in Discover causes a wrong version to be installed, so using the installation module in System Settings instead is recommended.

Expand Down
Binary file not shown.
85 changes: 71 additions & 14 deletions contents/code/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,41 @@ function debug(...args) {
}
debug("initializing");

// Detect KDE version
const isKDE6 = typeof workspace.windowList === 'function';

function isAppOnCurrentDesktopKDE6(window) {
return window &&
(window.desktops && window.desktops.includes(workspace.currentDesktop)) ||
(window.desktops && window.desktops.length === 0);
}

function isAppOnCurrentDesktopKDE5(window) {
return window &&
(window.x11DesktopIds && window.x11DesktopIds.includes(workspace.currentDesktop)) ||
(window.x11DesktopIds && window.x11DesktopIds.length === 0);
}

let activeWindow;
let windowList;
let connectWindowActivated;
let setActiveWindow;
let isAppOnCurrentDesktop;

// Set up aliases to abstract away the API differences between KDE 5 and KDE 6
if (isKDE6) {
activeWindow = () => workspace.activeWindow;
windowList = () => workspace.windowList();
connectWindowActivated = (handler) => workspace.windowActivated.connect(handler);
setActiveWindow = (window) => { workspace.activeWindow = window; };
isAppOnCurrentDesktop = isAppOnCurrentDesktopKDE6
} else {
activeWindow = () => workspace.activeClient;
windowList = () => workspace.clientList();
connectWindowActivated = (handler) => workspace.clientActivated.connect(handler);
setActiveWindow = (window) => { workspace.activeClient = window; };
isAppOnCurrentDesktop = isAppOnCurrentDesktopKDE5
}

///////////////////////
// special applications to ignore
Expand All @@ -33,7 +68,7 @@ const ignoredApps = ["plasmashell", "org.kde.plasmashell", // desktop shell

// "dolphin"
function getApp(current) {
if (!current) return "";
if (!current || typeof current.resourceClass !== 'string') return "";
return String(current.resourceClass);
}

Expand All @@ -46,7 +81,7 @@ function getApp(current) {
var prevActiveApp = ""

// set previously active application for initially active window
setPrevActiveApp(workspace.activeClient);
setPrevActiveApp(activeWindow())

// set previously active application for recently activated window
function setPrevActiveApp(current) {
Expand All @@ -68,7 +103,7 @@ function getPrevActiveApp() {
var appGroups = {};

// compute app groups for initially present windows
workspace.clientList().forEach(window => updateAppGroups(window));
windowList().forEach(window => updateAppGroups(window));

// update app groups with given window
function updateAppGroups(current) {
Expand All @@ -78,17 +113,35 @@ function updateAppGroups(current) {
appGroups[app] = appGroups[app].filter(window => window &&
window != current);
appGroups[app].push(current);
debug("updating app group", appGroups[app].map(window => window.caption));
debug("updating app group", appGroups[app].map(window =>
window && window.caption ? window.caption : "undefined window"
));
}

function isAppOnCurrentActivity(window) {
return (window.activities && window.activities.includes(workspace.currentActivity)) ||
(window.activities && window.activities.length === 0);
}

function getFilterConditions(window) {
return window && !window.minimized &&
isAppOnCurrentDesktop(window) && isAppOnCurrentActivity(window);
}

// return other visible windows of same application as given window
function getAppGroup(current) {
if (!current) return;
let appGroup = appGroups[getApp(current)].filter(window => window &&
!window.minimized &&
(window.x11DesktopIds.includes(workspace.currentDesktop) || window.x11DesktopIds.length == 0) &&
(window.activities.includes(workspace.currentActivity) || window.activities.length == 0));
debug("getting app group", appGroup.map(window => window.caption));

unfilteredAppGroup = appGroups[getApp(current)];
debug("unfiltered app group", unfilteredAppGroup.map(window =>
window && window.caption ? window.caption : "undefined window"));

// let appGroup = appGroups[getApp(current)].filter(getFilterConditions);
let appGroup = unfilteredAppGroup.filter(getFilterConditions);

debug("filtered app group", appGroup.map(window =>
window && window.caption ? window.caption : "undefined window"
));
return appGroup;
}

Expand All @@ -97,8 +150,8 @@ function getAppGroup(current) {
// main
///////////////////////

// when client is activated, auto-raise other windows of the same applicaiton
workspace.clientActivated.connect(active => {
// when client is activated, auto-raise other windows of the same application
function onWindowActivated(active) {
if (!active) return;
debug("---------");
debug("activated", active.caption);
Expand All @@ -117,8 +170,12 @@ workspace.clientActivated.connect(active => {
setPrevActiveApp(active);
// auto-raise other windows of same application
for (let window of getAppGroup(active)) {
debug("auto-raising", window.caption);
workspace.activeClient = window;
if (window) {
debug("auto-raising", window.caption);
setActiveWindow(window);
}
}
}
});
}

connectWindowActivated(onWindowActivated);
131 changes: 126 additions & 5 deletions install.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,126 @@
#!/bin/bash
name=$(grep -oP '"Id":\s*"[^"]*' ./metadata.json | grep -oP '[^"]*$')
kpackagetool5 --type=KWin/Script --install . || kpackagetool5 --type=KWin/Script --upgrade .
kwriteconfig5 --file kwinrc --group Plugins --key "$name"Enabled true
qdbus org.kde.KWin /KWin reconfigure
#!/usr/bin/env bash


# This script will install a KWin script structure found in the path designated in '$script_path'

exit_w_error() {
local msg="$1"
echo -e "\nERROR: ${msg} \nExiting...\n"
exit 1
}

install_w_kpackagetool6() {
if ! command -v kpackagetool6 &> /dev/null; then
exit_w_error "The 'kpackagetool6' command was not found. Cannot install KWin script."
fi
echo "Installing KWin script: '${script_name}'"
if ! kpackagetool6 --type="${script_type}" --install "${script_path}" &> /dev/null; then
kpackagetool6 --type="${script_type}" --upgrade "${script_path}"
fi
}

install_w_kpackagetool5() {
if ! command -v kpackagetool5 &> /dev/null; then
exit_w_error "The 'kpackagetool5' command was not found. Cannot install KWin script."
fi
echo "Installing KWin script: '${script_name}'"
if ! kpackagetool5 --type="${script_type}" --install "${script_path}" &> /dev/null; then
kpackagetool5 --type="${script_type}" --upgrade "${script_path}"
fi
}

KDE_ver=${KDE_SESSION_VERSION:-0} # Default to zero value if environment variable not set
script_type="KWin/Script"
script_path="."
script_name=""


if [ -f "./metadata.json" ]; then
script_name=$(grep -oP '"Id":\s*"[^"]*' ./metadata.json | grep -oP '[^"]*$')
elif [ -f "./metadata.desktop" ]; then
script_name=$(grep '^X-KDE-PluginInfo-Name=' ./metadata.desktop | cut -d '=' -f2)
echo "FYI: 'metadata.desktop' files are deprecated. Use 'metadata.json' format."
else
exit_w_error "No suitable metadata file found. Unable to get script name."
fi

if [ "$script_name" == "" ]; then
exit_w_error "Failed to parse KWin script name from metadata file."
fi

if [[ ${KDE_ver} -eq 0 ]]; then
echo "KDE_SESSION_VERSION environment variable was not set."
exit_w_error "Cannot install '${script_name}' KWin script."
elif [[ ${KDE_ver} -eq 6 ]]; then
if ! install_w_kpackagetool6; then
exit_w_error "Problem installing '${script_name}' with kpackagetool6."
fi
if ! command -v kwriteconfig6 &> /dev/null; then
exit_w_error "The 'kwriteconfig6' command was not found. Cannot enable KWin script."
fi
kwriteconfig6 --file kwinrc --group Plugins --key "$script_name"Enabled true
elif [[ ${KDE_ver} -eq 5 ]]; then
if ! install_w_kpackagetool5; then
exit_w_error "Problem installing '${script_name}' with kpackagetool5."
fi
if ! command -v kwriteconfig5 &> /dev/null; then
exit_w_error "The 'kwriteconfig5' command was not found. Cannot enable KWin script."
fi
kwriteconfig5 --file kwinrc --group Plugins --key "$script_name"Enabled true
else
echo "KDE_SESSION_VERSION had a value, but that value was unrecognized: '${KDE_ver}'"
exit_w_error "This script is meant to run only on KDE 5 or 6."
fi


sleep 0.5

# We need to gracefully cascade through common D-Bus utils to
# find one that is available to use for the KWin reconfigure
# command. Sometimes 'qdbus' is not available. Start with 'gdbus'.

# Extended array of D-Bus command names with prioritized qdbus variants
dbus_commands=("gdbus" "qdbus6" "qdbus-qt6" "qdbus-qt5" "qdbus" "dbus-send")

# Functions to handle reconfiguration with different dbus utilities
reconfigure() {
case "$1" in
gdbus)
gdbus call --session --dest org.kde.KWin --object-path /KWin --method org.kde.KWin.reconfigure
;;
qdbus6 | qdbus-qt6 | qdbus-qt5 | qdbus)
"$1" org.kde.KWin /KWin reconfigure
;;
dbus-send)
dbus-send --session --type=method_call --dest=org.kde.KWin /KWin org.kde.KWin.reconfigure
;;
*)
echo "Unsupported DBus utility: $1" >&2
return 1
;;
esac
}

# Unquoted 'true' and 'false' values are built-in commands in bash,
# returning 0 or 1 exit status.
# So they can sort of be treated like Python's 'True' or 'False' in 'if' conditions.
dbus_cmd_found=false

# Iterate through the dbus_commands array
for cmd in "${dbus_commands[@]}"; do
if command -v "${cmd}" &> /dev/null; then
dbus_cmd_found=true
echo "Refreshing KWin configuration using $cmd."
reconfigure "${cmd}" &> /dev/null
sleep 0.5
# Break out of the loop once a command is found and executed
break
fi
done

if ! $dbus_cmd_found; then
echo "No suitable DBus utility found. KWin configuration may need manual reloading."
fi


echo "Finished installing KWin script: '${script_name}'"
3 changes: 2 additions & 1 deletion metadata.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"KPackageStructure": "KWin/Script",
"KPlugin": {
"Authors": [
{
Expand All @@ -15,7 +16,7 @@
"KWin/Script",
"KCModule"
],
"Version": "1.6",
"Version": "1.7",
"Website": ""
},
"X-Plasma-API": "javascript",
Expand Down
Loading