src/video/windows/SDL_windowsmodes.c
author Sam Lantinga <slouken@libsdl.org>
Sat, 02 Jan 2016 10:10:34 -0800
changeset 9998 f67cf37e9cd4
parent 9889 7cb2c3ec8205
child 10019 36f7e8084508
permissions -rw-r--r--
Updated copyright to 2016
slouken@1895
     1
/*
slouken@5535
     2
  Simple DirectMedia Layer
slouken@9998
     3
  Copyright (C) 1997-2016 Sam Lantinga <slouken@libsdl.org>
slouken@1895
     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@1895
     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@1895
    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@1895
    20
*/
icculus@8093
    21
#include "../../SDL_internal.h"
slouken@1895
    22
slouken@6044
    23
#if SDL_VIDEO_DRIVER_WINDOWS
slouken@6044
    24
slouken@5062
    25
#include "SDL_windowsvideo.h"
slouken@1895
    26
slouken@5086
    27
/* Windows CE compatibility */
slouken@5086
    28
#ifndef CDS_FULLSCREEN
slouken@5086
    29
#define CDS_FULLSCREEN 0
slouken@5086
    30
#endif
slouken@1895
    31
alfred@9813
    32
typedef struct _WIN_GetMonitorDPIData {
slouken@9889
    33
    SDL_VideoData *vid_data;
slouken@9889
    34
    SDL_DisplayMode *mode;
slouken@9889
    35
    SDL_DisplayModeData *mode_data;
alfred@9813
    36
} WIN_GetMonitorDPIData;
alfred@9813
    37
alfred@9813
    38
static BOOL CALLBACK
alfred@9813
    39
WIN_GetMonitorDPI(HMONITOR hMonitor,
slouken@9889
    40
                  HDC      hdcMonitor,
slouken@9889
    41
                  LPRECT   lprcMonitor,
slouken@9889
    42
                  LPARAM   dwData)
alfred@9813
    43
{
slouken@9889
    44
    WIN_GetMonitorDPIData *data = (WIN_GetMonitorDPIData*) dwData;
slouken@9889
    45
    UINT hdpi, vdpi;
alfred@9813
    46
slouken@9889
    47
    if (data->vid_data->GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &hdpi, &vdpi) == S_OK &&
slouken@9889
    48
        hdpi > 0 &&
slouken@9889
    49
        vdpi > 0) {
slouken@9889
    50
        float hsize, vsize;
slouken@9889
    51
        
slouken@9889
    52
        data->mode_data->HorzDPI = (float)hdpi;
slouken@9889
    53
        data->mode_data->VertDPI = (float)vdpi;
alfred@9813
    54
slouken@9889
    55
        // Figure out the monitor size and compute the diagonal DPI.
slouken@9889
    56
        hsize = data->mode->w / data->mode_data->HorzDPI;
slouken@9889
    57
        vsize = data->mode->h / data->mode_data->VertDPI;
slouken@9889
    58
        
slouken@9889
    59
        data->mode_data->DiagDPI = SDL_ComputeDiagonalDPI( data->mode->w,
slouken@9889
    60
                                                           data->mode->h,
slouken@9889
    61
                                                           hsize,
slouken@9889
    62
                                                           vsize );
alfred@9813
    63
slouken@9889
    64
        // We can only handle one DPI per display mode so end the enumeration.
slouken@9889
    65
        return FALSE;
slouken@9889
    66
    }
alfred@9813
    67
slouken@9889
    68
    // We didn't get DPI information so keep going.
slouken@9889
    69
    return TRUE;
alfred@9813
    70
}
alfred@9813
    71
slouken@1895
    72
static SDL_bool
alfred@9813
    73
