Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hand cursor #14

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 74 additions & 66 deletions autoscroll.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from functools import partial
from queue import Queue
#!/home/kusti420/Downloads/git/linux-autoscroll/.autoscroll/bin/python3

from pynput.mouse import Button, Controller, Listener
from threading import Event, Thread
from time import sleep
Expand All @@ -8,14 +8,62 @@
from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtGui import QPixmap
from pathlib import Path
import subprocess
import sys
import pyXCursor
import tkinter as tk
from tkinter import ttk
import os
import hashlib

CURSOR_REFRENCE_IMAGE = "hand_cursor_refrence.png"
os.chdir(os.path.dirname(os.path.realpath(__file__)))


class CursorInfo():
def __init__(self) -> None:
self.cursor_refrence_image_exists = CURSOR_REFRENCE_IMAGE in os.listdir(os.path.dirname(os.path.realpath(__file__)))
self.cursor = pyXCursor.Xcursor()
self.imgarray = self.cursor.getCursorImageArrayFast()
self.root = None
self.sense()

def sense(self) -> None:
self.imgarray = self.cursor.getCursorImageArrayFast()
if not self.cursor_refrence_image_exists:
self.root = tk.Tk()
ttk.Button(self.root, text = "Click here to calibrate.\nKeep your cursor still on this button.",\
width = 40, command = self.save_cursor_image, cursor = "hand1").pack(padx = 50, pady = 50)
self.root.mainloop()

def save_cursor_image(self, filename = CURSOR_REFRENCE_IMAGE) -> None:
if not self.cursor_refrence_image_exists and filename == CURSOR_REFRENCE_IMAGE or filename != CURSOR_REFRENCE_IMAGE:
# print(f"saving '{filename}' to '{os.getcwd()}'")
self.cursor.saveImage(self.imgarray, filename)
# print("saved")
if filename == CURSOR_REFRENCE_IMAGE:
self.cursor_refrence_image_exists = True
self.root.destroy()

def is_hand_cursor(self) -> bool:
with open("hand_cursor_refrence.png", "rb") as f:
cursor_image_hash = hashlib.md5(f.read()).hexdigest()
f.close()
with open("cursor_image.png", "rb") as f:
cursor_image_hash_2 = hashlib.md5(f.read()).hexdigest()
f.close()
if cursor_image_hash != cursor_image_hash_2:
print("cursor image hash mismatch")
return False
else:
print("cursor image hash match")
return True

cursorInfo = CursorInfo()

class AutoscrollIconSvg(QSvgWidget):
scroll_mode_entered = pyqtSignal()
scroll_mode_exited = pyqtSignal()

def __init__(self, path, size):
super().__init__(path)
self.size = size
Expand All @@ -25,18 +73,17 @@ def __init__(self, path, size):
self.setAttribute(Qt.WA_TranslucentBackground)
self.scroll_mode_entered.connect(self.show)
self.scroll_mode_exited.connect(self.close)

def show(self):
x = self.pos[0] - self.size // 2
y = self.pos[1] - self.size // 2
self.move(x, y)
super().show()


class AutoscrollIconRaster(QLabel):
scroll_mode_entered = pyqtSignal()
scroll_mode_exited = pyqtSignal()

def __init__(self, path, size):
super().__init__()
self.size = size
Expand All @@ -47,15 +94,14 @@ def __init__(self, path, size):
self.setAttribute(Qt.WA_TranslucentBackground)
self.scroll_mode_entered.connect(self.show)
self.scroll_mode_exited.connect(self.close)

def show(self):
x = self.pos[0] - self.size // 2
y = self.pos[1] - self.size // 2
self.move(x, y)
super().show()


class Autoscroll:
class Autoscroll():
def __init__(self):
# modify this to adjust the speed of scrolling
self.DELAY = 5
Expand All @@ -65,34 +111,25 @@ def __init__(self):
self.BUTTON_STOP = Button.middle
# modify this to change the size (in px) of the area below and above the starting point where scrolling is paused
self.DEAD_AREA = 30
# modify this to change the time you have to hold BUTTON_START for in order to enter the scroll mode
self.TRIGGER_DELAY = 0
# set this to True if you want the clipboard to be cleared before entering the scroll mode
# applicable only if you are using Button.middle for BUTTON_START or BUTTON_STOP
# requires xsel
self.CLEAR_CLIPBOARD = False
# set this to True if you want to autoscroll only while BUTTON_START is held down
self.HOLD_MODE = False
# modify this to change the scroll mode icon
# supported formats: svg, png, jpg, jpeg, gif, bmp, pbm, pgm, ppm, xbm, xpm
# the path MUST be absolute
self.ICON_PATH = str(Path(__file__).parent.resolve()) + "/icon.svg"
# modify this to change the size (in px) of the icon
# note that only svg images can be resized without loss of quality
self.ICON_SIZE = 30

if self.ICON_PATH[-4:] == ".svg":
self.icon = AutoscrollIconSvg(self.ICON_PATH, self.ICON_SIZE)
else:
self.icon = AutoscrollIconRaster(self.ICON_PATH, self.ICON_SIZE)

self.mouse = Controller()
self.scroll_mode = Event()
self.queue = Queue()
self.cancelled = Event()
self.listener = Listener(on_move=self.on_move, on_click=self.on_click)
self.listener.start()
self.looper = Thread(target=self.loop)
self.consumer = Thread(target=self.consume)
self.looper.start()

