Search code examples
command-line-interfacescreenshotmultiple-monitorsxrandr

Command line tool to Screenshot multiple monitors separately


Problem-statement

I use scrot to take screenshots, which works perfectly except when I have multiple monitors or displays. In that case scrot joins the screenshots of different monitors into one single output.

From the manpage, scrot supports an option -m:

-m, --multidisp
        For multiple heads, grab shot from each and join them together.

So I imagined the default behavior would be to NOT join them together. However this is not the case. Even without the -m option I get joined screenshots.

I am optimistic that scrot should be able to do this as it support the -u option:

-u, --focused
        Use the currently focused window.

which works perfectly fine.

I also checked out another CLI tool called maim - but again I couldn't figure out how to take screenshot of different monitors separately.

So the solution I am excepting should work something like this:

screenshot_command <display_name> # and other options

to screenshot only the display <display_name>.

My attempts at solution so far

maim supports the curios looking option -x:

-x, --xdisplay=hostname:number.screen_number
          Sets the xdisplay to use.

So I tried maim -x 0.0 | xclip -selection clipboard -t image/png, but that doesn't work. I don't know how this option is intended to be used as there isn't enough documentation.

Both scrot and maim also supports the option -s:

-s, --select
        Interactively select a window or rectangle with the mouse.

So I am imagining a very ugly/ hacky solution using xdotool (or similar) to select the desired display and using with the option -s to maim or scrot may do the job. But I would rather not tread this route unless there is no other straight forward solution.

A wild speculation

I wonder if this problem could be because of how I am adding new monitors? I usually add my second display with a command something like this:

xrandr --output eDP-1 --auto --output HDMI-1-4 --auto --right-of eDP-1

So I am wondering, may be to scrot or maim there is only one display. And I imagine so because the output of xdpyinfo | grep -A4 '^screen' with ONE monitor looks like:

$ xdpyinfo | grep -A4 '^screen'
screen #0:
  dimensions:    1920x1080 pixels (506x285 millimeters)
  resolution:    96x96 dots per inch
  depths (7):    24, 1, 4, 8, 15, 16, 32
  root window id:    0x1ba

and with two monitors looks like this:

$ xdpyinfo | grep -A4 '^screen'
screen #0:
  dimensions:    3280x1080 pixels (865x285 millimeters)
  resolution:    96x96 dots per inch
  depths (7):    24, 1, 4, 8, 15, 16, 32
  root window id:    0x1ba

If this is indeed the cause of my problems, then how should I add my second monitor?


Solution

  • Using the very useful suggestion from @Arkadiusz Drabczyk I wrote a script that works. I thought I would share the solution.

    #!/bin/bash
    
    # to have at most one instance of this script running...
    script_name=${BASH_SOURCE[0]}
    for pid in $(pidof -x $script_name); do
        if [ $pid != $$ ]; then
            kill -9 $pid
        fi 
    done
    
    loc="/path/to/folder/"
    
    # take screenshots every minute starting at next minute...
    s=$((60 - $(date +%-S)))
    sleep "$s"s
    
    while true
    do
        f=$(date +'%Y%m%d_%H%M%S')
        scrot -q 20 "$f".jpg
        n=$(xrandr --listmonitors | head -n 1 | sed -e "s/Monitors: //g")
        if (( n == 2 )); then
        convert -crop 1920x1080 +repage "$f".jpg "$f"_%d.jpg
        mv -t "$loc" "$f"_0.jpg "$f"_1.jpg
        rm "$f".jpg
        else
        mv -t "$loc" "$f".jpg
        fi
        s=$((60 - $(date +%-S)))
        sleep "$s"s
    done
    

    NOTES:

    1. The above script does more than what I originally asked for:
      • It ensures at most one instance of this script is running (first code paragraph) which is borrowed from this stack-overflow post. (As pointed out by @tripleee in the comments this is not a good practice. Please be mindful of it.)
      • It takes screenshot every minute starting at the start of next minute using a bunch of sleep commands. This feature could be replaced using cron-job handler if you prefer.
      • The very minimal solution to above question would be convert -crop 1920x1090 +repage "$f".jpg "$f"_%d.jpg. (So you are welcome to strip these bloats, if you prefer). I have added the extra bits anticipating typical usage, so that one may use the code as it is.
    2. Here is how to do other things, if you want to:
      • You could replace 1920x1080 with whatever is the dimension of your left monitor. You could find these dimensions using xrandr -q | grep " connected".
      • My code is intended for use on a fixed set up where, the left monitor is always 1920x1080. You are welcome to make it fancier so that it would work with any setup.
      • Again my code is intended for use on a fixed set up with at most 2 monitors. Again you are welcome to generalize to arbitrary number of monitors.
      • A word of advice if you plan to do the above generalizations so that it would work with any setup: Yes you would need sed/awk/grep or similar to process the output of xrandr -q. But note that xrandr -q can be slow step (as noted here and here). However the output of xrandr -q wouldn't change as often. So to make faster code, you could dump the output of xrandr -q once, every time it's output would change and use the text from the dump for subsequent operations (as illustrated in this this answer) to avoid running xrandr -q on every loop.

    Hope that helps. Cheers!