]> git.tdb.fi Git - geometrycompositor.git/blob - source/main.c
React to ReparentNotify events
[geometrycompositor.git] / source / main.c
1 #define GL_GLEXT_PROTOTYPES
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <signal.h>
6 #include <X11/Xlib.h>
7 #include <X11/extensions/Xcomposite.h>
8 #include <X11/extensions/Xdamage.h>
9 #include <X11/extensions/shape.h>
10 #include <GL/gl.h>
11 #include <GL/glx.h>
12
13 typedef struct CompositedWindow
14 {
15         Window window;
16         int x;
17         int y;
18         unsigned width;
19         unsigned height;
20         unsigned border;
21         int map_state;
22         Damage damage;
23         Pixmap pixmap;
24         GLXPixmap glx_pixmap;
25         unsigned texture;
26 } CompositedWindow;
27
28 typedef struct CompositedScreen
29
30         int number;
31         Window root;
32         unsigned width;
33         unsigned height;
34         Window overlay;
35         Window render_window;
36         GLXFBConfig fbconfig;
37         GLXWindow glx_window;
38         GLXContext glx_context;
39         unsigned shaders[2];
40         unsigned program;
41         unsigned geometry_loc;
42         unsigned vertex_buffer;
43         unsigned vertex_array;
44         CompositedWindow *windows;
45         unsigned nwindows;
46         unsigned windows_capacity;
47         int dirty;
48 } CompositedScreen;
49
50 typedef struct Compositor
51 {
52         Display *display;
53         CompositedScreen *screens;
54         unsigned nscreens;
55         int damage_event;
56         PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB;
57         PFNGLXBINDTEXIMAGEEXTPROC glXBindTexImageEXT;
58         PFNGLXRELEASETEXIMAGEEXTPROC glXReleaseTexImageEXT;
59         int dirty;
60 } Compositor;
61
62 static const char *vshader =
63         "#version 150\n"
64         "uniform vec4 geometry;\n"
65         "in vec2 vertex;\n"
66         "out vec2 texcoord;\n"
67         "void main()\n"
68         "{\n"
69         "  gl_Position = vec4((geometry.xy+vertex*geometry.zw)*2.0-1.0, 0.0, 1.0);\n"
70         "  texcoord = vec2(vertex.x, 1.0-vertex.y);\n"
71         "}\n";
72
73 static const char *fshader =
74         "#version 150\n"
75         "uniform sampler2D window;\n"
76         "in vec2 texcoord;\n"
77         "out vec4 frag_color;\n"
78         "void main()\n"
79         "{\n"
80         "       frag_color = texture(window, texcoord);\n"
81         "}\n";
82
83 static const float vertices[] =
84 {
85         0.0f, 1.0f,
86         0.0f, 0.0f,
87         1.0f, 1.0f,
88         1.0f, 0.0f
89 };
90
91 int terminate_requested = 0;
92
93 int x_error_handler(Display *display, XErrorEvent *event)
94 {
95         printf("Ignoring X error %d on resource %lx\n", event->error_code, event->resourceid);
96         (void)display;
97         return 0;
98 }
99
100 int with_error(const char *message)
101 {
102         fprintf(stderr, "%s\n", message);
103         return 0;
104 }
105
106 int initialize_gl(Compositor *compositor, CompositedScreen *screen)
107 {
108         int attribs[9];
109         unsigned i;
110         GLXFBConfig *configs;
111         int nconfigs;
112         XVisualInfo *vi;
113         XSetWindowAttributes win_attr;
114
115         i = 0;
116         attribs[i++] = GLX_DRAWABLE_TYPE;
117         attribs[i++] = GLX_WINDOW_BIT;
118         attribs[i++] = GLX_RENDER_TYPE;
119         attribs[i++] = GLX_RGBA_BIT;
120         attribs[i++] = GLX_DOUBLEBUFFER;
121         attribs[i++] = True;
122         attribs[i++] = GLX_BIND_TO_TEXTURE_RGBA_EXT;
123         attribs[i++] = True;
124         attribs[i] = None;
125
126         configs = glXChooseFBConfig(compositor->display, screen->number, attribs, &nconfigs);
127         if(!configs || !nconfigs)
128                 return with_error("Could not find a suitable FBConfig");
129         screen->fbconfig = configs[0];
130         XFree(configs);
131
132         vi = glXGetVisualFromFBConfig(compositor->display, screen->fbconfig);
133         win_attr.colormap = XCreateColormap(compositor->display, screen->root, vi->visual, AllocNone);
134         screen->render_window = XCreateWindow(compositor->display, screen->overlay, 0, 0, screen->width, screen->height, 0, vi->depth, InputOutput, vi->visual, CWColormap, &win_attr);
135         XMapWindow(compositor->display, screen->render_window);
136
137         screen->glx_window = glXCreateWindow(compositor->display, screen->fbconfig, screen->render_window, NULL);
138
139         i = 0;
140         attribs[i++] = GLX_CONTEXT_FLAGS_ARB;
141         attribs[i++] = GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB;
142         attribs[i++] = GLX_CONTEXT_MAJOR_VERSION_ARB;
143         attribs[i++] = 3;
144         attribs[i++] = GLX_CONTEXT_MINOR_VERSION_ARB;
145         attribs[i++] = 0;
146         attribs[i] = None;
147
148         screen->glx_context = compositor->glXCreateContextAttribsARB(compositor->display, screen->fbconfig, NULL, True, attribs);
149
150         XFree(vi);
151
152         return 1;
153 }
154
155 void use_gl(Compositor *compositor, CompositedScreen *screen)
156 {
157         glXMakeContextCurrent(compositor->display, screen->glx_window, screen->glx_window, screen->glx_context);
158 }
159
160 unsigned compile_shader(GLenum type, const char *source)
161 {
162         unsigned shader;
163         int status;
164         char info_log[1024];
165         GLsizei length;
166         
167         shader = glCreateShader(type);
168         glShaderSource(shader, 1, &source, NULL);
169         glCompileShader(shader);
170         glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
171         glGetShaderInfoLog(shader, sizeof(info_log), &length, info_log);
172         if(!status)
173         {
174                 fprintf(stderr, "Shader compilation failed:\n%s\n", info_log);
175                 glDeleteShader(shader);
176                 return 0;
177         }
178         else if(length)
179                 printf("Shader info log:\n%s\n", info_log);
180
181         return shader;
182 }
183
184 int create_gl_resources(Compositor *compositor, CompositedScreen *screen)
185 {
186         int status;
187         char info_log[1024];
188         GLsizei length;
189
190         use_gl(compositor, screen);
191
192         screen->shaders[0] = compile_shader(GL_VERTEX_SHADER, vshader);
193         screen->shaders[1] = compile_shader(GL_FRAGMENT_SHADER, fshader);
194         if(!screen->shaders[0] || !screen->shaders[1])
195                 return 0;
196
197         screen->program = glCreateProgram();
198         glAttachShader(screen->program, screen->shaders[0]);
199         glAttachShader(screen->program, screen->shaders[1]);
200         glBindAttribLocation(screen->program, 0, "vertex");
201         glLinkProgram(screen->program);
202
203         screen->geometry_loc = glGetUniformLocation(screen->program, "geometry");
204
205         glGetProgramiv(screen->program, GL_LINK_STATUS, &status);
206         glGetProgramInfoLog(screen->program, sizeof(info_log), &length, info_log);
207         if(!status)
208         {
209                 fprintf(stderr, "Program link failed:\n%s\n", info_log);
210                 glDeleteProgram(screen->program);
211                 return 0;
212         }
213         else if(length)
214                 printf("Program info log:\n%s\n", info_log);
215
216         glGenBuffers(1, &screen->vertex_buffer);
217         glBindBuffer(GL_ARRAY_BUFFER, screen->vertex_buffer);
218         glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
219
220         glGenVertexArrays(1, &screen->vertex_array);
221         glBindVertexArray(screen->vertex_array);
222         glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
223         glEnableVertexAttribArray(0);
224
225         glBindBuffer(GL_ARRAY_BUFFER, 0);
226
227         return 1;
228 }
229
230 CompositedWindow *find_window(CompositedScreen *screen, Window w)
231 {
232         unsigned i;
233
234         for(i=0; i<screen->nwindows; ++i)
235                 if(screen->windows[i].window==w)
236                         return &screen->windows[i];
237
238         return NULL;
239 }
240
241 void create_window_pixmap(Compositor *compositor, CompositedScreen *screen, CompositedWindow *window)
242 {
243         int attribs[5];
244         unsigned i;
245
246         if(window->pixmap)
247         {
248                 glXDestroyPixmap(compositor->display, window->glx_pixmap);
249                 XFreePixmap(compositor->display, window->pixmap);
250         }
251
252         i = 0;
253         attribs[i++] = GLX_TEXTURE_TARGET_EXT;
254         attribs[i++] = GLX_TEXTURE_2D_EXT;
255         attribs[i++] = GLX_TEXTURE_FORMAT_EXT;
256         attribs[i++] = GLX_TEXTURE_FORMAT_RGBA_EXT;
257         attribs[i++] = None;
258
259         window->pixmap = XCompositeNameWindowPixmap(compositor->display, window->window);
260         window->glx_pixmap = glXCreatePixmap(compositor->display, screen->fbconfig, window->pixmap, attribs);
261 }
262
263 void add_window(Compositor *compositor, CompositedScreen *screen, Window w)
264 {
265         CompositedWindow *window;
266         XWindowAttributes win_attr;
267
268         if(w==screen->root || w==screen->overlay)
269                 return;
270
271         if(!XGetWindowAttributes(compositor->display, w, &win_attr))
272         {
273                 printf("XGetWindowAttributes failed; probably the window was already destroyed\n");
274                 return;
275         }
276         if(win_attr.class==InputOnly)
277                 return;
278
279         if(find_window(screen, w))
280                 return;
281
282         if(screen->nwindows==screen->windows_capacity)
283         {
284                 screen->windows = (CompositedWindow *)realloc(screen->windows, (screen->windows_capacity+1)*sizeof(CompositedWindow));
285                 ++screen->windows_capacity;
286         }
287
288         window = &screen->windows[screen->nwindows++];
289
290         window->window = w;
291         window->x = win_attr.x;
292         window->y = win_attr.y;
293         window->width = win_attr.width;
294         window->height = win_attr.height;
295         window->border = win_attr.border_width;
296         window->map_state = win_attr.map_state;
297
298         window->damage = XDamageCreate(compositor->display, window->window, XDamageReportNonEmpty);
299         window->pixmap = None;
300         window->glx_pixmap = None;
301
302         XCompositeRedirectWindow(compositor->display, window->window, CompositeRedirectManual);
303         if(window->map_state==IsViewable)
304                 create_window_pixmap(compositor, screen, window);
305
306         glGenTextures(1, &window->texture);
307         glBindTexture(GL_TEXTURE_2D, window->texture);
308         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
309         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
310         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
311 }
312
313 void remove_window(Compositor *compositor, CompositedScreen *screen, Window w, int destroyed)
314 {
315         unsigned i;
316
317         for(i=0; i<screen->nwindows; ++i)
318                 if(screen->windows[i].window==w)
319                 {
320                         glDeleteTextures(1, &screen->windows[i].texture);
321                         if(!destroyed)
322                         {
323                                 XDamageDestroy(compositor->display, screen->windows[i].damage);
324                                 if(screen->windows[i].pixmap)
325                                 {
326                                         glXDestroyPixmap(compositor->display, screen->windows[i].glx_pixmap);
327                                         XFreePixmap(compositor->display, screen->windows[i].pixmap);
328                                 }
329                                 XCompositeUnredirectWindow(compositor->display, screen->windows[i].window, CompositeRedirectManual);
330                         }
331
332                         --screen->nwindows;
333                         for(; i<screen->nwindows; ++i)
334                                 screen->windows[i] = screen->windows[i+1];
335                 }
336 }
337
338 CompositedScreen *find_screen_by_root(Compositor *compositor, Window root)
339 {
340         unsigned i;
341
342         for(i=0; i<compositor->nscreens; ++i)
343                 if(compositor->screens[i].root==root)
344                         return &compositor->screens[i];
345
346         return NULL;
347 }
348
349 CompositedScreen *find_screen_by_window(Compositor *compositor, Window w)
350 {
351         unsigned i, j;
352
353         for(i=0; i<compositor->nscreens; ++i)
354                 for(j=0; j<compositor->screens[i].nwindows; ++j)
355                         if(compositor->screens[i].windows[j].window==w)
356                                 return &compositor->screens[i];
357
358         return NULL;
359 }
360
361 int initialize_screen(Compositor *compositor, unsigned number)
362 {
363         CompositedScreen *screen;
364         const char *extensions;
365         Window dummy_root;
366         int x, y;
367         unsigned border;
368         unsigned depth;
369         Window dummy_parent;
370         Window *children;
371         unsigned nchildren;
372         unsigned i;
373
374         screen = &compositor->screens[number];
375         screen->number = number;
376
377         extensions = glXQueryExtensionsString(compositor->display, screen->number);
378         if(!strstr(extensions, "GLX_ARB_create_context"))
379                 return with_error("GLX_ARB_create_context is required");
380         if(!strstr(extensions, "GLX_EXT_texture_from_pixmap"))
381                 return with_error("GLX_EXT_texture_from_pixmap is required");
382
383         screen->root = RootWindow(compositor->display, screen->number);
384         XSelectInput(compositor->display, screen->root, SubstructureNotifyMask);
385         screen->overlay = XCompositeGetOverlayWindow(compositor->display, screen->root);
386         XGetGeometry(compositor->display, screen->overlay, &dummy_root, &x, &y, &screen->width, &screen->height, &border, &depth);
387         XShapeCombineRectangles(compositor->display, screen->overlay, ShapeInput, 0, 0, NULL, 0, ShapeSet, Unsorted);
388
389         if(!initialize_gl(compositor, screen))
390                 return 0;
391
392         if(!create_gl_resources(compositor, screen))
393                 return 0;
394
395         XQueryTree(compositor->display, screen->root, &dummy_root, &dummy_parent, &children, &nchildren);
396
397         screen->windows = (CompositedWindow *)malloc(nchildren*sizeof(CompositedWindow));
398         screen->nwindows = 0;
399         screen->windows_capacity = nchildren;
400
401         for(i=0; i<nchildren; ++i)
402                 add_window(compositor, screen, children[i]);
403
404         XFree(children);
405
406         screen->dirty = 1;
407
408         return 1;
409 }
410
411 int initialize_compositor(Compositor *compositor)
412 {
413         int event_base;
414         int error_base;
415         int major_ver;
416         int minor_ver;
417         unsigned i;
418
419         compositor->display = XOpenDisplay(NULL);
420         if(!compositor->display)
421                 return with_error("Could not open X display");
422
423         XSetErrorHandler(&x_error_handler);
424
425         if(!XCompositeQueryExtension(compositor->display, &event_base, &error_base))
426                 return with_error("XComposite is required but was not found");
427         else if(!XCompositeQueryVersion(compositor->display, &major_ver, &minor_ver))
428                 return with_error("Cannot determine XComposite version");
429         else if(major_ver==0 && minor_ver<3)
430                 return with_error("XComposite 0.3 or later is required");
431
432         if(!glXQueryExtension(compositor->display, &event_base, &error_base))
433                 return with_error("GLX is required but was not found");
434         else if(!glXQueryVersion(compositor->display, &major_ver, &minor_ver))
435                 return with_error("Cannot determine GLX version");
436         else if(major_ver<1 || (major_ver==1 && minor_ver<4))
437                 return with_error("GLX 1.4 or later is required");
438
439         if(!XDamageQueryExtension(compositor->display, &compositor->damage_event, &error_base))
440                 return with_error("XDamage is required but was not found");
441         else if(!XDamageQueryVersion(compositor->display, &major_ver, &minor_ver))
442                 return with_error("Cannot determine XDamage version");
443         else if(major_ver<1)
444                 return with_error("XDamage 1.0 or later is required");
445
446         if(!XShapeQueryExtension(compositor->display, &event_base, &error_base))
447                 return with_error("XShape is required but was not found");
448         else if(!XShapeQueryVersion(compositor->display, &major_ver, &minor_ver))
449                 return with_error("Cannot determine XShape version");
450         else if(major_ver<1 || (major_ver==1 && minor_ver<1))
451                 return with_error("XShape 1.1 or later is required");
452
453         compositor->glXCreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddress((unsigned char *)"glXCreateContextAttribsARB");
454         compositor->glXBindTexImageEXT = (PFNGLXBINDTEXIMAGEEXTPROC)glXGetProcAddress((unsigned char *)"glXBindTexImageEXT");
455         compositor->glXReleaseTexImageEXT = (PFNGLXRELEASETEXIMAGEEXTPROC)glXGetProcAddress((unsigned char *)"glXReleaseTexImageEXT");
456
457         compositor->nscreens = ScreenCount(compositor->display);
458         compositor->screens = (CompositedScreen *)malloc(compositor->nscreens*sizeof(CompositedScreen));
459         for(i=0; i<compositor->nscreens; ++i)
460                 if(!initialize_screen(compositor, i))
461                         return 0;
462
463         compositor->dirty = 1;
464
465         return 1;
466 }
467
468 void shutdown_screen(Compositor *compositor, CompositedScreen *screen)
469 {
470         unsigned i;
471
472         use_gl(compositor, screen);
473
474         for(i=0; i<screen->nwindows; ++i)
475         {
476                 glDeleteTextures(1, &screen->windows[i].texture);
477                 if(screen->windows[i].pixmap)
478                 {
479                         glXDestroyPixmap(compositor->display, screen->windows[i].glx_pixmap);
480                         XFreePixmap(compositor->display, screen->windows[i].pixmap);
481                         XDamageDestroy(compositor->display, screen->windows[i].damage);
482                 }
483         }
484
485         glXMakeContextCurrent(compositor->display, 0, 0, NULL);
486         glXDestroyContext(compositor->display, screen->glx_context);
487         glXDestroyWindow(compositor->display, screen->glx_window);
488         XDestroyWindow(compositor->display, screen->render_window);
489
490         XCompositeReleaseOverlayWindow(compositor->display, screen->overlay);
491
492         free(screen->windows);
493 }
494
495 void shutdown_compositor(Compositor *compositor)
496 {
497         unsigned i;
498
499         for(i=0; i<compositor->nscreens; ++i)
500                 shutdown_screen(compositor, &compositor->screens[i]);
501         free(compositor->screens);
502
503         XCloseDisplay(compositor->display);
504 }
505
506 void process_create_window_event(Compositor *compositor, XCreateWindowEvent *event)
507 {
508         CompositedScreen *screen = find_screen_by_root(compositor, event->parent);
509         if(!screen)
510                 return;
511
512         add_window(compositor, screen, event->window);
513 }
514
515 void process_destroy_window_event(Compositor *compositor, XDestroyWindowEvent *event)
516 {
517         CompositedScreen *screen = find_screen_by_root(compositor, event->event);
518         if(!screen)
519                 return;
520
521         remove_window(compositor, screen, event->window, 1);
522 }
523
524 void process_map_event(Compositor *compositor, XMapEvent *event)
525 {
526         CompositedScreen *screen = find_screen_by_root(compositor, event->event);
527         if(!screen)
528                 return;
529
530         CompositedWindow *window = find_window(screen, event->window);
531         if(window)
532         {
533                 window->map_state = IsViewable;
534                 create_window_pixmap(compositor, screen, window);
535         }
536 }
537
538 void process_unmap_event(Compositor *compositor, XUnmapEvent *event)
539 {
540         CompositedScreen *screen = find_screen_by_root(compositor, event->event);
541         if(!screen)
542                 return;
543
544         CompositedWindow *window = find_window(screen, event->window);
545         if(window)
546                 window->map_state = IsUnviewable;
547 }
548
549 void process_reparent_event(Compositor *compositor, XReparentEvent *event)
550 {
551         CompositedScreen *screen = find_screen_by_root(compositor, event->event);
552         if(!screen)
553                 return;
554
555         if(event->parent==screen->root)
556                 add_window(compositor, screen, event->window);
557         else
558                 remove_window(compositor, screen, event->window, 0);
559 }
560
561 void process_damage_event(Compositor *compositor, XDamageNotifyEvent *event)
562 {
563         CompositedScreen *screen = find_screen_by_window(compositor, event->drawable);
564         if(!screen)
565                 return;
566
567         screen->dirty = 1;
568         compositor->dirty = 1;
569 }
570
571 int process_event(Compositor *compositor)
572 {
573         XEvent event;
574         if(compositor->dirty)
575         {
576                 if(!XCheckMaskEvent(compositor->display, -1, &event))
577                         return 0;
578         }
579         else
580                 XNextEvent(compositor->display, &event);
581
582         switch(event.type)
583         {
584         case CreateNotify:
585                 process_create_window_event(compositor, &event.xcreatewindow);
586                 break;
587         case DestroyNotify:
588                 process_destroy_window_event(compositor, &event.xdestroywindow);
589                 break;
590         case MapNotify:
591                 process_map_event(compositor, &event.xmap);
592                 break;
593         case UnmapNotify:
594                 process_unmap_event(compositor, &event.xunmap);
595                 break;
596         case ReparentNotify:
597                 process_reparent_event(compositor, &event.xreparent);
598                 break;
599         default:
600                 if(event.type==compositor->damage_event+XDamageNotify)
601                         process_damage_event(compositor, (XDamageNotifyEvent *)&event);
602                 else
603                         printf("Event %d\n", event.type);
604         }
605
606         return 1;
607 }
608
609 void refresh_screens(Compositor *compositor)
610 {
611         unsigned i, j;
612
613         for(i=0; i<compositor->nscreens; ++i)
614         {
615                 CompositedScreen *screen = &compositor->screens[i];
616                 use_gl(compositor, screen);
617
618                 glClearColor(0.5f, 0.5f, 0.5f, 0.0f);
619                 glClear(GL_COLOR_BUFFER_BIT);
620
621                 glUseProgram(screen->program);
622                 glBindVertexArray(screen->vertex_array);
623                 for(j=0; j<screen->nwindows; ++j)
624                 {
625                         CompositedWindow *window = &screen->windows[j];
626                         if(window->map_state==IsViewable)
627                         {
628                                 glBindTexture(GL_TEXTURE_2D, window->texture);
629                                 compositor->glXBindTexImageEXT(compositor->display, window->glx_pixmap, GLX_FRONT_LEFT_EXT, NULL);
630                                 glUniform4f(screen->geometry_loc, (float)window->x/screen->width, (float)(screen->height-window->y-window->height)/screen->height, (float)(window->width+2*window->border)/screen->width, (float)(window->height+2*window->border)/screen->height);
631                                 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
632                                 compositor->glXReleaseTexImageEXT(compositor->display, window->glx_pixmap, GLX_FRONT_LEFT_EXT);
633                                 XDamageSubtract(compositor->display, window->damage, None, None);
634                         }
635                 }
636                 glXSwapBuffers(compositor->display, screen->glx_window);
637
638                 screen->dirty = 0;
639         }
640
641         compositor->dirty = 0;
642 }
643
644 void sighandler(int sig)
645 {
646         terminate_requested = 1;
647         (void)sig;
648 }
649
650 int main()
651 {
652         Compositor compositor;
653
654         signal(SIGINT, &sighandler);
655         signal(SIGTERM, &sighandler);
656
657         if(!initialize_compositor(&compositor))
658                 return 1;
659
660         while(!terminate_requested)
661         {
662                 if(!process_event(&compositor))
663                         refresh_screens(&compositor);
664         }
665
666         shutdown_compositor(&compositor);
667
668         return 0;
669 }