e14743d1 |
1 | /* |
2 | SDL - Simple DirectMedia Layer |
3 | Copyright (C) 1997-2009 Sam Lantinga |
4 | |
5 | This library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2.1 of the License, or (at your option) any later version. |
9 | |
10 | This library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Lesser General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with this library; if not, write to the Free Software |
17 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
18 | |
19 | Sam Lantinga |
20 | slouken@libsdl.org |
21 | */ |
22 | #include "SDL_config.h" |
23 | |
24 | #include <sys/time.h> |
25 | #include <sys/mman.h> |
26 | #include <sys/ioctl.h> |
27 | #include <dev/wscons/wsdisplay_usl_io.h> |
28 | #include <fcntl.h> |
29 | #include <unistd.h> |
30 | #include <errno.h> |
31 | |
32 | #include "SDL_video.h" |
33 | #include "SDL_mouse.h" |
34 | #include "../SDL_sysvideo.h" |
35 | #include "../SDL_pixels_c.h" |
36 | #include "../../events/SDL_events_c.h" |
37 | |
38 | #include "SDL_wsconsvideo.h" |
39 | #include "SDL_wsconsevents_c.h" |
40 | #include "SDL_wsconsmouse_c.h" |
41 | |
42 | #define WSCONSVID_DRIVER_NAME "wscons" |
43 | enum { |
44 | WSCONS_ROTATE_NONE = 0, |
45 | WSCONS_ROTATE_CCW = 90, |
46 | WSCONS_ROTATE_UD = 180, |
47 | WSCONS_ROTATE_CW = 270 |
48 | }; |
49 | |
50 | #define min(a,b) ((a)<(b)?(a):(b)) |
51 | |
52 | /* Initialization/Query functions */ |
53 | static int WSCONS_VideoInit(_THIS, SDL_PixelFormat *vformat); |
54 | static SDL_Rect **WSCONS_ListModes(_THIS, SDL_PixelFormat *format, Uint32 flags); |
55 | static SDL_Surface *WSCONS_SetVideoMode(_THIS, SDL_Surface *current, int width, int height, int bpp, Uint32 flags); |
56 | static int WSCONS_SetColors(_THIS, int firstcolor, int ncolors, SDL_Color *colors); |
57 | static void WSCONS_VideoQuit(_THIS); |
58 | |
59 | /* Hardware surface functions */ |
60 | static int WSCONS_AllocHWSurface(_THIS, SDL_Surface *surface); |
61 | static int WSCONS_LockHWSurface(_THIS, SDL_Surface *surface); |
62 | static void WSCONS_UnlockHWSurface(_THIS, SDL_Surface *surface); |
63 | static void WSCONS_FreeHWSurface(_THIS, SDL_Surface *surface); |
64 | |
65 | /* etc. */ |
66 | static WSCONS_bitBlit WSCONS_blit16; |
67 | static WSCONS_bitBlit WSCONS_blit16blocked; |
68 | static void WSCONS_UpdateRects(_THIS, int numrects, SDL_Rect *rects); |
69 | |
70 | void WSCONS_ReportError(char *fmt, ...) |
71 | { |
72 | char message[200]; |
73 | va_list vaArgs; |
74 | |
75 | message[199] = '\0'; |
76 | |
77 | va_start(vaArgs, fmt); |
78 | vsnprintf(message, 199, fmt, vaArgs); |
79 | va_end(vaArgs); |
80 | |
81 | SDL_SetError(message); |
82 | fprintf(stderr, "WSCONS error: %s\n", message); |
83 | } |
84 | |
85 | /* WSCONS driver bootstrap functions */ |
86 | |
87 | static int WSCONS_Available(void) |
88 | { |
89 | return 1; |
90 | } |
91 | |
92 | static void WSCONS_DeleteDevice(SDL_VideoDevice *device) |
93 | { |
94 | SDL_free(device->hidden); |
95 | SDL_free(device); |
96 | } |
97 | |
98 | static SDL_VideoDevice *WSCONS_CreateDevice(int devindex) |
99 | { |
100 | SDL_VideoDevice *device; |
101 | |
102 | /* Initialize all variables that we clean on shutdown */ |
103 | device = (SDL_VideoDevice *)SDL_malloc(sizeof(SDL_VideoDevice)); |
104 | if (device == NULL) { |
105 | SDL_OutOfMemory(); |
106 | return 0; |
107 | } |
108 | SDL_memset(device, 0, (sizeof *device)); |
109 | device->hidden = |
110 | (struct SDL_PrivateVideoData *)SDL_malloc((sizeof *device->hidden)); |
111 | if (device->hidden == NULL) { |
112 | SDL_OutOfMemory(); |
113 | SDL_free(device); |
114 | return(0); |
115 | } |
116 | SDL_memset(device->hidden, 0, (sizeof *device->hidden)); |
117 | device->hidden->fd = -1; |
118 | |
119 | /* Set the function pointers */ |
120 | device->VideoInit = WSCONS_VideoInit; |
121 | device->ListModes = WSCONS_ListModes; |
122 | device->SetVideoMode = WSCONS_SetVideoMode; |
123 | device->SetColors = WSCONS_SetColors; |
124 | device->UpdateRects = WSCONS_UpdateRects; |
125 | device->VideoQuit = WSCONS_VideoQuit; |
126 | device->AllocHWSurface = WSCONS_AllocHWSurface; |
127 | device->LockHWSurface = WSCONS_LockHWSurface; |
128 | device->UnlockHWSurface = WSCONS_UnlockHWSurface; |
129 | device->FreeHWSurface = WSCONS_FreeHWSurface; |
130 | device->InitOSKeymap = WSCONS_InitOSKeymap; |
131 | device->PumpEvents = WSCONS_PumpEvents; |
132 | device->free = WSCONS_DeleteDevice; |
133 | |
134 | return device; |
135 | } |
136 | |
137 | VideoBootStrap WSCONS_bootstrap = { |
138 | WSCONSVID_DRIVER_NAME, |
139 | "SDL wscons video driver", |
140 | WSCONS_Available, |
141 | WSCONS_CreateDevice |
142 | }; |
143 | |
144 | #define WSCONSDEV_FORMAT "/dev/ttyC%01x" |
145 | |
146 | int WSCONS_VideoInit(_THIS, SDL_PixelFormat *vformat) |
147 | { |
148 | char devnamebuf[30]; |
149 | char *devname; |
150 | char *rotation; |
151 | int wstype; |
152 | int wsmode = WSDISPLAYIO_MODE_DUMBFB; |
153 | size_t len, mapsize; |
154 | int pagemask; |
155 | int width, height; |
156 | |
157 | devname = SDL_getenv("SDL_WSCONSDEV"); |
158 | if (devname == NULL) { |
159 | int activeVT; |
160 | if (ioctl(STDIN_FILENO, VT_GETACTIVE, &activeVT) == -1) { |
161 | WSCONS_ReportError("Unable to determine active terminal: %s", |
162 | strerror(errno)); |
163 | return -1; |
164 | } |
165 | SDL_snprintf(devnamebuf, sizeof(devnamebuf), WSCONSDEV_FORMAT, activeVT - 1); |
166 | devname = devnamebuf; |
167 | } |
168 | |
169 | private->fd = open(devname, O_RDWR | O_NONBLOCK, 0); |
170 | if (private->fd == -1) { |
171 | WSCONS_ReportError("open %s: %s", devname, strerror(errno)); |
172 | return -1; |
173 | } |
174 | if (ioctl(private->fd, WSDISPLAYIO_GINFO, &private->info) == -1) { |
175 | WSCONS_ReportError("ioctl WSDISPLAY_GINFO: %s", strerror(errno)); |
176 | return -1; |
177 | } |
178 | if (ioctl(private->fd, WSDISPLAYIO_GTYPE, &wstype) == -1) { |
179 | WSCONS_ReportError("ioctl WSDISPLAY_GTYPE: %s", strerror(errno)); |
180 | return -1; |
181 | } |
182 | if (ioctl(private->fd, WSDISPLAYIO_LINEBYTES, &private->physlinebytes) == -1) { |
183 | WSCONS_ReportError("ioctl WSDISPLAYIO_LINEBYTES: %s", strerror(errno)); |
184 | return -1; |
185 | } |
186 | if (private->info.depth > 8) { |
187 | if (wstype == WSDISPLAY_TYPE_SUN24 || |
188 | wstype == WSDISPLAY_TYPE_SUNCG12 || |
189 | wstype == WSDISPLAY_TYPE_SUNCG14 || |
190 | wstype == WSDISPLAY_TYPE_SUNTCX || |
191 | wstype == WSDISPLAY_TYPE_SUNFFB) { |
192 | private->redMask = 0x0000ff; |
193 | private->greenMask = 0x00ff00; |
194 | private->blueMask = 0xff0000; |
195 | #ifdef WSDISPLAY_TYPE_PXALCD |
196 | } else if (wstype == WSDISPLAY_TYPE_PXALCD) { |
197 | private->redMask = 0x1f << 11; |
198 | private->greenMask = 0x3f << 5; |
199 | private->blueMask = 0x1f; |
200 | #endif |
201 | } else { |
202 | WSCONS_ReportError("Unknown video hardware"); |
203 | return -1; |
204 | } |
205 | } else { |
206 | WSCONS_ReportError("Displays with 8 bpp or less are not supported"); |
207 | return -1; |
208 | } |
209 | |
210 | private->rotate = WSCONS_ROTATE_NONE; |
211 | rotation = SDL_getenv("SDL_VIDEO_WSCONS_ROTATION"); |
212 | if (rotation != NULL) { |
213 | if (SDL_strlen(rotation) == 0) { |
214 | private->shadowFB = 0; |
215 | private->rotate = WSCONS_ROTATE_NONE; |
216 | printf("Not rotating, no shadow\n"); |
217 | } else if (!SDL_strcmp(rotation, "NONE")) { |
218 | private->shadowFB = 1; |
219 | private->rotate = WSCONS_ROTATE_NONE; |
220 | printf("Not rotating, but still using shadow\n"); |
221 | } else if (!SDL_strcmp(rotation, "CW")) { |
222 | private->shadowFB = 1; |
223 | private->rotate = WSCONS_ROTATE_CW; |
224 | printf("Rotating screen clockwise\n"); |
225 | } else if (!SDL_strcmp(rotation, "CCW")) { |
226 | private->shadowFB = 1; |
227 | private->rotate = WSCONS_ROTATE_CCW; |
228 | printf("Rotating screen counter clockwise\n"); |
229 | } else if (!SDL_strcmp(rotation, "UD")) { |
230 | private->shadowFB = 1; |
231 | private->rotate = WSCONS_ROTATE_UD; |
232 | printf("Rotating screen upside down\n"); |
233 | } else { |
234 | WSCONS_ReportError("\"%s\" is not a valid value for " |
235 | "SDL_VIDEO_WSCONS_ROTATION", rotation); |
236 | return -1; |
237 | } |
238 | } |
239 | |
240 | switch (private->info.depth) { |
241 | case 1: |
242 | case 4: |
243 | case 8: |
244 | len = private->physlinebytes * private->info.height; |
245 | break; |
246 | case 16: |
247 | if (private->physlinebytes == private->info.width) { |
248 | len = private->info.width * private->info.height * sizeof(short); |
249 | } else { |
250 | len = private->physlinebytes * private->info.height; |
251 | } |
252 | if (private->rotate == WSCONS_ROTATE_NONE || |
253 | private->rotate == WSCONS_ROTATE_UD) { |
254 | private->blitFunc = WSCONS_blit16; |
255 | } else { |
256 | private->blitFunc = WSCONS_blit16blocked; |
257 | } |
258 | break; |
259 | case 32: |
260 | if (private->physlinebytes == private->info.width) { |
261 | len = private->info.width * private->info.height * sizeof(int); |
262 | } else { |
263 | len = private->physlinebytes * private->info.height; |
264 | } |
265 | break; |
266 | default: |
267 | WSCONS_ReportError("unsupported depth %d", private->info.depth); |
268 | return -1; |
269 | } |
270 | |
271 | if (private->shadowFB && private->blitFunc == NULL) { |
272 | WSCONS_ReportError("Using software buffer, but no blitter function is " |
273 | "available for this %d bpp.", private->info.depth); |
274 | return -1; |
275 | } |
276 | |
277 | if (ioctl(private->fd, WSDISPLAYIO_SMODE, &wsmode) == -1) { |
278 | WSCONS_ReportError("ioctl SMODE"); |
279 | return -1; |
280 | } |
281 | |
282 | pagemask = getpagesize() - 1; |
283 | mapsize = ((int)len + pagemask) & ~pagemask; |
284 | private->physmem = (Uint8 *)mmap(NULL, mapsize, |
285 | PROT_READ | PROT_WRITE, MAP_SHARED, |
286 | private->fd, (off_t)0); |
287 | if (private->physmem == (Uint8 *)MAP_FAILED) { |
288 | private->physmem = NULL; |
289 | WSCONS_ReportError("mmap: %s", strerror(errno)); |
290 | return -1; |
291 | } |
292 | private->fbmem_len = len; |
293 | |
294 | if (private->rotate == WSCONS_ROTATE_CW || |
295 | private->rotate == WSCONS_ROTATE_CCW) { |
296 | width = private->info.height; |
297 | height = private->info.width; |
298 | } else { |
299 | width = private->info.width; |
300 | height = private->info.height; |
301 | } |
302 | |
303 | this->info.current_w = width; |
304 | this->info.current_h = height; |
305 | |
306 | if (private->shadowFB) { |
307 | private->shadowmem = (Uint8 *)SDL_malloc(len); |
308 | if (private->shadowmem == NULL) { |
309 | WSCONS_ReportError("No memory for shadow"); |
310 | return -1; |
311 | } |
312 | private->fbstart = private->shadowmem; |
313 | private->fblinebytes = width * ((private->info.depth + 7) / 8); |
314 | } else { |
315 | private->fbstart = private->physmem; |
316 | private->fblinebytes = private->physlinebytes; |
317 | } |
318 | |
319 | private->SDL_modelist[0] = (SDL_Rect *)SDL_malloc(sizeof(SDL_Rect)); |
320 | private->SDL_modelist[0]->w = width; |
321 | private->SDL_modelist[0]->h = height; |
322 | |
323 | vformat->BitsPerPixel = private->info.depth; |
324 | vformat->BytesPerPixel = private->info.depth / 8; |
325 | |
326 | if (WSCONS_InitKeyboard(this) == -1) { |
327 | return -1; |
328 | } |
329 | |
330 | return 0; |
331 | } |
332 | |
333 | SDL_Rect **WSCONS_ListModes(_THIS, SDL_PixelFormat *format, Uint32 flags) |
334 | { |
335 | if (format->BitsPerPixel == private->info.depth) { |
336 | return private->SDL_modelist; |
337 | } else { |
338 | return NULL; |
339 | } |
340 | } |
341 | |
342 | SDL_Surface *WSCONS_SetVideoMode(_THIS, SDL_Surface *current, |
343 | int width, int height, int bpp, Uint32 flags) |
344 | { |
345 | if (width != private->SDL_modelist[0]->w || |
346 | height != private->SDL_modelist[0]->h) { |
347 | WSCONS_ReportError("Requested video mode %dx%d not supported.", |
348 | width, height); |
349 | return NULL; |
350 | } |
351 | if (bpp != private->info.depth) { |
352 | WSCONS_ReportError("Requested video depth %d bpp not supported.", bpp); |
353 | return NULL; |
354 | } |
355 | |
356 | if (!SDL_ReallocFormat(current, |
357 | bpp, |
358 | private->redMask, |
359 | private->greenMask, |
360 | private->blueMask, |
361 | 0)) { |
362 | WSCONS_ReportError("Couldn't allocate new pixel format"); |
363 | return NULL; |
364 | } |
365 | |
366 | current->flags &= SDL_FULLSCREEN; |
367 | if (private->shadowFB) { |
368 | current->flags |= SDL_SWSURFACE; |
369 | } else { |
370 | current->flags |= SDL_HWSURFACE; |
371 | } |
372 | current->w = width; |
373 | current->h = height; |
374 | current->pitch = private->fblinebytes; |
375 | current->pixels = private->fbstart; |
376 | |
377 | SDL_memset(private->fbstart, 0, private->fbmem_len); |
378 | |
379 | return current; |
380 | } |
381 | |
382 | static int WSCONS_AllocHWSurface(_THIS, SDL_Surface *surface) |
383 | { |
384 | return -1; |
385 | } |
386 | static void WSCONS_FreeHWSurface(_THIS, SDL_Surface *surface) |
387 | { |
388 | } |
389 | |
390 | static int WSCONS_LockHWSurface(_THIS, SDL_Surface *surface) |
391 | { |
392 | return 0; |
393 | } |
394 | |
395 | static void WSCONS_UnlockHWSurface(_THIS, SDL_Surface *surface) |
396 | { |
397 | } |
398 | |
399 | static void WSCONS_blit16(Uint8 *byte_src_pos, |
400 | int srcRightDelta, |
401 | int srcDownDelta, |
402 | Uint8 *byte_dst_pos, |
403 | int dst_linebytes, |
404 | int width, |
405 | int height) |
406 | { |
407 | int w; |
408 | Uint16 *src_pos = (Uint16 *)byte_src_pos; |
409 | Uint16 *dst_pos = (Uint16 *)byte_dst_pos; |
410 | |
411 | while (height) { |
412 | Uint16 *src = src_pos; |
413 | Uint16 *dst = dst_pos; |
414 | for (w = width; w != 0; w--) { |
415 | *dst = *src; |
416 | src += srcRightDelta; |
417 | dst++; |
418 | } |
419 | dst_pos = (Uint16 *)((Uint8 *)dst_pos + dst_linebytes); |
420 | src_pos += srcDownDelta; |
421 | height--; |
422 | } |
423 | } |
424 | |
425 | #define BLOCKSIZE_W 32 |
426 | #define BLOCKSIZE_H 32 |
427 | |
428 | static void WSCONS_blit16blocked(Uint8 *byte_src_pos, |
429 | int srcRightDelta, |
430 | int srcDownDelta, |
431 | Uint8 *byte_dst_pos, |
432 | int dst_linebytes, |
433 | int width, |
434 | int height) |
435 | { |
436 | int w; |
437 | Uint16 *src_pos = (Uint16 *)byte_src_pos; |
438 | Uint16 *dst_pos = (Uint16 *)byte_dst_pos; |
439 | |
440 | while (height > 0) { |
441 | Uint16 *src = src_pos; |
442 | Uint16 *dst = dst_pos; |
443 | for (w = width; w > 0; w -= BLOCKSIZE_W) { |
444 | WSCONS_blit16((Uint8 *)src, |
445 | srcRightDelta, |
446 | srcDownDelta, |
447 | (Uint8 *)dst, |
448 | dst_linebytes, |
449 | min(w, BLOCKSIZE_W), |
450 | min(height, BLOCKSIZE_H)); |
451 | src += srcRightDelta * BLOCKSIZE_W; |
452 | dst += BLOCKSIZE_W; |
453 | } |
454 | dst_pos = (Uint16 *)((Uint8 *)dst_pos + dst_linebytes * BLOCKSIZE_H); |
455 | src_pos += srcDownDelta * BLOCKSIZE_H; |
456 | height -= BLOCKSIZE_H; |
457 | } |
458 | } |
459 | |
460 | static void WSCONS_UpdateRects(_THIS, int numrects, SDL_Rect *rects) |
461 | { |
462 | int width = private->SDL_modelist[0]->w; |
463 | int height = private->SDL_modelist[0]->h; |
464 | int bytesPerPixel = (private->info.depth + 7) / 8; |
465 | int i; |
466 | |
467 | if (!private->shadowFB) { |
468 | return; |
469 | } |
470 | |
471 | if (private->info.depth != 16) { |
472 | WSCONS_ReportError("Shadow copy only implemented for 16 bpp"); |
473 | return; |
474 | } |
475 | |
476 | for (i = 0; i < numrects; i++) { |
477 | int x1, y1, x2, y2; |
478 | int scr_x1, scr_y1, scr_x2, scr_y2; |
479 | int sha_x1, sha_y1; |
480 | int shadowRightDelta; /* Address change when moving right in dest */ |
481 | int shadowDownDelta; /* Address change when moving down in dest */ |
482 | Uint8 *src_start; |
483 | Uint8 *dst_start; |
484 | |
485 | x1 = rects[i].x; |
486 | y1 = rects[i].y; |
487 | x2 = x1 + rects[i].w; |
488 | y2 = y1 + rects[i].h; |
489 | |
490 | if (x1 < 0) { |
491 | x1 = 0; |
492 | } else if (x1 > width) { |
493 | x1 = width; |
494 | } |
495 | if (x2 < 0) { |
496 | x2 = 0; |
497 | } else if (x2 > width) { |
498 | x2 = width; |
499 | } |
500 | if (y1 < 0) { |
501 | y1 = 0; |
502 | } else if (y1 > height) { |
503 | y1 = height; |
504 | } |
505 | if (y2 < 0) { |
506 | y2 = 0; |
507 | } else if (y2 > height) { |
508 | y2 = height; |
509 | } |
510 | if (x2 <= x1 || y2 <= y1) { |
511 | continue; |
512 | } |
513 | |
514 | switch (private->rotate) { |
515 | case WSCONS_ROTATE_NONE: |
516 | sha_x1 = scr_x1 = x1; |
517 | sha_y1 = scr_y1 = y1; |
518 | scr_x2 = x2; |
519 | scr_y2 = y2; |
520 | shadowRightDelta = 1; |
521 | shadowDownDelta = width; |
522 | break; |
523 | case WSCONS_ROTATE_CCW: |
524 | scr_x1 = y1; |
525 | scr_y1 = width - x2; |
526 | scr_x2 = y2; |
527 | scr_y2 = width - x1; |
528 | sha_x1 = x2 - 1; |
529 | sha_y1 = y1; |
530 | shadowRightDelta = width; |
531 | shadowDownDelta = -1; |
532 | break; |
533 | case WSCONS_ROTATE_UD: |
534 | scr_x1 = width - x2; |
535 | scr_y1 = height - y2; |
536 | scr_x2 = width - x1; |
537 | scr_y2 = height - y1; |
538 | sha_x1 = x2 - 1; |
539 | sha_y1 = y2 - 1; |
540 | shadowRightDelta = -1; |
541 | shadowDownDelta = -width; |
542 | break; |
543 | case WSCONS_ROTATE_CW: |
544 | scr_x1 = height - y2; |
545 | scr_y1 = x1; |
546 | scr_x2 = height - y1; |
547 | scr_y2 = x2; |
548 | sha_x1 = x1; |
549 | sha_y1 = y2 - 1; |
550 | shadowRightDelta = -width; |
551 | shadowDownDelta = 1; |
552 | break; |
553 | default: |
554 | WSCONS_ReportError("Unknown rotation"); |
555 | return; |
556 | } |
557 | |
558 | src_start = private->shadowmem + (sha_y1 * width + sha_x1) * bytesPerPixel; |
559 | dst_start = private->physmem + scr_y1 * private->physlinebytes + |
560 | scr_x1 * bytesPerPixel; |
561 | |
562 | private->blitFunc(src_start, |
563 | shadowRightDelta, |
564 | shadowDownDelta, |
565 | dst_start, |
566 | private->physlinebytes, |
567 | scr_x2 - scr_x1, |
568 | scr_y2 - scr_y1); |
569 | } |
570 | } |
571 | |
572 | int WSCONS_SetColors(_THIS, int firstcolor, int ncolors, SDL_Color *colors) |
573 | { |
574 | return 0; |
575 | } |
576 | |
577 | /* |
578 | * Note: If we are terminated, this could be called in the middle of |
579 | * another SDL video routine -- notably UpdateRects. |
580 | */ |
581 | void WSCONS_VideoQuit(_THIS) |
582 | { |
583 | int mode = WSDISPLAYIO_MODE_EMUL; |
584 | |
585 | if (private->shadowmem != NULL) { |
586 | SDL_free(private->shadowmem); |
587 | private->shadowmem = NULL; |
588 | } |
589 | private->fbstart = NULL; |
590 | if (this->screen != NULL) { |
591 | this->screen->pixels = NULL; |
592 | } |
593 | |
594 | if (private->SDL_modelist[0] != NULL) { |
595 | SDL_free(private->SDL_modelist[0]); |
596 | private->SDL_modelist[0] = NULL; |
597 | } |
598 | |
599 | if (ioctl(private->fd, WSDISPLAYIO_SMODE, &mode) == -1) { |
600 | WSCONS_ReportError("ioctl SMODE"); |
601 | } |
602 | |
603 | WSCONS_ReleaseKeyboard(this); |
604 | |
605 | if (private->fd != -1) { |
606 | close(private->fd); |
607 | private->fd = -1; |
608 | } |
609 | } |