diff options
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/app.c | 166 | ||||
-rw-r--r-- | src/ipc.c | 186 | ||||
-rw-r--r-- | src/ipc.h | 17 |
4 files changed, 349 insertions, 22 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 6067f9cd..3ee6ea00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
@@ -100,6 +100,8 @@ set (SOURCES | |||
100 | src/gopher.h | 100 | src/gopher.h |
101 | src/history.c | 101 | src/history.c |
102 | src/history.h | 102 | src/history.h |
103 | src/ipc.c | ||
104 | src/ipc.h | ||
103 | src/lookup.c | 105 | src/lookup.c |
104 | src/lookup.h | 106 | src/lookup.h |
105 | src/media.c | 107 | src/media.c |
@@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
30 | #include "gmdocument.h" | 30 | #include "gmdocument.h" |
31 | #include "gmutil.h" | 31 | #include "gmutil.h" |
32 | #include "history.h" | 32 | #include "history.h" |
33 | #include "ipc.h" | ||
33 | #include "ui/certimportwidget.h" | 34 | #include "ui/certimportwidget.h" |
34 | #include "ui/color.h" | 35 | #include "ui/color.h" |
35 | #include "ui/command.h" | 36 | #include "ui/command.h" |
@@ -50,12 +51,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
50 | #include <the_Foundation/process.h> | 51 | #include <the_Foundation/process.h> |
51 | #include <the_Foundation/sortedarray.h> | 52 | #include <the_Foundation/sortedarray.h> |
52 | #include <the_Foundation/time.h> | 53 | #include <the_Foundation/time.h> |
53 | #include <SDL_events.h> | 54 | #include <SDL.h> |
54 | #include <SDL_filesystem.h> | ||
55 | #include <SDL_render.h> | ||
56 | #include <SDL_timer.h> | ||
57 | #include <SDL_video.h> | ||
58 | #include <SDL_version.h> | ||
59 | 55 | ||
60 | #include <stdio.h> | 56 | #include <stdio.h> |
61 | #include <stdarg.h> | 57 | #include <stdarg.h> |
@@ -422,8 +418,116 @@ static uint32_t postAutoReloadCommand_App_(uint32_t interval, void *param) { | |||
422 | return interval; | 418 | return interval; |
423 | } | 419 | } |
424 | 420 | ||
421 | static void terminate_App_(int rc) { | ||
422 | SDL_Quit(); | ||
423 | deinit_Foundation(); | ||
424 | exit(rc); | ||
425 | } | ||
426 | |||
427 | static void communicateWithRunningInstance_App_(iApp *d, iProcessId instance, | ||
428 | const iStringList *openCmds) { | ||
429 | iString *cmds = new_String(); | ||
430 | const iProcessId pid = currentId_Process(); | ||
431 | iConstForEach(CommandLine, i, &d->args) { | ||
432 | if (i.argType == value_CommandLineArgType) { | ||
433 | continue; | ||
434 | } | ||
435 | if (equal_CommandLineConstIterator(&i, "go-home")) { | ||
436 | appendCStr_String(cmds, "navigate.home\n"); | ||
437 | } | ||
438 | else if (equal_CommandLineConstIterator(&i, "new-tab")) { | ||
439 | iCommandLineArg *arg = argument_CommandLineConstIterator(&i); | ||
440 | if (!isEmpty_StringList(&arg->values)) { | ||
441 | appendFormat_String(cmds, "open newtab:1 url:%s\n", | ||
442 | cstr_String(constAt_StringList(&arg->values, 0))); | ||
443 | } | ||
444 | else { | ||
445 | appendCStr_String(cmds, "tabs.new\n"); | ||
446 | } | ||
447 | iRelease(arg); | ||
448 | } | ||
449 | else if (equal_CommandLineConstIterator(&i, "close-tab")) { | ||
450 | appendCStr_String(cmds, "tabs.close\n"); | ||
451 | } | ||
452 | else if (equal_CommandLineConstIterator(&i, "list-tab-urls;L")) { | ||
453 | appendFormat_String(cmds, "ipc.list.urls pid:%d\n", pid); | ||
454 | } | ||
455 | } | ||
456 | if (!isEmpty_StringList(openCmds)) { | ||
457 | append_String(cmds, collect_String(joinCStr_StringList(openCmds, "\n"))); | ||
458 | } | ||
459 | if (isEmpty_String(cmds)) { | ||
460 | /* By default open a new tab. */ | ||
461 | appendCStr_String(cmds, "tabs.new\n"); | ||
462 | } | ||
463 | if (!isEmpty_String(cmds)) { | ||
464 | iString *result = communicate_Ipc(cmds); | ||
465 | if (result) { | ||
466 | puts(cstr_String(result)); | ||
467 | } | ||
468 | delete_String(result); | ||
469 | } | ||
470 | iUnused(instance); | ||
471 | // else { | ||
472 | // printf("Lagrange already running (PID %d)\n", instance); | ||
473 | // } | ||
474 | terminate_App_(0); | ||
475 | } | ||
476 | |||
425 | static void init_App_(iApp *d, int argc, char **argv) { | 477 | static void init_App_(iApp *d, int argc, char **argv) { |
426 | init_CommandLine(&d->args, argc, argv); | 478 | init_CommandLine(&d->args, argc, argv); |
479 | /* Configure the valid command line options. */ { | ||
480 | defineValues_CommandLine(&d->args, "close-tab", 0); | ||
481 | defineValues_CommandLine(&d->args, "echo;E", 0); | ||
482 | defineValues_CommandLine(&d->args, "go-home", 0); | ||
483 | defineValues_CommandLine(&d->args, "help", 0); | ||
484 | defineValues_CommandLine(&d->args, "list-tab-urls;L", 0); | ||
485 | defineValuesN_CommandLine(&d->args, "new-tab", 0, 1); | ||
486 | defineValues_CommandLine(&d->args, "sw", 0); | ||
487 | defineValues_CommandLine(&d->args, "version;V", 0); | ||
488 | } | ||
489 | iStringList *openCmds = new_StringList(); | ||
490 | /* Handle command line options. */ { | ||
491 | if (contains_CommandLine(&d->args, "help")) { | ||
492 | printf("Usage: lagrange [options] [URLs] [paths]\n"); | ||
493 | terminate_App_(0); | ||
494 | } | ||
495 | if (contains_CommandLine(&d->args, "version;V")) { | ||
496 | printf("Lagrange version " LAGRANGE_APP_VERSION "\n"); | ||
497 | terminate_App_(0); | ||
498 | } | ||
499 | /* Check for URLs. */ | ||
500 | iBool newTab = iFalse; | ||
501 | iConstForEach(CommandLine, i, &d->args) { | ||
502 | const iRangecc arg = i.entry; | ||
503 | if (i.argType == value_CommandLineArgType) { | ||
504 | /* URLs and file paths accepted. */ | ||
505 | const iBool isKnownScheme = | ||
506 | startsWithCase_Rangecc(arg, "gemini:") || startsWithCase_Rangecc(arg, "gopher:") || | ||
507 | startsWithCase_Rangecc(arg, "finger:") || startsWithCase_Rangecc(arg, "file:") || | ||
508 | startsWithCase_Rangecc(arg, "data:") || startsWithCase_Rangecc(arg, "about:"); | ||
509 | if (isKnownScheme || fileExistsCStr_FileInfo(cstr_Rangecc(arg))) { | ||
510 | pushBack_StringList( | ||
511 | openCmds, | ||
512 | collectNewFormat_String("open newtab:%d url:%s", | ||
513 | newTab, | ||
514 | isKnownScheme | ||
515 | ? cstr_Rangecc(arg) | ||
516 | : cstrCollect_String(makeFileUrl_String( | ||
517 | collectNewRange_String(arg))))); | ||
518 | newTab = iTrue; | ||
519 | } | ||
520 | else { | ||
521 | fprintf(stderr, "Invalid URL/file: %s\n", cstr_Rangecc(arg)); | ||
522 | terminate_App_(1); | ||
523 | } | ||
524 | } | ||
525 | else if (!isDefined_CommandLine(&d->args, collectNewRange_String(i.entry))) { | ||
526 | fprintf(stderr, "Unknown option: %s\n", cstr_Rangecc(arg)); | ||
527 | terminate_App_(1); | ||
528 | } | ||
529 | } | ||
530 | } | ||
427 | /* Where was the app started from? We ask SDL first because the command line alone is | 531 | /* Where was the app started from? We ask SDL first because the command line alone is |
428 | not a reliable source of this information, particularly when it comes to different | 532 | not a reliable source of this information, particularly when it comes to different |
429 | operating systems. */ { | 533 | operating systems. */ { |
@@ -437,6 +541,17 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
437 | } | 541 | } |
438 | SDL_free(exec); | 542 | SDL_free(exec); |
439 | } | 543 | } |
544 | init_Ipc(dataDir_App_()); | ||
545 | /* Only one instance is allowed to run at a time; the runtime files (bookmarks, etc.) | ||
546 | are not shareable. */ { | ||
547 | const iProcessId instance = check_Ipc(); | ||
548 | if (instance) { | ||
549 | communicateWithRunningInstance_App_(d, instance, openCmds); | ||
550 | terminate_App_(0); | ||
551 | } | ||
552 | listen_Ipc(); /* We'll respond to commands from other instances. */ | ||
553 | } | ||
554 | printf("Lagrange: A Beautiful Gemini Client\n"); | ||
440 | const iBool isFirstRun = | 555 | const iBool isFirstRun = |
441 | !fileExistsCStr_FileInfo(cleanedPath_CStr(concatPath_CStr(dataDir_App_(), "prefs.cfg"))); | 556 | !fileExistsCStr_FileInfo(cleanedPath_CStr(concatPath_CStr(dataDir_App_(), "prefs.cfg"))); |
442 | d->isFinishedLaunching = iFalse; | 557 | d->isFinishedLaunching = iFalse; |
@@ -445,7 +560,7 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
445 | init_SortedArray(&d->tickers, sizeof(iTicker), cmp_Ticker_); | 560 | init_SortedArray(&d->tickers, sizeof(iTicker), cmp_Ticker_); |
446 | d->lastTickerTime = SDL_GetTicks(); | 561 | d->lastTickerTime = SDL_GetTicks(); |
447 | d->elapsedSinceLastTicker = 0; | 562 | d->elapsedSinceLastTicker = 0; |
448 | d->commandEcho = checkArgument_CommandLine(&d->args, "echo") != NULL; | 563 | d->commandEcho = checkArgument_CommandLine(&d->args, "echo;E") != NULL; |
449 | d->forceSoftwareRender = checkArgument_CommandLine(&d->args, "sw") != NULL; | 564 | d->forceSoftwareRender = checkArgument_CommandLine(&d->args, "sw") != NULL; |
450 | d->initialWindowRect = init_Rect(-1, -1, 900, 560); | 565 | d->initialWindowRect = init_Rect(-1, -1, 900, 560); |
451 | #if defined (iPlatformMsys) | 566 | #if defined (iPlatformMsys) |
@@ -529,21 +644,10 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
529 | } | 644 | } |
530 | } | 645 | } |
531 | /* URLs from the command line. */ { | 646 | /* URLs from the command line. */ { |
532 | iBool newTab = iFalse; | 647 | iConstForEach(StringList, i, openCmds) { |
533 | for (size_t i = 1; i < size_StringList(args_CommandLine(&d->args)); i++) { | 648 | postCommandString_App(i.value); |
534 | const iString *arg = constAt_StringList(args_CommandLine(&d->args), i); | ||
535 | const iBool isKnownScheme = | ||
536 | startsWithCase_String(arg, "gemini:") || startsWithCase_String(arg, "gopher:") || | ||
537 | startsWithCase_String(arg, "file:") || startsWithCase_String(arg, "data:") || | ||
538 | startsWithCase_String(arg, "about:"); | ||
539 | if (isKnownScheme || fileExists_FileInfo(arg)) { | ||
540 | postCommandf_App("open newtab:%d url:%s", | ||
541 | newTab, | ||
542 | isKnownScheme ? cstr_String(arg) | ||
543 | : cstrCollect_String(makeFileUrl_String(arg))); | ||
544 | newTab = iTrue; | ||
545 | } | ||
546 | } | 649 | } |
650 | iRelease(openCmds); | ||
547 | } | 651 | } |
548 | } | 652 | } |
549 | 653 | ||
@@ -567,6 +671,7 @@ static void deinit_App(iApp *d) { | |||
567 | deinit_CommandLine(&d->args); | 671 | deinit_CommandLine(&d->args); |
568 | iRelease(d->launchCommands); | 672 | iRelease(d->launchCommands); |
569 | delete_String(d->execPath); | 673 | delete_String(d->execPath); |
674 | deinit_Ipc(); | ||
570 | iRecycle(); | 675 | iRecycle(); |
571 | } | 676 | } |
572 | 677 | ||
@@ -954,6 +1059,9 @@ void postRefresh_App(void) { | |||
954 | void postCommand_App(const char *command) { | 1059 | void postCommand_App(const char *command) { |
955 | iApp *d = &app_; | 1060 | iApp *d = &app_; |
956 | iAssert(command); | 1061 | iAssert(command); |
1062 | if (strlen(command) == 0) { | ||
1063 | return; | ||
1064 | } | ||
957 | SDL_Event ev; | 1065 | SDL_Event ev; |
958 | if (*command == '!') { | 1066 | if (*command == '!') { |
959 | /* Global command; this is global context so just ignore. */ | 1067 | /* Global command; this is global context so just ignore. */ |
@@ -1784,6 +1892,22 @@ iBool handleCommand_App(const char *cmd) { | |||
1784 | } | 1892 | } |
1785 | return iFalse; | 1893 | return iFalse; |
1786 | } | 1894 | } |
1895 | else if (equal_Command(cmd, "ipc.list.urls")) { | ||
1896 | iProcessId pid = argLabel_Command(cmd, "pid"); | ||
1897 | if (pid) { | ||
1898 | iString *urls = collectNew_String(); | ||
1899 | iConstForEach(ObjectList, i, iClob(listDocuments_App())) { | ||
1900 | append_String(urls, url_DocumentWidget(i.object)); | ||
1901 | appendCStr_String(urls, "\n"); | ||
1902 | } | ||
1903 | write_Ipc(pid, urls, response_IpcWrite); | ||
1904 | } | ||
1905 | return iTrue; | ||
1906 | } | ||
1907 | else if (equal_Command(cmd, "ipc.signal")) { | ||
1908 | signal_Ipc(arg_Command(cmd)); | ||
1909 | return iTrue; | ||
1910 | } | ||
1787 | else { | 1911 | else { |
1788 | return iFalse; | 1912 | return iFalse; |
1789 | } | 1913 | } |
@@ -21,4 +21,190 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | 21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ |
22 | 22 | ||
23 | #include "ipc.h" | 23 | #include "ipc.h" |
24 | #include "app.h" | ||
24 | 25 | ||
26 | #include <the_Foundation/file.h> | ||
27 | #include <the_Foundation/fileinfo.h> | ||
28 | #include <the_Foundation/mutex.h> | ||
29 | #include <the_Foundation/path.h> | ||
30 | #include <the_Foundation/process.h> | ||
31 | #include <the_Foundation/time.h> | ||
32 | |||
33 | #include <signal.h> | ||
34 | |||
35 | iDeclareType(Ipc) | ||
36 | |||
37 | struct Impl_Ipc { | ||
38 | iString dir; | ||
39 | iBool isListening; | ||
40 | }; | ||
41 | |||
42 | static iIpc ipc_; | ||
43 | |||
44 | static const char *lockFilePath_(const iIpc *d) { | ||
45 | return concatPath_CStr(cstr_String(&d->dir), ".pid"); | ||
46 | } | ||
47 | |||
48 | static const char *inputFilePath_(const iIpc *d, int pid) { | ||
49 | return concatPath_CStr(cstr_String(&d->dir), | ||
50 | format_CStr(".run.%d.cfg", pid ? pid : currentId_Process())); | ||
51 | } | ||
52 | |||
53 | void init_Ipc(const char *runDir) { | ||
54 | iIpc *d = &ipc_; | ||
55 | initCStr_String(&d->dir, runDir); | ||
56 | d->isListening = iFalse; | ||
57 | signal(SIGUSR1, SIG_IGN); | ||
58 | } | ||
59 | |||
60 | void deinit_Ipc(void) { | ||
61 | iIpc *d = &ipc_; | ||
62 | signal(SIGUSR1, SIG_IGN); | ||
63 | if (d->isListening) { | ||
64 | remove(lockFilePath_(d)); | ||
65 | } | ||
66 | deinit_String(&d->dir); | ||
67 | } | ||
68 | |||
69 | static void handleUserSignal_(int sig) { | ||
70 | iIpc *d = &ipc_; | ||
71 | iAssert(sig == SIGUSR1); | ||
72 | iUnused(sig); | ||
73 | const char *path = inputFilePath_(d, 0); | ||
74 | iFile *f = newCStr_File(path); | ||
75 | if (open_File(f, readOnly_FileMode)) { | ||
76 | iString *cmds = new_String(); | ||
77 | initBlock_String(cmds, collect_Block(readAll_File(f))); | ||
78 | iRangecc line = iNullRange; | ||
79 | while (nextSplit_Rangecc(range_String(cmds), "\n", &line)) { | ||
80 | postCommand_App(cstr_Rangecc(line)); | ||
81 | } | ||
82 | delete_String(cmds); | ||
83 | } | ||
84 | iRelease(f); | ||
85 | remove(path); | ||
86 | } | ||
87 | |||
88 | void listen_Ipc(void) { | ||
89 | iIpc *d = &ipc_; | ||
90 | signal(SIGUSR1, handleUserSignal_); | ||
91 | iFile *f = newCStr_File(lockFilePath_(d)); | ||
92 | if (open_File(f, writeOnly_FileMode)) { | ||
93 | printf_Stream(stream_File(f), "%u", currentId_Process()); | ||
94 | d->isListening = iTrue; | ||
95 | } | ||
96 | iRelease(f); | ||
97 | } | ||
98 | |||
99 | iProcessId check_Ipc(void) { | ||
100 | const iIpc *d = &ipc_; | ||
101 | iProcessId pid = 0; | ||
102 | iFile *f = newCStr_File(lockFilePath_(d)); | ||
103 | if (open_File(f, readOnly_FileMode)) { | ||
104 | const iBlock *running = collect_Block(readAll_File(f)); | ||
105 | close_File(f); | ||
106 | pid = atoi(constData_Block(running)); | ||
107 | if (!exists_Process(pid)) { | ||
108 | pid = 0; | ||
109 | remove(cstr_String(path_File(f))); /* Stale. */ | ||
110 | } | ||
111 | } | ||
112 | iRelease(f); | ||
113 | return pid; | ||
114 | } | ||
115 | |||
116 | /*----------------------------------------------------------------------------------------------*/ | ||
117 | |||
118 | iDeclareType(IpcResponse) | ||
119 | |||
120 | struct Impl_IpcResponse { | ||
121 | iString *output; | ||
122 | iBool success; | ||
123 | iMutex mtx; | ||
124 | iCondition finished; | ||
125 | }; | ||
126 | |||
127 | static void init_IpcResponse(iIpcResponse *d) { | ||
128 | d->output = new_String(); | ||
129 | d->success = iFalse; | ||
130 | init_Mutex(&d->mtx); | ||
131 | init_Condition(&d->finished); | ||
132 | } | ||
133 | |||
134 | static void deinit_IpcResponse(iIpcResponse *d) { | ||
135 | deinit_Condition(&d->finished); | ||
136 | deinit_Mutex(&d->mtx); | ||
137 | delete_String(d->output); | ||
138 | } | ||
139 | |||
140 | iDefineTypeConstruction(IpcResponse) | ||
141 | |||
142 | static iIpcResponse *response_; | ||
143 | |||
144 | static void handleSignal_IpcResponse_(int sig) { | ||
145 | iUnused(sig); | ||
146 | iAssert(response_); | ||
147 | iIpcResponse *d = response_; | ||
148 | lock_Mutex(&d->mtx); | ||
149 | iFile *f = newCStr_File(inputFilePath_(&ipc_, 0)); | ||
150 | if (open_File(f, text_FileMode | readOnly_FileMode)) { | ||
151 | iBlock *input = readAll_File(f); | ||
152 | close_File(f); | ||
153 | remove(cstr_String(path_File(f))); | ||
154 | setBlock_String(d->output, input); | ||
155 | d->success = iTrue; | ||
156 | delete_Block(input); | ||
157 | } | ||
158 | iRelease(f); | ||
159 | signal_Condition(&d->finished); | ||
160 | unlock_Mutex(&d->mtx); | ||
161 | } | ||
162 | |||
163 | iBool write_Ipc(iProcessId pid, const iString *input, enum iIpcWrite type) { | ||
164 | iBool ok = iFalse; | ||
165 | iFile *f = newCStr_File(inputFilePath_(&ipc_, pid)); | ||
166 | if (open_File(f, text_FileMode | append_FileMode)) { | ||
167 | write_File(f, utf8_String(input)); | ||
168 | if (type == command_IpcWrite) { | ||
169 | printf_Stream(stream_File(f), "\nipc.signal arg:%d\n", currentId_Process()); | ||
170 | } | ||
171 | close_File(f); | ||
172 | ok = iTrue; | ||
173 | } | ||
174 | iRelease(f); | ||
175 | return ok; | ||
176 | } | ||
177 | |||
178 | iString *communicate_Ipc(const iString *command) { | ||
179 | const iProcessId dst = check_Ipc(); | ||
180 | if (dst) { | ||
181 | if (write_Ipc(dst, command, command_IpcWrite)) { | ||
182 | response_ = new_IpcResponse(); | ||
183 | signal(SIGUSR1, handleSignal_IpcResponse_); | ||
184 | lock_Mutex(&response_->mtx); | ||
185 | if (kill(dst, SIGUSR1) == 0) { | ||
186 | iTime until; | ||
187 | initTimeout_Time(&until, 1.0); | ||
188 | waitTimeout_Condition(&response_->finished, &response_->mtx, &until); | ||
189 | } | ||
190 | unlock_Mutex(&response_->mtx); | ||
191 | if (!response_->success) { | ||
192 | delete_IpcResponse(response_); | ||
193 | response_ = NULL; | ||
194 | } | ||
195 | } | ||
196 | } | ||
197 | signal(SIGUSR1, SIG_IGN); | ||
198 | if (response_) { | ||
199 | iString *result = copy_String(response_->output); | ||
200 | trimEnd_String(result); | ||
201 | delete_IpcResponse(response_); | ||
202 | response_ = NULL; | ||
203 | return result; | ||
204 | } | ||
205 | return NULL; | ||
206 | } | ||
207 | |||
208 | void signal_Ipc(iProcessId pid) { | ||
209 | kill(pid, SIGUSR1); | ||
210 | } | ||
@@ -22,5 +22,20 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
22 | 22 | ||
23 | #pragma once | 23 | #pragma once |
24 | 24 | ||
25 | #include <the_Foundation/defs.h> | 25 | #include <the_Foundation/string.h> |
26 | #include <the_Foundation/process.h> | ||
26 | 27 | ||
28 | void init_Ipc (const char *runDir); | ||
29 | void deinit_Ipc (void); | ||
30 | |||
31 | iProcessId check_Ipc (void); | ||
32 | void listen_Ipc (void); | ||
33 | iString * communicate_Ipc (const iString *command); | ||
34 | void signal_Ipc (iProcessId pid); | ||
35 | |||
36 | enum iIpcWrite { | ||
37 | command_IpcWrite, | ||
38 | response_IpcWrite, | ||
39 | }; | ||
40 | |||
41 | iBool write_Ipc (iProcessId pid, const iString *input, enum iIpcWrite type); | ||