jordansissel / xdotool

fake keyboard/mouse input, window management, and more
Other
3.17k stars 316 forks source link

xdotool hang if I tile / snap the window #398

Open blueray453 opened 2 years ago

blueray453 commented 2 years ago

xdotool version 3.20160805.1

I have tested on Zorin OS 16 (gnome) and Linux Mint 21

If I tile / snap the terminal emulator to the left, then the following command hangs (just like when we run cat).

xdotool getactivewindow windowsize --sync 100% 100% getwindowgeometry --shell
jordansissel commented 2 years ago

I haven't tested this yet, but it's likely this is a bug in xdotool.

Here's the bug I think is going on, and it can be tested later:

Question: After snapping/tiling, then running the command, does the window's size change at all when xdotool windowsize is run?

blueray453 commented 2 years ago

It does not change until i drag of move the window. If i move the window from tiled or snapped position, then it give output.

For example, I have a script like:

#!/bin/bash

WINDOW_ID=$(xdotool getactivewindow)
CLASS_NAME=$(xprop -id $WINDOW_ID | rg WM_CLASS | cut -d '"' -f2)
WORKSPACE=$(xdotool get_desktop)

WindowsArray=()

for i in $(xdotool search --onlyvisible --desktop $WORKSPACE --classname $CLASS_NAME); do
    WindowsArray+=($i)
done

SCREEN_WIDTH=3440
SCREEN_HEIGHT=1331

# 1 is 4 windows, 2 is 8 windows
FOUR_OR_EIGHT=1

allX=(0 $(( $SCREEN_WIDTH / 4 )) $(( $SCREEN_WIDTH / 2 )) $(( $SCREEN_WIDTH / 4 * 3 )) 0 $(( $SCREEN_WIDTH / 4 )) $(( $SCREEN_WIDTH / 2 )) $(( $SCREEN_WIDTH / 4 * 3 )))
allY=(0 0 0 0 665 665 665 665)

WINDOW_WIDTH=$(( $SCREEN_WIDTH / 4 ))

WINDOW_HEIGHT=$(( $SCREEN_HEIGHT / $FOUR_OR_EIGHT ))

ITER=0
for win in ${WindowsArray[@]}; do
    xdotool windowsize "${win}" $WINDOW_WIDTH $WINDOW_HEIGHT
    xdotool windowmove "${win}" ${allX[$ITER]} ${allY[$ITER]}
    xdotool windowactivate "${win}"
    ((ITER++))

    if [[ $ITER == $(( 4 * $FOUR_OR_EIGHT )) ]]
    then
        break
    fi
done

If I bind it to a key, it will not work on the windows that are tiled or snapped (even though their id is in WindowsArray).

jordansissel commented 2 years ago

This helps, thank you!

jordansissel commented 2 years ago

I'm thinking that xdotool could detect when a window has been docked/tiled by the window manager and maybe it could remove that state (docked, etc) when a windowmove/windowsize command is given.

I haven't tried this yet, though.

blueray453 commented 2 years ago

I ran xprop and xwininfo on both tiled (window with issues) and untiled (window without issues) window.

In a tiled window,

_NET_WM_STATE(ATOM) = _NET_WM_STATE_MAXIMIZED_VERT, _NET_WM_STATE_FOCUSED

In a untiled window,

_NET_WM_STATE(ATOM) = _NET_WM_STATE_FOCUSED

I think the problem is with _NET_WM_STATE_MAXIMIZED_VERT

If I can get a command to set and unset atoms namely _NET_WM_STATE_MAXIMIZED_VERT , and _NET_WM_STATE_MAXIMIZED_HORZ then I hopefully will be able to confirm.

blueray453 commented 2 years ago

I have tiled a window then remove _NET_WM_STATE_MAXIMIZED_VERT using:

xprop -id $(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2) -f _NET_WM_STATE 32a -set _NET_WM_STATE ""

still it does not work.

blueray453 commented 2 years ago

The news is, i replace the xdotool commands with wmctrl and the bug is still there.

#!/bin/bash

