Skip to content

Commit d5d9864

Browse files
Windows support
1 parent 5846516 commit d5d9864

File tree

15 files changed

+313
-71
lines changed

15 files changed

+313
-71
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@
1212
[![Publish](https://github.com/Commandcracker/display-server-interactions/actions/workflows/pypi-publish.yml/badge.svg)](https://github.com/Commandcracker/display-server-interactions/actions/workflows/pypi-publish.yml)
1313

1414
DSI allows you to perform basic interactions on your display server, like screenshotting a window or sending input to it.
15-
Currently, DSI only supports X11/Xorg (GNU/Linux) but it aims to be cross-platform.
15+
Currently, DSI only supports X11/Xorg (GNU/Linux) and Windows but it aims to be cross-platform.
1616

1717
**WARNING: Please Do not use DSI in production, because it's currently in development!**
1818

1919
## Quick overview
2020

21-
Look at the [documentation](https://display-server-interactions.readthedocs.io/en/latest/) for moor information's
21+
Look at the [documentation](https://display-server-interactions.readthedocs.io/en/latest/) for moor information
2222

2323
### Get a window
2424

display_server_interactions/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from platform import system as __system
55

6-
__version__ = "0.0.dev4"
6+
__version__ = "0.0.dev5"
77
__author__ = "Commandcracker"
88

99
__os_name = __system().lower()
@@ -12,7 +12,7 @@
1212
from .linux import DSI
1313

1414
elif __os_name == "windows":
15-
raise NotImplementedError("Windows is not yet implemented.")
15+
from .windows import DSI
1616

1717
elif __os_name == "darwin":
1818
raise NotImplementedError("MacOS is not yet implemented.")

display_server_interactions/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def get_active_window(self) -> WindowBase:
1616
pass
1717

1818
@abstractmethod
19-
def get_all_windows(self) -> list:
19+
def get_all_windows(self) -> list[WindowBase]:
2020
"""
2121
Returns a list of all Windows.
2222
"""

display_server_interactions/box.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/python3
2+
# -*- coding: utf-8 -*-
3+
4+
class Box(tuple):
5+
def __init__(self, x: int, y: int, width: int, height: int):
6+
"""This is just here for autocompletion"""
7+
pass
8+
9+
def __new__(self, x: int, y: int, width: int, height: int):
10+
return tuple.__new__(Box, (x, y, width, height))
11+
12+
@property
13+
def x(self) -> int:
14+
return self[0]
15+
16+
@property
17+
def y(self) -> int:
18+
return self[1]
19+
20+
@property
21+
def width(self) -> int:
22+
return self[2]
23+
24+
@property
25+
def height(self) -> int:
26+
return self[3]
27+
28+
def __repr__(self) -> str:
29+
return f'Box(x={self.x}, y={self.y}, width={self.width}, height={self.height})'
30+
31+
32+
def main() -> None:
33+
try:
34+
from rich import print
35+
except ImportError:
36+
pass
37+
box = Box(100, 200, 300, 400)
38+
print(box)
39+
print(f"x={box.x}")
40+
print(f"y={box.y}")
41+
print(f"width={box.width}")
42+
print(f"height={box.height}")
43+
44+
45+
if __name__ == "__main__":
46+
main()
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/python3
2+
# -*- coding: utf-8 -*-
3+
4+
class MouseButtons(object):
5+
"""
6+
https://tronche.com/gui/x/xlib/events/keyboard-pointer/keyboard-pointer.html\n
7+
/usr/include/X11/X.h: 259-263
8+
"""
9+
LEFT = 1
10+
RIGHT = 2
11+
MIDDLE = 3
12+
FORWARD = 4
13+
BACKWARD = 5

display_server_interactions/linux.py

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from .base import DSIBase
66
from .window import WindowBase
77
from .image import Image
8+
from .buttons import MouseButtons
9+
from .box import Box
810

911
# built-in modules
1012
import logging
@@ -290,19 +292,6 @@ class KeyMasks(object):
290292
Mod5Mask = 128
291293

292294

293-
class ButtonCodes(object):
294-
"""
295-
https://tronche.com/gui/x/xlib/events/keyboard-pointer/keyboard-pointer.html\n
296-
/usr/include/X11/X.h: 259-263
297-
"""
298-
AnyButton = 0
299-
Button1 = 1
300-
Button2 = 2
301-
Button3 = 3
302-
Button4 = 4
303-
Button5 = 5
304-
305-
306295
class Xlib(object):
307296
def __init__(self):
308297
# load libX11.so.6
@@ -454,34 +443,39 @@ def active(self) -> bool:
454443
return self.xid == get_active_window_xid(self.xlib)
455444

456445
@property
457-
def geometry(self) -> tuple:
446+
def geometry(self) -> Box:
458447
gwa = XWindowAttributes()
459448
self.xlib.XGetWindowAttributes(self.xlib.display, self.xid, byref(gwa))
460-
return (gwa.x, gwa.y, gwa.width, gwa.height)
449+
return Box(
450+
x=gwa.x,
451+
y=gwa.y,
452+
width=gwa.width,
453+
height=gwa.height
454+
)
461455

462-
def get_image(self, geometry: tuple = None) -> Image:
456+
def get_image(self, geometry: Box = None) -> Image:
463457
if geometry is None:
464458
geometry = self.geometry
465459

466460
ximage = self.xlib.XGetImage(
467461
self.xlib.display, # Display
468462
self.xid, # Drawable (Window XID)
469-
geometry[0], # x
470-
geometry[1], # y
471-
geometry[2], # width
472-
geometry[3], # height
463+
geometry.x, # x
464+
geometry.y, # y
465+
geometry.width, # width
466+
geometry.height, # height
473467
0x00FFFFFF, # plane_mask
474468
2 # format = ZPixmap
475469
)
476470

477471
raw_data = ctypes.cast(
478472
ximage.contents.data,
479-
POINTER(c_ubyte * geometry[3] * geometry[2] * 4)
473+
POINTER(c_ubyte * geometry.height * geometry.width * 4)
480474
)
481475

482476
data = bytearray(raw_data.contents)
483477

484-
data = Image(data, geometry[2], geometry[3])
478+
data = Image(data, geometry.width, geometry.height)
485479

486480
# don't forget to free the memory or you will be fucked
487481
self.xlib.XDestroyImage(ximage)
@@ -524,7 +518,7 @@ def send_str(self, str: str) -> None:
524518
for chr in str:
525519
self.send_chr(chr)
526520

527-
def warp_pointer(self, x: int, y: int, geometry: tuple = None) -> None:
521+
def warp_pointer(self, x: int, y: int, geometry: Box = None) -> None:
528522
if geometry is None:
529523
geometry = self.geometry
530524

@@ -533,18 +527,18 @@ def warp_pointer(self, x: int, y: int, geometry: tuple = None) -> None:
533527
self.xlib.display,
534528
self.xid, # src_w
535529
self.xid, # dest_w
536-
geometry[0],
537-
geometry[1],
538-
geometry[2],
539-
geometry[3],
530+
geometry.x,
531+
geometry.y,
532+
geometry.width,
533+
geometry.height,
540534
x,
541535
y
542536
)
543537

544538
# flush display or events will run delayed cus thai'r only called on the next update
545539
self.xlib.XFlush(self.xlib.display)
546540

547-
def send_mouse_click(self, x: int, y: int, button: ButtonCodes = ButtonCodes.Button1) -> None:
541+
def send_mouse_click(self, x: int, y: int, button: MouseButtons = MouseButtons.LEFT) -> None:
548542
"""
549543
Send a mouse click to the window at the given coordinates without moving the pointer.
550544
Some applications may not respond to the click so it is recommended to also move the pointer with `warp_pointer`.

display_server_interactions/window.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
from abc import ABCMeta, abstractmethod
55
from .image import Image
6+
from .buttons import MouseButtons
7+
from .box import Box
68

79

810
class WindowBase(object, metaclass=ABCMeta):
@@ -32,14 +34,14 @@ def active(self) -> bool:
3234

3335
@property
3436
@abstractmethod
35-
def geometry(self) -> tuple:
37+
def geometry(self) -> Box:
3638
"""
3739
Returns: tuple: (x, y, width, height)
3840
"""
3941
pass
4042

4143
@abstractmethod
42-
def get_image(self, geometry: tuple = None) -> Image:
44+
def get_image(self, geometry: Box = None) -> Image:
4345
"""
4446
Returns an Image of the window.
4547
With the geometry parameter you can specify a sub-region of the window that will be captured.
@@ -58,13 +60,13 @@ def send_str(self, str: str) -> None:
5860
"""
5961

6062
@abstractmethod
61-
def warp_pointer(self, x: int, y: int, geometry: tuple = None) -> None:
63+
def warp_pointer(self, x: int, y: int, geometry: Box = None) -> None:
6264
"""
6365
Moves the pointer relative to the window to the given coordinates.
6466
"""
6567

6668
@abstractmethod
67-
def send_mouse_click(self, x: int, y: int, button) -> None:
69+
def send_mouse_click(self, x: int, y: int, button: MouseButtons = MouseButtons.LEFT) -> None:
6870
"""
6971
Send a mouse click to the window at the given coordinates.
7072
On some windows/applications you need to move the pointer with warp_pointer() first.
@@ -73,5 +75,5 @@ def send_mouse_click(self, x: int, y: int, button) -> None:
7375
def __repr__(self) -> str:
7476
name = self.name
7577
if name:
76-
name = f'"{name}"'
77-
return f'Window(name={name}, pid={self.pid}, active={self.active}, geometry={self.geometry})'
78+
return f'Window(name="{self.name}", pid={self.pid}, active={self.active}, geometry={self.geometry})'
79+
return f'Window(pid={self.pid}, active={self.active}, geometry={self.geometry})'

0 commit comments

Comments
 (0)