os_windows.go 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995
  1. // SPDX-License-Identifier: Unlicense OR MIT
  2. package app
  3. import (
  4. "errors"
  5. "fmt"
  6. "image"
  7. "io"
  8. "runtime"
  9. "sort"
  10. "strings"
  11. "sync"
  12. "time"
  13. "unicode"
  14. "unicode/utf8"
  15. "unsafe"
  16. syscall "golang.org/x/sys/windows"
  17. "gioui.org/app/internal/windows"
  18. "gioui.org/op"
  19. "gioui.org/unit"
  20. gowindows "golang.org/x/sys/windows"
  21. "gioui.org/f32"
  22. "gioui.org/io/event"
  23. "gioui.org/io/key"
  24. "gioui.org/io/pointer"
  25. "gioui.org/io/system"
  26. "gioui.org/io/transfer"
  27. )
  28. type Win32ViewEvent struct {
  29. HWND uintptr
  30. }
  31. type window struct {
  32. hwnd syscall.Handle
  33. hdc syscall.Handle
  34. w *callbacks
  35. pointerBtns pointer.Buttons
  36. // cursorIn tracks whether the cursor was inside the window according
  37. // to the most recent WM_SETCURSOR.
  38. cursorIn bool
  39. cursor syscall.Handle
  40. animating bool
  41. borderSize image.Point
  42. config Config
  43. // frameDims stores the last seen window frame width and height.
  44. frameDims image.Point
  45. loop *eventLoop
  46. }
  47. const _WM_WAKEUP = windows.WM_USER + iota
  48. type gpuAPI struct {
  49. priority int
  50. initializer func(w *window) (context, error)
  51. }
  52. // drivers is the list of potential Context implementations.
  53. var drivers []gpuAPI
  54. // winMap maps win32 HWNDs to *windows.
  55. var winMap sync.Map
  56. // iconID is the ID of the icon in the resource file.
  57. const iconID = 1
  58. var resources struct {
  59. once sync.Once
  60. // handle is the module handle from GetModuleHandle.
  61. handle syscall.Handle
  62. // class is the Gio window class from RegisterClassEx.
  63. class uint16
  64. // cursor is the arrow cursor resource.
  65. cursor syscall.Handle
  66. }
  67. func osMain() {
  68. select {}
  69. }
  70. func newWindow(win *callbacks, options []Option) {
  71. done := make(chan struct{})
  72. go func() {
  73. // GetMessage and PeekMessage can filter on a window HWND, but
  74. // then thread-specific messages such as WM_QUIT are ignored.
  75. // Instead lock the thread so window messages arrive through
  76. // unfiltered GetMessage calls.
  77. runtime.LockOSThread()
  78. w := &window{
  79. w: win,
  80. }
  81. w.loop = newEventLoop(w.w, w.wakeup)
  82. w.w.SetDriver(w)
  83. err := w.init()
  84. done <- struct{}{}
  85. if err != nil {
  86. w.ProcessEvent(DestroyEvent{Err: err})
  87. return
  88. }
  89. winMap.Store(w.hwnd, w)
  90. defer winMap.Delete(w.hwnd)
  91. w.Configure(options)
  92. w.ProcessEvent(Win32ViewEvent{HWND: uintptr(w.hwnd)})
  93. windows.SetForegroundWindow(w.hwnd)
  94. windows.SetFocus(w.hwnd)
  95. // Since the window class for the cursor is null,
  96. // set it here to show the cursor.
  97. w.SetCursor(pointer.CursorDefault)
  98. w.runLoop()
  99. }()
  100. <-done
  101. }
  102. // initResources initializes the resources global.
  103. func initResources() error {
  104. windows.SetProcessDPIAware()
  105. hInst, err := windows.GetModuleHandle()
  106. if err != nil {
  107. return err
  108. }
  109. resources.handle = hInst
  110. c, err := windows.LoadCursor(windows.IDC_ARROW)
  111. if err != nil {
  112. return err
  113. }
  114. resources.cursor = c
  115. icon, _ := windows.LoadImage(hInst, iconID, windows.IMAGE_ICON, 0, 0, windows.LR_DEFAULTSIZE|windows.LR_SHARED)
  116. wcls := windows.WndClassEx{
  117. CbSize: uint32(unsafe.Sizeof(windows.WndClassEx{})),
  118. Style: windows.CS_HREDRAW | windows.CS_VREDRAW | windows.CS_OWNDC,
  119. LpfnWndProc: syscall.NewCallback(windowProc),
  120. HInstance: hInst,
  121. HIcon: icon,
  122. LpszClassName: syscall.StringToUTF16Ptr("GioWindow"),
  123. }
  124. cls, err := windows.RegisterClassEx(&wcls)
  125. if err != nil {
  126. return err
  127. }
  128. resources.class = cls
  129. return nil
  130. }
  131. const dwExStyle = windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE
  132. func (w *window) init() error {
  133. var resErr error
  134. resources.once.Do(func() {
  135. resErr = initResources()
  136. })
  137. if resErr != nil {
  138. return resErr
  139. }
  140. const dwStyle = windows.WS_OVERLAPPEDWINDOW
  141. hwnd, err := windows.CreateWindowEx(
  142. dwExStyle,
  143. resources.class,
  144. "",
  145. dwStyle|windows.WS_CLIPSIBLINGS|windows.WS_CLIPCHILDREN,
  146. windows.CW_USEDEFAULT, windows.CW_USEDEFAULT,
  147. windows.CW_USEDEFAULT, windows.CW_USEDEFAULT,
  148. 0,
  149. 0,
  150. resources.handle,
  151. 0)
  152. if err != nil {
  153. return err
  154. }
  155. w.hdc, err = windows.GetDC(hwnd)
  156. if err != nil {
  157. windows.DestroyWindow(hwnd)
  158. return err
  159. }
  160. w.hwnd = hwnd
  161. return nil
  162. }
  163. // update handles changes done by the user, and updates the configuration.
  164. // It reads the window style and size/position and updates w.config.
  165. // If anything has changed it emits a ConfigEvent to notify the application.
  166. func (w *window) update() {
  167. p := windows.GetWindowPlacement(w.hwnd)
  168. if !p.IsMinimized() {
  169. r := windows.GetWindowRect(w.hwnd)
  170. cr := windows.GetClientRect(w.hwnd)
  171. w.config.Size = image.Point{
  172. X: int(cr.Right - cr.Left),
  173. Y: int(cr.Bottom - cr.Top),
  174. }
  175. w.frameDims = image.Point{
  176. X: int(r.Right - r.Left),
  177. Y: int(r.Bottom - r.Top),
  178. }.Sub(w.config.Size)
  179. }
  180. w.borderSize = image.Pt(
  181. windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
  182. windows.GetSystemMetrics(windows.SM_CYSIZEFRAME),
  183. )
  184. style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
  185. switch {
  186. case p.IsMaximized() && style&windows.WS_OVERLAPPEDWINDOW != 0:
  187. w.config.Mode = Maximized
  188. case p.IsMaximized():
  189. w.config.Mode = Fullscreen
  190. default:
  191. w.config.Mode = Windowed
  192. }
  193. w.ProcessEvent(ConfigEvent{Config: w.config})
  194. w.draw(true)
  195. }
  196. func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
  197. win, exists := winMap.Load(hwnd)
  198. if !exists {
  199. return windows.DefWindowProc(hwnd, msg, wParam, lParam)
  200. }
  201. w := win.(*window)
  202. switch msg {
  203. case windows.WM_UNICHAR:
  204. if wParam == windows.UNICODE_NOCHAR {
  205. // Tell the system that we accept WM_UNICHAR messages.
  206. return windows.TRUE
  207. }
  208. fallthrough
  209. case windows.WM_CHAR:
  210. if r := rune(wParam); unicode.IsPrint(r) {
  211. w.w.EditorInsert(string(r))
  212. }
  213. // The message is processed.
  214. return windows.TRUE
  215. case windows.WM_DPICHANGED:
  216. // Let Windows know we're prepared for runtime DPI changes.
  217. return windows.TRUE
  218. case windows.WM_ERASEBKGND:
  219. // Avoid flickering between GPU content and background color.
  220. return windows.TRUE
  221. case windows.WM_KEYDOWN, windows.WM_KEYUP, windows.WM_SYSKEYDOWN, windows.WM_SYSKEYUP:
  222. if n, ok := convertKeyCode(wParam); ok {
  223. e := key.Event{
  224. Name: n,
  225. Modifiers: getModifiers(),
  226. State: key.Press,
  227. }
  228. if msg == windows.WM_KEYUP || msg == windows.WM_SYSKEYUP {
  229. e.State = key.Release
  230. }
  231. w.ProcessEvent(e)
  232. if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) {
  233. // Reserve F10 for ourselves, and don't let it open the system menu. Other Windows programs
  234. // such as cmd.exe and graphical debuggers also reserve F10.
  235. return 0
  236. }
  237. }
  238. case windows.WM_LBUTTONDOWN:
  239. w.pointerButton(pointer.ButtonPrimary, true, lParam, getModifiers())
  240. case windows.WM_LBUTTONUP:
  241. w.pointerButton(pointer.ButtonPrimary, false, lParam, getModifiers())
  242. case windows.WM_RBUTTONDOWN:
  243. w.pointerButton(pointer.ButtonSecondary, true, lParam, getModifiers())
  244. case windows.WM_RBUTTONUP:
  245. w.pointerButton(pointer.ButtonSecondary, false, lParam, getModifiers())
  246. case windows.WM_MBUTTONDOWN:
  247. w.pointerButton(pointer.ButtonTertiary, true, lParam, getModifiers())
  248. case windows.WM_MBUTTONUP:
  249. w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers())
  250. case windows.WM_CANCELMODE:
  251. w.ProcessEvent(pointer.Event{
  252. Kind: pointer.Cancel,
  253. })
  254. case windows.WM_SETFOCUS:
  255. w.config.Focused = true
  256. w.ProcessEvent(ConfigEvent{Config: w.config})
  257. case windows.WM_KILLFOCUS:
  258. w.config.Focused = false
  259. w.ProcessEvent(ConfigEvent{Config: w.config})
  260. case windows.WM_NCHITTEST:
  261. if w.config.Decorated {
  262. // Let the system handle it.
  263. break
  264. }
  265. x, y := coordsFromlParam(lParam)
  266. np := windows.Point{X: int32(x), Y: int32(y)}
  267. windows.ScreenToClient(w.hwnd, &np)
  268. return w.hitTest(int(np.X), int(np.Y))
  269. case windows.WM_MOUSEMOVE:
  270. x, y := coordsFromlParam(lParam)
  271. p := f32.Point{X: float32(x), Y: float32(y)}
  272. w.ProcessEvent(pointer.Event{
  273. Kind: pointer.Move,
  274. Source: pointer.Mouse,
  275. Position: p,
  276. Buttons: w.pointerBtns,
  277. Time: windows.GetMessageTime(),
  278. Modifiers: getModifiers(),
  279. })
  280. case windows.WM_MOUSEWHEEL:
  281. w.scrollEvent(wParam, lParam, false, getModifiers())
  282. case windows.WM_MOUSEHWHEEL:
  283. w.scrollEvent(wParam, lParam, true, getModifiers())
  284. case windows.WM_DESTROY:
  285. w.ProcessEvent(Win32ViewEvent{})
  286. w.ProcessEvent(DestroyEvent{})
  287. w.w = nil
  288. if w.hdc != 0 {
  289. windows.ReleaseDC(w.hdc)
  290. w.hdc = 0
  291. }
  292. // The system destroys the HWND for us.
  293. w.hwnd = 0
  294. windows.PostQuitMessage(0)
  295. return 0
  296. case windows.WM_NCCALCSIZE:
  297. if w.config.Decorated {
  298. // Let Windows handle decorations.
  299. break
  300. }
  301. // No client areas; we draw decorations ourselves.
  302. if wParam != 1 {
  303. return 0
  304. }
  305. // lParam contains an NCCALCSIZE_PARAMS for us to adjust.
  306. place := windows.GetWindowPlacement(w.hwnd)
  307. if !place.IsMaximized() {
  308. // Nothing do adjust.
  309. return 0
  310. }
  311. // Adjust window position to avoid the extra padding in maximized
  312. // state. See https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543.
  313. // Note that trying to do the adjustment in WM_GETMINMAXINFO is ignored by Windows.
  314. szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(lParam))
  315. mi := windows.GetMonitorInfo(w.hwnd)
  316. szp.Rgrc[0] = mi.WorkArea
  317. return 0
  318. case windows.WM_PAINT:
  319. w.draw(true)
  320. case windows.WM_STYLECHANGED:
  321. w.update()
  322. case windows.WM_WINDOWPOSCHANGED:
  323. w.update()
  324. case windows.WM_SIZE:
  325. w.update()
  326. case windows.WM_GETMINMAXINFO:
  327. mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam))
  328. var frameDims image.Point
  329. if w.config.Decorated {
  330. frameDims = w.frameDims
  331. }
  332. if p := w.config.MinSize; p.X > 0 || p.Y > 0 {
  333. p = p.Add(frameDims)
  334. mm.PtMinTrackSize = windows.Point{
  335. X: int32(p.X),
  336. Y: int32(p.Y),
  337. }
  338. }
  339. if p := w.config.MaxSize; p.X > 0 || p.Y > 0 {
  340. p = p.Add(frameDims)
  341. mm.PtMaxTrackSize = windows.Point{
  342. X: int32(p.X),
  343. Y: int32(p.Y),
  344. }
  345. }
  346. return 0
  347. case windows.WM_SETCURSOR:
  348. w.cursorIn = (lParam & 0xffff) == windows.HTCLIENT
  349. if w.cursorIn {
  350. windows.SetCursor(w.cursor)
  351. return windows.TRUE
  352. }
  353. case _WM_WAKEUP:
  354. w.loop.Wakeup()
  355. w.loop.FlushEvents()
  356. case windows.WM_IME_STARTCOMPOSITION:
  357. imc := windows.ImmGetContext(w.hwnd)
  358. if imc == 0 {
  359. return windows.TRUE
  360. }
  361. defer windows.ImmReleaseContext(w.hwnd, imc)
  362. sel := w.w.EditorState().Selection
  363. caret := sel.Transform.Transform(sel.Caret.Pos.Add(f32.Pt(0, sel.Caret.Descent)))
  364. icaret := image.Pt(int(caret.X+.5), int(caret.Y+.5))
  365. windows.ImmSetCompositionWindow(imc, icaret.X, icaret.Y)
  366. windows.ImmSetCandidateWindow(imc, icaret.X, icaret.Y)
  367. case windows.WM_IME_COMPOSITION:
  368. imc := windows.ImmGetContext(w.hwnd)
  369. if imc == 0 {
  370. return windows.TRUE
  371. }
  372. defer windows.ImmReleaseContext(w.hwnd, imc)
  373. state := w.w.EditorState()
  374. rng := state.compose
  375. if rng.Start == -1 {
  376. rng = state.Selection.Range
  377. }
  378. if rng.Start > rng.End {
  379. rng.Start, rng.End = rng.End, rng.Start
  380. }
  381. var replacement string
  382. switch {
  383. case lParam&windows.GCS_RESULTSTR != 0:
  384. replacement = windows.ImmGetCompositionString(imc, windows.GCS_RESULTSTR)
  385. case lParam&windows.GCS_COMPSTR != 0:
  386. replacement = windows.ImmGetCompositionString(imc, windows.GCS_COMPSTR)
  387. }
  388. end := rng.Start + utf8.RuneCountInString(replacement)
  389. w.w.EditorReplace(rng, replacement)
  390. state = w.w.EditorState()
  391. comp := key.Range{
  392. Start: rng.Start,
  393. End: end,
  394. }
  395. if lParam&windows.GCS_DELTASTART != 0 {
  396. start := windows.ImmGetCompositionValue(imc, windows.GCS_DELTASTART)
  397. comp.Start = state.RunesIndex(state.UTF16Index(comp.Start) + start)
  398. }
  399. w.w.SetComposingRegion(comp)
  400. pos := end
  401. if lParam&windows.GCS_CURSORPOS != 0 {
  402. rel := windows.ImmGetCompositionValue(imc, windows.GCS_CURSORPOS)
  403. pos = state.RunesIndex(state.UTF16Index(rng.Start) + rel)
  404. }
  405. w.w.SetEditorSelection(key.Range{Start: pos, End: pos})
  406. return windows.TRUE
  407. case windows.WM_IME_ENDCOMPOSITION:
  408. w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
  409. return windows.TRUE
  410. }
  411. return windows.DefWindowProc(hwnd, msg, wParam, lParam)
  412. }
  413. func getModifiers() key.Modifiers {
  414. var kmods key.Modifiers
  415. if windows.GetKeyState(windows.VK_LWIN)&0x1000 != 0 || windows.GetKeyState(windows.VK_RWIN)&0x1000 != 0 {
  416. kmods |= key.ModSuper
  417. }
  418. if windows.GetKeyState(windows.VK_MENU)&0x1000 != 0 {
  419. kmods |= key.ModAlt
  420. }
  421. if windows.GetKeyState(windows.VK_CONTROL)&0x1000 != 0 {
  422. kmods |= key.ModCtrl
  423. }
  424. if windows.GetKeyState(windows.VK_SHIFT)&0x1000 != 0 {
  425. kmods |= key.ModShift
  426. }
  427. return kmods
  428. }
  429. // hitTest returns the non-client area hit by the point, needed to
  430. // process WM_NCHITTEST.
  431. func (w *window) hitTest(x, y int) uintptr {
  432. if w.config.Mode != Windowed {
  433. // Only windowed mode should allow resizing.
  434. return windows.HTCLIENT
  435. }
  436. // Check for resize handle before system actions; otherwise it can be impossible to
  437. // resize a custom-decorations window when the system move area is flush with the
  438. // edge of the window.
  439. top := y <= w.borderSize.Y
  440. bottom := y >= w.config.Size.Y-w.borderSize.Y
  441. left := x <= w.borderSize.X
  442. right := x >= w.config.Size.X-w.borderSize.X
  443. switch {
  444. case top && left:
  445. return windows.HTTOPLEFT
  446. case top && right:
  447. return windows.HTTOPRIGHT
  448. case bottom && left:
  449. return windows.HTBOTTOMLEFT
  450. case bottom && right:
  451. return windows.HTBOTTOMRIGHT
  452. case top:
  453. return windows.HTTOP
  454. case bottom:
  455. return windows.HTBOTTOM
  456. case left:
  457. return windows.HTLEFT
  458. case right:
  459. return windows.HTRIGHT
  460. }
  461. p := f32.Pt(float32(x), float32(y))
  462. if a, ok := w.w.ActionAt(p); ok && a == system.ActionMove {
  463. return windows.HTCAPTION
  464. }
  465. return windows.HTCLIENT
  466. }
  467. func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) {
  468. if !w.config.Focused {
  469. windows.SetFocus(w.hwnd)
  470. }
  471. var kind pointer.Kind
  472. if press {
  473. kind = pointer.Press
  474. if w.pointerBtns == 0 {
  475. windows.SetCapture(w.hwnd)
  476. }
  477. w.pointerBtns |= btn
  478. } else {
  479. kind = pointer.Release
  480. w.pointerBtns &^= btn
  481. if w.pointerBtns == 0 {
  482. windows.ReleaseCapture()
  483. }
  484. }
  485. x, y := coordsFromlParam(lParam)
  486. p := f32.Point{X: float32(x), Y: float32(y)}
  487. w.ProcessEvent(pointer.Event{
  488. Kind: kind,
  489. Source: pointer.Mouse,
  490. Position: p,
  491. Buttons: w.pointerBtns,
  492. Time: windows.GetMessageTime(),
  493. Modifiers: kmods,
  494. })
  495. }
  496. func coordsFromlParam(lParam uintptr) (int, int) {
  497. x := int(int16(lParam & 0xffff))
  498. y := int(int16((lParam >> 16) & 0xffff))
  499. return x, y
  500. }
  501. func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.Modifiers) {
  502. x, y := coordsFromlParam(lParam)
  503. // The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast
  504. // to other mouse events.
  505. np := windows.Point{X: int32(x), Y: int32(y)}
  506. windows.ScreenToClient(w.hwnd, &np)
  507. p := f32.Point{X: float32(np.X), Y: float32(np.Y)}
  508. dist := float32(int16(wParam >> 16))
  509. var sp f32.Point
  510. if horizontal {
  511. sp.X = dist
  512. } else {
  513. // support horizontal scroll (shift + mousewheel)
  514. if kmods == key.ModShift {
  515. sp.X = -dist
  516. } else {
  517. sp.Y = -dist
  518. }
  519. }
  520. w.ProcessEvent(pointer.Event{
  521. Kind: pointer.Scroll,
  522. Source: pointer.Mouse,
  523. Position: p,
  524. Buttons: w.pointerBtns,
  525. Scroll: sp,
  526. Modifiers: kmods,
  527. Time: windows.GetMessageTime(),
  528. })
  529. }
  530. // Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/
  531. func (w *window) runLoop() {
  532. msg := new(windows.Msg)
  533. loop:
  534. for {
  535. anim := w.animating
  536. p := windows.GetWindowPlacement(w.hwnd)
  537. if anim && !p.IsMinimized() && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
  538. w.draw(false)
  539. continue
  540. }
  541. switch ret := windows.GetMessage(msg, 0, 0, 0); ret {
  542. case -1:
  543. panic(errors.New("GetMessage failed"))
  544. case 0:
  545. // WM_QUIT received.
  546. break loop
  547. }
  548. windows.TranslateMessage(msg)
  549. windows.DispatchMessage(msg)
  550. }
  551. }
  552. func (w *window) EditorStateChanged(old, new editorState) {
  553. imc := windows.ImmGetContext(w.hwnd)
  554. if imc == 0 {
  555. return
  556. }
  557. defer windows.ImmReleaseContext(w.hwnd, imc)
  558. if old.Selection.Range != new.Selection.Range || old.Snippet != new.Snippet {
  559. windows.ImmNotifyIME(imc, windows.NI_COMPOSITIONSTR, windows.CPS_CANCEL, 0)
  560. }
  561. }
  562. func (w *window) SetAnimating(anim bool) {
  563. w.animating = anim
  564. }
  565. func (w *window) ProcessEvent(e event.Event) {
  566. w.w.ProcessEvent(e)
  567. w.loop.FlushEvents()
  568. }
  569. func (w *window) Event() event.Event {
  570. return w.loop.Event()
  571. }
  572. func (w *window) Invalidate() {
  573. w.loop.Invalidate()
  574. }
  575. func (w *window) Run(f func()) {
  576. w.loop.Run(f)
  577. }
  578. func (w *window) Frame(frame *op.Ops) {
  579. w.loop.Frame(frame)
  580. }
  581. func (w *window) wakeup() {
  582. if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil {
  583. panic(err)
  584. }
  585. }
  586. func (w *window) draw(sync bool) {
  587. if w.config.Size.X == 0 || w.config.Size.Y == 0 {
  588. return
  589. }
  590. dpi := windows.GetWindowDPI(w.hwnd)
  591. cfg := configForDPI(dpi)
  592. w.ProcessEvent(frameEvent{
  593. FrameEvent: FrameEvent{
  594. Now: time.Now(),
  595. Size: w.config.Size,
  596. Metric: cfg,
  597. },
  598. Sync: sync,
  599. })
  600. }
  601. func (w *window) NewContext() (context, error) {
  602. sort.Slice(drivers, func(i, j int) bool {
  603. return drivers[i].priority < drivers[j].priority
  604. })
  605. var errs []string
  606. for _, b := range drivers {
  607. ctx, err := b.initializer(w)
  608. if err == nil {
  609. return ctx, nil
  610. }
  611. errs = append(errs, err.Error())
  612. }
  613. if len(errs) > 0 {
  614. return nil, fmt.Errorf("NewContext: failed to create a GPU device, tried: %s", strings.Join(errs, ", "))
  615. }
  616. return nil, errors.New("NewContext: no available GPU drivers")
  617. }
  618. func (w *window) ReadClipboard() {
  619. w.readClipboard()
  620. }
  621. func (w *window) readClipboard() error {
  622. if err := windows.OpenClipboard(w.hwnd); err != nil {
  623. return err
  624. }
  625. defer windows.CloseClipboard()
  626. mem, err := windows.GetClipboardData(windows.CF_UNICODETEXT)
  627. if err != nil {
  628. return err
  629. }
  630. ptr, err := windows.GlobalLock(mem)
  631. if err != nil {
  632. return err
  633. }
  634. defer windows.GlobalUnlock(mem)
  635. content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
  636. w.ProcessEvent(transfer.DataEvent{
  637. Type: "application/text",
  638. Open: func() io.ReadCloser {
  639. return io.NopCloser(strings.NewReader(content))
  640. },
  641. })
  642. return nil
  643. }
  644. func (w *window) Configure(options []Option) {
  645. dpi := windows.GetSystemDPI()
  646. metric := configForDPI(dpi)
  647. cnf := w.config
  648. cnf.apply(metric, options)
  649. w.config.Title = cnf.Title
  650. w.config.Decorated = cnf.Decorated
  651. w.config.MinSize = cnf.MinSize
  652. w.config.MaxSize = cnf.MaxSize
  653. windows.SetWindowText(w.hwnd, cnf.Title)
  654. style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
  655. var showMode int32
  656. var x, y, width, height int32
  657. swpStyle := uintptr(windows.SWP_NOZORDER | windows.SWP_FRAMECHANGED)
  658. winStyle := uintptr(windows.WS_OVERLAPPEDWINDOW)
  659. style &^= winStyle
  660. switch cnf.Mode {
  661. case Minimized:
  662. style |= winStyle
  663. swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
  664. showMode = windows.SW_SHOWMINIMIZED
  665. case Maximized:
  666. style |= winStyle
  667. swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
  668. showMode = windows.SW_SHOWMAXIMIZED
  669. case Windowed:
  670. style |= winStyle
  671. showMode = windows.SW_SHOWNORMAL
  672. // Get target for client area size.
  673. width = int32(cnf.Size.X)
  674. height = int32(cnf.Size.Y)
  675. // Get the current window size and position.
  676. wr := windows.GetWindowRect(w.hwnd)
  677. x = wr.Left
  678. y = wr.Top
  679. if cnf.Decorated {
  680. // Compute client size and position. Note that the client size is
  681. // equal to the window size when we are in control of decorations.
  682. r := windows.Rect{
  683. Right: width,
  684. Bottom: height,
  685. }
  686. windows.AdjustWindowRectEx(&r, uint32(style), 0, dwExStyle)
  687. width = r.Right - r.Left
  688. height = r.Bottom - r.Top
  689. } else {
  690. // Enable drop shadows when we draw decorations.
  691. windows.DwmExtendFrameIntoClientArea(w.hwnd, windows.Margins{-1, -1, -1, -1})
  692. }
  693. case Fullscreen:
  694. swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
  695. showMode = windows.SW_SHOWMAXIMIZED
  696. }
  697. windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style)
  698. windows.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle)
  699. windows.ShowWindow(w.hwnd, showMode)
  700. }
  701. func (w *window) WriteClipboard(mime string, s []byte) {
  702. w.writeClipboard(string(s))
  703. }
  704. func (w *window) writeClipboard(s string) error {
  705. if err := windows.OpenClipboard(w.hwnd); err != nil {
  706. return err
  707. }
  708. defer windows.CloseClipboard()
  709. if err := windows.EmptyClipboard(); err != nil {
  710. return err
  711. }
  712. u16, err := gowindows.UTF16FromString(s)
  713. if err != nil {
  714. return err
  715. }
  716. n := len(u16) * int(unsafe.Sizeof(u16[0]))
  717. mem, err := windows.GlobalAlloc(n)
  718. if err != nil {
  719. return err
  720. }
  721. ptr, err := windows.GlobalLock(mem)
  722. if err != nil {
  723. windows.GlobalFree(mem)
  724. return err
  725. }
  726. u16v := unsafe.Slice((*uint16)(ptr), len(u16))
  727. copy(u16v, u16)
  728. windows.GlobalUnlock(mem)
  729. if err := windows.SetClipboardData(windows.CF_UNICODETEXT, mem); err != nil {
  730. windows.GlobalFree(mem)
  731. return err
  732. }
  733. return nil
  734. }
  735. func (w *window) SetCursor(cursor pointer.Cursor) {
  736. c, err := loadCursor(cursor)
  737. if err != nil {
  738. c = resources.cursor
  739. }
  740. w.cursor = c
  741. if w.cursorIn {
  742. windows.SetCursor(w.cursor)
  743. }
  744. }
  745. // windowsCursor contains mapping from pointer.Cursor to an IDC.
  746. var windowsCursor = [...]uint16{
  747. pointer.CursorDefault: windows.IDC_ARROW,
  748. pointer.CursorNone: 0,
  749. pointer.CursorText: windows.IDC_IBEAM,
  750. pointer.CursorVerticalText: windows.IDC_IBEAM,
  751. pointer.CursorPointer: windows.IDC_HAND,
  752. pointer.CursorCrosshair: windows.IDC_CROSS,
  753. pointer.CursorAllScroll: windows.IDC_SIZEALL,
  754. pointer.CursorColResize: windows.IDC_SIZEWE,
  755. pointer.CursorRowResize: windows.IDC_SIZENS,
  756. pointer.CursorGrab: windows.IDC_SIZEALL,
  757. pointer.CursorGrabbing: windows.IDC_SIZEALL,
  758. pointer.CursorNotAllowed: windows.IDC_NO,
  759. pointer.CursorWait: windows.IDC_WAIT,
  760. pointer.CursorProgress: windows.IDC_APPSTARTING,
  761. pointer.CursorNorthWestResize: windows.IDC_SIZENWSE,
  762. pointer.CursorNorthEastResize: windows.IDC_SIZENESW,
  763. pointer.CursorSouthWestResize: windows.IDC_SIZENESW,
  764. pointer.CursorSouthEastResize: windows.IDC_SIZENWSE,
  765. pointer.CursorNorthSouthResize: windows.IDC_SIZENS,
  766. pointer.CursorEastWestResize: windows.IDC_SIZEWE,
  767. pointer.CursorWestResize: windows.IDC_SIZEWE,
  768. pointer.CursorEastResize: windows.IDC_SIZEWE,
  769. pointer.CursorNorthResize: windows.IDC_SIZENS,
  770. pointer.CursorSouthResize: windows.IDC_SIZENS,
  771. pointer.CursorNorthEastSouthWestResize: windows.IDC_SIZENESW,
  772. pointer.CursorNorthWestSouthEastResize: windows.IDC_SIZENWSE,
  773. }
  774. func loadCursor(cursor pointer.Cursor) (syscall.Handle, error) {
  775. switch cursor {
  776. case pointer.CursorDefault:
  777. return resources.cursor, nil
  778. case pointer.CursorNone:
  779. return 0, nil
  780. default:
  781. return windows.LoadCursor(windowsCursor[cursor])
  782. }
  783. }
  784. func (w *window) ShowTextInput(show bool) {}
  785. func (w *window) SetInputHint(_ key.InputHint) {}
  786. func (w *window) HDC() syscall.Handle {
  787. return w.hdc
  788. }
  789. func (w *window) HWND() (syscall.Handle, int, int) {
  790. return w.hwnd, w.config.Size.X, w.config.Size.Y
  791. }
  792. func (w *window) Perform(acts system.Action) {
  793. walkActions(acts, func(a system.Action) {
  794. switch a {
  795. case system.ActionCenter:
  796. if w.config.Mode != Windowed {
  797. break
  798. }
  799. r := windows.GetWindowRect(w.hwnd)
  800. dx := r.Right - r.Left
  801. dy := r.Bottom - r.Top
  802. // Calculate center position on current monitor.
  803. mi := windows.GetMonitorInfo(w.hwnd).Monitor
  804. x := (mi.Right - mi.Left - dx) / 2
  805. y := (mi.Bottom - mi.Top - dy) / 2
  806. windows.SetWindowPos(w.hwnd, 0, x, y, dx, dy, windows.SWP_NOZORDER|windows.SWP_FRAMECHANGED)
  807. case system.ActionRaise:
  808. w.raise()
  809. case system.ActionClose:
  810. windows.PostMessage(w.hwnd, windows.WM_CLOSE, 0, 0)
  811. }
  812. })
  813. }
  814. func (w *window) raise() {
  815. windows.SetForegroundWindow(w.hwnd)
  816. windows.SetWindowPos(w.hwnd, windows.HWND_TOPMOST, 0, 0, 0, 0,
  817. windows.SWP_NOMOVE|windows.SWP_NOSIZE|windows.SWP_SHOWWINDOW)
  818. }
  819. func convertKeyCode(code uintptr) (key.Name, bool) {
  820. if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' {
  821. return key.Name(rune(code)), true
  822. }
  823. var r key.Name
  824. switch code {
  825. case windows.VK_ESCAPE:
  826. r = key.NameEscape
  827. case windows.VK_LEFT:
  828. r = key.NameLeftArrow
  829. case windows.VK_RIGHT:
  830. r = key.NameRightArrow
  831. case windows.VK_RETURN:
  832. r = key.NameReturn
  833. case windows.VK_UP:
  834. r = key.NameUpArrow
  835. case windows.VK_DOWN:
  836. r = key.NameDownArrow
  837. case windows.VK_HOME:
  838. r = key.NameHome
  839. case windows.VK_END:
  840. r = key.NameEnd
  841. case windows.VK_BACK:
  842. r = key.NameDeleteBackward
  843. case windows.VK_DELETE:
  844. r = key.NameDeleteForward
  845. case windows.VK_PRIOR:
  846. r = key.NamePageUp
  847. case windows.VK_NEXT:
  848. r = key.NamePageDown
  849. case windows.VK_F1:
  850. r = key.NameF1
  851. case windows.VK_F2:
  852. r = key.NameF2
  853. case windows.VK_F3:
  854. r = key.NameF3
  855. case windows.VK_F4:
  856. r = key.NameF4
  857. case windows.VK_F5:
  858. r = key.NameF5
  859. case windows.VK_F6:
  860. r = key.NameF6
  861. case windows.VK_F7:
  862. r = key.NameF7
  863. case windows.VK_F8:
  864. r = key.NameF8
  865. case windows.VK_F9:
  866. r = key.NameF9
  867. case windows.VK_F10:
  868. r = key.NameF10
  869. case windows.VK_F11:
  870. r = key.NameF11
  871. case windows.VK_F12:
  872. r = key.NameF12
  873. case windows.VK_TAB:
  874. r = key.NameTab
  875. case windows.VK_SPACE:
  876. r = key.NameSpace
  877. case windows.VK_OEM_1:
  878. r = ";"
  879. case windows.VK_OEM_PLUS:
  880. r = "+"
  881. case windows.VK_OEM_COMMA:
  882. r = ","
  883. case windows.VK_OEM_MINUS:
  884. r = "-"
  885. case windows.VK_OEM_PERIOD:
  886. r = "."
  887. case windows.VK_OEM_2:
  888. r = "/"
  889. case windows.VK_OEM_3:
  890. r = "`"
  891. case windows.VK_OEM_4:
  892. r = "["
  893. case windows.VK_OEM_5, windows.VK_OEM_102:
  894. r = "\\"
  895. case windows.VK_OEM_6:
  896. r = "]"
  897. case windows.VK_OEM_7:
  898. r = "'"
  899. case windows.VK_CONTROL:
  900. r = key.NameCtrl
  901. case windows.VK_SHIFT:
  902. r = key.NameShift
  903. case windows.VK_MENU:
  904. r = key.NameAlt
  905. case windows.VK_LWIN, windows.VK_RWIN:
  906. r = key.NameSuper
  907. default:
  908. return "", false
  909. }
  910. return r, true
  911. }
  912. func configForDPI(dpi int) unit.Metric {
  913. const inchPrDp = 1.0 / 96.0
  914. ppdp := float32(dpi) * inchPrDp
  915. return unit.Metric{
  916. PxPerDp: ppdp,
  917. PxPerSp: ppdp,
  918. }
  919. }
  920. func (Win32ViewEvent) implementsViewEvent() {}
  921. func (Win32ViewEvent) ImplementsEvent() {}
  922. func (w Win32ViewEvent) Valid() bool {
  923. return w != (Win32ViewEvent{})
  924. }