diff --git a/mpd.py b/mpd.py index a4d629a..0973641 100644 --- a/mpd.py +++ b/mpd.py @@ -448,12 +448,10 @@ class MPDClient(MPDClientBase, GObject.Object): 'playing': (GObject.SignalFlags.RUN_FIRST, None, (object, object)), 'song_changed': (GObject.SignalFlags.RUN_FIRST, None, (object, object)), 'state_changed': (GObject.SignalFlags.RUN_FIRST, None, (object,)), + 'volume_changed': (GObject.SignalFlags.RUN_FIRST, None, (int,)), 'error': (GObject.SignalFlags.RUN_FIRST, None, (object,)) } - - # __events__ = {} - connected = False try_conn_timer = None @@ -461,6 +459,7 @@ class MPDClient(MPDClientBase, GObject.Object): current_song_id = None current_state = 'stop' + current_volume = 100 idletimeout = None _timeout = None @@ -790,6 +789,11 @@ class MPDClient(MPDClientBase, GObject.Object): status = self.status() song = self.currentsong() state = status.get('state') + vol = int(status.get('volume') or 100) + + if vol != self.current_volume: + self.emit('volume_changed', vol) + self.current_volume = vol if state != self.current_state: self.emit('state_changed', state) diff --git a/ui/ctrl_box.py b/ui/ctrl_box.py index 8b54866..77577e2 100644 --- a/ui/ctrl_box.py +++ b/ui/ctrl_box.py @@ -6,11 +6,13 @@ from gi.repository import Gtk, GObject from .image_button import ImageButton from .toggle_button import ToggleButton +from .volume import Volume class CtrlBox(Gtk.Box): __gsignals__ = { - 'clicked': (GObject.SignalFlags.RUN_FIRST, None, (str,)) + 'clicked': (GObject.SignalFlags.RUN_FIRST, None, (str,)), + 'volume_changed': (GObject.SignalFlags.RUN_FIRST, None, (int,)) } def __init__(self, spacing = 6): @@ -36,10 +38,12 @@ class CtrlBox(Gtk.Box): self.mode_btn.connect('clicked', self.on_btn_clicked) self.prev_btn.connect('clicked', self.on_btn_clicked) - # self.play_btn.connect('toggled', self.on_btn_clicked) self.play_btn.connect('clicked', self.on_btn_clicked) self.next_btn.connect('clicked', self.on_btn_clicked) self.vol_btn.connect('clicked', self.on_btn_clicked) + + self.volume_bar = Volume(self.vol_btn) + self.volume_bar.connect('volume_changed', lambda bar, vol: self.emit('volume_changed', vol)) def on_btn_clicked(self, btn): @@ -64,7 +68,11 @@ class CtrlBox(Gtk.Box): self.emit('clicked', 'next_btn') elif btn == self.vol_btn: - self.emit('clicked', 'vol_btn') + self.volume_bar.show_all() + + + def set_volume(self, vol): + self.volume_bar.set_volume(vol) def toggle_play_btn(self, on = True): diff --git a/ui/image_button.py b/ui/image_button.py index acd3c65..4d1560c 100644 --- a/ui/image_button.py +++ b/ui/image_button.py @@ -24,26 +24,6 @@ class ImageButton(Gtk.Button): self.set_image(filepath) - css_provider = Gtk.CssProvider() - style = f""" - #ImageButton {{ - border: 0; - border-radius: 50%; - background-color: transparent; - border-color:transparent; - outline: transparent; - }} - #ImageButton:hover {{ - background-color: rgba(255,255,255,.1); - outline: transparent; - box-shadow:none; - }} - """ - css_provider.load_from_data(style.encode('UTF-8')) - - context = self.get_style_context() - context.add_provider(css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER) - def set_image(self, filepath): if self._image_path == filepath: diff --git a/ui/slider.py b/ui/slider.py deleted file mode 100644 index e28ab22..0000000 --- a/ui/slider.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python3 - -import gi -gi.require_version('Gtk', '3.0') -from gi.repository import Gtk - -class Slider(Gtk.Scale): - def __init__(self, width = 256, height = 5): - Gtk.Scale.__init__(self) - - self.set_name('Slider') - self.set_range(0, 100) - self.set_size_request(width, height) - self.set_draw_value(False) - - - - css_provider = Gtk.CssProvider() - style = f""" - #Slider {{ - outline: none; - }} - #Slider trough {{ - background-color: rgba(129, 161, 193, 0.35); - outline: none; - }} - #Slider trough highlight {{ - background-color: rgba(163, 190, 140, 0.75); - }} - #Slider slider {{ - background-color: transparent; - border-color: transparent; - outline: none; - }} - """ - css_provider.load_from_data(style.encode('UTF-8')) - - context = self.get_style_context() - context.add_provider(css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER) diff --git a/ui/timebar.py b/ui/timebar.py index 3bc2a1e..2907783 100644 --- a/ui/timebar.py +++ b/ui/timebar.py @@ -4,8 +4,6 @@ import gi, threading gi.require_version('Gtk', '3.0') from gi.repository import Gtk, GObject -from .slider import Slider - def time_to_str(stamp = 0): m = stamp // 60 s = stamp % 60 @@ -16,6 +14,16 @@ def time_to_str(stamp = 0): return f"{m}:{s}" +class Slider(Gtk.Scale): + def __init__(self, width = 256, height = 5): + Gtk.Scale.__init__(self) + + self.set_name('Slider') + self.set_range(0, 100) + self.set_size_request(width, height) + self.set_draw_value(False) + + class Timebar(Gtk.Fixed): __gsignals__ = { diff --git a/ui/title_text.py b/ui/title_text.py index b21f988..8e6d39a 100644 --- a/ui/title_text.py +++ b/ui/title_text.py @@ -9,7 +9,7 @@ class TitleText(Gtk.Label): def __init__(self, text = 'mpd loading...'): Gtk.Box.__init__(self) - self.set_size_request(256, 20) + self.set_size_request(266, 20) self.set_name('text') self.set_hexpand(True) self.set_hexpand_set(True) diff --git a/ui/volume.py b/ui/volume.py new file mode 100644 index 0000000..f29bf02 --- /dev/null +++ b/ui/volume.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 + +import gi, threading +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, GObject + + +class Volume(Gtk.Popover): + + __gsignals__ = { + 'volume_changed': (GObject.SignalFlags.RUN_FIRST, None, (int,)) + } + + timer = None + volume = 100 + + def __init__(self, btn = None): + Gtk.Popover.__init__(self) + + self.set_relative_to(btn) + + slider = Gtk.Scale.new(Gtk.Orientation.VERTICAL) + slider.set_name('Slider') + slider.set_range(0, 100) + slider.set_size_request(5, 100) + slider.set_draw_value(False) + slider.set_inverted(True) + + self.slider = slider + self.slider.connect('change-value', self.debounce) + self.set_volume(100) + + self.add(slider) + + + def set_volume(self, vol = 0): + if self.volume == int(vol): + return + + self.volume = int(vol) + self.slider.set_value(self.volume) + + + def on_volume_changed(self): + vol = int(self.slider.get_value()) + self.emit('volume_changed', vol) + + + def debounce(self, slider, a, b): + if self.timer is not None: + self.timer.cancel() + + self.timer = threading.Timer(0.2, self.on_volume_changed) + self.timer.start() \ No newline at end of file diff --git a/window.py b/window.py index bf8c5b8..85325db 100644 --- a/window.py +++ b/window.py @@ -8,7 +8,6 @@ from gi.repository import Gtk, Gdk, GLib, GdkPixbuf, GObject from utils import blur_image, pic_to_pixbuf, base64_to_pixbuf, idle from ui.image import ScaleImage -from ui.slider import Slider from ui.image_button import ImageButton from ui.title_text import TitleText from ui.ctrl_box import CtrlBox @@ -30,12 +29,18 @@ class SonistWindow(Gtk.Window): self.app = app self.mpd = app.mpd + self.style_context = Gtk.StyleContext() + self.screen = Gdk.Screen.get_default() + self.css_provider = None + self.set_default_style() + self.connect("destroy", lambda win: app.remove_window(win)) self.mpd.connect('offline', lambda o: self.reset_player()) self.mpd.connect('online', lambda o: self.sync_state(None, None, True)) self.mpd.connect('state_changed', lambda o, stat: self.update_play_stat(stat == 'play')) self.mpd.connect('song_changed', lambda o, stat, song: self.sync_state(stat, song, False)) + self.mpd.connect('volume_changed', lambda o, vol: self.update_volume(vol)) self.mpd.connect('playing', lambda o, stat, song: self.update_playtime(stat)) self.set_name('SonistWindow') @@ -57,7 +62,7 @@ class SonistWindow(Gtk.Window): # 菜单按钮 menu_btn = ImageButton('./usr/share/sonist/menu.png') popup_menu = OptionMenu(app) - menu_btn.connect('clicked', lambda w: popup_menu.show(w)) + menu_btn.connect('clicked', lambda btn: popup_menu.show(btn)) layout.put(menu_btn, 276, 6) # 唱片 @@ -81,7 +86,6 @@ class SonistWindow(Gtk.Window): # title - self.title_box = TitleText() layout.put(self.title_box, 27, 244) @@ -90,12 +94,13 @@ class SonistWindow(Gtk.Window): # 播放进度 self.timebar = Timebar() self.timebar.connect('seeked', lambda a,v: self.mpd.seekcur(v)) - layout.put(self.timebar, 24, 276) + layout.put(self.timebar, 24, 272) # 控制条 self.ctrl_box = CtrlBox() self.ctrl_box.connect('clicked', self.ctrl_clicked) + self.ctrl_box.connect('volume_changed', lambda box, vol: self.mpd.setvol(vol)) layout.put(self.ctrl_box, 48, 312) @@ -103,7 +108,52 @@ class SonistWindow(Gtk.Window): self.add(layout) - + + def set_default_style(self): + provider = Gtk.CssProvider() + css = f""" + #SonistWindow {{ + background-image: url('./usr/share/sonist/album.png'); + background-size: 100% 100%; + background-position: center; + }} + #text {{ + color: #f2f5fc; + }} + + #ImageButton {{ + border: 0; + border-radius: 50%; + background-color: transparent; + border-color:transparent; + outline: transparent; + }} + #ImageButton:hover {{ + background-color: rgba(255,255,255,.1); + outline: transparent; + box-shadow:none; + }} + + #Slider {{ + outline: none; + }} + #Slider trough {{ + background-color: rgba(129, 161, 193, 0.35); + outline: none; + }} + #Slider trough highlight {{ + background-color: rgba(163, 190, 140, 0.75); + }} + #Slider slider {{ + background-color: transparent; + border-color: transparent; + outline: none; + }} + """ + provider.load_from_data(css.encode('UTF-8')) + self.style_context.add_provider_for_screen(self.screen, provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) + + def get_mpd_stat(self): @@ -126,19 +176,17 @@ class SonistWindow(Gtk.Window): css = f""" #SonistWindow {{ background-image: url('/tmp/sonist_album_cache'); - background-size: 100% 100%; - background-position: center; - }} - #text {{ - color: #f2f5fc; }} """ # 加载CSS样式 - css_provider = Gtk.CssProvider() - css_provider.load_from_data(css.encode('UTF-8')) - context = Gtk.StyleContext() - screen = Gdk.Screen.get_default() - context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) + if self.css_provider is None: + self.css_provider = Gtk.CssProvider() + else: + self.style_context.remove_provider_for_screen(self.screen, self.css_provider) + + self.css_provider.load_from_data(css.encode('UTF-8')) + + self.style_context.add_provider_for_screen(self.screen, self.css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) def ctrl_clicked(self, box, btn): @@ -215,6 +263,10 @@ class SonistWindow(Gtk.Window): def update_playtime(self, stat = {}): times = stat['time'].split(':') self.timebar.update_time(int(times[0]), int(times[1])) + + @idle + def update_volume(self, vol = 100): + self.ctrl_box.set_volume(vol) @idle def sync_state(self, stat = None, song = None, first = False): @@ -226,6 +278,7 @@ class SonistWindow(Gtk.Window): if first: self.update_play_stat(played == 'play') + self.update_volume(int(self.stat.get('volume'))) if self.stat.get('single') == '1': self.ctrl_box.toggle_mode_btn(mode = 'single') @@ -266,6 +319,8 @@ class SonistWindow(Gtk.Window): except Exception as err: pass + + @idle def reset_player(self): self.ctrl_box.disabled = True