bouncy/main.py

153 lines
4.1 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
import math
user32 = windll.user32
gdi32 = windll.gdi32
user32.GetSystemMetrics.argtypes = [INT]
user32.GetCursorPos.argtypes = [LPPOINT]
user32.SetCursorPos.argtypes = [INT, 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]
class BouncyObject(ABC):
@abstractmethod
def get_position(self) -> tuple[int, int]:
raise NotImplementedError
@abstractmethod
def set_position(self, xy: tuple[int, int]):
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]):
rect = RECT()
user32.GetWindowRect(self._handle, byref(rect))
width = rect.right - rect.left
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
)
def enumerate_windows() -> list[int]:
windows: list[int] = []
@CB_ENUM_WINDOWS
def _handle_window(hwnd, lparam):
if user32.IsWindowVisible(hwnd):
windows.append(hwnd)
return True
user32.EnumWindows(_handle_window, 0)
return windows
def get_resolution_y() -> int:
SM_CYSCREEN = 1
return user32.GetSystemMetrics(SM_CYSCREEN)
class Body(object):
def __init__(self, controlled_object: BouncyObject):
self._last_xy: tuple[int, int] | None =None
self._last_offset: tuple[float, float] = (0.5, 0.5)
self._velocity = 0.0
self._controlled_object = controlled_object
def update(self, res_y):
x, y = self._controlled_object.get_position()
ox, oy = self._last_offset
x += ox
y += oy
if self._last_xy:
last_x, last_y = self._last_xy
dx = x - last_x
dy = y - last_y
dist = math.sqrt(dx * dx + dy * dy)
# if they move the mouse they can fight it
# (but they have to move it a lot)
self._velocity *= 1.0 - min(dist / 128, 1.0)
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
self._controlled_object.set_position((int(x), int(y)))
self._last_xy = (int(x), int(y))
self._last_offset = (x % 1, y % 1)
class Bodies(object):
def __init__(self):
self._mouse = Body(Mouse())
self._windows = {}
def update_list(self):
windows2 = {}
for i in enumerate_windows():
if existing := self._windows.get(i):
windows2[i] = existing
else:
windows2[i] = Body(Window(i))
self._windows = windows2
def get(self) -> list[Body]:
return [self._mouse, *self._windows.values()]
def main():
bodies = Bodies()
while True:
res_y = get_resolution_y()
bodies.update_list()
print(len(bodies.get()));
for b in bodies.get():
b.update(res_y)
time.sleep(1/120.0)
if __name__ == "__main__":
main()