linux-autoscroll icon indicating copy to clipboard operation
linux-autoscroll copied to clipboard

Can your script be adapted for Wayland, as well as current X11?

Open guigirl42 opened this issue 2 years ago • 13 comments

Hello

I use KDE Plasma 5 for my ArchLinux's DE. Mostly atm i still login with X11 desktop sessions, but as the KDE Devs' ongoing Wayland improvements continue, more & more i am instead trialling Plasma Wayland sessions. Afaik your script atm is exclusively for X11. Do you think it might be possible to expand/modify it for use also in Wayland sessions please?

guigirl42 avatar Nov 04 '21 00:11 guigirl42

Hi! As far as I know, the library I use for listening and sending mouse events doesn't support Wayland (https://github.com/moses-palmer/pynput/issues/331). The problem is that there are no good alternatives to pynput. Initially I wanted to use mouse but it seems like it doesn't generate scroll events properly.

TWolczanski avatar Nov 05 '21 12:11 TWolczanski

i'm running it on Xwayland without any issues (DE is Gnome)

adeliktas avatar Nov 14 '21 12:11 adeliktas

Xwayland

Yes i know; also in KDE Plasma it still works fine in apps if they run in Xwayland mode... but that's not what i want. When i login to Plasma Wayland, i want to run my apps in full-Wayland mode. However, as i said in my OP, the script is inactive in Wayland apps.

guigirl42 avatar Nov 14 '21 13:11 guigirl42

This post is simply to cross-reference this nice news contained in one of my other Issues, given it is absolutely relevant to this Issue.


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.

Originally posted by @TWolczanski in https://github.com/TWolczanski/linux-autoscroll/issues/8#issuecomment-1053739416


That's exciting news!

guigirl42 avatar Feb 28 '22 01:02 guigirl42

The version without the icon is already complete

