|
1 | 1 | from pynput.mouse import Button, Controller, Listener
|
2 |
| -from threading import Event |
| 2 | +from threading import Event, Thread |
3 | 3 | from time import sleep
|
| 4 | +from PyQt5.QtWidgets import QApplication, QLabel |
| 5 | +from PyQt5.QtCore import Qt, pyqtSignal |
| 6 | +from PyQt5.QtSvg import QSvgWidget |
| 7 | +from PyQt5.QtGui import QPixmap |
| 8 | +from pathlib import Path |
| 9 | +import sys |
4 | 10 |
|
5 |
| -def on_move(x, y): |
6 |
| - global pos, scroll_mode, direction, interval, DELAY, DEAD_AREA |
7 |
| - if scroll_mode.is_set(): |
8 |
| - delta = pos[1] - y |
9 |
| - if abs(delta) <= DEAD_AREA: |
10 |
| - direction = 0 |
11 |
| - elif delta > 0: |
12 |
| - direction = 1 |
13 |
| - elif delta < 0: |
14 |
| - direction = -1 |
15 |
| - if abs(delta) <= DEAD_AREA + DELAY * 2: |
16 |
| - interval = 0.5 |
17 |
| - else: |
18 |
| - interval = DELAY / (abs(delta) - DEAD_AREA) |
| 11 | +class AutoscrollIconSvg(QSvgWidget): |
| 12 | + scroll_mode_entered = pyqtSignal() |
| 13 | + scroll_mode_exited = pyqtSignal() |
| 14 | + |
| 15 | + def __init__(self, path, size): |
| 16 | + super().__init__(path) |
| 17 | + self.size = size |
| 18 | + self.renderer().setAspectRatioMode(Qt.KeepAspectRatio) |
| 19 | + self.resize(self.size, self.size) |
| 20 | + self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint | Qt.X11BypassWindowManagerHint) |
| 21 | + self.setAttribute(Qt.WA_TranslucentBackground) |
| 22 | + self.scroll_mode_entered.connect(self.show) |
| 23 | + self.scroll_mode_exited.connect(self.close) |
| 24 | + |
| 25 | + def show(self): |
| 26 | + x = self.pos[0] - self.size // 2 |
| 27 | + y = self.pos[1] - self.size // 2 |
| 28 | + self.move(x, y) |
| 29 | + super().show() |
19 | 30 |
|
20 |
| -def on_click(x, y, button, pressed): |
21 |
| - global pos, scroll_mode, direction, interval, BUTTON_START, BUTTON_STOP |
22 |
| - if button == BUTTON_START and pressed and not scroll_mode.is_set(): |
23 |
| - pos = (x, y) |
24 |
| - direction = 0 |
25 |
| - interval = 0 |
26 |
| - scroll_mode.set() |
27 |
| - elif button == BUTTON_STOP and pressed and scroll_mode.is_set(): |
28 |
| - scroll_mode.clear() |
| 31 | +class AutoscrollIconRaster(QLabel): |
| 32 | + scroll_mode_entered = pyqtSignal() |
| 33 | + scroll_mode_exited = pyqtSignal() |
| 34 | + |
| 35 | + def __init__(self, path, size): |
| 36 | + super().__init__() |
| 37 | + self.size = size |
| 38 | + self.resize(self.size, self.size) |
| 39 | + self.img = QPixmap(path).scaled(self.size, self.size, Qt.KeepAspectRatio) |
| 40 | + self.setPixmap(self.img) |
| 41 | + self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint | Qt.X11BypassWindowManagerHint) |
| 42 | + self.setAttribute(Qt.WA_TranslucentBackground) |
| 43 | + self.scroll_mode_entered.connect(self.show) |
| 44 | + self.scroll_mode_exited.connect(self.close) |
29 | 45 |
|
30 |
| -def autoscroll(): |
31 |
| - global mouse, scroll_mode, direction, interval |
32 |
| - while True: |
33 |
| - scroll_mode.wait() |
34 |
| - sleep(interval) |
35 |
| - mouse.scroll(0, direction) |
| 46 | + def show(self): |
| 47 | + x = self.pos[0] - self.size // 2 |
| 48 | + y = self.pos[1] - self.size // 2 |
| 49 | + self.move(x, y) |
| 50 | + super().show() |
36 | 51 |
|
37 |
| -mouse = Controller() |
38 |
| -listener = Listener(on_move = on_move, on_click = on_click) |
39 |
| -scroll_mode = Event() |
40 |
| -pos = mouse.position |
41 |
| -direction = 0 |
42 |
| -interval = 0 |
| 52 | +class Autoscroll(): |
| 53 | + def __init__(self): |
| 54 | + # modify this to adjust the speed of scrolling |
| 55 | + self.DELAY = 5 |
| 56 | + # modify this to change the button used for entering the scroll mode |
| 57 | + self.BUTTON_START = Button.middle |
| 58 | + # modify this to change the button used for exiting the scroll mode |
| 59 | + self.BUTTON_STOP = Button.middle |
| 60 | + # modify this to change the size (in px) of the area below and above the starting point where scrolling is paused |
| 61 | + self.DEAD_AREA = 30 |
| 62 | + # modify this to change the scroll mode icon |
| 63 | + # supported formats: svg, png, jpg, jpeg, gif, bmp, pbm, pgm, ppm, xbm, xpm |
| 64 | + # the path MUST be absolute |
| 65 | + self.ICON_PATH = str(Path(__file__).parent.resolve()) + "/icon.svg" |
| 66 | + # modify this to change the size (in px) of the icon |
| 67 | + # note that only svg images can be resized without loss of quality |
| 68 | + self.ICON_SIZE = 30 |
| 69 | + |
| 70 | + if self.ICON_PATH[-4:] == ".svg": |
| 71 | + self.icon = AutoscrollIconSvg(self.ICON_PATH, self.ICON_SIZE) |
| 72 | + else: |
| 73 | + self.icon = AutoscrollIconRaster(self.ICON_PATH, self.ICON_SIZE) |
| 74 | + |
| 75 | + self.mouse = Controller() |
| 76 | + self.scroll_mode = Event() |
| 77 | + self.listener = Listener(on_move=self.on_move, on_click=self.on_click) |
| 78 | + self.listener.start() |
| 79 | + self.looper = Thread(target=self.loop) |
| 80 | + self.looper.start() |
| 81 | + |
| 82 | + def on_move(self, x, y): |
| 83 | + if self.scroll_mode.is_set(): |
| 84 | + delta = self.icon.pos[1] - y |
| 85 | + if abs(delta) <= self.DEAD_AREA: |
| 86 | + self.direction = 0 |
| 87 | + elif delta > 0: |
| 88 | + self.direction = 1 |
| 89 | + elif delta < 0: |
| 90 | + self.direction = -1 |
| 91 | + if abs(delta) <= self.DEAD_AREA + self.DELAY * 2: |
| 92 | + self.interval = 0.5 |
| 93 | + else: |
| 94 | + self.interval = self.DELAY / (abs(delta) - self.DEAD_AREA) |
43 | 95 |
|
44 |
| -# modify this to adjust the speed of scrolling |
45 |
| -DELAY = 5 |
46 |
| -# modify this to change the button used for entering the scroll mode |
47 |
| -BUTTON_START = Button.middle |
48 |
| -# modify this to change the button used for exiting the scroll mode |
49 |
| -BUTTON_STOP = Button.middle |
50 |
| -# modify this to change the size (in px) of the area below and above the starting point where the scrolling is paused |
51 |
| -DEAD_AREA = 30 |
| 96 | + def on_click(self, x, y, button, pressed): |
| 97 | + if button == self.BUTTON_START and pressed and not self.scroll_mode.is_set(): |
| 98 | + self.icon.pos = (x, y) |
| 99 | + self.direction = 0 |
| 100 | + self.interval = 0.5 |
| 101 | + self.scroll_mode.set() |
| 102 | + self.icon.scroll_mode_entered.emit() |
| 103 | + elif button == self.BUTTON_STOP and pressed and self.scroll_mode.is_set(): |
| 104 | + self.scroll_mode.clear() |
| 105 | + self.icon.scroll_mode_exited.emit() |
| 106 | + |
| 107 | + def loop(self): |
| 108 | + while True: |
| 109 | + self.scroll_mode.wait() |
| 110 | + sleep(self.interval) |
| 111 | + self.mouse.scroll(0, self.direction) |
52 | 112 |
|
53 |
| -listener.start() |
54 |
| -autoscroll() |
| 113 | +app = QApplication(sys.argv) |
| 114 | +app.setQuitOnLastWindowClosed(False) |
| 115 | +autoscroll = Autoscroll() |
| 116 | +sys.exit(app.exec()) |
0 commit comments