src/events/SDL_touch.c
author Sylvain Becker
Wed, 10 Apr 2019 10:59:53 +0200
changeset 12701 3477a301a5a8
parent 12700 1223f50edde3
child 12855 ac0c65535817
permissions -rw-r--r--
Fixed bug 4581 - generate synthetic mouse events at window boundaries
when real touch events are actually outside the window.
jim@4640
     1
/*
slouken@5535
     2
  Simple DirectMedia Layer
slouken@12503
     3
  Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
jim@4640
     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.
jim@4640
     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:
jim@4640
    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.
jim@4640
    20
*/
icculus@8093
    21
#include "../SDL_internal.h"
jim@4640
    22
jim@4640
    23
/* General touch handling code for SDL */
jim@4640
    24
slouken@6951
    25
#include "SDL_assert.h"
jim@4640
    26
#include "SDL_events.h"
jim@4640
    27
#include "SDL_events_c.h"
slouken@10880
    28
#include "../video/SDL_sysvideo.h"
jimtla@4648
    29
jim@4640
    30
jim@4640
    31
static int SDL_num_touch = 0;
slouken@6951
    32
static SDL_Touch **SDL_touchDevices = NULL;
jim@4640
    33
sylvain@12676
    34
/* for mapping touch events to mice */
sylvain@12676
    35
static SDL_bool finger_touching = SDL_FALSE;
sylvain@12687
    36
static SDL_FingerID track_fingerid;
sylvain@12687
    37
static SDL_TouchID  track_touchid;
jim@4640
    38
jim@4640
    39
/* Public functions */
jim@4640
    40
int
jim@4640
    41
SDL_TouchInit(void)
jim@4640
    42
{
slouken@6951
    43
    return (0);
slouken@6951
    44
}
slouken@6951
    45
slouken@6951
    46
int
philipp@7170
    47
SDL_GetNumTouchDevices(void)
slouken@6951
    48
{
slouken@6951
    49
    return SDL_num_touch;
jim@4640
    50
}
jim@4642
    51
slouken@6951
    52
SDL_TouchID
slouken@6951
    53
SDL_GetTouchDevice(int index)
jim@4641
    54
{
jim@4642
    55
    if (index < 0 || index >= SDL_num_touch) {
slouken@10880
    56
        SDL_SetError("Unknown touch device index %d", index);
slouken@6951
    57
        return 0;
jim@4642
    58
    }
slouken@6951
    59
    return SDL_touchDevices[index]->id;
jim@4640
    60
}
jim@4640
    61
slouken@6044
    62
static int
slouken@6951
    63
SDL_GetTouchIndex(SDL_TouchID id)
jim@4640
    64
{
jim@4640
    65
    int index;
jim@4640
    66
    SDL_Touch *touch;
jim@4640
    67
jim@4640
    68
    for (index = 0; index < SDL_num_touch; ++index) {
slouken@6951
    69
        touch = SDL_touchDevices[index];
jim@4640
    70
        if (touch->id == id) {
jim@4640
    71
            return index;
jim@4640
    72
        }
jim@4640
    73
    }
jim@4640
    74
    return -1;
jim@4640
    75
}
jim@4640
    76
slouken@6951
    77
SDL_Touch *
slouken@6951
    78
SDL_GetTouch(SDL_TouchID id)
jim@4640
    79
{
slouken@6951
    80
    int index = SDL_GetTouchIndex(id);
slouken@6951
    81
    if (index < 0 || index >= SDL_num_touch) {
slouken@10880
    82
        if (SDL_GetVideoDevice()->ResetTouch != NULL) {
slouken@10880
    83
            SDL_SetError("Unknown touch id %d, resetting", (int) id);
slouken@10880
    84
            (SDL_GetVideoDevice()->ResetTouch)(SDL_GetVideoDevice());
slouken@10880
    85
        } else {
slouken@10880
    86
            SDL_SetError("Unknown touch device id %d, cannot reset", (int) id);
slouken@10880
    87
        }
slouken@6951
    88
        return NULL;
slouken@6951
    89
    }
slouken@6951
    90
    return SDL_touchDevices[index];
slouken@6951
    91
}
slouken@6951
    92
