1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Inkscape - an ambitious vector drawing program
4  *
5  * Authors:
6  * Tavmjong Bah
7  *
8  * (C) 2018 Tavmjong Bah
9  *
10  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11  */
12 
13 #ifdef _WIN32
14 #include <windows.h> // SetDllDirectoryW, SetConsoleOutputCP
15 #include <fcntl.h> // _O_BINARY
16 #include <boost/algorithm/string/join.hpp>
17 #endif
18 
19 #include "inkscape-application.h"
20 #include "path-prefix.h"
21 
22 #include "io/resource.h"
23 
set_extensions_env()24 static void set_extensions_env()
25 {
26     // add inkscape to PATH, so the correct version is always available to extensions by simply calling "inkscape"
27     char const *program_dir = get_program_dir();
28     if (program_dir) {
29         gchar const *path = g_getenv("PATH");
30         gchar *new_path = g_strdup_printf("%s" G_SEARCHPATH_SEPARATOR_S "%s", program_dir, path);
31         g_setenv("PATH", new_path, true);
32         g_free(new_path);
33     }
34 
35     // add various locations to PYTHONPATH so extensions find their modules
36     auto extensiondir_user = get_path_string(Inkscape::IO::Resource::USER, Inkscape::IO::Resource::EXTENSIONS);
37     auto extensiondir_system = get_path_string(Inkscape::IO::Resource::SYSTEM, Inkscape::IO::Resource::EXTENSIONS);
38 
39     auto pythonpath = extensiondir_user + G_SEARCHPATH_SEPARATOR + extensiondir_system;
40 
41     auto pythonpath_old = Glib::getenv("PYTHONPATH");
42     if (!pythonpath_old.empty()) {
43         pythonpath += G_SEARCHPATH_SEPARATOR + pythonpath_old;
44     }
45 
46     pythonpath += G_SEARCHPATH_SEPARATOR + Glib::build_filename(extensiondir_system, "inkex", "deprecated-simple");
47 
48     Glib::setenv("PYTHONPATH", pythonpath);
49 
50     // Python 2.x attempts to encode output as ASCII by default when sent to a pipe.
51     Glib::setenv("PYTHONIOENCODING", "UTF-8");
52 
53 #ifdef _WIN32
54     // add inkscape directory to DLL search path so dynamically linked extension modules find their libraries
55     // should be fixed in Python 3.8 (https://github.com/python/cpython/commit/2438cdf0e932a341c7613bf4323d06b91ae9f1f1)
56     char const *installation_dir = get_program_dir();
57     wchar_t *installation_dir_w = (wchar_t *)g_utf8_to_utf16(installation_dir, -1, NULL, NULL, NULL);
58     SetDllDirectoryW(installation_dir_w);
59     g_free(installation_dir_w);
60 #endif
61 }
62 
63 /**
64  * Adds the local inkscape directory to the XDG_DATA_DIRS so themes and other Gtk
65  * resources which are specific to inkscape installations can be used.
66  */
set_themes_env()67 static void set_themes_env()
68 {
69     std::string xdg_data_dirs = Glib::getenv("XDG_DATA_DIRS");
70 
71     if (xdg_data_dirs.empty()) {
72         // initialize with reasonable defaults (should match what glib would do if the variable were unset!)
73 #ifdef _WIN32
74         // g_get_system_data_dirs is actually not cached on Windows,
75         // so we can just call it directly and modify XDG_DATA_DIRS later
76         auto data_dirs = Glib::get_system_data_dirs();
77         xdg_data_dirs = boost::join(data_dirs, G_SEARCHPATH_SEPARATOR_S);
78 #else
79         // initialize with glib default (don't call g_get_system_data_dirs; it's cached!)
80         xdg_data_dirs = "/usr/local/share/:/usr/share/";
81 #endif
82     }
83 
84     std::string inkscape_datadir = Glib::build_filename(get_inkscape_datadir(), "inkscape");
85     Glib::setenv("XDG_DATA_DIRS", xdg_data_dirs + G_SEARCHPATH_SEPARATOR_S + inkscape_datadir);
86 }
87 
88 #ifdef __APPLE__
set_macos_app_bundle_env(gchar const * program_dir)89 static void set_macos_app_bundle_env(gchar const *program_dir)
90 {
91     // use bundle identifier
92     // https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/MacOSXDirectories/MacOSXDirectories.html
93     auto app_support_dir = Glib::getenv("HOME") + "/Library/Application Support/org.inkscape.Inkscape";
94 
95     auto bundle_resources_dir       = Glib::path_get_dirname(get_inkscape_datadir());
96     auto bundle_resources_etc_dir   = bundle_resources_dir + "/etc";
97     auto bundle_resources_bin_dir   = bundle_resources_dir + "/bin";
98     auto bundle_resources_lib_dir   = bundle_resources_dir + "/lib";
99     auto bundle_resources_share_dir = bundle_resources_dir + "/share";
100 
101     // failsafe: Check if the expected content is really there, using GIO modules
102     // as an indicator.
103     // This is also helpful to developers as it enables the possibility to
104     //      1. cmake -DCMAKE_INSTALL_PREFIX=Inkscape.app/Contents/Resources
105     //      2. move binary to Inkscape.app/Contents/MacOS and set rpath
106     //      3. copy Info.plist
107     // to ease up on testing and get correct application behavior (like dock icon).
108     if (!Glib::file_test(bundle_resources_lib_dir + "/gio/modules", Glib::FILE_TEST_EXISTS)) {
109         // doesn't look like a standalone bundle
110         return;
111     }
112 
113     // XDG
114     // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
115     Glib::setenv("XDG_DATA_HOME",   app_support_dir + "/share");
116     Glib::setenv("XDG_DATA_DIRS",   bundle_resources_share_dir);
117     Glib::setenv("XDG_CONFIG_HOME", app_support_dir + "/config");
118     Glib::setenv("XDG_CONFIG_DIRS", bundle_resources_etc_dir + "/xdg");
119     Glib::setenv("XDG_CACHE_HOME",  app_support_dir + "/cache");
120 
121     // GTK
122     // https://developer.gnome.org/gtk3/stable/gtk-running.html
123     Glib::setenv("GTK_EXE_PREFIX",  bundle_resources_dir);
124     Glib::setenv("GTK_DATA_PREFIX", bundle_resources_dir);
125 
126     // GDK
127     Glib::setenv("GDK_PIXBUF_MODULE_FILE", bundle_resources_lib_dir + "/gdk-pixbuf-2.0/2.10.0/loaders.cache");
128 
129     // fontconfig
130     Glib::setenv("FONTCONFIG_PATH", bundle_resources_etc_dir + "/fonts");
131 
132     // GIO
133     Glib::setenv("GIO_MODULE_DIR", bundle_resources_lib_dir + "/gio/modules");
134 
135     // GNOME introspection
136     Glib::setenv("GI_TYPELIB_PATH", bundle_resources_lib_dir + "/girepository-1.0");
137 
138     // PATH
139     Glib::setenv("PATH", bundle_resources_bin_dir + ":" + Glib::getenv("PATH"));
140 
141     // DYLD_LIBRARY_PATH
142     // This is required to make Python GTK bindings work as they use dlopen()
143     // to load libraries.
144     Glib::setenv("DYLD_LIBRARY_PATH", bundle_resources_lib_dir + ":"
145             + bundle_resources_lib_dir + "/gdk-pixbuf-2.0/2.10.0/loaders");
146 }
147 #endif
148 
149 /**
150  * Convert some legacy 0.92.x command line options to 1.0.x options.
151  * @param[in,out] argc The main() argc argument, will be modified
152  * @param[in,out] argv The main() argv argument, will be modified
153  */
convert_legacy_options(int & argc,char ** & argv)154 static void convert_legacy_options(int &argc, char **&argv)
155 {
156     static std::vector<char *> argv_new;
157     char *file = nullptr;
158 
159     for (int i = 0; i < argc; ++i) {
160         if (g_str_equal(argv[i], "--without-gui") || g_str_equal(argv[i], "-z")) {
161             std::cerr << "Warning: Option --without-gui= is deprecated" << std::endl;
162             continue;
163         }
164 
165         if (g_str_has_prefix(argv[i], "--file=")) {
166             std::cerr << "Warning: Option --file= is deprecated" << std::endl;
167             file = argv[i] + 7;
168             continue;
169         }
170 
171         bool found_legacy_export = false;
172 
173         for (char const *type : { "png", "pdf", "ps", "eps", "emf", "wmf", "plain-svg" }) {
174             auto s = std::string("--export-").append(type).append("=");
175             if (g_str_has_prefix(argv[i], s.c_str())) {
176                 std::cerr << "Warning: Option " << s << " is deprecated" << std::endl;
177 
178                 if (g_str_equal(type, "plain-svg")) {
179                     argv_new.push_back(g_strdup("--export-plain-svg"));
180                     type = "svg";
181                 }
182 
183                 argv_new.push_back(g_strdup_printf("--export-type=%s", type));
184                 argv_new.push_back(g_strdup_printf("--export-filename=%s", argv[i] + s.size()));
185 
186                 found_legacy_export = true;
187                 break;
188             }
189         }
190 
191         if (found_legacy_export) {
192             continue;
193         }
194 
195         argv_new.push_back(argv[i]);
196     }
197 
198     if (file) {
199         argv_new.push_back(file);
200     }
201 
202     argc = argv_new.size();
203     argv = argv_new.data();
204 }
205 
main(int argc,char * argv[])206 int main(int argc, char *argv[])
207 {
208     convert_legacy_options(argc, argv);
209 
210 #ifdef __APPLE__
211     {   // Check if we're inside an application bundle and adjust environment
212         // accordingly.
213 
214         char const *program_dir = get_program_dir();
215         if (g_str_has_suffix(program_dir, "Contents/MacOS")) {
216 
217             // Step 1
218             // Remove macOS session identifier from command line arguments.
219             // Code adopted from GIMP's app/main.c
220 
221             int new_argc = 0;
222             for (int i = 0; i < argc; i++) {
223                 // Rewrite argv[] without "-psn_..." argument.
224                 if (!g_str_has_prefix(argv[i], "-psn_")) {
225                     argv[new_argc] = argv[i];
226                     new_argc++;
227                 }
228             }
229             if (argc > new_argc) {
230                 argv[new_argc] = nullptr; // glib expects null-terminated array
231                 argc = new_argc;
232             }
233 
234             // Step 2
235             // In the past, a launch script/wrapper was used to setup necessary environment
236             // variables to facilitate relocatability for the application bundle. Starting
237             // with Catalina, this approach is no longer feasible due to new security checks
238             // that get misdirected by using a launcher. The launcher needs to go and the
239             // binary needs to setup the environment itself.
240 
241             set_macos_app_bundle_env(program_dir);
242         }
243     }
244 #elif defined _WIN32
245     // temporarily switch console encoding to UTF8 while Inkscape runs
246     // as everything else is a mess and it seems to work just fine
247     const unsigned int initial_cp = GetConsoleOutputCP();
248     SetConsoleOutputCP(CP_UTF8);
249     fflush(stdout); // empty buffer, just to be safe (see warning in documentation for _setmode)
250     _setmode(_fileno(stdout), _O_BINARY); // binary mode seems required for this to work properly
251 #endif
252 
253     set_themes_env();
254     set_extensions_env();
255 
256     auto ret = InkscapeApplication::singleton().gio_app()->run(argc, argv);
257 
258 #ifdef _WIN32
259     // switch back to initial console encoding
260     SetConsoleOutputCP(initial_cp);
261 #endif
262 
263     return ret;
264 }
265 
266 /*
267   Local Variables:
268   mode:c++
269   c-file-style:"stroustrup"
270   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
271   indent-tabs-mode:nil
272   fill-column:99
273   End:
274 */
275 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
276