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