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 }