417 lines
9.9 KiB
Go
417 lines
9.9 KiB
Go
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()
|
|
}
|