Got a ton of things hooked up

This commit is contained in:
Sumner Evans
2019-06-15 19:44:50 -06:00
parent 941ad87961
commit 233f880883
7 changed files with 126 additions and 77 deletions

View File

@@ -1,13 +1,11 @@
#! /usr/bin/env python3 #! /usr/bin/env python3
import threading
import argparse
import sys import sys
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
from gi.repository import GLib, Gio, Gtk from gi.repository import Gtk
from .ui import LibremsonicApp from .app import LibremsonicApp
def main(): def main():

View File

@@ -1,4 +1,5 @@
import os import os
from typing import Callable
import mpv import mpv
@@ -6,10 +7,10 @@ import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
from gi.repository import Gio, Gtk, GLib, Gdk from gi.repository import Gio, Gtk, GLib, Gdk
from libremsonic.config import get_config, save_config from .ui.main import MainWindow
from .ui.configure_servers import ConfigureServersDialog
from .main import MainWindow from .state_manager import ApplicationState
from .configure_servers import ConfigureServersDialog
class LibremsonicApp(Gtk.Application): class LibremsonicApp(Gtk.Application):
@@ -21,8 +22,7 @@ class LibremsonicApp(Gtk.Application):
**kwargs, **kwargs,
) )
self.window = None self.window = None
self.config_file = None self.state = ApplicationState()
self.config = None
# Specify Command Line Options # Specify Command Line Options
self.add_main_option( self.add_main_option(
@@ -30,24 +30,30 @@ class LibremsonicApp(Gtk.Application):
'Specify a configuration file. Defaults to ~/.config/libremsonic/config.json', 'Specify a configuration file. Defaults to ~/.config/libremsonic/config.json',
None) None)
self.player = mpv.MPV() self.player = mpv.MPV(pause=True)
self.is_playing = False
@self.player.property_observer('time-pos')
def time_observer(_name, value):
# TODO handle this
# self.window.player_controls.update_scrubber
pass
# Handle command line option parsing. # Handle command line option parsing.
def do_command_line(self, command_line): def do_command_line(self, command_line):
options = command_line.get_options_dict() options = command_line.get_options_dict()
# Config File # Config File
self.config_file = options.lookup_value('config') config_file = options.lookup_value('config')
if self.config_file: if config_file:
self.config_file = self.config_file.get_bytestring().decode( config_file = config_file.get_bytestring().decode('utf-8')
'utf-8')
else: else:
# Default to ~/.config/libremsonic. # Default to ~/.config/libremsonic.
config_folder = (os.environ.get('XDG_CONFIG_HOME') config_folder = (os.environ.get('XDG_CONFIG_HOME')
or os.path.expanduser('~/.config')) or os.path.expanduser('~/.config'))
config_folder = os.path.join(config_folder, 'libremsonic') config_folder = os.path.join(config_folder, 'libremsonic')
self.config_file = os.path.join(config_folder, 'config.yaml') config_file = os.path.join(config_folder, 'config.yaml')
self.state.config_file = config_file
# Have to do this or else the application doesn't work. Not entirely # Have to do this or else the application doesn't work. Not entirely
# sure why, but C-bindings... # sure why, but C-bindings...
@@ -57,66 +63,83 @@ class LibremsonicApp(Gtk.Application):
def do_startup(self): def do_startup(self):
Gtk.Application.do_startup(self) Gtk.Application.do_startup(self)
# Add action for configuring servers def add_action(name: str, fn):
action = Gio.SimpleAction.new('configure_servers', None) """Registers an action with the application."""
action.connect('activate', self.on_configure_servers) action = Gio.SimpleAction.new(name, None)
self.add_action(action) action.connect('activate', fn)
self.add_action(action)
# Add action for configuring servers # Add action for menu items.
action = Gio.SimpleAction.new('play_pause', None) add_action('configure-servers', self.on_configure_servers)
action.connect('activate', self.on_play_pause)
self.add_action(action) # Add actions for player controls
add_action('play-pause', self.on_play_pause)
add_action('next-track', self.on_next_track)
add_action('prev-track', self.on_prev_track)
add_action('repeat-press', self.on_repeat_press)
add_action('shuffle-press', self.on_shuffle_press)
def do_activate(self): def do_activate(self):
# We only allow a single window and raise any existing ones # We only allow a single window and raise any existing ones
if not self.window: if not self.window:
# Windows are associated with the application # Windows are associated with the application when the last one is
# when the last one is closed the application shuts down # closed the application shuts down.
self.window = MainWindow(application=self, title="LibremSonic") self.window = MainWindow(application=self, title="LibremSonic")
# Configure the CSS provider so that we can style elements on the
# window.
css_provider = Gtk.CssProvider() css_provider = Gtk.CssProvider()
css_provider.load_from_path( css_provider.load_from_path(
os.path.join(os.path.dirname(__file__), 'app_styles.css')) os.path.join(os.path.dirname(__file__), 'ui/app_styles.css'))
context = Gtk.StyleContext() context = Gtk.StyleContext()
screen = Gdk.Screen.get_default() screen = Gdk.Screen.get_default()
context.add_provider_for_screen(screen, css_provider, context.add_provider_for_screen(screen, css_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER) Gtk.STYLE_PROVIDER_PRIORITY_USER)
# Display the window.
self.window.show_all() self.window.show_all()
self.window.present() self.window.present()
# Load the configuration and update the UI with the curent server, if # Load the configuration and update the UI with the curent server, if
# it exists. If there is no current server, show the dialog to select a # it exists.
# server. self.state.load_config()
self.load_config()
if self.config.current_server is None: # If there is no current server, show the dialog to select a server.
if self.state.config.current_server is None:
self.show_configure_servers_dialog() self.show_configure_servers_dialog()
else: else:
self.on_connected_server_changed(None, self.config.current_server) self.on_connected_server_changed(None,
self.state.config.current_server)
# ########## ACTION HANDLERS ########## # # ########## ACTION HANDLERS ########## #
def on_configure_servers(self, action, param): def on_configure_servers(self, action, param):
self.show_configure_servers_dialog() self.show_configure_servers_dialog()
def on_play_pause(self, action, param): def on_play_pause(self, action, param):
if self.is_playing: self.player.command('cycle', 'pause')
self.player.command('cycle', 'pause') self.state.playing = not self.state.playing
else:
self.player.loadfile(
'/home/sumner/Music/Sapphyre/All You See Is Christ (live).mp3')
self.is_playing = not self.is_playing
self.update_window() self.update_window()
def on_next_track(self, action, params):
self.player.playlist_next()
def on_prev_track(self, action, params):
self.player.playlist_prev()
def on_repeat_press(self, action, params):
print('repeat press')
def on_shuffle_press(self, action, params):
print('shuffle press')
def on_server_list_changed(self, action, servers): def on_server_list_changed(self, action, servers):
self.config.servers = servers self.state.config.servers = servers
self.save_config() self.state.save_config()
def on_connected_server_changed(self, action, current_server): def on_connected_server_changed(self, action, current_server):
self.config.current_server = current_server self.state.config.current_server = current_server
self.save_config() self.state.save_config()
# Update the window according to the new server configuration. # Update the window according to the new server configuration.
self.update_window() self.update_window()
@@ -124,22 +147,12 @@ class LibremsonicApp(Gtk.Application):
# ########## HELPER METHODS ########## # # ########## HELPER METHODS ########## #
def show_configure_servers_dialog(self): def show_configure_servers_dialog(self):
"""Show the Connect to Server dialog.""" """Show the Connect to Server dialog."""
dialog = ConfigureServersDialog(self.window, self.config) dialog = ConfigureServersDialog(self.window, self.state.config)
dialog.connect('server-list-changed', self.on_server_list_changed) dialog.connect('server-list-changed', self.on_server_list_changed)
dialog.connect('connected-server-changed', dialog.connect('connected-server-changed',
self.on_connected_server_changed) self.on_connected_server_changed)
dialog.run() dialog.run()
dialog.destroy() dialog.destroy()
def load_config(self):
self.config = get_config(self.config_file)
def save_config(self):
save_config(self.config, self.config_file)
def update_window(self): def update_window(self):
self.window.update( self.window.update(self.state)
server=self.config.servers[self.config.current_server],
current_song=None,
is_playing=self.is_playing,
)