WIN_GetDisplayMode(_THIS, LPCTSTR deviceName, DWORD index, SDL_DisplayMode * mode)
slouken@1895
    74
{
alfred@9813
    75
    SDL_VideoData *vid_data = (SDL_VideoData *) _this->driverdata;
slouken@1895
    76
    SDL_DisplayModeData *data;
slouken@1895
    77
    DEVMODE devmode;
slouken@1895
    78
    HDC hdc;
slouken@1895
    79
slouken@1895
    80
    devmode.dmSize = sizeof(devmode);
slouken@1895
    81
    devmode.dmDriverExtra = 0;
slouken@1895
    82
    if (!EnumDisplaySettings(deviceName, index, &devmode)) {
slouken@1895
    83
        return SDL_FALSE;
slouken@1895
    84
    }
slouken@1895
    85
slouken@1895
    86
    data = (SDL_DisplayModeData *) SDL_malloc(sizeof(*data));
slouken@1895
    87
    if (!data) {
slouken@1895
    88
        return SDL_FALSE;
slouken@1895
    89
    }
slouken@1895
    90
    data->DeviceMode = devmode;
slouken@1895
    91
    data->DeviceMode.dmFields =
slouken@1895
    92
        (DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY |
slouken@1895
    93
         DM_DISPLAYFLAGS);
slouken@9889
    94
    data->ScaleX = 1.0f;
slouken@9889
    95
    data->ScaleY = 1.0f;
slouken@9889
    96
    data->DiagDPI = 0.0f;
slouken@9889
    97
    data->HorzDPI = 0.0f;
slouken@9889
    98
    data->VertDPI = 0.0f;
slouken@1895
    99
slouken@1895
   100
    /* Fill in the mode information */
slouken@1965
   101
    mode->format = SDL_PIXELFORMAT_UNKNOWN;
slouken@1895
   102
    mode->w = devmode.dmPelsWidth;
slouken@1895
   103
    mode->h = devmode.dmPelsHeight;
slouken@1895
   104
    mode->refresh_rate = devmode.dmDisplayFrequency;
slouken@1895
   105
    mode->driverdata = data;
slouken@1895
   106
slouken@1913
   107
    if (index == ENUM_CURRENT_SETTINGS
slouken@1913
   108
        && (hdc = CreateDC(deviceName, NULL, NULL, NULL)) != NULL) {
slouken@1895
   109
        char bmi_data[sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)];
slouken@1895
   110
        LPBITMAPINFO bmi;
slouken@1895
   111
        HBITMAP hbm;
slouken@9889
   112
        int logical_width = GetDeviceCaps( hdc, HORZRES );
slouken@9889
   113
        int logical_height = GetDeviceCaps( hdc, VERTRES );
slouken@8086
   114
slouken@9889
   115
        data->ScaleX = (float)logical_width / devmode.dmPelsWidth;
slouken@9889
   116
        data->ScaleY = (float)logical_height / devmode.dmPelsHeight;
slouken@9889
   117
        mode->w = logical_width;
slouken@9889
   118
        mode->h = logical_height;
slouken@1895
   119
slouken@9889
   120
        // WIN_GetMonitorDPI needs mode->w and mode->h
slouken@9889
   121
        // so only call after those are set.
slouken@9889
   122
        if (vid_data->GetDpiForMonitor) {
slouken@9889
   123
            WIN_GetMonitorDPIData dpi_data;
alfred@9819
   124
            RECT monitor_rect;
alfred@9813
   125
slouken@9889
   126
            dpi_data.vid_data = vid_data;
slouken@9889
   127
            dpi_data.mode = mode;
slouken@9889
   128
            dpi_data.mode_data = data;
alfred@9819
   129
            monitor_rect.left = devmode.dmPosition.x;
alfred@9819
   130
            monitor_rect.top = devmode.dmPosition.y;
alfred@9819
   131
            monitor_rect.right = monitor_rect.left + 1;
alfred@9819
   132
            monitor_rect.bottom = monitor_rect.top + 1;
slouken@9889
   133
            EnumDisplayMonitors(NULL, &monitor_rect, WIN_GetMonitorDPI, (LPARAM)&dpi_data);
slouken@9889
   134
        } else {
slouken@9889
   135
            // We don't have the Windows 8.1 routine so just
slouken@9889
   136
            // get system DPI.
slouken@9889
   137
            data->HorzDPI = (float)GetDeviceCaps( hdc, LOGPIXELSX );
slouken@9889
   138
            data->VertDPI = (float)GetDeviceCaps( hdc, LOGPIXELSY );
slouken@9889
   139
            if (data->HorzDPI == data->VertDPI) {
slouken@9889
   140
                data->DiagDPI = data->HorzDPI;
slouken@9889
   141
            } else {
slouken@9889
   142
                data->DiagDPI = SDL_ComputeDiagonalDPI( mode->w,
slouken@9889
   143
                                                        mode->h,
slouken@9889
   144
                                                        (float)GetDeviceCaps( hdc, HORZSIZE ) / 25.4f,
slouken@9889
   145
                                                        (float)GetDeviceCaps( hdc, VERTSIZE ) / 25.4f );
slouken@9889
   146
            }
slouken@9889
   147
        }
slouken@9889
   148
        
slouken@1895
   149
        SDL_zero(bmi_data);
slouken@1895
   150
        bmi = (LPBITMAPINFO) bmi_data;
slouken@1895
   151
        bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
slouken@1895
   152
slouken@1895
   153
        hbm = CreateCompatibleBitmap(hdc, 1, 1);
slouken@1895
   154
        GetDIBits(hdc, hbm, 0, 1, NULL, bmi, DIB_RGB_COLORS);
slouken@1895
   155
        GetDIBits(hdc, hbm, 0, 1, NULL, bmi, DIB_RGB_COLORS);
slouken@1895
   156
        DeleteObject(hbm);
slouken@1895
   157
        DeleteDC(hdc);
slouken@1895
   158
        if (bmi->bmiHeader.biCompression == BI_BITFIELDS) {
slouken@1895
   159
            switch (*(Uint32 *) bmi->bmiColors) {
slouken@1895
   160
            case 0x00FF0000:
slouken@1965
   161
                mode->format = SDL_PIXELFORMAT_RGB888;
slouken@1895
   162
                break;
slouken@1895
   163
            case 0x000000FF:
slouken@1965
   164
                mode->format = SDL_PIXELFORMAT_BGR888;
slouken@1895
   165
                break;
slouken@1895
   166
            case 0xF800:
slouken@1965
   167
                mode->format = SDL_PIXELFORMAT_RGB565;
slouken@1895
   168
                break;
slouken@1895
   169
            case 0x7C00:
slouken@1965
   170
                mode->format = SDL_PIXELFORMAT_RGB555;
slouken@1895
   171
                break;
slouken@1895
   172
            }
slouken@1895
   173
        } else if (bmi->bmiHeader.biBitCount == 8) {
slouken@1965
   174
            mode->format = SDL_PIXELFORMAT_INDEX8;
lestat@3175
   175
        } else if (bmi->bmiHeader.biBitCount == 4) {
lestat@3175
   176
            mode->format = SDL_PIXELFORMAT_INDEX4LSB;
slouken@1895
   177
        }
slouken@9889
   178
    } else {
slouken@1913
   179
        /* FIXME: Can we tell what this will be? */
slouken@3186
   180
        if ((devmode.dmFields & DM_BITSPERPEL) == DM_BITSPERPEL) {
lestat@3175
   181
            switch (devmode.dmBitsPerPel) {
lestat@3175
   182
            case 32:
lestat@3175
   183
                mode->format = SDL_PIXELFORMAT_RGB888;
lestat@3175
   184
                break;
lestat@3175
   185
            case 24:
lestat@3175
   186
                mode->format = SDL_PIXELFORMAT_RGB24;
lestat@3175
   187
                break;
lestat@3175
   188
            case 16:
lestat@3175
   189
                mode->format = SDL_PIXELFORMAT_RGB565;
lestat@3175
   190
                break;
lestat@3175
   191
            case 15:
lestat@3175
   192
                mode->format = SDL_PIXELFORMAT_RGB555;
lestat@3175
   193
                break;
lestat@3175
   194
            case 8:
lestat@3175
   195
                mode->format = SDL_PIXELFORMAT_INDEX8;
lestat@3175
   196
                break;
lestat@3175
   197
            case 4:
lestat@3175
   198
                mode->format = SDL_PIXELFORMAT_INDEX4LSB;
lestat@3175
   199
                break;
lestat@3175
   200
            }
slouken@3186
   201
        }
slouken@1895
   202
    }
