github.com/jmigpin/editor@v1.6.0/driver/windriver/window.go (about)

     1  //go:build windows
     2  
     3  package windriver
     4  
     5  import (
     6  	"fmt"
     7  	"image"
     8  	"image/draw"
     9  	"reflect"
    10  	"runtime"
    11  	"sync"
    12  	"time"
    13  	"unsafe"
    14  
    15  	"golang.org/x/sys/windows"
    16  
    17  	"github.com/jmigpin/editor/util/imageutil"
    18  	"github.com/jmigpin/editor/util/syncutil"
    19  	"github.com/jmigpin/editor/util/uiutil/event"
    20  )
    21  
    22  // Functions preceded by "ost" run in the "operating-system-thread".
    23  type Window struct {
    24  	className *uint16
    25  	hwnd      windows.Handle
    26  	instance  windows.Handle
    27  
    28  	img draw.Image
    29  	bmH windows.Handle // bitmap handle
    30  
    31  	events chan interface{}
    32  	dndMan *DndMan
    33  
    34  	postM struct {
    35  		sync.Mutex
    36  		id int
    37  		m  map[int]interface{}
    38  	}
    39  	cursors struct {
    40  		currentId int
    41  		cache     map[int]windows.Handle
    42  	}
    43  }
    44  
    45  func NewWindow() (*Window, error) {
    46  	win := &Window{
    47  		events: make(chan interface{}, 8),
    48  	}
    49  	win.cursors.cache = map[int]windows.Handle{}
    50  	win.postM.m = map[int]interface{}{}
    51  	win.dndMan = NewDndMan()
    52  
    53  	// initial size
    54  	win.ostResizeImage(image.Rect(0, 0, 1, 1))
    55  
    56  	if err := win.initAndSetupLoop(); err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	return win, nil
    61  }
    62  
    63  //----------
    64  
    65  func (win *Window) initAndSetupLoop() error {
    66  	initErr := make(chan error)
    67  
    68  	go func() {
    69  		// ensure OS thread
    70  		runtime.LockOSThread()
    71  		defer runtime.UnlockOSThread()
    72  
    73  		if err := win.ostInitialize(); err != nil {
    74  			initErr <- err
    75  			return
    76  		}
    77  		initErr <- nil
    78  
    79  		// run event loop in OS thread
    80  		win.ostMsgLoop() // blocks
    81  	}()
    82  
    83  	return <-initErr
    84  }
    85  
    86  func (win *Window) ostInitialize() error {
    87  	_ = hideConsole()
    88  
    89  	// handle containing the window procedure for the class.
    90  	instance, err := _GetModuleHandleW(nil)
    91  	if err != nil {
    92  		return fmt.Errorf("getmodulehandle: %v", err)
    93  	}
    94  	win.instance = instance
    95  
    96  	// window class registration
    97  	win.className = UTF16PtrFromString("editorClass")
    98  	wce := _WndClassExW{
    99  		LpszClassName: win.className,
   100  		LpfnWndProc:   windows.NewCallback(win.wndProcCallback),
   101  		HInstance:     win.instance,
   102  		HbrBackground: _COLOR_WINDOW + 1,
   103  		Style:         _CS_HREDRAW | _CS_VREDRAW,
   104  	}
   105  	wce.CbSize = uint32(unsafe.Sizeof(wce))
   106  	if _, err := _RegisterClassExW(&wce); err != nil {
   107  		return fmt.Errorf("registerclassex: %w", err)
   108  	}
   109  
   110  	// create window
   111  	hwnd, err := _CreateWindowExW(
   112  		0,
   113  		win.className,
   114  		nil, // window title
   115  		_WS_OVERLAPPEDWINDOW,
   116  		_CW_USEDEFAULT, _CW_USEDEFAULT, // x,y
   117  		// TODO: failing, giving bad rectangle with a fixed integer
   118  		//_CW_USEDEFAULT, _CW_USEDEFAULT, // w,h
   119  		500, 500, // w,h
   120  		0, 0, win.instance, 0,
   121  	)
   122  	if err != nil {
   123  		return fmt.Errorf("createwindow: %w", err)
   124  	}
   125  	win.hwnd = hwnd
   126  
   127  	_ = _ShowWindowAsync(win.hwnd, _SW_SHOWDEFAULT)
   128  	//_ = _UpdateWindow(win.hwnd)
   129  
   130  	// cursor: don't set cursor at class struct to avoid auto-restoration
   131  	if err := win.ostSetCursor(event.NoneCursor); err != nil {
   132  		return err
   133  	}
   134  
   135  	if err := win.startDragDrop(); err != nil {
   136  		fmt.Printf("error: dragdrop: %v\n", err)
   137  	}
   138  
   139  	return nil
   140  }
   141  
   142  //----------
   143  
   144  // Called from OS thread.
   145  func (win *Window) ostMsgLoop() {
   146  	// ensure it is instantiated (avoid garbage collection when going throught windows functions that would make go's gc collect the variable)
   147  	msg := _Msg{}
   148  
   149  	for {
   150  		ok, err := win.nextMsg(&msg)
   151  		if err != nil {
   152  			win.events <- err
   153  		}
   154  		if !ok {
   155  			break
   156  		}
   157  		win.handleMsg(&msg)
   158  	}
   159  }
   160  
   161  func (win *Window) nextMsg(msg *_Msg) (ok bool, _ error) {
   162  	*msg = _Msg{} // reset to zero
   163  
   164  	res, err := _GetMessageW(msg, win.hwnd, 0, 0) // wait for next msg
   165  	if err != nil {
   166  		// improve error
   167  		if err2 := windows.GetLastError(); err2 != nil {
   168  			err = fmt.Errorf("%v: %v", err, err2)
   169  		}
   170  		return false, err
   171  	}
   172  	quit := res == 0
   173  	if quit {
   174  		return false, nil
   175  	}
   176  	return true, nil
   177  }
   178  
   179  //----------
   180  
   181  func (win *Window) NextEvent() (event.Event, bool) {
   182  	ev, ok := <-win.events
   183  	return ev, ok
   184  }
   185  
   186  //----------
   187  
   188  func (win *Window) handleMsg(msg *_Msg) {
   189  	// not used: virtual keys are translated ondemand (keydown/keyup)
   190  	//_ = _TranslateMessage(msg)
   191  
   192  	// dispatch to hwnd.class.LpfnWndProc (runs win.wndProcCallback)
   193  	_ = _DispatchMessageW(msg)
   194  }
   195  
   196  // Called by _DispatchMessageW() and via WndClassExW.
   197  func (win *Window) wndProcCallback(hwnd windows.Handle, msg uint32, wParam, lParam uintptr) uintptr {
   198  	m := &_Msg{
   199  		HWnd:   hwnd,
   200  		Msg:    msg,
   201  		WParam: wParam,
   202  		LParam: lParam,
   203  	}
   204  	return win.handleMsg2(m)
   205  }
   206  
   207  func (win *Window) handleMsg2(msg *_Msg) uintptr {
   208  	switch _wm(msg.Msg) {
   209  	case _WM_CREATE:
   210  		csw := (*_CreateStructW)(unsafe.Pointer(msg.LParam))
   211  		w, h := int(csw.CX), int(csw.CY)
   212  		r := image.Rect(0, 0, w, h)
   213  		win.events <- &event.WindowResize{Rect: r}
   214  	case _WM_SIZE:
   215  		w, h := unpackLowHigh(uint32(msg.LParam))
   216  		r := image.Rect(0, 0, w, h)
   217  		win.events <- &event.WindowResize{Rect: r}
   218  
   219  	// TODO: issues with window size (cut a little on side, frame size?)
   220  	//case _WM_WINDOWPOSCHANGED:
   221  	//	wp := (*_WindowPos)(unsafe.Pointer(msg.LParam))
   222  	//	w, h := int(wp.CX), int(wp.CY)
   223  	//	r := image.Rect(0, 0, w, h)
   224  	//	win.events <- &event.WindowResize{Rect: r}
   225  	//	return 0 // return zero if processed
   226  
   227  	case _WM_PAINT:
   228  		// validate region or it keeps sending msgs(?)
   229  		// always validate, the paint is done by AppPutImage msg
   230  		//_ = _ValidateRect(msg.HWnd, nil)
   231  		win.events <- &event.WindowExpose{}
   232  		//return 0 // return zero if processed (won't validate region!)
   233  	//case _WM_NCPAINT:
   234  	case _WM_ERASEBKGND: // handle to avoid flicker
   235  		// it does not erase bg
   236  		return 0 // return non-zero if it erases the background
   237  
   238  	case _WM_SETCURSOR:
   239  		l, _ := unpackLowHigh(uint32(msg.LParam))
   240  		if l == _HTCLIENT { // set only if in the client area (not the frame)
   241  			if err := win.loadAndSetCursor(win.cursors.currentId); err != nil {
   242  				win.events <- err
   243  			}
   244  			return 1 // return TRUE to halt further processing
   245  		}
   246  
   247  	case _WM_CLOSE: // window close button
   248  		win.events <- &event.WindowClose{}
   249  	case _WM_DESTROY: // possibly app request to close
   250  		_PostQuitMessage(0)
   251  	case _WM_SYSCOMMAND:
   252  		c := int(msg.WParam)
   253  		switch c {
   254  		case _SC_CLOSE:
   255  			win.events <- &event.WindowClose{}
   256  		}
   257  
   258  	//case _WM_CHAR: // not used: making the translation at keydown
   259  
   260  	case _WM_KEYDOWN:
   261  		win.events <- win.keyUpDown(msg, false)
   262  	case _WM_KEYUP:
   263  		win.events <- win.keyUpDown(msg, true)
   264  
   265  	case _WM_MOUSEMOVE:
   266  		win.events <- win.mouseMove(msg)
   267  	case _WM_LBUTTONDOWN:
   268  		win.events <- win.mouseButton(msg, event.ButtonLeft, false)
   269  	case _WM_LBUTTONUP:
   270  		win.events <- win.mouseButton(msg, event.ButtonLeft, true)
   271  	case _WM_RBUTTONDOWN:
   272  		win.events <- win.mouseButton(msg, event.ButtonRight, false)
   273  	case _WM_RBUTTONUP:
   274  		win.events <- win.mouseButton(msg, event.ButtonRight, true)
   275  	case _WM_MBUTTONDOWN:
   276  		win.events <- win.mouseButton(msg, event.ButtonMiddle, false)
   277  	case _WM_MBUTTONUP:
   278  		win.events <- win.mouseButton(msg, event.ButtonMiddle, true)
   279  	case _WM_MOUSEWHEEL:
   280  		_, h := unpackLowHigh(uint32(msg.WParam))
   281  		up := int16(h) > 0
   282  		b := event.ButtonWheelDown
   283  		if up {
   284  			b = event.ButtonWheelUp
   285  		}
   286  		// TODO: necessary?
   287  		// send two events to simulate down/up
   288  		win.events <- win.mouseButton(msg, b, false)
   289  		win.events <- win.mouseButton(msg, b, true)
   290  
   291  	case _WM_DROPFILES:
   292  		hDrop := msg.WParam
   293  		ev, ok, err := win.dndMan.HandleDrop(hDrop)
   294  		if err != nil {
   295  			win.events <- err
   296  		} else if ok {
   297  			win.events <- ev
   298  		}
   299  		return 0 // return 0 if processed
   300  
   301  	case _WM_APP:
   302  		id := int(msg.WParam)
   303  		win.handleAppMsg(id, msg)
   304  	}
   305  
   306  	return defaultMsgHandler(msg)
   307  }
   308  
   309  //----------
   310  
   311  func (win *Window) handleAppMsg(id int, msg *_Msg) {
   312  	req, appData, err := win.readAppMsgReq(id)
   313  	if err != nil {
   314  		win.events <- err
   315  		return
   316  	}
   317  	err = win.handleRequest(req, msg)
   318  	_ = appData.ReqErr.Set(err)
   319  }
   320  
   321  func (win *Window) handleRequest(req event.Request, msg *_Msg) error {
   322  	switch r := req.(type) {
   323  	case *event.ReqClose:
   324  		return win.ostClose()
   325  	case *event.ReqWindowSetName:
   326  		return win.ostSetWindowName(r.Name)
   327  	// Disabled: handled at Request() without roundtrip
   328  	//case *event.ReqImage:
   329  	//	r.ReplyImg = win.img
   330  	//	return nil
   331  	case *event.ReqImagePut:
   332  		return win.ostPaintImg(r.Rect)
   333  	case *event.ReqImageResize:
   334  		return win.ostResizeImage(r.Rect)
   335  	case *event.ReqCursorSet:
   336  		return win.ostSetCursor(r.Cursor)
   337  	case *event.ReqPointerQuery:
   338  		p, err := win.ostQueryPointer()
   339  		r.ReplyP = p
   340  		return err
   341  	case *event.ReqPointerWarp:
   342  		return win.ostWarpPointer(r.P)
   343  	case *event.ReqClipboardDataGet:
   344  		if r.Index == event.CIClipboard {
   345  			s, err := win.ostGetClipboardData()
   346  			r.ReplyS = s
   347  			return err
   348  		}
   349  		return nil
   350  	case *event.ReqClipboardDataSet:
   351  		if r.Index == event.CIClipboard {
   352  			return win.ostSetClipboardData(r.Str)
   353  		}
   354  		return nil
   355  	default:
   356  		panic(fmt.Sprintf("todo: %T", req))
   357  	}
   358  }
   359  
   360  //----------
   361  
   362  func (win *Window) Request(req event.Request) error {
   363  	// handle now without the appmsg roundtrip (performance)
   364  	switch r := req.(type) {
   365  	case *event.ReqImage:
   366  		r.ReplyImg = win.img
   367  		return nil
   368  	}
   369  
   370  	return win.runAppMsgReq(req)
   371  }
   372  
   373  func (win *Window) runAppMsgReq(req event.Request) error {
   374  	appData := NewAppData(req)
   375  	appData.ReqErr.Start(3 * time.Second)
   376  	if err := win.postAppMsg(appData); err != nil {
   377  		appData.ReqErr.Cancel()
   378  		return err
   379  	}
   380  	reqErrV, err := appData.ReqErr.WaitForSet()
   381  	if err != nil {
   382  		err = fmt.Errorf("win appdata: %T, %w", req, err)
   383  		return err
   384  	}
   385  	if err, ok := reqErrV.(error); ok {
   386  		err = fmt.Errorf("win appdata: %T, %w", req, err)
   387  		return err
   388  	}
   389  	return nil
   390  }
   391  
   392  func (win *Window) readAppMsgReq(id int) (event.Request, *AppData, error) {
   393  	data, err := win.getAppMsgData(id)
   394  	if err != nil {
   395  		return nil, nil, err
   396  	}
   397  	appData := data.(*AppData)
   398  	return appData.Value.(event.Request), appData, nil
   399  }
   400  
   401  //----------
   402  
   403  func (win *Window) keyUpDown(msg *_Msg, up bool) interface{} {
   404  	p, err := win.ostQueryPointer()
   405  	if err != nil {
   406  		return err
   407  	}
   408  
   409  	// TODO: use scancode instead of regetting at virtualkeyrune?
   410  	//kd := keyData(uint32(msg.LParam)) // has scancode
   411  
   412  	vkey := uint32(msg.WParam)
   413  	kstate := [256]byte{}
   414  	_ = _GetKeyboardState(&kstate)
   415  	ru, _ := vkeyRune(vkey, &kstate)
   416  	ks := translateVKeyToEventKeySym(vkey, ru)
   417  	km := translateKStateToEventKeyModifiers(&kstate)
   418  	bs := translateKStateToEventMouseButtons(&kstate)
   419  
   420  	var ev interface{}
   421  	if up {
   422  		ev = &event.KeyUp{p, ks, km, bs, ru}
   423  	} else {
   424  		ev = &event.KeyDown{p, ks, km, bs, ru}
   425  	}
   426  	return &event.WindowInput{Point: p, Event: ev}
   427  }
   428  
   429  func (win *Window) mouseMove(msg *_Msg) interface{} {
   430  	p := paramToPoint(uint32(msg.LParam)) // window point
   431  
   432  	vkey := uint32(msg.WParam)
   433  	km := translateVKeyToEventKeyModifiers(vkey)
   434  	bs := translateVKeyToEventMouseButtons(vkey)
   435  
   436  	ev := &event.MouseMove{p, bs, km}
   437  	return &event.WindowInput{Point: p, Event: ev}
   438  }
   439  
   440  func (win *Window) mouseButton(msg *_Msg, b event.MouseButton, up bool) interface{} {
   441  	p := paramToPoint(uint32(msg.LParam)) // window point
   442  	// screen point if mousewheel
   443  	if msg.Msg == uint32(_WM_MOUSEWHEEL) {
   444  		p2, err := win.screenToWindowPoint(p)
   445  		if err != nil {
   446  			return err
   447  		}
   448  		p = p2
   449  	}
   450  
   451  	vkey := uint32(msg.WParam)
   452  	km := translateVKeyToEventKeyModifiers(vkey)
   453  	bs := translateVKeyToEventMouseButtons(vkey)
   454  
   455  	var ev interface{}
   456  	if up {
   457  		ev = &event.MouseUp{p, b, bs, km}
   458  	} else {
   459  		ev = &event.MouseDown{p, b, bs, km}
   460  	}
   461  	return &event.WindowInput{Point: p, Event: ev}
   462  }
   463  
   464  //----------
   465  
   466  func (win *Window) ostPaintImg(r image.Rectangle) error {
   467  	//return win.paintImgWithSetPixel()
   468  	return win.paintImgWithBitmap(r)
   469  }
   470  
   471  func (win *Window) paintImgWithSetPixel() error {
   472  	hdc, err := _GetDC(win.hwnd)
   473  	if err != nil {
   474  		return fmt.Errorf("paintimg: getdc: %w", err)
   475  	}
   476  	defer _ReleaseDC(win.hwnd, hdc)
   477  
   478  	r := win.img.Bounds()
   479  	for x := r.Min.X; x < r.Max.X; x++ {
   480  		for y := r.Min.Y; y < r.Max.Y; y++ {
   481  			c := win.img.At(x, y)
   482  			u := ColorRefFromImageColor(c)
   483  			if _, err := _SetPixel(hdc, x, y, u); err != nil {
   484  				return fmt.Errorf("setpixel: %w", err)
   485  			}
   486  		}
   487  	}
   488  	return nil
   489  }
   490  
   491  func (win *Window) paintImgWithBitmap(r image.Rectangle) error {
   492  	// get/release dc (beginpaint/endpaint won't work here)
   493  	hdc, err := _GetDC(win.hwnd)
   494  	if err != nil {
   495  		return fmt.Errorf("paintimg: getdc: %w", err)
   496  	}
   497  	defer _ReleaseDC(win.hwnd, hdc)
   498  
   499  	// memory dc
   500  	hdcMem, err := _CreateCompatibleDC(hdc)
   501  	if err != nil {
   502  		return err
   503  	}
   504  	defer _DeleteDC(hdcMem) // deleted by releasedc
   505  
   506  	//// map image to bitmap
   507  	//bm, err := win.buildBitmap()
   508  	//if err != nil {
   509  	//	return err
   510  	//}
   511  	//defer _DeleteObject(bm)
   512  	bm := win.bmH
   513  
   514  	// setup bitmap into memory dc
   515  	prev, err := _SelectObject(hdcMem, bm)
   516  	if err != nil {
   517  		return err
   518  	}
   519  	defer _SelectObject(hdcMem, prev)
   520  
   521  	// copy memory dc into dc
   522  	b := win.img.Bounds()
   523  	r2 := r.Intersect(b)
   524  	size2 := r2.Size()
   525  	if !_BitBlt(hdc,
   526  		int32(r2.Min.X), int32(r2.Min.Y),
   527  		int32(size2.X), int32(size2.Y),
   528  		hdcMem,
   529  		int32(r2.Min.X), int32(r2.Min.Y),
   530  		_SRCCOPY) {
   531  		return fmt.Errorf("bitblt: false")
   532  	}
   533  
   534  	return nil
   535  }
   536  
   537  //----------
   538  
   539  func (win *Window) buildBitmap(size image.Point) (bmH windows.Handle, bits *byte, _ error) {
   540  	bmi := _BitmapInfo{
   541  		BmiHeader: _BitmapInfoHeader{
   542  			BiSize:        uint32(unsafe.Sizeof(_BitmapInfoHeader{})),
   543  			BiWidth:       int32(size.X),
   544  			BiHeight:      -int32(size.Y), // negative to invert y
   545  			BiPlanes:      1,
   546  			BiBitCount:    32,
   547  			BiCompression: _BI_RGB,
   548  			BiSizeImage:   uint32(size.X * size.Y * 4),
   549  		},
   550  	}
   551  
   552  	bmH, err := _CreateDIBSection(0, &bmi, _DIB_RGB_COLORS, &bits, 0, 0)
   553  	if err != nil {
   554  		return 0, nil, err
   555  	}
   556  	return bmH, bits, nil
   557  }
   558  
   559  //func (win *Window) buildBitmap_() (bm windows.Handle, _ error) {
   560  //	// image data
   561  //	r := win.img.Bounds()
   562  //	size := r.Size()
   563  //	rgba := &win.img.(*imageutil.BGRA).RGBA
   564  //	//pixHeader := (*reflect.SliceHeader)(unsafe.Pointer(&rgba.Pix))
   565  //	//bits := pixHeader.Data
   566  //	bits := uintptr(unsafe.Pointer(&rgba.Pix[0]))
   567  
   568  //	//if bits >= math.MaxUint32 {
   569  //	//	return 0, fmt.Errorf("bad bits: %v", bits)
   570  //	//}
   571  
   572  //	////TODO: works, using createbitmap instead (simpler)
   573  //	//// map the image into a bitmap
   574  //	//bm0 := _Bitmap{
   575  //	//	BmType:       0, // must be zero
   576  //	//	BmWidth:      int32(size.X),
   577  //	//	BmHeight:     int32(size.Y),
   578  //	//	BmWidthBytes: int32(rgba.Stride),
   579  //	//	BmPlanes:     1,
   580  //	//	BmBitsPixel:  4 * 8,
   581  //	//	BmBits:       bits,
   582  //	//}
   583  //	//win.bm = &bm0
   584  //	//// bitmap handle
   585  //	//bm, err := _CreateBitmapIndirect(win.bm)
   586  
   587  //	// map the image into a bitmap
   588  //	bm, err := _CreateBitmap(int32(size.X), int32(size.Y), 1, 4*8, bits)
   589  
   590  //	// improve error
   591  //	if err != nil {
   592  //		err2 := windows.GetLastError()
   593  //		err = fmt.Errorf("buildbitmap: fail: %v, %v", err, err2)
   594  //	}
   595  //	return bm, err
   596  //}
   597  
   598  //----------
   599  
   600  func (win *Window) ostResizeImage(r image.Rectangle) error {
   601  	bmH, bits, err := win.buildBitmap(r.Size())
   602  	if err != nil {
   603  		return err
   604  	}
   605  	if win.bmH != 0 {
   606  		_DeleteObject(win.bmH) // delete old
   607  	}
   608  	win.bmH = bmH
   609  
   610  	// mask mem into a slice
   611  	nbytes := imageutil.BGRASize(&r)
   612  	h := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(bits)), Len: nbytes, Cap: nbytes}
   613  	buf := *(*[]byte)(unsafe.Pointer(&h))
   614  
   615  	win.img = imageutil.NewBGRAFromBuffer(buf, &r)
   616  
   617  	return nil
   618  }
   619  
   620  //----------
   621  
   622  func (win *Window) ostSetCursor(c event.Cursor) (err error) {
   623  	sc := func(cId int) {
   624  		err = win.loadAndSetCursor(cId)
   625  	}
   626  
   627  	switch c {
   628  	case event.NoneCursor:
   629  		// TODO: parent window cursor
   630  		//sc(0) // TODO: failing
   631  		sc(_IDC_ARROW)
   632  	case event.DefaultCursor:
   633  		sc(_IDC_ARROW)
   634  	case event.NSResizeCursor:
   635  		sc(_IDC_SIZENS)
   636  	case event.WEResizeCursor:
   637  		sc(_IDC_SIZEWE)
   638  	case event.CloseCursor:
   639  		//sc(_IDC_HAND)
   640  		sc(_IDC_CROSS)
   641  	case event.MoveCursor:
   642  		sc(_IDC_SIZEALL)
   643  	case event.PointerCursor:
   644  		//sc(_IDC_HAND)
   645  		sc(_IDC_UPARROW)
   646  	case event.BeamCursor:
   647  		sc(_IDC_IBEAM)
   648  	case event.WaitCursor:
   649  		sc(_IDC_WAIT)
   650  	}
   651  	return
   652  }
   653  
   654  func (win *Window) loadAndSetCursor(cursorId int) error {
   655  	cursorHandle, err := win.loadCursor(cursorId)
   656  	if err != nil {
   657  		return err
   658  	}
   659  	_ = _SetCursor(cursorHandle) // returns prevCursorH
   660  	win.cursors.currentId = cursorId
   661  	return nil
   662  }
   663  
   664  func (win *Window) loadCursor(cursorId int) (windows.Handle, error) {
   665  	cursorHandle, ok := win.cursors.cache[cursorId]
   666  	if !ok {
   667  		ch, err := win.loadCursor2(cursorId)
   668  		if err != nil {
   669  			return 0, err
   670  		}
   671  		win.cursors.cache[cursorId] = ch
   672  		cursorHandle = ch
   673  	}
   674  	return cursorHandle, nil
   675  }
   676  
   677  func (win *Window) loadCursor2(c int) (windows.Handle, error) {
   678  	cursorId := packLowHigh(uint16(c), 0)
   679  
   680  	// TODO: failing on windows 10 with instance=0
   681  	//cursor, err := _LoadImageW(
   682  	//	0, // use nil instance not the win.instance (won't find resource)
   683  	//	uintptr(cursorId),
   684  	//	_IMAGE_CURSOR,
   685  	//	0, 0, // w,h: use zeros with _LR_DEFAULTSIZE
   686  	//	_LR_DEFAULTSIZE)
   687  
   688  	//return 0, nil
   689  
   690  	// Alternative func superseeded by LoadImageW(...)
   691  	//cursor, err := _LoadCursorW(win.instance, cursorId)
   692  	cursor, err := _LoadCursorW(0, cursorId)
   693  
   694  	if err != nil {
   695  		return 0, fmt.Errorf("loadimage: %v: %v\n", c, err)
   696  	}
   697  	return cursor, nil
   698  }
   699  
   700  //----------
   701  
   702  func (win *Window) ostQueryPointer() (image.Point, error) {
   703  	csp, err := win.cursorScreenPos()
   704  	if err != nil {
   705  		return image.ZP, err
   706  	}
   707  	return win.screenToWindowPoint(csp)
   708  }
   709  
   710  func (win *Window) ostWarpPointer(p image.Point) error {
   711  	wsp, err := win.windowScreenPos()
   712  	if err != nil {
   713  		return err
   714  	}
   715  	p2 := p.Add(wsp)
   716  	if !_SetCursorPos(int32(p2.X), int32(p2.Y)) {
   717  		return fmt.Errorf("setcursorpos: false")
   718  	}
   719  	return nil
   720  }
   721  
   722  //----------
   723  
   724  func (win *Window) ostGetClipboardData() (string, error) {
   725  	if !_OpenClipboard(0) {
   726  		return "", fmt.Errorf("openclipboard: false")
   727  	}
   728  	defer _CloseClipboard()
   729  
   730  	h, err := _GetClipboardData(_CF_UNICODETEXT)
   731  	if err != nil {
   732  		return "", fmt.Errorf("getclipboarddata: %v", err)
   733  	}
   734  
   735  	ptr, err := _GlobalLock(h)
   736  	if err != nil {
   737  		return "", fmt.Errorf("getclipboarddata: globallock: %v", err)
   738  	}
   739  	defer _GlobalUnlock(h)
   740  
   741  	// TODO: improve this, could crash
   742  	// translate ptr to []uint16
   743  	sh := reflect.SliceHeader{Data: ptr, Len: 5000, Cap: 5000}
   744  	buf := *(*[]uint16)(unsafe.Pointer(&sh))
   745  	// find string end (nil terminated)
   746  	for i, v := range buf {
   747  		if v == 0 {
   748  			buf = buf[:i]
   749  			break
   750  		}
   751  	}
   752  
   753  	s := windows.UTF16ToString(buf)
   754  	return s, nil
   755  }
   756  
   757  //----------
   758  
   759  func (win *Window) ostSetClipboardData(s string) error {
   760  	if !_OpenClipboard(0) {
   761  		return fmt.Errorf("openclipboard: false")
   762  	}
   763  	defer _CloseClipboard()
   764  
   765  	// translate string to utf16 (will include nil termination)
   766  	sl, err := windows.UTF16FromString(s)
   767  	if err != nil {
   768  		return err
   769  	}
   770  	// allocate memory for the clipboard
   771  	unit := int(unsafe.Sizeof(uint16(0)))
   772  	size := len(sl) * unit
   773  	h, err := _GlobalAlloc(_GMEM_MOVEABLE, uintptr(size))
   774  	if err != nil {
   775  		return err
   776  	}
   777  	// get handle pointer
   778  	ptr, err := _GlobalLock(h)
   779  	if err != nil {
   780  		return fmt.Errorf("getclipboarddata: globallock: %v", err)
   781  	}
   782  	defer _GlobalUnlock(h)
   783  	// mask pointer to slice
   784  	sh := reflect.SliceHeader{Data: ptr, Len: len(sl), Cap: len(sl)}
   785  	cbBuf := *(*[]uint16)(unsafe.Pointer(&sh))
   786  
   787  	// copy data to the allocated memory
   788  	copy(cbBuf, sl)
   789  
   790  	if _, err := _SetClipboardData(_CF_UNICODETEXT, h); err != nil {
   791  		return fmt.Errorf("setclipboarddata: %v", err)
   792  	}
   793  	return nil
   794  }
   795  
   796  //----------
   797  
   798  func (win *Window) cursorScreenPos() (image.Point, error) {
   799  	cp := _Point{}
   800  	if !_GetCursorPos(&cp) {
   801  		return image.ZP, fmt.Errorf("getcursorpos: false")
   802  	}
   803  	return cp.ToImagePoint(), nil
   804  }
   805  
   806  func (win *Window) screenToWindowPoint(sp image.Point) (image.Point, error) {
   807  	wsp, err := win.windowScreenPos()
   808  	if err != nil {
   809  		return image.ZP, err
   810  	}
   811  	return sp.Sub(wsp), nil
   812  }
   813  
   814  func (win *Window) windowScreenPos() (image.Point, error) {
   815  	// NOTE: returns window area (need client area)
   816  	//wr := _Rect{}
   817  	//if !_GetWindowRect(win.hwnd, &wr) {
   818  	//	return image.ZP, fmt.Errorf("getwindowrect: false")
   819  	//}
   820  	//return wr.ToImageRectangle().Min, nil
   821  
   822  	// NOTE: works, but apparently has issues on right-to-left systems...
   823  	//p := _Point{0, 0}
   824  	//if !_ClientToScreen(win.hwnd, &p) {
   825  	//	return image.ZP, fmt.Errorf("clienttoscreen: false")
   826  	//}
   827  	//return p.ToImagePoint(), nil
   828  
   829  	p := _Point{0, 0}
   830  	_ = _MapWindowPoints(win.hwnd, 0, &p, 1)
   831  	return p.ToImagePoint(), nil
   832  }
   833  
   834  //func (win *Window) getWindowRectangle() (image.Rectangle, error) {
   835  //	r := _Rect{}
   836  //	if !_GetWindowRect(win.hwnd, &r) {
   837  //		return image.ZR, fmt.Errorf("getwindowrect: false")
   838  //	}
   839  //	return r.ToImageRectangle(), nil
   840  //}
   841  
   842  //----------
   843  
   844  func (win *Window) ostSetWindowName(s string) error {
   845  	u := UTF16PtrFromString(s)
   846  	ok := _SetWindowTextW(win.hwnd, u)
   847  	if !ok {
   848  		return fmt.Errorf("SetWindowTextW failed")
   849  	}
   850  	return nil
   851  }
   852  
   853  //----------
   854  
   855  func (win *Window) postAppMsg(v interface{}) error {
   856  	win.postM.Lock()
   857  	defer win.postM.Unlock()
   858  	id := win.postM.id
   859  	win.postM.m[id] = v
   860  	if !_PostMessageW(win.hwnd, uint32(_WM_APP), uintptr(id), 0) {
   861  		delete(win.postM.m, id)
   862  		return fmt.Errorf("postevent: failed to post")
   863  	}
   864  	win.postM.id++
   865  	return nil
   866  }
   867  
   868  func (win *Window) getAppMsgData(id int) (interface{}, error) {
   869  	win.postM.Lock()
   870  	defer win.postM.Unlock()
   871  	v, ok := win.postM.m[id]
   872  	if !ok {
   873  		return nil, fmt.Errorf("postevent map: id not found: %v", id)
   874  	}
   875  	delete(win.postM.m, id)
   876  	return v, nil
   877  }
   878  
   879  //----------
   880  
   881  func (win *Window) ostClose() error {
   882  	defer win.stopDragDrop()
   883  	if !_DestroyWindow(win.hwnd) { // sends _WM_DESTROY
   884  		return fmt.Errorf("destroywindow: false")
   885  	}
   886  	return nil
   887  }
   888  
   889  //----------
   890  
   891  func (win *Window) startDragDrop() error {
   892  	_DragAcceptFiles(win.hwnd, true)
   893  	return nil
   894  }
   895  
   896  func (win *Window) stopDragDrop() {
   897  	_DragAcceptFiles(win.hwnd, false)
   898  }
   899  
   900  //----------
   901  
   902  type AppData struct {
   903  	ReqErr *syncutil.WaitForSet
   904  	Value  interface{}
   905  }
   906  
   907  func NewAppData(v interface{}) *AppData {
   908  	return &AppData{syncutil.NewWaitForSet(), v}
   909  }
   910  
   911  //----------
   912  
   913  func defaultMsgHandler(msg *_Msg) uintptr {
   914  	return _DefWindowProcW(msg.HWnd, msg.Msg, msg.WParam, msg.LParam)
   915  }
   916  
   917  //----------
   918  
   919  func paramToPoint(param uint32) image.Point {
   920  	x, y := unpackLowHigh(param)
   921  	return image.Point{X: x, Y: y}
   922  }
   923  
   924  //----------
   925  
   926  func vkeyRune(vkey uint32, kstate *[256]byte) (rune, bool) {
   927  	scanCode := _MapVirtualKeyW(vkey, _MAPVK_VK_TO_VSC)
   928  	wFlags := uint32(0) // 2: windows 10 no keyb state?
   929  	var res uint32      // TODO: low/high byte order?
   930  	resPtr := (*uint16)(unsafe.Pointer(&res))
   931  	v := _ToUnicode(vkey, scanCode, kstate, resPtr, 2, wFlags)
   932  	isDeadKey := v == -1
   933  	return rune(res), isDeadKey
   934  }
   935  
   936  //----------
   937  
   938  func hideConsole() bool {
   939  	// build notes that affect the console state
   940  	// # shows one console window (will be hidden, but makes a flash)
   941  	// $ go build
   942  	// # hides the console window, but cmds will popup consoles
   943  	// $ go build -ldflags -H=windowsgui
   944  
   945  	console := _GetConsoleWindow()
   946  	if console == 0 {
   947  		return false // no console attached
   948  	}
   949  
   950  	pid := uint32(0)                             // process id
   951  	_ = _GetWindowThreadProcessId(console, &pid) // returns thread id
   952  	if pid == _GetCurrentProcessId() {
   953  		return _ShowWindowAsync(console, _SW_HIDE)
   954  	}
   955  	return false
   956  }