fix drawing of zero-length notes
[ardour.git] / gtk2_ardour / bundle_env_msvc.cc
1 /*
2  * Copyright (C) 2014-2017 John Emmas <john@creativepost.co.uk>
3  * Copyright (C) 2015-2016 Paul Davis <paul@linuxaudiosystems.com>
4  * Copyright (C) 2015-2016 Robin Gareus <robin@gareus.org>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20
21 #include "bundle_env.h"
22 #include "pbd/i18n.h"
23
24 #include <shlobj.h>
25 #include <stdlib.h>
26
27 #include <iostream>
28 #include <string>
29 #include <vector>
30 #include <fstream>
31
32 #include <glibmm.h>
33 #include "pbd/gstdio_compat.h"
34
35 #include <fontconfig/fontconfig.h>
36
37 #include "ardour/ardour.h"
38 #include "ardour/search_paths.h"
39 #include "ardour/filesystem_paths.h"
40
41 #include "pbd/file_utils.h"
42 #include "pbd/epa.h"
43
44 using namespace std;
45 using namespace PBD;
46 using namespace ARDOUR;
47
48 std::string
49 get_windows_drive_volume_letter()
50 {
51 static std::string ret;
52 char path[PATH_MAX+1];
53 LPITEMIDLIST pidl = 0;
54
55         if (!ret.length()) {
56                 if (S_OK == SHGetSpecialFolderLocation (0, CSIDL_WINDOWS, &pidl))
57                 {
58                         if (SHGetPathFromIDListA (pidl, path)) {
59                                 path[2] = '\0'; // Gives us just the drive letter and colon
60                                 ret = path;
61                         }
62
63                         CoTaskMemFree (pidl);
64                 }
65                 // The above should never fail - but just in case...
66                 else if (char *env_path = getenv ("windir"))
67                 {
68                         strcpy (path, env_path);
69                         path[2] = '\0'; // Gives us just the drive letter and colon
70                         ret = path;
71                 }
72         }
73
74         return ret;
75 }
76
77 const string
78 get_module_folder ()
79 {
80 std::string ret;
81
82         // Gives the top-level Ardour installation folder (on Windows)
83         // Typically, this will be somehwere like "C:\Program Files"
84
85         gchar* pExeRoot = g_win32_get_package_installation_directory_of_module (0);
86
87         if (0 == pExeRoot) {
88                 pExeRoot = g_build_filename("C:\\", "Program Files", PROGRAM_NAME, 0);
89         }
90
91         if (pExeRoot) {
92                 gchar  tmp[PATH_MAX+1];
93                 gchar* p;
94
95                 strcpy(tmp, pExeRoot);
96                 if (0 != (p = strrchr (tmp, G_DIR_SEPARATOR))) {
97                         *p = '\0';
98
99                         if (0 != (p = g_build_filename(tmp, 0))) {
100                                 ret = p;
101                                 g_free (p);
102                         }
103                 }
104
105                 g_free (pExeRoot);
106         }
107
108         return (ret);
109 }
110
111 bool
112 fixup_config_file (Glib::ustring str_file_to_fix)
113 {
114 FILE* fd;
115 char  buf[4096];
116 bool  conversion_needed = false;
117 bool  succeeded = false;
118
119         fstream file_to_fix (fd = g_fopen(str_file_to_fix.c_str(), "r+b"));
120
121         if (file_to_fix.is_open()) {
122                 vector<std::string> lines;
123                 std::string line;
124
125                 file_to_fix.seekg (0, std::ios::beg);
126                 file_to_fix.seekp (0, std::ios::beg);
127
128                 try {
129                         while (!file_to_fix.eof() && file_to_fix.getline (buf, sizeof(buf))) {
130                                 line = buf;
131
132                                 if (!conversion_needed && (std::string::npos != line.find("$(")))
133                                         conversion_needed = true;
134                                 lines.push_back(line);
135                         }
136
137                         if (conversion_needed) {
138                                 bool error = false;
139                                 std::string::size_type token_begin, token_end;
140                                 vector<string>::iterator i;
141
142                                 for (i = lines.begin(); i != lines.end(); ++i) {
143                                         if (string::npos != (token_begin = i->find("$("))) {
144                                                 if (string::npos != (token_end = i->find(")", token_begin))) {
145                                                         std::string str_replace_with;
146                                                         std::string str_to_replace = i->substr(token_begin, ((token_end+1)-token_begin));
147
148                                                         if (0 == str_to_replace.compare("$(CWD)")) {
149                                                                 // Replace our token with the current working directory
150                                                                 if (getcwd(buf, sizeof(buf))) {
151                                                                         if (buf[strlen(buf)-1] == G_DIR_SEPARATOR)
152                                                                                 buf[strlen(buf)-1] = '\0';
153                                                                         str_replace_with = buf;
154
155                                                                         // Replace the first occurrence of our token with the required string
156                                                                         i->erase(token_begin, ((token_end+1)-token_begin));
157                                                                         i->insert(token_begin, str_replace_with);
158                                                                 } else {
159                                                                         error = true;
160                                                                 }
161                                                         } else if (0 == str_to_replace.compare("$(WINDRIVE)")){
162                                                                 // Replace our token with the drive letter (and colon) for the user's Windows volume
163                                                                 str_replace_with = get_windows_drive_volume_letter();
164
165                                                                 // Replace the first occurrence of our token with the required string
166                                                                 i->erase(token_begin, ((token_end+1)-token_begin));
167                                                                 i->insert(token_begin, str_replace_with);
168                                                         } else if (0 == str_to_replace.compare("$(LOCALCACHEDIR)")){
169                                                                 // Replace our token with the path to our Ardour cache directory
170                                                                 str_replace_with = user_cache_directory();
171
172                                                                 // Replace the first occurrence of our token with the required string
173                                                                 i->erase(token_begin, ((token_end+1)-token_begin));
174                                                                 i->insert(token_begin, str_replace_with);
175                                                         } else {
176                                                                 // Assume that our token represents an environment variable
177                                                                 std::string envvar_name = str_to_replace.substr(2, str_to_replace.length()-3);
178
179                                                                 if (const char *envvar_value = getenv(envvar_name.c_str())) {
180                                                                         strcpy(buf, envvar_value);
181                                                                         if (buf[strlen(buf)-1] == G_DIR_SEPARATOR)
182                                                                                 buf[strlen(buf)-1] = '\0';
183                                                                         str_replace_with = buf;
184
185                                                                         // Replace the first occurrence of our token with the required string
186                                                                         i->erase(token_begin, ((token_end+1)-token_begin));
187                                                                         i->insert(token_begin, str_replace_with);
188                                                                 } else {
189                                                                         error = true;
190                                                                         cerr << _("ERROR: unknown environment variable") << endl;
191                                                                 }
192                                                         }
193                                                 }
194                                         }
195                                 }
196
197                                 if (!error) {
198                                         file_to_fix.clear ();                  // Clear the EOF flag etc
199                                         file_to_fix.seekg (0, std::ios::beg);  // Seek our 'get' ptr to the file start pos
200                                                                                                                    // (our 'put' ptr shouldn't have moved yet).
201                                         chsize(fileno (fd), 0);                // Truncate the file, ready for re-writing
202
203                                         for (i = lines.begin(); i != lines.end(); ++i) {
204
205                                                 // Write the converted contents to our file
206                                                 file_to_fix << (*i).c_str() << endl;
207                                         }
208
209                                         try {
210                                                 file_to_fix.close();
211                                                 succeeded = true;
212                                         } catch (...) {}
213                                 }
214                         } else {
215                                 file_to_fix.close();
216                                 succeeded = true;
217                         }
218                 } catch (...) {
219                         file_to_fix.close();
220                         succeeded = false;
221                 }
222         } else {
223                 cerr << _("ERROR: Could not open config file '") << str_file_to_fix << "'" << endl;
224         }
225
226         return succeeded;
227 }
228
229 void
230 fixup_fonts_config ()
231 {
232 string fonts_conf_file;
233
234 #ifdef DEBUG
235         fonts_conf_file = get_module_folder();
236
237         if (!fonts_conf_file.empty()) {
238                 fonts_conf_file += "\\";
239                 fonts_conf_file += PROGRAM_NAME;
240                 fonts_conf_file += PROGRAM_VERSION;
241                 fonts_conf_file += FONTS_CONF_LOCATION;
242 #else
243         if (PBD::find_file (ARDOUR::ardour_config_search_path(), "fonts.conf", fonts_conf_file)) {
244 #endif
245                 Glib::setenv ("FONTCONFIG_FILE", fonts_conf_file, true);
246
247                 if (0 == fixup_config_file (fonts_conf_file))
248                         cerr << _("ERROR: processing error for 'fonts.conf' file") << endl;
249         } else {
250                 cerr << _("ERROR: Malformed module folder (fonts.conf)") << endl;
251         }
252 }
253
254 void
255 fixup_pango_config ()
256 {
257 string pango_modules_file;
258
259 #if defined(DEBUG) || defined(RDC_BUILD)
260         // Make sure we pick up the debuggable DLLs !!!
261         pango_modules_file = get_module_folder();
262
263         if (!pango_modules_file.empty()) {
264                 pango_modules_file += "\\";
265                 pango_modules_file += PROGRAM_NAME;
266                 pango_modules_file += PROGRAM_VERSION;
267                 pango_modules_file += PANGO_CONF_LOCATION;
268 #if 0
269 // JE - handy for non-English locale testing (Greek, in this case)
270                 Glib::ustring pango_modules_path = Glib::locale_to_utf8("C:\\Program Files\\Mixbus3\\etc\\��������\\pango.modules");
271 /**/
272 #else
273                 Glib::ustring pango_modules_path = pango_modules_file;
274 #endif
275                 pango_modules_path.resize (pango_modules_path.size()-14); // Remove "/pango.modules" from the end
276 #else
277         if (PBD::find_file (ARDOUR::ardour_config_search_path(), "pango.modules", pango_modules_file)) {
278
279                 Glib::ustring pango_modules_path = pango_modules_file;
280                 pango_modules_path.resize (pango_modules_path.size()-14); // Remove "/pango.modules" from the end
281 #endif
282                 // Set an environment variable so we can find our pango modules. Note
283                 // that this requires a modified version of libpango (pango-utils.c)
284                 Glib::setenv ("PANGO_MODULE_PATH", Glib::filename_from_utf8(pango_modules_path), true);
285
286                 if (0 == fixup_config_file (pango_modules_file))
287                         cerr << _("ERROR: processing error for 'pango.modules' file") << endl;
288         } else {
289                 cerr << _("ERROR: Malformed module folder (pango.modules)") << endl;
290         }
291 }
292
293 void
294 fixup_pixbuf_loaders_config ()
295 {
296 string gdk_pixbuf_loaders_file;
297
298 #if defined(DEBUG) || defined(RDC_BUILD)
299         // Make sure we pick up the debuggable DLLs !!!
300         gdk_pixbuf_loaders_file = get_module_folder();
301
302         if (!gdk_pixbuf_loaders_file.empty()) {
303                 gdk_pixbuf_loaders_file += "\\";
304                 gdk_pixbuf_loaders_file += PROGRAM_NAME;
305                 gdk_pixbuf_loaders_file += PROGRAM_VERSION;
306                 gdk_pixbuf_loaders_file += PIXBUFLOADERS_CONF_LOCATION;
307 #else
308         if (PBD::find_file (ARDOUR::ardour_config_search_path(), "gdk-pixbuf.loaders", gdk_pixbuf_loaders_file)) {
309 #endif
310                 // Set an environment variable so we can find our pixbuf modules.
311                 Glib::setenv ("GDK_PIXBUF_MODULE_FILE", Glib::filename_from_utf8(gdk_pixbuf_loaders_file), true);
312
313                 if (0 == fixup_config_file (gdk_pixbuf_loaders_file))
314                         cerr << _("ERROR: processing error for 'gdk-pixbuf.loaders' file") << endl;
315         } else {
316                 cerr << _("ERROR: Malformed module folder (gdk-pixbuf.loaders)") << endl;
317         }
318 }
319
320 void
321 fixup_clearlooks_config ()
322 {
323 string clearlooks_la_file;
324
325 #if defined(DEBUG) || defined(RDC_BUILD)
326         // Make sure we pick up the debuggable DLLs !!!
327         clearlooks_la_file = get_module_folder();
328
329         if (!clearlooks_la_file.empty()) {
330                 clearlooks_la_file += "\\";
331                 clearlooks_la_file += PROGRAM_NAME;
332                 clearlooks_la_file += PROGRAM_VERSION;
333                 clearlooks_la_file += CLEARLOOKS_CONF_LOCATION;
334 #else
335         if (PBD::find_file (ARDOUR::ardour_config_search_path(), "libclearlooks.la", clearlooks_la_file)) {
336 #endif
337                 // Set an environment variable so we can find our clearlooks engine.
338                 // Note that this requires a modified version of libgtk (gtkthemes.c)
339                 Glib::setenv ("GTK_THEME_ENGINE_FILE", Glib::filename_from_utf8(clearlooks_la_file).c_str(), true);
340
341                 if (0 == fixup_config_file (clearlooks_la_file))
342                         cerr << _("ERROR: processing error for 'clearlooks.la' file") << endl;
343         } else {
344                 cerr << _("ERROR: Malformed module folder (clearlooks.la)") << endl;
345         }
346 }
347
348 void
349 fixup_bundle_environment (int argc, char* argv[], string & localedir)
350 {
351         std::string exec_path = argv[0];
352         std::string dir_path  = Glib::path_get_dirname (exec_path);
353
354         // Make sure that our runtime CWD is set to Mixbus's install
355         // folder, regardless of where the caller's CWD was set to.
356         g_chdir (dir_path.c_str());
357
358         EnvironmentalProtectionAgency::set_global_epa (new EnvironmentalProtectionAgency (true));
359
360         // Now set 'dir_path' so we can append some relative paths
361         dir_path = Glib::path_get_dirname (dir_path);
362
363         std::string path;
364         const  char *cstr;
365
366         // First, set up 'ARDOUR_DLL_PATH'
367         path  = dir_path;
368         path += "\\lib\\ardour3\\surfaces;";
369         path += dir_path;
370         path += "\\lib\\ardour3\\panners;";
371         path += dir_path;
372         path += "\\lib\\ardour3\\backends;";
373         path += dir_path;
374         path += "\\bin";
375         Glib::setenv ("ARDOUR_DLL_PATH", path, true);
376
377
378         // Next, set up 'ARDOUR_DATA_PATH'
379         path  = get_module_folder() + "\\";
380         path += PROGRAM_NAME;
381         path += PROGRAM_VERSION;
382         path += "\\share";
383         Glib::setenv ("ARDOUR_DATA_PATH", path, true);
384
385
386         // Next, set up 'ARDOUR_CONFIG_PATH'
387 #ifdef _WIN64
388         path = user_config_directory() + "\\win64;";
389 #else
390         path = user_config_directory() + "\\win32;";
391 #endif
392         Glib::setenv ("ARDOUR_CONFIG_PATH", path, true);
393
394
395         // Next, set up 'ARDOUR_INSTANT_XML_PATH'
396         path = user_config_directory();
397         Glib::setenv ("ARDOUR_INSTANT_XML_PATH", path, true);
398
399
400         // Next, set up 'LADSPA_PATH'
401         path = ladspa_search_path().to_string();
402         Glib::setenv ("LADSPA_PATH", path, true);
403
404
405         // Next, set up 'SUIL_MODULE_DIR'
406         Glib::setenv ("SUIL_MODULE_DIR", Glib::build_filename(ardour_dll_directory(), "suil"), true);
407
408
409         // Next, set up 'VAMP_PATH'
410         cstr = getenv ("VAMP_PATH");
411         if (cstr) {
412                 path = cstr;
413                 path += G_SEARCHPATH_SEPARATOR;
414         } else {
415                 path = "";
416         }
417         path += get_module_folder() + "\\";
418         path += PROGRAM_NAME;
419         path += PROGRAM_VERSION;
420         path += "\\bin\\vamp";
421         path += G_SEARCHPATH_SEPARATOR;
422         path += "%ProgramFiles%\\Vamp Plugins";
423         Glib::setenv ("VAMP_PATH", path, true);
424
425
426         // Next, set up 'ARDOUR_CONTROL_SURFACE_PATH'
427         cstr = getenv ("ARDOUR_CONTROL_SURFACE_PATH");
428         if (cstr) {
429                 path = cstr;
430                 path += G_SEARCHPATH_SEPARATOR;
431         } else {
432                 path = "";
433         }
434         path += control_protocol_search_path().to_string();
435         Glib::setenv ("ARDOUR_CONTROL_SURFACE_PATH", path, true);
436
437
438         // Next, set up 'GTK_LOCALEDIR'
439         if (ARDOUR::translations_are_enabled ()) {
440                 path = windows_search_path().to_string();
441                 path += "\\locale";
442                 Glib::setenv ("GTK_LOCALEDIR", path, true);
443
444                 // and return the same path to our caller
445                 localedir = path;
446         }
447
448
449         // Next, set up 'GTK_PATH'
450         cstr = getenv ("GTK_PATH");
451         if (cstr) {
452                 path = cstr;
453                 path += G_SEARCHPATH_SEPARATOR;
454         } else {
455                 path = "";
456         }
457         path += user_config_directory();
458         path += "\\.gtk-2.0";
459         Glib::setenv ("GTK_PATH", path, true);
460
461
462         // Unset GTK2_RC_FILES so that we only load the RC files that we define
463         Glib::unsetenv ("GTK2_RC_FILES");
464
465
466         // and set a '$HOME' environment variable. This variable changes the value returned
467         // by 'g_get_home_dir()' so to prevent that function from unexpectedly changing its
468         // mind, we'll set '$HOME' to whatever 'g_get_home_dir()' is already returning!!
469         if (NULL == getenv("HOME")) {
470                 Glib::setenv ("HOME", Glib::locale_from_utf8(g_get_home_dir()), true);
471         }
472
473         fixup_fonts_config();
474         fixup_clearlooks_config();
475
476 #ifdef DLL_PIXBUF_LOADERS
477         fixup_pixbuf_loaders_config();
478 #endif
479 #ifdef DLL_PANGO_MODULES
480         fixup_pango_config();
481 #endif
482 }
483
484
485 void load_custom_fonts()
486 {
487         FcConfig* config = FcInitLoadConfigAndFonts();
488
489         std::string font_file;
490
491         if (!find_file (ardour_data_search_path(), "ArdourMono.ttf", font_file)) {
492                 cerr << _("Cannot find ArdourMono TrueType font") << endl;
493         } else {
494                 FcBool ret = FcConfigAppFontAddFile(config, reinterpret_cast<const FcChar8*>(font_file.c_str()));
495                 if (ret == FcFalse) {
496                         cerr << _("Cannot load ArdourMono TrueType font.") << endl;
497                 }
498         }
499
500         if (!find_file (ardour_data_search_path(), "ArdourSans.ttf", font_file)) {
501                 cerr << _("Cannot find ArdourSans TrueType font") << endl;
502         } else {
503                 FcBool ret = FcConfigAppFontAddFile(config, reinterpret_cast<const FcChar8*>(font_file.c_str()));
504                 if (ret == FcFalse) {
505                         cerr << _("Cannot load ArdourSans TrueType font.") << endl;
506                 }
507         }
508
509         if (FcFalse == FcConfigSetCurrent(config)) {
510                 cerr << _("Failed to set fontconfig configuration.") << endl;
511         }
512 }