slime73@12404
    93
SDL_TouchDeviceType
slime73@12404
    94
SDL_GetTouchDeviceType(SDL_TouchID id)
slime73@12404
    95
{
slime73@12404
    96
    SDL_Touch *touch = SDL_GetTouch(id);
slime73@12404
    97
    if (touch) {
slime73@12404
    98
        return touch->type;
slime73@12404
    99
    }
slime73@12404
   100
    return SDL_TOUCH_DEVICE_INVALID;
slime73@12404
   101
}
slime73@12404
   102
slouken@6951
   103
static int
slouken@6951
   104
SDL_GetFingerIndex(const SDL_Touch * touch, SDL_FingerID fingerid)
slouken@6951
   105
{
slouken@5076
   106
    int index;
slouken@6951
   107
    for (index = 0; index < touch->num_fingers; ++index) {
slouken@6951
   108
        if (touch->fingers[index]->id == fingerid) {
slouken@6951
   109
            return index;
slouken@6951
   110
        }
slouken@6951
   111
    }
slouken@6951
   112
    return -1;
slouken@6951
   113
}
slouken@6951
   114
slouken@10609
   115
static SDL_Finger *
slouken@6951
   116
SDL_GetFinger(const SDL_Touch * touch, SDL_FingerID id)
slouken@6951
   117
{
slouken@6951
   118
    int index = SDL_GetFingerIndex(touch, id);
slouken@6951
   119
    if (index < 0 || index >= touch->num_fingers) {
slouken@6951
   120
        return NULL;
slouken@6951
   121
    }
slouken@6951
   122
    return touch->fingers[index];
slouken@6951
   123
}
jim@4640
   124
slouken@6951
   125
int
slouken@6951
   126
SDL_GetNumTouchFingers(SDL_TouchID touchID)
slouken@6951
   127
{
slouken@6951
   128
    SDL_Touch *touch = SDL_GetTouch(touchID);
slouken@6951
   129
    if (touch) {
slouken@6951
   130
        return touch->num_fingers;
slouken@6951
   131
    }
slouken@6951
   132
    return 0;
slouken@6951
   133
}
slouken@6951
   134
slouken@6951
   135
SDL_Finger *
slouken@6951
   136
SDL_GetTouchFinger(SDL_TouchID touchID, int index)
slouken@6951
   137
{
slouken@6951
   138
    SDL_Touch *touch = SDL_GetTouch(touchID);
slouken@6951
   139
    if (!touch) {
slouken@6951
   140
        return NULL;
slouken@6951
   141
    }
slouken@6951
   142
    if (index < 0 || index >= touch->num_fingers) {
slouken@6951
   143
        SDL_SetError("Unknown touch finger");
slouken@6951
   144
        return NULL;
slouken@6951
   145
    }
slouken@6951
   146
    return touch->fingers[index];
slouken@6951
   147
}
slouken@6951
   148
slouken@6951
   149
int
slime73@12404
   150
