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 }