commit 3711fefd0aa51a1f30b2b29e53ca895398126fcc Author: Stanislav Brabec Date: Wed Sep 15 16:53:45 2010 +0200 Implemented defaults.list heuristics using desktop-defaults.conf preferences. diff --git a/Makefile.am b/Makefile.am index 734c480..2153fad 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,7 +2,7 @@ SUBDIRS = man misc src ACLOCAL_AMFLAGS = -I m4 -EXTRA_DIST = HACKING +EXTRA_DIST = HACKING TODO MAINTAINERCLEANFILES = \ $(srcdir)/aclocal.m4 \ diff --git a/TODO b/TODO new file mode 100644 index 0000000..070e28e --- /dev/null +++ b/TODO @@ -0,0 +1,21 @@ +update-desktop-database MIME heuristic may be improved in these ways: + +Make per-directory defaults lists: It would require defaults.list format +change: assigning of default priority to each key. Application would +have to parse each defaults.list files and pick the application with the +highest priority as the real default. + +Make possible to define per-user desktop-defaults.conf as the way user +selects defaults. + +Application preference for MIME type has string type. It could make +sense to use string array here. + +It would be nice to make possible transitive definition (e. g. GNOME +environment can define Inherit=GTK). + +It would be nice to implement negative matches for Categories (e. g. GPE +would like to prefer gtk+ applications that are not GNOME based, i. e. +Categories=GPE;GTK;-GNOME) and desktop files (e. g. small memory systems +would like to exclude some applications) and wildcard matches (e. g. +Default=gnome-*.desktop). diff --git a/man/update-desktop-database.1 b/man/update-desktop-database.1 index 5a177c3..a4235b9 100644 --- a/man/update-desktop-database.1 +++ b/man/update-desktop-database.1 @@ -2,12 +2,12 @@ .\" update-desktop-database manual page. .\" (C) 2010 Vincent Untz (vuntz@gnome.org) .\" -.TH DESKTOP-FILE-VALIDATE 1 FREEDESKTOP.ORG +.TH UPDATE-DESKTOP-DATABASE 1 FREEDESKTOP.ORG .SH NAME update-desktop-database \- Build cache database of MIME types handled by desktop files .SH SYNOPSIS -.B update-desktop-database [\-q|\-\-quiet] [\-v|\-\-verbose] [DIRECTORY...] +.B update-desktop-database [\-q|\-\-quiet] [\-v|\-\-verbose] [\-d|\-\-defaults\-list] [DIRECTORY...] .SH DESCRIPTION The \fIupdate-desktop-database\fP program is a tool to build a cache database of the MIME types handled by desktop files. @@ -29,11 +29,18 @@ If both the \fI--quiet\fP and \fI--verbose\fP options are used, then .SH OPTIONS The following options are supported: .TP +.I -d, --defaults-list +Use preferences from \fB${XDG_CONFIG_DIRS[0]}/desktop-defaults.conf\fP +and creates a best-attempt of the system defaults list. No +\fIDIRECTORY\fP can be specified as argument together. +.TP .I -q, --quiet Do not display any information about processing and updating progress. .TP .I -v, --verbose Display more information about processing and updating progress. +If \fBdefaults.list\fP are created, then it will also add comment +explaining each entry in the file. .SH NOTES .PP If an invalid MIME type is met, it will be ignored and the creation of @@ -47,7 +54,7 @@ that can handle this MIME type. The order of the desktop files found for a MIME type is not significant. Therefore, an external mechanism must be used to determine what is the preferred desktop file for a MIME type. -.SH EXAMPLE +.SH EXAMPLES Here is a simple example of a cache database: .IP [MIME Cache] @@ -61,11 +68,58 @@ a \fBMimeType\fP key: \fBgedit.desktop\fP: MimeType=text/plain;application/x-shellscript; \fBgvim.desktop\fP: MimeType=text/plain; \fBtotem.desktop\fP: MimeType=video/webm; + +Here is a simple example of \fBdesktop-defaults.conf\fP: +.IP + [Desktop Defaults] + Default=MozillaFirefox.desktop; + + [GNOME] + XDG_DESKTOP_PREFIX=gnome- + Categories=GNOME;GTK; + Preferred=totem.desktop; + Default=eog.desktop;evince.desktop;gedit.desktop; + image/tiff=eog.desktop +.PP +These preferences will create two defauls list, tha fallback +\fBdefaults.list\fP and \fBgnome-defaults.list\fP (the file name is +defined by the \fBXDG_DESKTOP_PREFIX\fP). +.PP +Only \fBMozillaFirefox.desktop\fP will appear in the \fBdefaults.list\fP +for MIME types it handles. +.PP +For GNOME, it will use defined preferences. +.PP +Assigning MIME works in this order. Values listed earlier in the list +take precedence in case of more matches. +.IP "1." +If it matches any particular key, the desktop file there will be used, +if available. +.IP "2." +If any desktop file in \fBPreferred\fP key matches the MIME type, it +will be used. +.IP "3." +If any desktop file in \fBDefault\fP key matches the MIME type, it +will be used. +.IP "4." +If any desktop file matches any of keys defined in \fBCategories\fP and +MIME type, it will be used. .SH FILES .PP .B $XDG_DATA_DIRS/applications/mimeinfo.cache .IP This file is the cache database created by \fIupdate-desktop-database\fP. +.PP +.B ${XDG_CONFIG_DIRS[0]}/desktop-defaults.conf +.IP +This file contains preferences for the defaults list generator. +.PP +.B ${XDG_DATA_DIRS[0]}/${ENVIRONMENT}defaults.list +.IP +These files are generated when using \fI--defaults-list\fP argument. +Lists are generated for empty \fB${ENVIRONMENT}\fP and for all +additional section titles in the \fBdesktop-defaults.conf\fP with +trailing hyphen added or values defined in \fBXDG_DESKTOP_PREFIX\fP key. .SH BUGS If you find bugs in the \fIupdate-desktop-database\fP program, please report these on https://bugs.freedesktop.org. diff --git a/src/Makefile.am b/src/Makefile.am index 038bba6..e6d3f1d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -9,6 +9,10 @@ bin_PROGRAMS = \ desktop-file-install \ update-desktop-database +xdgconfigdir = $(sysconfdir)/xdg +xdgconfig_DATA = \ + desktop-defaults.conf + desktop_file_validate_SOURCES= \ keyfileutils.c \ keyfileutils.h \ @@ -28,6 +32,8 @@ desktop_file_install_SOURCES= \ install.c update_desktop_database_SOURCES = \ + keyfileutils.c \ + keyfileutils.h \ mimeutils.c \ mimeutils.h \ update-desktop-database.c @@ -36,4 +42,7 @@ desktop_file_validate_LDADD = $(DESKTOP_FILE_UTILS_LIBS) desktop_file_install_LDADD = $(DESKTOP_FILE_UTILS_LIBS) update_desktop_database_LDADD = $(DESKTOP_FILE_UTILS_LIBS) +EXTRA_DIST = \ + $(xdgconfig_DATA) + -include $(top_srcdir)/git.mk diff --git a/src/desktop-defaults.conf b/src/desktop-defaults.conf new file mode 100644 index 0000000..0ac6b24 --- /dev/null +++ b/src/desktop-defaults.conf @@ -0,0 +1,17 @@ +#Desktop defaults for update-desktop-database. + +[Desktop Defaults] +Default=MozillaFirefox.desktop; + +[GNOME] +XDG_DESKTOP_PREFIX=gnome- +Categories=GNOME;GTK; +Preferred=totem.desktop; +Default=eog.desktop;evince.desktop;gedit.desktop;file-roller.desktop;nautilus-folder-handler.desktop;epiphany.desktop; +image/tiff=eog.desktop + +[KDE] +XDG_DESKTOP_PREFIX=kde- +Categories=KDE;Qt; +Preferred=kaffeine.desktop; +Default=gwenview.desktop;okular.desktop;kwrite.desktop;ark.desktop;dolphin.desktop;konqueror.desktop; diff --git a/src/update-desktop-database.c b/src/update-desktop-database.c index 4277fc2..6447c37 100644 --- a/src/update-desktop-database.c +++ b/src/update-desktop-database.c @@ -41,16 +41,97 @@ #define NAME "update-desktop-database" #define CACHE_FILENAME "mimeinfo.cache" +#define DEFAULTS_CONF "desktop-defaults.conf" +#define DEFAULTS_LIST "defaults.list" #define TEMP_CACHE_FILENAME_PREFIX ".mimeinfo.cache.XXXXXX" #define udd_print(...) if (!quiet) g_printerr (__VA_ARGS__) #define udd_verbose_print(...) if (!quiet && verbose) g_printerr (__VA_ARGS__) +enum defaults_order { + DEFAULT_FOR_MIME, + PREFERRED_DEFAULT, + PLAIN_DEFAULT, + CATEGORIES_MATCH, + UNKNOWN +}; + +struct mime_default { + gchar *desktop_file; + enum defaults_order order; + gint suborder; +}; + +struct desktop_environment { + gchar *prefix; /* XDG prefix */ + GHashTable *defaults_for_mime; /* key: mime-type, value: desktop file */ + GList *preferred_default_desktops; /* data: desktop file */ + GList *default_desktops; /* data: desktop file */ + GList *categories; /* data: categories */ + GList *inherit; /* data: struct desktop_environment */ + GList *inherit_names; /* data: gchar* */ + GHashTable *recognized_defaults; /* key: mime/type, value: mime_default */ + enum defaults_order processing_order; /* relevant to desktop file just processed */ + gint processing_suborder; /* relevant to desktop file just processed */ +}; + +/** + * g_list_index_custom: + * @list: a #GList + * @data: user data passed to the function + * @func: the function to call for each element. + * It should return 0 when the desired element is found + * + * Gets the position of the element containing + * the given data (starting from 0), using a supplied function to + * find the desired element. It iterates over the list, calling + * the given function which should return 0 when the desired + * element is found. The function takes two #gconstpointer arguments, + * the #GList element's data as the first argument and the + * given user data. + * + * Returns: the index of the element containing the data, + * or -1 if the data is not found + */ +static gint +g_list_index_custom (GList *list, + gconstpointer data, + GCompareFunc func) +{ + gint i; + + i = 0; + while (list) + { + if (! func (list->data, data)) + return i; + i++; + list = list->next; + } + + return -1; +} + +static inline gint +g_string_compare_equal (gconstpointer a, + gconstpointer b) +{ + if (g_strcmp0 (a, b)) + return 1; + else + return 0; +} + static FILE *open_temp_cache_file (const char *dir, char **filename, GError **error); static void add_mime_type (const char *mime_type, GList *desktop_files, FILE *f); static void sync_database (const char *dir, GError **error); +static void register_default_for_mime (GHashTable *recognized_defaults, + const char *mime_type, + const char *desktop_file, + enum defaults_order order, + gint suborder); static void cache_desktop_file (const char *desktop_file, const char *mime_type, GError **error); @@ -65,7 +146,8 @@ static const char ** get_default_search_path (void); static void print_desktop_dirs (const char **dirs); static GHashTable *mime_types_map = NULL; -static gboolean verbose = FALSE, quiet = FALSE; +static GList *enviro_list = NULL; +static gboolean verbose = FALSE, quiet = FALSE, defaults = FALSE; static void list_free_deep (gpointer key, GList *l, gpointer data) @@ -75,18 +157,71 @@ list_free_deep (gpointer key, GList *l, gpointer data) } static void +register_default_for_mime (GHashTable *recognized_defaults, + const char *mime_type, + const char *desktop_file, + enum defaults_order order, + gint suborder) +{ + struct mime_default *current_default; + + current_default = g_hash_table_lookup (recognized_defaults, mime_type); + + if (current_default) + { + if (order < current_default->order || + (order == current_default->order && suborder < current_default->suborder)) + { + g_free (current_default->desktop_file); + current_default->desktop_file = g_strdup (desktop_file); + current_default->order = order; + current_default->suborder = suborder; + } + } + else + { + current_default = g_new (struct mime_default, 1); + current_default->desktop_file = g_strdup (desktop_file); + current_default->order = order; + current_default->suborder = suborder; + g_hash_table_insert (recognized_defaults, g_strdup (mime_type), current_default); + } +} + + +static void cache_desktop_file (const char *desktop_file, const char *mime_type, GError **error) { - GList *desktop_files; + GList *desktop_files, *enviros; desktop_files = (GList *) g_hash_table_lookup (mime_types_map, mime_type); desktop_files = g_list_prepend (desktop_files, g_strdup (desktop_file)); g_hash_table_insert (mime_types_map, g_strdup (mime_type), desktop_files); -} + if (!defaults) + return; + + enviros = enviro_list; + while (enviros) + { + struct desktop_environment *enviro = enviros->data; + const gchar *current_default; + + /* desktop listed as default for certain MIME type */ + current_default = g_hash_table_lookup (enviro->defaults_for_mime, mime_type); + if (!g_strcmp0 (desktop_file, current_default)) + register_default_for_mime (enviro->recognized_defaults, mime_type, desktop_file, + DEFAULT_FOR_MIME, 0); + /* desktop listed in other way */ + else if (enviro->processing_order != UNKNOWN) + register_default_for_mime (enviro->recognized_defaults, mime_type, desktop_file, + enviro->processing_order, enviro->processing_suborder); + enviros = g_list_next (enviros); + } +} static void process_desktop_file (const char *desktop_file, @@ -95,7 +230,8 @@ process_desktop_file (const char *desktop_file, { GError *load_error; GKeyFile *keyfile; - char **mime_types; + GList *list; + char **mime_types, **categories; int i; keyfile = g_key_file_new (); @@ -113,6 +249,10 @@ process_desktop_file (const char *desktop_file, mime_types = g_key_file_get_string_list (keyfile, GROUP_DESKTOP_ENTRY, "MimeType", NULL, &load_error); + if (defaults) + categories = g_key_file_get_string_list (keyfile, + GROUP_DESKTOP_ENTRY, + "Categories", NULL, NULL); g_key_file_free (keyfile); @@ -122,6 +262,52 @@ process_desktop_file (const char *desktop_file, return; } + if (defaults) + { + list = enviro_list; + while (list) + { + struct desktop_environment *enviro = list->data; + gint suborder; + + enviro->processing_order = UNKNOWN; + suborder = g_list_index_custom (enviro->preferred_default_desktops, name, + g_string_compare_equal); + if (suborder != -1) + { + enviro->processing_order = PREFERRED_DEFAULT; + enviro->processing_suborder = suborder; + } + else + { + suborder = g_list_index_custom (enviro->default_desktops, name, + g_string_compare_equal); + if (suborder != -1) + { + enviro->processing_order = PLAIN_DEFAULT; + enviro->processing_suborder = suborder; + } + else if (categories) + { + gint i, min_suborder = G_MAXINT; + for (i = 0; categories[i] != NULL; i++) + { + suborder = g_list_index_custom (enviro->categories, categories[i], + g_string_compare_equal); + if (suborder != -1 && suborder < min_suborder) + { + enviro->processing_order = CATEGORIES_MATCH; + enviro->processing_suborder = suborder; + min_suborder = suborder; + } + } + } + } + list = g_list_next (list); + } + g_strfreev (categories); + } + for (i = 0; mime_types[i] != NULL; i++) { char *mime_type; @@ -353,6 +539,205 @@ sync_database (const char *dir, GError **error) } static void +strarray_to_list (gchar **strarray, + GList **list) +{ + gchar **ptr; + if (!list) + return; + if (!strarray) + return; + ptr = strarray; + while (*ptr) + { + *list = g_list_append (*list, g_strdup (*ptr)); + ptr++; + } +} + +static const char * +get_defaults_conf (void) +{ + static const char *arg = NULL; + const char * const *config_dirs; + + if (arg != NULL) + return arg; + + config_dirs = g_get_system_config_dirs (); + + arg = g_build_filename (config_dirs[0], DEFAULTS_CONF, NULL); + + return arg; +} + +static void +parse_defaults_conf (GError **error) +{ + GError *parse_error; + GKeyFile *keyfile; + gchar **groups, **group, **keys, **key; + gchar **strarray, *value; + struct desktop_environment *enviro; + + parse_error = NULL; + keyfile = g_key_file_new (); + + g_key_file_load_from_file (keyfile, get_defaults_conf (), + G_KEY_FILE_NONE, &parse_error); + + groups = g_key_file_get_groups (keyfile, NULL); + + if (groups) + { + group = groups; + while (*group) + { + keys = g_key_file_get_keys (keyfile, *group, NULL, NULL); + if (keys) + { + enviro = g_new0 (struct desktop_environment, 1); + enviro->defaults_for_mime = g_hash_table_new (g_str_hash, g_str_equal); + enviro->recognized_defaults = g_hash_table_new (g_str_hash, g_str_equal); + enviro_list = g_list_append (enviro_list, enviro); + + if (!strcmp (*group, "Desktop Defaults")) + enviro->prefix = ""; + else + { + value = g_key_file_get_string (keyfile, + *group, + "XDG_DESKTOP_PREFIX", NULL); + if (value) + enviro->prefix = value; + else + { + enviro->prefix = g_strdup_printf ("%s-", *group); + g_free (value); + } + } + + strarray = g_key_file_get_string_list (keyfile, + *group, + "Preferred", NULL, NULL); + strarray_to_list (strarray, &(enviro->preferred_default_desktops)); + + strarray = g_key_file_get_string_list (keyfile, + *group, + "Default", NULL, NULL); + strarray_to_list (strarray, &(enviro->default_desktops)); + + strarray = g_key_file_get_string_list (keyfile, + *group, + "Categories", NULL, NULL); + strarray_to_list (strarray, &(enviro->categories)); + + key = keys; + while (*key) + { + if (strstr (*key, "/")) + { + gchar *desktop_file; + desktop_file = g_key_file_get_string (keyfile, *group, *key, NULL); + if (desktop_file) + g_hash_table_insert (enviro->defaults_for_mime, g_strdup (*key), g_strdup (desktop_file)); + } + key++; + } + g_strfreev (keys); + } + group++; + } + g_strfreev (groups); + } + g_key_file_free (keyfile); + if (parse_error != NULL) + g_propagate_error (error, parse_error); +} + +static void +generate_default (gpointer key, + gpointer value, + gpointer user_data) +{ + struct mime_default *recognized_default = value; + g_key_file_set_string (user_data, + "Default Applications", + key, + recognized_default->desktop_file); + if (verbose) + { + const gchar *default_name; + gchar *comment; + switch (recognized_default->order) + { + case DEFAULT_FOR_MIME: + default_name = "configured as default for MIME type"; + break; + case PREFERRED_DEFAULT: + default_name = "configured by Preferred key, value"; + break; + case PLAIN_DEFAULT: + default_name = "configured by Default key, value"; + break; + case CATEGORIES_MATCH: + default_name = "selected by Categories match, value"; + break; + /* These two should never appear */ + case UNKNOWN: + default_name = "used as fallback"; + break; + default: + default_name = "invalid default"; + break; + } + comment = g_strdup_printf ("priority %d.%d (%s #%d)", + recognized_default->order, recognized_default->suborder, + default_name, recognized_default->suborder+1); + g_key_file_set_comment (user_data, + "Default Applications", + key, comment, NULL); + g_free (comment); + } +} + +static void +generate_defaults_list (GError **error) +{ + GList *list; + GKeyFile *keyfile; + GError *generate_error; + + generate_error = NULL; + list = enviro_list; + while (list) + { + gchar *basename, *filename; + struct desktop_environment *enviro = list->data; + + keyfile = g_key_file_new (); + + g_key_file_set_comment (keyfile, NULL, NULL, + "This file is auto-generated by update-desktop-database.", + &generate_error); + + g_hash_table_foreach (enviro->recognized_defaults, generate_default, keyfile); + + basename = g_strdup_printf ("%s" DEFAULTS_LIST, enviro->prefix); + filename = g_build_filename (g_get_system_data_dirs ()[0], + "applications", basename, NULL); + g_free (basename); + dfu_key_file_to_file (keyfile, filename, &generate_error); + g_free (filename); + g_key_file_free (keyfile); + + list = g_list_next (list); + } + if (generate_error != NULL) + g_propagate_error (error, generate_error); +} + +static void update_database (const char *desktop_dir, GError **error) { @@ -372,6 +757,7 @@ update_database (const char *desktop_dir, sync_database (desktop_dir, &update_error); if (update_error != NULL) g_propagate_error (error, update_error); + } g_hash_table_foreach (mime_types_map, (GHFunc) list_free_deep, NULL); g_hash_table_destroy (mime_types_map); @@ -423,12 +809,16 @@ main (int argc, const GOptionEntry options[] = { + { "defaults-list", 'd', 0, G_OPTION_ARG_NONE, &defaults, + N_("Generate defaults.list files using preferenced defined in ${XDG_CONFIG_DIRS[0]}/desktop-defaults.conf"), + NULL}, + { "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, N_("Do not display any information about processing and " "updating progress"), NULL}, { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, - N_("Display more information about processing and updating progress"), + N_("Display more information about processing and updating progress, comment reasons in defaults lists"), NULL}, { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &desktop_dirs, @@ -451,11 +841,31 @@ main (int argc, return 1; } + if (defaults && desktop_dirs) + { + udd_print (_("Defaults list can be generated only for default directories.\n")); + return 127; + } + if (desktop_dirs == NULL || desktop_dirs[0] == NULL) - desktop_dirs = get_default_search_path (); + { + desktop_dirs = get_default_search_path (); + defaults = TRUE; + } print_desktop_dirs (desktop_dirs); + if (defaults) + parse_defaults_conf (&error); + if (error != NULL) + { + udd_print (_("Error in desktop-defaults.conf: %s\nSkipping generating of system defaults.\n"), + error->message); + g_error_free (error); + error = NULL; + defaults = FALSE; + } + found_processable_dir = FALSE; for (i = 0; desktop_dirs[i] != NULL; i++) { @@ -487,5 +897,18 @@ main (int argc, return 1; } + if (defaults) + { + generate_defaults_list (&error); + if (error != NULL) + { + udd_print (_("Error during generating of defaults list: %s\n"), + error->message); + g_error_free (error); + error = NULL; + defaults = FALSE; + } + } + return 0; }