Skip to content

Commit

Permalink
Added getParent(), getHandle(), isParent() and isChild() methods
Browse files Browse the repository at this point in the history
  • Loading branch information
Kalmat committed Feb 27, 2022
1 parent 0a61e3f commit 604e001
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 65 deletions.
21 changes: 12 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
First off, my most sincere thanks and acknowledgement to macdeport (https://github.com/macdeport) and holychowders (https://github.com/holychowders) for their help and moral boost.

# PyWinCtl

This is a fork from asweigart's PyGetWindow module (https://github.com/asweigart/PyGetWindow), intended to obtain GUI information on and control application's windows.
Cross-Platform module to gather information on and control windows opened on screen.

This module is a fork from asweigart's PyGetWindow module (https://github.com/asweigart/PyGetWindow), which adds Linux and macOS experimental support to the MS Windows-only original module, in the hope others can use it, test it or contribute

This fork adds Linux and macOS experimental support to the MS Windows-only original module, in the hope others can use it, test it or contribute
My most sincere thanks and acknowledgement to macdeport (https://github.com/macdeport) and holychowders (https://github.com/holychowders) for their help and moral boost.

## Window Features

Expand All @@ -19,15 +19,18 @@ All these functions are available at the moment, in all three platforms (Windows
| getWindowsWithTitle | hide | isVisible |
| getWindowsAt | show | |
| cursor (mouse position) | activate | |
| resolution (screen size) | resize / resizeRel | |
| resolution (screen size) | resize / resizeRel | |
| | resizeTo | |
| | move / moveRel | |
| | move / moveRel | |
| | moveTo | |
| | alwaysOnTop | |
| | alwaysOnBottom | |
| | lowerWindow | |
| | raiseWindow | |
| | sendBehind | |
| | getParent | |
| | getHandle | |
| | isParent | |
| | isChild | |

#### IMPORTANT macOS NOTICE:

Expand Down Expand Up @@ -82,11 +85,11 @@ Note not all windows/applications will have a menu accessible by these methods.

To install this module on your system, you can use pip:

python3 -m pip install pywinctl
pip install pywinctl

Alternatively, you can download the wheel file (.whl) located in "dist" folder, and run this (don't forget to replace 'x.x.xx' with proper version number):

python3 -m pip install PyWinCtl-x.x.xx-py3-none-any.whl
pip install PyWinCtl-x.x.xx-py3-none-any.whl

You may want to add '--force-reinstall' option to be sure you are installing the right dependencies version.

Expand Down
Binary file removed dist/PyWinCtl-0.0.12-py3-none-any.whl
Binary file not shown.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
name='PyWinCtl',
version=version,
url='https://github.com/Kalmat/PyWinCtl',
download_url='https://github.com/Kalmat/PyWinCtl/archive/refs/tags/v.0.0.11-beta.tar.gz',
# download_url='https://github.com/Kalmat/PyWinCtl/archive/refs/tags/%s.tar.gz' % version,
author='Kalmat',
author_email='[email protected]',
description=('A cross-platform module to control and obtain GUI information on application\'s windows.'),
Expand Down
10 changes: 5 additions & 5 deletions src/pywinctl/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# PyWinCtl
# A cross-platform module to get info and control windows.
# A cross-platform module to get info on and control windows.

# pywin32 on Windows
# pyobjc (AppKit and Quartz) on macOS
# Xlib and ewmh on linux
# Xlib and ewmh on Linux


__version__ = "0.0.12"
__version__ = "0.0.15"

import sys, collections, pyrect

Expand Down Expand Up @@ -149,11 +149,11 @@ def getHandle(self):
"""Returns the handle of the window"""
raise NotImplementedError

def isParent(self, hWnd) -> bool:
def isParent(self, child) -> bool:
"""Returns True if the window is parent of the given window as input argument"""
raise NotImplementedError

def isChild(self, hWnd) -> bool:
def isChild(self, parent) -> bool:
"""Returns True if the window is child of the given window as input argument"""
raise NotImplementedError

Expand Down
32 changes: 22 additions & 10 deletions src/pywinctl/_pywinctl_linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ class LinuxWindow(BaseWindow):
def __init__(self, hWnd: Union[Cursor, Drawable, Pixmap, Resource, Fontable, Window, GC, Colormap, Font]):
super().__init__()
self._hWnd = hWnd
self._parent = self._hWnd.query_tree().parent
self._setupRectProperties()
# self._saveWindowInitValues() # Store initial Window parameters to allow reset and other actions

Expand Down Expand Up @@ -428,6 +429,7 @@ def sendBehind(self, sb: bool = True) -> bool:

else:
# Mint/Cinnamon: just clicking on the desktop, it raises, sending the window/wallpaper to the bottom!
# TODO: Find a smarter way to raise desktop icons instead of a mouse click
m = mouse.Controller()
m.move(SCREEN.width_in_pixels - 1, 100)
m.click(mouse.Button.left, 1)
Expand All @@ -448,21 +450,31 @@ def sendBehind(self, sb: bool = True) -> bool:
EWMH.display.flush()
return WINDOW_NORMAL in EWMH.getWmWindowType(self._hWnd, str=True) and self.isActive

def getParent(self):
def getParent(self) -> Union[Cursor, Drawable, Pixmap, Resource, Fontable, Window, GC, Colormap, Font]:
"""Returns the handle of the window parent"""
raise NotImplementedError
return self._hWnd.query_tree().parent

def getHandle(self):
def getHandle(self) -> Union[Cursor, Drawable, Pixmap, Resource, Fontable, Window, GC, Colormap, Font]:
"""Returns the handle of the window"""
raise NotImplementedError
return self._hWnd

def isParent(self, hWnd: BaseWindow) -> bool:
"""Returns True if the window is parent of the given window as input argument"""
raise NotImplementedError
def isParent(self, child: Union[Cursor, Drawable, Pixmap, Resource, Fontable, Window, GC, Colormap, Font]) -> bool:
"""Returns True if the window is parent of the given window as input argument
def isChild(self, hWnd: BaseWindow) -> bool:
"""Returns True if the window is child of the given window as input argument"""
raise NotImplementedError
Args:
----
''child'' handle of the window you want to check if the current window is parent of
"""
return child.query_tree().parent == self._hWnd

def isChild(self, parent: Union[Cursor, Drawable, Pixmap, Resource, Fontable, Window, GC, Colormap, Font]) -> bool:
"""Returns True if the window is child of the given window as input argument
Args:
----
''parent'' handle of the window/app you want to check if the current window is child of
"""
return parent == self.getParent()

@property
def isMinimized(self) -> bool:
Expand Down
110 changes: 80 additions & 30 deletions src/pywinctl/_pywinctl_macos.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
WAIT_ATTEMPTS = 10
WAIT_DELAY = 0.025 # Will be progressively increased on every retry

SEP = "|&|"


def getActiveWindow(app: AppKit.NSApplication = None):
"""Returns a Window object of the currently active Window or None."""
Expand Down Expand Up @@ -158,6 +160,7 @@ def __init__(self, app: AppKit.NSRunningApplication, title: str):
self._appName = app.localizedName()
self._appPID = app.processIdentifier()
self._winTitle = title
self._parent = self.getParent()
self._setupRectProperties()
v = platform.mac_ver()[0].split(".")
ver = float(v[0]+"."+v[1])
Expand Down Expand Up @@ -516,13 +519,15 @@ def alwaysOnTop(self, aot: bool = True) -> bool:
Use aot=False to deactivate always-on-top behavior
"""
# TODO: Is there an attribute or similar to force window always on top?
raise NotImplementedError

def alwaysOnBottom(self, aob: bool = True) -> bool:
"""Keeps window below of all others, but on top of desktop icons and keeping all window properties
Use aob=False to deactivate always-on-bottom behavior
"""
# TODO: Is there an attribute or similar to force window always at bottom?
raise NotImplementedError

def lowerWindow(self) -> None:
Expand All @@ -535,6 +540,7 @@ def lowerWindow(self) -> None:
try
tell application "System Events" to tell application "%s"
set winList to every window whose visible is true
set index of winName to (count of winList as integer)
if not winList = {} then
repeat with oWin in (items of reverse of winList)
if not name of oWin = winName then
Expand Down Expand Up @@ -569,23 +575,70 @@ def sendBehind(self, sb: bool = True) -> bool:
"""Sends the window to the very bottom, under all other windows, including desktop icons.
It may also cause that window does not accept focus nor keyboard/mouse events.
"""
# TODO: Is there an attribute or similar to set window level?
raise NotImplementedError

def getParent(self):
"""Returns the handle of the window parent"""
raise NotImplementedError
def getParent(self) -> str:
"""Returns the handle (role:title) of the parent window/app"""
cmd = """on run {arg1, arg2}
set appName to arg1 as string
set winName to arg2 as string
set parentRole to ""
set parentName to ""
try
tell application "System Events" to tell application process appName
set parentRole to value of attribute "AXRole" of (get value of attribute "AXParent" of window winName)
set parentName to value of attribute "AXTitle" of (get value of attribute "AXParent" of window winName)
end tell
end try
return {parentRole, parentName}
end run"""
proc = subprocess.Popen(['osascript', '-', self._appName, self.title],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, encoding='utf8')
ret, err = proc.communicate(cmd)
ret = ret.replace("\n", "")
role, parent = ret.split(", ")
result = ""
if role and parent:
result = role + SEP + parent
return result

def getHandle(self):
"""Returns the handle of the window"""
raise NotImplementedError
def getHandle(self) -> str:
"""Returns the handle (role:title) of the window"""
return "AXWindow" + SEP + self.title

def isParent(self, hWnd: BaseWindow) -> bool:
"""Returns True if the window is parent of the given window as input argument"""
raise NotImplementedError
def isParent(self, child: str) -> bool:
"""Returns True if the window is parent of the given window as input argument
def isChild(self, hWnd: BaseWindow) -> bool:
"""Returns True if the window is child of the given window as input argument"""
raise NotImplementedError
Args:
----
''child'' title/handle (role:title) of the window you want to check if the current window is parent of
"""
if ":" in child:
part = child.split(SEP)
if len(part) > 1:
child = part[1]
windows = getWindowsWithTitle(child)
found = False
for win in windows:
if win.getParent() == self.getHandle():
found = True
break
return found

def isChild(self, parent: str) -> bool:
"""Returns True if the window is child of the given window as input argument
Args:
----
''parent'' title/handle (role:title) of the window/app you want to check if the current window is child of
"""
currParent = self.getParent()
if ":" not in parent:
part = currParent.split(SEP)
if len(part) > 1:
currParent = part[1]
return currParent == parent

@property
def isMinimized(self) -> bool:
Expand Down Expand Up @@ -726,7 +779,6 @@ def __init__(self, parent: BaseWindow):
self._menuStructure = {}
self.menuList = []
self.itemList = []
self._sep = "|&|"

def getMenu(self, addItemInfo: bool = False) -> dict:
"""Loads and returns the MENU struct in a dictionary format, if exists, or empty.
Expand Down Expand Up @@ -882,7 +934,7 @@ def subfillit(subNameList, subSizeList, subPosList, subAttrList, section="", lev

option = self._menuStructure
if section:
for sec in section.split(self._sep):
for sec in section.split(SEP):
if sec:
option = option[sec]

Expand All @@ -892,7 +944,7 @@ def subfillit(subNameList, subSizeList, subPosList, subAttrList, section="", lev
item = "separator"
option[item] = {}
else:
ref = section.replace(self._sep + "entries", "") + self._sep + item
ref = section.replace(SEP + "entries", "") + SEP + item
option[item] = {"parent": parent, "wID": self._getNewWid(ref)}
if size and pos and size != "missing value" and pos != "missing value":
x, y = pos
Expand All @@ -906,7 +958,7 @@ def subfillit(subNameList, subSizeList, subPosList, subAttrList, section="", lev
option[item]["hSubMenu"] = self._getNewHSubMenu(ref)
option[item]["entries"] = {}
subfillit(submenu, subSize, subPos, subAttr,
section + self._sep + item + self._sep + "entries",
section + SEP + item + SEP + "entries",
level=level+1, mainlevel=mainlevel, parent=hSubMenu)
else:
option[item]["hSubMenu"] = 0
Expand All @@ -915,7 +967,7 @@ def subfillit(subNameList, subSizeList, subPosList, subAttrList, section="", lev
hSubMenu = self._getNewHSubMenu(item)
self._menuStructure[item] = {"hSubMenu": hSubMenu, "wID": self._getNewWid(item), "entries": {}}
subfillit(flatNameList[1][i], flatSizeList[1][i], flatPosList[1][i], flatAttrList[1][i] if addItemInfo else [],
item + self._sep + "entries", level=1, mainlevel=i, parent=hSubMenu)
item + SEP + "entries", level=1, mainlevel=i, parent=hSubMenu)

if findit():
flatenit()
Expand Down Expand Up @@ -1200,7 +1252,7 @@ def _getPathFromWid(self, wID):
itemPath = []
if self._checkMenuStruct():
if 0 < wID <= len(self.itemList):
itemPath = self.itemList[wID - 1].split(self._sep)
itemPath = self.itemList[wID - 1].split(SEP)
return itemPath

def _getNewHSubMenu(self, ref):
Expand All @@ -1211,7 +1263,7 @@ def _getPathFromHSubMenu(self, hSubMenu):
menuPath = []
if self._checkMenuStruct():
if 0 < hSubMenu <= len(self.menuList):
menuPath = self.menuList[hSubMenu - 1].split(self._sep)
menuPath = self.menuList[hSubMenu - 1].split(SEP)
return menuPath

def _getMenuItemWid(self, itemPath: str) -> str:
Expand Down Expand Up @@ -1450,21 +1502,19 @@ def sendBehind(self, sb: bool = True) -> bool:

def getParent(self):
"""Returns the handle of the window parent"""
raise NotImplementedError
return self._hWnd.hWnd.parentWindow()

def getHandle(self):
"""Returns the handle of the window"""
raise NotImplementedError
return self._hWnd

def isParent(self, hWnd: BaseWindow) -> bool:
"""Returns True if the window is parent of the given window as input argument
"""
raise NotImplementedError
def isParent(self, child) -> bool:
"""Returns True if the window is parent of the given window as input argument"""
return child.parentWindow() == self._hWnd

def isChild(self, hWnd: BaseWindow) -> bool:
"""Returns True if the window is child of the given window as input argument
"""
raise NotImplementedError
def isChild(self, parent) -> bool:
"""Returns True if the window is child of the given window as input argument"""
return parent == self.getParent()

@property
def isMinimized(self) -> bool:
Expand Down Expand Up @@ -1556,7 +1606,7 @@ def main():
print("ALL WINDOWS", getAllTitles())
npw = getActiveWindow()
print("ACTIVE WINDOW:", npw.title, "/", npw.box)
print("")
print()
displayWindowsUnderMouse(0, 0)


Expand Down
Loading

0 comments on commit 604e001

Please sign in to comment.