SDL_AddTouch(SDL_TouchID touchID, SDL_TouchDeviceType type, const char *name)
slouken@6951
   151
{
slouken@6951
   152
    SDL_Touch **touchDevices;
slouken@6951
   153
    int index;
slouken@6951
   154
slouken@6951
   155
    index = SDL_GetTouchIndex(touchID);
slouken@6951
   156
    if (index >= 0) {
slouken@6951
   157
        return index;
jim@4640
   158
    }
jim@4640
   159
jim@4640
   160
    /* Add the touch to the list of touch */
slouken@6951
   161
    touchDevices = (SDL_Touch **) SDL_realloc(SDL_touchDevices,
slouken@6951
   162
                                      (SDL_num_touch + 1) * sizeof(*touchDevices));
slouken@6951
   163
    if (!touchDevices) {
icculus@7037
   164
        return SDL_OutOfMemory();
slouken@6951
   165
    }
slouken@6951
   166
slouken@6951
   167
    SDL_touchDevices = touchDevices;
philipp@9730
   168
    index = SDL_num_touch;
slouken@6951
   169
slouken@6951
   170
    SDL_touchDevices[index] = (SDL_Touch *) SDL_malloc(sizeof(*SDL_touchDevices[index]));
slouken@6951
   171
    if (!SDL_touchDevices[index]) {
icculus@7037
   172
        return SDL_OutOfMemory();
jim@4640
   173
    }
jim@4640
   174
philipp@9730
   175
    /* Added touch to list */
philipp@9730
   176
    ++SDL_num_touch;
philipp@9730
   177
slouken@6951
   178
    /* we're setting the touch properties */
slouken@6951
   179
    SDL_touchDevices[index]->id = touchID;
slime73@12404
   180
    SDL_touchDevices[index]->type = type;
slouken@6951
   181
    SDL_touchDevices[index]->num_fingers = 0;
slouken@6951
   182
    SDL_touchDevices[index]->max_fingers = 0;
slouken@6951
   183
    SDL_touchDevices[index]->fingers = NULL;
slouken@6951
   184
slouken@6951
   185
    /* Record this touch device for gestures */
slouken@6951
   186
    /* We could do this on the fly in the gesture code if we wanted */
slouken@6951
   187
    SDL_GestureAddTouch(touchID);
slouken@6951
   188
slouken@6951
   189
    return index;
slouken@6951
   190
}
slouken@6951
   191
slouken@7191
   192
static int
slouken@6951
   193
SDL_AddFinger(SDL_Touch *touch, SDL_FingerID fingerid, float x, float y, float pressure)
slouken@6951
   194
{
slouken@6951
   195
    SDL_Finger *finger;
slouken@6951
   196
slouken@6951
   197
    if (touch->num_fingers == touch->max_fingers) {
slouken@6951
   198
        SDL_Finger **new_fingers;
slouken@6951
   199
        new_fingers = (SDL_Finger **)SDL_realloc(touch->fingers, (touch->max_fingers+1)*sizeof(*touch->fingers));
slouken@6951
   200
        if (!new_fingers) {
icculus@7037
   201
            return SDL_OutOfMemory();
slouken@6951
   202
        }
slouken@6951
   203
        touch->fingers = new_fingers;
slouken@6951
   204
        touch->fingers[touch->max_fingers] = (SDL_Finger *)SDL_malloc(sizeof(*finger));
slouken@6951
   205
        if (!touch->fingers[touch->max_fingers]) {
icculus@7037
   206
            return SDL_OutOfMemory();
slouken@6951
   207
        }
slouken@6951
   208
        touch->max_fingers++;
slouken@6951
   209
    }
jim@4640
   210
slouken@6951
   211
    finger = touch->fingers[touch->num_fingers++];
slouken@6951
   212
    finger->id = fingerid;
slouken@6951
   213
    finger->x = x;
slouken@6951
   214
    finger->y = y;
slouken@6951
   215
    finger->pressure = pressure;
slouken@6951
   216
    return 0;
slouken@6951
   217
}
slouken@6951
   218
slouken@6951
   219
static int
slouken@6951
   220
SDL_DelFinger(SDL_Touch* touch, SDL_FingerID fingerid)
slouken@6951
   221
{
slouken@6951
   222
    SDL_Finger *temp;
slouken@6951
   223
slouken@6951
   224
    int index = SDL_GetFingerIndex(touch, fingerid);
slouken@6951
   225
    if (index < 0) {
slouken@6951
   226
        return -1;
slouken@6951
   227
    }
slouken@6951
   228
slouken@6951
   229
    touch->num_fingers--;
slouken@6951
   230
    temp = touch->fingers[index];
slouken@6951
   231
    touch->fingers[index] = touch->fingers[touch->num_fingers];
slouken@6951
   232
    touch->fingers[touch->num_fingers] = temp;
slouken@6951
   233
    return 0;
slouken@6951
   234
}
slouken@6951
   235
slouken@6951
   236
int
slouken@6951
   237
SDL_SendTouch(SDL_TouchID id, SDL_FingerID fingerid,
slouken@6951
   238
              SDL_bool down, float x, float y, float pressure)
