src/video/x11/SDL_x11video.c
author Sam Lantinga <slouken@libsdl.org>
Wed, 18 Jul 2012 15:17:27 -0700
changeset 6370 93187f7f7d5d
parent 6369 a92fbd27127b
child 6422 fd0ac1b56115
permissions -rwxr-xr-x
Improved simultaneous support for OpenGL and OpenGL ES

From Scott Percival

Okay, I think I have something for this. Tested it on GL and GLES
machines, it seems to work okay.

- Add a new SDL GL attribute SDL_GL_CONTEXT_EGL:
- Only useful for the X11 video driver at the moment
- Set to 1 for an EGL context, 0 to use the default for the video driver
- Default is 0, unless library is built for EGL only
- Should be set after SDL init, but before window/context
creation (i.e. same place you'd specify attributes for major/minor GL
version)
- After a lot of agony pondering the least-terrible way to go about
it, made it so that X11_GL_LoadLibrary and X11_GLES_LoadLibrary check
SDL_GL_CONTEXT_EGL. If no GL context exists yet, and the attribute
choice doesn't match with the checking function, then it changes all
the function pointers in the video driver and passes control on to the
new LoadLibrary method.
- Likewise, make X11_CreateWindow check this attribute before firing
off a call to X11_GL_GetVisual/X11_GLES_GetVisual
- Added a sanity check to the start of X11_GL_LoadLibrary
- Tidied up SDL_x11opengles.h
- Moved ownership of the gles_data structure over to
X11_GLES_LoadLibrary/UnloadLibrary
- Should incorporate the 3 fixes posted by Andre Heider

This is obviously quite a bit to take in, but is (at least) a proof of
concept for the approach I think EGL/GLX mingling should take. Any
comments/criticism is much appreciated.
slouken@1950
     1
/*
slouken@5535
     2
  Simple DirectMedia Layer
slouken@6138
     3
  Copyright (C) 1997-2012 Sam Lantinga <slouken@libsdl.org>
slouken@1950
     4
slouken@5535
     5
  This software is provided 'as-is', without any express or implied
slouken@5535
     6
  warranty.  In no event will the authors be held liable for any damages
slouken@5535
     7
  arising from the use of this software.
slouken@1950
     8
slouken@5535
     9
  Permission is granted to anyone to use this software for any purpose,
slouken@5535
    10
  including commercial applications, and to alter it and redistribute it
slouken@5535
    11
  freely, subject to the following restrictions:
slouken@1950
    12
slouken@5535
    13
  1. The origin of this software must not be misrepresented; you must not
slouken@5535
    14
     claim that you wrote the original software. If you use this software
slouken@5535
    15
     in a product, an acknowledgment in the product documentation would be
slouken@5535
    16
     appreciated but is not required.
slouken@5535
    17
  2. Altered source versions must be plainly marked as such, and must not be
slouken@5535
    18
     misrepresented as being the original software.
slouken@5535
    19
  3. This notice may not be removed or altered from any source distribution.
slouken@1950
    20
*/
slouken@1950
    21
#include "SDL_config.h"
slouken@1950
    22
slouken@5481
    23
#if SDL_VIDEO_DRIVER_X11
slouken@5481
    24
slouken@4508
    25
#include <unistd.h> /* For getpid() and readlink() */
slouken@4508
    26
slouken@1950
    27
#include "SDL_video.h"
slouken@1950
    28
#include "SDL_mouse.h"
slouken@1950
    29
#include "../SDL_sysvideo.h"
slouken@1950
    30
#include "../SDL_pixels_c.h"
slouken@1950
    31
slouken@1950
    32
#include "SDL_x11video.h"
slouken@5182
    33
#include "SDL_x11framebuffer.h"
eligottlieb@4782
    34
#include "SDL_x11shape.h"
slouken@4923
    35
#include "SDL_x11touch.h" 
dimitris@6316
    36
#include "SDL_x11xinput2.h"
slouken@2710
    37
slouken@6188
    38
#if SDL_VIDEO_OPENGL_ES || SDL_VIDEO_OPENGL_ES2
slouken@3218
    39
#include "SDL_x11opengles.h"
slouken@3218
    40
#endif
slouken@1950
    41
slouken@1950
    42
/* Initialization/Query functions */
slouken@1950
    43
