1
2/*
3 * Copyright 2011 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8#include <X11/Xlib.h>
9#include <X11/Xatom.h>
10#include <X11/XKBlib.h>
11#include <GL/glx.h>
12#include <GL/gl.h>
13#include <GL/glu.h>
14
15#include "SkWindow.h"
16
17#include "SkBitmap.h"
18#include "SkCanvas.h"
19#include "SkColor.h"
20#include "SkEvent.h"
21#include "SkKey.h"
22#include "SkWindow.h"
23#include "XkeysToSkKeys.h"
24extern "C" {
25    #include "keysym2ucs.h"
26}
27
28const int WIDTH = 500;
29const int HEIGHT = 500;
30
31// Determine which events to listen for.
32const long EVENT_MASK = StructureNotifyMask|ButtonPressMask|ButtonReleaseMask
33        |ExposureMask|PointerMotionMask|KeyPressMask|KeyReleaseMask;
34
35SkOSWindow::SkOSWindow(void*)
36    : fVi(NULL)
37    , fMSAASampleCount(0) {
38    fUnixWindow.fDisplay = NULL;
39    fUnixWindow.fGLContext = NULL;
40    this->initWindow(0, NULL);
41    this->resize(WIDTH, HEIGHT);
42}
43
44SkOSWindow::~SkOSWindow() {
45    this->closeWindow();
46}
47
48void SkOSWindow::closeWindow() {
49    if (fUnixWindow.fDisplay) {
50        this->detach();
51        SkASSERT(fUnixWindow.fGc);
52        XFreeGC(fUnixWindow.fDisplay, fUnixWindow.fGc);
53        fUnixWindow.fGc = NULL;
54        XDestroyWindow(fUnixWindow.fDisplay, fUnixWindow.fWin);
55        fVi = NULL;
56        XCloseDisplay(fUnixWindow.fDisplay);
57        fUnixWindow.fDisplay = NULL;
58        fMSAASampleCount = 0;
59    }
60}
61
62void SkOSWindow::initWindow(int requestedMSAASampleCount, AttachmentInfo* info) {
63    if (fMSAASampleCount != requestedMSAASampleCount) {
64        this->closeWindow();
65    }
66    // presence of fDisplay means we already have a window
67    if (fUnixWindow.fDisplay) {
68        if (info) {
69            if (fVi) {
70                glXGetConfig(fUnixWindow.fDisplay, fVi, GLX_SAMPLES_ARB, &info->fSampleCount);
71                glXGetConfig(fUnixWindow.fDisplay, fVi, GLX_STENCIL_SIZE, &info->fStencilBits);
72            } else {
73                info->fSampleCount = 0;
74                info->fStencilBits = 0;
75            }
76        }
77        return;
78    }
79    fUnixWindow.fDisplay = XOpenDisplay(NULL);
80    Display* dsp = fUnixWindow.fDisplay;
81    if (NULL == dsp) {
82        SkDebugf("Could not open an X Display");
83        return;
84    }
85    // Attempt to create a window that supports GL
86    GLint att[] = {
87        GLX_RGBA,
88        GLX_DEPTH_SIZE, 24,
89        GLX_DOUBLEBUFFER,
90        GLX_STENCIL_SIZE, 8,
91        None
92    };
93    SkASSERT(NULL == fVi);
94    if (requestedMSAASampleCount > 0) {
95        static const GLint kAttCount = SK_ARRAY_COUNT(att);
96        GLint msaaAtt[kAttCount + 4];
97        memcpy(msaaAtt, att, sizeof(att));
98        SkASSERT(None == msaaAtt[kAttCount - 1]);
99        msaaAtt[kAttCount - 1] = GLX_SAMPLE_BUFFERS_ARB;
100        msaaAtt[kAttCount + 0] = 1;
101        msaaAtt[kAttCount + 1] = GLX_SAMPLES_ARB;
102        msaaAtt[kAttCount + 2] = requestedMSAASampleCount;
103        msaaAtt[kAttCount + 3] = None;
104        fVi = glXChooseVisual(dsp, DefaultScreen(dsp), msaaAtt);
105        fMSAASampleCount = requestedMSAASampleCount;
106    }
107    if (NULL == fVi) {
108        fVi = glXChooseVisual(dsp, DefaultScreen(dsp), att);
109        fMSAASampleCount = 0;
110    }
111
112    if (fVi) {
113        if (info) {
114            glXGetConfig(dsp, fVi, GLX_SAMPLES_ARB, &info->fSampleCount);
115            glXGetConfig(dsp, fVi, GLX_STENCIL_SIZE, &info->fStencilBits);
116        }
117        Colormap colorMap = XCreateColormap(dsp,
118                                            RootWindow(dsp, fVi->screen),
119                                            fVi->visual,
120                                             AllocNone);
121        XSetWindowAttributes swa;
122        swa.colormap = colorMap;
123        swa.event_mask = EVENT_MASK;
124        fUnixWindow.fWin = XCreateWindow(dsp,
125                                         RootWindow(dsp, fVi->screen),
126                                         0, 0, // x, y
127                                         WIDTH, HEIGHT,
128                                         0, // border width
129                                         fVi->depth,
130                                         InputOutput,
131                                         fVi->visual,
132                                         CWEventMask | CWColormap,
133                                         &swa);
134    } else {
135        if (info) {
136            info->fSampleCount = 0;
137            info->fStencilBits = 0;
138        }
139        // Create a simple window instead.  We will not be able to show GL
140        fUnixWindow.fWin = XCreateSimpleWindow(dsp,
141                                               DefaultRootWindow(dsp),
142                                               0, 0,  // x, y
143                                               WIDTH, HEIGHT,
144                                               0,     // border width
145                                               0,     // border value
146                                               0);    // background value
147    }
148    this->mapWindowAndWait();
149    fUnixWindow.fGc = XCreateGC(dsp, fUnixWindow.fWin, 0, NULL);
150}
151
152static unsigned getModi(const XEvent& evt) {
153    static const struct {
154        unsigned    fXMask;
155        unsigned    fSkMask;
156    } gModi[] = {
157        // X values found by experiment. Is there a better way?
158        { 1,    kShift_SkModifierKey },
159        { 4,    kControl_SkModifierKey },
160        { 8,    kOption_SkModifierKey },
161    };
162
163    unsigned modi = 0;
164    for (size_t i = 0; i < SK_ARRAY_COUNT(gModi); ++i) {
165        if (evt.xkey.state & gModi[i].fXMask) {
166            modi |= gModi[i].fSkMask;
167        }
168    }
169    return modi;
170}
171
172static SkMSec gTimerDelay;
173
174static bool MyXNextEventWithDelay(Display* dsp, XEvent* evt) {
175    // Check for pending events before entering the select loop. There might
176    // be events in the in-memory queue but not processed yet.
177    if (XPending(dsp)) {
178        XNextEvent(dsp, evt);
179        return true;
180    }
181
182    SkMSec ms = gTimerDelay;
183    if (ms > 0) {
184        int x11_fd = ConnectionNumber(dsp);
185        fd_set input_fds;
186        FD_ZERO(&input_fds);
187        FD_SET(x11_fd, &input_fds);
188
189        timeval tv;
190        tv.tv_sec = ms / 1000;              // seconds
191        tv.tv_usec = (ms % 1000) * 1000;    // microseconds
192
193        if (!select(x11_fd + 1, &input_fds, NULL, NULL, &tv)) {
194            if (!XPending(dsp)) {
195                return false;
196            }
197        }
198    }
199    XNextEvent(dsp, evt);
200    return true;
201}
202
203static Atom wm_delete_window_message;
204
205SkOSWindow::NextXEventResult SkOSWindow::nextXEvent() {
206    XEvent evt;
207    Display* dsp = fUnixWindow.fDisplay;
208
209    if (!MyXNextEventWithDelay(dsp, &evt)) {
210        return kContinue_NextXEventResult;
211    }
212
213    switch (evt.type) {
214        case Expose:
215            if (0 == evt.xexpose.count) {
216                return kPaintRequest_NextXEventResult;
217            }
218            break;
219        case ConfigureNotify:
220            this->resize(evt.xconfigure.width, evt.xconfigure.height);
221            break;
222        case ButtonPress:
223            if (evt.xbutton.button == Button1)
224                this->handleClick(evt.xbutton.x, evt.xbutton.y,
225                            SkView::Click::kDown_State, NULL, getModi(evt));
226            break;
227        case ButtonRelease:
228            if (evt.xbutton.button == Button1)
229                this->handleClick(evt.xbutton.x, evt.xbutton.y,
230                              SkView::Click::kUp_State, NULL, getModi(evt));
231            break;
232        case MotionNotify:
233            this->handleClick(evt.xmotion.x, evt.xmotion.y,
234                           SkView::Click::kMoved_State, NULL, getModi(evt));
235            break;
236        case KeyPress: {
237            int shiftLevel = (evt.xkey.state & ShiftMask) ? 1 : 0;
238            KeySym keysym = XkbKeycodeToKeysym(dsp, evt.xkey.keycode,
239                                               0, shiftLevel);
240            if (keysym == XK_Escape) {
241                return kQuitRequest_NextXEventResult;
242            }
243            this->handleKey(XKeyToSkKey(keysym));
244            long uni = keysym2ucs(keysym);
245            if (uni != -1) {
246                this->handleChar((SkUnichar) uni);
247            }
248            break;
249        }
250        case KeyRelease:
251            this->handleKeyUp(XKeyToSkKey(XkbKeycodeToKeysym(dsp, evt.xkey.keycode, 0, 0)));
252            break;
253        case ClientMessage:
254            if ((Atom)evt.xclient.data.l[0] == wm_delete_window_message) {
255                return kQuitRequest_NextXEventResult;
256            }
257            // fallthrough
258        default:
259            // Do nothing for other events
260            break;
261    }
262    return kContinue_NextXEventResult;
263}
264
265void SkOSWindow::loop() {
266    Display* dsp = fUnixWindow.fDisplay;
267    if (NULL == dsp) {
268        return;
269    }
270    Window win = fUnixWindow.fWin;
271
272    wm_delete_window_message = XInternAtom(dsp, "WM_DELETE_WINDOW", False);
273    XSetWMProtocols(dsp, win, &wm_delete_window_message, 1);
274
275    XSelectInput(dsp, win, EVENT_MASK);
276
277    bool sentExposeEvent = false;
278
279    for (;;) {
280        SkEvent::ServiceQueueTimer();
281
282        bool moreToDo = SkEvent::ProcessEvent();
283
284        if (this->isDirty() && !sentExposeEvent) {
285            sentExposeEvent = true;
286
287            XEvent evt;
288            sk_bzero(&evt, sizeof(evt));
289            evt.type = Expose;
290            evt.xexpose.display = dsp;
291            XSendEvent(dsp, win, false, ExposureMask, &evt);
292        }
293
294        if (XPending(dsp) || !moreToDo) {
295            switch (this->nextXEvent()) {
296                case kContinue_NextXEventResult:
297                    break;
298                case kPaintRequest_NextXEventResult:
299                    sentExposeEvent = false;
300                    if (this->isDirty()) {
301                        this->update(NULL);
302                    }
303                    this->doPaint();
304                    break;
305                case kQuitRequest_NextXEventResult:
306                    return;
307            }
308        }
309    }
310}
311
312void SkOSWindow::mapWindowAndWait() {
313    SkASSERT(fUnixWindow.fDisplay);
314    Display* dsp = fUnixWindow.fDisplay;
315    Window win = fUnixWindow.fWin;
316    XMapWindow(dsp, win);
317
318    long eventMask = StructureNotifyMask;
319    XSelectInput(dsp, win, eventMask);
320
321    // Wait until screen is ready.
322    XEvent evt;
323    do {
324        XNextEvent(dsp, &evt);
325    } while(evt.type != MapNotify);
326
327}
328
329bool SkOSWindow::attach(SkBackEndTypes, int msaaSampleCount, AttachmentInfo* info) {
330    this->initWindow(msaaSampleCount, info);
331
332    if (NULL == fUnixWindow.fDisplay) {
333        return false;
334    }
335    if (NULL == fUnixWindow.fGLContext) {
336        SkASSERT(fVi);
337
338        fUnixWindow.fGLContext = glXCreateContext(fUnixWindow.fDisplay,
339                                                  fVi,
340                                                  NULL,
341                                                  GL_TRUE);
342        if (NULL == fUnixWindow.fGLContext) {
343            return false;
344        }
345    }
346    glXMakeCurrent(fUnixWindow.fDisplay,
347                   fUnixWindow.fWin,
348                   fUnixWindow.fGLContext);
349    glViewport(0, 0,
350               SkScalarRoundToInt(this->width()),
351               SkScalarRoundToInt(this->height()));
352    glClearColor(0, 0, 0, 0);
353    glClearStencil(0);
354    glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
355    return true;
356}
357
358void SkOSWindow::detach() {
359    if (NULL == fUnixWindow.fDisplay || NULL == fUnixWindow.fGLContext) {
360        return;
361    }
362    glXMakeCurrent(fUnixWindow.fDisplay, None, NULL);
363    glXDestroyContext(fUnixWindow.fDisplay, fUnixWindow.fGLContext);
364    fUnixWindow.fGLContext = NULL;
365}
366
367void SkOSWindow::present() {
368    if (fUnixWindow.fDisplay && fUnixWindow.fGLContext) {
369        glXSwapBuffers(fUnixWindow.fDisplay, fUnixWindow.fWin);
370    }
371}
372
373void SkOSWindow::onSetTitle(const char title[]) {
374    if (NULL == fUnixWindow.fDisplay) {
375        return;
376    }
377    XTextProperty textProp;
378    textProp.value = (unsigned char*)title;
379    textProp.format = 8;
380    textProp.nitems = strlen((char*)textProp.value);
381    textProp.encoding = XA_STRING;
382    XSetWMName(fUnixWindow.fDisplay, fUnixWindow.fWin, &textProp);
383}
384
385static bool convertBitmapToXImage(XImage& image, const SkBitmap& bitmap) {
386    sk_bzero(&image, sizeof(image));
387
388    int bitsPerPixel = bitmap.bytesPerPixel() * 8;
389    image.width = bitmap.width();
390    image.height = bitmap.height();
391    image.format = ZPixmap;
392    image.data = (char*) bitmap.getPixels();
393    image.byte_order = LSBFirst;
394    image.bitmap_unit = bitsPerPixel;
395    image.bitmap_bit_order = LSBFirst;
396    image.bitmap_pad = bitsPerPixel;
397    image.depth = 24;
398    image.bytes_per_line = bitmap.rowBytes() - bitmap.width() * 4;
399    image.bits_per_pixel = bitsPerPixel;
400    return XInitImage(&image);
401}
402
403void SkOSWindow::doPaint() {
404    if (NULL == fUnixWindow.fDisplay) {
405        return;
406    }
407    // If we are drawing with GL, we don't need XPutImage.
408    if (fUnixWindow.fGLContext) {
409        return;
410    }
411    // Draw the bitmap to the screen.
412    const SkBitmap& bitmap = getBitmap();
413    int width = bitmap.width();
414    int height = bitmap.height();
415
416    XImage image;
417    if (!convertBitmapToXImage(image, bitmap)) {
418        return;
419    }
420
421    XPutImage(fUnixWindow.fDisplay,
422              fUnixWindow.fWin,
423              fUnixWindow.fGc,
424              &image,
425              0, 0,     // src x,y
426              0, 0,     // dst x,y
427              width, height);
428}
429
430///////////////////////////////////////////////////////////////////////////////
431
432void SkEvent::SignalNonEmptyQueue() {
433    // nothing to do, since we spin on our event-queue, polling for XPending
434}
435
436void SkEvent::SignalQueueTimer(SkMSec delay) {
437    // just need to record the delay time. We handle waking up for it in
438    // MyXNextEventWithDelay()
439    gTimerDelay = delay;
440}
441