MestreLion / ewmh-client

EWMH (Extended Window Manager Hints) Client API
GNU General Public License v3.0
2 stars 0 forks source link

Some general preliminary thoughts! #1

Closed Kalmat closed 1 year ago

Kalmat commented 1 year ago

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!

MestreLion commented 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:

And here's how I arrived at this (admittedly unusual) model:

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.

MestreLion commented 1 year ago

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:

  • 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:

The 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.

MestreLion commented 1 year ago
  • 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:

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.

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:

image

MestreLion commented 1 year ago

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.