]> git.decadent.org.uk Git - ion3.git/blob - ioncore/screen.c
[svn-upgrade] Integrating new upstream version, ion3 (20070203)
[ion3.git] / ioncore / screen.c
1 /*
2  * ion/ioncore/screen.c
3  *
4  * Copyright (c) Tuomo Valkonen 1999-2007. 
5  *
6  * Ion is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  */
11
12 #include <string.h>
13
14 #include <libtu/objp.h>
15 #include <libtu/minmax.h>
16 #include <libmainloop/defer.h>
17
18 #include "common.h"
19 #include "global.h"
20 #include "screen.h"
21 #include "region.h"
22 #include "attach.h"
23 #include "manage.h"
24 #include "focus.h"
25 #include "property.h"
26 #include "names.h"
27 #include "reginfo.h"
28 #include "saveload.h"
29 #include "resize.h"
30 #include "event.h"
31 #include "bindmaps.h"
32 #include "regbind.h"
33 #include "frame-pointer.h"
34 #include "rectangle.h"
35 #include "infowin.h"
36 #include "activity.h"
37 #include "extlconv.h"
38 #include "llist.h"
39 #include "group-ws.h"
40 #include "mplex.h"
41 #include "tags.h"
42 #include "gr.h"
43 #include "gr-util.h"
44 #include "conf.h"
45
46
47 WHook *screen_managed_changed_hook=NULL;
48
49
50 static void screen_update_infowin(WScreen *scr);
51
52
53
54 /*{{{ Init/deinit */
55
56
57 bool screen_init(WScreen *scr, WRootWin *parent,
58                  const WFitParams *fp, int id, Window rootwin)
59 {
60     Window win;
61     XSetWindowAttributes attr;
62     ulong attrflags=0;
63     bool is_root=FALSE;
64     
65     scr->id=id;
66     scr->atom_workspace=None;
67     scr->managed_off.x=0;
68     scr->managed_off.y=0;
69     scr->managed_off.w=0;
70     scr->managed_off.h=0;
71     scr->next_scr=NULL;
72     scr->prev_scr=NULL;
73     scr->rotation=SCREEN_ROTATION_0;
74     
75     watch_init(&(scr->notifywin_watch));
76     watch_init(&(scr->infowin_watch));
77
78     if(parent==NULL){
79         win=rootwin;
80         is_root=TRUE;
81     }else{
82         attr.background_pixmap=ParentRelative;
83         attrflags=CWBackPixmap;
84         
85         win=XCreateWindow(ioncore_g.dpy, WROOTWIN_ROOT(parent),
86                           fp->g.x, fp->g.y, fp->g.w, fp->g.h, 0, 
87                           DefaultDepth(ioncore_g.dpy, parent->xscr),
88                           InputOutput,
89                           DefaultVisual(ioncore_g.dpy, parent->xscr),
90                           attrflags, &attr);
91         if(win==None)
92             return FALSE;
93             
94     }
95
96     if(!mplex_do_init((WMPlex*)scr, (WWindow*)parent, fp, win)){
97         if(!is_root)
98             XDestroyWindow(ioncore_g.dpy, win);
99         return FALSE;
100     }
101
102     /*scr->mplex.win.region.rootwin=rootwin;
103     region_set_parent((WRegion*)scr, (WRegion*)rootwin);*/
104     scr->mplex.flags|=MPLEX_ADD_TO_END;
105     scr->mplex.win.region.flags|=REGION_BINDINGS_ARE_GRABBED;
106     
107     if(!is_root){
108         scr->mplex.win.region.flags|=REGION_MAPPED;
109         window_select_input((WWindow*)scr, IONCORE_EVENTMASK_SCREEN);
110     }
111     
112     if(id==0){
113         scr->atom_workspace=XInternAtom(ioncore_g.dpy, 
114                                         "_ION_WORKSPACE", False);
115     }else if(id>=0){
116         char *str;
117         libtu_asprintf(&str, "_ION_WORKSPACE%d", id);
118         if(str!=NULL){
119             scr->atom_workspace=XInternAtom(ioncore_g.dpy, str, False);
120             free(str);
121         }
122     }
123
124     /* Add all the needed bindings here; mplex does nothing so that
125      * frames don't have to remove extra bindings.
126      */
127     region_add_bindmap((WRegion*)scr, ioncore_screen_bindmap);
128     region_add_bindmap((WRegion*)scr, ioncore_mplex_bindmap);
129     region_add_bindmap((WRegion*)scr, ioncore_mplex_toplevel_bindmap);
130
131     LINK_ITEM(ioncore_g.screens, scr, next_scr, prev_scr);
132     
133     return TRUE;
134 }
135
136
137 WScreen *create_screen(WRootWin *parent, const WFitParams *fp, int id)
138 {
139     CREATEOBJ_IMPL(WScreen, screen, (p, parent, fp, id, None));
140 }
141
142
143 void screen_deinit(WScreen *scr)
144 {
145     UNLINK_ITEM(ioncore_g.screens, scr, next_scr, prev_scr);
146     
147     mplex_deinit((WMPlex*)scr);
148 }
149
150
151 /*}}}*/
152
153
154 /*{{{ Attach/detach */
155
156
157 void screen_managed_geom(WScreen *scr, WRectangle *geom)
158 {
159     geom->x=scr->managed_off.x;
160     geom->y=scr->managed_off.y;
161     geom->w=REGION_GEOM(scr).w+scr->managed_off.w;
162     geom->h=REGION_GEOM(scr).h+scr->managed_off.h;
163     geom->w=maxof(geom->w, 0);
164     geom->h=maxof(geom->h, 0);
165 }
166
167
168 static bool screen_handle_drop(WScreen *scr, int x, int y, WRegion *dropped)
169 {
170     WRegion *curr=mplex_mx_current(&(scr->mplex));
171
172     /* This code should handle dropping tabs on floating workspaces. */
173     if(curr && HAS_DYN(curr, region_handle_drop)){
174         int rx, ry;
175         region_rootpos(curr, &rx, &ry);
176         if(rectangle_contains(&REGION_GEOM(curr), x-rx, y-ry)){
177             if(region_handle_drop(curr, x, y, dropped))
178                 return TRUE;
179         }
180     }
181     
182     /* Do not attach to ourselves unlike generic WMPlex. */
183     return FALSE;
184 }
185
186
187 /*}}}*/
188
189
190 /*{{{ Region dynfun implementations */
191
192
193 static bool screen_fitrep(WScreen *scr, WWindow *par, const WFitParams *fp)
194 {
195     WRegion *sub;
196     
197     if(par==NULL)
198         return FALSE;
199     
200     if(scr->uses_root)
201         return FALSE;
202
203     return mplex_fitrep((WMPlex*)scr, NULL, fp);
204 }
205
206
207
208
209 static void screen_managed_changed(WScreen *scr, int mode, bool sw, 
210                                    WRegion *reg_)
211 {
212     if(ioncore_g.opmode==IONCORE_OPMODE_DEINIT)
213         return;
214     
215     if(sw && scr->atom_workspace!=None){
216         WRegion *reg=mplex_mx_current(&(scr->mplex));
217         const char *n=NULL;
218         
219         if(reg!=NULL)
220             n=region_displayname(reg);
221         
222         xwindow_set_string_property(region_root_of((WRegion*)scr),
223                                     scr->atom_workspace, 
224                                     n==NULL ? "" : n);
225     }
226     
227     screen_update_infowin(scr);
228     
229     mplex_call_changed_hook((WMPlex*)scr,
230                             screen_managed_changed_hook,
231                             mode, sw, reg_);
232 }
233
234
235 static void screen_map(WScreen *scr)
236 {
237     if(scr->uses_root)
238         return;
239     mplex_map((WMPlex*)scr);
240 }
241
242
243 static void screen_unmap(WScreen *scr)
244 {
245     if(scr->uses_root)
246         return;
247     mplex_unmap((WMPlex*)scr);
248 }
249
250 void screen_inactivated(WScreen *scr)
251 {
252     screen_update_infowin(scr);
253 }
254
255
256 void screen_activated(WScreen *scr)
257 {
258     screen_update_infowin(scr);
259 }
260
261
262 /*}}}*/
263
264
265 /*}}}*/
266
267
268 /*{{{ Notifications */
269
270
271 static void do_notify(WScreen *scr, Watch *watch, bool right,
272                       const char *str, char *style)
273 {
274
275     WInfoWin *iw=(WInfoWin*)(watch->obj);
276     WFitParams fp;
277     
278     if(iw==NULL){
279         WMPlexAttachParams param=MPLEXATTACHPARAMS_INIT;
280         
281         param.flags=(MPLEX_ATTACH_UNNUMBERED|
282                      MPLEX_ATTACH_SIZEPOLICY|
283                      MPLEX_ATTACH_GEOM|
284                      MPLEX_ATTACH_LEVEL);
285         param.level=STACKING_LEVEL_ON_TOP;
286         
287         if(!right){
288             param.szplcy=SIZEPOLICY_GRAVITY_NORTHWEST;
289             param.geom.x=0;
290         }else{
291             param.szplcy=SIZEPOLICY_GRAVITY_NORTHEAST;
292             param.geom.x=REGION_GEOM(scr).w-1;
293         }
294         
295         param.geom.y=0;
296         param.geom.w=1;
297         param.geom.h=1;
298         
299         iw=(WInfoWin*)mplex_do_attach_new(&scr->mplex, &param,
300                                           (WRegionCreateFn*)create_infowin, 
301                                           style);
302         
303         if(iw==NULL)
304             return;
305
306         watch_setup(watch, (Obj*)iw, NULL);
307     }
308
309     infowin_set_text(iw, str);
310 }
311
312
313 void screen_notify(WScreen *scr, const char *str)
314 {
315     do_notify(scr, &scr->notifywin_watch, FALSE, str, "actnotify");
316 }
317
318
319 void screen_windowinfo(WScreen *scr, const char *str)
320 {
321     do_notify(scr, &scr->infowin_watch, TRUE, str, "tab-info");
322 }
323
324
325 void screen_unnotify(WScreen *scr)
326 {
327     Obj *iw=scr->notifywin_watch.obj;
328     if(iw!=NULL){
329         watch_reset(&(scr->notifywin_watch));
330         region_dispose((WRegion*)iw, FALSE);
331     }
332 }
333
334
335 void screen_nowindowinfo(WScreen *scr)
336 {
337     Obj *iw=scr->infowin_watch.obj;
338     if(iw!=NULL){
339         watch_reset(&(scr->infowin_watch));
340         region_dispose((WRegion*)iw, FALSE);
341     }
342 }
343
344
345 static char *addnot(char *str, WRegion *reg)
346 {
347     const char *nm=region_name(reg);
348     char *nstr=NULL;
349     
350     if(nm==NULL)
351         return str;
352     
353     if(str==NULL)
354         return scat(TR("act: "), nm);
355
356     nstr=scat3(str, ", ", nm);
357     if(nstr!=NULL)
358         free(str);
359     return nstr;
360 }
361
362
363 static char *screen_managed_activity(WScreen *scr)
364 {
365     char *notstr=NULL;
366     WMPlexIterTmp tmp;
367     WRegion *reg;
368     
369     FOR_ALL_MANAGED_BY_MPLEX(&scr->mplex, reg, tmp){
370         if(region_is_activity_r(reg) && !REGION_IS_MAPPED(reg))
371             notstr=addnot(notstr, reg);
372     }
373     
374     return notstr;
375 }
376
377
378 static void screen_notify_activity(WScreen *scr)
379 {
380     if(ioncore_g.screen_notify){
381         char *notstr=screen_managed_activity(scr);
382         if(notstr!=NULL){
383             screen_notify(scr, notstr);
384             free(notstr);
385             return;
386         }
387     }
388
389     screen_unnotify(scr);
390     
391     screen_update_infowin(scr);
392 }
393
394
395 static void screen_notify_tag(WScreen *scr)
396 {
397     screen_update_infowin(scr);
398 }
399
400
401 GR_DEFATTR(active);
402 GR_DEFATTR(inactive);
403 GR_DEFATTR(selected);
404 GR_DEFATTR(tagged);
405 GR_DEFATTR(not_tagged);
406 GR_DEFATTR(not_dragged);
407 GR_DEFATTR(activity);
408 GR_DEFATTR(no_activity);
409
410
411 static void init_attr()
412 {
413     GR_ALLOCATTR_BEGIN;
414     GR_ALLOCATTR(active);
415     GR_ALLOCATTR(inactive);
416     GR_ALLOCATTR(selected);
417     GR_ALLOCATTR(tagged);
418     GR_ALLOCATTR(not_tagged);
419     GR_ALLOCATTR(not_dragged);
420     GR_ALLOCATTR(no_activity);
421     GR_ALLOCATTR(activity);
422     GR_ALLOCATTR_END;
423 }
424
425
426 static void screen_update_infowin(WScreen *scr)
427 {
428     WRegion *reg=mplex_mx_current(&(scr->mplex));
429     bool tag=(reg!=NULL && region_is_tagged(reg));
430     bool act=(reg!=NULL && region_is_activity_r(reg) && !REGION_IS_ACTIVE(scr));
431     bool sac=REGION_IS_ACTIVE(scr);
432     
433     if(tag || act){
434         const char *n=region_displayname(reg);
435         WInfoWin *iw;
436                 
437         screen_windowinfo(scr, n);
438         
439         iw=(WInfoWin*)scr->infowin_watch.obj;
440         
441         if(iw!=NULL){
442             GrStyleSpec *spec=infowin_stylespec(iw);
443             
444             init_attr();
445             
446             gr_stylespec_unalloc(spec);
447             
448             gr_stylespec_set(spec, GR_ATTR(selected));
449             gr_stylespec_set(spec, GR_ATTR(not_dragged));
450             gr_stylespec_set(spec, sac ? GR_ATTR(active) : GR_ATTR(inactive));
451             gr_stylespec_set(spec, tag ? GR_ATTR(tagged) : GR_ATTR(not_tagged));
452             gr_stylespec_set(spec, act ? GR_ATTR(activity) : GR_ATTR(no_activity));
453         }
454             
455     }else{
456         screen_nowindowinfo(scr);
457     }
458 }
459
460
461 static void screen_managed_notify(WScreen *scr, WRegion *reg, WRegionNotify how)
462 {
463     if(how==ioncore_g.notifies.sub_activity){
464         /* TODO: multiple calls */
465         mainloop_defer_action((Obj*)scr, 
466                               (WDeferredAction*)screen_notify_activity);
467     }else if(how==ioncore_g.notifies.tag){
468         mainloop_defer_action((Obj*)scr, 
469                               (WDeferredAction*)screen_notify_tag);
470     }
471 }
472
473
474 /*}}}*/
475
476
477 /*{{{ Misc. */
478
479
480 /*EXTL_DOC
481  * Find the screen with numerical id \var{id}. 
482  */
483 EXTL_SAFE
484 EXTL_EXPORT
485 WScreen *ioncore_find_screen_id(int id)
486 {
487     WScreen *scr, *maxscr=NULL;
488     
489     FOR_ALL_SCREENS(scr){
490         if(id==-1){
491             if(maxscr==NULL || scr->id>maxscr->id)
492                 maxscr=scr;
493         }
494         if(scr->id==id)
495             return scr;
496     }
497     
498     return maxscr;
499 }
500
501
502 /*EXTL_DOC
503  * Switch focus to the screen with id \var{id} and return it.
504  * 
505  * Note that this function is asynchronous; the screen will not
506  * actually have received the focus when this function returns.
507  */
508 EXTL_EXPORT
509 WScreen *ioncore_goto_nth_screen(int id)
510 {
511     WScreen *scr=ioncore_find_screen_id(id);
512     if(scr!=NULL){
513         if(!region_goto((WRegion*)scr))
514             return NULL;
515     }
516     return scr;
517 }
518
519
520 static WScreen *current_screen()
521 {
522     if(ioncore_g.focus_current==NULL)
523         return ioncore_g.screens;
524     else
525         return region_screen_of(ioncore_g.focus_current);
526 }
527
528        
529 /*EXTL_DOC
530  * Switch focus to the next screen and return it.
531  * 
532  * Note that this function is asynchronous; the screen will not
533  * actually have received the focus when this function returns.
534  */
535 EXTL_EXPORT
536 WScreen *ioncore_goto_next_screen()
537 {
538     WScreen *scr=current_screen();
539     
540     if(scr!=NULL)
541         scr=scr->next_scr;
542     if(scr==NULL)
543         scr=ioncore_g.screens;
544     if(scr!=NULL){
545         if(!region_goto((WRegion*)scr))
546             return NULL;
547     }
548     return scr;
549 }
550
551
552 /*EXTL_DOC
553  * Switch focus to the previous screen and return it.
554  * 
555  * Note that this function is asynchronous; the screen will not
556  * actually have received the focus when this function returns.
557  */
558 EXTL_EXPORT
559 WScreen *ioncore_goto_prev_screen()
560 {
561     WScreen *scr=current_screen();
562
563     if(scr!=NULL)
564         scr=scr->prev_scr;
565     else
566         scr=ioncore_g.screens;
567     if(scr!=NULL){
568         if(!region_goto((WRegion*)scr))
569             return NULL;
570     }
571     return scr;
572 }
573
574
575 /*EXTL_DOC
576  * Return the numerical id for screen \var{scr}.
577  */
578 EXTL_SAFE
579 EXTL_EXPORT_MEMBER
580 int screen_id(WScreen *scr)
581 {
582     return scr->id;
583 }
584
585
586 static bool screen_managed_may_destroy(WScreen *scr, WRegion *reg)
587 {
588     bool onmxlist=FALSE;
589     WLListNode *lnode;
590     WLListIterTmp tmp;
591
592     if(OBJ_IS(reg, WClientWin))
593         return TRUE;
594     
595     FOR_ALL_NODES_ON_LLIST(lnode, scr->mplex.mx_list, tmp){
596         if(lnode->st->reg==reg)
597             onmxlist=TRUE;
598         else /*if(OBJ_IS(node->reg, WGenWS))*/
599             return TRUE;
600     }
601     
602     if(!onmxlist)
603         return TRUE;
604     
605     warn(TR("Only workspace may not be destroyed."));
606     
607     return FALSE;
608 }
609
610
611 static bool screen_may_destroy(WScreen *scr)
612 {
613     warn(TR("Screens may not be destroyed."));
614     return FALSE;
615 }
616
617
618
619 void screen_set_managed_offset(WScreen *scr, const WRectangle *off)
620 {
621     scr->managed_off=*off;
622     mplex_fit_managed((WMPlex*)scr);
623 }
624
625
626 /*EXTL_DOC
627  * Set offset of objects managed by the screen from actual screen geometry.
628  * The table \var{offset} should contain the entries \code{x}, \code{y}, 
629  * \code{w} and \code{h} indicating offsets of that component of screen 
630  * geometry.
631  */
632 EXTL_EXPORT_AS(WScreen, set_managed_offset)
633 bool screen_set_managed_offset_extl(WScreen *scr, ExtlTab offset)
634 {
635     WRectangle g;
636     
637     if(!extl_table_to_rectangle(offset, &g))
638         goto err;
639     
640     if(-g.w>=REGION_GEOM(scr).w)
641         goto err;
642     if(-g.h>=REGION_GEOM(scr).h)
643         goto err;
644     
645     screen_set_managed_offset(scr, &g);
646     
647     return TRUE;
648 err:
649     warn(TR("Invalid offset."));
650     return FALSE;
651 }
652
653
654 WPHolder *screen_get_rescue_pholder_for(WScreen *scr, WRegion *mgd)
655 {
656 #warning "TODO: better special case handling for groups"
657     
658     return (WPHolder*)mplex_get_rescue_pholder_for(&(scr->mplex), mgd);
659 }
660
661 /*}}}*/
662
663
664 /*{{{ Save/load */
665
666
667 ExtlTab screen_get_configuration(WScreen *scr)
668 {
669     return mplex_get_configuration(&scr->mplex);
670 }
671
672
673 static WRegion *do_create_initial(WWindow *parent, const WFitParams *fp, 
674                                   WRegionLoadCreateFn *fn)
675 {
676     return fn(parent, fp, extl_table_none());
677 }
678
679
680 static bool create_initial_ws(WScreen *scr)
681 {
682     WRegion *reg=NULL;
683     WMPlexAttachParams par=MPLEXATTACHPARAMS_INIT;
684     ExtlTab lo=ioncore_get_layout("default");
685     
686     if(lo==extl_table_none()){
687         reg=mplex_do_attach_new(&scr->mplex, &par,
688                                 (WRegionCreateFn*)create_groupws, NULL);
689     }else{
690         reg=mplex_attach_new_(&scr->mplex, &par, 0, lo);
691         extl_unref_table(lo);
692     }
693     
694     if(reg==NULL){
695         warn(TR("Unable to create a workspace on screen %d."), scr->id);
696         return FALSE;
697     }
698     
699     return TRUE;
700 }
701
702
703 bool screen_init_layout(WScreen *scr, ExtlTab tab)
704 {
705     char *name;
706     ExtlTab substab, subtab;
707     int n, i;
708
709     if(tab==extl_table_none())
710         return create_initial_ws(scr);
711     
712     mplex_load_contents(&scr->mplex, tab);
713     
714     return TRUE;
715 }
716
717 /*}}}*/
718
719
720 /*{{{ Dynamic function table and class implementation */
721
722
723 static DynFunTab screen_dynfuntab[]={
724     {region_map, 
725      screen_map},
726     
727     {region_unmap, 
728      screen_unmap},
729      
730     {region_activated, 
731      screen_activated},
732      
733     {region_inactivated, 
734      screen_inactivated},
735     
736     {(DynFun*)region_managed_may_destroy,
737      (DynFun*)screen_managed_may_destroy},
738
739     {(DynFun*)region_may_destroy,
740      (DynFun*)screen_may_destroy},
741
742     {mplex_managed_changed, 
743      screen_managed_changed},
744     
745     {region_managed_notify, 
746      screen_managed_notify},
747     
748     {mplex_managed_geom, 
749      screen_managed_geom},
750
751     {(DynFun*)region_get_configuration,
752      (DynFun*)screen_get_configuration},
753
754     {(DynFun*)region_handle_drop, 
755      (DynFun*)screen_handle_drop},
756
757     {(DynFun*)region_fitrep,
758      (DynFun*)screen_fitrep},
759
760     {(DynFun*)region_get_rescue_pholder_for,
761      (DynFun*)screen_get_rescue_pholder_for},
762     
763     END_DYNFUNTAB
764 };
765
766
767 EXTL_EXPORT
768 IMPLCLASS(WScreen, WMPlex, screen_deinit, screen_dynfuntab);
769
770
771 /*}}}*/