Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 24 additions & 24 deletions src/pygetwindow/__init__.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,12 @@
# PyGetWindow
# A cross-platform module to find information about the windows on the screen.

# Work in progress

# Useful info:
# https://stackoverflow.com/questions/373020/finding-the-current-active-window-in-mac-os-x-using-python
# https://stackoverflow.com/questions/7142342/get-window-position-size-with-python


# win32 api and ctypes on Windows
# cocoa api and pyobjc on Mac
# Xlib on linux


# Possible Future Features:
# get/click menu (win32: GetMenuItemCount, GetMenuItemInfo, GetMenuItemID, GetMenu, GetMenuItemRect)


__version__ = "0.0.9"

import sys, collections, pyrect

import sys
import collections
import pyrect

class PyGetWindowException(Exception):
"""
Expand Down Expand Up @@ -96,7 +82,7 @@ def maximize(self):
raise NotImplementedError

def restore(self):
"""If maximized or minimized, restores the window to it's normal size."""
"""If maximized or minimized, restores the window to its normal size."""
raise NotImplementedError

def activate(self):
Expand Down Expand Up @@ -150,7 +136,6 @@ def left(self):

@left.setter
def left(self, value):
# import pdb; pdb.set_trace()
self._rect.left # Run rect's onRead to update the Rect object.
self._rect.left = value

Expand Down Expand Up @@ -326,12 +311,14 @@ def box(self, value):
self._rect.box = value


# Platform-specific imports
if sys.platform == "darwin":
# raise NotImplementedError('PyGetWindow currently does not support macOS. If you have Appkit/Cocoa knowledge, please contribute! https://github.com/asweigart/pygetwindow') # TODO - implement mac
# macOS support
from ._pygetwindow_macos import *

Window = MacOSWindow

elif sys.platform == "win32":
# Windows support
from ._pygetwindow_win import (
Win32Window,
getActiveWindow,
Expand All @@ -341,9 +328,22 @@ def box(self, value):
getAllWindows,
getAllTitles,
)

Window = Win32Window

elif sys.platform.startswith("linux"):
# Linux support
from ._pygetwindow_linux import (
LinuxWindow,
getActiveWindow,
getWindowsWithTitle,
getAllWindows,
getAllTitles,
)
Window = LinuxWindow

else:
raise NotImplementedError(
"PyGetWindow currently does not support Linux. If you have Xlib knowledge, please contribute! https://github.com/asweigart/pygetwindow"
)
"PyGetWindow currently does not support this platform. "
"If you have knowledge of the platform's windowing system, please contribute! "
"https://github.com/asweigart/pygetwindow"
)
105 changes: 105 additions & 0 deletions src/pygetwindow/_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# _base.py
import collections

Rect = collections.namedtuple("Rect", "left top right bottom")
Point = collections.namedtuple("Point", "x y")
Size = collections.namedtuple("Size", "width height")


class BaseWindow:
def __init__(self):
pass

def _setupRectProperties(self):
def _onRead(attrName):
r = self._getWindowRect()
self._rect._left = r.left # Setting _left directly to skip the onRead.
self._rect._top = r.top # Setting _top directly to skip the onRead.
self._rect._width = r.right - r.left # Setting _width directly to skip the onRead.
self._rect._height = r.bottom - r.top # Setting _height directly to skip the onRead.

def _onChange(oldBox, newBox):
self.moveTo(newBox.left, newBox.top)
self.resizeTo(newBox.width, newBox.height)

r = self._getWindowRect()
self._rect = pyrect.Rect(r.left, r.top, r.right - r.left, r.bottom - r.top, onChange=_onChange, onRead=_onRead)

def _getWindowRect(self):
raise NotImplementedError

def __str__(self):
r = self._getWindowRect()
width = r.right - r.left
height = r.bottom - r.top
return '<%s left="%s", top="%s", width="%s", height="%s", title="%s">' % (
self.__class__.__qualname__,
r.left,
r.top,
width,
height,
self.title,
)

def close(self):
"""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."""
raise NotImplementedError

def minimize(self):
"""Minimizes this window."""
raise NotImplementedError

def maximize(self):
"""Maximizes this window."""
raise NotImplementedError

def restore(self):
"""If maximized or minimized, restores the window to its normal size."""
raise NotImplementedError

def activate(self):
"""Activate this window and make it the foreground window."""
raise NotImplementedError

def resizeRel(self, widthOffset, heightOffset):
"""Resizes the window relative to its current size."""
raise NotImplementedError

def resizeTo(self, newWidth, newHeight):
"""Resizes the window to a new width and height."""
raise NotImplementedError

def moveRel(self, xOffset, yOffset):
"""Moves the window relative to its current position."""
raise NotImplementedError

def moveTo(self, newLeft, newTop):
"""Moves the window to new coordinates on the screen."""
raise NotImplementedError

@property
def isMinimized(self):
"""Returns True if the window is currently minimized."""
raise NotImplementedError

@property
def isMaximized(self):
"""Returns True if the window is currently maximized."""
raise NotImplementedError

@property
def isActive(self):
"""Returns True if the window is currently the active, foreground window."""
raise NotImplementedError

