1 /* validate.c: validate a desktop entry file
2 * vim: set ts=2 sw=2 et: */
5 * Copyright (C) 2007-2009 Vincent Untz <vuntz@gnome.org>
7 * A really small portion of this code comes from the old validate.c.
8 * The old validate.c was Copyright (C) 2002, 2004 Red Hat, Inc.
10 * Mark McLoughlin <mark@skynet.ie>
11 * Havoc Pennington <hp@pobox.com>
12 * Ray Strode <rstrode@redhat.com>
14 * A portion of this code comes from glib (gkeyfile.c)
15 * Authors of gkeyfile.c are:
19 * This program is free software; you can redistribute it and/or
20 * modify it under the terms of the GNU General Public License
21 * as published by the Free Software Foundation; either version 2
22 * of the License, or (at your option) any later version.
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, write to the Free Software
31 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
43 #include <glib/gstdio.h>
45 #include "keyfileutils.h"
46 #include "mimeutils.h"
49 /*FIXME: document where we are stricter than the spec
50 * + only UTF-8 (so no Legacy-Mixed encoding)
54 * + Lecagy-Mixed Encoding (annexe D)
55 * + The escape sequences \s, \n, \t, \r, and \\ are supported for values of
56 * type string and localestring, meaning ASCII space, newline, tab, carriage
57 * return, and backslash, respectively.
67 /* Types reserved for KDE */
74 /* Deprecated types */
83 DESKTOP_LOCALESTRING_TYPE,
86 DESKTOP_STRING_LIST_TYPE,
87 DESKTOP_LOCALESTRING_LIST_TYPE,
88 /* Deprecated types */
90 DESKTOP_REGEXP_LIST_TYPE
93 typedef struct _kf_keyvalue kf_keyvalue;
100 typedef struct _kf_validator kf_validator;
102 struct _kf_validator {
103 const char *filename;
105 GString *parse_buffer;
106 gboolean utf8_warning;
111 GHashTable *current_keys;
113 gboolean kde_reserved_warnings;
114 gboolean no_deprecated_warnings;
122 GList *application_keys;
124 GList *fsdevice_keys;
125 GList *mimetype_keys;
127 GHashTable *action_values;
128 GHashTable *action_groups;
130 gboolean fatal_error;
134 validate_string_key (kf_validator *kf,
139 validate_localestring_key (kf_validator *kf,
144 validate_boolean_key (kf_validator *kf,
149 validate_numeric_key (kf_validator *kf,
154 validate_string_list_key (kf_validator *kf,
159 validate_regexp_list_key (kf_validator *kf,
164 validate_localestring_list_key (kf_validator *kf,
170 handle_type_key (kf_validator *kf,
171 const char *locale_key,
174 handle_version_key (kf_validator *kf,
175 const char *locale_key,
178 handle_comment_key (kf_validator *kf,
179 const char *locale_key,
182 handle_icon_key (kf_validator *kf,
183 const char *locale_key,
186 handle_show_in_key (kf_validator *kf,
187 const char *locale_key,
190 handle_desktop_exec_key (kf_validator *kf,
191 const char *locale_key,
194 handle_exec_key (kf_validator *kf,
195 const char *locale_key,
198 handle_path_key (kf_validator *kf,
199 const char *locale_key,
202 handle_mime_key (kf_validator *kf,
203 const char *locale_key,
206 handle_categories_key (kf_validator *kf,
207 const char *locale_key,
210 handle_actions_key (kf_validator *kf,
211 const char *locale_key,
214 handle_dbus_activatable_key (kf_validator *kf,
215 const char *locale_key,
218 handle_dev_key (kf_validator *kf,
219 const char *locale_key,
222 handle_mountpoint_key (kf_validator *kf,
223 const char *locale_key,
226 handle_encoding_key (kf_validator *kf,
227 const char *locale_key,
230 handle_autostart_condition_key (kf_validator *kf,
231 const char *locale_key,
234 handle_key_for_application (kf_validator *kf,
235 const char *locale_key,
238 handle_key_for_link (kf_validator *kf,
239 const char *locale_key,
242 handle_key_for_fsdevice (kf_validator *kf,
243 const char *locale_key,
246 handle_key_for_mimetype (kf_validator *kf,
247 const char *locale_key,
253 gboolean kde_reserved;
255 } registered_types[] = {
256 { APPLICATION_TYPE, "Application", FALSE, FALSE },
257 { LINK_TYPE, "Link", FALSE, FALSE },
258 { DIRECTORY_TYPE, "Directory", FALSE, FALSE },
259 { SERVICE_TYPE, "Service", TRUE, FALSE },
260 { SERVICE_TYPE_TYPE, "ServiceType", TRUE, FALSE },
261 { FSDEVICE_TYPE, "FSDevice", TRUE, FALSE },
262 { MIMETYPE_TYPE, "MimeType", FALSE, TRUE }
267 gboolean (* validate) (kf_validator *kf,
271 } validate_for_type[] = {
272 { DESKTOP_STRING_TYPE, validate_string_key },
273 { DESKTOP_LOCALESTRING_TYPE, validate_localestring_key },
274 { DESKTOP_BOOLEAN_TYPE, validate_boolean_key },
275 { DESKTOP_NUMERIC_TYPE, validate_numeric_key },
276 { DESKTOP_STRING_LIST_TYPE, validate_string_list_key },
277 { DESKTOP_REGEXP_LIST_TYPE, validate_regexp_list_key },
278 { DESKTOP_LOCALESTRING_LIST_TYPE, validate_localestring_list_key }
286 gboolean kde_reserved;
287 gboolean (* handle_and_validate) (kf_validator *kf,
288 const char *locale_key,
290 } DesktopKeyDefinition;
292 static DesktopKeyDefinition registered_desktop_keys[] = {
293 { DESKTOP_STRING_TYPE, "Type", TRUE, FALSE, FALSE, handle_type_key },
294 /* it is numeric according to the spec, but it's not true in previous
295 * versions of the spec. handle_version_key() will manage this */
296 { DESKTOP_STRING_TYPE, "Version", FALSE, FALSE, FALSE, handle_version_key },
297 { DESKTOP_LOCALESTRING_TYPE, "Name", TRUE, FALSE, FALSE, NULL },
298 { DESKTOP_LOCALESTRING_TYPE, "GenericName", FALSE, FALSE, FALSE, NULL },
299 { DESKTOP_BOOLEAN_TYPE, "NoDisplay", FALSE, FALSE, FALSE, NULL },
300 { DESKTOP_LOCALESTRING_TYPE, "Comment", FALSE, FALSE, FALSE, handle_comment_key },
301 { DESKTOP_LOCALESTRING_TYPE, "Icon", FALSE, FALSE, FALSE, handle_icon_key },
302 { DESKTOP_BOOLEAN_TYPE, "Hidden", FALSE, FALSE, FALSE, NULL },
303 { DESKTOP_STRING_LIST_TYPE, "OnlyShowIn", FALSE, FALSE, FALSE, handle_show_in_key },
304 { DESKTOP_STRING_LIST_TYPE, "NotShowIn", FALSE, FALSE, FALSE, handle_show_in_key },
305 { DESKTOP_STRING_TYPE, "TryExec", FALSE, FALSE, FALSE, handle_key_for_application },
306 { DESKTOP_STRING_TYPE, "Exec", FALSE, FALSE, FALSE, handle_desktop_exec_key },
307 { DESKTOP_STRING_TYPE, "Path", FALSE, FALSE, FALSE, handle_path_key },
308 { DESKTOP_BOOLEAN_TYPE, "Terminal", FALSE, FALSE, FALSE, handle_key_for_application },
309 { DESKTOP_STRING_LIST_TYPE, "MimeType", FALSE, FALSE, FALSE, handle_mime_key },
310 { DESKTOP_STRING_LIST_TYPE, "Categories", FALSE, FALSE, FALSE, handle_categories_key },
311 { DESKTOP_BOOLEAN_TYPE, "StartupNotify", FALSE, FALSE, FALSE, handle_key_for_application },
312 { DESKTOP_STRING_TYPE, "StartupWMClass", FALSE, FALSE, FALSE, handle_key_for_application },
313 { DESKTOP_STRING_TYPE, "URL", FALSE, FALSE, FALSE, handle_key_for_link },
314 /* since 1.1 (used to be a key reserved for KDE since 0.9.4) */
315 { DESKTOP_LOCALESTRING_LIST_TYPE, "Keywords", FALSE, FALSE, FALSE, NULL },
316 /* since 1.1 (used to be in the spec before 1.0, but was not really
318 { DESKTOP_STRING_LIST_TYPE, "Actions", FALSE, FALSE, FALSE, handle_actions_key },
320 { DESKTOP_BOOLEAN_TYPE, "DBusActivatable", FALSE, FALSE, FALSE, handle_dbus_activatable_key },
322 /* Keys reserved for KDE */
325 { DESKTOP_STRING_TYPE, "ServiceTypes", FALSE, FALSE, TRUE, NULL },
326 { DESKTOP_STRING_TYPE, "DocPath", FALSE, FALSE, TRUE, NULL },
327 { DESKTOP_STRING_TYPE, "InitialPreference", FALSE, FALSE, TRUE, NULL },
329 { DESKTOP_STRING_TYPE, "Dev", FALSE, FALSE, TRUE, handle_dev_key },
330 { DESKTOP_STRING_TYPE, "FSType", FALSE, FALSE, TRUE, handle_key_for_fsdevice },
331 { DESKTOP_STRING_TYPE, "MountPoint", FALSE, FALSE, TRUE, handle_mountpoint_key },
332 { DESKTOP_BOOLEAN_TYPE, "ReadOnly", FALSE, FALSE, TRUE, handle_key_for_fsdevice },
333 { DESKTOP_STRING_TYPE, "UnmountIcon", FALSE, FALSE, TRUE, handle_key_for_fsdevice },
335 /* Deprecated keys */
338 { DESKTOP_STRING_TYPE, "Protocols", FALSE, TRUE, FALSE, NULL },
339 { DESKTOP_STRING_TYPE, "Extensions", FALSE, TRUE, FALSE, NULL },
340 { DESKTOP_STRING_TYPE, "BinaryPattern", FALSE, TRUE, FALSE, NULL },
341 { DESKTOP_STRING_TYPE, "MapNotify", FALSE, TRUE, FALSE, NULL },
343 { DESKTOP_REGEXP_LIST_TYPE, "Patterns", FALSE, TRUE, FALSE, handle_key_for_mimetype },
344 { DESKTOP_STRING_TYPE, "DefaultApp", FALSE, TRUE, FALSE, handle_key_for_mimetype },
345 { DESKTOP_STRING_TYPE, "MiniIcon", FALSE, TRUE, FALSE, NULL },
346 { DESKTOP_STRING_TYPE, "TerminalOptions", FALSE, TRUE, FALSE, NULL },
348 { DESKTOP_STRING_TYPE, "Encoding", FALSE, TRUE, FALSE, handle_encoding_key },
349 { DESKTOP_LOCALESTRING_TYPE, "SwallowTitle", FALSE, TRUE, FALSE, NULL },
350 { DESKTOP_STRING_TYPE, "SwallowExec", FALSE, TRUE, FALSE, NULL },
352 { DESKTOP_STRING_LIST_TYPE, "SortOrder", FALSE, TRUE, FALSE, NULL },
353 { DESKTOP_REGEXP_LIST_TYPE, "FilePattern", FALSE, TRUE, FALSE, NULL },
355 /* Keys from other specifications */
357 /* Autostart spec, currently proposed; adopted by GNOME */
358 { DESKTOP_STRING_TYPE, "AutostartCondition", FALSE, FALSE, FALSE, handle_autostart_condition_key }
361 static DesktopKeyDefinition registered_action_keys[] = {
362 { DESKTOP_LOCALESTRING_TYPE, "Name", TRUE, FALSE, FALSE, NULL },
363 { DESKTOP_LOCALESTRING_TYPE, "Icon", FALSE, FALSE, FALSE, handle_icon_key },
364 { DESKTOP_STRING_LIST_TYPE, "OnlyShowIn", FALSE, TRUE, FALSE, handle_show_in_key },
365 { DESKTOP_STRING_LIST_TYPE, "NotShowIn", FALSE, TRUE, FALSE, handle_show_in_key },
366 { DESKTOP_STRING_TYPE, "Exec", TRUE, FALSE, FALSE, handle_exec_key }
369 /* This should be the same list as in xdg-specs/menu/menu-spec.xml */
370 static const char *show_in_registered[] = {
371 "GNOME", "KDE", "LXDE", "LXQt", "MATE", "Razor", "ROX", "TDE", "Unity", "XFCE", "EDE", "Cinnamon", "Pantheon", "Budgie", "Enlightenment", "Deepin", "Old"
376 const char *first_arg[3];
377 unsigned int additional_args;
378 } registered_autostart_condition[] = {
379 { "GNOME", { NULL }, 1 },
380 { "GNOME3", { "if-session", "unless-session", NULL }, 1},
381 { "GSettings", { NULL }, 2 }
387 gboolean require_only_show_in;
389 const char *requires[2];
390 const char *suggests[4];
391 } registered_categories[] = {
392 { "AudioVideo", TRUE, FALSE, FALSE, { NULL }, { NULL } },
393 { "Audio", TRUE, FALSE, FALSE, { "AudioVideo", NULL }, { NULL } },
394 { "Video", TRUE, FALSE, FALSE, { "AudioVideo", NULL }, { NULL } },
395 { "Development", TRUE, FALSE, FALSE, { NULL }, { NULL } },
396 { "Education", TRUE, FALSE, FALSE, { NULL }, { NULL } },
397 { "Game", TRUE, FALSE, FALSE, { NULL }, { NULL } },
398 { "Graphics", TRUE, FALSE, FALSE, { NULL }, { NULL } },
399 { "Network", TRUE, FALSE, FALSE, { NULL }, { NULL } },
400 { "Office", TRUE, FALSE, FALSE, { NULL }, { NULL } },
401 { "Science", TRUE, FALSE, FALSE, { NULL }, { NULL } },
402 { "Settings", TRUE, FALSE, FALSE, { NULL }, { NULL } },
403 { "System", TRUE, FALSE, FALSE, { NULL }, { NULL } },
404 { "Utility", TRUE, FALSE, FALSE, { NULL }, { NULL } },
405 { "Audio", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
406 { "Video", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
407 { "Building", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
408 { "Debugger", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
409 { "IDE", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
410 { "GUIDesigner", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
411 { "Profiling", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
412 { "RevisionControl", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
413 { "Translation", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
414 { "Calendar", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
415 { "ContactManagement", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
416 { "Database", FALSE, FALSE, FALSE, { NULL }, { "Office", "Development", "AudioVideo", NULL } },
417 { "Dictionary", FALSE, FALSE, FALSE, { NULL }, { "Office", "TextTools", NULL } },
418 { "Chart", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
419 { "Email", FALSE, FALSE, FALSE, { NULL }, { "Office", "Network", NULL } },
420 { "Finance", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
421 { "FlowChart", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
422 { "PDA", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
423 { "ProjectManagement", FALSE, FALSE, FALSE, { NULL }, { "Office", "Development", NULL } },
424 { "Presentation", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
425 { "Spreadsheet", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
426 { "WordProcessor", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
427 { "2DGraphics", FALSE, FALSE, FALSE, { NULL }, { "Graphics", NULL } },
428 { "VectorGraphics", FALSE, FALSE, FALSE, { NULL }, { "Graphics;2DGraphics", NULL } },
429 { "RasterGraphics", FALSE, FALSE, FALSE, { NULL }, { "Graphics;2DGraphics", NULL } },
430 { "3DGraphics", FALSE, FALSE, FALSE, { NULL }, { "Graphics", NULL } },
431 { "Scanning", FALSE, FALSE, FALSE, { NULL }, { "Graphics", NULL } },
432 { "OCR", FALSE, FALSE, FALSE, { NULL }, { "Graphics;Scanning", NULL } },
433 { "Photography", FALSE, FALSE, FALSE, { NULL }, { "Graphics", "Office", NULL } },
434 { "Publishing", FALSE, FALSE, FALSE, { NULL }, { "Graphics", "Office", NULL } },
435 { "Viewer", FALSE, FALSE, FALSE, { NULL }, { "Graphics", "Office", NULL } },
436 { "TextTools", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
437 { "DesktopSettings", FALSE, FALSE, FALSE, { NULL }, { "Settings", NULL } },
438 { "HardwareSettings", FALSE, FALSE, FALSE, { NULL }, { "Settings", NULL } },
439 { "Printing", FALSE, FALSE, FALSE, { NULL }, { "HardwareSettings;Settings", NULL } },
440 { "PackageManager", FALSE, FALSE, FALSE, { NULL }, { "Settings", NULL } },
441 { "Dialup", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
442 { "InstantMessaging", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
443 { "Chat", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
444 { "IRCClient", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
445 { "Feed", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
446 { "FileTransfer", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
447 { "HamRadio", FALSE, FALSE, FALSE, { NULL }, { "Network", "Audio", NULL } },
448 { "News", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
449 { "P2P", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
450 { "RemoteAccess", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
451 { "Telephony", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
452 { "TelephonyTools", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
453 { "VideoConference", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
454 { "WebBrowser", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
455 { "WebDevelopment", FALSE, FALSE, FALSE, { NULL }, { "Network", "Development", NULL } },
456 { "Midi", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Audio", NULL } },
457 { "Mixer", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Audio", NULL } },
458 { "Sequencer", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Audio", NULL } },
459 { "Tuner", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Audio", NULL } },
460 { "TV", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Video", NULL } },
461 { "AudioVideoEditing", FALSE, FALSE, FALSE, { NULL }, { "Audio", "Video", "AudioVideo", NULL } },
462 { "Player", FALSE, FALSE, FALSE, { NULL }, { "Audio", "Video", "AudioVideo", NULL } },
463 { "Recorder", FALSE, FALSE, FALSE, { NULL }, { "Audio", "Video", "AudioVideo", NULL } },
464 { "DiscBurning", FALSE, FALSE, FALSE, { NULL }, { "Audio", "Video", "AudioVideo", NULL } },
465 { "ActionGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
466 { "AdventureGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
467 { "ArcadeGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
468 { "BoardGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
469 { "BlocksGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
470 { "CardGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
471 { "KidsGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
472 { "LogicGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
473 { "RolePlaying", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
474 { "Shooter", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
475 { "Simulation", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
476 { "SportsGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
477 { "StrategyGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
478 { "Art", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
479 { "Construction", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
480 { "Music", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo", "Education", NULL } },
481 { "Languages", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
482 { "ArtificialIntelligence", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
483 { "Astronomy", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
484 { "Biology", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
485 { "Chemistry", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
486 { "ComputerScience", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
487 { "DataVisualization", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
488 { "Economy", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
489 { "Electricity", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
490 { "Geography", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
491 { "Geology", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
492 { "Geoscience", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
493 { "History", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
494 { "Humanities", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
495 { "ImageProcessing", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
496 { "Literature", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
497 { "Maps", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", "Utility", NULL } },
498 { "Math", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
499 { "NumericalAnalysis", FALSE, FALSE, FALSE, { NULL }, { "Education;Math", "Science;Math", NULL } },
500 { "MedicalSoftware", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
501 { "Physics", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
502 { "Robotics", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
503 { "Spirituality", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", "Utility", NULL } },
504 { "Sports", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
505 { "ParallelComputing", FALSE, FALSE, FALSE, { NULL }, { "Education;ComputerScience", "Science;ComputerScience", NULL } },
506 { "Amusement", FALSE, FALSE, FALSE, { NULL }, { NULL } },
507 { "Archiving", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
508 { "Compression", FALSE, FALSE, FALSE, { NULL }, { "Utility;Archiving", NULL } },
509 { "Electronics", FALSE, FALSE, FALSE, { NULL }, { NULL } },
510 { "Emulator", FALSE, FALSE, FALSE, { NULL }, { "System", "Game", NULL } },
511 { "Engineering", FALSE, FALSE, FALSE, { NULL }, { NULL } },
512 { "FileTools", FALSE, FALSE, FALSE, { NULL }, { "Utility", "System", NULL } },
513 { "FileManager", FALSE, FALSE, FALSE, { NULL }, { "System;FileTools", NULL } },
514 { "TerminalEmulator", FALSE, FALSE, FALSE, { NULL }, { "System", NULL } },
515 { "Filesystem", FALSE, FALSE, FALSE, { NULL }, { "System", NULL } },
516 { "Monitor", FALSE, FALSE, FALSE, { NULL }, { "System", "Network", NULL } },
517 { "Security", FALSE, FALSE, FALSE, { NULL }, { "Settings", "System", NULL } },
518 { "Accessibility", FALSE, FALSE, FALSE, { NULL }, { "Settings", "Utility", NULL } },
519 { "Calculator", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
520 { "Clock", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
521 { "TextEditor", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
522 { "Documentation", FALSE, FALSE, FALSE, { NULL }, { NULL } },
523 { "Adult", FALSE, FALSE, FALSE, { NULL }, { NULL } },
524 { "Core", FALSE, FALSE, FALSE, { NULL }, { NULL } },
525 { "KDE", FALSE, FALSE, FALSE, { NULL }, { "Qt", NULL } },
526 { "GNOME", FALSE, FALSE, FALSE, { NULL }, { "GTK", NULL } },
527 { "XFCE", FALSE, FALSE, FALSE, { NULL }, { "GTK", NULL } },
528 { "GTK", FALSE, FALSE, FALSE, { NULL }, { NULL } },
529 { "Qt", FALSE, FALSE, FALSE, { NULL }, { NULL } },
530 { "Motif", FALSE, FALSE, FALSE, { NULL }, { NULL } },
531 { "Java", FALSE, FALSE, FALSE, { NULL }, { NULL } },
532 { "ConsoleOnly", FALSE, FALSE, FALSE, { NULL }, { NULL } },
533 { "Screensaver", FALSE, TRUE, FALSE, { NULL }, { NULL } },
534 { "TrayIcon", FALSE, TRUE, FALSE, { NULL }, { NULL } },
535 { "Applet", FALSE, TRUE, FALSE, { NULL }, { NULL } },
536 { "Shell", FALSE, TRUE, FALSE, { NULL }, { NULL } },
537 { "Application", FALSE, FALSE, TRUE, { NULL }, { NULL } },
538 { "Applications", FALSE, FALSE, TRUE, { NULL }, { NULL } }
542 print_fatal (kf_validator *kf, const char *format, ...)
547 g_return_if_fail (kf != NULL && format != NULL);
549 kf->fatal_error = TRUE;
551 va_start (args, format);
552 str = g_strdup_vprintf (format, args);
555 g_print ("%s: error: %s", kf->filename, str);
561 print_future_fatal (kf_validator *kf, const char *format, ...)
566 g_return_if_fail (kf != NULL && format != NULL);
568 va_start (args, format);
569 str = g_strdup_vprintf (format, args);
572 g_print ("%s: error: (will be fatal in the future): %s", kf->filename, str);
578 print_warning (kf_validator *kf, const char *format, ...)
583 g_return_if_fail (kf != NULL && format != NULL);
585 va_start (args, format);
586 str = g_strdup_vprintf (format, args);
589 g_print ("%s: warning: %s", kf->filename, str);
595 print_hint (kf_validator *kf, const char *format, ...)
600 g_return_if_fail (kf != NULL && format != NULL);
605 va_start (args, format);
606 str = g_strdup_vprintf (format, args);
609 g_print ("%s: hint: %s", kf->filename, str);
614 /* + Key names must contain only the characters A-Za-z0-9-.
618 key_is_valid (const char *key,
624 for (i = 0; i < len; i++) {
626 if (!g_ascii_isalnum (c) && c != '-')
633 /* + Values of type string may contain all ASCII characters except for control
638 validate_string_key (kf_validator *kf,
648 for (i = 0; value[i] != '\0'; i++) {
649 if (g_ascii_iscntrl (value[i])) {
656 print_fatal (kf, "value \"%s\" for string key \"%s\" in group \"%s\" "
657 "contains invalid characters, string values may contain "
658 "all ASCII characters except for control characters\n",
659 value, key, kf->current_group);
667 /* + Values of type localestring are user displayable, and are encoded in
670 * + If a postfixed key occurs, the same key must be also present without the
675 validate_localestring_key (kf_validator *kf,
683 locale_key = g_strdup_printf ("%s[%s]", key, locale);
685 locale_key = g_strdup_printf ("%s", key);
687 if (!g_utf8_validate (value, -1, NULL)) {
688 print_fatal (kf, "value \"%s\" for locale string key \"%s\" in group "
689 "\"%s\" contains invalid UTF-8 characters, locale string "
690 "values should be encoded in UTF-8\n",
691 value, locale_key, kf->current_group);
697 if (!g_hash_table_lookup (kf->current_keys, key)) {
698 print_fatal (kf, "key \"%s\" in group \"%s\" is a localized key, but "
699 "there is no non-localized key \"%s\"\n",
700 locale_key, kf->current_group, key);
711 /* + Values of type boolean must either be the string true or false.
713 * + Historically some booleans have been represented by the numeric entries 0
714 * or 1. With this version of the standard they are now to be represented as
715 * a boolean string. However, if an implementation is reading a pre-1.0
716 * desktop entry, it should interpret 0 and 1 as false and true,
721 validate_boolean_key (kf_validator *kf,
726 if (strcmp (value, "true") && strcmp (value, "false") &&
727 strcmp (value, "0") && strcmp (value, "1")) {
728 print_fatal (kf, "value \"%s\" for boolean key \"%s\" in group \"%s\" "
729 "contains invalid characters, boolean values must be "
730 "\"false\" or \"true\"\n",
731 value, key, kf->current_group);
735 if (!kf->no_deprecated_warnings &&
736 (!strcmp (value, "0") || !strcmp (value, "1")))
737 print_warning (kf, "boolean key \"%s\" in group \"%s\" has value \"%s\", "
738 "which is deprecated: boolean values should be "
739 "\"false\" or \"true\"\n",
740 key, kf->current_group, value);
745 /* + Values of type numeric must be a valid floating point number as recognized
746 * by the %f specifier for scanf.
750 validate_numeric_key (kf_validator *kf,
758 res = sscanf (value, "%f", &d);
760 print_fatal (kf, "value \"%s\" for numeric key \"%s\" in group \"%s\" "
761 "contains invalid characters, numeric values must be "
762 "valid floating point numbers\n",
763 value, key, kf->current_group);
770 /* + Values of type string may contain all ASCII characters except for control
773 * + FIXME: how should an empty list be handled?
776 validate_string_regexp_list_key (kf_validator *kf,
787 for (i = 0; value[i] != '\0'; i++) {
788 if (g_ascii_iscntrl (value[i])) {
795 print_fatal (kf, "value \"%s\" for %s list key \"%s\" in group \"%s\" "
796 "contains invalid character '%c', %s list values may "
797 "contain all ASCII characters except for control "
799 value, type, key, kf->current_group, value[i], type);
808 validate_string_list_key (kf_validator *kf,
813 return validate_string_regexp_list_key (kf, key, locale, value, "string");
817 validate_regexp_list_key (kf_validator *kf,
822 return validate_string_regexp_list_key (kf, key, locale, value, "regexp");
825 /* + Values of type localestring are user displayable, and are encoded in
827 * FIXME: partly checked; we checked the whole value is encored in UTF-8, but
828 * not that each value of the list is. Although this might be equivalent?
829 * + If a postfixed key occurs, the same key must be also present without the
832 * + FIXME: how should an empty list be handled?
835 validate_localestring_list_key (kf_validator *kf,
843 locale_key = g_strdup_printf ("%s[%s]", key, locale);
845 locale_key = g_strdup_printf ("%s", key);
848 if (!g_utf8_validate (value, -1, NULL)) {
849 print_fatal (kf, "value \"%s\" for locale string list key \"%s\" in group "
850 "\"%s\" contains invalid UTF-8 characters, locale string "
851 "list values should be encoded in UTF-8\n",
852 value, locale_key, kf->current_group);
858 if (!g_hash_table_lookup (kf->current_keys, key)) {
859 print_fatal (kf, "key \"%s\" in group \"%s\" is a localized key, but "
860 "there is no non-localized key \"%s\"\n",
861 locale_key, kf->current_group, key);
872 /* + This specification defines 3 types of desktop entries: Application
873 * (type 1), Link (type 2) and Directory (type 3). To allow the addition of
874 * new types in the future, implementations should ignore desktop entries
875 * with an unknown type.
877 * + KDE specific types: ServiceType, Service and FSDevice
881 handle_type_key (kf_validator *kf,
882 const char *locale_key,
887 for (i = 0; i < G_N_ELEMENTS (registered_types); i++) {
888 if (!strcmp (value, registered_types[i].name))
892 if (i == G_N_ELEMENTS (registered_types)) {
893 /* force the type, since the key might be present multiple times... */
894 kf->type = INVALID_TYPE;
896 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
897 "is not a registered type value (\"Application\", "
898 "\"Link\" and \"Directory\")\n",
899 value, locale_key, kf->current_group);
903 if (registered_types[i].kde_reserved && kf->kde_reserved_warnings)
904 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
905 "is a reserved value for KDE\n",
906 value, locale_key, kf->current_group);
908 if (registered_types[i].deprecated && !kf->no_deprecated_warnings)
909 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
911 value, locale_key, kf->current_group);
913 kf->type = registered_types[i].type;
914 kf->type_string = registered_types[i].name;
919 /* + Entries that confirm with this version of the specification should use
922 * + Previous versions of the spec: 0.9.x where 3 <= x <= 8
926 handle_version_key (kf_validator *kf,
927 const char *locale_key,
930 if (!strcmp (value, "1.2"))
933 if (!strcmp (value, "1.1"))
936 if (!strcmp (value, "1.0"))
939 if (!strncmp (value, "0.9.", strlen ("0.9."))) {
942 c = value[strlen ("0.9.")];
943 if ('3' <= c && c <= '8' && value[strlen ("0.9.") + 1] == '\0')
947 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
948 "is not a known version\n",
949 value, locale_key, kf->current_group);
953 /* + Tooltip for the entry, for example "View sites on the Internet", should
954 * not be redundant with Name or GenericName.
958 handle_comment_key (kf_validator *kf,
959 const char *locale_key,
962 char *locale_compare_key;
963 kf_keyvalue *keyvalue;
965 locale_compare_key = g_strdup_printf ("Name%s",
966 locale_key + strlen ("Comment"));
967 keyvalue = g_hash_table_lookup (kf->current_keys, locale_compare_key);
968 g_free (locale_compare_key);
970 if (keyvalue && g_ascii_strcasecmp (value, keyvalue->value) == 0) {
971 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
972 "looks redundant with value \"%s\" of key \"%s\"\n",
973 value, locale_key, kf->current_group,
974 keyvalue->value, keyvalue->key);
978 locale_compare_key = g_strdup_printf ("GenericName%s",
979 locale_key + strlen ("Comment"));
980 keyvalue = g_hash_table_lookup (kf->current_keys, locale_compare_key);
981 g_free (locale_compare_key);
983 if (keyvalue && g_ascii_strcasecmp (value, keyvalue->value) == 0) {
984 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
985 "looks redundant with value \"%s\" of key \"%s\"\n",
986 value, locale_key, kf->current_group,
987 keyvalue->value, keyvalue->key);
994 /* + If the name is an absolute path, the given file will be used.
996 * + If the name is not an absolute path, the algorithm described in the Icon
997 * Theme Specification will be used to locate the icon.
999 * FIXME: add clarification to desktop entry spec that the name doesn't
1000 * contain an extension
1003 handle_icon_key (kf_validator *kf,
1004 const char *locale_key,
1007 if (g_path_is_absolute (value)) {
1008 if (g_str_has_suffix (value, "/")) {
1009 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" is an "
1010 "absolute path to a directory, instead of being an "
1011 "absolute path to an icon or an icon name\n",
1012 value, locale_key, kf->current_group);
1018 if (g_utf8_strchr (value, -1, G_DIR_SEPARATOR)) {
1019 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" looks like "
1020 "a relative path, instead of being an absolute path to "
1021 "an icon or an icon name\n",
1022 value, locale_key, kf->current_group);
1026 if (g_str_has_suffix (value, ".png") ||
1027 g_str_has_suffix (value, ".xpm") ||
1028 g_str_has_suffix (value, ".svg")) {
1029 print_future_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" is an "
1030 "icon name with an extension, but there should be "
1031 "no extension as described in the Icon Theme "
1032 "Specification if the value is not an absolute "
1034 value, locale_key, kf->current_group);
1041 /* + Only one of these keys, either OnlyShowIn or NotShowIn, may appear in a
1044 * + (for possible values see the Desktop Menu Specification)
1046 * FIXME: this is not perfect because it could fail if a new value with
1047 * a semicolon is registered.
1048 * + All values extending the format should start with "X-".
1050 * + FIXME: is this okay to have only ";"? (gnome-theme-installer.desktop does)
1053 handle_show_in_key (kf_validator *kf,
1054 const char *locale_key,
1059 GHashTable *hashtable;
1066 print_fatal (kf, "only one of \"OnlyShowIn\" and \"NotShowIn\" keys "
1067 "may appear in group \"%s\"\n",
1073 hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
1074 show = g_strsplit (value, ";", 0);
1076 for (i = 0; show[i]; i++) {
1077 /* since the value ends with a semicolon, we'll have an empty string
1079 if (*show[i] == '\0' && show[i + 1] == NULL)
1082 if (g_hash_table_lookup (hashtable, show[i])) {
1083 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1084 "contains \"%s\" more than once\n",
1085 value, locale_key, kf->current_group, show[i]);
1089 g_hash_table_insert (hashtable, show[i], show[i]);
1091 if (!strncmp (show[i], "X-", 2))
1094 for (j = 0; j < G_N_ELEMENTS (show_in_registered); j++) {
1095 if (!strcmp (show[i], show_in_registered[j]))
1099 if (j == G_N_ELEMENTS (show_in_registered)) {
1100 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1101 "contains an unregistered value \"%s\"; values "
1102 "extending the format should start with \"X-\"\n",
1103 value, locale_key, kf->current_group, show[i]);
1109 g_hash_table_destroy (hashtable);
1114 /* + A command line consists of an executable program optionally followed by
1115 * one or more arguments. The executable program can either be specified with
1116 * its full path or with the name of the executable only. If no full path is
1117 * provided the executable is looked up in the $PATH used by the desktop
1118 * environment. The name or path of the executable program may not contain
1119 * the equal sign ("=").
1121 * + Arguments are separated by a space.
1123 * + Arguments may be quoted in whole.
1125 * + If an argument contains a reserved character the argument must be quoted.
1127 * + The rules for quoting of arguments is also applicable to the executable
1128 * name or path of the executable program as provided.
1130 * + Quoting must be done by enclosing the argument between double quotes and
1131 * escaping the double quote character, backtick character ("`"), dollar sign
1132 * ("$") and backslash character ("\") by preceding it with an additional
1133 * backslash character. Implementations must undo quoting before expanding
1134 * field codes and before passing the argument to the executable program.
1135 * Reserved characters are space (" "), tab, newline, double quote, single
1136 * quote ("'"), backslash character ("\"), greater-than sign (">"), less-than
1137 * sign ("<"), tilde ("~"), vertical bar ("|"), ampersand ("&"), semicolon
1138 * (";"), dollar sign ("$"), asterisk ("*"), question mark ("?"), hash mark
1139 * ("#"), parenthesis ("(") and (")") and backtick character ("`").
1141 * + Note that the general escape rule for values of type string states that
1142 * the backslash character can be escaped as ("\\") as well and that this
1143 * escape rule is applied before the quoting rule. As such, to unambiguously
1144 * represent a literal backslash character in a quoted argument in a desktop
1145 * entry file requires the use of four successive backslash characters
1146 * ("\\\\"). Likewise, a literal dollar sign in a quoted argument in a
1147 * desktop entry file is unambiguously represented with ("\\$").
1149 * + Field codes consist of the percentage character ("%") followed by an alpha
1150 * character. Literal percentage characters must be escaped as %%.
1152 * + Command lines that contain a field code that is not listed in this
1153 * specification are invalid and must not be processed, in particular
1154 * implementations may not introduce support for field codes not listed in
1155 * this specification. Extensions, if any, should be introduced by means of a
1158 * + A command line may contain at most one %f, %u, %F or %U field code.
1160 * + The %F and %U field codes may only be used as an argument on their own.
1164 handle_exec_key (kf_validator *kf,
1165 const char *locale_key,
1182 #define PRINT_INVALID_IF_FLAG \
1184 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" " \
1185 "contains an invalid field code \"%%%c\"\n", \
1186 value, locale_key, kf->current_group, *c); \
1195 /* quotes and escaped characters in quotes */
1197 PRINT_INVALID_IF_FLAG;
1205 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1206 "contains an escaped double quote (\\\\\") "
1207 "outside of a quote, but the double quote is "
1208 "a reserved character\n",
1209 value, locale_key, kf->current_group);
1218 PRINT_INVALID_IF_FLAG;
1221 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1222 "contains a non-escaped character '%c' in a "
1223 "quote, but it should be escaped with two "
1224 "backslashes (\"\\\\%c\")\n",
1225 value, locale_key, kf->current_group, *c, *c);
1230 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1231 "contains a reserved character '%c' outside of a "
1233 value, locale_key, kf->current_group, *c);
1238 PRINT_INVALID_IF_FLAG;
1240 /* Escape character immediately followed by \0? */
1241 if (*(c + 1) == '\0') {
1242 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1243 "ends in an incomplete escape sequence\n",
1244 value, locale_key, kf->current_group);
1250 if (*c == '\\' && in_quote)
1254 /* reserved characters */
1272 PRINT_INVALID_IF_FLAG;
1274 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1275 "contains a reserved character '%c' outside of a "
1277 value, locale_key, kf->current_group, *c);
1290 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1291 "may contain at most one \"%f\", \"%u\", "
1292 "\"%F\" or \"%U\" field code\n",
1293 value, locale_key, kf->current_group);
1305 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1306 "may contain at most one \"%f\", \"%u\", "
1307 "\"%F\" or \"%U\" field code\n",
1308 value, locale_key, kf->current_group);
1329 if (!kf->no_deprecated_warnings)
1330 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1331 "contains a deprecated field code \"%%%c\"\n",
1332 value, locale_key, kf->current_group, *c);
1338 PRINT_INVALID_IF_FLAG;
1346 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" contains a "
1347 "quote which is not closed\n",
1348 value, locale_key, kf->current_group);
1353 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" contains a "
1354 "non-complete field code\n",
1355 value, locale_key, kf->current_group);
1362 /* See checks for handle_exec_key().
1365 handle_desktop_exec_key (kf_validator *kf,
1366 const char *locale_key,
1369 handle_key_for_application (kf, locale_key, value);
1371 return handle_exec_key (kf, locale_key, value);
1374 /* + If entry is of type Application, the working directory to run the program
1375 * in. (probably implies an absolute path)
1377 * + FIXME: is it okay to have an empty string here? (wireshark.desktop does)
1380 handle_path_key (kf_validator *kf,
1381 const char *locale_key,
1384 handle_key_for_application (kf, locale_key, value);
1386 if (!g_path_is_absolute (value))
1387 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1388 "does not look like an absolute path\n",
1389 value, locale_key, kf->current_group);
1394 /* + The MIME type(s) supported by this application. Check they are valid
1399 handle_mime_key (kf_validator *kf,
1400 const char *locale_key,
1405 GHashTable *hashtable;
1408 MimeUtilsValidity valid;
1410 handle_key_for_application (kf, locale_key, value);
1414 hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
1415 types = g_strsplit (value, ";", 0);
1417 for (i = 0; types[i]; i++) {
1418 /* since the value ends with a semicolon, we'll have an empty string
1420 if (*types[i] == '\0' && types[i + 1] == NULL)
1423 if (g_hash_table_lookup (hashtable, types[i])) {
1424 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1425 "contains \"%s\" more than once\n",
1426 value, locale_key, kf->current_group, types[i]);
1430 g_hash_table_insert (hashtable, types[i], types[i]);
1432 valid = mu_mime_type_is_valid (types[i], &valid_error);
1436 case MU_DISCOURAGED:
1437 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1438 "contains value \"%s\" which is a MIME type that "
1439 "should probably not be used: %s\n",
1440 value, locale_key, kf->current_group,
1441 types[i], valid_error);
1443 g_free (valid_error);
1446 print_future_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1447 "contains value \"%s\" which is an invalid "
1449 value, locale_key, kf->current_group,
1450 types[i], valid_error);
1453 g_free (valid_error);
1456 g_assert_not_reached ();
1461 g_hash_table_destroy (hashtable);
1466 /* + FIXME: are there restrictions on how a category should be named?
1467 * + Categories in which the entry should be shown in a menu (for possible
1468 * values see the Desktop Menu Specification).
1470 * + The table below describes Reserved Categories. Reserved Categories have a
1471 * specific desktop specific meaning that has not been standardized (yet).
1472 * Desktop entry files that use a reserved category MUST also include an
1473 * appropriate OnlyShowIn= entry to restrict themselves to those environments
1474 * that properly support the reserved category as used.
1476 * + Accept "Application" as a deprecated category.
1478 * FIXME: it's not really deprecated, so the error message is wrong
1479 * + All categories extending the format should start with "X-".
1481 * + Using multiple main categories may lead to appearing more than once in
1484 * + One main category should be included, otherwise application will appear in
1485 * "catch-all" section of application menu.
1487 * FIXME: decide if it's okay to have an empty list of categories.
1488 * + Some categories, if included, require that another category is included.
1489 * Eg: if Audio is there, AudioVideo must be there.
1491 * + Some categories, if included, suggest that another category is included.
1492 * Eg: Debugger suggests Development.
1493 * This is the case for most additional categories.
1497 handle_categories_key (kf_validator *kf,
1498 const char *locale_key,
1503 GHashTable *hashtable;
1506 int main_categories_nb;
1508 handle_key_for_application (kf, locale_key, value);
1512 /* accept empty value as valid: this is like having no category at all */
1513 if (value[0] == '\0')
1516 hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
1517 categories = g_strsplit (value, ";", 0);
1519 /* this is a two-pass check: we first put the categories in a hash table so
1520 * that they are easy-to-find, and we then do many checks */
1523 for (i = 0; categories[i]; i++) {
1524 /* since the value ends with a semicolon, we'll have an empty string
1526 if (*categories[i] == '\0' && categories[i + 1] == NULL)
1529 if (g_hash_table_lookup (hashtable, categories[i])) {
1530 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1531 "contains \"%s\" more than once\n",
1532 value, locale_key, kf->current_group, categories[i]);
1536 g_hash_table_insert (hashtable, categories[i], categories[i]);
1540 main_categories_nb = 0;
1542 for (i = 0; categories[i]; i++) {
1545 /* since the value ends with a semicolon, we'll have an empty string
1547 if (*categories[i] == '\0' && categories[i + 1] == NULL)
1550 if (!strncmp (categories[i], "X-", 2))
1553 for (j = 0; j < G_N_ELEMENTS (registered_categories); j++) {
1554 if (!strcmp (categories[i], registered_categories[j].name))
1558 if (j == G_N_ELEMENTS (registered_categories)) {
1559 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1560 "contains an unregistered value \"%s\"; values "
1561 "extending the format should start with \"X-\"\n",
1562 value, locale_key, kf->current_group, categories[i]);
1567 if (registered_categories[j].main) {
1568 /* only count it as a main category if none of the required categories
1569 * for this one is also a main category (and is present) */
1570 gboolean required_main_category_present = FALSE;
1572 for (k = 0; registered_categories[j].requires[k] != NULL; k++) {
1573 char **required_categories;
1576 required_categories = g_strsplit (registered_categories[j].requires[k],
1579 for (l = 0; required_categories[l]; l++) {
1582 if (!g_hash_table_lookup (hashtable, required_categories[l]))
1585 for (m = 0; m < G_N_ELEMENTS (registered_categories); m++) {
1586 if (strcmp (required_categories[l],
1587 registered_categories[m].name) != 0)
1590 if (registered_categories[m].main)
1591 required_main_category_present = TRUE;
1596 if (required_main_category_present)
1600 if (required_main_category_present) {
1601 g_strfreev (required_categories);
1605 g_strfreev (required_categories);
1608 if (!required_main_category_present)
1609 main_categories_nb++;
1612 if (registered_categories[j].main && main_categories_nb > 1)
1613 print_hint (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1614 "contains more than one main category; application "
1615 "might appear more than once in the application menu\n",
1616 value, locale_key, kf->current_group);
1619 if (registered_categories[j].deprecated) {
1620 if (!kf->no_deprecated_warnings)
1621 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1622 "contains a deprecated value \"%s\"\n",
1623 value, locale_key, kf->current_group,
1627 if (registered_categories[j].require_only_show_in) {
1628 if (!g_hash_table_lookup (kf->current_keys, "OnlyShowIn")) {
1629 print_fatal (kf, "value item \"%s\" in key \"%s\" in group \"%s\" "
1630 "is a reserved category, so a \"OnlyShowIn\" key "
1631 "must be included\n",
1632 categories[i], locale_key, kf->current_group);
1637 /* required categories */
1639 for (k = 0; registered_categories[j].requires[k] != NULL; k++) {
1640 char **required_categories;
1643 required_categories = g_strsplit (registered_categories[j].requires[k],
1646 for (l = 0; required_categories[l]; l++) {
1647 if (!g_hash_table_lookup (hashtable, required_categories[l]))
1651 /* we've reached the end of a list of required categories, so
1652 * the condition is satisfied */
1653 if (required_categories[l] == NULL) {
1654 g_strfreev (required_categories);
1658 g_strfreev (required_categories);
1661 /* we've reached the end of a non-empty set of required categories; this
1662 * means none of the possible required category (or list of required
1663 * categories) was found */
1664 if (k != 0 && registered_categories[j].requires[k] == NULL) {
1665 GString *output_required;
1667 output_required = g_string_new (registered_categories[j].requires[0]);
1668 for (k = 1; registered_categories[j].requires[k] != NULL; k++)
1669 g_string_append_printf (output_required, ", or %s",
1670 registered_categories[j].requires[k]);
1672 print_future_fatal (kf, "value item \"%s\" in key \"%s\" in group \"%s\" "
1673 "requires another category to be present among "
1674 "the following categories: %s\n",
1675 categories[i], locale_key, kf->current_group,
1676 output_required->str);
1678 g_string_free (output_required, TRUE);
1682 /* suggested categories */
1684 for (k = 0; registered_categories[j].suggests[k] != NULL; k++) {
1685 char **suggested_categories;
1688 suggested_categories = g_strsplit (registered_categories[j].suggests[k],
1691 for (l = 0; suggested_categories[l]; l++) {
1692 if (!g_hash_table_lookup (hashtable, suggested_categories[l]))
1696 /* we've reached the end of a list of suggested categories, so
1697 * the condition is satisfied */
1698 if (suggested_categories[l] == NULL) {
1699 g_strfreev (suggested_categories);
1703 g_strfreev (suggested_categories);
1706 /* we've reached the end of a non-empty set of suggested categories; this
1707 * means none of the possible suggested category (or list of suggested
1708 * categories) was found */
1709 if (k != 0 && registered_categories[j].suggests[k] == NULL) {
1710 GString *output_suggested;
1712 output_suggested = g_string_new (registered_categories[j].suggests[0]);
1713 for (k = 1; registered_categories[j].suggests[k] != NULL; k++)
1714 g_string_append_printf (output_suggested, ", or %s",
1715 registered_categories[j].suggests[k]);
1717 print_hint (kf, "value item \"%s\" in key \"%s\" in group \"%s\" "
1718 "can be extended with another category among the "
1719 "following categories: %s\n",
1720 categories[i], locale_key, kf->current_group,
1721 output_suggested->str);
1723 g_string_free (output_suggested, TRUE);
1728 g_strfreev (categories);
1729 g_hash_table_destroy (hashtable);
1731 g_assert (main_categories_nb >= 0);
1733 if (main_categories_nb == 0)
1734 print_hint (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1735 "does not contain a registered main category; application "
1736 "might only show up in a \"catch-all\" section of the "
1737 "application menu\n",
1738 value, locale_key, kf->current_group);
1743 /* + Identifiers for application actions. Check they are using a valid format.
1746 * Note that we will check later on (in * validate_actions()) that there is a
1747 * "Desktop Action foobar" group for each "foobar" identifier.
1750 handle_actions_key (kf_validator *kf,
1751 const char *locale_key,
1759 handle_key_for_application (kf, locale_key, value);
1762 actions = g_strsplit (value, ";", 0);
1764 for (i = 0; actions[i]; i++) {
1765 /* since the value ends with a semicolon, we'll have an empty string
1767 if (*actions[i] == '\0') {
1768 if (actions[i + 1] != NULL) {
1769 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1770 "contains an empty action\n",
1771 value, locale_key, kf->current_group);
1779 if (g_hash_table_lookup (kf->action_values, actions[i])) {
1780 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1781 "contains action \"%s\" more than once\n",
1782 value, locale_key, kf->current_group, actions[i]);
1786 if (!key_is_valid (actions[i], strlen (actions[i]))) {
1787 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1788 "contains invalid action identifier \"%s\", only "
1789 "alphanumeric characters and '-' are allowed\n",
1790 value, locale_key, kf->current_group, actions[i]);
1795 action = g_strdup (actions[i]);
1796 g_hash_table_insert (kf->action_values, action, action);
1799 g_strfreev (actions);
1804 /* + If the file describes a D-Bus activatable service, the filename must be in
1805 * reverse-DNS notation, i.e. contain at least two dots including the dot
1810 handle_dbus_activatable_key (kf_validator *kf,
1811 const char *locale_key,
1814 gchar *basename_utf8;
1816 const gchar *p = NULL;
1817 gboolean retval = TRUE;
1819 /* If DBusActivatable=false, don't check */
1820 if (strcmp (value, "true") && strcmp (value, "1"))
1823 basename = g_path_get_basename (kf->filename);
1824 basename_utf8 = g_filename_to_utf8 (basename, -1, NULL, NULL, NULL);
1828 p = g_utf8_strchr (basename_utf8, -1, '.');
1831 p = g_utf8_strchr (p + 1, -1, '.');
1835 print_fatal (kf, "DBusActivatable filename must conform to reverse-DNS notation\n");
1839 g_free (basename_utf8);
1844 /* + The device to mount. (probably implies an absolute path)
1848 handle_dev_key (kf_validator *kf,
1849 const char *locale_key,
1852 handle_key_for_fsdevice (kf, locale_key, value);
1854 if (!g_path_is_absolute (value))
1855 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1856 "does not look like an absolute path\n",
1857 value, locale_key, kf->current_group);
1862 /* + The mount point of the device in question. (probably implies an absolute
1867 handle_mountpoint_key (kf_validator *kf,
1868 const char *locale_key,
1871 handle_key_for_fsdevice (kf, locale_key, value);
1873 if (!g_path_is_absolute (value))
1874 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1875 "does not look like an absolute path\n",
1876 value, locale_key, kf->current_group);
1881 /* + Possible values are UTF-8 and Legacy-Mixed.
1885 handle_encoding_key (kf_validator *kf,
1886 const char *locale_key,
1889 if (!strcmp (value, "UTF-8") || !strcmp (value, "Legacy-Mixed"))
1892 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1893 "is not a registered encoding value (\"UTF-8\", and "
1894 "\"Legacy-Mixed\")\n",
1895 value, locale_key, kf->current_group);
1900 /* + See http://lists.freedesktop.org/archives/xdg/2007-January/007436.html
1901 * + Value is one of:
1903 * - unless-exists FILE
1904 * - DESKTOP-ENVIRONMENT-NAME [DESKTOP-SPECIFIC-TEST]
1905 * - other known conditions (GNOME3, GSettings, etc.)
1907 * + FILE must be a path to a filename, relative to $XDG_CONFIG_HOME.
1909 * + DESKTOP-ENVIRONMENT-NAME should be a registered value (in Desktop Menu
1910 * Specification) or start with "X-".
1912 * + [DESKTOP-SPECIFIC-TEST] is optional.
1916 handle_autostart_condition_key (kf_validator *kf,
1917 const char *locale_key,
1924 handle_key_for_application (kf, locale_key, value);
1928 condition = g_strdup (value);
1929 argument = g_utf8_strchr (condition, -1, ' ');
1932 /* make condition a 0-ended string */
1935 /* skip the space(s) */
1937 while (*argument == ' ') {
1942 if (!strcmp (condition, "if-exists") || !strcmp (condition, "unless-exists")) {
1943 if (!argument || argument[0] == '\0') {
1944 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1945 "does not contain a path to a file to test the "
1947 value, locale_key, kf->current_group);
1949 } else if (argument[0] == G_DIR_SEPARATOR) {
1950 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1951 "contains a path \"%s\" that is absolute, while it "
1952 "should be relative (to $XDG_CONFIG_HOME)\n",
1953 value, locale_key, kf->current_group, argument);
1955 } else if (argument[0] == '.' &&
1956 ((strlen (argument) == 2 &&
1957 argument[1] == '.') ||
1958 (strlen (argument) >= 3 &&
1959 argument[1] == '.' &&
1960 argument[2] == G_DIR_SEPARATOR))) {
1961 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1962 "contains a path \"%s\" that depends on the value "
1963 "of $XDG_CONFIG_HOME (\"..\" should be avoided)\n",
1964 value, locale_key, kf->current_group, argument);
1967 } else if (strncmp (condition, "X-", 2) == 0) {
1968 if (argument && argument[0] == '\0')
1969 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1970 "has trailing space(s)\n",
1971 value, locale_key, kf->current_group);
1976 /* Look if it's a registered AutostartCondition */
1978 for (i = 0; i < G_N_ELEMENTS (registered_autostart_condition); i++) {
1980 if (strcmp (condition, registered_autostart_condition[i].name) != 0)
1983 /* check if first argument is one of the expected ones */
1984 for (j = 0; registered_autostart_condition[i].first_arg[j] != NULL; j++) {
1985 const char *first = registered_autostart_condition[i].first_arg[j];
1986 char *after_first = argument;
1988 if (argument && !strncmp (argument, first, strlen (first))) {
1989 after_first += strlen (first);
1990 if (after_first[0] == '\0' || after_first[0] == ' ') {
1991 /* find next argument */
1992 argument = after_first;
1993 while (*argument == ' ')
2001 /* we've reached the end of a non-empty set of first arguments; this
2002 * means none of the possible first arguments was found */
2003 if (j != 0 && registered_autostart_condition[i].first_arg[j] == NULL) {
2006 output = g_string_new (registered_autostart_condition[i].first_arg[0]);
2007 for (j = 1; registered_autostart_condition[i].first_arg[j] != NULL; j++)
2008 g_string_append_printf (output, ", or %s",
2009 registered_autostart_condition[i].first_arg[j]);
2011 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2012 "does not contain a valid first argument for "
2013 "condition \"%s\"; valid first arguments are: %s\n",
2014 value, locale_key, kf->current_group,
2015 condition, output->str);
2018 g_string_free (output, TRUE);
2022 switch (registered_autostart_condition[i].additional_args) {
2024 if (argument && argument[0] != '\0') {
2025 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2026 "has too many arguments for condition \"%s\"\n",
2027 value, locale_key, kf->current_group, condition);
2033 /* we handle the "one argument" case specially, as spaces might be
2034 * normal there, and therefore we don't want to split the string
2035 * based on spaces */
2036 if (!argument || argument[0] == '\0') {
2037 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2038 "is missing a last argument for condition "
2040 value, locale_key, kf->current_group, condition);
2047 int argc_diff = -registered_autostart_condition[i].additional_args;
2049 while (argument && argument[0] != '\0') {
2051 argument = g_utf8_strchr (argument, -1, ' ');
2052 while (argument && *argument == ' ')
2056 if (argc_diff > 0) {
2057 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2058 "has %d too many arguments for condition "
2060 value, locale_key, kf->current_group,
2061 argc_diff, condition);
2063 } else if (argc_diff < 0) {
2064 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2065 "has %d too few arguments for condition "
2067 value, locale_key, kf->current_group,
2068 -argc_diff, condition);
2081 /* Now, if we didn't find condition in list of registered
2082 * AutostartCondition... */
2083 if (i == G_N_ELEMENTS (registered_autostart_condition)) {
2084 /* Accept conditions with same name as OnlyShowIn values */
2086 for (i = 0; i < G_N_ELEMENTS (show_in_registered); i++) {
2087 if (!strcmp (condition, show_in_registered[i]))
2091 if (i == G_N_ELEMENTS (show_in_registered)) {
2092 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2093 "contains an unregistered value \"%s\" for the "
2094 "condition; values extending the format should "
2095 "start with \"X-\"\n",
2096 value, locale_key, kf->current_group, condition);
2100 if (argument && argument[0] == '\0')
2101 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2102 "has trailing space(s)\n",
2103 value, locale_key, kf->current_group);
2113 handle_key_for_application (kf_validator *kf,
2114 const char *locale_key,
2117 kf->application_keys = g_list_append (kf->application_keys,
2118 g_strdup (locale_key));
2123 handle_key_for_link (kf_validator *kf,
2124 const char *locale_key,
2127 kf->link_keys = g_list_append (kf->link_keys,
2128 g_strdup (locale_key));
2133 handle_key_for_fsdevice (kf_validator *kf,
2134 const char *locale_key,
2137 kf->fsdevice_keys = g_list_append (kf->fsdevice_keys,
2138 g_strdup (locale_key));
2143 handle_key_for_mimetype (kf_validator *kf,
2144 const char *locale_key,
2147 kf->mimetype_keys = g_list_append (kf->mimetype_keys,
2148 g_strdup (locale_key));
2152 /* + Key names must contain only the characters A-Za-z0-9-.
2153 * Checked (through key_is_valid()).
2154 * + LOCALE must be of the form lang_COUNTRY.ENCODING@MODIFIER, where _COUNTRY,
2155 * .ENCODING, and @MODIFIER may be omitted.
2159 key_extract_locale (const char *key,
2163 const char *start_locale;
2173 start_locale = g_strrstr (key, "[");
2176 len = start_locale - key;
2180 if (!key_is_valid(key, len))
2183 if (!start_locale) {
2185 *real_key = g_strdup (key);
2192 len = strlen (start_locale);
2193 if (len <= 2 || start_locale[len - 1] != ']')
2196 /* ignore first [ and last ] */
2197 for (i = 1; i < len - 2; i++) {
2198 c = start_locale[i];
2199 if (!g_ascii_isalnum (c) && c != '-' && c != '_' && c != '.' && c != '@')
2204 *real_key = g_strndup (key, strlen (key) - len);
2206 *locale = g_strndup (start_locale + 1, len - 2);
2211 /* + All keys extending the format should start with "X-".
2215 validate_known_key (kf_validator *kf,
2216 const char *locale_key,
2220 DesktopKeyDefinition *keys,
2221 unsigned int n_keys)
2226 if (!strncmp (key, "X-", 2))
2229 for (i = 0; i < n_keys; i++) {
2230 if (strcmp (key, keys[i].name))
2233 if (keys[i].type != DESKTOP_LOCALESTRING_TYPE &&
2234 keys[i].type != DESKTOP_LOCALESTRING_LIST_TYPE &&
2236 print_fatal (kf, "file contains key \"%s\" in group \"%s\", "
2237 "but \"%s\" is not defined as a locale string\n",
2238 locale_key, kf->current_group, key);
2242 for (j = 0; j < G_N_ELEMENTS (validate_for_type); j++) {
2243 if (validate_for_type[j].type == keys[i].type)
2247 g_assert (j != G_N_ELEMENTS (validate_for_type));
2249 if (!kf->no_deprecated_warnings && keys[i].deprecated)
2250 print_warning (kf, "key \"%s\" in group \"%s\" is deprecated\n",
2251 locale_key, kf->current_group);
2253 if (keys[i].kde_reserved && kf->kde_reserved_warnings)
2254 print_warning (kf, "key \"%s\" in group \"%s\" is a reserved key for "
2256 locale_key, kf->current_group);
2258 if (!validate_for_type[j].validate (kf, key, locale, value))
2261 if (keys[i].handle_and_validate != NULL) {
2262 if (!keys[i].handle_and_validate (kf, locale_key, value))
2270 print_fatal (kf, "file contains key \"%s\" in group \"%s\", but "
2271 "keys extending the format should start with "
2272 "\"X-\"\n", key, kf->current_group);
2280 validate_desktop_key (kf_validator *kf,
2281 const char *locale_key,
2286 return validate_known_key (kf, locale_key, key, locale, value,
2287 registered_desktop_keys,
2288 G_N_ELEMENTS (registered_desktop_keys));
2292 validate_action_key (kf_validator *kf,
2293 const char *locale_key,
2298 return validate_known_key (kf, locale_key, key, locale, value,
2299 registered_action_keys,
2300 G_N_ELEMENTS (registered_action_keys));
2303 /* + Multiple keys in the same group may not have the same name.
2307 validate_keys_for_current_group (kf_validator *kf)
2309 gboolean desktop_group;
2310 gboolean action_group;
2312 GHashTable *duplicated_keys_hash;
2321 desktop_group = (!strcmp (kf->current_group, GROUP_DESKTOP_ENTRY) ||
2322 !strcmp (kf->current_group, GROUP_KDE_DESKTOP_ENTRY));
2323 action_group = (!strncmp (kf->current_group, GROUP_DESKTOP_ACTION,
2324 strlen (GROUP_DESKTOP_ACTION)));
2326 keys = g_slist_copy (g_hash_table_lookup (kf->groups, kf->current_group));
2327 /* keys were prepended, so reverse the list (that's why we use a
2329 keys = g_slist_reverse (keys);
2331 kf->current_keys = g_hash_table_new_full (g_str_hash, g_str_equal,
2333 duplicated_keys_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
2336 /* we need two passes: some checks are looking if another key exists in the
2338 for (sl = keys; sl != NULL; sl = sl->next) {
2339 kf_keyvalue *keyvalue;
2341 keyvalue = (kf_keyvalue *) sl->data;
2342 g_hash_table_insert (kf->current_keys, keyvalue->key, keyvalue);
2344 /* we could display the error about duplicate keys here, but it's better
2345 * to display it with the first occurence of this key */
2346 hashvalue = g_hash_table_lookup (duplicated_keys_hash, keyvalue->key);
2348 g_hash_table_insert (duplicated_keys_hash, keyvalue->key,
2349 GINT_TO_POINTER (1));
2351 g_hash_table_replace (duplicated_keys_hash, keyvalue->key,
2352 GINT_TO_POINTER (GPOINTER_TO_INT (hashvalue) + 1));
2356 for (sl = keys; sl != NULL; sl = sl->next) {
2357 kf_keyvalue *keyvalue;
2358 gboolean skip_desktop_check;
2360 keyvalue = (kf_keyvalue *) sl->data;
2362 skip_desktop_check = FALSE;
2364 if (!key_extract_locale (keyvalue->key, &key, &locale)) {
2365 print_fatal (kf, "file contains key \"%s\" in group \"%s\", but "
2366 "key names must contain only the characters "
2367 "A-Za-z0-9- (they may have a \"[LOCALE]\" postfix)\n",
2368 keyvalue->key, kf->current_group);
2370 skip_desktop_check = TRUE;
2372 key = g_strdup (keyvalue->key);
2375 g_assert (key != NULL);
2377 hashvalue = g_hash_table_lookup (duplicated_keys_hash, keyvalue->key);
2378 if (GPOINTER_TO_INT (hashvalue) > 1) {
2379 g_hash_table_remove (duplicated_keys_hash, keyvalue->key);
2380 print_fatal (kf, "file contains multiple keys named \"%s\" in "
2381 "group \"%s\"\n", keyvalue->key, kf->current_group);
2385 if (desktop_group && !skip_desktop_check) {
2386 if (!validate_desktop_key (kf, keyvalue->key,
2387 key, locale, keyvalue->value))
2389 } else if (action_group && !skip_desktop_check) {
2390 if (!validate_action_key (kf, keyvalue->key,
2391 key, locale, keyvalue->value))
2401 g_slist_free (keys);
2402 g_hash_table_destroy (duplicated_keys_hash);
2403 g_hash_table_destroy (kf->current_keys);
2404 kf->current_keys = NULL;
2405 /* Clear ShowIn flag, so that different groups can each have a OnlyShowIn /
2407 kf->show_in = FALSE;
2412 /* + Using [KDE Desktop Entry] instead of [Desktop Entry] as header is
2415 * + Group names may contain all ASCII characters except for [ and ] and
2416 * control characters.
2418 * + All groups extending the format should start with "X-".
2420 * + Accept "Desktop Action foobar" group, where foobar is a valid key
2424 * Note that for "Desktop Action foobar" group, we will check later on (in
2425 * validate_actions()) that the Actions key contains "foobar".
2428 validate_group_name (kf_validator *kf,
2434 for (i = 0; group[i] != '\0'; i++) {
2436 if (g_ascii_iscntrl (c) || c == '[' || c == ']') {
2437 print_fatal (kf, "file contains group \"%s\", but group names "
2438 "may contain all ASCII characters except for [ "
2439 "and ] and control characters\n", group);
2444 if (!strncmp (group, "X-", 2))
2447 if (!strcmp (group, GROUP_DESKTOP_ENTRY)) {
2448 if (kf->main_group && !strcmp (kf->main_group, GROUP_KDE_DESKTOP_ENTRY))
2449 print_warning (kf, "file contains groups \"%s\" and \"%s\", which play "
2451 GROUP_KDE_DESKTOP_ENTRY, GROUP_DESKTOP_ENTRY);
2453 kf->main_group = GROUP_DESKTOP_ENTRY;
2458 if (!strcmp (group, GROUP_KDE_DESKTOP_ENTRY)) {
2459 if (kf->kde_reserved_warnings || !kf->no_deprecated_warnings)
2460 print_warning (kf, "file contains group \"%s\", which is deprecated "
2461 "in favor of \"%s\"\n", group, GROUP_DESKTOP_ENTRY);
2463 if (kf->main_group && !strcmp (kf->main_group, GROUP_DESKTOP_ENTRY))
2464 print_warning (kf, "file contains groups \"%s\" and \"%s\", which play "
2466 GROUP_DESKTOP_ENTRY, GROUP_KDE_DESKTOP_ENTRY);
2468 kf->main_group = GROUP_KDE_DESKTOP_ENTRY;
2473 if (!strncmp (group, GROUP_DESKTOP_ACTION, strlen (GROUP_DESKTOP_ACTION))) {
2474 if (group[strlen (GROUP_DESKTOP_ACTION) - 1] == '\0') {
2475 print_fatal (kf, "file contains group \"%s\", which is an action "
2476 "group with no action name\n", group);
2481 action = g_strdup (group + strlen (GROUP_DESKTOP_ACTION));
2483 if (!key_is_valid (action, strlen (action))) {
2484 print_fatal (kf, "file contains group \"%s\", which has an invalid "
2485 "action identifier, only alphanumeric characters and "
2486 "'-' are allowed\n", group);
2491 g_hash_table_insert (kf->action_groups, action, action);
2497 print_fatal (kf, "file contains group \"%s\", but groups extending "
2498 "the format should start with \"X-\"\n", group);
2503 validate_required_keys (kf_validator *kf,
2504 const char *group_name,
2505 DesktopKeyDefinition *key_definitions,
2506 unsigned int n_keys)
2512 GHashTable *hashtable;
2516 hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
2517 keys = g_hash_table_lookup (kf->groups, group_name);
2519 for (sl = keys; sl != NULL; sl = sl->next) {
2520 kf_keyvalue *keyvalue;
2522 keyvalue = (kf_keyvalue *) sl->data;
2523 g_hash_table_insert (hashtable, keyvalue->key, keyvalue->key);
2526 for (i = 0; i < n_keys; i++) {
2527 if (key_definitions[i].required) {
2528 if (!g_hash_table_lookup (hashtable,
2529 key_definitions[i].name)) {
2530 print_fatal (kf, "required key \"%s\" in group \"%s\" is not "
2532 key_definitions[i].name, group_name);
2538 g_hash_table_destroy (hashtable);
2544 validate_required_desktop_keys (kf_validator *kf)
2546 return validate_required_keys (kf, kf->main_group,
2547 registered_desktop_keys,
2548 G_N_ELEMENTS (registered_desktop_keys));
2551 #define PRINT_ERROR_FOREACH_KEY(lower, real) \
2553 print_error_foreach_##lower##_key (const char *name, \
2556 print_fatal (kf, "key \"%s\" is present in group \"%s\", but the type is " \
2557 "\"%s\" while this key is only valid for type \"%s\"\n", \
2558 name, kf->main_group, kf->type_string, real); \
2561 PRINT_ERROR_FOREACH_KEY (application, "Application")
2562 PRINT_ERROR_FOREACH_KEY (link, "Link")
2563 PRINT_ERROR_FOREACH_KEY (fsdevice, "FSDevice")
2564 PRINT_ERROR_FOREACH_KEY (mimetype, "MimeType")
2567 validate_type_keys (kf_validator *kf)
2576 case APPLICATION_TYPE:
2577 g_list_foreach (kf->link_keys,
2578 (GFunc) print_error_foreach_link_key, kf);
2579 g_list_foreach (kf->fsdevice_keys,
2580 (GFunc) print_error_foreach_fsdevice_key, kf);
2581 g_list_foreach (kf->mimetype_keys,
2582 (GFunc) print_error_foreach_mimetype_key, kf);
2583 retval = (g_list_length (kf->link_keys) +
2584 g_list_length (kf->fsdevice_keys) +
2585 g_list_length (kf->mimetype_keys) == 0);
2588 g_list_foreach (kf->application_keys,
2589 (GFunc) print_error_foreach_application_key, kf);
2590 g_list_foreach (kf->fsdevice_keys,
2591 (GFunc) print_error_foreach_fsdevice_key, kf);
2592 g_list_foreach (kf->mimetype_keys,
2593 (GFunc) print_error_foreach_mimetype_key, kf);
2594 retval = (g_list_length (kf->application_keys) +
2595 g_list_length (kf->fsdevice_keys) +
2596 g_list_length (kf->mimetype_keys) == 0);
2598 case DIRECTORY_TYPE:
2600 case SERVICE_TYPE_TYPE:
2601 g_list_foreach (kf->application_keys,
2602 (GFunc) print_error_foreach_application_key, kf);
2603 g_list_foreach (kf->link_keys,
2604 (GFunc) print_error_foreach_link_key, kf);
2605 g_list_foreach (kf->fsdevice_keys,
2606 (GFunc) print_error_foreach_fsdevice_key, kf);
2607 g_list_foreach (kf->mimetype_keys,
2608 (GFunc) print_error_foreach_mimetype_key, kf);
2609 retval = (g_list_length (kf->application_keys) +
2610 g_list_length (kf->link_keys) +
2611 g_list_length (kf->fsdevice_keys) +
2612 g_list_length (kf->mimetype_keys) == 0);
2615 g_list_foreach (kf->application_keys,
2616 (GFunc) print_error_foreach_application_key, kf);
2617 g_list_foreach (kf->link_keys,
2618 (GFunc) print_error_foreach_link_key, kf);
2619 g_list_foreach (kf->mimetype_keys,
2620 (GFunc) print_error_foreach_mimetype_key, kf);
2621 retval = (g_list_length (kf->application_keys) +
2622 g_list_length (kf->link_keys) +
2623 g_list_length (kf->mimetype_keys) == 0);
2626 g_list_foreach (kf->application_keys,
2627 (GFunc) print_error_foreach_application_key, kf);
2628 g_list_foreach (kf->link_keys,
2629 (GFunc) print_error_foreach_link_key, kf);
2630 g_list_foreach (kf->fsdevice_keys,
2631 (GFunc) print_error_foreach_fsdevice_key, kf);
2632 retval = (g_list_length (kf->application_keys) +
2633 g_list_length (kf->link_keys) +
2634 g_list_length (kf->fsdevice_keys) == 0);
2637 g_assert_not_reached ();
2644 lookup_group_foreach_action (char *key,
2648 if (g_hash_table_lookup (kf->action_groups, key)) {
2651 group_name = g_strconcat (GROUP_DESKTOP_ACTION, key, NULL);
2652 validate_required_keys (kf, group_name,
2653 registered_action_keys,
2654 G_N_ELEMENTS (registered_action_keys));
2655 g_free (group_name);
2657 g_hash_table_remove (kf->action_groups, key);
2665 print_error_foreach_action (char *key,
2669 print_fatal (kf, "action \"%s\" is defined, but there is no matching "
2670 "\"%s%s\" group\n", key, GROUP_DESKTOP_ACTION, key);
2674 print_error_foreach_group (char *key,
2678 print_fatal (kf, "action group \"%s%s\" exists, but there is no matching "
2679 "action \"%s\"\n", GROUP_DESKTOP_ACTION, key, key);
2683 validate_actions (kf_validator *kf)
2685 g_hash_table_foreach_remove (kf->action_values,
2686 (GHRFunc) lookup_group_foreach_action, kf);
2688 g_hash_table_foreach (kf->action_values,
2689 (GHFunc) print_error_foreach_action, kf);
2691 g_hash_table_foreach (kf->action_groups,
2692 (GHFunc) print_error_foreach_group, kf);
2694 return (g_hash_table_size (kf->action_values) +
2695 g_hash_table_size (kf->action_groups) == 0);
2698 /* + These desktop entry files should have the extension .desktop.
2700 * + Desktop entries which describe how a directory is to be
2701 * formatted/displayed should be simply called .directory.
2703 * + Using .kdelnk instead of .desktop as the file extension is deprecated.
2705 * FIXME: we're not doing what the spec says wrt Directory.
2708 validate_filename (kf_validator *kf)
2710 if (kf->type == DIRECTORY_TYPE) {
2711 if (g_str_has_suffix (kf->filename, ".directory"))
2714 print_fatal (kf, "file is of type \"Directory\", but filename does not "
2715 "have a .directory extension\n");
2720 if (g_str_has_suffix (kf->filename, ".desktop"))
2723 if (g_str_has_suffix (kf->filename, ".kdelnk")) {
2724 if (kf->kde_reserved_warnings || !kf->no_deprecated_warnings)
2725 print_warning (kf, "filename has a .kdelnk extension, which is "
2726 "deprecated in favor of .desktop\n");
2730 print_fatal (kf, "filename does not have a .desktop extension\n");
2734 /* + Lines beginning with a # and blank lines are considered comments.
2738 validate_line_is_comment (kf_validator *kf,
2741 return (*line == '#' || *line == '\0');
2744 /* + A group header with name groupname is a line in the format: [groupname]
2746 * + Group names may contain all ASCII characters except for [ and ] and
2747 * control characters.
2748 * This is done in validate_group_name().
2751 validate_line_looks_like_group (kf_validator *kf,
2758 chomped = g_strdup (line);
2759 g_strchomp (chomped);
2761 result = (*chomped == '[' && chomped[strlen (chomped) - 1] == ']');
2763 if (result && strcmp (chomped, line))
2764 print_fatal (kf, "line \"%s\" ends with a space, but looks like a group. "
2765 "The validation will continue, with the trailing spaces "
2766 "ignored.\n", line);
2768 if (group && result)
2769 *group = g_strndup (chomped + 1, strlen (chomped) - 2);
2776 /* + Space before and after the equals sign should be ignored; the = sign is
2777 * the actual delimiter.
2781 validate_line_looks_like_entry (kf_validator *kf,
2788 p = g_utf8_strchr (line, -1, '=');
2793 /* key must be non-empty */
2798 *key = g_strndup (line, p - line);
2802 *value = g_strdup (p + 1);
2809 /* + Only comments are accepted before the first group.
2811 * + The first group should be "Desktop Entry".
2813 * + Multiple groups may not have the same name.
2817 validate_parse_line (kf_validator *kf)
2825 line = kf->parse_buffer->str;
2826 len = kf->parse_buffer->len;
2828 if (!kf->utf8_warning && !g_utf8_validate (line, len, NULL)) {
2829 print_warning (kf, "file contains lines that are not UTF-8 encoded. There "
2830 "is no guarantee the validator will correctly work.\n");
2831 kf->utf8_warning = TRUE;
2834 if (g_ascii_isspace (*line)) {
2835 print_fatal (kf, "line \"%s\" starts with a space. Comment, group and "
2836 "key-value lines should not start with a space. The "
2837 "validation will continue, with the leading spaces "
2838 "ignored.\n", line);
2839 while (g_ascii_isspace (*line))
2843 if (validate_line_is_comment (kf, line))
2847 if (validate_line_looks_like_group (kf, line, &group)) {
2848 if (!kf->current_group &&
2849 (strcmp (group, GROUP_DESKTOP_ENTRY) &&
2850 strcmp (group, GROUP_KDE_DESKTOP_ENTRY)))
2851 print_fatal (kf, "first group is not \"" GROUP_DESKTOP_ENTRY "\"\n");
2853 if (kf->current_group && strcmp (kf->current_group, group))
2854 validate_keys_for_current_group (kf);
2856 if (g_hash_table_lookup_extended (kf->groups, group, NULL, NULL)) {
2857 print_fatal (kf, "file contains multiple groups named \"%s\", but "
2858 "multiple groups may not have the same name\n", group);
2860 validate_group_name (kf, group);
2861 g_hash_table_insert (kf->groups, g_strdup (group), NULL);
2864 if (kf->current_group)
2865 g_free (kf->current_group);
2866 kf->current_group = group;
2873 if (validate_line_looks_like_entry (kf, line, &key, &value)) {
2874 if (kf->current_group) {
2876 kf_keyvalue *keyvalue;
2878 keyvalue = g_slice_new (kf_keyvalue);
2879 keyvalue->key = key;
2880 keyvalue->value = value;
2882 keys = g_hash_table_lookup (kf->groups, kf->current_group);
2883 keys = g_slist_prepend (keys, keyvalue);
2884 g_hash_table_replace (kf->groups, g_strdup (kf->current_group), keys);
2891 print_fatal (kf, "file contains entry \"%s\" before the first group, "
2892 "but only comments are accepted before the first "
2899 print_fatal (kf, "file contains line \"%s\", which is not a comment, "
2900 "a group or an entry\n", line);
2903 /* + Desktop entry files are encoded as lines of 8-bit characters separated by
2908 validate_parse_data (kf_validator *kf,
2914 for (i = 0; i < length; i++) {
2915 if (data[i] == '\n') {
2916 if (i > 0 && data[i - 1] == '\r') {
2917 g_string_erase (kf->parse_buffer, kf->parse_buffer->len - 1, 1);
2919 if (!kf->cr_error) {
2920 print_fatal (kf, "file contains at least one line ending with a "
2921 "carriage return before the line feed, while lines "
2922 "should only be separated by a line feed "
2923 "character. First such line is: \"%s\"\n",
2924 kf->parse_buffer->str);
2925 kf->cr_error = TRUE;
2929 if (kf->parse_buffer->len > 0) {
2930 validate_parse_line (kf);
2931 g_string_erase (kf->parse_buffer, 0, -1);
2934 } else if (data[i] == '\r') {
2935 if (!kf->cr_error) {
2936 print_fatal (kf, "file contains at least one line ending with a "
2937 "carriage return, while lines should only be "
2938 "separated by a line feed character. First such "
2939 "line is: \"%s\"\n", kf->parse_buffer->str);
2940 kf->cr_error = TRUE;
2946 g_string_append_c (kf->parse_buffer, data[i]);
2951 validate_flush_parse_buffer (kf_validator *kf)
2953 if (kf->parse_buffer->len > 0) {
2954 validate_parse_line (kf);
2955 g_string_erase (kf->parse_buffer, 0, -1);
2958 if (kf->current_group)
2959 validate_keys_for_current_group (kf);
2962 #define VALIDATE_READ_SIZE 4096
2964 validate_parse_from_fd (kf_validator *kf,
2968 struct stat stat_buf;
2969 char read_buf[VALIDATE_READ_SIZE];
2971 if (fstat (fd, &stat_buf) < 0) {
2972 print_fatal (kf, "while reading the file: %s\n", g_strerror (errno));
2976 if (!S_ISREG (stat_buf.st_mode)) {
2977 print_fatal (kf, "file is not a regular file\n");
2981 if (stat_buf.st_size == 0) {
2982 print_fatal (kf, "file is empty\n");
2988 bytes_read = read (fd, read_buf, VALIDATE_READ_SIZE);
2990 if (bytes_read == 0) /* End of File */
2993 if (bytes_read < 0) {
2994 if (errno == EINTR || errno == EAGAIN)
2997 /* let's validate what we already have */
2998 validate_flush_parse_buffer (kf);
3000 print_fatal (kf, "while reading the file: %s\n", g_strerror (errno));
3004 validate_parse_data (kf, read_buf, bytes_read);
3007 validate_flush_parse_buffer (kf);
3013 validate_load_and_parse (kf_validator *kf)
3018 fd = g_open (kf->filename, O_RDONLY, 0);
3021 print_fatal (kf, "while reading the file: %s\n", g_strerror (errno));
3025 ret = validate_parse_from_fd (kf, fd);
3033 groups_hashtable_free (gpointer key,
3040 list = (GSList *) value;
3041 for (sl = list; sl != NULL; sl = sl->next) {
3042 kf_keyvalue *keyvalue;
3044 keyvalue = (kf_keyvalue *) sl->data;
3045 g_free (keyvalue->key);
3046 g_free (keyvalue->value);
3047 g_slice_free (kf_keyvalue, keyvalue);
3050 g_slist_free (list);
3056 desktop_file_validate (const char *filename,
3058 gboolean no_warn_deprecated,
3063 /* just a consistency check */
3064 g_assert (G_N_ELEMENTS (registered_types) == LAST_TYPE - 1);
3066 kf.filename = filename;
3067 kf.parse_buffer = g_string_new ("");
3068 kf.utf8_warning = FALSE;
3069 kf.cr_error = FALSE;
3070 kf.current_group = NULL;
3071 kf.groups = g_hash_table_new_full (g_str_hash, g_str_equal,
3073 kf.current_keys = NULL;
3074 kf.kde_reserved_warnings = warn_kde;
3075 kf.no_deprecated_warnings = no_warn_deprecated;
3076 kf.no_hints = no_hints;
3078 kf.main_group = NULL;
3079 kf.type = INVALID_TYPE;
3080 kf.type_string = NULL;
3082 kf.application_keys = NULL;
3083 kf.link_keys = NULL;
3084 kf.fsdevice_keys = NULL;
3085 kf.mimetype_keys = NULL;
3086 kf.action_values = g_hash_table_new_full (g_str_hash, g_str_equal,
3088 kf.action_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
3090 kf.fatal_error = FALSE;
3092 validate_load_and_parse (&kf);
3093 //FIXME: this does not work well if there are both a Desktop Entry and a KDE
3094 //Desktop Entry groups since only the last one will be validated for this.
3095 if (kf.main_group) {
3096 validate_required_desktop_keys (&kf);
3097 validate_type_keys (&kf);
3099 validate_actions (&kf);
3100 validate_filename (&kf);
3102 g_list_foreach (kf.application_keys, (GFunc) g_free, NULL);
3103 g_list_free (kf.application_keys);
3104 g_list_foreach (kf.link_keys, (GFunc) g_free, NULL);
3105 g_list_free (kf.link_keys);
3106 g_list_foreach (kf.fsdevice_keys, (GFunc) g_free, NULL);
3107 g_list_free (kf.fsdevice_keys);
3108 g_list_foreach (kf.mimetype_keys, (GFunc) g_free, NULL);
3109 g_list_free (kf.mimetype_keys);
3111 g_hash_table_destroy (kf.action_values);
3112 g_hash_table_destroy (kf.action_groups);
3114 g_assert (kf.current_keys == NULL);
3115 /* we can't add an automatic destroy handler for the value because we replace
3116 * it when adding keys, and this means we'd have to copy the value each time
3118 g_hash_table_foreach_remove (kf.groups, groups_hashtable_free, NULL);
3119 g_hash_table_destroy (kf.groups);
3120 g_free (kf.current_group);
3121 g_string_free (kf.parse_buffer, TRUE);
3123 return (!kf.fatal_error);
3126 /* return FALSE if we were unable to fix the file */
3128 desktop_file_fixup (GKeyFile *keyfile,
3129 const char *filename)
3131 if (g_key_file_has_group (keyfile, GROUP_KDE_DESKTOP_ENTRY)) {
3132 g_printerr ("%s: warning: renaming deprecated \"%s\" group to \"%s\"\n",
3133 filename, GROUP_KDE_DESKTOP_ENTRY, GROUP_DESKTOP_ENTRY);
3134 dfu_key_file_rename_group (keyfile,
3135 GROUP_KDE_DESKTOP_ENTRY, GROUP_DESKTOP_ENTRY);