Closed guigirl42 closed 2 years ago
Oh, whoops, i just discovered a file-note i'd forgotten about. The breakage was first noticed by me on 9/1/22, immediately after running my routine Arch KDE package updates. Those included Plasma bumping from 5.23.4 to 5.23.5, but of course being Arch, probably numerous other packages also changed then.
Hi, I'll take a look at this issue when I have time (in a few days).
@guigirl42
Is autoscroll_no_icon.py
broken too?
That was a good idea. I've just tested, & can advise that autoscroll_no_icon.py
continues to work as expected. It seems only autoscroll.py
broke.
when i middleclick, your nice "new" scroll icon appears, but... no scrolling at all occurs, whilst instead one of my cpu cores goes to 100% usage by the script. I need to either kill the script, or just logout again.
Are you able to exit the scroll mode by middleclicking again?
Does this code work on your machine (it should be equivalent to autoscroll_no_icon.py
)?
from pynput.mouse import Button, Controller, Listener
from threading import Event, Thread
from time import sleep
from PyQt5.QtWidgets import QApplication
import sys
class Autoscroll():
def __init__(self):
# modify this to adjust the speed of scrolling
self.DELAY = 5
# modify this to change the button used for entering the scroll mode
self.BUTTON_START = Button.middle
# modify this to change the button used for exiting the scroll mode
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
self.mouse = Controller()
self.scroll_mode = Event()
self.listener = Listener(on_move=self.on_move, on_click=self.on_click)
self.listener.start()
self.looper = Thread(target=self.loop)
self.looper.start()
def on_move(self, x, y):
if self.scroll_mode.is_set():
delta = self.pos[1] - y
if abs(delta) <= self.DEAD_AREA:
self.direction = 0
elif delta > 0:
self.direction = 1
elif delta < 0:
self.direction = -1
if abs(delta) <= self.DEAD_AREA + self.DELAY * 2:
self.interval = 0.5
else:
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.pos = (x, y)
self.direction = 0
self.interval = 0.5
self.scroll_mode.set()
elif button == self.BUTTON_STOP and pressed and self.scroll_mode.is_set():
self.scroll_mode.clear()
def loop(self):
while True:
self.scroll_mode.wait()
sleep(self.interval)
self.mouse.scroll(0, self.direction)
app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
autoscroll = Autoscroll()
sys.exit(app.exec())
I want to check if something is wrong with the logic of the script or something has broken in PyQt.
Are you able to exit the scroll mode by middleclicking again?
No, neither does left-click [which is my preferred edit of your script] work -- as i wrote in my OP, with this current bug, the only option afaik is to
either kill the script, or just logout again
Does this code work on your machine (it should be equivalent to autoscroll_no_icon.py)?
Yes, it works fine, with either your default MMB or my customised edit LMB to stop the scroll again. There is no high-cpu%.
So, it would appear that the breakage is associated somehow with PyQt. Please note that KDE Plasma [& Arch itself, of course] has received numerous package updates since you originally delivered your script successfully last year.
Do (1) and (2) work on your machine?
(1)
It should be equivalent to autoscroll_no_icon.py
.
from pynput.mouse import Button, Controller, Listener
from threading import Event, Thread
from time import sleep
from PyQt5.QtWidgets import QApplication, QLabel, qApp
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtGui import QPixmap
from pathlib import Path
import sys
class AutoscrollIconSvg(QSvgWidget):
scroll_mode_entered = pyqtSignal()
scroll_mode_exited = pyqtSignal()
def __init__(self, path, size):
super().__init__(path)
self.size = size
self.renderer().setAspectRatioMode(Qt.KeepAspectRatio)
self.resize(self.size, self.size)
self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint | Qt.X11BypassWindowManagerHint)
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
self.resize(self.size, self.size)
self.img = QPixmap(path).scaled(self.size, self.size, Qt.KeepAspectRatio)
self.setPixmap(self.img)
self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint | Qt.X11BypassWindowManagerHint)
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():
def __init__(self):
# modify this to adjust the speed of scrolling
self.DELAY = 5
# modify this to change the button used for entering the scroll mode
self.BUTTON_START = Button.middle
# modify this to change the button used for exiting the scroll mode
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 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.listener = Listener(on_move=self.on_move, on_click=self.on_click)
self.listener.start()
self.looper = Thread(target=self.loop, daemon=True)
self.looper.start()
def on_move(self, x, y):
if self.scroll_mode.is_set():
delta = self.icon.pos[1] - y
if abs(delta) <= self.DEAD_AREA:
self.direction = 0
elif delta > 0:
self.direction = 1
elif delta < 0:
self.direction = -1
if abs(delta) <= self.DEAD_AREA + self.DELAY * 2:
self.interval = 0.5
else:
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.icon.pos = (x, y)
self.direction = 0
self.interval = 0.5
self.scroll_mode.set()
print("BUTTON_START pressed")
elif button == self.BUTTON_STOP and pressed and self.scroll_mode.is_set():
self.scroll_mode.clear()
print("BUTTON_STOP pressed")
elif button == Button.right:
qApp.quit()
def loop(self):
while True:
self.scroll_mode.wait()
sleep(self.interval)
self.mouse.scroll(0, self.direction)
app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
autoscroll = Autoscroll()
sys.exit(app.exec())
(2)
It should be equivalent to autoscroll.py
.
from pynput.mouse import Button, Controller, Listener
from threading import Event, Thread
from time import sleep
from PyQt5.QtWidgets import QApplication, QLabel, qApp
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtGui import QPixmap
from pathlib import Path
import sys
class AutoscrollIconSvg(QSvgWidget):
scroll_mode_entered = pyqtSignal()
scroll_mode_exited = pyqtSignal()
def __init__(self, path, size):
super().__init__(path)
self.size = size
self.renderer().setAspectRatioMode(Qt.KeepAspectRatio)
self.resize(self.size, self.size)
self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint | Qt.X11BypassWindowManagerHint)
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
self.resize(self.size, self.size)
self.img = QPixmap(path).scaled(self.size, self.size, Qt.KeepAspectRatio)
self.setPixmap(self.img)
self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint | Qt.X11BypassWindowManagerHint)
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():
def __init__(self):
# modify this to adjust the speed of scrolling
self.DELAY = 5
# modify this to change the button used for entering the scroll mode
self.BUTTON_START = Button.middle
# modify this to change the button used for exiting the scroll mode
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 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.listener = Listener(on_move=self.on_move, on_click=self.on_click)
self.listener.start()
self.looper = Thread(target=self.loop, daemon=True)
self.looper.start()
def on_move(self, x, y):
if self.scroll_mode.is_set():
delta = self.icon.pos[1] - y
if abs(delta) <= self.DEAD_AREA:
self.direction = 0
elif delta > 0:
self.direction = 1
elif delta < 0:
self.direction = -1
if abs(delta) <= self.DEAD_AREA + self.DELAY * 2:
self.interval = 0.5
else:
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.icon.pos = (x, y)
self.direction = 0
self.interval = 0.5
self.scroll_mode.set()
self.icon.scroll_mode_entered.emit()
print("BUTTON_START pressed")
elif button == self.BUTTON_STOP and pressed and self.scroll_mode.is_set():
self.scroll_mode.clear()
self.icon.scroll_mode_exited.emit()
print("BUTTON_STOP pressed")
elif button == Button.right:
qApp.quit()
def loop(self):
while True:
self.scroll_mode.wait()
sleep(self.interval)
self.mouse.scroll(0, self.direction)
app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
autoscroll = Autoscroll()
sys.exit(app.exec())
Both of these programs should output "BUTTON_START pressed" when you enter the scroll mode and "BUTTON_STOP pressed" when you exit the scroll mode. If the programs don't work (e.g. if they use a lot of CPU), do they correctly print the above messages to the terminal?
It looks strange so far. You said that the icon appears on the screen, no scrolling occurs and the program doesn't respond to mouse buttons being clicked. The problem is that showing the icon, scrolling and listening for mouse events are split into 3 different threads.
Sorry, but atm i don't understand what you need me to do here. It seems like you need me to run your new scripts in a terminal, but that's not how i've been doing it so far. Once i've made each previous script version executable & inserted the necessary crunchbang path at the top, i've simply added the script to KDE's autologin function, logged out/in, & then the script is active; no terminal involved. It seems there's something important that maybe i've been failing to understand?
Sorry, I should have clarified that. After adding the shebang and making a script executable, you can run the script from terminal:
./autoscroll.py
This way you can test scripts without having to logout. Additionally you're able to see their text output.
I've just edited the code in the previous comment. You can now kill a script that you've run in a terminal by clicking the right mouse button (this will work if the script isn't broken, otherwise you'll have to kill it manually).
Thank you for the nice clarification [& also for the much easier way to initiate & stop each test]. I've run multiple tests on both scripts in the terminal now. Neither of them caused any high cpu hassle.
The no-icon one is working fine, & produces the expected output you wanted. The terminal transcript following, shows my several starts & stops, before i right-clicked to end the test.
guigirl@archlinuxTower[~/linux-autoscroll] 16:07:28 Sat Feb 26 $> ./autoscroll_no_icon.py
BUTTON_START pressed
BUTTON_STOP pressed
BUTTON_START pressed
BUTTON_STOP pressed
BUTTON_START pressed
BUTTON_STOP pressed
guigirl@archlinuxTower[~/linux-autoscroll] 16:07:50 Sat Feb 26 $>
The latest icon one now does provide working autoscroll again, of sorts, but it is not working well. It produces NO terminal output despite multiple starts & stops. Each time i MMB-start the AS, it does indeed start. However, most times that i LMB-stopped the AS, the browser page takes a big jump [but see Note 1], such that i entirely lose my place each time [frequently the place i was at when i LMB-stopped, is completely off-screen once the jump occurs]. I hope you can fix that, as the experience atm is so disturbing to my workflow that i simply would not bother using this.
guigirl@archlinuxTower[~/linux-autoscroll] 16:07:55 Sat Feb 26 $> ./autoscroll.py
guigirl@archlinuxTower[~/linux-autoscroll] 16:09:36 Sat Feb 26 $>
guigirl@archlinuxTower[~/linux-autoscroll] 16:10:13 Sat Feb 26 $> ./autoscroll.py
guigirl@archlinuxTower[~/linux-autoscroll] 16:10:53 Sat Feb 26 $>
Btw, i should mention that before i contacted you the other day, i adjusted my KDE Plasma X11 mouse wheel scroll setting to its smallest value, ie, one line. I did that to try to make the low-speed AS motion less jerky. Thus afaik, that unpleasant big jump that now occurs each time i LMB-stop the icon-AS, cannot be caused by me having some large mouse scroll setting value by mistake.
Note 1. Maybe that big jump is my user-error, not a code problem? I retested just before posting this. I found there is some subtlety involved. The big jump only occurs if my LMB-stop action is performed out of the dead-zone... & if it is far out of the zone, the jump is bigger. Otoh, if i manage to remember to always return the pointer to the dead-zone, especially to be on top of the icon, before doing the LMB-stop, then there is no jump. Maybe that's how it was last year too, & i've simply forgotten the correct way to use it?
Thanks for feedback!
It produces NO terminal output despite multiple starts & stops.
Yes, that's because I've given you a wrong code. :laughing: The code I've pasted in (2) is just autoscroll.py
with the option to kill the script by right click. However, this means that autoscroll.py
works fine (no high CPU usage) when run from terminal and doesn't work when launched at login. Please check if autoscroll.py
does indeed work when you run it in the terminal.
Otoh, if i manage to remember to always return the pointer to the dead-zone, especially to be on top of the icon, before doing the LMB-stop, then there is no jump. Maybe that's how it was last year too, & i've simply forgotten the correct way to use it?
I use autoscroll.py
on a daily basis and I've never experienced the jumps you mentioned. What's strange is that there is no difference in the scrolling logic between (1) and (2).
Btw, i should mention that before i contacted you the other day, i adjusted my KDE Plasma X11 mouse wheel scroll setting to its smallest value, ie, one line. I did that to try to make the low-speed AS motion less jerky.
Adjusting the scroll settings in the way you described may help, but to create a truly smooth autoscroll on Linux, one would have to find a way to programmatically reduce the distance scrolled by the wheel to e.g. 1px.
I've just edited (2) (it should produce some output now) and would like you to check if it works when you log out and log in.
Please check if autoscroll.py does indeed work when you run it in the terminal.
Yes it does, same as it also did yesterday with the slightly older version.
never experienced the jumps you mentioned
Ha, now i do not either. I don't know why it happened yesterday ... sunspots? 😜
just edited (2) (it should produce some output now
Yes, it sure does! 😆
check if it works when you log out and log in
Yes, it's working nicely 👍
Fwiw, the version i am now using from login autostart, is your last version except i've commented-out lines 110-111, as i don't want my frequently-needed right-clicks to kill the script anymore. It still seems to be working fine this way.
### guigirl 27/2/22: I disabled the following 2 lines; needed during Dev terminal troubleshooting, but now redundant for final autostart version [https://github.com/TWolczanski/linux-autoscroll/issues/8]
### elif button == Button.right:
### qApp.quit()
Thank you very much for taking the time & trouble to fix whatever it was that broke.
The great pity of all this is that, as we canvassed before in a different Issue, this python script does not work in Plasma Wayland. These days, i tend to be using Wayland more than X11 for my daily Plasma sessions.
Best wishes!
The great pity of all this is that, as we canvassed before in a different Issue, this python script does not work in Plasma Wayland.
I'm trying to make the script compatible with Wayland. The version without the icon is already complete.
Hi. Contrary to my older Issue https://github.com/TWolczanski/linux-autoscroll/issues/3#issue-1044257866, for a while now i've been running more in Wayland than X11 sessions. Hence as per that other issue, your script has been unavailable to me. Just now, i wanted to briefly login again to X11 to try something, & in the course of that i expected to again be able to use the script.
Given the rate of Arch/Plasma updates, i've really no idea how long ago the breakage might have occurred. All i know today [Arch Plasma 5.24.1 X11] is that when i middleclick, your nice "new" scroll icon appears, but... no scrolling at all occurs, whilst instead one of my cpu cores goes to 100% usage by the script. I need to either kill the script, or just logout again.
Is there any specific data i can provide you to assist in investigating & resolving this... if you're still inclined?