@property
def title(self):
"""Returns the window title as a string."""
raise NotImplementedError

@property
def visible(self):
raise NotImplementedError
135 changes: 135 additions & 0 deletions src/pygetwindow/_pygetwindow_linux.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import sys
from collections import namedtuple
from Xlib import X, display, protocol
from Xlib.ext import randr
from ._base import BaseWindow, Rect, Point, Size

# Definition of basic data structures
Rect = namedtuple("Rect", "left top right bottom")
Point = namedtuple("Point", "x y")
Size = namedtuple("Size", "width height")

class LinuxWindow(BaseWindow):
def __init__(self, hwnd):
self._hwnd = hwnd # Window identifier (Window ID)
self._display = display.Display() # Connection to the X server
self._setupRectProperties()

def _getWindowRect(self):
"""Gets the window rectangle (left, top, right, bottom)."""
geom = self._display.create_resource_object('window', self._hwnd).get_geometry()
return Rect(geom.x, geom.y, geom.x + geom.width, geom.y + geom.height)

def close(self):
"""Closes the window."""
self._send_event(self._hwnd, protocol.event.ClientMessage,
data=(32, [self._display.intern_atom('WM_DELETE_WINDOW'), X.CurrentTime, 0]))

def minimize(self):
"""Minimizes the window."""
self._change_state(self._display.intern_atom('_NET_WM_STATE_HIDDEN'))

def maximize(self):
"""Maximizes the window."""
self._change_state(self._display.intern_atom('_NET_WM_STATE_MAXIMIZED_VERT'),
self._display.intern_atom('_NET_WM_STATE_MAXIMIZED_HORZ'))

def restore(self):
"""Restores the window from minimized/maximized state."""
self._change_state(self._display.intern_atom('_NET_WM_STATE_NORMAL'))

def activate(self):
"""Activates the window."""
self._send_event(self._hwnd, protocol.event.ClientMessage,
data=(32, [self._display.intern_atom('_NET_ACTIVE_WINDOW'), X.CurrentTime, 0]))

def resizeRel(self, widthOffset, heightOffset):
"""Resizes the window relative to its current size."""
rect = self._getWindowRect()
self.resizeTo(rect.width + widthOffset, rect.height + heightOffset)

def resizeTo(self, newWidth, newHeight):
"""Resizes the window."""
self._send_event(self._hwnd, protocol.event.ConfigureNotify,
data=(newWidth, newHeight))

def moveRel(self, xOffset, yOffset):
"""Moves the window relative to its current position."""
rect = self._getWindowRect()
self.moveTo(rect.left + xOffset, rect.top + yOffset)

def moveTo(self, newLeft, newTop):
"""Moves the window."""
self._send_event(self._hwnd, protocol.event.ConfigureNotify,
data=(newLeft, newTop))

@property
def isMinimized(self):
"""Checks if the window is minimized."""
return '_NET_WM_STATE_HIDDEN' in self._get_window_states()

@property
def isMaximized(self):
"""Checks if the window is maximized."""
states = self._get_window_states()
return ('_NET_WM_STATE_MAXIMIZED_VERT' in states and
'_NET_WM_STATE_MAXIMIZED_HORZ' in states)

@property
def isActive(self):
"""Checks if the window is active."""
active_window = self._display.get_input_focus().focus
return active_window == self._hwnd

@property
def title(self):
"""Returns the window title."""
return self._display.create_resource_object('window', self._hwnd).get_wm_name()

@property
def visible(self):
"""Checks if the window is visible."""
return self._display.create_resource_object('window', self._hwnd).get_attributes().map_state == X.IsViewable

def _send_event(self, window, event_type, data):
"""Sends an event to the window."""
event = event_type(
window=window,
client_type=self._display.intern_atom('_NET_WM_STATE'),
data=data
)
self._display.send_event(window, event, event_mask=X.SubstructureRedirectMask | X.SubstructureNotifyMask)
self._display.sync()

def _change_state(self, *states):
"""Changes the window state."""
data = (32, list(states))
self._send_event(self._hwnd, protocol.event.ClientMessage, data)

def _get_window_states(self):
"""Returns the current states of the window."""
atom = self._display.intern_atom('_NET_WM_STATE')
prop = self._display.create_resource_object('window', self._hwnd).get_property(atom, X.AnyPropertyType)
return [self._display.get_atom_name(state) for state in prop.value]

def getActiveWindow():
"""Returns the active window."""
d = display.Display()
root = d.screen().root
active_window_id = root.get_full_property(d.intern_atom('_NET_ACTIVE_WINDOW'), X.AnyPropertyType).value[0]
return LinuxWindow(active_window_id)

def getAllWindows():
"""Returns a list of all windows."""
d = display.Display()
root = d.screen().root
window_ids = root.query_tree().children
return [LinuxWindow(wid) for wid in window_ids]

def getWindowsWithTitle(title):
"""Returns windows with the specified title."""
return [win for win in getAllWindows() if title in win.title]

def getAllTitles():
"""Returns titles of all windows."""
return [win.title for win in getAllWindows()]