2 Complete implementation of the XDG Desktop Entry Specification Version 0.9.4
3 http://standards.freedesktop.org/desktop-entry-spec/
6 - Encoding: Legacy Mixed
7 - Does not check exec parameters
9 - Does not completly validate deprecated/kde items
10 - Does not completly check categories
13 from xdg.IniFile import *
14 from xdg.BaseDirectory import *
17 class DesktopEntry(IniFile):
18 "Class to parse and validate DesktopEntries"
20 defaultGroup = 'Desktop Entry'
22 def __init__(self, filename=None):
24 if filename and os.path.exists(filename):
32 def parse(self, file):
33 IniFile.parse(self, file, ["Desktop Entry", "KDE Desktop Entry"])
37 return self.get('Type')
38 """ @deprecated, use getVersionString instead """
40 return self.get('Version', type="numeric")
41 def getVersionString(self):
42 return self.get('Version')
44 return self.get('Name', locale=True)
45 def getGenericName(self):
46 return self.get('GenericName', locale=True)
47 def getNoDisplay(self):
48 return self.get('NoDisplay', type="boolean")
50 return self.get('Comment', locale=True)
52 return self.get('Icon', locale=True)
54 return self.get('Hidden', type="boolean")
55 def getOnlyShowIn(self):
56 return self.get('OnlyShowIn', list=True)
57 def getNotShowIn(self):
58 return self.get('NotShowIn', list=True)
60 return self.get('TryExec')
62 return self.get('Exec')
64 return self.get('Path')
65 def getTerminal(self):
66 return self.get('Terminal', type="boolean")
67 """ @deprecated, use getMimeTypes instead """
68 def getMimeType(self):
69 return self.get('MimeType', list=True, type="regex")
70 def getMimeTypes(self):
71 return self.get('MimeType', list=True)
72 def getCategories(self):
73 return self.get('Categories', list=True)
74 def getStartupNotify(self):
75 return self.get('StartupNotify', type="boolean")
76 def getStartupWMClass(self):
77 return self.get('StartupWMClass')
79 return self.get('URL')
83 def getServiceTypes(self):
84 return self.get('ServiceTypes', list=True)
86 return self.get('DocPath')
87 def getKeywords(self):
88 return self.get('Keywords', list=True, locale=True)
89 def getInitialPreference(self):
90 return self.get('InitialPreference')
92 return self.get('Dev')
94 return self.get('FSType')
95 def getMountPoint(self):
96 return self.get('MountPoint')
97 def getReadonly(self):
98 return self.get('ReadOnly', type="boolean")
99 def getUnmountIcon(self):
100 return self.get('UnmountIcon', locale=True)
103 # start deprecated keys
104 def getMiniIcon(self):
105 return self.get('MiniIcon', locale=True)
106 def getTerminalOptions(self):
107 return self.get('TerminalOptions')
108 def getDefaultApp(self):
109 return self.get('DefaultApp')
110 def getProtocols(self):
111 return self.get('Protocols', list=True)
112 def getExtensions(self):
113 return self.get('Extensions', list=True)
114 def getBinaryPattern(self):
115 return self.get('BinaryPattern')
116 def getMapNotify(self):
117 return self.get('MapNotify')
118 def getEncoding(self):
119 return self.get('Encoding')
120 def getSwallowTitle(self):
121 return self.get('SwallowTitle', locale=True)
122 def getSwallowExec(self):
123 return self.get('SwallowExec')
124 def getSortOrder(self):
125 return self.get('SortOrder', list=True)
126 def getFilePattern(self):
127 return self.get('FilePattern', type="regex")
128 def getActions(self):
129 return self.get('Actions', list=True)
130 # end deprecated keys
132 # desktop entry edit stuff
133 def new(self, filename):
134 if os.path.splitext(filename)[1] == ".desktop":
136 elif os.path.splitext(filename)[1] == ".directory":
139 raise ParsingError("Unknown extension", filename)
141 self.content = dict()
142 self.addGroup(self.defaultGroup)
143 self.set("Type", type)
144 self.filename = filename
145 # end desktop entry edit stuff
148 def checkExtras(self):
150 if self.defaultGroup == "KDE Desktop Entry":
151 self.warnings.append('[KDE Desktop Entry]-Header is deprecated')
154 if self.fileExtension == ".kdelnk":
155 self.warnings.append("File extension .kdelnk is deprecated")
156 elif self.fileExtension != ".desktop" and self.fileExtension != ".directory":
157 self.warnings.append('Unknown File extension')
161 self.type = self.content[self.defaultGroup]["Type"]
163 self.errors.append("Key 'Type' is missing")
167 self.name = self.content[self.defaultGroup]["Name"]
169 self.errors.append("Key 'Name' is missing")
171 def checkGroup(self, group):
172 # check if group header is valid
173 if not (group == self.defaultGroup \
174 or re.match("^\Desktop Action [a-zA-Z]+\$", group) \
175 or (re.match("^\X-", group) and group.decode("utf-8", "ignore").encode("ascii", 'ignore') == group)):
176 self.errors.append("Invalid Group name: %s" % group)
178 #OnlyShowIn and NotShowIn
179 if self.content[group].has_key("OnlyShowIn") and self.content[group].has_key("NotShowIn"):
180 self.errors.append("Group may either have OnlyShowIn or NotShowIn, but not both")
182 def checkKey(self, key, value, group):
185 if value == "ServiceType" or value == "Service" or value == "FSDevice":
186 self.warnings.append("Type=%s is a KDE extension" % key)
187 elif value == "MimeType":
188 self.warnings.append("Type=MimeType is deprecated")
189 elif not (value == "Application" or value == "Link" or value == "Directory"):
190 self.errors.append("Value of key 'Type' must be Application, Link or Directory, but is '%s'" % value)
192 if self.fileExtension == ".directory" and not value == "Directory":
193 self.warnings.append("File extension is .directory, but Type is '%s'" % value)
194 elif self.fileExtension == ".desktop" and value == "Directory":
195 self.warnings.append("Files with Type=Directory should have the extension .directory")
197 if value == "Application":
198 if not self.content[group].has_key("Exec"):
199 self.warnings.append("Type=Application needs 'Exec' key")
201 if not self.content[group].has_key("URL"):
202 self.warnings.append("Type=Application needs 'Exec' key")
204 elif key == "Version":
205 self.checkValue(key, value)
207 elif re.match("^Name"+xdg.Locale.regex+"$", key):
210 elif re.match("^GenericName"+xdg.Locale.regex+"$", key):
213 elif key == "NoDisplay":
214 self.checkValue(key, value, type="boolean")
216 elif re.match("^Comment"+xdg.Locale.regex+"$", key):
219 elif re.match("^Icon"+xdg.Locale.regex+"$", key):
220 self.checkValue(key, value)
222 elif key == "Hidden":
223 self.checkValue(key, value, type="boolean")
225 elif key == "OnlyShowIn":
226 self.checkValue(key, value, list=True)
227 self.checkOnlyShowIn(value)
229 elif key == "NotShowIn":
230 self.checkValue(key, value, list=True)
231 self.checkOnlyShowIn(value)
233 elif key == "TryExec":
234 self.checkValue(key, value)
235 self.checkType(key, "Application")
238 self.checkValue(key, value)
239 self.checkType(key, "Application")
242 self.checkValue(key, value)
243 self.checkType(key, "Application")
245 elif key == "Terminal":
246 self.checkValue(key, value, type="boolean")
247 self.checkType(key, "Application")
249 elif key == "MimeType":
250 self.checkValue(key, value, list=True)
251 self.checkType(key, "Application")
253 elif key == "Categories":
254 self.checkValue(key, value)
255 self.checkType(key, "Application")
256 self.checkCategorie(value)
258 elif key == "StartupNotify":
259 self.checkValue(key, value, type="boolean")
260 self.checkType(key, "Application")
262 elif key == "StartupWMClass":
263 self.checkType(key, "Application")
266 self.checkValue(key, value)
267 self.checkType(key, "URL")
270 elif key == "ServiceTypes":
271 self.checkValue(key, value, list=True)
272 self.warnings.append("Key '%s' is a KDE extension" % key)
274 elif key == "DocPath":
275 self.checkValue(key, value)
276 self.warnings.append("Key '%s' is a KDE extension" % key)
278 elif re.match("^Keywords"+xdg.Locale.regex+"$", key):
279 self.checkValue(key, value, list=True)
280 self.warnings.append("Key '%s' is a KDE extension" % key)
282 elif key == "InitialPreference":
283 self.checkValue(key, value, type="numeric")
284 self.warnings.append("Key '%s' is a KDE extension" % key)
287 self.checkValue(key, value)
288 self.checkType(key, "FSDevice")
289 self.warnings.append("Key '%s' is a KDE extension" % key)
291 elif key == "FSType":
292 self.checkValue(key, value)
293 self.checkType(key, "FSDevice")
294 self.warnings.append("Key '%s' is a KDE extension" % key)
296 elif key == "MountPoint":
297 self.checkValue(key, value)
298 self.checkType(key, "FSDevice")
299 self.warnings.append("Key '%s' is a KDE extension" % key)
301 elif key == "ReadOnly":
302 self.checkValue(key, value, type="boolean")
303 self.checkType(key, "FSDevice")
304 self.warnings.append("Key '%s' is a KDE extension" % key)
306 elif re.match("^UnmountIcon"+xdg.Locale.regex+"$", key):
307 self.checkValue(key, value)
308 self.checkType(key, "FSDevice")
309 self.warnings.append("Key '%s' is a KDE extension" % key)
312 elif key == "Encoding":
313 self.checkValue(key, value)
314 self.warnings.append("Key '%s' is deprecated" % key)
316 elif re.match("^MiniIcon"+xdg.Locale.regex+"$", key):
317 self.checkValue(key, value)
318 self.warnings.append("Key '%s' is deprecated" % key)
320 elif key == "TerminalOptions":
321 self.checkValue(key, value)
322 self.warnings.append("Key '%s' is deprecated" % key)
324 elif key == "DefaultApp":
325 self.checkValue(key, value)
326 self.warnings.append("Key '%s' is deprecated" % key)
328 elif key == "Protocols":
329 self.checkValue(key, value, list=True)
330 self.warnings.append("Key '%s' is deprecated" % key)
332 elif key == "Extensions":
333 self.checkValue(key, value, list=True)
334 self.warnings.append("Key '%s' is deprecated" % key)
336 elif key == "BinaryPattern":
337 self.checkValue(key, value)
338 self.warnings.append("Key '%s' is deprecated" % key)
340 elif key == "MapNotify":
341 self.checkValue(key, value)
342 self.warnings.append("Key '%s' is deprecated" % key)
344 elif re.match("^SwallowTitle"+xdg.Locale.regex+"$", key):
345 self.warnings.append("Key '%s' is deprecated" % key)
347 elif key == "SwallowExec":
348 self.checkValue(key, value)
349 self.warnings.append("Key '%s' is deprecated" % key)
351 elif key == "FilePattern":
352 self.checkValue(key, value, type="regex", list=True)
353 self.warnings.append("Key '%s' is deprecated" % key)
355 elif key == "SortOrder":
356 self.checkValue(key, value, list=True)
357 self.warnings.append("Key '%s' is deprecated" % key)
359 elif key == "Actions":
360 self.checkValue(key, value, list=True)
361 self.warnings.append("Key '%s' is deprecated" % key)
364 elif re.match("^X-[a-zA-Z0-9-]+", key):
368 self.errors.append("Invalid key: %s" % key)
370 def checkType(self, key, type):
371 if not self.getType() == type:
372 self.errors.append("Key '%s' only allowed in Type=%s" % (key, type))
374 def checkOnlyShowIn(self, value):
375 values = self.getList(value)
376 valid = ["GNOME", "KDE", "ROX", "XFCE", "Old", "LXDE"]
378 if item not in valid and item[0:2] != "X-":
379 self.errors.append("'%s' is not a registered OnlyShowIn value" % item);
381 def checkCategorie(self, value):
382 values = self.getList(value)
384 main = ["AudioVideo", "Audio", "Video", "Development", "Education", "Game", "Graphics", "Network", "Office", "Settings", "System", "Utility"]
390 self.errors.append("Missing main category")
392 additional = ["Building", "Debugger", "IDE", "GUIDesigner", "Profiling", "RevisionControl", "Translation", "Calendar", "ContactManagement", "Database", "Dictionary", "Chart", "Email", "Finance", "FlowChart", "PDA", "ProjectManagement", "Presentation", "Spreadsheet", "WordProcessor", "2DGraphics", "VectorGraphics", "3DGraphics", "RasterGraphics", "Scanning", "OCR", "Photography", "Publishing", "Viewer", "TextTools", "DesktopSettings", "HardwareSettings", "Printing", "PackageManager", "Dialup", "InstantMessaging", "Chat", "IRCClient", "FileTransfer", "HamRadio", "News", "P2P", "RemoteAccess", "Telephony", "TelephonyTools", "VideoConference", "WebBrowser", "WebDevelopment", "Midi", "Mixer", "Sequencer", "Tuner", "TV", "AudioVideoEditing", "Player", "Recorder", "DiscBurning", "ActionGame", "AdventureGame", "ArcadeGame", "BoardGame", "BlocksGame", "CardGame", "KidsGame", "LogicGame", "RolePlaying", "Simulation", "SportsGame", "StrategyGame", "Art", "Construction", "Music", "Languages", "Science", "ArtificialIntelligence", "Astronomy", "Biology", "Chemistry", "ComputerScience", "DataVisualization", "Economy", "Electricity", "Geography", "Geology", "Geoscience", "History", "ImageProcessing", "Literature", "Math", "NumericalAnalysis", "MedicalSoftware", "Physics", "Robotics", "Sports", "ParallelComputing", "Amusement", "Archiving", "Compression", "Electronics", "Emulator", "Engineering", "FileTools", "FileManager", "TerminalEmulator", "Filesystem", "Monitor", "Security", "Accessibility", "Calculator", "Clock", "TextEditor", "Documentation", "Core", "KDE", "GNOME", "GTK", "Qt", "Motif", "Java", "ConsoleOnly", "Screensaver", "TrayIcon", "Applet", "Shell"]
395 if item not in additional + main and item[0:2] != "X-":
396 self.errors.append("'%s' is not a registered Category" % item);