/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * gimp-user-install.c * Copyright (C) 2000-2008 Michael Natterer and Sven Neumann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /* This file contains functions to help migrate the settings from a * previous GIMP version to be used with the current (newer) version. */ #include "config.h" #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef PLATFORM_OSX #include #endif #include #include #ifdef G_OS_WIN32 #include #endif #include "libgimpbase/gimpbase.h" #include "core-types.h" #include "config/gimpconfig-file.h" #include "config/gimprc.h" #include "gimp-templates.h" #include "gimp-tags.h" #include "gimp-user-install.h" #include "gimp-intl.h" struct _GimpUserInstall { GObject *gimp; gboolean verbose; gchar *old_dir; gint old_major; gint old_minor; gint scale_factor; gboolean migrate; GimpUserInstallLogFunc log; gpointer log_data; GHashTable *accels; }; typedef enum { USER_INSTALL_MKDIR, /* Create the directory */ USER_INSTALL_COPY /* Copy from sysconf directory */ } GimpUserInstallAction; static const struct { const gchar *name; GimpUserInstallAction action; } gimp_user_install_items[] = { { "brushes", USER_INSTALL_MKDIR }, { "dynamics", USER_INSTALL_MKDIR }, { "fonts", USER_INSTALL_MKDIR }, { "gradients", USER_INSTALL_MKDIR }, { "palettes", USER_INSTALL_MKDIR }, { "patterns", USER_INSTALL_MKDIR }, { "tool-presets", USER_INSTALL_MKDIR }, { "plug-ins", USER_INSTALL_MKDIR }, { "modules", USER_INSTALL_MKDIR }, { "interpreters", USER_INSTALL_MKDIR }, { "environ", USER_INSTALL_MKDIR }, { "scripts", USER_INSTALL_MKDIR }, { "templates", USER_INSTALL_MKDIR }, { "themes", USER_INSTALL_MKDIR }, { "icons", USER_INSTALL_MKDIR }, { "tmp", USER_INSTALL_MKDIR }, { "curves", USER_INSTALL_MKDIR }, { "levels", USER_INSTALL_MKDIR }, { "filters", USER_INSTALL_MKDIR }, { "fractalexplorer", USER_INSTALL_MKDIR }, { "gfig", USER_INSTALL_MKDIR }, { "gflare", USER_INSTALL_MKDIR }, { "gimpressionist", USER_INSTALL_MKDIR } }; static gboolean user_install_detect_old (GimpUserInstall *install, const gchar *gimp_dir); static gchar * user_install_old_style_gimpdir (void); static gchar * user_install_flatpak_gimpdir (gint minor); static void user_install_log (GimpUserInstall *install, const gchar *format, ...) G_GNUC_PRINTF (2, 3); static void user_install_log_newline (GimpUserInstall *install); static void user_install_log_error (GimpUserInstall *install, GError **error); static gboolean user_install_mkdir (GimpUserInstall *install, const gchar *dirname); static gboolean user_install_mkdir_with_parents (GimpUserInstall *install, const gchar *dirname); static gboolean user_install_file_copy (GimpUserInstall *install, const gchar *source, const gchar *dest, const gchar *old_options_regexp, GRegexEvalCallback update_callback, GimpCopyPostProcess post_process_callback); static gboolean user_install_dir_copy (GimpUserInstall *install, gint level, const gchar *source, const gchar *base, const gchar *update_pattern, GRegexEvalCallback update_callback, GimpCopyPostProcess post_process_callback); static gboolean user_install_create_files (GimpUserInstall *install); static gboolean user_install_migrate_files (GimpUserInstall *install); /* public functions */ GimpUserInstall * gimp_user_install_new (GObject *gimp, gboolean verbose) { GimpUserInstall *install = g_slice_new0 (GimpUserInstall); install->gimp = gimp; install->verbose = verbose; install->accels = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); user_install_detect_old (install, gimp_directory ()); #ifdef PLATFORM_OSX /* The config path on OSX has for a very short time frame (2.8.2 only) been "~/Library/GIMP". It changed to "~/Library/Application Support" in 2.8.4 and was in the home folder (as was other UNIX) before. */ if (! install->old_dir) { gchar *dir; NSAutoreleasePool *pool; NSArray *path; NSString *library_dir; pool = [[NSAutoreleasePool alloc] init]; path = NSSearchPathForDirectoriesInDomains (NSLibraryDirectory, NSUserDomainMask, YES); library_dir = [path objectAtIndex:0]; dir = g_build_filename ([library_dir UTF8String], GIMPDIR, GIMP_USER_VERSION, NULL); [pool drain]; user_install_detect_old (install, dir); g_free (dir); } #endif if (! install->old_dir) { /* if the default XDG-style config directory was not found, try * the "old-style" path in the home folder. */ gchar *dir = user_install_old_style_gimpdir (); user_install_detect_old (install, dir); g_free (dir); } return install; } gboolean gimp_user_install_run (GimpUserInstall *install, gint scale_factor) { gchar *dirname; g_return_val_if_fail (install != NULL, FALSE); install->scale_factor = scale_factor; dirname = g_filename_display_name (gimp_directory ()); if (install->migrate) { gchar *verstring; /* TODO: these 2 strings should be merged into one, but it was not * possible to do it at implementation time, in order not to break * string freeze. */ verstring = g_strdup_printf ("%d.%d", install->old_major, install->old_minor); user_install_log (install, _("It seems you have used GIMP %s before. " "GIMP will now migrate your user settings to '%s'."), verstring, dirname); g_free (verstring); } else { user_install_log (install, _("It appears that you are using GIMP for the " "first time. GIMP will now create a folder " "named '%s' and copy some files to it."), dirname); } g_free (dirname); user_install_log_newline (install); if (! user_install_mkdir_with_parents (install, gimp_directory ())) return FALSE; if (install->migrate) if (! user_install_migrate_files (install)) return FALSE; return user_install_create_files (install); } void gimp_user_install_free (GimpUserInstall *install) { g_return_if_fail (install != NULL); g_free (install->old_dir); g_hash_table_destroy (install->accels); g_slice_free (GimpUserInstall, install); } void gimp_user_install_set_log_handler (GimpUserInstall *install, GimpUserInstallLogFunc log, gpointer user_data) { g_return_if_fail (install != NULL); install->log = log; install->log_data = user_data; } /* Local functions */ static gboolean user_install_detect_old (GimpUserInstall *install, const gchar *gimp_dir) { gchar *dir = g_strconcat (gimp_dir, "ZZZ", NULL); gchar *version; gboolean migrate = FALSE; version = strstr (dir, GIMP_APP_VERSION); if (version) { gint major; gint minor; for (major = 3; major >= 2; major--) { gint max_minor; g_snprintf (version, 5, "%d.XY", major); switch (major) { case 3: max_minor = GIMP_MINOR_VERSION; break; case 2: max_minor = 10; break; default: g_return_val_if_reached (FALSE); } for (minor = (max_minor & ~1); minor >= 0; minor -= 2) { /* we assume that GIMP_APP_VERSION is in the form '2.x' */ g_snprintf (version + 2, 3, "%d", minor); migrate = g_file_test (dir, G_FILE_TEST_IS_DIR); if (migrate) { install->old_major = 2; install->old_minor = minor; break; } #ifdef G_OS_UNIX if (minor == 10) { /* This is special-casing for GIMP 2.10 as flatpak where * we had this weird inconsistency: depending on whether a * previous $XDG_CONFIG_HOME/GIMP/ folder was found or * not, the config folder would be either inside, or would * be in $HOME/.var/ as other flatpak (see #5331). * For GIMP 3, even the flatpak will always be in * $XDG_CONFIG_HOME. But then we want a migration to still * find the previous config folder. * If one was found in $XDG_CONFIG_HOME, it is used in * priority. Then ~/.var/ is used a fallback, if found. */ gchar *flatpak_dir = user_install_flatpak_gimpdir (minor); if (flatpak_dir) /* This first test is for finding a 2.10 flatpak config * dir from a non-flatpak GIMP 3+. */ migrate = g_file_test (flatpak_dir, G_FILE_TEST_IS_DIR); if (! migrate && g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS)) { /* Now we check /var/config/GIMP because this is where * local ~/.var/ is mounted inside the sandbox. * So this second test is for finding a 2.10 flatpak * config dir from a flatpak GIMP 3+. */ g_free (flatpak_dir); flatpak_dir = g_build_filename ("/var/config/GIMP/", version, NULL); migrate = g_file_test (flatpak_dir, G_FILE_TEST_IS_DIR); } if (migrate) { install->old_major = 2; install->old_minor = minor; g_free (dir); dir = flatpak_dir; break; } else { g_free (flatpak_dir); } } #endif } } } install->migrate = migrate; if (migrate) { install->old_dir = dir; #ifdef GIMP_UNSTABLE g_printerr ("gimp-user-install: migrating from %s\n", dir); #endif } else { g_free (dir); } return migrate; } static gchar * user_install_old_style_gimpdir (void) { const gchar *home_dir = g_get_home_dir (); gchar *gimp_dir = NULL; if (home_dir) { gimp_dir = g_build_filename (home_dir, ".gimp-" GIMP_APP_VERSION, NULL); } else { gchar *user_name = g_strdup (g_get_user_name ()); gchar *subdir_name; #ifdef G_OS_WIN32 gchar *p = user_name; while (*p) { /* Replace funny characters in the user name with an * underscore. The code below also replaces some * characters that in fact are legal in file names, but * who cares, as long as the definitely illegal ones are * caught. */ if (!g_ascii_isalnum (*p) && !strchr ("-.,@=", *p)) *p = '_'; p++; } #endif #ifndef G_OS_WIN32 g_message ("warning: no home directory."); #endif subdir_name = g_strconcat (".gimp-" GIMP_APP_VERSION ".", user_name, NULL); gimp_dir = g_build_filename (gimp_data_directory (), subdir_name, NULL); g_free (user_name); g_free (subdir_name); } return gimp_dir; } static gchar * user_install_flatpak_gimpdir (gint minor) { const gchar *home_dir = g_get_home_dir (); gchar *version = g_strdup_printf ("2.%d", minor); gchar *gimp_dir = NULL; if (home_dir) /* AFAIK (after researching flatpak docs, and searching the web), * the ~/.var/ directory is hardcoded and cannot be modified by an * environment variable. */ gimp_dir = g_build_filename (home_dir, ".var/app/org.gimp.GIMP/config/GIMP/", version, NULL); g_free (version); return gimp_dir; } static void user_install_log (GimpUserInstall *install, const gchar *format, ...) { va_list args; va_start (args, format); if (format) { gchar *message = g_strdup_vprintf (format, args); if (install->verbose) g_print ("%s\n", message); if (install->log) install->log (message, FALSE, install->log_data); g_free (message); } va_end (args); } static void user_install_log_newline (GimpUserInstall *install) { if (install->verbose) g_print ("\n"); if (install->log) install->log (NULL, FALSE, install->log_data); } static void user_install_log_error (GimpUserInstall *install, GError **error) { if (error && *error) { const gchar *message = ((*error)->message ? (*error)->message : "(unknown error)"); if (install->log) install->log (message, TRUE, install->log_data); else g_print ("error: %s\n", message); g_clear_error (error); } } static gboolean user_install_file_copy (GimpUserInstall *install, const gchar *source, const gchar *dest, const gchar *old_options_regexp, GRegexEvalCallback update_callback, GimpCopyPostProcess post_process_callback) { GError *error = NULL; gboolean success; user_install_log (install, _("Copying file '%s' from '%s'..."), gimp_filename_to_utf8 (dest), gimp_filename_to_utf8 (source)); success = gimp_config_file_copy (source, dest, old_options_regexp, update_callback, post_process_callback, install, &error); user_install_log_error (install, &error); return success; } static gboolean user_install_mkdir (GimpUserInstall *install, const gchar *dirname) { user_install_log (install, _("Creating folder '%s'..."), gimp_filename_to_utf8 (dirname)); if (g_mkdir (dirname, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == -1) { GError *error = NULL; g_set_error (&error, G_FILE_ERROR, g_file_error_from_errno (errno), _("Cannot create folder '%s': %s"), gimp_filename_to_utf8 (dirname), g_strerror (errno)); user_install_log_error (install, &error); return FALSE; } return TRUE; } static gboolean user_install_mkdir_with_parents (GimpUserInstall *install, const gchar *dirname) { user_install_log (install, _("Creating folder '%s'..."), gimp_filename_to_utf8 (dirname)); if (g_mkdir_with_parents (dirname, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == -1) { GError *error = NULL; g_set_error (&error, G_FILE_ERROR, g_file_error_from_errno (errno), _("Cannot create folder '%s': %s"), gimp_filename_to_utf8 (dirname), g_strerror (errno)); user_install_log_error (install, &error); return FALSE; } return TRUE; } /* The regexp pattern of all options changed from menurc of GIMP 2.8. * Add any pattern that we want to recognize for replacement in the menurc of * the next release */ #define MENURC_OVER20_UPDATE_PATTERN \ "(;)? *\\(gtk_accel_path \"/[a-zA-Z0-9-]*/([a-zA-Z0-9-]*)\" *\"([a-zA-Z0-9<>_]*)\"\\)" "|" \ "(;.*)" /** * callback to use for updating a menurc from GIMP over 2.0. * data is unused (always NULL). * The updated value will be matched line by line. */ static gboolean user_update_menurc_over20 (const GMatchInfo *matched_value, GString *new_value, gpointer data) { GimpUserInstall *install = (GimpUserInstall *) data; gchar *comment_match = g_match_info_fetch (matched_value, 1); gchar *action_match = g_match_info_fetch (matched_value, 2); gchar *accel_match = g_match_info_fetch (matched_value, 3); gchar *ignore_match = g_match_info_fetch (matched_value, 4); gchar *new_action_name = NULL; gboolean accel_variant = FALSE; if (strlen (ignore_match) == 0) { /* "*-paste-as-new" renamed to "*-paste-as-new-image" */ if (g_strcmp0 (action_match, "buffers-paste-as-new") == 0) new_action_name = g_strdup ("buffers-paste-as-new-image"); else if (g_strcmp0 (action_match, "edit-paste-as-new") == 0) new_action_name = g_strdup ("edit-paste-as-new-image"); /* file-export-* changes to follow file-save-* patterns. Actions * available since GIMP 2.8, changed for 2.10 in commit 4b14ed2. */ else if (g_strcmp0 (action_match, "file-export") == 0 && install->old_major == 2 && install->old_minor <= 8) new_action_name = g_strdup ("file-export-as"); else if (g_strcmp0 (action_match, "file-export-to") == 0 && install->old_major == 2 && install->old_minor <= 8) new_action_name = g_strdup ("file-export"); else if (g_strcmp0 (action_match, "layers-text-tool") == 0) new_action_name = g_strdup ("layers-edit"); /* plug-in-gauss doesn't exist anymore since commit ff59aebbe88. * The expected replacement would be filters-gaussian-blur which is * gegl:gaussian-blur operation. See also bug 775931. */ else if (g_strcmp0 (action_match, "plug-in-gauss") == 0) new_action_name = g_strdup ("filters-gaussian-blur"); /* Tools settings renamed more user-friendly. Actions available * since GIMP 2.4, changed for 2.10 in commit 0bdb747. */ else if (g_str_has_prefix (action_match, "tools-value-1-")) new_action_name = g_strdup_printf ("tools-opacity-%s", action_match + 14); else if (g_str_has_prefix (action_match, "tools-value-2-")) new_action_name = g_strdup_printf ("tools-size-%s", action_match + 14); else if (g_str_has_prefix (action_match, "tools-value-3-")) new_action_name = g_strdup_printf ("tools-aspect-%s", action_match + 14); else if (g_str_has_prefix (action_match, "tools-value-4-")) new_action_name = g_strdup_printf ("tools-angle-%s", action_match + 14); else if (g_strcmp0 (action_match, "tools-blend") == 0) new_action_name = g_strdup ("tools-gradient"); else if (g_strcmp0 (action_match, "vectors-path-tool") == 0) new_action_name = g_strdup ("paths-edit"); /* Following the s/GimpVectors/GimpPath/ renaming to be more * consistent with the GUI, we also rename all the action names. * Since GIMP 3.0, commit XXXX. */ else if (g_strcmp0 (action_match, "vectors-selection-from-vectors") == 0) new_action_name = g_strdup ("paths-selection-from-paths"); else if (g_str_has_prefix (action_match, "vectors-selection-to-vectors")) new_action_name = g_strdup_printf ("paths-selection-to-path%s", action_match + strlen ("vectors-selection-to-vectors")); else if (g_str_has_prefix (action_match, "vectors-")) new_action_name = g_strdup_printf ("paths-%s", action_match + 8); /* view-rotate-reset became view-reset and new view-rotate-reset and * view-flip-reset actions were created. See commit 15fb4a7be0. */ else if (g_strcmp0 (action_match, "view-rotate-reset") == 0 && install->old_major == 2) new_action_name = g_strdup ("view-reset"); /* select-float became select-cut-float in 3.0 (select-copy-float added). */ else if (g_strcmp0 (action_match, "select-float") == 0) new_action_name = g_strdup ("select-cut-float"); /* edit-paste-as-new-layer* actions removed in 3.0.0 (commit * 2c4f91f585) because the default "edit-paste" pastes as new layer. * * We used to migrate these to "edit-paste" and * "edit-paste-in-place", but it meant that it would override the * default shortcut (ctrl-v) for the basic paste, which may be * counter-productive (cf. #13414). Furthermore, if you had a * shortcut to "edit-paste*" actions (since these existed in 2.10 * too), you'd have a shortcut conflict. * * Instead since GIMP 3.0.4, we migrate them to "edit-paste-merged*" * (introduced in 143496af22) though it's not an exact equivalent * (but neither were "edit-paste*"), in particular: * * - If a layer mask is selected, "edit-paste-merged*" would still * create a floating mask (unlike old "edit-paste-as-new-layer*" * which was always creating a new layer). * - Even when multiple layers are copied, the paste is always a * single layer. But the latter is not a big issue since in GIMP * 2.x, you could not copy multiple layers anyway, so in this * aspect, we may say it works the same. */ else if (g_strcmp0 (action_match, "edit-paste-as-new-layer") == 0) new_action_name = g_strdup ("edit-paste-merged"); else if (g_strcmp0 (action_match, "edit-paste-as-new-layer-in-place") == 0) new_action_name = g_strdup ("edit-paste-merged-in-place"); /* These actions had an -accel variant which got removed in commit * 71c8ff1f21. Since we cannot know if both variants were given a * custom shortcut when processing per-line, we temporarily store * them all and will do a second pass allowing us to store one or * both shortcuts if needed. */ else if (g_str_has_suffix (action_match, "-accel") || g_strcmp0 (action_match, "view-zoom-out") == 0 || g_strcmp0 (action_match, "view-zoom-in") == 0 || g_strcmp0 (action_match, "view-zoom-16-1") == 0 || g_strcmp0 (action_match, "view-zoom-8-1") == 0 || g_strcmp0 (action_match, "view-zoom-4-1") == 0 || g_strcmp0 (action_match, "view-zoom-2-1") == 0 || g_strcmp0 (action_match, "view-zoom-1-1") == 0) accel_variant = TRUE; if (new_action_name == NULL) new_action_name = g_strdup (action_match); if (g_strcmp0 (comment_match, ";") == 0) { g_string_append (new_value, "# "); } else if (accel_variant) { g_hash_table_insert (install->accels, action_match, accel_match); action_match = NULL; accel_match = NULL; } if (action_match) { if (strlen (accel_match) > 0) g_string_append_printf (new_value, "(action \"%s\" \"%s\")", new_action_name, accel_match); else g_string_append_printf (new_value, "(action \"%s\")", new_action_name); } } g_free (comment_match); g_free (action_match); g_free (accel_match); g_free (ignore_match); g_free (new_action_name); return FALSE; } gchar * user_update_post_process_menurc_over20 (gpointer user_data) { GString *string = g_string_new (NULL); GimpUserInstall *install = (GimpUserInstall *) user_data; static gchar * gimp_2_accels[][3] = { { "view-zoom-out", "minus", "KP_Subtract" }, { "view-zoom-in", "plus", "KP_Add" }, { "view-zoom-16-1", "5", "KP_5" }, { "view-zoom-8-1", "4", "KP_4" }, { "view-zoom-4-1", "3", "KP_3" }, { "view-zoom-2-1", "2", "KP_2" }, { "view-zoom-1-1", "1", "KP_1" } }; for (gint i = 0; i < G_N_ELEMENTS (gimp_2_accels); i++) { const gchar *action = gimp_2_accels[i][0]; gchar *action_variant = g_strconcat (action, "-accel", NULL); gchar *accel; gchar *accel_variant; accel = g_hash_table_lookup (install->accels, action); accel_variant = g_hash_table_lookup (install->accels, action_variant); if (accel != NULL && strlen (accel) > 0 && accel_variant != NULL && strlen (accel_variant) > 0) { g_string_append_printf (string, "\n(action \"%s\" \"%s\" \"%s\")", action, accel, accel_variant); } else if (accel != NULL) { if (strlen (accel) > 0) { if (accel_variant == NULL) g_string_append_printf (string, "\n(action \"%s\" \"%s\" \"%s\")", action, accel, gimp_2_accels[i][2]); else g_string_append_printf (string, "\n(action \"%s\" \"%s\")", action, accel); } else if (accel_variant != NULL) { if (strlen (accel_variant) > 0) g_string_append_printf (string, "\n(action \"%s\" \"%s\")", action, accel_variant); else g_string_append_printf (string, "\n(action \"%s\")", action); } else { g_string_append_printf (string, "\n(action \"%s\" \"%s\")", action, gimp_2_accels[i][2]); } } else if (accel_variant != NULL) { if (strlen (accel_variant) > 0) { g_string_append_printf (string, "\n(action \"%s\" \"%s\" \"%s\")", action, accel_variant, gimp_2_accels[i][1]); } else { g_string_append_printf (string, "\n(action \"%s\" \"%s\")", action, gimp_2_accels[i][1]); } } g_free (action_variant); } return g_string_free (string, FALSE); } #define TEMPLATERC_UPDATE_PATTERN \ "\\(precision (.*)-gamma\\)" static gboolean user_update_templaterc (const GMatchInfo *matched_value, GString *new_value, gpointer data) { gchar *original = g_match_info_fetch (matched_value, 0); gchar *match = g_match_info_fetch (matched_value, 1); /* GIMP_PRECISION_*_GAMMA removed in GIMP 3.0.0 (commit 2559138931). */ g_string_append_printf (new_value, "(precision %s-non-linear)", match); g_free (original); g_free (match); return FALSE; } #define CONTROLLERRC_UPDATE_PATTERN \ "\\(map \"(scroll|cursor)-[^\"]*\\bcontrol\\b[^\"]*\"" static gboolean user_update_controllerrc (const GMatchInfo *matched_value, GString *new_value, gpointer data) { gchar *original; gchar *replacement; GRegex *regexp = NULL; /* No need of a complicated pattern here. * CONTROLLERRC_UPDATE_PATTERN took care of it first. */ regexp = g_regex_new ("\\bcontrol\\b", 0, 0, NULL); original = g_match_info_fetch (matched_value, 0); replacement = g_regex_replace (regexp, original, -1, 0, "primary", 0, NULL); g_string_append (new_value, replacement); g_free (original); g_free (replacement); g_regex_unref (regexp); return FALSE; } #define SESSIONRC_UPDATE_PATTERN \ "\\(position [0-9]* [0-9]*\\)" "|" \ "\\(size [0-9]* [0-9]*\\)" "|" \ "\\(left-docks-width \"?[0-9]*\"?\\)" "|" \ "\\(right-docks-width \"?[0-9]*\"?\\)" static gboolean user_update_sessionrc (const GMatchInfo *matched_value, GString *new_value, gpointer data) { GimpUserInstall *install = (GimpUserInstall *) data; gchar *original; original = g_match_info_fetch (matched_value, 0); if (install->scale_factor != 1 && install->scale_factor > 0) { /* GTK < 3.0 didn't have scale factor support. It means that any * size and position back then would be in real pixel size. Now * with GTK3 and over, we need to think of position and size in * virtual/application pixels. * In particular it means that if we were to just copy the numbers * from GTK2 to GTK3 on a display with scale factor of 2, every * width, height and position would be 2 times too big (a full * screen window would end up off-screen). */ GRegex *regexp; GMatchInfo *match_info; gchar *match; /* First copy the pattern title. */ regexp = g_regex_new ("\\([a-z-]* ", 0, 0, NULL); g_regex_match (regexp, original, 0, &match_info); match = g_match_info_fetch (match_info, 0); g_string_append (new_value, match); g_match_info_free (match_info); g_regex_unref (regexp); g_free (match); /* Now copy the numbers. */ regexp = g_regex_new ("[0-9]+|\"", 0, 0, NULL); g_regex_match (regexp, original, 0, &match_info); while (g_match_info_matches (match_info)) { match = g_match_info_fetch (match_info, 0); if (g_strcmp0 (match, "\"") == 0) { g_string_append (new_value, match); } else { gint num; num = g_ascii_strtoll (match, NULL, 10); num /= install->scale_factor; g_string_append_printf (new_value, " %d", num); } g_free (match); g_match_info_next (match_info, NULL); } g_match_info_free (match_info); g_regex_unref (regexp); g_string_append (new_value, ")"); } else { /* Just copy as-is. */ g_string_append (new_value, original); } g_free (original); return FALSE; } #define GIMPRC_UPDATE_PATTERN \ "\\(show-tooltips [^)]*\\)" "|" \ "\\(theme [^)]*\\)" "|" \ "^ *\\(.*-path \".*\"\\) *$" "|" \ "\\(style solid\\)" "|" \ "\\(precision (.*)-gamma\\)" "|" \ "\\(filter-tool-show-color-options [^)]*\\)" static gboolean user_update_gimprc (const GMatchInfo *matched_value, GString *new_value, gpointer data) { gchar *match = g_match_info_fetch (matched_value, 0); if (g_strcmp0 (match, "(style solid)") == 0) { /* See MR !706: GIMP_FILL_STYLE_SOLID was split in * GIMP_FILL_STYLE_FG_COLOR and GIMP_FILL_STYLE_BG_COLOR. */ g_string_append (new_value, "(style fg-color)"); } else if (g_str_has_prefix (match, "(precision ")) { gchar *precision_match = g_match_info_fetch (matched_value, 1); /* GIMP_PRECISION_*_GAMMA removed in GIMP 3.0.0 (commit 2559138931). */ g_string_append_printf (new_value, "(precision %s-non-linear)", precision_match); g_free (precision_match); } else { /* Do not migrate show-tooltips GIMP < 3.0. Cf. #1965. */ /* Do not migrate paths and themes from GIMP < 3.0. */ /* Do not migrate the advanced color options which was the gamma * hack removed for GIMP 3.0. Cf. #12577. */ } g_free (match); return FALSE; } #define GIMPRESSIONIST_UPDATE_PATTERN \ "selectedbrush=Brushes/paintbrush.pgm" static gboolean user_update_gimpressionist (const GMatchInfo *matched_value, GString *new_value, gpointer data) { gchar *match = g_match_info_fetch (matched_value, 0); /* See bug 791934: both brushes are identical. */ if (g_strcmp0 (match, "selectedbrush=Brushes/paintbrush.pgm") == 0) { g_string_append (new_value, "selectedbrush=Brushes/paintbrush01.pgm"); } else { g_message ("(WARNING) %s: invalid match \"%s\"", G_STRFUNC, match); g_string_append (new_value, match); } g_free (match); return FALSE; } #define TOOL_PRESETS_UPDATE_PATTERN \ "GimpImageMapOptions" "|" \ "GimpBlendOptions" "|" \ "gimp-blend-tool" "|" \ "gimp-tool-blend" "|" \ "dynamics \"Dynamics Off\"" "|" \ "\\(dynamics-expanded yes\\)" static gboolean user_update_tool_presets (const GMatchInfo *matched_value, GString *new_value, gpointer data) { gchar *match = g_match_info_fetch (matched_value, 0); if (g_strcmp0 (match, "GimpImageMapOptions") == 0) { g_string_append (new_value, "GimpFilterOptions"); } else if (g_strcmp0 (match, "GimpBlendOptions") == 0) { g_string_append (new_value, "GimpGradientOptions"); } else if (g_strcmp0 (match, "gimp-blend-tool") == 0) { g_string_append (new_value, "gimp-gradient-tool"); } else if (g_strcmp0 (match, "gimp-tool-blend") == 0) { g_string_append (new_value, "gimp-tool-gradient"); } else if (g_strcmp0 (match, "dynamics \"Dynamics Off\"") == 0) { g_string_append (new_value, "dynamics-enabled no"); } else if (g_strcmp0 (match, "(dynamics-expanded yes)") == 0) { /* This option just doesn't exist anymore. */ } else { g_message ("(WARNING) %s: invalid match \"%s\"", G_STRFUNC, match); g_string_append (new_value, match); } g_free (match); return FALSE; } /* Actually not only for contextrc, but all other files where * gimp-blend-tool may appear. Apparently that is also "devicerc", as * well as "toolrc" (but this one is skipped anyway). */ #define CONTEXTRC_UPDATE_PATTERN \ "gimp-blend-tool" "|" \ "dynamics \"Dynamics Off\"" "|" \ "\\(dynamics-expanded yes\\)" "|" \ "\\(color-options-expanded [^)]*\\)" static gboolean user_update_contextrc_over20 (const GMatchInfo *matched_value, GString *new_value, gpointer data) { gchar *match = g_match_info_fetch (matched_value, 0); if (g_strcmp0 (match, "gimp-blend-tool") == 0) { g_string_append (new_value, "gimp-gradient-tool"); } else if (g_strcmp0 (match, "dynamics \"Dynamics Off\"") == 0) { g_string_append (new_value, "dynamics-enabled no"); } else if (g_strcmp0 (match, "(dynamics-expanded yes)") == 0) { /* This option just doesn't exist anymore. */ } else if (g_str_has_prefix (match, "(color-options-expanded ")) { /* This option was removed with the gamma-hack. Cf. #12577. */ } else { g_message ("(WARNING) %s: invalid match \"%s\"", G_STRFUNC, match); g_string_append (new_value, match); } g_free (match); return FALSE; } static gboolean user_install_dir_copy (GimpUserInstall *install, gint level, const gchar *source, const gchar *base, const gchar *update_pattern, GRegexEvalCallback update_callback, GimpCopyPostProcess post_process_callback) { GDir *source_dir = NULL; GDir *dest_dir = NULL; gchar dest[1024]; const gchar *basename; gchar *dirname = NULL; gchar *name; GError *error = NULL; gboolean success = FALSE; if (level >= 5) { /* Config migration is recursive, but we can't go on forever, * since we may fall into recursive symlinks in particular (which * is a security risk to fill a disk, and would also block GIMP * forever at migration stage). * Let's just break the recursivity at 5 levels, which is just an * arbitrary value (but I don't think there should be any data * deeper than this). */ goto error; } name = g_path_get_basename (source); dirname = g_build_filename (base, name, NULL); g_free (name); success = user_install_mkdir (install, dirname); if (! success) goto error; success = (dest_dir = g_dir_open (dirname, 0, &error)) != NULL; if (! success) goto error; success = (source_dir = g_dir_open (source, 0, &error)) != NULL; if (! success) goto error; while ((basename = g_dir_read_name (source_dir)) != NULL) { name = g_build_filename (source, basename, NULL); if (g_file_test (name, G_FILE_TEST_IS_REGULAR)) { g_snprintf (dest, sizeof (dest), "%s%c%s", dirname, G_DIR_SEPARATOR, basename); success = user_install_file_copy (install, name, dest, update_pattern, update_callback, post_process_callback); if (! success) { g_free (name); goto error; } } else { user_install_dir_copy (install, level + 1, name, dirname, update_pattern, update_callback, post_process_callback); } g_free (name); } error: user_install_log_error (install, &error); if (source_dir) g_dir_close (source_dir); if (dest_dir) g_dir_close (dest_dir); if (dirname) g_free (dirname); return success; } static gboolean user_install_create_files (GimpUserInstall *install) { gchar dest[1024]; gchar source[1024]; gint i; for (i = 0; i < G_N_ELEMENTS (gimp_user_install_items); i++) { g_snprintf (dest, sizeof (dest), "%s%c%s", gimp_directory (), G_DIR_SEPARATOR, gimp_user_install_items[i].name); if (g_file_test (dest, G_FILE_TEST_EXISTS)) continue; switch (gimp_user_install_items[i].action) { case USER_INSTALL_MKDIR: if (! user_install_mkdir (install, dest)) return FALSE; break; case USER_INSTALL_COPY: g_snprintf (source, sizeof (source), "%s%c%s", gimp_sysconf_directory (), G_DIR_SEPARATOR, gimp_user_install_items[i].name); if (! user_install_file_copy (install, source, dest, NULL, NULL, NULL)) return FALSE; break; } } g_snprintf (dest, sizeof (dest), "%s%c%s", gimp_directory (), G_DIR_SEPARATOR, "tags.xml"); if (! g_file_test (dest, G_FILE_TEST_IS_REGULAR)) { /* if there was no tags.xml, install it with default tag set. */ if (! gimp_tags_user_install ()) { return FALSE; } } return TRUE; } static gboolean user_install_migrate_files (GimpUserInstall *install) { GDir *dir; const gchar *basename; gchar dest[1024]; GimpRc *gimprc; GError *error = NULL; dir = g_dir_open (install->old_dir, 0, &error); if (! dir) { user_install_log_error (install, &error); return FALSE; } while ((basename = g_dir_read_name (dir)) != NULL) { gchar *source = g_build_filename (install->old_dir, basename, NULL); const gchar *new_dest = NULL; if (g_file_test (source, G_FILE_TEST_IS_REGULAR)) { const gchar *update_pattern = NULL; GRegexEvalCallback update_callback = NULL; GimpCopyPostProcess post_process_callback = NULL; /* skip these files for all old versions */ if (strcmp (basename, "documents") == 0 || g_str_has_prefix (basename, "gimpswap.") || strcmp (basename, "pluginrc") == 0 || strcmp (basename, "themerc") == 0 || strcmp (basename, "toolrc") == 0 || strcmp (basename, "gtkrc") == 0) { goto next_file; } else if (install->old_major < 3 && strcmp (basename, "sessionrc") == 0) { /* We need to update size and positions because of scale * factor support. */ update_pattern = SESSIONRC_UPDATE_PATTERN; update_callback = user_update_sessionrc; } else if (strcmp (basename, "menurc") == 0) { switch (install->old_minor) { case 0: /* skip menurc for gimp 2.0 as the format has changed */ goto next_file; break; default: update_pattern = MENURC_OVER20_UPDATE_PATTERN; update_callback = user_update_menurc_over20; post_process_callback = user_update_post_process_menurc_over20; /* menurc becomes shortcutsrc in 3.0. */ new_dest = "shortcutsrc"; break; } } else if (strcmp (basename, "templaterc") == 0) { update_pattern = TEMPLATERC_UPDATE_PATTERN; update_callback = user_update_templaterc; } else if (strcmp (basename, "controllerrc") == 0) { update_pattern = CONTROLLERRC_UPDATE_PATTERN; update_callback = user_update_controllerrc; } else if (strcmp (basename, "gimprc") == 0) { update_pattern = GIMPRC_UPDATE_PATTERN; update_callback = user_update_gimprc; } else if (strcmp (basename, "contextrc") == 0 || strcmp (basename, "devicerc") == 0) { update_pattern = CONTEXTRC_UPDATE_PATTERN; update_callback = user_update_contextrc_over20; } g_snprintf (dest, sizeof (dest), "%s%c%s", gimp_directory (), G_DIR_SEPARATOR, new_dest ? new_dest : basename); user_install_file_copy (install, source, dest, update_pattern, update_callback, post_process_callback); } else if (g_file_test (source, G_FILE_TEST_IS_DIR)) { const gchar *update_pattern = NULL; GRegexEvalCallback update_callback = NULL; /* skip these directories for all old versions */ if (strcmp (basename, "tmp") == 0 || strcmp (basename, "tool-options") == 0 || strcmp (basename, "themes") == 0) { goto next_file; } else if (install->old_major < 3 && (strcmp (basename, "plug-ins") == 0 || strcmp (basename, "scripts") == 0)) { /* Major API update. */ goto next_file; } if (strcmp (basename, "gimpressionist") == 0) { update_pattern = GIMPRESSIONIST_UPDATE_PATTERN; update_callback = user_update_gimpressionist; } else if (strcmp (basename, "tool-presets") == 0) { update_pattern = TOOL_PRESETS_UPDATE_PATTERN; update_callback = user_update_tool_presets; } user_install_dir_copy (install, 0, source, gimp_directory (), update_pattern, update_callback, NULL); } next_file: g_free (source); } /* create the tmp directory that was explicitly not copied */ g_snprintf (dest, sizeof (dest), "%s%c%s", gimp_directory (), G_DIR_SEPARATOR, "tmp"); user_install_mkdir (install, dest); g_dir_close (dir); gimp_templates_migrate (install->old_dir); gimprc = gimp_rc_new (install->gimp, NULL, NULL, FALSE); gimp_rc_migrate (gimprc); gimp_rc_save (gimprc); g_object_unref (gimprc); return TRUE; }