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