os_x11.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929
  1. // SPDX-License-Identifier: Unlicense OR MIT
  2. //go:build ((linux && !android) || freebsd || openbsd) && !nox11
  3. // +build linux,!android freebsd openbsd
  4. // +build !nox11
  5. package app
  6. /*
  7. #cgo freebsd openbsd CFLAGS: -I/usr/X11R6/include -I/usr/local/include
  8. #cgo freebsd openbsd LDFLAGS: -L/usr/X11R6/lib -L/usr/local/lib
  9. #cgo freebsd openbsd LDFLAGS: -lX11 -lxkbcommon -lxkbcommon-x11 -lX11-xcb -lXcursor -lXfixes
  10. #cgo linux pkg-config: x11 xkbcommon xkbcommon-x11 x11-xcb xcursor xfixes
  11. #include <stdlib.h>
  12. #include <locale.h>
  13. #include <X11/Xlib.h>
  14. #include <X11/Xatom.h>
  15. #include <X11/Xutil.h>
  16. #include <X11/Xresource.h>
  17. #include <X11/XKBlib.h>
  18. #include <X11/Xlib-xcb.h>
  19. #include <X11/extensions/Xfixes.h>
  20. #include <X11/Xcursor/Xcursor.h>
  21. #include <xkbcommon/xkbcommon-x11.h>
  22. */
  23. import "C"
  24. import (
  25. "errors"
  26. "fmt"
  27. "image"
  28. "io"
  29. "strconv"
  30. "strings"
  31. "sync"
  32. "time"
  33. "unsafe"
  34. "gioui.org/f32"
  35. "gioui.org/io/event"
  36. "gioui.org/io/key"
  37. "gioui.org/io/pointer"
  38. "gioui.org/io/system"
  39. "gioui.org/io/transfer"
  40. "gioui.org/op"
  41. "gioui.org/unit"
  42. syscall "golang.org/x/sys/unix"
  43. "gioui.org/app/internal/xkb"
  44. )
  45. const (
  46. _NET_WM_STATE_REMOVE = 0
  47. _NET_WM_STATE_ADD = 1
  48. )
  49. type x11Window struct {
  50. w *callbacks
  51. x *C.Display
  52. xkb *xkb.Context
  53. xkbEventBase C.int
  54. xw C.Window
  55. atoms struct {
  56. // "UTF8_STRING".
  57. utf8string C.Atom
  58. // "text/plain;charset=utf-8".
  59. plaintext C.Atom
  60. // "TARGETS"
  61. targets C.Atom
  62. // "CLIPBOARD".
  63. clipboard C.Atom
  64. // "PRIMARY".
  65. primary C.Atom
  66. // "CLIPBOARD_CONTENT", the clipboard destination property.
  67. clipboardContent C.Atom
  68. // "WM_DELETE_WINDOW"
  69. evDelWindow C.Atom
  70. // "ATOM"
  71. atom C.Atom
  72. // "GTK_TEXT_BUFFER_CONTENTS"
  73. gtk_text_buffer_contents C.Atom
  74. // "_NET_WM_NAME"
  75. wmName C.Atom
  76. // "_NET_WM_STATE"
  77. wmState C.Atom
  78. // "_NET_WM_STATE_FULLSCREEN"
  79. wmStateFullscreen C.Atom
  80. // "_NET_ACTIVE_WINDOW"
  81. wmActiveWindow C.Atom
  82. // _NET_WM_STATE_MAXIMIZED_HORZ
  83. wmStateMaximizedHorz C.Atom
  84. // _NET_WM_STATE_MAXIMIZED_VERT
  85. wmStateMaximizedVert C.Atom
  86. }
  87. metric unit.Metric
  88. notify struct {
  89. read, write int
  90. }
  91. animating bool
  92. pointerBtns pointer.Buttons
  93. clipboard struct {
  94. content []byte
  95. }
  96. cursor pointer.Cursor
  97. config Config
  98. wakeups chan struct{}
  99. handler x11EventHandler
  100. buf [100]byte
  101. }
  102. var (
  103. newX11EGLContext func(w *x11Window) (context, error)
  104. newX11VulkanContext func(w *x11Window) (context, error)
  105. )
  106. // X11 and Vulkan doesn't work reliably on NVIDIA systems.
  107. // See https://gioui.org/issue/347.
  108. const vulkanBuggy = true
  109. func (w *x11Window) NewContext() (context, error) {
  110. var firstErr error
  111. if f := newX11VulkanContext; f != nil && !vulkanBuggy {
  112. c, err := f(w)
  113. if err == nil {
  114. return c, nil
  115. }
  116. firstErr = err
  117. }
  118. if f := newX11EGLContext; f != nil {
  119. c, err := f(w)
  120. if err == nil {
  121. return c, nil
  122. }
  123. firstErr = err
  124. }
  125. if firstErr != nil {
  126. return nil, firstErr
  127. }
  128. return nil, errors.New("x11: no available GPU backends")
  129. }
  130. func (w *x11Window) SetAnimating(anim bool) {
  131. w.animating = anim
  132. }
  133. func (w *x11Window) ReadClipboard() {
  134. C.XDeleteProperty(w.x, w.xw, w.atoms.clipboardContent)
  135. C.XConvertSelection(w.x, w.atoms.clipboard, w.atoms.utf8string, w.atoms.clipboardContent, w.xw, C.CurrentTime)
  136. }
  137. func (w *x11Window) WriteClipboard(mime string, s []byte) {
  138. w.clipboard.content = s
  139. C.XSetSelectionOwner(w.x, w.atoms.clipboard, w.xw, C.CurrentTime)
  140. C.XSetSelectionOwner(w.x, w.atoms.primary, w.xw, C.CurrentTime)
  141. }
  142. func (w *x11Window) Configure(options []Option) {
  143. var shints C.XSizeHints
  144. prev := w.config
  145. cnf := w.config
  146. cnf.apply(w.metric, options)
  147. // Decorations are never disabled.
  148. cnf.Decorated = true
  149. switch cnf.Mode {
  150. case Fullscreen:
  151. switch prev.Mode {
  152. case Fullscreen:
  153. case Minimized:
  154. w.raise()
  155. fallthrough
  156. default:
  157. w.config.Mode = Fullscreen
  158. w.sendWMStateEvent(_NET_WM_STATE_ADD, w.atoms.wmStateFullscreen, 0)
  159. }
  160. case Minimized:
  161. switch prev.Mode {
  162. case Minimized, Fullscreen:
  163. default:
  164. w.config.Mode = Minimized
  165. screen := C.XDefaultScreen(w.x)
  166. C.XIconifyWindow(w.x, w.xw, screen)
  167. }
  168. case Maximized:
  169. switch prev.Mode {
  170. case Fullscreen:
  171. case Minimized:
  172. w.raise()
  173. fallthrough
  174. default:
  175. w.config.Mode = Maximized
  176. w.sendWMStateEvent(_NET_WM_STATE_ADD, w.atoms.wmStateMaximizedHorz, w.atoms.wmStateMaximizedVert)
  177. w.setTitle(prev, cnf)
  178. }
  179. case Windowed:
  180. switch prev.Mode {
  181. case Fullscreen:
  182. w.config.Mode = Windowed
  183. w.sendWMStateEvent(_NET_WM_STATE_REMOVE, w.atoms.wmStateFullscreen, 0)
  184. C.XResizeWindow(w.x, w.xw, C.uint(cnf.Size.X), C.uint(cnf.Size.Y))
  185. case Minimized:
  186. w.config.Mode = Windowed
  187. w.raise()
  188. case Maximized:
  189. w.config.Mode = Windowed
  190. w.sendWMStateEvent(_NET_WM_STATE_REMOVE, w.atoms.wmStateMaximizedHorz, w.atoms.wmStateMaximizedVert)
  191. }
  192. w.setTitle(prev, cnf)
  193. if prev.Size != cnf.Size {
  194. w.config.Size = cnf.Size
  195. C.XResizeWindow(w.x, w.xw, C.uint(cnf.Size.X), C.uint(cnf.Size.Y))
  196. }
  197. if prev.MinSize != cnf.MinSize {
  198. w.config.MinSize = cnf.MinSize
  199. shints.min_width = C.int(cnf.MinSize.X)
  200. shints.min_height = C.int(cnf.MinSize.Y)
  201. shints.flags = C.PMinSize
  202. }
  203. if prev.MaxSize != cnf.MaxSize {
  204. w.config.MaxSize = cnf.MaxSize
  205. shints.max_width = C.int(cnf.MaxSize.X)
  206. shints.max_height = C.int(cnf.MaxSize.Y)
  207. shints.flags = shints.flags | C.PMaxSize
  208. }
  209. if shints.flags != 0 {
  210. C.XSetWMNormalHints(w.x, w.xw, &shints)
  211. }
  212. }
  213. if cnf.Decorated != prev.Decorated {
  214. w.config.Decorated = cnf.Decorated
  215. }
  216. w.ProcessEvent(ConfigEvent{Config: w.config})
  217. }
  218. func (w *x11Window) setTitle(prev, cnf Config) {
  219. if prev.Title != cnf.Title {
  220. title := cnf.Title
  221. ctitle := C.CString(title)
  222. defer C.free(unsafe.Pointer(ctitle))
  223. C.XStoreName(w.x, w.xw, ctitle)
  224. // set _NET_WM_NAME as well for UTF-8 support in window title.
  225. C.XSetTextProperty(w.x, w.xw,
  226. &C.XTextProperty{
  227. value: (*C.uchar)(unsafe.Pointer(ctitle)),
  228. encoding: w.atoms.utf8string,
  229. format: 8,
  230. nitems: C.ulong(len(title)),
  231. },
  232. w.atoms.wmName)
  233. }
  234. }
  235. func (w *x11Window) Perform(acts system.Action) {
  236. walkActions(acts, func(a system.Action) {
  237. switch a {
  238. case system.ActionCenter:
  239. w.center()
  240. case system.ActionRaise:
  241. w.raise()
  242. }
  243. })
  244. if acts&system.ActionClose != 0 {
  245. w.close()
  246. }
  247. }
  248. func (w *x11Window) center() {
  249. screen := C.XDefaultScreen(w.x)
  250. width := C.XDisplayWidth(w.x, screen)
  251. height := C.XDisplayHeight(w.x, screen)
  252. var attrs C.XWindowAttributes
  253. C.XGetWindowAttributes(w.x, w.xw, &attrs)
  254. width -= attrs.border_width
  255. height -= attrs.border_width
  256. sz := w.config.Size
  257. x := (int(width) - sz.X) / 2
  258. y := (int(height) - sz.Y) / 2
  259. C.XMoveResizeWindow(w.x, w.xw, C.int(x), C.int(y), C.uint(sz.X), C.uint(sz.Y))
  260. }
  261. func (w *x11Window) raise() {
  262. var xev C.XEvent
  263. ev := (*C.XClientMessageEvent)(unsafe.Pointer(&xev))
  264. *ev = C.XClientMessageEvent{
  265. _type: C.ClientMessage,
  266. display: w.x,
  267. window: w.xw,
  268. message_type: w.atoms.wmActiveWindow,
  269. format: 32,
  270. }
  271. C.XSendEvent(
  272. w.x,
  273. C.XDefaultRootWindow(w.x), // MUST be the root window
  274. C.False,
  275. C.SubstructureNotifyMask|C.SubstructureRedirectMask,
  276. &xev,
  277. )
  278. C.XMapRaised(w.display(), w.xw)
  279. }
  280. func (w *x11Window) SetCursor(cursor pointer.Cursor) {
  281. if cursor == pointer.CursorNone {
  282. w.cursor = cursor
  283. C.XFixesHideCursor(w.x, w.xw)
  284. return
  285. }
  286. xcursor := xCursor[cursor]
  287. cname := C.CString(xcursor)
  288. defer C.free(unsafe.Pointer(cname))
  289. c := C.XcursorLibraryLoadCursor(w.x, cname)
  290. if c == 0 {
  291. cursor = pointer.CursorDefault
  292. }
  293. w.cursor = cursor
  294. // If c if null (i.e. cursor was not found),
  295. // XDefineCursor will use the default cursor.
  296. C.XDefineCursor(w.x, w.xw, c)
  297. }
  298. func (w *x11Window) ShowTextInput(show bool) {}
  299. func (w *x11Window) SetInputHint(_ key.InputHint) {}
  300. func (w *x11Window) EditorStateChanged(old, new editorState) {}
  301. // close the window.
  302. func (w *x11Window) close() {
  303. var xev C.XEvent
  304. ev := (*C.XClientMessageEvent)(unsafe.Pointer(&xev))
  305. *ev = C.XClientMessageEvent{
  306. _type: C.ClientMessage,
  307. display: w.x,
  308. window: w.xw,
  309. message_type: w.atom("WM_PROTOCOLS", true),
  310. format: 32,
  311. }
  312. arr := (*[5]C.long)(unsafe.Pointer(&ev.data))
  313. arr[0] = C.long(w.atoms.evDelWindow)
  314. arr[1] = C.CurrentTime
  315. C.XSendEvent(w.x, w.xw, C.False, C.NoEventMask, &xev)
  316. }
  317. // action is one of _NET_WM_STATE_REMOVE, _NET_WM_STATE_ADD.
  318. func (w *x11Window) sendWMStateEvent(action C.long, atom1, atom2 C.ulong) {
  319. var xev C.XEvent
  320. ev := (*C.XClientMessageEvent)(unsafe.Pointer(&xev))
  321. *ev = C.XClientMessageEvent{
  322. _type: C.ClientMessage,
  323. display: w.x,
  324. window: w.xw,
  325. message_type: w.atoms.wmState,
  326. format: 32,
  327. }
  328. data := (*[5]C.long)(unsafe.Pointer(&ev.data))
  329. data[0] = C.long(action)
  330. data[1] = C.long(atom1)
  331. data[2] = C.long(atom2)
  332. data[3] = 1 // application
  333. C.XSendEvent(
  334. w.x,
  335. C.XDefaultRootWindow(w.x), // MUST be the root window
  336. C.False,
  337. C.SubstructureNotifyMask|C.SubstructureRedirectMask,
  338. &xev,
  339. )
  340. }
  341. var x11OneByte = make([]byte, 1)
  342. func (w *x11Window) ProcessEvent(e event.Event) {
  343. w.w.ProcessEvent(e)
  344. }
  345. func (w *x11Window) shutdown(err error) {
  346. w.ProcessEvent(X11ViewEvent{})
  347. w.ProcessEvent(DestroyEvent{Err: err})
  348. w.destroy()
  349. }
  350. func (w *x11Window) Event() event.Event {
  351. for {
  352. evt, ok := w.w.nextEvent()
  353. if !ok {
  354. w.dispatch()
  355. continue
  356. }
  357. return evt
  358. }
  359. }
  360. func (w *x11Window) Run(f func()) {
  361. f()
  362. }
  363. func (w *x11Window) Frame(frame *op.Ops) {
  364. w.w.ProcessFrame(frame, nil)
  365. }
  366. func (w *x11Window) Invalidate() {
  367. select {
  368. case w.wakeups <- struct{}{}:
  369. default:
  370. }
  371. if _, err := syscall.Write(w.notify.write, x11OneByte); err != nil && err != syscall.EAGAIN {
  372. panic(fmt.Errorf("failed to write to pipe: %v", err))
  373. }
  374. }
  375. func (w *x11Window) display() *C.Display {
  376. return w.x
  377. }
  378. func (w *x11Window) window() (C.Window, int, int) {
  379. return w.xw, w.config.Size.X, w.config.Size.Y
  380. }
  381. func (w *x11Window) dispatch() {
  382. if w.x == nil {
  383. // Only Invalidate can wake us up.
  384. <-w.wakeups
  385. w.w.Invalidate()
  386. return
  387. }
  388. select {
  389. case <-w.wakeups:
  390. w.w.Invalidate()
  391. default:
  392. }
  393. xfd := C.XConnectionNumber(w.x)
  394. // Poll for events and notifications.
  395. pollfds := []syscall.PollFd{
  396. {Fd: int32(xfd), Events: syscall.POLLIN | syscall.POLLERR},
  397. {Fd: int32(w.notify.read), Events: syscall.POLLIN | syscall.POLLERR},
  398. }
  399. xEvents := &pollfds[0].Revents
  400. // Plenty of room for a backlog of notifications.
  401. var syn, anim bool
  402. // Check for pending draw events before checking animation or blocking.
  403. // This fixes an issue on Xephyr where on startup XPending() > 0 but
  404. // poll will still block. This also prevents no-op calls to poll.
  405. syn = w.handler.handleEvents()
  406. if w.x == nil {
  407. // handleEvents received a close request and destroyed the window.
  408. return
  409. }
  410. if !syn {
  411. anim = w.animating
  412. if !anim {
  413. // Clear poll events.
  414. *xEvents = 0
  415. // Wait for X event or gio notification.
  416. if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
  417. panic(fmt.Errorf("x11 loop: poll failed: %w", err))
  418. }
  419. switch {
  420. case *xEvents&syscall.POLLIN != 0:
  421. syn = w.handler.handleEvents()
  422. if w.x == nil {
  423. return
  424. }
  425. case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
  426. }
  427. }
  428. }
  429. // Clear notifications.
  430. for {
  431. _, err := syscall.Read(w.notify.read, w.buf[:])
  432. if err == syscall.EAGAIN {
  433. break
  434. }
  435. if err != nil {
  436. panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
  437. }
  438. }
  439. if (anim || syn) && w.config.Size.X != 0 && w.config.Size.Y != 0 {
  440. w.ProcessEvent(frameEvent{
  441. FrameEvent: FrameEvent{
  442. Now: time.Now(),
  443. Size: w.config.Size,
  444. Metric: w.metric,
  445. },
  446. Sync: syn,
  447. })
  448. }
  449. }
  450. func (w *x11Window) destroy() {
  451. if w.notify.write != 0 {
  452. syscall.Close(w.notify.write)
  453. w.notify.write = 0
  454. }
  455. if w.notify.read != 0 {
  456. syscall.Close(w.notify.read)
  457. w.notify.read = 0
  458. }
  459. if w.xkb != nil {
  460. w.xkb.Destroy()
  461. w.xkb = nil
  462. }
  463. C.XDestroyWindow(w.x, w.xw)
  464. C.XCloseDisplay(w.x)
  465. w.x = nil
  466. }
  467. // atom is a wrapper around XInternAtom. Callers should cache the result
  468. // in order to limit round-trips to the X server.
  469. func (w *x11Window) atom(name string, onlyIfExists bool) C.Atom {
  470. cname := C.CString(name)
  471. defer C.free(unsafe.Pointer(cname))
  472. flag := C.Bool(C.False)
  473. if onlyIfExists {
  474. flag = C.True
  475. }
  476. return C.XInternAtom(w.x, cname, flag)
  477. }
  478. // x11EventHandler wraps static variables for the main event loop.
  479. // Its sole purpose is to prevent heap allocation and reduce clutter
  480. // in x11window.loop.
  481. type x11EventHandler struct {
  482. w *x11Window
  483. text []byte
  484. xev *C.XEvent
  485. }
  486. // handleEvents returns true if the window needs to be redrawn.
  487. func (h *x11EventHandler) handleEvents() bool {
  488. w := h.w
  489. xev := h.xev
  490. redraw := false
  491. for C.XPending(w.x) != 0 {
  492. C.XNextEvent(w.x, xev)
  493. if C.XFilterEvent(xev, C.None) == C.True {
  494. continue
  495. }
  496. switch _type := (*C.XAnyEvent)(unsafe.Pointer(xev))._type; _type {
  497. case h.w.xkbEventBase:
  498. xkbEvent := (*C.XkbAnyEvent)(unsafe.Pointer(xev))
  499. switch xkbEvent.xkb_type {
  500. case C.XkbNewKeyboardNotify, C.XkbMapNotify:
  501. if err := h.w.updateXkbKeymap(); err != nil {
  502. panic(err)
  503. }
  504. case C.XkbStateNotify:
  505. state := (*C.XkbStateNotifyEvent)(unsafe.Pointer(xev))
  506. h.w.xkb.UpdateMask(uint32(state.base_mods), uint32(state.latched_mods), uint32(state.locked_mods),
  507. uint32(state.base_group), uint32(state.latched_group), uint32(state.locked_group))
  508. }
  509. case C.KeyPress, C.KeyRelease:
  510. ks := key.Press
  511. if _type == C.KeyRelease {
  512. ks = key.Release
  513. }
  514. kevt := (*C.XKeyPressedEvent)(unsafe.Pointer(xev))
  515. for _, e := range h.w.xkb.DispatchKey(uint32(kevt.keycode), ks) {
  516. if ee, ok := e.(key.EditEvent); ok {
  517. // There's no support for IME yet.
  518. w.w.EditorInsert(ee.Text)
  519. } else {
  520. w.ProcessEvent(e)
  521. }
  522. }
  523. case C.ButtonPress, C.ButtonRelease:
  524. bevt := (*C.XButtonEvent)(unsafe.Pointer(xev))
  525. ev := pointer.Event{
  526. Kind: pointer.Press,
  527. Source: pointer.Mouse,
  528. Position: f32.Point{
  529. X: float32(bevt.x),
  530. Y: float32(bevt.y),
  531. },
  532. Time: time.Duration(bevt.time) * time.Millisecond,
  533. Modifiers: w.xkb.Modifiers(),
  534. }
  535. if bevt._type == C.ButtonRelease {
  536. ev.Kind = pointer.Release
  537. }
  538. var btn pointer.Buttons
  539. const scrollScale = 10
  540. switch bevt.button {
  541. case C.Button1:
  542. btn = pointer.ButtonPrimary
  543. case C.Button2:
  544. btn = pointer.ButtonTertiary
  545. case C.Button3:
  546. btn = pointer.ButtonSecondary
  547. case C.Button4:
  548. ev.Kind = pointer.Scroll
  549. // scroll up or left (if shift is pressed).
  550. if ev.Modifiers == key.ModShift {
  551. ev.Scroll.X = -scrollScale
  552. } else {
  553. ev.Scroll.Y = -scrollScale
  554. }
  555. case C.Button5:
  556. // scroll down or right (if shift is pressed).
  557. ev.Kind = pointer.Scroll
  558. if ev.Modifiers == key.ModShift {
  559. ev.Scroll.X = +scrollScale
  560. } else {
  561. ev.Scroll.Y = +scrollScale
  562. }
  563. case 6:
  564. // http://xahlee.info/linux/linux_x11_mouse_button_number.html
  565. // scroll left.
  566. ev.Kind = pointer.Scroll
  567. ev.Scroll.X = -scrollScale * 2
  568. case 7:
  569. // scroll right
  570. ev.Kind = pointer.Scroll
  571. ev.Scroll.X = +scrollScale * 2
  572. default:
  573. continue
  574. }
  575. switch _type {
  576. case C.ButtonPress:
  577. w.pointerBtns |= btn
  578. case C.ButtonRelease:
  579. w.pointerBtns &^= btn
  580. }
  581. ev.Buttons = w.pointerBtns
  582. w.ProcessEvent(ev)
  583. case C.MotionNotify:
  584. mevt := (*C.XMotionEvent)(unsafe.Pointer(xev))
  585. w.ProcessEvent(pointer.Event{
  586. Kind: pointer.Move,
  587. Source: pointer.Mouse,
  588. Buttons: w.pointerBtns,
  589. Position: f32.Point{
  590. X: float32(mevt.x),
  591. Y: float32(mevt.y),
  592. },
  593. Time: time.Duration(mevt.time) * time.Millisecond,
  594. Modifiers: w.xkb.Modifiers(),
  595. })
  596. case C.Expose: // update
  597. // redraw only on the last expose event
  598. redraw = (*C.XExposeEvent)(unsafe.Pointer(xev)).count == 0
  599. case C.FocusIn:
  600. w.config.Focused = true
  601. w.ProcessEvent(ConfigEvent{Config: w.config})
  602. case C.FocusOut:
  603. w.config.Focused = false
  604. w.ProcessEvent(ConfigEvent{Config: w.config})
  605. case C.ConfigureNotify: // window configuration change
  606. cevt := (*C.XConfigureEvent)(unsafe.Pointer(xev))
  607. if sz := image.Pt(int(cevt.width), int(cevt.height)); sz != w.config.Size {
  608. w.config.Size = sz
  609. w.ProcessEvent(ConfigEvent{Config: w.config})
  610. }
  611. // redraw will be done by a later expose event
  612. case C.SelectionNotify:
  613. cevt := (*C.XSelectionEvent)(unsafe.Pointer(xev))
  614. prop := w.atoms.clipboardContent
  615. if cevt.property != prop {
  616. break
  617. }
  618. if cevt.selection != w.atoms.clipboard {
  619. break
  620. }
  621. var text C.XTextProperty
  622. if st := C.XGetTextProperty(w.x, w.xw, &text, prop); st == 0 {
  623. // Failed; ignore.
  624. break
  625. }
  626. if text.format != 8 || text.encoding != w.atoms.utf8string {
  627. // Ignore non-utf-8 encoded strings.
  628. break
  629. }
  630. str := C.GoStringN((*C.char)(unsafe.Pointer(text.value)), C.int(text.nitems))
  631. w.ProcessEvent(transfer.DataEvent{
  632. Type: "application/text",
  633. Open: func() io.ReadCloser {
  634. return io.NopCloser(strings.NewReader(str))
  635. },
  636. })
  637. case C.SelectionRequest:
  638. cevt := (*C.XSelectionRequestEvent)(unsafe.Pointer(xev))
  639. if (cevt.selection != w.atoms.clipboard && cevt.selection != w.atoms.primary) || cevt.property == C.None {
  640. // Unsupported clipboard or obsolete requestor.
  641. break
  642. }
  643. notify := func() {
  644. var xev C.XEvent
  645. ev := (*C.XSelectionEvent)(unsafe.Pointer(&xev))
  646. *ev = C.XSelectionEvent{
  647. _type: C.SelectionNotify,
  648. display: cevt.display,
  649. requestor: cevt.requestor,
  650. selection: cevt.selection,
  651. target: cevt.target,
  652. property: cevt.property,
  653. time: cevt.time,
  654. }
  655. C.XSendEvent(w.x, cevt.requestor, 0, 0, &xev)
  656. }
  657. switch cevt.target {
  658. case w.atoms.targets:
  659. // The requestor wants the supported clipboard
  660. // formats. First write the targets...
  661. formats := [...]C.long{
  662. C.long(w.atoms.targets),
  663. C.long(w.atoms.utf8string),
  664. C.long(w.atoms.plaintext),
  665. // GTK clients need this.
  666. C.long(w.atoms.gtk_text_buffer_contents),
  667. }
  668. C.XChangeProperty(w.x, cevt.requestor, cevt.property, w.atoms.atom,
  669. 32 /* bitwidth of formats */, C.PropModeReplace,
  670. (*C.uchar)(unsafe.Pointer(&formats)), C.int(len(formats)),
  671. )
  672. // ...then notify the requestor.
  673. notify()
  674. case w.atoms.plaintext, w.atoms.utf8string, w.atoms.gtk_text_buffer_contents:
  675. content := w.clipboard.content
  676. var ptr *C.uchar
  677. if len(content) > 0 {
  678. ptr = (*C.uchar)(unsafe.Pointer(&content[0]))
  679. }
  680. C.XChangeProperty(w.x, cevt.requestor, cevt.property, cevt.target,
  681. 8 /* bitwidth */, C.PropModeReplace,
  682. ptr, C.int(len(content)),
  683. )
  684. notify()
  685. }
  686. case C.ClientMessage: // extensions
  687. cevt := (*C.XClientMessageEvent)(unsafe.Pointer(xev))
  688. switch *(*C.long)(unsafe.Pointer(&cevt.data)) {
  689. case C.long(w.atoms.evDelWindow):
  690. w.shutdown(nil)
  691. return false
  692. }
  693. }
  694. }
  695. return redraw
  696. }
  697. var (
  698. x11Threads sync.Once
  699. )
  700. func init() {
  701. x11Driver = newX11Window
  702. }
  703. func newX11Window(gioWin *callbacks, options []Option) error {
  704. var err error
  705. pipe := make([]int, 2)
  706. if err := syscall.Pipe2(pipe, syscall.O_NONBLOCK|syscall.O_CLOEXEC); err != nil {
  707. return fmt.Errorf("NewX11Window: failed to create pipe: %w", err)
  708. }
  709. x11Threads.Do(func() {
  710. if C.XInitThreads() == 0 {
  711. err = errors.New("x11: threads init failed")
  712. }
  713. C.XrmInitialize()
  714. })
  715. if err != nil {
  716. return err
  717. }
  718. dpy := C.XOpenDisplay(nil)
  719. if dpy == nil {
  720. return errors.New("x11: cannot connect to the X server")
  721. }
  722. var major, minor C.int = C.XkbMajorVersion, C.XkbMinorVersion
  723. var xkbEventBase C.int
  724. if C.XkbQueryExtension(dpy, nil, &xkbEventBase, nil, &major, &minor) != C.True {
  725. C.XCloseDisplay(dpy)
  726. return errors.New("x11: XkbQueryExtension failed")
  727. }
  728. const bits = C.uint(C.XkbNewKeyboardNotifyMask | C.XkbMapNotifyMask | C.XkbStateNotifyMask)
  729. if C.XkbSelectEvents(dpy, C.XkbUseCoreKbd, bits, bits) != C.True {
  730. C.XCloseDisplay(dpy)
  731. return errors.New("x11: XkbSelectEvents failed")
  732. }
  733. xkb, err := xkb.New()
  734. if err != nil {
  735. C.XCloseDisplay(dpy)
  736. return fmt.Errorf("x11: %v", err)
  737. }
  738. ppsp := x11DetectUIScale(dpy)
  739. cfg := unit.Metric{PxPerDp: ppsp, PxPerSp: ppsp}
  740. // Only use cnf for getting the window size.
  741. var cnf Config
  742. cnf.apply(cfg, options)
  743. swa := C.XSetWindowAttributes{
  744. event_mask: C.ExposureMask | C.FocusChangeMask | // update
  745. C.KeyPressMask | C.KeyReleaseMask | // keyboard
  746. C.ButtonPressMask | C.ButtonReleaseMask | // mouse clicks
  747. C.PointerMotionMask | // mouse movement
  748. C.StructureNotifyMask, // resize
  749. background_pixmap: C.None,
  750. override_redirect: C.False,
  751. }
  752. win := C.XCreateWindow(dpy, C.XDefaultRootWindow(dpy),
  753. 0, 0, C.uint(cnf.Size.X), C.uint(cnf.Size.Y),
  754. 0, C.CopyFromParent, C.InputOutput, nil,
  755. C.CWEventMask|C.CWBackPixmap|C.CWOverrideRedirect, &swa)
  756. w := &x11Window{
  757. w: gioWin, x: dpy, xw: win,
  758. metric: cfg,
  759. xkb: xkb,
  760. xkbEventBase: xkbEventBase,
  761. wakeups: make(chan struct{}, 1),
  762. config: Config{Size: cnf.Size},
  763. }
  764. w.handler = x11EventHandler{w: w, xev: new(C.XEvent), text: make([]byte, 4)}
  765. w.notify.read = pipe[0]
  766. w.notify.write = pipe[1]
  767. w.w.SetDriver(w)
  768. if err := w.updateXkbKeymap(); err != nil {
  769. w.destroy()
  770. return err
  771. }
  772. var hints C.XWMHints
  773. hints.input = C.True
  774. hints.flags = C.InputHint
  775. C.XSetWMHints(dpy, win, &hints)
  776. name := C.CString(ID)
  777. defer C.free(unsafe.Pointer(name))
  778. wmhints := C.XClassHint{name, name}
  779. C.XSetClassHint(dpy, win, &wmhints)
  780. w.atoms.utf8string = w.atom("UTF8_STRING", false)
  781. w.atoms.plaintext = w.atom("text/plain;charset=utf-8", false)
  782. w.atoms.gtk_text_buffer_contents = w.atom("GTK_TEXT_BUFFER_CONTENTS", false)
  783. w.atoms.evDelWindow = w.atom("WM_DELETE_WINDOW", false)
  784. w.atoms.clipboard = w.atom("CLIPBOARD", false)
  785. w.atoms.primary = w.atom("PRIMARY", false)
  786. w.atoms.clipboardContent = w.atom("CLIPBOARD_CONTENT", false)
  787. w.atoms.atom = w.atom("ATOM", false)
  788. w.atoms.targets = w.atom("TARGETS", false)
  789. w.atoms.wmName = w.atom("_NET_WM_NAME", false)
  790. w.atoms.wmState = w.atom("_NET_WM_STATE", false)
  791. w.atoms.wmStateFullscreen = w.atom("_NET_WM_STATE_FULLSCREEN", false)
  792. w.atoms.wmActiveWindow = w.atom("_NET_ACTIVE_WINDOW", false)
  793. w.atoms.wmStateMaximizedHorz = w.atom("_NET_WM_STATE_MAXIMIZED_HORZ", false)
  794. w.atoms.wmStateMaximizedVert = w.atom("_NET_WM_STATE_MAXIMIZED_VERT", false)
  795. // extensions
  796. C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1)
  797. // make the window visible on the screen
  798. C.XMapWindow(dpy, win)
  799. w.Configure(options)
  800. w.ProcessEvent(X11ViewEvent{Display: unsafe.Pointer(dpy), Window: uintptr(win)})
  801. return nil
  802. }
  803. // detectUIScale reports the system UI scale, or 1.0 if it fails.
  804. func x11DetectUIScale(dpy *C.Display) float32 {
  805. // default fixed DPI value used in most desktop UI toolkits
  806. const defaultDesktopDPI = 96
  807. var scale float32 = 1.0
  808. // Get actual DPI from X resource Xft.dpi (set by GTK and Qt).
  809. // This value is entirely based on user preferences and conflates both
  810. // screen (UI) scaling and font scale.
  811. rms := C.XResourceManagerString(dpy)
  812. if rms != nil {
  813. db := C.XrmGetStringDatabase(rms)
  814. if db != nil {
  815. var (
  816. t *C.char
  817. v C.XrmValue
  818. )
  819. if C.XrmGetResource(db, (*C.char)(unsafe.Pointer(&[]byte("Xft.dpi\x00")[0])),
  820. (*C.char)(unsafe.Pointer(&[]byte("Xft.Dpi\x00")[0])), &t, &v) != C.False {
  821. if t != nil && C.GoString(t) == "String" {
  822. f, err := strconv.ParseFloat(C.GoString(v.addr), 32)
  823. if err == nil {
  824. scale = float32(f) / defaultDesktopDPI
  825. }
  826. }
  827. }
  828. C.XrmDestroyDatabase(db)
  829. }
  830. }
  831. return scale
  832. }
  833. func (w *x11Window) updateXkbKeymap() error {
  834. w.xkb.DestroyKeymapState()
  835. ctx := (*C.struct_xkb_context)(unsafe.Pointer(w.xkb.Ctx))
  836. xcb := C.XGetXCBConnection(w.x)
  837. if xcb == nil {
  838. return errors.New("x11: XGetXCBConnection failed")
  839. }
  840. xkbDevID := C.xkb_x11_get_core_keyboard_device_id(xcb)
  841. if xkbDevID == -1 {
  842. return errors.New("x11: xkb_x11_get_core_keyboard_device_id failed")
  843. }
  844. keymap := C.xkb_x11_keymap_new_from_device(ctx, xcb, xkbDevID, C.XKB_KEYMAP_COMPILE_NO_FLAGS)
  845. if keymap == nil {
  846. return errors.New("x11: xkb_x11_keymap_new_from_device failed")
  847. }
  848. state := C.xkb_x11_state_new_from_device(keymap, xcb, xkbDevID)
  849. if state == nil {
  850. C.xkb_keymap_unref(keymap)
  851. return errors.New("x11: xkb_x11_keymap_new_from_device failed")
  852. }
  853. w.xkb.SetKeymap(unsafe.Pointer(keymap), unsafe.Pointer(state))
  854. return nil
  855. }