Skip to content

Commit

Permalink
add mouse_xy macro
Browse files Browse the repository at this point in the history
  • Loading branch information
sezanzeb committed Jan 1, 2025
1 parent b4852f1 commit 98b11fd
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 36 deletions.
9 changes: 7 additions & 2 deletions inputremapper/configs/preset.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"""Contains and manages mappings."""

from __future__ import annotations
from evdev import ecodes

import json
import os
Expand All @@ -36,6 +35,8 @@
overload,
)

from evdev import ecodes

try:
from pydantic.v1 import ValidationError
except ImportError:
Expand Down Expand Up @@ -291,7 +292,11 @@ def _get_mappings_from_disc(self) -> Dict[InputCombination, MappingModel]:

for mapping_dict in preset_list:
if not isinstance(mapping_dict, dict):
logger.error("Expected mapping to be a dict: %s", mapping_dict)
logger.error(
"Expected mapping to be a dict: %s %s",
type(mapping_dict),
mapping_dict,
)
continue

try:
Expand Down
2 changes: 2 additions & 0 deletions inputremapper/injection/macros/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
from inputremapper.injection.macros.tasks.mod_tap import ModTapTask
from inputremapper.injection.macros.tasks.modify import ModifyTask
from inputremapper.injection.macros.tasks.mouse import MouseTask
from inputremapper.injection.macros.tasks.mouse_xy import MouseXYTask
from inputremapper.injection.macros.tasks.repeat import RepeatTask
from inputremapper.injection.macros.tasks.set import SetTask
from inputremapper.injection.macros.tasks.wait import WaitTask
Expand All @@ -67,6 +68,7 @@ class Parser:
"hold": HoldTask,
"hold_keys": HoldKeysTask,
"mouse": MouseTask,
"mouse_xy": MouseXYTask,
"wheel": WheelTask,
"if_eq": IfEqTask,
"if_numlock": IfNumlockTask,
Expand Down
50 changes: 17 additions & 33 deletions inputremapper/injection/macros/tasks/mouse.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,14 @@

from __future__ import annotations

import asyncio

from evdev._ecodes import REL_Y, REL_X
from evdev.ecodes import EV_REL

from inputremapper.injection.macros.argument import ArgumentConfig
from inputremapper.injection.macros.task import Task
from inputremapper.injection.macros.macro import InjectEventCallback
from inputremapper.injection.macros.tasks.mouse_xy import MouseXYTask


class MouseTask(Task):
class MouseTask(MouseXYTask):
"""Move the mouse cursor."""

argument_configs = [
Expand All @@ -46,40 +44,26 @@ class MouseTask(Task):
ArgumentConfig(
name="acceleration",
position=2,
types=[int, float, None],
default=None,
types=[int, float],
default=1,
),
]

async def run(self, callback) -> None:
async def run(self, callback: InjectEventCallback) -> None:
direction = self.get_argument("direction").get_value()
speed = self.get_argument("speed").get_value()
acceleration = self.get_argument("acceleration").get_value()

code, value = {
"up": (REL_Y, -1),
"down": (REL_Y, 1),
"left": (REL_X, -1),
"right": (REL_X, 1),
code = {
"up": REL_Y,
"down": REL_Y,
"left": REL_X,
"right": REL_X,
}[direction.lower()]

current_speed = 0.0
displacement_accumulator = 0.0
displacement = 0
if not acceleration:
displacement = speed

while self.is_holding():
# Cursors can only move by integers. To get smooth acceleration for
# small acceleration values, the cursor needs to move by a pixel every
# few iterations. This can be achieved by remembering the decimal
# places that were cast away, and using them for the next iteration.
if acceleration and current_speed < speed:
current_speed += acceleration
current_speed = min(current_speed, speed)
displacement_accumulator += current_speed
displacement = int(displacement_accumulator)
displacement_accumulator -= displacement

callback(EV_REL, code, value * displacement)
await asyncio.sleep(1 / self.mapping.rel_rate)
await self.axis(
code,
speed,
acceleration,
callback,
)
93 changes: 93 additions & 0 deletions inputremapper/injection/macros/tasks/mouse_xy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# input-remapper - GUI for device specific keyboard mappings
# Copyright (C) 2024 sezanzeb <[email protected]>
#
# This file is part of input-remapper.
#
# input-remapper is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# input-remapper is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with input-remapper. If not, see <https://www.gnu.org/licenses/>.

from __future__ import annotations

import asyncio
from typing import Union

from evdev._ecodes import REL_Y, REL_X
from evdev.ecodes import EV_REL

from inputremapper.injection.macros.argument import ArgumentConfig
from inputremapper.injection.macros.macro import InjectEventCallback
from inputremapper.injection.macros.task import Task


class MouseXYTask(Task):
"""Move the mouse cursor."""

argument_configs = [
ArgumentConfig(
name="x",
position=0,
types=[int, float],
),
ArgumentConfig(
name="y",
position=1,
types=[int, float],
),
ArgumentConfig(
name="acceleration",
position=2,
types=[int, float],
default=1,
),
]

async def run(self, callback: InjectEventCallback) -> None:
x = self.get_argument("x").get_value()
y = self.get_argument("y").get_value()
acceleration = self.get_argument("acceleration").get_value()
await asyncio.gather(
self.axis(REL_X, x, acceleration, callback),
self.axis(REL_Y, y, acceleration, callback),
)

async def axis(
self,
code: int,
speed: Union[int, float],
fractional_acceleration: Union[int, float],
callback: InjectEventCallback,
) -> None:
acceleration = speed * fractional_acceleration
direction = -1 if speed < 0 else 1
current_speed = 0.0
displacement_accumulator = 0.0
displacement = 0
if not acceleration:
displacement = speed

while self.is_holding():
# Cursors can only move by integers. To get smooth acceleration for
# small acceleration values, the cursor needs to move by a pixel every
# few iterations. This can be achieved by remembering the decimal
# places that were cast away, and using them for the next iteration.
if acceleration and abs(current_speed) < abs(speed):
current_speed += acceleration
current_speed = direction * min(abs(current_speed), abs(speed))
displacement_accumulator += current_speed
displacement = int(displacement_accumulator)
displacement_accumulator -= displacement

callback(EV_REL, code, displacement)
await asyncio.sleep(1 / self.mapping.rel_rate)
21 changes: 20 additions & 1 deletion readme/macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ Bear in mind that anti-cheat software might detect macros in games.
> Moves the mouse cursor
>
> If `acceleration` is provided then the cursor will accelerate from zero to a maximum speed of `speed`.
> If `acceleration` is provided then the cursor will accelerate from zero to a maximum
> speed of `speed`.
>
> ```ts
> mouse(direction: str, speed: int, acceleration: float | None)
Expand All @@ -165,6 +166,24 @@ Bear in mind that anti-cheat software might detect macros in games.
> mouse(down, 10, 0.3)
> ```
### mouse_xy
> Moves the mouse cursor in both x and y direction.
>
> If `acceleration` is provided then the cursor will accelerate from zero to the
> maximum specified x and y speeds.
>
> ```ts
> mouse(x: int | float, y: int | float, acceleration: float | None)
> ```
>
> Examples:
>
> ```ts
> mouse_xy(x=10, y=20)
> mouse_xy(-5, -1, 0.01)
> ```
### wheel
> Injects scroll wheel events
Expand Down

0 comments on commit 98b11fd

Please sign in to comment.