Add spawninfo patch

This commit is contained in:
Nikita Ivanov 2025-02-14 23:10:09 +01:00
parent 268bee3cee
commit c01728c629
No known key found for this signature in database
GPG Key ID: 6E656AC5B97B5133
4 changed files with 306 additions and 0 deletions

View File

@ -0,0 +1,41 @@
### Description
This patch adds `spawninfo` function that is very similar to `spawn`, except it
also passes some information about the focused client via stdin.
The info is passed in this format:
PID
TITLE
APPID
TAGS
X,Y WIDTHxHEIGHT
I use it for 2 things: grabbing a screenshot of a focused window and adjusting
volume of audio produced by a focused window (so much simpler than having to
open pulsemixer every time). If you want to have the same functionality, you
need to put these scripts into your PATH and make them executable:
[screenshotwin](/dwl/dwl-patches/raw/branch/main/patches/spawninfo/screenshotwin)
for taking a screenshot (`grim` is required):
```c
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_S, spawninfo, SHCMD("screenshotwin") },
```
[pamixerc](/dwl/dwl-patches/raw/branch/main/patches/spawninfo/pamixerc)
for adjusting volume (`pactl` is required):
```c
{ MODKEY, XKB_KEY_XF86AudioRaiseVolume,spawninfo,SHCMD("pamixerc -- -i 5") },
{ MODKEY, XKB_KEY_XF86AudioLowerVolume,spawninfo,SHCMD("pamixerc -- -d 5") },
{ MODKEY, XKB_KEY_XF86AudioMute, spawninfo, SHCMD("pamixerc -- -t") },
```
### Download
- [0.7](/dwl/dwl-patches/raw/branch/main/patches/spawninfo/spawninfo.patch)
### Authors
- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV))

134
patches/spawninfo/pamixerc Executable file
View File