static int X11_VideoInit(_THIS);
slouken@1950
    44
static void X11_VideoQuit(_THIS);
slouken@1950
    45
slouken@1951
    46
/* Find out what class name we should use */
slouken@1951
    47
static char *
slouken@1951
    48
get_classname()
slouken@1951
    49
{
slouken@1951
    50
    char *spot;
slouken@1951
    51
#if defined(__LINUX__) || defined(__FREEBSD__)
slouken@1951
    52
    char procfile[1024];
slouken@1951
    53
    char linkfile[1024];
slouken@1951
    54
    int linksize;
slouken@1951
    55
#endif
slouken@1951
    56
slouken@1951
    57
    /* First allow environment variable override */
slouken@1951
    58
    spot = SDL_getenv("SDL_VIDEO_X11_WMCLASS");
slouken@1951
    59
    if (spot) {
slouken@1951
    60
        return SDL_strdup(spot);
slouken@1951
    61
    }
slouken@1951
    62
slouken@1951
    63
    /* Next look at the application's executable name */
slouken@1951
    64
#if defined(__LINUX__) || defined(__FREEBSD__)
slouken@1951
    65
#if defined(__LINUX__)
slouken@1951
    66
    SDL_snprintf(procfile, SDL_arraysize(procfile), "/proc/%d/exe", getpid());
slouken@1951
    67
#elif defined(__FREEBSD__)
slouken@1951
    68
    SDL_snprintf(procfile, SDL_arraysize(procfile), "/proc/%d/file",
slouken@1951
    69
                 getpid());
slouken@1951
    70
#else
slouken@1951
    71
#error Where can we find the executable name?
slouken@1951
    72
#endif
slouken@1951
    73
    linksize = readlink(procfile, linkfile, sizeof(linkfile) - 1);
slouken@1951
    74
    if (linksize > 0) {
slouken@1951
    75
        linkfile[linksize] = '\0';
slouken@1951
    76
        spot = SDL_strrchr(linkfile, '/');
slouken@1951
    77
        if (spot) {
slouken@1951
    78
            return SDL_strdup(spot + 1);
slouken@1951
    79
        } else {
slouken@1951
    80
            return SDL_strdup(linkfile);
slouken@1951
    81
        }
slouken@1951
    82
    }
slouken@1951
    83
#endif /* __LINUX__ || __FREEBSD__ */
slouken@1951
    84
slouken@1951
    85
    /* Finally use the default we've used forever */
slouken@1951
    86
    return SDL_strdup("SDL_App");
slouken@1951
    87
}
slouken@1951
    88
slouken@1950
    89
/* X11 driver bootstrap functions */
slouken@1950
    90
slouken@1950
    91
static int
slouken@1950
    92
X11_Available(void)
slouken@1950
    93
{
slouken@1950
    94
    Display *display = NULL;
slouken@1950
    95
    if (SDL_X11_LoadSymbols()) {
slouken@1950
    96
        display = XOpenDisplay(NULL);
slouken@1950
    97
        if (display != NULL) {
slouken@1950
    98
            XCloseDisplay(display);
slouken@1950
    99
        }
slouken@1950
   100
        SDL_X11_UnloadSymbols();
slouken@1950
   101
    }
slouken@1950
   102
    return (display != NULL);
slouken@1950
   103
}
slouken@1950
   104
slouken@1950
   105
static void
slouken@1950
   106
X11_DeleteDevice(SDL_VideoDevice * device)
slouken@1950
   107
{
slouken@1950
   108
    SDL_VideoData *data = (SDL_VideoData *) device->driverdata;
slouken@1950
   109
    if (data->display) {
slouken@1950
   110
        XCloseDisplay(data->display);
slouken@1950
   111
    }
bob@2324
   112
    SDL_free(data->windowlist);
slouken@1950
   113
    SDL_free(device->driverdata);
slouken@1950
   114
    SDL_free(device);
slouken@1950
   115
slouken@1950
   116
    SDL_X11_UnloadSymbols();
slouken@1950
   117
}
slouken@1950
   118
slouken@1950
   119
static SDL_VideoDevice *
slouken@1950
   120