slouken@1895
   203
    return SDL_TRUE;
slouken@1895
   204
}
slouken@1895
   205
slouken@2149
   206
static SDL_bool
alfred@9813
   207
WIN_AddDisplay(_THIS, LPTSTR DeviceName)
slouken@2119
   208
{
slouken@2119
   209
    SDL_VideoDisplay display;
slouken@2119
   210
    SDL_DisplayData *displaydata;
slouken@2119
   211
    SDL_DisplayMode mode;
slouken@6793
   212
    DISPLAY_DEVICE device;
slouken@2119
   213
slouken@2119
   214
#ifdef DEBUG_MODES
slouken@2119
   215
    printf("Display: %s\n", WIN_StringToUTF8(DeviceName));
slouken@2119
   216
#endif
alfred@9813
   217
    if (!WIN_GetDisplayMode(_this, DeviceName, ENUM_CURRENT_SETTINGS, &mode)) {
slouken@2149
   218
        return SDL_FALSE;
slouken@2119
   219
    }
slouken@2119
   220
slouken@2119
   221
    displaydata = (SDL_DisplayData *) SDL_malloc(sizeof(*displaydata));
slouken@2119
   222
    if (!displaydata) {
slouken@2149
   223
        return SDL_FALSE;
slouken@2119
   224
    }
slouken@2119
   225
    SDL_memcpy(displaydata->DeviceName, DeviceName,
slouken@2119
   226
               sizeof(displaydata->DeviceName));
slouken@2119
   227
slouken@2119
   228
    SDL_zero(display);
slouken@6793
   229
    device.cb = sizeof(device);
slouken@6793
   230
    if (EnumDisplayDevices(DeviceName, 0, &device, 0)) {
slouken@6793
   231
        display.name = WIN_StringToUTF8(device.DeviceString);
slouken@6793
   232
    }
slouken@2119
   233
    display.desktop_mode = mode;
slouken@2119
   234
    display.current_mode = mode;
slouken@2119
   235
    display.driverdata = displaydata;
slouken@2119
   236
    SDL_AddVideoDisplay(&display);
slouken@6793
   237
    SDL_free(display.name);
slouken@2149
   238
    return SDL_TRUE;
slouken@2119
   239
}
slouken@2119
   240
