]> git.decadent.org.uk Git - ion3.git/blob - ioncore/ioncore.c
49da6c715f4dc04d7f3c80fd8e1a416c31bfff41
[ion3.git] / ioncore / ioncore.c
1 /*
2  * ion/ioncore/ioncore.c
3  *
4  * Copyright (c) Tuomo Valkonen 1999-2007. 
5  *
6  * Ion is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  */
11
12 #include <stdlib.h>
13 #include <stdio.h>
14 #include <unistd.h>
15 #include <fcntl.h>
16 #include <string.h>
17 #include <errno.h>
18 #include <sys/types.h>
19 #include <ctype.h>
20 #ifndef CF_NO_LOCALE
21 #include <locale.h>
22 #include <langinfo.h>
23 #include <libintl.h>
24 #endif
25
26 #include <libtu/util.h>
27 #include <libtu/optparser.h>
28 #include <libextl/readconfig.h>
29 #include <libextl/extl.h>
30 #include <libmainloop/select.h>
31 #include <libmainloop/signal.h>
32 #include <libmainloop/hooks.h>
33 #include <libmainloop/exec.h>
34
35 #include "common.h"
36 #include "rootwin.h"
37 #include "event.h"
38 #include "cursor.h"
39 #include "global.h"
40 #include "modules.h"
41 #include "eventh.h"
42 #include "ioncore.h"
43 #include "manage.h"
44 #include "conf.h"
45 #include "binding.h"
46 #include "bindmaps.h"
47 #include "strings.h"
48 #include "gr.h"
49 #include "xic.h"
50 #include "netwm.h"
51 #include "focus.h"
52 #include "frame.h"
53 #include "saveload.h"
54 #include "infowin.h"
55 #include "activity.h"
56 #include "group-cw.h"
57 #include "group-ws.h"
58 #include "llist.h"
59 #include "exec.h"
60 #include "screen-notify.h"
61
62
63 #include "../version.h"
64 #include "exports.h"
65
66
67 /*{{{ Variables */
68
69
70 WGlobal ioncore_g;
71
72 static const char *progname="ion";
73
74 static const char ioncore_copy[]=
75     "Ion " ION_VERSION ", copyright (c) Tuomo Valkonen 1999-2007.";
76
77 static const char ioncore_license[]=DUMMY_TR(
78     "This program is free software; you can redistribute it and/or\n"
79     "modify it under the terms of the GNU Lesser General Public\n"
80     "License as published by the Free Software Foundation; either\n"
81     "version 2.1 of the License, or (at your option) any later version.\n"
82     "\n"
83     "This program is distributed in the hope that it will be useful,\n"
84     "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
85     "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n"
86     "Lesser General Public License for more details.\n");
87
88 static const char *ioncore_about=NULL;
89
90 WHook *ioncore_post_layout_setup_hook=NULL;
91 WHook *ioncore_snapshot_hook=NULL;
92 WHook *ioncore_deinit_hook=NULL;
93
94
95 /*}}}*/
96
97
98 /*{{{ warn_nolog */
99
100
101 void ioncore_warn_nolog(const char *str)
102 {
103     fprintf(stderr, "%s: %s\n", libtu_progname(), str);
104 }
105
106
107 /*}}}*/
108
109
110 /*{{{ init_locale */
111
112
113 #ifndef CF_NO_LOCALE
114
115
116 static bool check_encoding()
117 {
118     int i;
119     char chs[8]=" ";
120     wchar_t wc;
121     const char *langi, *ctype, *a, *b;
122     bool enc_check_ok=FALSE;
123
124     langi=nl_langinfo(CODESET);
125     ctype=setlocale(LC_CTYPE, NULL);
126     
127     if(langi==NULL || ctype==NULL)
128         goto integr_err;
129
130     if(strcmp(ctype, "C")==0 || strcmp(ctype, "POSIX")==0)
131         return TRUE;
132     
133     /* Compare encodings case-insensitively, ignoring dashes (-) */
134     a=langi; 
135     b=strchr(ctype, '.');
136     if(b!=NULL){
137         b=b+1;
138         while(1){
139             if(*a=='-'){
140                 a++;
141                 continue;
142             }
143             if(*b=='-'){
144                 b++;
145                 continue;
146             }
147             if(*b=='\0' || *b=='@'){
148                 enc_check_ok=(*a=='\0');
149                 break;
150             }
151             if(*a=='\0' || tolower(*a)!=tolower(*b))
152                 break;
153             a++;
154             b++;
155         }
156         if(!enc_check_ok)
157             goto integr_err;
158     }else{
159         ioncore_warn_nolog(TR("No encoding given in LC_CTYPE."));
160     }
161         
162     if(strcasecmp(langi, "UTF-8")==0 || strcasecmp(langi, "UTF8")==0){
163         ioncore_g.enc_sb=FALSE;
164         ioncore_g.enc_utf8=TRUE;
165         ioncore_g.use_mb=TRUE;
166         return TRUE;
167     }
168     
169     for(i=0; i<256; i++){
170         chs[0]=i;
171         if(mbtowc(&wc, chs, 8)==-1){
172             /* Doesn't look like a single-byte encoding. */
173             break;
174         }
175         
176     }
177     
178     if(i==256){
179         /* Seems like a single-byte encoding... */
180         ioncore_g.use_mb=TRUE;
181         return TRUE;
182     }
183
184     if(mbtowc(NULL, NULL, 0)!=0){
185         warn("Statefull encodings are unsupported.");
186         return FALSE;
187     }
188     
189     ioncore_g.enc_sb=FALSE;
190     ioncore_g.use_mb=TRUE;
191
192     return TRUE;
193     
194 integr_err:
195     warn("Cannot verify locale encoding setting integrity "
196          "(LC_CTYPE=%s, nl_langinfo(CODESET)=%s). "
197          "The LC_CTYPE environment variable should be of the form "
198          "language_REGION.encoding (e.g. en_GB.UTF-8), and encoding "
199          "should match the nl_langinfo value above.", ctype, langi);
200     return FALSE;
201 }
202
203
204 static bool init_locale()
205 {
206     const char *p;
207     
208     p=setlocale(LC_ALL, "");
209     
210     if(p==NULL){
211         warn("setlocale() call failed.");
212         return FALSE;
213     }
214
215     /*if(strcmp(p, "C")==0 || strcmp(p, "POSIX")==0)
216         return TRUE;*/
217
218     if(!XSupportsLocale()){
219         warn("XSupportsLocale() failed.");
220     }else{
221         if(check_encoding())
222             return TRUE;
223     }
224     
225     warn("Reverting locale settings to \"C\".");
226     
227     if(setlocale(LC_ALL, "C")==NULL)
228         warn("setlocale() call failed.");
229         
230     return FALSE;
231 }
232
233 #define TEXTDOMAIN "ion3"
234
235 static bool init_messages(const char *localedir)
236 {
237     if(bindtextdomain(TEXTDOMAIN, localedir)==NULL){
238         warn_err_obj("bindtextdomain");
239         return FALSE;
240     }else if(textdomain(TEXTDOMAIN)==NULL){
241         warn_err_obj("textdomain");
242         return FALSE;
243     }
244     return TRUE;
245 }
246
247
248 #endif
249
250
251 /*}}}*/
252
253
254 /*{{{ ioncore_init */
255
256
257 #define INIT_HOOK_(NM)                             \
258     NM=mainloop_register_hook(#NM, create_hook()); \
259     if(NM==NULL) return FALSE
260
261 #define ADD_HOOK_(NM, FN)                          \
262     if(!hook_add(NM, (void (*)())FN)) return FALSE
263
264 #define INIT_HOOK(NM, DFLT) INIT_HOOK_(NM); ADD_HOOK_(NM, DFLT)
265
266 static bool init_hooks()
267 {
268     INIT_HOOK_(ioncore_post_layout_setup_hook);
269     INIT_HOOK_(ioncore_snapshot_hook);
270     INIT_HOOK_(ioncore_deinit_hook);
271     INIT_HOOK_(screen_managed_changed_hook);
272     INIT_HOOK_(frame_managed_changed_hook);
273     INIT_HOOK_(clientwin_mapped_hook);
274     INIT_HOOK_(clientwin_unmapped_hook);
275     INIT_HOOK_(clientwin_property_change_hook);
276     
277     INIT_HOOK_(region_notify_hook);
278     ADD_HOOK_(region_notify_hook, ioncore_frame_quasiactivation_notify);
279     ADD_HOOK_(region_notify_hook, ioncore_screen_activity_notify);
280     
281     INIT_HOOK(clientwin_do_manage_alt, clientwin_do_manage_default);
282     INIT_HOOK(ioncore_handle_event_alt, ioncore_handle_event);
283     INIT_HOOK(region_do_warp_alt, region_do_warp_default);
284     INIT_HOOK(ioncore_exec_environ_hook, ioncore_setup_environ);
285     
286     mainloop_sigchld_hook=mainloop_register_hook("ioncore_sigchld_hook",
287                                                  create_hook());
288     mainloop_sigusr2_hook=mainloop_register_hook("ioncore_sigusr2_hook",
289                                                  create_hook());
290     
291     return TRUE;
292 }
293
294
295 static bool register_classes()
296 {
297     int fail=0;
298     
299     fail|=!ioncore_register_regclass(&CLASSDESCR(WClientWin), 
300                                      (WRegionLoadCreateFn*)clientwin_load);
301     fail|=!ioncore_register_regclass(&CLASSDESCR(WMPlex), 
302                                      (WRegionLoadCreateFn*)mplex_load);
303     fail|=!ioncore_register_regclass(&CLASSDESCR(WFrame), 
304                                      (WRegionLoadCreateFn*)frame_load);
305     fail|=!ioncore_register_regclass(&CLASSDESCR(WInfoWin), 
306                                      (WRegionLoadCreateFn*)infowin_load);
307     fail|=!ioncore_register_regclass(&CLASSDESCR(WGroupCW), 
308                                      (WRegionLoadCreateFn*)groupcw_load);
309     fail|=!ioncore_register_regclass(&CLASSDESCR(WGroupWS), 
310                                      (WRegionLoadCreateFn*)groupws_load);
311     
312     return !fail;
313 }
314
315
316 #define INITSTR(NM)                             \
317     ioncore_g.notifies.NM=stringstore_alloc(#NM); \
318     if(ioncore_g.notifies.NM==STRINGID_NONE) return FALSE;
319     
320 static bool init_global()
321 {
322     /* argc, argv must be set be the program */
323     ioncore_g.dpy=NULL;
324     ioncore_g.display=NULL;
325
326     ioncore_g.sm_client_id=NULL;
327     ioncore_g.rootwins=NULL;
328     ioncore_g.screens=NULL;
329     ioncore_g.focus_next=NULL;
330     ioncore_g.warp_next=FALSE;
331     ioncore_g.focus_next_source=IONCORE_FOCUSNEXT_OTHER;
332     
333     ioncore_g.focus_current=NULL;
334
335     ioncore_g.input_mode=IONCORE_INPUTMODE_NORMAL;
336     ioncore_g.opmode=IONCORE_OPMODE_INIT;
337     ioncore_g.dblclick_delay=CF_DBLCLICK_DELAY;
338     ioncore_g.opaque_resize=0;
339     ioncore_g.warp_enabled=TRUE;
340     ioncore_g.switchto_new=TRUE;
341     ioncore_g.no_mousefocus=FALSE;
342     ioncore_g.unsqueeze_enabled=TRUE;
343     ioncore_g.autoraise=TRUE;
344     
345     ioncore_g.enc_utf8=FALSE;
346     ioncore_g.enc_sb=TRUE;
347     ioncore_g.use_mb=FALSE;
348     
349     ioncore_g.screen_notify=TRUE;
350     
351     ioncore_g.frame_default_index=LLIST_INDEX_AFTER_CURRENT_ACT;
352     
353     ioncore_g.framed_transients=TRUE;
354     
355     INITSTR(activated);
356     INITSTR(inactivated);
357     INITSTR(activity);
358     INITSTR(sub_activity);
359     INITSTR(name);
360     INITSTR(unset_manager);
361     INITSTR(set_manager);
362     INITSTR(unset_return);
363     INITSTR(set_return);
364     INITSTR(pseudoactivated);
365     INITSTR(pseudoinactivated);
366     INITSTR(tag);
367     INITSTR(deinit);
368     INITSTR(map);
369     INITSTR(unmap);
370     
371     return TRUE;
372 }
373
374
375 bool ioncore_init(const char *prog, int argc, char *argv[],
376                   const char *localedir)
377 {
378     if(!init_global())
379         return FALSE;
380     
381     progname=prog;
382     ioncore_g.argc=argc;
383     ioncore_g.argv=argv;
384
385 #ifndef CF_NO_LOCALE    
386     init_locale();
387     init_messages(localedir);
388 #endif
389
390     ioncore_about=scat3(ioncore_copy, "\n\n", TR(ioncore_license));
391     
392     if(!ioncore_init_bindmaps())
393         return FALSE;
394     
395     if(!register_classes())
396         return FALSE;
397
398     if(!init_hooks())
399         return FALSE;
400
401     if(!ioncore_init_module_support())
402         return FALSE;
403
404     return TRUE;
405 }
406
407
408 /*}}}*/
409
410
411 /*{{{ ioncore_startup */
412
413
414 static void ioncore_init_session(const char *display)
415 {
416     const char *dpyend=NULL;
417     char *tmp=NULL, *colon=NULL;
418     const char *sm=getenv("SESSION_MANAGER");
419     
420     if(sm!=NULL)
421         ioncore_load_module("mod_sm");
422
423     if(extl_sessiondir()!=NULL)
424         return;
425     
426     /* Not running under SM; use display-specific directory */
427     dpyend=strchr(display, ':');
428     if(dpyend!=NULL)
429         dpyend=strchr(dpyend, '.');
430     if(dpyend==NULL){    
431         libtu_asprintf(&tmp, "default-session-%s", display);
432     }else{
433         libtu_asprintf(&tmp, "default-session-%.*s",
434                        (int)(dpyend-display), display);
435     }
436     
437     if(tmp==NULL)
438         return;
439     
440     colon=tmp;
441     while(1){
442         colon=strchr(colon, ':');
443         if(colon==NULL)
444             break;
445         *colon='-';
446     }
447     
448     extl_set_sessiondir(tmp);
449     free(tmp);
450 }
451     
452
453 static bool ioncore_init_x(const char *display, int stflags)
454 {
455     Display *dpy;
456     int i, drw, nrw;
457     static bool called=FALSE;
458
459     /* Sorry, this function can not be re-entered due to laziness
460      * towards implementing checking of things already initialized.
461      * Nobody would call this twice anyway.
462      */
463     assert(!called);
464     called=TRUE;
465
466     /* Open the display. */
467     dpy=XOpenDisplay(display);
468     
469     if(dpy==NULL){
470         warn(TR("Could not connect to X display '%s'"), 
471              XDisplayName(display));
472         return FALSE;
473     }
474
475     if(stflags&IONCORE_STARTUP_ONEROOT){
476         drw=DefaultScreen(dpy);
477         nrw=drw+1;
478     }else{
479         drw=0;
480         nrw=ScreenCount(dpy);
481     }
482     
483     /* Initialize */
484     if(display!=NULL){
485         ioncore_g.display=scopy(display);
486         if(ioncore_g.display==NULL){
487             XCloseDisplay(dpy);
488             return FALSE;
489         }
490     }
491     
492     ioncore_g.dpy=dpy;
493     ioncore_g.win_context=XUniqueContext();
494     ioncore_g.conn=ConnectionNumber(dpy);
495     
496     cloexec_braindamage_fix(ioncore_g.conn);
497     
498     ioncore_g.atom_wm_state=XInternAtom(dpy, "WM_STATE", False);
499     ioncore_g.atom_wm_change_state=XInternAtom(dpy, "WM_CHANGE_STATE", False);
500     ioncore_g.atom_wm_protocols=XInternAtom(dpy, "WM_PROTOCOLS", False);
501     ioncore_g.atom_wm_delete=XInternAtom(dpy, "WM_DELETE_WINDOW", False);
502     ioncore_g.atom_wm_take_focus=XInternAtom(dpy, "WM_TAKE_FOCUS", False);
503     ioncore_g.atom_wm_colormaps=XInternAtom(dpy, "WM_COLORMAP_WINDOWS", False);
504     ioncore_g.atom_wm_window_role=XInternAtom(dpy, "WM_WINDOW_ROLE", False);
505     ioncore_g.atom_checkcode=XInternAtom(dpy, "_ION_CWIN_RESTART_CHECKCODE", False);
506     ioncore_g.atom_selection=XInternAtom(dpy, "_ION_SELECTION_STRING", False);
507     ioncore_g.atom_mwm_hints=XInternAtom(dpy, "_MOTIF_WM_HINTS", False);
508
509     ioncore_init_xim();
510     ioncore_init_bindings();
511     ioncore_init_cursors();
512
513     netwm_init();
514     
515     ioncore_init_session(XDisplayName(display));
516
517     for(i=drw; i<nrw; i++)
518         create_rootwin(i);
519
520     if(ioncore_g.rootwins==NULL){
521         if(nrw-drw>1)
522             warn(TR("Could not find a screen to manage."));
523         return FALSE;
524     }
525
526     if(!mainloop_register_input_fd(ioncore_g.conn, NULL,
527                                    ioncore_x_connection_handler)){
528         return FALSE;
529     }
530     
531     return TRUE;
532 }
533
534
535 static void set_initial_focus()
536 {
537     Window root=None, win=None;
538     int x, y, wx, wy;
539     uint mask;
540     WScreen *scr;
541     WWindow *wwin;
542     
543     XQueryPointer(ioncore_g.dpy, None, &root, &win,
544                   &x, &y, &wx, &wy, &mask);
545     
546     FOR_ALL_SCREENS(scr){
547         Window scrroot=region_root_of((WRegion*)scr);
548         if(scrroot==root && rectangle_contains(&REGION_GEOM(scr), x, y)){
549             break;
550         }
551     }
552     
553     if(scr==NULL)
554         scr=ioncore_g.screens;
555     
556     region_focuslist_push((WRegion*)scr);
557     region_do_set_focus((WRegion*)scr, FALSE);
558 }
559
560
561 bool ioncore_startup(const char *display, const char *cfgfile,
562                      int stflags)
563 {
564     WRootWin *rootwin;
565     sigset_t inittrap;
566
567     /* Don't trap termination signals just yet. */
568     sigemptyset(&inittrap);
569     sigaddset(&inittrap, SIGALRM);
570     sigaddset(&inittrap, SIGCHLD);
571     sigaddset(&inittrap, SIGPIPE);
572     sigaddset(&inittrap, SIGUSR2);
573     mainloop_trap_signals(&inittrap);
574
575     if(!extl_init())
576         return FALSE;
577
578     ioncore_register_exports();
579     
580     if(!ioncore_init_x(display, stflags))
581         return FALSE;
582
583     gr_read_config();
584
585     if(!extl_read_config("ioncore_ext", NULL, TRUE))
586         return FALSE;
587     
588     ioncore_read_main_config(cfgfile);
589     
590     if(!ioncore_init_layout())
591         return FALSE;
592     
593     hook_call_v(ioncore_post_layout_setup_hook);
594     
595     FOR_ALL_ROOTWINS(rootwin)
596         rootwin_manage_initial_windows(rootwin);
597     
598     set_initial_focus();
599     
600     return TRUE;
601 }
602
603
604 /*}}}*/
605
606
607 /*{{{ ioncore_deinit */
608
609
610 void ioncore_deinit()
611 {
612     Display *dpy;
613     WRootWin *rootwin;
614     
615     ioncore_g.opmode=IONCORE_OPMODE_DEINIT;
616     
617     if(ioncore_g.dpy==NULL)
618         return;
619
620     hook_call_v(ioncore_deinit_hook);
621
622     while(ioncore_g.screens!=NULL)
623         destroy_obj((Obj*)ioncore_g.screens);
624
625     /*ioncore_unload_modules();*/
626
627     while(ioncore_g.rootwins!=NULL)
628         destroy_obj((Obj*)ioncore_g.rootwins);
629
630     ioncore_deinit_bindmaps();
631
632     mainloop_unregister_input_fd(ioncore_g.conn);
633     
634     dpy=ioncore_g.dpy;
635     ioncore_g.dpy=NULL;
636     
637     XSync(dpy, True);
638     XCloseDisplay(dpy);
639     
640     extl_deinit();
641 }
642
643
644 /*}}}*/
645
646
647 /*{{{ Miscellaneous */
648
649
650 /*EXTL_DOC
651  * Is Ion supporting locale-specifically multibyte-encoded strings?
652  */
653 EXTL_SAFE
654 EXTL_EXPORT
655 bool ioncore_is_i18n()
656 {
657     return ioncore_g.use_mb;
658 }
659
660
661 /*EXTL_DOC
662  * Returns Ioncore version string.
663  */
664 EXTL_SAFE
665 EXTL_EXPORT
666 const char *ioncore_version()
667 {
668     return ION_VERSION;
669 }
670
671 /*EXTL_DOC
672  * Returns the name of program using Ioncore.
673  */
674 EXTL_SAFE
675 EXTL_EXPORT
676 const char *ioncore_progname()
677 {
678     return progname;
679 }
680
681
682 /*EXTL_DOC
683  * Returns an about message (version, author, copyright notice).
684  */
685 EXTL_SAFE
686 EXTL_EXPORT
687 const char *ioncore_aboutmsg()
688 {
689     return ioncore_about;
690 }
691
692
693 /*}}}*/
694