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