From 363865a8d5a304892fbedce58003028b0b04578c Mon Sep 17 00:00:00 2001 From: Kalmat Date: Wed, 17 Apr 2024 10:58:43 +0200 Subject: [PATCH] v0.7 pre-release --- .gitignore | 4 +- CHANGES.txt | 6 +- README.md | 74 +- dist/PyWinCtl-dev0.1-py3-none-any.whl | Bin 218042 -> 0 bytes docs/requirements.txt | 8 - setup.py | 7 +- src/pywinctl/__init__.py | 9 - src/pywinctl/_main.py | 8 +- src/pywinctl/_pywinctl_linux.py | 46 +- src/pywinctl/_pywinctl_macos.py | 1187 ++++----- src/pywinctl/_pywinctl_win.py | 36 +- src/pywinctl/ewmhlib/Props/__init__.py | 5 - src/pywinctl/ewmhlib/Props/_props.py | 141 -- src/pywinctl/ewmhlib/Structs/__init__.py | 4 - src/pywinctl/ewmhlib/Structs/_structs.py | 123 - src/pywinctl/ewmhlib/__init__.py | 30 - src/pywinctl/ewmhlib/_ewmhlib.py | 2882 ---------------------- src/pywinctl/ewmhlib/_main.py | 12 - src/pywinctl/ewmhlib/py.typed | 1 - tests/test_MacNSWindow.py | 311 --- tests/test_pywinctl.py | 691 ++---- typings/AppKit.pyi | 4 + 22 files changed, 818 insertions(+), 4771 deletions(-) delete mode 100644 dist/PyWinCtl-dev0.1-py3-none-any.whl delete mode 100644 docs/requirements.txt delete mode 100644 src/pywinctl/ewmhlib/Props/__init__.py delete mode 100644 src/pywinctl/ewmhlib/Props/_props.py delete mode 100644 src/pywinctl/ewmhlib/Structs/__init__.py delete mode 100644 src/pywinctl/ewmhlib/Structs/_structs.py delete mode 100644 src/pywinctl/ewmhlib/__init__.py delete mode 100644 src/pywinctl/ewmhlib/_ewmhlib.py delete mode 100644 src/pywinctl/ewmhlib/_main.py delete mode 100644 src/pywinctl/ewmhlib/py.typed delete mode 100644 tests/test_MacNSWindow.py diff --git a/.gitignore b/.gitignore index f98be71..9374f20 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,8 @@ instance/ .coverage htmlcov/ -dist/ build/ *.egg-info/ .vscode/ -/make_dev_wheel.bat +dist/ +*.whl diff --git a/CHANGES.txt b/CHANGES.txt index b8f0e2a..83d3c90 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,7 +1,7 @@ 0.4, 2023/10/11 -- ALL: Added getMonitor() as alias for getDisplay() - LINUX: Fixed getAllMonitors() returns empty list if XDG_CURRENT_DESKTOP is not set, improved getClientFrame() and getExtraFrameSize() by properly using _NET_EXTENTS and GTK_EXTENTS, Added a new Window.LEGACY_NAME="WM_NAME" property (for apps not setting _NET_WM_NAME) - (ewmhlib): Fixed Mint returning str (not bytes) in some properties. Fixed Mint not having get_monitors() method in xrandr extension. - MACOS: Fixed lowerWindow(), raiseWindow() and isAlive. Fixed test_pywinctl.py to avoid crashing in small screens + LINUX: Added ewmhlib as separate module. Fixed getAllMonitors() returns empty list if XDG_CURRENT_DESKTOP is not set. Improved getClientFrame() and getExtraFrameSize() by properly using _NET_EXTENTS and GTK_EXTENTS, Added a new Window.LEGACY_NAME="WM_NAME" property (for apps not setting _NET_WM_NAME) + (ewmhlib): Fixed Mint returning str (not bytes) in some properties. Fixed Mint not having get_monitors() method in xrandr extension. + MACOS: Removed MacOSNSWindow. Fixed lowerWindow(), raiseWindow() and isAlive. Fixed test_pywinctl.py to avoid crashing in small screens 0.3, 2023/09/20 -- LINUX: Improved Wayland support for some apps which (surprisingly) work using X11/XOrg (thanks to SamuMazzi for his help!). 0.2, 2023/09/09 -- LINUX: Added experimental Wayland support (only if unsafe mode enabled and only for some apps). Fixed ewmhlib freezing in Wayland when connecting to display ":1", and added some performance improvements diff --git a/README.md b/README.md index a8ce21d..19eac17 100644 --- a/README.md +++ b/README.md @@ -25,10 +25,10 @@ My most sincere thanks and acknowledgement. amongst many others (see AUTHORS.txt ## Window Features -There are three kind of function to be used within PyWinCtl: +There are three kinds of functions to be used within PyWinCtl: - General, independent functions: These functions can be directly invoked at module level, without the need of referencing a Window object - Window class: - - Methods: You need a Window object to control or get info on the target window on screen. It's possible to get a Window object by using any of the general methods (e.g. getActiveWidow() or getWindowsWithTitle()). You can also use windows id, as returned by PyQt's self.winId() or tkinter's root.frame(), which is very handy to get the Window object for your own application. + - Methods: You need a Window object to control or get info on the target window on screen. It is possible to get a Window object by using any of the general methods (e.g. getActiveWidow() or getWindowsWithTitle()). You can also use the window id, as returned by PyQt's self.winId() or tkinter's root.frame(), which is very handy to get the Window object for your own application. - Properties: Window attributes, getters and setters, that also require to use a Window object A very simple example: @@ -54,43 +54,41 @@ A very simple example: These functions are available at the moment, in all three platforms (Windows, Linux and macOS) -| General, independent functions: | Window class methods: | Window class properties: | -|:------------------------------------------------------------------:|:----------------------------------------------------:|:-------------------------------------------------------------------:| -| [getActiveWindow](docstrings.md#getactivewindow) | [close](docstrings.md#close) | (GET) [title](docstrings.md#title) | -| [getActiveWindowTitle](docstrings.md#getactivewindowtitle) | [minimize](docstrings.md#minimize) | (GET) [updatedTitle](docstrings.md#updatedtitle) (MacOSWindow only) | -| [getAllWindows](docstrings.md#getallwindows) | [maximize](docstrings.md#maximize) | (GET) [isMaximized](docstrings.md#ismaximized) | -| [getAllTitles](docstrings.md#getalltitles) | [restore](docstrings.md#restore) | (GET) [isMinimized](docstrings.md#isminimized) | -| [getWindowsWithTitle](docstrings.md#getwindowswithtitle) | [hide](docstrings.md#hide) | (GET) [isActive](docstrings.md#isactive) | -| [getAllAppsNames](docstrings.md#getallappsnames) | [show](docstrings.md#show) | (GET) [isVisible](docstrings.md#isvisible) | -| [getAppsWithName](docstrings.md#getappswithname) | [activate](docstrings.md#activate) | (GET) [isAlive](docstrings.md#isvisible) | -| [getAllAppsWindowsTitles](docstrings.md#getallappswindowstitles) | [resize / resizeRel](docstrings.md#resize) | (GET/SET) position (x, y) | -| [getWindowsAt](docstrings.md#getwindowsat) | [resizeTo](docstrings.md#resizeto) | (GET/SET) left (x) | -| [getTopWindowAt](docstrings.md#gettopwindowat) | [move / moveRel](docstrings.md#move) | (GET/SET) top (y) | -| [displayWindowsUnderMouse](docstrings.md#displaywindowsundermouse) | [moveTo](docstrings.md#moveto) | (GET/SET) right (x) | -| [version](docstrings.md#version) | [raiseWindow](docstrings.md#raisewindow) | (GET/SET) bottom (y) | -| [checkPermissions](docstrings.md#checkpermissions) (macOS only) | [lowerWindow](docstrings.md#lowerwindow) | (GET/SET) topleft (x, y) | -| | [alwaysOnTop](docstrings.md#alwaysontop) | (GET/SET) topright (x, y) | -| | [alwaysOnBottom](docstrings.md#alwaysonbottom) | (GET/SET) bottomleft (x, y) | -| | [sendBehind](docstrings.md#sendbehind) | (GET/SET) bottomright (x, y) | -| | [acceptInput](docstrings.md#acceptinput) | (GET/SET) midtop (x, y) | -| | [getAppName](docstrings.md#getappname) | (GET/SET) midleft (x, y) | -| | [getHandle](docstrings.md#gethandle) | (GET/SET) midbotton (x, y) | -| | [getParent](docstrings.md#getparent) | (GET/SET) midright (x, y) | -| | [setParent](docstrings.md#setparent) | (GET/SET) center (x, y) | -| | [getChildren](docstrings.md#getchildren) | (GET/SET) centerx (x, y) | -| | [isParent](docstrings.md#isparent) | (GET/SET) centery (x, y) | -| | [isChild](docstrings.md#ischild) | (GET/SET) size (width, height) | -| | [getDisplay](docstrings.md#getdisplay) | (GET/SET) width | -| | [getExtraFrameSize](docstrings.md#getextraframesize) | (GET/SET) height | -| | [getClientFrame](docstrings.md#getclientframe) | (GET/SET) box (x, y, width, height) | -| | | (GET/SET) rect (x, y, right, bottom) | - +| General, independent functions: | Window class methods: | Window class properties: | +|:------------------------------------------------------------------:|:----------------------------------------------------:|:------------------------------------------------------------------------------------------:| +| [getActiveWindow](docstrings.md#getactivewindow) | [close](docstrings.md#close) | (GET) [title](docstrings.md#title) | +| [getActiveWindowTitle](docstrings.md#getactivewindowtitle) | [minimize](docstrings.md#minimize) | (GET) [updatedTitle](docstrings.md#updatedtitle) (MacOSWindow only) | +| [getAllWindows](docstrings.md#getallwindows) | [maximize](docstrings.md#maximize) | (GET) [isMaximized](docstrings.md#ismaximized) | +| [getAllTitles](docstrings.md#getalltitles) | [restore](docstrings.md#restore) | (GET) [isMinimized](docstrings.md#isminimized) | +| [getWindowsWithTitle](docstrings.md#getwindowswithtitle) | [hide](docstrings.md#hide) | (GET) [isActive](docstrings.md#isactive) | +| [getAllAppsNames](docstrings.md#getallappsnames) | [show](docstrings.md#show) | (GET) [isVisible](docstrings.md#isvisible) | +| [getAppsWithName](docstrings.md#getappswithname) | [activate](docstrings.md#activate) | (GET) [isAlive](docstrings.md#isvisible) | +| [getAllAppsWindowsTitles](docstrings.md#getallappswindowstitles) | [resize / resizeRel](docstrings.md#resize) | **Position / Size** (inherited from [PyWinBox module](https://github.com/Kalmat/PyWinBox)) | +| [getWindowsAt](docstrings.md#getwindowsat) | [resizeTo](docstrings.md#resizeto) | (GET/SET) position (x, y) | +| [getTopWindowAt](docstrings.md#gettopwindowat) | [move / moveRel](docstrings.md#move) | (GET/SET) left (x) | +| [displayWindowsUnderMouse](docstrings.md#displaywindowsundermouse) | [moveTo](docstrings.md#moveto) | (GET/SET) top (y) | +| [version](docstrings.md#version) | [raiseWindow](docstrings.md#raisewindow) | (GET/SET) right (x) | +| [checkPermissions](docstrings.md#checkpermissions) (macOS only) | [lowerWindow](docstrings.md#lowerwindow) | (GET/SET) bottom (y) | +| | [alwaysOnTop](docstrings.md#alwaysontop) | (GET/SET) topleft (x, y) | +| | [alwaysOnBottom](docstrings.md#alwaysonbottom) | (GET/SET) topright (x, y) | +| | [sendBehind](docstrings.md#sendbehind) | (GET/SET) bottomleft (x, y) | +| | [acceptInput](docstrings.md#acceptinput) | (GET/SET) bottomright (x, y) | +| | [getAppName](docstrings.md#getappname) | (GET/SET) midtop (x, y) | +| | [getHandle](docstrings.md#gethandle) | (GET/SET) midleft (x, y) | +| | [getParent](docstrings.md#getparent) | (GET/SET) midbotton (x, y) | +| | [setParent](docstrings.md#setparent) | (GET/SET) midright (x, y) | +| | [getChildren](docstrings.md#getchildren) | (GET/SET) center (x, y) | +| | [isParent](docstrings.md#isparent) | (GET/SET) centerx (x) | +| | [isChild](docstrings.md#ischild) | (GET/SET) centery (y) | +| | [getDisplay](docstrings.md#getdisplay) | (GET/SET) size (width, height) | +| | [getExtraFrameSize](docstrings.md#getextraframesize) | (GET/SET) width | +| | [getClientFrame](docstrings.md#getclientframe) | (GET/SET) height | +| | | (GET/SET) box (x, y, width, height | +| | | (GET/SET) rect (x, y, right, bottom) | ***Important macOS notice *** -macOS doesn't "like" controlling windows from other apps, so there are two separate classes you can use: -- To control your own application's windows: MacOSNSWindow() is based on NSWindow Objects (you have to pass the NSApp() and the NSWindow() objects reference). -- To control other applications' windows: MacOSWindow() is based on Apple Script, so it is non-standard, slower and, in some cases, tricky (uses window name as reference, which may change or be duplicate), but it's working fine in most cases. You will likely need to grant permissions on Settings -> Security&Privacy -> Accessibility. ***Notice some applications will have limited Apple Script support or no support at all, so some or even all methods may fail!*** +macOS doesn't "like" controlling windows from other apps. MacOSWindow() class is based on Apple Script, so it is non-standard, slower and, in some cases, tricky (uses window name as reference, which may change or be duplicate), but it's working fine in most cases. You will likely need to grant permissions on Settings -> Security&Privacy -> Accessibility. ***Notice some applications will have limited Apple Script support or no support at all, so some or even all methods may fail!*** ***Important Linux notice *** @@ -280,7 +278,3 @@ To test this module on your own system, cd to "tests" folder and run: python3 test_pywinctl.py -MacOSNSWindow class and methods can be tested by running this, also on "tests" folder: - - python3 test_MacNSWindow.py - diff --git a/dist/PyWinCtl-dev0.1-py3-none-any.whl b/dist/PyWinCtl-dev0.1-py3-none-any.whl deleted file mode 100644 index c9f1e9eea21df07a294c47f0cf15a1c72a3f8964..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 218042 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

9*H8btxaLnkM+DJJ%c4G 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

%^Av{+8w+R=9#1kpjlL$)R$SB<}k{rChHs=1P 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 JE)LEK@DIZsmX81c diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 9f7d947..0000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -# Requirements needed for development -# Run: `pip install -r docs/requirements.txt` -# Build/Production requirements are found in ../setup.py -# Run: `pip install -e .` -# -# installs dependencies from setup.py, and the package itself, -# in editable mode --e .[dev] diff --git a/setup.py b/setup.py index fcc5633..fe4642d 100644 --- a/setup.py +++ b/setup.py @@ -33,15 +33,16 @@ license='BSD 3', packages=find_packages(where='src'), package_dir={'': 'src'}, - package_data={"pywinctl": ["py.typed"], "ewmhlib": ["py.typed"]}, + package_data={"pywinctl": ["py.typed"]}, test_suite='tests', install_requires=[ "pywin32>=302; sys_platform == 'win32'", "python-xlib>=0.21; sys_platform == 'linux'", + "ewmhlib>=0.1; sys_platform == 'linux'", "pyobjc>=8.1; sys_platform == 'darwin'", "typing_extensions>=4.4.0", - "pywinbox>=0.6", - "pymonctl>=0.6" + "pywinbox>=0.7", + "pymonctl>=0.8" ], extras_require={ 'dev': [ diff --git a/src/pywinctl/__init__.py b/src/pywinctl/__init__.py index c76e29c..f1adcda 100644 --- a/src/pywinctl/__init__.py +++ b/src/pywinctl/__init__.py @@ -10,12 +10,6 @@ "getAllScreens", "getScreenSize", "getWorkArea", "getMousePos" ] -import sys - -# Mac only -if sys.platform == "darwin": - __all__ += ["NSWindow"] - __version__ = "0.4" @@ -30,6 +24,3 @@ def version(numberOnly: bool = True) -> str: getTopWindowAt, getWindowsAt, displayWindowsUnderMouse, getAllScreens, getScreenSize, getWorkArea, getMousePos ) - -if sys.platform == "darwin": - from ._main import NSWindow diff --git a/src/pywinctl/_main.py b/src/pywinctl/_main.py index 18d7eaa..5d96fe4 100644 --- a/src/pywinctl/_main.py +++ b/src/pywinctl/_main.py @@ -564,7 +564,7 @@ def setTryToFind(self, tryToFind: bool): IMPORTANT: - - It will have no effect in other platforms (Windows and Linux) and classes (MacOSNSWindow) + - It will have no effect in other platforms (Windows and Linux) - This behavior is deactivated by default, so you need to explicitly activate it :param tryToFind: set to ''True'' to try to find a similar title. Set to ''False'' to deactivate this behavior @@ -920,7 +920,7 @@ def getScreenSize(name: str = ""): :return: Size struct or None """ import warnings - warnings.warn('getScreenSize() is deprecated. Use getSize() from PyMonCtl module instead', + warnings.warn('getScreenSize() is deprecated. Use monitor.getSize() from PyMonCtl module instead', DeprecationWarning, stacklevel=2) for monitor in getAllMonitors(): if (name and name == monitor.name) or (not name and monitor.isPrimary): @@ -937,7 +937,7 @@ def getWorkArea(name: str = ""): :return: Rect struct or None """ import warnings - warnings.warn('getWorkArea() is deprecated. Use getWorkArea() from PyMonCtl module instead', + warnings.warn('getWorkArea() is deprecated. Use monitor.getWorkArea() from PyMonCtl module instead', DeprecationWarning, stacklevel=2) for monitor in getAllMonitors(): if (name and name == monitor.name) or (not name and monitor.isPrimary): @@ -988,7 +988,7 @@ def displayWindowsUnderMouse(xOffset: int = 0, yOffset: int = 0) -> None: if sys.platform == "darwin": - from ._pywinctl_macos import (MacOSNSWindow as NSWindow, MacOSWindow as Window, checkPermissions, getActiveWindow, + from ._pywinctl_macos import (MacOSWindow as Window, checkPermissions, getActiveWindow, getActiveWindowTitle, getAllAppsNames, getAllAppsWindowsTitles, getAllTitles, getAllWindows, getAppsWithName, getWindowsWithTitle, getTopWindowAt, getWindowsAt diff --git a/src/pywinctl/_pywinctl_linux.py b/src/pywinctl/_pywinctl_linux.py index 77f8d9d..fd47bef 100644 --- a/src/pywinctl/_pywinctl_linux.py +++ b/src/pywinctl/_pywinctl_linux.py @@ -22,9 +22,9 @@ import Xlib.ext from Xlib.xobject.drawable import Window as XWindow -from ._main import BaseWindow, Re, _WatchDog, _findMonitorName, displayWindowsUnderMouse -from .ewmhlib import EwmhWindow, EwmhRoot, defaultEwmhRoot, Props -from .ewmhlib._ewmhlib import _xlibGetAllWindows +from ._main import BaseWindow, Re, _WatchDog, _findMonitorName +from ewmhlib import EwmhWindow, EwmhRoot, defaultEwmhRoot, Props +from ewmhlib._ewmhlib import _xlibGetAllWindows from pywinbox import Size, Point, Rect, pointInBox @@ -72,7 +72,7 @@ def getActiveWindow() -> Optional[LinuxWindow]: # https://www.reddit.com/r/gnome/comments/d8x27b/is_there_a_program_that_can_show_keypresses_on/ win_id: Union[str, int] = 0 if os.environ.get('XDG_SESSION_TYPE', '').lower() == "wayland": - # swaymsg -t get_tree | jq '.. | select(.type?) | select(.focused==true).pid' -> Not working (socket issue) + # IN SWAY: swaymsg -t get_tree | jq '.. | select(.type?) | select(.focused==true).pid' # pynput / mouse --> Not working (no global events allowed, only application events) _, activeWindow = _WgetAllWindows() if activeWindow: @@ -166,7 +166,8 @@ def getWindowsWithTitle(title: Union[str, re.Pattern[str]], app: Optional[Tuple[ lower = True if isinstance(title, re.Pattern): title = title.pattern - title = title.lower() + else: + title = title.lower() for win in getAllWindows(): if win.title and Re._cond_dic[condition](title, win.title.lower() if lower else win.title, flags) \ and (not app or (app and win.getAppName() in app)): @@ -219,7 +220,8 @@ def getAppsWithName(name: Union[str, re.Pattern[str]], condition: int = Re.IS, f lower = True if isinstance(name, re.Pattern): name = name.pattern - name = name.lower() + else: + name = name.lower() for title in getAllAppsNames(): if title and Re._cond_dic[condition](name, title.lower() if lower else title, flags): matches.append(title) @@ -759,6 +761,11 @@ def getDisplay(self) -> List[str]: """ Get display names in which current window space is mostly visible + On Windows, the list will contain up to one display (displays can not overlap), whilst in Linux and macOS, the + list may contain several displays. + + If you need to get info or control the monitor, use these names as input to PyMonCtl's findMonitorWithName(). + :return: display name as list of strings or empty (couldn't retrieve it or window is off-screen) """ x, y = self.center @@ -846,30 +853,3 @@ def _isMapped(self) -> bool: # Returns ``True`` if the window is currently mapped state: int = self._xWin.get_attributes().map_state return bool(state != Xlib.X.IsUnmapped) - - -def main(): - """Run this script from command-line to get windows under mouse pointer""" - from pymonctl import getAllMonitorsDict, Monitor, findMonitorWithName - print("PLATFORM:", sys.platform) - print("ALL WINDOWS", getAllTitles()) - print("ALL APPS & WINDOWS", getAllAppsWindowsTitles()) - print("MONITORS:", getAllMonitorsDict()) - npw = getActiveWindow() - if npw is None: - print("ACTIVE WINDOW:", None) - else: - print("ACTIVE WINDOW:", npw.title, "/", npw.box) - dpy = npw.getDisplay() - print("DISPLAY", dpy) - if dpy: - monitor: Monitor = findMonitorWithName(dpy[0]) - if monitor: - print("SCREEN SIZE:", monitor.size) - print("WORKAREA:", monitor.workarea) - print() - displayWindowsUnderMouse() - - -if __name__ == "__main__": - main() diff --git a/src/pywinctl/_pywinctl_macos.py b/src/pywinctl/_pywinctl_macos.py index e708c1a..e64dd93 100644 --- a/src/pywinctl/_pywinctl_macos.py +++ b/src/pywinctl/_pywinctl_macos.py @@ -15,20 +15,19 @@ import threading import time from collections.abc import Iterable -from typing import Any, overload, cast, Sequence, Dict, Optional, Union, List, Tuple +from typing import Any, cast, Sequence, Dict, Optional, Union, List, Tuple from typing_extensions import TypeAlias, TypedDict, Literal import AppKit import Quartz -from ._main import BaseWindow, Re, _WatchDog, _findMonitorName, displayWindowsUnderMouse +from ._main import BaseWindow, Re, _WatchDog, _findMonitorName from pywinbox import Size, Point, Rect, pointInBox Incomplete: TypeAlias = Any Attribute: TypeAlias = Sequence['Tuple[str, str, bool, str]'] -WS = AppKit.NSWorkspace.sharedWorkspace() WAIT_ATTEMPTS = 10 WAIT_DELAY = 0.025 # Will be progressively increased on every retry @@ -65,139 +64,108 @@ def checkPermissions(activate: bool = False) -> bool: ret = ret.replace("\n", "") return ret == "true" -@overload -def getActiveWindow(app: AppKit.NSApplication) -> Optional[MacOSNSWindow]: ... -@overload -def getActiveWindow(app: None = ...) -> Optional[MacOSWindow]: ... -def getActiveWindow(app: Optional[AppKit.NSApplication] = None): +def getActiveWindow() -> Optional[MacOSWindow]: """ Get the currently active (focused) Window - :param app: (optional) NSApp() object. If passed, returns the active (main/key) window of given app :return: Window object or None """ - if not app: - # app = WS.frontmostApplication() # This fails after using .activateWithOptions_()?!?!?! - cmd = """on run - set appName to "" - set appID to "" - set winData to {} - try - tell application "System Events" - set appName to name of first application process whose frontmost is true - set appID to unix id of application process appName - tell application process appName - set winName to name of (first window whose value of attribute "AXMain" is true) - end tell + # app = AppKit.NSWorkspace.sharedWorkspace().frontmostApplication() # This fails after using .activateWithOptions_()?!?!?! + cmd = """on run + set appName to "" + set appID to "" + set winName to "" + try + tell application "System Events" + set frontApp to first application process whose frontmost is true + set frontAppName to name of frontApp + set appID to unix id of frontApp + tell process frontAppName + set winName to value of attribute "AXTitle" of (1st window whose value of attribute "AXMain" is true) end tell - end try - return {appID, winName} - end run""" - proc = subprocess.Popen(['osascript'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, encoding='utf8') - ret, err = proc.communicate(cmd) - entries = ret.replace("\n", "").split(", ") - try: - appID = entries[0] - # Thanks to Anthony Molinaro (djnym) for pointing out this bug and provide the solution!!! - # sometimes the title of the window contains ',' characters, so just get the first entry as the appName and join the rest - # back together as a string - title = ", ".join(entries[1:]) - if appID: # and title: - activeApps = _getAllApps() - for a in activeApps: - if str(a.processIdentifier()) == appID: - return MacOSWindow(a, title) - except Exception as e: - print(e) - else: - for win in app.orderedWindows(): # .keyWindow() / .mainWindow() not working?!?!?! - return MacOSNSWindow(app, win) + end tell + end try + return {appID, winName} + end run""" + proc = subprocess.Popen(['osascript'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, encoding='utf8') + ret, err = proc.communicate(cmd) + entries = ret.replace("\n", "").split(", ") + appID = entries[0] + # Thanks to Anthony Molinaro (djnym) for pointing out this bug and provide the solution!!! + # sometimes the title of the window contains ',' characters, so just get the first entry as the appName and + # join the rest back together as a string + title = ", ".join(entries[1:]) + if appID: # and title: + activeApps = _getAllApps() + for a in activeApps: + if str(a.processIdentifier()) == appID: + return MacOSWindow(a, title) return None -def getActiveWindowTitle(app: Optional[AppKit.NSApplication] = None) -> str: +def getActiveWindowTitle() -> str: """ Get the title of the currently active (focused) Window - :param app: (optional) NSApp() object. If passed, returns the title of the main/key window of given app :return: window title as string or empty """ - win = getActiveWindow(app) + win = getActiveWindow() if win: return win.title or "" else: return "" -@overload -def getAllWindows(app: AppKit.NSApplication) -> List[MacOSNSWindow]: ... -@overload -def getAllWindows(app: None = ...) -> List[MacOSWindow]: ... -def getAllWindows(app:Optional[AppKit.NSApplication] = None): +def getAllWindows() -> List[MacOSWindow]: """ Get the list of Window objects for all visible windows - :param app: (optional) NSApp() object. If passed, returns the Window objects of all windows of given app :return: list of Window objects """ # TODO: Find a way to return windows as per the stacking order (not sure if it is even possible!) - if not app: - windows: List[MacOSWindow] = [] - activeApps = _getAllApps() - titleList = _getWindowTitles() - for item in titleList: - try: - pID = item[0] - title = item[1] - except: - continue - for activeApp in activeApps: - if activeApp.processIdentifier() == pID: - windows.append(MacOSWindow(activeApp, title)) - break - return windows - else: - nsWindows: List[MacOSNSWindow] = [] - for win in app.orderedWindows(): - nsWindows.append(MacOSNSWindow(app, win)) - return nsWindows + windows: List[MacOSWindow] = [] + activeApps = _getAllApps() + titleList = _getWindowTitles() + for item in titleList: + try: + pID = item[0] + title = item[1] + except: + continue + for activeApp in activeApps: + if activeApp.processIdentifier() == pID: + windows.append(MacOSWindow(activeApp, title)) + break + return windows -def getAllTitles(app: Optional[AppKit.NSApplication] = None): +def getAllTitles() -> List[str]: """ Get the list of titles of all visible windows - :param app: (optional) NSApp() object. If passed, returns the titles of the windows of given app :return: list of titles as strings """ - if not app: - cmd = """osascript -s 's' -e 'tell application "System Events" - set winNames to {} - try - set winNames to {name of every window} of (every process whose background only is false) - end try - end tell - return winNames'""" - ret = subprocess.check_output(cmd, shell=True).decode(encoding="utf-8").replace("\n", "") \ - .replace('missing value', '"missing value"') \ - .replace("{", "[").replace("}", "]") - res = ast.literal_eval(ret) - matches: List[str] = [] - if len(res) > 0: - for item in res[0]: - for title in item: - matches.append(title) - else: - matches = [win.title for win in getAllWindows(app)] + cmd = """osascript -s 's' -e 'tell application "System Events" + set winNames to {} + try + set winNames to {name of every window} of (every process whose background only is false) + end try + end tell + return winNames'""" + ret = subprocess.check_output(cmd, shell=True).decode(encoding="utf-8").replace("\n", "") \ + .replace('missing value', '"missing value"') \ + .replace("{", "[").replace("}", "]") + res = ast.literal_eval(ret) + matches: List[str] = [] + if len(res) > 0: + for item in res[0]: + for title in item: + matches.append(title) return matches -@overload -def getWindowsWithTitle(title: Union[str, re.Pattern[str]], app: Optional[Tuple[str, ...]] = ..., condition: int = ..., flags: int = ...) -> List[MacOSWindow]: ... -@overload -def getWindowsWithTitle(title: Union[str, re.Pattern[str]], app: AppKit.NSApp, condition: int = ..., flags: int = ...) -> List[MacOSNSWindow]: ... -def getWindowsWithTitle(title: Union[str, re.Pattern[str]], app: Optional[Union[AppKit.NSApp, Tuple[str, ...]]] = None, condition: int = Re.IS, flags: int = 0): +def getWindowsWithTitle(title: Union[str, re.Pattern[str]], condition: int = Re.IS, flags: int = 0) -> List[MacOSWindow]: """ Get the list of window objects whose title match the given string with condition and flags. Use ''condition'' to delimit the search. Allowed values are stored in pywinctl.Re sub-class (e.g. pywinctl.Re.CONTAINS) @@ -217,7 +185,6 @@ def getWindowsWithTitle(title: Union[str, re.Pattern[str]], app: Optional[Union[ - DIFFRATIO -- window title matched using difflib similarity ratio (allowed flags: 0-100. Defaults to 90) :param title: title or regex pattern to match, as string - :param app: NSApp object (NSWindow version) / (optional) tuple of app names (Apple Script version), defaults to ALL (empty list) :param condition: (optional) condition to apply when searching the window. Defaults to ''Re.IS'' (is equal to) :param flags: (optional) specific flags to apply to condition. Defaults to 0 (no flags) :return: list of Window objects @@ -238,25 +205,18 @@ def getWindowsWithTitle(title: Union[str, re.Pattern[str]], app: Optional[Union[ title = title.pattern title = title.lower() - if app is None or isinstance(app, tuple): - matches: List[MacOSWindow] = [] - activeApps = _getAllApps() - titleList = _getWindowTitles() - for item in titleList: - pID = item[0] - winTitle = item[1].lower() if lower else item[1] - if winTitle and Re._cond_dic[condition](title, winTitle, flags): - for a in activeApps: - if (app and a.localizedName() in app) or (a.processIdentifier() == pID): - matches.append(MacOSWindow(a, item[1])) - break - return matches - else: - return [ - win for win - in getAllWindows(app) - if win.title and Re._cond_dic[condition](title, win.title.lower() if lower else win.title, flags) - ] + matches: List[MacOSWindow] = [] + activeApps = _getAllApps() + titleList = _getWindowTitles() + for item in titleList: + pID = item[0] + winTitle = item[1].lower() if lower else item[1] + if winTitle and Re._cond_dic[condition](title, winTitle, flags): + for a in activeApps: + if a.processIdentifier() == pID: + matches.append(MacOSWindow(a, item[1])) + break + return matches def getAllAppsNames() -> List[str]: @@ -351,86 +311,48 @@ def getAllAppsWindowsTitles(): return result -@overload -def getWindowsAt(x: int, y: int, app: AppKit.NSApplication, allWindows: Optional[List[MacOSNSWindow]] = ...) -> List[MacOSNSWindow]: ... -@overload -def getWindowsAt(x: int, y: int, app: None = ..., allWindows: Optional[List[MacOSWindow]] = ...) -> List[MacOSWindow]: ... -@overload -def getWindowsAt(x: int, y: int, app: Optional[AppKit.NSApplication] = ..., allWindows: Optional[Union[List[Union[MacOSWindow, MacOSNSWindow]], List[MacOSNSWindow], List[MacOSWindow]]] = ...) -> Union[List[Union[MacOSWindow, MacOSNSWindow]], List[MacOSNSWindow], List[MacOSWindow]]: ... - -def getWindowsAt(x: int, y: int, app: Optional[AppKit.NSApplication] = None, allWindows: Optional[Union[List[Union[MacOSNSWindow, MacOSWindow]], List[MacOSNSWindow], List[MacOSWindow]]] = None) -> Union[List[Union[MacOSWindow, MacOSNSWindow]], List[MacOSNSWindow], List[MacOSWindow]]: +def getWindowsAt(x: int, y: int, allWindows: Optional[List[MacOSWindow]] = None) -> List[MacOSWindow]: """ Get the list of Window objects whose windows contain the point ``(x, y)`` on screen :param x: X screen coordinate of the window(s) :param y: Y screen coordinate of the window(s) - :param app: (optional) NSApp() object. If passed, returns the list of windows at (x, y) position of given app :param allWindows: (optional) list of window objects (required to improve performance in Apple Script version) :return: list of Window objects """ - windows = allWindows if allWindows else getAllWindows(app) + windows = allWindows if allWindows else getAllWindows() windowBoxGenerator = ((window, window.box) for window in windows) return [ window for (window, box) in windowBoxGenerator if pointInBox(x, y, box)] -@overload -def getTopWindowAt(x: int, y: int, app: AppKit.NSApplication, allWindows: Optional[List[MacOSNSWindow]] = ...) -> Optional[MacOSNSWindow]: ... -@overload -def getTopWindowAt(x: int, y: int, app: None = ..., allWindows: Optional[List[MacOSWindow]] = ...) -> Optional[MacOSWindow]: ... -@overload -def getTopWindowAt(x: int, y: int, app: Optional[AppKit.NSApplication] = ..., allWindows: Optional[Union[List[Union[MacOSWindow, MacOSNSWindow]], List[MacOSNSWindow], List[MacOSWindow]]] = ...) -> Optional[Union[MacOSWindow, MacOSNSWindow]]: ... -def getTopWindowAt(x: int, y: int, app: Optional[AppKit.NSApplication] = None, allWindows: Optional[Union[List[Union[MacOSNSWindow, MacOSWindow]], List[MacOSNSWindow], List[MacOSWindow]]] = None): +def getTopWindowAt(x: int, y: int, allWindows: Optional[List[MacOSWindow]] = None) -> MacOSWindow: """ Get *a* Window object at the point ``(x, y)`` on screen. Which window is not guaranteed. See https://github.com/Kalmat/PyWinCtl/issues/20#issuecomment-1193348238 :param x: X screen coordinate of the window :param y: Y screen coordinate of the window + :param allWindows: list of window objects previously obtained :return: Window object or None """ # Once we've figured out why getWindowsAt may not always return all windows # (see https://github.com/Kalmat/PyWinCtl/issues/21), # we can look into a more efficient implementation that only gets a single window - windows = getWindowsAt(x, y, app, allWindows) + windows = getWindowsAt(x, y, allWindows) return None if len(windows) == 0 else windows[-1] - def _getAllApps(userOnly: bool = True): matches: List[AppKit.NSRunningApplication] = [] - for app in WS.runningApplications(): + for app in AppKit.NSWorkspace.sharedWorkspace().runningApplications(): if not userOnly or (userOnly and app.activationPolicy() == Quartz.NSApplicationActivationPolicyRegular): matches.append(app) return matches -def _getAllWindows(app: Optional[AppKit.NSApplication], userLayer: bool = True): - # Source: https://stackoverflow.com/questions/53237278/obtain-list-of-all-window-titles-on-macos-from-a-python-script/53985082#53985082 - # This returns a list of window info objects, which is static, so needs to be refreshed and takes some time to the OS to refresh it - # Besides, since it may not have kCGWindowName value and the kCGWindowNumber can't be accessed from Apple Script, it's useless - pid: int = app.processIdentifier() if app is not None else 0 - ret: List[dict[Incomplete, int]] = Quartz.CGWindowListCopyWindowInfo(Quartz.kCGWindowListExcludeDesktopElements | Quartz.kCGWindowListOptionOnScreenOnly, Quartz.kCGNullWindowID) - matches: List[dict[Incomplete, int]] = [] - for win in ret: - if (not app and not userLayer) or (userLayer and not app and win.get(Quartz.kCGWindowLayer, "") == 0) or \ - (app and win[Quartz.kCGWindowOwnerPID] == pid): - matches.append(win) - ret = matches - return ret - - -def _getAllAppWindows(app: AppKit.NSApplication, userLayer: bool = True): - windows = _getAllWindows(app) - windowsInApp: List[dict[Incomplete, int]] = [] - for win in windows: - if not userLayer or (userLayer and win[Quartz.kCGWindowLayer] == 0): - windowsInApp.append(win) - return windowsInApp - - def _getAppWindowsTitles(app: AppKit.NSRunningApplication): pid: str = str(app.processIdentifier()) cmd = """on run arg1 @@ -507,7 +429,7 @@ def __init__(self, app: AppKit.NSRunningApplication, title: str): self._appName: str = self.getProcName(self._appPID) if not self._appName: # localizedName() is not recognized in AppleScript for non-English languages - self._appName = app.localizedName() + self._appName: str = app.localizedName() self._initTitle: str = title self._winTitle: str = title # self._parent = self.getParent() # It is slow and not required by now @@ -611,6 +533,7 @@ def close(self, force: bool = False) -> bool: actually closing. This is identical to clicking the X button on the window. + :param force: if ''True'' it will try to close the app if closing the window fails :return: ''True'' if window is closed """ if not self._winTitle: @@ -704,14 +627,14 @@ def maximize(self, wait: bool = False) -> bool: def restore(self, wait: bool = False, user: bool = False) -> bool: """ - If maximized or minimized, restores the window to it's normal size + If maximized or minimized, restores the window to its normal size :param wait: set to ''True'' to confirm action requested (in a reasonable time) :param user: ignored on macOS platform :return: ''True'' if window restored """ if not self._winTitle: - return False + return False if self.isMaximized: if self._use_zoom: @@ -728,18 +651,19 @@ def restore(self, wait: bool = False, user: bool = False) -> bool: stdin=subprocess.PIPE, stdout=subprocess.PIPE, encoding='utf8') ret, err = proc.communicate(cmd) else: - cmd = """on run arg1 + cmd = """on run {arg1, arg2} set appName to arg1 as string + set winName to arg2 as string try tell application "System Events" to tell application process appName - set value of attribute "AXFullScreen" of window 1 to false + set value of attribute "AXFullScreen" of window winName to false end tell end try end run""" - proc = subprocess.Popen(['osascript', '-', self._appName], + proc = subprocess.Popen(['osascript', '-', self._appName, self._winTitle], stdin=subprocess.PIPE, stdout=subprocess.PIPE, encoding='utf8') ret, err = proc.communicate(cmd) - if self.isMinimized: + elif self.isMinimized: cmd = """on run {arg1, arg2} set appName to arg1 as string set winName to arg2 as string @@ -848,6 +772,7 @@ def activate(self, wait: bool = False, user: bool = True) -> bool: set appName to arg1 as string set winName to arg2 as string try + activate application appName tell application "System Events" to tell application process appName set frontmost to true tell window winName to set value of attribute "AXMain" to true @@ -987,7 +912,9 @@ def lowerWindow(self): end tell repeat with procName in procList if procName is not equal to appName then - activate application procName + try + activate application procName + end try end if end repeat end try @@ -1011,8 +938,10 @@ def raiseWindow(self): set winName to arg2 as string try activate application appName - tell application "System Events" to tell process appName - perform action "AXRaise" of window winName + tell application "System Events" + tell application process appName + perform action "AXRaise" of window winName + end tell end tell end try end run""" @@ -1301,7 +1230,8 @@ def visible(self) -> bool: :return: ``True`` if the window is currently visible """ - return bool(self._winTitle and self._winTitle in _getAppWindowsTitles(self._app)) + return self._winTitle and self._winTitle in _getAppWindowsTitles(self._app) + isVisible: bool = cast(bool, visible) # isVisible is an alias for the visible property. @property @@ -1319,7 +1249,8 @@ def isAlive(self) -> bool: set isDone to false try tell application "System Events" to tell application process appName - tell window winName to set isMain to value of attribute "AXMain" + tell window winName + end tell set isDone to true end tell end try @@ -1893,7 +1824,7 @@ def _getaccesskey(self, item_info: Union[Dict[str, Dict[str, str]], Dict[str, _I class _SendTop(threading.Thread): - def __init__(self, hWnd: Union[MacOSWindow, MacOSNSWindow], kill: threading.Event, interval: float = 0.5): + def __init__(self, hWnd: MacOSWindow, kill: threading.Event, interval: float = 0.5): threading.Thread.__init__(self) self._hWnd = hWnd self._kill = kill @@ -1941,446 +1872,443 @@ def restart(self): self.run() -class MacOSNSWindow(BaseWindow): - - def __init__(self, app: AppKit.NSApplication, hWnd: AppKit.NSWindow): - super().__init__(hWnd) - - self._app = app - self._hWnd = hWnd - self._parent = hWnd.parentWindow() - self.watchdog = _WatchDog(self) - - def getExtraFrameSize(self, includeBorder: bool = True) -> Tuple[int, int, int, int]: - """ - Get the extra space, in pixels, around the window, including or not the border - - :param includeBorder: set to ''False'' to avoid including borders - :return: (left, top, right, bottom) frame size as a tuple of int - """ - borderWidth = 0 - if includeBorder: - ret = self._hWnd.contentRectForFrameRect_(self._hWnd.frame()) - frame = self._hWnd.screen().convertRectToBacking_(ret) - borderWidth = frame.origin.x - self.left - frame = (borderWidth, borderWidth, borderWidth, borderWidth) - return frame - - def getClientFrame(self): - """ - Get the client area of window, as a Rect (x, y, right, bottom) - Notice that scroll bars will be included within this area - - :return: Rect struct - """ - ret = self._hWnd.contentRectForFrameRect_(self._hWnd.frame()) - frame = self._hWnd.screen().convertRectToBacking_(ret) - x = int(frame.origin.x) - y = int(frame.origin.y) - w = int(frame.size.width) - h = int(frame.size.height) - r = x + w - b = y + h - return Rect(x, y, r, b) - - def __repr__(self): - return '%s(hWnd=%s)' % (self.__class__.__name__, self._hWnd) - - def __eq__(self, other: object): - return isinstance(other, MacOSNSWindow) and self._hWnd == other._hWnd - - def close(self) -> bool: - """ - Closes this window. This may trigger "Are you sure you want to - quit?" dialogs or other actions that prevent the window from - actually closing. This is identical to clicking the X button on the - window. - - :return: ''True'' if window is closed - """ - return self._hWnd.performClose_(self._app) - - def minimize(self, wait: bool = False) -> bool: - """ - Minimizes this window - - :param wait: set to ''True'' to confirm action requested (in a reasonable time) - :return: ''True'' if window minimized - """ - if not self.isMinimized: - self._hWnd.performMiniaturize_(self._app) - retries = 0 - while wait and retries < WAIT_ATTEMPTS and not self.isMinimized: - retries += 1 - time.sleep(WAIT_DELAY * retries) - return self.isMinimized - - def maximize(self, wait: bool = False) -> bool: - """ - Maximizes this window - - :param wait: set to ''True'' to confirm action requested (in a reasonable time) - :return: ''True'' if window maximized - """ - if not self.isMaximized: - self._hWnd.performZoom_(self._app) - retries = 0 - while wait and retries < WAIT_ATTEMPTS and not self.isMaximized: - retries += 1 - time.sleep(WAIT_DELAY * retries) - return self.isMaximized - - def restore(self, wait: bool = False, user: bool = False) -> bool: - """ - If maximized or minimized, restores the window to it's normal size - - :param wait: set to ''True'' to confirm action requested (in a reasonable time) - :param user: ignored on macOS platform - :return: ''True'' if window restored - """ - self.activate(wait=True) - if self.isMaximized: - self._hWnd.performZoom_(self._app) - if self.isMinimized: - self._hWnd.deminiaturize_(self._app) - retries = 0 - while wait and retries < WAIT_ATTEMPTS and (self.isMinimized or self.isMaximized): - retries += 1 - time.sleep(WAIT_DELAY * retries) - return not self.isMaximized and not self.isMinimized - - def show(self, wait: bool = False) -> bool: - """ - If hidden or showing, shows the window on screen and in title bar - - :param wait: set to ''True'' to wait until action is confirmed (in a reasonable time lap) - :return: ''True'' if window showed - """ - self.activate(wait=wait) - retries = 0 - while wait and retries < WAIT_ATTEMPTS and not self.visible: - retries += 1 - time.sleep(WAIT_DELAY * retries) - return self.visible - - def hide(self, wait: bool = False) -> bool: - """ - If hidden or showing, hides the window from screen and title bar - - :param wait: set to ''True'' to wait until action is confirmed (in a reasonable time lap) - :return: ''True'' if window hidden - """ - self._hWnd.orderOut_(self._app) - retries = 0 - while wait and retries < WAIT_ATTEMPTS and self.visible: - retries += 1 - time.sleep(WAIT_DELAY * retries) - return not self.visible - - def activate(self, wait: bool = False, user: bool = True) -> bool: - """ - Activate this window and make it the foreground (focused) window - - :param wait: set to ''True'' to wait until action is confirmed (in a reasonable time lap) - :param user: ignored on macOS platform - :return: ''True'' if window activated - """ - self._app.activateIgnoringOtherApps_(True) - self._hWnd.makeKeyAndOrderFront_(self._app) - retries = 0 - while wait and retries < WAIT_ATTEMPTS and not self.isActive: - retries += 1 - time.sleep(WAIT_DELAY * retries) - return self.isActive - - def resize(self, widthOffset: int, heightOffset: int, wait: bool = False) -> bool: - """ - Resizes the window relative to its current size - - :param widthOffset: offset to add to current window width as target width - :param heightOffset: offset to add to current window height as target height - :param wait: set to ''True'' to wait until action is confirmed (in a reasonable time lap) - :return: ''True'' if window resized to the given size - """ - box = self.box - return self.resizeTo(box.width + widthOffset, box.height + heightOffset, wait) - - resizeRel = resize # resizeRel is an alias for the resize() method. - - def resizeTo(self, newWidth: int, newHeight: int, wait: bool = False) -> bool: - """ - Resizes the window to a new width and height - - :param newWidth: target window width - :param newHeight: target window height - :param wait: set to ''True'' to wait until action is confirmed (in a reasonable time lap) - :return: ''True'' if window resized to the given size - """ - self.size = Size(newWidth, newHeight) - box = self.box - retries = 0 - while wait and retries < WAIT_ATTEMPTS and box.width != newWidth and box.height != newHeight: - retries += 1 - time.sleep(WAIT_DELAY * retries) - box = self.box - return box.width == newWidth and box.height == newHeight - - def move(self, xOffset: int, yOffset: int, wait: bool = False) -> bool: - """ - Moves the window relative to its current position - - :param xOffset: offset relative to current X coordinate to move the window to - :param yOffset: offset relative to current Y coordinate to move the window to - :param wait: set to ''True'' to wait until action is confirmed (in a reasonable time lap) - :return: ''True'' if window moved to the given position - """ - box = self.box - return self.moveTo(box.left + xOffset, box.top + yOffset, wait) - - moveRel = move # moveRel is an alias for the move() method. - - def moveTo(self, newLeft:int, newTop: int, wait: bool = False) -> bool: - """ - Moves the window to new coordinates on the screen - - :param newLeft: target X coordinate to move the window to - :param newTop: target Y coordinate to move the window to - :param wait: set to ''True'' to wait until action is confirmed (in a reasonable time lap) - :return: ''True'' if window moved to the given position - """ - self.topleft = Point(newLeft, newTop) - box = self.box - retries = 0 - while wait and retries < WAIT_ATTEMPTS and box.left != newLeft and box.top != newTop: - retries += 1 - time.sleep(WAIT_DELAY * retries) - box = self.box - return box.left == newLeft and box.top == newTop - - def alwaysOnTop(self, aot: bool = True) -> bool: - """ - Keeps window on top of all others. - - :param aot: set to ''False'' to deactivate always-on-top behavior - :return: ''True'' if command succeeded - """ - if aot: - ret = self._hWnd.setLevel_(Quartz.kCGScreenSaverWindowLevel) - else: - ret = self._hWnd.setLevel_(Quartz.kCGNormalWindowLevel) - return ret - - def alwaysOnBottom(self, aob: bool = True) -> bool: - """ - Keeps window below of all others, but on top of desktop icons and keeping all window properties - - :param aob: set to ''False'' to deactivate always-on-bottom behavior - :return: ''True'' if command succeeded - """ - if aob: - ret = self._hWnd.setLevel_(Quartz.kCGDesktopWindowLevel) - else: - ret = self._hWnd.setLevel_(Quartz.kCGNormalWindowLevel) - return ret - - def lowerWindow(self) -> bool: - """ - Lowers the window to the bottom so that it does not obscure any sibling windows - - :return: ''True'' if window lowered - """ - # self._hWnd.orderBack_(self._app) # Not working or using it wrong??? - windows = self._app.orderedWindows() - ret = False - if windows: - windows.reverse() - for win in windows: - if win != self._hWnd: - ret = win.makeKeyAndOrderFront_(self._app) - return ret - - def raiseWindow(self, sb: bool = True) -> bool: - """ - Raises the window to top so that it is not obscured by any sibling windows. - - :return: ''True'' if window raised - """ - return self._hWnd.makeKeyAndOrderFront_(self._app) - - def sendBehind(self, sb: bool = True) -> bool: - """ - Sends the window to the very bottom, below all other windows, including desktop icons. - It may also cause that the window does not accept focus nor keyboard/mouse events as well as - make the window disappear from taskbar and/or pager. - - :param sb: set to ''False'' to bring the window back to front - :return: ''True'' if window sent behind desktop icons - """ - # https://stackoverflow.com/questions/4982584/how-do-i-draw-the-desktop-on-mac-os-x - if sb: - ret1 = self._hWnd.setLevel_(Quartz.kCGDesktopWindowLevel - 1) - ret2 = self._hWnd.setCollectionBehavior_(Quartz.NSWindowCollectionBehaviorCanJoinAllSpaces | - Quartz.NSWindowCollectionBehaviorStationary | - Quartz.NSWindowCollectionBehaviorIgnoresCycle) - else: - ret1 = self._hWnd.setLevel_(Quartz.kCGNormalWindowLevel) - ret2 = self._hWnd.setCollectionBehavior_(Quartz.NSWindowCollectionBehaviorDefault | - Quartz.NSWindowCollectionBehaviorParticipatesInCycle | - Quartz.NSWindowCollectionBehaviorManaged) - return ret1 and ret2 - - def acceptInput(self, setTo: bool) -> None: - """Toggles the window transparent to input and focus - - :param setTo: True/False to toggle window transparent to input and focus - :return: None - """ - self._hWnd.setIgnoresMouseEvents_(not setTo) - - def getAppName(self) -> str: - """ - Get the name of the app current window belongs to - - :return: name of the app as string - """ - return self._app.localizedName() - - def getParent(self) -> int: - """ - Get the handle of the current window parent. It can be another window or an application - - :return: handle of the window parent - """ - return self._hWnd.parentWindow() - - def setParent(self, parent) -> bool: - """ - Current window will become child of given parent - WARNIG: Not implemented in AppleScript (not possible in macOS for foreign (other apps') windows) - - :param parent: window to set as current window parent - :return: ''True'' if current window is now child of given parent - """ - parent.addChildWindow(self._hWnd, 1) - return bool(self.isChild(parent)) - - def getChildren(self) -> List[int]: - """ - Get the children handles of current window - - :return: list of handles - """ - return self._hWnd.childWindows() - - def getHandle(self): - """ - Get the current window handle - - :return: window handle - """ - return self._hWnd - - def isParent(self, child: AppKit.NSWindow) -> bool: - """ - Check if current window is parent of given window (handle) - - :param child: handle of the window you want to check if the current window is parent of - :return: ''True'' if current window is parent of the given window - """ - return child.parentWindow() == self._hWnd - isParentOf = isParent # isParentOf is an alias of isParent method - - def isChild(self, parent: int) -> bool: - """ - Check if current window is child of given window/app (handle) - - :param parent: handle of the window/app you want to check if the current window is child of - :return: ''True'' if current window is child of the given window - """ - return parent == self.getParent() - isChildOf = isChild # isChildOf is an alias of isParent method - - def getDisplay(self) -> List[str]: - """ - Get display name in which current window space is mostly visible - - :return: display name as string or empty (couldn't retrieve it or window is offscreen) - """ - x, y = self.center - return _findMonitorName(x, y) - getMonitor = getDisplay # getMonitor is an alias of getDisplay method - - @property - def isMinimized(self) -> bool: - """ - Check if current window is currently minimized - - :return: ``True`` if the window is minimized - """ - return self._hWnd.isMiniaturized() - - @property - def isMaximized(self) -> bool: - """ - Check if current window is currently maximized - - :return: ``True`` if the window is maximized - """ - return self._hWnd.isZoomed() - - @property - def isActive(self) -> bool: - """ - Check if current window is currently the active, foreground window - - :return: ``True`` if the window is the active, foreground window - """ - windows = getAllWindows(self._app) - for win in windows: - return self._hWnd == win._hWnd - return False - - @property - def title(self) -> str: - """ - Get the current window title, as string - - :return: title as a string - """ - return self._hWnd.title() - - @property - def updatedTitle(self) -> str: - """ - WARNING: MacOSWindow ONLY. For MacOSNSWindow, this property returns actual title - - :return: title as a string - """ - return self.title - - @property - def visible(self) -> bool: - """ - Check if current window is visible (minimized windows are also visible) - - :return: ``True`` if the window is currently visible - """ - return self._hWnd.isVisible() - - isVisible = visible # isVisible is an alias for the visible property. - - @property - def isAlive(self) -> bool: - """ - Check if window (and application) still exists (minimized and hidden windows are included as existing) - - :return: ''True'' if window exists - """ - ret = False - if self._hWnd in self._app.orderedWindows(): - ret = True - return ret - +# class MacOSNSWindow(BaseWindow): +# +# def __init__(self, app: AppKit.NSApplication, hWnd: AppKit.NSWindow): +# super().__init__(hWnd) +# +# self._app = app +# self._hWnd = hWnd +# self._parent = hWnd.parentWindow() +# self.watchdog = _WatchDog(self) +# +# def getExtraFrameSize(self, includeBorder: bool = True) -> Tuple[int, int, int, int]: +# """ +# Get the extra space, in pixels, around the window, including or not the border +# +# :param includeBorder: set to ''False'' to avoid including borders +# :return: (left, top, right, bottom) frame size as a tuple of int +# """ +# borderWidth = 0 +# if includeBorder: +# ret = self._hWnd.contentRectForFrameRect_(self._hWnd.frame()) +# frame = self._hWnd.screen().convertRectToBacking_(ret) +# borderWidth = frame.origin.x - self.left +# frame = (borderWidth, borderWidth, borderWidth, borderWidth) +# return frame +# +# def getClientFrame(self): +# """ +# Get the client area of window, as a Rect (x, y, right, bottom) +# Notice that scroll bars will be included within this area +# +# :return: Rect struct +# """ +# ret = self._hWnd.contentRectForFrameRect_(self._hWnd.frame()) +# frame = self._hWnd.screen().convertRectToBacking_(ret) +# x = int(frame.origin.x) +# y = int(frame.origin.y) +# w = int(frame.size.width) +# h = int(frame.size.height) +# r = x + w +# b = y + h +# return Rect(x, y, r, b) +# +# def __repr__(self): +# return '%s(hWnd=%s)' % (self.__class__.__name__, self._hWnd) +# +# def __eq__(self, other: object): +# return isinstance(other, MacOSNSWindow) and self._hWnd == other._hWnd +# +# def close(self) -> bool: +# """ +# Closes this window. This may trigger "Are you sure you want to +# quit?" dialogs or other actions that prevent the window from +# actually closing. This is identical to clicking the X button on the +# window. +# +# :return: ''True'' if window is closed +# """ +# return self._hWnd.performClose_(self._app) +# +# def minimize(self, wait: bool = False) -> bool: +# """ +# Minimizes this window +# +# :param wait: set to ''True'' to confirm action requested (in a reasonable time) +# :return: ''True'' if window minimized +# """ +# self._hWnd.performMiniaturize_(self._app) +# retries = 0 +# while wait and retries < WAIT_ATTEMPTS and not self.isMinimized: +# retries += 1 +# time.sleep(WAIT_DELAY * retries) +# return self.isMinimized +# +# def maximize(self, wait: bool = False) -> bool: +# """ +# Maximizes this window +# +# :param wait: set to ''True'' to confirm action requested (in a reasonable time) +# :return: ''True'' if window maximized +# """ +# self._hWnd.performZoom_(self._app) +# retries = 0 +# while wait and retries < WAIT_ATTEMPTS and not self.isMaximized: +# retries += 1 +# time.sleep(WAIT_DELAY * retries) +# return self.isMaximized +# +# def restore(self, wait: bool = False, user: bool = False) -> bool: +# """ +# If maximized or minimized, restores the window to it's normal size +# +# :param wait: set to ''True'' to confirm action requested (in a reasonable time) +# :param user: ignored on macOS platform +# :return: ''True'' if window restored +# """ +# self.activate(wait=True) +# if self.isMaximized: +# self._hWnd.performZoom_(self._app) +# self._hWnd.deminiaturize_(self._app) +# retries = 0 +# while wait and retries < WAIT_ATTEMPTS and (self.isMinimized or self.isMaximized): +# retries += 1 +# time.sleep(WAIT_DELAY * retries) +# return not self.isMaximized and not self.isMinimized +# +# def show(self, wait: bool = False) -> bool: +# """ +# If hidden or showing, shows the window on screen and in title bar +# +# :param wait: set to ''True'' to wait until action is confirmed (in a reasonable time lap) +# :return: ''True'' if window showed +# """ +# self.activate(wait=wait) +# retries = 0 +# while wait and retries < WAIT_ATTEMPTS and not self.visible: +# retries += 1 +# time.sleep(WAIT_DELAY * retries) +# return self.visible +# +# def hide(self, wait: bool = False) -> bool: +# """ +# If hidden or showing, hides the window from screen and title bar +# +# :param wait: set to ''True'' to wait until action is confirmed (in a reasonable time lap) +# :return: ''True'' if window hidden +# """ +# self._hWnd.orderOut_(self._app) +# retries = 0 +# while wait and retries < WAIT_ATTEMPTS and self.visible: +# retries += 1 +# time.sleep(WAIT_DELAY * retries) +# return not self.visible +# +# def activate(self, wait: bool = False, user: bool = True) -> bool: +# """ +# Activate this window and make it the foreground (focused) window +# +# :param wait: set to ''True'' to wait until action is confirmed (in a reasonable time lap) +# :param user: ignored on macOS platform +# :return: ''True'' if window activated +# """ +# self._app.activateIgnoringOtherApps_(True) +# self._hWnd.makeKeyAndOrderFront_(self._app) +# retries = 0 +# while wait and retries < WAIT_ATTEMPTS and not self.isActive: +# retries += 1 +# time.sleep(WAIT_DELAY * retries) +# return self.isActive +# +# def resize(self, widthOffset: int, heightOffset: int, wait: bool = False) -> bool: +# """ +# Resizes the window relative to its current size +# +# :param widthOffset: offset to add to current window width as target width +# :param heightOffset: offset to add to current window height as target height +# :param wait: set to ''True'' to wait until action is confirmed (in a reasonable time lap) +# :return: ''True'' if window resized to the given size +# """ +# box = self.box +# return self.resizeTo(box.width + widthOffset, box.height + heightOffset, wait) +# +# resizeRel = resize # resizeRel is an alias for the resize() method. +# +# def resizeTo(self, newWidth: int, newHeight: int, wait: bool = False) -> bool: +# """ +# Resizes the window to a new width and height +# +# :param newWidth: target window width +# :param newHeight: target window height +# :param wait: set to ''True'' to wait until action is confirmed (in a reasonable time lap) +# :return: ''True'' if window resized to the given size +# """ +# self.size = Size(newWidth, newHeight) +# box = self.box +# retries = 0 +# while wait and retries < WAIT_ATTEMPTS and box.width != newWidth and box.height != newHeight: +# retries += 1 +# time.sleep(WAIT_DELAY * retries) +# box = self.box +# return box.width == newWidth and box.height == newHeight +# +# def move(self, xOffset: int, yOffset: int, wait: bool = False) -> bool: +# """ +# Moves the window relative to its current position +# +# :param xOffset: offset relative to current X coordinate to move the window to +# :param yOffset: offset relative to current Y coordinate to move the window to +# :param wait: set to ''True'' to wait until action is confirmed (in a reasonable time lap) +# :return: ''True'' if window moved to the given position +# """ +# box = self.box +# return self.moveTo(box.left + xOffset, box.top + yOffset, wait) +# +# moveRel = move # moveRel is an alias for the move() method. +# +# def moveTo(self, newLeft:int, newTop: int, wait: bool = False) -> bool: +# """ +# Moves the window to new coordinates on the screen +# +# :param newLeft: target X coordinate to move the window to +# :param newTop: target Y coordinate to move the window to +# :param wait: set to ''True'' to wait until action is confirmed (in a reasonable time lap) +# :return: ''True'' if window moved to the given position +# """ +# self.topleft = Point(newLeft, newTop) +# box = self.box +# retries = 0 +# while wait and retries < WAIT_ATTEMPTS and box.left != newLeft and box.top != newTop: +# retries += 1 +# time.sleep(WAIT_DELAY * retries) +# box = self.box +# return box.left == newLeft and box.top == newTop +# +# def alwaysOnTop(self, aot: bool = True) -> bool: +# """ +# Keeps window on top of all others. +# +# :param aot: set to ''False'' to deactivate always-on-top behavior +# :return: ''True'' if command succeeded +# """ +# if aot: +# ret = self._hWnd.setLevel_(Quartz.kCGScreenSaverWindowLevel) +# else: +# ret = self._hWnd.setLevel_(Quartz.kCGNormalWindowLevel) +# return ret +# +# def alwaysOnBottom(self, aob: bool = True) -> bool: +# """ +# Keeps window below of all others, but on top of desktop icons and keeping all window properties +# +# :param aob: set to ''False'' to deactivate always-on-bottom behavior +# :return: ''True'' if command succeeded +# """ +# if aob: +# ret = self._hWnd.setLevel_(Quartz.kCGDesktopWindowLevel) +# else: +# ret = self._hWnd.setLevel_(Quartz.kCGNormalWindowLevel) +# return ret +# +# def lowerWindow(self) -> bool: +# """ +# Lowers the window to the bottom so that it does not obscure any sibling windows +# +# :return: ''True'' if window lowered +# """ +# # self._hWnd.orderBack_(self._app) # Not working or using it wrong??? +# windows = self._app.orderedWindows() +# ret = False +# if windows: +# windows.reverse() +# for win in windows: +# if win != self._hWnd: +# ret = win.makeKeyAndOrderFront_(self._app) +# return ret +# +# def raiseWindow(self, sb: bool = True) -> bool: +# """ +# Raises the window to top so that it is not obscured by any sibling windows. +# +# :return: ''True'' if window raised +# """ +# return self._hWnd.makeKeyAndOrderFront_(self._app) +# +# def sendBehind(self, sb: bool = True) -> bool: +# """ +# Sends the window to the very bottom, below all other windows, including desktop icons. +# It may also cause that the window does not accept focus nor keyboard/mouse events as well as +# make the window disappear from taskbar and/or pager. +# +# :param sb: set to ''False'' to bring the window back to front +# :return: ''True'' if window sent behind desktop icons +# """ +# # https://stackoverflow.com/questions/4982584/how-do-i-draw-the-desktop-on-mac-os-x +# if sb: +# ret1 = self._hWnd.setLevel_(Quartz.kCGDesktopWindowLevel - 1) +# ret2 = self._hWnd.setCollectionBehavior_(Quartz.NSWindowCollectionBehaviorCanJoinAllSpaces | +# Quartz.NSWindowCollectionBehaviorStationary | +# Quartz.NSWindowCollectionBehaviorIgnoresCycle) +# else: +# ret1 = self._hWnd.setLevel_(Quartz.kCGNormalWindowLevel) +# ret2 = self._hWnd.setCollectionBehavior_(Quartz.NSWindowCollectionBehaviorDefault | +# Quartz.NSWindowCollectionBehaviorParticipatesInCycle | +# Quartz.NSWindowCollectionBehaviorManaged) +# return ret1 and ret2 +# +# def acceptInput(self, setTo: bool) -> None: +# """Toggles the window transparent to input and focus +# +# :param setTo: True/False to toggle window transparent to input and focus +# :return: None +# """ +# self._hWnd.setIgnoresMouseEvents_(not setTo) +# +# def getAppName(self) -> str: +# """ +# Get the name of the app current window belongs to +# +# :return: name of the app as string +# """ +# return self._app.localizedName() +# +# def getParent(self) -> int: +# """ +# Get the handle of the current window parent. It can be another window or an application +# +# :return: handle of the window parent +# """ +# return self._hWnd.parentWindow() +# +# def setParent(self, parent) -> bool: +# """ +# Current window will become child of given parent +# WARNIG: Not implemented in AppleScript (not possible in macOS for foreign (other apps') windows) +# +# :param parent: window to set as current window parent +# :return: ''True'' if current window is now child of given parent +# """ +# parent.addChildWindow(self._hWnd, 1) +# return bool(self.isChild(parent)) +# +# def getChildren(self) -> List[int]: +# """ +# Get the children handles of current window +# +# :return: list of handles +# """ +# return self._hWnd.childWindows() +# +# def getHandle(self): +# """ +# Get the current window handle +# +# :return: window handle +# """ +# return self._hWnd +# +# def isParent(self, child: AppKit.NSWindow) -> bool: +# """ +# Check if current window is parent of given window (handle) +# +# :param child: handle of the window you want to check if the current window is parent of +# :return: ''True'' if current window is parent of the given window +# """ +# return child.parentWindow() == self._hWnd +# isParentOf = isParent # isParentOf is an alias of isParent method +# +# def isChild(self, parent: int) -> bool: +# """ +# Check if current window is child of given window/app (handle) +# +# :param parent: handle of the window/app you want to check if the current window is child of +# :return: ''True'' if current window is child of the given window +# """ +# return parent == self.getParent() +# isChildOf = isChild # isChildOf is an alias of isParent method +# +# def getDisplay(self) -> List[str]: +# """ +# Get display name in which current window space is mostly visible +# +# :return: display name as string or empty (couldn't retrieve it or window is offscreen) +# """ +# x, y = self.center +# return _findMonitorName(x, y) +# getMonitor = getDisplay # getMonitor is an alias of getDisplay method +# +# @property +# def isMinimized(self) -> bool: +# """ +# Check if current window is currently minimized +# +# :return: ``True`` if the window is minimized +# """ +# return self._hWnd.isMiniaturized() +# +# @property +# def isMaximized(self) -> bool: +# """ +# Check if current window is currently maximized +# +# :return: ``True`` if the window is maximized +# """ +# return self._hWnd.isZoomed() +# +# @property +# def isActive(self) -> bool: +# """ +# Check if current window is currently the active, foreground window +# +# :return: ``True`` if the window is the active, foreground window +# """ +# windows = getAllWindows(self._app) +# for win in windows: +# return self._hWnd == win._hWnd +# return False +# +# @property +# def title(self) -> str: +# """ +# Get the current window title, as string +# +# :return: title as a string +# """ +# return self._hWnd.title() +# +# @property +# def updatedTitle(self) -> str: +# """ +# WARNING: MacOSWindow ONLY. For MacOSNSWindow, this property returns actual title +# +# :return: title as a string +# """ +# return self.title +# +# @property +# def visible(self) -> bool: +# """ +# Check if current window is visible (minimized windows are also visible) +# +# :return: ``True`` if the window is currently visible +# """ +# return self._hWnd.isVisible() +# +# isVisible = visible # isVisible is an alias for the visible property. +# +# @property +# def isAlive(self) -> bool: +# """ +# Check if window (and application) still exists (minimized and hidden windows are included as existing) +# +# :return: ''True'' if window exists +# """ +# ret = False +# if self._hWnd in self._app.orderedWindows(): +# ret = True +# return ret +# # @property # def isAlerting(self) -> bool: # """Check if window is flashing on taskbar while demanding user attention @@ -2388,30 +2316,3 @@ def isAlive(self) -> bool: # :return: ''True'' if window is demanding attention # """ # return False - - -def main(): - """Run this script from command-line to get windows under mouse pointer""" - from pymonctl import getAllMonitorsDict, Monitor, findMonitorWithName - print("PLATFORM:", sys.platform) - print("ALL WINDOWS", getAllTitles()) - print("ALL APPS & WINDOWS", getAllAppsWindowsTitles()) - print("MONITORS:", getAllMonitorsDict()) - npw = getActiveWindow() - if npw is None: - print("ACTIVE WINDOW:", None) - else: - print("ACTIVE WINDOW:", npw.title, "/", npw.box) - dpy = npw.getDisplay() - print("DISPLAY", dpy) - if dpy: - monitor: Monitor = findMonitorWithName(dpy[0]) - if monitor: - print("SCREEN SIZE:", monitor.size) - print("WORKAREA:", monitor.workarea) - print() - displayWindowsUnderMouse() - - -if __name__ == "__main__": - main() diff --git a/src/pywinctl/_pywinctl_win.py b/src/pywinctl/_pywinctl_win.py index a3cc92f..29ca74a 100644 --- a/src/pywinctl/_pywinctl_win.py +++ b/src/pywinctl/_pywinctl_win.py @@ -24,7 +24,7 @@ import win32api import win32gui -from ._main import BaseWindow, Re, _WatchDog, _findMonitorName,displayWindowsUnderMouse +from ._main import BaseWindow, Re, _WatchDog, _findMonitorName from pywinbox import Size, Point, Rect, pointInBox # WARNING: Changes are not immediately applied, specially for hide/show (unmap/map) @@ -662,8 +662,10 @@ def alwaysOnTop(self, aot: bool = True) -> bool: # https://stackoverflow.com/questions/17131857/python-windows-keep-program-on-top-of-another-full-screen-application # This can be interesting to do all of the above with Python # https://www.apriorit.com/dev-blog/727-win-guide-to-hooking-windows-apis-with-python - if self._t and self._t.is_alive(): - self._t.kill() + if self._t: + self._kill_t.set() + self._t.join() + self._t = None zorder = win32con.HWND_TOPMOST if aot else win32con.HWND_NOTOPMOST win32gui.SetWindowPos(self._hWnd, zorder, 0, 0, 0, 0, win32con.SWP_NOMOVE | win32con.SWP_NOSIZE | win32con.SWP_NOACTIVATE) @@ -1407,31 +1409,3 @@ def run(self): # buttons.append((tbButton.idCommand, idx_rect, butPid)) # # return hWnd, buttons - - -def main(): - """Run this script from command-line to get windows under mouse pointer""" - from pymonctl import getAllMonitorsDict, Monitor, findMonitorWithName - print("PLATFORM:", sys.platform) - print("ALL WINDOWS", getAllTitles()) - print("ALL APPS & WINDOWS", getAllAppsWindowsTitles()) - print("MONITORS:", getAllMonitorsDict()) - npw = getActiveWindow() - if npw is None: - print("ACTIVE WINDOW:", None) - else: - print("ACTIVE WINDOW:", npw.title, "/", npw.box) - dpy = npw.getDisplay() - print("DISPLAY", dpy) - if dpy: - monitor: Monitor = findMonitorWithName(dpy[0]) - if monitor: - print("SCREEN SIZE:", monitor.size) - print("WORKAREA:", monitor.workarea) - print() - displayWindowsUnderMouse() - # print(npw.menu.getMenu()) # Not working in windows 11?!?!?!?! - - -if __name__ == "__main__": - main() diff --git a/src/pywinctl/ewmhlib/Props/__init__.py b/src/pywinctl/ewmhlib/Props/__init__.py deleted file mode 100644 index c38be67..0000000 --- a/src/pywinctl/ewmhlib/Props/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -from ._props import (Root, DesktopLayout, Window, WindowType, State, StateAction, - MoveResize, DataFormat, Mode, StackMode, HintAction) diff --git a/src/pywinctl/ewmhlib/Props/_props.py b/src/pywinctl/ewmhlib/Props/_props.py deleted file mode 100644 index 9ef146f..0000000 --- a/src/pywinctl/ewmhlib/Props/_props.py +++ /dev/null @@ -1,141 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -from enum import IntEnum -import Xlib.X - - -class Root: - SUPPORTED = "_NET_SUPPORTED" - CLIENT_LIST = "_NET_CLIENT_LIST" - CLIENT_LIST_STACKING = "_NET_CLIENT_LIST_STACKING" - NUMBER_OF_DESKTOPS = "_NET_NUMBER_OF_DESKTOPS" - DESKTOP_GEOMETRY = "_NET_DESKTOP_GEOMETRY" - DESKTOP_VIEWPORT = "_NET_DESKTOP_VIEWPORT" - CURRENT_DESKTOP = "_NET_CURRENT_DESKTOP" - DESKTOP_NAMES = "_NET_DESKTOP_NAMES" - ACTIVE = "_NET_ACTIVE_WINDOW" - WORKAREA = "_NET_WORKAREA" - SUPPORTING_WM_CHECK = "_NET_SUPPORTING_WM_CHECK" - VIRTUAL_ROOTS = "_NET_VIRTUAL_ROOTS" - SHOWING_DESKTOP = "_NET_SHOWING_DESKTOP" - DESKTOP_LAYOUT = "_NET_DESKTOP_LAYOUT" - # Additional Root properties (always related to a specific window) - CLOSE = "_NET_CLOSE_WINDOW" - MOVERESIZE = "_NET_MOVERESIZE_WINDOW" - WM_MOVERESIZE = "_NET_WM_MOVERESIZE" - RESTACK = "_NET_RESTACK_WINDOW" - REQ_FRAME_EXTENTS = "_NET_REQUEST_FRAME_EXTENTS" - # WM_PROTOCOLS messages - PROTOCOLS = "WM_PROTOCOLS" - PING = "_NET_WM_PING" - SYNC = "_NET_WM_SYNC_REQUEST" - - -class DesktopLayout(IntEnum): - ORIENTATION_HORZ = 0 - ORIENTATION_VERT = 1 - TOPLEFT = 0 - TOPRIGHT = 1 - BOTTOMRIGHT = 2 - BOTTOMLEFT = 3 - - -class Window: - NAME = "_NET_WM_NAME" - LEGACY_NAME = "WM_NAME" - VISIBLE_NAME = "_NET_WM_VISIBLE_NAME" - ICON_NAME = "_NET_WM_ICON_NAME" - VISIBLE_ICON_NAME = "_NET_WM_VISIBLE_ICON_NAME" - DESKTOP = "_NET_WM_DESKTOP" - WM_WINDOW_TYPE = "_NET_WM_WINDOW_TYPE" - CHANGE_STATE = "WM_CHANGE_STATE" - WM_STATE = "_NET_WM_STATE" - ALLOWED_ACTIONS = "_NET_WM_ALLOWED_ACTIONS" - STRUT = "_NET_WM_STRUT" - STRUT_PARTIAL = "_NET_WM_STRUT_PARTIAL" - ICON_GEOMETRY = "_NET_WM_ICON_GEOMETRY" - ICON = "_NET_WM_ICON" - PID = "_NET_WM_PID" - HANDLED_ICONS = "_NET_WM_HANDLED_ICONS" - USER_TIME = "_NET_WM_USER_TIME" - FRAME_EXTENTS = "_NET_FRAME_EXTENTS" - # These are Root properties, but always related to a specific window - ACTIVE = "_NET_ACTIVE_WINDOW" - CLOSE = "_NET_CLOSE_WINDOW" - MOVERESIZE = "_NET_MOVERESIZE_WINDOW" - WM_MOVERESIZE = "_NET_WM_MOVERESIZE" - RESTACK = "_NET_RESTACK_WINDOW" - REQ_FRAME_EXTENTS = "_NET_REQUEST_FRAME_EXTENTS" - OPAQUE_REGION = "_NET_WM_OPAQUE_REGION" - BYPASS_COMPOSITOR = "_NET_WM_BYPASS_COMPOSITOR" - - -class WindowType: - DESKTOP = "_NET_WM_WINDOW_TYPE_DESKTOP" - DOCK = "_NET_WM_WINDOW_TYPE_DOCK" - TOOLBAR = "_NET_WM_WINDOW_TYPE_TOOLBAR" - MENU = "_NET_WM_WINDOW_TYPE_MENU" - UTILITY = "_NET_WM_WINDOW_TYPE_UTILITY" - SPLASH = "_NET_WM_WINDOW_TYPE_SPLASH" - DIALOG = "_NET_WM_WINDOW_TYPE_DIALOG" - NORMAL = "_NET_WM_WINDOW_TYPE_NORMAL" - - -class State: - NULL = "0" - MODAL = "_NET_WM_STATE_MODAL" - STICKY = "_NET_WM_STATE_STICKY" - MAXIMIZED_VERT = "_NET_WM_STATE_MAXIMIZED_VERT" - MAXIMIZED_HORZ = "_NET_WM_STATE_MAXIMIZED_HORZ" - SHADED = "_NET_WM_STATE_SHADED" - SKIP_TASKBAR = "_NET_WM_STATE_SKIP_TASKBAR" - SKIP_PAGER = "_NET_WM_STATE_SKIP_PAGER" - HIDDEN = "_NET_WM_STATE_HIDDEN" - FULLSCREEN = "_NET_WM_STATE_FULLSCREEN" - ABOVE = "_NET_WM_STATE_ABOVE" - BELOW = "_NET_WM_STATE_BELOW" - DEMANDS_ATTENTION = "_NET_WM_STATE_DEMANDS_ATTENTION" - FOCUSED = "_NET_WM_STATE_FOCUSED" - - -class StateAction(IntEnum): - REMOVE = 0 - ADD = 1 - TOGGLE = 2 - - -class MoveResize(IntEnum): - SIZE_TOPLEFT = 0 - SIZE_TOP = 1 - SIZE_TOPRIGHT = 2 - SIZE_RIGHT = 3 - SIZE_BOTTOMRIGHT = 4 - SIZE_BOTTOM = 5 - SIZE_BOTTOMLEFT = 6 - SIZE_LEFT = 7 - MOVE = 8 # movement only - SIZE_KEYBOARD = 9 # size via keyboard - MOVE_KEYBOARD = 10 # move via keyboard - - -class DataFormat(IntEnum): - # I guess 16 is not used in Python (no difference between short and long int) - STR = 8 - INT = 32 - - -class Mode(IntEnum): - REPLACE = Xlib.X.PropModeReplace - APPEND = Xlib.X.PropModeAppend - PREPEND = Xlib.X.PropModePrepend - - -class StackMode(IntEnum): - ABOVE = Xlib.X.Above - BELOW = Xlib.X.Below - - -class HintAction(IntEnum): - KEEP = -1 - REMOVE = -2 diff --git a/src/pywinctl/ewmhlib/Structs/__init__.py b/src/pywinctl/ewmhlib/Structs/__init__.py deleted file mode 100644 index 8391b84..0000000 --- a/src/pywinctl/ewmhlib/Structs/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -from ._structs import (ScreensInfo, DisplaysInfo, WmHints, Aspect, WmNormalHints) diff --git a/src/pywinctl/ewmhlib/Structs/_structs.py b/src/pywinctl/ewmhlib/Structs/_structs.py deleted file mode 100644 index 7ce4feb..0000000 --- a/src/pywinctl/ewmhlib/Structs/_structs.py +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*-#from typing import List - -from typing_extensions import TypedDict -from typing import List - -from ctypes import Structure, c_int32, c_ulong, c_uint32 -import Xlib.xobject -from Xlib.protocol.rq import Struct -from Xlib.xobject.drawable import Window as XWindow - - -class ScreensInfo(TypedDict): - """ - Container class to handle ScreensInfo struct: - - - screen_number (str): int (sequential) - - is_default (bool): ''True'' if the screen is the default screen - - screen (Xlib.Struct): screen Struct (see Xlib documentation) - - root (Xlib.xobject.drawable.Window): root X-Window object belonging to screen - """ - screen_number: str - is_default: bool - screen: Struct - root: XWindow - - -class DisplaysInfo(TypedDict): - """ - Container class to handle DisplaysInfo struct: - - - name: Display name (as per Xlib.display.Display(name)) - - is_default: ''True'' if the display is the default display - - screens: list of ScreensInfo structs belonging to display - """ - display: Xlib.display.Display - name: str - is_default: bool - screens: List[ScreensInfo] - - -""" -Perhaps unnecesary since structs below are defined in Xlib.xobject.icccm.*, though in a more complex way. -""" -class WmHints(TypedDict): - """ - Container class to handle WmHints struct: - - Example: - { - 'flags': 103, - 'input': 1, - 'initial_state': 1, - 'icon_pixmap': , - 'icon_window': , - 'icon_x': 0, - 'icon_y': 0, - 'icon_mask': , - 'window_group': - } - """ - flags: int - input_mode: int - initial_state: int - icon_pixmap: Xlib.xobject.drawable.Pixmap - icon_window: Xlib.xobject.drawable.Window - icon_x: int - icon_y: int - icon_mask: Xlib.xobject.drawable.Pixmap - window_group: Xlib.xobject.drawable.Window - - -class Aspect(TypedDict): - """Container class to handle Aspect struct (num, denum)""" - num: int - denum: int - - -class WmNormalHints(TypedDict): - """ - Container class to handle WmNormalHints - - Example: - { - 'flags': 848, - 'min_width': 387, - 'min_height': 145, - 'max_width': 0, - 'max_height': 0, - 'width_inc': 9, - 'height_inc': 18, - 'min_aspect': ({'num': 0, 'denum': 0}), - 'max_aspect': ({'num': 0, 'denum': 0}), - 'base_width': 66, - 'base_height': 101, - 'win_gravity': 1 - } - """ - flags: int - min_width: int - min_height: int - max_width: int - max_height: int - width_inc: int - height_inc: int - min_aspect: Aspect - max_aspect: Aspect - base_width: int - base_height: int - win_gravity: int - - -class _XWindowAttributes(Structure): - _fields_ = [('x', c_int32), ('y', c_int32), - ('width', c_int32), ('height', c_int32), ('border_width', c_int32), - ('depth', c_int32), ('visual', c_ulong), ('root', c_ulong), - ('class', c_int32), ('bit_gravity', c_int32), - ('win_gravity', c_int32), ('backing_store', c_int32), - ('backing_planes', c_ulong), ('backing_pixel', c_ulong), - ('save_under', c_int32), ('colourmap', c_ulong), - ('mapinstalled', c_uint32), ('map_state', c_uint32), - ('all_event_masks', c_ulong), ('your_event_mask', c_ulong), - ('do_not_propagate_mask', c_ulong), ('override_redirect', c_int32), ('screen', c_ulong)] diff --git a/src/pywinctl/ewmhlib/__init__.py b/src/pywinctl/ewmhlib/__init__.py deleted file mode 100644 index 90d16e5..0000000 --- a/src/pywinctl/ewmhlib/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - - -__all__ = [ - "version", "displaysCount", "getDisplays", "getDisplaysInfo", "getRoots", - "defaultDisplay", "defaultScreen", "defaultRoot", "defaultEwmhRoot", - "getDisplayFromRoot", "getScreenFromRoot", - "getDisplayFromWindow", "getScreenFromWindow", "getRootFromWindow", - "getProperty", "getPropertyValue", "changeProperty", "sendMessage", - "EwmhRoot", "EwmhWindow", - "Props", "Structs" -] - -__version__ = "0.0.1" - - -def version(numberOnly: bool = True): - """Returns the current version of ewmhlib module, in the form ''x.x.xx'' as string""" - return ("" if numberOnly else "EWMHlib-")+__version__ - - -from ._main import (displaysCount, getDisplays, getDisplaysInfo, getRoots, - defaultDisplay, defaultScreen, defaultRoot, defaultEwmhRoot, - getDisplayFromRoot, getScreenFromRoot, - getDisplayFromWindow, getScreenFromWindow, getRootFromWindow, - getProperty, getPropertyValue, changeProperty, sendMessage, - EwmhRoot, EwmhWindow, - Props, Structs - ) diff --git a/src/pywinctl/ewmhlib/_ewmhlib.py b/src/pywinctl/ewmhlib/_ewmhlib.py deleted file mode 100644 index 2a3bc73..0000000 --- a/src/pywinctl/ewmhlib/_ewmhlib.py +++ /dev/null @@ -1,2882 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -from __future__ import annotations - -import sys -assert sys.platform == "linux" - -import array -import os -import threading -import time -from typing import Optional, cast, Callable, Union, List, Tuple, Iterable - -import Xlib.display -import Xlib.protocol -from Xlib.protocol.rq import Struct -import Xlib.X -import Xlib.Xatom -import Xlib.Xutil -import Xlib.ext -import Xlib.xobject -from Xlib.xobject.drawable import Window as XWindow - -from .Props import (Root, DesktopLayout, Window, WindowType, State, StateAction, - MoveResize, DataFormat, Mode, HintAction) -from .Structs import DisplaysInfo, ScreensInfo, WmHints, Aspect, WmNormalHints - - -defaultDisplay: Xlib.display.Display = Xlib.display.Display() -defaultScreen: Struct = defaultDisplay.screen() -defaultRoot: XWindow = defaultScreen.root - - -def _getDisplays() -> List[Xlib.display.Display]: - """ - Get a list of connections to all existing Displays. - - :return: list of display connections - """ - displays: List[Xlib.display.Display] = [] - if os.environ.get('XDG_SESSION_TYPE', "").lower() != "wayland": - # Wayland adds a "fake" display (typically ":1") that freezes when trying to get a connection. Using default - # Thanks to SamuMazzi - https://github.com/SamuMazzi for pointing out this issue - files: List[str] = os.listdir("/tmp/.X11-unix") - for d in files: - if d.startswith("X"): - name: str = d.replace("X", ":", 1) - try: - displays.append(Xlib.display.Display(name)) - except: - pass - if not displays: - displays = [defaultDisplay] - return displays -_displays: List[Xlib.display.Display] = _getDisplays() -displaysCount: int = len(_displays) - - -def getDisplays() -> List[Xlib.display.Display]: - global _displays - return _displays - - -def getDisplaysInfo() -> dict[str, DisplaysInfo]: - """ - Get relevant information on all present displays, including its screens and roots - - Returned dictionary has the following structure: - - "display": display connection to access all display features and protocols - "name": display name - "is_default": ''True'' if it's the default display, ''False'' otherwise - "screens": List of sub-dict containing all screens owned by the display - "scrSeq": number of the screen (as per display.screen(scrSeq)) - "screen": Struct containing all screen info - "root": root window (Xlib Window) which belongs to the screen - "is_default": ''True'' if it's the default screen/root, ''False'' otherwise - - :return: dict with all displays, screens and roots info - """ - dspInfo: dict[str, DisplaysInfo] = {} - for display in getDisplays(): - name = display.get_display_name() - screens: List[ScreensInfo] = [] - for s in range(display.screen_count()): - try: - screen: Struct = display.screen(s) - root = screen.root - screenInfo: ScreensInfo = { - "screen_number": str(s), - "is_default": bool(root.id == defaultRoot.id), - "screen": screen, - "root": root - } - screens.append(screenInfo) - except: - pass - displayInfo: DisplaysInfo = { - "display": display, - "name": name, - "is_default": (name == defaultDisplay.get_display_name()), - "screens": screens - } - dspInfo[name] = displayInfo - return dspInfo - - -def getDisplayFromWindow(winId: int) -> Tuple[Xlib.display.Display, Struct, XWindow]: - """ - Get display connection, screen and root window from a given window id to which it belongs - - :param winId: id of the window - :return: tuple containing display connection, screen struct and root window - """ - if displaysCount > 1 or defaultDisplay.screen_count() > 1: - rootsInfo = getRoots() - for rootData in rootsInfo: - display, screen, root = rootData - atom: int = display.get_atom(Root.CLIENT_LIST) - ret: Optional[Xlib.protocol.request.GetProperty] = root.get_full_property(atom, Xlib.X.AnyPropertyType) - if ret and hasattr(ret, "value") and winId in ret.value: - return display, screen, root - return defaultDisplay, defaultScreen, defaultRoot -getScreenFromWindow = getDisplayFromWindow -getRootFromWindow = getDisplayFromWindow - - -def getDisplayFromRoot(rootId: Optional[int]) -> Tuple[Xlib.display.Display, Struct, XWindow]: - """ - Get display connection, screen and root window from a given root id to which it belongs. - For default root, this is not needed. Use defaultDisplay, defaultScreen and defaultRoot instead. - - :param rootId: id of the target root - :return: tuple containing display connection, screen struct and root window - """ - if rootId and rootId != defaultRoot.id and (displaysCount > 1 or defaultDisplay.screen_count() > 1): - rootsInfo = getRoots() - for rootData in rootsInfo: - display, screen, root = rootData - if rootId == root.id: - return display, screen, root - return defaultDisplay, defaultScreen, defaultRoot -getScreenFromRoot = getDisplayFromRoot - - -def _getRoots(updateDisplays: bool = False) -> List[Tuple[Xlib.display.Display, Struct, XWindow]]: - rootsInfo: List[Tuple[Xlib.display.Display, Struct, XWindow]] = [(defaultDisplay, defaultScreen, defaultRoot)] - global displaysCount - if updateDisplays or displaysCount > 1 or defaultDisplay.screen_count() > 1: - if updateDisplays: - display = Xlib.display.Display() - screen: Struct = display.screen() - rootsInfo = [(display, screen, screen.root)] - displays: List[Xlib.display.Display] = _getDisplays() - else: - displays = getDisplays() - for display in displays: - for i in range(display.screen_count()): - try: - screen = display.screen(i) - root: XWindow = screen.root - if root.id != defaultRoot.id: - rootsInfo.append((display, screen, root)) - except: - pass - return rootsInfo -_roots: List[Tuple[Xlib.display.Display, Struct, XWindow]] = _getRoots() - - -def getRoots() -> List[Tuple[Xlib.display.Display, Struct, XWindow]]: - """ - Get root windows objects. - - :return: list of X-Window objects - """ - global _roots - return _roots - - -def getProperty(window: XWindow, prop: Union[str, int], - prop_type: int = Xlib.X.AnyPropertyType, - display: Xlib.display.Display = defaultDisplay) -> Optional[Xlib.protocol.request.GetProperty]: - """ - Get given window/root property - - :param window: window from which get the property - :param prop: property to retrieve as int or str (will be translated to int) - :param prop_type: property type (e.g. Xlib.X.AnyPropertyType or Xlib.Xatom.ATOM) - :param display: display to which window belongs to (defaults to default display) - :return: Xlib.protocol.request.GetProperty struct or None (property couldn't be obtained) - """ - if isinstance(prop, str): - prop = display.get_atom(prop) - if isinstance(prop, int) and prop != 0: - return window.get_full_property(prop, prop_type) - return None - - -def changeProperty(window: XWindow, prop: Union[str, int], data: Union[List[int], str], - prop_type: int = Xlib.Xatom.ATOM, propMode: int = Xlib.X.PropModeReplace, - display: Xlib.display.Display = defaultDisplay): - """ - Change given window/root property - - :param window: window to which change the property - :param prop: property to change as int or str (will be translated to int) - :param data: data of the property as string or List of int (atoms) - :param prop_type: property type (e.g. Xlib.Xatom.STRING or Xlib.Xatom.ATOM) - :param propMode: Property mode: APPEND/PREPEND/REPLACE (defaults to Xlib.X.PropModeReplace) - :param display: display to which window belongs to (defaults to default display) - """ - if isinstance(prop, str): - prop = display.get_atom(prop) - - if isinstance(prop, int) and prop != 0: - if isinstance(data, str): - dataFormat: int = DataFormat.STR - data = data.encode(encoding="utf-8") - else: - dataFormat = DataFormat.INT - data = (data + [0] * (5 - len(data)))[:5] - - window.change_property(prop, prop_type, dataFormat, data, propMode) - display.flush() - - -def sendMessage(winId: int, prop: Union[str, int], data: Union[List[int], str], - display: Xlib.display.Display = defaultDisplay, root: XWindow = defaultRoot): - """ - Send Client Message to given window/root - - :param winId: window id (int) to which send the message - :param prop: property to change as int or str (will be translated to int) - :param data: data of the message as string or list of int (atoms) - :param display: display to which window belongs to (defaults to default display) - :param root: root to which window is placed (defaults to default root) - """ - if isinstance(prop, str): - prop = display.get_atom(prop) - - if isinstance(prop, int) and prop != 0: - if isinstance(data, str): - dataFormat: int = DataFormat.STR - else: - data = (data + [0] * (5 - len(data)))[:5] - dataFormat = DataFormat.INT - - ev: Xlib.protocol.event.ClientMessage = Xlib.protocol.event.ClientMessage(window=winId, client_type=prop, - data=(dataFormat, data)) - mask: int = Xlib.X.SubstructureRedirectMask | Xlib.X.SubstructureNotifyMask - root.send_event(event=ev, event_mask=mask) - display.flush() - - -def getPropertyValue(prop: Optional[Xlib.protocol.request.GetProperty], text: bool = False, - display: Xlib.display.Display = defaultDisplay) -> Optional[Union[List[int], List[str]]]: - """ - Extract data from retrieved window/root property - - :param prop: Xlib.protocol.request.GetProperty struct from which extract data - :param text: set to ''True'' to convert the atoms (int) to their names (str) - :param display: display to which window belongs to (defaults to default display) - :return: extracted property data (as a list of integers or strings) or None - """ - if prop and hasattr(prop, "value"): - # Value is either str, bytes (separated by '\x00' when multiple values) or array.array of integers. - # The type of array values is stored in array.typecode ('I' in this case). - valueData: Union[array.array[int], bytes] = prop.value - if isinstance(valueData, str) or isinstance(valueData, int): - return [valueData] - if isinstance(valueData, bytes): - resultStr: List[str] = [a for a in valueData.decode().split("\x00") if a] - return resultStr - elif isinstance(valueData, array.array): - if text: - resultStr = [display.get_atom_name(a) for a in valueData if isinstance(a, int) and a != 0] - return resultStr - else: - resultInt: List[int] = [a for a in valueData if isinstance(a, int)] - return resultInt - # Leaving this to detect if data has an unexpected type - return [a for a in valueData] if isinstance(valueData, Iterable) else [valueData] - return None - - -class EwmhRoot: - """ - Base class to access root features. - - To get a EwmhRoot object it's necessary to pass the target root id. This can be achieved in several ways: - - - You already have a root, so pass root.id param - - - You have some criteria to select a root, so use the convenience function getAllDisplaysInfo(), to look - for all roots and select the desired one - - - You have a target window, so use the convenience function getDisplayFromWindow(window.id), so you will - retrieve the associated display connection and root window - - - Instantiate this class with no param (None), so it will retrieve the default display and root - - Apart from given methods, you can access these other values to be used with python-xlib: - - - display: XDisplay connection - - - screen: screen Struct - - - root: root X Window object - - - id: root window id - - WM_PROTOCOLS messages (PING/SYNC) are accessible using wmProtocols subclass (EwmhRoot.wmProtocols.Ping/Sync) - """ - - def __init__(self, root: Optional[Union[XWindow, int]] = None): - - if root and isinstance(root, XWindow): - root = root.id - self.display, self.screen, self.root = getDisplayFromRoot(cast(Optional[int], root)) - self.id = self.root.id - self.wmProtocols = self._WmProtocols(self.display, self.root) - - def getProperty(self, prop: Union[str, int], prop_type: int = Xlib.X.AnyPropertyType) -> Optional[Xlib.protocol.request.GetProperty]: - """ - Retrieves given property from root - - :param prop: Property to query (int or str format) - :param prop_type: Property type (e.g. X.AnyPropertyType or Xatom.STRING) - :return: List of int, List of str or None (nothing obtained) - """ - if isinstance(prop, str): - prop = self.display.get_atom(prop) - return getProperty(self.root, prop, prop_type, self.display) - - def setProperty(self, prop: Union[str, int], data: Union[List[int], str]): - """ - Sets the given property for root - - :param prop: property to set in int or str format. The property can be either an existing property, known and - managed by the Window Manager, or completely new, non-previously existing property. In this last case, the - Wndow Manager will store the property and return its values in a getProperty call but will also ignore it. - :param data: Data related to given property, in List of int or str (like in name) format - """ - sendMessage(self.root.id, prop, data, self.display, self.root) - - def sendMessage(self, winId: int, prop: Union[str, int], data: Union[List[int], str]): - """ - Sends a ClientMessage event to given window - - :param winId: id of the target window - :param prop: property/atom of the event in int or str format - :param data: Data related to the event. It can be str or a list of up to 5 integers - """ - sendMessage(winId, prop, data, self.display, self.root) - - def getSupportedHints(self, text: bool = False) -> Optional[Union[List[int], List[str]]]: - """ - Returns the list of supported hints by the Window Manager. - - This property MUST be set by the Window Manager to indicate which hints it supports. For example: - considering _NET_WM_STATE both this atom and all supported states e.g. _NET_WM_STATE_MODAL, - _NET_WM_STATE_STICKY, would be listed. This assumes that backwards incompatible changes will not be made - to the hints (without being renamed). - - :param text: if ''True'' the values will be returned as strings, or as integers if ''False'' - :return: supported hints as a list of strings / integers - """ - return getPropertyValue(self.getProperty(Root.SUPPORTED), text, self.display) - - def getClientList(self) -> Optional[List[int]]: - """ - Returns the list of XWindows currently opened and managed by the Window Manager, ordered older-to-newer. - - These arrays contain all X Windows managed by the Window Manager. _NET_CLIENT_LIST has initial mapping order, - starting with the oldest window. These properties SHOULD be set and updated by the Window Manager. - - :return: list of integers (XWindows id's) - """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.CLIENT_LIST) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res is not None: - res = cast(List[int], res) - return res - - def getClientListStacking(self) -> Optional[List[int]]: - """ - Returns the list of XWindows currently opened and managed by the Window Manager, ordered in bottom-to-top. - - These arrays contain all X Windows managed by the Window Manager. _NET_CLIENT_LIST_STACKING has - bottom-to-top stacking order. These properties SHOULD be set and updated by the Window Manager. - - :return: list of integers (XWindows id's) - """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.CLIENT_LIST_STACKING) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res is not None: - res = cast(List[int], res) - return res - - def getNumberOfDesktops(self) -> Optional[int]: - """ - This property SHOULD be set and updated by the Window Manager to indicate the number of virtual desktops. - - :return: number of desktops in int format or None - """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.NUMBER_OF_DESKTOPS) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res and isinstance(res[0], int): - return res[0] - return None - - def setNumberOfDesktops(self, number: int): - """ - This property SHOULD be set and updated by the Window Manager to indicate the number of virtual desktops. - - A Pager can request a change in the number of desktops by sending a _NET_NUMBER_OF_DESKTOPS message to the - root window. - - The Window Manager is free to honor or reject this request. If the request is honored _NET_NUMBER_OF_DESKTOPS - MUST be set to the new number of desktops, _NET_VIRTUAL_ROOTS MUST be set to store the new number of desktop - virtual root window IDs and _NET_DESKTOP_VIEWPORT and _NET_WORKAREA must also be changed accordingly. - The _NET_DESKTOP_NAMES property MAY remain unchanged. - - If the number of desktops is shrinking and _NET_CURRENT_DESKTOP is out of the new range of available desktops, - then this MUST be set to the last available desktop from the new set. Clients that are still present on - desktops that are out of the new range MUST be moved to the very last desktop from the new set. For these - _NET_WM_DESKTOP MUST be updated. - - :param number: desired number of desktops to be set by Window Manager - """ - self.setProperty(Root.NUMBER_OF_DESKTOPS, [number]) - - def getDesktopGeometry(self) -> Optional[List[int]]: - """ - Array of two cardinals that defines the common size of all desktops (this is equal to the screen size if the - Window Manager doesn't support large desktops, otherwise it's equal to the virtual size of the desktop). - This property SHOULD be set by the Window Manager. - - :return: tuple of integers (width, height) or None if it couldn't be retrieved - """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.DESKTOP_GEOMETRY) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res is not None: - res = cast(List[int], res) - return res - - def setDesktopGeometry(self, newWidth: int, newHeight: int): - """ - Array of two cardinals that defines the common size of all desktops (this is equal to the screen size if the - Window Manager doesn't support large desktops, otherwise it's equal to the virtual size of the desktop). - This property SHOULD be set by the Window Manager. - - A Pager can request a change in the desktop geometry by sending a _NET_DESKTOP_GEOMETRY client message - to the root window. - - The Window Manager MAY choose to ignore this message, in which case _NET_DESKTOP_GEOMETRY property will - remain unchanged. - - :param newWidth: value for the new target desktop width - :param newHeight: value for the new target desktop height - """ - self.setProperty(Root.DESKTOP_GEOMETRY, [newWidth, newHeight]) - - def getDesktopViewport(self) -> Optional[List[Tuple[int, int]]]: - """ - Array of pairs of cardinals that define the top left corner of each desktop's viewport. - For Window Managers that don't support large desktops, this MUST always be set to (0,0). - - :return: list of int tuples or None (if the value couldn't be retrieved) - """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.DESKTOP_VIEWPORT) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res is not None: - result: List[Tuple[int, int]] = [] - for i in range(len(res)): - if i % 2 != 0: - item: Tuple[int, int] = (res[i-1], res[i]) - result.append(item) - else: - result = [] - return result - - def setDesktopViewport(self, newWidth: int, newHeight: int): - """ - Array of pairs of cardinals that define the top left corner of each desktop's viewport. - For Window Managers that don't support large desktops, this MUST always be set to (0,0). - - A Pager can request to change the viewport for the current desktop by sending a _NET_DESKTOP_VIEWPORT - client message to the root window. - The Window Manager MAY choose to ignore this message, in which case _NET_DESKTOP_VIEWPORT property will - remain unchanged. - """ - self.setProperty(Root.DESKTOP_VIEWPORT, [newWidth, newHeight]) - - def getCurrentDesktop(self) -> Optional[int]: - """ - The index of the current desktop. This is always an integer between 0 and _NET_NUMBER_OF_DESKTOPS - 1. - This MUST be set and updated by the Window Manager. If a Pager wants to switch to another virtual desktop, - it MUST send a _NET_CURRENT_DESKTOP client message to the root window - - :return: index of current desktop in int format or None if it couldn't be retrieved - """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.CURRENT_DESKTOP) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res and isinstance(res[0], int): - return res[0] - return None - - def setCurrentDesktop(self, newDesktop: int): - """ - Change the current desktop to the given desktop. The index of the current desktop is always an integer - between 0 and _NET_NUMBER_OF_DESKTOPS - 1. This MUST be set and updated by the Window Manager. If a - Pager wants to switch to another virtual desktop, it MUST send a _NET_CURRENT_DESKTOP client message to the root window - - :param newDesktop: Index of the target desktop - """ - desks = self.getNumberOfDesktops() - if desks and newDesktop < desks and newDesktop != self.getCurrentDesktop(): - self.setProperty(Root.CURRENT_DESKTOP, [newDesktop, Xlib.X.CurrentTime]) - - def getDesktopNames(self) -> Optional[List[str]]: - """ - The names of all virtual desktops. This is a list of NULL-terminated strings in UTF-8 encoding [UTF8]. - This property MAY be changed by a Pager or the Window Manager at any time. - - Note: The number of names could be different from _NET_NUMBER_OF_DESKTOPS. If it is less than - _NET_NUMBER_OF_DESKTOPS, then the desktops with high numbers are unnamed. If it is larger than - _NET_NUMBER_OF_DESKTOPS, then the excess names outside of the _NET_NUMBER_OF_DESKTOPS are considered - to be reserved in case the number of desktops is increased. - - Rationale: The name is not a necessary attribute of a virtual desktop. Thus the availability or - unavailability of names has no impact on virtual desktop functionality. Since names are set by users - and users are likely to preset names for a fixed number of desktops, it doesn't make sense to shrink - or grow this list when the number of available desktops changes. - - :return: list of desktop names in str format - """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.DESKTOP_NAMES) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res is not None: - res = cast(List[str], res) - return res - - def getActiveWindow(self) -> Optional[int]: - """ - The window ID of the currently active window or None if no window has the focus. This is a read-only - property set by the Window Manager. If a Client wants to activate another window, it MUST send a - _NET_ACTIVE_WINDOW client message to the root window: - - :return: window id or None - """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.ACTIVE) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res and isinstance(res[0], int): - return res[0] - return None - - def getWorkArea(self) -> Optional[List[int]]: - """ - This property MUST be set by the Window Manager upon calculating the work area for each desktop. - Contains a geometry for each desktop. These geometries are specified relative to the viewport on each - desktop and specify an area that is completely contained within the viewport. Work area SHOULD be used - by desktop applications to place desktop icons appropriately. - - The Window Manager SHOULD calculate this space by taking the current page minus space occupied by dock - and panel windows, as indicated by the _NET_WM_STRUT or _NET_WM_STRUT_PARTIAL properties set on client windows. - - :return: tuple containing workarea coordinates (x, y, width, height) - """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.WORKAREA) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res is not None: - res = cast(List[int], res) - return res - - def getSupportingWMCheck(self) -> Optional[List[int]]: - """ - The Window Manager MUST set this property on the root window to be the ID of a child window created by himself, - to indicate that a compliant window manager is active. The child window MUST also have the - _NET_SUPPORTING_WM_CHECK property set to the ID of the child window. The child window MUST also have the - _NET_WM_NAME property set to the name of the Window Manager. - - Rationale: The child window is used to distinguish an active Window Manager from a stale - _NET_SUPPORTING_WM_CHECK property that happens to point to another window. If the _NET_SUPPORTING_WM_CHECK - window on the client window is missing or not properly set, clients SHOULD assume that no conforming - Window Manager is present. - - :return: ''True'' if compliant Window Manager is active or None if it couldn't be retrieved - """ - # Not sure what this property is intended to return. In my system it returns None! - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.SUPPORTING_WM_CHECK) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res is not None: - res = cast(List[int], res) - return res - - def getVirtualRoots(self) -> Optional[List[int]]: - """ - To implement virtual desktops, some Window Managers reparent client windows to a child of the root window. - Window Managers using this technique MUST set this property to a list of IDs for windows that are acting - as virtual root windows. This property allows background setting programs to work with virtual roots and - allows clients to figure out the window manager frame windows of their windows. - - :return: List of virtual roots id's as integers - """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.VIRTUAL_ROOTS) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res is not None: - res = cast(List[int], res) - return res - - def getDesktopLayout(self) -> Optional[List[int]]: - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.DESKTOP_LAYOUT) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res is not None: - res = cast(List[int], res) - return res - - def setDesktopLayout(self, orientation: Union[int, DesktopLayout], columns: int, rows: int, starting_corner: Union[int, DesktopLayout]): - """ - Values (as per EwmhRoot.DesktopLayout): - _NET_WM_ORIENTATION_HORZ 0 - _NET_WM_ORIENTATION_VERT 1 - - _NET_WM_TOPLEFT 0 - _NET_WM_TOPRIGHT 1 - _NET_WM_BOTTOMRIGHT 2 - _NET_WM_BOTTOMLEFT 3 - - This property is set by a Pager, not by the Window Manager. When setting this property, the Pager must - own a manager selection (as defined in the ICCCM 2.8). The manager selection is called _NET_DESKTOP_LAYOUT_Sn - where n is the screen number. The purpose of this property is to allow the Window Manager to know the desktop - layout displayed by the Pager. - - _NET_DESKTOP_LAYOUT describes the layout of virtual desktops relative to each other. More specifically, - it describes the layout used by the owner of the manager selection. The Window Manager may use this layout - information or may choose to ignore it. The property contains four values: the Pager orientation, the number - of desktops in the X direction, the number in the Y direction, and the starting corner of the layout, i.e. - the corner containing the first desktop. - - Note: In order to inter-operate with Pagers implementing an earlier draft of this document, Window Managers - should accept a _NET_DESKTOP_LAYOUT property of length 3 and use _NET_WM_TOPLEFT as the starting corner in - this case. - - The virtual desktops are arranged in a rectangle with rows rows and columns columns. If rows times columns - does not match the total number of desktops as specified by _NET_NUMBER_OF_DESKTOPS, the highest-numbered - workspaces are assumed to be nonexistent. Either rows or columns (but not both) may be specified as 0 in - which case its actual value will be derived from _NET_NUMBER_OF_DESKTOPS. - - When the orientation is _NET_WM_ORIENTATION_HORZ the desktops are laid out in rows, with the first desktop - in the specified starting corner. So a layout with four columns and three rows starting in the _NET_WM_TOPLEFT - corner looks like this: - - +--+--+--+--+ - | 0| 1| 2| 3| - +--+--+--+--+ - | 4| 5| 6| 7| - +--+--+--+--+ - | 8| 9|10|11| - +--+--+--+--+ - With starting_corner _NET_WM_BOTTOMRIGHT, it looks like this: - - +--+--+--+--+ - |11|10| 9| 8| - +--+--+--+--+ - | 7| 6| 5| 4| - +--+--+--+--+ - | 3| 2| 1| 0| - +--+--+--+--+ - When the orientation is _NET_WM_ORIENTATION_VERT the layout with four columns and three rows starting in - the _NET_WM_TOPLEFT corner looks like: - - +--+--+--+--+ - | 0| 3| 6| 9| - +--+--+--+--+ - | 1| 4| 7|10| - +--+--+--+--+ - | 2| 5| 8|11| - +--+--+--+--+ - With starting_corner _NET_WM_TOPRIGHT, it looks like: - - +--+--+--+--+ - | 9| 6| 3| 0| - +--+--+--+--+ - |10| 7| 4| 1| - +--+--+--+--+ - |11| 8| 5| 2| - +--+--+--+--+ - The numbers here are the desktop numbers, as for _NET_CURRENT_DESKTOP. - """ - self.setProperty(Root.DESKTOP_LAYOUT, [orientation, columns, rows, starting_corner]) - - def getShowingDesktop(self) -> Optional[bool]: - """ - Some Window Managers have a "showing the desktop" mode in which windows are hidden, and the desktop - background is displayed and focused. If a Window Manager supports the _NET_SHOWING_DESKTOP hint, it - MUST set it to a value of 1 when the Window Manager is in "showing the desktop" mode, and a value of - zero if the Window Manager is not in this mode. - - :return: ''True'' if showing desktop - """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.SHOWING_DESKTOP) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res: - return res[0] != 0 - return None - - def setShowingDesktop(self, show: bool): - """ - Force showing desktop (minimizing or somehow hiding all open windows) - Some Window Managers have a "showing the desktop" mode in which windows are hidden, and the desktop - background is displayed and focused. If a Window Manager supports the _NET_SHOWING_DESKTOP hint, it - MUST set it to a value of 1 when the Window Manager is in "showing the desktop" mode, and a value of - zero if the Window Manager is not in this mode. - - :param show: ''True'' to enter showing desktop mode, ''False'' to exit - """ - if self.getShowingDesktop() != show: - self.setProperty(Root.SHOWING_DESKTOP, [1 if show else 0]) - - """ - Methods below are always related to a given window. - Makes it sense to include them within Window class in addition to or instead of here? - """ - - def setClosed(self, winId: int, userAction: bool = True): - """ - Close target window - - The Window Manager MUST then attempt to close the window specified. See the section called “Source indication - in requests” for details on the source indication. - - Rationale: A Window Manager might be more clever than the usual method (send WM_DELETE message if the protocol - is selected, XKillClient otherwise). It might introduce a timeout, for example. Instead of duplicating the - code, the Window Manager can easily do the job. - - :param winId: id of window to be closed - :param userAction: set to ''True'' to force action, as if it was requested by an user action - """ - self.sendMessage(winId, Root.CLOSE, [Xlib.X.CurrentTime, 2 if userAction else 1]) - - def setMoveResize(self, winId: int, gravity: int = 0, x: Optional[int] = None, y: Optional[int] = None, width: Optional[int] = None, height: Optional[int] = None, userAction: bool = True): - """ - Moves and/or resize given window - - The low byte of data.l[0] contains the gravity to use; it may contain any value allowed for the - WM_SIZE_HINTS.win_gravity property: NorthWest (1), North (2), NorthEast (3), West (4), Center (5), - East (6), SouthWest (7), South (8), SouthEast (9), Static (10) - - A gravity of 0 indicates that the Window Manager should use the gravity specified in WM_SIZE_HINTS.win_gravity. - - The bits 8 to 11 indicate the presence of x, y, width and height. - - The bits 12 to 15 indicate the source (see the section called “Source indication - in requests”), so 0001 indicates the application and 0010 indicates a Pager or a Taskbar. - - The remaining bits should be set to zero. - - Pagers wanting to move or resize a window may send a _NET_MOVERESIZE_WINDOW client message request to the - root window instead of using a ConfigureRequest. - - Window Managers should treat a _NET_MOVERESIZE_WINDOW message exactly like a ConfigureRequest (in particular, - adhering to the ICCCM rules about synthetic ConfigureNotify events), except that they should use the gravity - specified in the message. - - Rationale: Using a _NET_MOVERESIZE_WINDOW message with StaticGravity allows Pagers to exactly position and - resize a window including its decorations without knowing the size of the decorations. - - :param winId: id of target window to be moved/resized - :param gravity: gravity to apply to the window action. Defaults to 0 (using window defined gravity) - :param x: target x coordinate of window. Defaults to None (unchanged) - :param y: target y coordinate of window. Defaults to None (unchanged) - :param width: target width of window. Defaults to None (unchanged) - :param height: target height of window. Defaults to None (unchanged) - :param userAction: set to ''True'' to force action, as if it was requested by a user action. Defaults to True - """ - # gravity_flags calculations from 'old' ewmh seem to be wrong - # Thanks to elraymond (https://github.com/elraymond) for his help! - gravity_flags = gravity - if x is None: - x = 0 - else: - gravity_flags = gravity_flags | (1 << 8) - if y is None: - y = 0 - else: - gravity_flags = gravity_flags | (1 << 9) - if width is None: - width = 0 - else: - gravity_flags = gravity_flags | (1 << 10) - if height is None: - height = 0 - else: - gravity_flags = gravity_flags | (1 << 11) - if userAction: - gravity_flags = gravity_flags | (1 << 12) - else: - gravity_flags = gravity_flags | (1 << 13) - self.sendMessage(winId, Root.MOVERESIZE, [gravity_flags, x, y, width, height]) - - def setWmMoveResize(self, winId: int, x_root: int, y_root: int, orientation: int, button: int, userAction: bool = True): - """ - This message allows Clients to initiate window movement or resizing. They can define their own move and size - "grips", whilst letting the Window Manager control the actual operation. This means that all moves/resizes - can happen in a consistent manner as defined by the Window Manager. See the section called “Source indication - in requests” for details on the source indication. - - When sending this message in response to a button press event, button SHOULD indicate the button which - was pressed, x_root and y_root MUST indicate the position of the button press with respect to the root window - and direction MUST indicate whether this is a move or resize event, and if it is a resize event, which edges - of the window the size grip applies to. When sending this message in response to a key event, the direction - MUST indicate whether this is a move or resize event and the other fields are unused. - - The Client MUST release all grabs prior to sending such message. - - The Window Manager can use the button field to determine the events on which it terminates the operation - initiated by the _NET_WM_MOVERESIZE message. Since there is a race condition between a client sending the - _NET_WM_MOVERESIZE message and the user releasing the button, Window Managers are advised to offer some - other means to terminate the operation, e.g. by pressing the ESC key. - - :param winId: id of the window to be moved/resized - :param x_root: position of the button press with respect to the root window - :param y_root: position of the button press with respect to the root window - :param orientation: move or resize event - :param button: button pressed - :param userAction: set to ''True'' to force action, as if it was requested by a user action. Defaults to True - """ - # Need to understand this property - self.sendMessage(winId, Root.WM_MOVERESIZE, [x_root, y_root, orientation, button, 2 if userAction else 1]) - - def setWmStacking(self, winId: int, siblingId: int, mode: int, userAction: bool = True): - """ - This request is similar to ConfigureRequest with CWSibling and CWStackMode flags. It should be used only by - pagers, applications can use normal ConfigureRequests. The source indication field should be therefore - set to 2, see the section called “Source indication in requests” for details. - - To obtain good interoperability between different Desktop Environments, the following layered stacking - order is recommended, from the bottom: - - windows of type _NET_WM_TYPE_DESKTOP - - windows having state _NET_WM_STATE_BELOW - - windows not belonging in any other layer - - windows of type _NET_WM_TYPE_DOCK (unless they have state _NET_WM_TYPE_BELOW) and windows having state - _NET_WM_STATE_ABOVE - - focused windows having state _NET_WM_STATE_FULLSCREEN - - Windows that are transient for another window should be kept above this window. - - The window manager may choose to put some windows in different stacking positions, for example to allow - the user to bring a currently active window to the top and return it back when the window looses focus. - - Rationale: A Window Manager may put restrictions on configure requests from applications, for example it may - under some conditions refuse to raise a window. This request makes it clear it comes from a pager or similar - tool, and therefore the Window Manager should always obey it. - - If a sibling and a stack_mode are specified, the window is restacked as follows: - - Above The window is placed just above the sibling. - Below The window is placed just below the sibling. - TopIf If the sibling occludes the window, the window is placed at the top of the stack. - BottomIf If the window occludes the sibling, the window is placed at the bottom of the stack. - Opposite If the sibling occludes the window, the window is placed at the top of the stack. If the window occludes the sibling, the window is placed at the bottom of the stack. - - If a stack_mode is specified but no sibling is specified, the window is restacked as follows: - - Above The window is placed at the top of the stack. - Below The window is placed at the bottom of the stack. - TopIf If any sibling occludes the window, the window is placed at the top of the stack. - BottomIf If the window occludes any sibling, the window is placed at the bottom of the stack. - Opposite If any sibling occludes the window, the window is placed at the top of the stack. If the window occludes any sibling, the window is placed at the bottom of the stack. - - :param winId: id of window to be restacked - :param siblingId: id of sibling window related to restacking action - :param mode: Stacking mode as per table above - :param userAction: set to ''True'' to force action, as if it was requested by a user action. Defaults to True - """ - # Need to understand this property - self.sendMessage(winId, Root.RESTACK, [2 if userAction else 1, siblingId, mode]) - - def requestFrameExtents(self, winId: int): - """ - A Client whose window has not yet been mapped can request of the Window Manager an estimate of the - frame extents it will be given upon mapping. To retrieve such an estimate, the Client MUST send a - _NET_REQUEST_FRAME_EXTENTS message to the root window. The Window Manager MUST respond by estimating - the prospective frame extents and setting the window's _NET_FRAME_EXTENTS property accordingly. - The Client MUST handle the resulting _NET_FRAME_EXTENTS PropertyNotify event. So that the Window Manager - has a good basis for estimation, the Client MUST set any window properties it intends to set before - sending this message. The Client MUST be able to cope with imperfect estimates. - - Rationale: A client cannot calculate the dimensions of its window's frame before the window is mapped, - but some toolkits need this information. Asking the window manager for an estimate of the extents is a - workable solution. The estimate may depend on the current theme, font sizes or other window properties. - The client can track changes to the frame's dimensions by listening for _NET_FRAME_EXTENTS PropertyNotify event - - :param winId: id of window for which estimate the frame extents - """ - # Need to understand this property - self.sendMessage(winId, Root.REQ_FRAME_EXTENTS, []) - - class _WmProtocols: - # Is all this necessary/interesting? - # Besides, should they be included within EwmhRoot class or outside? - - def __init__(self, display: Xlib.display.Display, root: XWindow): - self._display = display - self._root = root - - def ping(self, winId: int): - """ - This protocol allows the Window Manager to determine if the Client is still processing X events. - This can be used by the Window Manager to determine if a window which fails to close after being - sent WM_DELETE_WINDOW has stopped responding or has stalled for some other reason, such as waiting - for user confirmation. A Client SHOULD indicate that it is willing to participate in this protocol - by listing _NET_WM_PING in the WM_PROTOCOLS property of the client window. - - A Window Manager can use this protocol at any time by sending a client message as follows: - - type = ClientMessage - window = the respective client window - message_type = WM_PROTOCOLS - format = 32 - data.l[0] = _NET_WM_PING - data.l[1] = timestamp - data.l[2] = the respective client window - other data.l[] elements = 0 - - A participating Client receiving this message MUST send it back to the root window immediately, - by setting window = root, and calling XSendEvent with the same event mask like all other root - window messages in this specification use. The Client MUST NOT alter any field in the event - other than the window. This includes all 5 longs in the data.l[5] array. The Window Manager - can uniquely identify the ping by the timestamp and the data.l[2] field if necessary. Note - that some older clients may not preserve data.l[2] through data.l[4]. - - The Window Manager MAY kill the Client (using _NET_WM_PID) if it fails to respond to this protocol - within a reasonable time. - - See also the implementation notes on killing hung processes. - """ - sendMessage(winId, self._display.get_atom(Root.PROTOCOLS), - [self._display.get_atom(Root.PING), Xlib.X.CurrentTime, winId], self._display, - self._root) - - def sync(self, winId: int, lowValue: int, highValue: int): - """ - This protocol uses the XSync extension (see the protocol specification and the library documentation) to - let client and window manager synchronize the repaint of the window manager frame and the client window. - A client indicates that it is willing to participate in the protocol by listing _NET_WM_SYNC_REQUEST in - the WM_PROTOCOLS property of the client window and storing the XID of an XSync counter in the property - _NET_WM_SYNC_REQUEST_COUNTER. The initial value of this counter is not defined by this specification. - - A window manager uses this protocol by preceding a ConfigureNotify event sent to a client by a client - message as follows: - - type = ClientMessage - window = the respective client window - message_type = WM_PROTOCOLS - format = 32 - data.l[0] = _NET_WM_SYNC_REQUEST - data.l[1] = timestamp - data.l[2] = low 32 bits of the update request number - data.l[3] = high 32 bits of the update request number - other data.l[] elements = 0 - - After receiving one or more such message/ConfigureNotify pairs, and having handled all repainting - associated with the ConfigureNotify events, the client MUST set the _NET_WM_SYNC_REQUEST_COUNTER to - the 64-bit number indicated by the data.l[2] and data.l[3] fields of the last client message received. - - By using either the Alarm or the Await mechanisms of the XSync extension, the window manager can - know when the client has finished handling the ConfigureNotify events. The window manager SHOULD - not resize the window faster than the client can keep up. - - The update request number in the client message is determined by the window manager subject to - the restriction that it MUST NOT be 0. The number is generally intended to be incremented by one - for each message sent. Since the initial value of the XSync counter is not defined by this specification, - the window manager MAY set the value of the XSync counter at any time, and MUST do so when it first - manages a new window. - """ - sendMessage(winId, self._display.get_atom(Root.PROTOCOLS), - [self._display.get_atom(Root.SYNC), Xlib.X.CurrentTime, lowValue, highValue], - self._display, self._root) - - -defaultEwmhRoot = EwmhRoot() - - -class EwmhWindow: - """ - Base class to access application windows related features. - - To instantiate this class only a window id is required. It's possible to retrieve this value in several ways: - - - Target a specific window using an external module (e.g. PyWinCtl.getAllWindowsWithTitle(title)) - - - Retrieve it from your own application (e.g. PyQt's winId() or TKinter's frame()) - - Note that, although a root is also a window, these methods will not likely work with it. - - Apart from given methods, there are some values you can use with python-xlib: - - - display: XDisplay connection - - - screen: screen Struct - - - root: root X Window object - - - EwmhRoot: object to access EwmhRoot methods - - - xWindow: X Window object associated to current window - - - id: current window's id - - Additional, non-EWMH features, related to low-level window properties like hints, protocols and events are - available using extensions subclass (EwmhWindow.extensions.*) - """ - - def __init__(self, window: Union[int, XWindow]): - - if isinstance(window, XWindow): - self.id: int = window.id - self.display, self.screen, self.root = getDisplayFromWindow(self.id) - self.xWindow: XWindow = window - elif isinstance(window, int): - self.id = window - self.display, self.screen, self.root = getDisplayFromWindow(self.id) - self.xWindow = self.display.create_resource_object('window', self.id) - else: - raise ValueError - self.ewmhRoot: EwmhRoot = defaultEwmhRoot if self.root.id == defaultRoot.id else EwmhRoot(self.root) - self.extensions = _Extensions(self.id, self.display, self.root) - - self._currDesktop = os.environ.get('XDG_CURRENT_DESKTOP', '').lower() - - def getProperty(self, prop: Union[str, int], prop_type: int = Xlib.X.AnyPropertyType) \ - -> Optional[Xlib.protocol.request.GetProperty]: - """ - Retrieves given property data from given window - - :param prop: Property to query (int or str format) - :param prop_type: Property type (e.g. X.AnyPropertyType or Xatom.STRING) - :return: List of int, List of str or None (nothing obtained) - """ - if isinstance(prop, str): - prop = self.display.get_atom(prop) - return getProperty(self.xWindow, prop, prop_type, self.display) - - def sendMessage(self, prop: Union[str, int], data: Union[List[int], str]): - """ - Sends a ClientMessage event to current window - - :param prop: Property/atom of the event in int or str format - :param data: Data related to the event. It can be str (format is 8) or a list of up to 5 integers (format is 32) - """ - return sendMessage(self.id, prop, data) - - def changeProperty(self, prop: Union[str, int], data: Union[List[int], str], - prop_type: int = Xlib.Xatom.ATOM, propMode: Mode = Mode.REPLACE): - """ - Sets given property for the current window. The property might be ignored by the Window Manager, but returned - in getProperty() calls together with its data. - - :param prop: Property/atom of the event in int or str format - :param data: Data related to the event. It can be a string or a list of up to 5 integers - :param prop_type: Property type (e.g. X.AnyPropertyType or Xatom.STRING) - :param propMode: whether to Replace/Append/Prepend (Mode.*) the property in respect to the rest of - existing properties - """ - changeProperty(self.xWindow, prop, data, prop_type, propMode, self.display) - - def getName(self) -> Optional[str]: - """ - Get the name of the current window. - Some windows may not have a title, the title may change or even this query may fail (e.g. for root windows) - - The Client SHOULD set this to the title of the window in UTF-8 encoding. If set, the Window Manager should - use this in preference to WM_NAME. - - :return: name of the window as str or None (nothing obtained) - """ - # Xlib's get_wm_name() returns empty strings in some cases - # Thanks to cedricscheepers - https://github.com/cedricscheepers for pointing out this issue - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.NAME) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - # Some (older) apps do not set _NET_WM_NAME property, but WM_NAME. Using it as fallback - # Thans to ReaperMantis - https://github.com/ReaperMantis for finding a solution! - if res: - return str(res[0]) - ret = self.getProperty(Window.LEGACY_NAME) - res = getPropertyValue(ret, display=self.display) - if res: - return str(res[0]) - return None - - def setName(self, name: str): - """ - Change the name of the current window - - :param name: new name as string - """ - self.changeProperty(Window.NAME, name) - - def getVisibleName(self) -> Optional[str]: - """ - Get the visible name of the current window. - Some windows may not have a title, the title may change or even this query may fail (e.g. for root windows) - - If the Window Manager displays a window name other than _NET_WM_NAME the Window Manager MUST set this to - the title displayed in UTF-8 encoding. - Rationale: This property is for Window Managers that display a title different from the _NET_WM_NAME or - WM_NAME of the window (i.e. xterm <1>, xterm <2>, ... is shown, but _NET_WM_NAME / WM_NAME is still xterm - for each window) thereby allowing Pagers to display the same title as the Window Manager. - - :return: visible name of the window as str or None (nothing obtained) - """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.VISIBLE_NAME) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res: - return str(res[0]) - return None - - def setVisibleName(self, name: str): - """ - Set the visible name of the current window - - :param name: new visible name as string - """ - self.changeProperty(Window.VISIBLE_NAME, name) - - def getIconName(self) -> Optional[str]: - """ - Get the name of the window icon - - The Client SHOULD set this to the title of the icon for this window in UTF-8 encoding. If set, the Window - Manager should use this in preference to WM_ICON_NAME. - - :return: icon name as string - """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Xlib.Xatom.WM_ICON_NAME) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res: - return str(res[0]) - return None - - def setIconName(self, name: str): - """ - Change the name of the window icon - - :param name: new icon name as string - """ - self.changeProperty(Xlib.Xatom.WM_ICON_NAME, name) - - def getVisibleIconName(self) -> Optional[str]: - """ - Get the visible name of the window icon. - - If the Window Manager displays an icon name other than _NET_WM_ICON_NAME the Window Manager MUST set this - to the title displayed in UTF-8 encoding. - - :return: visible icon name as string - """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.VISIBLE_ICON_NAME) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res: - return str(res[0]) - return None - - def setVisibleIconName(self, name: str): - """ - Change the visible name of window icon - - :param name: new visible icon name as string - """ - self.changeProperty(Window.VISIBLE_ICON_NAME, name) - - def getDesktop(self) -> Optional[int]: - """ - Cardinal to determine the desktop the window is in (or wants to be) starting with 0 for the first desktop. - A Client MAY choose not to set this property, in which case the Window Manager SHOULD place it as it wishes. - 0xFFFFFFFF indicates that the window SHOULD appear on all desktops. - - The Window Manager should honor _NET_WM_DESKTOP whenever a withdrawn window requests to be mapped. - - The Window Manager should remove the property whenever a window is withdrawn but it should leave the - property in place when it is shutting down, e.g. in response to losing ownership of the WM_Sn manager - selection. - - Rationale: Removing the property upon window withdrawal helps legacy applications which want to reuse - withdrawn windows. Not removing the property upon shutdown allows the next Window Manager to restore - windows to their previous desktops. - - :return: desktop index on which current window is showing - """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.DESKTOP) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res and isinstance(res[0], int): - return int(res[0]) - return None - - def setDesktop(self, newDesktop: int, userAction: bool = True): - """ - Move the window to the given desktop - - :param newDesktop: target desktop index (as per getNumberOfDesktops()) - :param userAction: source indication (user or pager/manager action). Defaults to True - """ - numDesktops: Optional[int] = self.ewmhRoot.getNumberOfDesktops() - currDesktop: Optional[int] = self.ewmhRoot.getCurrentDesktop() - if numDesktops is not None and 0 <= newDesktop <= numDesktops and currDesktop is not None and newDesktop != currDesktop: - self.sendMessage(Window.DESKTOP, [newDesktop, Xlib.X.CurrentTime, 2 if userAction else 1]) - - def getWmWindowType(self, text: bool = False) -> Optional[Union[List[int], List[str]]]: - """ - Gets the window type of current window - - This SHOULD be set by the Client before mapping to a list of atoms indicating the functional type of the window. - This property SHOULD be used by the window manager in determining the decoration, stacking position and other - behavior of the window. The Client SHOULD specify window types in order of preference (the first being most - preferable) but MUST include at least one of the basic window type atoms from the list below. This is to allow - for extension of the list of types whilst providing default behavior for Window Managers that do not recognize - the extensions. - - Rationale: This hint is intended to replace the MOTIF hints. One of the objections to the MOTIF hints is that - they are a purely visual description of the window decoration. By describing the function of the window, - the Window Manager can apply consistent decoration and behavior to windows of the same type. Possible examples - of behavior include keeping dock/panels on top or allowing pinnable menus / toolbars to only be hidden when - another window has focus (NextStep style). - - _NET_WM_WINDOW_TYPE_DESKTOP indicates a desktop feature. This can include a single window containing desktop - icons with the same dimensions as the screen, allowing the desktop environment to have full control of the - desktop, without the need for proxying root window clicks. - - _NET_WM_WINDOW_TYPE_DOCK indicates a dock or panel feature. Typically a Window Manager would keep such windows - on top of all other windows. - - _NET_WM_WINDOW_TYPE_TOOLBAR and _NET_WM_WINDOW_TYPE_MENU indicate toolbar and pinnable menu windows, - respectively (i.e. toolbars and menus "torn off" from the main application). Windows of this type may - set the WM_TRANSIENT_FOR hint indicating the main application window. - - _NET_WM_WINDOW_TYPE_UTILITY indicates a small persistent utility window, such as a palette or toolbox. - It is distinct from type TOOLBAR because it does not correspond to a toolbar torn off from the main - application. It's distinct from type DIALOG because it isn't a transient dialog, the user will probably - keep it open while they're working. Windows of this type may set the WM_TRANSIENT_FOR hint indicating - the main application window. - - _NET_WM_WINDOW_TYPE_SPLASH indicates that the window is a splash screen displayed as an application - is starting up. - - _NET_WM_WINDOW_TYPE_DIALOG indicates that this is a dialog window. If _NET_WM_WINDOW_TYPE is not set, - then windows with WM_TRANSIENT_FOR set MUST be taken as this type. - - _NET_WM_WINDOW_TYPE_NORMAL indicates that this is a normal, top-level window. Windows with neither - _NET_WM_WINDOW_TYPE nor WM_TRANSIENT_FOR set MUST be taken as this type. - - :param text: if ''True'', the types will be returned in string format, or as integers if ''False'' - :return: List of window types as integer or strings - """ - return getPropertyValue(self.getProperty(Window.WM_WINDOW_TYPE), text, self.display) - - def setWmWindowType(self, winType: Union[str, WindowType]): - """ - Changes the type of current window. - - See getWmWindowType() documentation for more information about window types. - - :param winType: target window type as WindowType (WindowType.*) - """ - x, y, w, h = _getWindowGeom(self.xWindow, self.root.id) - self.xWindow.unmap() # -> Needed in Mint/Cinnamon - atom = self.display.get_atom(str(winType), True) - self.changeProperty(Window.WM_WINDOW_TYPE, [atom]) - self.xWindow.map() - self.display.flush() - self.setMoveResize(x=x, y=y, width=w, height=h) - - def getWmState(self, text: bool = False) -> Optional[Union[List[int], List[str]]]: - """ - Get the window states values of current window. - - A list of hints describing the window State. Atoms present in the list MUST be considered set, atoms not - present in the list MUST be considered not set. The Window Manager SHOULD honor _NET_WM_STATE whenever a - withdrawn window requests to be mapped. A Client wishing to change the state of a window MUST send a - _NET_WM_STATE client message to the root window (see below). The Window Manager MUST keep this property - updated to reflect the current state of the window. - - The Window Manager should remove the property whenever a window is withdrawn, but it should leave the - property in place when it is shutting down, e.g. in response to losing ownership of the WM_Sn manager - selection. - - Rationale: Removing the property upon window withdrawal helps legacy applications which want to reuse - withdrawn windows. Not removing the property upon shutdown allows the next Window Manager to restore - windows to their previous State. - - An implementation MAY add new atoms to this list. Implementations without extensions MUST ignore any - unknown atoms, effectively removing them from the list. These extension atoms MUST NOT start with the - prefix _NET. - - _NET_WM_STATE_MODAL indicates that this is a modal dialog box. If the WM_TRANSIENT_FOR hint is set to - another toplevel window, the dialog is modal for that window; if WM_TRANSIENT_FOR is not set or set to - the root window the dialog is modal for its window group. - - _NET_WM_STATE_STICKY indicates that the Window Manager SHOULD keep the window's position fixed on the - screen, even when the virtual desktop scrolls. - - _NET_WM_STATE_MAXIMIZED_{VERT,HORZ} indicates that the window is {vertically,horizontally} maximized. - - _NET_WM_STATE_SHADED indicates that the window is shaded. - - _NET_WM_STATE_SKIP_TASKBAR indicates that the window should not be included on a taskbar. This hint should - be requested by the application, i.e. it indicates that the window by nature is never in the taskbar. - Applications should not set this hint if _NET_WM_WINDOW_TYPE already conveys the exact nature of the window. - - _NET_WM_STATE_SKIP_PAGER indicates that the window should not be included on a Pager. This hint should - be requested by the application, i.e. it indicates that the window by nature is never in the Pager. - Applications should not set this hint if _NET_WM_WINDOW_TYPE already conveys the exact nature of the window. - - _NET_WM_STATE_HIDDEN should be set by the Window Manager to indicate that a window would not be visible - on the screen if its desktop/viewport were active and its coordinates were within the screen bounds. - The canonical example is that minimized windows should be in the _NET_WM_STATE_HIDDEN State. Pagers and - similar applications should use _NET_WM_STATE_HIDDEN instead of WM_STATE to decide whether to display a - window in miniature representations of the windows on a desktop. - - Implementation note: if an Application asks to toggle _NET_WM_STATE_HIDDEN the Window Manager should - probably just ignore the request, since _NET_WM_STATE_HIDDEN is a function of some other aspect of the - window such as minimization, rather than an independent State. - - _NET_WM_STATE_FULLSCREEN indicates that the window should fill the entire screen and have no window - decorations. Additionally, the Window Manager is responsible for restoring the original geometry after - a switch from fullscreen back to normal window. For example, a presentation program would use this hint. - - _NET_WM_STATE_ABOVE indicates that the window should be on top of most windows (see the section called - “Stacking order” for details). - - _NET_WM_STATE_BELOW indicates that the window should be below most windows (see the section called - “Stacking order” for details). - - _NET_WM_STATE_ABOVE and _NET_WM_STATE_BELOW are mainly meant for user preferences and should not be - used by applications e.g. for drawing attention to their dialogs (the Urgency hint should be used in - that case, see the section called “Urgency”).' - - _NET_WM_STATE_DEMANDS_ATTENTION indicates that some action in or with the window happened. For example, - it may be set by the Window Manager if the window requested activation but the Window Manager refused it, - or the application may set it if it finished some work. This state may be set by both the Client and the - Window Manager. It should be unset by the Window Manager when it decides the window got the required - attention (usually, that it got activated). - - :param text: if ''True'', the states will be returned in string format, or as integers if ''False'' - :return: List of integers or strings - """ - return getPropertyValue(self.getProperty(Window.WM_STATE), text, self.display) - - def changeWmState(self, action: StateAction, state: Union[str, State], state2: Union[str, State] = State.NULL, userAction: bool = True): - """ - Sets the window states values of current window. - - See setWmState() documentation for more information on Window States. - - This message allows two properties to be changed simultaneously, specifically to allow both horizontal - and vertical maximization to be altered together. l[2] MUST be set to zero if only one property is to - be changed. See the section called “Source indication in requests” for details on the source indication. - l[0], the action, MUST be one of: - - _NET_WM_STATE_REMOVE 0 # remove/unset property - _NET_WM_STATE_ADD 1 # add/set property - _NET_WM_STATE_TOGGLE 2 # toggle property - - :param action: Action to perform with the state: ADD/REMOVE/TOGGLE (StateAction.*) - :param state: Target new state as State (State.*) - :param state2: Up to two states can be changed at once. Defaults to 0 (no second state to change). - :param userAction: source indication (user or pager/manager action). Defaults to True - """ - st1: int = self.display.get_atom(str(state)) - st2: int = self.display.get_atom(str(state2)) if state2 != State.NULL else 0 - self.sendMessage(self.display.get_atom(Window.WM_STATE), [action, st1, st2, 2 if userAction else 1]) - - def setMaximized(self, maxHorz: bool, maxVert: bool): - """ - Set or unset the values of maximized states (horizontal/vertical), individually. - - :param maxHorz: ''True'' / ''False'' to indicate whether the window should be horizontally maximized or not - :param maxVert: ''True'' / ''False'' to indicate whether the window should be vertically maximized or not - """ - NULL = State.NULL - state1 = NULL - state2 = NULL - ret: Optional[Union[List[int], List[str]]] = self.getWmState(True) - states: List[str] = [] if ret is None else [a for a in ret if a and isinstance(a, str)] - if maxHorz and maxVert: - if State.MAXIMIZED_HORZ not in states: - state1 = State.MAXIMIZED_HORZ - if State.MAXIMIZED_VERT not in states: - state2 = State.MAXIMIZED_VERT - if state1 or state2: - self.changeWmState(StateAction.ADD, cast(State, state1) if state1 != NULL else cast(State, state2), - cast(State, state2) if state1 != NULL else cast(State, NULL)) - elif maxHorz: - if State.MAXIMIZED_HORZ not in states: - state = State.MAXIMIZED_HORZ - self.changeWmState(StateAction.ADD, cast(State, state), cast(State, NULL)) - if State.MAXIMIZED_VERT in states: - state = State.MAXIMIZED_VERT - self.changeWmState(StateAction.REMOVE, cast(State, state), cast(State, NULL)) - elif maxVert: - if State.MAXIMIZED_HORZ in states: - state = State.MAXIMIZED_HORZ - self.changeWmState(StateAction.REMOVE, cast(State, state), cast(State, NULL)) - if State.MAXIMIZED_VERT not in states: - state = State.MAXIMIZED_VERT - self.changeWmState(StateAction.ADD, cast(State, state), cast(State, NULL)) - else: - if State.MAXIMIZED_HORZ in states: - state1 = State.MAXIMIZED_HORZ - if State.MAXIMIZED_VERT in states: - state2 = State.MAXIMIZED_VERT - if state1 or state2: - self.changeWmState(StateAction.REMOVE, cast(State, state1) if state1 != NULL else cast(State, state2), - cast(State, state2) if state1 != NULL else cast(State, NULL)) - - def setMinimized(self): - """ - Set Iconified (minimized) state for current window - - Unlike maximized, this action can only be reverted by using setActive() method. - """ - states = self.getWmState(True) - if not states or (states and State.HIDDEN not in states): - atom = self.display.get_atom(Window.CHANGE_STATE, True) - self.sendMessage(atom, [Xlib.Xutil.IconicState]) - - def getAllowedActions(self, text: bool = False) -> Optional[Union[List[int], List[str]]]: - """ - Gets the list of allowed actions for current window. - - A list of atoms indicating user operations that the Window Manager supports for this window. Atoms - present in the list indicate allowed actions, atoms not present in the list indicate actions that are - not supported for this window. The Window Manager MUST keep this property updated to reflect the actions - which are currently "active" or "sensitive" for a window. Taskbars, Pagers, and other tools use - _NET_WM_ALLOWED_ACTIONS to decide which actions should be made available to the user. - - An implementation MAY add new atoms to this list. Implementations without extensions MUST ignore any - unknown atoms, effectively removing them from the list. These extension atoms MUST NOT start with the - prefix _NET. - - Note that the actions listed here are those that the Window Manager will honor for this window. The - operations must still be requested through the normal mechanisms outlined in this specification. For example, - _NET_WM_ACTION_CLOSE does not mean that clients can send a WM_DELETE_WINDOW message to this window; it means - that clients can use a _NET_CLOSE_WINDOW message to ask the Window Manager to do so. - - Window Managers SHOULD ignore the value of _NET_WM_ALLOWED_ACTIONS when they initially manage a window. - This value may be left over from a previous Window Manager with different policies. - - _NET_WM_ACTION_MOVE indicates that the window may be moved around the screen. - - _NET_WM_ACTION_RESIZE indicates that the window may be resized. (Implementation note: Window Managers can - identify a non-resizable window because its minimum and maximum size in WM_NORMAL_HINTS will be the same.) - - _NET_WM_ACTION_MINIMIZE indicates that the window may be iconified. - - _NET_WM_ACTION_SHADE indicates that the window may be shaded. - - _NET_WM_ACTION_STICK indicates that the window may have its sticky state toggled (as for _NET_WM_STATE_STICKY). - Note that this state has to do with viewports, not desktops. - - _NET_WM_ACTION_MAXIMIZE_HORZ indicates that the window may be maximized horizontally. - - _NET_WM_ACTION_MAXIMIZE_VERT indicates that the window may be maximized vertically. - - _NET_WM_ACTION_FULLSCREEN indicates that the window may be brought to fullscreen State. - - _NET_WM_ACTION_CHANGE_DESKTOP indicates that the window may be moved between desktops. - - _NET_WM_ACTION_CLOSE indicates that the window may be closed (i.e. a WM_DELETE_WINDOW message may be sent). - - :param text: if ''True'', the actions will be returned in string format, or as integers if ''False'' - :return: List of integers or strings - """ - return getPropertyValue(self.getProperty(Window.ALLOWED_ACTIONS), text, self.display) - - # Can this be set??? If not, investigate hints and protocols, which might be related (e.g. 'WM_DELETE_WINDOW') - # def setAllowedActions(self, newActions: Union[List[int], List[str]]): - # """ - # Set the allowed actions of current window. - # - # :param newActions: List of new actions allowed, in integer or string format - # """ - # pass - - def getStrut(self) -> Optional[List[int]]: - """ - This property is equivalent to a _NET_WM_STRUT_PARTIAL property where all start values are 0 and all - end values are the height or width of the logical screen. _NET_WM_STRUT_PARTIAL was introduced later - than _NET_WM_STRUT, however, so clients MAY set this property in addition to _NET_WM_STRUT_PARTIAL to - ensure backward compatibility with Window Managers supporting older versions of the Specification. - - :return: List of integers (left, right, top, bottom) defining the width of the reserved area at each border of - the screen - """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.STRUT) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res is not None: - res = cast(List[int], res) - return res - - def setStrut(self, left: int, right: int, top: int, bottom: int): - """ - Set a new desktop strut (reserved space at the screen borders) - - See getStrut() and getStrutPartial() documentation for more info on this feature - - :param left: left coordinate of strut - :param right: left coordinate of strut - :param top: top coordinate of strut - :param bottom: bottom coordinate of strut - """ - # Need to understand this - self.sendMessage(Window.STRUT, [left, right, top, bottom]) - - def getStrutPartial(self) -> Optional[List[int]]: - """ - This property MUST be set by the Client if the window is to reserve space at the edge of the screen. - The property contains 4 cardinals specifying the width of the reserved area at each border of the screen, - and an additional 8 cardinals specifying the beginning and end corresponding to each of the four struts. - The order of the values is left, right, top, bottom, left_start_y, left_end_y, right_start_y, right_end_y, - top_start_x, top_end_x, bottom_start_x, bottom_end_x. All coordinates are root window coordinates. - The client MAY change this property at any time, therefore the Window Manager MUST watch for property - notify events if the Window Manager uses this property to assign special semantics to the window. - - If both this property and the _NET_WM_STRUT property are set, the Window Manager MUST ignore the _NET_WM_STRUT - property values and use instead the values for _NET_WM_STRUT_PARTIAL. This will ensure that Clients can safely - set both properties without giving up the improved semantics of the new property. - - The purpose of struts is to reserve space at the borders of the desktop. This is very useful for a docking area, - a taskbar or a panel, for instance. The Window Manager should take this reserved area into account when - constraining window positions - maximized windows, for example, should not cover that area. - - The start and end values associated with each strut allow areas to be reserved which do not span the entire - width or height of the screen. Struts MUST be specified in root window coordinates, that is, they are not - relative to the edges of any view port or Xinerama monitor. - - For example, for a panel-style Client appearing at the bottom of the screen, 50 pixels tall, and occupying - the space from 200-600 pixels from the left of the screen edge would set a bottom strut of 50, and set - bottom_start_x to 200 and bottom_end_x to 600. Another example is a panel on a screen using the Xinerama - extension. Assume that the set-up uses two monitors, one running at 1280x1024 and the other to the right - running at 1024x768, with the top edge of the two physical displays aligned. If the panel wants to fill the - entire bottom edge of the smaller display with a panel 50 pixels tall, it should set a bottom strut of 306, - with bottom_start_x of 1280, and bottom_end_x of 2303. Note that the strut is relative to the screen edge, - and not the edge of the xinerama monitor. - - Rationale: A simple "do not cover" hint is not enough for dealing with e.g. auto-hide panels. - - Notes: An auto-hide panel SHOULD set the strut to be its minimum, hidden size. A "corner" panel that does not - extend for the full length of a screen border SHOULD only set one strut. - - :return: List of integers containing 4 cardinals specifying the width of the reserved area at each border of - the screen, and an additional 8 cardinals specifying the beginning and end corresponding to each of the - four struts. The order of the values is left, right, top, bottom, left_start_y, left_end_y, right_start_y, - right_end_y, top_start_x, top_end_x, bottom_start_x, bottom_end_x - """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.STRUT_PARTIAL) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res is not None: - res = cast(List[int], res) - return res - - # Need to understand this / Can it be set and, if so, how to pass all those values??? - # def setStrutPartial(self, left: int, right: int, top: int, bottom: int, - # left_start_y: int, left_end_y: int, right_start_y: int, right_end_y: int, - # top_start_x: int, top_end_x: int, bottom_start_x: int, bottom_end_x: int): - # """ - # Set new Strut Partial property. - # - # See getStrutPartial() documentation for more information on this property. - # """ - # pass - - def getIconGeometry(self) -> Optional[List[int]]: - """ - Get the geometry of current window icon. - - This optional property MAY be set by stand-alone tools like a taskbar or an iconbox. It specifies the geometry - of a possible icon in case the window is iconified. - - Rationale: This makes it possible for a Window Manager to display a nice animation like morphing the window - into its icon. - - :return: List of integers containing the icon geometry or None (no obtained) - """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.ICON_GEOMETRY) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res is not None: - res = cast(List[int], res) - return res - - def getPid(self) -> Optional[int]: - """ - Get the Process ID (pid) of the process to which current window belongs to. - - If set, this property MUST contain the process ID of the client owning this window. This MAY be used by - the Window Manager to kill windows which do not respond to the _NET_WM_PING protocol. - - If _NET_WM_PID is set, the ICCCM-specified property WM_CLIENT_MACHINE MUST also be set. While the ICCCM - only requests that WM_CLIENT_MACHINE is set “ to a string that forms the name of the machine running the - client as seen from the machine running the server” conformance to this specification requires that - WM_CLIENT_MACHINE be set to the fully-qualified domain name of the client's host. - - :return: pid of current process as integer - """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.PID) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res: - return int(res[0]) - return None - - def getHandledIcons(self) -> Optional[List[int]]: - """ - Get the id of icons handled by the window. - - This property can be set by a Pager on one of its own toplevel windows to indicate that the Window Manager - need not provide icons for iconified windows, for example if it is a taskbar and provides buttons for - iconified windows. - - :return: List of integers or None (not obtained) - """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.HANDLED_ICONS) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res is not None: - res = cast(List[int], res) - return res - - def getUserTime(self) -> Optional[List[int]]: - """ - Get time since last user activity on current window. - - This property contains the XServer time at which last user activity in this window took place. - - Clients should set this property on every new toplevel window, before mapping the window, to the - timestamp of the user interaction that caused the window to appear. A client that only deals with - core events, might, for example, use the timestamp of the last KeyPress or ButtonPress event. - ButtonRelease and KeyRelease events should not generally be considered to be user interaction, - because an application may receive KeyRelease events from global keybindings, and generally release - events may have later timestamp than actions that were triggered by the matching press events. - Clients can obtain the timestamp that caused its first window to appear from the DESKTOP_STARTUP_ID - environment variable, if the app was launched with startup notification. If the client does not know - the timestamp of the user interaction that caused the first window to appear (e.g. because it was not - launched with startup notification), then it should not set the property for that window. The special - value of zero on a newly mapped window can be used to request that the window not be initially focused - when it is mapped. - - If the client has the active window, it should also update this property on the window whenever there's - user activity. - - Rationale: This property allows a Window Manager to alter the focus, stacking, and/or placement behavior - of windows when they are mapped depending on whether the new window was created by a user action or is a - "pop-up" window activated by a timer or some other event. - - :return: timestamp in integer format or None (not obtained) - """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.USER_TIME) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res is not None: - res = cast(List[int], res) - - return res - - def _getNetFrameExtents(self) -> Optional[Union[List[int], List[str]]]: - return getPropertyValue(self.getProperty("_NET_FRAME_EXTENTS")) - - def _getGtkFrameExtents(self) -> Optional[Union[List[int], List[str]]]: - return getPropertyValue(self.getProperty("_GTK_FRAME_EXTENTS")) - - def getFrameExtents(self) -> Optional[List[int]]: - """ - Get the current window frame extents (space reserved by the window manager around window) - - The Window Manager MUST set _NET_FRAME_EXTENTS to the extents of the window's frame. left, right, top - and bottom are widths of the respective borders added by the Window Manager. - - AUTHOR COMMENT: Since GNOME doesn't obey this rule, _GTK_FRAME_EXTENTS has been added to try to get - the proper values (though, again, GNOME uses them in a very different way). - - :return: left, right, top, bottom - """ - res: Optional[Union[List[int], List[str]]] = self._getNetFrameExtents() or self._getGtkFrameExtents() - if res is not None: - res = cast(List[int], res) - return res - - def getOpaqueRegion(self) -> Optional[List[int]]: - """ - The Client MAY set this property to a list of 4-tuples [x, y, width, height], each representing a rectangle - in window coordinates where the pixels of the window's contents have a fully opaque alpha value. If the - window is drawn by the compositor without adding any transparency, then such a rectangle will occlude - whatever is drawn behind it. When the window has an RGB visual rather than an ARGB visual, this property - is not typically useful, since the effective opaque region of a window is exactly the bounding region of - the window as set via the shape extension. For windows with an ARGB visual and also a bounding region set - via the shape extension, the effective opaque region is given by the intersection of the region set by this - property and the bounding region set via the shape extension. The compositing manager MAY ignore this hint. - - Rationale: This gives the compositing manager more room for optimizations. For example, it can avoid drawing - occluded portions behind the window. - """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.OPAQUE_REGION) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res is not None: - res = cast(List[int], res) - return res - - def getBypassCompositor(self) -> Optional[int]: - """ - The Client MAY set this property to a list of 4-tuples [x, y, width, height], each representing a rectangle - in window coordinates where the pixels of the window's contents have a fully opaque alpha value. If the window - is drawn by the compositor without adding any transparency, then such a rectangle will occlude whatever is - drawn behind it. When the window has an RGB visual rather than an ARGB visual, this property is not typically - useful, since the effective opaque region of a window is exactly the bounding region of the window as set via - the shape extension. For windows with an ARGB visual and also a bounding region set via the shape extension, - the effective opaque region is given by the intersection of the region set by this property and the bounding - region set via the shape extension. The compositing manager MAY ignore this hint. - - Rationale: This gives the compositing manager more room for optimizations. For example, it can avoid - drawing occluded portions behind the window. - """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.BYPASS_COMPOSITOR) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) - if res: - return int(res[0]) - return None - - def setActive(self, userAction: bool = True): - """ - Set current window as active (focused). - - Source indication should be 1 when the request comes from an application, and 2 when it comes from a pager. - Clients using older version of this spec use 0 as source indication, see the section called “Source indication - in requests” for details. The timestamp is Client's last user activity timestamp (see _NET_WM_USER_TIME) at - the time of the request, and the currently active window is the Client's active toplevel window, if any - (the Window Manager may be e.g. more likely to obey the request if it will mean transferring focus from one - active window to another). - - Depending on the information provided with the message, the Window Manager may decide to refuse the request - (either completely ignore it, or e.g. use _NET_WM_STATE_DEMANDS_ATTENTION). - - :param userAction: source indication (user or pager/manager action). Defaults to True - """ - atom = self.display.get_atom(Window.ACTIVE, True) - self.sendMessage(atom, [2 if userAction else 1, Xlib.X.CurrentTime, self.xWindow.id]) - - def setClosed(self, userAction: bool = True): - """ - Request to close current window. - - The Window Manager MUST then attempt to close the window specified. See the section called “Source - indication in requests” for details on the source indication. - - Rationale: A Window Manager might be more clever than the usual method (send WM_DELETE message if the - protocol is selected, XKillClient otherwise). It might introduce a timeout, for example. Instead of - duplicating the code, the Window Manager can easily do the job. - - :param userAction: source indication (user or pager/manager action). Defaults to True - """ - atom = self.display.get_atom(Window.CLOSE, True) - self.sendMessage(atom, [Xlib.X.CurrentTime, 2 if userAction else 1]) - - def changeStacking(self, mode: int, sibling: Optional[XWindow] = None): - """ - Changes current window position (stacking) in relation to its siblings. - - To obtain good interoperability between different Desktop Environments, the following layered stacking - order is recommended, from the bottom: - - windows of type _NET_WM_TYPE_DESKTOP - - windows having state _NET_WM_STATE_BELOW - - windows not belonging in any other layer - - windows of type _NET_WM_TYPE_DOCK (unless they have state _NET_WM_TYPE_BELOW) and windows having state - _NET_WM_STATE_ABOVE - - focused windows having state _NET_WM_STATE_FULLSCREEN - - Windows that are transient for another window should be kept above this window. - - The window manager may choose to put some windows in different stacking positions, for example to allow - the user to bring a currently active window to the top and return it back when the window looses focus. - - If a sibling and a stack_mode are specified, the window is restacked as follows: - - Above The window is placed just above the sibling. - Below The window is placed just below the sibling. - TopIf If the sibling occludes the window, the window is placed at the top of the stack. - BottomIf If the window occludes the sibling, the window is placed at the bottom of the stack. - Opposite If the sibling occludes the window, the window is placed at the top of the stack. If the window occludes the sibling, the window is placed at the bottom of the stack. - - If a stack_mode is specified but no sibling is specified, the window is restacked as follows: - - Above The window is placed at the top of the stack. - Below The window is placed at the bottom of the stack. - TopIf If any sibling occludes the window, the window is placed at the top of the stack. - BottomIf If the window occludes any sibling, the window is placed at the bottom of the stack. - Opposite If any sibling occludes the window, the window is placed at the top of the stack. If the window occludes any sibling, the window is placed at the bottom of the stack. - - :param mode: stack mode as per table above - :param sibling: Sibling window to which re-stacking action will be related to - """ - if sibling is not None: - self.xWindow.configure(sibling=sibling, stack_mode=mode) - else: - self.xWindow.configure(stack_mode=mode) - self.display.flush() - - def setMoveResize(self, gravity: int = 0, x: Optional[int] = None, y: Optional[int] = None, width: Optional[int] = None, height: Optional[int] = None, userAction: bool = True): - """ - Move and/or resize current window - - The low byte of data.l[0] contains the gravity to use; it may contain any value allowed for the - WM_SIZE_HINTS.win_gravity property: NorthWest (1), North (2), NorthEast (3), West (4), Center (5), - East (6), SouthWest (7), South (8), SouthEast (9), Static (10) - - A gravity of 0 indicates that the Window Manager should use the gravity specified in WM_SIZE_HINTS.win_gravity. - - The bits 8 to 11 indicate the presence of x, y, width and height. - - The bits 12 to 15 indicate the source (see the section called “Source indication - in requests”), so 0001 indicates the application and 0010 indicates a Pager or a Taskbar. - - The remaining bits should be set to zero. - - Pagers wanting to move or resize a window may send a _NET_MOVERESIZE_WINDOW client message request to the - root window instead of using a ConfigureRequest. - - Window Managers should treat a _NET_MOVERESIZE_WINDOW message exactly like a ConfigureRequest (in particular, - adhering to the ICCCM rules about synthetic ConfigureNotify events), except that they should use the gravity - specified in the message. - - Rationale: Using a _NET_MOVERESIZE_WINDOW message with StaticGravity allows Pagers to exactly position and - resize a window including its decorations without knowing the size of the decorations. - - :param gravity: gravity to apply to the window action. Defaults to 0 (using window defined gravity) - :param x: target x coordinate of window. Defaults to None (unchanged) - :param y: target y coordinate of window. Defaults to None (unchanged) - :param width: target width of window. Defaults to None (unchanged) - :param height: target height of window. Defaults to None (unchanged) - :param userAction: set to ''True'' to force action, as if it was requested by a user action. Defaults to True - """ - # gravity_flags calculations from 'old' ewmh seem to be wrong - # Thanks to elraymond (https://github.com/elraymond) for his help! - gravity_flags = gravity - if x is None: - x = 0 - else: - gravity_flags = gravity_flags | (1 << 8) - if y is None: - y = 0 - else: - gravity_flags = gravity_flags | (1 << 9) - if width is None: - width = 0 - else: - gravity_flags = gravity_flags | (1 << 10) - if height is None: - height = 0 - else: - gravity_flags = gravity_flags | (1 << 11) - if userAction: - gravity_flags = gravity_flags | (1 << 12) - else: - gravity_flags = gravity_flags | (1 << 13) - self.sendMessage(Window.MOVERESIZE, [gravity_flags, x, y, width, height]) - - def setWmMoveResize(self, x_root: int, y_root: int, orientation: Union[int, MoveResize], button: int, userAction: bool = True): - """ - This message allows Clients to initiate window movement or resizing. They can define their own move and size - "grips", whilst letting the Window Manager control the actual operation. This means that all moves/resizes - can happen in a consistent manner as defined by the Window Manager. See the section called “Source indication - in requests” for details on the source indication. - - When sending this message in response to a button press event, button SHOULD indicate the button which - was pressed, x_root and y_root MUST indicate the position of the button press with respect to the root window - and direction MUST indicate whether this is a move or resize event, and if it is a resize event, which edges - of the window the size grip applies to. When sending this message in response to a key event, the direction - MUST indicate whether this is a move or resize event and the other fields are unused. - - The Client MUST release all grabs prior to sending such message. - - The Window Manager can use the button field to determine the events on which it terminates the operation - initiated by the _NET_WM_MOVERESIZE message. Since there is a race condition between a client sending the - _NET_WM_MOVERESIZE message and the user releasing the button, Window Managers are advised to offer some - other means to terminate the operation, e.g. by pressing the ESC key. - - :param x_root: position of the button press with respect to the root window - :param y_root: position of the button press with respect to the root window - :param orientation: move or resize event - :param button: button pressed - :param userAction: set to ''True'' to force action, as if it was requested by a user action. Defaults to True - """ - # Need to understand this property - self.sendMessage(Window.WM_MOVERESIZE, [x_root, y_root, orientation, button, 2 if userAction else 1]) - - def setWmStacking(self, siblingId: int, detail: int, userAction: bool = True): - """ - This request is similar to ConfigureRequest with CWSibling and CWStackMode flags. It should be used only by - pagers, applications can use normal ConfigureRequests. The source indication field should be therefore - set to 2, see the section called “Source indication in requests” for details. - - Rationale: A Window Manager may put restrictions on configure requests from applications, for example it may - under some conditions refuse to raise a window. This request makes it clear it comes from a pager or similar - tool, and therefore the Window Manager should always obey it. - - :param siblingId: id of sibling window related to restacking action - :param detail: details of action as integer (does this include mode???) - :param userAction: should be set to 2 (typically used by pagers) - """ - # Need to understand this property - self.sendMessage(Window.RESTACK, [2 if userAction else 1, siblingId, detail]) - - def requestFrameExtents(self): - """ - Ask Window Manager to estimate frame extents before mapping current window. - - A Client whose window has not yet been mapped can request of the Window Manager an estimate of the - frame extents it will be given upon mapping. To retrieve such an estimate, the Client MUST send a - _NET_REQUEST_FRAME_EXTENTS message to the root window. The Window Manager MUST respond by estimating - the prospective frame extents and setting the window's _NET_FRAME_EXTENTS property accordingly. - The Client MUST handle the resulting _NET_FRAME_EXTENTS PropertyNotify event. So that the Window Manager - has a good basis for estimation, the Client MUST set any window properties it intends to set before - sending this message. The Client MUST be able to cope with imperfect estimates. - - Rationale: A client cannot calculate the dimensions of its window's frame before the window is mapped, - but some toolkits need this information. Asking the window manager for an estimate of the extents is a - workable solution. The estimate may depend on the current theme, font sizes or other window properties. - The client can track changes to the frame's dimensions by listening for _NET_FRAME_EXTENTS PropertyNotify event - """ - # Need to understand this property - self.sendMessage(Window.REQ_FRAME_EXTENTS, [self.id]) - - -class _Extensions: - """ - Additional, non-EWMH features, related to low-level window properties like hints, protocols and events - """ - - def __init__(self, winId: int, display: Xlib.display.Display, root: XWindow): - self.winId = winId - self.display = display - self.root = root - self.xWindow = self.display.create_resource_object('window', winId) - self.checkEvents = self._CheckEvents(winId, display, root) - - def getWmHints(self) -> Optional[WmHints]: - """ - Get window hints. - - {'flags': 103, 'input': 1, 'initial_state': 1, 'icon_pixmap': , 'icon_window': , 'icon_x': 0, 'icon_y': 0, 'icon_mask': , 'window_group': } - - Xlib provides functions that you can use to set and read the WM_HINTS property for a given window. - These functions use the flags and the XWMHints structure, as defined in Xlib.Xutil.* - - The XWMHints structure contains: - - flags: int - input: int - initial_state: int - icon_pixmap: Xlib.xobject.drawable.Pixmap - icon_window: Xlib.xobject.drawable.Window - icon_x: int - icon_y: int - icon_mask: Xlib.xobject.drawable.Pixmap - window_group: Xlib.xobject.drawable.Window - - To check if a hint is present or not, use bitwise operand OR ('|') between flags and following values in - Xlib.Xutil.* - - InputHint (1L << 0) - StateHint (1L << 1) - IconPixmapHint (1L << 2) - IconWindowHint (1L << 3) - IconPositionHint (1L << 4) - IconMaskHint (1L << 5) - WindowGroupHint (1L << 6) - UrgencyHint (1L << 8) - - The input member is used to communicate to the window manager the input focus model used by the application. - Applications that expect input but never explicitly set focus to any of their subwindows (that is, use the - push model of focus management), such as X Version 10 style applications that use real-estate driven focus, - should set this member to True. Similarly, applications that set input focus to their subwindows only when - it is given to their top-level window by a window manager should also set this member to True. Applications - that manage their own input focus by explicitly setting focus to one of their subwindows whenever they want - keyboard input (that is, use the pull model of focus management) should set this member to False. - Applications that never expect any keyboard input also should set this member to False. - - Pull model window managers should make it possible for push model applications to get input by setting - input focus to the top-level windows of applications whose input member is True. Push model window managers - should make sure that pull model applications do not break them by resetting input focus to PointerRoot - when it is appropriate (for example, whenever an application whose input member is False sets input focus - to one of its subwindows). - - The definitions for the initial_state flag are: - - Xlib.X.WithdrawnState 0 - Xlib.X.NormalState 1 # most applications start this way - Xlib.X.IconicState 3 # application wants to start as an icon - - The icon_mask specifies which pixels of the icon_pixmap should be used as the icon. This allows for - non-rectangular icons. Both icon_pixmap and icon_mask must be bitmaps. The icon_window lets an application - provide a window for use as an icon for window managers that support such use. The window_group lets you - specify that this window belongs to a group of other windows. For example, if a single application - manipulates multiple top-level windows, this allows you to provide enough information that a window - manager can iconify all the windows rather than just the one window. - - The UrgencyHint flag, if set in the flags field, indicates that the client deems the window contents - to be urgent, requiring the timely response of the user. The window manager will make some effort to - draw the user's attention to this window while this flag is set. The client must provide some means - by which the user can cause the urgency flag to be cleared (either mitigating the condition that made - the window urgent or merely shutting off the alarm) or the window to be withdrawn. - - :return: Hints struct - """ - hints: Optional[Xlib.protocol.rq.DictWrapper] = self.xWindow.get_wm_hints() - if hints is not None: - ret: WmHints = WmHints( - flags=hints.flags, - input_mode=hints.input, - initial_state=hints.initial_state, - icon_pixmap=hints.icon_pixmap, - icon_window=hints.icon_window, - icon_x=hints.icon_x, - icon_y=hints.icon_y, - icon_mask=hints.icon_mask, - window_group=hints.window_group - ) - return ret - return None - - def setWmHints(self, input_mode: int = HintAction.KEEP, initial_state: int = HintAction.KEEP, - icon_pixmap: Union[Xlib.xobject.drawable.Pixmap, int] = HintAction.KEEP, - icon_window: Union[XWindow, int] = HintAction.KEEP, - icon_x: int = HintAction.KEEP, icon_y: int = HintAction.KEEP, - icon_mask: Union[Xlib.xobject.drawable.Pixmap, int] = HintAction.KEEP, - window_group: Union[XWindow, int] = HintAction.KEEP, - urgency: Union[bool, int] = HintAction.KEEP): - """ - Set new hints for current window. - - Current window hints can be retrieved using getWmHints(), then apply desired changes. - To calculate new flags in case of adding or removing hints (not needed if just changing its value), - use bitwise operands between current flags and appropriate values in Xlib.Xutil to - add ('|') or remove ('| ~') hints (e.g. to remove icon_mask, do: flags = flags | ~Xlib.Xutil.IconMaskHint) - - Hints mask values: - - InputHint (1L << 0) - StateHint (1L << 1) - IconPixmapHint (1L << 2) - IconWindowHint (1L << 3) - IconPositionHint (1L << 4) - IconMaskHint (1L << 5) - WindowGroupHint (1L << 6) - UrgencyHint (1L << 8) - - See getWmHints() documentation for more info about hints. - - To modify existing window hints use: - - HintAction.KEEP Keeps current value so if it's present or not (Default behavior) - HintAction.REMOVE Removes hint from existing window hints - Target value (int/Pixmap/XWindow/bool) Adds new value or changes existing one - - :param input_mode: input focus model used by the application (0 / 1) - :param initial_state: WithdrawnState/NormalState/IconicState initial state preferred by window - :param icon_pixmap: bitmap to use as window icon - :param icon_window: window to use as icon - :param icon_x: x position of icon - :param icon_y: y position of icon - :param icon_mask: pixels of the icon_pixmap should be used as the icon. This allows for non-rectangular icons - :param window_group: group of windows the current window belongs to - :param urgency: True/False to activate/deactivate urgency falg - """ - hints: Optional[Xlib.protocol.rq.DictWrapper] = self.xWindow.get_wm_hints() - if hints is not None: - # If None: WM doesn't use them or should we initialize them (and how)? - if input_mode != HintAction.KEEP: - if input_mode != HintAction.REMOVE: - if input_mode in (0, 1): - hints.input_mode = input_mode - hints.flags = hints.flags | Xlib.Xutil.InputHint - else: - hints.flags = hints.flags & ~Xlib.Xutil.InputHint - if initial_state != HintAction.KEEP: - if initial_state != HintAction.REMOVE: - if initial_state in (Xlib.Xutil.NormalState, Xlib.Xutil.IconicState, Xlib.Xutil.WithdrawnState, Xlib.Xutil.ZoomState): - hints.initial_state = initial_state - hints.flags = hints.flags | Xlib.Xutil.StateHint - else: - hints.flags = hints.flags & ~Xlib.Xutil.StateHint - if icon_pixmap != HintAction.KEEP: - if icon_pixmap != HintAction.REMOVE: - if isinstance(icon_pixmap, Xlib.xobject.drawable.Pixmap): - hints.icon_pixmap = icon_pixmap - hints.flags = hints.flags | Xlib.Xutil.IconPixmapHint - else: - hints.flags = hints.flags & ~Xlib.Xutil.IconPixmapHint - if icon_window != HintAction.KEEP: - if icon_window != HintAction.REMOVE: - if isinstance(icon_window, XWindow): - hints.icon_window = icon_window - hints.flags = hints.flags | Xlib.Xutil.IconWindowHint - else: - hints.flags = hints.flags & ~Xlib.Xutil.IconWindowHint - if icon_x != HintAction.KEEP: - if icon_x != HintAction.REMOVE: - hints.icon_x = icon_x - hints.flags = hints.flags | Xlib.Xutil.IconPositionHint - else: - hints.flags = hints.flags & ~Xlib.Xutil.IconPositionHint - if icon_y != HintAction.KEEP: - if icon_y != HintAction.REMOVE: - hints.icon_y = icon_y - hints.flags = hints.flags | Xlib.Xutil.IconPositionHint - else: - hints.flags = hints.flags & ~Xlib.Xutil.IconPositionHint - if icon_mask != HintAction.KEEP: - if icon_mask != HintAction.REMOVE: - if isinstance(icon_mask, Xlib.xobject.drawable.Pixmap): - hints.icon_mask = icon_mask - hints.flags = hints.flags | Xlib.Xutil.IconMaskHint - else: - hints.flags = hints.flags & ~Xlib.Xutil.IconMaskHint - if window_group != HintAction.KEEP: - if window_group != HintAction.REMOVE: - if isinstance(window_group, XWindow): - hints.window_group = window_group - hints.flags = hints.flags | Xlib.Xutil.WindowGroupHint - else: - hints.flags = hints.flags & ~Xlib.Xutil.WindowGroupHint - if urgency != HintAction.KEEP: - if urgency != HintAction.REMOVE: - if isinstance(urgency, bool): - if urgency: - hints.flags = hints.flags | Xlib.Xutil.UrgencyHint - else: - hints.flags = hints.flags & ~Xlib.Xutil.UrgencyHint - self.xWindow.set_wm_hints(hints) - self.display.flush() - - def getWmNormalHints(self) -> Optional[WmNormalHints]: - """ - Xlib provides functions that you can use to set or read the WM_NORMAL_HINTS property for a given window. - The functions use the flags and the XSizeHints structure, as defined in the X11/Xutil.h header file. - The size of the XSizeHints structure may grow in future releases, as new components are added to support - new ICCCM features. Passing statically allocated instances of this structure into Xlib may result in memory - corruption when running against a future release of the library. As such, it is recommended that only - dynamically allocated instances of the structure be used. - - To allocate an XSizeHints structure, use XAllocSizeHints(). - - The XSizeHints structure contains: - - # Size hints mask bits - - USPosition (1L << 0) # user specified x, y - USSize (1L << 1) # user specified width, height - PPosition (1L << 2) # program specified position - PSize (1L << 3) # program specified size - PMinSize (1L << 4) # program specified minimum size - PMaxSize (1L << 5) # program specified maximum size - PResizeInc (1L << 6) # program specified resize increments - PAspect (1L << 7) # program specified min and max aspect ratios - PBaseSize (1L << 8) - PWinGravity (1L << 9) - PAllHints (PPosition|PSize|PMinSize|PMaxSize|PResizeInc|PAspect) - - # Values - - typedef struct { - long flags; # marks which fields in this structure are defined - int x, y; # Obsolete - int width, height; # Obsolete - int min_width, min_height; - int max_width, max_height; - int width_inc, height_inc; - struct { - int x; # numerator - int y; # denominator - } min_aspect, max_aspect; - int base_width, base_height; - int win_gravity; - # this structure may be extended in the future - } XSizeHints; - - The x, y, width, and height members are now obsolete and are left solely for compatibility reasons. - - The min_width and min_height members specify the minimum window size that still allows the application to - be useful. - - The max_width and max_height members specify the maximum window size. The width_inc and height_inc - members define an arithmetic progression of sizes (minimum to maximum) into which the window prefers - to be resized. The min_aspect and max_aspect members are expressed as ratios of x and y, and they - allow an application to specify the range of aspect ratios it prefers. The base_width and base_height - members define the desired size of the window. The window manager will interpret the position of the - window and its border width to position the point of the outer rectangle of the overall window specified - by the win_gravity member. The outer rectangle of the window includes any borders or decorations supplied - by the window manager. In other words, if the window manager decides to place the window where the - client asked, the position on the parent window's border named by the win_gravity will be placed where - the client window would have been placed in the absence of a window manager. - """ - normal_hints: Optional[Xlib.protocol.rq.DictWrapper] = self.xWindow.get_wm_normal_hints() - if normal_hints is not None: - min_aspect: Aspect = Aspect( - num=normal_hints.min_aspect.num, - denum=normal_hints.min_aspect.denum - ) - max_aspect: Aspect = Aspect( - num=normal_hints.max_aspect.num, - denum=normal_hints.max_aspect.denum - ) - ret: WmNormalHints = WmNormalHints( - flags=normal_hints.flags, - min_width=normal_hints.min_width, - min_height=normal_hints.min_height, - max_width=normal_hints.max_width, - max_height=normal_hints.max_height, - width_inc=normal_hints.width_inc, - height_inc=normal_hints.height_inc, - min_aspect=min_aspect, - max_aspect=max_aspect, - base_width=normal_hints.base_width, - base_height=normal_hints.base_height, - win_gravity=normal_hints.win_gravity - ) - return ret - return None - - def setWmNormalHints(self, min_width: int = HintAction.KEEP, min_height: int = HintAction.KEEP, - max_width: int = HintAction.KEEP, max_height: int = HintAction.KEEP, - width_inc: int = HintAction.KEEP, height_inc: int = HintAction.KEEP, - min_aspect: Union[Aspect, int] = HintAction.KEEP, - max_aspect: Union[Aspect, int] = HintAction.KEEP, - base_width: int = HintAction.KEEP, base_height: int = HintAction.KEEP, - win_gravity: int = HintAction.KEEP): - """ - Set new normal hints for current window. - - Current window normal hints can be retrieved using getWmNormalHints(), then apply desired changes. - - To calculate new flags in case of adding or removing hints (not needed if just changing its value), - use bitwise operands between current flags and appropriate values in Xlib.Xutil to - add ('|') or remove ('& ~') hints (e.g. to remove PSize hint, do: flags = flags & ~Xlib.Xutil.PSize) - - USPosition (1L << 0) # user specified x, y - USSize (1L << 1) # user specified width, height - PPosition (1L << 2) # program specified position - PSize (1L << 3) # program specified size - PMinSize (1L << 4) # program specified minimum size - PMaxSize (1L << 5) # program specified maximum size - PResizeInc (1L << 6) # program specified resize increments - PAspect (1L << 7) # program specified min and max aspect ratios - PBaseSize (1L << 8) - PWinGravity (1L << 9) - - See getWmNormalHints() documentation for more info about normal hints. - - To modify existing window normal hints use: - - HintAction.KEEP Keeps current value so if it's present or not (Default behavior) - HintAction.REMOVE Removes hint from existing window normal hints - Target value (int/Pixmap/XWindow/bool) Adds new value or changes existing one - - :param min_width: minimum width of window - :param min_height: minimum height of window - :param max_width: max width of window - :param max_height: max height of window - :param width_inc: width changes increments (in pixels) - :param height_inc: height changes increments (in pixels) - :param min_aspect: X (numerator), Y (denumerator) ratio for min_aspect - :param max_aspect: X (numerator), Y (denumerator) ratio for max_aspect - :param base_width: Preferred width of window - :param base_height: Preferred height of window - :param int win_gravity: window gravity for placing an re-stacking - """ - normal_hints: Optional[Xlib.protocol.rq.DictWrapper] = self.xWindow.get_wm_normal_hints() - if normal_hints is not None: - # If None: WM doesn't use them or should we initialize them (and how)? - if min_width != HintAction.KEEP: - if min_width != HintAction.REMOVE or min_width == 0: - normal_hints.min_width = min_width - normal_hints.flags = normal_hints.flags | Xlib.Xutil.PMinSize - else: - normal_hints.flags = normal_hints.flags & ~Xlib.Xutil.PMinSize - if min_height != HintAction.KEEP: - if min_height != HintAction.REMOVE or min_height == 0: - normal_hints.min_height = min_height - normal_hints.flags = normal_hints.flags | Xlib.Xutil.PMinSize - else: - normal_hints.flags = normal_hints.flags & ~Xlib.Xutil.PMinSize - if max_width != HintAction.KEEP: - if max_width != HintAction.REMOVE and max_width != 0: - normal_hints.max_width = max_width - normal_hints.flags = normal_hints.flags | Xlib.Xutil.PMaxSize - else: - normal_hints.flags = normal_hints.flags & ~Xlib.Xutil.PMaxSize - if max_height != HintAction.KEEP: - if max_height != HintAction.REMOVE and max_height != 0: - normal_hints.max_height = max_height - normal_hints.flags = normal_hints.flags | Xlib.Xutil.PMaxSize - else: - normal_hints.flags = normal_hints.flags & ~Xlib.Xutil.PMaxSize - if width_inc != HintAction.KEEP: - if width_inc != HintAction.REMOVE or width_inc == 0: - normal_hints.width_inc = width_inc - normal_hints.flags = normal_hints.flags | Xlib.Xutil.PResizeInc - else: - normal_hints.flags = normal_hints.flags & ~Xlib.Xutil.PResizeInc - if height_inc != HintAction.KEEP or height_inc == 0: - if height_inc != HintAction.REMOVE: - normal_hints.height_inc = height_inc - normal_hints.flags = normal_hints.flags | Xlib.Xutil.PResizeInc - else: - normal_hints.flags = normal_hints.flags & ~Xlib.Xutil.PResizeInc - if min_aspect != HintAction.KEEP: - if isinstance(min_aspect, dict) and min_aspect["num"] != 0 and min_aspect["denum"] != 0: - normal_hints.min_aspect.x = min_aspect["num"] - normal_hints.min_aspect.y = min_aspect["denum"] - normal_hints.flags = normal_hints.flags | Xlib.Xutil.PAspect - elif min_aspect == HintAction.REMOVE or \ - (isinstance(min_aspect, dict) and min_aspect["num"] == 0 and min_aspect["denum"] == 0): - normal_hints.flags = normal_hints.flags & ~Xlib.Xutil.PAspect - if max_aspect != HintAction.KEEP: - if isinstance(max_aspect, dict) and max_aspect["num"] != 0 and max_aspect["denum"] != 0: - normal_hints.max_aspect.x = max_aspect["num"] - normal_hints.max_aspect.y = max_aspect["denum"] - normal_hints.flags = normal_hints.flags | Xlib.Xutil.PAspect - elif max_aspect == HintAction.REMOVE or \ - (isinstance(max_aspect, dict) and max_aspect["num"] == 0 and max_aspect["denum"] == 0): - normal_hints.flags = normal_hints.flags & ~Xlib.Xutil.PAspect - if base_width != HintAction.KEEP: - if base_width != HintAction.REMOVE or base_width == 0: - normal_hints.base_width = base_width - normal_hints.flags = normal_hints.flags | Xlib.Xutil.PBaseSize - else: - normal_hints.flags = normal_hints.flags & ~Xlib.Xutil.PBaseSize - if base_height != HintAction.KEEP: - if base_height != HintAction.REMOVE or base_height == 0: - normal_hints.base_height = base_height - normal_hints.flags = normal_hints.flags | Xlib.Xutil.PBaseSize - else: - normal_hints.flags = normal_hints.flags & ~Xlib.Xutil.PBaseSize - if win_gravity != HintAction.KEEP: - if win_gravity != HintAction.REMOVE: - normal_hints.win_gravity = win_gravity - normal_hints.flags = normal_hints.flags | Xlib.Xutil.PWinGravity - else: - normal_hints.flags = normal_hints.flags & ~Xlib.Xutil.PWinGravity - self.xWindow.set_wm_normal_hints(normal_hints) - self.display.flush() - - def getWmProtocols(self, text: bool = False) -> Union[List[int], List[str]]: - """ - Get the protocols supported by current window. - - The WM_PROTOCOLS property (of type ATOM) is a list of atoms. Each atom identifies a communication protocol - between the client and the window manager in which the client is willing to participate. Atoms can identify - both standard protocols and private protocols specific to individual window managers. - - All the protocols in which a client can volunteer to take part involve the window manager sending the - client a ClientMessage event and the client taking appropriate action. For details of the contents of - the event, see section 4.2.8. In each case, the protocol transactions are initiated by the window manager. - - The WM_PROTOCOLS property is not required. If it is not present, the client does not want to participate - in any window manager protocols. - - The X Consortium will maintain a registry of protocols to avoid collisions in the name space. The following - table lists the protocols that have been defined to date. - - Protocol Section Purpose - WM_TAKE_FOCUS 4.1.7 Assignment of input focus - WM_SAVE_YOURSELF Appendix C Save client state request (deprecated) - WM_DELETE_WINDOW 4.2.8.1 Request to delete top-level window - - :param text: select whether the protocols will be returned as integers or strings - :return: List of protocols in integer or string format - """ - prots: List[int] = self.xWindow.get_wm_protocols() - if text: - return [self.display.get_atom_name(p) for p in prots if isinstance(p, int) and p != 0] - else: - return [p for p in prots] - - def addWmProtocols(self, atoms: Union[List[str], List[int]]): - """ - Adds new protocols atoms for current window. - - See getWmProtocols() documentation for more info about protocols. - - The X Consortium will maintain a registry of protocols to avoid collisions in the name space. The following - table lists the protocols that have been defined to date. - - Protocol Section Purpose - WM_TAKE_FOCUS 4.1.7 Assignment of input focus - WM_SAVE_YOURSELF Appendix C Save client state request (deprecated) - WM_DELETE_WINDOW 4.2.8.1 Request to delete top-level window - - :param atoms: List of protocols to be added - """ - prots: List[int] = self.xWindow.get_wm_protocols() - newAtoms: List[int] = [] - for atom in atoms: - if isinstance(atom, str): - a: int = self.display.get_atom(atom) - else: - a = int(atom) - if a not in prots: - newAtoms.append(a) - self.xWindow.set_wm_protocols(newAtoms) - self.display.flush() - - def delWmProtocols(self, atoms: Union[List[str], List[int]]): - """ - Deletes existing protocols atoms for current window. - - See getWmProtocols() documentation for more info about protocols. - - The X Consortium will maintain a registry of protocols to avoid collisions in the name space. The following - table lists the protocols that have been defined to date. - - Protocol Section Purpose - WM_TAKE_FOCUS 4.1.7 Assignment of input focus - WM_SAVE_YOURSELF Appendix C Save client state request (deprecated) - WM_DELETE_WINDOW 4.2.8.1 Request to delete top-level window - - :param atoms: List of protocols to be deleted - """ - atomsList: List[int] = [] - for atom in atoms: - if isinstance(atom, str): - atomsList.append(self.display.get_atom(atom)) - else: - atomsList.append(int(atom)) - prots: List[int] = self.xWindow.get_wm_protocols() - newAtoms: List[int] = [a for a in prots if a not in atomsList] - self.xWindow.set_wm_protocols(newAtoms) - self.display.flush() - - class _CheckEvents: - """ - Activate a watchdog to be notified on given events (to provided callback function). - - It's important to define proper mask and event list accordingly. See checkEvents() documentation. - """ - - def __init__(self, winId: int, display: Xlib.display.Display, root: XWindow): - - self._winId: int = winId - self._display: Xlib.display.Display = display - self._root: XWindow = root - - self._keep: threading.Event = threading.Event() - self._stopRequested: bool = False - self._checkThread: Optional[threading.Thread] = None - self._threadStarted: bool = False - self._interval = 0.1 - - # self._isCinnamon = "cinnamon" in os.environ['XDG_CURRENT_DESKTOP'].lower() - - def _checkDisplayEvents(self): - - while self._keep.wait() and not self._stopRequested: - i = self._root.display.pending_events() - while i > 0 and not self._stopRequested: - event = self._root.display.next_event() - if event.type in self._events: - if event.window.id == self._winId: - self._callback(event) - else: - tree = event.window.query_tree() - if tree and hasattr(tree, "children"): - for child in tree.children: - if isinstance(child, XWindow) and child.id == self._winId: - self._callback(event) - break - i -= 1 - time.sleep(self._interval) - - # Is this necessary to somehow "free" the events catching??? - self._root.change_attributes(event_mask=Xlib.X.NoEventMask) - self._display.flush() - - def start(self, events: List[int], mask: int, callback: Callable[[Xlib.protocol.rq.Event], None]): - """ - Activate a watchdog to be notified on given events (to provided callback function). - - It is possible to update target events, mask or callback function just invoking start() again and - passing new arguments. - - Be aware it is important to define proper mask and event list accordingly. - - Clients select event reporting of most events relative to a window. To do this, pass an event mask to an - Xlib event-handling function that takes an event_mask argument. The bits of the event mask are defined in - X11/X.h. Each bit in the event mask maps to an event mask name, which describes the event or events you - want the X server to return to a client application. - - Unless the client has specifically asked for them, most events are not reported to clients when they are - generated. Unless the client suppresses them by setting graphics-exposures in the GC to False , - GraphicsExpose and NoExpose are reported by default as a result of XCopyPlane() and XCopyArea(). - SelectionClear, SelectionRequest, SelectionNotify, or ClientMessage cannot be masked. Selection related - events are only sent to clients cooperating with selections (see section "Selections"). When the keyboard - or pointer mapping is changed, MappingNotify is always sent to clients. - - The following table lists the events constants you can pass to the events argument (defined in Xlib.X.*): - - Keyboard events KeyPress, KeyRelease - Pointer events ButtonPress, ButtonRelease, MotionNotify - Window crossing events EnterNotify, LeaveNotify - Input focus events FocusIn, FocusOut - Keymap state notification event KeymapNotify - Exposure events Expose, GraphicsExpose, NoExpose - Structure control events CirculateRequest, ConfigureRequest, MapRequest, ResizeRequest - Window state notification events CirculateNotify, ConfigureNotify, CreateNotify, DestroyNotify, GravityNotify, MapNotify, MappingNotify, ReparentNotify, UnmapNotify, VisibilityNotify - Colormap state notification event ColormapNotify - Client communication events ClientMessage, PropertyNotify, SelectionClear, SelectionNotify, SelectionRequest - - The following table lists the event mask constants you can pass to the event_mask argument and the - circumstances in which you would want to specify the event mask (defined in Xlib.X.*): - - NoEventMask No events wanted - KeyPressMask Keyboard down events wanted - KeyReleaseMask Keyboard up events wanted - ButtonPressMask Pointer button down events wanted - ButtonReleaseMask Pointer button up events wanted - EnterWindowMask Pointer window entry events wanted - LeaveWindowMask Pointer window leave events wanted - PointerMotionMask Pointer motion events wanted - PointerMotionHintMask Pointer motion hints wanted - Button1MotionMask Pointer motion while button 1 down - Button2MotionMask Pointer motion while button 2 down - Button3MotionMask Pointer motion while button 3 down - Button4MotionMask Pointer motion while button 4 down - Button5MotionMask Pointer motion while button 5 down - ButtonMotionMask Pointer motion while any button down - KeymapStateMask Keyboard state wanted at window entry and focus in - ExposureMask Any exposure wanted - VisibilityChangeMask Any change in visibility wanted - StructureNotifyMask Any change in window structure wanted - ResizeRedirectMask Redirect resize of this window - SubstructureNotifyMask Substructure notification wanted - SubstructureRedirectMask Redirect structure requests on children - FocusChangeMask Any change in input focus wanted - PropertyChangeMask Any change in property wanted - ColormapChangeMask Any change in colormap wanted - OwnerGrabButtonMask Automatic grabs should activate with owner_events set to True - - :param events: List of events to be notified on as a list of integers: [Xlib.X.event1, Xlib.X.event2, ...] - :param mask: Events mask according to selected events as integer: Xlib.X.mask1 | Xlib.mask2 | ... - :param callback: Function to be invoked when a selected event is received, passing received event to it - """ - self._events: List[int] = events - self._mask: int = mask - self._callback: Callable[[Xlib.protocol.rq.Event], None] = callback - - self._root.change_attributes(event_mask=self._mask) - self._display.flush() - - if not self._threadStarted and self._checkThread is None: - self._checkThread = threading.Thread(target=self._checkDisplayEvents) - self._checkThread.daemon = True - self._threadStarted = True - self._checkThread.start() - self._stopRequested = False - self._keep.set() - - def pause(self): - """ - Pause the watchdog so the callback is not invoked. - - Restart the watchdog just invoking start() again using same arguments or new ones. - """ - self._keep.clear() - - def stop(self): - """ - Stop the watchdog so the thread is ended. - - Start a new watchdog using start() again. - """ - if self._threadStarted and self._checkThread is not None: - self._threadStarted = False - self._stopRequested = True - self._keep.set() - self._checkThread.join(1) - self._checkThread = None - - -def _getWindowParent(win: XWindow, rootId: int) -> int: - while True: - parent = win.query_tree().parent - if parent.id == rootId or not isinstance(parent, XWindow): - break - win = parent - return win.id - - -def _getWindowGeom(win: XWindow, rootId: int = defaultRoot.id) -> Tuple[int, int, int, int]: - # https://stackoverflow.com/questions/12775136/get-window-position-and-size-in-python-with-xlib - mgalgs - geom = win.get_geometry() - x = geom.x - y = geom.y - w = geom.width - h = geom.height - while True: - parent = win.query_tree().parent - if not isinstance(parent, XWindow): - break - pgeom = parent.get_geometry() - x += pgeom.x - y += pgeom.y - if parent.id == rootId: - break - win = parent - return x, y, w, h - - -def _xlibGetAllWindows(parent: Optional[XWindow] = None, title: str = "", klass: Optional[Tuple[str, str]] = None) -> List[XWindow]: - """ - Retrieves all open windows, including "system", non-user windows (unlike getClientList() or getClientListStacking()). - - :param parent: parent window to limit the search to its children. Defaults to root window - :param title: include only windows that match given title - :param klass: include only windows that match given class - :return: List of windows objects (X-Window) - """ - - parentWin: XWindow = parent or defaultRoot - allWindows: List[XWindow] = [] - - def findit(hwnd: XWindow) -> None: - try: - query = hwnd.query_tree() - children: List[XWindow] = query.children - except: - children = [] - for child in children: - try: - # This returns empty name in some cases... must use getProperty!!!! - ret: Optional[str] = child.get_wm_name() - except: - ret = None - if ret is not None: - winTitle: str = ret - else: - winTitle = "" - winClass: Optional[Tuple[str, str]] = None - try: - winClass = child.get_wm_class() - except: - pass - if winClass is None: - winClass = ("", "") - if (not title and not klass) or (title and winTitle == title) or (klass and winClass == klass): - allWindows.append(child) - findit(child) - - findit(parentWin) - return allWindows - - -# def _createSimpleWindow(parent: XWindow, x: int, y: int, width: int, height: int, override: bool = False, -# inputOnly: bool = False, display: Xlib.display.Display = defaultDisplay) -> EwmhWindow: -# if inputOnly: -# mask = Xlib.X.ButtonPressMask | Xlib.X.ButtonReleaseMask | Xlib.X.KeyPressMask | Xlib.X.KeyReleaseMask -# else: -# mask = Xlib.X.NoEventMask -# win: XWindow = parent.create_window(x=x, y=y, width=width, height=height, -# border_width=0, depth=Xlib.X.CopyFromParent, -# window_class=Xlib.X.InputOutput, -# # window_class=Xlib.X.InputOnly, # -> This fails! -# visual=Xlib.X.CopyFromParent, -# background_pixel=Xlib.X.CopyFromParent, -# event_mask=mask, -# colormap=Xlib.X.CopyFromParent, -# override_redirect=override, -# ) -# win.map() -# display.flush() -# window: EwmhWindow = EwmhWindow(win.id) -# return window -# -# -# def _createTransient(display: Xlib.display.Display, parent: XWindow, transient_for: XWindow, -# callback: Callable[[Xlib.protocol.rq.Event], None], x: int, y: int, width: int, height: int, -# override: bool = False, inputOnly: bool = False) -> Tuple[EwmhWindow, List[int]]: -# # https://shallowsky.com/blog/programming/click-thru-translucent-update.html -# # https://github.com/python-xlib/python-xlib/issues/200 -# -# transientWindow: EwmhWindow = _createSimpleWindow(parent, x, y, width, height, override, inputOnly, display) -# xWin: XWindow = transientWindow.xWindow -# -# onebyte: int = int(0xAA) # Calculate as 0xff * target_opacity -# fourbytes: int = onebyte | (onebyte << 8) | (onebyte << 16) | (onebyte << 24) -# xWin.change_property(display.get_atom('_NET_WM_WINDOW_OPACITY'), Xlib.Xatom.CARDINAL, 32, [fourbytes]) -# -# input_pm: Xlib.xobject.drawable.Pixmap = xWin.create_pixmap(width, height, 1) -# gc: Xlib.xobject.fontable.GC = input_pm.create_gc(foreground=0, background=0) -# input_pm.fill_rectangle(gc.id, 0, 0, width, height) -# xWin.shape_mask(Xlib.ext.shape.SO.Set, Xlib.ext.shape.SK.Input, 0, 0, input_pm) # type: ignore[attr-defined] # pyright: ignore[reportGeneralTypeIssues, reportUnknownMemberType] -# # xWin.shape_select_input(0) -# -# xWin.map() -# display.flush() -# -# xWin.set_wm_transient_for(transient_for) -# display.flush() -# -# currDesktop = os.environ['XDG_CURRENT_DESKTOP'].lower() -# # otherDesktop = os.environ.get("DESKTOP_SESSION").lower() # -> Returns None -# if "gnome" in currDesktop: -# gaps = [24, 24, -40, -80] -# elif "cinnamon" in currDesktop: -# gaps = [-2, -32, +46, +112] -# elif "kde" in currDesktop: -# # KDE has a totally different behavior. Must investigate/test -# gaps = [0, 0, 0, 0] -# else: -# gaps = [0, 0, 0, 0] -# -# pgeom: Xlib.protocol.request.GetGeometry = transient_for.get_geometry() -# xWin.configure(x=max(0, x + gaps[0]), y=max(0, y + gaps[1]), width=pgeom.width + gaps[2], height=pgeom.height + gaps[3]) -# display.flush() -# -# transientWindow.extensions.checkEvents.start( -# [Xlib.X.ConfigureNotify], -# Xlib.X.StructureNotifyMask | Xlib.X.SubstructureNotifyMask, -# callback -# ) -# -# # Removing actions but not decoration, since it causes not to capture Keyboard and mouse, -# transientWindow.changeProperty(display.get_atom("_MOTIF_WM_HINTS"), [1, 0, 1, 0, 0]) -# # Same happens with DESKTOP (???), SPLASH, DOCK or override_redirect -# # transientWindow.setWmWindowType(WindowType.DESKTOP) -# # MODAL doesn't behave as expected (it doesn't block main window) -# transientWindow.changeWmState(StateAction.ADD, State.MODAL, State.BELOW) -# # x, y, w, h = _getWindowGeom(transientWindow.xWindow, defaultRoot) -# # normal_hints = transient_for.get_wm_normal_hints() -# # normal_hints.flags = 808 -# # normal_hints.min_width = normal_hints.max_width = w + gaps[2] -# # normal_hints.min_height = normal_hints.max_height = h + gaps[3] -# # transientWindow.xWindow.set_wm_normal_hints(normal_hints) -# # hints = transient_for.get_wm_hints() -# # transientWindow.xWindow.set_wm_hints(hints) -# -# return transientWindow, gaps -# -# -# def _closeTransient(transientWindow: EwmhWindow): -# transientWindow.extensions.checkEvents.stop() -# transientWindow.xWindow.set_wm_transient_for(transientWindow.root) -# transientWindow.display.flush() -# transientWindow.xWindow.unmap() # It seems not to properly close if not unmapped first -# transientWindow.display.flush() -# transientWindow.setClosed() -# -# from ctypes import cdll, CDLL -# from ctypes.util import find_library -# -# _xlib: Optional[Union[CDLL, int]] = -1 -# _xcomp: Optional[Union[CDLL, int]] = -1 -# -# -# def _loadX11Library() -> Optional[CDLL]: -# global _xlib -# if isinstance(_xlib, int): -# lib: Optional[CDLL] = None -# try: -# libPath: Optional[str] = find_library('X11') -# if libPath: -# lib = cdll.LoadLibrary(libPath) -# except: -# pass -# _xlib = lib -# return _xlib -# -# -# def _loadXcompLibrary() -> Optional[CDLL]: -# global _xcomp -# if isinstance(_xcomp, int): -# lib: Optional[CDLL] = None -# try: -# libPath: Optional[str] = find_library('Xcomposite') -# if libPath: -# lib = cdll.LoadLibrary(libPath) -# except: -# pass -# _xcomp = lib -# return _xcomp - -# from ewmhlib.Structs._structs import _XWindowAttributes -# def _XGetAttributes(winId: int, dpyName: str = "") -> Tuple[bool, _XWindowAttributes]: -# """ -# int x, y; /* location of window */ -# int width, height; /* width and height of window */ -# int border_width; /* border width of window */ -# int depth; /* depth of window */ -# Visual *visual; /* the associated visual structure */ -# Window root; /* root of screen containing window */ -# int class; /* InputOutput, InputOnly*/ -# int bit_gravity; /* one of the bit gravity values */ -# int win_gravity; /* one of the window gravity values */ -# int backing_store; /* NotUseful, WhenMapped, Always */ -# unsigned long backing_planes; /* planes to be preserved if possible */ -# unsigned long backing_pixel; /* value to be used when restoring planes */ -# Bool save_under; /* boolean, should bits under be saved? */ -# Colormap colormap; /* color map to be associated with window */ -# Bool map_installed; /* boolean, is color map currently installed*/ -# int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ -# long all_event_masks; /* set of events all people have interest in*/ -# long your_event_mask; /* my event mask */ -# long do_not_propagate_mask; /* set of events that should not propagate */ -# Bool override_redirect; /* boolean value for override-redirect */ -# Screen *screen; /* back pointer to correct screen */ -# """ -# res: bool = False -# attr: _XWindowAttributes = _XWindowAttributes() -# -# xlib: Optional[CDLL] = _loadX11Library() -# -# if xlib: -# try: -# if not dpyName: -# dpyName = defaultDisplay.get_display_name() -# dpy: int = xlib.XOpenDisplay(dpyName.encode()) -# xlib.XGetWindowAttributes(dpy, winId, byref(attr)) -# xlib.XCloseDisplay(dpy) -# res = True -# except: -# pass -# return res, attr - - # Leaving this as reference of using X11 library - # https://github.com/evocount/display-management/blob/c4f58f6653f3457396e44b8c6dc97636b18e8d8a/displaymanagement/rotation.py - # https://github.com/nathanlopez/Stitch/blob/master/Configuration/mss/linux.py - # https://gist.github.com/ssokolow/e7c9aae63fb7973e4d64cff969a78ae8 - # https://stackoverflow.com/questions/36188154/get-x11-window-caption-height - # https://refspecs.linuxfoundation.org/LSB_1.3.0/gLSB/gLSB/libx11-ddefs.html - # s = xlib.XDefaultScreen(d) - # root = xlib.XDefaultRootWindow(d) - # fg = xlib.XBlackPixel(d, s) - # bg = xlib.XWhitePixel(d, s) - # w = xlib.XCreateSimpleWindow(d, root, 600, 300, 400, 200, 0, fg, bg) - # xlib.XMapWindow(d, w) - # time.sleep(4) - # a = xlib.XInternAtom(d, "_GTK_FRAME_EXTENTS", True) - # if not a: - # a = xlib.XInternAtom(d, "_NET_FRAME_EXTENTS", True) - # t = c_int() - # f = c_int() - # n = c_ulong() - # b = c_ulong() - # xlib.XGetWindowProperty(d, w, a, 0, 4, False, Xlib.X.AnyPropertyType, byref(t), byref(f), byref(n), byref(b), byref(attr)) - # r = c_ulong() - # x = c_int() - # y = c_int() - # w = c_uint() - # h = c_uint() - # b = c_uint() - # d = c_uint() - # xlib.XGetGeometry(d, hWnd.id, byref(r), byref(x), byref(y), byref(w), byref(h), byref(b), byref(d)) - # print(x, y, w, h) - # Other references (send_event and setProperty): - # prop = DISP.intern_atom(WM_CHANGE_STATE, False) - # data = (32, [Xlib.Xutil.IconicState, 0, 0, 0, 0]) - # ev = Xlib.protocol.event.ClientMessage(window=self._hWnd.id, client_type=prop, data=data) - # mask = Xlib.X.SubstructureRedirectMask | Xlib.X.SubstructureNotifyMask - # DISP.send_event(destination=ROOT, event=ev, event_mask=mask) - # data = [Xlib.Xutil.IconicState, 0, 0, 0, 0] - # _setProperty(_type="WM_CHANGE_STATE", data=data, mask=mask) - # for atom in w.list_properties(): - # print(DISP.atom_name(atom)) - # props = DISP.xrandr_list_output_properties(output) - # for atom in atoms: - # print(atom, DISP.get_atom_name(atom)) - # print(DISP.xrandr_get_output_property(output, atom, 0, 0, 1000)._data['value']) diff --git a/src/pywinctl/ewmhlib/_main.py b/src/pywinctl/ewmhlib/_main.py deleted file mode 100644 index a529339..0000000 --- a/src/pywinctl/ewmhlib/_main.py +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -from ._ewmhlib import (displaysCount, getDisplays, getDisplaysInfo, getRoots, - defaultDisplay, defaultScreen, defaultRoot, defaultEwmhRoot, - getDisplayFromRoot, getScreenFromRoot, - getDisplayFromWindow, getScreenFromWindow, getRootFromWindow, - getProperty, getPropertyValue, changeProperty, sendMessage, - EwmhRoot, EwmhWindow - ) -from . import Props -from . import Structs diff --git a/src/pywinctl/ewmhlib/py.typed b/src/pywinctl/ewmhlib/py.typed deleted file mode 100644 index 8adf904..0000000 --- a/src/pywinctl/ewmhlib/py.typed +++ /dev/null @@ -1 +0,0 @@ -# Marker file for PEP 561. The ewmhlib package uses inline types. \ No newline at end of file diff --git a/tests/test_MacNSWindow.py b/tests/test_MacNSWindow.py deleted file mode 100644 index e78119b..0000000 --- a/tests/test_MacNSWindow.py +++ /dev/null @@ -1,311 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 - -# Lawrence Akka - https://sourceforge.net/p/pyobjc/mailman/pyobjc-dev/thread/0B4BC391-6491-445D-92D0-7B1CEF6F51BE%40me.com/#msg27726282 - -# We need to import the relevant object definitions from PyObjC - -import sys -assert sys.platform == "darwin" - -import time - -from AppKit import ( - NSApp, NSObject, NSApplication, NSMakeRect, NSWindow, NSWindowStyleMaskTitled, NSWindowStyleMaskClosable, - NSWindowStyleMaskMiniaturizable, NSWindowStyleMaskResizable, NSBackingStoreBuffered) - -import pywinctl - - -# Cocoa prefers composition to inheritance. The members of an object's -# delegate will be called upon the happening of certain events. Once we define -# methods with particular names, they will be called automatically -class Delegate(NSObject): - - npw = None - demoMode = False - - def getDemoMode(self): - return self.demoMode - - def setDemoMode(self): - self.demoMode = True - - def unsetDemoMode(self): - self.demoMode = False - - def applicationDidFinishLaunching_(self, aNotification: None): - '''Called automatically when the application has launched''' - # Set it as the frontmost application - NSApp().activateIgnoringOtherApps_(True) - for win in NSApp().orderedWindows(): - print(win.title(), win.frame(), type(win.frame().origin)) - - if self.demoMode: - - if not self.npw: - self.npw = pywinctl.getActiveWindow(NSApp()) - - if self.npw: - print("ACTIVE WINDOW:", self.npw.title) - else: - print("NO ACTIVE WINDOW FOUND") - return - - wait = True - timelap = 0.3 - - self.npw.maximize(wait=wait) - time.sleep(timelap) - assert self.npw.isMaximized - self.npw.restore(wait=wait) - time.sleep(timelap) - assert not self.npw.isMaximized - - self.npw.minimize(wait=wait) - time.sleep(timelap) - assert self.npw.isMinimized - self.npw.restore(wait=wait) - time.sleep(timelap) - assert not self.npw.isMinimized - - self.npw.hide(wait=wait) - time.sleep(timelap) - assert not self.npw.visible - self.npw.show(wait=wait) - time.sleep(timelap) - assert self.npw.visible - - # Test resizing - self.npw.resizeTo(600, 400, wait=wait) - print("RESIZE", self.npw.size) - time.sleep(timelap) - assert self.npw.size == (600, 400) - assert self.npw.width == 600 - assert self.npw.height == 400 - - self.npw.resizeRel(10, 20, wait=wait) - print("RESIZEREL", self.npw.size) - time.sleep(timelap) - assert self.npw.size == (610, 420) - assert self.npw.width == 610 - assert self.npw.height == 420 - - # Test moving - self.npw.moveTo(600, 300, wait=wait) - print("MOVE", self.npw.topleft) - time.sleep(timelap) - assert self.npw.topleft == (600, 300) - assert self.npw.left == 600 - assert self.npw.top == 300 - assert self.npw.right == 1210 - assert self.npw.bottom == 720 - assert self.npw.bottomright == (1210, 720) - assert self.npw.bottomleft == (600, 720) - assert self.npw.topright == (1210, 300) - - self.npw.moveRel(1, 2, wait=wait) - print("MOVEREL", self.npw.topleft) - time.sleep(timelap) - assert self.npw.topleft == (601, 302) - assert self.npw.left == 601 - assert self.npw.top == 302 - assert self.npw.right == 1211 - assert self.npw.bottom == 722 - assert self.npw.bottomright == (1211, 722) - assert self.npw.bottomleft == (601, 722) - assert self.npw.topright == (1211, 302) - - # Move via the properties - self.npw.resizeTo(601, 401, wait=wait) - print("RESIZE", self.npw.size) - time.sleep(timelap) - self.npw.moveTo(100, 600, wait=wait) - print("MOVE", self.npw.topleft) - time.sleep(timelap) - self.npw.left = 200 - print("MOVE", self.npw.topleft) - time.sleep(timelap) - assert self.npw.left == 200 - - self.npw.right = 200 - print("MOVE", self.npw.topleft) - time.sleep(timelap) - assert self.npw.right == 200 - - self.npw.top = 200 - print("MOVE", self.npw.topleft) - time.sleep(timelap) - assert self.npw.top == 200 - - self.npw.bottom = 800 - print("MOVE", self.npw.topleft) - time.sleep(timelap) - assert self.npw.bottom == 800 - - self.npw.topleft = (300, 400) - print("MOVE", self.npw.topleft) - time.sleep(timelap) - assert self.npw.topleft == (300, 400) - - self.npw.topright = (300, 400) - print("MOVE", self.npw.topleft) - time.sleep(timelap) - assert self.npw.topright == (300, 400) - - self.npw.bottomleft = (300, 700) - print("MOVE", self.npw.topleft) - time.sleep(timelap) - assert self.npw.bottomleft == (300, 700) - - self.npw.bottomright = (300, 900) - print("MOVE", self.npw.topleft) - time.sleep(timelap) - assert self.npw.bottomright == (300, 900) - - self.npw.midleft = (300, 400) - print("MOVE", self.npw.topleft) - time.sleep(timelap) - assert self.npw.midleft == (300, 400) - - self.npw.midright = (300, 400) - print("MOVE", self.npw.topleft) - time.sleep(timelap) - assert self.npw.midright == (300, 400) - - self.npw.midtop = (300, 400) - print("MOVE", self.npw.topleft) - time.sleep(timelap) - assert self.npw.midtop == (300, 400) - - self.npw.midbottom = (300, 700) - print("MOVE", self.npw.topleft) - time.sleep(timelap) - assert self.npw.midbottom == (300, 700) - - self.npw.center = (300, 400) - print("MOVE", self.npw.topleft) - time.sleep(timelap) - assert self.npw.center == (300, 400) - - self.npw.centerx = 1000 - print("MOVE", self.npw.topleft) - time.sleep(timelap) - assert self.npw.centerx == 1000 - - self.npw.centery = 300 - print("MOVE", self.npw.topleft) - time.sleep(timelap) - assert self.npw.centery == 300 - - self.npw.width = 600 - print("RESIZE", self.npw.size) - time.sleep(timelap) - assert self.npw.width == 600 - - self.npw.height = 400 - time.sleep(timelap) - assert self.npw.height == 400 - - self.npw.size = (810, 610) - time.sleep(timelap) - assert self.npw.size == (810, 610) - - # Test lower and raise window - print("LOWER") - self.npw.lowerWindow() - time.sleep(timelap) - print("RAISE") - self.npw.raiseWindow() - time.sleep(timelap) - - # Test managing window stacking - print("ALWAYS ON TOP") - self.npw.alwaysOnTop() - time.sleep(timelap) - print("DEACTIVATE AOT") - self.npw.alwaysOnTop(aot=False) - time.sleep(timelap) - print("ALWAYS AT BOTTOM") - self.npw.alwaysOnBottom() - time.sleep(timelap) - print("DEACTIVATE AOB") - self.npw.alwaysOnBottom(aob=False) - time.sleep(timelap) - print("SEND BEHIND") - self.npw.sendBehind() - time.sleep(timelap) - print("BRING FROM BEHIND") - self.npw.sendBehind(sb=False) - time.sleep(timelap) - - # Test parent methods - print("GET PARENT") - parent = self.npw.getParent() - assert self.npw.isChild(parent) - - # Test visibility - print("HIDE") - self.npw.hide() - time.sleep(timelap) - assert not self.npw.isVisible - assert self.npw.isAlive - print("SHOW") - self.npw.show() - time.sleep(timelap) - assert self.npw.isVisible - assert self.npw.isAlive - - # Test ClientFrame (called twice to assure no re-registration) - print("CLIENT FRAME", self.npw.getClientFrame()) - print("CLIENT FRAME", self.npw.getClientFrame()) - - # Test closing - print("CLOSE") - self.npw.close() - assert not self.npw.isVisible - assert not self.npw.isAlive - - def windowWillClose_(self, aNotification: None): - '''Called automatically when the window is closed''' - print("Window has been closed") - # Terminate the application - NSApp().terminate_(self) - - def windowDidBecomeKey_(self, aNotification: None): - print("Now I'm ACTIVE") - - -def demo(): - # Create a new application instance ... - a = NSApplication.sharedApplication() - # ... and create its delegate. Note the use of the - # Objective C constructors below, because Delegate - # is a subclass of an Objective C class, NSObject - delegate = Delegate.alloc().init() - delegate.setDemoMode() - # Tell the application which delegate object to use. - a.setDelegate_(delegate) - - # Now we can start to create the window ... - frame = NSMakeRect(400, pywinctl.getScreenSize().height - 400, 250, 100) - # (Don't worry about these parameters for the moment. They just specify - # the type of window, its size and position etc) - mask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable - w = NSWindow.alloc().initWithContentRect_styleMask_backing_defer_(frame, mask, NSBackingStoreBuffered, False) - - # ... tell it which delegate object to use (here it happens - # to be the same delegate as the application is using)... - w.setDelegate_(delegate) - # ... and set some properties. Unicode strings are preferred. - w.setTitle_(u'Hello, World!') - # All set. Now we can show the window ... - w.orderFrontRegardless() - - # ... and start the application - a.run() - #AppHelper.runEventLoop() - - -if __name__ == '__main__': - demo() diff --git a/tests/test_pywinctl.py b/tests/test_pywinctl.py index 31cfc1f..5e386ae 100644 --- a/tests/test_pywinctl.py +++ b/tests/test_pywinctl.py @@ -1,6 +1,5 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - from __future__ import annotations import subprocess @@ -13,28 +12,45 @@ def test_basic(): + print("PLATFORM:", sys.platform) + print() + print("MONITORS:") + monitors = pywinctl.getAllScreens() + for monitor in monitors.keys(): + print(monitor, monitors[monitor]) + print() + print("ALL WINDOWS") + print(pywinctl.getAllTitles()) + print() + print("ALL APPS & WINDOWS") + print(pywinctl.getAllAppsWindowsTitles()) + print() + if sys.platform == "win32": subprocess.Popen('notepad') - time.sleep(0.5) + time.sleep(2) testWindows = [pywinctl.getActiveWindow()] - # testWindows = pywinctl.getWindowsWithTitle('Untitled - Notepad') # Not working in other languages assert len(testWindows) == 1 npw = testWindows[0] + wait = True + timelap = 0.50 - basic_win32(npw) + basic_test(npw, wait, timelap) elif sys.platform == "linux": subprocess.Popen('gedit') - time.sleep(5) + time.sleep(2) testWindows = [pywinctl.getActiveWindow()] assert len(testWindows) == 1 npw = testWindows[0] + wait = True + timelap = 0.50 - basic_linux(npw) + basic_test(npw, wait, timelap) elif sys.platform == "darwin": if not pywinctl.checkPermissions(activate=True): @@ -48,522 +64,225 @@ def test_basic(): assert len(testWindows) == 1 npw = testWindows[0] - assert isinstance(npw, pywinctl.Window) - - npw = testWindows[0] - - basic_macOS(npw) + wait = True + timelap = 0.50 + basic_test(npw, wait, timelap) subprocess.Popen(['rm', 'test.py']) else: raise NotImplementedError('PyWinCtl currently does not support this platform. If you have useful knowledge, please contribute! https://github.com/Kalmat/pywinctl') -if sys.platform == "win32": - def basic_win32(npw: pywinctl.Window | None): - - assert npw is not None - - wait = True - timelap = 0.5 - - # Test maximize/minimize/restore. - if npw.isMaximized: # Make sure it starts un-maximized - npw.restore(wait=wait) - - assert not npw.isMaximized - - npw.maximize(wait=wait) - assert npw.isMaximized - npw.restore(wait=wait) - assert not npw.isMaximized - - npw.minimize(wait=wait) - assert npw.isMinimized - npw.restore(wait=wait) - assert not npw.isMinimized - - # Test resizing - npw.resizeTo(800, 600, wait=wait) - assert npw.size == (800, 600) - assert npw.width == 800 - assert npw.height == 600 - - npw.resizeRel(10, 20, wait=wait) - assert npw.size == (810, 620) - assert npw.width == 810 - assert npw.height == 620 - - # Test moving - npw.moveTo(100, 200, wait=wait) - assert npw.topleft == (100, 200) - assert npw.left == 100 - assert npw.top == 200 - assert npw.right == 910 - assert npw.bottom == 820 - assert npw.bottomright == (910, 820) - assert npw.bottomleft == (100, 820) - assert npw.topright == (910, 200) - - npw.moveRel(1, 2, wait=wait) - assert npw.topleft == (101, 202) - assert npw.left == 101 - assert npw.top == 202 - assert npw.right == 911 - assert npw.bottom == 822 - assert npw.bottomright == (911, 822) - assert npw.bottomleft == (101, 822) - assert npw.topright == (911, 202) - - # Move via the properties - npw.resizeTo(800, 600) - npw.moveTo(200, 200) - - npw.left = 250 - time.sleep(timelap) - assert npw.left == 250 - - npw.right = 950 - time.sleep(timelap) - assert npw.right == 950 - - npw.top = 150 - time.sleep(timelap) - assert npw.top == 150 - - npw.bottom = 775 - time.sleep(timelap) - assert npw.bottom == 775 - - npw.topleft = (155, 350) - time.sleep(timelap) - assert npw.topleft == (155, 350) - - npw.topright = (1000, 300) - time.sleep(timelap) - assert npw.topright == (1000, 300) - - npw.bottomleft = (300, 975) - time.sleep(timelap) - assert npw.bottomleft == (300, 975) - - npw.bottomright = (1000, 900) - time.sleep(timelap) - assert npw.bottomright == (1000, 900) - - npw.midleft = (300, 400) - time.sleep(timelap) - assert npw.midleft == (300, 400) - - npw.midright = (1050, 600) - time.sleep(timelap) - assert npw.midright == (1050, 600) - - npw.midtop = (500, 350) - time.sleep(timelap) - assert npw.midtop == (500, 350) - - npw.midbottom = (500, 800) - time.sleep(timelap) - assert npw.midbottom == (500, 800) - - npw.center = (500, 350) - time.sleep(timelap) - assert npw.center == (500, 350) - - npw.centerx = 1000 - time.sleep(timelap) - assert npw.centerx == 1000 - - npw.centery = 600 - time.sleep(timelap) - assert npw.centery == 600 - - npw.width = 700 - time.sleep(timelap) - assert npw.width == 700 - npw.height = 500 - time.sleep(timelap) - assert npw.height == 500 +def basic_test(npw: pywinctl.Window | None, wait: bool, timelap: float): + assert npw is not None - npw.size = (801, 601) + def test_moveresize(attr, value): + setattr(npw, attr, value) time.sleep(timelap) - assert npw.size == (801, 601) + new_value = getattr(npw, attr) + assert new_value == value, f"Error while changing the window using the attribute {attr}. Expected value {value}, instead found {new_value}" - # Test window stacking - npw.lowerWindow() - time.sleep(timelap) - npw.raiseWindow() - time.sleep(timelap) - npw.alwaysOnTop() - time.sleep(timelap) - npw.alwaysOnTop(aot=False) - time.sleep(timelap) - npw.alwaysOnBottom() - time.sleep(timelap) - npw.alwaysOnBottom(aob=False) - time.sleep(timelap) - npw.sendBehind() - time.sleep(timelap) - npw.sendBehind(sb=False) - time.sleep(timelap) + def moveCB(pos): + print("WINDOW MOVED!!! New topleft (x, y) position:", pos) - # Test parent methods - parent = npw.getParent() - assert npw.isChild(parent) + def resizeCB(size): + print("WINDOW RESIZED!!! New size (width, height):", size) - # Test menu options - menu = npw.menu.getMenu() - submenu: dict[str, Any] = {} - for i, key in enumerate(menu): - if i == 4: - submenu = menu[key]["entries"] + def visibleCB(isVisible): + print("WINDOW VISIBILITY CHANGED!!! New visibility value:", isVisible) - option: dict[str, Any] | None = None - for i, key in enumerate(submenu): - if i == 3: - option = submenu[key] - if option: - npw.menu.clickMenuItem(wID=option["wID"]) - time.sleep(5) + def activeCB(isActive): + print("WINDOW FOCUS CHANGED!!!", isActive) - # Test closing - npw.close() + print("ACTIVE WINDOW:", npw.title, "/", npw.box) + print() + print("CLIENT FRAME:", npw.getClientFrame(), "EXTRA FRAME:", npw.getExtraFrameSize()) + print() + print("MINIMIZED:", npw.isMinimized, "MAXIMIZED:", npw.isMaximized, "ACTIVE:", npw.isActive, "ALIVE:", + npw.isAlive, "VISIBLE:", npw.isVisible) + print() + print("APP NAME:", npw.getAppName()) + print() + dpys = npw.getDisplay() + for dpy in dpys: + print("WINDOW DISPLAY:", dpy) + print() -if sys.platform == "linux": - def basic_linux(npw: pywinctl.Window | None): - # WARNING: Xlib/EWMH does not support negative positions, so be careful with positions calculations - # and/or set proper screen resolution to avoid negative values (tested OK on 1920x1200) - - assert npw is not None - - wait = True - timelap = 0.5 - - # Test maximize/minimize/restore. - if npw.isMaximized: # Make sure it starts un-maximized - npw.restore(wait=wait) - - assert not npw.isMaximized - - npw.maximize(wait=wait) - assert npw.isMaximized + # Test maximize/minimize/restore + if npw.isMaximized: # Make sure it starts un-maximized npw.restore(wait=wait) - assert not npw.isMaximized - - npw.minimize(wait=wait) - assert npw.isMinimized - npw.restore(wait=wait) - assert not npw.isMinimized - - # Test resizing - npw.resizeTo(800, 600, wait=wait) - assert npw.size == (800, 600) - assert npw.width == 800 - assert npw.height == 600 - - npw.resizeRel(10, 20, wait=wait) - assert npw.size == (810, 620) - assert npw.width == 810 - assert npw.height == 620 - - # Test moving - npw.moveTo(100, 200, wait=wait) - assert npw.topleft == (100, 200) - assert npw.left == 100 - assert npw.top == 200 - assert npw.right == 910 - assert npw.bottom == 820 - assert npw.bottomright == (910, 820) - assert npw.bottomleft == (100, 820) - assert npw.topright == (910, 200) - - npw.moveRel(1, 2, wait=wait) - assert npw.topleft == (101, 202) - assert npw.left == 101 - assert npw.top == 202 - assert npw.right == 911 - assert npw.bottom == 822 - assert npw.bottomright == (911, 822) - assert npw.bottomleft == (101, 822) - assert npw.topright == (911, 202) - - # Move via the properties - npw.resizeTo(800, 600) - npw.moveTo(200, 200) - - npw.left = 250 - time.sleep(timelap) - assert npw.left == 250 - - npw.right = 950 - time.sleep(timelap) - assert npw.right == 950 - - npw.top = 150 - time.sleep(timelap) - assert npw.top == 150 - - npw.bottom = 775 - time.sleep(timelap) - assert npw.bottom == 775 - - npw.topleft = (155, 350) - time.sleep(timelap) - assert npw.topleft == (155, 350) - - npw.topright = (1000, 300) - time.sleep(timelap) - assert npw.topright == (1000, 300) - - npw.bottomleft = (300, 975) - time.sleep(timelap) - assert npw.bottomleft == (300, 975) - - npw.bottomright = (1000, 900) - time.sleep(timelap) - assert npw.bottomright == (1000, 900) - - npw.midleft = (300, 400) - time.sleep(timelap) - assert npw.midleft == (300, 400) - - npw.midright = (1050, 600) - time.sleep(timelap) - assert npw.midright == (1050, 600) - - npw.midtop = (500, 350) - time.sleep(timelap) - assert npw.midtop == (500, 350) - - npw.midbottom = (500, 800) - time.sleep(timelap) - assert npw.midbottom == (500, 800) - - npw.center = (500, 350) - time.sleep(timelap) - assert npw.center == (500, 350) - - npw.centerx = 1000 - time.sleep(timelap) - assert npw.centerx == 1000 - - npw.centery = 600 - time.sleep(timelap) - assert npw.centery == 600 - - npw.width = 700 - time.sleep(timelap) - assert npw.width == 700 - - npw.height = 500 - time.sleep(timelap) - assert npw.height == 500 - - npw.size = (801, 601) - time.sleep(timelap) - assert npw.size == (801, 601) - - # Test window stacking - npw.lowerWindow() - time.sleep(timelap) - npw.raiseWindow() - time.sleep(timelap) - npw.alwaysOnTop() - time.sleep(timelap) - npw.alwaysOnTop(aot=False) - time.sleep(timelap) - npw.alwaysOnBottom() - time.sleep(timelap) - npw.alwaysOnBottom(aob=False) - time.sleep(timelap) + assert not npw.isMaximized + + print("MAXIMIZE") + npw.maximize(wait=wait) + time.sleep(timelap) + assert npw.isMaximized + print("RESTORE") + npw.restore(wait=wait) + time.sleep(timelap) + assert not npw.isMaximized + + print("MINIMIZE") + npw.minimize(wait=wait) + time.sleep(timelap) + assert npw.isMinimized + print("RESTORE") + npw.restore(wait=wait) + time.sleep(timelap) + assert not npw.isMinimized + + # Test resizing + print("RESIZE TO", 600, 400) + npw.resizeTo(600, 400, wait=wait) + time.sleep(timelap) + assert npw.size == (600, 400) + assert npw.width == 600 + assert npw.height == 400 + + print("RESIZE", "+10", "+20") + npw.resizeRel(10, 20, wait=wait) + assert npw.size == (610, 420) + assert npw.width == 610 + assert npw.height == 420 + + # Test moving + print("MOVE TO", 150, 154) + npw.moveTo(150, 154, wait=wait) + assert npw.topleft == (150, 154) + assert npw.left == 150 + assert npw.top == 154 + assert npw.right == 760 + assert npw.bottom == 574 + assert npw.bottomright == (760, 574) + assert npw.bottomleft == (150, 574) + assert npw.topright == (760, 154) + + print("MOVE", "+1", "+2") + npw.moveRel(1, 2, wait=wait) + assert npw.topleft == (151, 156) + assert npw.left == 151 + assert npw.top == 156 + assert npw.right == 761 + assert npw.bottom == 576 + assert npw.bottomright == (761, 576) + assert npw.bottomleft == (151, 576) + assert npw.topright == (761, 156) + + # Move via the properties + npw.resizeTo(601, 401, wait=wait) + npw.moveTo(100, 250, wait=wait) + npw.watchdog.start(movedCB=moveCB, resizedCB=resizeCB, isVisibleCB=visibleCB, isActiveCB=activeCB) + + print("MOVE LEFT", 200) + test_moveresize('left', 200) + print("MOVE RIGHT", 860) + test_moveresize('right', 860) + print("MOVE TOP", 200) + test_moveresize('top', 200) + print("MOVE BOTTOM", 800) + test_moveresize('bottom', 800) + print("MOVE TOPLEFT", 300, 400) + test_moveresize('topleft', (300, 400)) + print("MOVE TOPRIGHT", 800, 400) + test_moveresize('topright', (800, 400)) + print("MOVE BOTTOMLEFT", 300, 700) + test_moveresize('bottomleft', (300, 700)) + print("MOVE BOTTOMRIGHT", 850, 900) + test_moveresize('bottomright', (850, 900)) + print("MOVE MIDLEFT", 300, 400) + test_moveresize('midleft', (300, 400)) + print("MOVE MIDRIGHT", 770, 400) + test_moveresize('midright', (770, 400)) + print("MOVE MIDTOP", 800, 400) + test_moveresize('midtop', (800, 400)) + print("MOVE MIDBOTTOM", 700, 700) + test_moveresize('midbottom', (700, 700)) + print("MOVE CENTER", 760, 400) + test_moveresize('center', (760, 400)) + print("MOVE CENTERX", 900) + test_moveresize('centerx', 900) + print("MOVE CENTERY", 600) + test_moveresize('centery', 600) + print("RESIZE WIDTH", 500) + test_moveresize('width', 500) + print("RESIZE HEIGHT", 400) + test_moveresize('height', 400) + print("RESIZE", 540, 410) + test_moveresize('size', (540, 410)) + + # Test window stacking + print("LOWER WINDOW") + lowered = npw.lowerWindow() + time.sleep(timelap*3) + # assert lowered, 'Window has not been lowered' + + print("RAISE WINDOW") + raised = npw.raiseWindow() + time.sleep(timelap) + # assert raised, 'Window has not been raised' + + if sys.platform != "darwin": + print("SEND BEHIND") npw.sendBehind() time.sleep(timelap) - npw.sendBehind(sb=False) + print("RESTORE") + npw.sendBehind(False) time.sleep(timelap) - # Test parent methods - parent = npw.getParent() - assert npw.isChild(parent) - - # Test closing - npw.close() - -if sys.platform == "darwin": - def basic_macOS(npw: pywinctl.Window): + print("ALWAYS ON TOP") + npw.alwaysOnTop() + time.sleep(timelap) + print("RESTORE") + npw.alwaysOnTop(False) + time.sleep(timelap) - assert npw is not None + print("ALWAYS AT BOTTOM") + npw.alwaysOnBottom() + time.sleep(timelap*3) + print("RESTORE") + npw.alwaysOnBottom(False) + time.sleep(timelap) - wait = True - timelap = 0.50 - - # Test maximize/minimize/restore. - if npw.isMaximized: # Make sure it starts un-maximized - npw.restore(wait=wait) - - assert not npw.isMaximized - - npw.maximize(wait=wait) - time.sleep(timelap) - assert npw.isMaximized - npw.restore(wait=wait) - time.sleep(timelap) - assert not npw.isMaximized - - npw.minimize(wait=wait) - time.sleep(timelap) - assert npw.isMinimized - npw.restore(wait=wait) - time.sleep(timelap) - assert not npw.isMinimized - - # Test resizing - npw.resizeTo(600, 400, wait=wait) - time.sleep(timelap) - assert npw.size == (600, 400) - assert npw.width == 600 - assert npw.height == 400 - - npw.resizeRel(10, 20, wait=wait) - assert npw.size == (610, 420) - assert npw.width == 610 - assert npw.height == 420 - - # Test moving - npw.moveTo(50, 54, wait=wait) - assert npw.topleft == (50, 54) - assert npw.left == 50 - assert npw.top == 54 - assert npw.right == 660 - assert npw.bottom == 474 - assert npw.bottomright == (660, 474) - assert npw.bottomleft == (50, 474) - assert npw.topright == (660, 54) - - npw.moveRel(1, 2, wait=wait) - assert npw.topleft == (51, 56) - assert npw.left == 51 - assert npw.top == 56 - assert npw.right == 661 - assert npw.bottom == 476 - assert npw.bottomright == (661, 476) - assert npw.bottomleft == (51, 476) - assert npw.topright == (661, 56) - - # Move via the properties - npw.resizeTo(601, 401, wait=wait) - npw.moveTo(100, 250, wait=wait) - - npw.left = 200 - time.sleep(timelap) - assert npw.left == 200 - - npw.right = 200 - time.sleep(timelap) - assert npw.right == 200 + npw.watchdog.stop() - npw.top = 200 - time.sleep(timelap) - assert npw.top == 200 - - npw.bottom = 800 - time.sleep(timelap) - assert npw.bottom == 800 - - npw.topleft = (300, 400) - time.sleep(timelap) - assert npw.topleft == (300, 400) - - npw.topright = (300, 400) - time.sleep(timelap) - assert npw.topright == (300, 400) - - npw.bottomleft = (300, 700) - time.sleep(timelap) - assert npw.bottomleft == (300, 700) - - npw.bottomright = (300, 900) - time.sleep(timelap) - assert npw.bottomright == (300, 900) - - npw.midleft = (300, 400) - time.sleep(timelap) - assert npw.midleft == (300, 400) - - npw.midright = (300, 400) - time.sleep(timelap) - assert npw.midright == (300, 400) - - npw.midtop = (300, 400) - time.sleep(timelap) - assert npw.midtop == (300, 400) - - npw.midbottom = (300, 700) - time.sleep(timelap) - assert npw.midbottom == (300, 700) - - npw.center = (300, 400) - time.sleep(timelap) - assert npw.center == (300, 400) - - npw.centerx = 1000 - time.sleep(timelap) - assert npw.centerx == 1000 - - npw.centery = 300 - time.sleep(timelap) - assert npw.centery == 300 - - npw.width = 600 - time.sleep(timelap) - assert npw.width == 600 - - npw.height = 400 - time.sleep(timelap) - assert npw.height == 400 - - npw.size = (810, 610) - time.sleep(timelap) - assert npw.size == (810, 610) - - npw.lowerWindow() - time.sleep(timelap) - - npw.raiseWindow() - time.sleep(timelap) - - # Test window stacking - npw.lowerWindow() - time.sleep(timelap) - npw.raiseWindow() - time.sleep(timelap) - - # Test parent methods - parent = npw.getParent() - assert parent and npw.isChild(parent) - - # Test menu options + # Test parent methods + print("PARENT INFO") + parent = npw.getParent() + if parent: + print("WINDOW PARENT:", parent, npw.isChild(parent)) + assert npw.isChild(parent) + children = npw.getChildren() + for child in children: + if child: + if isinstance(child, int): + print("WINDOW CHILD:", child, npw.isParent(child)) + else: + print("WINDOW CHILD:", child.id, npw.isParent(child)) + + # Test menu options + print("MENU INFO (WORKING IN WINDOWS 10 AND MACOS, BUT NOT IN WINDOWS 11 NOR LINUX)") + if sys.platform in ("win32", "darwin"): menu = npw.menu.getMenu() submenu = {} for i, key in enumerate(menu): if i == 1: submenu = menu[key].get("entries", {}) + break option: dict[str, Any] | None = None for i, key in enumerate(submenu): if i == 0: option = cast("dict[str, Any]", submenu[key]) + if option: + print("CLICK OPTION") npw.menu.clickMenuItem(wID=option.get("wID", "")) time.sleep(5) - # Test closing - npw.close() - - -def main(): - test_basic() + # Test closing + print("CLOSE WINDOW") + npw.close() if __name__ == '__main__': - main() + test_basic() diff --git a/typings/AppKit.pyi b/typings/AppKit.pyi index 8148de0..71036ce 100644 --- a/typings/AppKit.pyi +++ b/typings/AppKit.pyi @@ -54,3 +54,7 @@ class NSWindow(NSObject): class NSBackingStoreBuffered(NSObject): def __getattr__(self, name: str) -> Any: ... + + +class WindowDelegate(NSObject): + def __getattr__(self, name:str) -> Any: ... \ No newline at end of file