View File

@@ -0,0 +1,19 @@
from typing import List, Any
from .config import get_config, save_config, AppConfiguration
class ApplicationState:
config: AppConfiguration = AppConfiguration.get_default_configuration()
current_song: Any # TODO fix
config_file: str
playing: bool = False
song_progress: float = 0.0
up_next: List[Any] # TODO should be song
volume: int = 100
def load_config(self):
self.config = get_config(self.config_file)
def save_config(self):
save_config(self.config, self.config_file)

View File

@@ -1 +0,0 @@
from .app import LibremsonicApp

View File

@@ -13,11 +13,11 @@
} }
#player-controls-bar #song-scrubber { #player-controls-bar #song-scrubber {
min-width: 250px; min-width: 300px;
} }
#player-controls-bar #volume-slider { #player-controls-bar #volume-slider {
min-width: 120px; min-width: 90px;
} }
#player-controls-bar #volume-slider value { #player-controls-bar #volume-slider value {
@@ -27,3 +27,7 @@
#player-controls-bar #song-name { #player-controls-bar #song-name {
margin-bottom: 3px; margin-bottom: 3px;
} }
#player-controls-bar #album-name {
margin-bottom: 3px;
}

View File

@@ -7,6 +7,7 @@ from gi.repository import Gio, Gtk
from .albums import AlbumsPanel from .albums import AlbumsPanel
from .player_controls import PlayerControls from .player_controls import PlayerControls
from libremsonic.server import Server from libremsonic.server import Server
from libremsonic.state_manager import ApplicationState
class MainWindow(Gtk.ApplicationWindow): class MainWindow(Gtk.ApplicationWindow):
@@ -38,15 +39,17 @@ class MainWindow(Gtk.ApplicationWindow):
self.add(flowbox) self.add(flowbox)
# TODO the song should eventually be an API object... # TODO the song should eventually be an API object...
def update(self, server: Optional[Server], current_song, is_playing): def update(self, state: ApplicationState):
# Update the Connected to label on the popup menu. # Update the Connected to label on the popup menu.
if server: if state.config.current_server >= 0:
self.connected_to_label.set_markup(f'Connected to {server.name}') server_name = state.config.servers[
state.config.current_server].name
self.connected_to_label.set_markup(f'Connected to {server_name}')
else: else:
self.connected_to_label.set_markup( self.connected_to_label.set_markup(
f'<span style="italic">Not Connected to a Server</span>') f'<span style="italic">Not Connected to a Server</span>')
self.player_controls.update(current_song, is_playing) self.player_controls.update(state)
def create_stack(self, **kwargs): def create_stack(self, **kwargs):
stack = Gtk.Stack() stack = Gtk.Stack()
@@ -95,7 +98,7 @@ class MainWindow(Gtk.ApplicationWindow):
menu_items = [ menu_items = [
(None, self.connected_to_label), (None, self.connected_to_label),
('app.configure_servers', Gtk.ModelButton('Connect to Server')), ('app.configure-servers', Gtk.ModelButton('Connect to Server')),
] ]
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

