1 /*
2 FXiTe - The Free eXtensIble Text Editor
3 Copyright (c) 2009-2013 Jeffrey Pohlmeyer <yetanothergeek@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify it
6 under the terms of the GNU General Public License version 3 as
7 published by the Free Software Foundation.
8
9 This software is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19 #include <fx.h>
20
21 #ifdef WIN32
22 # include <windows.h>
23 #else
24 # include <cerrno>
25 # include <unistd.h>
26 # include <sys/socket.h>
27 # include <sys/un.h>
28 #include <sys/stat.h>
29 #endif
30
31
32 #ifdef ENABLE_NLS
33 # include <libintl.h>
34 # define _(S) gettext(S)
35 #else
36 # define _
37 #endif
38
39
40 #include "interproc.h"
41
42
43 #ifdef WIN32 // For MS-Windows, we use the old-fashioned DDE for client/server communications...
44
45 FXIMPLEMENT(InterProc,FXObject,NULL,0);
46
47
48 static FXHash Conversations;
49 static FXHash Servers;
50 static FXHash Clients;
51
MakeAtoms()52 void InterProc::MakeAtoms()
53 {
54 FXString strTopic;
55 FXString strApplication;
56 if (DdeTopic.empty()) {
57 strTopic=DdePrefix+"_Topic";
58 strApplication=DdePrefix+"_Application";
59 } else {
60 strTopic=DdeTopic;
61 strApplication=DdePrefix;
62 }
63 atomApplication=GlobalAddAtom((LPSTR) strApplication.text());
64 atomTopic=GlobalAddAtom((LPSTR) strTopic.text());
65 atoms=MAKELONG(atomApplication, atomTopic);
66 Conversations.insert((void*)atoms, this);
67 }
68
69
70
KillAtoms()71 void InterProc::KillAtoms()
72 {
73 if (atomApplication) { GlobalDeleteAtom(atomApplication); }
74 if (atomTopic) { GlobalDeleteAtom(atomTopic); }
75 Conversations.remove((void*)atoms);
76 }
77
78
79 /*
80 The InterProc object should never be a client and server at the same time, else we would be
81 communicating with ourself, which is pointless. But problems arise when the same application
82 has one object acting as a client and another as a server, how can dispatchEvent() know which
83 object it is dealing with? The solution is to make dispatchEvent() into a static method, and
84 use global lookup tables to dispatch the event to the correct object. Since each object should
85 have a unique conversation atom within the same application, we can hash it to the object and
86 use that as a starting point for keeping things straight...
87
88 When iMsg is WM_DDE_INITIATE:
89 We can find the InterProc object in the Conversations table.
90 The wParam contains the remote client id, we must add it to the Clients table.
91 When iMsg is WM_DDE_EXECUTE:
92 The wParam contains the remote client id, we can find the InterProc object via the
93 Clients table. After we find it, we must remove client id from the table.
94 When iMsg is WM_DDE_ACK:
95 If we find the InterProc object in the Conversations table:
96 The wParam contains the remote server id, we must add it to the Servers table.
97 Else If we don't find the InterProc object in the Conversations table:
98 The wParam contains the remote server id, we can find the InterProc object via the
99 Servers table. After we find it, we must remove server id from the table.
100
101 */
102
103
dispatchEvent(FXID hwnd,unsigned int iMsg,unsigned int wParam,long lParam)104 void InterProc::dispatchEvent(FXID hwnd,unsigned int iMsg,unsigned int wParam,long lParam)
105 {
106 InterProc*ipc=NULL;
107
108 switch (iMsg) {
109 case WM_DDE_INITIATE: {
110 ipc=(InterProc*)(Conversations.find((void*)((FXival)lParam)));
111 break;
112 }
113 case WM_DDE_EXECUTE: {
114 ipc=(InterProc*)(Clients.remove((void*)((FXival)wParam)));
115 break;
116 }
117 case WM_DDE_ACK: {
118 ipc=(InterProc*)(Conversations.find((void*)((FXival)lParam)));
119 if (!ipc) {
120 ipc=(InterProc*)(Servers.remove((void*)((FXival)wParam)));
121 }
122 break;
123 }
124 }
125 if (ipc) { ipc->DoDispatch(hwnd,iMsg,wParam,lParam); }
126 }
127
128
129
130 #define AtomsMatch(lParam) ((LOWORD(lParam)==atomApplication)&&(HIWORD(lParam)==atomTopic))
131
DoDispatch(FXID hwnd,unsigned int iMsg,unsigned int wParam,long lParam)132 void InterProc::DoDispatch(FXID hwnd,unsigned int iMsg,unsigned int wParam,long lParam)
133 {
134 switch (iMsg) {
135 case WM_DDE_INITIATE: {
136 if (LocalServerID && (wParam!=RemoteClientID) && AtomsMatch(lParam)) {
137 // We are the server, one of our clients wants to connect...
138 RemoteClientID=wParam;
139 Clients.insert((void*)RemoteClientID, (void*)this);
140 SendMessage((HWND)RemoteClientID, WM_DDE_ACK, LocalServerID, atoms);
141 }
142 break;
143 }
144 case WM_DDE_EXECUTE:{
145 if (LocalServerID&&RemoteClientID&&(wParam==RemoteClientID)) {
146 // We are the server, and the client sent us something to do...
147 UINT uiLo;
148 char* puiHi;
149 if (UnpackDDElParam(WM_DDE_EXECUTE,lParam,(PUINT)&uiLo,(PUINT)&puiHi)) {
150 FXString ReceivedFromClient=(char*)GlobalLock((void*)puiHi);
151 GlobalUnlock((void*)puiHi);
152 PostMessage((HWND)RemoteClientID, WM_DDE_ACK, LocalServerID, lParam);
153 RemoteClientID=0;
154 ExecuteClientRequest(&ReceivedFromClient);
155 }
156 }
157 break;
158 }
159 case WM_DDE_ACK: {
160 if (LocalClientID && (((WPARAM)hwnd)==LocalClientID) && !LocalServerID) {
161 if (AtomsMatch(lParam)) {
162 // We are the client, and the server acknowledged out initial request...
163 RemoteServerID=wParam;
164 hCommand = GlobalAlloc(GMEM_MOVEABLE, commands->length()+1);
165 if (!hCommand) { break; }
166 char* lpCommand = (char*)GlobalLock(hCommand);
167 if (!lpCommand) {
168 GlobalFree(hCommand);
169 hCommand=NULL;
170 break;
171 }
172 strncpy(lpCommand,commands->text(),commands->length());
173 lpCommand[commands->length()]='\0';
174 GlobalUnlock(hCommand);
175 LPARAM ddeParam=PackDDElParam(WM_DDE_EXECUTE,0,(UINT)hCommand);
176 Servers.insert((void*)RemoteServerID, (void*)this);
177 if (PostMessage((HWND)RemoteServerID,WM_DDE_EXECUTE,(WPARAM)hwnd,ddeParam)) {
178 found_server=true;
179 } else {
180 GlobalFree(hCommand);
181 FreeDDElParam(WM_DDE_EXECUTE, (LPARAM)hCommand);
182 hCommand=NULL;
183 Servers.remove((void*)RemoteServerID);
184 }
185 } else if (hCommand && RemoteServerID && (wParam==RemoteServerID)) {
186 // We are the client, and the server has completed processing our commands...
187 GlobalFree(hCommand);
188 FreeDDElParam(WM_DDE_EXECUTE, (LPARAM)hCommand);
189 hCommand=NULL;
190 }
191 }
192 break;
193 }
194 }
195 }
196
197
198
199 class ClientApp: public FXApp {
dispatchEvent(FXID hwnd,unsigned int iMsg,unsigned int wParam,long lParam)200 virtual long dispatchEvent(FXID hwnd,unsigned int iMsg,unsigned int wParam,long lParam){
201 ipc->dispatchEvent(hwnd,iMsg,wParam,lParam);
202 return FXApp::dispatchEvent(hwnd,iMsg,wParam,lParam);
203 }
204 InterProc*ipc;
205 public:
ClientApp(InterProc * p)206 ClientApp(InterProc*p):FXApp() { ipc=p; }
207 };
208
209
210
ClientSend(FXTopWindow * client,const FXString & data)211 bool InterProc::ClientSend(FXTopWindow *client, const FXString &data)
212 {
213 if (LocalServerID) {
214 fxerror(_("%s: object cannot act as both client and server"), getClassName());
215 }
216 found_server=false;
217 hCommand=NULL;
218 RemoteServerID=0;
219 FXApp*a=app?app:new ClientApp(this);
220 if (!a->isInitialized()) {
221 static char argv0[MAX_PATH+1]="\0";
222 int argc=1;
223 if (!argv0[0]) { GetModuleFileName(NULL,argv0,MAX_PATH); }
224 char *argv[]={argv0,NULL};
225 a->init(argc,argv,true);
226 }
227 FXTopWindow *w=client?client:new FXMainWindow(a, DdePrefix+"_Client");
228 if (!w->id()) {
229 a->create();
230 w->create();
231 }
232 LocalClientID=(WPARAM)w->id();
233 commands=&data;
234 MakeAtoms();
235 SendMessage((HWND)HWND_BROADCAST,WM_DDE_INITIATE,(WPARAM)w->id(),atoms);
236 for (FXint i=0; i<5; i++) { // wait for first DDE_ACK
237 a->runWhileEvents();
238 if (found_server) { // got first DDE_ACK
239 for (FXint j=0; j<5; j++) { // wait for second DDE_ACK
240 if (hCommand==NULL) { // got second DDE_ACK
241 break;
242 }
243 FXThread::sleep(100000);
244 a->runWhileEvents();
245 }
246 break;
247 }
248 FXThread::sleep(100000);
249 }
250 LocalClientID=0;
251 if (hCommand) {
252 GlobalFree(hCommand);
253 FreeDDElParam(WM_DDE_EXECUTE, (LPARAM)hCommand);
254 hCommand=NULL;
255 }
256 KillAtoms();
257 if (w!=client) {
258 w->destroy();
259 delete w;
260 }
261 if (a!=app) {
262 a->destroy();
263 delete a;
264 }
265 if (RemoteServerID) {
266 Servers.remove((void*)RemoteServerID);
267 RemoteServerID=0;
268 }
269 return found_server;
270 }
271
272
273
StartServer(FXTopWindow * win,FXObject * trg,FXSelector sel)274 void InterProc::StartServer(FXTopWindow *win, FXObject*trg, FXSelector sel)
275 {
276 if (win) {
277 if (!win->id()) { win->create(); }
278 LocalServerID=(WPARAM)(win->id());
279 } else LocalServerID=0;
280 if (!LocalServerID) {
281 fxerror(_("%s: invalid window parameter.\n"), getClassName());
282 }
283 target=trg;
284 message=sel;
285 MakeAtoms();
286 }
287
288
289
StopServer()290 void InterProc::StopServer()
291 {
292 LocalServerID=0;
293 target=NULL;
294 message=0;
295 KillAtoms();
296 }
297
298
299 #else // For X11, we use a UNIX socket for client/server communications...
300
301
302 FXDEFMAP(InterProc) InterProcMap[]={
303 FXMAPFUNC(SEL_IO_READ, InterProc::ID_SOCKET_ACCEPT, InterProc::onSocketRead),
304 FXMAPFUNC(SEL_IO_READ, InterProc::ID_SOCKET_READ, InterProc::onSocketRead),
305 };
306
307 FXIMPLEMENT(InterProc,FXObject,InterProcMap,ARRAYNUMBER(InterProcMap));
308
309
310 #ifdef FOX_1_6
311 # define AddInput(file,mode,tgt,sel,ptr) addInput(file,mode,tgt,sel)
312 #else
313 # define AddInput(file,mode,tgt,sel,ptr) addInput(tgt,sel,file,mode,ptr)
314 #endif
315
316
317
SocketFailure(const char * func)318 static int SocketFailure(const char*func)
319 {
320 fxwarning(_("Error calling %s() function: %s.\n"), func, strerror(errno));
321 exit(EXIT_FAILURE);
322 return -2;
323 }
324
325
326 #if (FOX_MINOR>6)
327 # if (FOX_MAJOR>1) || (FOX_MINOR>7) || (FOX_LEVEL>49)
328 # define FOX_1_7_50_OR_NEWER
329 # endif
330 #endif
331
332 #ifdef FOX_1_7_50_OR_NEWER
333 # define LookupInHash(d,k) ((d)->at(k))
334 #else
335 # define LookupInHash(d,k) ((d)->find(k))
336 #endif
337
338
onSocketRead(FXObject * o,FXSelector sel,void * p)339 long InterProc::onSocketRead(FXObject*o,FXSelector sel,void*p)
340 {
341 switch (FXSELID(sel)) {
342 case ID_SOCKET_ACCEPT: {
343 struct sockaddr_un sa;
344 socklen_t size=sizeof(sa);
345 int read_fd=accept(listen_fd,(struct sockaddr*)&sa,&size);
346 if (read_fd==-1) {
347 SocketFailure("accept");
348 return 1;
349 }
350 connlist->insert((void*)((FXival)read_fd),new FXString());
351 app->AddInput(read_fd,INPUT_READ,this,ID_SOCKET_READ,(void*)((FXival)read_fd));
352 break;
353 }
354 case ID_SOCKET_READ: {
355 static const ssize_t bufsize=1024;
356 ssize_t len=0;
357 char buf[bufsize];
358 int read_fd=(FXival)p;
359 FXString*s=(FXString*)LookupInHash(connlist,p);
360 do {
361 len=read(read_fd,buf,bufsize);
362 if (len>0) { s->append(buf, len); }
363 #ifdef __minix
364 if (s->contains("\n\n")) {
365 len=0;
366 break;
367 }
368 #endif
369 } while (len>0);
370 if (len==0) {
371 app->removeInput(read_fd,INPUT_READ);
372 connlist->remove(p);
373 close(read_fd);
374 ExecuteClientRequest(s);
375 delete s;
376 }
377 break;
378 }
379 }
380 return 1;
381 }
382
383
384
CreateSocket(const char * filename,bool listening)385 static int CreateSocket(const char *filename, bool listening)
386 {
387 struct sockaddr_un sa;
388 int sock;
389 size_t size;
390 sock = socket(PF_LOCAL, SOCK_STREAM, 0);
391 if (sock < 0) { return SocketFailure("socket"); }
392 sa.sun_family = AF_UNIX;
393 strncpy(sa.sun_path, filename, sizeof(sa.sun_path));
394 sa.sun_path[sizeof(sa.sun_path) - 1] = '\0';
395 size = SUN_LEN(&sa);
396 if (listening) {
397 struct stat st;
398 if (stat(filename,&st)==0) {
399 if (unlink(filename)!=0) {
400 return SocketFailure("unlink");
401 }
402 } else {
403 if (errno!=ENOENT) {
404 return SocketFailure("stat");
405 }
406 }
407 if ( bind(sock, (struct sockaddr*)&sa, size) < 0 ) { return SocketFailure("bind"); }
408 if ( listen(sock,32) < 0 ) { return SocketFailure("listen"); }
409 } else {
410 if ( connect(sock, (struct sockaddr*)&sa, size) < 0 ) {
411 if ( errno==ECONNREFUSED || errno==EACCES ) {
412 close(sock);
413 return -1;
414 } else {
415 return SocketFailure("connect");
416 }
417 }
418 }
419 return sock;
420 }
421
422
423
ClientSend(FXTopWindow * client,const FXString & data)424 bool InterProc::ClientSend(FXTopWindow *client, const FXString &data)
425 {
426 FXStat st;
427 int write_fd;
428 bool found_server=false;
429 if ( FXStat::statFile(sock_name, st) && st.isSocket() ) {
430 write_fd=CreateSocket(sock_name.text(), false);
431 if (write_fd>=0) {
432 const char*p=data.text();
433 FXint rem=data.length();
434 while (rem>0) {
435 ssize_t wrote=write(write_fd,(const void*)p,rem);
436 if (wrote>=0) {
437 rem-=wrote;
438 p+=wrote;
439 } else {
440 break;
441 }
442 }
443 close(write_fd);
444 found_server=true;
445 }
446 }
447 return found_server;
448 }
449
450
451
StartServer(FXTopWindow * win,FXObject * trg,FXSelector sel)452 void InterProc::StartServer(FXTopWindow *win, FXObject*trg, FXSelector sel)
453 {
454 target=trg;
455 message=sel;
456 FXASSERT(connlist==NULL);
457 connlist=new FXHash();
458 listen_fd=CreateSocket(sock_name.text(), true);
459 app->AddInput(listen_fd,INPUT_READ,this,ID_SOCKET_ACCEPT,(void*)((FXival)listen_fd));
460 }
461
462
463 #if (FOX_MINOR>6)
464 # if (FOX_MAJOR>1) || (FOX_MINOR>7) || (FOX_LEVEL>42)
465 # define FOX_1_7_43_OR_NEWER
466 # endif
467 #endif
468
469
470 #ifdef FOX_1_7_43_OR_NEWER
471 # define TotalSlotsInHash(h) (h)->no()
472 # define UsedSlotsInHash(h) (h)->used()
473 #else
474 # define TotalSlotsInHash(h) (h)->size()
475 # define UsedSlotsInHash(h) (h)->no()
476 #endif
477
478
StopServer()479 void InterProc::StopServer()
480 {
481 app->removeInput(listen_fd,INPUT_READ);
482 close(listen_fd);
483 FXFile::remove(sock_name);
484 if ((connlist!=NULL) && (UsedSlotsInHash(connlist)>0)) {
485 for (FXint i=0; i<TotalSlotsInHash(connlist); i++) {
486 if (!connlist->empty(i)) {
487 FXString*s=(FXString*)connlist->value(i);
488 FXival fd=(FXival)connlist->key(i);
489 if (fd) {
490 app->removeInput(fd,INPUT_READ);
491 close(fd);
492 }
493 if (s) { delete s; }
494 }
495 }
496 delete connlist;
497 connlist=NULL;
498 }
499 }
500
501
502 #endif
503
504
ExecuteClientRequest(FXString * s)505 long InterProc::ExecuteClientRequest(FXString*s)
506 {
507 long rv=0;
508 if (target && message) {
509 rv=target->handle(this, FXSEL(SEL_COMMAND,message), (void*)s);
510 }
511 s->clear();
512 return rv;
513 }
514
515
516
InterProc(FXApp * a,const FXString & connection,const FXString & topic)517 InterProc::InterProc(FXApp*a, const FXString &connection, const FXString &topic)
518 {
519 app=a;
520 #ifdef WIN32
521 LocalClientID=0;
522 RemoteClientID=0;
523 DdePrefix=connection;
524 DdeTopic=topic;
525 LocalServerID=0;
526 #else
527 sock_name=connection;
528 connlist=NULL;
529 #endif
530 }
531
532