@ -0,0 +1,134 @@
#!/usr/bin/awk -f
function get_parent(pid, cmd, ppid, arr) {
cmd = sprintf("/proc/%d/stat", pid)
ppid = ""
if ((getline line < cmd) > 0) {
split(line, arr)
ppid = arr[4]
}
close(cmd)
return ppid
}
function proc_clients(line, arr) {
if (match(line, /^Client #(.*)/, arr)) {
store_client()
client_index = arr[1]
return
}
else if (match(line, /^\s*application\.process\.id = "(.*)"/, arr)) {
process_id = arr[1]
return
}
else if (match(line, /^\s*application\.name = "(.*)"/, arr)) {
application_name = arr[1]
return
}
}
function store_client() {
if (client_index == "")
return
clients[client_index] = 1
clients[client_index, "pid"] = process_id
clients[client_index, "parent_pid"] = get_parent(process_id)
clients[client_index, "name"] = application_name
client_index = ""
process_id = ""
}
function proc_sink_inputs(line, arr) {
if (match(line, /^Sink Input #(.*)/, arr)) {
apply_sink_input()
sink_index = arr[1]
return
}
else if (match(line, /^\s*application\.process\.id = "(.*)"/, arr)) {
process_id = arr[1]
return
}
else if (match(line, /^\s*Client: (.*)/, arr)) {
client_index = arr[1]
return
}
else if (match(line, /^\s*Mute: (.*)/, arr)) {
muted = (arr[1] == "yes")
return
}
else if (match(line, /^\s*Volume:.* ([0-9]+)%/, arr)) {
volume = strtonum(arr[1])
return
}
}
function apply_sink_input( cmd, header, msg, inc) {
if (sink_index == "")
return
# Do stuff if PID matches
if (PID == clients[client_index, "pid"] || PID == clients[client_index, "parent_pid"]) {
switch (ACTION) {
case "-i":
case "-d":
inc = strtonum(ARG)
if (ACTION == "-i")
volume += inc
else
volume -= inc
volume = volume > 100 ? 100 : volume
volume = volume < 0 ? 0 : volume
cmd = sprintf("pactl set-sink-input-volume '%d' '%d'%", sink_index, volume)
system(cmd)
print cmd
break
case "-t":
muted = !muted
cmd = sprintf("pactl set-sink-input-mute '%d' '%s'", sink_index, muted ? "true" : "false")
system(cmd)
print cmd
break
}
# Display a "popup" with new volume
header = sprintf("Client Volume: %d%%%s", volume, muted ? " MUTED" : "")
msg = clients[client_index, "name"]
system(sprintf("notify-send -r 101 -u low -h 'int:value:%d' '%s' '%s'", volume, header, msg))
}
sink_index = ""
}
BEGIN {
# Get arguments
# ACTION: -d, -i or -t (like pamixer)
# ARG: num arg for -d or -i
ACTION = ARGV[1]
ARG = ARGV[2]
for (i = 1; i < ARGC; i++)
delete ARGV[i]
# Get client info
getline PID
getline TITLE # Not used
getline APPID # Not used
getline TAGS # Not used
getline GEOMETRY # Not used
if (PID == "")
exit 1
# Process PulseAudio clients list and store PIDs for each
cmd = "pactl list clients"
while ((cmd | getline line) > 0)
proc_clients(line)
close(cmd)
store_client()
# Process PulseAudio sink inputs list and run apply_sink_input() for each
cmd = "pactl list sink-inputs"
while ((cmd | getline line) > 0)
proc_sink_inputs(line)
close(cmd)
apply_sink_input()
}

21
patches/spawninfo/screenshotwin Executable file
View File

@ -0,0 +1,21 @@
#!/bin/sh
# Get client info
read -r PID
read -r TITLE
read -r APPID
read -r TAGS
read -r GEOMETRY
[ -n "$GEOMETRY" ] || exit 1
tempdir="/tmp/screenshots"
mkdir -p "$tempdir"
file="$(mktemp -p "$tempdir" "XXXXXX.png")"
# Grab the screenshot! Very conviniently, GEOMETRY format matches the one
# expected by grim
grim -g "$GEOMETRY" "$file" || exit
wl-copy -t image/png < "$file"
notify-send -i "$file" "Screenshot taken!" "Image copied to clipboard and saved to $file"

View File

@ -0,0 +1,110 @@
From 83b8dc03f5ea40f472e90d452671f8e55faf2c4c Mon Sep 17 00:00:00 2001
From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com>
Date: Sun, 9 Feb 2025 23:27:48 +0100
Subject: [PATCH] spawninfo: spawn but pass client info via stdin
---
client.h | 12 ++++++++++++
dwl.c | 42 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 54 insertions(+)
diff --git a/client.h b/client.h
index 42f225f..bc9cad2 100644
--- a/client.h
+++ b/client.h
@@ -131,6 +131,18 @@ client_get_appid(Client *c)
return c->surface.xdg->toplevel->app_id;
}
+static inline int
+client_get_pid(Client *c)
+{
+ pid_t pid;
+#ifdef XWAYLAND
+ if (client_is_x11(c))
+ return c->surface.xwayland->pid;
+#endif
+ wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL);
+ return pid;
+}
+
static inline void
client_get_clip(Client *c, struct wlr_box *clip)
{
diff --git a/dwl.c b/dwl.c
index def2562..859514c 100644
--- a/dwl.c
+++ b/dwl.c
@@ -141,6 +141,7 @@ typedef struct {
uint32_t tags;
int isfloating, isurgent, isfullscreen;
uint32_t resize; /* configure serial of a pending resize */
+ pid_t pid;
} Client;
typedef struct {
@@ -334,6 +335,7 @@ static void setpsel(struct wl_listener *listener, void *data);
static void setsel(struct wl_listener *listener, void *data);
static void setup(void);
static void spawn(const Arg *arg);
+static void spawninfo(const Arg *arg);
static void startdrag(struct wl_listener *listener, void *data);
static void tag(const Arg *arg);
static void tagmon(const Arg *arg);
@@ -466,6 +468,8 @@ applyrules(Client *c)
if (!(title = client_get_title(c)))
title = broken;
+ c->pid = client_get_pid(c);
+
for (r = rules; r < END(rules); r++) {
if ((!r->title || strstr(title, r->title))
&& (!r->id || strstr(appid, r->id))) {
@@ -2658,6 +2662,44 @@ spawn(const Arg *arg)
}
}
+void
+spawninfo(const Arg *arg)
+{
+ int fd[2];
+ pid_t pid;
+ const char *title, *appid;
+ Client *c = focustop(selmon);
+
+ if (pipe(fd) == -1)
+ return;
+ if ((pid = fork()) == -1)
+ return;
+ if (pid == 0) {
+ dup2(fd[0], STDIN_FILENO);
+ close(fd[0]);
+ close(fd[1]);
+ dup2(STDERR_FILENO, STDOUT_FILENO);
+ setsid();
+ execvp(((char **)arg->v)[0], (char **)arg->v);
+ die("dwl: execvp %s failed:", ((char **)arg->v)[0]);
+ }
+
+ close(fd[0]);
+
+ if (c) {
+ if (!(title = client_get_title(c)))
+ title = "";
+ if (!(appid = client_get_appid(c)))
+ appid = "";
+ dprintf(fd[1], "%d\n%s\n%s\n%"PRIu32"\n%d,%d %dx%d\n", c->pid,
+ title, appid, c->tags,
+ c->geom.x + c->bw, c->geom.y + c->bw,
+ c->geom.width - 2 * c->bw, c->geom.height - 2 * c->bw);
+ }
+
+ close(fd[1]);
+}
+
void
startdrag(struct wl_listener *listener, void *data)
{
--
2.48.1