Esempio n. 1
0
File: hotkeys.py Progetto: thor/clay
    def _parse_hotkeys(self):
        """
        Reads out the configuration file and parse them into a hotkeys for urwid.
        """
        hotkey_config = settings_manager.get_default_config_section('hotkeys')
        mod_key = settings_manager.get('mod_key', 'clay_settings')
        hotkeys = {}

        for hotkey_name, hotkey_dict in hotkey_config.items():
            hotkeys[hotkey_name] = {}
            for action, sequence in hotkey_dict.items():
                key_seq = settings_manager.get(action, 'hotkeys', hotkey_name)

                if key_seq is None:
                    key_seq = sequence

                for key in key_seq.split(', '):
                    hotkey = key.split(' + ')

                    if hotkey[0].strip() == 'mod':
                        hotkey[0] = mod_key

                    hotkeys[hotkey_name][' '.join(hotkey)] = action

        return hotkeys
Esempio n. 2
0
 def __init__(self, app):
     self.app = app
     self.username = urwid.Edit(
         edit_text=settings_manager.get('username', 'play_settings') or '')
     self.password = urwid.Edit(
         mask='*',
         edit_text=settings_manager.get('password', 'play_settings') or '')
     self.device_id = urwid.Edit(
         edit_text=settings_manager.get('device_id', 'play_settings') or '')
     self.download_tracks = urwid.CheckBox(
         'Download tracks before playback',
         state=settings_manager.get('download_tracks', 'play_settings')
         or False)
     super(SettingsPage, self).__init__([
         urwid.ListBox(
             urwid.SimpleListWalker([
                 urwid.Text('Settings'),
                 urwid.Divider(' '),
                 urwid.Text('Username'),
                 urwid.AttrWrap(self.username, 'input', 'input_focus'),
                 urwid.Divider(' '),
                 urwid.Text('Password'),
                 urwid.AttrWrap(self.password, 'input', 'input_focus'),
                 urwid.Divider(' '),
                 urwid.Text('Device ID'),
                 urwid.AttrWrap(self.device_id, 'input', 'input_focus'),
                 urwid.Divider(' '), self.download_tracks,
                 urwid.Divider(' '),
                 urwid.AttrWrap(urwid.Button('Save', on_press=self.on_save),
                                'input', 'input_focus')
             ]))
     ])
Esempio n. 3
0
    def log_in(self, use_token=True):
        """
        Called when this page is shown.

        Request user authorization.
        """
        authtoken, device_id, username, password = [
            settings_manager.get(key, "play_settings")
            for key in ('authtoken', 'device_id', 'username', 'password')
        ]

        if self._login_notification:
            self._login_notification.close()
        if use_token and authtoken:
            self._login_notification = notification_area.notify(
                'Using cached auth token...')
            gp.use_authtoken_async(authtoken,
                                   device_id,
                                   callback=self.on_check_authtoken)
        elif username and password and device_id:
            self._login_notification = notification_area.notify(
                'Logging in...')
            gp.login_async(username,
                           password,
                           device_id,
                           callback=self.on_login)
        else:
            self._login_notification = notification_area.notify(
                'Please set your credentials on the settings page.')
Esempio n. 4
0
File: vlc.py Progetto: thor/clay
    def play(self):
        """
        Pick current track from a queue and requests media stream URL.
        Completes in background.
        """
        track = self.queue.get_current_track()
        if track is None:
            return
        self._loading = True
        self.broadcast_state()
        self.track_changed.fire(track)

        if settings_manager.get('download_tracks', 'play_settings') or \
           settings_manager.get_is_file_cached(track.filename):
            path = settings_manager.get_cached_file_path(track.filename)

            if path is None:
                logger.debug('Track %s not in cache, downloading...', track.id)
                track.get_url(callback=self._download_track)
            else:
                logger.debug('Track %s in cache, playing', track.id)
                self._play_ready(path, None, track)
        else:
            logger.debug('Starting to stream %s', track.id)
            track.get_url(callback=self._play_ready)
