summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-02-28 18:07:04 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-02-28 18:07:04 +0200
commitb97462993b00ecc5cd04051b4fc2abd164fefb04 (patch)
tree6575f4c47b658a69c7da60e72d1f748e866e0aaf
parent2583e020e7f1f1036f6df8f5b93668e7a0187d60 (diff)
Single app instance; IPC mechanism
Only one instance of Lagrange is allowed to run per a runtime directory. Otherwise the instances will overwrite each others' files. Added a check at startup to see if an instance is already running. If so, options can be used to communicate with it. By default, a new tab is opened in the running instance. IssueID #37 IssueID #164 IssueID #174 IssueID #178
-rw-r--r--CMakeLists.txt2
-rw-r--r--src/app.c166
-rw-r--r--src/ipc.c186
-rw-r--r--src/ipc.h17
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
diff --git a/src/app.c b/src/app.c
index b76c4aaa..8a02ec3f 100644
--- a/src/app.c
+++ b/src/app.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
421static void terminate_App_(int rc) {
422 SDL_Quit();
423 deinit_Foundation();
424 exit(rc);
425}
426
427static 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
425static void init_App_(iApp *d, int argc, char **argv) { 477static 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) {
954void postCommand_App(const char *command) { 1059void 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 }
diff --git a/src/ipc.c b/src/ipc.c
index 81d4e35b..8fdc3bd5 100644
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -21,4 +21,190 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ 21SOFTWARE, 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
35iDeclareType(Ipc)
36
37struct Impl_Ipc {
38 iString dir;
39 iBool isListening;
40};
41
42static iIpc ipc_;
43
44static const char *lockFilePath_(const iIpc *d) {
45 return concatPath_CStr(cstr_String(&d->dir), ".pid");
46}
47
48static 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
53void 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
60void 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
69static 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
88void 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
99iProcessId 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
118iDeclareType(IpcResponse)
119
120struct Impl_IpcResponse {
121 iString *output;
122 iBool success;
123 iMutex mtx;
124 iCondition finished;
125};
126
127static 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
134static void deinit_IpcResponse(iIpcResponse *d) {
135 deinit_Condition(&d->finished);
136 deinit_Mutex(&d->mtx);
137 delete_String(d->output);
138}
139
140iDefineTypeConstruction(IpcResponse)
141
142static iIpcResponse *response_;
143
144static 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
163iBool 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
178iString *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
208void signal_Ipc(iProcessId pid) {
209 kill(pid, SIGUSR1);
210}
diff --git a/src/ipc.h b/src/ipc.h
index 22dce2a8..8127bf86 100644
--- a/src/ipc.h
+++ b/src/ipc.h
@@ -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
28void init_Ipc (const char *runDir);
29void deinit_Ipc (void);
30
31iProcessId check_Ipc (void);
32void listen_Ipc (void);
33iString * communicate_Ipc (const iString *command);
34void signal_Ipc (iProcessId pid);
35
36enum iIpcWrite {
37 command_IpcWrite,
38 response_IpcWrite,
39};
40
41iBool write_Ipc (iProcessId pid, const iString *input, enum iIpcWrite type);