-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathww
executable file
·253 lines (219 loc) · 8.69 KB
/
ww
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
#!/bin/bash
TOGGLE="false"
POSITIONAL=()
while [[ $# -gt 0 ]]; do
key="$1"
case $key in
-c | --command)
COMMAND="$2"
shift # past argument
shift # past value
;;
-f | --filter)
FILTERBY="$2"
shift # past argument
shift # past value
;;
-p | --process)
PROCESS="$2"
shift # past argument
shift # past value
;;
-fa | --filter-alternative)
FILTERALT="$2"
shift # past argument
shift # past value
;;
-t | --toggle)
TOGGLE="true"
shift # past argument
;;
-ia | --info-active)
INFO_ACTIVE="1"
shift # past argument
;;
-u | --current-user)
CURRENTUSERONLY="true"
shift # past argument
;;
-h | --help)
HELP="1"
shift # past argument
shift # past value
;;
*) # unknown option
POSITIONAL+=("$1") # save it in an array for later
shift # past argument
;;
esac
done
if [[ -z "$PROCESS" ]]; then
PROCESS=$COMMAND
fi
set -- "${POSITIONAL[@]}" # restore positional parameters
if [[ -n "$HELP" ]]; then
cat <<EOF
ww. Utility to launch a window (or raise it, if it was minimized), or to show information about the active window, or to perform other operations with windows in KDE Plasma. It interacts with KWin using KWin scripts and it is compatible with X11 and Wayland.
Parameters:
-h --help show this help
-ia --info-active show information about the active window. Using this parameter, this program can be periodically called from
other programs, so the user is able to know how much time he/she spends using particular windows, or the user
is able to stop (in order to save CPU use, bandwith or downloaded MBs) programs when they are not in the
foreground, etc.
-f --filter filter by window class
-fa --filter-alternative filter by window title (caption)
-t --toggle also minimize the window if it is already active
-c --command command to check if running and run if no process is found
-p --process overide the process name used when checking if running, defaults to --command
-u --current-user will only search processes of the current user. requires loginctl
EOF
exit 0
fi
function get_kwin_version() {
kwinSupportInfo="$(qdbus org.kde.KWin /KWin supportInformation)" || exit 1
kwinVersion="$(awk '/KWin version:/ {print $3}' <<< "$kwinSupportInfo")" || exit 1
echo "$kwinVersion"
}
if [[ -n "$INFO_ACTIVE" ]]; then
kwinVersion=$(get_kwin_version)
kwinMajorVersion="$(awk -F"." '{print $1}' <<< "$kwinVersion")" || exit 1
# This feature needs at least this KWin version
readonly minimumVersion=6 || exit 1
if [[ "$kwinMajorVersion" -lt "$minimumVersion" ]]; then
echo "ERROR: This feature needs KWin $minimumVersion or later." >&2
exit 1
fi
# This way is similar to the one used on https://discuss.kde.org/t/xdotool-replacement-on-wayland/7242/9
jsFile="$(mktemp)" || exit 1 # It is the file where the javascript code is going to be saved
echo "print(\"$jsFile\",workspace.activeWindow.internalId);" > "$jsFile" || exit 1
scriptId="$(qdbus org.kde.KWin /Scripting loadScript "$jsFile")" || exit 1
timestamp="$(date +"%Y-%m-%d %H:%M:%S")" || exit 1
# Starts the script
qdbus org.kde.KWin /Scripting/Script"$scriptId" run || exit 1
# Uses some arguments that are also seen on https://github.com/jinliu/kdotool/blob/master/src/main.rs
outputJournalctl="$(journalctl --since "$timestamp" --user --user-unit=plasma-kwin_wayland.service --user-unit=plasma-kwin_x11.service --output=cat -g "js: $jsFile")" || exit 1
# Uses `awk` separately in order to avoid masking a return value, as Shellcheck recommends
windowId="$(awk '{print $3}' <<< "$outputJournalctl")" || exit 1
# Stops the script
qdbus org.kde.KWin /Scripting/Script"$scriptId" stop || exit 1
# Shows the information about that window
qdbus org.kde.KWin /KWin org.kde.KWin.getWindowInfo "$windowId" || exit 1
exit 0
fi
SCRIPT_TEMPLATE=$(
cat <<EOF
function kwinactivateclient(clientClass, clientCaption, toggle) {
var clients = workspace.clientList ? workspace.clientList() : workspace.windowList();
var activeWindow = workspace.activeClient || workspace.activeWindow;
var compareToCaption = new RegExp(clientCaption || '', 'i');
var compareToClass = clientClass;
var isCompareToClass = clientClass.length > 0;
var matchingClients = [];
for (var i = 0; i < clients.length; i++) {
var client = clients[i];
var classCompare = (isCompareToClass && client.resourceClass == compareToClass);
var captionCompare = (!isCompareToClass && compareToCaption.exec(client.caption));
if (classCompare || captionCompare) {
matchingClients.push(client);
}
}
if (matchingClients.length === 1) {
var client = matchingClients[0];
if (activeWindow !== client) {
setActiveClient(client);
} else if (toggle) {
client.minimized = !client.minimized;
}
} else if (matchingClients.length > 1) {
matchingClients.sort(function (a, b) {
return a.stackingOrder - b.stackingOrder;
});
const client = matchingClients[0];
setActiveClient(client);
}
}
function setActiveClient(client){
if (workspace.activeClient !== undefined) {
workspace.activeClient = client;
} else {
workspace.activeWindow = client;
}
}
kwinactivateclient('CLASS_NAME', 'CAPTION_NAME', TOGGLE);
EOF
)
# ensure the script file exists
function ensure_script {
if [[ ! -f SCRIPT_PATH ]]; then
if [[ ! -d "$SCRIPT_FOLDER" ]]; then
mkdir -p "$SCRIPT_FOLDER"
fi
SCRIPT_CONTENT=${SCRIPT_TEMPLATE/CLASS_NAME/$1}
SCRIPT_CONTENT=${SCRIPT_CONTENT/CAPTION_NAME/$2}
SCRIPT_CONTENT=${SCRIPT_CONTENT/TOGGLE/$3}
echo "$SCRIPT_CONTENT" >"$SCRIPT_PATH"
fi
}
# Check if a version string is between two inclusive versions.
function ver_between() {
# args: min, actual, max
printf '%s\n' "$@" | sort -C -V
}
# Check if a version string is lower than another.
function ver_lt() {
printf '%s\n' "$1" "$2" | sort -C -V
}
if [[ -z "$FILTERBY" && -z "$FILTERALT" ]]; then
echo "If you want that this program find a window, you need to specify a window filter — either by class (\`-f\`) or by title (\`-fa\`). More information can be seen if this script is called using the \`--help\` parameter."
exit 1
fi
USER_FILTER=""
if [[ -n "$CURRENTUSERONLY" ]] && command -v loginctl >/dev/null 2>&1; then
if command -v loginctl >/dev/null 2>&1; then
session_id=$(loginctl show-seat seat0 -p ActiveSession --value)
user_id=$(loginctl show-session "$session_id" -p User --value)
USER_FILTER="-u $user_id"
fi
fi
# Note: In this case, `$USER_FILTER` must not have quotes around it.
# shellcheck disable=SC2086
IS_RUNNING=$(pgrep $USER_FILTER -o -a -f "$PROCESS" --ignore-ancestors)
if [[ -n "$IS_RUNNING" || -n "$FILTERALT" ]]; then
# trying for XDG_CONFIG_HOME first.
# shellcheck disable=SC2154
SCRIPT_FOLDER_ROOT=$XDG_CONFIG_HOME
if [[ -z $SCRIPT_FOLDER_ROOT ]]; then
# fallback to the home folder
SCRIPT_FOLDER_ROOT=$HOME
fi
SCRIPT_FOLDER="$SCRIPT_FOLDER_ROOT/.wwscripts/"
# Uses `md5sum` separately in order to avoid masking a return value, as Shellcheck recommends
INFO_MD5SUM=$(md5sum <<< "$FILTERBY$FILTERALT") || exit 1
# Ensures that the script file exists
SCRIPT_NAME=$(head -c 32 <<< "$INFO_MD5SUM") || exit 1
SCRIPT_PATH="$SCRIPT_FOLDER$SCRIPT_NAME"
ensure_script "$FILTERBY" "$FILTERALT" "$TOGGLE"
SCRIPT_NAME="ww$RANDOM"
INFO_DBUS_SEND=$(dbus-send --session --dest=org.kde.KWin --print-reply=literal /Scripting org.kde.kwin.Scripting.loadScript "string:$SCRIPT_PATH" "string:$SCRIPT_NAME") || exit 1
# Uses `awk` separately in order to avoid masking a return value, as Shellcheck recommends
ID=$(awk '{print $2}' <<< "$INFO_DBUS_SEND") || exit 1
# Use kwin version to decide how to call the script run api which changes between kwin versions.
# See https://github.com/academo/ww-run-raise/issues/15#issuecomment-2632214974 for more info.
kwinVersion=$(get_kwin_version)
if ver_between 5.21.90 "$kwinVersion" 5.27.79; then
SCRIPT_API_PATH=org.kde.kwin.Script
SCRIPT_PATH="/$ID"
elif ver_lt 5.27.80 "$kwinVersion"; then
SCRIPT_API_PATH=org.kde.kwin.Script
SCRIPT_PATH="/Scripting/Script$ID"
else
SCRIPT_API_PATH=org.kde.kwin.Scripting
SCRIPT_PATH="/$ID"
fi
# Run using detected pat
dbus-send --session --dest=org.kde.KWin --print-reply=literal "$SCRIPT_PATH" ${SCRIPT_API_PATH}.run >/dev/null 2>&1
# Stop using same path
dbus-send --session --dest=org.kde.KWin --print-reply=literal "$SCRIPT_PATH" ${SCRIPT_API_PATH}.stop >/dev/null 2>&1
elif [[ -n "$COMMAND" ]]; then
$COMMAND &
fi