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