# getactivewindow
# selectwindow

WINDOW_ID=$(xdotool getactivewindow)
# WINDOW_ID=$(wmctrl -a :ACTIVE: -v 2>&1 | grep -Eo "0x[0-9a-f]{8}")
CLASS_NAME=$(xprop -id $WINDOW_ID | rg WM_CLASS | cut -d '"' -f2)
WORKSPACE=$(xdotool get_desktop)

xdotool search --onlyvisible --desktop $WORKSPACE --classname $CLASS_NAME | sort -n > /tmp/align-four-windows

NUMBER_OF_WINDOWS=$(cat /tmp/align-four-windows | wc -l)

SCREEN_WIDTH=3440
SCREEN_HEIGHT=1331

WINDOWS_PER_CONTAINER=4

WINDOW_HEIGHT=$(( $SCREEN_HEIGHT ))
WINDOW_WIDTH=$(($SCREEN_WIDTH/$WINDOWS_PER_CONTAINER))

# make 4 slice of 0-100
# 0, 25, 50, 75

# number_to_divide=100
# slice_of_number=4
# number_divide_by=$(($number_to_divide/$slice_of_number))

# slices=()

# for ((n=0;n<$slice_of_number;n++)); do
#     slices[$n]+=$(($number_divide_by*$n))
# done

# for value in "${slices[@]}"
# do
#      echo $value
# done

allX=()

for ((n=0;n<$WINDOWS_PER_CONTAINER;n++)); do
    allX[$n]+=$(($WINDOW_WIDTH*$n))
done

function_name () {
    ITER=0
    # HOW=$(echo "sed -n ${1}p /tmp/align-four-windows" >> /home/ismail/Desktop/test/debug.txt)
    # HOW=$(echo "sed ${1}d /tmp/align-four-windows" >> /home/ismail/Desktop/test/debug.txt)

    for win in $(sed "${1}"d /tmp/align-four-windows); do
        xdotool windowminimize "${win}"
        # wmctrl -i -r $win -b toggle,shaded
    done

    for win in $(sed -n "${1}"p /tmp/align-four-windows); do
        # xdotool windowactivate "${win}"
        # wmctrl -i -r $win -e 0,${allX[$ITER]},0,$WINDOW_WIDTH,$WINDOW_HEIGHT
        xdotool windowsize "${win}" $WINDOW_WIDTH $WINDOW_HEIGHT
        xdotool windowmove "${win}" ${allX[$ITER]} 0
        xdotool windowactivate "${win}"
        ((ITER++))
    done
}

# round a float to the nearest integer above its current value
((NUMBER_OF_STATES=($NUMBER_OF_WINDOWS+$WINDOWS_PER_CONTAINER-1)/$WINDOWS_PER_CONTAINER))

# Three States
state="/tmp/align-four-windows-state"

index=$(cat "$state" 2>/dev/null)
[[ -z "$index" ]] && index=0 || index=$((++index % $NUMBER_OF_STATES))

printf "%d\n" $index >"$state"

# 1,4
# 5,8
# 9,12
# 13,16

# value_to_add=4
# loop_n_times=5

# for ((n=1;n<=loop_n_times;n++)); do
#     DIVX=$((value_to_add - 1))
#     BOX=$(($value_to_add * $n))
#     echo $(($BOX - $DIVX )) $BOX
# done

NUMBER_TO_SUBTRACT=$((WINDOWS_PER_CONTAINER - 1))
TO_WINDOW=$((WINDOWS_PER_CONTAINER * (index + 1) ))
function_name "$((TO_WINDOW - NUMBER_TO_SUBTRACT )),$TO_WINDOW"

In the given code:

If I replace:

xdotool windowsize "${win}" $WINDOW_WIDTH $WINDOW_HEIGHT
xdotool windowmove "${win}" ${allX[$ITER]} 0
xdotool windowactivate "${win}"

with

xdotool windowactivate "${win}"
wmctrl -i -r $win -e 0,${allX[$ITER]},0,$WINDOW_WIDTH,$WINDOW_HEIGHT

