coldfix / udiskie

Automounter for removable media
MIT License
866 stars 53 forks source link

[Sway/Wayland] Smart tray is never hidden? #224

Closed rieje closed 3 years ago

rieje commented 3 years ago

On X11, I run udiskie udiskie --no-automount --smart-tray & and with rules that work as expected--ignored devices such as internal drives are ignored. There is no tray icon until an external drive is plugged in.

On Wayland, the same command will always show the tray icon on Waybar (even with --appindicator option and with libappindicator-gtk3 installed)--even when there are only ignored drives. In addition, despite having a setting the menu as flat, it show a menu "Managed devices" containing both ignored (like my internal drives) and unignored drives. I prefer a flat menu for convenience via less clicks and visibility. I think this workaround is why Managed devices parent menu exists despite setting menu to be flat, but I'm also not sure if that might also be what is preventing smart tray from being hidden when there are no unignored devices.

I know you found AppIndicator to be broken, so it might be the culprit.

coldfix commented 3 years ago

Hi, and thanks again for reporting!

As you noted, the menu not being flat with wayland/appindicator is a workaround for a bug with dynamically sized menus that I currently don't see any other way to mitigate on udiskie side, other than possibly switching to another KSNI implementation. It should be fixed upstream (but I'm not sure whether in sway, wayland, waybar, or appindicator).

Regarding the second issue, this seems to be an upstream bug as well. I checked that setting the indicator's status to APP_INDICATOR_STATUS_PASSIVE has no effect in sway (even though this should hide the icon according to the docs).

I considered the following potential workaround: let udiskie destroy and create the icon entirely rather than just update its status. However, the AppIndicator3 API sadly doesn't seem to expose any method to actually get rid of the icon (appindicator is just ridiculously broken.....). So one would have to hope that nobody else holds a reference to the indicator object and that it is hidden after the python object is freed. Consider the following minimal example:

import sys
import signal
import weakref

from gi import require_version
require_version('GLib', '2.0')
require_version('Gtk', '3.0')
require_version('AppIndicator3', '0.1')

from gi.repository import GLib
from gi.repository import Gtk
from gi.repository import AppIndicator3

ACTIVE = AppIndicator3.IndicatorStatus.ACTIVE
PASSIVE = AppIndicator3.IndicatorStatus.PASSIVE

def create_icon():
    indicator = AppIndicator3.Indicator.new(
        'myappindicator',
        'ICON-NOT-FOUND',
        AppIndicator3.IndicatorCategory.OTHER)
    menu = Gtk.Menu()
    item_quit = Gtk.MenuItem()
    item_quit.set_label('Quit')
    item_quit.connect('activate', Gtk.main_quit)
    menu.append(item_quit)
    menu.show_all()
    indicator.set_menu(menu)
    return indicator

class NormalModeIcon:

    def __init__(self):
        self.icon = create_icon()

    def is_active(self):
        return self.icon.get_status() == ACTIVE

    def show(self):
        self.icon.set_status(ACTIVE)

    def hide(self):
        self.icon.set_status(PASSIVE)

class WorkaroundIcon:

    def __init__(self):
        self.icon = None

    def is_active(self):
        return self.icon is not None

    def show(self):
        self.icon = create_icon()
        self.icon.set_status(ACTIVE)
        # get notified when the `Indicator` object is finalized:
        self.w = weakref.ref(self.icon, print)

    def hide(self):
        self.icon = None

def toggle_icon(icon):
    if icon.is_active():
        print("hide")
        icon.hide()
    else:
        print("show")
        icon.show()
    return True

def main(mode='normal'):
    if mode == 'normal':
        icon = NormalModeIcon()
    else:
        icon = WorkaroundIcon()
    GLib.timeout_add(1000, toggle_icon, icon)

    signal.signal(signal.SIGINT, signal.SIG_DFL)
    Gtk.main()

if __name__ == '__main__':
    main(*sys.argv[1:])

Call it like this for the normal mode:

python appindicator.py normal

or like this to try the workaround:

python appindicator.py workaround

For me, both work in X, neither works in sway.

Note that the weakref shows that the python Indicator proxy is in fact destroyed, but the system icon stays visible nevertheless.

Now, we could of course go further in the workaround and create the icon each time in their own subprocess and stop the process to hide it (and that might actually work, maybe?), but that's obviously ridiculous :)

I have forwarded the issue to waybar, see https://github.com/Alexays/Waybar/issues/1150, but I wouldn't count on this being addressed anytime soon, as they might be overwhelmed by issues.

Best, Thomas

coldfix commented 3 years ago

Should be fixed as of waybar v0.9.8.