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 
20 #include <csignal>
21 #ifdef WIN32
22 # include <windows.h>
23 # include <ctype.h>
24 #else
25 # include <sys/socket.h>
26 # include <sys/un.h>
27 # include <X11/Xlib.h>
28 #endif
29 
30 #include <unistd.h>
31 #include <fcntl.h>
32 #include <cerrno>
33 
34 #include <fx.h>
35 
36 #include "appname.h"
37 #include "compat.h"
38 #include "appwin_pub.h"
39 
40 #include "interproc.h"
41 #include "theme.h"
42 
43 #include "intl.h"
44 #include "appmain.h"
45 
46 
47 FXDEFMAP(AppClass) AppMap[]={
48   FXMAPFUNC(SEL_SIGNAL,  AppClass::ID_CLOSEALL,    AppClass::onCmdCloseAll),
49   FXMAPFUNC(SEL_COMMAND, AppClass::ID_CLOSEALL,    AppClass::onCmdCloseAll),
50   FXMAPFUNC(SEL_COMMAND, AppClass::ID_IPC_EXEC,    AppClass::onIpcExec),
51 };
52 
53 
54 FXIMPLEMENT(FXiTe,FXApp,AppMap,ARRAYNUMBER(AppMap));
55 
56 
57 
AppClass(const FXString & name,const FXString & title)58 AppClass::AppClass(const FXString& name, const FXString& title):FXApp(name,title)
59 {
60   quitting=false;
61   commands=FXString::null;
62 //  addSignal(SIGINT,  this, App::ID_CLOSEALL);
63 #ifndef WIN32
64   addSignal(SIGQUIT, this, AppClass::ID_CLOSEALL);
65   addSignal(SIGHUP,  this, AppClass::ID_CLOSEALL);
66   addSignal(SIGPIPE, this, AppClass::ID_CLOSEALL);
67   addSignal(SIGTERM, this, AppClass::ID_CLOSEALL);
68 #endif
69 }
70 
71 
72 
onCmdCloseAll(FXObject * o,FXSelector sel,void * p)73 long AppClass::onCmdCloseAll(FXObject*o,FXSelector sel,void*p)
74 {
75 #ifndef WIN32
76   if (((FXival)p)==SIGPIPE) { return 1; }
77 #endif
78   if (quitting) { return 1; }
79   quitting=true;
80   TopWinPub::close();
81   quitting=false;
82   return 1;
83 }
84 
85 
86 
ParseCommandLine()87 void AppClass::ParseCommandLine()
88 {
89   bool skip_next=false;
90   bool is_macro=false;
91   commands=FXString::null;
92   for (FXint i=1; i<getArgc(); i++) {
93     if (skip_next) {
94       skip_next=false;
95       continue;
96     }
97     const char *arg=getArgv()[i];
98     FXuint arglen=arg?strlen(arg):0;
99     if (arglen)  {
100       if ( (arg[0]=='-') && (arg[1]=='e') ) { is_macro=true; }
101       if ( (!strchr("-+",arg[0])) && FXStat::exists(arg) && FXStat::isFile(arg) ) {
102         FXString filename=FXPath::absolute(arg);
103         commands.append(filename.text());
104       } else {
105         if ( (arg[0]=='-') && (strchr("cs", arg[1])) ) {
106           if ( arglen == 2 ) {
107             skip_next=true;
108             continue;
109           }
110         } else {
111           if ( (strchr("-+",arg[0])) ) {
112             commands.append(arg);
113           } else {
114             if (is_macro) {
115               commands.append(arg);
116               is_macro=false;
117             } else {
118               FXString filename=FXPath::absolute(arg);
119               commands.append(filename.text());
120             }
121           }
122         }
123       }
124       commands.append("\n");
125     }
126   }
127   commands.append("\n\n");
128 }
129 
130 
131 
onIpcExec(FXObject * o,FXSelector sel,void * p)132 long AppClass::onIpcExec(FXObject*o, FXSelector sel, void*p)
133 {
134   TopWinPub::ParseCommands(*((FXString*)p));
135   return 1;
136 }
137 
138 
139 
140 static const char* helptext[]= {
141   "",
142   _("Options:"),
143   "",
144   _("  -s <name>        Create or control a named instance <name>."),
145   _("  -c <name>        Use the alternate configuration <name>."),
146   "",
147   _("  -r               Open the next files read-only."),
148   _("  -w               Open the next files read-write. (default)"),
149   "",
150   _("  +27              Open the next file to line 27."),
151   _("  +35,8            Open the next file to line 35, column 8."),
152   _("  readme.txt:163   Open \"readme.txt\" to line 163."),
153   "",
154   _("  -p               Restore previous session."),
155   _("  -e <command>     Execute Lua macro string <command>."),
156   _("  -t <file>        Load tags from <file>."),
157   "",
158   _("  -q               Quiet, do not raise existing instance."),
159 #ifndef WIN32
160   _("  -d <name>        Connect to X display <name>."),
161 #endif
162   _("  -v               Show version information and exit."),
163   "",
164   NULL
165 };
166 
167 
usage(const char * prog)168 static void usage(const char*prog)
169 {
170   printf("\n");
171   printf(_("Usage: %s [options] [files] ...\n"), FXPath::name(prog).text());
172   for (const char**s=helptext; *s; s++) {
173     printf("%s\n", *s);
174   }
175 }
176 
177 
178 
179 #ifdef WIN32
dispatchEvent(FXID hwnd,FXuint iMsg,FXuval wParam,FXival lParam)180 FXival AppClass::dispatchEvent(FXID hwnd, FXuint iMsg, FXuval wParam, FXival lParam)
181 {
182   switch (iMsg) {
183       case WM_DDE_INITIATE:
184       case WM_DDE_EXECUTE:
185       case WM_DDE_ACK: {
186         ipc->dispatchEvent(hwnd,iMsg,wParam,lParam);
187         break;
188       }
189       case WM_DROPFILES: {
190         HDROP hdrop = reinterpret_cast<HDROP>(wParam);
191         int nFiles = ::DragQueryFile(hdrop, 0xffffffff, NULL, 0);
192         FXchar files[nFiles][MAX_PATH];
193         FXint i;
194         for (i=0; i<nFiles; ++i) { ::DragQueryFile(hdrop, i, files[i], sizeof(files[i])); }
195         ::DragFinish(hdrop);
196         for (i=0; i<nFiles; ++i) { TopWinPub::OpenFile(files[i], NULL, false, true); }
197         break;
198       }
199   }
200   return FXApp::dispatchEvent(hwnd,iMsg,wParam,lParam);
201 }
202 #else
203 static char display_opt[]="-display";
204 #endif
205 
206 
CreatePathOrDie(const FXString & dirname)207 void AppClass::CreatePathOrDie(const FXString &dirname)
208 {
209   FXString dn="";
210   FXint n=dirname.contains(PATHSEP);
211 #ifdef WIN32
212   if ((dirname[1]==':') && isalpha(dirname[0])) {
213     dn.append(dirname[0]);
214     dn.append(':');
215   }
216 #endif
217   dn.append(PATHSEP);
218   for (FXint i=1; i<=n; i++) {
219     dn.append(dirname.section(PATHSEP,i));
220     if (!(IsDir(dn)||FXDir::create(dn,FXIO::OwnerFull))) {
221       FXString msg=SystemErrorStr();
222       fxwarning("\n%s: %s:\n    %s\n(%s)\n\n",
223          getArgv()[0],
224          _("FATAL: Failed to create directory path"),
225          dn.text(), msg.text()
226       );
227       create();
228       FXMessageBox::error(this, MBOX_OK, _(APP_NAME" error"), "%s:\n\n    %s\n\n(%s)\n\n",
229       _("FATAL: Failed to create directory path"),
230       dn.text(), msg.text()
231       );
232       destroy();
233       fflush(stderr);
234       ::exit(EXIT_FAILURE);
235     }
236     dn.append(PATHSEP);
237   }
238 }
239 
240 #ifdef FOX_1_6
241 
242 // Old FOX-1.6 config directory location...
243 
CreateConfigDir()244 void AppClass::CreateConfigDir()
245 {
246   configdir=FXSystem::getHomeDirectory()+ PATHSEP+ ".foxrc"+ PATHSEP+ getVendorName()+ PATHSEP;
247   CreatePathOrDie(configdir);
248 }
249 
250 
251 #else
252 
253 extern void MigrateConfigDir(FXApp*a, const FXString &src, const FXString &dst, FXString &errors);
254 
255 # ifdef WIN32
256 
257 // New Win32 config directory location...
CreateConfigDir()258 void AppClass::CreateConfigDir()
259 {
260   configdir=reg().getUserDirectory()+PATHSEP+getVendorName();
261   configdir.substitute('/',PATHSEP,true);
262   FXString oldconfig=FXString::null;
263   FXString newconfig=FXPath::directory(configdir);
264   const char*config_tail=PATHSEPSTRING "foxrc" PATHSEPSTRING "fxite";
265   if (IsWin9x()) {
266     if (!IsDir(newconfig)) {
267       FXString homedir_cfg=FXSystem::getEnvironment("HOME");
268       if (!homedir_cfg.empty()) {
269         homedir_cfg+=config_tail;
270         homedir_cfg.substitute('/',PATHSEP,true);
271       }
272       if (IsDir(homedir_cfg)) {
273         oldconfig=homedir_cfg;
274       } else {
275         FXString mydocs_cfg=GetShellFolder("Personal")+config_tail;
276         if (IsDir(mydocs_cfg)) {
277           oldconfig=mydocs_cfg;
278         }
279       }
280     }
281   } else {
282     oldconfig=FXSystem::getEnvironment("USERPROFILE");
283     if (!oldconfig.empty()) {
284       oldconfig+=config_tail;
285     }
286   }
287   if (!oldconfig.empty()) {
288     MigrateConfigDir(this, oldconfig, newconfig, migration_errors);
289   }
290   configdir.append(PATHSEP);
291   CreatePathOrDie(configdir);
292 }
293 
294 # else
295 
296 // New FOX-1.7 XDG config directory location...
CreateConfigDir()297 void AppClass::CreateConfigDir()
298 {
299   migration_errors="";
300   FXString old_config=FXSystem::getHomeDirectory()+ PATHSEP+ ".foxrc"+ PATHSEP+ getVendorName();
301   FXString xdg_config="";
302   if (use_xdg_config()) {
303     xdg_config=getenv("XDG_CONFIG_HOME");
304     if (xdg_config.empty()) {
305       xdg_config=FXSystem::getHomeDirectory()+ PATHSEP+ ".config";
306     } else {
307       xdg_config=FXPath::simplify(FXPath::absolute(xdg_config));
308     }
309     CreatePathOrDie(xdg_config);
310     xdg_config += PATHSEP + getVendorName();
311     MigrateConfigDir(this,
312       FXPath::directory(old_config), FXPath::directory(xdg_config), migration_errors);
313     configdir=xdg_config.text();
314   } else {
315     configdir=old_config.text();
316   }
317   configdir=FXPath::simplify(FXPath::absolute(configdir));
318   configdir.append( PATHSEP );
319   CreatePathOrDie(configdir);
320 }
321 
322 # endif // OS
323 
324 #endif // FOX Version
325 
326 
327 
init(int & argc,char ** argv,bool connect)328 void AppClass::init(int& argc, char** argv, bool connect)
329 {
330 #ifdef WIN32
331   FXString AppDataDir;
332   GetAppDataDir(AppDataDir);
333   reg().setUserDirectory(AppDataDir);
334   reg().setAsciiMode(true);
335   FXApp::init(argc,argv,connect);
336   reg().setUserDirectory(AppDataDir);
337   reg().read();
338 #else
339   for (int i=1; i<argc; i++) {
340     if (argv[i] && (strcmp(argv[i],"-d")==0)) { argv[i]=display_opt; }
341   }
342   FXApp::init(argc,argv,connect);
343 #endif
344   Theme::init();
345   CreateConfigDir();
346   for (FXint i=1; i<getArgc(); i++) {
347     const char *arg=getArgv()[i];
348     if (argv[i][0]=='-') {
349       switch (arg[1]) {
350         case 's': {
351           if (arg[2]) {
352             sock_name=arg+2;
353           } else {
354              if ((i+1)<getArgc()) {
355                sock_name=getArgv()[i+1];
356              } else {
357                sock_name="";
358              }
359           }
360           if (sock_name.empty()) {
361             fxwarning(_("Option -s requires an argument.\n"));
362             ::exit(1);
363           }
364           break;
365         }
366         case 'c':
367 #ifndef WIN32
368         case 'd':
369 #endif
370         case 'e':
371         case 'p':
372         case 'q':
373         case 'r':
374         case 't':
375         case 'w':
376         {
377           break;
378         }
379         default: {
380           fxwarning(_("Unrecognized option: -%c\n"), argv[i][1]);
381           ::exit(1);
382         }
383       }
384     }
385   }
386   if ( sock_name.empty() ) { sock_name="DEFAULT"; }
387   server_name=sock_name.text();
388 #ifndef WIN32
389   char*d=DisplayString(getDisplay());
390   if (d) {
391     if (*d!=':') { sock_name.append('_'); }
392     sock_name.append(d);
393     FXSystem::setEnvironment("DISPLAY", d);
394   }
395 #endif
396   sock_name.upper();
397   server_name.upper();
398   for (unsigned char c=1; ; c++) {
399     if ( ((c>='A')&&(c<='Z')) || ((c>='0')&&(c<='9')) || (c=='_') ) { continue; }
400     sock_name.substitute(c, '_', true);
401     server_name.substitute(c, '_', true);
402     if (c==255) { break; }
403   }
404   server_name.lower();
405   sessionfile=configdir+"sessions"+PATHSEP;
406   CreatePathOrDie(sessionfile);
407   sessionfile.append(sock_name);
408   settingsfile=configdir+"settings";
409 #ifdef WIN32
410   sock_name.prepend(APP_NAME"_");
411   settingsfile.append(".ini");
412 #else
413   FXString serverdir=configdir+"servers"+PATHSEP;
414   CreatePathOrDie(serverdir);
415   sock_name.prepend(serverdir);
416   if (use_xdg_config()) { settingsfile.append(".rc"); }
417 #endif
418   ParseCommandLine();
419   ipc=new InterProc(this, sock_name);
420   if (ipc->ClientSend(NULL, commands)) {
421     delete ipc;
422     destroy();
423     ::exit(0);
424   } else {
425 #if defined(WIN32) && !defined(FOX_1_6)
426     setToolTipTime(2000000000);
427     setToolTipPause(250000000);
428 #endif
429     TopWinPub::instantiate(this);
430     if (getRootWindow() && getRootWindow()->id()) { TopWinPub::create(); } else { create(); }
431     ipc->StartServer(TopWinPub::instance(),this,ID_IPC_EXEC);
432 #if !(defined(WIN32) || defined(__minix))
433     fclose(stdin);
434     stdin=fopen(NULL_FILE, "r");
435 #endif
436   }
437 }
438 
439 
440 
441 extern "C" { int ini_sort(const char *filename); }
442 
443 
exit(FXint code)444 void AppClass::exit(FXint code)
445 {
446   ipc->StopServer();
447   delete ipc;
448   Theme::done();
449   FXApp::exit(code);
450   ini_sort(settingsfile.text());
451 }
452 
453 
454 
get_config_name(int argc,char * argv[],FXString & cfg_name)455 static bool get_config_name(int argc, char *argv[], FXString &cfg_name)
456 {
457   cfg_name="";
458   int i;
459   for (i=1; i<argc; i++) {
460     const char*arg=argv[i];
461     if ( (arg[0]=='-') && (arg[1]=='c') ) {
462       if (arg[2]) { arg+=2; } else {
463         if ((i+1)>=argc) {
464           fxwarning("%s: %s\n", argv[0], _("Error: option -c requires an argument."));
465           return false;
466         }
467         arg=argv[i+1];
468       }
469       int n=strlen(arg);
470       int j;
471       if (n<4) {
472         fxwarning(
473           "\n%s:\n  %s\n\n", argv[0], _("Error: Config name length must be at least 4 characters."));
474         return false;
475       }
476       if (n>32) {
477         fxwarning(
478           "\n%s:\n %s\n\n", argv[0], _("Error: Config name length must not exceed 32 characters."));
479         return false;
480       }
481       for (j=0; j<n; j++) {
482         if (((arg[j]<'a')||(arg[j]>'z'))&&((arg[j]<'0')||(arg[j]>'9'))) {
483           fxwarning(
484           "\n%s:\n %s\n\n",
485           argv[0], _("Error: Config name can have only lowercase [a-z] and numbers [0-9]."));
486           return false;
487         }
488       }
489       cfg_name=arg;
490       break;
491     }
492   }
493   if (cfg_name.empty()) { cfg_name="default"; }
494   cfg_name.prepend(PATHSEP);
495   cfg_name.prepend(APP_VENDOR);
496   return true;
497 }
498 
499 
500 
check_info_args(int argc,char * argv[])501 static void check_info_args(int argc, char *argv[])
502 {
503   for (int i=1; i<argc; i++) {
504     if (argv[i][0]=='-') {
505       switch (argv[i][1]) {
506         case 'v': {
507           AppAbout::VersionInfo();
508           exit(0);
509         }
510         case 'h': {
511           usage(argv[0]);
512           exit(0);
513         }
514         case '-': {
515           if ((strcmp(argv[i],"--help")==0)||(strcmp(argv[i],_("--help"))==0)) {
516             usage(argv[0]);
517             exit(0);
518           }
519         }
520       }
521     }
522   }
523 }
524 
525 
526 
main(int argc,char * argv[])527 int main(int argc, char *argv[])
528 {
529 #ifdef ENABLE_NLS
530   bindtextdomain(PACKAGE, LOCALEDIR);
531   textdomain(PACKAGE);
532 #endif
533   if ((argc==2)&&(strcmp(argv[1],"--dump-lexers")==0)) {
534     TopWinPub::DumpLexers();
535     exit(0);
536   }
537   check_info_args(argc,argv); // Checks for switches that exit after they print some info.
538   FXString cfg_name="";
539   if (!get_config_name(argc,argv,cfg_name)) { exit(1); }
540   AppClass app("settings", cfg_name);
541   app.init(argc,argv);
542   return app.run();
543 }
544 
545