X11_CreateDevice(int devindex)
slouken@1950
   121
{
slouken@1950
   122
    SDL_VideoDevice *device;
slouken@1950
   123
    SDL_VideoData *data;
slouken@1950
   124
    const char *display = NULL; /* Use the DISPLAY environment variable */
slouken@1950
   125
slouken@1950
   126
    if (!SDL_X11_LoadSymbols()) {
slouken@1950
   127
        return NULL;
slouken@1950
   128
    }
slouken@1950
   129
slouken@1950
   130
    /* Initialize all variables that we clean on shutdown */
slouken@1950
   131
    device = (SDL_VideoDevice *) SDL_calloc(1, sizeof(SDL_VideoDevice));
bob@2323
   132
    if (!device) {
bob@2323
   133
        SDL_OutOfMemory();
bob@2323
   134
        return NULL;
slouken@1950
   135
    }
bob@2323
   136
    data = (struct SDL_VideoData *) SDL_calloc(1, sizeof(SDL_VideoData));
bob@2323
   137
    if (!data) {
slouken@1950
   138
        SDL_OutOfMemory();
bob@2323
   139
        SDL_free(device);
slouken@1950
   140
        return NULL;
slouken@1950
   141
    }
slouken@1950
   142
    device->driverdata = data;
slouken@1950
   143
slouken@1950
   144
    /* FIXME: Do we need this?
slouken@1950
   145
       if ( (SDL_strncmp(XDisplayName(display), ":", 1) == 0) ||
slouken@1950
   146
       (SDL_strncmp(XDisplayName(display), "unix:", 5) == 0) ) {
slouken@1950
   147
       local_X11 = 1;
slouken@1950
   148
       } else {
slouken@1950
   149
       local_X11 = 0;
slouken@1950
   150
       }
slouken@1950
   151
     */
slouken@1950
   152
    data->display = XOpenDisplay(display);
slouken@1950
   153
#if defined(__osf__) && defined(SDL_VIDEO_DRIVER_X11_DYNAMIC)
slouken@1950
   154
    /* On Tru64 if linking without -lX11, it fails and you get following message.
slouken@1950
   155
     * Xlib: connection to ":0.0" refused by server
slouken@1950
   156
     * Xlib: XDM authorization key matches an existing client!
slouken@1950
   157
     *
slouken@1950
   158
     * It succeeds if retrying 1 second later
slouken@1950
   159
     * or if running xhost +localhost on shell.
slouken@1950
   160
     */
slouken@1950
   161
    if (data->display == NULL) {
slouken@1950
   162
        SDL_Delay(1000);
slouken@1950
   163
        data->display = XOpenDisplay(display);
slouken@1950
   164
    }
slouken@1950
   165
#endif
slouken@1950
   166
    if (data->display == NULL) {
slouken@6367
   167
        SDL_free(device->driverdata);
slouken@1950
   168
        SDL_free(device);
slouken@1950
   169
        SDL_SetError("Couldn't open X11 display");
slouken@1950
   170
        return NULL;
slouken@1950
   171
    }
slouken@1950
   172
#ifdef X11_DEBUG
slouken@1950
   173
    XSynchronize(data->display, True);
slouken@1950
   174
#endif
slouken@1950
   175
slouken@1950
   176
    /* Set the function pointers */
slouken@1950
   177
    device->VideoInit = X11_VideoInit;
slouken@1950
   178
    device->VideoQuit = X11_VideoQuit;
slouken@1950
   179
    device->GetDisplayModes = X11_GetDisplayModes;
gabomdq@6331
   180
    device->GetDisplayBounds = X11_GetDisplayBounds;
slouken@1950
   181
    device->SetDisplayMode = X11_SetDisplayMode;
slouken@3025
   182
    device->SuspendScreenSaver = X11_SuspendScreenSaver;
slouken@1951
   183
    device->PumpEvents = X11_PumpEvents;
slouken@1950
   184
slouken@1950
   185
    device->CreateWindow = X11_CreateWindow;
slouken@1950
   186
    device->CreateWindowFrom = X11_CreateWindowFrom;
slouken@1950
   187
    device->SetWindowTitle = X11_SetWindowTitle;
slouken@2967
   188
    device->SetWindowIcon = X11_SetWindowIcon;
slouken@1950
   189
    device->SetWindowPosition = X11_SetWindowPosition;
slouken@1950
   190
    device->SetWindowSize = X11_SetWindowSize;
slouken@1950
   191
    device->ShowWindow = X11_ShowWindow;
slouken@1950
   192
    device->HideWindow = X11_HideWindow;
slouken@1950
   193
    device->RaiseWindow = X11_RaiseWindow;
slouken@1950
   194
    device->MaximizeWindow = X11_MaximizeWindow;
slouken@1950
   195
    device->MinimizeWindow = X11_MinimizeWindow;
slouken@1950
   196
    device->RestoreWindow = X11_RestoreWindow;
slouken@5302
   197
    device->SetWindowFullscreen = X11_SetWindowFullscreen;
slouken@5466
   198
    device->SetWindowGammaRamp = X11_SetWindowGammaRamp;
slouken@1950
   199
    device->SetWindowGrab = X11_SetWindowGrab;
slouken@1950
   200
    device->DestroyWindow = X11_DestroyWindow;
slouken@5182
   201
    device->CreateWindowFramebuffer = X11_CreateWindowFramebuffer;
slouken@5182
   202
    device->UpdateWindowFramebuffer = X11_UpdateWindowFramebuffer;
slouken@5182
   203
    device->DestroyWindowFramebuffer = X11_DestroyWindowFramebuffer;
slouken@1950
   204
    device->GetWindowWMInfo = X11_GetWindowWMInfo;
slouken@5182
   205
eligottlieb@4782
   206
    device->shape_driver.CreateShaper = X11_CreateShaper;
eligottlieb@4782
   207
    device->shape_driver.SetWindowShape = X11_SetWindowShape;
eligottlieb@4782
   208
    device->shape_driver.ResizeWindowShape = X11_ResizeWindowShape;
slouken@5182
   209
slouken@5088
   210
#if SDL_VIDEO_OPENGL_GLX
slouken@6370
   211
    device->GL_LoadLibrary = X11_GL_LoadLibrary;
slouken@6370
   212
    device->GL_GetProcAddress = X11_GL_GetProcAddress;
slouken@6370
   213
    device->GL_UnloadLibrary = X11_GL_UnloadLibrary;
slouken@6370
   214
    device->GL_CreateContext = X11_GL_CreateContext;
slouken@6370
   215
    device->GL_MakeCurrent = X11_GL_MakeCurrent;
slouken@6370
   216
    device->GL_SetSwapInterval = X11_GL_SetSwapInterval;
slouken@6370
   217
    device->GL_GetSwapInterval = X11_GL_GetSwapInterval;
slouken@6370
   218
    device->GL_SwapWindow = X11_GL_SwapWindow;
slouken@6370
   219
    device->GL_DeleteContext = X11_GL_DeleteContext;
slouken@6370
   220
#elif SDL_VIDEO_OPENGL_ES || SDL_VIDEO_OPENGL_ES2
slouken@6370
   221
    device->GL_LoadLibrary = X11_GLES_LoadLibrary;
slouken@6370
   222
    device->GL_GetProcAddress = X11_GLES_GetProcAddress;
slouken@6370
   223
    device->GL_UnloadLibrary = X11_GLES_UnloadLibrary;
slouken@6370
   224
    device->GL_CreateContext = X11_GLES_CreateContext;
slouken@6370
   225
    device->GL_MakeCurrent = X11_GLES_MakeCurrent;
slouken@6370
   226
    device->GL_SetSwapInterval = X11_GLES_SetSwapInterval;
slouken@6370
   227
    device->GL_GetSwapInterval = X11_GLES_GetSwapInterval;
slouken@6370
   228
    device->GL_SwapWindow = X11_GLES_SwapWindow;
slouken@6370
   229
    device->GL_DeleteContext = X11_GLES_DeleteContext;
slouken@3218
   230
#endif
slouken@1950
   231
slouken@4508
   232
    device->SetClipboardText = X11_SetClipboardText;
slouken@4508
   233
    device->GetClipboardText = X11_GetClipboardText;
slouken@4508
   234
    device->HasClipboardText = X11_HasClipboardText;
slouken@4508
   235
slouken@1950
   236
    device->free = X11_DeleteDevice;
slouken@1950
   237
slouken@1950
   238
    return device;
slouken@1950
   239
}
slouken@1950
   240
