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 (NULL != fUnixWindow.fDisplay) {
50        this->detach();
51        SkASSERT(NULL != 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 (NULL != fUnixWindow.fDisplay) {
68        if (NULL != info) {
69            if (NULL != 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 (NULL != 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 (NULL != 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
203SkOSWindow::NextXEventResult SkOSWindow::nextXEvent() {
204    XEvent evt;
205    Display* dsp = fUnixWindow.fDisplay;
206
207    if (!MyXNextEventWithDelay(fUnixWindow.fDisplay, &evt)) {
208        return kContinue_NextXEventResult;
209    }
210
211    switch (evt.type) {
212        case Expose:
213            if (0 == evt.xexpose.count) {
214                return kPaintRequest_NextXEventResult;
215            }
216            break;
217        case ConfigureNotify:
218            this->resize(evt.xconfigure.width, evt.xconfigure.height);
219            break;
220        case ButtonPress:
221            if (evt.xbutton.button == Button1)
222                this->handleClick(evt.xbutton.x, evt.xbutton.y,
223                            SkView::Click::kDown_State, NULL, getModi(evt));
224            break;
225        case ButtonRelease:
226            if (evt.xbutton.button == Button1)
227                this->handleClick(evt.xbutton.x, evt.xbutton.y,
228                              SkView::Click::kUp_State, NULL, getModi(evt));
229            break;
230        case MotionNotify:
231            this->handleClick(evt.xmotion.x, evt.xmotion.y,
232                           SkView::Click::kMoved_State, NULL, getModi(evt));
233            break;
234        case KeyPress: {
235            int shiftLevel = (evt.xkey.state & ShiftMask) ? 1 : 0;
236            KeySym keysym = XkbKeycodeToKeysym(dsp, evt.xkey.keycode,
237                                               0, shiftLevel);
238            if (keysym == XK_Escape) {
239                return kQuitRequest_NextXEventResult;
240            }
241            this->handleKey(XKeyToSkKey(keysym));
242            long uni = keysym2ucs(keysym);
243            if (uni != -1) {
244                this->handleChar((SkUnichar) uni);
245            }
246            break;
247        }
248        case KeyRelease:
249            this->handleKeyUp(XKeyToSkKey(XkbKeycodeToKeysym(dsp, evt.xkey.keycode, 0, 0)));
250            break;
251        default:
252            // Do nothing for other events
253            break;
254    }
255    return kContinue_NextXEventResult;
256}
257
258void SkOSWindow::loop() {
259    Display* dsp = fUnixWindow.fDisplay;
260    if (NULL == dsp) {
261        return;
262    }
263    Window win = fUnixWindow.fWin;
264
265    XSelectInput(dsp, win, EVENT_MASK);
266
267    bool sentExposeEvent = false;
268
269    for (;;) {
270        SkEvent::ServiceQueueTimer();
271
272        bool moreToDo = SkEvent::ProcessEvent();
273
274        if (this->isDirty() && !sentExposeEvent) {
275            sentExposeEvent = true;
276
277            XEvent evt;
278            sk_bzero(&evt, sizeof(evt));
279            evt.type = Expose;
280            evt.xexpose.display = dsp;
281            XSendEvent(dsp, win, false, ExposureMask, &evt);
282        }
283
284        if (XPending(dsp) || !moreToDo) {
285            switch (this->nextXEvent()) {
286                case kContinue_NextXEventResult:
287                    break;
288                case kPaintRequest_NextXEventResult:
289                    sentExposeEvent = false;
290                    if (this->isDirty()) {
291                        this->update(NULL);
292                    }
293                    this->doPaint();
294                    break;
295                case kQuitRequest_NextXEventResult:
296                    return;
297            }
298        }
299    }
300}
301
302void SkOSWindow::mapWindowAndWait() {
303    SkASSERT(NULL != fUnixWindow.fDisplay);
304    Display* dsp = fUnixWindow.fDisplay;
305    Window win = fUnixWindow.fWin;
306    XMapWindow(dsp, win);
307
308    long eventMask = StructureNotifyMask;
309    XSelectInput(dsp, win, eventMask);
310
311    // Wait until screen is ready.
312    XEvent evt;
313    do {
314        XNextEvent(dsp, &evt);
315    } while(evt.type != MapNotify);
316
317}
318
319bool SkOSWindow::attach(SkBackEndTypes, int msaaSampleCount, AttachmentInfo* info) {
320    this->initWindow(msaaSampleCount, info);
321
322    if (NULL == fUnixWindow.fDisplay) {
323        return false;
324    }
325    if (NULL == fUnixWindow.fGLContext) {
326        SkASSERT(NULL != fVi);
327
328        fUnixWindow.fGLContext = glXCreateContext(fUnixWindow.fDisplay,
329                                                  fVi,
330                                                  NULL,
331                                                  GL_TRUE);
332        if (NULL == fUnixWindow.fGLContext) {
333            return false;
334        }
335    }
336    glXMakeCurrent(fUnixWindow.fDisplay,
337                   fUnixWindow.fWin,
338                   fUnixWindow.fGLContext);
339    glViewport(0, 0,
340               SkScalarRoundToInt(this->width()),
341               SkScalarRoundToInt(this->height()));
342    glClearColor(0, 0, 0, 0);
343    glClearStencil(0);
344    glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
345    return true;
346}
347
348void SkOSWindow::detach() {
349    if (NULL == fUnixWindow.fDisplay || NULL == fUnixWindow.fGLContext) {
350        return;
351    }
352    glXMakeCurrent(fUnixWindow.fDisplay, None, NULL);
353    glXDestroyContext(fUnixWindow.fDisplay, fUnixWindow.fGLContext);
354    fUnixWindow.fGLContext = NULL;
355}
356
357void SkOSWindow::present() {
358    if (NULL != fUnixWindow.fDisplay && NULL != fUnixWindow.fGLContext) {
359        glXSwapBuffers(fUnixWindow.fDisplay, fUnixWindow.fWin);
360    }
361}
362
363void SkOSWindow::onSetTitle(const char title[]) {
364    if (NULL == fUnixWindow.fDisplay) {
365        return;
366    }
367    XTextProperty textProp;
368    textProp.value = (unsigned char*)title;
369    textProp.format = 8;
370    textProp.nitems = strlen((char*)textProp.value);
371    textProp.encoding = XA_STRING;
372    XSetWMName(fUnixWindow.fDisplay, fUnixWindow.fWin, &textProp);
373}
374
375static bool convertBitmapToXImage(XImage& image, const SkBitmap& bitmap) {
376    sk_bzero(&image, sizeof(image));
377
378    int bitsPerPixel = bitmap.bytesPerPixel() * 8;
379    image.width = bitmap.width();
380    image.height = bitmap.height();
381    image.format = ZPixmap;
382    image.data = (char*) bitmap.getPixels();
383    image.byte_order = LSBFirst;
384    image.bitmap_unit = bitsPerPixel;
385    image.bitmap_bit_order = LSBFirst;
386    image.bitmap_pad = bitsPerPixel;
387    image.depth = 24;
388    image.bytes_per_line = bitmap.rowBytes() - bitmap.width() * 4;
389    image.bits_per_pixel = bitsPerPixel;
390    return XInitImage(&image);
391}
392
393void SkOSWindow::doPaint() {
394    if (NULL == fUnixWindow.fDisplay) {
395        return;
396    }
397    // If we are drawing with GL, we don't need XPutImage.
398    if (NULL != fUnixWindow.fGLContext) {
399        return;
400    }
401    // Draw the bitmap to the screen.
402    const SkBitmap& bitmap = getBitmap();
403    int width = bitmap.width();
404    int height = bitmap.height();
405
406    XImage image;
407    if (!convertBitmapToXImage(image, bitmap)) {
408        return;
409    }
410
411    XPutImage(fUnixWindow.fDisplay,
412              fUnixWindow.fWin,
413              fUnixWindow.fGc,
414              &image,
415              0, 0,     // src x,y
416              0, 0,     // dst x,y
417              width, height);
418}
419
420///////////////////////////////////////////////////////////////////////////////
421
422void SkEvent::SignalNonEmptyQueue() {
423    // nothing to do, since we spin on our event-queue, polling for XPending
424}
425
426void SkEvent::SignalQueueTimer(SkMSec delay) {
427    // just need to record the delay time. We handle waking up for it in
428    // MyXNextEventWithDelay()
429    gTimerDelay = delay;
430}
431