Exemplo n.º 1
0
 def __init__(self, app):
     self.app = app
     self.username = urwid.Edit(
         edit_text=settings.get('username', 'play_settings') or '')
     self.password = urwid.Edit(
         mask='*',
         edit_text=settings.get('password', 'play_settings') or '')
     self.device_id = urwid.Edit(
         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') or False)
     self.equalizer = Equalizer()
     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'),
                 urwid.Divider(u'\u2500'),
                 self.equalizer,
             ]))
     ])
Exemplo n.º 2
0
    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._is_loading = True
        self.broadcast_state()
        self.track_changed.fire(track)

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

            if path is None:
                logger.debug('Track %s not in cache, downloading...',
                             track.store_id)
                track.get_url(callback=self._download_track)
            else:
                logger.debug('Track %s in cache, playing', track.store_id)
                self._play_ready(path, None, track)
        else:
            logger.debug('Starting to stream %s', track.store_id)
            track.get_url(callback=self._play_ready)
Exemplo n.º 3
0
Arquivo: app.py Projeto: xuhui/clay
    def log_in(self, use_token=True):
        """
        Called when this page is shown.

        Request user authorization.
        """
        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()
        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.')
Exemplo n.º 4
0
Arquivo: app.py Projeto: xuhui/clay
def main():
    """
    Application entrypoint.

    This function is required to allow Clay to be ran as application when installed via setuptools.
    """
    parser = argparse.ArgumentParser(
        prog=meta.APP_NAME,
        description=meta.DESCRIPTION,
        epilog="This project is neither affiliated nor endorsed by Google.")

    parser.add_argument("-v", "--version", action=MultilineVersionAction)

    keybinds_group = parser.add_mutually_exclusive_group()

    keybinds_group.add_argument(
        "--with-x-keybinds",
        help="define global X keybinds (requires Keybinder and PyGObject)",
        action='store_true')

    keybinds_group.add_argument(
        "--without-x-keybinds",
        help="Don't define global keybinds (overrides configuration file)",
        action='store_true')

    args = parser.parse_args()

    if args.version:
        exit(0)

    if (args.with_x_keybinds or settings.get('x_keybinds', 'clay_settings')) \
       and not args.without_x_keybinds:
        player.enable_xorg_bindings()

    # Create a 256 colour palette.
    palette = [(name, '', '', '', res['foreground'], res['background'])
               for name, res in settings.colours_config.items()]

    try:
        from setproctitle import setproctitle
    except ImportError:
        pass
    else:
        setproctitle('clay')

    # Run the actual program
    app_widget = AppWidget()
    loop = urwid.MainLoop(app_widget, palette)
    app_widget.set_loop(loop)
    loop.screen.set_terminal_properties(256)
    loop.run()
Exemplo n.º 5
0
    def _parse_x_hotkeys(self):
        """
        Reads out them configuration file and parses them into hotkeys readable by GTK.
        """
        hotkey_default_config = settings.get_default_config_section(
            'hotkeys', 'x_hotkeys')
        mod_key = settings.get('mod_key', 'hotkeys')
        hotkeys = {}

        for action in hotkey_default_config:
            key_seq = settings.get(action, 'hotkeys', 'x_hotkeys')

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

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

                hotkey = [self._to_gtk_modifier(key) for key in hotkey]

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

        return hotkeys
Exemplo n.º 6
0
    def _parse_hotkeys(self):
        """
        Reads out the configuration file and parse them into a hotkeys for urwid.
        """
        hotkey_config = settings.get_default_config_section(
            'hotkeys', 'clay_hotkeys')
        mod_key = settings.get('mod_key', 'hotkeys')
        hotkeys = {}

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

                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
Exemplo n.º 7
0
class PlayBar(urwid.Pile):
    """
    A widget that shows currently played track, playback progress and flags.
    """
    _unicode = settings.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.infobar = urwid.Columns([
            self.text,
            ('pack', self.shuffle_el),
            # ('pack', urwid.Text(' ')),
            ('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.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.is_loading or player.is_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.get_play_progress_seconds()
        total = player.get_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.is_loading else
                u'\u25B6' if player.is_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.get_play_progress())
        self.progressbar.set_done_style('progressbar_done' if player.is_playing
                                        else 'progressbar_done_paused')
        self.shuffle_el.attr = 'flag-active' \
            if player.get_is_random() \
            else 'flag'
        self.repeat_el.attr = 'flag-active' \
            if player.get_is_repeat_one() \
            else 'flag'
        self.app.redraw()

    def tick(self):
        """
        Increase rotating index & trigger redraw.
        """
        self.rotating_index += 1
        self.update()
Exemplo n.º 8
0
class SongListItem(urwid.Pile):
    """
    Widget that represents single song item.
    """
    _unicode = settings.get('unicode', 'clay_settings')
    signals = [
        'activate', 'play', 'append-requested', 'unappend-requested',
        '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.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("library_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 play(self):
        """
        Play this song.
        """
        self._send_signal("play")

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

    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)