mirror of
https://github.com/vale981/clay
synced 2025-03-04 17:11:41 -05:00
Cleanups
This commit is contained in:
parent
77e3f44fa5
commit
5b88ae681a
7 changed files with 160 additions and 51 deletions
31
.flake8
Normal file
31
.flake8
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
[flake8]
|
||||||
|
# ignore = D203
|
||||||
|
max-line-length=120
|
||||||
|
|
||||||
|
ignore =
|
||||||
|
# blank line at end of file
|
||||||
|
W391
|
||||||
|
|
||||||
|
exclude =
|
||||||
|
# Generated stuff
|
||||||
|
gen,
|
||||||
|
# Coverage HTML report
|
||||||
|
htmlcov,
|
||||||
|
# Logs
|
||||||
|
log,
|
||||||
|
# Requirements
|
||||||
|
requirements,
|
||||||
|
# Temporary stuff
|
||||||
|
tmp,
|
||||||
|
# Virtual envs
|
||||||
|
.env,
|
||||||
|
.env2,
|
||||||
|
.env3,
|
||||||
|
.env36,
|
||||||
|
.venv,
|
||||||
|
.git,
|
||||||
|
wsgi.py,
|
||||||
|
utils,
|
||||||
|
# VLC bindings
|
||||||
|
vlc.py
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
from clay.app import main
|
|
||||||
|
|
99
clay/app.py
99
clay/app.py
|
@ -1,23 +1,26 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# pylint: disable=wrong-import-position
|
||||||
|
# pylint: disable=too-many-instance-attributes
|
||||||
|
"""
|
||||||
|
Main app entrypoint.
|
||||||
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
sys.path.append('.')
|
sys.path.append('.') # noqa
|
||||||
|
|
||||||
import urwid
|
import urwid
|
||||||
|
|
||||||
from clay.player import player
|
from clay.player import player
|
||||||
from clay.widgets import PlayProgress
|
from clay.widgets import PlayProgress
|
||||||
from clay.pages import StartUp, Error
|
from clay.pages import StartUp
|
||||||
from clay.mylibrary import MyLibrary
|
from clay.mylibrary import MyLibrary
|
||||||
from clay.myplaylists import MyPlaylists
|
from clay.myplaylists import MyPlaylists
|
||||||
from clay.playerqueue import Queue
|
from clay.playerqueue import Queue
|
||||||
from clay.settings import Settings
|
from clay.settings import Settings
|
||||||
|
from clay.notifications import NotificationArea
|
||||||
# from clay import hotkeys
|
# from clay import hotkeys
|
||||||
|
|
||||||
|
|
||||||
loop = None
|
|
||||||
|
|
||||||
PALETTE = [
|
PALETTE = [
|
||||||
('logo', '', '', '', '#F54', ''),
|
('logo', '', '', '', '#F54', ''),
|
||||||
|
|
||||||
|
@ -45,24 +48,39 @@ PALETTE = [
|
||||||
|
|
||||||
('flag', '', '', '', '#AAA', ''),
|
('flag', '', '', '', '#AAA', ''),
|
||||||
('flag-active', '', '', '', '#F54', ''),
|
('flag-active', '', '', '', '#F54', ''),
|
||||||
|
|
||||||
|
('notification', '', '', '', '#F54', '#222'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class AppWidget(urwid.Frame):
|
class AppWidget(urwid.Frame):
|
||||||
|
"""
|
||||||
|
Root widget.
|
||||||
|
|
||||||
|
Handles tab switches, global keypresses etc.
|
||||||
|
"""
|
||||||
class Tab(urwid.Text):
|
class Tab(urwid.Text):
|
||||||
|
"""
|
||||||
|
Represents a single tab in header tabbar.
|
||||||
|
"""
|
||||||
def __init__(self, page):
|
def __init__(self, page):
|
||||||
self.page = page
|
self.page = page
|
||||||
# self.attrwrap = urwid.AttrWrap(urwid.Text(), 'panel')
|
|
||||||
super(AppWidget.Tab, self).__init__(
|
super(AppWidget.Tab, self).__init__(
|
||||||
self.get_title()
|
self.get_title()
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_active(self, active):
|
def set_active(self, active):
|
||||||
|
"""
|
||||||
|
Mark tab visually as active.
|
||||||
|
"""
|
||||||
self.set_text(
|
self.set_text(
|
||||||
[('panel_focus' if active else 'panel', self.get_title())]
|
[('panel_focus' if active else 'panel', self.get_title())]
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_title(self):
|
def get_title(self):
|
||||||
|
"""
|
||||||
|
Render tab title.
|
||||||
|
"""
|
||||||
return ' {} {} '.format(
|
return ' {} {} '.format(
|
||||||
self.page.key,
|
self.page.key,
|
||||||
self.page.name
|
self.page.name
|
||||||
|
@ -71,7 +89,6 @@ class AppWidget(urwid.Frame):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.pages = [
|
self.pages = [
|
||||||
StartUp(self),
|
StartUp(self),
|
||||||
Error(self),
|
|
||||||
MyLibrary(self),
|
MyLibrary(self),
|
||||||
MyPlaylists(self),
|
MyPlaylists(self),
|
||||||
Queue(self),
|
Queue(self),
|
||||||
|
@ -83,6 +100,7 @@ class AppWidget(urwid.Frame):
|
||||||
in self.pages
|
in self.pages
|
||||||
if hasattr(page, 'key')
|
if hasattr(page, 'key')
|
||||||
]
|
]
|
||||||
|
NotificationArea.set_app(self)
|
||||||
self.header = urwid.Pile([
|
self.header = urwid.Pile([
|
||||||
# urwid.Divider('\u2500'),
|
# urwid.Divider('\u2500'),
|
||||||
urwid.AttrWrap(urwid.Columns([
|
urwid.AttrWrap(urwid.Columns([
|
||||||
|
@ -90,6 +108,7 @@ class AppWidget(urwid.Frame):
|
||||||
for tab
|
for tab
|
||||||
in self.tabs
|
in self.tabs
|
||||||
], dividechars=1), 'panel'),
|
], dividechars=1), 'panel'),
|
||||||
|
NotificationArea.get()
|
||||||
# urwid.Divider('\u2500')
|
# urwid.Divider('\u2500')
|
||||||
])
|
])
|
||||||
self.seekbar = PlayProgress(
|
self.seekbar = PlayProgress(
|
||||||
|
@ -116,27 +135,48 @@ class AppWidget(urwid.Frame):
|
||||||
body=self.current_page
|
body=self.current_page
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.loop = None
|
||||||
|
|
||||||
player.media_position_changed += self.media_position_changed
|
player.media_position_changed += self.media_position_changed
|
||||||
player.media_state_changed += self.media_state_changed
|
player.media_state_changed += self.media_state_changed
|
||||||
player.track_changed += self.track_changed
|
player.track_changed += self.track_changed
|
||||||
player.playback_flags_changed += self.playback_flags_changed
|
player.playback_flags_changed += self.playback_flags_changed
|
||||||
|
|
||||||
|
def set_loop(self, loop):
|
||||||
|
"""
|
||||||
|
Assign a MainLoop to this app.
|
||||||
|
"""
|
||||||
|
self.loop = loop
|
||||||
|
|
||||||
def media_position_changed(self, progress):
|
def media_position_changed(self, progress):
|
||||||
|
"""
|
||||||
|
Update slider in seekbar.
|
||||||
|
Called when current play position changes.
|
||||||
|
"""
|
||||||
if progress < 0:
|
if progress < 0:
|
||||||
progress = 0
|
progress = 0
|
||||||
self.seekbar.set_completion(progress * 100)
|
self.seekbar.set_completion(progress * 100)
|
||||||
loop.draw_screen()
|
self.loop.draw_screen()
|
||||||
# sleep(0.2)
|
|
||||||
|
|
||||||
# self.set_page(MyLibrary())
|
def media_state_changed(self, _):
|
||||||
|
"""
|
||||||
def media_state_changed(self, is_playing):
|
Update seekbar.
|
||||||
|
Called when playback is paused/unpaused.
|
||||||
|
"""
|
||||||
self.seekbar.update()
|
self.seekbar.update()
|
||||||
|
|
||||||
def track_changed(self, track):
|
def track_changed(self, track):
|
||||||
|
"""
|
||||||
|
Update displayed track in seekbar.
|
||||||
|
Called when current track changes.
|
||||||
|
"""
|
||||||
self.seekbar.set_track(track)
|
self.seekbar.set_track(track)
|
||||||
|
|
||||||
def playback_flags_changed(self):
|
def playback_flags_changed(self):
|
||||||
|
"""
|
||||||
|
Update seekbar flags.
|
||||||
|
Called when random/repeat flags change.
|
||||||
|
"""
|
||||||
self.shuffle_el.attr = 'flag-active' \
|
self.shuffle_el.attr = 'flag-active' \
|
||||||
if player.get_is_random() \
|
if player.get_is_random() \
|
||||||
else 'flag'
|
else 'flag'
|
||||||
|
@ -145,15 +185,10 @@ class AppWidget(urwid.Frame):
|
||||||
else 'flag'
|
else 'flag'
|
||||||
self.seekbar.update()
|
self.seekbar.update()
|
||||||
|
|
||||||
def set_page(self, classname, *args):
|
def set_page(self, classname):
|
||||||
# if isinstance(page_class, str):
|
"""
|
||||||
# page_class = [
|
Switch to a different tab.
|
||||||
# page
|
"""
|
||||||
# for page
|
|
||||||
# in self.pages
|
|
||||||
# if page.__name__ == page_class
|
|
||||||
# ][0]
|
|
||||||
# self.current_page = page_class(self, *args)
|
|
||||||
page = [page for page in self.pages if page.__class__.__name__ == classname][0]
|
page = [page for page in self.pages if page.__class__.__name__ == classname][0]
|
||||||
self.current_page = page
|
self.current_page = page
|
||||||
self.contents['body'] = (page, None)
|
self.contents['body'] = (page, None)
|
||||||
|
@ -169,12 +204,18 @@ class AppWidget(urwid.Frame):
|
||||||
page.start()
|
page.start()
|
||||||
|
|
||||||
def redraw(self):
|
def redraw(self):
|
||||||
if loop:
|
"""
|
||||||
loop.draw_screen()
|
Redraw screen.
|
||||||
|
Needs to be called by other widgets if UI was changed from a different thread.
|
||||||
|
"""
|
||||||
|
if self.loop:
|
||||||
|
self.loop.draw_screen()
|
||||||
|
|
||||||
def keypress(self, size, key):
|
def keypress(self, size, key):
|
||||||
if isinstance(self.current_page, StartUp):
|
"""
|
||||||
return
|
Handle keypress.
|
||||||
|
Can switch tabs, control playbackm, flags, notifications and app state.
|
||||||
|
"""
|
||||||
for tab in self.tabs:
|
for tab in self.tabs:
|
||||||
if 'meta {}'.format(tab.page.key) == key:
|
if 'meta {}'.format(tab.page.key) == key:
|
||||||
self.set_page(tab.page.__class__.__name__)
|
self.set_page(tab.page.__class__.__name__)
|
||||||
|
@ -196,18 +237,22 @@ class AppWidget(urwid.Frame):
|
||||||
player.set_repeat_one(not player.get_is_repeat_one())
|
player.set_repeat_one(not player.get_is_repeat_one())
|
||||||
elif key == 'ctrl q':
|
elif key == 'ctrl q':
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
elif key == 'esc':
|
||||||
|
NotificationArea.close_all()
|
||||||
else:
|
else:
|
||||||
super(AppWidget, self).keypress(size, key)
|
super(AppWidget, self).keypress(size, key)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
global loop
|
"""
|
||||||
|
Application entrypoint.
|
||||||
|
"""
|
||||||
app_widget = AppWidget()
|
app_widget = AppWidget()
|
||||||
loop = urwid.MainLoop(app_widget, PALETTE)
|
loop = urwid.MainLoop(app_widget, PALETTE)
|
||||||
|
app_widget.set_loop(loop)
|
||||||
loop.screen.set_terminal_properties(256)
|
loop.screen.set_terminal_properties(256)
|
||||||
loop.run()
|
loop.run()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ def async(fn):
|
||||||
result = fn(*args, **kwargs)
|
result = fn(*args, **kwargs)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
callback(None, e, **extra)
|
callback(None, e, **extra)
|
||||||
raise
|
|
||||||
else:
|
else:
|
||||||
callback(result, None, **extra)
|
callback(result, None, **extra)
|
||||||
|
|
||||||
|
@ -147,5 +146,6 @@ class GP(object):
|
||||||
def is_authenticated(self):
|
def is_authenticated(self):
|
||||||
return self.mc.is_authenticated()
|
return self.mc.is_authenticated()
|
||||||
|
|
||||||
|
|
||||||
gp = GP()
|
gp = GP()
|
||||||
|
|
||||||
|
|
50
clay/notifications.py
Normal file
50
clay/notifications.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import urwid
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationArea(urwid.Pile):
|
||||||
|
instance = None
|
||||||
|
app = None
|
||||||
|
|
||||||
|
TEMPLATE = ' \u26A1 {}'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__([])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls):
|
||||||
|
if cls.instance is None:
|
||||||
|
cls.instance = NotificationArea()
|
||||||
|
|
||||||
|
return cls.instance
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def set_app(cls, app):
|
||||||
|
cls.app = app
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def notify(cls, message):
|
||||||
|
cls.instance._notify(message)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def close_all(cls):
|
||||||
|
cls.instance._close_all()
|
||||||
|
|
||||||
|
def _notify(self, message):
|
||||||
|
text = urwid.Text(self.__class__.TEMPLATE.format(message))
|
||||||
|
self.contents.append(
|
||||||
|
(
|
||||||
|
urwid.AttrWrap(
|
||||||
|
urwid.Columns([
|
||||||
|
text,
|
||||||
|
('pack', urwid.Text('[Hit ESC to close] '))
|
||||||
|
]),
|
||||||
|
'notification'
|
||||||
|
),
|
||||||
|
('weight', 1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.__class__.app.redraw()
|
||||||
|
|
||||||
|
def _close_all(self):
|
||||||
|
self.contents[:] = []
|
||||||
|
|
|
@ -2,6 +2,7 @@ import urwid
|
||||||
from clay.settings import Settings
|
from clay.settings import Settings
|
||||||
from clay.gp import gp
|
from clay.gp import gp
|
||||||
from clay.meta import VERSION
|
from clay.meta import VERSION
|
||||||
|
from clay.notifications import NotificationArea
|
||||||
|
|
||||||
|
|
||||||
class StartUp(urwid.Filler):
|
class StartUp(urwid.Filler):
|
||||||
|
@ -29,14 +30,11 @@ class StartUp(urwid.Filler):
|
||||||
|
|
||||||
def on_login(self, success, error):
|
def on_login(self, success, error):
|
||||||
if error:
|
if error:
|
||||||
self.app.set_page(
|
NotificationArea.notify('Failed to log in: {}'.format(str(error)))
|
||||||
'Error',
|
|
||||||
'Failed to log in: {}'.format(str(error))
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
self.app.set_page(
|
NotificationArea.notify(
|
||||||
'Error',
|
|
||||||
'Google Play Music login failed '
|
'Google Play Music login failed '
|
||||||
'(API returned false)'
|
'(API returned false)'
|
||||||
)
|
)
|
||||||
|
@ -54,20 +52,7 @@ class StartUp(urwid.Filler):
|
||||||
callback=self.on_login
|
callback=self.on_login
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.app.set_page(
|
NotificationArea.notify(
|
||||||
'Error',
|
|
||||||
'Please set your credentials on the settings page.'
|
'Please set your credentials on the settings page.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Error(urwid.Filler):
|
|
||||||
def __init__(self, app):
|
|
||||||
self.text = urwid.Text('Error'),
|
|
||||||
super(Error, self).__init__(
|
|
||||||
self.text,
|
|
||||||
valign='top'
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_error(self, error):
|
|
||||||
self.text = 'Error:\n\n{}'.format(str(error))
|
|
||||||
|
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -12,7 +12,7 @@ setup(
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': [
|
'console_scripts': [
|
||||||
'clay=clay.__main__:main'
|
'clay=clay.app:main'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Reference in a new issue