slouken@1950
   241
VideoBootStrap X11_bootstrap = {
slouken@1950
   242
    "x11", "SDL X11 video driver",
slouken@1950
   243
    X11_Available, X11_CreateDevice
slouken@1950
   244
};
slouken@1950
   245
slouken@4559
   246
static int (*handler) (Display *, XErrorEvent *) = NULL;
slouken@4559
   247
static int
slouken@4559
   248
X11_CheckWindowManagerErrorHandler(Display * d, XErrorEvent * e)
slouken@4559
   249
{
slouken@4559
   250
    if (e->error_code == BadWindow) {
slouken@4559
   251
        return (0);
slouken@4559
   252
    } else {
slouken@4559
   253
        return (handler(d, e));
slouken@4559
   254
    }
slouken@4559
   255
}
slouken@4508
   256
slouken@4518
   257
static void
slouken@4518
   258
X11_CheckWindowManager(_THIS)
slouken@4518
   259
{
slouken@4518
   260
    SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
slouken@4518
   261
    Display *display = data->display;
slouken@4518
   262
    Atom _NET_SUPPORTING_WM_CHECK;
slouken@4518
   263
    int status, real_format;
slouken@4518
   264
    Atom real_type;
slouken@4518
   265
    unsigned long items_read, items_left;
slouken@4518
   266
    unsigned char *propdata;
slouken@4518
   267
    Window wm_window = 0;
slouken@4518
   268
#ifdef DEBUG_WINDOW_MANAGER
slouken@4518
   269
    char *wm_name;
slouken@4518
   270
#endif
slouken@4518
   271
slouken@4559
   272
    /* Set up a handler to gracefully catch errors */
slouken@4559
   273
    XSync(display, False);
slouken@4559
   274
    handler = XSetErrorHandler(X11_CheckWindowManagerErrorHandler);
slouken@4559
   275
slouken@4518
   276
    _NET_SUPPORTING_WM_CHECK = XInternAtom(display, "_NET_SUPPORTING_WM_CHECK", False);
slouken@4518
   277
    status = XGetWindowProperty(display, DefaultRootWindow(display), _NET_SUPPORTING_WM_CHECK, 0L, 1L, False, XA_WINDOW, &real_type, &real_format, &items_read, &items_left, &propdata);
slouken@4518
   278
    if (status == Success && items_read) {
slouken@4518
   279
        wm_window = ((Window*)propdata)[0];
slouken@4518
   280
    }
slouken@4559
   281
    if (propdata) {
slouken@4559
   282
        XFree(propdata);
slouken@4559
   283
    }
slouken@4559
   284
slouken@4559
   285
    if (wm_window) {
slouken@4559
   286
        status = XGetWindowProperty(display, wm_window, _NET_SUPPORTING_WM_CHECK, 0L, 1L, False, XA_WINDOW, &real_type, &real_format, &items_read, &items_left, &propdata);
slouken@4559
   287
        if (status != Success || !items_read || wm_window != ((Window*)propdata)[0]) {
slouken@4559
   288
            wm_window = None;
slouken@4559
   289
        }
slouken@4559
   290
        if (propdata) {
slouken@4559
   291
            XFree(propdata);
slouken@4559
   292
        }
slouken@4559
   293
    }
slouken@4559
   294
slouken@4559
   295
    /* Reset the error handler, we're done checking */
slouken@4559
   296
    XSync(display, False);
slouken@4559
   297
    XSetErrorHandler(handler);
slouken@4518
   298
slouken@4518
   299
    if (!wm_window) {
slouken@4518
   300
#ifdef DEBUG_WINDOW_MANAGER
slouken@4518
   301
        printf("Couldn't get _NET_SUPPORTING_WM_CHECK property\n");
slouken@4518
   302
#endif
slouken@4518
   303
        return;
slouken@4518
   304
    }
slouken@4518
   305
    data->net_wm = SDL_TRUE;
slouken@4518
   306
slouken@4518
   307
#ifdef DEBUG_WINDOW_MANAGER
slouken@4518
   308
    wm_name = X11_GetWindowTitle(_this, wm_window);
slouken@4518
   309
    printf("Window manager: %s\n", wm_name);
slouken@4518
   310
    SDL_free(wm_name);
slouken@4518
   311
#endif
slouken@4518
   312
}
slouken@1950
   313