slouken@6951
   239
{
slouken@6951
   240
    int posted;
slouken@6951
   241
    SDL_Finger *finger;
slouken@6951
   242
slouken@6951
   243
    SDL_Touch* touch = SDL_GetTouch(id);
slouken@6951
   244
    if (!touch) {
jim@4640
   245
        return -1;
jim@4640
   246
    }
slouken@6951
   247
sylvain@12687
   248
    /* SDL_HINT_TOUCH_MOUSE_EVENTS: controlling whether touch events should generate synthetic mouse events */
sylvain@12676
   249
    {
sylvain@12687
   250
        SDL_Mouse *mouse = SDL_GetMouse();
sylvain@12687
   251
        if (mouse->touch_mouse_events) {
sylvain@12688
   252
            /* FIXME: maybe we should only restrict to a few SDL_TouchDeviceType */
sylvain@12688
   253
            if (id != SDL_MOUSE_TOUCHID) {
sylvain@12688
   254
                SDL_Window *window = SDL_GetMouseFocus();
sylvain@12688
   255
                if (window) {
sylvain@12688
   256
                    if (down) {
sylvain@12688
   257
                        if (finger_touching == SDL_FALSE) {
sylvain@12688
   258
                            int pos_x = (int)(x * (float)window->w);
sylvain@12688
   259
                            int pos_y = (int)(y * (float)window->h);
sylvain@12701
   260
                            if (pos_x < 0) pos_x = 0;
sylvain@12701
   261
                            if (pos_x > window->w - 1) pos_x = window->w - 1;
sylvain@12701
   262
                            if (pos_y < 0) pos_y = 0;
sylvain@12701
   263
                            if (pos_y > window->h - 1) pos_y = window->h - 1;
sylvain@12701
   264
                            SDL_SendMouseMotion(window, SDL_TOUCH_MOUSEID, 0, pos_x, pos_y);
sylvain@12701
   265
                            SDL_SendMouseButton(window, SDL_TOUCH_MOUSEID, SDL_PRESSED, SDL_BUTTON_LEFT);
sylvain@12688
   266
                        }
sylvain@12688
   267
                    } else {
sylvain@12688
   268
                        if (finger_touching == SDL_TRUE && track_touchid == id && track_fingerid == fingerid) {
sylvain@12701
   269
                            SDL_SendMouseButton(window, SDL_TOUCH_MOUSEID, SDL_RELEASED, SDL_BUTTON_LEFT);
sylvain@12688
   270
                        }
sylvain@12687
   271
                    }
sylvain@12676
   272
                }
sylvain@12697
   273
                if (down) {
sylvain@12697
   274
                    if (finger_touching == SDL_FALSE) {
sylvain@12697
   275
                        finger_touching = SDL_TRUE;
sylvain@12697
   276
                        track_touchid = id;
sylvain@12697
   277
                        track_fingerid = fingerid;
sylvain@12697
   278
                    }
sylvain@12697
   279
                } else {
sylvain@12697
   280
                    if (finger_touching == SDL_TRUE && track_touchid == id && track_fingerid == fingerid) {
sylvain@12697
   281
                        finger_touching = SDL_FALSE;
sylvain@12697
   282
                    }
sylvain@12697
   283
                }
sylvain@12676
   284
            }
sylvain@12676
   285
        }
sylvain@12676
   286
    }
sylvain@12676
   287
slouken@6951
   288
    finger = SDL_GetFinger(touch, fingerid);
slouken@6951
   289
    if (down) {
slouken@6951
   290
        if (finger) {
slouken@6951
   291
            /* This finger is already down */
slouken@6951
   292
            return 0;
slouken@6951
   293
        }
slouken@6951
   294
slouken@6951
   295
        if (SDL_AddFinger(touch, fingerid, x, y, pressure) < 0) {
slouken@6951
   296
            return 0;
slouken@6951
   297
        }
jim@4640
   298
slouken@6951
   299
        posted = 0;
slouken@6951
   300
        if (SDL_GetEventState(SDL_FINGERDOWN) == SDL_ENABLE) {
slouken@6951
   301
            SDL_Event event;
slouken@6951
   302
            event.tfinger.type = SDL_FINGERDOWN;
slouken@6951
   303
            event.tfinger.touchId = id;
slouken@6951
   304
            event.tfinger.fingerId = fingerid;
slouken@6951
   305
            event.tfinger.x = x;
slouken@6951
   306
            event.tfinger.y = y;
slouken@6951
   307
            event.tfinger.dx = 0;
slouken@6951
   308
            event.tfinger.dy = 0;
slouken@6951
   309
            event.tfinger.pressure = pressure;
slouken@6951
   310
            posted = (SDL_PushEvent(&event) > 0);
slouken@6951
   311
        }
slouken@6951
   312
    } else {
slouken@6951
   313
        if (!finger) {
slouken@6951
   314
            /* This finger is already up */
slouken@6951
   315
            return 0;
slouken@6951
   316
        }
slouken@6951
   317
slouken@6951
   318
        posted = 0;
slouken@6951
   319
        if (SDL_GetEventState(SDL_FINGERUP) == SDL_ENABLE) {
slouken@6951
   320
            SDL_Event event;
slouken@6951
   321
            event.tfinger.type = SDL_FINGERUP;
slouken@6951
   322
            event.tfinger.touchId =  id;
slouken@6951
   323
            event.tfinger.fingerId = fingerid;
slouken@6951
   324
            /* I don't trust the coordinates passed on fingerUp */
slouken@7191
   325
            event.tfinger.x = finger->x;
slouken@6951
   326
            event.tfinger.y = finger->y;
slouken@6951
   327
            event.tfinger.dx = 0;
slouken@6951
   328
            event.tfinger.dy = 0;
slouken@6951
   329
            event.tfinger.pressure = pressure;
slouken@6951
   330
            posted = (SDL_PushEvent(&event) > 0);
slouken@6951
   331
        }
slouken@6951
   332
slouken@6951
   333
        SDL_DelFinger(touch, fingerid);
slouken@6951
   334
    }
slouken@6951
   335
    return posted;
slouken@6951
   336
}
jim@4642
   337
