Spell 'serialize' with a 'z'
[notion.git] / ioncore / ioncore.c
blobbb7b06f185fd1e1895b60c04d43308fb50e4f0ef
1 /*
2 * ion/ioncore/ioncore.c
4 * Copyright (c) The Notion Team 2011.
5 * Copyright (c) Tuomo Valkonen 1999-2009.
7 * See the included file LICENSE for details.
8 */
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <unistd.h>
13 #include <fcntl.h>
14 #include <string.h>
15 #include <strings.h>
16 #include <errno.h>
17 #include <sys/types.h>
18 #include <ctype.h>
19 #ifndef CF_NO_LOCALE
20 #include <locale.h>
21 #include <langinfo.h>
22 #endif
23 #ifndef CF_NO_GETTEXT
24 #include <libintl.h>
25 #endif
26 #include <stdarg.h>
27 #include <X11/extensions/shape.h>
28 #include <X11/extensions/Xext.h>
30 #include <libtu/util.h>
31 #include <libtu/optparser.h>
32 #include <libextl/readconfig.h>
33 #include <libextl/extl.h>
34 #include <libmainloop/select.h>
35 #include <libmainloop/signal.h>
36 #include <libmainloop/hooks.h>
37 #include <libmainloop/exec.h>
39 #include "common.h"
40 #include "rootwin.h"
41 #include "event.h"
42 #include "cursor.h"
43 #include "global.h"
44 #include "modules.h"
45 #include "eventh.h"
46 #include "ioncore.h"
47 #include "manage.h"
48 #include "conf.h"
49 #include "binding.h"
50 #include "bindmaps.h"
51 #include "strings.h"
52 #include "gr.h"
53 #include "xic.h"
54 #include "netwm.h"
55 #include "focus.h"
56 #include "frame.h"
57 #include "saveload.h"
58 #include "infowin.h"
59 #include "activity.h"
60 #include "group-cw.h"
61 #include "group-ws.h"
62 #include "llist.h"
63 #include "exec.h"
64 #include "screen-notify.h"
65 #include "key.h"
66 #include "log.h"
69 #include "../version.h"
70 #include "exports.h"
73 /*{{{ Variables */
76 WGlobal ioncore_g;
78 static const char *progname="notion";
80 static const char ioncore_copy[]=
81 "Notion " NOTION_VERSION ", see the README for copyright details.";
83 static const char ioncore_license[]=DUMMY_TR(
84 "This software is licensed under the GNU Lesser General Public License\n"
85 "(LGPL), version 2.1, extended with terms applying to the use of the\n"
86 "former name of the project, Ion(tm), unless otherwise indicated in\n"
87 "components taken from elsewhere. For details, see the file LICENSE\n"
88 "that you should have received with this software.\n"
89 "\n"
90 "This program is distributed in the hope that it will be useful,\n"
91 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
92 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n");
94 static const char *ioncore_about=NULL;
96 WHook *ioncore_post_layout_setup_hook=NULL;
97 WHook *ioncore_snapshot_hook=NULL;
98 WHook *ioncore_deinit_hook=NULL;
101 /*}}}*/
104 /*{{{ warn_nolog */
107 void ioncore_warn_nolog(const char *str, ...)
109 va_list args;
111 va_start(args, str);
113 if(ioncore_g.opmode==IONCORE_OPMODE_INIT){
114 fprintf(stderr, "%s: ", libtu_progname());
115 vfprintf(stderr, str, args);
116 fprintf(stderr, "\n");
117 }else{
118 warn_v(str, args);
121 va_end(args);
124 /*}}}*/
127 /*{{{ init_locale */
130 #ifndef CF_NO_LOCALE
133 static bool check_encoding()
135 int i;
136 char chs[8]=" ";
137 wchar_t wc;
138 const char *langi, *ctype, *a, *b;
139 bool enc_check_ok=FALSE;
141 langi=nl_langinfo(CODESET);
142 ctype=setlocale(LC_CTYPE, NULL);
144 if(langi==NULL || ctype==NULL)
145 goto integr_err;
147 if(strcmp(ctype, "C")==0 || strcmp(ctype, "POSIX")==0)
148 return TRUE;
150 /* Compare encodings case-insensitively, ignoring dashes (-) */
151 a=langi;
152 b=strchr(ctype, '.');
153 if(b!=NULL){
154 b=b+1;
155 while(1){
156 if(*a=='-'){
157 a++;
158 continue;
160 if(*b=='-'){
161 b++;
162 continue;
164 if(*b=='\0' || *b=='@'){
165 enc_check_ok=(*a=='\0');
166 break;
168 if(*a=='\0' || tolower(*a)!=tolower(*b))
169 break;
170 a++;
171 b++;
173 if(!enc_check_ok)
174 goto integr_err;
175 }else{
176 ioncore_warn_nolog(TR("No encoding given in LC_CTYPE."));
179 if(strcasecmp(langi, "UTF-8")==0 || strcasecmp(langi, "UTF8")==0){
180 ioncore_g.enc_sb=FALSE;
181 ioncore_g.enc_utf8=TRUE;
182 ioncore_g.use_mb=TRUE;
183 return TRUE;
186 for(i=0; i<256; i++){
187 chs[0]=i;
188 if(mbtowc(&wc, chs, 8)==-1){
189 /* Doesn't look like a single-byte encoding. */
190 break;
195 if(i==256){
196 /* Seems like a single-byte encoding... */
197 ioncore_g.use_mb=TRUE;
198 return TRUE;
201 if(mbtowc(NULL, NULL, 0)!=0){
202 warn(TR("Statefull encodings are unsupported."));
203 return FALSE;
206 ioncore_g.enc_sb=FALSE;
207 ioncore_g.use_mb=TRUE;
209 return TRUE;
211 integr_err:
212 warn(TR("Cannot verify locale encoding setting integrity "
213 "(LC_CTYPE=%s, nl_langinfo(CODESET)=%s). "
214 "The LC_CTYPE environment variable should be of the form "
215 "language_REGION.encoding (e.g. en_GB.UTF-8), and encoding "
216 "should match the nl_langinfo value above."), ctype, langi);
217 return FALSE;
221 static bool init_locale()
223 const char *p;
225 p=setlocale(LC_ALL, "");
227 if(p==NULL){
228 warn("setlocale() call failed.");
229 return FALSE;
232 /*if(strcmp(p, "C")==0 || strcmp(p, "POSIX")==0)
233 return TRUE;*/
235 if(!XSupportsLocale()){
236 warn("XSupportsLocale() failed.");
237 }else{
238 if(check_encoding())
239 return TRUE;
242 warn("Reverting locale settings to \"C\".");
244 if(setlocale(LC_ALL, "C")==NULL)
245 warn("setlocale() call failed.");
247 return FALSE;
250 #endif
252 #ifndef CF_NO_GETTEXT
254 #define TEXTDOMAIN "notion"
256 static bool init_messages(const char *localedir)
258 if(bindtextdomain(TEXTDOMAIN, localedir)==NULL){
259 warn_err_obj("bindtextdomain");
260 return FALSE;
261 }else if(textdomain(TEXTDOMAIN)==NULL){
262 warn_err_obj("textdomain");
263 return FALSE;
265 return TRUE;
269 #endif
272 /*}}}*/
275 /*{{{ ioncore_init */
278 #define INIT_HOOK_(NM) \
279 NM=mainloop_register_hook(#NM, create_hook()); \
280 if(NM==NULL) return FALSE
282 #define ADD_HOOK_(NM, FN) \
283 if(!hook_add(NM, (void (*)())FN)) return FALSE
285 #define INIT_HOOK(NM, DFLT) INIT_HOOK_(NM); ADD_HOOK_(NM, DFLT)
287 static bool init_hooks()
289 INIT_HOOK_(ioncore_post_layout_setup_hook);
290 INIT_HOOK_(ioncore_snapshot_hook);
291 INIT_HOOK_(ioncore_deinit_hook);
292 INIT_HOOK_(screen_managed_changed_hook);
293 INIT_HOOK_(frame_managed_changed_hook);
294 INIT_HOOK_(clientwin_mapped_hook);
295 INIT_HOOK_(clientwin_unmapped_hook);
296 INIT_HOOK_(clientwin_property_change_hook);
297 INIT_HOOK_(ioncore_submap_ungrab_hook);
299 INIT_HOOK_(region_notify_hook);
300 ADD_HOOK_(region_notify_hook, ioncore_screen_activity_notify);
301 ADD_HOOK_(region_notify_hook, ioncore_region_notify);
303 INIT_HOOK(clientwin_do_manage_alt, clientwin_do_manage_default);
304 INIT_HOOK(ioncore_handle_event_alt, ioncore_handle_event);
305 INIT_HOOK(region_do_warp_alt, region_do_warp_default);
306 INIT_HOOK(ioncore_exec_environ_hook, ioncore_setup_environ);
308 mainloop_sigchld_hook=mainloop_register_hook("ioncore_sigchld_hook",
309 create_hook());
310 mainloop_sigusr2_hook=mainloop_register_hook("ioncore_sigusr2_hook",
311 create_hook());
313 return TRUE;
317 static bool register_classes()
319 int fail=0;
321 fail|=!ioncore_register_regclass(&CLASSDESCR(WClientWin),
322 (WRegionLoadCreateFn*)clientwin_load);
323 fail|=!ioncore_register_regclass(&CLASSDESCR(WMPlex),
324 (WRegionLoadCreateFn*)mplex_load);
325 fail|=!ioncore_register_regclass(&CLASSDESCR(WFrame),
326 (WRegionLoadCreateFn*)frame_load);
327 fail|=!ioncore_register_regclass(&CLASSDESCR(WInfoWin),
328 (WRegionLoadCreateFn*)infowin_load);
329 fail|=!ioncore_register_regclass(&CLASSDESCR(WGroupCW),
330 (WRegionLoadCreateFn*)groupcw_load);
331 fail|=!ioncore_register_regclass(&CLASSDESCR(WGroupWS),
332 (WRegionLoadCreateFn*)groupws_load);
334 return !fail;
338 #define INITSTR(NM) \
339 ioncore_g.notifies.NM=stringstore_alloc(#NM); \
340 if(ioncore_g.notifies.NM==STRINGID_NONE) return FALSE;
342 static bool init_global()
344 /* argc, argv must be set be the program */
345 ioncore_g.dpy=NULL;
346 ioncore_g.display=NULL;
348 ioncore_g.sm_client_id=NULL;
349 ioncore_g.rootwins=NULL;
350 ioncore_g.screens=NULL;
351 ioncore_g.focus_next=NULL;
352 ioncore_g.warp_next=FALSE;
353 ioncore_g.focus_next_source=IONCORE_FOCUSNEXT_OTHER;
355 ioncore_g.focuslist=NULL;
356 ioncore_g.focus_current=NULL;
358 ioncore_g.input_mode=IONCORE_INPUTMODE_NORMAL;
359 ioncore_g.opmode=IONCORE_OPMODE_INIT;
360 ioncore_g.dblclick_delay=CF_DBLCLICK_DELAY;
361 ioncore_g.usertime_diff_current=CF_USERTIME_DIFF_CURRENT;
362 ioncore_g.usertime_diff_new=CF_USERTIME_DIFF_NEW;
363 ioncore_g.opaque_resize=0;
364 ioncore_g.warp_enabled=TRUE;
365 ioncore_g.switchto_new=TRUE;
366 ioncore_g.no_mousefocus=FALSE;
367 ioncore_g.unsqueeze_enabled=TRUE;
368 ioncore_g.autoraise=TRUE;
369 ioncore_g.autosave_layout=TRUE;
370 ioncore_g.window_stacking_request=IONCORE_WINDOWSTACKINGREQUEST_IGNORE;
371 ioncore_g.focuslist_insert_delay=CF_FOCUSLIST_INSERT_DELAY;
372 ioncore_g.workspace_indicator_timeout=CF_WORKSPACE_INDICATOR_TIMEOUT;
373 ioncore_g.activity_notification_on_all_screens=FALSE;
375 ioncore_g.enc_utf8=FALSE;
376 ioncore_g.enc_sb=TRUE;
377 ioncore_g.use_mb=FALSE;
379 ioncore_g.screen_notify=TRUE;
381 ioncore_g.frame_default_index=LLIST_INDEX_AFTER_CURRENT_ACT;
383 ioncore_g.framed_transients=TRUE;
385 ioncore_g.shape_extension=FALSE;
386 ioncore_g.shape_event_basep=0;
387 ioncore_g.shape_error_basep=0;
389 INITSTR(activated);
390 INITSTR(inactivated);
391 INITSTR(activity);
392 INITSTR(sub_activity);
393 INITSTR(name);
394 INITSTR(unset_manager);
395 INITSTR(set_manager);
396 INITSTR(unset_return);
397 INITSTR(set_return);
398 INITSTR(pseudoactivated);
399 INITSTR(pseudoinactivated);
400 INITSTR(tag);
401 INITSTR(deinit);
402 INITSTR(map);
403 INITSTR(unmap);
405 return TRUE;
409 bool ioncore_init(const char *prog, int argc, char *argv[],
410 const char *localedir)
412 if(!init_global())
413 return FALSE;
415 progname=prog;
416 ioncore_g.argc=argc;
417 ioncore_g.argv=argv;
419 #ifndef CF_NO_LOCALE
420 init_locale();
421 #endif
422 #ifndef CF_NO_GETTEXT
423 init_messages(localedir);
424 #endif
426 ioncore_about=scat3(ioncore_copy, "\n\n", TR(ioncore_license));
428 if(!ioncore_init_bindmaps())
429 return FALSE;
431 if(!register_classes())
432 return FALSE;
434 if(!init_hooks())
435 return FALSE;
437 if(!ioncore_init_module_support())
438 return FALSE;
440 return TRUE;
444 /*}}}*/
447 /*{{{ ioncore_startup */
450 static void ioncore_init_session(const char *display)
452 const char *dpyend=NULL;
453 char *tmp=NULL, *colon=NULL;
454 const char *sm=getenv("SESSION_MANAGER");
456 if(sm!=NULL)
457 ioncore_load_module("mod_sm");
459 if(extl_sessiondir()!=NULL)
460 return;
462 /* Not running under SM; use display-specific directory */
463 dpyend=strchr(display, ':');
464 if(dpyend!=NULL)
465 dpyend=strchr(dpyend, '.');
466 if(dpyend==NULL){
467 libtu_asprintf(&tmp, "default-session-%s", display);
468 }else{
469 libtu_asprintf(&tmp, "default-session-%.*s",
470 (int)(dpyend-display), display);
473 if(tmp==NULL)
474 return;
476 colon=tmp;
477 while(1){
478 colon=strchr(colon, ':');
479 if(colon==NULL)
480 break;
481 *colon='-';
484 extl_set_sessiondir(tmp);
485 free(tmp);
489 static bool ioncore_init_x(const char *display, int stflags)
491 Display *dpy;
492 int i, drw, nrw;
493 static bool called=FALSE;
495 /* Sorry, this function can not be re-entered due to laziness
496 * towards implementing checking of things already initialized.
497 * Nobody would call this twice anyway.
499 assert(!called);
500 called=TRUE;
502 /* Open the display. */
503 dpy=XOpenDisplay(display);
505 if(dpy==NULL){
506 warn(TR("Could not connect to X display '%s'"),
507 XDisplayName(display));
508 return FALSE;
511 if(stflags&IONCORE_STARTUP_ONEROOT){
512 drw=DefaultScreen(dpy);
513 nrw=drw+1;
514 }else{
515 drw=0;
516 nrw=ScreenCount(dpy);
519 /* Initialize */
520 if(display!=NULL){
521 ioncore_g.display=scopy(display);
522 if(ioncore_g.display==NULL){
523 XCloseDisplay(dpy);
524 return FALSE;
528 ioncore_g.dpy=dpy;
529 ioncore_g.win_context=XUniqueContext();
530 ioncore_g.conn=ConnectionNumber(dpy);
532 if(XShapeQueryExtension(ioncore_g.dpy, &ioncore_g.shape_event_basep,
533 &ioncore_g.shape_error_basep))
534 ioncore_g.shape_extension=TRUE;
535 else
536 XMissingExtension(ioncore_g.dpy, "SHAPE");
538 cloexec_braindamage_fix(ioncore_g.conn);
540 ioncore_g.atom_wm_state=XInternAtom(dpy, "WM_STATE", False);
541 ioncore_g.atom_wm_change_state=XInternAtom(dpy, "WM_CHANGE_STATE", False);
542 ioncore_g.atom_wm_protocols=XInternAtom(dpy, "WM_PROTOCOLS", False);
543 ioncore_g.atom_wm_delete=XInternAtom(dpy, "WM_DELETE_WINDOW", False);
544 ioncore_g.atom_wm_take_focus=XInternAtom(dpy, "WM_TAKE_FOCUS", False);
545 ioncore_g.atom_wm_colormaps=XInternAtom(dpy, "WM_COLORMAP_WINDOWS", False);
546 ioncore_g.atom_wm_window_role=XInternAtom(dpy, "WM_WINDOW_ROLE", False);
547 ioncore_g.atom_checkcode=XInternAtom(dpy, "_ION_CWIN_RESTART_CHECKCODE", False);
548 ioncore_g.atom_selection=XInternAtom(dpy, "_ION_SELECTION_STRING", False);
549 ioncore_g.atom_dockapp_hack=XInternAtom(dpy, "_ION_DOCKAPP_HACK", False);
550 ioncore_g.atom_mwm_hints=XInternAtom(dpy, "_MOTIF_WM_HINTS", False);
552 ioncore_init_xim();
553 ioncore_init_bindings();
554 ioncore_init_cursors();
556 netwm_init();
558 ioncore_init_session(XDisplayName(display));
560 for(i=drw; i<nrw; i++)
561 create_rootwin(i);
563 if(ioncore_g.rootwins==NULL){
564 if(nrw-drw>1)
565 warn(TR("Could not find a screen to manage."));
566 return FALSE;
569 if(!mainloop_register_input_fd(ioncore_g.conn, NULL,
570 ioncore_x_connection_handler)){
571 return FALSE;
574 return TRUE;
578 static void set_initial_focus()
580 Window root=None, win=None;
581 int x, y, wx, wy;
582 uint mask;
583 WScreen *scr;
585 XQueryPointer(ioncore_g.dpy, None, &root, &win,
586 &x, &y, &wx, &wy, &mask);
588 FOR_ALL_SCREENS(scr){
589 Window scrroot=region_root_of((WRegion*)scr);
590 if(scrroot==root && rectangle_contains(&REGION_GEOM(scr), x, y)){
591 break;
595 if(scr==NULL)
596 scr=ioncore_g.screens;
598 region_focuslist_push((WRegion*)scr);
599 region_do_set_focus((WRegion*)scr, FALSE);
603 bool ioncore_startup(const char *display, const char *cfgfile,
604 int stflags)
606 WRootWin *rootwin;
607 sigset_t inittrap;
609 LOG(INFO, GENERAL, TR("Starting Notion"));
611 /* Don't trap termination signals just yet. */
612 sigemptyset(&inittrap);
613 sigaddset(&inittrap, SIGALRM);
614 sigaddset(&inittrap, SIGCHLD);
615 sigaddset(&inittrap, SIGPIPE);
616 sigaddset(&inittrap, SIGUSR2);
617 mainloop_trap_signals(&inittrap);
619 if(!extl_init())
620 return FALSE;
622 ioncore_register_exports();
624 if(!ioncore_init_x(display, stflags))
625 return FALSE;
627 gr_read_config();
629 if(!extl_read_config("ioncore_ext", NULL, TRUE))
630 return FALSE;
632 ioncore_read_main_config(cfgfile);
634 if(!ioncore_init_layout())
635 return FALSE;
637 hook_call_v(ioncore_post_layout_setup_hook);
639 FOR_ALL_ROOTWINS(rootwin)
640 rootwin_manage_initial_windows(rootwin);
642 set_initial_focus();
644 return TRUE;
648 /*}}}*/
651 /*{{{ ioncore_deinit */
654 void ioncore_deinit()
656 Display *dpy;
658 ioncore_g.opmode=IONCORE_OPMODE_DEINIT;
660 if(ioncore_g.dpy==NULL)
661 return;
663 hook_call_v(ioncore_deinit_hook);
665 while(ioncore_g.screens!=NULL)
666 destroy_obj((Obj*)ioncore_g.screens);
668 /*ioncore_unload_modules();*/
670 while(ioncore_g.rootwins!=NULL)
671 destroy_obj((Obj*)ioncore_g.rootwins);
673 ioncore_deinit_bindmaps();
675 mainloop_unregister_input_fd(ioncore_g.conn);
677 dpy=ioncore_g.dpy;
678 ioncore_g.dpy=NULL;
680 XSync(dpy, True);
681 XCloseDisplay(dpy);
683 extl_deinit();
687 /*}}}*/
690 /*{{{ Miscellaneous */
693 /*EXTL_DOC
694 * Is Notion supporting locale-specifically multibyte-encoded strings?
696 EXTL_SAFE
697 EXTL_EXPORT
698 bool ioncore_is_i18n()
700 return ioncore_g.use_mb;
704 /*EXTL_DOC
705 * Returns Ioncore version string.
707 EXTL_SAFE
708 EXTL_EXPORT
709 const char *ioncore_version()
711 return ION_VERSION;
714 /*EXTL_DOC
715 * Returns the name of program using Ioncore.
717 EXTL_SAFE
718 EXTL_EXPORT
719 const char *ioncore_progname()
721 return progname;
725 /*EXTL_DOC
726 * Returns an about message (version, author, copyright notice).
728 EXTL_SAFE
729 EXTL_EXPORT
730 const char *ioncore_aboutmsg()
732 return ioncore_about;
736 /*}}}*/