Closed Kalmat closed 1 year ago
Wow, thanks so much for the feedback! Keep'em coming!
- I guess this module is intended to be a new, separate module, not an ewmh update, right?
Correct. Brand new module and API, no ties or worries about backward-compatibility. Everything can be discussed, suggested and changed, and nothing is set on stone. Even the project and module name is not set on stone!
- Window() and EWMH(): Not sure if EWMH() is needed as it was in ewmh original module. Perhaps it's more intuitive leaving "general" functions (e.g. get_active() or get_list()) as: ewmh-client.function(); then using Window() for all win-related functions (e.g. get_property())... Not sure, just wondering, and for sure this doesn't apply if this is intended to be an ewmh update (again, I guess it's not)
How to best model the classes hierarchy is an on-going challenge, here's where I'm currently at:
EWMH
is both the "main" module class (representing the Window Manager) and the Root Window.Window
is a wrapper around Xlib.xobject.drawable.Window
.And here's how I arrived at this (admittedly unusual) model:
Xlib.display.Display.display.screen(sno).root
(the Root Window handle) is a Xlib.xobject.drawable.Window
, same as any "regular" window. X, as opposed to the EWMH spec, the concept of "root window" is not strong, except that it's the only window with no parent.RootWindow
as a subclass of Window
: It can and should do everything a Window can. And in EWMH Spec it has additional methods.Xlib
such as get_property()
and send_message()
ended up in Window
[note 1]EWMH
/(or WM
) and RootWindow
classes, but in the EWMH spec the relation (and roles distinction) between the Window Manager and the Root Window is... blurred at best:_NET_SUPPORTED
property of the root window. But note this does not necessarily means that all windows, not even the root, will have all properties listed! Even when _NET_SUPPORTED
includes _NET_WM_NAME
, the root window does not have that property! Check this (using the "low level" get_property
so we see what's going on):
Python 3.8.0 (default, Dec 9 2021, 17:53:27)
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import ewmh_client
>>> root = ewmh_client.EWMH()
>>> wm_name = root.Atom('_NET_WM_NAME')
>>> prop = root.get_active_window().get_property(wm_name); print(prop)
Property(name='_NET_WM_NAME', handle=<Window 0x04200006>, type=<Atom(307) UTF8_STRING>, format=<Format.CHAR: 8>, value=b'Terminal')
>>> prop = root.get_property(wm_name); print(prop)
Property(name='_NET_WM_NAME', handle=<Window 0x000006c3>, type=0, format=0, value=b'')
>>> prop.raise_on_missing()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/projects/ewmh-client/ewmh_client.py", line 149, in raise_on_missing
raise EwmhError("Property %r not found in %s", self.name, self.handle)
ewmh_client.EwmhError: Property '_NET_WM_NAME' not found in <Window 0x000006c3>
Note 1: I also considered an "Xlib interface-only" class for the base methods. They would then have a handle
(or window
, or window_id
) parameter, and all Window
/RootWindow
(now EWMH
) would pass as self.handle
(or self
, or self.id
). In the end I didn't see much gain in this. Properties are always tied to a Window (Root or not), and so is the send_message
destination. You must have a window reference to use those, so why not include the methods in the the class?
The only gain I could see in separating them is a more clear distinction between the "low level" Xlib methods and the EWMH methods. In the I thought the distinction is not that useful (and can be inferred from context) and took the OOP approach
This, obviously, is not set in stone. None of my lib is, from models to naming to API.
That said, let's talk about your actual suggestion:
- Window() and EWMH(): Not sure if EWMH() is needed as it was in ewmh original module.
It isn't. It could be just class Window
and class RootWindow(Window)
(for its specific WM-related methods), and everything else could be module functions..
But... what would this "everything else" be? The only function I can think of that does not require a window instance is... connect(display_name: str, screen_number: int)
, and it would return a RootWindow
instance, as all EWMH methods are there. And this hypothetical connect()
feels a lot like it's just a RootWindow
constructor.
Even things related to X server and completely independent of a Root Window, such as atoms, require a display, which you get from connect()
. You need at least display_name
to fiddle with Atoms, it make sense for them to share the same connection (ie, Xlib.display.Display
instance) with Root Window, and that additionally requires the screen_number
(called sno
in Xlib
). So I just stuffed all together, and named it class EWMH
to indicate it's the "entry point" for everything.
This can be changed (to reiterate: everything can), and I'm currently exploring ideas such as:
Display
or Connection
class as the "main" module gateway (possibly named EWMH
to indicate so), with .__init__(display_name: str)
and .get_root(sno: int)
methods, and it would also be responsible for atoms.Connection.get_root()
above would return a RootWindow
instance, not the Xlib handle. And as much as I hate the java way of naming all get/set methods for everything, no better alternative here as otherwise I would need to pre-create all RootWindow
instances from Xlib's Display.screens[sno].root
. Not sure if in EWMH a screen is a first-class citizen or have any relevance besides getting its root window.
- I would name ewmh-client functions in a different way as python-xlib does. e.g. Window.getProperty() vs. XWindow.get_property(), so it's clear for the user in which "environment" they are (I had to mix python-xlib functions with ewmh functions, and even with "x11" library functions! - these last ones start by "X" -)
As per PEP-8 I'm avoiding favoring snake_case
over camelCase
in general, as the tradition in Python. (that's why I chose Window.handle
instead of yours .hWnd
, but it's the same concept of "a reference to the underlying object"). And in this particular case I think camelCase could add more confusion: the X Protocol Standard uses PascalCase (and so does the C Xlib, prefixing with X
), but python-xlib
uses snake_case. So if my "get property" wrapper is getProperty
it could be confused with X's GetProperty
and as such perceived as even more "low level" than python-xlib
's get_property
.
That said, I'm in favor of using nomenclature to differentiate from python-xlib
's methods. I'm already prefixing its classes with X
, so XWindow
is an alias to Xlib.xobject.drawable.Window
, XDisplay
is Xlib.display.Display
(even if I'm not wrapping it) , XAtom
is int
, and so on. I even created an XRid
alias for "whatever python-xlib
means as the Resource ID it ends up using in its Window.id
"
So... how about Window.get_x_property()
? Would that be a good indication that this is a higher-level wrapper to Xlib's XWindow.get_property()
? Any other suggestions?
- I'm confused with "root". In some cases it seems to (or it should) mean root window (Display -> Screen -> root), but in other cases it's EWMH() class (or am I missing something?).
Welcome to the club, thanks to the EWMH spec, lol. It's the same thing. As I merged the classes, Window.root
is an instance of the EWMH
class, which also doubles as the Root Window. And since the Root Window (and hence an EWMH
instance) is a subclass of Window (as I believe it should be), then its root == self
. It could be None
, but not much gain I guess.
Besides, why is root required to get a ewmh-Window object?
2 reasons:
display
instance, a connection to the server anyway, and root has such reference. I think you also need the sno
, but maybe not. Not sure if in python-lib
distinct screens can have windows using the same ID. Possibly not. But RootWindow certainly requires sno
, and it is a Window too.send_message()
in EWMH spec is always to the Root Window. So a regular window needs a reference to its root window in order to use some of set_*
methods in the specThe former might be seen as an implementation detail of mine, but the letter derives from the spec. Unless none of the "regular window" methods require sending a message to its root.
Time for a full disclaimer here: I still didn't carefully read all of the EWMH spec... I'm reading as I'm implementing the methods (and paused to reply your great points and feedback). Maybe after all methods are implemented some of my initial assumptions will be proven false.
- Can Display, Screen, Root or even (X)window object change? How does this all may affect to the module and what's the best way to react?
My understanding so far is:
In python-xlib
, Xlib.display.Display
class seems to be a high-level wrapper its own Xlib.protocol.display.Display
, which represents a connection to an X Display Server. And that connection can be to a local server (:0
) through a unix socket, or a remote server xxx.server.com:0
using TCP (as when connected via SSH).
In that sense, given the same display_name
string, multiple display
instances would end up connecting to the same X server, so they would be equivalent. Neither me nor python-xlib
has any control of "returning the same object given the same display_name
".
Maybe python-xlib
has such control regarding its Window
if given the same ID. Gotta test if win1.handle is win2.handle
. I currently don't, but I'm considering adding it. They're already equivalent (==
) if same ID, but an is
identity would be welcome.
- Can Display, Screen, Root or even (X)window object change? How does this all may affect to the module and what's the best way to react?
About Screen: it seems not to a first-class citizen of an X Server, but merely an sno
integer you use when talking to a Display
connection. My Root is ultimately taken from a a (presumably static) Display.display.info.roots[sno]
list attribute, so I assume it does not change.
Current status is:
EWMH()
instance represents a connection to an X Server via display
attribute. To change the display means connecting to another (possibly the same) server. But it's another connection. As Atoms are tied to a display, if you want another display, use another EWMH()
instance (and don't mix its atoms!)display
server has a fixed number of screens. Not an object, just an index (the sno
). A root window is tied to that sno
. In the end, this could be simplified to "An X Display Server has Windows, all have IDs, some of them have no parent, we call them roots, and as such are indexed in the Display by what we call an sno
"EWMH()
instance doubles as a Root Window instance, it also have a fixed sno
. So, currently, given a display_name
you have a connection, and given an sno
in that connection you define your Root Window instance. And since a Root Window is the the entry point for all EWMH methods (and to access other Windows with root._NET_ACTIVE_WINDOW
or root._NET_CLIENT_LIST
), my EWMH()
instance has (immutable) display
and screen_number
so it represents a single Root and its Screen in a given Display connection.
- Perhaps this may happen in a multi-display environment (https://wiki.archlinux.org/title/multihead)? or after some actions on the window (e.g. after unmap it or alike?)
AFAIK, unmap
alone only makes a Window invisible, it does not destroy the object in the X Server.
Also remember "display" as in "a monitor" is not the same as X Display Server. To understand the X point of view and terminology, this helped me a lot:
The above image doesn't even include DE/WM concepts such as "viewport", "workspaces", "stretching a desktop over multiple monitors", "virtual desktops in the same monitor", "panning screen over a larger-than-the-monitor desktop size", etc. Apart from Xinerama and possibly other X extensions, the X Server has no such concepts. I guess.
EWMH does, but in a very confusing way. I'm still not sure what it means by viewport, desktop, large desktops, virtual desktops, etc. But it seems they all operate in the same Root Window (and hence in the same X screen and display)
- Is "id" inmutable so we can fully rely on it?
I believe so, apart from ID re-usage/recycling in the X server. Why I think is beyond my scope. Same ID means same Window. Different ID is different window. That's how you "speak" with the X Server.
- Start a daemon that detects if any of these values above have changed, renewing the vars and objects as required or somehow warning
Or simply returning the same Window
instance given the same Window ID. Which python-xlib
might already do, and regardless that's something I should/must do anyway. Perhaps caching all Window instances created by the ewmh/root
instance (as windows are tied to a display connection). Not hard to do, maybe just add a __new__()
method in Window
and a corresponding EWMH._windows: Dict[XRid, Window]
- How/when Should Display connection be closed? (Note: To avoid a memory leak [...], at the end of the program, you should disconnect the display server using the XCloseDisplay function)
Hum, never I guess? It's a local socket (or TCP) connection, hopefully properly handled and closed by python-xlib
in Display.display.__del__
. If not, there's python's garbage collection. If python-xlib
is not closing the socket when the object is destroyed, that's a python-xlib
bug.
- EWMH has nothing to do with events, but I see lots of questions regarding these. Perhaps we can consider to add a separate sub-module to work with them!
X has a solid concept of events, but AFAIK EWMH spec doesn't touch this. Well, the methods end up using events, as the "Send a Client Message to the Root Window" is actually sending an event of type Client Message. And "setting a property" involves a ton of requests and replies from the X server, all using events.
... but that's all properly abstracted away by python-xlib
with its Window.send_event()
, Window.get_property()
and Window.change_property()
, and so far it seems it's all EWMH needs.
Sorry if it's too long or too useless if some of these doubts come from my xlib ignorance and not real cases!!! On the other hand, I can not promisse that more of these doubts/ideas will arise in the next days when I start to intensively test ewmh-client... it's a menace!! HAHAHAHAHA!
Please, keep'em coming!!!! Everything is open for change, debate, and your feedback is invaluable and much, much appreciated!!! Consider this library a complete draft, send any ideas you have, feel free to shape its API in whatever direction you want.
Hi! So happy to see you finally decided to go ahead with this! My congrats for that!
Let me write here some general issues that I hope can be useful to you in order to focus the module in the best and more useful way Some of these arose to myself with PyWinCtl and also when I re-coded some ewmh functions (though, in this case, these functions where intended to be used by myself exclusively, so it's not the same case!!).
Sorry if it's too long or too useless if some of these doubts come from my xlib ignorance and not real cases!!! On the other hand, I can not promisse that more of these doubts/ideas will arise in the next days when I start to intensively test ewmh-client... it's a menace!! HAHAHAHAHA!