1 /*- 2 * Copyright (c) 1998 Kazutaka YOKOTA <yokota@zodiac.mech.utsunomiya-u.ac.jp> 3 * All rights reserved. 4 * 5 * This code is derived from software contributed to The DragonFly Project 6 * by Sascha Wildner <saw@online.de> 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer as 13 * the first lines of this file unmodified. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 * 29 * $FreeBSD: src/sys/dev/syscons/scvidctl.c,v 1.19.2.2 2000/05/05 09:16:08 nyan Exp $ 30 * $DragonFly: src/sys/dev/misc/syscons/scvidctl.c,v 1.16 2007/08/19 11:39:11 swildner Exp $ 31 */ 32 33 #include "opt_syscons.h" 34 35 #include <sys/param.h> 36 #include <sys/systm.h> 37 #include <sys/conf.h> 38 #include <sys/signalvar.h> 39 #include <sys/tty.h> 40 #include <sys/kernel.h> 41 #include <sys/thread2.h> 42 43 #include <machine/console.h> 44 45 #include <dev/video/fb/fbreg.h> 46 #include "syscons.h" 47 48 SET_DECLARE(scrndr_set, const sc_renderer_t); 49 50 int 51 sc_set_text_mode(scr_stat *scp, struct tty *tp, int mode, int xsize, int ysize, 52 int fontsize) 53 { 54 video_info_t info; 55 u_char *font; 56 int prev_ysize; 57 int new_ysize; 58 int error; 59 60 if ((*vidsw[scp->sc->adapter]->get_info)(scp->sc->adp, mode, &info)) 61 return ENODEV; 62 63 /* adjust argument values */ 64 if (fontsize <= 0) 65 fontsize = info.vi_cheight; 66 if (fontsize < 14) { 67 fontsize = 8; 68 #ifndef SC_NO_FONT_LOADING 69 if (!(scp->sc->fonts_loaded & FONT_8)) 70 return EINVAL; 71 font = scp->sc->font_8; 72 #else 73 font = NULL; 74 #endif 75 } else if (fontsize >= 16) { 76 fontsize = 16; 77 #ifndef SC_NO_FONT_LOADING 78 if (!(scp->sc->fonts_loaded & FONT_16)) 79 return EINVAL; 80 font = scp->sc->font_16; 81 #else 82 font = NULL; 83 #endif 84 } else { 85 fontsize = 14; 86 #ifndef SC_NO_FONT_LOADING 87 if (!(scp->sc->fonts_loaded & FONT_14)) 88 return EINVAL; 89 font = scp->sc->font_14; 90 #else 91 font = NULL; 92 #endif 93 } 94 if ((xsize <= 0) || (xsize > info.vi_width)) 95 xsize = info.vi_width; 96 if ((ysize <= 0) || (ysize > info.vi_height)) 97 ysize = info.vi_height; 98 99 /* stop screen saver, etc */ 100 crit_enter(); 101 if ((error = sc_clean_up(scp))) { 102 crit_exit(); 103 return error; 104 } 105 106 if (sc_render_match(scp, scp->sc->adp->va_name, V_INFO_MM_TEXT) == NULL) { 107 crit_exit(); 108 return ENODEV; 109 } 110 111 /* set up scp */ 112 new_ysize = 0; 113 #ifndef SC_NO_HISTORY 114 if (scp->history != NULL) { 115 sc_hist_save(scp); 116 new_ysize = sc_vtb_rows(scp->history); 117 } 118 #endif 119 prev_ysize = scp->ysize; 120 /* 121 * This is a kludge to fend off scrn_update() while we 122 * muck around with scp. XXX 123 */ 124 scp->status |= UNKNOWN_MODE | MOUSE_HIDDEN; 125 scp->status &= ~(GRAPHICS_MODE | PIXEL_MODE | MOUSE_VISIBLE); 126 scp->mode = mode; 127 scp->model = V_INFO_MM_TEXT; 128 scp->xsize = xsize; 129 scp->ysize = ysize; 130 scp->xoff = 0; 131 scp->yoff = 0; 132 scp->xpixel = scp->xsize*8; 133 scp->ypixel = scp->ysize*fontsize; 134 scp->font = font; 135 scp->font_size = fontsize; 136 137 /* allocate buffers */ 138 sc_alloc_scr_buffer(scp, TRUE, TRUE); 139 sc_init_emulator(scp, NULL); 140 #ifndef SC_NO_CUTPASTE 141 sc_alloc_cut_buffer(scp, FALSE); 142 #endif 143 #ifndef SC_NO_HISTORY 144 sc_alloc_history_buffer(scp, new_ysize, prev_ysize, FALSE); 145 #endif 146 crit_exit(); 147 148 if (scp == scp->sc->cur_scp) 149 set_mode(scp); 150 scp->status &= ~UNKNOWN_MODE; 151 152 if (tp == NULL) 153 return 0; 154 DPRINTF(5, ("ws_*size (%d,%d), size (%d,%d)\n", 155 tp->t_winsize.ws_col, tp->t_winsize.ws_row, scp->xsize, scp->ysize)); 156 if (tp->t_winsize.ws_col != scp->xsize 157 || tp->t_winsize.ws_row != scp->ysize) { 158 tp->t_winsize.ws_col = scp->xsize; 159 tp->t_winsize.ws_row = scp->ysize; 160 pgsignal(tp->t_pgrp, SIGWINCH, 1); 161 } 162 163 return 0; 164 } 165 166 int 167 sc_set_graphics_mode(scr_stat *scp, struct tty *tp, int mode) 168 { 169 #ifdef SC_NO_MODE_CHANGE 170 return ENODEV; 171 #else 172 video_info_t info; 173 int error; 174 175 if ((*vidsw[scp->sc->adapter]->get_info)(scp->sc->adp, mode, &info)) 176 return ENODEV; 177 178 /* stop screen saver, etc */ 179 crit_enter(); 180 if ((error = sc_clean_up(scp))) { 181 crit_exit(); 182 return error; 183 } 184 185 if (sc_render_match(scp, scp->sc->adp->va_name, V_INFO_MM_OTHER) == NULL) { 186 crit_exit(); 187 return ENODEV; 188 } 189 190 /* set up scp */ 191 scp->status |= (UNKNOWN_MODE | GRAPHICS_MODE | MOUSE_HIDDEN); 192 scp->status &= ~(PIXEL_MODE | MOUSE_VISIBLE); 193 scp->mode = mode; 194 scp->model = V_INFO_MM_OTHER; 195 /* 196 * Don't change xsize and ysize; preserve the previous vty 197 * and history buffers. 198 */ 199 scp->xoff = 0; 200 scp->yoff = 0; 201 scp->xpixel = info.vi_width; 202 scp->ypixel = info.vi_height; 203 scp->font = NULL; 204 scp->font_size = 0; 205 #ifndef SC_NO_SYSMOUSE 206 /* move the mouse cursor at the center of the screen */ 207 sc_mouse_move(scp, scp->xpixel / 2, scp->ypixel / 2); 208 #endif 209 sc_init_emulator(scp, NULL); 210 crit_exit(); 211 212 if (scp == scp->sc->cur_scp) 213 set_mode(scp); 214 /* clear_graphics();*/ 215 refresh_ega_palette(scp); 216 scp->status &= ~UNKNOWN_MODE; 217 218 if (tp == NULL) 219 return 0; 220 if (tp->t_winsize.ws_xpixel != scp->xpixel 221 || tp->t_winsize.ws_ypixel != scp->ypixel) { 222 tp->t_winsize.ws_xpixel = scp->xpixel; 223 tp->t_winsize.ws_ypixel = scp->ypixel; 224 pgsignal(tp->t_pgrp, SIGWINCH, 1); 225 } 226 227 return 0; 228 #endif /* SC_NO_MODE_CHANGE */ 229 } 230 231 int 232 sc_set_pixel_mode(scr_stat *scp, struct tty *tp, int xsize, int ysize, 233 int fontsize) 234 { 235 #ifndef SC_PIXEL_MODE 236 return ENODEV; 237 #else 238 video_info_t info; 239 u_char *font; 240 int prev_ysize; 241 int new_ysize; 242 int error; 243 244 if ((*vidsw[scp->sc->adapter]->get_info)(scp->sc->adp, scp->mode, &info)) 245 return ENODEV; /* this shouldn't happen */ 246 247 /* adjust argument values */ 248 if (fontsize <= 0) 249 fontsize = info.vi_cheight; 250 if (fontsize < 14) { 251 fontsize = 8; 252 #ifndef SC_NO_FONT_LOADING 253 if (!(scp->sc->fonts_loaded & FONT_8)) 254 return EINVAL; 255 font = scp->sc->font_8; 256 #else 257 font = NULL; 258 #endif 259 } else if (fontsize >= 16) { 260 fontsize = 16; 261 #ifndef SC_NO_FONT_LOADING 262 if (!(scp->sc->fonts_loaded & FONT_16)) 263 return EINVAL; 264 font = scp->sc->font_16; 265 #else 266 font = NULL; 267 #endif 268 } else { 269 fontsize = 14; 270 #ifndef SC_NO_FONT_LOADING 271 if (!(scp->sc->fonts_loaded & FONT_14)) 272 return EINVAL; 273 font = scp->sc->font_14; 274 #else 275 font = NULL; 276 #endif 277 } 278 if (xsize <= 0) 279 xsize = info.vi_width/8; 280 if (ysize <= 0) 281 ysize = info.vi_height/fontsize; 282 283 if ((info.vi_width < xsize*8) || (info.vi_height < ysize*fontsize)) 284 return EINVAL; 285 286 /* 287 * We currently support the following graphic modes: 288 * 289 * - 4 bpp planar modes whose memory size does not exceed 64K 290 * - 8 bbp packed pixel modes 291 * - 15, 16, 24 and 32 bpp direct modes with linear frame buffer 292 */ 293 294 if (info.vi_mem_model == V_INFO_MM_PLANAR) { 295 if (info.vi_planes != 4) 296 return ENODEV; 297 298 /* 299 * A memory size >64K requires bank switching to access the entire 300 * screen. XXX 301 */ 302 303 if (info.vi_width * info.vi_height / 8 > info.vi_window_size) 304 return ENODEV; 305 } else if (info.vi_mem_model == V_INFO_MM_PACKED) { 306 if (info.vi_depth != 8) 307 return ENODEV; 308 } else if (info.vi_mem_model == V_INFO_MM_DIRECT) { 309 if (!(info.vi_flags & V_INFO_LINEAR) && 310 (info.vi_depth != 15) && (info.vi_depth != 16) && 311 (info.vi_depth != 24) && (info.vi_depth != 32)) 312 return ENODEV; 313 } else 314 return ENODEV; 315 316 /* stop screen saver, etc */ 317 crit_enter(); 318 if ((error = sc_clean_up(scp))) { 319 crit_exit(); 320 return error; 321 } 322 323 if (sc_render_match(scp, scp->sc->adp->va_name, info.vi_mem_model) == NULL) { 324 crit_exit(); 325 return ENODEV; 326 } 327 328 #if 0 329 if (scp->tsw) 330 (*scp->tsw->te_term)(scp, scp->ts); 331 scp->tsw = NULL; 332 scp->ts = NULL; 333 #endif 334 335 /* set up scp */ 336 new_ysize = 0; 337 #ifndef SC_NO_HISTORY 338 if (scp->history != NULL) { 339 sc_hist_save(scp); 340 new_ysize = sc_vtb_rows(scp->history); 341 } 342 #endif 343 prev_ysize = scp->ysize; 344 scp->status |= (UNKNOWN_MODE | PIXEL_MODE | MOUSE_HIDDEN); 345 scp->status &= ~(GRAPHICS_MODE | MOUSE_VISIBLE); 346 scp->model = info.vi_mem_model; 347 scp->xsize = xsize; 348 scp->ysize = ysize; 349 scp->xoff = (scp->xpixel/8 - xsize)/2; 350 scp->yoff = (scp->ypixel/fontsize - ysize)/2; 351 scp->font = font; 352 scp->font_size = fontsize; 353 354 /* allocate buffers */ 355 sc_alloc_scr_buffer(scp, TRUE, TRUE); 356 sc_init_emulator(scp, NULL); 357 #ifndef SC_NO_CUTPASTE 358 sc_alloc_cut_buffer(scp, FALSE); 359 #endif 360 #ifndef SC_NO_HISTORY 361 sc_alloc_history_buffer(scp, new_ysize, prev_ysize, FALSE); 362 #endif 363 crit_exit(); 364 365 if (scp == scp->sc->cur_scp) { 366 sc_set_border(scp, scp->border); 367 sc_set_cursor_image(scp); 368 } 369 370 scp->status &= ~UNKNOWN_MODE; 371 372 if (tp == NULL) 373 return 0; 374 if (tp->t_winsize.ws_col != scp->xsize 375 || tp->t_winsize.ws_row != scp->ysize) { 376 tp->t_winsize.ws_col = scp->xsize; 377 tp->t_winsize.ws_row = scp->ysize; 378 pgsignal(tp->t_pgrp, SIGWINCH, 1); 379 } 380 381 return 0; 382 #endif /* SC_PIXEL_MODE */ 383 } 384 385 #define fb_ioctl(a, c, d) \ 386 (((a) == NULL) ? ENODEV : \ 387 (*vidsw[(a)->va_index]->ioctl)((a), (c), (caddr_t)(d))) 388 389 int 390 sc_vid_ioctl(struct tty *tp, u_long cmd, caddr_t data, int flag) 391 { 392 scr_stat *scp; 393 video_adapter_t *adp; 394 video_info_t info; 395 int error; 396 397 KKASSERT(tp->t_dev); 398 399 scp = SC_STAT(tp->t_dev); 400 if (scp == NULL) /* tp == SC_MOUSE */ 401 return ENOIOCTL; 402 adp = scp->sc->adp; 403 if (adp == NULL) /* shouldn't happen??? */ 404 return ENODEV; 405 406 switch (cmd) { 407 408 case CONS_CURRENTADP: /* get current adapter index */ 409 case FBIO_ADAPTER: 410 return fb_ioctl(adp, FBIO_ADAPTER, data); 411 412 case CONS_CURRENT: /* get current adapter type */ 413 case FBIO_ADPTYPE: 414 return fb_ioctl(adp, FBIO_ADPTYPE, data); 415 416 case CONS_ADPINFO: /* adapter information */ 417 case FBIO_ADPINFO: 418 if (((video_adapter_info_t *)data)->va_index >= 0) { 419 adp = vid_get_adapter(((video_adapter_info_t *)data)->va_index); 420 if (adp == NULL) 421 return ENODEV; 422 } 423 return fb_ioctl(adp, FBIO_ADPINFO, data); 424 425 case CONS_GET: /* get current video mode */ 426 case FBIO_GETMODE: 427 *(int *)data = scp->mode; 428 return 0; 429 430 #ifndef SC_NO_MODE_CHANGE 431 case CONS_SET: 432 case FBIO_SETMODE: /* set video mode */ 433 if (!(adp->va_flags & V_ADP_MODECHANGE)) 434 return ENODEV; 435 info.vi_mode = *(int *)data; 436 error = fb_ioctl(adp, FBIO_MODEINFO, &info); 437 if (error) 438 return error; 439 if (info.vi_flags & V_INFO_GRAPHICS) 440 return sc_set_graphics_mode(scp, tp, *(int *)data); 441 else 442 return sc_set_text_mode(scp, tp, *(int *)data, 0, 0, 0); 443 #endif /* SC_NO_MODE_CHANGE */ 444 445 case CONS_MODEINFO: /* get mode information */ 446 case FBIO_MODEINFO: 447 return fb_ioctl(adp, FBIO_MODEINFO, data); 448 449 case CONS_FINDMODE: /* find a matching video mode */ 450 case FBIO_FINDMODE: 451 return fb_ioctl(adp, FBIO_FINDMODE, data); 452 453 case CONS_SETWINORG: /* set frame buffer window origin */ 454 case FBIO_SETWINORG: 455 if (scp != scp->sc->cur_scp) 456 return ENODEV; /* XXX */ 457 return fb_ioctl(adp, FBIO_SETWINORG, data); 458 459 case FBIO_GETWINORG: /* get frame buffer window origin */ 460 if (scp != scp->sc->cur_scp) 461 return ENODEV; /* XXX */ 462 return fb_ioctl(adp, FBIO_GETWINORG, data); 463 464 case FBIO_GETDISPSTART: 465 case FBIO_SETDISPSTART: 466 case FBIO_GETLINEWIDTH: 467 case FBIO_SETLINEWIDTH: 468 if (scp != scp->sc->cur_scp) 469 return ENODEV; /* XXX */ 470 return fb_ioctl(adp, cmd, data); 471 472 case FBIO_GETPALETTE: 473 case FBIO_SETPALETTE: 474 case FBIOPUTCMAP: 475 case FBIOGETCMAP: 476 case FBIOGTYPE: 477 case FBIOGATTR: 478 case FBIOSVIDEO: 479 case FBIOGVIDEO: 480 case FBIOSCURSOR: 481 case FBIOGCURSOR: 482 case FBIOSCURPOS: 483 case FBIOGCURPOS: 484 case FBIOGCURMAX: 485 if (scp != scp->sc->cur_scp) 486 return ENODEV; /* XXX */ 487 return fb_ioctl(adp, cmd, data); 488 489 case KDSETMODE: /* set current mode of this (virtual) console */ 490 switch (*(int *)data) { 491 case KD_TEXT: /* switch to TEXT (known) mode */ 492 /* 493 * If scp->mode is of graphics modes, we don't know which 494 * text mode to switch back to... 495 */ 496 if (scp->status & GRAPHICS_MODE) 497 return EINVAL; 498 /* restore fonts & palette ! */ 499 #if 0 500 #ifndef SC_NO_FONT_LOADING 501 if (ISFONTAVAIL(adp->va_flags) 502 && !(scp->status & (GRAPHICS_MODE | PIXEL_MODE))) 503 /* 504 * FONT KLUDGE 505 * Don't load fonts for now... XXX 506 */ 507 if (scp->sc->fonts_loaded & FONT_8) 508 sc_load_font(scp, 0, 8, scp->sc->font_8, 0, 256); 509 if (scp->sc->fonts_loaded & FONT_14) 510 sc_load_font(scp, 0, 14, scp->sc->font_14, 0, 256); 511 if (scp->sc->fonts_loaded & FONT_16) 512 sc_load_font(scp, 0, 16, scp->sc->font_16, 0, 256); 513 } 514 #endif /* SC_NO_FONT_LOADING */ 515 #endif 516 517 #ifndef SC_NO_PALETTE_LOADING 518 load_palette(adp, scp->sc->palette); 519 #endif 520 521 /* move hardware cursor out of the way */ 522 (*vidsw[adp->va_index]->set_hw_cursor)(adp, -1, -1); 523 /* FALL THROUGH */ 524 525 case KD_TEXT1: /* switch to TEXT (known) mode */ 526 /* 527 * If scp->mode is of graphics modes, we don't know which 528 * text/pixel mode to switch back to... 529 */ 530 if (scp->status & GRAPHICS_MODE) 531 return EINVAL; 532 crit_enter(); 533 if ((error = sc_clean_up(scp))) { 534 crit_exit(); 535 return error; 536 } 537 scp->status |= UNKNOWN_MODE | MOUSE_HIDDEN; 538 crit_exit(); 539 /* no restore fonts & palette */ 540 if (scp == scp->sc->cur_scp) 541 set_mode(scp); 542 sc_clear_screen(scp); 543 scp->status &= ~UNKNOWN_MODE; 544 return 0; 545 546 #ifdef SC_PIXEL_MODE 547 case KD_PIXEL: /* pixel (raster) display */ 548 if (!(scp->status & (GRAPHICS_MODE | PIXEL_MODE))) 549 return EINVAL; 550 if (scp->status & GRAPHICS_MODE) 551 return sc_set_pixel_mode(scp, tp, scp->xsize, scp->ysize, 552 scp->font_size); 553 crit_enter(); 554 if ((error = sc_clean_up(scp))) { 555 crit_exit(); 556 return error; 557 } 558 scp->status |= (UNKNOWN_MODE | PIXEL_MODE | MOUSE_HIDDEN); 559 crit_exit(); 560 if (scp == scp->sc->cur_scp) { 561 set_mode(scp); 562 #ifndef SC_NO_PALETTE_LOADING 563 load_palette(adp, scp->sc->palette); 564 #endif 565 } 566 sc_clear_screen(scp); 567 scp->status &= ~UNKNOWN_MODE; 568 return 0; 569 #endif /* SC_PIXEL_MODE */ 570 571 case KD_GRAPHICS: /* switch to GRAPHICS (unknown) mode */ 572 crit_enter(); 573 if ((error = sc_clean_up(scp))) { 574 crit_exit(); 575 return error; 576 } 577 scp->status |= UNKNOWN_MODE | MOUSE_HIDDEN; 578 crit_exit(); 579 return 0; 580 581 default: 582 return EINVAL; 583 } 584 /* NOT REACHED */ 585 586 #ifdef SC_PIXEL_MODE 587 case KDRASTER: /* set pixel (raster) display mode */ 588 if (ISUNKNOWNSC(scp) || ISTEXTSC(scp)) 589 return ENODEV; 590 return sc_set_pixel_mode(scp, tp, ((int *)data)[0], ((int *)data)[1], 591 ((int *)data)[2]); 592 #endif /* SC_PIXEL_MODE */ 593 594 case KDGETMODE: /* get current mode of this (virtual) console */ 595 /* 596 * From the user program's point of view, KD_PIXEL is the same 597 * as KD_TEXT... 598 */ 599 *data = ISGRAPHSC(scp) ? KD_GRAPHICS : KD_TEXT; 600 return 0; 601 602 case KDSBORDER: /* set border color of this (virtual) console */ 603 scp->border = *data; 604 if (scp == scp->sc->cur_scp) 605 sc_set_border(scp, scp->border); 606 return 0; 607 } 608 609 return ENOIOCTL; 610 } 611 612 static LIST_HEAD(, sc_renderer) sc_rndr_list = 613 LIST_HEAD_INITIALIZER(sc_rndr_list); 614 615 int 616 sc_render_add(sc_renderer_t *rndr) 617 { 618 LIST_INSERT_HEAD(&sc_rndr_list, rndr, link); 619 return 0; 620 } 621 622 int 623 sc_render_remove(sc_renderer_t *rndr) 624 { 625 /* 626 LIST_REMOVE(rndr, link); 627 */ 628 return EBUSY; /* XXX */ 629 } 630 631 sc_rndr_sw_t * 632 sc_render_match(scr_stat *scp, char *name, int model) 633 { 634 const sc_renderer_t **list; 635 const sc_renderer_t *p; 636 637 if (!LIST_EMPTY(&sc_rndr_list)) { 638 LIST_FOREACH(p, &sc_rndr_list, link) { 639 if ((strcmp(p->name, name) == 0) && 640 (model == p->model)) { 641 scp->status &= 642 ~(VR_CURSOR_ON | VR_CURSOR_BLINK); 643 return p->rndrsw; 644 } 645 } 646 } else { 647 SET_FOREACH(list, scrndr_set) { 648 p = *list; 649 if ((strcmp(p->name, name) == 0) && 650 (model == p->model)) { 651 scp->status &= 652 ~(VR_CURSOR_ON | VR_CURSOR_BLINK); 653 return p->rndrsw; 654 } 655 } 656 } 657 658 return NULL; 659 } 660