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