slouken@3521
   241
int
slouken@1895
   242
WIN_InitModes(_THIS)
slouken@1895
   243
{
slouken@5305
   244
    int pass;
slouken@2149
   245
    DWORD i, j, count;
slouken@1895
   246
    DISPLAY_DEVICE device;
slouken@1895
   247
slouken@1895
   248
    device.cb = sizeof(device);
slouken@1895
   249
slouken@5305
   250
    /* Get the primary display in the first pass */
slouken@5305
   251
    for (pass = 0; pass < 2; ++pass) {
slouken@5305
   252
        for (i = 0; ; ++i) {
slouken@5305
   253
            TCHAR DeviceName[32];
slouken@5305
   254
slouken@5305
   255
            if (!EnumDisplayDevices(NULL, i, &device, 0)) {
slouken@1895
   256
                break;
slouken@1895
   257
            }
slouken@1895
   258
            if (!(device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP)) {
slouken@1895
   259
                continue;
slouken@1895
   260
            }
slouken@5305
   261
            if (pass == 0) {
slouken@5305
   262
                if (!(device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)) {
slouken@5305
   263
                    continue;
slouken@5305
   264
                }
slouken@5305
   265
            } else {
slouken@5305
   266
                if (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) {
slouken@5305
   267
                    continue;
slouken@5305
   268
                }
slouken@5305
   269
            }
slouken@5305
   270
            SDL_memcpy(DeviceName, device.DeviceName, sizeof(DeviceName));
slouken@5305
   271
#ifdef DEBUG_MODES
slouken@5305
   272
            printf("Device: %s\n", WIN_StringToUTF8(DeviceName));
slouken@5305
   273
#endif
slouken@5305
   274
            count = 0;
slouken@5305
   275
            for (j = 0; ; ++j) {
slouken@5305
   276
                if (!EnumDisplayDevices(DeviceName, j, &device, 0)) {
slouken@5305
   277
                    break;
slouken@5305
   278
                }
slouken@5305
   279
                if (!(device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP)) {
slouken@5305
   280
                    continue;
slouken@5305
   281
                }
slouken@5305
   282
                if (pass == 0) {
slouken@5305
   283
                    if (!(device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)) {
slouken@5305
   284
                        continue;
slouken@5305
   285
                    }
slouken@5305
   286
                } else {
slouken@5305
   287
                    if (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) {
slouken@5305
   288
                        continue;
slouken@5305
   289
                    }
slouken@5305
   290
                }
alfred@9813
   291
                count += WIN_AddDisplay(_this, device.DeviceName);
slouken@5305
   292
            }
slouken@5305
   293
            if (count == 0) {
alfred@9813
   294
                WIN_AddDisplay(_this, DeviceName);
slouken@5305
   295
            }
slouken@1895
   296
        }
slouken@1895
   297
    }
slouken@3521
   298
    if (_this->num_displays == 0) {
icculus@7037
   299
        return SDL_SetError("No displays available");
slouken@3521
   300
    }
slouken@3521
   301
    return 0;
slouken@1895
   302
}
slouken@1895
   303
