def configure(self, parent): """ Show the configuration window """ if self.cfgWin is None: from pogo import gui # Create the window self.cfgWin = gui.window.Window('DesktopNotification.ui', 'vbox1', __name__, MOD_INFO[modules.MODINFO_L10N], 355, 345) self.cfgWin.getWidget('btn-ok').connect('clicked', self.onBtnOk) self.cfgWin.getWidget('btn-help').connect('clicked', self.onBtnHelp) self.cfgWin.getWidget('btn-cancel').connect( 'clicked', lambda btn: self.cfgWin.hide()) # Disable the 'Skip track' button if the server doesn't support buttons in notifications if 'actions' not in Notify.get_server_caps(): self.cfgWin.getWidget('chk-skipTrack').set_sensitive(False) if not self.cfgWin.isVisible(): self.cfgWin.getWidget('txt-title').set_text( prefs.get(__name__, 'title', PREFS_DEFAULT_TITLE)) self.cfgWin.getWidget('spn-duration').set_value( prefs.get(__name__, 'timeout', PREFS_DEFAULT_TIMEOUT)) self.cfgWin.getWidget('txt-body').get_buffer().set_text( prefs.get(__name__, 'body', PREFS_DEFAULT_BODY)) self.cfgWin.getWidget('chk-skipTrack').set_active( prefs.get(__name__, 'skip-track', PREFS_DEFAULT_SKIP_TRACK)) self.cfgWin.getWidget('btn-ok').grab_focus() self.cfgWin.show()
def onBtnOk(self, btn): """ Save new preferences """ # Skipping tracks newSkipTrack = self.cfgWin.getWidget('chk-skipTrack').get_active() oldSkipTrack = prefs.get(__name__, 'skip-track', PREFS_DEFAULT_SKIP_TRACK) prefs.set(__name__, 'skip-track', newSkipTrack) if oldSkipTrack != newSkipTrack and self.notif is not None: if newSkipTrack: self.notif.add_action('stop', _('Skip track'), self.onSkipTrack) else: self.notif.clear_actions() # Timeout newTimeout = int(self.cfgWin.getWidget('spn-duration').get_value()) oldTimeout = prefs.get(__name__, 'timeout', PREFS_DEFAULT_TIMEOUT) prefs.set(__name__, 'timeout', newTimeout) if oldTimeout != newTimeout and self.notif is not None: self.notif.set_timeout(newTimeout * 1000) # Other preferences prefs.set(__name__, 'title', self.cfgWin.getWidget('txt-title').get_text()) (start, end) = self.cfgWin.getWidget('txt-body').get_buffer().get_bounds() prefs.set( __name__, 'body', self.cfgWin.getWidget('txt-body').get_buffer().get_text( start, end, False)) self.cfgWin.hide()
def __createNotification(self, title, body, icon): """ Create the Notification object """ if not Notify.init(consts.appNameShort): logger.error('[%s] Initialization of python-notify failed' % MOD_INFO[modules.MODINFO_NAME]) self.notif = Notify.Notification.new(title, body, icon) self.notif.set_urgency(Notify.Urgency.LOW) self.notif.set_timeout( prefs.get(__name__, 'timeout', PREFS_DEFAULT_TIMEOUT) * 1000) if prefs.get(__name__, 'skip-track', PREFS_DEFAULT_SKIP_TRACK): self.notif.add_action('stop', _('Skip track'), self.onSkipTrack)
def onAppStarted(self): """ The module has been loaded """ self.tree = None self.cfgWin = None self.scrolled = Gtk.ScrolledWindow() self.treeState = prefs.get(__name__, 'saved-states', None) self.scrolled.set_shadow_type(Gtk.ShadowType.IN) self.scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.scrolled.show() left_vbox = prefs.getWidgetsTree().get_object('vbox3') left_vbox.pack_start(self.scrolled, True, True, 0) self.static_paths = ['/', consts.dirBaseUsr] self.displaying_results = False self.populate_tree() music_paths = self.get_music_paths_from_tree() modules.postMsg(consts.MSG_EVT_MUSIC_PATHS_CHANGED, {'paths': music_paths}) self.tree.connect('drag-begin', self.onDragBegin)
def onAppStarted(self): """ Real initialization function, called when this module has been loaded """ # Widgets self.currTrack = None show_thumb = prefs.get(__name__, 'show_thumb', True) self.cover_spot = CoverSpot(show_thumb)
def main(): log.logger.info('Started') # Localization locale.setlocale(locale.LC_ALL, '') gettext.textdomain(consts.appNameShort) gettext.bindtextdomain(consts.appNameShort, consts.dirLocale) # Command line prefs.setCmdLine((optOptions, optArgs)) # Create the GUI wTree = loadGladeFile('MainWindow.ui') paned = wTree.get_object('pan-main') window = wTree.get_object('win-main') prefs.setWidgetsTree(wTree) window.set_icon_list([ GdkPixbuf.Pixbuf.new_from_file(consts.fileImgIcon16), GdkPixbuf.Pixbuf.new_from_file(consts.fileImgIcon24), GdkPixbuf.Pixbuf.new_from_file(consts.fileImgIcon32), GdkPixbuf.Pixbuf.new_from_file(consts.fileImgIcon48), GdkPixbuf.Pixbuf.new_from_file(consts.fileImgIcon64), GdkPixbuf.Pixbuf.new_from_file(consts.fileImgIcon128)]) # RGBA support # TODO: Is this still needed? visual = window.get_screen().get_rgba_visual() window.set_visual(visual) # Show all widgets and restore the window size BEFORE hiding some of them # when restoring the view mode # Resizing must be done before showing the window to make sure that the WM # correctly places the window if prefs.get(__name__, 'win-is-maximized', DEFAULT_MAXIMIZED_STATE): window.maximize() height = prefs.get(__name__, 'win-height', DEFAULT_WIN_HEIGHT) window.resize(prefs.get(__name__, 'win-width', DEFAULT_WIN_WIDTH), height) window.show_all() paned.set_position(prefs.get(__name__, 'paned-pos', DEFAULT_PANED_POS)) # Initialization done, let's continue the show GObject.idle_add(realStartup, window, paned) Gtk.main()
def onResize(win, rect): """ Save the new size of the window """ maximized = win.get_state() & Gdk.WindowState.MAXIMIZED if not maximized: prefs.set(__name__, 'win-width', rect.width) prefs.set(__name__, 'win-height', rect.height) view_mode = prefs.get(__name__, 'view-mode', DEFAULT_VIEW_MODE) if view_mode in (consts.VIEW_MODE_FULL, consts.VIEW_MODE_PLAYLIST): prefs.set(__name__, 'full-win-height', rect.height)
def showNotification(self): """ Show the notification based on the current track """ self.timeout = None # Can this happen? if self.currTrack is None: return False # Contents body = self.currTrack.formatHTMLSafe( prefs.get(__name__, 'body', PREFS_DEFAULT_BODY)) title = self.currTrack.format( prefs.get(__name__, 'title', PREFS_DEFAULT_TITLE)) # Icon if self.currCover is None: img = consts.fileImgIcon64 else: img = self.currCover if os.path.isfile(img): icon = 'file://' + img else: icon = Gtk.STOCK_DIALOG_INFO # Create / Update the notification and show it if self.notif is None: self.__createNotification(title, body, icon) else: self.notif.update(title, body, icon) # Catch errors that occur when pynotify is not installed properly. try: self.notif.show() except GObject.GError: pass return False
def configure(self, parent): """ Show the configuration window """ if self.cfgWin is None: from pogo.gui.window import Window self.cfgWin = Window('Covers.ui', 'vbox1', __name__, MOD_INFO[modules.MODINFO_L10N], 320, 265) self.cfgWin.getWidget('btn-ok').connect('clicked', self.onBtnOk) self.cfgWin.getWidget('img-lastfm').set_from_file( os.path.join(consts.dirPix, 'audioscrobbler.png')) self.cfgWin.getWidget('btn-help').connect('clicked', self.onBtnHelp) self.cfgWin.getWidget('chk-downloadCovers').connect( 'toggled', self.onDownloadCoversToggled) self.cfgWin.getWidget('btn-cancel').connect( 'clicked', lambda btn: self.cfgWin.hide()) if not self.cfgWin.isVisible(): downloadCovers = prefs.get(__name__, 'download-covers', PREFS_DFT_DOWNLOAD_COVERS) preferUserCovers = prefs.get(__name__, 'prefer-user-covers', PREFS_DFT_PREFER_USER_COVERS) userCoverFilenames = prefs.get(__name__, 'user-cover-filenames', PREFS_DFT_USER_COVER_FILENAMES) self.cfgWin.getWidget('btn-ok').grab_focus() self.cfgWin.getWidget('txt-filenames').set_text( ', '.join(userCoverFilenames)) self.cfgWin.getWidget('chk-downloadCovers').set_active( downloadCovers) self.cfgWin.getWidget('chk-preferUserCovers').set_active( preferUserCovers) self.cfgWin.getWidget('chk-preferUserCovers').set_sensitive( downloadCovers) self.cfgWin.show()
def getUserCover(self, trackPath): """ Return the path to a cover file in trackPath, None if no cover found """ # Create a dictionary with candidates candidates = {} for (file, path) in tools.listDir(trackPath, True): (name, ext) = os.path.splitext(file.lower()) if ext in ACCEPTED_FILE_FORMATS: candidates[name] = path # Check each possible name using the its index in the list as its priority for name in prefs.get(__name__, 'user-cover-filenames', PREFS_DFT_USER_COVER_FILENAMES): if name in candidates: return candidates[name] if name == '*' and len(candidates) != 0: return next(iter(candidates.values())) return None
def modInit(self): """ Initialize the module """ self.lvls = prefs.get(__name__, 'levels', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) self.preset = prefs.get(__name__, 'preset', 'Flat') self.cfgWindow = None
func(*args) else: self.handlers[msg](**params) # --== Entry point ==-- mModDir = os.path.dirname(__file__) # Where modules are located mModules = {} # All known modules associated to an 'active' boolean mHandlers = dict([(msg, set()) for msg in range(consts.MSG_END_VALUE) ]) # For each message, store the set of registered modules mModulesLock = threading.Lock( ) # Protects the modules list from concurrent access mHandlersLock = threading.Lock( ) # Protects the handlers list from concurrent access mEnabledModules = prefs.get(__name__, 'enabled_modules', []) # List of modules currently enabled # Do not load modules in blacklist. They also won't show up in the preferences. blacklist = ['__init__.py'] def load_enabled_modules(): # Find modules, instantiate those that are mandatory or that have been previously enabled by the user sys.path.append(mModDir) for file in sorted( os.path.splitext(file)[0] for file in os.listdir(mModDir) if file.endswith('.py') and file not in blacklist): try: pModule = __import__(file) modInfo = getattr(pModule, 'MOD_INFO')
def realStartup(window, paned): """ Perform all the initialization stuff which is not mandatory to display the window. This function should be called within the GTK main loop, once the window has been displayed """ # Is the application started for the first time? first_start = prefs.get(__name__, 'first-time', True) logging.debug('First start: {}'.format(first_start)) if first_start: prefs.set(__name__, 'first-time', False) # Enable some modules by default prefs.set('modules', 'enabled_modules', ['Covers', 'Desktop Notification']) import atexit import signal import dbus.mainloop.glib from pogo import modules modules.load_enabled_modules() def onDelete(win, event): """ Use our own quit sequence, that will itself destroy the window """ win.hide() modules.postQuitMsg() return True def onResize(win, rect): """ Save the new size of the window """ maximized = win.get_state() & Gdk.WindowState.MAXIMIZED if not maximized: prefs.set(__name__, 'win-width', rect.width) prefs.set(__name__, 'win-height', rect.height) view_mode = prefs.get(__name__, 'view-mode', DEFAULT_VIEW_MODE) if view_mode in (consts.VIEW_MODE_FULL, consts.VIEW_MODE_PLAYLIST): prefs.set(__name__, 'full-win-height', rect.height) def onPanedResize(win, rect): prefs.set(__name__, 'paned-pos', paned.get_position()) def onState(win, event): """ Save the new state of the window """ if event.changed_mask & Gdk.WindowState.MAXIMIZED: maximized = bool(event.new_window_state & Gdk.WindowState.MAXIMIZED) prefs.set(__name__, 'win-is-maximized', maximized) def atExit(): """ Final function, called just before exiting the Python interpreter """ prefs.save() log.logger.info('Stopped') # D-Bus dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) # Register some handlers (Signal SIGKILL cannot be caught) atexit.register(atExit) signal.signal(signal.SIGINT, lambda _sig, _frame: onDelete(window, None)) signal.signal(signal.SIGTERM, lambda _sig, _frame: onDelete(window, None)) # GTK handlers window.connect('delete-event', onDelete) window.connect('size-allocate', onResize) window.connect('window-state-event', onState) paned.connect('size-allocate', onPanedResize) # Let's go GObject.idle_add(modules.postMsg, consts.MSG_EVT_APP_STARTED)
def select_last_played_track(self): last_path = prefs.get(__name__, 'last-played-track', None) if last_path: parent_path = (last_path[0],) GObject.idle_add(self.tree.scroll_to_cell, parent_path) self.tree.get_selection().select_path(parent_path)
def onNewTrack(self, track): """ A new track is being played, try to retrieve the corresponding cover """ # Make sure we have enough information if track.getArtist() == consts.UNKNOWN_ARTIST or track.getAlbum( ) == consts.UNKNOWN_ALBUM: modules.postMsg(consts.MSG_CMD_SET_COVER, { 'track': track, 'pathThumbnail': None, 'pathFullSize': None }) return album = track.getAlbum().lower() artist = track.getArtist().lower() rawCover = None self.currTrack = track # Let's see whether we already have the cover if (artist, album) in self.coverMap: covers = self.coverMap[(artist, album)] pathFullSize = covers[CVR_FULL] pathThumbnail = covers[CVR_THUMB] # Make sure the files are still there if os.path.exists(pathThumbnail) and os.path.exists(pathFullSize): modules.postMsg( consts.MSG_CMD_SET_COVER, { 'track': track, 'pathThumbnail': pathThumbnail, 'pathFullSize': pathFullSize }) return # Should we check for a user cover? if (not prefs.get(__name__, 'download-covers', PREFS_DFT_DOWNLOAD_COVERS) or prefs.get(__name__, 'prefer-user-covers', PREFS_DFT_PREFER_USER_COVERS)): rawCover = self.getUserCover(os.path.dirname(track.getFilePath())) # Is it in our cache? if rawCover is None: rawCover = self.getFromCache(artist, album) # If we still don't have a cover, maybe we can try to download it if rawCover is None: modules.postMsg(consts.MSG_CMD_SET_COVER, { 'track': track, 'pathThumbnail': None, 'pathFullSize': None }) if prefs.get(__name__, 'download-covers', PREFS_DFT_DOWNLOAD_COVERS): rawCover = self.getFromInternet(artist, album) # If we still don't have a cover, too bad # Otherwise, generate a thumbnail and a full size cover, and add it to our cover map if rawCover is not None: import tempfile thumbnail = tempfile.mktemp() + '.png' fullSizeCover = tempfile.mktemp() + '.png' self.generateThumbnail(rawCover, thumbnail, 'PNG') self.generateFullSizeCover(rawCover, fullSizeCover, 'PNG') if os.path.exists(thumbnail) and os.path.exists(fullSizeCover): self.coverMap[(artist, album)] = (thumbnail, fullSizeCover) modules.postMsg( consts.MSG_CMD_SET_COVER, { 'track': track, 'pathThumbnail': thumbnail, 'pathFullSize': fullSizeCover }) else: modules.postMsg(consts.MSG_CMD_SET_COVER, { 'track': track, 'pathThumbnail': None, 'pathFullSize': None })