you will still find the bug for snapped or tiled windows.

Few things about the script. You have to bind this key to a keyboard shortcut. This script shows four window of current app in current workspace. If there are more than four windows then it toggles. The SCREEN_WIDTH and SCREEN_HEIGHT is hardcoded (because of the original bug i reported). You can choose how many windows you want to see at a time using WINDOWS_PER_CONTAINER.

blueray453 commented 2 years ago

I have done some research.

https://discourse.gnome.org/t/what-are-the-commands-for-the-given-keybindings/10868

It will be hard to maintain compatibility with gnome.

The best solution might be to crate a gnome extension that will give us some commands. Which we will invoke using busctl, dbus-send or gdbus etc.

For example, if we install https://github.com/hseliger/window-calls-extended

we can now run:

% gdbus call --session --dest org.gnome.Shell --object-path /org/gnome/Shell/Extensions/WindowsExt --method org.gnome.Shell.Extensions.WindowsExt.List | cut -c 3- | rev | cut -c4- | rev | jq .
[
  {
    "class": "Fsearch",
    "pid": 20893,
    "id": 1140312234,
    "maximized": 0,
    "focus": false,
    "title": "FSearch"
  },
  {
    "class": "VSCodium",
    "pid": 2156,
    "id": 1140312235,
    "maximized": 0,
    "focus": false,
    "title": "● init.adoc - gnome-extension - VSCodium"
  },
  {
    "class": "Nemo",
    "pid": 14514,
    "id": 1140312243,
    "maximized": 0,
    "focus": false,
    "title": "dbus - /media/ismail/SSDWorking/_Working/_NotesFiltered/linux/dbus"
  },
  {
    "class": "Nemo-desktop",
    "pid": 1937,
    "id": 1140312236,
    "maximized": 0,
    "focus": false,
    "title": "Desktop"
  },
  {
    "class": "firefox",
    "pid": 17717,
    "id": 1140312237,
    "maximized": 2,
    "focus": false,
    "title": "ickyicky/window-calls: Gnome Extension for getting windows list in wayland — Mozilla Firefox"
  },
  {
    "class": "Nemo",
    "pid": 14514,
    "id": 1140312242,
    "maximized": 2,
    "focus": false,
    "title": "extensions - /home/ismail/.local/share/gnome-shell/extensions"
  },
  {
    "class": "firefox",
    "pid": 17717,
    "id": 1140312238,
    "maximized": 0,
    "focus": false,
    "title": "xdotool hang if I tile / snap the window · Issue #398 · jordansissel/xdotool — Mozilla Firefox"
  },
  {
    "class": "Alacritty",
    "pid": 24890,
    "id": 1140312247,
    "maximized": 2,
    "focus": true,
    "title": "Alacritty"
  },
  {
    "class": "Nemo",
    "pid": 14514,
    "id": 1140312239,
    "maximized": 0,
    "focus": false,
    "title": "nemo"
  },
  {
    "class": "Nemo",
    "pid": 14514,
    "id": 1140312240,
    "maximized": 0,
    "focus": false,
    "title": "nemo"
  },
  {
    "class": "firefox",
    "pid": 17717,
    "id": 1140312241,
    "maximized": 0,
    "focus": false,
    "title": "Firefox"
  },
  {
    "class": "Gnome-shell",
    "pid": 24048,
    "id": 1140312244,
    "maximized": 0,
    "focus": false,
    "title": "gnome-shell"
  }
]

For example, there is an extension https://github.com/gonzaarcr/unmaximize-gnome-ext which unmaximize window on panel double click. We might modify it to unmaximize a window by it's id. Or, unmaximize multiple windows by class (it will have to have a dbus interface, so that we can run it via shell script).

Same way, getactivewindow, get_desktop, windowminimize, windowsize, windowmove, windowactivate etc. will have to have dbus commands.

The point is, xdotool will now need a gnome extension. And behind the scene it will call dbus commands.

That way we will get unmaximize type features which i think xdotool currently do not have. We will also get wayland compatibility.

