mirror of
https://github.com/vale981/clay
synced 2025-03-05 09:31:40 -05:00
Merge branch 'master' into ratings
This commit is contained in:
commit
3200c67b72
18 changed files with 399 additions and 45 deletions
143
CHANGELOG.rst
Normal file
143
CHANGELOG.rst
Normal file
|
@ -0,0 +1,143 @@
|
|||
Changelog
|
||||
---------
|
||||
|
||||
Clay 1.0.0
|
||||
==========
|
||||
|
||||
2018-04-06
|
||||
|
||||
* Configurable keybinds (by Valentijn)
|
||||
* Configurable colors (by Valentijn)
|
||||
* Pluggable X keybinds (by Valentijn)
|
||||
* "My stations" page (by @Fluctuz)
|
||||
* Better settings management (by Valentijn)
|
||||
* Equalizer
|
||||
* Track caching indicator
|
||||
* Optimized settings & cache
|
||||
* Code complexity & code climate integration
|
||||
* Countless fixes
|
||||
* Badges!
|
||||
* IRC channel!
|
||||
|
||||
Clay 0.7.2
|
||||
==========
|
||||
|
||||
2018-02-12
|
||||
|
||||
* Added --transparent flag
|
||||
|
||||
Clay 0.7.1
|
||||
==========
|
||||
|
||||
2018-02-08
|
||||
|
||||
* Late fix for broken setup.py entrypoint support
|
||||
|
||||
Clay 0.7.0
|
||||
==========
|
||||
|
||||
2018-02-08
|
||||
|
||||
* Added Dockerfile
|
||||
* Fixed installation instructions in README (by Valentijn)
|
||||
* Load global hotkeys when X is running only (by Valentijn)
|
||||
* Clarified in README that keybinder and pygobject are optional (by Valentijn)
|
||||
* Improved error handling and reporting (by Valentijn)
|
||||
* Version, help and keybinder command line arguments (by Valentijn)
|
||||
* Added copyright (by Valentijn)
|
||||
|
||||
Clay 0.6.2
|
||||
==========
|
||||
|
||||
2018-02-02
|
||||
|
||||
* Fixed playback for non-subscribed accounts
|
||||
|
||||
Clay 0.6.1
|
||||
==========
|
||||
|
||||
2018-02-02
|
||||
|
||||
* Attempt to fix purchased song while not on paid subscription
|
||||
|
||||
Clay 0.6.0
|
||||
==========
|
||||
|
||||
2018-02-01
|
||||
|
||||
* Added track caching option
|
||||
* More debugging
|
||||
|
||||
Clay 0.5.6
|
||||
==========
|
||||
|
||||
2018-01-31
|
||||
|
||||
* Added debug page
|
||||
|
||||
Clay 0.5.5
|
||||
==========
|
||||
|
||||
2018-01-31
|
||||
|
||||
* Added CLAY_DEBUG to log Google Play Music traffic
|
||||
* Fixed typo in install_requires
|
||||
* Updated README
|
||||
|
||||
Clay 0.5.3
|
||||
==========
|
||||
|
||||
2018-01-30
|
||||
|
||||
* Added codename
|
||||
* Linter fixes
|
||||
|
||||
Clay 0.5.2
|
||||
==========
|
||||
|
||||
2018-01-30
|
||||
|
||||
* Fixed versioning
|
||||
|
||||
Clay 0.5.1
|
||||
==========
|
||||
|
||||
2018-01-30
|
||||
|
||||
* Debugging
|
||||
* Cleanup & typos
|
||||
* Fixed issue with uploaded tracks
|
||||
|
||||
Clay 0.5
|
||||
========
|
||||
|
||||
2018-01-29
|
||||
|
||||
* Added slider for eqializer
|
||||
* Updated README
|
||||
* Misc fixes
|
||||
|
||||
Clay 0.4
|
||||
========
|
||||
* Added equalizer
|
||||
|
||||
2018-01-29
|
||||
|
||||
Clay 0.3
|
||||
========
|
||||
|
||||
2018-01-26
|
||||
|
||||
* Initial functionality
|
||||
* Cleanups
|
||||
* Notifications
|
||||
* Hotkeys
|
||||
* Linting
|
||||
* Documentation
|
||||
* Song search
|
||||
* Song context menu
|
||||
* Clearer song IDs
|
||||
* Auth token caching
|
||||
* Colors
|
||||
* Copy URL to clipboard
|
||||
|
|
@ -15,8 +15,6 @@ RUN apt-get install -y python3.6-dev python3-pip libvlc-dev vlc locales language
|
|||
RUN locale-gen en_US.UTF-8
|
||||
|
||||
RUN useradd ${HOST_USER} -m -G audio -u ${HOST_UID}
|
||||
#RUN mkdir -p /home/${HOST_USER}/.config/clay
|
||||
#RUN chown ${HOST_USER} /home/${HOST_USER}/.config/clay
|
||||
|
||||
WORKDIR /home/${HOST_USER}
|
||||
|
||||
|
|
4
Makefile
4
Makefile
|
@ -1,9 +1,11 @@
|
|||
CMD ?= "./clay/app.py"
|
||||
|
||||
# Build Clay Docker image
|
||||
build:
|
||||
echo $(shell id -u)
|
||||
docker build -t clay --build-arg HOST_USER=${USER} --build-arg HOST_UID=$(shell id -u) .
|
||||
|
||||
# Run Clay Docker image
|
||||
run: | build
|
||||
docker run -it \
|
||||
--rm \
|
||||
|
@ -20,10 +22,12 @@ run: | build
|
|||
clay \
|
||||
${CMD}
|
||||
|
||||
# Generate Sphinx docs
|
||||
.PHONY: docs
|
||||
docs:
|
||||
make -C docs html
|
||||
|
||||
# Run pylint & radon
|
||||
check:
|
||||
pylint clay --ignore-imports=y
|
||||
radon cc -a -s -nC -e clay/vlc.py clay
|
||||
|
|
12
README.md
12
README.md
|
@ -19,6 +19,7 @@
|
|||
* [Misc](#misc)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Credits](#credits)
|
||||
- [Changelog](./CHANGELOG.rst)
|
||||
|
||||
# Clay [beta]
|
||||
|
||||
|
@ -55,7 +56,7 @@ clay
|
|||
|
||||
# Documentation
|
||||
|
||||
Documentation is .
|
||||
Documentation is [available here](http://clay.readthedocs.io/en/latest/).
|
||||
|
||||
# Requirements
|
||||
|
||||
|
@ -235,14 +236,15 @@ Made by Andrew Dunai.
|
|||
|
||||
Regards to [gmusicapi] and [VLC] who made this possible.
|
||||
|
||||
Special thanks to the people who contribute to this project:
|
||||
People who contribute to this project:
|
||||
|
||||
- [Valentijn (@ValentijnvdBeek)](https://github.com/ValentijnvdBeek)
|
||||
- [Sam Kingston (@sjkingo)](https://github.com/sjkingo)
|
||||
- [@ValentijnvdBeek (Valentijn)](https://github.com/ValentijnvdBeek)
|
||||
- [@Fluctuz](https://github.com/Fluctuz)
|
||||
- [@sjkingo (Sam Kingston)](https://github.com/sjkingo)
|
||||
|
||||
[gmusicapi]: https://github.com/simon-weber/gmusicapi
|
||||
[VLC]: https://wiki.videolan.org/python_bindings
|
||||
[urwid]: urwid.org/
|
||||
[urwid]: http://www.urwid.org/
|
||||
[pyyaml]: https://github.com/yaml/pyyaml
|
||||
[PyGObject]: https://pygobject.readthedocs.io/en/latest/getting_started.html
|
||||
[Keybinder]: https://github.com/kupferlauncher/keybinder
|
||||
|
|
|
@ -18,6 +18,7 @@ from clay.playbar import PlayBar
|
|||
from clay.pages.debug import DebugPage
|
||||
from clay.pages.mylibrary import MyLibraryPage
|
||||
from clay.pages.myplaylists import MyPlaylistsPage
|
||||
from clay.pages.mystations import MyStationsPage
|
||||
from clay.pages.playerqueue import QueuePage
|
||||
from clay.pages.search import SearchPage
|
||||
from clay.pages.settings import SettingsPage
|
||||
|
@ -68,6 +69,7 @@ class AppWidget(urwid.Frame):
|
|||
DebugPage(self),
|
||||
MyLibraryPage(self),
|
||||
MyPlaylistsPage(self),
|
||||
MyStationsPage(self),
|
||||
QueuePage(self),
|
||||
SearchPage(self),
|
||||
SettingsPage(self)
|
||||
|
@ -105,7 +107,11 @@ class AppWidget(urwid.Frame):
|
|||
|
||||
Request user authorization.
|
||||
"""
|
||||
authtoken, device_id, _, password, username = settings.get_section("play_settings").values()
|
||||
authtoken, device_id, username, password = [
|
||||
settings.get(key, "play_settings")
|
||||
for key
|
||||
in ('authtoken', 'device_id', 'username', 'password')
|
||||
]
|
||||
|
||||
if self._login_notification:
|
||||
self._login_notification.close()
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
default: &default
|
||||
foreground: "#FFF"
|
||||
background: "#222"
|
||||
background: null
|
||||
|
||||
primary: &primary
|
||||
foreground: "#F54"
|
||||
background: "#FFF"
|
||||
background: null
|
||||
|
||||
primary_inv: &primary_inv
|
||||
foreground: "#FFF"
|
||||
|
|
|
@ -40,6 +40,9 @@ hotkeys:
|
|||
playlist_page:
|
||||
start_playlist: enter
|
||||
|
||||
station_page:
|
||||
start_station: enter
|
||||
|
||||
debug_page:
|
||||
copy_message: enter
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
"""
|
||||
Events implemetation for signal handling.
|
||||
"""
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
||||
|
||||
class EventHook(object):
|
||||
|
|
56
clay/gp.py
56
clay/gp.py
|
@ -2,11 +2,7 @@
|
|||
Google Play Music integration via gmusicapi.
|
||||
"""
|
||||
# pylint: disable=broad-except
|
||||
# pylint: disable=too-many-arguments
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
# pylint: disable=too-many-return-statements
|
||||
# pylint: disable=protected-access
|
||||
# pylint: disable=no-self-use
|
||||
from __future__ import print_function
|
||||
from threading import Thread, Lock
|
||||
from uuid import UUID
|
||||
|
@ -16,6 +12,8 @@ from gmusicapi.clients import Mobileclient
|
|||
from clay.eventhook import EventHook
|
||||
from clay.log import logger
|
||||
|
||||
STATION_FETCH_LEN = 50
|
||||
|
||||
|
||||
def asynchronous(func):
|
||||
"""
|
||||
|
@ -202,11 +200,12 @@ class Track(object):
|
|||
|
||||
Returns :class:`.Station` instance.
|
||||
"""
|
||||
station_name = u'Station - {}'.format(self.title)
|
||||
station_id = gp.mobile_client.create_station(
|
||||
name=u'Station - {}'.format(self.title),
|
||||
name=station_name,
|
||||
track_id=self.store_id
|
||||
)
|
||||
station = Station(station_id)
|
||||
station = Station(station_id, station_name)
|
||||
station.load_tracks()
|
||||
return station
|
||||
|
||||
|
@ -285,7 +284,8 @@ class Station(object):
|
|||
"""
|
||||
Model that represents specific station on Google Play Music.
|
||||
"""
|
||||
def __init__(self, station_id):
|
||||
def __init__(self, station_id, name):
|
||||
self.name = name
|
||||
self._id = station_id
|
||||
self._tracks = []
|
||||
self._tracks_loaded = False
|
||||
|
@ -302,9 +302,12 @@ class Station(object):
|
|||
Fetch tracks related to this station and
|
||||
populate it with :class:`Track` instances.
|
||||
"""
|
||||
data = gp.mobile_client.get_station_tracks(self.id, 100)
|
||||
data = gp.mobile_client.get_station_tracks(self.id, STATION_FETCH_LEN)
|
||||
self._tracks = Track.from_data(data, Track.SOURCE_STATION, many=True)
|
||||
self._tracks_loaded = True
|
||||
return self
|
||||
|
||||
load_tracks_async = asynchronous(load_tracks)
|
||||
|
||||
def get_tracks(self):
|
||||
"""
|
||||
|
@ -313,6 +316,20 @@ class Station(object):
|
|||
assert self._tracks_loaded, 'Must call ".load_tracks()" before ".get_tracks()"'
|
||||
return self._tracks
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, data, many=False):
|
||||
"""
|
||||
Construct and return one or many :class:`.Station` instances
|
||||
from Google Play Music API response.
|
||||
"""
|
||||
if many:
|
||||
return [cls.from_data(one) for one in data if one['inLibrary']]
|
||||
|
||||
return Station(
|
||||
station_id=data['id'],
|
||||
name=data['name']
|
||||
)
|
||||
|
||||
|
||||
class SearchResults(object):
|
||||
"""
|
||||
|
@ -443,6 +460,7 @@ class _GP(object):
|
|||
self.cached_tracks = None
|
||||
self.cached_liked_songs = LikedSongs()
|
||||
self.cached_playlists = None
|
||||
self.cached_stations = None
|
||||
|
||||
self.invalidate_caches()
|
||||
|
||||
|
@ -475,10 +493,11 @@ class _GP(object):
|
|||
|
||||
def invalidate_caches(self):
|
||||
"""
|
||||
Clear cached tracks & playlists.
|
||||
Clear cached tracks & playlists & stations.
|
||||
"""
|
||||
self.cached_tracks = None
|
||||
self.cached_playlists = None
|
||||
self.cached_stations = None
|
||||
self.caches_invalidated.fire()
|
||||
|
||||
@synchronized
|
||||
|
@ -548,6 +567,25 @@ class _GP(object):
|
|||
|
||||
get_stream_url_async = asynchronous(get_stream_url)
|
||||
|
||||
@synchronized
|
||||
def get_all_user_station_contents(self, **_):
|
||||
"""
|
||||
Return list of :class:`.Station` instances.
|
||||
"""
|
||||
if self.cached_stations:
|
||||
return self.cached_stations
|
||||
self.get_all_tracks()
|
||||
|
||||
self.cached_stations = Station.from_data(
|
||||
self.mobile_client.get_all_stations(),
|
||||
True
|
||||
)
|
||||
return self.cached_stations
|
||||
|
||||
get_all_user_station_contents_async = ( # pylint: disable=invalid-name
|
||||
asynchronous(get_all_user_station_contents)
|
||||
)
|
||||
|
||||
@synchronized
|
||||
def get_all_user_playlist_contents(self, **_):
|
||||
"""
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
Predefined values.
|
||||
"""
|
||||
APP_NAME = 'Clay Player'
|
||||
VERSION = '0.7.2'
|
||||
VERSION = '1.0.0'
|
||||
AUTHOR = "Andrew Dunai"
|
||||
DESCRIPTION = "Awesome standalone command line player for Google Play Music"
|
||||
|
||||
|
|
|
@ -43,7 +43,6 @@ class MyLibraryPage(urwid.Columns, AbstractPage):
|
|||
if error:
|
||||
notification_area.notify('Failed to load my library: {}'.format(str(error)))
|
||||
return
|
||||
# self.notification.close()
|
||||
self.songlist.populate(tracks)
|
||||
self.app.redraw()
|
||||
|
||||
|
|
|
@ -97,8 +97,6 @@ class MyPlaylistListBox(urwid.ListBox):
|
|||
)
|
||||
items.append(myplaylistlistitem)
|
||||
|
||||
# self.notification.close()
|
||||
|
||||
self.walker[:] = items
|
||||
|
||||
self.app.redraw()
|
||||
|
|
160
clay/pages/mystations.py
Normal file
160
clay/pages/mystations.py
Normal file
|
@ -0,0 +1,160 @@
|
|||
"""
|
||||
Components for "My stations" page.
|
||||
"""
|
||||
import urwid
|
||||
|
||||
from clay.gp import gp
|
||||
from clay.songlist import SongListBox
|
||||
from clay.notifications import notification_area
|
||||
from clay.pages.page import AbstractPage
|
||||
from clay.hotkeys import hotkey_manager
|
||||
|
||||
|
||||
class MyStationListItem(urwid.Columns):
|
||||
"""
|
||||
One station in the list of stations.
|
||||
"""
|
||||
signals = ['activate']
|
||||
|
||||
def __init__(self, station):
|
||||
self.station = station
|
||||
self.text = urwid.SelectableIcon(u' \u2708 {} '.format(
|
||||
self.station.name
|
||||
), cursor_position=3)
|
||||
self.text.set_layout('left', 'clip', None)
|
||||
self.content = urwid.AttrWrap(
|
||||
self.text,
|
||||
'default',
|
||||
'selected'
|
||||
)
|
||||
super(MyStationListItem, self).__init__([self.content])
|
||||
|
||||
def keypress(self, size, key):
|
||||
"""
|
||||
Handle keypress.
|
||||
"""
|
||||
return hotkey_manager.keypress("station_page", self, super(MyStationListItem, self),
|
||||
size, key)
|
||||
|
||||
def start_station(self):
|
||||
"""
|
||||
Start playing the selected station
|
||||
"""
|
||||
urwid.emit_signal(self, 'activate', self)
|
||||
return None
|
||||
|
||||
|
||||
class MyStationListBox(urwid.ListBox):
|
||||
"""
|
||||
List of stations.
|
||||
"""
|
||||
signals = ['activate']
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
self.walker = urwid.SimpleListWalker([
|
||||
urwid.Text('Not ready')
|
||||
])
|
||||
self.notification = None
|
||||
|
||||
gp.auth_state_changed += self.auth_state_changed
|
||||
|
||||
super(MyStationListBox, self).__init__(self.walker)
|
||||
|
||||
def auth_state_changed(self, is_auth):
|
||||
"""
|
||||
Called when auth state changes (e. g. user is logged in).
|
||||
Requests fetching of station.
|
||||
"""
|
||||
if is_auth:
|
||||
self.walker[:] = [
|
||||
urwid.Text(u'\n \uf01e Loading stations...', align='center')
|
||||
]
|
||||
|
||||
gp.get_all_user_station_contents_async(callback=self.on_get_stations)
|
||||
|
||||
def on_get_stations(self, stations, error):
|
||||
"""
|
||||
Called when a list of stations fetch completes.
|
||||
Populates list of stations.
|
||||
"""
|
||||
if error:
|
||||
notification_area.notify('Failed to get stations: {}'.format(str(error)))
|
||||
|
||||
items = []
|
||||
for station in stations:
|
||||
mystationlistitem = MyStationListItem(station)
|
||||
urwid.connect_signal(
|
||||
mystationlistitem, 'activate', self.item_activated
|
||||
)
|
||||
items.append(mystationlistitem)
|
||||
|
||||
self.walker[:] = items
|
||||
|
||||
self.app.redraw()
|
||||
|
||||
def item_activated(self, mystationlistitem):
|
||||
"""
|
||||
Called when a specific station is selected.
|
||||
Re-emits this event.
|
||||
"""
|
||||
urwid.emit_signal(self, 'activate', mystationlistitem)
|
||||
|
||||
|
||||
class MyStationsPage(urwid.Columns, AbstractPage):
|
||||
"""
|
||||
Stations page.
|
||||
|
||||
Contains two parts:
|
||||
|
||||
- List of stations (:class:`.MyStationBox`)
|
||||
- List of songs in selected station (:class:`clay:songlist:SongListBox`)
|
||||
"""
|
||||
@property
|
||||
def name(self):
|
||||
return 'Stations'
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
return 3
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
self.stationlist = MyStationListBox(app)
|
||||
self.songlist = SongListBox(app)
|
||||
self.songlist.set_placeholder('\n Select a station.')
|
||||
|
||||
urwid.connect_signal(
|
||||
self.stationlist, 'activate', self.mystationlistitem_activated
|
||||
)
|
||||
|
||||
super(MyStationsPage, self).__init__([
|
||||
self.stationlist,
|
||||
self.songlist
|
||||
])
|
||||
|
||||
def mystationlistitem_activated(self, mystationlistitem):
|
||||
"""
|
||||
Called when specific station is selected.
|
||||
Requests fetching of station tracks
|
||||
"""
|
||||
self.songlist.set_placeholder(u'\n \uf01e Loading station tracks...')
|
||||
mystationlistitem.station.load_tracks_async(callback=self.on_station_loaded)
|
||||
|
||||
def on_station_loaded(self, station, error):
|
||||
"""
|
||||
Called when station tracks fetch completes.
|
||||
Populates songlist with tracks from the selected station.
|
||||
"""
|
||||
if error:
|
||||
notification_area.notify('Failed to get station tracks: {}'.format(str(error)))
|
||||
|
||||
self.songlist.populate(
|
||||
station.get_tracks()
|
||||
)
|
||||
self.app.redraw()
|
||||
|
||||
def activate(self):
|
||||
pass
|
|
@ -18,7 +18,7 @@ class QueuePage(urwid.Columns, AbstractPage):
|
|||
|
||||
@property
|
||||
def key(self):
|
||||
return 3
|
||||
return 4
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
|
|
@ -58,7 +58,7 @@ class SearchPage(urwid.Pile, AbstractPage):
|
|||
|
||||
@property
|
||||
def key(self):
|
||||
return 4
|
||||
return 5
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
|
|
@ -17,11 +17,11 @@ class Slider(urwid.Widget):
|
|||
_sizing = frozenset([urwid.FLOW])
|
||||
|
||||
CHARS = [
|
||||
# '_',
|
||||
u'\u2581',
|
||||
u'\u2500',
|
||||
u'\u2594'
|
||||
u'\u2584',
|
||||
u'\u25A0',
|
||||
u'\u2580',
|
||||
]
|
||||
ZERO_CHAR = u'\u2500'
|
||||
|
||||
def selectable(self):
|
||||
return True
|
||||
|
@ -36,7 +36,7 @@ class Slider(urwid.Widget):
|
|||
else:
|
||||
self.freq_str = str(freq) + '\nHz'
|
||||
self.value = 0
|
||||
self.slider_height = 5
|
||||
self.slider_height = 13
|
||||
self.max_value = 20
|
||||
super(Slider, self).__init__()
|
||||
|
||||
|
@ -50,16 +50,18 @@ class Slider(urwid.Widget):
|
|||
"""
|
||||
Render widget.
|
||||
"""
|
||||
rows = [('+' if self.value >= 0 else '') + str(self.value) + ' dB']
|
||||
rows = [('+' if self.value > 0 else '') + str(self.value) + ' dB']
|
||||
|
||||
chars = [' '] * 5
|
||||
chars = [' '] * self.slider_height
|
||||
|
||||
if self.value == 0:
|
||||
chars[self.slider_height // 2] = Slider.ZERO_CHAR
|
||||
else:
|
||||
k = ((float(self.value) / (self.max_value + 1)) + 1) / 2 # Convert value to [0;1] range
|
||||
section_index = int(k * self.slider_height)
|
||||
char_index = int(k * self.slider_height * len(Slider.CHARS)) % len(Slider.CHARS)
|
||||
chars[section_index] = Slider.CHARS[char_index]
|
||||
|
||||
# rows.extend(['X'] * self.slider_height)
|
||||
rows.extend([
|
||||
(
|
||||
u'\u2524{}\u251C'
|
||||
|
@ -139,17 +141,17 @@ class SettingsPage(urwid.Columns, AbstractPage):
|
|||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.username = urwid.Edit(
|
||||
edit_text=settings.get('username', 'play_settings')
|
||||
edit_text=settings.get('username', 'play_settings') or ''
|
||||
)
|
||||
self.password = urwid.Edit(
|
||||
mask='*', edit_text=settings.get('password', 'play_settings')
|
||||
mask='*', edit_text=settings.get('password', 'play_settings') or ''
|
||||
)
|
||||
self.device_id = urwid.Edit(
|
||||
edit_text=settings.get('device_id', 'play_settings')
|
||||
edit_text=settings.get('device_id', 'play_settings') or ''
|
||||
)
|
||||
self.download_tracks = urwid.CheckBox(
|
||||
'Download tracks before playback',
|
||||
state=settings.get('download_tracks', 'play_settings')
|
||||
state=settings.get('download_tracks', 'play_settings') or False
|
||||
)
|
||||
self.equalizer = Equalizer()
|
||||
super(SettingsPage, self).__init__([urwid.ListBox(urwid.SimpleListWalker([
|
||||
|
@ -178,6 +180,8 @@ class SettingsPage(urwid.Columns, AbstractPage):
|
|||
Called when "Save" button is pressed.
|
||||
"""
|
||||
with settings.edit() as config:
|
||||
if 'play_settings' not in config:
|
||||
config['play_settings'] = {}
|
||||
config['play_settings']['username'] = self.username.edit_text
|
||||
config['play_settings']['password'] = self.password.edit_text
|
||||
config['play_settings']['device_id'] = self.device_id.edit_text
|
||||
|
|
|
@ -121,10 +121,10 @@ class _Settings(object):
|
|||
section = self.get_section(*sections)
|
||||
|
||||
try:
|
||||
return section[key]
|
||||
return section.get(key)
|
||||
except (KeyError, TypeError):
|
||||
section = self.get_default_config_section(*sections)
|
||||
return section[key]
|
||||
return section.get(key)
|
||||
|
||||
def _get_section(self, config, *sections):
|
||||
config = config.copy()
|
||||
|
|
|
@ -9,7 +9,7 @@ try:
|
|||
# Python 3.x
|
||||
from string import ascii_letters
|
||||
except ImportError:
|
||||
# Python 2.3
|
||||
# Python 2.x
|
||||
from string import letters as ascii_letters
|
||||
import urwid
|
||||
from clay.notifications import notification_area
|
||||
|
|
Loading…
Add table
Reference in a new issue