View File

@@ -3,6 +3,8 @@ import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
from gi.repository import Gio, Gtk, Gtk from gi.repository import Gio, Gtk, Gtk
from libremsonic.state_manager import ApplicationState
class PlayerControls(Gtk.ActionBar): class PlayerControls(Gtk.ActionBar):
""" """
@@ -13,18 +15,18 @@ class PlayerControls(Gtk.ActionBar):
Gtk.ActionBar.__init__(self) Gtk.ActionBar.__init__(self)
self.set_name('player-controls-bar') self.set_name('player-controls-bar')
self.song_display = self.create_song_display() song_display = self.create_song_display()
self.playback_controls = self.create_playback_controls() playback_controls = self.create_playback_controls()
self.up_next_volume = self.create_up_next_volume() up_next_volume = self.create_up_next_volume()
self.pack_start(self.song_display) self.pack_start(song_display)
self.set_center_widget(self.playback_controls) self.set_center_widget(playback_controls)
self.pack_end(self.up_next_volume) self.pack_end(up_next_volume)
def update(self, current_song, playing): def update(self, state: ApplicationState):
icon = 'pause' if state.playing else 'start'
self.play_button.get_child().set_from_icon_name( self.play_button.get_child().set_from_icon_name(
f"media-playback-{'start' if not playing else 'pause'}-symbolic", f"media-playback-{icon}-symbolic", Gtk.IconSize.LARGE_TOOLBAR)
Gtk.IconSize.LARGE_TOOLBAR)
def create_song_display(self): def create_song_display(self):
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
@@ -42,8 +44,15 @@ class PlayerControls(Gtk.ActionBar):
details_box.add(self.song_name) details_box.add(self.song_name)
self.album_name = Gtk.Label('Album name', halign=Gtk.Align.START) self.album_name = Gtk.Label('Album name', halign=Gtk.Align.START)
self.album_name.set_name('album-name')
details_box.add(self.album_name) details_box.add(self.album_name)
self.artist_name = Gtk.Label('<i>Artist name</i>',
halign=Gtk.Align.START,
use_markup=True)
self.artist_name.set_name('artist-name')
details_box.add(self.artist_name)
details_box.pack_start(Gtk.Box(), True, True, 0) details_box.pack_start(Gtk.Box(), True, True, 0)
box.pack_start(details_box, True, True, 5) box.pack_start(details_box, True, True, 5)
@@ -64,8 +73,8 @@ class PlayerControls(Gtk.ActionBar):
self.song_scrubber.set_draw_value(False) self.song_scrubber.set_draw_value(False)
scrubber_box.pack_start(self.song_scrubber, True, True, 0) scrubber_box.pack_start(self.song_scrubber, True, True, 0)
self.song_lengh_label = Gtk.Label('0:00') self.song_length_label = Gtk.Label('0:00')
scrubber_box.pack_start(self.song_lengh_label, False, False, 5) scrubber_box.pack_start(self.song_length_label, False, False, 5)
box.add(scrubber_box) box.add(scrubber_box)
@@ -75,12 +84,14 @@ class PlayerControls(Gtk.ActionBar):
# Repeat button # Repeat button
self.repeat_button = self.button_with_icon( self.repeat_button = self.button_with_icon(
'media-playlist-repeat-symbolic') 'media-playlist-repeat-symbolic')
self.repeat_button.set_action_name('app.repeat-press')
buttons.pack_start(self.repeat_button, False, False, 5) buttons.pack_start(self.repeat_button, False, False, 5)
# Previous button # Previous button
previous_button = self.button_with_icon( previous_button = self.button_with_icon(
'media-skip-backward-symbolic', 'media-skip-backward-symbolic',
icon_size=Gtk.IconSize.LARGE_TOOLBAR) icon_size=Gtk.IconSize.LARGE_TOOLBAR)
previous_button.set_action_name('app.prev-track')
buttons.pack_start(previous_button, False, False, 5) buttons.pack_start(previous_button, False, False, 5)
# Play button # Play button
@@ -89,18 +100,20 @@ class PlayerControls(Gtk.ActionBar):
relief=True, relief=True,
icon_size=Gtk.IconSize.LARGE_TOOLBAR) icon_size=Gtk.IconSize.LARGE_TOOLBAR)
self.play_button.set_name('play-button') self.play_button.set_name('play-button')
self.play_button.set_action_name('app.play_pause') self.play_button.set_action_name('app.play-pause')
buttons.pack_start(self.play_button, False, False, 0) buttons.pack_start(self.play_button, False, False, 0)
# Next button # Next button
next_button = self.button_with_icon( next_button = self.button_with_icon(
'media-skip-forward-symbolic', 'media-skip-forward-symbolic',
icon_size=Gtk.IconSize.LARGE_TOOLBAR) icon_size=Gtk.IconSize.LARGE_TOOLBAR)
next_button.set_action_name('app.next-track')
buttons.pack_start(next_button, False, False, 5) buttons.pack_start(next_button, False, False, 5)
# Shuffle button # Shuffle button
self.shuffle_button = self.button_with_icon( self.shuffle_button = self.button_with_icon(
'media-playlist-shuffle-symbolic') 'media-playlist-shuffle-symbolic')
self.shuffle_button.set_action_name('app.shuffle-press')
buttons.pack_start(self.shuffle_button, False, False, 5) buttons.pack_start(self.shuffle_button, False, False, 5)
buttons.pack_start(Gtk.Box(), True, True, 0) buttons.pack_start(Gtk.Box(), True, True, 0)
@@ -127,7 +140,7 @@ class PlayerControls(Gtk.ActionBar):
volume_slider = Gtk.Scale.new_with_range( volume_slider = Gtk.Scale.new_with_range(
orientation=Gtk.Orientation.HORIZONTAL, min=0, max=100, step=5) orientation=Gtk.Orientation.HORIZONTAL, min=0, max=100, step=5)
volume_slider.set_name('volume-slider') volume_slider.set_name('volume-slider')
volume_slider.set_value_pos(Gtk.PositionType.RIGHT) volume_slider.set_draw_value(False)
volume_slider.set_value(100) volume_slider.set_value(100)
box.pack_start(volume_slider, True, True, 0) box.pack_start(volume_slider, True, True, 0)