Esempio n. 5
0
class Icons:
    """
    Icons used to indicate the state, rating or how explicit a song is.
    """
    _unicode = settings_manager.get('unicode', 'clay_settings')
    state = [' ', u'\u2505', u'\u25B6', u'\u25A0']

    if _unicode:
        ratings = [' ', '\U0001F593', '\U0001F593', '\U0001F593', '\U0001F593', '\U0001F592']
        explicit = [' ', u'\U0001F174', ' ', '']
    else:
        ratings = [' ', '-', '2', '3', '4', '+']
        explicit = [' ', '[E]', ' ', ' ']
Esempio n. 6
0
"""
Clipboard utils.
"""
import shlex, subprocess
from clay.core import settings_manager
from clay.ui.urwid.notifications import notification_area

cmd = settings_manager.get('copy_command', 'clay_settings')
COMMAND = shlex.split(cmd) if cmd is not None else None


def copy(text):
    """
    Copy text to clipboard.

    Return True on success.
    """
    try:
        if COMMAND is None:
            return
        proc = subprocess.Popen(COMMAND, stdin=subprocess.PIPE)
        proc.communicate(text.encode('utf-8'))
    except FileNotFoundError:
        notification_area.notify('Failed to copy text to clipboard. '
                                 'Please install "%s"' % COMMAND[0])
    except Exception as e:
        notification_area.notify('Failed to copy text to clipboard. '
                                 'Unknown error: "%s".' % e)
