]> git.decadent.org.uk Git - ion3.git/blob - ioncore/ioncore.c
0f1744c640636f82f8d828e754197288a56ee147
[ion3.git] / ioncore / ioncore.c
1 /*
2  * ion/ioncore/ioncore.c
3  *
4  * Copyright (c) Tuomo Valkonen 1999-2007. 
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 #include <libintl.h>
22 #endif
23
24 #include <libtu/util.h>
25 #include <libtu/optparser.h>
26 #include <libextl/readconfig.h>
27 #include <libextl/extl.h>
28 #include <libmainloop/select.h>
29 #include <libmainloop/signal.h>
30 #include <libmainloop/hooks.h>
31 #include <libmainloop/exec.h>
32
33 #include "common.h"
34 #include "rootwin.h"
35 #include "event.h"
36 #include "cursor.h"
37 #include "global.h"
38 #include "modules.h"
39 #include "eventh.h"
40 #include "ioncore.h"
41 #include "manage.h"
42 #include "conf.h"
43 #include "binding.h"
44 #include "bindmaps.h"
45 #include "strings.h"
46 #include "gr.h"
47 #include "xic.h"
48 #include "netwm.h"
49 #include "focus.h"
50 #include "frame.h"
51 #include "saveload.h"
52 #include "infowin.h"
53 #include "activity.h"
54 #include "group-cw.h"
55 #include "group-ws.h"
56 #include "llist.h"
57 #include "exec.h"
58 #include "screen-notify.h"
59 #include "key.h"
60
61
62 #include "../version.h"
63 #include "exports.h"
64
65
66 /*{{{ Variables */
67
68
69 WGlobal ioncore_g;
70
71 static const char *progname="ion";
72
73 static const char ioncore_copy[]=
74     "Ion " ION_VERSION ", copyright (c) Tuomo Valkonen 1999-2007.";
75
76 static const char ioncore_license[]=DUMMY_TR(
77     "This program is free software; you can redistribute it and/or\n"
78     "modify it under the terms of the GNU Lesser General Public\n"
79     "License as published by the Free Software Foundation; either\n"
80     "version 2.1 of the License, or (at your option) any later version.\n"
81     "\n"
82     "This program is distributed in the hope that it will be useful,\n"
83     "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
84     "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n"
85     "Lesser General Public License for more details.\n");
86
87 static const char *ioncore_about=NULL;
88
89 WHook *ioncore_post_layout_setup_hook=NULL;
90 WHook *ioncore_snapshot_hook=NULL;
91 WHook *ioncore_deinit_hook=NULL;
92
93
94 /*}}}*/
95
96
97 /*{{{ warn_nolog */
98
99
100 void ioncore_warn_nolog(const char *str)
101 {
102     fprintf(stderr, "%s: %s\n", libtu_progname(), str);
103 }
104
105
106 /*}}}*/
107
108
109 /*{{{ init_locale */
110
111
112 #ifndef CF_NO_LOCALE
113
114
115 static bool check_encoding()
116 {
117     int i;
118     char chs[8]=" ";
119     wchar_t wc;
120     const char *langi, *ctype, *a, *b;
121     bool enc_check_ok=FALSE;
122
123     langi=nl_langinfo(CODESET);
124     ctype=setlocale(LC_CTYPE, NULL);
125     
126     if(langi==NULL || ctype==NULL)
127         goto integr_err;
128
129     if(strcmp(ctype, "C")==0 || strcmp(ctype, "POSIX")==0)
130         return TRUE;
131     
132     /* Compare encodings case-insensitively, ignoring dashes (-) */
133     a=langi; 
134     b=strchr(ctype, '.');
135     if(b!=NULL){
136         b=b+1;
137         while(1){
138             if(*a=='-'){
139                 a++;
140                 continue;
141             }
142             if(*b=='-'){
143                 b++;
144                 continue;
145             }
146             if(*b=='\0' || *b=='@'){
147                 enc_check_ok=(*a=='\0');
148                 break;
149             }
150             if(*a=='\0' || tolower(*a)!=tolower(*b))
151                 break;
152             a++;
153             b++;
154         }
155         if(!enc_check_ok)
156             goto integr_err;
157     }else{
158         ioncore_warn_nolog(TR("No encoding given in LC_CTYPE."));
159     }
160         
161     if(strcasecmp(langi, "UTF-8")==0 || strcasecmp(langi, "UTF8")==0){
162         ioncore_g.enc_sb=FALSE;
163         ioncore_g.enc_utf8=TRUE;
164         ioncore_g.use_mb=TRUE;
165         return TRUE;
166     }
167     
168     for(i=0; i<256; i++){
169         chs[0]=i;
170         if(mbtowc(&wc, chs, 8)==-1){
171             /* Doesn't look like a single-byte encoding. */
172             break;
173         }
174         
175     }
176     
177     if(i==256){
178         /* Seems like a single-byte encoding... */
179         ioncore_g.use_mb=TRUE;
180         return TRUE;
181     }
182
183     if(mbtowc(NULL, NULL, 0)!=0){
184         warn("Statefull encodings are unsupported.");
185         return FALSE;
186     }
187     
188     ioncore_g.enc_sb=FALSE;
189     ioncore_g.use_mb=TRUE;
190
191     return TRUE;
192     
193 integr_err:
194     warn("Cannot verify locale encoding setting integrity "
195          "(LC_CTYPE=%s, nl_langinfo(CODESET)=%s). "
196          "The LC_CTYPE environment variable should be of the form "
197          "language_REGION.encoding (e.g. en_GB.UTF-8), and encoding "
198          "should match the nl_langinfo value above.", ctype, langi);
199     return FALSE;
200 }
201
202
203 static bool init_locale()
204 {
205     const char *p;
206     
207     p=setlocale(LC_ALL, "");
208     
209     if(p==NULL){
210         warn("setlocale() call failed.");
211         return FALSE;
212     }
213
214     /*if(strcmp(p, "C")==0 || strcmp(p, "POSIX")==0)
215         return TRUE;*/
216
217     if(!XSupportsLocale()){
218         warn("XSupportsLocale() failed.");
219     }else{
220         if(check_encoding())
221             return TRUE;
222     }
223     
224     warn("Reverting locale settings to \"C\".");
225     
226     if(setlocale(LC_ALL, "C")==NULL)
227         warn("setlocale() call failed.");
228         
229     return FALSE;
230 }
231
232 #define TEXTDOMAIN "ion3"
233
234 static bool init_messages(const char *localedir)
235 {
236     if(bindtextdomain(TEXTDOMAIN, localedir)==NULL){
237         warn_err_obj("bindtextdomain");
238         return FALSE;
239     }else if(textdomain(TEXTDOMAIN)==NULL){
240         warn_err_obj("textdomain");
241         return FALSE;
242     }
243     return TRUE;
244 }
245
246
247 #endif
248
249
250 /*}}}*/
251
252
253 /*{{{ ioncore_init */
254
255
256 #define INIT_HOOK_(NM)                             \
257     NM=mainloop_register_hook(#NM, create_hook()); \
258     if(NM==NULL) return FALSE
259
260 #define ADD_HOOK_(NM, FN)                          \
261     if(!hook_add(NM, (void (*)())FN)) return FALSE
262
263 #define INIT_HOOK(NM, DFLT) INIT_HOOK_(NM); ADD_HOOK_(NM, DFLT)
264
265 static bool init_hooks()
266 {
267     INIT_HOOK_(ioncore_post_layout_setup_hook);
268     INIT_HOOK_(ioncore_snapshot_hook);
269     INIT_HOOK_(ioncore_deinit_hook);
270     INIT_HOOK_(screen_managed_changed_hook);
271     INIT_HOOK_(frame_managed_changed_hook);
272     INIT_HOOK_(clientwin_mapped_hook);
273     INIT_HOOK_(clientwin_unmapped_hook);
274     INIT_HOOK_(clientwin_property_change_hook);
275     INIT_HOOK_(ioncore_submap_ungrab_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_dockapp_hack=XInternAtom(dpy, "_ION_DOCKAPP_HACK", False);
508     ioncore_g.atom_mwm_hints=XInternAtom(dpy, "_MOTIF_WM_HINTS", False);
509
510     ioncore_init_xim();
511     ioncore_init_bindings();
512     ioncore_init_cursors();
513
514     netwm_init();
515     
516     ioncore_init_session(XDisplayName(display));
517
518     for(i=drw; i<nrw; i++)
519         create_rootwin(i);
520
521     if(ioncore_g.rootwins==NULL){
522         if(nrw-drw>1)
523             warn(TR("Could not find a screen to manage."));
524         return FALSE;
525     }
526
527     if(!mainloop_register_input_fd(ioncore_g.conn, NULL,
528                                    ioncore_x_connection_handler)){
529         return FALSE;
530     }
531     
532     return TRUE;
533 }
534
535
536 static void set_initial_focus()
537 {
538     Window root=None, win=None;
539     int x, y, wx, wy;
540     uint mask;
541     WScreen *scr;
542     WWindow *wwin;
543     
544     XQueryPointer(ioncore_g.dpy, None, &root, &win,
545                   &x, &y, &wx, &wy, &mask);
546     
547     FOR_ALL_SCREENS(scr){
548         Window scrroot=region_root_of((WRegion*)scr);
549         if(scrroot==root && rectangle_contains(&REGION_GEOM(scr), x, y)){
550             break;
551         }
552     }
553     
554     if(scr==NULL)
555         scr=ioncore_g.screens;
556     
557     region_focuslist_push((WRegion*)scr);
558     region_do_set_focus((WRegion*)scr, FALSE);
559 }
560
561
562 bool ioncore_startup(const char *display, const char *cfgfile,
563                      int stflags)
564 {
565     WRootWin *rootwin;
566     sigset_t inittrap;
567
568     /* Don't trap termination signals just yet. */
569     sigemptyset(&inittrap);
570     sigaddset(&inittrap, SIGALRM);
571     sigaddset(&inittrap, SIGCHLD);
572     sigaddset(&inittrap, SIGPIPE);
573     sigaddset(&inittrap, SIGUSR2);
574     mainloop_trap_signals(&inittrap);
575
576     if(!extl_init())
577         return FALSE;
578
579     ioncore_register_exports();
580     
581     if(!ioncore_init_x(display, stflags))
582         return FALSE;
583
584     gr_read_config();
585
586     if(!extl_read_config("ioncore_ext", NULL, TRUE))
587         return FALSE;
588     
589     ioncore_read_main_config(cfgfile);
590     
591     if(!ioncore_init_layout())
592         return FALSE;
593     
594     hook_call_v(ioncore_post_layout_setup_hook);
595     
596     FOR_ALL_ROOTWINS(rootwin)
597         rootwin_manage_initial_windows(rootwin);
598     
599     set_initial_focus();
600     
601     return TRUE;
602 }
603
604
605 /*}}}*/
606
607
608 /*{{{ ioncore_deinit */
609
610
611 void ioncore_deinit()
612 {
613     Display *dpy;
614     WRootWin *rootwin;
615     
616     ioncore_g.opmode=IONCORE_OPMODE_DEINIT;
617     
618     if(ioncore_g.dpy==NULL)
619         return;
620
621     hook_call_v(ioncore_deinit_hook);
622
623     while(ioncore_g.screens!=NULL)
624         destroy_obj((Obj*)ioncore_g.screens);
625
626     /*ioncore_unload_modules();*/
627
628     while(ioncore_g.rootwins!=NULL)
629         destroy_obj((Obj*)ioncore_g.rootwins);
630
631     ioncore_deinit_bindmaps();
632
633     mainloop_unregister_input_fd(ioncore_g.conn);
634     
635     dpy=ioncore_g.dpy;
636     ioncore_g.dpy=NULL;
637     
638     XSync(dpy, True);
639     XCloseDisplay(dpy);
640     
641     extl_deinit();
642 }
643
644
645 /*}}}*/
646
647
648 /*{{{ Miscellaneous */
649
650
651 /*EXTL_DOC
652  * Is Ion supporting locale-specifically multibyte-encoded strings?
653  */
654 EXTL_SAFE
655 EXTL_EXPORT
656 bool ioncore_is_i18n()
657 {
658     return ioncore_g.use_mb;
659 }
660
661
662 /*EXTL_DOC
663  * Returns Ioncore version string.
664  */
665 EXTL_SAFE
666 EXTL_EXPORT
667 const char *ioncore_version()
668 {
669     return ION_VERSION;
670 }
671
672 /*EXTL_DOC
673  * Returns the name of program using Ioncore.
674  */
675 EXTL_SAFE
676 EXTL_EXPORT
677 const char *ioncore_progname()
678 {
679     return progname;
680 }
681
682
683 /*EXTL_DOC
684  * Returns an about message (version, author, copyright notice).
685  */
686 EXTL_SAFE
687 EXTL_EXPORT
688 const char *ioncore_aboutmsg()
689 {
690     return ioncore_about;
691 }
692
693
694 /*}}}*/
695