slouken@1950
   314
int
slouken@1950
   315
X11_VideoInit(_THIS)
slouken@1950
   316
{
slouken@1951
   317
    SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
slouken@1951
   318
slouken@1951
   319
    /* Get the window class name, usually the name of the application */
slouken@1951
   320
    data->classname = get_classname();
slouken@1951
   321
mail@6167
   322
    /* Get the process PID to be associated to the window */
mail@6167
   323
    data->pid = getpid();
mail@6167
   324
slouken@1951
   325
    /* Open a connection to the X input manager */
slouken@1951
   326
#ifdef X_HAVE_UTF8_STRING
slouken@1951
   327
    if (SDL_X11_HAVE_UTF8) {
slouken@1951
   328
        data->im =
slouken@1951
   329
            XOpenIM(data->display, NULL, data->classname, data->classname);
slouken@1951
   330
    }
slouken@1951
   331
#endif
slouken@1951
   332
slouken@1951
   333
    /* Look up some useful Atoms */
slouken@4518
   334
#define GET_ATOM(X) data->X = XInternAtom(data->display, #X, False)
slouken@4518
   335
    GET_ATOM(WM_DELETE_WINDOW);
slouken@4518
   336
    GET_ATOM(_NET_WM_STATE);
slouken@4518
   337
    GET_ATOM(_NET_WM_STATE_HIDDEN);
slouken@4518
   338
    GET_ATOM(_NET_WM_STATE_MAXIMIZED_VERT);
slouken@4518
   339
    GET_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ);