Esempio n. 7
0
class SongListItem(urwid.Pile):
    """
    Widget that represents single song item.
    """
    _unicode = settings_manager.get('unicode', 'clay_settings')
    signals = [
        'activate',
        'play',
        'append-requested',
        'unappend-requested',
        'clear-queue',
        'station-requested',
        'context-menu-requested'
    ]

    STATE_IDLE = 0
    STATE_LOADING = 1
    STATE_PLAYING = 2
    STATE_PAUSED = 3

    LINE1_ATTRS = {
        STATE_IDLE: ('line1', 'line1_focus'),
        STATE_LOADING: ('line1_active', 'line1_active_focus'),
        STATE_PLAYING: ('line1_active', 'line1_active_focus'),
        STATE_PAUSED: ('line1_active', 'line1_active_focus')
    }
    LINE2_ATTRS = {
        STATE_IDLE: ('line2', 'line2_focus'),
        STATE_LOADING: ('line2', 'line2_focus'),
        STATE_PLAYING: ('line2', 'line2_focus'),
        STATE_PAUSED: ('line2', 'line2_focus')
    }

    STATE_ICONS = {
        0: ' ',
        1: u'\u2505',
        2: u'\u25B6',
        3: u'\u25A0'
    }

    RATING_ICONS = {
        0: ' ',
        1: u'\U0001F593' if _unicode else '-',
        2: u'\U0001F593' if _unicode else '2',
        3: u'\U0001F593' if _unicode else '3',
        4: u'\U0001F593' if _unicode else '4',
        5: u'\U0001F592' if _unicode else '+'
    }

    EXPLICIT_ICONS = {
        0: ' ',  # not actually used?
        1: u'\U0001F174' if _unicode else '[E]',
        2: ' ',
        3: ' '
    }

    def __init__(self, track):
        self.track = track
        self.rating = self.RATING_ICONS[track.rating]
        self.explicit = self.EXPLICIT_ICONS[track.explicit_rating]
        self.index = 0
        self.state = SongListItem.STATE_IDLE
        self.line1_left = urwid.SelectableIcon('', cursor_position=1000)
        self.line1_left.set_layout('left', 'clip', None)
        self.line1_right = urwid.Text('x')
        self.line1 = urwid.Columns([
            self.line1_left,
            ('pack', self.line1_right),
            ('pack', urwid.Text(' '))
        ])
        self.line2 = urwid.Text('', wrap='clip')

        self.line1_wrap = urwid.AttrWrap(self.line1, 'line1')
        self.line2_wrap = urwid.AttrWrap(self.line2, 'line2')

        self.content = urwid.Pile([
            self.line1_wrap,
            self.line2_wrap,
            urwid.Text('')
        ])

        self.is_focused = False

        super(SongListItem, self).__init__([
            self.content
        ])
        self.update_text()

    def set_state(self, state):
        """
        Set state for this song.
        Possible choices are:

        - :attr:`.SongListItem.STATE_IDLE`
        - :attr:`.SongListItem.STATE_LOADING`
        - :attr:`.SongListItem.STATE_PLAYING`
        - :attr:`.SongListItem.STATE_PAUSED`
        """
        self.state = state
        self.update_text()

    @staticmethod
    def get_state_icon(state):
        """
        Get icon char for specific state.
        """
        return SongListItem.STATE_ICONS[state]

    def update_text(self):
        """
        Update text of this item from the attached track.
        """
        self.line1_left.set_text(
            u'{index:3d} {icon} {title} [{minutes:02d}:{seconds:02d}]'.format(
                index=self.index + 1,
                icon=self.get_state_icon(self.state),
                title=self.track.title,
                minutes=self.track.duration // (1000 * 60),
                seconds=(self.track.duration // 1000) % 60,
            )
        )

        if settings_manager.get_is_file_cached(self.track.filename):
            self.line1_right.set_text(u' \u25bc Cached')
        else:
            self.line1_right.set_text(u'')

        self.line1_right.set_text(u'{explicit} {rating}'.format(explicit=self.explicit,
                                                                rating=self.rating))

        self.line2.set_text(
            u'      {} \u2015 {}'.format(self.track.artist, self.track.album_name)
        )
        self.line1_wrap.set_attr(SongListItem.LINE1_ATTRS[self.state][self.is_focused])
        self.line2_wrap.set_attr(SongListItem.LINE2_ATTRS[self.state][self.is_focused])

    @property
    def full_title(self):
        """
        Return song artist and title.
        """
        return u'{} - {} {}'.format(
            self.track.artist,
            self.track.title,
            self.rating
        )

    def keypress(self, size, key):
        """
        Handle keypress.
        """
        return hotkey_manager.keypress("song_item", self, super(SongListItem, self), size, key)

    def mouse_event(self, size, event, button, col, row, focus):
        """
        Handle mouse event.
        """
        if button == 1 and focus:
            urwid.emit_signal(self, 'activate', self)
            return None
        return super(SongListItem, self).mouse_event(size, event, button, col, row, focus)

    def thumbs_up(self):
        """
        Thumb the currently selected song up.
        """
        self.track.rate_song((0 if self.track.rating == 5 else 5))

    def thumbs_down(self):
        """
        Thumb the currently selected song down.
        """
        self.track.rate_song((0 if self.track.rating == 1 else 1))

    def _send_signal(self, signal):
        urwid.emit_signal(self, signal, self)

    def activate(self):
        """
        Add the entire list to queue and begin playing
        """
        self._send_signal("activate")

    def clear_queue(self):
        """
        Removes all the songs from the queue.
        """
        self._send_signal("clear-queue")

    def play(self):
        """
        Play this song.
        """
        self._send_signal("play")

    def append(self):
        """
        Add this song to the queue.
        """
        self._send_signal("append-requested")

    def unappend(self):
        """
        Remove this song from the queue.
        """
        if not self.is_currently_played:
            self._send_signal("unappend-requested")

    def request_station(self):
        """
        Create a Google Play Music radio for this song.
        """
        self._send_signal("station-requested")

    def show_context_menu(self):
        """
        Display the context menu for this song.
        """
        self._send_signal("context-menu-requested")

    @property
    def is_currently_played(self):
        """
        Return ``True`` if song is in state :attr:`.SongListItem.STATE_PLAYING`
        or :attr:`.SongListItem.STATE_PAUSED`.
        """
        return self.state in (
            SongListItem.STATE_LOADING,
            SongListItem.STATE_PLAYING,
            SongListItem.STATE_PAUSED
        )

    def set_index(self, index):
        """
        Set numeric index for this item.
        """
        self.index = index
        self.update_text()

    def render(self, size, focus=False):
        """
        Render widget & set focused state.
        """
        self.is_focused = focus
        self.update_text()
        return super(SongListItem, self).render(size, focus)
Esempio n. 8
0
File: playbar.py Progetto: thor/clay
class PlayBar(urwid.Pile):
    """
    A widget that shows currently played track, playback progress and flags.
    """
    _unicode = settings_manager.get('unicode', 'clay_settings')
    ROTATING = u'|' u'/' u'\u2014' u'\\'
    RATING_ICONS = {
        0: ' ',
        1: u'\U0001F593' if _unicode else '-',
        4: u'\U0001F592' if _unicode else '+',
        5: u'\U0001F592' if _unicode else '+'
    }

    def __init__(self, app):
        # super(PlayBar, self).__init__(*args, **kwargs)
        self.app = app
        self.rotating_index = 0
        self.text = urwid.Text('', align=urwid.LEFT)
        self.flags = []
        self.progressbar = ProgressBar()

        self.shuffle_el = urwid.AttrWrap(urwid.Text(u' \u22cd SHUF '), 'flag')
        self.repeat_el = urwid.AttrWrap(urwid.Text(u' \u27f2 REP '), 'flag')
        self.repeat_one_el = urwid.AttrWrap(urwid.Text(' 1 ONE'), 'flag')

        self.infobar = urwid.Columns([
            self.text, ('pack', self.shuffle_el), ('pack', self.repeat_one_el),
            ('pack', self.repeat_el)
        ])
        super(PlayBar, self).__init__([
            ('pack', self.progressbar),
            ('pack', self.infobar),
        ])
        self.update()

        player.media_position_changed += self.update
        player.media_state_changed += self.update
        player.media_state_stopped += self.stop
        player.track_changed += self.update
        player.playback_flags_changed += self.update

    def get_rotating_bar(self):
        """
        Return a spinner char for current rotating_index.
        """
        return PlayBar.ROTATING[self.rotating_index % len(PlayBar.ROTATING)]

    @staticmethod
    def get_style():
        """
        Return the style for current playback state.
        """
        if player.loading or player.playing:
            return 'title-playing'
        return 'title-idle'

    def get_text(self):
        """
        Return text for display in this bar.
        """
        track = player.get_current_track()
        if track is None:
            return u'{} {}'.format(meta.APP_NAME, meta.VERSION_WITH_CODENAME)
        progress = player.play_progress_seconds
        total = player.length_seconds
        return (
            self.get_style(),
            u' {} {} - {} {} [{:02d}:{:02d} / {:02d}:{:02d}]'.format(
                # u'|>' if player.is_playing else u'||',
                # self.get_rotating_bar(),
                u'\u2505' if player.loading else
                u'\u25B6' if player.playing else u'\u25A0',
                track.artist,
                track.title,
                self.RATING_ICONS[track.rating],
                progress // 60,
                progress % 60,
                total // 60,
                total % 60,
            ))

    def update(self, *_):
        """
        Force update of this widget.
        Called when something unrelated to completion value changes,
        e.g. current track or playback flags.
        """
        self.text.set_text(self.get_text())
        self.progressbar.set_progress(player.play_progress)
        self.progressbar.set_done_style('progressbar_done' if player.
                                        playing else 'progressbar_done_paused')
        self.shuffle_el.attr = 'flag-active' \
            if player.random \
            else 'flag'
        self.repeat_one_el.attr = 'flag-active' \
            if player.repeat_one \
            else 'flag'
        self.repeat_el.attr = 'flag-active' \
            if player.repeat_queue \
            else 'flag'
        self.app.redraw()

    def stop(self, *_):
        """
        Force update of this widget.
        Only runs when the queue is entirely cleared.
        """
        self.text.set_text("")
        self.progressbar.set_progress(0)
        self.progressbar.set_done_style('progressbar_done')
        self.shuffle_el.attr = 'flag-active' \
            if player.random \
            else 'flag'
        self.repeat_el.attr = 'flag-active' \
            if player.repeat_one \
            else 'flag'
        self.app.redraw()

    def tick(self):
        """
        Increase rotating index & trigger redraw.
        """
        self.rotating_index += 1
        self.update()
Esempio n. 9
0
class SongListItem(urwid.Pile):
    """
    Widget that represents single song item.
    """
    _unicode = settings_manager.get('unicode', 'clay_settings')
    signals = [
        'activate', 'play', 'append-requested', 'unappend-requested',
        'clear-queue', 'station-requested', 'context-menu-requested'
    ]

    def __init__(self, track):
        self.track = track
        self.rating = Icons.ratings[track.rating]
        self.explicit = Icons.explicit[track.explicit_rating]
        self.index = 0
        self.state = States.idle
        self.line1 = _Line1()
        self.line2 = _Line2()

        self.content = urwid.Pile(
            [self.line1._wrap, self.line2._wrap,
             urwid.Text('')])

        self.is_focused = False

        super(SongListItem, self).__init__([self.content])
        self.update_text()

    def set_state(self, state):
        """
        Set state for this song.
        Possible choices are:

        - :attr:`States.idle`
        - :attr:`States.loading`
        - :attr:`States.playing`
        - :attr:`States.paused`
        """
        self.state = state
        self.update_text()

    @staticmethod
    def get_state_icon(state):
        """
        Get icon char for specific state.
        """
        return Icons.state[state.value]

    def update_text(self):
        """
        Update text of this item from the attached track.
        """
        self.line1.update_text(self)
        self.line2.update_text(self)

    @property
    def full_title(self):
        """
        Return song artist and title.
        """
        return u'{} - {} {}'.format(self.track.artist, self.track.title,
                                    self.rating)

    def keypress(self, size, key):
        """
        Handle keypress.
        """
        return hotkey_manager.keypress("song_item", self,
                                       super(SongListItem, self), size, key)

    def mouse_event(self, size, event, button, col, row, focus):
        """
        Handle mouse event.
        """
        if button == 1 and focus:
            urwid.emit_signal(self, 'activate', self)
            return None
        return super(SongListItem, self).mouse_event(size, event, button, col,
                                                     row, focus)

    def thumbs_up(self):
        """
        Thumb the currently selected song up.
        """
        self.track.rate_song((0 if self.track.rating == 5 else 5))

    def thumbs_down(self):
        """
        Thumb the currently selected song down.
        """
        self.track.rate_song((0 if self.track.rating == 1 else 1))

    def _send_signal(self, signal):
        urwid.emit_signal(self, signal, self)

    def activate(self):
        """
        Add the entire list to queue and begin playing
        """
        self._send_signal("activate")

    def clear_queue(self):
        """
        Removes all the songs from the queue.
        """
        self.set_state(States.idle)
        self.is_focused = False
        self._send_signal("clear-queue")

    def play(self):
        """
        Play this song.
        """
        self._send_signal("play")

    def append(self):
        """
        Add this song to the queue.
        """
        self._send_signal("append-requested")

    def unappend(self):
        """
        Remove this song from the queue.
        """
        if not self.is_currently_played:
            self._send_signal("unappend-requested")

    def request_station(self):
        """
        Create a Google Play Music radio for this song.
        """
        self._send_signal("station-requested")

    def show_context_menu(self):
        """
        Display the context menu for this song.
        """
        self._send_signal("context-menu-requested")

    @property
    def is_currently_played(self):
        """
        Return ``True`` if song is in state :attr:`.States.playing`
        or :attr:`.States.paused`.
        """
        return self.state in (States.loading, States.playing, States.paused)

    def set_index(self, index):
        """
        Set numeric index for this item.
        """
        self.index = index
        self.update_text()

    def render(self, size, focus=False):
        """
        Render widget & set focused state.
        """
        self.is_focused = focus
        self.update_text()
        return super(SongListItem, self).render(size, focus)
Esempio n. 10
0
"""
On-screen display stuff.
"""
from pydbus import SessionBus, Variant
from clay.core import meta, logger, settings_manager
from gi.repository import GLib

NOTIFICATION_BUS_NAME = ".Notifications"
BASE_NAME = "org.freedesktop"
ENABLED = settings_manager.get('desktop_notifications', 'clay_settings')


class _OSDManager(object):
    """
    Manages OSD notifications via DBus.
    """
    def __init__(self):
        if not ENABLED:
            self._actions = {}
            return

        self._last_id = 0
        self.bus = SessionBus()

        self.notifications = None
        self.bus.watch_name(BASE_NAME + NOTIFICATION_BUS_NAME,
                            name_appeared=self._register_bus_name,
                            name_vanished=self._deregister_bus_name)
        self._register_bus_name(None)

        self._actions = {"default": lambda *args: None}