1 #define Unknown WUnknown
2 #define Colormap WColormap
3 #define Cursor WCursor
4 #define Display WDisplay
5 #define Drawable WDrawable
6 #define Font WFont
7 #define GC WGC
8 #define Point WPoint
9 #define Rectangle WRectangle
10 #define Screen WScreen
11 #define Visual WVisual
12 #define Window WWindow
13
14 #include <windows.h>
15
16 #undef Colormap
17 #undef Cursor
18 #undef Display
19 #undef XDrawable
20 #undef Font
21 #undef GC
22 #undef Point
23 #undef Rectangle
24 #undef Screen
25 #undef Visual
26 #undef Window
27 #undef Unknown
28
29 #include "dat.h"
30 #include "fns.h"
31 #include "error.h"
32 #include <draw.h>
33 #include "keyboard.h"
34 #include "cursor.h"
35 #include "r16.h"
36
37 extern ulong displaychan;
38
39 extern int bytesperline(Rectangle, int);
40 extern int main(int argc, char **argv);
41 static void dprint(char*, ...);
42 static DWORD WINAPI winproc(LPVOID);
43
44 static HINSTANCE inst;
45 static HINSTANCE previnst;
46 static int cmdshow;
47 static HWND window;
48 static HDC screen;
49 static HPALETTE palette;
50 static int maxxsize;
51 static int maxysize;
52 static int attached;
53 static int isunicode = 1;
54 static HCURSOR hcursor;
55
56 char *argv0 = "inferno";
57 static ulong *data;
58
59 extern DWORD PlatformId;
60 char* gkscanid = "emu_win32vk";
61
62 int WINAPI
WinMain(HINSTANCE winst,HINSTANCE wprevinst,LPSTR cmdline,int wcmdshow)63 WinMain(HINSTANCE winst, HINSTANCE wprevinst, LPSTR cmdline, int wcmdshow)
64 {
65 inst = winst;
66 previnst = wprevinst;
67 cmdshow = wcmdshow;
68
69 /* cmdline passed into WinMain does not contain name of executable.
70 * The globals __argc and __argv to include this info - like UNIX
71 */
72 main(__argc, __argv);
73 return 0;
74 }
75
76 static void
dprint(char * fmt,...)77 dprint(char *fmt, ...)
78 {
79 va_list arg;
80 char buf[128];
81
82 va_start(arg, fmt);
83 vseprint(buf, buf+sizeof(buf), fmt, (LPSTR)arg);
84 va_end(arg);
85 OutputDebugString("inferno: ");
86 OutputDebugString(buf);
87 }
88
89 static void
graphicscmap(PALETTEENTRY * pal)90 graphicscmap(PALETTEENTRY *pal)
91 {
92 int r, g, b, cr, cg, cb, v, p;
93 int num, den;
94 int i, j;
95 for(r=0,i=0;r!=4;r++) for(v=0;v!=4;v++,i+=16){
96 for(g=0,j=v-r;g!=4;g++) for(b=0;b!=4;b++,j++){
97 den=r;
98 if(g>den) den=g;
99 if(b>den) den=b;
100 if(den==0) /* divide check -- pick grey shades */
101 cr=cg=cb=v*17;
102 else{
103 num=17*(4*den+v);
104 cr=r*num/den;
105 cg=g*num/den;
106 cb=b*num/den;
107 }
108 p = i+(j&15);
109 pal[p].peRed = cr*0x01010101;
110 pal[p].peGreen = cg*0x01010101;
111 pal[p].peBlue = cb*0x01010101;
112 pal[p].peFlags = 0;
113 }
114 }
115 }
116
117 static void
graphicsgmap(PALETTEENTRY * pal,int d)118 graphicsgmap(PALETTEENTRY *pal, int d)
119 {
120 int i, j, s, m, p;
121
122 s = 8-d;
123 m = 1;
124 while(--d >= 0)
125 m *= 2;
126 m = 255/(m-1);
127 for(i=0; i < 256; i++){
128 j = (i>>s)*m;
129 p = 255-i;
130 pal[p].peRed = pal[p].peGreen = pal[p].peBlue = (255-j)*0x01010101;
131 pal[p].peFlags = 0;
132 }
133 }
134
135 static ulong
autochan(void)136 autochan(void)
137 {
138 HDC dc;
139 int bpp;
140
141 dc = GetDC(NULL);
142 if (dc == NULL)
143 return CMAP8;
144
145 bpp = GetDeviceCaps(dc, BITSPIXEL);
146 if (bpp < 15)
147 return CMAP8;
148 if (bpp < 24)
149 return RGB15;
150 if (bpp < 32)
151 return RGB24;
152 return XRGB32;
153 }
154
155 uchar*
attachscreen(Rectangle * r,ulong * chan,int * d,int * width,int * softscreen)156 attachscreen(Rectangle *r, ulong *chan, int *d, int *width, int *softscreen)
157 {
158 int i, k;
159 ulong c;
160 DWORD h;
161 RECT bs;
162 RGBQUAD *rgb;
163 HBITMAP bits;
164 BITMAPINFO *bmi;
165 LOGPALETTE *logpal;
166 PALETTEENTRY *pal;
167 int bsh, bsw, sx, sy;
168
169 if(attached)
170 goto Return;
171
172 /* Compute bodersizes */
173 memset(&bs, 0, sizeof(bs));
174 AdjustWindowRect(&bs, WS_OVERLAPPEDWINDOW, 0);
175 bsw = bs.right - bs.left;
176 bsh = bs.bottom - bs.top;
177 sx = GetSystemMetrics(SM_CXFULLSCREEN) - bsw;
178 Xsize -= Xsize % 4; /* Round down */
179 if(Xsize > sx)
180 Xsize = sx;
181 sy = GetSystemMetrics(SM_CYFULLSCREEN) - bsh + 20;
182 if(Ysize > sy)
183 Ysize = sy;
184
185 logpal = malloc(sizeof(LOGPALETTE) + 256*sizeof(PALETTEENTRY));
186 if(logpal == nil)
187 return nil;
188 logpal->palVersion = 0x300;
189 logpal->palNumEntries = 256;
190 pal = logpal->palPalEntry;
191
192 c = displaychan;
193 if(c == 0)
194 c = autochan();
195 k = 8;
196 if(TYPE(c) == CGrey){
197 graphicsgmap(pal, NBITS(c));
198 c = GREY8;
199 }else{
200 if(c == RGB15)
201 k = 16;
202 else if(c == RGB24)
203 k = 24;
204 else if(c == XRGB32)
205 k = 32;
206 else
207 c = CMAP8;
208 graphicscmap(pal);
209 }
210
211 palette = CreatePalette(logpal);
212
213 if(k == 8)
214 bmi = malloc(sizeof(BITMAPINFOHEADER) + 256*sizeof(RGBQUAD));
215 else
216 bmi = malloc(sizeof(BITMAPINFOHEADER));
217 if(bmi == nil)
218 return nil;
219 bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
220 bmi->bmiHeader.biWidth = Xsize;
221 bmi->bmiHeader.biHeight = -Ysize; /* - => origin upper left */
222 bmi->bmiHeader.biPlanes = 1; /* always 1 */
223 bmi->bmiHeader.biBitCount = k;
224 bmi->bmiHeader.biCompression = BI_RGB;
225 bmi->bmiHeader.biSizeImage = 0; /* Xsize*Ysize*(k/8) */
226 bmi->bmiHeader.biXPelsPerMeter = 0;
227 bmi->bmiHeader.biYPelsPerMeter = 0;
228 bmi->bmiHeader.biClrUsed = 0;
229 bmi->bmiHeader.biClrImportant = 0; /* number of important colors: 0 means all */
230
231 if(k == 8){
232 rgb = bmi->bmiColors;
233 for(i = 0; i < 256; i++){
234 rgb[i].rgbRed = pal[i].peRed;
235 rgb[i].rgbGreen = pal[i].peGreen;
236 rgb[i].rgbBlue = pal[i].peBlue;
237 }
238 }
239
240 screen = CreateCompatibleDC(NULL);
241 if(screen == nil){
242 fprint(2, "screen dc nil\n");
243 return nil;
244 }
245
246 if(SelectPalette(screen, palette, 1) == nil){
247 fprint(2, "select pallete failed\n");
248 }
249 i = RealizePalette(screen);
250 GdiFlush();
251 bits = CreateDIBSection(screen, bmi, DIB_RGB_COLORS, &data, nil, 0);
252 if(bits == nil){
253 fprint(2, "CreateDIBSection failed\n");
254 return nil;
255 }
256
257 SelectObject(screen, bits);
258 GdiFlush();
259 CreateThread(0, 16384, winproc, nil, 0, &h);
260 attached = 1;
261
262 Return:
263 r->min.x = 0;
264 r->min.y = 0;
265 r->max.x = Xsize;
266 r->max.y = Ysize;
267 displaychan = c;
268 *chan = c;
269 *d = k;
270 *width = (Xsize/4)*(k/8);
271 *softscreen = 1;
272 return (uchar*)data;
273 }
274
275 void
flushmemscreen(Rectangle r)276 flushmemscreen(Rectangle r)
277 {
278 RECT wr;
279
280 if(r.max.x<=r.min.x || r.max.y<=r.min.y)
281 return;
282 wr.left = r.min.x;
283 wr.top = r.min.y;
284 wr.right = r.max.x;
285 wr.bottom = r.max.y;
286 InvalidateRect(window, &wr, 0);
287 }
288
289 static void
scancode(WPARAM wparam,LPARAM lparam,int keyup)290 scancode(WPARAM wparam, LPARAM lparam, int keyup)
291 {
292 uchar buf[2];
293
294 if(!(lparam & (1<<30))) { /* don't auto-repeat chars */
295 buf[0] = wparam;
296 buf[1] = wparam >> 8;
297 if (keyup)
298 buf[1] |= 0x80;
299 qproduce(gkscanq, buf, sizeof buf);
300 }
301 }
302
303 LRESULT CALLBACK
WindowProc(HWND hwnd,UINT msg,WPARAM wparam,LPARAM lparam)304 WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
305 {
306 PAINTSTRUCT paint;
307 HDC hdc;
308 LPMINMAXINFO mmi;
309 LONG x, y, w, h, b;
310 HCURSOR dcurs;
311 POINT m;
312
313 switch(msg) {
314 case WM_SETCURSOR:
315 /* User set */
316 if(hcursor != NULL) {
317 SetCursor(hcursor);
318 break;
319 }
320 /* Pick the default */
321 dcurs = LoadCursor(NULL, IDC_ARROW);
322 SetCursor(dcurs);
323 break;
324 case WM_MOUSEWHEEL:
325 if((int)wparam>0)
326 b = 8;
327 else
328 b = 16;
329 m.x = LOWORD(lparam);
330 m.y = HIWORD(lparam);
331 ScreenToClient(hwnd, &m);
332 goto mok;
333 case WM_LBUTTONDBLCLK:
334 b = (1<<8) | 1;
335 goto process;
336 case WM_MBUTTONDBLCLK:
337 b = (1<<8) | 2;
338 goto process;
339 case WM_RBUTTONDBLCLK:
340 b = (1<<8) | 4;
341 goto process;
342 case WM_MOUSEMOVE:
343 case WM_LBUTTONUP:
344 case WM_MBUTTONUP:
345 case WM_RBUTTONUP:
346 case WM_LBUTTONDOWN:
347 case WM_MBUTTONDOWN:
348 case WM_RBUTTONDOWN:
349 b = 0;
350 process:
351 m.x = LOWORD(lparam);
352 m.y = HIWORD(lparam);
353 mok:
354 if(wparam & MK_LBUTTON)
355 b |= 1;
356 if(wparam & MK_MBUTTON)
357 b |= 2;
358 if(wparam & MK_RBUTTON) {
359 if(wparam & MK_CONTROL)
360 b |= 2; //simulate middle button
361 else
362 b |= 4; //right button
363 }
364 mousetrack(b, m.x, m.y, 0);
365 break;
366 case WM_SYSKEYDOWN:
367 if(gkscanq)
368 scancode(wparam, lparam, 0);
369 break;
370 case WM_SYSKEYUP:
371 if(gkscanq)
372 scancode(wparam, lparam, 1);
373 else if(wparam == VK_MENU)
374 gkbdputc(gkbdq, Latin);
375 break;
376 case WM_KEYDOWN:
377 if(gkscanq) {
378 scancode(wparam, lparam, 0);
379 break;
380 }
381 switch(wparam) {
382 default:
383 return 0;
384 case VK_HOME:
385 wparam = Home;
386 break;
387 case VK_END:
388 wparam = End;
389 break;
390 case VK_UP:
391 wparam = Up;
392 break;
393 case VK_DOWN:
394 wparam = Down;
395 break;
396 case VK_LEFT:
397 wparam = Left;
398 break;
399 case VK_RIGHT:
400 wparam = Right;
401 break;
402 case VK_PRIOR: /* VK_PAGE_UP */
403 wparam = Pgup;
404 break;
405 case VK_NEXT: /* VK_PAGE_DOWN */
406 wparam = Pgdown;
407 break;
408 case VK_PRINT:
409 wparam = Print;
410 break;
411 case VK_SCROLL:
412 wparam = Scroll;
413 break;
414 case VK_PAUSE:
415 wparam = Pause;
416 break;
417 case VK_INSERT:
418 wparam = Ins;
419 break;
420 case VK_DELETE:
421 wparam = Del;
422 break;
423 /*
424 case VK_TAB:
425 if(GetKeyState(VK_SHIFT)<0)
426 wparam = BackTab;
427 else
428 wparam = '\t';
429 break;
430 */
431 }
432 gkbdputc(gkbdq, wparam);
433 break;
434 case WM_KEYUP:
435 if(gkscanq)
436 scancode(wparam, lparam, 1);
437 break;
438 case WM_CHAR:
439 if(gkscanq)
440 break;
441 switch(wparam) {
442 case '\n':
443 wparam = '\r';
444 break;
445 case '\r':
446 wparam = '\n';
447 break;
448 case '\t':
449 if(GetKeyState(VK_SHIFT)<0)
450 wparam = BackTab;
451 else
452 wparam = '\t';
453 break;
454 }
455 if(lparam & KF_ALTDOWN)
456 wparam = APP | (wparam & 0xFF);
457 gkbdputc(gkbdq, wparam);
458 break;
459 case WM_CLOSE:
460 DestroyWindow(hwnd);
461 break;
462 case WM_DESTROY:
463 PostQuitMessage(0);
464 cleanexit(0);
465 break;
466 case WM_PALETTECHANGED:
467 if((HWND)wparam == hwnd)
468 break;
469 /* fall through */
470 case WM_QUERYNEWPALETTE:
471 hdc = GetDC(hwnd);
472 SelectPalette(hdc, palette, 0);
473 if(RealizePalette(hdc) != 0)
474 InvalidateRect(hwnd, nil, 0);
475 ReleaseDC(hwnd, hdc);
476 break;
477 case WM_PAINT:
478 hdc = BeginPaint(hwnd, &paint);
479 SelectPalette(hdc, palette, 0);
480 RealizePalette(hdc);
481 x = paint.rcPaint.left;
482 y = paint.rcPaint.top;
483 w = paint.rcPaint.right - x;
484 h = paint.rcPaint.bottom - y;
485 BitBlt(hdc, x, y, w, h, screen, x, y, SRCCOPY);
486 EndPaint(hwnd, &paint);
487 break;
488 case WM_GETMINMAXINFO:
489 mmi = (LPMINMAXINFO)lparam;
490 mmi->ptMaxSize.x = maxxsize;
491 mmi->ptMaxSize.y = maxysize;
492 mmi->ptMaxTrackSize.x = maxxsize;
493 mmi->ptMaxTrackSize.y = maxysize;
494 break;
495 case WM_SYSCHAR:
496 case WM_COMMAND:
497 case WM_CREATE:
498 case WM_SETFOCUS:
499 case WM_DEVMODECHANGE:
500 case WM_WININICHANGE:
501 case WM_INITMENU:
502 default:
503 if(isunicode)
504 return DefWindowProcW(hwnd, msg, wparam, lparam);
505 return DefWindowProcA(hwnd, msg, wparam, lparam);
506 }
507 return 0;
508 }
509
510 static DWORD WINAPI
winproc(LPVOID x)511 winproc(LPVOID x)
512 {
513 MSG msg;
514 RECT size;
515 WNDCLASSW wc;
516 WNDCLASSA wca;
517 DWORD ws;
518
519 if(!previnst){
520 wc.style = CS_DBLCLKS;
521 wc.lpfnWndProc = WindowProc;
522 wc.cbClsExtra = 0;
523 wc.cbWndExtra = 0;
524 wc.hInstance = inst;
525 wc.hIcon = LoadIcon(inst, MAKEINTRESOURCE(100));
526 wc.hCursor = NULL;
527 wc.hbrBackground = GetStockObject(WHITE_BRUSH);
528
529 wc.lpszMenuName = 0;
530 wc.lpszClassName = L"inferno";
531
532 if(RegisterClassW(&wc) == 0){
533 wca.style = wc.style;
534 wca.lpfnWndProc = wc.lpfnWndProc;
535 wca.cbClsExtra = wc.cbClsExtra;
536 wca.cbWndExtra = wc.cbWndExtra;
537 wca.hInstance = wc.hInstance;
538 wca.hIcon = wc.hIcon;
539 wca.hCursor = wc.hCursor;
540 wca.hbrBackground = wc.hbrBackground;
541
542 wca.lpszMenuName = 0;
543 wca.lpszClassName = "inferno";
544 isunicode = 0;
545
546 RegisterClassA(&wca);
547 }
548 }
549
550 size.left = 0;
551 size.top = 0;
552 size.right = Xsize;
553 size.bottom = Ysize;
554
555 ws = WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX;
556
557 if(AdjustWindowRect(&size, ws, 0)) {
558 maxxsize = size.right - size.left;
559 maxysize = size.bottom - size.top;
560 }else{
561 maxxsize = Xsize + 40;
562 maxysize = Ysize + 40;
563 }
564
565 if(isunicode) {
566 window = CreateWindowExW(
567 0, /* extended style */
568 L"inferno", /* class */
569 L"Inferno", /* caption */
570 ws, /* style */
571 CW_USEDEFAULT, /* init. x pos */
572 CW_USEDEFAULT, /* init. y pos */
573 maxxsize, /* init. x size */
574 maxysize, /* init. y size */
575 NULL, /* parent window (actually owner window for overlapped) */
576 NULL, /* menu handle */
577 inst, /* program handle */
578 NULL /* create parms */
579 );
580 }else{
581 window = CreateWindowExA(
582 0, /* extended style */
583 "inferno", /* class */
584 "Inferno", /* caption */
585 ws, /* style */
586 CW_USEDEFAULT, /* init. x pos */
587 CW_USEDEFAULT, /* init. y pos */
588 maxxsize, /* init. x size */
589 maxysize, /* init. y size */
590 NULL, /* parent window (actually owner window for overlapped) */
591 NULL, /* menu handle */
592 inst, /* program handle */
593 NULL /* create parms */
594 );
595 }
596
597 if(window == nil){
598 fprint(2, "can't make window\n");
599 ExitThread(0);
600 }
601
602 SetForegroundWindow(window);
603 ShowWindow(window, cmdshow);
604 UpdateWindow(window);
605 // CloseWindow(window);
606
607 if(isunicode) {
608 while(GetMessageW(&msg, NULL, 0, 0)) {
609 TranslateMessage(&msg);
610 DispatchMessageW(&msg);
611 }
612 }else{
613 while(GetMessageA(&msg, NULL, 0, 0)) {
614 TranslateMessage(&msg);
615 DispatchMessageA(&msg);
616 }
617 }
618 attached = 0;
619 ExitThread(msg.wParam);
620 return 0;
621 }
622
623 void
setpointer(int x,int y)624 setpointer(int x, int y)
625 {
626 POINT pt;
627
628 pt.x = x; pt.y = y;
629 ClientToScreen(window, &pt);
630 SetCursorPos(pt.x, pt.y);
631 }
632
633 void
drawcursor(Drawcursor * c)634 drawcursor(Drawcursor* c)
635 {
636 HCURSOR nh, oh;
637 Rectangle ir;
638 int i, h, j, bpl, ch, cw;
639 uchar *bs, *bc, *and, *xor, *cand, *cxor;
640
641 /* Set the default system cursor */
642 if(c->data == nil) {
643 oh = hcursor;
644 hcursor = NULL;
645 if(oh != NULL) {
646 SendMessage(window, WM_SETCURSOR, (int)window, 0);
647 DestroyCursor(oh);
648 }
649 return;
650 }
651
652 ir.min.x = c->minx;
653 ir.min.y = c->miny;
654 ir.max.x = c->maxx;
655 ir.max.y = c->maxy;
656 bpl = bytesperline(ir, 1);
657
658 h = (c->maxy-c->miny)/2;
659
660 ch = GetSystemMetrics(SM_CYCURSOR);
661 cw = (GetSystemMetrics(SM_CXCURSOR)+7)/8;
662
663 i = ch*cw;
664 and = malloc(2*i);
665 if(and == nil)
666 return;
667 xor = and + i;
668 memset(and, 0xff, i);
669 memset(xor, 0, i);
670
671 cand = and;
672 cxor = xor;
673 bc = c->data;
674 bs = c->data + h*bpl;
675
676 for(i = 0; i < ch && i < h; i++) {
677 for(j = 0; j < cw && j < bpl; j++) {
678 cand[j] = ~(bs[j] | bc[j]);
679 cxor[j] = ~bs[j] & bc[j];
680 }
681 cand += cw;
682 cxor += cw;
683 bs += bpl;
684 bc += bpl;
685 }
686 nh = CreateCursor(inst, -c->hotx, -c->hoty, 8*cw, ch, and, xor);
687 if(nh != NULL) {
688 oh = hcursor;
689 hcursor = nh;
690 SendMessage(window, WM_SETCURSOR, (int)window, 0);
691 if(oh != NULL)
692 DestroyCursor(oh);
693 }else{
694 print("CreateCursor error %d\n", GetLastError());
695 print("CXCURSOR=%d\n", GetSystemMetrics(SM_CXCURSOR));
696 print("CYCURSOR=%d\n", GetSystemMetrics(SM_CYCURSOR));
697 }
698 free(and);
699 }
700
701 /*
702 * thanks to drawterm for these
703 */
704
705 static char*
clipreadunicode(HANDLE h)706 clipreadunicode(HANDLE h)
707 {
708 Rune16 *p;
709 int n;
710 char *q;
711
712 p = GlobalLock(h);
713 n = rune16nlen(p, runes16len(p)+1);
714 q = malloc(n);
715 if(q != nil)
716 runes16toutf(q, p, n);
717 GlobalUnlock(h);
718
719 if(q == nil)
720 error(Enovmem);
721 return q;
722 }
723
724 static char *
clipreadutf(HANDLE h)725 clipreadutf(HANDLE h)
726 {
727 uchar *p;
728
729 p = GlobalLock(h);
730 p = strdup(p);
731 GlobalUnlock(h);
732
733 if(p == nil)
734 error(Enovmem);
735 return p;
736 }
737
738 char*
clipread(void)739 clipread(void)
740 {
741 HANDLE h;
742 char *p;
743
744 if(!OpenClipboard(window))
745 return strdup("");
746
747 if((h = GetClipboardData(CF_UNICODETEXT)))
748 p = clipreadunicode(h);
749 else if((h = GetClipboardData(CF_TEXT)))
750 p = clipreadutf(h);
751 else
752 p = strdup("");
753
754 CloseClipboard();
755 return p;
756 }
757
758 int
clipwrite(char * buf)759 clipwrite(char *buf)
760 {
761 HANDLE h;
762 char *p;
763 Rune16 *rp;
764 int n;
765
766 n = 0;
767 if(buf != nil)
768 n = strlen(buf);
769 if(!OpenClipboard(window))
770 return -1;
771
772 if(!EmptyClipboard()){
773 CloseClipboard();
774 return -1;
775 }
776
777 h = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, (n+1)*sizeof(Rune16));
778 if(h == NULL)
779 error(Enovmem);
780 rp = GlobalLock(h);
781 utftorunes16(rp, buf, n+1);
782 GlobalUnlock(h);
783
784 SetClipboardData(CF_UNICODETEXT, h);
785
786 h = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, n+1);
787 if(h == NULL)
788 error(Enovmem);
789 p = GlobalLock(h);
790 memmove(p, buf, n);
791 p[n] = 0;
792 GlobalUnlock(h);
793
794 SetClipboardData(CF_TEXT, h);
795
796 CloseClipboard();
797 return n;
798 }
799