bouncy/python/main.py

141 lines
3.7 KiB
Python

from abc import ABC, abstractmethod
from ctypes import WINFUNCTYPE, byref, windll
from ctypes.wintypes import BOOL, HWND, INT, LPARAM, LPPOINT, LPRECT, POINT, RECT
import time
user32 = windll.user32
user32.GetCursorPos.argtypes = [LPPOINT]
user32.SetCursorPos.argtypes = [INT, INT]
user32.GetSystemMetrics.argtypes = [INT]
CB_ENUM_WINDOWS = WINFUNCTYPE(BOOL, HWND, LPARAM)
user32.EnumWindows.argtypes = [CB_ENUM_WINDOWS, LPARAM]
user32.GetWindowRect.argtypes = [HWND, LPRECT]
user32.SetWindowPos.argtypes = [HWND, INT, INT, INT, INT, INT, INT]
user32.IsWindowVisible.argtypes = [HWND]
def get_resolution_y() -> int:
SM_CYSCREEN = 1
return user32.GetSystemMetrics(SM_CYSCREEN)
class BouncyObject(ABC):
@abstractmethod
def get_position(self) -> tuple[int, int]:
raise NotImplementedError
@abstractmethod
def set_position(self, xy: tuple[int, int]) -> None:
raise NotImplementedError
class Mouse(BouncyObject):
def get_position(self) -> tuple[int, int]:
point = POINT()
user32.GetCursorPos(byref(point))
return point.x, point.y
def set_position(self, xy: tuple[int, int]):
x, y = xy
user32.SetCursorPos(x, y)
class Window(BouncyObject):
def __init__(self, handle: int):
self._handle = handle
def get_position(self) -> tuple[int, int]:
rect = RECT()
user32.GetWindowRect(self._handle, byref(rect))
return (rect.left, rect.bottom)
def set_position(self, xy: tuple[int, int]) -> None:
rect = RECT()
user32.GetWindowRect(self._handle, byref(rect))
height = rect.bottom - rect.top
x, y = xy
user32.SetWindowPos(
self._handle, 0, x, y - height, -1, -1,
0x0010 | # SWP_NOACTIVATE
0x0001 | # SWP_NOSIZE
0x0004 | # SWP_NOZORDER
0x4000 # SWP_ASYNCWINDOWPOS
)
class Body(object):
def __init__(self, controlled_object: BouncyObject):
self._last_offset: tuple[float, float] = (0.5, 0.5)
self._velocity = 0.0
self._controlled_object = controlled_object
def update(self, res_y: int) -> None:
x, y = self._controlled_object.get_position()
ox, oy = self._last_offset
x += ox
y += oy
for _ in range(100):
self._velocity += 0.0002
y += self._velocity
if y > res_y and self._velocity > 0:
self._velocity = -self._velocity * 0.75
if abs(self._velocity) < 0.05:
self._velocity = 0
x_int, y_int = int(x), min(int(y), res_y)
self._controlled_object.set_position((x_int, y_int))
self._last_offset = x - x_int, y - y_int
def enumerate_windows() -> list[int]:
windows: list[int] = []
@CB_ENUM_WINDOWS
def _handle_window(hwnd: int, _lparam: int):
if user32.IsWindowVisible(hwnd):
windows.append(hwnd)
return True
user32.EnumWindows(_handle_window, 0)
return windows
class Bodies(object):
def __init__(self):
self._mouse = Body(Mouse())
self._windows = {}
def enumerate(self):
windows_2 = {}
for i in enumerate_windows():
if existing := self._windows.get(i):
windows_2[i] = existing
else:
windows_2[i] = Body(Window(i))
self._windows = windows_2
def get(self) -> list[Body]:
return [self._mouse, *self._windows.values()]
def main():
bodies = Bodies()
while True:
res_y = get_resolution_y()
bodies.enumerate()
for b in bodies.get():
b.update(res_y)
time.sleep(1.0/120.0)
if __name__ == "__main__":
main()