linuxmint / muffin

The window management library for the Cinnamon desktop (libmuffin) and its sample WM binary (muffin)
GNU General Public License v2.0
197 stars 93 forks source link

[Bug] NET_WM_WINDOW_TILE_INFO assignments are ignored and / or overridden #612

Open crashvb opened 2 years ago

crashvb commented 2 years ago
 * muffin version 4.8.1
 * Distribution - Mint 20.1
 * Graphics hardware: NVIDIA Corporation TU106 [GeForce RTX 2060] / nvidia-driver-470
 * 64 bit

Issue Question

I'm trying to tile xclock using xprop but it doesn't appear to be working. The window does not resize or change location after executing the command even though the properties appear to be updated. Is this a bug, or am I attempting to do something that is not supported / is out of alignment with how a window manager is intended to operate?

Steps to reproduce

Command execution (resolution: 5120x1415):

$ xdotool search -name xclock
144703498
$ xprop -id 144703498 -fs /tmp/xprop.formats -set _NET_WM_STATE "_NET_WM_STATE_MAXIMIZED_VERT, _NET_WM_STATE_TILED"
$ xprop -id 144703498 -fs /tmp/xprop.formats -set _NET_WM_WINDOW_TILE_INFO "4, 1, 0, 720, 2560, 720, 0, 0"
$ xprop -id 144703498 -fs /tmp/xprop.formats -set _NET_FRAME_EXTENDS "0, 1, 24, 1"
$ xprop -id 144703498 | egrep "_NET_FRAME_EXTENDS|_NET_WM_WINDOW_TILE_INFO|_NET_WM_STATE"
_NET_FRAME_EXTENDS(CARDINAL) = 0, 1, 24, 1
_NET_WM_WINDOW_TILE_INFO(CARDINAL) = 4, 1, 0, 720, 2560, 720, 0, 0
_NET_WM_STATE(ATOM) = _NET_WM_STATE_MAXIMIZED_VERT, _NET_WM_STATE_TILED

Full xprop after command execution:

$ xprop -id 144703498
_NET_FRAME_EXTENDS(CARDINAL) = 0, 1, 24, 1
_NET_WM_WINDOW_TILE_INFO(CARDINAL) = 4, 1, 0, 720, 2560, 720, 0, 0
XKLAVIER_STATE(INTEGER) = 0, -269574400
_GTK_EDGE_CONSTRAINTS(CARDINAL) = 170
_NET_WM_STATE(ATOM) = _NET_WM_STATE_MAXIMIZED_VERT, _NET_WM_STATE_TILED
WM_STATE(WM_STATE):
        window state: Normal
        icon window: 0x0
_NET_FRAME_EXTENTS(CARDINAL) = 0, 0, 24, 0
_NET_WM_DESKTOP(CARDINAL) = 0
_NET_WM_ALLOWED_ACTIONS(ATOM) = _NET_WM_ACTION_MOVE, _NET_WM_ACTION_RESIZE, _NET_WM_ACTION_FULLSCREEN, _NET_WM_ACTION_MINIMIZE, _NET_WM_ACTION_SHADE, _NET_WM_ACTION_MAXIMIZE_HORZ, _NET_WM_ACTION_MAXIMIZE_VERT, _NET_WM_ACTION_CHANGE_DESKTOP, _NET_WM_ACTION_CLOSE, _NET_WM_ACTION_ABOVE, _NET_WM_ACTION_BELOW
_NET_WM_PID(CARDINAL) = 1829267
WM_PROTOCOLS(ATOM): protocols  WM_DELETE_WINDOW
SM_CLIENT_ID(STRING) = "1066fafc84f83a9828164419975251826000000021570350"
WM_CLIENT_LEADER(WINDOW): window id # 0x8a0000a
WM_LOCALE_NAME(STRING) = "en_US.UTF-8"
WM_CLASS(STRING) = "xclock", "XClock"
WM_HINTS(WM_HINTS):
        Client accepts input or input focus: False
        Initial state is Normal State.
        bitmap id # to use for icon: 0x8a00001
        bitmap id # of mask for icon: 0x8a00003
WM_NORMAL_HINTS(WM_SIZE_HINTS):
        program specified size: 164 by 164
        window gravity: NorthWest