slouken@6951
   338
int
slouken@6951
   339
SDL_SendTouchMotion(SDL_TouchID id, SDL_FingerID fingerid,
slouken@6951
   340
                    float x, float y, float pressure)
slouken@6951
   341
{
slouken@6951
   342
    SDL_Touch *touch;
slouken@6951
   343
    SDL_Finger *finger;
slouken@6951
   344
    int posted;
slouken@6951
   345
    float xrel, yrel, prel;
slouken@6951
   346
slouken@6951
   347
    touch = SDL_GetTouch(id);
slouken@6951
   348
    if (!touch) {
slouken@6951
   349
        return -1;
slouken@6951
   350
    }
slouken@6951
   351
sylvain@12687
   352
    /* SDL_HINT_TOUCH_MOUSE_EVENTS: controlling whether touch events should generate synthetic mouse events */
sylvain@12676
   353
    {
sylvain@12687
   354
        SDL_Mouse *mouse = SDL_GetMouse();
sylvain@12687
   355
        if (mouse->touch_mouse_events) {
sylvain@12688
   356
            if (id != SDL_MOUSE_TOUCHID) {
sylvain@12688
   357
                SDL_Window *window = SDL_GetMouseFocus();
sylvain@12688
   358
                if (window) {
sylvain@12688
   359
                    if (finger_touching == SDL_TRUE && track_touchid == id && track_fingerid == fingerid) {
sylvain@12688
   360
                        int pos_x = (int)(x * (float)window->w);
sylvain@12688
   361
                        int pos_y = (int)(y * (float)window->h);
sylvain@12701
   362
                        if (pos_x < 0) pos_x = 0;
sylvain@12701
   363
                        if (pos_x > window->w - 1) pos_x = window->w - 1;
sylvain@12701
   364
                        if (pos_y < 0) pos_y = 0;
sylvain@12701
   365
                        if (pos_y > window->h - 1) pos_y = window->h - 1;
sylvain@12701
   366
                        SDL_SendMouseMotion(window, SDL_TOUCH_MOUSEID, 0, pos_x, pos_y);
sylvain@12688
   367
                    }
sylvain@12687
   368
                }
sylvain@12676
   369
            }
sylvain@12676
   370
        }
sylvain@12676
   371
    }
sylvain@12676
   372
slouken@6951
   373
    finger = SDL_GetFinger(touch,fingerid);
slouken@6951
   374
    if (!finger) {
slouken@7191
   375
        return SDL_SendTouch(id, fingerid, SDL_TRUE, x, y, pressure);
slouken@6951
   376
    }
slouken@6951
   377
slouken@6951
   378
    xrel = x - finger->x;
slouken@6951
   379
    yrel = y - finger->y;
slouken@6951
   380
    prel = pressure - finger->pressure;
jim@4657
   381
slouken@6951
   382
    /* Drop events that don't change state */
slouken@12466
   383
    if (xrel == 0.0f && yrel == 0.0f && prel == 0.0f) {
slouken@6951
   384
#if 0
slouken@6951
   385
        printf("Touch event didn't change state - dropped!\n");
slouken@6951
   386
#endif
slouken@6951
   387
        return 0;
slouken@6951
   388
    }
slouken@6951
   389
slouken@6951
   390
    /* Update internal touch coordinates */
slouken@6951
   391
    finger->x = x;
slouken@6951
   392
    finger->y = y;
slouken@6951
   393
    finger->pressure = pressure;
slouken@7191
   394
slouken@6951
   395
    /* Post the event, if desired */
slouken@6951
   396
    posted = 0;
slouken@6951
   397
    if (SDL_GetEventState(SDL_FINGERMOTION) == SDL_ENABLE) {
slouken@6951
   398
        SDL_Event event;
slouken@6951
   399
        event.tfinger.type = SDL_FINGERMOTION;
slouken@6951
   400
        event.tfinger.touchId = id;
slouken@6951
   401
        event.tfinger.fingerId = fingerid;
slouken@6951
   402
        event.tfinger.x = x;
slouken@6951
   403
        event.tfinger.y = y;
slouken@6951
   404
        event.tfinger.dx = xrel;
slouken@7191
   405
        event.tfinger.dy = yrel;
slouken@6951
   406
        event.tfinger.pressure = pressure;
slouken@6951
   407
        posted = (SDL_PushEvent(&event) > 0);
slouken@6951
   408
    }
slouken@6951
   409
    return posted;
jim@4640
   410
}
jim@4640
   411
