1 /*
2 * ti omap35 display subsystem (dss)
3 *
4 * can handle 2ⁿ bits per pixel for 0 < n ≤ 4, and 12 and 24 bits.
5 * can handle 1024×768 at 60 Hz with pixel clock of 63.5 MHz
6 * 1280×800 at 59.91 Hz with pixel clock of 71 MHz
7 * 1400×1050 lcd at 50 MHz with pixel clock of 75 MHz
8 * has 256 24-bit entries in RGB palette
9 */
10 #include "u.h"
11 #include "../port/lib.h"
12 #include "mem.h"
13 #include "dat.h"
14 #include "fns.h"
15 #include "io.h"
16 #include "ureg.h"
17 #include "../port/error.h"
18
19 #define Image IMAGE
20 #include <draw.h>
21 #include <memdraw.h>
22 #include <cursor.h>
23 #include "screen.h"
24 // #include "gamma.h"
25
26 enum {
27 Tabstop = 4, /* should be 8 */
28 Scroll = 8, /* lines to scroll at one time */
29 /*
30 * screen settings for Wid and Ht, should a bit more dynamic?
31 * http://www.epanorama.net/faq/vga2rgb/calc.html
32 * used to calculate settings.
33 */
34
35 // Hbp = (248-1) << 20,
36 // Hfp = (48-1) << 8,
37 // Hsw = 112-1,
38
39 // Vbp = 38 << 20,
40 // Vfp = 1 << 8,
41 // Vsw = 3,
42
43 Tft = 0x60,
44
45 Loadmode = 2 << 1,
46 Fifosize = 0x400,
47
48 /* dispc sysconfig */
49 Midlemode = 2 << 12,
50 Sidlemode = 2 << 3,
51 EnableWakeup = 1 << 2,
52 Autoidle = 1 << 0,
53
54 /* dispc pool_freq */
55 Ipc = 1 << 14,
56 Ihs = 1 << 13,
57 Ivs = 1 << 12,
58 Acb = 0x28,
59
60 /* gfx attribs */
61 Burstsize = 2 << 6,
62 Format = 6 << 1,
63 Gfxenable = 1 << 0,
64
65 /* dispc control */
66 Gpout1 = 1 << 16,
67 Gpout0 = 1 << 15,
68 Tftdata = 3 << 8,
69 Digital = 1 << 6,
70 Lcd = 1 << 5,
71 Stntft = 1 << 3,
72 Digitalen = 1 << 1,
73 // Lcden = 1 << 0, /* unused */
74 };
75
76 typedef struct Dispcregs Dispc;
77 typedef struct Dssregs Dss;
78 typedef struct Ioregs Ioregs;
79
80 struct Ioregs { /* common registers, 68 (0x44) bytes */
81 ulong rev;
82 uchar _pad0[0x10-0x4];
83 ulong sysconf;
84 ulong sysstat;
85 ulong irqstat1;
86
87 /* Dispc only regs */
88 ulong irqen1;
89 ulong wkupen;
90 ulong _pad1;
91 ulong irqsts2;
92 ulong irqen2;
93 ulong _pad2[4];
94
95 ulong ctrl;
96 };
97
98 struct Dssregs { /* display subsys at 0x48050000 */
99 Ioregs;
100 ulong sdicrtl;
101 ulong pllcrtl;
102 uchar _pad3[0x5c-0x4c];
103 ulong sdistat;
104 };
105
106 struct Dispcregs { /* display ctlr at 0x48050400 */
107 Ioregs;
108 ulong config;
109 ulong _pad3;
110 ulong defaultcolor[2];
111 ulong transcolor[2];
112 ulong linestat;
113 ulong linenum;
114 ulong timing_h;
115 ulong timing_v;
116 ulong pol_req;
117 ulong divisor;
118 ulong alpha;
119 ulong digsize;
120 ulong lcdsize;
121
122 ulong base[2]; /* should allocate both to avoid dithering */
123 ulong pos;
124 ulong size;
125 ulong _pad4[4];
126 ulong attrib;
127 ulong fifothr;
128 ulong fifosize;
129 ulong rowinc;
130 ulong pixelinc;
131 ulong winskip;
132 ulong palette; /* gfx_table_ba */
133 uchar _pad5[0x5d4 - 0x4bc];
134
135 ulong datacycle[3];
136 uchar _pad5[0x620 - 0x5e0];
137
138 ulong cprcoefr;
139 ulong cprcoefg;
140 ulong cprcoefb;
141 ulong preload;
142 };
143
144 int drawdebug;
145 Point ZP = {0, 0};
146 Cursor arrow = {
147 { -1, -1 },
148 { 0xFF, 0xFF, 0x80, 0x01, 0x80, 0x02, 0x80, 0x0C,
149 0x80, 0x10, 0x80, 0x10, 0x80, 0x08, 0x80, 0x04,
150 0x80, 0x02, 0x80, 0x01, 0x80, 0x02, 0x8C, 0x04,
151 0x92, 0x08, 0x91, 0x10, 0xA0, 0xA0, 0xC0, 0x40,
152 },
153 { 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFC, 0x7F, 0xF0,
154 0x7F, 0xE0, 0x7F, 0xE0, 0x7F, 0xF0, 0x7F, 0xF8,
155 0x7F, 0xFC, 0x7F, 0xFE, 0x7F, 0xFC, 0x73, 0xF8,
156 0x61, 0xF0, 0x60, 0xE0, 0x40, 0x40, 0x00, 0x00,
157 },
158 };
159
160 OScreen oscreen;
161 Settings settings[] = {
162 [Res800x600] { 800, 600, 60, RGB16, 40000, 88, 40, 128, 23, 1, 5, },
163 [Res1024x768] { 1024, 768, 60, RGB16, 65000, 160, 24, 136, 29, 3, 7, },
164 [Res1280x1024] { 1280, 1024, 60, RGB16, 108000, 248, 48, 112, 38, 1, 4, },
165 [Res1400x1050] { 1400, 1050, 50, RGB16, 108000, 248, 48, 112, 38, 1, 4, }, // TODO
166 };
167 Omap3fb *framebuf;
168 Memimage *gscreen;
169
170 static Memdata xgdata;
171
172 static Memimage xgscreen =
173 {
174 { 0, 0, Wid, Ht }, /* r */
175 { 0, 0, Wid, Ht }, /* clipr */
176 Depth, /* depth */
177 3, /* nchan */
178 RGB16, /* chan */
179 nil, /* cmap */
180 &xgdata, /* data */
181 0, /* zero */
182 Wid*(Depth/BI2BY)/BY2WD, /* width in words of a single scan line */
183 0, /* layer */
184 0, /* flags */
185 };
186
187 static Memimage *conscol;
188 static Memimage *back;
189
190 static Memsubfont *memdefont;
191
192 static Lock screenlock;
193
194 static Point curpos;
195 static int h, w;
196 static int landscape = 0; /* screen orientation, default is 0: portrait */
197 static ushort *vscreen; /* virtual screen */
198 static Rectangle window;
199
200 static Dispc *dispc = (Dispc *)PHYSDISPC;
201 static Dss *dss = (Dss *)PHYSDSS;
202
203 static void omapscreenputs(char *s, int n);
204 static ulong rep(ulong, int);
205 static void screenputc(char *buf);
206 static void screenwin(void);
207
208 /*
209 * Software cursor.
210 */
211 int swvisible; /* is the cursor visible? */
212 int swenabled; /* is the cursor supposed to be on the screen? */
213 Memimage* swback; /* screen under cursor */
214 Memimage* swimg; /* cursor image */
215 Memimage* swmask; /* cursor mask */
216 Memimage* swimg1;
217 Memimage* swmask1;
218
219 Point swoffset;
220 Rectangle swrect; /* screen rectangle in swback */
221 Point swpt; /* desired cursor location */
222 Point swvispt; /* actual cursor location */
223 int swvers; /* incremented each time cursor image changes */
224 int swvisvers; /* the version on the screen */
225
226 static void
lcdoff(void)227 lcdoff(void)
228 {
229 dispc->ctrl &= ~1; /* disable the lcd */
230 coherence();
231
232 dispc->irqstat1 |= 1; /* set framedone */
233 coherence();
234
235 /* the lcd never comes ready, so don't bother with this */
236 #ifdef notdef
237 /* spin until the frame is complete, but not forever */
238 for(cnt = 50; !(dispc->irqstat1 & 1) && cnt-- > 0; )
239 delay(10);
240 #endif
241 delay(20); /* worst case for 1 frame, 50Hz */
242 }
243
244 static void
dssstart(void)245 dssstart(void)
246 {
247 /* should reset the dss system */
248 dss->sysconf |= 1;
249 coherence();
250 }
251
252 /* see spruf98i §15.6.7.4.2 */
253 static void
configdispc(void)254 configdispc(void)
255 {
256 Settings *sp;
257
258 sp = oscreen.settings;
259 dss->ctrl &= 0x78; /* choose dss clock */
260 dispc->sysconf = Midlemode | Sidlemode | EnableWakeup | Autoidle;
261 dispc->config = Loadmode;
262 coherence();
263
264 /* pll */
265 dispc->defaultcolor[0] = 0; /* set background color to black? */
266 dispc->defaultcolor[1] = 0;
267 dispc->transcolor[0] = 0; /* set transparency to full */
268 dispc->transcolor[1] = 0;
269
270 dispc->timing_h = (sp->hbp-1) << 20 | (sp->hfp-1) << 8 |
271 (sp->hsw-1);
272 dispc->timing_v = sp->vbp << 20 | sp->vfp << 8 |
273 (sp->vsw-1);
274
275 dispc->pol_req = Ipc | Ihs | Ivs | Acb;
276 dispc->divisor = 1 << 16 | HOWMANY(432000, sp->pixelclock);
277
278 dispc->lcdsize = (sp->ht - 1) << 16 | (sp->wid - 1);
279 coherence();
280
281 dispc->base[0] = PADDR(framebuf->pixel);
282 dispc->base[1] = PADDR(framebuf->pixel);
283
284 dispc->pos = 0; /* place screen in the left corner */
285 /* use the whole screen */
286 dispc->size = (sp->ht - 1) << 16 | (sp->wid - 1);
287
288 /* what mode does plan 9 use for fb? */
289 dispc->attrib = Burstsize | Format | Gfxenable;
290
291 dispc->preload = Tft;
292 dispc->fifosize = Fifosize;
293 /* 1008 is max for our Burstsize */
294 dispc->fifothr = (Fifosize - 1) << 16 | (1008 - 1);
295
296 /* 1 byte is one pixel (not true, we use 2 bytes per pixel) */
297 dispc->rowinc = 1;
298 dispc->pixelinc = 1;
299 dispc->winskip = 0; /* don't skip anything */
300 coherence();
301
302 // dispc->palette = PADDR(framebuf->palette);
303 }
304
305 static void
lcdon(int enable)306 lcdon(int enable)
307 {
308 dispc->ctrl = Gpout1 | Gpout0 | Tftdata | Digital | Lcd | Stntft |
309 Digitalen | enable;
310 coherence();
311 delay(10);
312 }
313
314 static void
lcdstop(void)315 lcdstop(void)
316 {
317 configscreengpio();
318 screenclockson();
319
320 lcdoff();
321 }
322
323 static void
lcdinit(void)324 lcdinit(void)
325 {
326 lcdstop();
327
328 dssstart();
329 configdispc();
330 }
331
332 /* Paint the image data with blue pixels */
333 void
screentest(void)334 screentest(void)
335 {
336 int i;
337
338 for (i = nelem(framebuf->pixel) - 1; i >= 0; i--)
339 framebuf->pixel[i] = 0x1f; /* blue */
340 // memset(framebuf->pixel, ~0, sizeof framebuf->pixel); /* white */
341 }
342
343 void
screenpower(int on)344 screenpower(int on)
345 {
346 blankscreen(on == 0);
347 }
348
349 /*
350 * called with drawlock locked for us, most of the time.
351 * kernel prints at inopportune times might mean we don't
352 * hold the lock, but memimagedraw is now reentrant so
353 * that should be okay: worst case we get cursor droppings.
354 */
355 void
swcursorhide(void)356 swcursorhide(void)
357 {
358 if(swvisible == 0)
359 return;
360 if(swback == nil)
361 return;
362 swvisible = 0;
363 memimagedraw(gscreen, swrect, swback, ZP, memopaque, ZP, S);
364 flushmemscreen(swrect);
365 }
366
367 void
swcursoravoid(Rectangle r)368 swcursoravoid(Rectangle r)
369 {
370 if(swvisible && rectXrect(r, swrect))
371 swcursorhide();
372 }
373
374 void
swcursordraw(void)375 swcursordraw(void)
376 {
377 if(swvisible)
378 return;
379 if(swenabled == 0)
380 return;
381 if(swback == nil || swimg1 == nil || swmask1 == nil)
382 return;
383 // assert(!canqlock(&drawlock)); // assertion fails on omap
384 swvispt = swpt;
385 swvisvers = swvers;
386 swrect = rectaddpt(Rect(0,0,16,16), swvispt);
387 memimagedraw(swback, swback->r, gscreen, swpt, memopaque, ZP, S);
388 memimagedraw(gscreen, swrect, swimg1, ZP, swmask1, ZP, SoverD);
389 flushmemscreen(swrect);
390 swvisible = 1;
391 }
392
393 int
cursoron(int dolock)394 cursoron(int dolock)
395 {
396 if (dolock)
397 lock(&oscreen);
398 cursoroff(0);
399 swcursordraw();
400 if (dolock)
401 unlock(&oscreen);
402 return 0;
403 }
404
405 void
cursoroff(int dolock)406 cursoroff(int dolock)
407 {
408 if (dolock)
409 lock(&oscreen);
410 swcursorhide();
411 if (dolock)
412 unlock(&oscreen);
413 }
414
415 void
swload(Cursor * curs)416 swload(Cursor *curs)
417 {
418 uchar *ip, *mp;
419 int i, j, set, clr;
420
421 if(!swimg || !swmask || !swimg1 || !swmask1)
422 return;
423 /*
424 * Build cursor image and mask.
425 * Image is just the usual cursor image
426 * but mask is a transparent alpha mask.
427 *
428 * The 16x16x8 memimages do not have
429 * padding at the end of their scan lines.
430 */
431 ip = byteaddr(swimg, ZP);
432 mp = byteaddr(swmask, ZP);
433 for(i=0; i<32; i++){
434 set = curs->set[i];
435 clr = curs->clr[i];
436 for(j=0x80; j; j>>=1){
437 *ip++ = set&j ? 0x00 : 0xFF;
438 *mp++ = (clr|set)&j ? 0xFF : 0x00;
439 }
440 }
441 swoffset = curs->offset;
442 swvers++;
443 memimagedraw(swimg1, swimg1->r, swimg, ZP, memopaque, ZP, S);
444 memimagedraw(swmask1, swmask1->r, swmask, ZP, memopaque, ZP, S);
445 }
446
447 /* called from devmouse */
448 void
setcursor(Cursor * curs)449 setcursor(Cursor* curs)
450 {
451 cursoroff(1);
452 oscreen.Cursor = *curs;
453 swload(curs);
454 cursoron(1);
455 }
456
457 int
swmove(Point p)458 swmove(Point p)
459 {
460 swpt = addpt(p, swoffset);
461 return 0;
462 }
463
464 void
swcursorclock(void)465 swcursorclock(void)
466 {
467 int x;
468
469 if(!swenabled)
470 return;
471 swmove(mousexy());
472 if(swvisible && eqpt(swpt, swvispt) && swvers==swvisvers)
473 return;
474
475 x = splhi();
476 if(swenabled)
477 if(!swvisible || !eqpt(swpt, swvispt) || swvers!=swvisvers)
478 if(canqlock(&drawlock)){
479 swcursorhide();
480 swcursordraw();
481 qunlock(&drawlock);
482 }
483 splx(x);
484 }
485
486 void
swcursorinit(void)487 swcursorinit(void)
488 {
489 static int init;
490
491 if(!init){
492 init = 1;
493 addclock0link(swcursorclock, 10);
494 }
495 if(swback){
496 freememimage(swback);
497 freememimage(swmask);
498 freememimage(swmask1);
499 freememimage(swimg);
500 freememimage(swimg1);
501 }
502
503 swback = allocmemimage(Rect(0,0,32,32), gscreen->chan);
504 swmask = allocmemimage(Rect(0,0,16,16), GREY8);
505 swmask1 = allocmemimage(Rect(0,0,16,16), GREY1);
506 swimg = allocmemimage(Rect(0,0,16,16), GREY8);
507 swimg1 = allocmemimage(Rect(0,0,16,16), GREY1);
508 if(swback==nil || swmask==nil || swmask1==nil || swimg==nil || swimg1 == nil){
509 print("software cursor: allocmemimage fails\n");
510 return;
511 }
512
513 memfillcolor(swmask, DOpaque);
514 memfillcolor(swmask1, DOpaque);
515 memfillcolor(swimg, DBlack);
516 memfillcolor(swimg1, DBlack);
517 }
518
519 /* called from main and possibly later from devdss to change resolution */
520 void
screeninit(void)521 screeninit(void)
522 {
523 static int first = 1;
524
525 if (first) {
526 iprint("screeninit...");
527 oscreen.settings = &settings[Res1280x1024];
528
529 lcdstop();
530 if (framebuf)
531 free(framebuf);
532 /* mode is 16*32 = 512 */
533 framebuf = xspanalloc(sizeof *framebuf, 16*32, 0);
534 }
535
536 lcdinit();
537 lcdon(1);
538 if (first) {
539 memimageinit();
540 memdefont = getmemdefont();
541 screentest();
542 }
543
544 xgdata.ref = 1;
545 xgdata.bdata = (uchar *)framebuf->pixel;
546
547 gscreen = &xgscreen;
548 gscreen->r = Rect(0, 0, Wid, Ht);
549 gscreen->clipr = gscreen->r;
550 /* width, in words, of a single scan line */
551 gscreen->width = Wid * (Depth / BI2BY) / BY2WD;
552 flushmemscreen(gscreen->r);
553
554 blanktime = 3; /* minutes */
555
556 if (first) {
557 iprint("on: blue for 3 seconds...");
558 delay(3*1000);
559 iprint("\n");
560
561 screenwin(); /* draw border & top orange bar */
562 screenputs = omapscreenputs;
563 iprint("screen: frame buffer at %#p for %dx%d\n",
564 framebuf, oscreen.settings->wid, oscreen.settings->ht);
565
566 swenabled = 1;
567 swcursorinit(); /* needs gscreen set */
568 setcursor(&arrow);
569
570 first = 0;
571 }
572 }
573
574 /* flushmemscreen should change buffer? */
575 void
flushmemscreen(Rectangle r)576 flushmemscreen(Rectangle r)
577 {
578 ulong start, end;
579
580 if (r.min.x < 0)
581 r.min.x = 0;
582 if (r.max.x > Wid)
583 r.max.x = Wid;
584 if (r.min.y < 0)
585 r.min.y = 0;
586 if (r.max.y > Ht)
587 r.max.y = Ht;
588 if (rectclip(&r, gscreen->r) == 0)
589 return;
590 start = (ulong)&framebuf->pixel[r.min.y*Wid + r.min.x];
591 end = (ulong)&framebuf->pixel[(r.max.y - 1)*Wid + r.max.x -1];
592 cachedwbse((ulong *)start, end - start);
593 }
594
595 /*
596 * export screen to devdraw
597 */
598 uchar*
attachscreen(Rectangle * r,ulong * chan,int * d,int * width,int * softscreen)599 attachscreen(Rectangle *r, ulong *chan, int *d, int *width, int *softscreen)
600 {
601 *r = gscreen->r;
602 *d = gscreen->depth;
603 *chan = gscreen->chan;
604 *width = gscreen->width;
605 *softscreen = (landscape == 0);
606 return (uchar *)gscreen->data->bdata;
607 }
608
609 void
getcolor(ulong p,ulong * pr,ulong * pg,ulong * pb)610 getcolor(ulong p, ulong *pr, ulong *pg, ulong *pb)
611 {
612 USED(p, pr, pg, pb);
613 }
614
615 int
setcolor(ulong p,ulong r,ulong g,ulong b)616 setcolor(ulong p, ulong r, ulong g, ulong b)
617 {
618 USED(p, r, g, b);
619 return 0;
620 }
621
622 void
blankscreen(int blank)623 blankscreen(int blank)
624 {
625 if (blank)
626 lcdon(0);
627 else {
628 lcdinit();
629 lcdon(1);
630 }
631 }
632
633 static void
omapscreenputs(char * s,int n)634 omapscreenputs(char *s, int n)
635 {
636 int i;
637 Rune r;
638 char buf[4];
639
640 if (!islo()) {
641 /* don't deadlock trying to print in interrupt */
642 if (!canlock(&screenlock))
643 return; /* discard s */
644 } else
645 lock(&screenlock);
646
647 while (n > 0) {
648 i = chartorune(&r, s);
649 if (i == 0) {
650 s++;
651 --n;
652 continue;
653 }
654 memmove(buf, s, i);
655 buf[i] = 0;
656 n -= i;
657 s += i;
658 screenputc(buf);
659 }
660 unlock(&screenlock);
661 }
662
663 static void
screenwin(void)664 screenwin(void)
665 {
666 char *greet;
667 Memimage *orange;
668 Point p, q;
669 Rectangle r;
670
671 memsetchan(gscreen, RGB16);
672
673 back = memwhite;
674 conscol = memblack;
675
676 orange = allocmemimage(Rect(0, 0, 1, 1), RGB16);
677 orange->flags |= Frepl;
678 orange->clipr = gscreen->r;
679 orange->data->bdata[0] = 0x40; /* magic: colour? */
680 orange->data->bdata[1] = 0xfd; /* magic: colour? */
681
682 w = memdefont->info[' '].width;
683 h = memdefont->height;
684
685 r = insetrect(gscreen->r, 4);
686
687 memimagedraw(gscreen, r, memblack, ZP, memopaque, ZP, S);
688 window = insetrect(r, 4);
689 memimagedraw(gscreen, window, memwhite, ZP, memopaque, ZP, S);
690
691 memimagedraw(gscreen, Rect(window.min.x, window.min.y,
692 window.max.x, window.min.y + h + 5 + 6), orange, ZP, nil, ZP, S);
693 freememimage(orange);
694 window = insetrect(window, 5);
695
696 greet = " Plan 9 Console ";
697 p = addpt(window.min, Pt(10, 0));
698 q = memsubfontwidth(memdefont, greet);
699 memimagestring(gscreen, p, conscol, ZP, memdefont, greet);
700 flushmemscreen(r);
701 window.min.y += h + 6;
702 curpos = window.min;
703 window.max.y = window.min.y + ((window.max.y - window.min.y) / h) * h;
704 }
705
706 static void
scroll(void)707 scroll(void)
708 {
709 int o;
710 Point p;
711 Rectangle r;
712
713 /* move window contents up Scroll text lines */
714 o = Scroll * h;
715 r = Rpt(window.min, Pt(window.max.x, window.max.y - o));
716 p = Pt(window.min.x, window.min.y + o);
717 memimagedraw(gscreen, r, gscreen, p, nil, p, S);
718 flushmemscreen(r);
719
720 /* clear the bottom Scroll text lines */
721 r = Rpt(Pt(window.min.x, window.max.y - o), window.max);
722 memimagedraw(gscreen, r, back, ZP, nil, ZP, S);
723 flushmemscreen(r);
724
725 curpos.y -= o;
726 }
727
728 static void
screenputc(char * buf)729 screenputc(char *buf)
730 {
731 int w;
732 uint pos;
733 Point p;
734 Rectangle r;
735 static int *xp;
736 static int xbuf[256];
737
738 if (xp < xbuf || xp >= &xbuf[sizeof(xbuf)])
739 xp = xbuf;
740
741 switch (buf[0]) {
742 case '\n':
743 if (curpos.y + h >= window.max.y)
744 scroll();
745 curpos.y += h;
746 screenputc("\r");
747 break;
748 case '\r':
749 xp = xbuf;
750 curpos.x = window.min.x;
751 break;
752 case '\t':
753 p = memsubfontwidth(memdefont, " ");
754 w = p.x;
755 if (curpos.x >= window.max.x - Tabstop * w)
756 screenputc("\n");
757
758 pos = (curpos.x - window.min.x) / w;
759 pos = Tabstop - pos % Tabstop;
760 *xp++ = curpos.x;
761 r = Rect(curpos.x, curpos.y, curpos.x + pos * w, curpos.y + h);
762 memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S);
763 flushmemscreen(r);
764 curpos.x += pos * w;
765 break;
766 case '\b':
767 if (xp <= xbuf)
768 break;
769 xp--;
770 r = Rect(*xp, curpos.y, curpos.x, curpos.y + h);
771 memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S);
772 flushmemscreen(r);
773 curpos.x = *xp;
774 break;
775 case '\0':
776 break;
777 default:
778 p = memsubfontwidth(memdefont, buf);
779 w = p.x;
780
781 if (curpos.x >= window.max.x - w)
782 screenputc("\n");
783
784 *xp++ = curpos.x;
785 r = Rect(curpos.x, curpos.y, curpos.x + w, curpos.y + h);
786 memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S);
787 memimagestring(gscreen, curpos, conscol, ZP, memdefont, buf);
788 flushmemscreen(r);
789 curpos.x += w;
790 }
791 }
792