WM_CLIENT_MACHINE(STRING) = "s27lxlap"
WM_COMMAND(STRING) = { "xclock" }
WM_ICON_NAME(STRING) = "xclock"
WM_NAME(STRING) = "xclock"

contents of /tmp/xprop.formats:

_NET_FRAME_EXTENDS 16c
_NET_WM_STATE 32a
_NET_WM_WINDOW_TILE_INFO 16c

Expected behaviour

The changes are detected and the window is tiled to the bottom left corner (LLC).

Other information

I did come across an issue with xprop being unable to assign multiple atom values, and have opened a related question to the xprop developers. But I am not sure if that is the underlying issue, as I have also tested with each _NET_WM_STATE atom value individually with the same results. From what I can tell by browsing this repository, _NET_WM_STATE_MAXIMIZED_VERT was added to accommodate some GTK behavior; however, it's my understanding that xclock does not use GTK.

crashvb commented 2 years ago

Update: found a typo in what I was trying where I was using _NET_FRAME_EXTENDS instead of _NET_FRAME_EXTENTS; after testing with the new value, nothing has changed.

mtwebster commented 2 years ago

I've only ever used xprop for querying windows, not setting anything. Have you tried wmctrl for this? I think it's a bit more commonly used for this sort of application.

crashvb commented 2 years ago

@mtwebster Thank you for the reply.

Yes, I have tried using wmctrl; however, I don't know how to assign the value of _NET_WM_WINDOW_TILE_INFO using that tool.

Update:

I found out that I was using the wrong format for the cardinal properties.

Updated /tmp/xprop.formats:

_NET_FRAME_EXTENTS 0c
_NET_WM_STATE 32a
_NET_WM_WINDOW_TILE_INFO 0c

... now results in this error:

$ xprop -id 142606346 -fs /tmp/xprop.formats -set _NET_WM_WINDOW_TILE_INFO 4,1,0,720,2560,720,0,0
X Error of failed request:  BadValue (integer parameter out of range for operation)
  Major opcode of failed request:  18 (X_ChangeProperty)
  Value in failed request:  0x0
  Serial number of failed request:  15
  Current serial number in output stream:  17

... but I am unsure how to find what are the limits of the range, or even which value (there are multiple '0's provided) is out of range ...

crashvb commented 2 years ago

I believe that I have located the relevant code within core/window.c::set_net_wm_state that assigns values to both _NET_WM_STATE and _NET_WM_WINDOW_TILE_INFO, and I don't see any special range checking; which leads me to believe that this is being done by something at a lower level (x11?) and not the WM.

I did check ~/.xsession.errors, but I don't see any entries related to this action.

crashvb commented 2 years ago

Update:

I have learned way more information about window managers than I was expecting to need to know; however, a answer to this question still eludes me.

To progress, I have wrapped python-xlib and implemented about 90% of EWMH, but the _NET_WM_STATE_TILED does not appear to be part of that standard. More so, it would appear that is it precluded from existing with a _NET prefix; however, I am not familiar with the history of things in this area.

I have tried various combinations of actions including:

I'm still trying to wrap my head around what I have observed, but this is my understanding at this point:

Side Notes:

crashvb commented 2 years ago

Update:

I (think) I have successfully assigned the properties to the window; however, no action is taken by the WM to perform the tiling.

I have used python-xlib window.changeproperty() method to assign the frame extents (just noticed the typo, again, the the post above =/ ... extenTs)_ and tile info (i.e. XChangeProperty), followed by window.send_event() to assign the WM state via a message to the root window. ... then ... nothing happens (xprop verification below).

When I attempt to assign _NET_WM_STATE using change_property(), instead of notifying the root window as the docs say, nothing happens, as expected. When I attempt to assign _NET_WM_STATE_MAXIMIZED_VERT as the second state in the client message, only that state is assigned, _NET_WM_STATE_TILED is ignored, and _NET_WM_WINDOW_TILE_INFO is removed by the WM?!?