jim@4640
   412
void
jimtla@4678
   413
SDL_DelTouch(SDL_TouchID id)
jim@4640
   414
{
slouken@6951
   415
    int i;
slouken@6951
   416
    int index = SDL_GetTouchIndex(id);
jim@4641
   417
    SDL_Touch *touch = SDL_GetTouch(id);
jim@4640
   418
jim@4640
   419
    if (!touch) {
jim@4640
   420
        return;
jim@4640
   421
    }
jim@4640
   422
slouken@6951
   423
    for (i = 0; i < touch->max_fingers; ++i) {
slouken@6951
   424
        SDL_free(touch->fingers[i]);
jim@4640
   425
    }
slouken@6951
   426
    SDL_free(touch->fingers);
jim@4640
   427
    SDL_free(touch);
jim@4640
   428
jim@4641
   429
    SDL_num_touch--;
slouken@6951
   430
    SDL_touchDevices[index] = SDL_touchDevices[SDL_num_touch];
slouken@11290
   431
slouken@11290
   432
    /* Delete this touch device for gestures */
slouken@11290
   433
    SDL_GestureDelTouch(id);
jim@4640
   434
}
jim@4640
   435
jim@4640
   436
void
jim@4640
   437
SDL_TouchQuit(void)
jim@4640
   438
{
jim@4640
   439
    int i;
jim@4640
   440
slouken@6951
   441
    for (i = SDL_num_touch; i--; ) {
slouken@6951
   442
        SDL_DelTouch(SDL_touchDevices[i]->id);
jim@4640
   443
    }
slouken@6951
   444
    SDL_assert(SDL_num_touch == 0);
jim@4640
   445
slouken@7719
   446
    SDL_free(SDL_touchDevices);
slouken@7719
   447
    SDL_touchDevices = NULL;
slouken@11290
   448
    SDL_GestureQuit();
jim@4640
   449
}
jim@4640
   450
jim@4640
   451
/* vi: set ts=4 sw=4 expandtab: */