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