I was looking into the codebase of cinnamon, and noticed that it uses GTK to emit a signal, and both the muffin code overview and inline comments mention that GTK events are processed by display.c::event_callback(), but I'm not familiar enough (yet?) to trace how / where the signal is processed. -- My current understanding is that GTK assigns values to a "meta" object that is then referenced in response to signaling/events/messages??? (i.e. window->tile_mode window->tile_type) ...

I did come across PyGObject; however, I'm not sure that going that route is needed if it's only wrapping lower-level signalling, or effectively assigning the same state (i.e. That either way it's muffin making the change).

Additionally, I was looking for unit-tests of this functionality, or a standard that governs _NET_WM_WINDOW_TILE_INFO, but I might have been looking in the wrong places ...

What I assigned:

_NET_WM_WINDOW_TILE_INFO(CARDINAL) = 4, 1, 0, 720, 2560, 720, 0, 0
XKLAVIER_STATE(INTEGER) = 0, -269574400
_GTK_EDGE_CONSTRAINTS(CARDINAL) = 170
_NET_WM_STATE(ATOM) = _NET_WM_STATE_TILED
WM_STATE(WM_STATE):
        window state: Normal
        icon window: 0x0
_NET_FRAME_EXTENTS(CARDINAL) = 0, 1, 24, 1
_NET_WM_DESKTOP(CARDINAL) = 0
_NET_WM_ALLOWED_ACTIONS(ATOM) = _NET_WM_ACTION_MOVE, _NET_WM_ACTION_RESIZE, _NET_WM_ACTION_FULLSCREEN, _NET_WM_ACTION_MINIMIZE, _NET_WM_ACTION_SHADE, _NET_WM_ACTION_MAXIMIZE_HORZ, _NET_WM_ACTION_MAXIMIZE_VERT, _NET_WM_ACTION_CHANGE_DESKTOP, _NET_WM_ACTION_CLOSE, _NET_WM_ACTION_ABOVE, _NET_WM_ACTION_BELOW
_NET_WM_PID(CARDINAL) = 3743926
WM_PROTOCOLS(ATOM): protocols  WM_DELETE_WINDOW
SM_CLIENT_ID(STRING) = "1066fafc84f83a982816456722452259900000021571259"
WM_CLIENT_LEADER(WINDOW): window id # 0x860000a
WM_LOCALE_NAME(STRING) = "en_US.UTF-8"
WM_CLASS(STRING) = "xclock", "XClock"
WM_HINTS(WM_HINTS):
        Client accepts input or input focus: False
        Initial state is Normal State.
        bitmap id # to use for icon: 0x8600001
        bitmap id # of mask for icon: 0x8600003
WM_NORMAL_HINTS(WM_SIZE_HINTS):
        program specified size: 164 by 164
        window gravity: NorthWest
WM_CLIENT_MACHINE(STRING) = "s27lxlap"
WM_COMMAND(STRING) = { "xclock" }
WM_ICON_NAME(STRING) = "xclock"
WM_NAME(STRING) = "xclock"

What was assigned manually:

_NET_WM_WINDOW_TILE_INFO(CARDINAL) = 4, 1, 0, 707, 2560, 707, 0, 0
XKLAVIER_STATE(INTEGER) = 0, -269574400
_GTK_EDGE_CONSTRAINTS(CARDINAL) = 170
_NET_WM_STATE(ATOM) = _NET_WM_STATE_MAXIMIZED_VERT, _NET_WM_STATE_TILED
WM_STATE(WM_STATE):
        window state: Normal
        icon window: 0x0
_NET_FRAME_EXTENTS(CARDINAL) = 0, 1, 24, 1
_NET_WM_DESKTOP(CARDINAL) = 0
_NET_WM_ALLOWED_ACTIONS(ATOM) = _NET_WM_ACTION_MOVE, _NET_WM_ACTION_RESIZE, _NET_WM_ACTION_FULLSCREEN, _NET_WM_ACTION_MINIMIZE, _NET_WM_ACTION_SHADE, _NET_WM_ACTION_MAXIMIZE_HORZ, _NET_WM_ACTION_MAXIMIZE_VERT, _NET_WM_ACTION_CHANGE_DESKTOP, _NET_WM_ACTION_CLOSE, _NET_WM_ACTION_ABOVE, _NET_WM_ACTION_BELOW
_NET_WM_PID(CARDINAL) = 3747321
WM_PROTOCOLS(ATOM): protocols  WM_DELETE_WINDOW
SM_CLIENT_ID(STRING) = "1066fafc84f83a9828164567226354832400000021571260"
WM_CLIENT_LEADER(WINDOW): window id # 0x840000a
WM_LOCALE_NAME(STRING) = "en_US.UTF-8"
WM_CLASS(STRING) = "xclock", "XClock"
WM_HINTS(WM_HINTS):
        Client accepts input or input focus: False
        Initial state is Normal State.
        bitmap id # to use for icon: 0x8400001
        bitmap id # of mask for icon: 0x8400003
WM_NORMAL_HINTS(WM_SIZE_HINTS):
        program specified size: 164 by 164
        window gravity: NorthWest
WM_CLIENT_MACHINE(STRING) = "s27lxlap"
WM_COMMAND(STRING) = { "xclock" }
WM_ICON_NAME(STRING) = "xclock"
WM_NAME(STRING) = "xclock"

Aside from the variance between 707 and 720, which I suspect is due to not using _NET_REQUEST_FRAME_EXTENTS, they appear to be equivalent. Is there something I'm missing?

crashvb commented 2 years ago

Found this in src/core/window-props.c:

 846 static void
 847 reload_net_wm_state (MetaWindow    *window,
 848                      MetaPropValue *value,
 849                      gboolean       initial)
 850 {
 851   int i;
 852 
 853   /* We know this is only an initial window creation,
 854    * clients don't change the property.
 855    */
 856 
 857   if (!initial) {
 858     /* no, they DON'T change the property */
 859     meta_verbose ("Ignoring _NET_WM_STATE: we should be the one who set "
 860                   "the property in the first place\n");
 861     return;
 862   }

... doesn't look good, but not sure of the impact yet ...

crashvb commented 1 year ago

Update:

I've continued down the python-xlib path. I implemented a few workarounds including using moveresize and feeding keys into the WM to get windows to tile. I've been using them for about a year, but they are messy and still feel like a hack.

Recently, I've even asked ChatGPT to help (despite the 2021 cutoff), but it didn't yield anything new...

Me: Please provide example python code to assign the "NET_WM_WINDOW_TILE_INFO" atom using the Xlib library targeted at the Muffin window manager.

ChatGPT: Sure! Here's an example Python code snippet that demonstrates how to assign the "NET_WM_WINDOW_TILE_INFO" atom using the Xlib library, specifically targeting the Muffin window manager:


from Xlib import X, display, protocol, Xatom

Create a connection to the X server

d = display.Display()

Get the root window

root = d.screen().root

Create a window and set its properties

win = root.create_window(0, 0, 500, 500, 0, d.screen().root_depth)

Set the "NET_WM_WINDOW_TYPE" property to "NORMAL"

win.change_property(d.get_atom("_NET_WM_WINDOW_TYPE"), Xatom.WINDOW, 32, [d.get_atom("_NET_WM_WINDOW_TYPE_NORMAL")])

Set the "NET_WM_WINDOW_TILE_INFO" property to indicate how the window should be tiled

info = [ 1, # flags (1 = tileable) 0, # left inset 0, # right inset 0, # top inset 0, # bottom inset 0, # start of content area (x-coordinate) 0, # start of content area (y-coordinate) 500, # width of content area 500 # height of content area ] win.change_property(d.get_atom("_NET_WM_WINDOW_TILE_INFO"), d.get_atom("32_CARDINAL"), 32, info)

Map the window and flush changes to the server

win.map() d.flush()


> **Me**: I'm sorry, but this does not work. Are you sure the Muffin window manager supports assigning the "NET_WM_WINDOW_TILE_INFO" property?
>
> **ChatGPT**: I apologize for the confusion. You are correct that Muffin does not currently support the _NET_WM_WINDOW_TILE_INFO property.
>
> In fact, this property is not part of the EWMH specification, but rather an experimental extension that was proposed by the Openbox window manager. While some tiling window managers have implemented this extension, it is not widely supported and is not part of the standard EWMH specification.
>
> If you are looking to implement tiling behavior in Muffin, you may want to explore other options such as using a tiling extension like shellshape, or implementing your own tiling logic within your application.
>
> Once again, I apologize for any confusion caused by my earlier response.

I'm currently using Cinnamon on Linux Mint, so I'm pretty sure I'm sticking with Muffin; but at least I got some feedback that somewhere on the internet, other people have tried something similar. ;)
DirkHaar commented 1 year ago

Did you check all the tiling functions directly available via keyboard shortcuts (see in the keyboard settings), some of them aren't assigned to keys in default settings.

Am 9. April 2023 04:53:37 MESZ schrieb Richard Davis @.***>:

Update:

I've continued down the python-xlib path. I implemented a few workarounds including using moveresize and feeding keys into the WM to get windows to tile. I've been using them for about a year, but they are messy and still feel like a hack.

Recently, I've even asked ChatGPT to help (despite the 2021 cutoff), but it didn't yield anything new...

Me: Please provide example python code to assign the "NET_WM_WINDOW_TILE_INFO" atom using the Xlib library targeted at the Muffin window manager.

ChatGPT: Sure! Here's an example Python code snippet that demonstrates how to assign the "NET_WM_WINDOW_TILE_INFO" atom using the Xlib library, specifically targeting the Muffin window manager:


from Xlib import X, display, protocol, Xatom

Create a connection to the X server

d = display.Display()

Get the root window

root = d.screen().root

Create a window and set its properties

win = root.create_window(0, 0, 500, 500, 0, d.screen().root_depth)

Set the "NET_WM_WINDOW_TYPE" property to "NORMAL"

win.change_property(d.get_atom("_NET_WM_WINDOW_TYPE"), Xatom.WINDOW, 32, [d.get_atom("_NET_WM_WINDOW_TYPE_NORMAL")])

Set the "NET_WM_WINDOW_TILE_INFO" property to indicate how the window should be tiled

info = [ 1, # flags (1 = tileable) 0, # left inset 0, # right inset 0, # top inset 0, # bottom inset 0, # start of content area (x-coordinate) 0, # start of content area (y-coordinate) 500, # width of content area 500 # height of content area ] win.change_property(d.get_atom("_NET_WM_WINDOW_TILE_INFO"), d.get_atom("32_CARDINAL"), 32, info)

Map the window and flush changes to the server

win.map() d.flush()


> **Me**: I'm sorry, but this does not work. Are you sure the Muffin window manager supports assigning the "NET_WM_WINDOW_TILE_INFO" property?
>
> **ChatGPT**: I apologize for the confusion. You are correct that Muffin does not currently support the _NET_WM_WINDOW_TILE_INFO property.
>
> In fact, this property is not part of the EWMH specification, but rather an experimental extension that was proposed by the Openbox window manager. While some tiling window managers have implemented this extension, it is not widely supported and is not part of the standard EWMH specification.
>
> If you are looking to implement tiling behavior in Muffin, you may want to explore other options such as using a tiling extension like shellshape, or implementing your own tiling logic within your application.
>
> Once again, I apologize for any confusion caused by my earlier response.

I'm currently using Cinnamon on Linux Mint, so I'm pretty sure I'm sticking with Muffin; but at least I got some feedback that somewhere on the internet, other people have tried something similar. ;)

-- 
Reply to this email directly or view it on GitHub:
https://github.com/linuxmint/muffin/issues/612#issuecomment-1501025838
You are receiving this because you are subscribed to this thread.

Message ID: ***@***.***>
crashvb commented 1 year ago

@DirkHaar thank you for the reply :)

Yes, I do have something working, but there are edge cases where things go sideways. (e.g. If call to activate / focus the window is not successful prior sending the keys then the wrong window, or no window, ends up being tiled)

My hope is to be able to programatically assign the desired state in such a way that it is both deterministic and compatible with what the underlying WM is doing. (i.e. If I snap a window, that the WM also considers the window to be snapped).

DirkHaar commented 1 year ago

To keep the WM informed about the status you pretty sure must use its function calls for tiling (just saying, in case you are manipulating size/width/height manually). But I guess that's more a question going to the muffin developers.

crashvb commented 1 year ago

Agreed; based on what I know now, I'm going to convert this question into a bug.