emmental/emmental/__init__.py

182 lines
7.1 KiB
Python

# 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", "artwork", "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", "artwork"]:
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