slouken@3528
   304
int
slouken@3528
   305
WIN_GetDisplayBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect)
slouken@3528
   306
{
slouken@5305
   307
    SDL_DisplayModeData *data = (SDL_DisplayModeData *) display->current_mode.driverdata;
slouken@3528
   308
slouken@8086
   309
    rect->x = (int)SDL_ceil(data->DeviceMode.dmPosition.x * data->ScaleX);
slouken@8086
   310
    rect->y = (int)SDL_ceil(data->DeviceMode.dmPosition.y * data->ScaleY);
slouken@8086
   311
    rect->w = (int)SDL_ceil(data->DeviceMode.dmPelsWidth * data->ScaleX);
slouken@8086
   312
    rect->h = (int)SDL_ceil(data->DeviceMode.dmPelsHeight * data->ScaleY);
icculus@6430
   313
slouken@3528
   314
    return 0;
slouken@3528
   315
}
slouken@3528
   316
alfred@9813
   317
int
alfred@9813
   318
WIN_GetDisplayDPI(_THIS, SDL_VideoDisplay * display, float * ddpi, float * hdpi, float * vdpi)
alfred@9813
   319
{
alfred@9813
   320
    SDL_DisplayModeData *data = (SDL_DisplayModeData *) display->current_mode.driverdata;
alfred@9813
   321
slouken@9889
   322
    if (ddpi) {
slouken@9889
   323
        *ddpi = data->DiagDPI;
slouken@9889
   324
    }
slouken@9889
   325
    if (hdpi) {
slouken@9889
   326
        *hdpi = data->HorzDPI;
slouken@9889
   327
    }
slouken@9889
   328
    if (vdpi) {
slouken@9889
   329
        *vdpi = data->VertDPI;
slouken@9889
   330
    }
alfred@9813
   331
slouken@9889
   332
    return data->DiagDPI != 0.0f ? 0 : -1;
alfred@9813
   333
}
alfred@9813
   334
slouken@1895
   335
void
slouken@3500
   336
