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

     1  //go:build linux && !android
     2  
     3  package xcb
     4  
     5  /*
     6  
     7  #include <stdlib.h>
     8  #include <X11/Xlib-xcb.h>
     9  #include <xcb/xinput.h>
    10  #include <xcb/xcb_icccm.h>
    11  #include <X11/Xcursor/Xcursor.h>
    12  
    13  */
    14  import "C"
    15  
    16  import (
    17  	"errors"
    18  	"math"
    19  	"sync"
    20  	"unsafe"
    21  
    22  	"github.com/rajveermalviya/gamen/cursors"
    23  	"github.com/rajveermalviya/gamen/dpi"
    24  	"github.com/rajveermalviya/gamen/events"
    25  	"github.com/rajveermalviya/gamen/internal/common/atomicx"
    26  	"github.com/rajveermalviya/gamen/internal/common/mathx"
    27  )
    28  
    29  type Window struct {
    30  	d *Display
    31  	// x window id
    32  	win C.xcb_window_t
    33  	// we allow destroy function to be called multiple
    34  	// times, but in reality we run it once
    35  	destroyOnce sync.Once
    36  	mu          sync.Mutex
    37  
    38  	// state
    39  	previousCursorIcon cursors.Icon // shared mutex
    40  	currentCursorIcon  cursors.Icon // shared mutex
    41  
    42  	size      dpi.PhysicalSize[uint32]      // non-shared
    43  	cursorPos dpi.PhysicalPosition[float64] // non-shared
    44  
    45  	// callbacks
    46  	resizedCb           atomicx.Pointer[events.WindowResizedCallback]
    47  	closeRequestedCb    atomicx.Pointer[events.WindowCloseRequestedCallback]
    48  	focusedCb           atomicx.Pointer[events.WindowFocusedCallback]
    49  	unfocusedCb         atomicx.Pointer[events.WindowUnfocusedCallback]
    50  	cursorEnteredCb     atomicx.Pointer[events.WindowCursorEnteredCallback]
    51  	cursorLeftCb        atomicx.Pointer[events.WindowCursorLeftCallback]
    52  	cursorMovedCb       atomicx.Pointer[events.WindowCursorMovedCallback]
    53  	mouseWheelCb        atomicx.Pointer[events.WindowMouseScrollCallback]
    54  	mouseInputCb        atomicx.Pointer[events.WindowMouseInputCallback]
    55  	modifiersChangedCb  atomicx.Pointer[events.WindowModifiersChangedCallback]
    56  	keyboardInputCb     atomicx.Pointer[events.WindowKeyboardInputCallback]
    57  	receivedCharacterCb atomicx.Pointer[events.WindowReceivedCharacterCallback]
    58  }
    59  
    60  func NewWindow(d *Display) (*Window, error) {
    61  	win := d.l.xcb_generate_id(d.xcbConn)
    62  
    63  	// create window
    64  	{
    65  		var mask C.uint32_t = C.XCB_CW_BACK_PIXMAP |
    66  			C.XCB_CW_BORDER_PIXEL |
    67  			C.XCB_CW_BIT_GRAVITY |
    68  			C.XCB_CW_EVENT_MASK
    69  		values := [...]C.uint32_t{
    70  			C.XCB_BACK_PIXMAP_NONE,             // none background
    71  			d.screens[0].xcbScreen.black_pixel, // black border
    72  			C.XCB_GRAVITY_NORTH_WEST,           // shift inner window from north west
    73  			C.XCB_EVENT_MASK_STRUCTURE_NOTIFY | // listen for resize, keypress, keyrelease
    74  				C.XCB_EVENT_MASK_KEY_PRESS | // (we listen for other input events via xinput)
    75  				C.XCB_EVENT_MASK_KEY_RELEASE,
    76  		}
    77  
    78  		cookie := d.l.xcb_create_window_checked(d.xcbConn,
    79  			0,
    80  			win,
    81  			d.screens[0].xcbScreen.root,
    82  			0, 0,
    83  			640, 480,
    84  			0,
    85  			C.XCB_WINDOW_CLASS_INPUT_OUTPUT,
    86  			d.screens[0].xcbScreen.root_visual,
    87  			mask, unsafe.Pointer(&values),
    88  		)
    89  		err := d.l.xcb_request_check(d.xcbConn, cookie)
    90  		if err != nil {
    91  			defer C.free(unsafe.Pointer(err))
    92  			return nil, errors.New("unable to create window")
    93  		}
    94  	}
    95  
    96  	// opt into window close event
    97  	{
    98  		wmDeleteWindow := d.wmDeleteWindow
    99  		d.l.xcb_change_property(
   100  			d.xcbConn,
   101  			C.XCB_PROP_MODE_REPLACE,
   102  			win,
   103  			d.wmProtocols,
   104  			C.XCB_ATOM_ATOM,
   105  			32,
   106  			1,
   107  			unsafe.Pointer(&wmDeleteWindow),
   108  		)
   109  	}
   110  
   111  	// select xinput events
   112  	{
   113  		var mask struct {
   114  			head C.xcb_input_event_mask_t
   115  			mask C.xcb_input_xi_event_mask_t
   116  		}
   117  		mask.head.deviceid = C.XCB_INPUT_DEVICE_ALL_MASTER
   118  		mask.head.mask_len = C.uint16_t(unsafe.Sizeof(mask.mask) / unsafe.Sizeof(C.uint32_t(0)))
   119  
   120  		mask.mask = C.XCB_INPUT_XI_EVENT_MASK_MOTION |
   121  			C.XCB_INPUT_XI_EVENT_MASK_ENTER |
   122  			C.XCB_INPUT_XI_EVENT_MASK_LEAVE |
   123  			C.XCB_INPUT_XI_EVENT_MASK_FOCUS_IN |
   124  			C.XCB_INPUT_XI_EVENT_MASK_FOCUS_OUT |
   125  			C.XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS |
   126  			C.XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE
   127  
   128  		d.l.xcb_input_xi_select_events(d.xcbConn, win, 1, &mask.head)
   129  	}
   130  
   131  	w := &Window{
   132  		d:                 d,
   133  		win:               win,
   134  		currentCursorIcon: cursors.Default,
   135  	}
   136  
   137  	w.setDecorations(d.motifWmHints, true)
   138  
   139  	// map window
   140  	{
   141  		cookie := d.l.xcb_map_window_checked(d.xcbConn, win)
   142  		err := d.l.xcb_request_check(d.xcbConn, cookie)
   143  		if err != nil {
   144  			defer C.free(unsafe.Pointer(err))
   145  			return nil, errors.New("unable to map window")
   146  		}
   147  	}
   148  
   149  	d.l.xcb_flush(d.xcbConn)
   150  
   151  	d.windows[win] = w
   152  	return w, nil
   153  }
   154  
   155  func (w *Window) XcbConnection() unsafe.Pointer {
   156  	return unsafe.Pointer(w.d.xcbConn)
   157  }
   158  func (w *Window) XcbWindow() uint32 {
   159  	return uint32(w.win)
   160  }
   161  
   162  func (w *Window) XlibDisplay() unsafe.Pointer {
   163  	return unsafe.Pointer(w.d.xlibDisp)
   164  }
   165  func (w *Window) XlibWindow() uint32 {
   166  	return uint32(w.win)
   167  }
   168  
   169  func (w *Window) SetTitle(title string) {
   170  	titlePtr := C.CString(title)
   171  	defer C.free(unsafe.Pointer(titlePtr))
   172  
   173  	w.d.l.xcb_change_property(
   174  		w.d.xcbConn,
   175  		C.XCB_PROP_MODE_REPLACE,
   176  		w.win,
   177  		C.XCB_ATOM_WM_NAME,
   178  		C.XCB_ATOM_STRING, 8,
   179  		C.uint32_t(len(title)+1),
   180  		unsafe.Pointer(titlePtr),
   181  	)
   182  }
   183  
   184  func (w *Window) Destroy() {
   185  	w.destroyOnce.Do(func() {
   186  		w.resizedCb.Store(nil)
   187  		w.closeRequestedCb.Store(nil)
   188  		w.focusedCb.Store(nil)
   189  		w.unfocusedCb.Store(nil)
   190  		w.cursorEnteredCb.Store(nil)
   191  		w.cursorLeftCb.Store(nil)
   192  		w.cursorMovedCb.Store(nil)
   193  		w.mouseWheelCb.Store(nil)
   194  		w.mouseInputCb.Store(nil)
   195  		w.modifiersChangedCb.Store(nil)
   196  		w.keyboardInputCb.Store(nil)
   197  		w.receivedCharacterCb.Store(nil)
   198  
   199  		if _, ok := w.d.windows[w.win]; ok {
   200  			w.d.windows[w.win] = nil
   201  			delete(w.d.windows, w.win)
   202  		}
   203  
   204  		w.d.l.xcb_destroy_window(w.d.xcbConn, w.win)
   205  		w.d.l.xcb_flush(w.d.xcbConn)
   206  	})
   207  }
   208  
   209  func (w *Window) InnerSize() dpi.PhysicalSize[uint32] {
   210  	r := w.d.l.xcb_get_geometry_reply(w.d.xcbConn, w.win)
   211  	if r == nil {
   212  		return dpi.PhysicalSize[uint32]{}
   213  	}
   214  
   215  	defer C.free(unsafe.Pointer(r))
   216  	return dpi.PhysicalSize[uint32]{
   217  		Width:  uint32(r.width),
   218  		Height: uint32(r.height),
   219  	}
   220  }
   221  
   222  func (w *Window) SetInnerSize(size dpi.Size[uint32]) {
   223  	physicalSize := size.ToPhysical(1)
   224  
   225  	var mask C.uint16_t = C.XCB_CONFIG_WINDOW_WIDTH | C.XCB_CONFIG_WINDOW_HEIGHT
   226  	values := [...]uint32{
   227  		mathx.Max(1, mathx.Min(physicalSize.Width, math.MaxInt16)),
   228  		mathx.Max(1, mathx.Min(physicalSize.Height, math.MaxInt16)),
   229  	}
   230  
   231  	w.d.l.xcb_configure_window(w.d.xcbConn, w.win, mask, unsafe.Pointer(&values))
   232  	w.d.l.xcb_flush(w.d.xcbConn)
   233  }
   234  
   235  func (w *Window) SetMinInnerSize(size dpi.Size[uint32]) {
   236  	physicalSize := size.ToPhysical(1)
   237  
   238  	var hints C.xcb_size_hints_t
   239  	w.d.l.xcb_icccm_get_wm_normal_hints_reply(
   240  		w.d.xcbConn,
   241  		w.win,
   242  		&hints,
   243  	)
   244  
   245  	w.d.l.xcb_icccm_size_hints_set_min_size(
   246  		&hints,
   247  		C.int32_t(mathx.Min(physicalSize.Width, math.MaxInt16)),
   248  		C.int32_t(mathx.Min(physicalSize.Height, math.MaxInt16)),
   249  	)
   250  
   251  	w.d.l.xcb_icccm_set_wm_normal_hints(w.d.xcbConn, w.win, &hints)
   252  }
   253  
   254  func (w *Window) SetMaxInnerSize(size dpi.Size[uint32]) {
   255  	physicalSize := size.ToPhysical(1)
   256  
   257  	var hints C.xcb_size_hints_t
   258  	w.d.l.xcb_icccm_get_wm_normal_hints_reply(
   259  		w.d.xcbConn,
   260  		w.win,
   261  		&hints,
   262  	)
   263  
   264  	w.d.l.xcb_icccm_size_hints_set_max_size(
   265  		&hints,
   266  		C.int32_t(mathx.Min(physicalSize.Width, math.MaxInt16)),
   267  		C.int32_t(mathx.Min(physicalSize.Height, math.MaxInt16)),
   268  	)
   269  
   270  	w.d.l.xcb_icccm_set_wm_normal_hints(w.d.xcbConn, w.win, &hints)
   271  }
   272  
   273  func (w *Window) Maximized() bool {
   274  	r := w.d.l.xcb_get_property_reply(
   275  		w.d.xcbConn,
   276  		0,
   277  		w.win,
   278  		w.d.netWmState,
   279  		C.XCB_ATOM_ATOM,
   280  		0,
   281  		1024,
   282  	)
   283  	defer C.free(unsafe.Pointer(r))
   284  
   285  	dataSlice := unsafe.Slice(
   286  		(*C.xcb_atom_t)(w.d.l.xcb_get_property_value(r)),
   287  		uintptr(w.d.l.xcb_get_property_value_length(r))/unsafe.Sizeof(C.xcb_atom_t(0)),
   288  	)
   289  
   290  	var hasMaximizedHorz, hasMaximizedVert bool
   291  	for _, atom := range dataSlice {
   292  		if !hasMaximizedHorz && atom == w.d.netWmStateMaximizedHorz {
   293  			hasMaximizedHorz = true
   294  		}
   295  		if !hasMaximizedVert && atom == w.d.netWmStateMaximizedVert {
   296  			hasMaximizedVert = true
   297  		}
   298  		if hasMaximizedHorz && hasMaximizedVert {
   299  			return true
   300  		}
   301  	}
   302  
   303  	return hasMaximizedHorz && hasMaximizedVert
   304  }
   305  
   306  func (w *Window) SetMinimized() {
   307  	event := C.xcb_client_message_event_t{
   308  		response_type: C.XCB_CLIENT_MESSAGE,
   309  		format:        32,
   310  		sequence:      0,
   311  		window:        w.win,
   312  		_type:         w.d.wmChangeState,
   313  	}
   314  
   315  	data := (*[5]uint32)(unsafe.Pointer(&event.data))
   316  	data[0] = C.XCB_ICCCM_WM_STATE_ICONIC
   317  
   318  	w.d.l.xcb_send_event(
   319  		w.d.xcbConn,
   320  		0,
   321  		w.d.screens[0].xcbScreen.root,
   322  		C.XCB_EVENT_MASK_STRUCTURE_NOTIFY|C.XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
   323  		(*C.char)(unsafe.Pointer(&event)),
   324  	)
   325  
   326  	var hints C.xcb_icccm_wm_hints_t
   327  	if w.d.l.xcb_icccm_get_wm_hints_reply(
   328  		w.d.xcbConn,
   329  		w.win,
   330  		&hints,
   331  	) != 0 {
   332  		w.d.l.xcb_icccm_wm_hints_set_iconic(&hints)
   333  		w.d.l.xcb_icccm_set_wm_hints(w.d.xcbConn, w.win, &hints)
   334  	}
   335  	w.d.l.xcb_flush(w.d.xcbConn)
   336  }
   337  
   338  func (w *Window) SetMaximized(maximized bool) {
   339  	event := C.xcb_client_message_event_t{
   340  		response_type: C.XCB_CLIENT_MESSAGE,
   341  		format:        32,
   342  		sequence:      0,
   343  		window:        w.win,
   344  		_type:         w.d.netWmState,
   345  	}
   346  
   347  	data := (*[5]uint32)(unsafe.Pointer(&event.data))
   348  	if maximized {
   349  		data[0] = 1
   350  	} else {
   351  		data[0] = 0
   352  	}
   353  	data[1] = uint32(w.d.netWmStateMaximizedHorz)
   354  	data[2] = uint32(w.d.netWmStateMaximizedVert)
   355  
   356  	w.d.l.xcb_send_event(
   357  		w.d.xcbConn,
   358  		0,
   359  		w.d.screens[0].xcbScreen.root,
   360  		C.XCB_EVENT_MASK_STRUCTURE_NOTIFY|C.XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
   361  		(*C.char)(unsafe.Pointer(&event)),
   362  	)
   363  	w.d.l.xcb_flush(w.d.xcbConn)
   364  }
   365  
   366  func (w *Window) SetCursorIcon(icon cursors.Icon) {
   367  	if icon == 0 {
   368  		// 0 is internally used to hide cursor,
   369  		// users should instead use SetCursorVisible()
   370  		// so make this no-op
   371  		return
   372  	}
   373  
   374  	w.mu.Lock()
   375  	w.currentCursorIcon = icon
   376  	w.mu.Unlock()
   377  
   378  	cursor := w.d.loadCursorIcon(icon)
   379  	w.d.l.xcb_change_window_attributes(
   380  		w.d.xcbConn,
   381  		w.win,
   382  		C.XCB_CW_CURSOR,
   383  		unsafe.Pointer(&cursor),
   384  	)
   385  	w.d.l.xcb_flush(w.d.xcbConn)
   386  }
   387  
   388  func (w *Window) SetCursorVisible(visible bool) {
   389  	if visible {
   390  		w.mu.Lock()
   391  		if w.currentCursorIcon == 0 {
   392  			w.currentCursorIcon = w.previousCursorIcon
   393  			currentCursor := w.currentCursorIcon
   394  			w.mu.Unlock()
   395  
   396  			cursor := w.d.loadCursorIcon(currentCursor)
   397  			w.d.l.xcb_change_window_attributes(
   398  				w.d.xcbConn,
   399  				w.win,
   400  				C.XCB_CW_CURSOR,
   401  				unsafe.Pointer(&cursor),
   402  			)
   403  			w.d.l.xcb_flush(w.d.xcbConn)
   404  		} else {
   405  			w.mu.Unlock()
   406  		}
   407  	} else {
   408  		w.mu.Lock()
   409  		if w.currentCursorIcon != 0 {
   410  			w.previousCursorIcon = w.currentCursorIcon
   411  			w.currentCursorIcon = 0
   412  			w.mu.Unlock()
   413  
   414  			cursor := w.d.loadCursorIcon(0)
   415  			w.d.l.xcb_change_window_attributes(
   416  				w.d.xcbConn,
   417  				w.win,
   418  				C.XCB_CW_CURSOR,
   419  				unsafe.Pointer(&cursor),
   420  			)
   421  			w.d.l.xcb_flush(w.d.xcbConn)
   422  		} else {
   423  			w.mu.Unlock()
   424  		}
   425  	}
   426  }
   427  
   428  func (w *Window) SetFullscreen(fullscreen bool) {
   429  	event := C.xcb_client_message_event_t{
   430  		response_type: C.XCB_CLIENT_MESSAGE,
   431  		format:        32,
   432  		sequence:      0,
   433  		window:        w.win,
   434  		_type:         w.d.netWmState,
   435  	}
   436  
   437  	data := (*[5]uint32)(unsafe.Pointer(&event.data))
   438  	if fullscreen {
   439  		data[0] = 1
   440  	} else {
   441  		data[0] = 0
   442  	}
   443  	data[1] = uint32(w.d.netWmStateFullscreen)
   444  
   445  	w.d.l.xcb_send_event(
   446  		w.d.xcbConn,
   447  		0,
   448  		w.d.screens[0].xcbScreen.root,
   449  		C.XCB_EVENT_MASK_STRUCTURE_NOTIFY|C.XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
   450  		(*C.char)(unsafe.Pointer(&event)),
   451  	)
   452  	w.d.l.xcb_flush(w.d.xcbConn)
   453  }
   454  func (w *Window) Fullscreen() bool {
   455  	r := w.d.l.xcb_get_property_reply(
   456  		w.d.xcbConn,
   457  		0,
   458  		w.win,
   459  		w.d.netWmState,
   460  		C.XCB_ATOM_ATOM,
   461  		0,
   462  		1024,
   463  	)
   464  	defer C.free(unsafe.Pointer(r))
   465  
   466  	dataSlice := unsafe.Slice(
   467  		(*C.xcb_atom_t)(w.d.l.xcb_get_property_value(r)),
   468  		uintptr(w.d.l.xcb_get_property_value_length(r))/unsafe.Sizeof(C.xcb_atom_t(0)),
   469  	)
   470  
   471  	for _, atom := range dataSlice {
   472  		if atom == w.d.netWmStateFullscreen {
   473  			return true
   474  		}
   475  	}
   476  	return false
   477  }
   478  
   479  func (w *Window) DragWindow() {
   480  	const _NET_WM_MOVERESIZE_MOVE = 8
   481  
   482  	w.d.mu.Lock()
   483  	mousePosX := w.d.lastMousePositionX
   484  	mousePosY := w.d.lastMousePositionY
   485  	w.d.mu.Unlock()
   486  
   487  	r := w.d.l.xcb_translate_coordinates_reply(
   488  		w.d.xcbConn,
   489  		w.win,
   490  		w.d.screens[0].xcbScreen.root,
   491  		C.int16_t(fixed1616ToFloat64(mousePosX)),
   492  		C.int16_t(fixed1616ToFloat64(mousePosY)),
   493  	)
   494  
   495  	var posX, posY C.int16_t
   496  	if r != nil {
   497  		defer C.free(unsafe.Pointer(r))
   498  
   499  		posX = r.dst_x
   500  		posY = r.dst_y
   501  	}
   502  
   503  	event := C.xcb_client_message_event_t{
   504  		response_type: C.XCB_CLIENT_MESSAGE,
   505  		format:        32,
   506  		sequence:      0,
   507  		window:        w.win,
   508  		_type:         w.d.netWmMoveResize,
   509  	}
   510  
   511  	data := (*[5]uint32)(unsafe.Pointer(&event.data))
   512  	data[0] = uint32(posX)
   513  	data[1] = uint32(posY)
   514  	data[2] = _NET_WM_MOVERESIZE_MOVE
   515  	data[3] = C.XCB_BUTTON_INDEX_1
   516  
   517  	w.d.l.xcb_ungrab_pointer(w.d.xcbConn, C.XCB_CURRENT_TIME)
   518  	w.d.l.xcb_send_event(
   519  		w.d.xcbConn,
   520  		0,
   521  		w.d.screens[0].xcbScreen.root,
   522  		C.XCB_EVENT_MASK_STRUCTURE_NOTIFY|C.XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
   523  		(*C.char)(unsafe.Pointer(&event)),
   524  	)
   525  }
   526  
   527  func (w *Window) setDecorations(motifWmHints C.xcb_atom_t, decorate bool) {
   528  	var hints struct {
   529  		flags       uint32
   530  		functions   uint32
   531  		decorations uint32
   532  		inputMode   int32
   533  		status      uint32
   534  	}
   535  
   536  	hints.flags = 2 // MWM_HINTS_DECORATIONS
   537  	if decorate {
   538  		hints.decorations = 1
   539  	} else {
   540  		hints.decorations = 0
   541  	}
   542  
   543  	w.d.l.xcb_change_property(
   544  		w.d.xcbConn,
   545  		C.XCB_PROP_MODE_REPLACE,
   546  		w.win,
   547  		motifWmHints,
   548  		motifWmHints,
   549  		32,
   550  		5,
   551  		unsafe.Pointer(&hints),
   552  	)
   553  }
   554  
   555  func (w *Window) SetDecorations(decorate bool) {
   556  	w.setDecorations(w.d.motifWmHints, decorate)
   557  }
   558  
   559  func (w *Window) Decorated() bool {
   560  	type wmHints struct {
   561  		flags       uint32
   562  		functions   uint32
   563  		decorations uint32
   564  		inputMode   int32
   565  		status      uint32
   566  	}
   567  
   568  	r := w.d.l.xcb_get_property_reply(
   569  		w.d.xcbConn,
   570  		0,
   571  		w.win,
   572  		w.d.motifWmHints,
   573  		w.d.motifWmHints,
   574  		0,
   575  		C.uint32_t(unsafe.Sizeof(wmHints{})),
   576  	)
   577  	defer C.free(unsafe.Pointer(r))
   578  
   579  	var hints wmHints
   580  	if w.d.l.xcb_get_property_value_length(r) == C.int(unsafe.Sizeof(wmHints{})) {
   581  		if v := (*wmHints)(w.d.l.xcb_get_property_value(r)); v != nil {
   582  			hints = *v
   583  		}
   584  	}
   585  
   586  	if hints.decorations == 0 {
   587  		return false
   588  	}
   589  	return true
   590  }
   591  
   592  func (w *Window) SetCloseRequestedCallback(cb events.WindowCloseRequestedCallback) {
   593  	w.closeRequestedCb.Store(&cb)
   594  }
   595  func (w *Window) SetResizedCallback(cb events.WindowResizedCallback) {
   596  	w.resizedCb.Store(&cb)
   597  }
   598  func (w *Window) SetFocusedCallback(cb events.WindowFocusedCallback) {
   599  	w.focusedCb.Store(&cb)
   600  }
   601  func (w *Window) SetUnfocusedCallback(cb events.WindowUnfocusedCallback) {
   602  	w.unfocusedCb.Store(&cb)
   603  }
   604  func (w *Window) SetCursorEnteredCallback(cb events.WindowCursorEnteredCallback) {
   605  	w.cursorEnteredCb.Store(&cb)
   606  }
   607  func (w *Window) SetCursorLeftCallback(cb events.WindowCursorLeftCallback) {
   608  	w.cursorLeftCb.Store(&cb)
   609  }
   610  func (w *Window) SetCursorMovedCallback(cb events.WindowCursorMovedCallback) {
   611  	w.cursorMovedCb.Store(&cb)
   612  }
   613  func (w *Window) SetMouseScrollCallback(cb events.WindowMouseScrollCallback) {
   614  	w.mouseWheelCb.Store(&cb)
   615  }
   616  func (w *Window) SetMouseInputCallback(cb events.WindowMouseInputCallback) {
   617  	w.mouseInputCb.Store(&cb)
   618  }
   619  func (w *Window) SetTouchInputCallback(cb events.WindowTouchInputCallback) {
   620  	// TODO:
   621  }
   622  func (w *Window) SetModifiersChangedCallback(cb events.WindowModifiersChangedCallback) {
   623  	w.modifiersChangedCb.Store(&cb)
   624  }
   625  func (w *Window) SetKeyboardInputCallback(cb events.WindowKeyboardInputCallback) {
   626  	w.keyboardInputCb.Store(&cb)
   627  }
   628  func (w *Window) SetReceivedCharacterCallback(cb events.WindowReceivedCharacterCallback) {
   629  	w.receivedCharacterCb.Store(&cb)
   630  }