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
33 from configparser
import RawConfigParser
35 from gi
.repository
import GdkPixbuf
, GObject
, Gtk
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))
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
]:
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
79 self
.append((None, name
, value
, True))
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
):
96 if self
.icon
is not None:
97 # Load it from an absolute filename
98 if os
.path
.exists(self
.icon
):
100 return GdkPixbuf
.Pixbuf
.new_from_file_at_size(self
.icon
, 24, 24)
101 except GObject
.GError
:
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
):
128 'HKEY_CLASSES_ROOT': winreg
.HKEY_CLASSES_ROOT
,
131 parts
= path
.split('\\')
133 key
= winreg
.OpenKey(rootmap
[root
], parts
.pop(0))
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
:
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
):
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
)),
165 def add_separator(self
):
166 self
.apps
.append(UserApplication(
168 ';'.join((mime
+ '/*' for mime
in self
.mimetypes
)), ''))
169 self
.__has
_sep
= True
175 self
.__has
_read
= True
177 for caption
, types
, hkey
in WIN32_APP_REG_KEYS
:
179 cmdline
= win32_read_registry_key(hkey
)
180 self
.apps
.append(UserApplication(
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
):
194 parser
= RawConfigParser()
195 parser
.read([filename
])
196 if not parser
.has_section(sect
):
199 app_categories
= parser
.get(sect
, 'Categories')
200 if not app_categories
:
203 if not any(category
in self
.PLAYER_CATEGORIES
204 for category
in app_categories
.split(';')):
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
:
216 self
.apps
.append(UserApplication(app_name
, app_cmd
, app_mime
, app_icon
))
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
)