package lib // #include "evdi_lib.h" // #include "go_ffi.h" // #cgo CFLAGS: -w import "C" import ( "fmt" "os" "sync" "unsafe" "log" ) var ( // Buffer allocation currentBufNumber = 0 bufferRegisterLock = sync.Mutex{} // EVDI Event data cEventToGoEventMapping = map[unsafe.Pointer]*EvdiEventContext{} activeLogger = &EvdiLogger{ Log: func(msg string) { log.Printf("evdi: %s", msg) }, } ) type EvdiLogger struct { Log func(message string) } type EvdiMode struct { Width int Height int RefreshRate int BitsPerPixel int PixelFormat uint } type EvdiCursorSet struct { HotX int32 HotY int32 Width uint32 Height uint32 Enabled uint8 Buffer []byte PixelFormat uint32 Stride uint32 } type DDCCIData struct { Address uint16 Flags uint16 Buffer []byte } type EvdiEventContext struct { cEventContext *C.struct_evdi_event_context DPMSHandler func(dpmsMode int) ModeChangeHandler func(mode *EvdiMode) UpdateReadyHandler func(bufferToBeUpdated int) CRTCStateHandler func(state int) CursorSetHandler func(cursor *EvdiCursorSet) CursorMoveHandler func(x, y int32) DDCCIDataHandler func(ddcciData *DDCCIData) } type EvdiBuffer struct { ID int Buffer []byte Width int Height int Stride int internalEvdiBuffer *C.struct_evdi_buffer } type EvdiDisplayRect struct { X1 int Y1 int X2 int Y2 int hasCInit bool cDisplayRect *C.struct_evdi_rect } //export goDPMSHandler func goDPMSHandler(event C.int, userData unsafe.Pointer) { goData, ok := cEventToGoEventMapping[userData] if !ok { panic("could not find Go event from C event map for EvdiEventContext") } goData.DPMSHandler(int(event)) } //export goModeChangedHandler func goModeChangedHandler(mode C.struct_evdi_mode, userData unsafe.Pointer) { goData, ok := cEventToGoEventMapping[userData] if !ok { panic("could not find Go event from C event map for EvdiEventContext") } goData.ModeChangeHandler(&EvdiMode{ Width: int(mode.width), Height: int(mode.height), RefreshRate: int(mode.refresh_rate), BitsPerPixel: int(mode.bits_per_pixel), PixelFormat: uint(mode.pixel_format), }) } //export goUpdateReadyHandler func goUpdateReadyHandler(event C.int, userData unsafe.Pointer) { goData, ok := cEventToGoEventMapping[userData] if !ok { panic("could not find Go event from C event map for EvdiEventContext") } goData.UpdateReadyHandler(int(event)) } //export goCRTCStateHandler func goCRTCStateHandler(event C.int, userData unsafe.Pointer) { goData, ok := cEventToGoEventMapping[userData] if !ok { panic("could not find Go event from C event map for EvdiEventContext") } goData.UpdateReadyHandler(int(event)) } //export goCursorSetHandler func goCursorSetHandler(cursor C.struct_evdi_cursor_set, userData unsafe.Pointer) { goData, ok := cEventToGoEventMapping[userData] if !ok { panic("could not find Go event from C event map for EvdiEventContext") } pointerBuffer := unsafe.Slice((*byte)(unsafe.Pointer(cursor.buffer)), cursor.buffer_length) // Clone it for good measure safeBuffer := make([]byte, cursor.buffer_length) copy(safeBuffer, pointerBuffer) goData.CursorSetHandler(&EvdiCursorSet{ HotX: int32(cursor.hot_x), HotY: int32(cursor.hot_y), Width: uint32(cursor.width), Height: uint32(cursor.height), Enabled: uint8(cursor.enabled), Buffer: safeBuffer, PixelFormat: uint32(cursor.pixel_format), Stride: uint32(cursor.stride), }) } //export goCursorMoveHandler func goCursorMoveHandler(cursor C.struct_evdi_cursor_move, userData unsafe.Pointer) { goData, ok := cEventToGoEventMapping[userData] if !ok { panic("could not find Go event from C event map for EvdiEventContext") } goData.CursorMoveHandler(int32(cursor.x), int32(cursor.y)) } //export goDDCCIDataHandler func goDDCCIDataHandler(data C.struct_evdi_ddcci_data, userData unsafe.Pointer) { goData, ok := cEventToGoEventMapping[userData] if !ok { panic("could not find Go event from C event map for EvdiEventContext") } pointerBuffer := unsafe.Slice((*byte)(unsafe.Pointer(data.buffer)), data.buffer_length) // Clone it for good measure safeBuffer := make([]byte, data.buffer_length) copy(safeBuffer, pointerBuffer) goData.DDCCIDataHandler(&DDCCIData{ Address: uint16(data.address), Flags: uint16(data.flags), Buffer: safeBuffer, }) } //export goLoggerHandler func goLoggerHandler(message *C.char) { activeLogger.Log(C.GoString(message)) C.free(unsafe.Pointer(message)) } type EvdiNode struct { handle C.evdi_handle } func (node *EvdiNode) Disconnect() { C.evdi_disconnect(node.handle) } func (node *EvdiNode) Close() { C.evdi_disconnect(node.handle) } func (node *EvdiNode) RegisterEventHandler(handler *EvdiEventContext) { if handler.cEventContext == nil { handler.cEventContext = &C.struct_evdi_event_context{ dpms_handler: (*[0]byte)(C.dpmsHandler), mode_changed_handler: (*[0]byte)(C.modeChangedHandler), update_ready_handler: (*[0]byte)(C.updateReadyHandler), crtc_state_handler: (*[0]byte)(C.crtcStateHandler), cursor_set_handler: (*[0]byte)(C.cursorSetHandler), cursor_move_handler: (*[0]byte)(C.cursorMoveHandler), ddcci_data_handler: (*[0]byte)(C.ddcciDataHandler), } handler.cEventContext.user_data = unsafe.Pointer(handler.cEventContext) } cEventToGoEventMapping[unsafe.Pointer(handler.cEventContext)] = handler } func (node *EvdiNode) UnregisterEventHandler(handler *EvdiEventContext) error { if _, ok := cEventToGoEventMapping[unsafe.Pointer(handler.cEventContext)]; !ok { return fmt.Errorf("could not find event map") } delete(cEventToGoEventMapping, unsafe.Pointer(handler.cEventContext)) if handler.cEventContext == nil { return fmt.Errorf("cEventContext pointer is somehow nil! Please report this bug at https://git.terah.dev/imterah/goevdi") } C.free(unsafe.Pointer(handler.cEventContext)) return nil } func (node *EvdiNode) HandleEvents(handler *EvdiEventContext) error { if _, ok := cEventToGoEventMapping[unsafe.Pointer(handler.cEventContext)]; ok { return fmt.Errorf("could not find event map") } C.evdi_handle_events(node.handle, handler.cEventContext) return nil } func (node *EvdiNode) Connect(EDID []byte, pixelWidthLimit, pixelHeightLimit, FPSLimit uint) { rawCEDID := C.CString(string(EDID)) cEDID := (*C.uchar)(unsafe.Pointer(rawCEDID)) defer C.free(unsafe.Pointer(cEDID)) pixelAreaLimit := pixelWidthLimit * pixelHeightLimit C.evdi_connect2(node.handle, cEDID, C.uint(uint(len(EDID))), C.uint(pixelAreaLimit), C.uint(pixelAreaLimit*FPSLimit)) } func (node *EvdiNode) EnableCursorEvents(enable bool) { C.evdi_enable_cursor_events(node.handle, C.bool(enable)) } func (node *EvdiNode) GetOnReadyFile() *os.File { fdC := C.evdi_get_event_ready(node.handle) fd := int(fdC) file := os.NewFile(uintptr(fd), "evdi-fd") return file } func (node *EvdiNode) CreateBuffer(width, height, stride int, rect *EvdiDisplayRect) *EvdiBuffer { bufferRegisterLock.Lock() defer bufferRegisterLock.Unlock() cBuffer := C.malloc(C.size_t(width * height * stride)) normalBuffer := unsafe.Slice((*byte)(cBuffer), width*height*stride) evdiRect := C.struct_evdi_rect{ x1: C.int(rect.X1), x2: C.int(rect.X2), y1: C.int(rect.Y1), y2: C.int(rect.Y2), } rect.hasCInit = true rect.cDisplayRect = &evdiRect evdiBuffer := C.struct_evdi_buffer{ id: C.int(currentBufNumber), buffer: cBuffer, width: C.int(width), height: C.int(height), stride: C.int(stride), rects: &evdiRect, rect_count: C.int(0), } buf := &EvdiBuffer{ ID: currentBufNumber, Buffer: normalBuffer, Width: width, Height: height, Stride: stride, internalEvdiBuffer: &evdiBuffer, } C.evdi_register_buffer(node.handle, evdiBuffer) currentBufNumber++ return buf } func (node *EvdiNode) RemoveBuffer(buffer *EvdiBuffer) { C.evdi_unregister_buffer(node.handle, C.int(buffer.ID)) buffer.Buffer = nil C.free(buffer.internalEvdiBuffer.buffer) } func (node *EvdiNode) GrabPixels(rect *EvdiDisplayRect) int { rectNumCIntPointer := C.malloc(C.sizeof_int) defer C.free(rectNumCIntPointer) rectNumCInt := (*C.int)(rectNumCIntPointer) if !rect.hasCInit || rect.cDisplayRect == nil || int(rect.cDisplayRect.x1) != rect.X1 || int(rect.cDisplayRect.x2) != rect.X2 || int(rect.cDisplayRect.y1) != rect.Y1 || int(rect.cDisplayRect.y2) != rect.Y2 { evdiRect := C.struct_evdi_rect{ x1: C.int(rect.X1), x2: C.int(rect.X2), y1: C.int(rect.Y1), y2: C.int(rect.Y2), } rect.hasCInit = true rect.cDisplayRect = &evdiRect } C.evdi_grab_pixels(node.handle, rect.cDisplayRect, rectNumCInt) rectNum := int(*rectNumCInt) return rectNum } func (node *EvdiNode) RequestUpdate(buffer *EvdiBuffer) { C.evdi_request_update(node.handle, C.int(buffer.ID)) } // Creates a new EVDI node. Loosely based on `evdi_open_attached_to_fixed()`. func Open(parentDevice *string) (*EvdiNode, error) { var parentCString *C.char length := 0 if parentDevice != nil { parentCString = C.CString(*parentDevice) length = len(*parentDevice) defer C.free(unsafe.Pointer(parentCString)) } handle := C.evdi_open_attached_to_fixed(parentCString, C.size_t(uint(length))) if handle == nil { return nil, fmt.Errorf("failed to initialize EVDI node") } node := &EvdiNode{ handle: handle, } return node, nil } // Checks if Xorg is running. Based on the C function `Xorg_running()`. func IsXorgRunning() bool { xorgRunningC := C.Xorg_running() return bool(xorgRunningC) } // Gets the underlying library version. Based on the C function `evdi_get_lib_version()`. // // 1st int: major version // // 2nd int: minor version // // 3rd int: patch level func GetLibraryVersion() (int, int, int) { version := C.struct_evdi_lib_version{} C.evdi_get_lib_version(&version) return int(version.version_major), int(version.version_minor), int(version.version_patchlevel) } func SetupLogger(logger *EvdiLogger) { activeLogger = logger } func init() { C.loggerInit() }