Solved How do I create screenshots in DWM through a keybinding without using SHCMD or shell scripts ?

I took two approaches to bind a command to a key for taking screenshots.
1) static const char *screenshotcmd[] = { "screenshot.sh", NULL };
2) static const char *screenshotcmd[] = { "import", "/tmp/scr-`date +\%m\%d-\%H\%M\%S`.png", NULL };

Approach 1 takes the command of approach 2 and puts the command into a script.
It works, but if possible, I would like to get it to work without any script, and without SHCMD.

Approach 2 creates a screenshot, too, but the `date...` part is not interpreted as a shell command...
I looked up some threads, and the suckless website, but I didn't found a solution for making the date command to work between ...scr- and .png.
Maybe I need to structure the screenshotcmd different ? :-/
 
Hi,

I am a dwm user too and i use graphics/scrot with a keybind. If no arguments are given, it just takes a shot of the screen and puts it into your $HOME directory like so: 2025-04-13-185312_1920x1080_scrot.png. I think it's easy to use. I couldn't understand why you don't wanna use something like that, though. Can you please elaborate? I can also recommend you deskutils/flameshot if you wanna take specific screenshots with mouse but I don't know how to bind it to a key, yet, though. It has a system tray.

Code:
% grep -E "takess|scrot" *
patch-09-personal-preferences:+ { 0x0,                          XK_Print,  spawn,          SHCMD("scrot") },
For my dwm patches: https://github.com/nxjosephofficial/suckless-patches
 
I am a dwm user too and i use graphics/scrot with a keybind. If no arguments are given, it just takes a shot of the screen and puts it into your $HOME directory like so: 2025-04-13-185312_1920x1080_scrot.png. I think it's easy to use. I couldn't understand why you don't wanna use something like that, though. Can you please elaborate?
Scrot didn't work for me with i3-wm so, I considered to skip that with DWM, too.
After taking a .png image with scrot, I always got a broken file.
The file was indeed created, but checking the content with feh, I saw only different color stripes like in a old broken TV.

I can also recommend you deskutils/flameshot if you wanna take specific screenshots with mouse but I don't know how to bind it to a key, yet, though. It has a system tray.
Flameshot was my favourite screenshot program 9 years ago.
I practically forgot that it existed, but thank you for reminding me.
Flameshot works very great, and I could bind it to a key.

In my config.h file:
Code:
...
static const char *screenshotcmd[] = { "flameshot", "gui", NULL };
...
{ 0,                            XK_Print,  spawn,          {.v = screenshotcmd } },
...

Recompiling DWM and testing, flameshot works like intended.
I prefer the mouse to take a screenshot, because I don't always want to have a screenshot of the whole screen.
 
2) static const char *screenshotcmd[] = { "import", "/tmp/scr-`date +\%m\%d-\%H\%M\%S`.png", NULL };

Approach 1 takes the command of approach 2 and puts the command into a script.
It works, but if possible, I would like to get it to work without any script, and without SHCMD.
You're trying to use command substitution ( `command`, see sh(1)), but it won't work without spawning a shell. What actually happens in your case is that you're just passing an argument to import as is, without any additional processing (including backticks). I'm not a dwm user, but I assume that SHCMD macro wraps the string into something like sh -c '...', which is exactly what you need if you want a command to be interpreted by a shell in any way. If not, then maybe you can hook a couple of libc functions (namely, snprintf(3) and strftime(3)) to produce a filename you need before exec'ing, which would be slightly more efficient.
 
but I assume that SHCMD macro wraps the string into something like sh -c '...', which is exactly what you need if you want a command to be interpreted by a shell in any way.
Exactly.

C:
# grep -i shcmd config.def.h
#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } }
 
You're trying to use command substitution ( `command`, see sh(1)), but it won't work without spawning a shell. What actually happens in your case is that you're just passing an argument to import as is, without any additional processing (including backticks). I'm not a dwm user, but I assume that SHCMD macro wraps the string into something like sh -c '...', which is exactly what you need if you want a command to be interpreted by a shell in any way. If not, then maybe you can hook a couple of libc functions (namely, snprintf(3) and strftime(3)) to produce a filename you need before exec'ing, which would be slightly more efficient.
I tried SHCMD with the actual command, and assigned it to a key.
It worked, but it is the pre-5.0 way of doing it, and I believe I read on the ARCH linux wiki that this method is not really secure compared to the new post-5.0 one.
Well, I don't know the reason, but I am quite happy with flameshot, it works like 9 years ago. :)
 
It worked, but it is the pre-5.0 way of doing it, and I believe I read on the ARCH linux wiki that this method is not really secure compared to the new post-5.0 one.
No, that's not true. I was curious as to what could go wrong with spawning a shell in this context, so I took a quick look at the code. It turned out (mostly from commit c565336, but git best practices weren't very common back then I guess) that the "pre dwm-5.0 fashion" was that every command was invoked from a shell implicitly, while in said release the SHCMD macro was added as a way to do the same explicitly when required, which is perfectly reasonable, but certainly has nothing to do with security or anything like that.

I don't think this confusion would have occurred if the associated comment had been worded more clearly though:
C:
/* helper for spawning shell commands in the pre dwm-5.0 fashion */
#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } }
 
Back
Top