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