I assume that version is still only local to you, because the latest version you gave me yesterday in my other Issue, is still not working for me in Plasma Wayland here [except for individual apps running as Xwayland apps, which i don't count, as the whole point of me using a Wayland session is to be able to run all my apps in full-wayland mode].

I keenly look forward to your next push version.

guigirl42 avatar Feb 28 '22 01:02 guigirl42

@guigirl42

I assume that version is still only local to you

That's right, I haven't pushed it to GitHub yet.

Please check if this code works on your Plasma Wayland (it's the no-icon version I mentioned):

from evdev import InputDevice, list_devices, UInput, ecodes as e
from threading import Event, Thread
from time import sleep


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 = e.BTN_MIDDLE
        # modify this to change the button used for exiting the scroll mode
        self.BUTTON_STOP = e.BTN_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.scroll_mode = Event()
        
    def on_move(self, dy):
        if self.scroll_mode.is_set():
            self.delta += dy
            if abs(self.delta) <= self.DEAD_AREA:
                self.direction = 0
            elif self.delta < 0:
                self.direction = 1
            elif self.delta > 0:
                self.direction = -1
            if abs(self.delta) <= self.DEAD_AREA + self.DELAY * 2:
                self.interval = 0.5
            else:
                self.interval = self.DELAY / (abs(self.delta) - self.DEAD_AREA)

    def on_click(self, button, pressed):
        if button == self.BUTTON_START and pressed and not self.scroll_mode.is_set():
            self.delta = 0
            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 find_mouse(self):
        mouse = None
        paths = list_devices()
        count_max = 0
        
        for path in paths:
            device = InputDevice(path)
            cap = device.capabilities()
            events = cap.keys()
            if 1 in events and 2 in events:
                if 1 in cap[2] and 8 in cap[2]:
                    count = sum(cap[1])
                    if count > count_max:
                        count_max = count
                        if mouse is not None:
                            mouse.close()
                        mouse = device
                        continue
            device.close()
        
        if mouse is None:
            raise OSError("No mouse found")
        return mouse
    
    def listen(self):
        for event in self.mouse.read_loop():
            if event.type == e.EV_KEY:
                button = event.code
                pressed = (event.value == 1)
                if button == e.BTN_RIGHT:
                    self.time_to_quit = True
                    self.delta = 0
                    self.direction = 0
                    self.interval = 0
                    self.scroll_mode.set()
                    break
                self.on_click(button, pressed)
            elif event.type == e.EV_REL and event.code == e.REL_Y:
                dy = event.value
                self.on_move(dy)
    
    def start(self):
        self.mouse = self.find_mouse()
        self.ui = UInput.from_device(self.mouse)
        self.time_to_quit = False
        self.listener = Thread(target=self.listen)
        self.listener.start()
        
        while not self.time_to_quit:
            self.scroll_mode.wait()
            sleep(self.interval)
            # scroll
            self.ui.write(e.EV_REL, e.REL_WHEEL, self.direction)
            self.ui.syn()
            
        self.mouse.close()
        self.ui.close()

autoscroll = Autoscroll()
autoscroll.start()

You don't have to install any new libraries (evdev is automatically installed when you install pynput). I would like you to test the script in the terminal - I made sure that when you kill it by right click, the device files are properly closed. Note that BUTTON_START and BUTTON_STOP have different values now. For the left click you should use e.BTN_LEFT and for the right click e.BTN_RIGHT.

I've tested this script on Xorg and it works fine (even better than the current no-icon version). As it uses uinput, it should work on Wayland too (I'm waiting for your feedback though). The problem is how to implement the version with the icon - on Wayland it's not possible to get the absolute mouse pointer position (at least not without hacking).

TWolczanski avatar Mar 01 '22 01:03 TWolczanski

Hi. Thanks for your ongoing efforts here.

No joy i'm afraid; it throws errors & exits. My only edits to your latest paste were to change the stop line to self.BUTTON_STOP = e.BTN_LEFT, & to insert #!/home/guigirl/linux-autoscroll/.autoscroll/bin/python3 at the top as usual. However, both times that i tried to run it:

guigirl@archlinuxTower[~/linux-autoscroll] 13:27:05 Tue Mar 01 $> ./autoscroll_no_icon.py 
Traceback (most recent call last):
  File "/home/guigirl/linux-autoscroll/./autoscroll_no_icon.py", line 103, in <module>
    autoscroll.start()
  File "/home/guigirl/linux-autoscroll/./autoscroll_no_icon.py", line 86, in start
    self.mouse = self.find_mouse()
  File "/home/guigirl/linux-autoscroll/./autoscroll_no_icon.py", line 65, in find_mouse
    raise OSError("No mouse found")
OSError: No mouse found
guigirl@archlinuxTower[~/linux-autoscroll] 13:27:12 Tue Mar 01 $> 

guigirl42 avatar Mar 01 '22 02:03 guigirl42

I forgot to mention that the script needs to be run with sudo. Otherwise it won't have access to /dev/input and the "No mouse found" error will be raised.

TWolczanski avatar Mar 01 '22 12:03 TWolczanski

Yes, running in terminal as sudo indeed does work. However, now i'm confused how to translate this to my autostart settings, because how can i sudo-run there? Furthermore, no insult intended, but i'm not sure that i want to have to permanently run a script with elevated privileges. Am i misunderstanding?

guigirl42 avatar Mar 01 '22 12:03 guigirl42

how to translate this to my autostart settings, because how can i sudo-run there?

It's likely that you can't. However, there are other ways to run a script as root at startup.

i'm not sure that i want to have to permanently run a script with elevated privileges

As there is no way other than reading raw device events (uinput) to make the script compatible with Wayland, it'll have to be run with elevated privileges. Alternatively, you could add yourself to the input group, but I don't know if this is safe. I'll try to find a better solution when I have time.

TWolczanski avatar Mar 01 '22 14:03 TWolczanski

OK. Unfortunately i find myself now losing enthusiasm at this unexpected, albeit possibly unavoidable, new direction. For now i think i'll just sit on the sidelines & watch for any possible future non-sudo breakthroughs.

Many thanks for your hard work.

guigirl42 avatar Mar 02 '22 03:03 guigirl42

Dear @TWolczanski -- i have [IMO] some wonderful news for you, albeit it might actually make you sad [because of all the hard work with your nice project here].

Today, in the Vivaldi [browser] forum, i discovered that, apparently buried deep inside the chromium code for some years, is the capability to invoke MMB-Autoscroll natively in chromium-based Linux browsers. 😮

Our forum discussions lead to this -- https://medium.com/@1nikolas/linux-enable-middle-mouse-button-scrolling-on-chrome-ium-and-electron-apps-discord-etc-ab2d0a213505. Here's the magic [simply append this chromium commandline switch to all your chromium-based browser launchers]:

--enable-blink-features=MiddleClickAutoscroll

After testing it, i posted this confirmation in the forum:

I am most happy to report that, as well as this switch working beautifully in my Arch KDE X11 session [which was active for all my earlier posts above], atm i am logged into my Arch KDE Wayland session, in which this switch continues to work perfectly [my Vivaldi is herein running as a full-wayland app via my already-present other switch --ozone-platform-hint=auto]. 👍

Anyway, i thought you might like to know about this information. Best wishes!

guigirl42 avatar Jul 19 '22 06:07 guigirl42

Hi @guigirl42, thanks for the exciting news! I'll however continue using the script despite its low speed jerkiness as I've just got used to my current config and I also don't want to limit myself to the browser.

Speaking of low speed jerkiness, I've recently found this article: https://www.phoronix.com/scan.php?page=news_item&px=xf86-input-libinput-1.2 It's possible that a slight modification to the script I posted in this issue can result in a smooth autoscroll experience at low speed, provided that your libinput version is at least 1.19 and your xf86-input-libinput version is at least 1.2. Unfortunately, these requirements aren't met on my machine. I successfully installed the newest version of libinput, but failed to build xf86-input-libinput. The modification I'm talking about is replacing self.ui.write(e.EV_REL, e.REL_WHEEL, self.direction) with self.ui.write(e.EV_REL, e.REL_WHEEL_HI_RES, self.direction * 16) (the multiplier in the third argument doesn't need to be 16).

TWolczanski avatar Jul 19 '22 13:07 TWolczanski