def on_move(self, x, y):
if self.scroll_mode.is_set():
Expand All @@ -109,55 +146,26 @@ def on_move(self, x, y):
self.interval = self.DELAY / (abs(delta) - self.DEAD_AREA)

def on_click(self, x, y, button, pressed):
if button == self.BUTTON_START and pressed and not self.scroll_mode.is_set():
self.queue.put(partial(self.enter_scroll_mode, x, y))
self.started = True
elif button == self.BUTTON_START and not pressed and self.started:
self.cancelled.set()
self.started = False
if self.HOLD_MODE:
self.queue.put(partial(self.exit_scroll_mode))
elif button == self.BUTTON_STOP and not pressed and self.scroll_mode.is_set():
self.queue.put(partial(self.exit_scroll_mode))

def enter_scroll_mode(self, x, y):
if self.CLEAR_CLIPBOARD:
subprocess.run(["xsel", "-c"])
self.icon.pos = (x, y)
self.direction = 0
self.interval = 0.5
self.scroll_mode.set()
self.icon.scroll_mode_entered.emit()

def exit_scroll_mode(self):
self.scroll_mode.clear()
self.icon.scroll_mode_exited.emit()

def consume(self):
while True:
f = self.queue.get(block=True)
if f.func == self.enter_scroll_mode:
self.cancelled.clear()
self.cancelled.wait(self.TRIGGER_DELAY)
if not self.cancelled.is_set():
f()
elif f.func == self.exit_scroll_mode:
f()

cursorInfo.sense()
cursorInfo.save_cursor_image("cursor_image.png")
isHand = cursorInfo.is_hand_cursor()
if button == self.BUTTON_START and pressed and not self.scroll_mode.is_set() and not isHand:
self.icon.pos = (x, y)
self.direction = 0
self.interval = 0.5
self.scroll_mode.set()
self.icon.scroll_mode_entered.emit()
elif button == self.BUTTON_STOP and pressed and self.scroll_mode.is_set():
self.scroll_mode.clear()
self.icon.scroll_mode_exited.emit()

def loop(self):
while True:
self.scroll_mode.wait()
sleep(self.interval)
self.mouse.scroll(0, self.direction)

def start(self):
self.consumer.start()
self.listener.start()
self.looper.start()


app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
autoscroll = Autoscroll()
autoscroll.start()
sys.exit(app.exec())
sys.exit(app.exec())
54 changes: 53 additions & 1 deletion autoscroll_no_icon.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,56 @@
from functools import partial
from queue import Queue
import subprocess
import pyXCursor
import tkinter as tk
from tkinter import ttk
import os
import hashlib

CURSOR_REFRENCE_IMAGE = "hand_cursor_refrence.png"
os.chdir(os.path.dirname(os.path.realpath(__file__)))


class CursorInfo():
def __init__(self) -> None:
self.cursor_refrence_image_exists = CURSOR_REFRENCE_IMAGE in os.listdir(os.path.dirname(os.path.realpath(__file__)))
self.cursor = pyXCursor.Xcursor()
self.imgarray = self.cursor.getCursorImageArrayFast()
self.root = None
self.sense()

def sense(self) -> None:
self.imgarray = self.cursor.getCursorImageArrayFast()
if not self.cursor_refrence_image_exists:
self.root = tk.Tk()
ttk.Button(self.root, text = "Click here to calibrate.\nKeep your cursor still on this button.",\
width = 40, command = self.save_cursor_image, cursor = "hand1").pack(padx = 50, pady = 50)
self.root.mainloop()

def save_cursor_image(self, filename = CURSOR_REFRENCE_IMAGE) -> None:
if not self.cursor_refrence_image_exists and filename == CURSOR_REFRENCE_IMAGE or filename != CURSOR_REFRENCE_IMAGE:
# print(f"saving '{filename}' to '{os.getcwd()}'")
self.cursor.saveImage(self.imgarray, filename)
# print("saved")
if filename == CURSOR_REFRENCE_IMAGE:
self.cursor_refrence_image_exists = True
self.root.destroy()

def is_hand_cursor(self) -> bool:
with open("hand_cursor_refrence.png", "rb") as f:
cursor_image_hash = hashlib.md5(f.read()).hexdigest()
f.close()
with open("cursor_image.png", "rb") as f:
cursor_image_hash_2 = hashlib.md5(f.read()).hexdigest()
f.close()
if cursor_image_hash != cursor_image_hash_2:
print("cursor image hash mismatch")
return False
else:
print("cursor image hash match")
return True

cursorInfo = CursorInfo()

class Autoscroll:
def __init__(self):
Expand Down Expand Up @@ -47,7 +96,10 @@ def on_move(self, x, y):
self.interval = self.DELAY / (abs(delta) - self.DEAD_AREA)

def on_click(self, x, y, button, pressed):
if button == self.BUTTON_START and pressed and not self.scroll_mode.is_set():
cursorInfo.sense()
cursorInfo.save_cursor_image("cursor_image.png")
isHand = cursorInfo.is_hand_cursor()
if button == self.BUTTON_START and pressed and not self.scroll_mode.is_set() and not isHand:
self.queue.put(partial(self.enter_scroll_mode, x, y))
self.started = True
elif button == self.BUTTON_START and not pressed and self.started:
Expand Down
Loading