def default_click(click_info: Click): """Perform ``click_function`` according to the context.""" with FrozenEyeMouse(): # Backdating clicks is a good default for arbitrary pointing # devices. actions.mouse_move(*click_info.position_at) click_info.function(click_info.modifiers) # Sleep for enough time for the click to complete. time.sleep(0.1)
def end_zoom() -> Point2d: """Terminate the zoom. Mouse will be moved to the user's gaze position. :returns: the final position """ # TODO: Will this be reactive enough, or should we make this accessible # anywhere in the zoom mouse? _, origin = eye_zoom_mouse.zoom_mouse.get_pos() if origin: eye_zoom_mouse.zoom_mouse.cancel() actions.mouse_move(origin.x, origin.y) return origin
def spline_mouse(x: int, y: int, seconds: float = 1.0) -> None: """Move mouse gradually to point `(x, y)`.""" FRAME_PAUSE = 0.016 # Use a defined number of moves (not time) so movement is smooth. n_steps = min(int(seconds // FRAME_PAUSE), 1) start_x = actions.mouse_x() start_y = actions.mouse_y() delta_x = (x - start_x) / n_steps delta_y = (y - start_y) / n_steps for i in range(n_steps): actions.mouse_move(start_x + (i * delta_x), start_y + (i * delta_y)) time.sleep(FRAME_PAUSE) actions.mouse_move()
def edge_mouse_scroll(north, south, east, west): """Map scroller that moves the mouse to the edge of the screen.""" screen = eye_mouse.main_screen.rect if east: x = 0 elif west: x = screen.width else: x = round(screen.width / 2) if north: y = 0 elif south: y = screen.height else: y = round(screen.height / 2) # TODO: Maybe figure out a way of not hammering this actions.mouse_move(x, y)
def telector_select(anchor1: str, anchor2: str): """ Selects the text indicated by the given anchors """ global labels_ui rect1 = labels_ui.find_rect(anchor1) rect2 = labels_ui.find_rect(anchor2) if rect1 is None or rect2 is None: # Couldn't find them, quit return init_mouse_x = actions.mouse_x() init_mouse_y = actions.mouse_y() actions.mouse_move( rect1.x, rect1.y + rect1.height / 2, ) actions.mouse_drag(0) time.sleep(0.1) actions.mouse_move( # The bounding box looks fine in a screenshot but +2 helps to get # the whole word with my terminal and seems to work OK elsewhere. # Guess it's up to the application how it responds. rect2.x + rect2.width + 2, rect2.y + rect2.height / 2) time.sleep(0.1) actions.mouse_release(0) actions.mouse_move(init_mouse_x, init_mouse_y)
def telector_click(anchor: str, button: int = 0): """ Clicks the given anchor """ global labels_ui rect = labels_ui.find_rect(anchor) if rect is None: # Couldn't find them, quit return init_mouse_x = actions.mouse_x() init_mouse_y = actions.mouse_y() actions.mouse_move( rect.x + rect.width / 2, rect.y + rect.height / 2, ) actions.mouse_click(button) time.sleep(0.1) actions.mouse_move(init_mouse_x, init_mouse_y)
def shake_mouse(seconds: float = 0.1, allowed_deviation: int = 5): """Briefly shake the cursor around its current position. Can be used to compensate for instantaneous mouse movement not being detected as a drag, e.g. when clicking + dragging Firefox tabs. """ FRAME_PAUSE = 0.016 # In secs PIXEL_RANGE = 5 # Use a defined number of moves (not time) so behaviour is predictable. n_moves = max(int(seconds // FRAME_PAUSE), 1) start_x = actions.mouse_x() # Technically a race condition, but never going to come up start_y = actions.mouse_y() for i in range(n_moves): # NOTE: This will move in a square pattern, not a circle. That's # probably fine. actions.mouse_move( start_x + randint(-PIXEL_RANGE, PIXEL_RANGE), start_y + randint(-PIXEL_RANGE, PIXEL_RANGE), ) time.sleep(FRAME_PAUSE) actions.mouse_move(start_x, start_y)
def do_action(position): """Perform the queued action at `position`.""" nonlocal function LOGGER.debug(f"Performing queued zoom function `{function}` at {position}") actions.mouse_move(position.x, position.y) function()
def corner_hover(position: Corner) -> None: """Move mouse to a specific position, relative to a corner.""" corner = Corner.absolute_position(position.corner) x = corner[0] + position.x y = corner[1] + position.y actions.mouse_move(x, y)
def eu4_hover_notification(number: int) -> None: """Move mouse over a specific notification.""" assert number >= 1 # TODO: Second row of notifications x = Buttons.FIRST_NOTIFICATION.x + NOTIFICATION_WIDTH * (number - 1) actions.mouse_move(x, Buttons.FIRST_NOTIFICATION.y)
def center_mouse() -> None: """Move the mouse to the center of the active window.""" rect = ui.active_window().rect center = (rect.x + round(rect.width / 2), rect.y + round(rect.height / 2)) actions.mouse_move(*center)