chiark / gitweb /
Import upstream version 5.3.
[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