Merge branch 'arkkra' into shiny
[mup] / mup / mupmate / utils.C
1 /* Copyright (c) 2006 by Arkkra Enterprises */
2 /* All rights reserved */
3
4 // This file contains code for miscellaneous things that don't seem to really
5 // belong with any particular menu.
6
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <unistd.h>
11 #include "globals.H"
12 #include "utils.H"
13 #include <FL/fl_ask.H>
14 #include <FL/filename.H>
15 #ifdef OS_LIKE_WIN32
16 #include <windef.h>
17 #include <winbase.h>
18 #include <winreg.h>
19 #include <ctype.h>
20 #include <unistd.h>
21 #include <sys/stat.h>
22 #endif
23
24
25 // The FLTK Fl_Int_Input is almost what we want, but it allows
26 // octal and hex input via leading 0 and 0x respectively.
27 // So the Int_Input derived class intercepts the input and throws away
28 // characters from the set [xX] and leading zeros.
29 // Sometimes we also want to restrict to positive numbers,
30 // so the Positive_Int_Input class discards the - character as well.
31
32 Int_Input::Int_Input(int x, int y, int w, int h, const char * label)
33 : Fl_Int_Input(x, y, w, h, label)
34 {
35 allow_negative = true;
36 }
37
38
39 Positive_Int_Input::Positive_Int_Input(int x, int y, int w, int h, const char * label)
40 : Int_Input(x, y, w, h, label)
41 {
42 allow_negative = false;
43 }
44
45 int
46 Int_Input::handle(int event)
47 {
48 if (event == FL_KEYBOARD) {
49 int key = Fl::event_key();
50 if (key == 'x' || key == 'X') {
51 return(1);
52 }
53 if (key == '0' && position() == 0) {
54 return(1);
55 }
56 if (key == '-' && ! allow_negative) {
57 return(1);
58 }
59 }
60 return(Fl_Int_Input::handle(event));
61 }
62
63
64
65 // Character used to separate items in $PATH and to separate directory names
66 #ifdef OS_LIKE_WIN32
67 static const char path_sep = ';';
68 static const char dir_sep = '\\';
69 #else
70 static const char path_sep = ':';
71 static const char dir_sep = '/';
72 #endif
73
74
75 // Return the native OS's directory separator character
76
77 char
78 dir_separator(void)
79 {
80 return(dir_sep);
81 }
82
83 // Return the native OS's path separator character
84 char
85 path_separator(void)
86 {
87 return(path_sep);
88 }
89
90 // Set the value of $MUPPATH. We "new" space for it and save a static
91 // pointer to that space.
92 // If it had been set before, we delete the old space.
93
94 void
95 set_muppath(const char * new_muppath)
96 {
97 static char * muppath = 0;
98
99 if (muppath != 0) {
100 // The +8 is to skip past the MUPPATH= part
101 if (strcmp(muppath + 8, new_muppath) == 0) {
102 // Setting to existing value, so nothing to do
103 return;
104 }
105 delete muppath;
106 }
107 muppath = new char [strlen(new_muppath) + 9];
108 (void) sprintf(muppath, "MUPPATH=%s", new_muppath);
109 (void) putenv(muppath);
110 }
111
112
113
114 // Given a path to a file in "location", and the length of that path,
115 // and a suffix, see if the location with the suffix added is an
116 // executable file. If so, return true, with the suffixed name left
117 // in location. Otherwise return false with location as it came in.
118 // Could also return false if path would be longer than FL_PATH_MAX,
119 // and therefore will not fit. (Better to fail than core dump.)
120
121 static bool
122 access_with_suffix(char * location, int length, const char * suffix)
123 {
124 if (length + strlen(suffix) + 1 > FL_PATH_MAX) {
125 // Too long to store
126 return(false);
127 }
128
129 // Add suffix and see if it is an executable file
130 (void) strcpy(location + length, suffix);
131 if(access(location, X_OK) == 0) {
132 return(true);
133 }
134 else {
135 // This suffix didn't work. Remove it before returning
136 location[length] = '\0';
137 return(false);
138 }
139 }
140
141
142 // Given a file location, see if it exists as an executable file,
143 // taking into account the DOS/Windows strangeness of implicit suffixes.
144
145 static bool
146 check_access(char * location)
147 {
148 int len = strlen(location);
149 #ifdef __WIN32
150 // If doesn't have a suffix, try with .com, .exe, and .bat suffix
151 if (len < 5 || strchr(location + len - 4, '.') == 0) {
152 // This is the precedence order for executable suffixes
153 if (access_with_suffix(location, len, ".com")) {
154 return(true);
155 }
156 if (access_with_suffix(location, len, ".exe")) {
157 return(true);
158 }
159 if (access_with_suffix(location, len, ".bat")) {
160 return(true);
161 }
162 return(false);
163 }
164 // If did have a suffix, go ahead and try name as is
165 #endif
166 return access_with_suffix(location, len, "");
167 }
168
169
170 // Find the value of PATH. First try in third arg of main()
171 // since that seems more reliable on some OSs. Failing that, try getenv().
172
173 // We cache the value so we only have to search for it one time.
174 // This also rescues us in case env_p becomes invalid due to setting
175 // new environment variable values.
176
177 static const char * Path = 0;
178
179 void
180 get_path(const char ** const env_p)
181 {
182 if (Path != 0) {
183 // Already did it before
184 return;
185 }
186
187 if (env_p != 0) {
188 // Find $PATH in the environment variable list
189 int e;
190 for (e = 0; env_p[e] != 0; e++) {
191 if (strncmp(env_p[e], "PATH=", 5) == 0) {
192 Path = strdup(env_p[e] + 5);
193 break;
194 }
195 }
196 }
197 if (Path == 0) {
198 // Not found in the arge, so try looking up directly
199 Path = getenv("PATH");
200 }
201 }
202
203
204 // Return true if given path is an absoluate path
205
206 bool
207 is_absolute(const char * const path)
208 {
209 #ifdef OS_LIKE_WIN32
210 if ((path[0] != '\0' && path[1] == ':') || path[0] == dir_sep) {
211 #else
212 if (path[0] == dir_sep) {
213 #endif
214 return(true);
215 }
216 return(false);
217 }
218
219
220 // Given the name of a executable program, find the directory from
221 // which it comes, and put the full path into "location,"
222 // which is expected to be at least FL_PATH_MAX bytes long.
223 // The incoming pgm_name is expected to be no more than FL_PATH_MAX long.
224 // It uses the components of PATH to try to find the executable.
225 // For Windows, if the program name doesn't have a suffix,
226 // it tries to find a .com, .exe, or .bat file with the pgm_name.
227 // It returns true on success. On failure, it returns false,
228 // and the contents of location are not defined.
229
230 bool
231 find_executable(const char * const pgm_name, char * location)
232 {
233 // If pgm_name is already absolute path,
234 // just check if it exists and is executable
235 if (is_absolute(pgm_name)) {
236 (void) strcpy(location, pgm_name);
237 return (check_access(location));
238 }
239
240 if (Path == 0) {
241 // Should have already looked up $PATH,
242 // but make another attempt, just in case...
243 get_path(0);
244 if (Path == 0) {
245 return(false);
246 }
247 }
248
249 // We'll try the program name added to each PATH component
250 // until we find it or have to give up.
251 #ifdef OS_LIKE_WIN32
252 // DOS/Windows implicitly adds current working directory first
253 bool add_implicit_cwd = true;
254 #else
255 bool add_implicit_cwd = false;
256 #endif
257 const char * component; // current component of PATH
258 const char * next_component; // next component of PATH
259 const char * sep_p; // location of PATH separator
260 int len; // length of component
261 for (component = Path; *component != '\0'; component = next_component) {
262 if (add_implicit_cwd) {
263 // DOS/Windows implicitly adds current directory
264 // as first PATH component.
265 len = 0;
266 next_component = component;
267 add_implicit_cwd = false;
268 }
269
270 else if ((sep_p = strchr(component, path_sep)) != 0) {
271 // Not the last component in the PATH
272 len = sep_p - component;
273 next_component = sep_p + 1;
274 }
275 else {
276 // Is the last component in the PATH
277 len = strlen(component);
278 next_component = component + len;
279 }
280
281 if (len == 0) {
282 // Empty path component means current directory.
283 // Allow enough room for directory separator,
284 // pgm_name, suffix, and null terminator
285 if (getcwd(location, FL_PATH_MAX
286 - strlen(pgm_name) - 6) == 0) {
287 // Current directory unobtainable or too long
288 return(false);
289 }
290 len = strlen(location);
291 }
292 else {
293 strncpy(location, component, len);
294 }
295
296 // If PATH component didn't already add a directory
297 // separator, we add one. In some OSs it doesn't hurt
298 // to add another, but no reason to use an extra byte.
299 if (location[len-1] != dir_sep) {
300 location[len++] = dir_sep;
301 }
302
303 // Now add the progam name itself and see if it exists.
304 // The check_access() will add implied suffix if necessary.
305 (void) strcpy(location + len, pgm_name);
306 if (check_access(location)) {
307 return(true);
308 }
309 }
310 return(false);
311 }
312
313
314 // Returns location of magic file that lets us know user agreed
315 // to the license. The value is saved so subsequent calls can just return
316 // the saved value. If something goes wrong, a null string is returned.
317
318 #ifdef OS_LIKE_WIN32
319 #define MAGIC_FILE_NAME "mup.ok"
320 #else
321 #if defined(OS_LIKE_UNIX) || defined(VMS) || defined(AMIGA) || defined(EMX)
322 #define MAGIC_FILE_NAME ".mup"
323 #else
324 #ifdef Mac_BBEdit
325 #define MAGIC_FILE_NAME (char *)(MupRegFileName + 1)
326 #endif
327 #endif
328 #endif
329
330 #ifndef MAGIC_FILE_NAME
331 #error OS not supported
332 #endif
333
334 const char *
335 magic_file(const char * pname, const char ** env_p)
336 {
337 char * home;
338 char * fname = MAGIC_FILE_NAME;
339 static char * magicpath = 0; // Path is saved here
340
341 if (magicpath != 0) {
342 // Must have figured it out on previous call
343 return(magicpath);
344 }
345
346 // First check current directory. This will only work
347 // for the case where user has already created the file,
348 // and is of somewhat questionable use, since if the user
349 // changes directories to somewhere without the magic file,
350 // Mup will print the watermark.
351 if (access(fname, F_OK) == 0) {
352 magicpath = new char[strlen(fname) + 1];
353 strcpy(magicpath, fname);
354 return(magicpath);
355 }
356
357 #ifdef OS_LIKE_WIN32
358 // Construct pathname to magic file in the directory where
359 // mup.exe came from
360 if (pname == 0) {
361 // Shouldn't happen; We should get called once with valid
362 // pname, and return cached value after that
363 fl_alert("Unable to determine magic file name.");
364 return("");
365 }
366 char location[FL_PATH_MAX];
367 if (find_executable(pname, location)) {
368 int baselength = strlen(location)
369 - strlen(fl_filename_name(location));
370 magicpath = new char[baselength + strlen(fname) + 1];
371 // Copy pname up to last backslash
372 (void) strncpy(magicpath, location, baselength);
373 // add magic file name
374 (void) strcpy(magicpath + baselength, fname);
375 }
376 #else
377 // Construct pathname to magic file if it is in $HOME
378 if ((home = getenv("HOME")) != (char *) 0) {
379 magicpath = new char[strlen(home) + strlen(fname) + 2];
380 #ifdef VMS
381 (void) sprintf(magicpath, "%s%s", home, fname);
382 #else
383 (void) sprintf(magicpath, "%s/%s", home, fname);
384 #endif
385 }
386
387 #endif
388 #ifdef Mac_BBEdit
389 #pragma unused(pname)
390 // Check for file in Preferences folder inside System folder
391 magicpath = 0;
392 home = 0;
393 {
394 short vRefNum;
395 long dirID;
396 FSSpec fsSpec;
397
398 if (FindFolder(kOnSystemDisk, kPreferencesFolderType, false,
399 &vRefNum, &dirID) == noErr) {
400 // Preferences folder exists
401 if (FSMakeFSSpec(vRefNum, dirID,
402 (StringPtr) MupRegFileName, &fsSpec)
403 == noErr) {
404 // File exists
405 short old_vRefNum;
406 long old_dirID;
407 if (HGetVol((StringPtr) 0, &old_vRefNum,
408 &old_dirID) != noErr) {
409 return;
410 }
411
412 if (HSetVol((StringPtr) 0, vRefNum, dirID)
413 != noErr) {
414 return;
415 }
416 HSetVol((StringPtr) 0, old_vRefNum, old_dirID);
417 magicpath = new char[strlen(MAGIC_FILE_NAME) + 1];
418 (void) sprintf(magicpath, MAGIC_FILE_NAME);
419 }
420 }
421 }
422 #endif
423
424 if (magicpath == 0) {
425 // Not sure what to really do here... Punt.
426 fl_alert("Unable to find magic file path.");
427 magicpath = "";
428 }
429
430 return(magicpath);
431 }
432
433
434 #ifdef OS_LIKE_WIN32
435
436 // On Windows, we read the registry to try to determine the proper
437 // program to use for a given file type, like .mid or .ps files.
438 // This function will return the path to the appropriate file,
439 // if found, in a static area that may get overwritten on next call,
440 // so caller needs to make its own copy. If program is not found,
441 // returns null.
442
443
444 char *
445 lookup_pgm_for_file_suffix(const char * file_suffix)
446 {
447 static char data[512];
448 char name[512];
449 long len = sizeof(data);
450 // First find entry for file suffix mapping to a class
451 (void) sprintf(name, "Software\\Classes\\%s", file_suffix);
452 HRESULT result = RegQueryValue(HKEY_LOCAL_MACHINE, name, data, &len);
453 if (result != ERROR_SUCCESS) {
454 return(0);
455 }
456
457 // Next look up the program associated with that class
458 (void) sprintf(name, "Software\\Classes\\%s\\shell\\open\\command", data);
459 len = sizeof(data);
460 result = RegQueryValue(HKEY_LOCAL_MACHINE, name, data, &len);
461 if (result != ERROR_SUCCESS) {
462 return(0);
463 }
464
465 // We might get multiple strings back,
466 // giving the program itself plus arguments.
467 // We only want the program itself. So if the first string is quoted,
468 // strip the quotes and anything after it.
469 char * d;
470 if (*data == '"') {
471 for (d = data + 1; *d != '\0'; d++) {
472 if (*d == '"') {
473 *(d-1) = '\0';
474 break;
475 }
476 *(d-1) = *d;
477 }
478 }
479 if (access(data, X_OK) == 0) {
480 return(data);
481 }
482 return(0);
483 }
484
485
486 // Look up the given name in the CURRENT_USER area of registry
487 // and if found, fill in the data and return true, except return false.
488
489 static bool
490 reg_dir_found(char * name, char * data, DWORD len)
491 {
492 HRESULT result = RegQueryValueEx(HKEY_CURRENT_USER, name, 0, 0,
493 (LPBYTE)data, &len);
494 if (result == ERROR_SUCCESS) {
495 struct stat info;
496 if (stat(data, &info) == 0 && (info.st_mode & S_IFDIR)) {
497 return(true);
498 }
499 }
500 return(false);
501 }
502
503
504 // Look for likely default folder for Mup files.
505 // Use the current user's "My Music" folder if there is one,
506 // otherwise try their "Personal" folder.
507 // Returns path in static area or null on failure.
508
509 char *
510 find_music_folder(void)
511 {
512 static char best_value[FL_PATH_MAX];
513
514 // Get the registry info about folders
515 HKEY key = 0;
516 // Win'98 uses "User Shell Folders" but newer versions use just
517 // "Shell Folders," so we check for both, newer first, since that
518 // is probably more likely to work.
519 if ((RegOpenKeyEx(HKEY_CURRENT_USER,
520 "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders",
521 0, KEY_READ, &key) == ERROR_SUCCESS) ||
522 (RegOpenKeyEx(HKEY_CURRENT_USER,
523 "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders",
524 0, KEY_READ, &key) == ERROR_SUCCESS)) {
525 DWORD max_key_length;
526 DWORD count;
527 DWORD max_name_length;
528 DWORD max_value_length;
529 // Find out how many subkeys there are, and max lengths.
530 if (RegQueryInfoKey(key, 0, 0, 0, 0, &max_key_length, 0, &count,
531 &max_name_length, &max_value_length, 0, 0)
532 == ERROR_SUCCESS) {
533 TCHAR name[max_name_length + 1];
534 DWORD name_length;
535 DWORD value_type;
536 BYTE value[max_value_length + 1];
537 DWORD value_length;
538 int i;
539 best_value[0] = '\0';
540 // Look for "My Music" and "Personal" subkeys.
541 // There's probably a better way to query for specific
542 // subkey than linear search, but this works...
543 for (i = 0; i < count; i++) {
544 name_length = sizeof(name);
545 value_length = sizeof(value);
546 if (RegEnumValue(key, i, name, &name_length, 0,
547 &value_type, value,
548 &value_length)
549 == ERROR_SUCCESS) {
550 if (value_type != REG_SZ) {
551 continue;
552 }
553 if (strcasecmp(name, "My Music") == 0) {
554 // Found the ones we want.
555 strcpy(best_value, (char *) value);
556 break;
557 }
558 if (strcasecmp(name, "Personal") == 0) {
559 // This is our second choice.
560 // Save as best so far.
561 strcpy(best_value, (char *) value);
562 }
563 }
564 }
565 }
566 }
567 if (key != 0) {
568 RegCloseKey(key);
569 }
570 return(best_value[0] == '\0' ? 0 : best_value);
571 }
572
573 #endif