DRC | 2ff39b8 | 2011-07-28 08:38:59 +0000 | [diff] [blame^] | 1 | // |
| 2 | // "$Id: fl_open_uri.cxx 8063 2010-12-19 21:20:10Z matt $" |
| 3 | // |
| 4 | // fl_open_uri() code for FLTK. |
| 5 | // |
| 6 | // Test with: |
| 7 | // |
| 8 | // gcc -I/fltk/dir -I/fltk/dir/src -DTEST -o fl_open_uri fl_open_uri.cxx -lfltk |
| 9 | // |
| 10 | // Copyright 2003-2010 by Michael R Sweet |
| 11 | // |
| 12 | // This library is free software; you can redistribute it and/or |
| 13 | // modify it under the terms of the GNU Library General Public |
| 14 | // License as published by the Free Software Foundation; either |
| 15 | // version 2 of the License, or (at your option) any later version. |
| 16 | // |
| 17 | // This library is distributed in the hope that it will be useful, |
| 18 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 19 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 20 | // Library General Public License for more details. |
| 21 | // |
| 22 | // You should have received a copy of the GNU Library General Public |
| 23 | // License along with this library; if not, write to the Free Software |
| 24 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
| 25 | // USA. |
| 26 | // |
| 27 | |
| 28 | // |
| 29 | // Include necessary headers... |
| 30 | // |
| 31 | |
| 32 | #include <FL/filename.H> |
| 33 | #include <stdio.h> |
| 34 | #include <stdlib.h> |
| 35 | #include <errno.h> |
| 36 | #include <sys/types.h> |
| 37 | #include "flstring.h" |
| 38 | #ifdef WIN32 |
| 39 | # include <windows.h> |
| 40 | # include <shellapi.h> |
| 41 | #else |
| 42 | # include <sys/wait.h> |
| 43 | # include <signal.h> |
| 44 | # include <fcntl.h> |
| 45 | # include <unistd.h> |
| 46 | #endif // WIN32 |
| 47 | |
| 48 | |
| 49 | // |
| 50 | // Local functions... |
| 51 | // |
| 52 | |
| 53 | #if !defined(WIN32) && !defined(__APPLE__) |
| 54 | static char *path_find(const char *program, char *filename, int filesize); |
| 55 | #endif // !WIN32 && !__APPLE__ |
| 56 | #ifndef WIN32 |
| 57 | static int run_program(const char *program, char **argv, char *msg, int msglen); |
| 58 | #endif // !WIN32 |
| 59 | |
| 60 | /** \addtogroup filenames |
| 61 | @{ */ |
| 62 | |
| 63 | /** |
| 64 | * Opens the specified Uniform Resource Identifier (URI). |
| 65 | * Uses an operating-system dependent program or interface. For URIs |
| 66 | * using the "ftp", "http", or "https" schemes, the system default web |
| 67 | * browser is used to open the URI, while "mailto" and "news" URIs are |
| 68 | * typically opened using the system default mail reader and "file" URIs |
| 69 | * are opened using the file system navigator. |
| 70 | * |
| 71 | * On success, the (optional) msg buffer is filled with the command that |
| 72 | * was run to open the URI; on Windows, this will always be "open uri". |
| 73 | * |
| 74 | * On failure, the msg buffer is filled with an English error message. |
| 75 | * |
| 76 | * \b Example |
| 77 | * \code |
| 78 | * #include <FL/filename.H> |
| 79 | * [..] |
| 80 | * char errmsg[512]; |
| 81 | * if ( !fl_open_uri("http://google.com/", errmsg, sizeof(errmsg)) ) { |
| 82 | * char warnmsg[768]; |
| 83 | * sprintf(warnmsg, "Error: %s", errmsg); |
| 84 | * fl_alert(warnmsg); |
| 85 | * } |
| 86 | * \endcode |
| 87 | * |
| 88 | * @param uri The URI to open |
| 89 | * @param msg Optional buffer which contains the command or error message |
| 90 | * @param msglen Length of optional buffer |
| 91 | * @return 1 on success, 0 on failure |
| 92 | */ |
| 93 | |
| 94 | int |
| 95 | fl_open_uri(const char *uri, char *msg, int msglen) { |
| 96 | // Supported URI schemes... |
| 97 | static const char * const schemes[] = { |
| 98 | "file://", |
| 99 | "ftp://", |
| 100 | "http://", |
| 101 | "https://", |
| 102 | "mailto:", |
| 103 | "news://", |
| 104 | NULL |
| 105 | }; |
| 106 | |
| 107 | // Validate the URI scheme... |
| 108 | int i; |
| 109 | for (i = 0; schemes[i]; i ++) |
| 110 | if (!strncmp(uri, schemes[i], strlen(schemes[i]))) |
| 111 | break; |
| 112 | |
| 113 | if (!schemes[i]) { |
| 114 | if (msg) { |
| 115 | char scheme[255]; |
| 116 | if (sscanf(uri, "%254[^:]", scheme) == 1) { |
| 117 | snprintf(msg, msglen, "URI scheme \"%s\" not supported.", scheme); |
| 118 | } else { |
| 119 | snprintf(msg, msglen, "Bad URI \"%s\"", uri); |
| 120 | } |
| 121 | } |
| 122 | |
| 123 | return 0; |
| 124 | } |
| 125 | |
| 126 | #ifdef WIN32 |
| 127 | if (msg) snprintf(msg, msglen, "open %s", uri); |
| 128 | |
| 129 | return (int)(ShellExecute(HWND_DESKTOP, "open", uri, NULL, NULL, SW_SHOW) > (void *)32); |
| 130 | |
| 131 | #elif defined(__APPLE__) |
| 132 | char *argv[3]; // Command-line arguments |
| 133 | |
| 134 | argv[0] = (char*)"open"; |
| 135 | argv[1] = (char*)uri; |
| 136 | argv[2] = (char*)0; |
| 137 | |
| 138 | if (msg) snprintf(msg, msglen, "open %s", uri); |
| 139 | |
| 140 | return run_program("/usr/bin/open", argv, msg, msglen) != 0; |
| 141 | |
| 142 | #else // !WIN32 && !__APPLE__ |
| 143 | // Run any of several well-known commands to open the URI. |
| 144 | // |
| 145 | // We give preference to the Portland group's xdg-utils |
| 146 | // programs which run the user's preferred web browser, etc. |
| 147 | // based on the current desktop environment in use. We fall |
| 148 | // back on older standards and then finally test popular programs |
| 149 | // until we find one we can use. |
| 150 | // |
| 151 | // Note that we specifically do not support the MAILER and |
| 152 | // BROWSER environment variables because we have no idea whether |
| 153 | // we need to run the listed commands in a terminal program. |
| 154 | |
| 155 | char command[FL_PATH_MAX], // Command to run... |
| 156 | *argv[4], // Command-line arguments |
| 157 | remote[1024]; // Remote-mode command... |
| 158 | const char * const *commands; // Array of commands to check... |
| 159 | static const char * const browsers[] = { |
| 160 | "xdg-open", // Portland |
| 161 | "htmlview", // Freedesktop.org |
| 162 | "firefox", |
| 163 | "mozilla", |
| 164 | "netscape", |
| 165 | "konqueror", // KDE |
| 166 | "opera", |
| 167 | "hotjava", // Solaris |
| 168 | "mosaic", |
| 169 | NULL |
| 170 | }; |
| 171 | static const char * const readers[] = { |
| 172 | "xdg-email", // Portland |
| 173 | "thunderbird", |
| 174 | "mozilla", |
| 175 | "netscape", |
| 176 | "evolution", // GNOME |
| 177 | "kmailservice", // KDE |
| 178 | NULL |
| 179 | }; |
| 180 | static const char * const managers[] = { |
| 181 | "xdg-open", // Portland |
| 182 | "fm", // IRIX |
| 183 | "dtaction", // CDE |
| 184 | "nautilus", // GNOME |
| 185 | "konqueror", // KDE |
| 186 | NULL |
| 187 | }; |
| 188 | |
| 189 | // Figure out which commands to check for... |
| 190 | if (!strncmp(uri, "file://", 7)) commands = managers; |
| 191 | else if (!strncmp(uri, "mailto:", 7) || |
| 192 | !strncmp(uri, "news:", 5)) commands = readers; |
| 193 | else commands = browsers; |
| 194 | |
| 195 | // Find the command to run... |
| 196 | for (i = 0; commands[i]; i ++) |
| 197 | if (path_find(commands[i], command, sizeof(command))) break; |
| 198 | |
| 199 | if (!commands[i]) { |
| 200 | if (msg) { |
| 201 | snprintf(msg, msglen, "No helper application found for \"%s\"", uri); |
| 202 | } |
| 203 | |
| 204 | return 0; |
| 205 | } |
| 206 | |
| 207 | // Handle command-specific arguments... |
| 208 | argv[0] = (char *)commands[i]; |
| 209 | |
| 210 | if (!strcmp(commands[i], "firefox") || |
| 211 | !strcmp(commands[i], "mozilla") || |
| 212 | !strcmp(commands[i], "netscape") || |
| 213 | !strcmp(commands[i], "thunderbird")) { |
| 214 | // program -remote openURL(uri) |
| 215 | snprintf(remote, sizeof(remote), "openURL(%s)", uri); |
| 216 | |
| 217 | argv[1] = (char *)"-remote"; |
| 218 | argv[2] = remote; |
| 219 | argv[3] = 0; |
| 220 | } else if (!strcmp(commands[i], "dtaction")) { |
| 221 | // dtaction open uri |
| 222 | argv[1] = (char *)"open"; |
| 223 | argv[2] = (char *)uri; |
| 224 | argv[3] = 0; |
| 225 | } else { |
| 226 | // program uri |
| 227 | argv[1] = (char *)uri; |
| 228 | argv[2] = 0; |
| 229 | } |
| 230 | |
| 231 | if (msg) { |
| 232 | strlcpy(msg, argv[0], msglen); |
| 233 | |
| 234 | for (i = 1; argv[i]; i ++) { |
| 235 | strlcat(msg, " ", msglen); |
| 236 | strlcat(msg, argv[i], msglen); |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | return run_program(command, argv, msg, msglen) != 0; |
| 241 | #endif // WIN32 |
| 242 | } |
| 243 | |
| 244 | /** @} */ |
| 245 | |
| 246 | #if !defined(WIN32) && !defined(__APPLE__) |
| 247 | // Find a program in the path... |
| 248 | static char *path_find(const char *program, char *filename, int filesize) { |
| 249 | const char *path; // Search path |
| 250 | char *ptr, // Pointer into filename |
| 251 | *end; // End of filename buffer |
| 252 | |
| 253 | |
| 254 | if ((path = getenv("PATH")) == NULL) path = "/bin:/usr/bin"; |
| 255 | |
| 256 | for (ptr = filename, end = filename + filesize - 1; *path; path ++) { |
| 257 | if (*path == ':') { |
| 258 | if (ptr > filename && ptr[-1] != '/' && ptr < end) *ptr++ = '/'; |
| 259 | |
| 260 | strlcpy(ptr, program, end - ptr + 1); |
| 261 | |
| 262 | if (!access(filename, X_OK)) return filename; |
| 263 | |
| 264 | ptr = filename; |
| 265 | } else if (ptr < end) *ptr++ = *path; |
| 266 | } |
| 267 | |
| 268 | if (ptr > filename) { |
| 269 | if (ptr[-1] != '/' && ptr < end) *ptr++ = '/'; |
| 270 | |
| 271 | strlcpy(ptr, program, end - ptr + 1); |
| 272 | |
| 273 | if (!access(filename, X_OK)) return filename; |
| 274 | } |
| 275 | |
| 276 | return 0; |
| 277 | } |
| 278 | #endif // !WIN32 && !__APPLE__ |
| 279 | |
| 280 | |
| 281 | #ifndef WIN32 |
| 282 | // Run the specified program, returning 1 on success and 0 on failure |
| 283 | static int |
| 284 | run_program(const char *program, char **argv, char *msg, int msglen) { |
| 285 | pid_t pid; // Process ID of first child |
| 286 | int status; // Exit status from first child |
| 287 | sigset_t set, oldset; // Signal masks |
| 288 | |
| 289 | |
| 290 | // Block SIGCHLD while we run the program... |
| 291 | // |
| 292 | // Note that I only use the POSIX signal APIs, however older operating |
| 293 | // systems may either not support POSIX signals or have side effects. |
| 294 | // IRIX, for example, provides three separate and incompatible signal |
| 295 | // APIs, so it is possible that an application setting a signal handler |
| 296 | // via signal() or sigset() will not have its SIGCHLD signals blocked... |
| 297 | |
| 298 | sigemptyset(&set); |
| 299 | sigaddset(&set, SIGCHLD); |
| 300 | sigprocmask(SIG_BLOCK, &set, &oldset); |
| 301 | |
| 302 | // Create child processes that actually run the program for us... |
| 303 | if ((pid = fork()) == 0) { |
| 304 | // First child comes here, fork a second child and exit... |
| 305 | if (!fork()) { |
| 306 | // Second child comes here, redirect stdin/out/err to /dev/null... |
| 307 | close(0); |
| 308 | open("/dev/null", O_RDONLY); |
| 309 | |
| 310 | close(1); |
| 311 | open("/dev/null", O_WRONLY); |
| 312 | |
| 313 | close(2); |
| 314 | open("/dev/null", O_WRONLY); |
| 315 | |
| 316 | // Detach from the current process group... |
| 317 | setsid(); |
| 318 | |
| 319 | // Run the program... |
| 320 | execv(program, argv); |
| 321 | _exit(0); |
| 322 | } else { |
| 323 | // First child gets here, exit immediately... |
| 324 | _exit(0); |
| 325 | } |
| 326 | } else if (pid < 0) { |
| 327 | // Restore signal handling... |
| 328 | sigprocmask(SIG_SETMASK, &oldset, NULL); |
| 329 | |
| 330 | // Return indicating failure... |
| 331 | return 0; |
| 332 | } |
| 333 | |
| 334 | // Wait for the first child to exit... |
| 335 | while (waitpid(pid, &status, 0) < 0) { |
| 336 | if (errno != EINTR) { |
| 337 | // Someone else grabbed the child status... |
| 338 | if (msg) snprintf(msg, msglen, "waitpid(%ld) failed: %s", (long)pid, |
| 339 | strerror(errno)); |
| 340 | |
| 341 | // Restore signal handling... |
| 342 | sigprocmask(SIG_SETMASK, &oldset, NULL); |
| 343 | |
| 344 | // Return indicating failure... |
| 345 | return 0; |
| 346 | } |
| 347 | } |
| 348 | |
| 349 | // Restore signal handling... |
| 350 | sigprocmask(SIG_SETMASK, &oldset, NULL); |
| 351 | |
| 352 | // Return indicating success... |
| 353 | return 1; |
| 354 | } |
| 355 | #endif // !WIN32 |
| 356 | |
| 357 | |
| 358 | #ifdef TEST |
| 359 | // |
| 360 | // Test code... |
| 361 | // |
| 362 | |
| 363 | // Open the URI on the command-line... |
| 364 | int main(int argc, char **argv) { |
| 365 | char msg[1024]; |
| 366 | |
| 367 | |
| 368 | if (argc != 2) { |
| 369 | puts("Usage: fl_open_uri URI"); |
| 370 | return 1; |
| 371 | } |
| 372 | |
| 373 | if (!fl_open_uri(argv[1], msg, sizeof(msg))) { |
| 374 | puts(msg); |
| 375 | return 1; |
| 376 | } else return 0; |
| 377 | } |
| 378 | #endif // TEST |
| 379 | |
| 380 | |
| 381 | // |
| 382 | // End of "$Id: fl_open_uri.cxx 8063 2010-12-19 21:20:10Z matt $". |
| 383 | // |