super fading
[dana/dcompmgr.git] / window.c
1 #include "efence.h"
2
3 #include "window.h"
4 #include "screen.h"
5 #include "plugin.h"
6 #include "list.h"
7 #include "display.h"
8 #include <stdlib.h>
9 #include <assert.h>
10 #include <stdio.h>
11 #include <xcb/composite.h>
12 #include <xcb/damage.h>
13
14 #define TOPLEVEL_WINDOW_EVENT_MASK (XCB_EVENT_MASK_PROPERTY_CHANGE)
15
16 typedef struct {
17     /* public stuff */
18     xcb_window_t     id;
19     struct d_screen *sc;
20
21     /* private stuff */
22     int              ref;
23     int              zombieref;
24
25     /* queried things, don't read them directly from the struct */
26     int                 x, y, w, h, bw;
27     gboolean            attr_mapped;
28     gboolean            input_only;
29     gboolean            argb;
30     xcb_visualid_t      visual;
31     xcb_pixmap_t        pixmap;
32     xcb_xfixes_region_t region;
33     d_window_type_t     type;
34     /* the window id where we got the window type from.  further property
35        queries should go to this window */
36     xcb_window_t        client;
37
38     /* opacity is from 0xffff to 0, these 2 are combined to give the final
39        result */
40     uint16_t            opacity;      /* this one is set by plugins/settings */
41     uint16_t            user_opacity; /* this one is set by the user */
42
43     gboolean            mapped;
44     gboolean            zombie;
45
46     d_list_t           *plugin_data;
47
48     xcb_damage_damage_t damage;
49
50     gboolean waiting_attr;
51     xcb_get_window_attributes_cookie_t ck_get_attr;
52     gboolean waiting_geom;
53     xcb_get_geometry_cookie_t          ck_get_geom;
54     gboolean waiting_opac;
55     xcb_get_property_cookie_t          ck_get_opac;
56 } d_window_priv_t;
57
58 static void window_get_attributes_reply(d_window_priv_t *w);
59 static void window_get_geometry_reply(d_window_priv_t *w);
60 static void window_update_pixmap(d_window_priv_t *w);
61 static void window_update_region(d_window_priv_t *w);
62 static void window_update_type(d_window_priv_t *w);
63
64 d_window_t*
65 window_new(xcb_window_t id, struct d_screen *sc)
66 {
67     d_window_priv_t *w;
68
69     w = malloc(sizeof(d_window_priv_t));
70     w->id = id;
71     w->ref = 1;
72     w->zombieref = 0;
73     w->sc = sc;
74     w->zombie = FALSE;
75     w->mapped = FALSE;
76     w->pixmap = XCB_NONE;
77     w->damage = XCB_NONE;
78     w->region = XCB_NONE;
79     w->type = DC_WINDOW_TYPE_INVALID;
80     w->opacity = 0xffff;
81     w->user_opacity = 0xffff;
82
83     screen_stacking_add(sc, (d_window_t*)w);
84
85     w->ck_get_attr = xcb_get_window_attributes(sc->dpy->conn, id);
86     w->waiting_attr = TRUE;
87
88     w->ck_get_geom = xcb_get_geometry(sc->dpy->conn, id);
89     w->waiting_geom = TRUE;
90
91     w->plugin_data = list_new();
92
93     //printf("new window 0x%x\n", w->id);
94
95     return (d_window_t*)w;
96 }
97
98 void
99 window_ref(d_window_t *pubw)
100 {
101     d_window_priv_t *w = (d_window_priv_t*)pubw;
102
103     ++w->ref;
104 }
105
106 void
107 window_unref(d_window_t *pubw)
108 {
109     d_window_priv_t *w = (d_window_priv_t*)pubw;
110
111     if (w && --w->ref == 0) {
112         screen_stacking_remove(w->sc, (d_window_t*)w);
113
114         assert(w->zombieref == 0);
115         while (w->zombieref)
116             window_zombie_unref(pubw);
117
118         list_unref(w->plugin_data);
119         free(w);
120     }
121 }
122
123 xcb_pixmap_t
124 window_get_pixmap(d_window_t *pubw)
125 {
126     d_window_priv_t *w = (d_window_priv_t*)pubw;
127
128     return w->pixmap;
129 }
130
131 void
132 window_update_user_opacity(d_window_t *pubw)
133 {
134     d_window_priv_t *w = (d_window_priv_t*)pubw;
135
136     w->ck_get_opac =
137         xcb_get_property(w->sc->dpy->conn, FALSE, w->id,
138                          w->sc->dpy->a.net_wm_window_opacity,
139                          w->sc->dpy->a.cardinal, 0, 1);
140     w->waiting_opac = TRUE;
141 }
142
143 static void
144 window_update_region(d_window_priv_t *w)
145 {
146     int x, y, wi, hei, bw;
147
148     if (window_is_zombie((d_window_t*)w)) return;
149
150     if (w->region) {
151         xcb_xfixes_destroy_region(w->sc->dpy->conn, w->region);
152         w->region = XCB_NONE;
153     }
154
155     w->region = xcb_generate_id(w->sc->dpy->conn);
156     xcb_xfixes_create_region_from_window(w->sc->dpy->conn, w->region,
157                                          w->id, XCB_SHAPE_SK_BOUNDING);
158     window_get_area((d_window_t*)w, &x, &y, &wi, &hei, &bw);
159     xcb_xfixes_translate_region(w->sc->dpy->conn, w->region, x+bw, y+bw);
160 }
161
162 static void
163 window_update_pixmap(d_window_priv_t *w)
164 {
165     if (window_is_zombie((d_window_t*)w)) return;
166
167     /* the pixmap may not be valid even though it is non-zero, but
168        we can free it anyways and let it fail.  we don't need to wait
169        for a response from the server */
170     if (w->pixmap) {
171         xcb_free_pixmap(w->sc->dpy->conn, w->pixmap);
172         w->pixmap = XCB_NONE;
173     }
174
175     //printf("updating pixmap for 0x%x\n", w->id);
176
177     /* we don't check the result of this call, because it seems that sometimes
178        the X server just doesn't reply.  if we check it, we end up hanging
179        sometimes waiting for the reply */
180     w->pixmap = xcb_generate_id(w->sc->dpy->conn);
181     xcb_composite_name_window_pixmap(w->sc->dpy->conn, w->id, w->pixmap);
182     //printf("requested pixmap sequence %u\n", w->ck_get_pixmap.sequence);
183     //fflush(stdout);
184     xcb_flush(w->sc->dpy->conn);
185 }
186
187 void
188 window_show(d_window_t *pubw)
189 {
190     d_window_priv_t *w = (d_window_priv_t*)pubw;
191     unsigned int mask;
192
193     assert(!w->mapped);
194
195     //printf("show window 0x%x\n", w->id);
196
197     /* make sure this is before we update the window's region */
198     if (w->sc->dpy->shape.present)
199         xcb_shape_select_input(w->sc->dpy->conn, w->id, TRUE);
200
201     mask = TOPLEVEL_WINDOW_EVENT_MASK;
202     xcb_change_window_attributes(w->sc->dpy->conn, w->id,
203                                  XCB_CW_EVENT_MASK, &mask);
204
205     window_update_pixmap(w);
206     window_update_region(w);
207     window_update_user_opacity(pubw);
208     window_update_type(w);
209     w->mapped = TRUE;
210
211     /* hold one reference for ourselves */
212     window_zombie_ref(pubw);
213 }
214
215 void
216 window_hide(d_window_t *pubw)
217 {
218     d_window_priv_t *w = (d_window_priv_t*)pubw;
219     unsigned int mask;
220
221     assert(w->mapped);
222
223     //printf("hide window 0x%x\n", w->id);
224     if (w->sc->dpy->shape.present)
225         xcb_shape_select_input(w->sc->dpy->conn, w->id, FALSE);
226     mask = 0;
227     xcb_change_window_attributes(w->sc->dpy->conn, w->id,
228                                  XCB_CW_EVENT_MASK, &mask);
229
230     w->mapped = FALSE;
231
232     /* try to free zombie things */
233     window_zombie_unref(pubw);
234 }
235
236 void
237 window_fake_unmapped(d_window_t *pubw)
238 {
239     d_window_priv_t *w = (d_window_priv_t*)pubw;
240
241     w->mapped = FALSE;
242 }
243
244 void
245 window_become_zombie(d_window_t *pubw)
246 {
247     d_window_priv_t *w = (d_window_priv_t*)pubw;
248
249     if (w->zombie) return;
250
251     w->zombie = TRUE;
252 }
253
254 void
255 window_zombie_dead(d_window_t *pubw)
256 {
257     d_window_priv_t *w = (d_window_priv_t*)pubw;
258
259     if (!w->zombie) return;
260
261     w->zombie = FALSE;
262 }
263
264 gboolean
265 window_is_zombie(d_window_t *pubw)
266 {
267     d_window_priv_t *w = (d_window_priv_t*)pubw;
268     return w->zombie;
269 }
270
271 gboolean
272 window_is_input_only(d_window_t *pubw)
273 {
274     d_window_priv_t *w = (d_window_priv_t*)pubw;
275     if (w->waiting_attr)
276         window_get_attributes_reply(w);
277     return w->input_only;
278 }
279
280 void
281 window_get_area(d_window_t *pubw, int *x, int *y, int *width, int *height,
282                 int *border_width)
283 {
284     d_window_priv_t *w = (d_window_priv_t*)pubw;
285     if (w->waiting_geom)
286         window_get_geometry_reply(w);
287     *x = w->x;
288     *y = w->y;
289     *width = w->w;
290     *height = w->h;
291     *border_width = w->bw;
292 }
293
294 static void
295 window_get_attributes_reply(d_window_priv_t *w)
296 {
297     xcb_get_window_attributes_reply_t *rep;
298     xcb_generic_error_t *err = NULL;
299
300     rep = xcb_get_window_attributes_reply(w->sc->dpy->conn,
301                                           w->ck_get_attr,
302                                           &err);
303
304     if (rep) {
305         w->input_only = rep->_class == XCB_WINDOW_CLASS_INPUT_ONLY;
306         w->attr_mapped = rep->map_state != XCB_MAP_STATE_UNMAPPED;
307         w->visual = rep->visual;
308         //printf("0x%x attributes mapped %d\n", w->id, w->mapped);
309         free(rep);
310     }
311     else {
312         w->input_only = TRUE;
313         w->attr_mapped = FALSE;
314         w->visual = XCB_NONE;
315     }
316     if (err) {
317         printf("error getting attributes for window 0x%x\n", w->id);
318         free(err);
319     }
320     w->waiting_attr = FALSE;
321 }
322
323 static void
324 window_get_geometry_reply(d_window_priv_t *w)
325 {
326     xcb_get_geometry_reply_t *rep;
327     xcb_generic_error_t *err = NULL;
328
329     rep = xcb_get_geometry_reply(w->sc->dpy->conn,
330                                  w->ck_get_geom,
331                                  &err);
332
333     if (rep) {
334         w->x = rep->x;
335         w->y = rep->y;
336         w->w = rep->width;
337         w->h = rep->height;
338         w->bw = rep->border_width;
339         w->argb = rep->depth == 32;
340         free(rep);
341     }
342     else {
343         w->x = w->y = -1;
344         w->w = w->h = 1;
345         w->bw = 0;
346         w->argb = FALSE;
347     }
348     if (err) {
349         printf("error getting geometry for window 0x%x\n", w->id);
350         free(err);
351     }
352     w->waiting_geom = FALSE;
353 }
354
355 gboolean
356 window_is_mapped(d_window_t *pubw)
357 {
358     d_window_priv_t *w = (d_window_priv_t*)pubw;
359     return w->mapped;
360 }
361
362 gboolean
363 window_is_attr_mapped(d_window_t *pubw)
364 {
365     d_window_priv_t *w = (d_window_priv_t*)pubw;
366     if (w->waiting_attr)
367         window_get_attributes_reply(w);
368     return w->attr_mapped;
369 }
370
371 gboolean
372 window_is_argb(d_window_t *pubw)
373 {
374     d_window_priv_t *w = (d_window_priv_t*)pubw;
375     if (w->waiting_geom)
376         window_get_geometry_reply(w);
377     return w->argb;
378 }
379
380 xcb_visualid_t
381 window_get_visual(d_window_t *pubw)
382 {
383     d_window_priv_t *w = (d_window_priv_t*)pubw;
384     if (w->waiting_attr)
385         window_get_attributes_reply(w);
386     return w->visual;
387 }
388
389 xcb_xfixes_region_t
390 window_get_region(d_window_t *pubw)
391 {
392     d_window_priv_t *w = (d_window_priv_t*)pubw;
393
394     return w->region;
395 }
396
397 void
398 window_configure(d_window_t *pubw, int x, int y, int width, int height,
399                  int border_width)
400 {
401     d_window_priv_t *w = (d_window_priv_t*)pubw;
402
403     /* this overrides any reply from our get_geometry call */
404     if (w->waiting_geom)
405         w->waiting_geom = FALSE;
406     w->x = x;
407     w->y = y;
408     w->w = width;
409     w->h = height;
410     w->bw = border_width;
411 }
412
413 void
414 window_move(d_window_t *pubw)
415 {
416     //d_window_priv_t *w = (d_window_priv_t*)pubw;
417     window_update_region((d_window_priv_t*)pubw);
418 }
419
420 void
421 window_resize(d_window_t *w)
422 {
423     window_update_pixmap((d_window_priv_t*)w);
424     window_update_region((d_window_priv_t*)w);
425 }
426
427 void
428 window_reshape(d_window_t *w)
429 {
430     window_update_region((d_window_priv_t*)w);
431 }
432
433 void window_opacity_change(d_window_t *w)
434 {
435     (void)w;
436 }
437
438 void
439 window_add_plugin_data(d_window_t *pubw, int id, void *data)
440 {
441     d_window_priv_t *w = (d_window_priv_t*)pubw;
442     plugin_data_add(w->plugin_data, id, data);
443 }
444
445 void*
446 window_find_plugin_data(d_window_t *pubw, int id)
447 {
448     d_window_priv_t *w = (d_window_priv_t*)pubw;
449     return plugin_data_find(w->plugin_data, id);
450 }
451
452 void
453 window_remove_plugin_data(d_window_t *pubw, int id)
454 {
455     d_window_priv_t *w = (d_window_priv_t*)pubw;
456     plugin_data_remove(w->plugin_data, id);
457 }
458
459 void
460 window_create_damage(d_window_t *pubw)
461 {
462     d_window_priv_t *w = (d_window_priv_t*)pubw;
463
464     if (!window_is_input_only(pubw)) {
465         assert(w->damage == XCB_NONE);
466         w->damage = xcb_generate_id(w->sc->dpy->conn);
467         //printf("creating damage 0x%x\n", w->damage);
468         xcb_damage_create(w->sc->dpy->conn, w->damage, w->id,
469                           XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY);
470     }
471 }
472
473 void
474 window_destroy_damage(d_window_t *pubw)
475 {
476     d_window_priv_t *w = (d_window_priv_t*)pubw;
477
478     if (w->damage) {
479         //printf("destroying damage 0x%x\n", w->damage);
480         xcb_damage_destroy(w->sc->dpy->conn, w->damage);
481         w->damage = XCB_NONE;
482     }
483 }
484
485 static void
486 type_request(d_display_t *dpy, d_window_priv_t *w, xcb_window_t *id, int nid)
487 {
488     xcb_get_property_cookie_t *cks;
489     int i;
490
491     cks = malloc(sizeof(xcb_get_property_cookie_t) * nid * 2);
492     for (i = 0; i < nid; ++i) {
493         cks[i*2+0] =
494             xcb_get_property_unchecked(dpy->conn, FALSE, id[i],
495                                        dpy->a.net_wm_window_type,
496                                        dpy->a.atom, 0, 20);
497         cks[i*2+1] =
498             xcb_get_property_unchecked(dpy->conn, FALSE, id[i],
499                                        dpy->a.wm_transient_for,
500                                        dpy->a.window, 0, 1);
501     }
502
503     for (i = 0; i < nid; ++i) {
504         xcb_get_property_reply_t *rep;
505         d_window_type_t type = DC_WINDOW_TYPE_INVALID;
506
507         rep = xcb_get_property_reply(dpy->conn, cks[i*2+0], NULL);
508         if (rep) {
509             if (!w->type && rep->type == dpy->a.atom && rep->length >= 1) {
510                 xcb_atom_t *a = (xcb_atom_t*)(xcb_get_property_value(rep));
511                 uint32_t j;
512                 for (j = 0; j < rep->length; ++j) {
513                     if (a[j] == dpy->a.net_wm_window_type_normal) {
514                         type = DC_WINDOW_TYPE_NORMAL; break;
515                     }
516                     if (a[j] == dpy->a.net_wm_window_type_desktop) {
517                         type = DC_WINDOW_TYPE_DESKTOP; break;
518                     }
519                     if (a[j] == dpy->a.net_wm_window_type_dock) {
520                         type = DC_WINDOW_TYPE_DOCK; break;
521                     }
522                     if (a[j] == dpy->a.net_wm_window_type_dialog) {
523                         type = DC_WINDOW_TYPE_DIALOG; break;
524                     }
525                     if (a[j] == dpy->a.net_wm_window_type_toolbar) {
526                         type = DC_WINDOW_TYPE_TOOLBAR; break;
527                     }
528                     if (a[j] == dpy->a.net_wm_window_type_menu) {
529                         type = DC_WINDOW_TYPE_MENU; break;
530                     }
531                     if (a[j] == dpy->a.net_wm_window_type_utility) {
532                         type = DC_WINDOW_TYPE_UTILITY; break;
533                     }
534                     if (a[j] == dpy->a.net_wm_window_type_splash) {
535                         type = DC_WINDOW_TYPE_SPLASH; break;
536                     }
537                     if (a[j] == dpy->a.net_wm_window_type_dropdown_menu) {
538                         type = DC_WINDOW_TYPE_DROPDOWN_MENU; break;
539                     }
540                     if (a[j] == dpy->a.net_wm_window_type_popup_menu) {
541                         type = DC_WINDOW_TYPE_POPUP_MENU; break;
542                     }
543                     if (a[j] == dpy->a.net_wm_window_type_tooltip) {
544                         type = DC_WINDOW_TYPE_TOOLTIP; break;
545                     }
546                     if (a[j] == dpy->a.net_wm_window_type_notification) {
547                         type = DC_WINDOW_TYPE_NOTIFICATION; break;
548                     }
549                     if (a[j] == dpy->a.net_wm_window_type_combo) {
550                         type = DC_WINDOW_TYPE_COMBO; break;
551                     }
552                     if (a[j] == dpy->a.net_wm_window_type_dnd) {
553                         type = DC_WINDOW_TYPE_DND; break;
554                     }
555                 }
556             }
557             free(rep);
558         }
559         rep = xcb_get_property_reply(dpy->conn, cks[i*2+1], NULL);
560         if (rep) {
561             if (!w->type && rep->type == dpy->a.window && rep->length == 1)
562                 type = DC_WINDOW_TYPE_DIALOG;
563             free(rep);
564         }
565
566         /* also save the window id that we got the type/transient hint from */
567         if (!w->type && type) {
568             w->type = type;
569             w->client = id[i];
570         }
571     }
572
573
574     for (i = 0; i < nid && !w->type; ++i) {
575         xcb_query_tree_cookie_t ck;
576         xcb_query_tree_reply_t *rep;
577
578         ck = xcb_query_tree(dpy->conn, id[i]);
579         rep = xcb_query_tree_reply(dpy->conn, ck, NULL);
580         if (rep) {
581             int num = xcb_query_tree_children_length(rep);
582             xcb_window_t *ch = xcb_query_tree_children(rep);
583
584             type_request(dpy, w, ch, num);
585             free(rep);
586         }
587     }
588 }
589
590 static void
591 window_update_type(d_window_priv_t *w)
592 {
593     w->type = DC_WINDOW_TYPE_INVALID;
594     type_request(w->sc->dpy, w, &w->id, 1);
595     if (!w->type) {
596         w->type = DC_WINDOW_TYPE_NORMAL;
597         w->client = w->id;
598     }
599     //printf("window 0x%x type %d\n", w->id, w->type);
600 }
601
602 d_window_type_t
603 window_get_type(d_window_t *pubw)
604 {
605     d_window_priv_t *w = (d_window_priv_t*)pubw;
606
607     return w->type;
608 }
609
610 uint16_t
611 window_get_opacity(d_window_t *pubw)
612 {
613     d_window_priv_t *w = (d_window_priv_t*)pubw;
614     unsigned long long l;
615
616     if (w->waiting_opac) {
617         xcb_get_property_reply_t *rep;
618
619         w->user_opacity = 0xffff;
620         rep = xcb_get_property_reply(w->sc->dpy->conn, w->ck_get_opac, NULL);
621         if (rep) {
622             if (rep->type == w->sc->dpy->a.cardinal && rep->length >= 1) {
623                 l = ((uint32_t*)xcb_get_property_value(rep))[0];
624                 l = 0xffff * l / 0xffffffff;
625                 w->user_opacity = l;
626             }
627             free(rep);
628         }
629         w->waiting_opac = FALSE;
630     }
631
632     l = w->opacity;
633     l = l * w->user_opacity / 0xffff;
634     return l;
635 }
636
637 void
638 window_set_opacity(d_window_t *pubw, uint16_t o)
639 {
640     d_window_priv_t *w = (d_window_priv_t*)pubw;
641
642     w->opacity = o;
643     if (w->mapped || w->zombie) {
644         w->sc->window_opacity_change(pubw);
645         screen_refresh(w->sc);
646     }
647     //printf("mapped %d opacity 0x%x\n", w->mapped, w->opacity);
648 }
649
650 void
651 window_zombie_ref(d_window_t *pubw)
652 {
653     d_window_priv_t *w = (d_window_priv_t*)pubw;
654
655     ++w->zombieref;
656 }
657
658 void
659 window_zombie_unref(d_window_t *pubw)
660 {
661     d_window_priv_t *w = (d_window_priv_t*)pubw;
662
663     if (--w->zombieref == 0) {
664         w->zombie = FALSE;
665
666         if (w->region) {
667             xcb_xfixes_destroy_region(w->sc->dpy->conn, w->region);
668             w->region = XCB_NONE;
669         }
670
671         if (w->pixmap) {
672             /* this may cause an error if the pixmap was never valid, but
673                that's fine */
674             xcb_free_pixmap(w->sc->dpy->conn, w->pixmap);
675             w->pixmap = XCB_NONE;
676         }
677
678         w->sc->window_zombie_dead(pubw);
679     }
680 }