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 }