From a3808fa9998ed02cd785acd9f2856132570f8954 Mon Sep 17 00:00:00 2001 From: Belden Lyman Date: Thu, 11 Jul 2024 09:26:41 -0700 Subject: [PATCH 1/2] Improve multiple monitor detection on Linux with i3wm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit My laptop is connected via HDMI to an external monitor. My window manager is **i3wm**. `ffscreencast` incorrectly detects my built-in and external displays as a single very large screen. This is because `xdpyinfo` does not report on physical hardware: ``` $ xdpyinfo | grep '^screen #' -A 1 screen #0: dimensions: 6096x2160 pixels (1612x571 millimeters) ``` `xrandr` correctly detects that I have two displays attached: ``` $ xrandr | grep ' connected' eDP-1 connected primary 2256x1504+0+0 (normal left inverted right x axis y axis) 285mm x 190mm DP-4 connected 3840x2160+2256+0 (normal left inverted right x axis y axis) 697mm x 392mm ``` This commit ensures the external displays are correctly detected. ---- Before: $ ./ffscreencast --dry /usr/bin/ffmpeg \ -hide_banner \ -loglevel info \ -thread_queue_size 512 \ -f x11grab \ -video_size 6096x2160 \ 💥 too big -i ":0.0" \ 💥 no offset -c:v libx264 \ -crf 0 \ -preset ultrafast \ -threads 0 "/home/blyman/Desktop/Screencast 2024-07-11 at 10.48.17.mkv" After: $ ./ffscreencast --dry Available devices: [1] eDP-1 connected primary 2256x1504+0+0 (normal left inverted right x axis y axis) 285mm x 190mm [2] DP-4 connected 3840x2160+2256+0 (normal left inverted right x axis y axis) 697mm x 392mm Enter device number: 2 /usr/bin/ffmpeg \ -hide_banner \ -loglevel info \ -thread_queue_size 512 \ -f x11grab \ -video_size 3840x2160 \ 💥 correct size of DP-4 -i ":0.0+2256,0" \ 💥 correct offset of DP-4 -c:v libx264 \ -crf 0 \ -preset ultrafast \ -threads 0 "/home/blyman/Desktop/Screencast 2024\-07-11 at 10.48.57.mkv" --- bin/ffscreencast | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/bin/ffscreencast b/bin/ffscreencast index 95fa5f4..248acb0 100755 --- a/bin/ffscreencast +++ b/bin/ffscreencast @@ -103,6 +103,7 @@ AWK="$(which awk 2> /dev/null)" SED="$(which sed 2> /dev/null)" FFMPEG="$(which ffmpeg 2> /dev/null)" UNAME="$(uname 2> /dev/null)" +SORT="$(which sort 2> /dev/null)" @@ -517,7 +518,8 @@ get_screen_device_names() { if [ "$(uname)" = "Darwin" ]; then DEVICE_NAMES="paste <(echo \"\$(ffmpeg -f avfoundation -list_devices true -i '' 2>&1 | $GREP 'AVFoundation input' | $SED -n '/AVFoundation video/,/AVFoundation audio/p' | $GREP -oE '\[[0-9]\].*$' | $GREP 'Capture screen')\") <(echo \"\$(system_profiler SPDisplaysDataType | $SED -n '/^\s.*Displays:$/,\$p' | $GREP -vE '^\s.*Displays:$' | $GREP -E '^\s.*w*:$|Resolution:' | $SED 'N;s/\n/ /' | $SED 's/ \{1,\}/ /g' | $SED 's/^[ \t ]*//;s/[ \t ]*$//')\")" elif [ "$(uname)" = "Linux" ]; then - DEVICE_NAMES="xdpyinfo | $GREP -A 1 -E '^screen #[0-9]*:' | $GREP -vE '^\-\-' | $SED 'N;s/\n/ /' | $SED 's/dimensions://g' | $SED 's/ \{1,\}/ /g' | $AWK '{printf \"[%d] %s\n\", NR, \$0}'" + # The intermediate `sed ... | sort ... | sed ...` ensures the `primary` display is always item [1]. + DEVICE_NAMES="xrandr | $GREP ' connected' | $SED 's/^/1#/; s/^1#\(.* primary .*\)\$/0#\1/;' | $SORT -t '#' -k1n | $SED 's/^[01]#//;' | $AWK '{printf \"[%d] %s\n\", NR, \$0}'" fi if [ "${1}" = "yes" ]; then echo "${DEVICE_NAMES}"; else eval "${DEVICE_NAMES}"; fi } @@ -707,9 +709,7 @@ get_screen_resolution() { if [ "$(uname)" = "Darwin" ]; then resolution="$(get_screen_device_names | $GREP "\[${screen_device_index}\]" | $GREP -oE '[0-9]*\sx\s[0-9]*' | $SED 's/\s//g')" elif [ "$(uname)" = "Linux" ]; then - # ShellCheck does not recognize awk, as we are using it in a variable - # shellcheck disable=SC2016 - resolution="$(get_screen_device_names | $GREP "\[${screen_device_index}\]" | $GREP -oE '[0-9]*x[0-9]*\spixels' | $AWK '{print $1}')" + resolution="$(get_screen_device_names | $GREP "\[${screen_device_index}\]" | $GREP -oE '[0-9]+x[0-9]+')" fi # Format: [0-9].*x[0-9].* (e.g.: 640x480) @@ -717,6 +717,22 @@ get_screen_resolution() { } +# +# Get offset of chosen screen (monitor) +# +# @param integer Screen device index +get_screen_offset() { + screen_device_index=$1 + + if [ "$(uname)" = "Darwin" ]; then + offset="" + elif [ "$(uname)" = "Linux" ]; then + offset="$(get_screen_device_names | $GREP "\[${screen_device_index}\]" | $GREP -oE '[0-9]+x[0-9]+\+[0-9]+\+[0-9]+' | $SED 's/^[0-9]*x[0-9]*+//; s/+/,/; s/^/+/;')" + fi + + echo "${offset}" +} + # # Get all resolutions/framerates of a given camera @@ -1176,8 +1192,7 @@ elif [ "${UNAME}" = "Linux" ]; then camera_fix="" fi - screen_device=":0.0" - + screen_device=":0.0$(get_screen_offset "${screen_device}")" fi @@ -1227,4 +1242,3 @@ else echo "$FFMPEG" eval "$FFMPEG" fi - From 6f64c27fe85817e826971fdc688c8227336799fd Mon Sep 17 00:00:00 2001 From: Belden Lyman Date: Thu, 11 Jul 2024 11:06:55 -0700 Subject: [PATCH 2/2] Wire screen listing checks into init and test I think I got the package names correct for Arch and CentOS. --- bin/ffscreencast | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/bin/ffscreencast b/bin/ffscreencast index 248acb0..99c6380 100755 --- a/bin/ffscreencast +++ b/bin/ffscreencast @@ -359,6 +359,18 @@ print_requirements() { echo " + Desktop recording possible." fi + if ! can_screen_list; then + echo "[ERR] [Linux]: xrandr not found." + echo " - Screen listing not possible." + echo + echo " Debian: apt-get x11-xserver-utils" + echo " CentOS: yum install xorg-x11-server-utils" + echo " Arch: pacman -S xorg-xrandr" + else + echo "[OK] [Linux]: xrandr found: $(which xrandr)" + echo " + Screen listing possible." + fi + if ! can_audio_record; then echo "[WARN] [Linux]: arecord not found." echo " - Sound recording not possible." @@ -458,6 +470,25 @@ can_screen_record() { return "${EXIT_ERR}" } +can_screen_list() { + if [ "${UNAME}" = "Darwin" ]; then + if ! can_screen_record; then + return "${EXIT_ERR}" + else + return "${EXIT_OK}" + fi + elif [ "${UNAME}" = "Linux" ]; then + if ! command -v xrandr > /dev/null 2>&1; then + return "${EXIT_ERR}" + else + return "${EXIT_OK}" + fi + fi + + # Hmm, no supported OS found, not good! + return "${EXIT_ERR}" +} + can_audio_record() { # TODO: Check if audio devices are available @@ -973,7 +1004,7 @@ fi # Check 'list devices' requirements if [ "${LIST_SCREEN_DEVS}" = "yes" ]; then - if ! can_screen_record; then + if ! can_screen_list; then echo "Cannot list screen devices. Test requirements with:" echo "${INFO_NAME} --test" exit "${EXIT_ERR}" @@ -994,7 +1025,7 @@ if [ "${LIST_CAMERA_DEVS}" = "yes" ]; then fi fi if [ "${LIST_ALL_DEVS}" = "yes" ]; then - if ! can_screen_record; then + if ! can_screen_list; then echo "Cannot list screen devices. Test requirements with:" echo "${INFO_NAME} --test" exit "${EXIT_ERR}"