I am not an expert. I might be wrong in so many level. Please let me know in case there is a better solution.

blueray453 commented 2 years ago

This is the library we are looking at to manage windows

https://gjs-docs.gnome.org/meta9~9_api/meta.window#method-activate

blueray453 commented 2 years ago

You might find the following gists interesting

https://gist.github.com/rbreaves/257c3edfa301786e66e964d7ac036269 https://gist.github.com/tuberry/dc651e69d9b7044359d25f1493ee0b39

blueray453 commented 2 years ago

It does not seem to be a xdotool issue. I have filed a bug report on https://gitlab.gnome.org/GNOME/mutter/-/issues/2407

jordansissel commented 2 years ago

Hmm. I have an idea.

I have tiled a window then remove _NET_WM_STATE_MAXIMIZED_VERT using

  xprop -id $(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2) -f _NET_WM_STATE 32a -set _NET_WM_STATE ""

still it does not work.

I don't think this is the correct way to change this property. Here's what I found:

These properties are used as part of EWMH, a standard for window managers, applications, and accessory tools like window pagers to work together.

In order to communicate a change to _NET_WM_STATE, EWMH says this:

To change the state of a mapped window, a Client MUST send a _NET_WM_STATE client message to the root window

In your example, you are using xprop -set. Reading the xprop source code, it looks like -set calls XChangeProperty: https://gitlab.freedesktop.org/xorg/app/xprop/-/blob/master/xprop.c#L1807-1808

xdotool has a command for changing properties using EWMH's protocol, windowstate, code here: https://github.com/jordansissel/xdotool/blob/master/xdo.c#L1948-L1967

Try this, to remove the "maximized vertically" state on the currently-active window, try:

xdotool getactivewindow windowstate --remove MAXIMIZED_VERT

This could remove the tiled attribute from the window and then allow your window sizing commands to work. Let me know if this helps? xprop -id $(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2) -f _NET_WM_STATE 32a -set _NET_WM_STATE ""

blueray453 commented 2 years ago

If I run the following commands one after another then it works.

xdotool getactivewindow windowstate --remove MAXIMIZED_VERT
xdotool getactivewindow windowsize --sync 100% 100% getwindowgeometry --shell
jordansissel commented 2 years ago

Nice! I'm glad we got it figured out :)

in the future, we could probably add a --sync flag to the windowstate command to have it wait until the state is removed from the window; that would help with any timing issues like windowsize running before the window manager has removed the maximized_vert state.

blueray453 commented 2 years ago

Have you checked https://gitlab.gnome.org/GNOME/mutter/-/issues/2407 ?

They said:

"Tiled" is treated as a window state like "maximized", so the window is positioned and sized automatically until it becomes unconstrained again.

I am not sure by what they meant by unconstrained , but i checked https://gjs-docs.gnome.org/clutter9~9_api/clutter.actor#property-constraints and there are

I also checked https://gjs-docs.gnome.org/meta9~9_api/meta.window#method-get_tile_match

get_tile_match()

most interesting to me was:

https://gjs-docs.gnome.org/meta9~9_api-keybindingaction/ and, https://gjs-docs.gnome.org/meta9~9_api/meta.keybindingaction#default-toggle_tiled_left

TOGGLE_TILED_LEFT = 39 TOGGLE_TILED_RIGHT = 40 TOGGLE_ABOVE = 41

I am not proficient at this. otherwise i would have tried to figure this out myself. Please let us know why clicking maximize button on a tiled window maximizes it, but running xdotool getactivewindow windowsize 100% 100% on a tiled window does not maximize it. What is the native and optimal solution here?

blueray453 commented 2 years ago

there are also

https://gjs-docs.gnome.org/clutter9~9_api/clutter.actor#method-clear_constraints

get_constraints() clear_constraints()

blueray453 commented 2 years ago

I did not notice it earlier.

"Tiled" is treated as a window state like "maximized"

Actually xdotool getactivewindow windowsize 40% 40% also does not work on a maximized window.