github.com/rajveermalviya/gamen@v0.1.2-0.20220930195403-9be15877c1aa/internal/xcb/display.go (about)

     1  //go:build linux && !android
     2  
     3  package xcb
     4  
     5  /*
     6  
     7  #include <stdlib.h>
     8  
     9  #include <X11/Xlib-xcb.h>
    10  #include <xcb/randr.h>
    11  #include <xcb/xinput.h>
    12  #include <xcb/xkb.h>
    13  #include <xcb/xcb.h>
    14  
    15  #include <xkbcommon/xkbcommon-x11.h>
    16  
    17  */
    18  import "C"
    19  
    20  import (
    21  	"errors"
    22  	"log"
    23  	"sync"
    24  	"time"
    25  	"unsafe"
    26  
    27  	"github.com/rajveermalviya/gamen/cursors"
    28  	"github.com/rajveermalviya/gamen/dpi"
    29  	"github.com/rajveermalviya/gamen/events"
    30  	"github.com/rajveermalviya/gamen/internal/common/atomicx"
    31  	"github.com/rajveermalviya/gamen/internal/xkbcommon"
    32  	"golang.org/x/sys/unix"
    33  )
    34  
    35  type Display struct {
    36  	l *xcb_library
    37  
    38  	mu sync.Mutex
    39  	// we allow destroy function to be called multiple
    40  	// times, but in reality we run it once
    41  	destroyRequested atomicx.Bool
    42  	destroyed        atomicx.Bool
    43  	doneFirstLoop    bool
    44  
    45  	xlibDisp *C.Display
    46  	xcbConn  *C.struct_xcb_connection_t
    47  	screens  []*Output
    48  
    49  	xrandrFirstEvent C.uint8_t
    50  
    51  	xiOpcode     C.uint8_t
    52  	xiFirstEvent C.uint8_t
    53  
    54  	xkbFirstEvent C.uint8_t
    55  
    56  	xkb           *xkbcommon.Xkb
    57  	deviceID      int32
    58  	firstXkbEvent C.uint8_t
    59  
    60  	// state
    61  	cursors            map[cursors.Icon]C.xcb_cursor_t // shared mutex
    62  	lastMousePositionX C.xcb_input_fp1616_t            // shared mutex
    63  	lastMousePositionY C.xcb_input_fp1616_t            // shared mutex
    64  
    65  	windows          map[C.xcb_window_t]*Window                  // non-shared
    66  	scrollingDevices map[C.xcb_input_device_id_t]scrollingDevice // non-shared
    67  	focus            C.xcb_window_t                              // non-shared
    68  	modifiers        events.ModifiersState                       // non-shared
    69  
    70  	// atoms
    71  	wmProtocols             C.xcb_atom_t
    72  	wmDeleteWindow          C.xcb_atom_t
    73  	wmState                 C.xcb_atom_t
    74  	wmChangeState           C.xcb_atom_t
    75  	netWmState              C.xcb_atom_t
    76  	netWmStateMaximizedHorz C.xcb_atom_t
    77  	netWmStateMaximizedVert C.xcb_atom_t
    78  	netWmStateFullscreen    C.xcb_atom_t
    79  	netWmMoveResize         C.xcb_atom_t
    80  	motifWmHints            C.xcb_atom_t
    81  	relHorizWheel           C.xcb_atom_t
    82  	relVertWheel            C.xcb_atom_t
    83  	relHorizScroll          C.xcb_atom_t
    84  	relVertScroll           C.xcb_atom_t
    85  }
    86  
    87  func NewDisplay() (*Display, error) {
    88  	l, err := open_xcb_library()
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	l.XInitThreads()
    94  	xlibDisp := l.XOpenDisplay(nil)
    95  	if xlibDisp == nil {
    96  		return nil, errors.New("XOpenDisplay failed")
    97  	}
    98  	xcbConn := l.XGetXCBConnection(xlibDisp)
    99  	l.XSetEventQueueOwner(xlibDisp, C.XCBOwnsEventQueue)
   100  
   101  	d := &Display{
   102  		l:                l,
   103  		xlibDisp:         xlibDisp,
   104  		xcbConn:          xcbConn,
   105  		windows:          map[C.xcb_window_t]*Window{},
   106  		scrollingDevices: map[C.xcb_input_device_id_t]scrollingDevice{},
   107  		cursors:          map[cursors.Icon]C.xcb_cursor_t{},
   108  	}
   109  
   110  	// xcb-randr
   111  	{
   112  		reply := l.xcb_get_extension_data(xcbConn, (*C.xcb_extension_t)(l.xcb_randr_id))
   113  		if reply == nil || reply.present == 0 {
   114  			return nil, errors.New("xcb-randr not available")
   115  		}
   116  
   117  		query := l.xcb_randr_query_version_reply(
   118  			xcbConn,
   119  			C.XCB_RANDR_MAJOR_VERSION,
   120  			C.XCB_RANDR_MINOR_VERSION,
   121  		)
   122  		defer C.free(unsafe.Pointer(query))
   123  		if query == nil || (query.major_version < 1 || (query.major_version == 1 && query.minor_version < 2)) {
   124  			return nil, errors.New("xcb-randr not available")
   125  		}
   126  
   127  		d.xrandrFirstEvent = reply.first_event
   128  	}
   129  
   130  	// xcb-xinput
   131  	{
   132  		reply := l.xcb_get_extension_data(xcbConn, (*C.xcb_extension_t)(l.xcb_input_id))
   133  		if reply == nil || reply.present == 0 {
   134  			return nil, errors.New("xcb-xinput not available")
   135  		}
   136  
   137  		query := l.xcb_input_xi_query_version_reply(
   138  			xcbConn,
   139  			C.XCB_INPUT_MAJOR_VERSION,
   140  			C.XCB_INPUT_MINOR_VERSION,
   141  		)
   142  		defer C.free(unsafe.Pointer(query))
   143  		if query == nil || query.major_version < 2 {
   144  			return nil, errors.New("xcb-xinput not available")
   145  		}
   146  	}
   147  
   148  	// xcb-xkb
   149  	{
   150  		reply := l.xcb_get_extension_data(xcbConn, (*C.xcb_extension_t)(l.xcb_xkb_id))
   151  		if reply == nil || reply.present == 0 {
   152  			return nil, errors.New("xcb-xkb not available")
   153  		}
   154  
   155  		query := l.xcb_xkb_use_extension_reply(
   156  			xcbConn,
   157  			1,
   158  			0,
   159  		)
   160  		defer C.free(unsafe.Pointer(query))
   161  		if query == nil || query.supported == 0 {
   162  			return nil, errors.New("xcb-xkb not available")
   163  		}
   164  	}
   165  
   166  	// atoms
   167  	{
   168  		d.wmProtocols = d.internAtom(true, "WM_PROTOCOLS")
   169  		d.wmDeleteWindow = d.internAtom(false, "WM_DELETE_WINDOW")
   170  		d.wmState = d.internAtom(false, "WM_STATE")
   171  		d.wmChangeState = d.internAtom(false, "WM_CHANGE_STATE")
   172  
   173  		d.netWmState = d.internAtom(false, "_NET_WM_STATE")
   174  		d.netWmStateMaximizedHorz = d.internAtom(false, "_NET_WM_STATE_MAXIMIZED_HORZ")
   175  		d.netWmStateMaximizedVert = d.internAtom(false, "_NET_WM_STATE_MAXIMIZED_VERT")
   176  		d.netWmStateFullscreen = d.internAtom(false, "_NET_WM_STATE_FULLSCREEN")
   177  		d.netWmMoveResize = d.internAtom(false, "_NET_WM_MOVERESIZE")
   178  
   179  		d.motifWmHints = d.internAtom(false, "_MOTIF_WM_HINTS")
   180  
   181  		d.relHorizWheel = d.internAtom(false, "Rel Horiz Wheel")
   182  		d.relVertWheel = d.internAtom(false, "Rel Vert Wheel")
   183  		d.relHorizScroll = d.internAtom(false, "Rel Horiz Scroll")
   184  		d.relVertScroll = d.internAtom(false, "Rel Vert Scroll")
   185  	}
   186  
   187  	setup := l.xcb_get_setup(xcbConn)
   188  	d.initializeOutputs(setup)
   189  
   190  	if err := d.xiSetupScrollingDevices(C.XCB_INPUT_DEVICE_ALL); err != nil {
   191  		return nil, err
   192  	}
   193  
   194  	// xi events
   195  	{
   196  		var eventMask struct {
   197  			deviceid C.xcb_input_device_id_t
   198  			mask_len C.uint16_t
   199  			mask     [8]C.uint8_t
   200  		}
   201  
   202  		eventMask.deviceid = C.XCB_INPUT_DEVICE_ALL
   203  		eventMask.mask_len = 1
   204  		setXiMask(&eventMask.mask, C.XCB_INPUT_HIERARCHY)
   205  		setXiMask(&eventMask.mask, C.XCB_INPUT_DEVICE_CHANGED)
   206  
   207  		d.l.xcb_input_xi_select_events(xcbConn, 0, 1, (*C.xcb_input_event_mask_t)(unsafe.Pointer(&eventMask)))
   208  	}
   209  
   210  	xkb, deviceID, firstXkbEvent, err := xkbcommon.NewFromXcb((*xkbcommon.XcbConnection)(xcbConn))
   211  	if err != nil {
   212  		log.Printf("unable to inititalize xkbcommon: %v\n", err)
   213  	}
   214  	d.xkb = xkb
   215  	d.firstXkbEvent = C.uint8_t(firstXkbEvent)
   216  	d.deviceID = deviceID
   217  
   218  	return d, nil
   219  }
   220  
   221  func (d *Display) Poll() bool {
   222  	events := make([]*C.xcb_generic_event_t, 0, 2048)
   223  
   224  	for {
   225  		ev := d.l.xcb_poll_for_event(d.xcbConn)
   226  		if ev == nil {
   227  			break
   228  		}
   229  
   230  		events = append(events, ev)
   231  	}
   232  
   233  	for _, ev := range events {
   234  		d.processEvent(ev)
   235  	}
   236  
   237  	if d.destroyRequested.Load() && !d.destroyed.Load() {
   238  		d.destroy()
   239  		return false
   240  	}
   241  	return !d.destroyed.Load()
   242  }
   243  
   244  func (d *Display) Wait() bool {
   245  	if !d.doneFirstLoop {
   246  		d.doneFirstLoop = true
   247  		return d.Poll()
   248  	}
   249  
   250  	fds := []unix.PollFd{{
   251  		Fd:     int32(d.l.xcb_get_file_descriptor(d.xcbConn)),
   252  		Events: unix.POLLIN,
   253  	}}
   254  	if !poll(fds, -1) {
   255  		return false
   256  	}
   257  
   258  	return d.Poll()
   259  }
   260  
   261  func (d *Display) WaitTimeout(timeout time.Duration) bool {
   262  	if !d.doneFirstLoop {
   263  		d.doneFirstLoop = true
   264  		return d.Poll()
   265  	}
   266  
   267  	fds := []unix.PollFd{{
   268  		Fd:     int32(d.l.xcb_get_file_descriptor(d.xcbConn)),
   269  		Events: unix.POLLIN,
   270  	}}
   271  	if !poll(fds, timeout) {
   272  		return false
   273  	}
   274  
   275  	return d.Poll()
   276  }
   277  
   278  // helper poll function to correctly handle
   279  // timeouts when EINTR occurs
   280  func poll(fds []unix.PollFd, timeout time.Duration) bool {
   281  	switch timeout {
   282  	case -1:
   283  		for {
   284  			result, errno := unix.Ppoll(fds, nil, nil)
   285  			if result > 0 {
   286  				return true
   287  			} else if result == -1 && errno != unix.EINTR && errno != unix.EAGAIN {
   288  				return false
   289  			}
   290  		}
   291  
   292  	case 0:
   293  		for {
   294  			result, errno := unix.Ppoll(fds, &unix.Timespec{}, nil)
   295  			if result == -1 && errno != unix.EINTR && errno != unix.EAGAIN {
   296  				return false
   297  			} else {
   298  				return true
   299  			}
   300  		}
   301  
   302  	default:
   303  		for {
   304  			start := time.Now()
   305  
   306  			ts := unix.NsecToTimespec(int64(timeout))
   307  			result, errno := unix.Ppoll(fds, &ts, nil)
   308  
   309  			timeout -= time.Since(start)
   310  
   311  			if result > 0 {
   312  				return true
   313  			} else if result == -1 && errno != unix.EINTR && errno != unix.EAGAIN {
   314  				return false
   315  			} else if timeout <= 0 {
   316  				return true
   317  			}
   318  		}
   319  	}
   320  }
   321  
   322  func (d *Display) Destroy() {
   323  	d.destroyRequested.Store(true)
   324  }
   325  
   326  func (d *Display) destroy() {
   327  	for _, w := range d.windows {
   328  		w.Destroy()
   329  	}
   330  
   331  	{
   332  		d.mu.Lock()
   333  		for i, c := range d.cursors {
   334  			d.l.xcb_free_cursor(d.xcbConn, c)
   335  			delete(d.cursors, i)
   336  		}
   337  		d.mu.Unlock()
   338  	}
   339  
   340  	if d.xkb != nil {
   341  		d.xkb.Destroy()
   342  		d.xkb = nil
   343  	}
   344  
   345  	if d.xlibDisp != nil {
   346  		d.l.XCloseDisplay(d.xlibDisp)
   347  		d.xlibDisp = nil
   348  		d.xcbConn = nil
   349  	}
   350  
   351  	if d.l != nil {
   352  		d.l.close()
   353  		d.l = nil
   354  	}
   355  
   356  	d.destroyed.Store(true)
   357  }
   358  
   359  func (d *Display) processEvent(e *C.xcb_generic_event_t) {
   360  	defer C.free(unsafe.Pointer(e))
   361  
   362  	switch e.response_type & ^C.uint8_t(0x80) {
   363  	case C.XCB_CONFIGURE_NOTIFY:
   364  		ev := (*C.xcb_configure_notify_event_t)(unsafe.Pointer(e))
   365  
   366  		w, ok := d.windows[ev.event]
   367  		if !ok {
   368  			return
   369  		}
   370  
   371  		size := dpi.PhysicalSize[uint32]{
   372  			Width:  uint32(ev.width),
   373  			Height: uint32(ev.height),
   374  		}
   375  
   376  		w.mu.Lock()
   377  		if w.size != size {
   378  			w.size = size
   379  			w.mu.Unlock()
   380  
   381  			if cb := w.resizedCb.Load(); cb != nil {
   382  				if cb := (*cb); cb != nil {
   383  					cb(size.Width, size.Height, 1)
   384  				}
   385  			}
   386  		} else {
   387  			w.mu.Unlock()
   388  		}
   389  
   390  	case C.XCB_CLIENT_MESSAGE:
   391  		ev := (*C.xcb_client_message_event_t)(unsafe.Pointer(e))
   392  
   393  		w, ok := d.windows[ev.window]
   394  		if !ok {
   395  			return
   396  		}
   397  
   398  		data32 := unsafe.Slice((*C.xcb_atom_t)(unsafe.Pointer(&ev.data)), 5)
   399  		if data32[0] == d.wmDeleteWindow {
   400  
   401  			if cb := w.closeRequestedCb.Load(); cb != nil {
   402  				if cb := (*cb); cb != nil {
   403  					cb()
   404  				}
   405  			}
   406  		}
   407  
   408  	case C.XCB_KEY_PRESS:
   409  		ev := (*C.xcb_key_press_event_t)(unsafe.Pointer(e))
   410  
   411  		w, ok := d.windows[ev.event]
   412  		if !ok {
   413  			return
   414  		}
   415  
   416  		sym := d.xkb.GetOneSym(xkbcommon.KeyCode(ev.detail))
   417  
   418  		if cb := w.keyboardInputCb.Load(); cb != nil {
   419  			if cb := (*cb); cb != nil {
   420  				cb(
   421  					events.ButtonStatePressed,
   422  					events.ScanCode(ev.detail),
   423  					xkbcommon.KeySymToVirtualKey(sym),
   424  				)
   425  			}
   426  		}
   427  
   428  		if cb := w.receivedCharacterCb.Load(); cb != nil {
   429  			if cb := (*cb); cb != nil {
   430  				utf8 := d.xkb.GetUtf8(xkbcommon.KeyCode(ev.detail), xkbcommon.KeySym(sym))
   431  				for _, char := range utf8 {
   432  					cb(char)
   433  				}
   434  			}
   435  		}
   436  
   437  	case C.XCB_KEY_RELEASE:
   438  		ev := (*C.xcb_key_release_event_t)(unsafe.Pointer(e))
   439  
   440  		w, ok := d.windows[ev.event]
   441  		if !ok {
   442  			return
   443  		}
   444  
   445  		if cb := w.keyboardInputCb.Load(); cb != nil {
   446  			if cb := (*cb); cb != nil {
   447  				sym := d.xkb.GetOneSym(xkbcommon.KeyCode(ev.detail))
   448  
   449  				cb(
   450  					events.ButtonStateReleased,
   451  					events.ScanCode(ev.detail),
   452  					xkbcommon.KeySymToVirtualKey(sym),
   453  				)
   454  			}
   455  		}
   456  
   457  	case C.XCB_GE_GENERIC:
   458  		d.processXIEvents(e)
   459  
   460  	case d.firstXkbEvent:
   461  		d.processXkbEvent(e)
   462  	}
   463  }
   464  
   465  func (d *Display) processXIEvents(e *C.xcb_generic_event_t) {
   466  	ev := (*C.xcb_ge_generic_event_t)(unsafe.Pointer(e))
   467  
   468  	switch ev.event_type {
   469  	case C.XCB_INPUT_DEVICE_CHANGED:
   470  		ev := (*C.xcb_input_device_changed_event_t)(unsafe.Pointer(ev))
   471  
   472  		switch ev.reason {
   473  		case C.XCB_INPUT_CHANGE_REASON_DEVICE_CHANGE:
   474  			// reset all devices
   475  			d.xiSetupScrollingDevices(ev.sourceid)
   476  
   477  		case C.XCB_INPUT_CHANGE_REASON_SLAVE_SWITCH:
   478  			// only reset current device
   479  			d.resetScrollPosition(ev.sourceid)
   480  		}
   481  
   482  	case C.XCB_INPUT_HIERARCHY:
   483  		ev := (*C.xcb_input_hierarchy_event_t)(unsafe.Pointer(ev))
   484  
   485  		// ignore other events
   486  		if ev.flags&(C.XCB_INPUT_HIERARCHY_MASK_SLAVE_REMOVED|C.XCB_INPUT_HIERARCHY_MASK_SLAVE_ADDED) == 0 {
   487  			return
   488  		}
   489  
   490  		d.xiSetupScrollingDevices(C.XCB_INPUT_DEVICE_ALL)
   491  
   492  	case C.XCB_INPUT_ENTER:
   493  		ev := (*C.xcb_input_enter_event_t)(unsafe.Pointer(e))
   494  
   495  		d.resetScrollPosition(ev.sourceid)
   496  
   497  		w, ok := d.windows[ev.event]
   498  		if !ok {
   499  			return
   500  		}
   501  
   502  		if cb := w.cursorEnteredCb.Load(); cb != nil {
   503  			if cb := (*cb); cb != nil {
   504  				cb()
   505  			}
   506  		}
   507  
   508  	case C.XCB_INPUT_LEAVE:
   509  		ev := (*C.xcb_input_leave_event_t)(unsafe.Pointer(e))
   510  
   511  		w, ok := d.windows[ev.event]
   512  		if !ok {
   513  			return
   514  		}
   515  
   516  		if cb := w.cursorLeftCb.Load(); cb != nil {
   517  			if cb := (*cb); cb != nil {
   518  				cb()
   519  			}
   520  		}
   521  
   522  	case C.XCB_INPUT_FOCUS_IN:
   523  		ev := (*C.xcb_input_focus_in_event_t)(unsafe.Pointer(e))
   524  
   525  		d.focus = ev.event
   526  
   527  		w, ok := d.windows[ev.event]
   528  		if !ok {
   529  			return
   530  		}
   531  
   532  		if cb := w.focusedCb.Load(); cb != nil {
   533  			if cb := (*cb); cb != nil {
   534  				cb()
   535  			}
   536  		}
   537  
   538  	case C.XCB_INPUT_FOCUS_OUT:
   539  		ev := (*C.xcb_input_focus_out_event_t)(unsafe.Pointer(e))
   540  
   541  		w, ok := d.windows[ev.event]
   542  		if ok {
   543  			if d.modifiers != 0 {
   544  				if cb := w.modifiersChangedCb.Load(); cb != nil {
   545  					if cb := (*cb); cb != nil {
   546  						cb(0)
   547  					}
   548  				}
   549  			}
   550  
   551  			if cb := w.unfocusedCb.Load(); cb != nil {
   552  				if cb := (*cb); cb != nil {
   553  					cb()
   554  				}
   555  			}
   556  		}
   557  
   558  		d.focus = 0
   559  
   560  	case C.XCB_INPUT_BUTTON_PRESS, C.XCB_INPUT_BUTTON_RELEASE:
   561  		ev := (*C.xcb_input_button_press_event_t)(unsafe.Pointer(e))
   562  
   563  		d.mu.Lock()
   564  		d.lastMousePositionX = ev.event_x
   565  		d.lastMousePositionY = ev.event_y
   566  		d.mu.Unlock()
   567  
   568  		// ignore emulated touch & mouse wheel events
   569  		if ev.flags&C.XCB_INPUT_POINTER_EVENT_FLAGS_POINTER_EMULATED != 0 {
   570  			return
   571  		}
   572  
   573  		w, ok := d.windows[ev.event]
   574  		if !ok {
   575  			return
   576  		}
   577  
   578  		if cb := w.mouseInputCb.Load(); cb != nil {
   579  			if cb := (*cb); cb != nil {
   580  				var state events.ButtonState
   581  				if ev.event_type == C.XCB_INPUT_BUTTON_PRESS {
   582  					state = events.ButtonStatePressed
   583  				} else {
   584  					state = events.ButtonStateReleased
   585  				}
   586  
   587  				switch ev.detail {
   588  				case C.XCB_BUTTON_INDEX_1:
   589  					cb(state, events.MouseButtonLeft)
   590  
   591  				case C.XCB_BUTTON_INDEX_2:
   592  					cb(state, events.MouseButtonMiddle)
   593  
   594  				case C.XCB_BUTTON_INDEX_3:
   595  					cb(state, events.MouseButtonRight)
   596  
   597  				case 4, 5, 6, 7:
   598  					// ignore, handled below
   599  
   600  				default:
   601  					cb(state, events.MouseButton(ev.detail))
   602  				}
   603  			}
   604  		}
   605  
   606  		if cb := w.mouseWheelCb.Load(); cb != nil {
   607  			if cb := (*cb); cb != nil {
   608  				switch ev.detail {
   609  				case 4:
   610  					cb(events.MouseScrollDeltaLine, events.MouseScrollAxisVertical, 1)
   611  				case 5:
   612  					cb(events.MouseScrollDeltaLine, events.MouseScrollAxisVertical, -1)
   613  				case 6:
   614  					cb(events.MouseScrollDeltaLine, events.MouseScrollAxisHorizontal, 1)
   615  				case 7:
   616  					cb(events.MouseScrollDeltaLine, events.MouseScrollAxisVertical, -1)
   617  				}
   618  			}
   619  		}
   620  
   621  	case C.XCB_INPUT_MOTION:
   622  		ev := (*C.xcb_input_motion_event_t)(unsafe.Pointer(e))
   623  
   624  		d.mu.Lock()
   625  		d.lastMousePositionX = ev.event_x
   626  		d.lastMousePositionY = ev.event_y
   627  		d.mu.Unlock()
   628  
   629  		w, ok := d.windows[ev.event]
   630  		if ok {
   631  			newCursorPos := dpi.PhysicalPosition[float64]{
   632  				X: fixed1616ToFloat64(ev.event_x),
   633  				Y: fixed1616ToFloat64(ev.event_y),
   634  			}
   635  
   636  			w.mu.Lock()
   637  			if w.cursorPos != newCursorPos {
   638  				w.cursorPos = newCursorPos
   639  				w.mu.Unlock()
   640  
   641  				if cb := w.cursorMovedCb.Load(); cb != nil {
   642  					if cb := (*cb); cb != nil {
   643  						cb(newCursorPos.X, newCursorPos.Y)
   644  					}
   645  				}
   646  			} else {
   647  				w.mu.Unlock()
   648  			}
   649  		}
   650  
   651  		dev, ok := d.scrollingDevices[ev.sourceid]
   652  		if !ok {
   653  			return
   654  		}
   655  
   656  		maskLen := d.l.xcb_input_button_press_valuator_mask_length(ev)
   657  		mask := unsafe.Slice(d.l.xcb_input_button_press_valuator_mask(ev), maskLen)
   658  
   659  		axisValues := unsafe.Slice(
   660  			d.l.xcb_input_button_press_axisvalues(ev),
   661  			d.l.xcb_input_button_press_axisvalues_length(ev),
   662  		)
   663  
   664  		axisValuesIndex := 0
   665  
   666  		for i := C.uint16_t(0); i < C.uint16_t(maskLen)*8; i++ {
   667  			if hasXiMask(mask, i) {
   668  				if dev.horizontalScroll.index == i {
   669  					x := fixed3232ToFloat64(axisValues[axisValuesIndex])
   670  					axisValuesIndex++
   671  
   672  					delta := (x - dev.horizontalScroll.position) / dev.horizontalScroll.increment
   673  					dev.horizontalScroll.position = x
   674  					d.scrollingDevices[ev.sourceid] = dev
   675  
   676  					if cb := w.mouseWheelCb.Load(); cb != nil {
   677  						if cb := (*cb); cb != nil {
   678  							cb(
   679  								events.MouseScrollDeltaLine,
   680  								events.MouseScrollAxisHorizontal,
   681  								-float64(delta),
   682  							)
   683  						}
   684  					}
   685  				} else if dev.verticalScroll.index == i {
   686  					x := fixed3232ToFloat64(axisValues[axisValuesIndex])
   687  					axisValuesIndex++
   688  
   689  					delta := (x - dev.verticalScroll.position) / dev.verticalScroll.increment
   690  					dev.verticalScroll.position = x
   691  					d.scrollingDevices[ev.sourceid] = dev
   692  
   693  					if cb := w.mouseWheelCb.Load(); cb != nil {
   694  						if cb := (*cb); cb != nil {
   695  							cb(
   696  								events.MouseScrollDeltaLine,
   697  								events.MouseScrollAxisVertical,
   698  								-float64(delta),
   699  							)
   700  						}
   701  					}
   702  				}
   703  			}
   704  		}
   705  	}
   706  }
   707  
   708  func (d *Display) processXkbEvent(e *C.xcb_generic_event_t) {
   709  	type xkbEventBase struct {
   710  		response_type C.uint8_t
   711  		xkbType       C.uint8_t
   712  		sequence      C.uint16_t
   713  		time          C.xcb_timestamp_t
   714  		deviceID      C.uint8_t
   715  		_             [3]byte
   716  	}
   717  
   718  	ev := (*xkbEventBase)(unsafe.Pointer(e))
   719  
   720  	if ev.deviceID != C.uint8_t(d.deviceID) {
   721  		return
   722  	}
   723  
   724  	switch ev.xkbType {
   725  	case C.XCB_XKB_NEW_KEYBOARD_NOTIFY:
   726  		ev := (*C.xcb_xkb_new_keyboard_notify_event_t)(unsafe.Pointer(e))
   727  
   728  		if ev.changed&C.XCB_XKB_NKN_DETAIL_KEYCODES != 0 {
   729  			d.xkb.UpdateKeymap((*xkbcommon.XcbConnection)(d.xcbConn), d.deviceID)
   730  		}
   731  
   732  	case C.XCB_XKB_MAP_NOTIFY:
   733  		d.xkb.UpdateKeymap((*xkbcommon.XcbConnection)(d.xcbConn), d.deviceID)
   734  
   735  	case C.XCB_XKB_STATE_NOTIFY:
   736  		ev := (*C.xcb_xkb_state_notify_event_t)(unsafe.Pointer(e))
   737  
   738  		if d.xkb.UpdateMask(
   739  			xkbcommon.ModMask(ev.baseMods),
   740  			xkbcommon.ModMask(ev.latchedMods),
   741  			xkbcommon.ModMask(ev.lockedMods),
   742  			xkbcommon.LayoutIndex(ev.baseGroup),
   743  			xkbcommon.LayoutIndex(ev.latchedGroup),
   744  			xkbcommon.LayoutIndex(ev.lockedGroup),
   745  		) {
   746  			return
   747  		}
   748  
   749  		var m events.ModifiersState
   750  
   751  		if d.xkb.ModIsShift() {
   752  			m |= events.ModifiersStateShift
   753  		}
   754  		if d.xkb.ModIsCtrl() {
   755  			m |= events.ModifiersStateCtrl
   756  		}
   757  		if d.xkb.ModIsAlt() {
   758  			m |= events.ModifiersStateAlt
   759  		}
   760  		if d.xkb.ModIsLogo() {
   761  			m |= events.ModifiersStateLogo
   762  		}
   763  
   764  		d.modifiers = m
   765  
   766  		if d.focus == 0 {
   767  			return
   768  		}
   769  
   770  		w, ok := d.windows[d.focus]
   771  		if !ok {
   772  			return
   773  		}
   774  
   775  		if cb := w.modifiersChangedCb.Load(); cb != nil {
   776  			if cb := (*cb); cb != nil {
   777  				cb(m)
   778  			}
   779  		}
   780  	}
   781  }