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