WIN_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
slouken@1895
   337
{
slouken@3500
   338
    SDL_DisplayData *data = (SDL_DisplayData *) display->driverdata;
slouken@1895
   339
    DWORD i;
slouken@1895
   340
    SDL_DisplayMode mode;
slouken@1895
   341
slouken@1895
   342
    for (i = 0;; ++i) {
alfred@9813
   343
        if (!WIN_GetDisplayMode(_this, data->DeviceName, i, &mode)) {
slouken@1895
   344
            break;
slouken@1895
   345
        }
slouken@5305
   346
        if (SDL_ISPIXELFORMAT_INDEXED(mode.format)) {
slouken@5305
   347
            /* We don't support palettized modes now */
gabomdq@7102
   348
            SDL_free(mode.driverdata);
slouken@5305
   349
            continue;
slouken@5305
   350
        }
slouken@3521
   351
        if (mode.format != SDL_PIXELFORMAT_UNKNOWN) {
slouken@3500
   352
            if (!SDL_AddDisplayMode(display, &mode)) {
lestat@3175
   353
                SDL_free(mode.driverdata);
slouken@3186
   354
            }
slouken@8086
   355
        } else {
gabomdq@7102
   356
            SDL_free(mode.driverdata);
gabomdq@7102
   357
        }
slouken@1895
   358
    }
slouken@1895
   359
}
slouken@1895
   360
slouken@1895
   361
int
slouken@3500
   362
WIN_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode)
slouken@1895
   363
{
slouken@3500
   364
    SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata;
slouken@1895
   365
    SDL_DisplayModeData *data = (SDL_DisplayModeData *) mode->driverdata;
slouken@1895
   366
    LONG status;
slouken@3139
   367
slouken@8795
   368
    if (mode->driverdata == display->desktop_mode.driverdata) {
slouken@8795
   369
        status = ChangeDisplaySettingsEx(displaydata->DeviceName, NULL, NULL, 0, NULL);
slouken@8795
   370
    } else {
slouken@8795
   371
        status = ChangeDisplaySettingsEx(displaydata->DeviceName, &data->DeviceMode, NULL, CDS_FULLSCREEN, NULL);
slouken@8795
   372
    }
slouken@5305
   373
    if (status != DISP_CHANGE_SUCCESSFUL) {
slouken@1895
   374
        const char *reason = "Unknown reason";
slouken@1895
   375
        switch (status) {
slouken@1895
   376
        case DISP_CHANGE_BADFLAGS:
slouken@1895
   377
            reason = "DISP_CHANGE_BADFLAGS";
slouken@1895
   378
            break;
slouken@1895
   379
        case DISP_CHANGE_BADMODE:
slouken@1895
   380
            reason = "DISP_CHANGE_BADMODE";
slouken@1895
   381
            break;
slouken@1895
   382
        case DISP_CHANGE_BADPARAM:
slouken@1895
   383
            reason = "DISP_CHANGE_BADPARAM";
slouken@1895
   384
            break;
slouken@1895
   385
        case DISP_CHANGE_FAILED:
slouken@1895
   386
            reason = "DISP_CHANGE_FAILED";
slouken@1895
   387
            break;
slouken@1895
   388
        }
icculus@7037
   389
        return SDL_SetError("ChangeDisplaySettingsEx() failed: %s", reason);
slouken@1895
   390
    }
slouken@5305
   391
    EnumDisplaySettings(displaydata->DeviceName, ENUM_CURRENT_SETTINGS, &data->DeviceMode);
slouken@5305
   392
    return 0;
slouken@1895
   393
}
slouken@1895
   394
slouken@1895
   395
void
slouken@1895
   396
WIN_QuitModes(_THIS)
slouken@1895
   397
{
slouken@5305
   398
    /* All fullscreen windows should have restored modes by now */
slouken@1895
   399
}
slouken@1895
   400
slouken@6044
   401
#endif /* SDL_VIDEO_DRIVER_WINDOWS */
slouken@6044
   402
slouken@1895
   403
/* vi: set ts=4 sw=4 expandtab: */