1// +build windows
2
3// Copyright 2021 The TCell Authors
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use file except in compliance with the License.
7// You may obtain a copy of the license at
8//
9//    http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17package tcell
18
19import (
20	"errors"
21	"fmt"
22	"os"
23	"strings"
24	"sync"
25	"syscall"
26	"unicode/utf16"
27	"unsafe"
28)
29
30type cScreen struct {
31	in         syscall.Handle
32	out        syscall.Handle
33	cancelflag syscall.Handle
34	scandone   chan struct{}
35	evch       chan Event
36	quit       chan struct{}
37	curx       int
38	cury       int
39	style      Style
40	clear      bool
41	fini       bool
42	vten       bool
43	truecolor  bool
44	running    bool
45
46	w int
47	h int
48
49	oscreen consoleInfo
50	ocursor cursorInfo
51	oimode  uint32
52	oomode  uint32
53	cells   CellBuffer
54
55	finiOnce sync.Once
56
57	mouseEnabled bool
58	wg           sync.WaitGroup
59	stopQ        chan struct{}
60
61	sync.Mutex
62}
63
64var winLock sync.Mutex
65
66var winPalette = []Color{
67	ColorBlack,
68	ColorMaroon,
69	ColorGreen,
70	ColorNavy,
71	ColorOlive,
72	ColorPurple,
73	ColorTeal,
74	ColorSilver,
75	ColorGray,
76	ColorRed,
77	ColorLime,
78	ColorBlue,
79	ColorYellow,
80	ColorFuchsia,
81	ColorAqua,
82	ColorWhite,
83}
84
85var winColors = map[Color]Color{
86	ColorBlack:   ColorBlack,
87	ColorMaroon:  ColorMaroon,
88	ColorGreen:   ColorGreen,
89	ColorNavy:    ColorNavy,
90	ColorOlive:   ColorOlive,
91	ColorPurple:  ColorPurple,
92	ColorTeal:    ColorTeal,
93	ColorSilver:  ColorSilver,
94	ColorGray:    ColorGray,
95	ColorRed:     ColorRed,
96	ColorLime:    ColorLime,
97	ColorBlue:    ColorBlue,
98	ColorYellow:  ColorYellow,
99	ColorFuchsia: ColorFuchsia,
100	ColorAqua:    ColorAqua,
101	ColorWhite:   ColorWhite,
102}
103
104var (
105	k32 = syscall.NewLazyDLL("kernel32.dll")
106	u32 = syscall.NewLazyDLL("user32.dll")
107)
108
109// We have to bring in the kernel32 and user32 DLLs directly, so we can get
110// access to some system calls that the core Go API lacks.
111//
112// Note that Windows appends some functions with W to indicate that wide
113// characters (Unicode) are in use.  The documentation refers to them
114// without this suffix, as the resolution is made via preprocessor.
115var (
116	procReadConsoleInput           = k32.NewProc("ReadConsoleInputW")
117	procWaitForMultipleObjects     = k32.NewProc("WaitForMultipleObjects")
118	procCreateEvent                = k32.NewProc("CreateEventW")
119	procSetEvent                   = k32.NewProc("SetEvent")
120	procGetConsoleCursorInfo       = k32.NewProc("GetConsoleCursorInfo")
121	procSetConsoleCursorInfo       = k32.NewProc("SetConsoleCursorInfo")
122	procSetConsoleCursorPosition   = k32.NewProc("SetConsoleCursorPosition")
123	procSetConsoleMode             = k32.NewProc("SetConsoleMode")
124	procGetConsoleMode             = k32.NewProc("GetConsoleMode")
125	procGetConsoleScreenBufferInfo = k32.NewProc("GetConsoleScreenBufferInfo")
126	procFillConsoleOutputAttribute = k32.NewProc("FillConsoleOutputAttribute")
127	procFillConsoleOutputCharacter = k32.NewProc("FillConsoleOutputCharacterW")
128	procSetConsoleWindowInfo       = k32.NewProc("SetConsoleWindowInfo")
129	procSetConsoleScreenBufferSize = k32.NewProc("SetConsoleScreenBufferSize")
130	procSetConsoleTextAttribute    = k32.NewProc("SetConsoleTextAttribute")
131	procMessageBeep                = u32.NewProc("MessageBeep")
132)
133
134const (
135	w32Infinite    = ^uintptr(0)
136	w32WaitObject0 = uintptr(0)
137)
138
139const (
140	// VT100/XTerm escapes understood by the console
141	vtShowCursor = "\x1b[?25h"
142	vtHideCursor = "\x1b[?25l"
143	vtCursorPos  = "\x1b[%d;%dH" // Note that it is Y then X
144	vtSgr0       = "\x1b[0m"
145	vtBold       = "\x1b[1m"
146	vtUnderline  = "\x1b[4m"
147	vtBlink      = "\x1b[5m" // Not sure this is processed
148	vtReverse    = "\x1b[7m"
149	vtSetFg      = "\x1b[38;5;%dm"
150	vtSetBg      = "\x1b[48;5;%dm"
151	vtSetFgRGB   = "\x1b[38;2;%d;%d;%dm" // RGB
152	vtSetBgRGB   = "\x1b[48;2;%d;%d;%dm" // RGB
153)
154
155// NewConsoleScreen returns a Screen for the Windows console associated
156// with the current process.  The Screen makes use of the Windows Console
157// API to display content and read events.
158func NewConsoleScreen() (Screen, error) {
159	return &cScreen{}, nil
160}
161
162func (s *cScreen) Init() error {
163	s.evch = make(chan Event, 10)
164	s.quit = make(chan struct{})
165	s.scandone = make(chan struct{})
166
167	in, e := syscall.Open("CONIN$", syscall.O_RDWR, 0)
168	if e != nil {
169		return e
170	}
171	s.in = in
172	out, e := syscall.Open("CONOUT$", syscall.O_RDWR, 0)
173	if e != nil {
174		syscall.Close(s.in)
175		return e
176	}
177	s.out = out
178
179	s.truecolor = true
180
181	// ConEmu handling of colors and scrolling when in terminal
182	// mode is extremely problematic at the best.  The color
183	// palette will scroll even though characters do not, when
184	// emitting stuff for the last character.  In the future we
185	// might change this to look at specific versions of ConEmu
186	// if they fix the bug.
187	if os.Getenv("ConEmuPID") != "" {
188		s.truecolor = false
189	}
190	switch os.Getenv("TCELL_TRUECOLOR") {
191	case "disable":
192		s.truecolor = false
193	case "enable":
194		s.truecolor = true
195	}
196
197	s.Lock()
198
199	s.curx = -1
200	s.cury = -1
201	s.style = StyleDefault
202	s.getCursorInfo(&s.ocursor)
203	s.getConsoleInfo(&s.oscreen)
204	s.getOutMode(&s.oomode)
205	s.getInMode(&s.oimode)
206	s.resize()
207
208	s.fini = false
209	s.setInMode(modeResizeEn | modeExtndFlg)
210
211	// 24-bit color is opt-in for now, because we can't figure out
212	// to make it work consistently.
213	if s.truecolor {
214		s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut)
215		var omode uint32
216		s.getOutMode(&omode)
217		if omode&modeVtOutput == modeVtOutput {
218			s.vten = true
219		} else {
220			s.truecolor = false
221			s.setOutMode(0)
222		}
223	} else {
224		s.setOutMode(0)
225	}
226
227	s.Unlock()
228
229	return s.engage()
230}
231
232func (s *cScreen) CharacterSet() string {
233	// We are always UTF-16LE on Windows
234	return "UTF-16LE"
235}
236
237func (s *cScreen) EnableMouse(...MouseFlags) {
238	s.Lock()
239	s.mouseEnabled = true
240	s.enableMouse(true)
241	s.Unlock()
242}
243
244func (s *cScreen) DisableMouse() {
245	s.Lock()
246	s.mouseEnabled = false
247	s.enableMouse(false)
248	s.Unlock()
249}
250
251func (s *cScreen) enableMouse(on bool) {
252	if on {
253		s.setInMode(modeResizeEn | modeMouseEn | modeExtndFlg)
254	} else {
255		s.setInMode(modeResizeEn | modeExtndFlg)
256	}
257}
258
259// Windows lacks bracketed paste (for now)
260
261func (s *cScreen) EnablePaste() {}
262
263func (s *cScreen) DisablePaste() {}
264
265func (s *cScreen) Fini() {
266	s.disengage()
267}
268
269func (s *cScreen) disengage() {
270	s.Lock()
271	if !s.running {
272		s.Unlock()
273		return
274	}
275	s.running = false
276	stopQ := s.stopQ
277	procSetEvent.Call(uintptr(s.cancelflag))
278	close(stopQ)
279	s.Unlock()
280
281	s.wg.Wait()
282
283	s.setInMode(s.oimode)
284	s.setOutMode(s.oomode)
285	s.setBufferSize(int(s.oscreen.size.x), int(s.oscreen.size.y))
286	s.clearScreen(StyleDefault, false)
287	s.setCursorPos(0, 0, false)
288	s.setCursorInfo(&s.ocursor)
289	procSetConsoleTextAttribute.Call(
290		uintptr(s.out),
291		uintptr(s.mapStyle(StyleDefault)))
292}
293
294func (s *cScreen) engage() error {
295	s.Lock()
296	defer s.Unlock()
297	if s.running {
298		return errors.New("already engaged")
299	}
300	s.stopQ = make(chan struct{})
301	cf, _, e := procCreateEvent.Call(
302		uintptr(0),
303		uintptr(1),
304		uintptr(0),
305		uintptr(0))
306	if cf == uintptr(0) {
307		return e
308	}
309	s.running = true
310	s.cancelflag = syscall.Handle(cf)
311	s.enableMouse(s.mouseEnabled)
312
313	if s.vten {
314		s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut)
315	} else {
316		s.setOutMode(0)
317	}
318
319	s.clearScreen(s.style, s.vten)
320	s.hideCursor()
321
322	s.cells.Invalidate()
323	s.hideCursor()
324	s.resize()
325	s.draw()
326	s.doCursor()
327
328	s.wg.Add(1)
329	go s.scanInput(s.stopQ)
330	return nil
331}
332
333func (s *cScreen) PostEventWait(ev Event) {
334	s.evch <- ev
335}
336
337func (s *cScreen) PostEvent(ev Event) error {
338	select {
339	case s.evch <- ev:
340		return nil
341	default:
342		return ErrEventQFull
343	}
344}
345
346func (s *cScreen) ChannelEvents(ch chan<- Event, quit <-chan struct{}) {
347	defer close(ch)
348	for {
349		select {
350		case <-quit:
351			return
352		case <-s.stopQ:
353			return
354		case ev := <-s.evch:
355			select {
356			case <-quit:
357				return
358			case <-s.stopQ:
359				return
360			case ch <- ev:
361			}
362		}
363	}
364}
365
366func (s *cScreen) PollEvent() Event {
367	select {
368	case <-s.stopQ:
369		return nil
370	case ev := <-s.evch:
371		return ev
372	}
373}
374
375func (s *cScreen) HasPendingEvent() bool {
376	return len(s.evch) > 0
377}
378
379type cursorInfo struct {
380	size    uint32
381	visible uint32
382}
383
384type coord struct {
385	x int16
386	y int16
387}
388
389func (c coord) uintptr() uintptr {
390	// little endian, put x first
391	return uintptr(c.x) | (uintptr(c.y) << 16)
392}
393
394type rect struct {
395	left   int16
396	top    int16
397	right  int16
398	bottom int16
399}
400
401func (s *cScreen) emitVtString(vs string) {
402	esc := utf16.Encode([]rune(vs))
403	syscall.WriteConsole(s.out, &esc[0], uint32(len(esc)), nil, nil)
404}
405
406func (s *cScreen) showCursor() {
407	if s.vten {
408		s.emitVtString(vtShowCursor)
409	} else {
410		s.setCursorInfo(&cursorInfo{size: 100, visible: 1})
411	}
412}
413
414func (s *cScreen) hideCursor() {
415	if s.vten {
416		s.emitVtString(vtHideCursor)
417	} else {
418		s.setCursorInfo(&cursorInfo{size: 1, visible: 0})
419	}
420}
421
422func (s *cScreen) ShowCursor(x, y int) {
423	s.Lock()
424	if !s.fini {
425		s.curx = x
426		s.cury = y
427	}
428	s.doCursor()
429	s.Unlock()
430}
431
432func (s *cScreen) doCursor() {
433	x, y := s.curx, s.cury
434
435	if x < 0 || y < 0 || x >= s.w || y >= s.h {
436		s.hideCursor()
437	} else {
438		s.setCursorPos(x, y, s.vten)
439		s.showCursor()
440	}
441}
442
443func (s *cScreen) HideCursor() {
444	s.ShowCursor(-1, -1)
445}
446
447type inputRecord struct {
448	typ  uint16
449	_    uint16
450	data [16]byte
451}
452
453const (
454	keyEvent    uint16 = 1
455	mouseEvent  uint16 = 2
456	resizeEvent uint16 = 4
457	menuEvent   uint16 = 8  // don't use
458	focusEvent  uint16 = 16 // don't use
459)
460
461type mouseRecord struct {
462	x     int16
463	y     int16
464	btns  uint32
465	mod   uint32
466	flags uint32
467}
468
469const (
470	mouseDoubleClick uint32 = 0x2
471	mouseHWheeled    uint32 = 0x8
472	mouseVWheeled    uint32 = 0x4
473	mouseMoved       uint32 = 0x1
474)
475
476type resizeRecord struct {
477	x int16
478	y int16
479}
480
481type keyRecord struct {
482	isdown int32
483	repeat uint16
484	kcode  uint16
485	scode  uint16
486	ch     uint16
487	mod    uint32
488}
489
490const (
491	// Constants per Microsoft.  We don't put the modifiers
492	// here.
493	vkCancel = 0x03
494	vkBack   = 0x08 // Backspace
495	vkTab    = 0x09
496	vkClear  = 0x0c
497	vkReturn = 0x0d
498	vkPause  = 0x13
499	vkEscape = 0x1b
500	vkSpace  = 0x20
501	vkPrior  = 0x21 // PgUp
502	vkNext   = 0x22 // PgDn
503	vkEnd    = 0x23
504	vkHome   = 0x24
505	vkLeft   = 0x25
506	vkUp     = 0x26
507	vkRight  = 0x27
508	vkDown   = 0x28
509	vkPrint  = 0x2a
510	vkPrtScr = 0x2c
511	vkInsert = 0x2d
512	vkDelete = 0x2e
513	vkHelp   = 0x2f
514	vkF1     = 0x70
515	vkF2     = 0x71
516	vkF3     = 0x72
517	vkF4     = 0x73
518	vkF5     = 0x74
519	vkF6     = 0x75
520	vkF7     = 0x76
521	vkF8     = 0x77
522	vkF9     = 0x78
523	vkF10    = 0x79
524	vkF11    = 0x7a
525	vkF12    = 0x7b
526	vkF13    = 0x7c
527	vkF14    = 0x7d
528	vkF15    = 0x7e
529	vkF16    = 0x7f
530	vkF17    = 0x80
531	vkF18    = 0x81
532	vkF19    = 0x82
533	vkF20    = 0x83
534	vkF21    = 0x84
535	vkF22    = 0x85
536	vkF23    = 0x86
537	vkF24    = 0x87
538)
539
540var vkKeys = map[uint16]Key{
541	vkCancel: KeyCancel,
542	vkBack:   KeyBackspace,
543	vkTab:    KeyTab,
544	vkClear:  KeyClear,
545	vkPause:  KeyPause,
546	vkPrint:  KeyPrint,
547	vkPrtScr: KeyPrint,
548	vkPrior:  KeyPgUp,
549	vkNext:   KeyPgDn,
550	vkReturn: KeyEnter,
551	vkEnd:    KeyEnd,
552	vkHome:   KeyHome,
553	vkLeft:   KeyLeft,
554	vkUp:     KeyUp,
555	vkRight:  KeyRight,
556	vkDown:   KeyDown,
557	vkInsert: KeyInsert,
558	vkDelete: KeyDelete,
559	vkHelp:   KeyHelp,
560	vkF1:     KeyF1,
561	vkF2:     KeyF2,
562	vkF3:     KeyF3,
563	vkF4:     KeyF4,
564	vkF5:     KeyF5,
565	vkF6:     KeyF6,
566	vkF7:     KeyF7,
567	vkF8:     KeyF8,
568	vkF9:     KeyF9,
569	vkF10:    KeyF10,
570	vkF11:    KeyF11,
571	vkF12:    KeyF12,
572	vkF13:    KeyF13,
573	vkF14:    KeyF14,
574	vkF15:    KeyF15,
575	vkF16:    KeyF16,
576	vkF17:    KeyF17,
577	vkF18:    KeyF18,
578	vkF19:    KeyF19,
579	vkF20:    KeyF20,
580	vkF21:    KeyF21,
581	vkF22:    KeyF22,
582	vkF23:    KeyF23,
583	vkF24:    KeyF24,
584}
585
586// NB: All Windows platforms are little endian.  We assume this
587// never, ever change.  The following code is endian safe. and does
588// not use unsafe pointers.
589func getu32(v []byte) uint32 {
590	return uint32(v[0]) + (uint32(v[1]) << 8) + (uint32(v[2]) << 16) + (uint32(v[3]) << 24)
591}
592func geti32(v []byte) int32 {
593	return int32(getu32(v))
594}
595func getu16(v []byte) uint16 {
596	return uint16(v[0]) + (uint16(v[1]) << 8)
597}
598func geti16(v []byte) int16 {
599	return int16(getu16(v))
600}
601
602// Convert windows dwControlKeyState to modifier mask
603func mod2mask(cks uint32) ModMask {
604	mm := ModNone
605	// Left or right control
606	if (cks & (0x0008 | 0x0004)) != 0 {
607		mm |= ModCtrl
608	}
609	// Left or right alt
610	if (cks & (0x0002 | 0x0001)) != 0 {
611		mm |= ModAlt
612	}
613	// Any shift
614	if (cks & 0x0010) != 0 {
615		mm |= ModShift
616	}
617	return mm
618}
619
620func mrec2btns(mbtns, flags uint32) ButtonMask {
621	btns := ButtonNone
622	if mbtns&0x1 != 0 {
623		btns |= Button1
624	}
625	if mbtns&0x2 != 0 {
626		btns |= Button2
627	}
628	if mbtns&0x4 != 0 {
629		btns |= Button3
630	}
631	if mbtns&0x8 != 0 {
632		btns |= Button4
633	}
634	if mbtns&0x10 != 0 {
635		btns |= Button5
636	}
637	if mbtns&0x20 != 0 {
638		btns |= Button6
639	}
640	if mbtns&0x40 != 0 {
641		btns |= Button7
642	}
643	if mbtns&0x80 != 0 {
644		btns |= Button8
645	}
646
647	if flags&mouseVWheeled != 0 {
648		if mbtns&0x80000000 == 0 {
649			btns |= WheelUp
650		} else {
651			btns |= WheelDown
652		}
653	}
654	if flags&mouseHWheeled != 0 {
655		if mbtns&0x80000000 == 0 {
656			btns |= WheelRight
657		} else {
658			btns |= WheelLeft
659		}
660	}
661	return btns
662}
663
664func (s *cScreen) getConsoleInput() error {
665	// cancelFlag comes first as WaitForMultipleObjects returns the lowest index
666	// in the event that both events are signalled.
667	waitObjects := []syscall.Handle{s.cancelflag, s.in}
668	// As arrays are contiguous in memory, a pointer to the first object is the
669	// same as a pointer to the array itself.
670	pWaitObjects := unsafe.Pointer(&waitObjects[0])
671
672	rv, _, er := procWaitForMultipleObjects.Call(
673		uintptr(len(waitObjects)),
674		uintptr(pWaitObjects),
675		uintptr(0),
676		w32Infinite)
677	// WaitForMultipleObjects returns WAIT_OBJECT_0 + the index.
678	switch rv {
679	case w32WaitObject0: // s.cancelFlag
680		return errors.New("cancelled")
681	case w32WaitObject0 + 1: // s.in
682		rec := &inputRecord{}
683		var nrec int32
684		rv, _, er := procReadConsoleInput.Call(
685			uintptr(s.in),
686			uintptr(unsafe.Pointer(rec)),
687			uintptr(1),
688			uintptr(unsafe.Pointer(&nrec)))
689		if rv == 0 {
690			return er
691		}
692		if nrec != 1 {
693			return nil
694		}
695		switch rec.typ {
696		case keyEvent:
697			krec := &keyRecord{}
698			krec.isdown = geti32(rec.data[0:])
699			krec.repeat = getu16(rec.data[4:])
700			krec.kcode = getu16(rec.data[6:])
701			krec.scode = getu16(rec.data[8:])
702			krec.ch = getu16(rec.data[10:])
703			krec.mod = getu32(rec.data[12:])
704
705			if krec.isdown == 0 || krec.repeat < 1 {
706				// its a key release event, ignore it
707				return nil
708			}
709			if krec.ch != 0 {
710				// synthesized key code
711				for krec.repeat > 0 {
712					// convert shift+tab to backtab
713					if mod2mask(krec.mod) == ModShift && krec.ch == vkTab {
714						s.PostEventWait(NewEventKey(KeyBacktab, 0,
715							ModNone))
716					} else {
717						s.PostEventWait(NewEventKey(KeyRune, rune(krec.ch),
718							mod2mask(krec.mod)))
719					}
720					krec.repeat--
721				}
722				return nil
723			}
724			key := KeyNUL // impossible on Windows
725			ok := false
726			if key, ok = vkKeys[krec.kcode]; !ok {
727				return nil
728			}
729			for krec.repeat > 0 {
730				s.PostEventWait(NewEventKey(key, rune(krec.ch),
731					mod2mask(krec.mod)))
732				krec.repeat--
733			}
734
735		case mouseEvent:
736			var mrec mouseRecord
737			mrec.x = geti16(rec.data[0:])
738			mrec.y = geti16(rec.data[2:])
739			mrec.btns = getu32(rec.data[4:])
740			mrec.mod = getu32(rec.data[8:])
741			mrec.flags = getu32(rec.data[12:])
742			btns := mrec2btns(mrec.btns, mrec.flags)
743			// we ignore double click, events are delivered normally
744			s.PostEventWait(NewEventMouse(int(mrec.x), int(mrec.y), btns,
745				mod2mask(mrec.mod)))
746
747		case resizeEvent:
748			var rrec resizeRecord
749			rrec.x = geti16(rec.data[0:])
750			rrec.y = geti16(rec.data[2:])
751			s.PostEventWait(NewEventResize(int(rrec.x), int(rrec.y)))
752
753		default:
754		}
755	default:
756		return er
757	}
758
759	return nil
760}
761
762func (s *cScreen) scanInput(stopQ chan struct{}) {
763	defer s.wg.Done()
764	for {
765		select {
766		case <-stopQ:
767			return
768		default:
769		}
770		if e := s.getConsoleInput(); e != nil {
771			return
772		}
773	}
774}
775
776// Windows console can display 8 characters, in either low or high intensity
777func (s *cScreen) Colors() int {
778	if s.vten {
779		return 1 << 24
780	}
781	return 16
782}
783
784var vgaColors = map[Color]uint16{
785	ColorBlack:   0,
786	ColorMaroon:  0x4,
787	ColorGreen:   0x2,
788	ColorNavy:    0x1,
789	ColorOlive:   0x6,
790	ColorPurple:  0x5,
791	ColorTeal:    0x3,
792	ColorSilver:  0x7,
793	ColorGrey:    0x8,
794	ColorRed:     0xc,
795	ColorLime:    0xa,
796	ColorBlue:    0x9,
797	ColorYellow:  0xe,
798	ColorFuchsia: 0xd,
799	ColorAqua:    0xb,
800	ColorWhite:   0xf,
801}
802
803// Windows uses RGB signals
804func mapColor2RGB(c Color) uint16 {
805	winLock.Lock()
806	if v, ok := winColors[c]; ok {
807		c = v
808	} else {
809		v = FindColor(c, winPalette)
810		winColors[c] = v
811		c = v
812	}
813	winLock.Unlock()
814
815	if vc, ok := vgaColors[c]; ok {
816		return vc
817	}
818	return 0
819}
820
821// Map a tcell style to Windows attributes
822func (s *cScreen) mapStyle(style Style) uint16 {
823	f, b, a := style.Decompose()
824	fa := s.oscreen.attrs & 0xf
825	ba := (s.oscreen.attrs) >> 4 & 0xf
826	if f != ColorDefault && f != ColorReset {
827		fa = mapColor2RGB(f)
828	}
829	if b != ColorDefault && b != ColorReset {
830		ba = mapColor2RGB(b)
831	}
832	var attr uint16
833	// We simulate reverse by doing the color swap ourselves.
834	// Apparently windows cannot really do this except in DBCS
835	// views.
836	if a&AttrReverse != 0 {
837		attr = ba
838		attr |= (fa << 4)
839	} else {
840		attr = fa
841		attr |= (ba << 4)
842	}
843	if a&AttrBold != 0 {
844		attr |= 0x8
845	}
846	if a&AttrDim != 0 {
847		attr &^= 0x8
848	}
849	if a&AttrUnderline != 0 {
850		// Best effort -- doesn't seem to work though.
851		attr |= 0x8000
852	}
853	// Blink is unsupported
854	return attr
855}
856
857func (s *cScreen) SetCell(x, y int, style Style, ch ...rune) {
858	if len(ch) > 0 {
859		s.SetContent(x, y, ch[0], ch[1:], style)
860	} else {
861		s.SetContent(x, y, ' ', nil, style)
862	}
863}
864
865func (s *cScreen) SetContent(x, y int, mainc rune, combc []rune, style Style) {
866	s.Lock()
867	if !s.fini {
868		s.cells.SetContent(x, y, mainc, combc, style)
869	}
870	s.Unlock()
871}
872
873func (s *cScreen) GetContent(x, y int) (rune, []rune, Style, int) {
874	s.Lock()
875	mainc, combc, style, width := s.cells.GetContent(x, y)
876	s.Unlock()
877	return mainc, combc, style, width
878}
879
880func (s *cScreen) sendVtStyle(style Style) {
881	esc := &strings.Builder{}
882
883	fg, bg, attrs := style.Decompose()
884
885	esc.WriteString(vtSgr0)
886
887	if attrs&(AttrBold|AttrDim) == AttrBold {
888		esc.WriteString(vtBold)
889	}
890	if attrs&AttrBlink != 0 {
891		esc.WriteString(vtBlink)
892	}
893	if attrs&AttrUnderline != 0 {
894		esc.WriteString(vtUnderline)
895	}
896	if attrs&AttrReverse != 0 {
897		esc.WriteString(vtReverse)
898	}
899	if fg.IsRGB() {
900		r, g, b := fg.RGB()
901		fmt.Fprintf(esc, vtSetFgRGB, r, g, b)
902	} else if fg.Valid() {
903		fmt.Fprintf(esc, vtSetFg, fg&0xff)
904	}
905	if bg.IsRGB() {
906		r, g, b := bg.RGB()
907		fmt.Fprintf(esc, vtSetBgRGB, r, g, b)
908	} else if bg.Valid() {
909		fmt.Fprintf(esc, vtSetBg, bg&0xff)
910	}
911	s.emitVtString(esc.String())
912}
913
914func (s *cScreen) writeString(x, y int, style Style, ch []uint16) {
915	// we assume the caller has hidden the cursor
916	if len(ch) == 0 {
917		return
918	}
919	s.setCursorPos(x, y, s.vten)
920
921	if s.vten {
922		s.sendVtStyle(style)
923	} else {
924		procSetConsoleTextAttribute.Call(
925			uintptr(s.out),
926			uintptr(s.mapStyle(style)))
927	}
928	syscall.WriteConsole(s.out, &ch[0], uint32(len(ch)), nil, nil)
929}
930
931func (s *cScreen) draw() {
932	// allocate a scratch line bit enough for no combining chars.
933	// if you have combining characters, you may pay for extra allocs.
934	if s.clear {
935		s.clearScreen(s.style, s.vten)
936		s.clear = false
937		s.cells.Invalidate()
938	}
939	buf := make([]uint16, 0, s.w)
940	wcs := buf[:]
941	lstyle := styleInvalid
942
943	lx, ly := -1, -1
944	ra := make([]rune, 1)
945
946	for y := 0; y < s.h; y++ {
947		for x := 0; x < s.w; x++ {
948			mainc, combc, style, width := s.cells.GetContent(x, y)
949			dirty := s.cells.Dirty(x, y)
950			if style == StyleDefault {
951				style = s.style
952			}
953
954			if !dirty || style != lstyle {
955				// write out any data queued thus far
956				// because we are going to skip over some
957				// cells, or because we need to change styles
958				s.writeString(lx, ly, lstyle, wcs)
959				wcs = buf[0:0]
960				lstyle = StyleDefault
961				if !dirty {
962					continue
963				}
964			}
965			if x > s.w-width {
966				mainc = ' '
967				combc = nil
968				width = 1
969			}
970			if len(wcs) == 0 {
971				lstyle = style
972				lx = x
973				ly = y
974			}
975			ra[0] = mainc
976			wcs = append(wcs, utf16.Encode(ra)...)
977			if len(combc) != 0 {
978				wcs = append(wcs, utf16.Encode(combc)...)
979			}
980			for dx := 0; dx < width; dx++ {
981				s.cells.SetDirty(x+dx, y, false)
982			}
983			x += width - 1
984		}
985		s.writeString(lx, ly, lstyle, wcs)
986		wcs = buf[0:0]
987		lstyle = styleInvalid
988	}
989}
990
991func (s *cScreen) Show() {
992	s.Lock()
993	if !s.fini {
994		s.hideCursor()
995		s.resize()
996		s.draw()
997		s.doCursor()
998	}
999	s.Unlock()
1000}
1001
1002func (s *cScreen) Sync() {
1003	s.Lock()
1004	if !s.fini {
1005		s.cells.Invalidate()
1006		s.hideCursor()
1007		s.resize()
1008		s.draw()
1009		s.doCursor()
1010	}
1011	s.Unlock()
1012}
1013
1014type consoleInfo struct {
1015	size  coord
1016	pos   coord
1017	attrs uint16
1018	win   rect
1019	maxsz coord
1020}
1021
1022func (s *cScreen) getConsoleInfo(info *consoleInfo) {
1023	procGetConsoleScreenBufferInfo.Call(
1024		uintptr(s.out),
1025		uintptr(unsafe.Pointer(info)))
1026}
1027
1028func (s *cScreen) getCursorInfo(info *cursorInfo) {
1029	procGetConsoleCursorInfo.Call(
1030		uintptr(s.out),
1031		uintptr(unsafe.Pointer(info)))
1032}
1033
1034func (s *cScreen) setCursorInfo(info *cursorInfo) {
1035	procSetConsoleCursorInfo.Call(
1036		uintptr(s.out),
1037		uintptr(unsafe.Pointer(info)))
1038
1039}
1040
1041func (s *cScreen) setCursorPos(x, y int, vtEnable bool) {
1042	if vtEnable {
1043		// Note that the string is Y first.  Origin is 1,1.
1044		s.emitVtString(fmt.Sprintf(vtCursorPos, y+1, x+1))
1045	} else {
1046		procSetConsoleCursorPosition.Call(
1047			uintptr(s.out),
1048			coord{int16(x), int16(y)}.uintptr())
1049	}
1050}
1051
1052func (s *cScreen) setBufferSize(x, y int) {
1053	procSetConsoleScreenBufferSize.Call(
1054		uintptr(s.out),
1055		coord{int16(x), int16(y)}.uintptr())
1056}
1057
1058func (s *cScreen) Size() (int, int) {
1059	s.Lock()
1060	w, h := s.w, s.h
1061	s.Unlock()
1062
1063	return w, h
1064}
1065
1066func (s *cScreen) resize() {
1067	info := consoleInfo{}
1068	s.getConsoleInfo(&info)
1069
1070	w := int((info.win.right - info.win.left) + 1)
1071	h := int((info.win.bottom - info.win.top) + 1)
1072
1073	if s.w == w && s.h == h {
1074		return
1075	}
1076
1077	s.cells.Resize(w, h)
1078	s.w = w
1079	s.h = h
1080
1081	s.setBufferSize(w, h)
1082
1083	r := rect{0, 0, int16(w - 1), int16(h - 1)}
1084	procSetConsoleWindowInfo.Call(
1085		uintptr(s.out),
1086		uintptr(1),
1087		uintptr(unsafe.Pointer(&r)))
1088	s.PostEvent(NewEventResize(w, h))
1089}
1090
1091func (s *cScreen) Clear() {
1092	s.Fill(' ', s.style)
1093}
1094
1095func (s *cScreen) Fill(r rune, style Style) {
1096	s.Lock()
1097	if !s.fini {
1098		s.cells.Fill(r, style)
1099		s.clear = true
1100	}
1101	s.Unlock()
1102}
1103
1104func (s *cScreen) clearScreen(style Style, vtEnable bool) {
1105	if vtEnable {
1106		s.sendVtStyle(style)
1107		row := strings.Repeat(" ", s.w)
1108		for y := 0; y < s.h; y++ {
1109			s.setCursorPos(0, y, vtEnable)
1110			s.emitVtString(row)
1111		}
1112		s.setCursorPos(0, 0, vtEnable)
1113
1114	} else {
1115		pos := coord{0, 0}
1116		attr := s.mapStyle(style)
1117		x, y := s.w, s.h
1118		scratch := uint32(0)
1119		count := uint32(x * y)
1120
1121		procFillConsoleOutputAttribute.Call(
1122			uintptr(s.out),
1123			uintptr(attr),
1124			uintptr(count),
1125			pos.uintptr(),
1126			uintptr(unsafe.Pointer(&scratch)))
1127		procFillConsoleOutputCharacter.Call(
1128			uintptr(s.out),
1129			uintptr(' '),
1130			uintptr(count),
1131			pos.uintptr(),
1132			uintptr(unsafe.Pointer(&scratch)))
1133	}
1134}
1135
1136const (
1137	// Input modes
1138	modeExtndFlg uint32 = 0x0080
1139	modeMouseEn         = 0x0010
1140	modeResizeEn        = 0x0008
1141	modeCooked          = 0x0001
1142	modeVtInput         = 0x0200
1143
1144	// Output modes
1145	modeCookedOut uint32 = 0x0001
1146	modeWrapEOL          = 0x0002
1147	modeVtOutput         = 0x0004
1148	modeNoAutoNL         = 0x0008
1149)
1150
1151func (s *cScreen) setInMode(mode uint32) error {
1152	rv, _, err := procSetConsoleMode.Call(
1153		uintptr(s.in),
1154		uintptr(mode))
1155	if rv == 0 {
1156		return err
1157	}
1158	return nil
1159}
1160
1161func (s *cScreen) setOutMode(mode uint32) error {
1162	rv, _, err := procSetConsoleMode.Call(
1163		uintptr(s.out),
1164		uintptr(mode))
1165	if rv == 0 {
1166		return err
1167	}
1168	return nil
1169}
1170
1171func (s *cScreen) getInMode(v *uint32) {
1172	procGetConsoleMode.Call(
1173		uintptr(s.in),
1174		uintptr(unsafe.Pointer(v)))
1175}
1176
1177func (s *cScreen) getOutMode(v *uint32) {
1178	procGetConsoleMode.Call(
1179		uintptr(s.out),
1180		uintptr(unsafe.Pointer(v)))
1181}
1182
1183func (s *cScreen) SetStyle(style Style) {
1184	s.Lock()
1185	s.style = style
1186	s.Unlock()
1187}
1188
1189// No fallback rune support, since we have Unicode.  Yay!
1190
1191func (s *cScreen) RegisterRuneFallback(r rune, subst string) {
1192}
1193
1194func (s *cScreen) UnregisterRuneFallback(r rune) {
1195}
1196
1197func (s *cScreen) CanDisplay(r rune, checkFallbacks bool) bool {
1198	// We presume we can display anything -- we're Unicode.
1199	// (Sadly this not precisely true.  Combinings are especially
1200	// poorly supported under Windows.)
1201	return true
1202}
1203
1204func (s *cScreen) HasMouse() bool {
1205	return true
1206}
1207
1208func (s *cScreen) Resize(int, int, int, int) {}
1209
1210func (s *cScreen) HasKey(k Key) bool {
1211	// Microsoft has codes for some keys, but they are unusual,
1212	// so we don't include them.  We include all the typical
1213	// 101, 105 key layout keys.
1214	valid := map[Key]bool{
1215		KeyBackspace: true,
1216		KeyTab:       true,
1217		KeyEscape:    true,
1218		KeyPause:     true,
1219		KeyPrint:     true,
1220		KeyPgUp:      true,
1221		KeyPgDn:      true,
1222		KeyEnter:     true,
1223		KeyEnd:       true,
1224		KeyHome:      true,
1225		KeyLeft:      true,
1226		KeyUp:        true,
1227		KeyRight:     true,
1228		KeyDown:      true,
1229		KeyInsert:    true,
1230		KeyDelete:    true,
1231		KeyF1:        true,
1232		KeyF2:        true,
1233		KeyF3:        true,
1234		KeyF4:        true,
1235		KeyF5:        true,
1236		KeyF6:        true,
1237		KeyF7:        true,
1238		KeyF8:        true,
1239		KeyF9:        true,
1240		KeyF10:       true,
1241		KeyF11:       true,
1242		KeyF12:       true,
1243		KeyRune:      true,
1244	}
1245
1246	return valid[k]
1247}
1248
1249func (s *cScreen) Beep() error {
1250	// A simple beep. If the sound card is not available, the sound is generated
1251	// using the speaker.
1252	//
1253	// Reference:
1254	// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebeep
1255	const simpleBeep = 0xffffffff
1256	if rv, _, err := procMessageBeep.Call(simpleBeep); rv == 0 {
1257		return err
1258	}
1259	return nil
1260}
1261
1262func (s *cScreen) Suspend() error {
1263	s.disengage()
1264	return nil
1265}
1266
1267func (s *cScreen) Resume() error {
1268	return s.engage()
1269}
1270