Sort episodes by published date when sending to folder
[gpodder.git] / src / gpodder / gtkui / desktopfile.py
blobfe195006e50c26b75095e5cebfe34e788268e950
1 # -*- coding: utf-8 -*-
3 # gPodder - A media aggregator and podcast client
4 # Copyright (c) 2005-2018 The gPodder Team
6 # gPodder is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # gPodder is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 # libplayers.py -- get list of potential playback apps
23 # thomas perl <thp@perli.net> 20060329
27 import glob
28 import logging
29 import os
30 import os.path
31 import re
32 import threading
33 from configparser import RawConfigParser
35 from gi.repository import GdkPixbuf, GObject, Gtk
37 import gpodder
39 _ = gpodder.gettext
41 logger = logging.getLogger(__name__)
43 # where are the .desktop files located?
44 userappsdirs = [os.path.expanduser(p) for p in (
45 '/usr/share/applications/',
46 '/usr/local/share/applications/',
47 '~/.local/share/applications',
48 '/var/lib/flatpak/exports/share/applications/',
49 '~/.local/share/flatpak/exports/share/applications/',
52 # the name of the section in the .desktop files
53 sect = 'Desktop Entry'
56 class PlayerListModel(Gtk.ListStore):
57 C_ICON, C_NAME, C_COMMAND, C_CUSTOM = list(range(4))
59 def __init__(self):
60 Gtk.ListStore.__init__(self, GdkPixbuf.Pixbuf, str, str, bool)
62 def insert_app(self, pixbuf, name, command):
63 self.append((pixbuf, name, command, False))
65 def get_command(self, index):
66 return self[index][self.C_COMMAND]
68 def get_index(self, value):
69 for index, row in enumerate(self):
70 if value == row[self.C_COMMAND]:
71 return index
73 last_row = self[-1]
74 name = _('Command: %s') % value
75 if last_row[self.C_CUSTOM]:
76 last_row[self.C_COMMAND] = value
77 last_row[self.C_NAME] = name
78 else:
79 self.append((None, name, value, True))
81 return len(self) - 1
83 @classmethod
84 def is_separator(cls, model, iterator):
85 return model.get_value(iterator, cls.C_COMMAND) == ''
88 class UserApplication(object):
89 def __init__(self, name, cmd, mime, icon):
90 self.name = name
91 self.cmd = cmd
92 self.icon = icon
93 self.mime = mime
95 def get_icon(self):
96 if self.icon is not None:
97 # Load it from an absolute filename
98 if os.path.exists(self.icon):
99 try:
100 return GdkPixbuf.Pixbuf.new_from_file_at_size(self.icon, 24, 24)
101 except GObject.GError:
102 pass
104 # Load it from the current icon theme
105 (icon_name, extension) = os.path.splitext(os.path.basename(self.icon))
106 theme = Gtk.IconTheme()
107 if theme.has_icon(icon_name):
108 return theme.load_icon(icon_name, 24, Gtk.IconLookupFlags.FORCE_SIZE)
110 def is_mime(self, mimetype):
111 return self.mime.find(mimetype + '/') != -1
114 WIN32_APP_REG_KEYS = [
115 ('Winamp', ('audio',), r'HKEY_CLASSES_ROOT\Winamp.File\shell\Play\command'),
116 ('foobar2000', ('audio',), r'HKEY_CLASSES_ROOT\Applications\foobar2000.exe\shell\open\command'),
117 ('Windows Media Player 11', ('audio', 'video'), r'HKEY_CLASSES_ROOT\WMP11.AssocFile.MP3\shell\open\command'),
118 ('QuickTime Player', ('audio', 'video'), r'HKEY_CLASSES_ROOT\QuickTime.mp3\shell\open\command'),
119 ('VLC', ('audio', 'video'), r'HKEY_CLASSES_ROOT\VLC.mp3\shell\open\command'),
120 ('PotPlayer', ('audio', 'video'), r'HKEY_CLASSES_ROOT\potrun\shell\open\command'),
124 def win32_read_registry_key(path):
125 import winreg
127 rootmap = {
128 'HKEY_CLASSES_ROOT': winreg.HKEY_CLASSES_ROOT,
131 parts = path.split('\\')
132 root = parts.pop(0)
133 key = winreg.OpenKey(rootmap[root], parts.pop(0))
135 while parts:
136 key = winreg.OpenKey(key, parts.pop(0))
138 value, type_ = winreg.QueryValueEx(key, '')
139 if type_ == winreg.REG_EXPAND_SZ:
140 cmdline = re.sub(r'%([^%]+)%', lambda m: os.environ[m.group(1)], value)
141 elif type_ == winreg.REG_SZ:
142 cmdline = value
143 else:
144 raise ValueError('Not a string: ' + path)
146 return cmdline.replace('%1', '%f').replace('%L', '%f')
149 class UserAppsReader(object):
150 # XDG categories, plus some others found in random .desktop files
151 # https://standards.freedesktop.org/menu-spec/latest/apa.html
152 PLAYER_CATEGORIES = ('Audio', 'Video', 'AudioVideo', 'Player')
154 def __init__(self, mimetypes):
155 self.apps = []
156 self.mimetypes = mimetypes
157 self.__has_read = False
158 self.__finished = threading.Event()
159 self.__has_sep = False
160 self.apps.append(UserApplication(
161 _('Default application'), 'default',
162 ';'.join((mime + '/*' for mime in self.mimetypes)),
163 'document-open'))
165 def add_separator(self):
166 self.apps.append(UserApplication(
167 '', '',
168 ';'.join((mime + '/*' for mime in self.mimetypes)), ''))
169 self.__has_sep = True
171 def read(self):
172 if self.__has_read:
173 return
175 self.__has_read = True
176 if gpodder.ui.win32:
177 for caption, types, hkey in WIN32_APP_REG_KEYS:
178 try:
179 cmdline = win32_read_registry_key(hkey)
180 self.apps.append(UserApplication(
181 caption, cmdline,
182 ';'.join(typ + '/*' for typ in types), None))
183 except Exception as e:
184 logger.warning('Parse HKEY error: %s (%s)', hkey, e)
186 for appdir in userappsdirs:
187 if os.path.exists(appdir):
188 for file in glob.glob(os.path.join(appdir, '*.desktop')):
189 self.parse_and_append(file)
190 self.__finished.set()
192 def parse_and_append(self, filename):
193 try:
194 parser = RawConfigParser()
195 parser.read([filename])
196 if not parser.has_section(sect):
197 return
199 app_categories = parser.get(sect, 'Categories')
200 if not app_categories:
201 return
203 if not any(category in self.PLAYER_CATEGORIES
204 for category in app_categories.split(';')):
205 return
207 # Find out if we need it by comparing mime types
208 app_mime = parser.get(sect, 'MimeType')
209 for needed_type in self.mimetypes:
210 if app_mime.find(needed_type + '/') != -1:
211 app_name = parser.get(sect, 'Name')
212 app_cmd = parser.get(sect, 'Exec')
213 app_icon = parser.get(sect, 'Icon')
214 if not self.__has_sep:
215 self.add_separator()
216 self.apps.append(UserApplication(app_name, app_cmd, app_mime, app_icon))
217 return
218 except:
219 return
221 def get_model(self, mimetype):
222 self.__finished.wait()
224 model = PlayerListModel()
225 for app in self.apps:
226 if app.is_mime(mimetype):
227 model.insert_app(app.get_icon(), app.name, app.cmd)
228 return model