From 97943e47b979e61fc7929da065f06b927294b795 Mon Sep 17 00:00:00 2001 From: lemonxah Date: Mon, 16 Jan 2023 11:10:44 +0200 Subject: [PATCH 1/8] added tray icon and minimze to tray and startup minimized functionality --- jack_mixer/app.py | 41 ++++++++++++++++------ jack_mixer/gui.py | 24 ++++++++++++- jack_mixer/indicator.py | 74 +++++++++++++++++++++++++++++++++++++++ jack_mixer/meson.build | 1 + jack_mixer/preferences.py | 11 ++++++ 5 files changed, 140 insertions(+), 11 deletions(-) create mode 100644 jack_mixer/indicator.py diff --git a/jack_mixer/app.py b/jack_mixer/app.py index 7328b5b..2748a51 100644 --- a/jack_mixer/app.py +++ b/jack_mixer/app.py @@ -34,7 +34,7 @@ from .serialization import SerializedObject, Serializator from .styling import load_css_styles from .version import __version__ - +from .indicator import Indicator __program__ = "jack_mixer" # A "locale" directory present within the package take precedence @@ -92,7 +92,8 @@ class JackMixer(SerializedObject): # scales suitable as volume slider scales slider_scales = [scale.Linear30dB(), scale.Linear70dB()] - def __init__(self, client_name=__program__): + def __init__(self, client_name=__program__, minimized=False): + self.minimize_on_start = minimized self.visible = False self.nsm_client = None # name of project file that is currently open @@ -103,6 +104,9 @@ def __init__(self, client_name=__program__): self.last_xml_serialization = None self.cached_xml_serialization = None + log.info("Starting %s %s", __program__, __version__) + self.indicator = Indicator(self) + if os.environ.get("NSM_URL"): self.nsm_client = NSMClient( prettyName=__program__, @@ -719,6 +723,10 @@ def on_save_as_cb(self, *args): dlg.destroy() def on_quit_cb(self, *args, on_delete=False): + if self.gui_factory.get_tray_minimized(): + self.window.hide() + return True + if not self.nsm_client and self.gui_factory.get_confirm_quit(): dlg = Gtk.MessageDialog( parent=self.window, @@ -1049,7 +1057,7 @@ def save_to_xml(self, file): b.save(file) def load_from_xml(self, file, silence_errors=False, from_nsm=False): - log.debug("Loading from XML...") + log.debug("Loading from XML... YES WE ARE!") self.unserialized_channels = [] b = XmlSerialization() try: @@ -1146,11 +1154,16 @@ def main(self): if not self.mixer: return - if self.visible or self.nsm_client is None: - width, height = self.window.get_size() - self.window.show_all() - if hasattr(self, "paned_position"): - self.paned.set_position(self.paned_position / self.width * width) + if not self.minimize_on_start: + log.info("Showing main window") + if self.visible or self.nsm_client is None: + width, height = self.window.get_size() + self.window.show_all() + if hasattr(self, "paned_position"): + self.paned.set_position(self.paned_position / self.width * width) + else: + log.info("Minimizing main window") + self.window.hide() signal.signal(signal.SIGUSR1, self.sighandler) signal.signal(signal.SIGTERM, self.sighandler) @@ -1176,7 +1189,16 @@ def error_dialog(parent, msg, *args, **kw): def main(): + log.debug("JACK Mixer version %s" % __version__) + sys.stdout.flush() parser = argparse.ArgumentParser(prog=__program__, description=_(__doc__.splitlines()[0])) + parser.add_argument( + "-m", + "--minimized", + action="store_true", + default=False, + help=_("start JACK Mixer minimized to system tray (default: %(default)s)"), + ) parser.add_argument( "-c", "--config", @@ -1204,7 +1226,7 @@ def main(): ) try: - mixer = JackMixer(args.client_name) + mixer = JackMixer(args.client_name, args.minimized) except Exception as e: error_dialog(None, _("Mixer creation failed:\n\n{}"), e, debug=args.debug) sys.exit(1) @@ -1230,6 +1252,5 @@ def main(): mixer.main() mixer.cleanup() - if __name__ == "__main__": main() diff --git a/jack_mixer/gui.py b/jack_mixer/gui.py index 855d9f5..2ad78db 100644 --- a/jack_mixer/gui.py +++ b/jack_mixer/gui.py @@ -74,6 +74,7 @@ def __init__(self, topwindow, meter_scales, slider_scales): ) def set_default_preferences(self): + self.tray_minimized = False self.confirm_quit = False self.default_meter_scale = self.meter_scales[0] self.default_project_path = None @@ -93,6 +94,10 @@ def read_preferences(self): "Preferences", "confirm_quit", fallback=self.confirm_quit ) + self.tray_minimized = self.config.getboolean( + "Preferences", "tray_minimized", fallback=self.tray_minimized + ) + scale_id = self.config["Preferences"]["default_meter_scale"] self.default_meter_scale = lookup_scale(self.meter_scales, scale_id) if not self.default_meter_scale: @@ -137,6 +142,7 @@ def read_preferences(self): def write_preferences(self): self.config["Preferences"] = {} + self.config["Preferences"]["tray_minimized"] = str(self.tray_minimized) self.config["Preferences"]["confirm_quit"] = str(self.confirm_quit) self.config["Preferences"]["default_meter_scale"] = self.default_meter_scale.scale_id self.config["Preferences"]["default_project_path"] = self.default_project_path or "" @@ -164,6 +170,9 @@ def _update_setting(self, name, value): signal = "{}-changed".format(name.replace("_", "-")) self.emit(signal, value) + def set_tray_minimized(self, tray_minimized): + self._update_setting("tray_minimized", tray_minimized) + def set_confirm_quit(self, confirm_quit): self._update_setting("confirm_quit", confirm_quit) @@ -210,6 +219,9 @@ def set_auto_reset_peak_meters_time_seconds(self, time): def set_meter_refresh_period_milliseconds(self, period): self._update_setting("meter_refresh_period_milliseconds", period) + def get_tray_minimized(self): + return self.tray_minimized + def get_confirm_quit(self): return self.confirm_quit @@ -284,7 +296,10 @@ def serialize(self, object_backend): ) def unserialize_property(self, name, value): - if name == "confirm_quit": + if name == "tray_minimized": + self.set_tray_minimized(value == "True") + return True + elif name == "confirm_quit": self.set_confirm_quit(value == "True") return True elif name == "default_meter_scale": @@ -319,6 +334,13 @@ def unserialize_property(self, name, value): return False +GObject.signal_new( + "tray-minimized-changed", + Factory, + GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.ACTION, + None, + [bool], +) GObject.signal_new( "confirm-quit-changed", Factory, diff --git a/jack_mixer/indicator.py b/jack_mixer/indicator.py new file mode 100644 index 0000000..e1179fa --- /dev/null +++ b/jack_mixer/indicator.py @@ -0,0 +1,74 @@ +import gi +try: + gi.require_version('AppIndicator3', '0.1') + gi.require_version('Gtk', '3.0') +except Exception as e: + print(e) + print('Repository version required not present') + exit(1) + +import os +import logging as log +from gi.repository import Gtk, AppIndicator3 as appindicator +from os import environ, path + +prefix = environ.get('MESON_INSTALL_PREFIX', '/usr/local') +datadir = path.join(prefix, 'share') +icondir = path.join(datadir, 'icons', 'hicolor', 'scalable', 'apps') + +class Indicator: + def __init__(self, jack_mixer): + self.app = jack_mixer + icon = os.path.join(icondir, 'jack_mixer.svg') + self.indicator = appindicator.Indicator.new("Jack Mixer", + icon, + appindicator.IndicatorCategory.APPLICATION_STATUS) + self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE) + self.indicator.set_menu(self.create_menu()) + + def create_menu(self): + self.menu = Gtk.Menu() + self.menu.set_title('Jack Mixer') + self.menu + + self.hidewindow = Gtk.MenuItem(label = 'Hide / Show Jack Mixer') + self.hidewindow.connect('activate', self.hideshow) + self.menu.append(self.hidewindow) + + self.separator = Gtk.SeparatorMenuItem() + self.menu.append(self.separator) + + self.exittray = Gtk.MenuItem(label = 'Quit') + self.exittray.connect('activate', self.quit) + self.menu.append(self.exittray) + + self.menu.show_all() + return self.menu + + def hideshow(self, source): + self.app.window.set_visible(not self.app.window.get_visible()) + + def quit(self, source, on_delete=False): + if not self.app.nsm_client and self.app.gui_factory.get_confirm_quit(): + dlg = Gtk.MessageDialog( + parent=self.app.window, + message_type=Gtk.MessageType.QUESTION, + buttons=Gtk.ButtonsType.NONE, + ) + dlg.set_markup(_("Quit application?")) + dlg.format_secondary_markup( + _( + "All jack_mixer ports will be closed and connections lost," + "\nstopping all sound going through jack_mixer.\n\n" + "Are you sure?" + ) + ) + dlg.add_buttons( + Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_QUIT, Gtk.ResponseType.OK + ) + response = dlg.run() + dlg.destroy() + if response != Gtk.ResponseType.OK: + return on_delete + + Gtk.main_quit() diff --git a/jack_mixer/meson.build b/jack_mixer/meson.build index edb5f60..9baa530 100644 --- a/jack_mixer/meson.build +++ b/jack_mixer/meson.build @@ -53,6 +53,7 @@ python_sources = files([ 'serialization_xml.py', 'slider.py', 'styling.py', + 'indicator.py', ]) diff --git a/jack_mixer/preferences.py b/jack_mixer/preferences.py index 920fbf6..ef61fe8 100644 --- a/jack_mixer/preferences.py +++ b/jack_mixer/preferences.py @@ -80,6 +80,14 @@ def create_ui(self): self.language_box.pack_start(Gtk.Label(_("Language:")), False, True, 5) self.language_box.pack_start(self.language_combo, True, True, 0) + self.tray_minimized_checkbutton = Gtk.CheckButton(_("Minimize to tray")) + self.tray_minimized_checkbutton.set_tooltip_text( + _("Minimize the application to the system tray when the window is closed") + ) + self.tray_minimized_checkbutton.set_active(self.app.gui_factory.get_tray_minimized()) + self.tray_minimized_checkbutton.connect("toggled", self.on_tray_minimized_toggled) + interface_vbox.pack_start(self.tray_minimized_checkbutton, True, True, 3) + self.confirm_quit_checkbutton = Gtk.CheckButton(_("Confirm quit")) self.confirm_quit_checkbutton.set_tooltip_text( _("Always ask for confirmation before quitting the application") @@ -336,6 +344,9 @@ def on_vumeter_color_change(self, *args): self.custom_color_box.set_sensitive(self.vumeter_color_checkbutton.get_active()) + def on_tray_minimized_toggled(self, *args): + self.app.gui_factory.set_tray_minimized(self.tray_minimized_checkbutton.get_active()) + def on_confirm_quit_toggled(self, *args): self.app.gui_factory.set_confirm_quit(self.confirm_quit_checkbutton.get_active()) From 6824a9e4ddb6b6eda045b6e4bb9a9271363ed6d9 Mon Sep 17 00:00:00 2001 From: lemonxah Date: Mon, 16 Jan 2023 11:23:06 +0200 Subject: [PATCH 2/8] removed broken line --- jack_mixer/indicator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/jack_mixer/indicator.py b/jack_mixer/indicator.py index e1179fa..4778bc0 100644 --- a/jack_mixer/indicator.py +++ b/jack_mixer/indicator.py @@ -29,7 +29,6 @@ def __init__(self, jack_mixer): def create_menu(self): self.menu = Gtk.Menu() self.menu.set_title('Jack Mixer') - self.menu self.hidewindow = Gtk.MenuItem(label = 'Hide / Show Jack Mixer') self.hidewindow.connect('activate', self.hideshow) From 2b4f09100b59be75467afdeb0dbbe7c74a966a0e Mon Sep 17 00:00:00 2001 From: lemonxah Date: Tue, 17 Jan 2023 11:54:07 +0200 Subject: [PATCH 3/8] If AppIndicator is not availble then there is no tray icon supported --- jack_mixer/app.py | 2 +- jack_mixer/indicator.py | 14 ++++++++++++-- jack_mixer/preferences.py | 15 ++++++++------- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/jack_mixer/app.py b/jack_mixer/app.py index 2748a51..1f9569a 100644 --- a/jack_mixer/app.py +++ b/jack_mixer/app.py @@ -723,7 +723,7 @@ def on_save_as_cb(self, *args): dlg.destroy() def on_quit_cb(self, *args, on_delete=False): - if self.gui_factory.get_tray_minimized(): + if self.Indicator.available() and self.indicator. self.gui_factory.get_tray_minimized(): self.window.hide() return True diff --git a/jack_mixer/indicator.py b/jack_mixer/indicator.py index 4778bc0..4c64cb4 100644 --- a/jack_mixer/indicator.py +++ b/jack_mixer/indicator.py @@ -1,15 +1,19 @@ import gi try: - gi.require_version('AppIndicator3', '0.1') gi.require_version('Gtk', '3.0') except Exception as e: print(e) print('Repository version required not present') exit(1) +try: + from gi.repository import AppIndicator3 as appindicator +except ImportError: + appindicator = None + import os import logging as log -from gi.repository import Gtk, AppIndicator3 as appindicator +from gi.repository import Gtk from os import environ, path prefix = environ.get('MESON_INSTALL_PREFIX', '/usr/local') @@ -19,6 +23,9 @@ class Indicator: def __init__(self, jack_mixer): self.app = jack_mixer + if appindicator is None: + log.warning('AppIndicator3 not found, indicator will not be available') + return icon = os.path.join(icondir, 'jack_mixer.svg') self.indicator = appindicator.Indicator.new("Jack Mixer", icon, @@ -26,6 +33,9 @@ def __init__(self, jack_mixer): self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE) self.indicator.set_menu(self.create_menu()) + def available(self): + return not self.indicator is None + def create_menu(self): self.menu = Gtk.Menu() self.menu.set_title('Jack Mixer') diff --git a/jack_mixer/preferences.py b/jack_mixer/preferences.py index ef61fe8..4ed88fd 100644 --- a/jack_mixer/preferences.py +++ b/jack_mixer/preferences.py @@ -80,13 +80,14 @@ def create_ui(self): self.language_box.pack_start(Gtk.Label(_("Language:")), False, True, 5) self.language_box.pack_start(self.language_combo, True, True, 0) - self.tray_minimized_checkbutton = Gtk.CheckButton(_("Minimize to tray")) - self.tray_minimized_checkbutton.set_tooltip_text( - _("Minimize the application to the system tray when the window is closed") - ) - self.tray_minimized_checkbutton.set_active(self.app.gui_factory.get_tray_minimized()) - self.tray_minimized_checkbutton.connect("toggled", self.on_tray_minimized_toggled) - interface_vbox.pack_start(self.tray_minimized_checkbutton, True, True, 3) + if self.app.indicator.available(): + self.tray_minimized_checkbutton = Gtk.CheckButton(_("Minimize to tray")) + self.tray_minimized_checkbutton.set_tooltip_text( + _("Minimize the application to the system tray when the window is closed") + ) + self.tray_minimized_checkbutton.set_active(self.app.gui_factory.get_tray_minimized()) + self.tray_minimized_checkbutton.connect("toggled", self.on_tray_minimized_toggled) + interface_vbox.pack_start(self.tray_minimized_checkbutton, True, True, 3) self.confirm_quit_checkbutton = Gtk.CheckButton(_("Confirm quit")) self.confirm_quit_checkbutton.set_tooltip_text( From cdae06135399d0654b8f27c65570b6e4998255df Mon Sep 17 00:00:00 2001 From: lemonxah Date: Tue, 17 Jan 2023 12:10:40 +0200 Subject: [PATCH 4/8] start minimized to taskbar if the appindicator is not available --- jack_mixer/app.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/jack_mixer/app.py b/jack_mixer/app.py index 1f9569a..46a8371 100644 --- a/jack_mixer/app.py +++ b/jack_mixer/app.py @@ -1163,7 +1163,10 @@ def main(self): self.paned.set_position(self.paned_position / self.width * width) else: log.info("Minimizing main window") - self.window.hide() + if self.indicator.available: + self.window.hide() + else: + self.window.iconify() signal.signal(signal.SIGUSR1, self.sighandler) signal.signal(signal.SIGTERM, self.sighandler) @@ -1197,7 +1200,7 @@ def main(): "--minimized", action="store_true", default=False, - help=_("start JACK Mixer minimized to system tray (default: %(default)s)"), + help=_("start JACK Mixer minimized to system tray (default: %(default)s) If system tray is not available, JACK Mixer will start minimized to taskbar."), ) parser.add_argument( "-c", From 63928ad43408aec45ea2d27496a6e14e0276473c Mon Sep 17 00:00:00 2001 From: lemonxah Date: Thu, 19 Jan 2023 08:45:24 +0200 Subject: [PATCH 5/8] fixed quitting after minimizing --- jack_mixer/app.py | 10 +++++++--- jack_mixer/indicator.py | 7 +++---- jack_mixer/preferences.py | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/jack_mixer/app.py b/jack_mixer/app.py index 46a8371..de4d702 100644 --- a/jack_mixer/app.py +++ b/jack_mixer/app.py @@ -723,11 +723,14 @@ def on_save_as_cb(self, *args): dlg.destroy() def on_quit_cb(self, *args, on_delete=False): - if self.Indicator.available() and self.indicator. self.gui_factory.get_tray_minimized(): - self.window.hide() + log.info("on_quit_cb") + if self.indicator.available and self.gui_factory.get_tray_minimized(): + log.info("on_quit_cb: hiding window") + self.window.set_visible(False) return True - if not self.nsm_client and self.gui_factory.get_confirm_quit(): + elif not self.nsm_client and self.gui_factory.get_confirm_quit(): + log.info("on_quit_cb: confirm quit") dlg = Gtk.MessageDialog( parent=self.window, message_type=Gtk.MessageType.QUESTION, @@ -749,6 +752,7 @@ def on_quit_cb(self, *args, on_delete=False): if response != Gtk.ResponseType.OK: return on_delete + log.info("on_quit_cb: quitting") Gtk.main_quit() def on_shrink_channels_cb(self, widget): diff --git a/jack_mixer/indicator.py b/jack_mixer/indicator.py index 4c64cb4..c6b7a58 100644 --- a/jack_mixer/indicator.py +++ b/jack_mixer/indicator.py @@ -16,7 +16,7 @@ from gi.repository import Gtk from os import environ, path -prefix = environ.get('MESON_INSTALL_PREFIX', '/usr/local') +prefix = environ.get('MESON_INSTALL_PREFIX', '/usr') datadir = path.join(prefix, 'share') icondir = path.join(datadir, 'icons', 'hicolor', 'scalable', 'apps') @@ -25,7 +25,9 @@ def __init__(self, jack_mixer): self.app = jack_mixer if appindicator is None: log.warning('AppIndicator3 not found, indicator will not be available') + self.available = False return + self.available = True icon = os.path.join(icondir, 'jack_mixer.svg') self.indicator = appindicator.Indicator.new("Jack Mixer", icon, @@ -33,9 +35,6 @@ def __init__(self, jack_mixer): self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE) self.indicator.set_menu(self.create_menu()) - def available(self): - return not self.indicator is None - def create_menu(self): self.menu = Gtk.Menu() self.menu.set_title('Jack Mixer') diff --git a/jack_mixer/preferences.py b/jack_mixer/preferences.py index 4ed88fd..3a575d9 100644 --- a/jack_mixer/preferences.py +++ b/jack_mixer/preferences.py @@ -80,7 +80,7 @@ def create_ui(self): self.language_box.pack_start(Gtk.Label(_("Language:")), False, True, 5) self.language_box.pack_start(self.language_combo, True, True, 0) - if self.app.indicator.available(): + if self.app.indicator.available: self.tray_minimized_checkbutton = Gtk.CheckButton(_("Minimize to tray")) self.tray_minimized_checkbutton.set_tooltip_text( _("Minimize the application to the system tray when the window is closed") From ed82e77d54d4dd2cde9ac731c97ef85d5aa40ea7 Mon Sep 17 00:00:00 2001 From: Ryno Kotze Date: Thu, 19 Jan 2023 09:58:27 +0200 Subject: [PATCH 6/8] added authors --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 8c87bf7..66c321e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -14,6 +14,7 @@ Frédéric Péters * Frédéric Péters * Daniel Sheeler * Athanasios Silis +* Ryno Kotze # Translation From fd4fd9b2f9c10e42725880170e432d6d10b83b5c Mon Sep 17 00:00:00 2001 From: Ryno Kotze Date: Tue, 24 Jan 2023 22:02:08 +0200 Subject: [PATCH 7/8] fixed issue with -m --minimized when tray is not available --- jack_mixer/app.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/jack_mixer/app.py b/jack_mixer/app.py index de4d702..743a05f 100644 --- a/jack_mixer/app.py +++ b/jack_mixer/app.py @@ -1166,11 +1166,9 @@ def main(self): if hasattr(self, "paned_position"): self.paned.set_position(self.paned_position / self.width * width) else: - log.info("Minimizing main window") if self.indicator.available: + log.info("Minimizing main window") self.window.hide() - else: - self.window.iconify() signal.signal(signal.SIGUSR1, self.sighandler) signal.signal(signal.SIGTERM, self.sighandler) @@ -1204,7 +1202,7 @@ def main(): "--minimized", action="store_true", default=False, - help=_("start JACK Mixer minimized to system tray (default: %(default)s) If system tray is not available, JACK Mixer will start minimized to taskbar."), + help=_("start JACK Mixer minimized to system tray (default: %(default)s) If system tray is available."), ) parser.add_argument( "-c", From c72f791c99e5bb4175a275e2dd88163b8bb585e2 Mon Sep 17 00:00:00 2001 From: Ryno Kotze Date: Mon, 30 Jan 2023 16:44:41 +0200 Subject: [PATCH 8/8] added jackmixer systemd service --- jackmixer.service | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 jackmixer.service diff --git a/jackmixer.service b/jackmixer.service new file mode 100644 index 0000000..8a4a528 --- /dev/null +++ b/jackmixer.service @@ -0,0 +1,13 @@ +[Unit] +Description=Jack Mixer Service +Requires=pipewire.socket +After=graphical.target pwg.service + +[Service] +Type=simple +ExecStart=/usr/local/bin/jack_mixer --minimized --config /home/lemonxah/.config/jack_mixer/lemonxah.xml +ExecStop=/bin/kill -9 $MAINPID +Restart=always + +[Install] +WantedBy=default.target