FSF address
[notion/jeffpc.git] / ioncore / ioncore.c
blob9f4009848dc82e4b325110893939e359b479fd65
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.focus_current=NULL;
357 ioncore_g.input_mode=IONCORE_INPUTMODE_NORMAL;
358 ioncore_g.opmode=IONCORE_OPMODE_INIT;
359 ioncore_g.dblclick_delay=CF_DBLCLICK_DELAY;
360 ioncore_g.usertime_diff_current=CF_USERTIME_DIFF_CURRENT;
361 ioncore_g.usertime_diff_new=CF_USERTIME_DIFF_NEW;
362 ioncore_g.opaque_resize=0;
363 ioncore_g.warp_enabled=TRUE;
364 ioncore_g.switchto_new=TRUE;
365 ioncore_g.no_mousefocus=FALSE;
366 ioncore_g.unsqueeze_enabled=TRUE;
367 ioncore_g.autoraise=TRUE;
368 ioncore_g.autosave_layout=TRUE;
369 ioncore_g.window_stacking_request=IONCORE_WINDOWSTACKINGREQUEST_IGNORE;
371 ioncore_g.enc_utf8=FALSE;
372 ioncore_g.enc_sb=TRUE;
373 ioncore_g.use_mb=FALSE;
375 ioncore_g.screen_notify=TRUE;
377 ioncore_g.frame_default_index=LLIST_INDEX_AFTER_CURRENT_ACT;
379 ioncore_g.framed_transients=TRUE;
381 ioncore_g.shape_extension=FALSE;
382 ioncore_g.shape_event_basep=0;
383 ioncore_g.shape_error_basep=0;
385 INITSTR(activated);
386 INITSTR(inactivated);
387 INITSTR(activity);
388 INITSTR(sub_activity);
389 INITSTR(name);
390 INITSTR(unset_manager);
391 INITSTR(set_manager);
392 INITSTR(unset_return);
393 INITSTR(set_return);
394 INITSTR(pseudoactivated);
395 INITSTR(pseudoinactivated);
396 INITSTR(tag);
397 INITSTR(deinit);
398 INITSTR(map);
399 INITSTR(unmap);
401 return TRUE;
405 bool ioncore_init(const char *prog, int argc, char *argv[],
406 const char *localedir)
408 if(!init_global())
409 return FALSE;
411 progname=prog;
412 ioncore_g.argc=argc;
413 ioncore_g.argv=argv;
415 #ifndef CF_NO_LOCALE
416 init_locale();
417 #endif
418 #ifndef CF_NO_GETTEXT
419 init_messages(localedir);
420 #endif
422 ioncore_about=scat3(ioncore_copy, "\n\n", TR(ioncore_license));
424 if(!ioncore_init_bindmaps())
425 return FALSE;
427 if(!register_classes())
428 return FALSE;
430 if(!init_hooks())
431 return FALSE;
433 if(!ioncore_init_module_support())
434 return FALSE;
436 return TRUE;
440 /*}}}*/
443 /*{{{ ioncore_startup */
446 static void ioncore_init_session(const char *display)
448 const char *dpyend=NULL;
449 char *tmp=NULL, *colon=NULL;
450 const char *sm=getenv("SESSION_MANAGER");
452 if(sm!=NULL)
453 ioncore_load_module("mod_sm");
455 if(extl_sessiondir()!=NULL)
456 return;
458 /* Not running under SM; use display-specific directory */
459 dpyend=strchr(display, ':');
460 if(dpyend!=NULL)
461 dpyend=strchr(dpyend, '.');
462 if(dpyend==NULL){
463 libtu_asprintf(&tmp, "default-session-%s", display);
464 }else{
465 libtu_asprintf(&tmp, "default-session-%.*s",
466 (int)(dpyend-display), display);
469 if(tmp==NULL)
470 return;
472 colon=tmp;
473 while(1){
474 colon=strchr(colon, ':');
475 if(colon==NULL)
476 break;
477 *colon='-';
480 extl_set_sessiondir(tmp);
481 free(tmp);
485 static bool ioncore_init_x(const char *display, int stflags)
487 Display *dpy;
488 int i, drw, nrw;
489 static bool called=FALSE;
491 /* Sorry, this function can not be re-entered due to laziness
492 * towards implementing checking of things already initialized.
493 * Nobody would call this twice anyway.
495 assert(!called);
496 called=TRUE;
498 /* Open the display. */
499 dpy=XOpenDisplay(display);
501 if(dpy==NULL){
502 warn(TR("Could not connect to X display '%s'"),
503 XDisplayName(display));
504 return FALSE;
507 if(stflags&IONCORE_STARTUP_ONEROOT){
508 drw=DefaultScreen(dpy);
509 nrw=drw+1;
510 }else{
511 drw=0;
512 nrw=ScreenCount(dpy);
515 /* Initialize */
516 if(display!=NULL){
517 ioncore_g.display=scopy(display);
518 if(ioncore_g.display==NULL){
519 XCloseDisplay(dpy);
520 return FALSE;
524 ioncore_g.dpy=dpy;
525 ioncore_g.win_context=XUniqueContext();
526 ioncore_g.conn=ConnectionNumber(dpy);
528 if(XShapeQueryExtension(ioncore_g.dpy, &ioncore_g.shape_event_basep,
529 &ioncore_g.shape_error_basep))
530 ioncore_g.shape_extension=TRUE;
531 else
532 XMissingExtension(ioncore_g.dpy, "SHAPE");
534 cloexec_braindamage_fix(ioncore_g.conn);
536 ioncore_g.atom_wm_state=XInternAtom(dpy, "WM_STATE", False);
537 ioncore_g.atom_wm_change_state=XInternAtom(dpy, "WM_CHANGE_STATE", False);
538 ioncore_g.atom_wm_protocols=XInternAtom(dpy, "WM_PROTOCOLS", False);
539 ioncore_g.atom_wm_delete=XInternAtom(dpy, "WM_DELETE_WINDOW", False);
540 ioncore_g.atom_wm_take_focus=XInternAtom(dpy, "WM_TAKE_FOCUS", False);
541 ioncore_g.atom_wm_colormaps=XInternAtom(dpy, "WM_COLORMAP_WINDOWS", False);
542 ioncore_g.atom_wm_window_role=XInternAtom(dpy, "WM_WINDOW_ROLE", False);
543 ioncore_g.atom_checkcode=XInternAtom(dpy, "_ION_CWIN_RESTART_CHECKCODE", False);
544 ioncore_g.atom_selection=XInternAtom(dpy, "_ION_SELECTION_STRING", False);
545 ioncore_g.atom_dockapp_hack=XInternAtom(dpy, "_ION_DOCKAPP_HACK", False);
546 ioncore_g.atom_mwm_hints=XInternAtom(dpy, "_MOTIF_WM_HINTS", False);
548 ioncore_init_xim();
549 ioncore_init_bindings();
550 ioncore_init_cursors();
552 netwm_init();
554 ioncore_init_session(XDisplayName(display));
556 for(i=drw; i<nrw; i++)
557 create_rootwin(i);
559 if(ioncore_g.rootwins==NULL){
560 if(nrw-drw>1)
561 warn(TR("Could not find a screen to manage."));
562 return FALSE;
565 if(!mainloop_register_input_fd(ioncore_g.conn, NULL,
566 ioncore_x_connection_handler)){
567 return FALSE;
570 return TRUE;
574 static void set_initial_focus()
576 Window root=None, win=None;
577 int x, y, wx, wy;
578 uint mask;
579 WScreen *scr;
580 WWindow *wwin;
582 XQueryPointer(ioncore_g.dpy, None, &root, &win,
583 &x, &y, &wx, &wy, &mask);
585 FOR_ALL_SCREENS(scr){
586 Window scrroot=region_root_of((WRegion*)scr);
587 if(scrroot==root && rectangle_contains(&REGION_GEOM(scr), x, y)){
588 break;
592 if(scr==NULL)
593 scr=ioncore_g.screens;
595 region_focuslist_push((WRegion*)scr);
596 region_do_set_focus((WRegion*)scr, FALSE);
600 bool ioncore_startup(const char *display, const char *cfgfile,
601 int stflags)
603 WRootWin *rootwin;
604 sigset_t inittrap;
606 LOG(INFO, GENERAL, TR("Starting Notion"));
608 /* Don't trap termination signals just yet. */
609 sigemptyset(&inittrap);
610 sigaddset(&inittrap, SIGALRM);
611 sigaddset(&inittrap, SIGCHLD);
612 sigaddset(&inittrap, SIGPIPE);
613 sigaddset(&inittrap, SIGUSR2);
614 mainloop_trap_signals(&inittrap);
616 if(!extl_init())
617 return FALSE;
619 ioncore_register_exports();
621 if(!ioncore_init_x(display, stflags))
622 return FALSE;
624 gr_read_config();
626 if(!extl_read_config("ioncore_ext", NULL, TRUE))
627 return FALSE;
629 ioncore_read_main_config(cfgfile);
631 if(!ioncore_init_layout())
632 return FALSE;
634 hook_call_v(ioncore_post_layout_setup_hook);
636 FOR_ALL_ROOTWINS(rootwin)
637 rootwin_manage_initial_windows(rootwin);
639 set_initial_focus();
641 return TRUE;
645 /*}}}*/
648 /*{{{ ioncore_deinit */
651 void ioncore_deinit()
653 Display *dpy;
654 WRootWin *rootwin;
656 ioncore_g.opmode=IONCORE_OPMODE_DEINIT;
658 if(ioncore_g.dpy==NULL)
659 return;
661 hook_call_v(ioncore_deinit_hook);
663 while(ioncore_g.screens!=NULL)
664 destroy_obj((Obj*)ioncore_g.screens);
666 /*ioncore_unload_modules();*/
668 while(ioncore_g.rootwins!=NULL)
669 destroy_obj((Obj*)ioncore_g.rootwins);
671 ioncore_deinit_bindmaps();
673 mainloop_unregister_input_fd(ioncore_g.conn);
675 dpy=ioncore_g.dpy;
676 ioncore_g.dpy=NULL;
678 XSync(dpy, True);
679 XCloseDisplay(dpy);
681 extl_deinit();
685 /*}}}*/
688 /*{{{ Miscellaneous */
691 /*EXTL_DOC
692 * Is Notion supporting locale-specifically multibyte-encoded strings?
694 EXTL_SAFE
695 EXTL_EXPORT
696 bool ioncore_is_i18n()
698 return ioncore_g.use_mb;
702 /*EXTL_DOC
703 * Returns Ioncore version string.
705 EXTL_SAFE
706 EXTL_EXPORT
707 const char *ioncore_version()
709 return ION_VERSION;
712 /*EXTL_DOC
713 * Returns the name of program using Ioncore.
715 EXTL_SAFE
716 EXTL_EXPORT
717 const char *ioncore_progname()
719 return progname;
723 /*EXTL_DOC
724 * Returns an about message (version, author, copyright notice).
726 EXTL_SAFE
727 EXTL_EXPORT
728 const char *ioncore_aboutmsg()
730 return ioncore_about;
734 /*}}}*/