slouken@4518
   340
    GET_ATOM(_NET_WM_STATE_FULLSCREEN);
slouken@4518
   341
    GET_ATOM(_NET_WM_NAME);
slouken@4518
   342
    GET_ATOM(_NET_WM_ICON_NAME);
slouken@4518
   343
    GET_ATOM(_NET_WM_ICON);
slouken@4518
   344
    GET_ATOM(UTF8_STRING);
slouken@4518
   345
slouken@4518
   346
    /* Detect the window manager */
slouken@4518
   347
    X11_CheckWindowManager(_this);
slouken@1951
   348
slouken@3521
   349
    if (X11_InitModes(_this) < 0) {
slouken@3521
   350
        return -1;
slouken@3521
   351
    }
slouken@1950
   352
dimitris@6316
   353
    X11_InitXinput2(_this);
slouken@6311
   354
bob@2295
   355
    if (X11_InitKeyboard(_this) != 0) {
bob@2295
   356
        return -1;
bob@2295
   357
    }
slouken@1950
   358
    X11_InitMouse(_this);
slouken@1950
   359
jim@4645
   360
    X11_InitTouch(_this);
slouken@1950
   361
    return 0;
slouken@1950
   362
}
slouken@1950
   363
slouken@1950
   364
void
slouken@1950
   365
X11_VideoQuit(_THIS)
slouken@1950
   366
{
slouken@1951
   367
    SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
slouken@1951
   368
slouken@1951
   369
    if (data->classname) {
slouken@1951
   370
        SDL_free(data->classname);
slouken@1951
   371
    }
slouken@1951
   372
#ifdef X_HAVE_UTF8_STRING
slouken@1951
   373
    if (data->im) {
slouken@1951
   374
        XCloseIM(data->im);
slouken@1951
   375
    }
slouken@1951
   376
#endif
slouken@1951
   377
slouken@1950
   378
    X11_QuitModes(_this);
slouken@1950
   379
    X11_QuitKeyboard(_this);
slouken@1950
   380
    X11_QuitMouse(_this);
jim@4645
   381
    X11_QuitTouch(_this);
slouken@1950
   382
}
slouken@1950
   383
slouken@5466
   384
SDL_bool
slouken@5466
   385
X11_UseDirectColorVisuals(void)
slouken@5466
   386
{
slouken@5466
   387
    return SDL_getenv("SDL_VIDEO_X11_NODIRECTCOLOR") ? SDL_FALSE : SDL_TRUE;
slouken@5466
   388
}
slouken@5466
   389
slouken@5481
   390
#endif /* SDL_VIDEO_DRIVER_X11 */
slouken@5481
   391
slouken@1950
   392
/* vim: set ts=4 sw=4 expandtab: */