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

     1  //go:build linux && !android
     2  
     3  package wayland
     4  
     5  /*
     6  
     7  #include <stdlib.h>
     8  #include "wayland-client-protocol.h"
     9  #include "xdg-shell-client-protocol.h"
    10  #include "xdg-decoration-unstable-v1-client-protocol.h"
    11  
    12  extern const struct wl_surface_listener gamen_wl_surface_listener;
    13  extern const struct xdg_surface_listener gamen_xdg_surface_listener;
    14  extern const struct xdg_toplevel_listener gamen_xdg_toplevel_listener;
    15  extern const struct zxdg_toplevel_decoration_v1_listener gamen_zxdg_toplevel_decoration_v1_listener;
    16  
    17  */
    18  import "C"
    19  
    20  import (
    21  	"math"
    22  	"runtime/cgo"
    23  	"sync"
    24  	"unsafe"
    25  
    26  	"github.com/rajveermalviya/gamen/cursors"
    27  	"github.com/rajveermalviya/gamen/dpi"
    28  	"github.com/rajveermalviya/gamen/events"
    29  	"github.com/rajveermalviya/gamen/internal/common/atomicx"
    30  	"github.com/rajveermalviya/gamen/internal/common/mathx"
    31  	"github.com/rajveermalviya/gamen/internal/common/xcursor"
    32  )
    33  
    34  type Window struct {
    35  	// handle for Window to be passed between cgo callbacks
    36  	handle *cgo.Handle
    37  	d      *Display
    38  	// we allow destroy function to be called multiple
    39  	// times, but in reality we run it once
    40  	destroyOnce sync.Once
    41  	mu          sync.Mutex
    42  
    43  	// wayland objects
    44  	surface               *C.struct_wl_surface
    45  	xdgSurface            *C.struct_xdg_surface
    46  	xdgToplevel           *C.struct_xdg_toplevel
    47  	xdgToplevelDecoration *C.struct_zxdg_toplevel_decoration_v1
    48  
    49  	// state
    50  	scaleFactor        float64                          // shared mutex
    51  	size               dpi.LogicalSize[uint32]          // shared mutex
    52  	outputs            map[*C.struct_wl_output]struct{} // shared mutex
    53  	previousCursorIcon string                           // shared mutex
    54  	currentCursorIcon  string                           // shared mutex
    55  
    56  	maximized  atomicx.Bool // shared atomic
    57  	fullscreen atomicx.Bool // shared atomic
    58  
    59  	// window callbacks
    60  	resizedCb           atomicx.Pointer[events.WindowResizedCallback]
    61  	closeRequestedCb    atomicx.Pointer[events.WindowCloseRequestedCallback]
    62  	focusedCb           atomicx.Pointer[events.WindowFocusedCallback]
    63  	unfocusedCb         atomicx.Pointer[events.WindowUnfocusedCallback]
    64  	cursorEnteredCb     atomicx.Pointer[events.WindowCursorEnteredCallback]
    65  	cursorLeftCb        atomicx.Pointer[events.WindowCursorLeftCallback]
    66  	cursorMovedCb       atomicx.Pointer[events.WindowCursorMovedCallback]
    67  	mouseWheelCb        atomicx.Pointer[events.WindowMouseScrollCallback]
    68  	mouseInputCb        atomicx.Pointer[events.WindowMouseInputCallback]
    69  	modifiersChangedCb  atomicx.Pointer[events.WindowModifiersChangedCallback]
    70  	keyboardInputCb     atomicx.Pointer[events.WindowKeyboardInputCallback]
    71  	receivedCharacterCb atomicx.Pointer[events.WindowReceivedCharacterCallback]
    72  }
    73  
    74  func NewWindow(d *Display) (*Window, error) {
    75  	w := &Window{
    76  		d:                 d,
    77  		outputs:           make(map[*C.struct_wl_output]struct{}),
    78  		scaleFactor:       1,
    79  		currentCursorIcon: "left_ptr",
    80  		size: dpi.LogicalSize[uint32]{
    81  			Width:  640,
    82  			Height: 480,
    83  		},
    84  	}
    85  	handle := cgo.NewHandle(w)
    86  	w.handle = &handle
    87  
    88  	w.surface = d.l.wl_compositor_create_surface(d.compositor)
    89  	d.l.wl_surface_add_listener(w.surface, &C.gamen_wl_surface_listener, unsafe.Pointer(w.handle))
    90  
    91  	w.xdgSurface = d.l.xdg_wm_base_get_xdg_surface(d.xdgWmBase, w.surface)
    92  	d.l.xdg_surface_add_listener(w.xdgSurface, &C.gamen_xdg_surface_listener, unsafe.Pointer(w.handle))
    93  
    94  	w.xdgToplevel = d.l.xdg_surface_get_toplevel(w.xdgSurface)
    95  	d.l.xdg_toplevel_add_listener(w.xdgToplevel, &C.gamen_xdg_toplevel_listener, unsafe.Pointer(w.handle))
    96  
    97  	if d.xdgDecorationManager != nil {
    98  		w.xdgToplevelDecoration = d.l.zxdg_decoration_manager_v1_get_toplevel_decoration(d.xdgDecorationManager, w.xdgToplevel)
    99  		d.l.zxdg_toplevel_decoration_v1_add_listener(w.xdgToplevelDecoration, &C.gamen_zxdg_toplevel_decoration_v1_listener, unsafe.Pointer(w.handle))
   100  		d.l.zxdg_toplevel_decoration_v1_set_mode(w.xdgToplevelDecoration, ZXDG_TOPLEVEL_DECORATION_V_1_MODE_SERVER_SIDE)
   101  	}
   102  
   103  	d.l.wl_surface_commit(w.surface)
   104  
   105  	d.windows[w.surface] = w
   106  	return w, nil
   107  }
   108  
   109  func (w *Window) WlDisplay() unsafe.Pointer { return unsafe.Pointer(w.d.display) }
   110  func (w *Window) WlSurface() unsafe.Pointer { return unsafe.Pointer(w.surface) }
   111  
   112  func (w *Window) Destroy() {
   113  	w.destroyOnce.Do(func() {
   114  		w.resizedCb.Store(nil)
   115  		w.closeRequestedCb.Store(nil)
   116  		w.focusedCb.Store(nil)
   117  		w.unfocusedCb.Store(nil)
   118  		w.cursorEnteredCb.Store(nil)
   119  		w.cursorLeftCb.Store(nil)
   120  		w.cursorMovedCb.Store(nil)
   121  		w.mouseWheelCb.Store(nil)
   122  		w.mouseInputCb.Store(nil)
   123  		w.modifiersChangedCb.Store(nil)
   124  		w.keyboardInputCb.Store(nil)
   125  		w.receivedCharacterCb.Store(nil)
   126  
   127  		if _, ok := w.d.windows[w.surface]; ok {
   128  			w.d.windows[w.surface] = nil
   129  			delete(w.d.windows, w.surface)
   130  		}
   131  
   132  		if w.xdgToplevelDecoration != nil {
   133  			w.d.l.zxdg_toplevel_decoration_v1_destroy(w.xdgToplevelDecoration)
   134  			w.xdgToplevelDecoration = nil
   135  		}
   136  
   137  		if w.xdgToplevel != nil {
   138  			w.d.l.xdg_toplevel_destroy(w.xdgToplevel)
   139  			w.xdgToplevel = nil
   140  		}
   141  
   142  		if w.xdgSurface != nil {
   143  			w.d.l.xdg_surface_destroy(w.xdgSurface)
   144  			w.xdgSurface = nil
   145  		}
   146  
   147  		if w.surface != nil {
   148  			w.d.l.wl_surface_destroy(w.surface)
   149  			w.surface = nil
   150  		}
   151  
   152  		if w.handle != nil {
   153  			w.handle.Delete()
   154  			w.handle = nil
   155  		}
   156  	})
   157  }
   158  
   159  func (w *Window) SetTitle(title string) {
   160  	titlePtr := C.CString(title)
   161  	defer C.free(unsafe.Pointer(titlePtr))
   162  
   163  	w.d.l.xdg_toplevel_set_title(w.xdgToplevel, titlePtr)
   164  }
   165  
   166  func (w *Window) InnerSize() dpi.PhysicalSize[uint32] {
   167  	w.mu.Lock()
   168  	defer w.mu.Unlock()
   169  
   170  	return w.size.ToPhysical(w.scaleFactor)
   171  }
   172  
   173  func (w *Window) SetInnerSize(size dpi.Size[uint32]) {
   174  	w.mu.Lock()
   175  	defer w.mu.Unlock()
   176  
   177  	scaleFactor := w.scaleFactor
   178  	physicalSize := size.ToPhysical(scaleFactor)
   179  	logicalSize := size.ToLogical(scaleFactor)
   180  
   181  	width := mathx.Max(1, physicalSize.Width)
   182  	height := mathx.Max(1, physicalSize.Height)
   183  
   184  	w.size = logicalSize
   185  
   186  	w.d.scheduleCallback(func() {
   187  		if cb := w.resizedCb.Load(); cb != nil {
   188  			if cb := (*cb); cb != nil {
   189  				cb(width, height, scaleFactor)
   190  			}
   191  		}
   192  	})
   193  }
   194  
   195  func (w *Window) SetMinInnerSize(size dpi.Size[uint32]) {
   196  	w.mu.Lock()
   197  	scaleFactor := w.scaleFactor
   198  	w.mu.Unlock()
   199  
   200  	logicalSize := size.ToLogical(scaleFactor)
   201  
   202  	w.d.l.xdg_toplevel_set_min_size(
   203  		w.xdgToplevel,
   204  		C.int32_t(logicalSize.Width),
   205  		C.int32_t(logicalSize.Height),
   206  	)
   207  }
   208  
   209  func (w *Window) SetMaxInnerSize(size dpi.Size[uint32]) {
   210  	w.mu.Lock()
   211  	scaleFactor := w.scaleFactor
   212  	w.mu.Unlock()
   213  
   214  	logicalSize := size.ToLogical(scaleFactor)
   215  
   216  	w.d.l.xdg_toplevel_set_max_size(
   217  		w.xdgToplevel,
   218  		C.int32_t(logicalSize.Width),
   219  		C.int32_t(logicalSize.Height),
   220  	)
   221  }
   222  
   223  func (w *Window) Maximized() bool {
   224  	return w.maximized.Load()
   225  }
   226  func (w *Window) SetMinimized() {
   227  	w.d.l.xdg_toplevel_set_minimized(w.xdgToplevel)
   228  }
   229  func (w *Window) SetMaximized(maximized bool) {
   230  	if maximized {
   231  		w.d.l.xdg_toplevel_set_maximized(w.xdgToplevel)
   232  	} else {
   233  		w.d.l.xdg_toplevel_unset_maximized(w.xdgToplevel)
   234  	}
   235  }
   236  
   237  func (w *Window) SetCursorIcon(icon cursors.Icon) {
   238  	if icon == 0 {
   239  		// 0 is internally used to hide cursor,
   240  		// users should instead use SetCursorVisible()
   241  		// so make this no-op
   242  		return
   243  	}
   244  
   245  	w.mu.Lock()
   246  	scaleFactor := w.scaleFactor
   247  	w.mu.Unlock()
   248  
   249  	var cursor *C.struct_wl_cursor
   250  	var name string
   251  	for _, n := range xcursor.ToXcursorName(icon) {
   252  		name = n
   253  		cursor = w.d.pointer.loadCursor(n, 24, scaleFactor)
   254  		if cursor != nil {
   255  			break
   256  		}
   257  	}
   258  
   259  	// couldn't find the specified cursor, so no-op
   260  	if cursor == nil {
   261  		return
   262  	}
   263  
   264  	w.d.pointer.mu.Lock()
   265  	// if not current window, don't change cursor for pointer
   266  	// as doing so can incorrectly set cursor for other window
   267  	// if application has multiple windows
   268  	if w.d.pointer.focus != w.surface {
   269  		w.d.pointer.mu.Unlock()
   270  
   271  		// save it so that when pointer enters this window,
   272  		// pointer will set this cursor
   273  		w.mu.Lock()
   274  		w.currentCursorIcon = name
   275  		w.mu.Unlock()
   276  		return
   277  	} else {
   278  		w.d.pointer.mu.Unlock()
   279  	}
   280  
   281  	w.mu.Lock()
   282  	w.currentCursorIcon = name
   283  	scaleFactor = w.scaleFactor
   284  	w.mu.Unlock()
   285  	w.d.pointer.setCursor(cursor, name, scaleFactor)
   286  }
   287  
   288  func (w *Window) SetCursorVisible(visible bool) {
   289  	if visible {
   290  		w.mu.Lock()
   291  		if w.currentCursorIcon == "" {
   292  			w.currentCursorIcon = w.previousCursorIcon
   293  			currentCursor := w.currentCursorIcon
   294  			scaleFactor := w.scaleFactor
   295  			w.mu.Unlock()
   296  
   297  			w.d.pointer.mu.Lock()
   298  			if w.d.pointer.focus == w.surface {
   299  				w.d.pointer.mu.Unlock()
   300  
   301  				cursor := w.d.pointer.loadCursor(currentCursor, 24, scaleFactor)
   302  				if cursor != nil {
   303  					w.d.pointer.setCursor(cursor, currentCursor, scaleFactor)
   304  				}
   305  			} else {
   306  				w.d.pointer.mu.Unlock()
   307  			}
   308  		} else {
   309  			w.mu.Unlock()
   310  		}
   311  	} else {
   312  		w.mu.Lock()
   313  		if w.currentCursorIcon != "" {
   314  			w.previousCursorIcon = w.currentCursorIcon
   315  			w.currentCursorIcon = ""
   316  			w.mu.Unlock()
   317  
   318  			w.d.pointer.mu.Lock()
   319  			if w.d.pointer.focus == w.surface {
   320  				w.d.pointer.mu.Unlock()
   321  
   322  				w.d.pointer.setCursor(nil, "", 0)
   323  			} else {
   324  				w.d.pointer.mu.Unlock()
   325  			}
   326  		} else {
   327  			w.mu.Unlock()
   328  		}
   329  	}
   330  }
   331  
   332  func (w *Window) SetFullscreen(fullscreen bool) {
   333  	if fullscreen {
   334  		w.d.l.xdg_toplevel_set_fullscreen(w.xdgToplevel, nil)
   335  	} else {
   336  		w.d.l.xdg_toplevel_unset_fullscreen(w.xdgToplevel)
   337  	}
   338  }
   339  
   340  func (w *Window) Fullscreen() bool {
   341  	return w.fullscreen.Load()
   342  }
   343  
   344  func (w *Window) DragWindow() {
   345  	w.d.pointer.mu.Lock()
   346  	serial := w.d.pointer.serial
   347  	w.d.pointer.mu.Unlock()
   348  
   349  	w.d.l.xdg_toplevel_move(w.xdgToplevel, w.d.seat, C.uint32_t(serial))
   350  }
   351  
   352  func (w *Window) SetDecorations(decorate bool) {
   353  	w.mu.Lock()
   354  	defer w.mu.Unlock()
   355  
   356  	if decorate {
   357  		if w.d.xdgDecorationManager != nil && w.xdgToplevelDecoration == nil {
   358  			w.xdgToplevelDecoration = w.d.l.zxdg_decoration_manager_v1_get_toplevel_decoration(w.d.xdgDecorationManager, w.xdgToplevel)
   359  			w.d.l.zxdg_toplevel_decoration_v1_add_listener(w.xdgToplevelDecoration, &C.gamen_zxdg_toplevel_decoration_v1_listener, unsafe.Pointer(w.handle))
   360  			w.d.l.zxdg_toplevel_decoration_v1_set_mode(w.xdgToplevelDecoration, ZXDG_TOPLEVEL_DECORATION_V_1_MODE_SERVER_SIDE)
   361  			w.d.l.wl_surface_commit(w.surface)
   362  		}
   363  	} else {
   364  		if w.d.xdgDecorationManager != nil && w.xdgToplevelDecoration != nil {
   365  			w.d.l.zxdg_toplevel_decoration_v1_set_mode(w.xdgToplevelDecoration, ZXDG_TOPLEVEL_DECORATION_V_1_MODE_CLIENT_SIDE)
   366  			w.d.l.zxdg_toplevel_decoration_v1_destroy(w.xdgToplevelDecoration)
   367  			w.xdgToplevelDecoration = nil
   368  			w.d.l.wl_surface_commit(w.surface)
   369  		}
   370  	}
   371  }
   372  
   373  func (w *Window) Decorated() bool {
   374  	w.mu.Lock()
   375  	defer w.mu.Unlock()
   376  	return w.xdgToplevelDecoration != nil
   377  }
   378  
   379  func (w *Window) SetCloseRequestedCallback(cb events.WindowCloseRequestedCallback) {
   380  	w.closeRequestedCb.Store(&cb)
   381  }
   382  func (w *Window) SetResizedCallback(cb events.WindowResizedCallback) {
   383  	w.resizedCb.Store(&cb)
   384  }
   385  func (w *Window) SetFocusedCallback(cb events.WindowFocusedCallback) {
   386  	w.focusedCb.Store(&cb)
   387  }
   388  func (w *Window) SetUnfocusedCallback(cb events.WindowUnfocusedCallback) {
   389  	w.unfocusedCb.Store(&cb)
   390  }
   391  func (w *Window) SetCursorEnteredCallback(cb events.WindowCursorEnteredCallback) {
   392  	w.cursorEnteredCb.Store(&cb)
   393  }
   394  func (w *Window) SetCursorLeftCallback(cb events.WindowCursorLeftCallback) {
   395  	w.cursorLeftCb.Store(&cb)
   396  }
   397  func (w *Window) SetCursorMovedCallback(cb events.WindowCursorMovedCallback) {
   398  	w.cursorMovedCb.Store(&cb)
   399  }
   400  func (w *Window) SetMouseScrollCallback(cb events.WindowMouseScrollCallback) {
   401  	w.mouseWheelCb.Store(&cb)
   402  }
   403  func (w *Window) SetMouseInputCallback(cb events.WindowMouseInputCallback) {
   404  	w.mouseInputCb.Store(&cb)
   405  }
   406  func (w *Window) SetTouchInputCallback(cb events.WindowTouchInputCallback) {
   407  	// TODO:
   408  }
   409  func (w *Window) SetModifiersChangedCallback(cb events.WindowModifiersChangedCallback) {
   410  	w.modifiersChangedCb.Store(&cb)
   411  }
   412  func (w *Window) SetKeyboardInputCallback(cb events.WindowKeyboardInputCallback) {
   413  	w.keyboardInputCb.Store(&cb)
   414  }
   415  func (w *Window) SetReceivedCharacterCallback(cb events.WindowReceivedCharacterCallback) {
   416  	w.receivedCharacterCb.Store(&cb)
   417  }
   418  
   419  func (w *Window) updateScaleFactor() {
   420  	var scaleFactor float64 = 1
   421  
   422  	w.mu.Lock()
   423  	for output := range w.outputs {
   424  		o, ok := w.d.outputs[output]
   425  		if ok {
   426  			scaleFactor = math.Max(float64(o.scaleFactor), scaleFactor)
   427  		}
   428  	}
   429  
   430  	if w.scaleFactor == scaleFactor {
   431  		w.mu.Unlock()
   432  		return
   433  	}
   434  	w.scaleFactor = scaleFactor
   435  	logicalSize := w.size
   436  	physicalSize := logicalSize.ToPhysical(scaleFactor)
   437  	w.mu.Unlock()
   438  
   439  	w.d.l.wl_surface_set_buffer_scale(w.surface, C.int32_t(scaleFactor))
   440  	w.d.l.wl_surface_commit(w.surface)
   441  
   442  	w.d.scheduleCallback(func() {
   443  		if cb := w.resizedCb.Load(); cb != nil {
   444  			if cb := (*cb); cb != nil {
   445  				cb(
   446  					physicalSize.Width,
   447  					physicalSize.Height,
   448  					scaleFactor,
   449  				)
   450  			}
   451  		}
   452  	})
   453  }
   454  
   455  //export windowSurfaceHandleEnter
   456  func windowSurfaceHandleEnter(data unsafe.Pointer, wl_surface *C.struct_wl_surface, output *C.struct_wl_output) {
   457  	w, ok := (*cgo.Handle)(data).Value().(*Window)
   458  	if !ok {
   459  		return
   460  	}
   461  
   462  	w.mu.Lock()
   463  	w.outputs[output] = struct{}{}
   464  	w.mu.Unlock()
   465  
   466  	w.updateScaleFactor()
   467  }
   468  
   469  //export windowSurfaceHandleLeave
   470  func windowSurfaceHandleLeave(data unsafe.Pointer, wl_surface *C.struct_wl_surface, output *C.struct_wl_output) {
   471  	w, ok := (*cgo.Handle)(data).Value().(*Window)
   472  	if !ok {
   473  		return
   474  	}
   475  
   476  	w.mu.Lock()
   477  	delete(w.outputs, output)
   478  	w.mu.Unlock()
   479  
   480  	w.updateScaleFactor()
   481  }
   482  
   483  //export xdgSurfaceHandleConfigure
   484  func xdgSurfaceHandleConfigure(data unsafe.Pointer, xdg_surface *C.struct_xdg_surface, serial C.uint32_t) {
   485  	w, ok := (*cgo.Handle)(data).Value().(*Window)
   486  	if !ok {
   487  		return
   488  	}
   489  
   490  	w.d.l.xdg_surface_ack_configure(xdg_surface, serial)
   491  }
   492  
   493  //export xdgToplevelHandleConfigure
   494  func xdgToplevelHandleConfigure(data unsafe.Pointer, xdg_toplevel *C.struct_xdg_toplevel, width C.int32_t, height C.int32_t, states *C.struct_wl_array) {
   495  	if width == 0 || height == 0 {
   496  		return
   497  	}
   498  
   499  	w, ok := (*cgo.Handle)(data).Value().(*Window)
   500  	if !ok {
   501  		return
   502  	}
   503  
   504  	maximized := false
   505  	fullscreen := false
   506  
   507  	for _, state := range castWlArrayToSlice[enum_xdg_toplevel_state](states) {
   508  		switch state {
   509  		case XDG_TOPLEVEL_STATE_MAXIMIZED:
   510  			maximized = true
   511  		case XDG_TOPLEVEL_STATE_FULLSCREEN:
   512  			fullscreen = true
   513  		}
   514  	}
   515  
   516  	logicalSize := dpi.LogicalSize[uint32]{
   517  		Width:  uint32(width),
   518  		Height: uint32(height),
   519  	}
   520  
   521  	w.maximized.Store(maximized)
   522  	w.fullscreen.Store(fullscreen)
   523  
   524  	w.mu.Lock()
   525  	w.size = logicalSize
   526  	scaleFactor := w.scaleFactor
   527  	w.mu.Unlock()
   528  
   529  	physicalSize := logicalSize.ToPhysical(scaleFactor)
   530  
   531  	if cb := w.resizedCb.Load(); cb != nil {
   532  		if cb := (*cb); cb != nil {
   533  			cb(
   534  				physicalSize.Width,
   535  				physicalSize.Height,
   536  				scaleFactor,
   537  			)
   538  		}
   539  	}
   540  }
   541  
   542  //export xdgToplevelHandleClose
   543  func xdgToplevelHandleClose(data unsafe.Pointer, xdg_toplevel *C.struct_xdg_toplevel) {
   544  	w, ok := (*cgo.Handle)(data).Value().(*Window)
   545  	if !ok {
   546  		return
   547  	}
   548  
   549  	if cb := w.closeRequestedCb.Load(); cb != nil {
   550  		if cb := (*cb); cb != nil {
   551  			cb()
   552  		}
   553  	}
   554  }