Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
akademi4eg committed Mar 18, 2019
1 parent 18de671 commit a8e376d
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 1 deletion.
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,34 @@
# miceless
# MiceLess
Utitlity that helps binding keyboard shortcuts for some of the operations that you do with mouse.

# Usecase
Primary usecase that this tool was written for is switching focus between windows on different monitors.
In some cases, e.g. multiple desktops, simple `Alt-TAB` wont help,
because it would either switch you to latest used app or would require you to press this combo several times,
until you reach target window. With this tool you can configure shortcuts so that, for example, `Ctrl-Alt-1` would
set window in left monitor in focus and `Ctrl+Alt+2` would set focus for window in right monitor.

# Manual
MiceLess stores a mapping between key-combos and sequences of mouse clicks.
To run the app, execute `run.py` with `python3` interpreter.

App config would be stored in home folder in `.miceless` file.

The app should work anywhere where `pynput` works, yet it has been tested only on Ubuntu with X server.

## Modes
The tool has two operation modes: **recording** and **playback**. You can switch between modes by pressing `Ctrl+Alt+~`.

## Recording
While in **recording** mode, `Ctrl-Alt-<key>` combo would **append** click in current mouse location to the list
of events for a given combo. Pressing special combination `Ctrl+Alt+0` would clear events list for last used key combo.

## Playback
In playback mode pressing `Ctrl+Alt+<key>` combo would invoke a sequence of events stored for that combination.

# Known issues
* Playback clicks are executed with `Ctrl+Alt` pressed, because all combos have these keys in them.
This might be a problem for some applications.
* There might be collisions with app-specific hotkeys that would result in not desired firings of event sequences.
* Events capture is based on `pynput` which in turn uses `Xlib`, thus there might be issues in non-X environments,
e.g. in Wayland.
Empty file added miceless/__init__.py
Empty file.
16 changes: 16 additions & 0 deletions miceless/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/python3
import logging
import os
from pynput import keyboard

from miceless.state import StateTracker


if __name__ == "__main__":
logging.basicConfig(level=logging.INFO, handlers=[logging.StreamHandler()])
state = StateTracker(os.path.join(os.getenv('HOME'), '.miceless'))

with keyboard.Listener(
on_press=state.pressed,
on_release=state.released) as listener:
listener.join()
82 changes: 82 additions & 0 deletions miceless/state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import json
import logging
from pynput import keyboard, mouse
from time import sleep


class StateTracker:
def __init__(self, config_path):
self._config_path = config_path
self._logger = logging.getLogger(__name__)
self._is_r_alt = False
self._is_r_ctrl = False
self._is_rec_mode = False
self._mouse = mouse.Controller()
self._memory = {}
self.load()
self._last_key = None
for k, v in self._memory.items():
self._logger.info(f'Loaded bind {k} = {v}')
self._logger.info(f'Initialized in {"REC" if self._is_rec_mode else "PLAY"}')

def check_modifiers(self):
return self._is_r_alt and self._is_r_ctrl

def dump(self):
with open(self._config_path, 'w') as f:
json.dump(self._memory, f)

def load(self):
try:
with open(self._config_path, 'r') as f:
self._memory = json.load(f)
self._logger.info(f'Loaded {self._config_path}')
except FileNotFoundError:
self._logger.info('Nothing to load')

@staticmethod
def is_key_allowed(key):
return len(str(key)) == 3

def pressed(self, key):
if key == keyboard.Key.alt_r:
self.set_r_alt(True)
elif key == keyboard.Key.ctrl_r:
self.set_r_ctrl(True)

def released(self, key):
if key == keyboard.Key.alt_r:
self.set_r_alt(False)
elif key == keyboard.Key.ctrl_r:
self.set_r_ctrl(False)
if self.check_modifiers() and self.is_key_allowed(key):
if key == keyboard.KeyCode.from_char(char='`'):
# Toggle mode.
self._is_rec_mode = not self._is_rec_mode
self._logger.info(f'Mode set to {"REC" if self._is_rec_mode else "PLAY"}')
elif self._is_rec_mode:
if key == keyboard.KeyCode.from_char(char='0'):
if self._last_key.char in self._memory:
del self._memory[self._last_key.char]
self.dump()
self._logger.info(f'Cleared key {self._last_key}')
else:
if key.char not in self._memory:
self._memory[key.char] = []
self._memory[key.char].append(self._mouse.position)
self.dump()
self._logger.info(f'Recorded position {self._memory[key.char]} for key {key}')
elif key.char in self._memory:
self._logger.info(f'Executing {len(self._memory)} actions for key {key}')
for pos in self._memory[key.char]:
self._mouse.position = pos
self._mouse.click(mouse.Button.left)
self._logger.info(f'Clicked: {pos}')
sleep(0.2)
self._last_key = key

def set_r_alt(self, state):
self._is_r_alt = state

def set_r_ctrl(self, state):
self._is_r_ctrl = state
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pynput==1.4
30 changes: 30 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import setuptools


with open('README.md', 'r') as f:
long_description = f.read()


with open('requirements.txt', 'r') as f:
requirements = f.readlines()


setuptools.setup(
name='miceless',
version='1.0.0',
maintainer='Dmytro Tkanov',
maintainer_email='[email protected]',
license='Apache License 2.0',
description='Hotkeys for mouse click events.',
long_description=long_description,
long_description_content_type="text/markdown",
url='https://github.com/akademi4eg/miceless',
packages=setuptools.find_packages(),
install_requires=requirements,
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: Apache Software License",
"Operating System :: POSIX :: Linux",
"Topic :: Multimedia :: Sound/Audio",
],
)

0 comments on commit a8e376d

Please sign in to comment.