# Copyright 2022 (c) Anna Schumaker. """Set up our Application.""" import pathlib from . import gsetup from . import audio from . import db from . import header from . import mpris2 from . import nowplaying from . import options from . import window from gi.repository import GObject from gi.repository import GLib from gi.repository import Gio from gi.repository import Adw MAJOR_VERSION = 3 MINOR_VERSION = 0 VERSION_STRING = f"Emmental {MAJOR_VERSION}.{MINOR_VERSION}{gsetup.DEBUG_STR}" class Application(Adw.Application): """Our custom Adw.Application.""" db = GObject.Property(type=db.Connection) mpris = GObject.Property(type=mpris2.Connection) player = GObject.Property(type=audio.Player) win = GObject.Property(type=window.Window) autopause = GObject.Property(type=int, default=-1, minimum=-1, maximum=99) def __init__(self): """Initialize an Application.""" super().__init__(application_id=gsetup.APPLICATION_ID, resource_base_path=gsetup.RESOURCE_PATH, flags=Gio.ApplicationFlags.HANDLES_OPEN) self.add_main_option_entries([options.Version]) def __load_file(self, file: pathlib.Path) -> None: self.player.stop() self.player.file = file self.player.play() def __load_path(self, src: GObject.GObject, path: pathlib.Path) -> None: self.__load_file(path) def __on_seek(self, nowplay: nowplaying.Card, newpos: float) -> None: """Handle a seek event.""" self.player.seek(newpos) self.mpris.player.seeked(newpos) def __seek(self, player: mpris2.player.Player, offset: float) -> None: self.player.seek(self.player.position + offset) def __set_position(self, player: mpris2.player.Player, trackid: str, position: float) -> None: self.player.seek(position) def __set_replaygain(self, *args) -> None: enabled = self.db.settings["audio.replaygain.enabled"] mode = self.db.settings["audio.replaygain.mode"] mode = "track" if mode == "auto" else mode self.player.set_replaygain(enabled, mode) def build_header(self) -> header.Header: """Build a new header instance.""" hdr = header.Header(sql=self.db, title=VERSION_STRING) hdr.bind_property("volume", self.player, "volume") for (setting, property) in [("audio.volume", "volume"), ("audio.replaygain.enabled", "rg-enabled"), ("audio.replaygain.mode", "rg-mode")]: self.db.settings.bind_setting(setting, hdr, property) hdr.connect("notify::rg-enabled", self.__set_replaygain) hdr.connect("notify::rg-mode", self.__set_replaygain) hdr.connect("track-requested", self.__load_path) self.__set_replaygain() return hdr def build_now_playing(self) -> nowplaying.Card: """Build a new now playing card.""" playing = nowplaying.Card() playing.bind_property("autopause", self, "autopause", GObject.BindingFlags.BIDIRECTIONAL) for prop in ["title", "album", "artist", "album-artist", "playing", "position", "duration", "have-track"]: self.player.bind_property(prop, playing, prop) self.db.settings.bind_setting("now-playing.prefer-artist", playing, "prefer-artist") playing.connect("play", self.player.play) playing.connect("pause", self.player.pause) playing.connect("seek", self.__on_seek) return playing def build_window(self) -> window.Window: """Build a new window instance.""" win = window.Window(VERSION_STRING, header=self.build_header(), now_playing=self.build_now_playing()) for (setting, property) in [("window.width", "default-width"), ("window.height", "default-height"), ("now-playing.size", "now-playing-size")]: self.db.settings.bind_setting(setting, win, property) return win def connect_mpris2(self) -> None: """Connect the mpris2 properties and functions.""" self.mpris.app.link_property("Fullscreen", self.win, "fullscreened") self.mpris.app.connect("Raise", self.win.present) self.mpris.app.connect("Quit", self.win.close) for tag in ["artist", "album", "album-artist", "album-disc-number", "title", "track-number", "duration", "file"]: self.player.bind_property(tag, self.mpris.player, tag) for (prop, mpris_prop) in [("have-track", "CanPlay"), ("have-track", "CanPause"), ("have-track", "CanSeek"), ("status", "PlaybackStatus"), ("position", "Position")]: self.player.bind_property(prop, self.mpris.player, mpris_prop) self.mpris.player.link_property("Volume", self.win.header, "volume") self.mpris.player.connect("OpenPath", self.__load_path) self.mpris.player.connect("Pause", self.player.pause) self.mpris.player.connect("Play", self.player.play) self.mpris.player.connect("PlayPause", self.player.play_pause) self.mpris.player.connect("Seek", self.__seek) self.mpris.player.connect("SetPosition", self.__set_position) self.mpris.player.connect("Stop", self.player.stop) def do_handle_local_options(self, opts: GLib.VariantDict) -> int: """Handle any command line options.""" if opts.contains("version"): print(VERSION_STRING) gsetup.print_versions() return 0 return -1 def do_startup(self) -> None: """Handle the Adw.Application::startup signal.""" Adw.Application.do_startup(self) self.db = db.Connection() self.mpris = mpris2.Connection() self.player = audio.Player() gsetup.add_style() self.db.load() self.win = self.build_window() self.add_window(self.win) self.connect_mpris2() def do_activate(self) -> None: """Handle the Adw.Application::activate signal.""" Adw.Application.do_activate(self) self.win.present() def do_open(self, files: list, n_files: int, hint: str) -> None: """Play an audio file passed from the command line.""" if n_files > 0: self.__load_file(pathlib.Path(files[0].get_path())) self.activate() def do_shutdown(self) -> None: """Handle the Adw.Application::shutdown signal.""" Adw.Application.do_shutdown(self) if self.player is not None: self.player.shutdown() self.player = None if self.win is not None: self.win.close() self.win = None if self.mpris is not None: self.mpris.disconnect() self.mpris = None if self.db is not None: self.db.close() self.db = None