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