diff --git a/dxinput/utils.go b/dxinput/utils.go index fe981e7..44693d4 100644 --- a/dxinput/utils.go +++ b/dxinput/utils.go @@ -190,43 +190,78 @@ func setTransformationMatrix(id int32, m [9]float32) error { } func getInt8Prop(id int32, prop string, nitems int32) ([]int8, error) { - datas, num := utils.GetProperty(id, prop) - if len(datas) == 0 || num != nitems { - return nil, fmt.Errorf("Get prop '%v -- %s' values failed", + datas, nBytes := utils.GetProperty(id, prop) + if len(datas) == 0 { + return nil, fmt.Errorf("Get prop '%v -- %s' failed: property data is empty", id, prop) } + // For int8, nBytes should equal nitems (1 byte per item) + if nBytes != nitems { + return nil, fmt.Errorf("Get prop '%v -- %s' failed: expected %d items but got %d bytes", + id, prop, nitems, nBytes) + } return utils.ReadInt8(datas, nitems), nil } func getInt16Prop(id int32, prop string, nitems int32) ([]int16, error) { - datas, num := utils.GetProperty(id, prop) - if len(datas) == 0 || num != nitems { - return nil, fmt.Errorf("Get prop '%v -- %s' values failed", + datas, nBytes := utils.GetProperty(id, prop) + if len(datas) == 0 { + return nil, fmt.Errorf("Get prop '%v -- %s' failed: property data is empty", id, prop) } + // Check if nBytes is divisible by 2 (int16 size) + if nBytes%2 != 0 { + return nil, fmt.Errorf("Get prop '%v -- %s' failed: byte count %d is not divisible by 2 (int16 size)", + id, prop, nBytes) + } + // For int16, nBytes should equal nitems * 2 (2 bytes per item) + if nBytes/2 != nitems { + return nil, fmt.Errorf("Get prop '%v -- %s' failed: expected %d items but got %d bytes (%d items)", + id, prop, nitems, nBytes, nBytes/2) + } return utils.ReadInt16(datas, nitems), nil } func getInt32Prop(id int32, prop string, nitems int32) ([]int32, error) { - datas, num := utils.GetProperty(id, prop) - if len(datas) == 0 || num != nitems { - return nil, fmt.Errorf("Get prop '%v -- %s' values failed", + datas, nBytes := utils.GetProperty(id, prop) + if len(datas) == 0 { + return nil, fmt.Errorf("Get prop '%v -- %s' failed: property data is empty", id, prop) } + // Check if nBytes is divisible by 4 (int32 size) + if nBytes%4 != 0 { + return nil, fmt.Errorf("Get prop '%v -- %s' failed: byte count %d is not divisible by 4 (int32 size)", + id, prop, nBytes) + } + // For int32, nBytes should equal nitems * 4 (4 bytes per item) + if nBytes/4 != nitems { + return nil, fmt.Errorf("Get prop '%v -- %s' failed: expected %d items but got %d bytes (%d items)", + id, prop, nitems, nBytes, nBytes/4) + } return utils.ReadInt32(datas, nitems), nil } -func getFloat32Prop(id int32, prop string, nitems int32) ([]float32, error) { - datas, num := utils.GetProperty(id, prop) - if len(datas) == 0 || num != nitems { - return nil, fmt.Errorf("Get prop '%v -- %s' values failed", +func getFloat32Prop(id int32, prop string, nItems int32) ([]float32, error) { + datas, nBytes := utils.GetProperty(id, prop) + if len(datas) == 0 { + return nil, fmt.Errorf("Get prop '%v -- %s' failed: property data is empty", id, prop) } + // Check if nBytes is divisible by 4 (float32 size) + if nBytes%4 != 0 { + return nil, fmt.Errorf("Get prop '%v -- %s' failed: byte count %d is not divisible by 4 (float32 size)", + id, prop, nBytes) + } + // utils.GetProperty returns byte count, float32 occupies 4 bytes, divide by 4 to get item count + if nBytes/4 != nItems { + return nil, fmt.Errorf("Get prop '%v -- %s' failed: expected %d items but got %d bytes (%d items)", + id, prop, nItems, nBytes, nBytes/4) + } - return utils.ReadFloat32(datas, nitems), nil + return utils.ReadFloat32(datas, nItems), nil } func absInt32(v int32) int32 { diff --git a/dxinput/utils/button_map.go b/dxinput/utils/button_map.go index 97bfb34..b94664f 100644 --- a/dxinput/utils/button_map.go +++ b/dxinput/utils/button_map.go @@ -29,7 +29,7 @@ func GetButtonMap(xid uint32, devName string) ([]byte, error) { } defer C.free(unsafe.Pointer(cbtnMap)) - return ucharArrayToByte(cbtnMap, int(cbtnNum)), nil + return C.GoBytes(unsafe.Pointer(cbtnMap), cbtnNum), nil } func SetButtonMap(xid uint32, devName string, btnMap []byte) error { diff --git a/dxinput/utils/property.c b/dxinput/utils/property.c index 50668f4..a7c243c 100644 --- a/dxinput/utils/property.c +++ b/dxinput/utils/property.c @@ -3,6 +3,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include +#include #include #include @@ -12,26 +13,29 @@ #include "type.h" #include "x11_mutex.h" -#define MAX_BUF_LEN 1000 - /** * The return data type if 'char' must be convert to 'int8_t*' * if 'int' must be convert to 'int32_t*' * if 'float' must be convert to 'float*' + * + * Returns the property data and sets nbytes to the actual byte length. + * The caller is responsible for calling XFree() on the returned data. **/ unsigned char* -get_prop(int id, const char* prop, int* nitems) +get_prop(int id, const char* prop, int* nbytes) { if (!prop) { fprintf(stderr, "[get_prop] Empty property for %d\n", id); return NULL; } - if (!nitems) { - fprintf(stderr, "[get_prop] Invalid item number for %d\n", id); + if (!nbytes) { + fprintf(stderr, "[get_prop] Invalid nbytes pointer for %d\n", id); return NULL; } + *nbytes = 0; + pthread_mutex_lock(&x11_global_mutex); setErrorHandler(); @@ -54,24 +58,82 @@ get_prop(int id, const char* prop, int* nitems) int act_format; unsigned long num_items, bytes_after; unsigned char* data = NULL; - int ret = XIGetProperty(disp, id, prop_id, 0, MAX_BUF_LEN, False, + + // Step 1: Query property size (length=0 to get bytes_after) + int ret = XIGetProperty(disp, id, prop_id, 0, 0, False, AnyPropertyType, &act_type, &act_format, &num_items, &bytes_after, &data); + if (ret != Success || act_type == None) { + if (data) { + XFree(data); + } + XCloseDisplay(disp); + pthread_mutex_unlock(&x11_global_mutex); + return NULL; + } + + if (data) { + XFree(data); + data = NULL; + } + + if (bytes_after == 0) { + // Property exists but has no data + XCloseDisplay(disp); + pthread_mutex_unlock(&x11_global_mutex); + return NULL; + } + + // Step 2: Read all property data + // length is in 32-bit units, so divide bytes_after by 4 (round up) + unsigned long length = (bytes_after + 3) / 4; + + ret = XIGetProperty(disp, id, prop_id, 0, length, False, + AnyPropertyType, &act_type, &act_format, + &num_items, &bytes_after, &data); if (ret != Success) { + if (data) { + XFree(data); + } XCloseDisplay(disp); fprintf(stderr, "[get_prop] Get %s data failed for %d\n", prop, id); pthread_mutex_unlock(&x11_global_mutex); return NULL; } - *nitems = (int)num_items; - XCloseDisplay(disp); + // Calculate actual byte length based on format and num_items + // format is 8, 16, or 32 bits per item + // Guard against integer overflow when converting unsigned long to int + unsigned long nbytes_ul = num_items * (act_format / 8); + if (nbytes_ul > INT_MAX) { + if (data) { + XFree(data); + } + XCloseDisplay(disp); + fprintf(stderr, "[get_prop] Property data too large: %lu bytes (max %d)\n", nbytes_ul, INT_MAX); + pthread_mutex_unlock(&x11_global_mutex); + return NULL; + } + *nbytes = (int)nbytes_ul; + XCloseDisplay(disp); pthread_mutex_unlock(&x11_global_mutex); return data; } +/** + * Free the property data returned by get_prop. + * This is a wrapper for XFree to be called from Go. + */ +void +free_prop_data(unsigned char* data) +{ + if (data) { + XFree(data); + } +} + // bit: range(8,16,32) int set_prop_int(int id, const char* prop, unsigned char* data, int nitems, int bit) diff --git a/dxinput/utils/property.h b/dxinput/utils/property.h index caba0b3..df19899 100644 --- a/dxinput/utils/property.h +++ b/dxinput/utils/property.h @@ -7,7 +7,8 @@ #include -unsigned char* get_prop(int id, const char* prop, int* nitems); +unsigned char* get_prop(int id, const char* prop, int* nbytes); +void free_prop_data(unsigned char* data); int set_prop_int(int id, const char* prop, unsigned char* data, int nitems, int bit); int set_prop_float(int id, const char* prop, unsigned char* data, int nitems); int set_prop(int id, const char* prop, unsigned char* data, int nitems, diff --git a/dxinput/utils/type.c b/dxinput/utils/type.c index 79bcc7d..3aa77c4 100644 --- a/dxinput/utils/type.c +++ b/dxinput/utils/type.c @@ -37,6 +37,7 @@ listener_error_handler(Display * display, XErrorEvent * event) int listener_ioerror_handler(Display * display) { + (void)display; // Suppress unused parameter warning return 0; } @@ -157,7 +158,7 @@ static int is_keyboard_device(int deviceid) { Display *display; - int num_devices, i; + int num_devices; // NOTE: No mutex lock here - called from query_device_type which already holds the lock // 打开 X11 显示 diff --git a/dxinput/utils/wrapper.go b/dxinput/utils/wrapper.go index 389efab..d673863 100644 --- a/dxinput/utils/wrapper.go +++ b/dxinput/utils/wrapper.go @@ -23,11 +23,6 @@ import ( "github.com/linuxdeepin/dde-api/dxinput/kwayland" ) -const ( - // see 'property.c' MAX_BUF_LEN - maxBufferLen = 1000 -) - func ListDevice() DeviceInfos { if len(os.Getenv("WAYLAND_DISPLAY")) != 0 { infos, _ := kwayland.ListDevice() @@ -103,21 +98,19 @@ func GetProperty(id int32, prop string) ([]byte, int32) { } cprop := C.CString(prop) + defer C.free(unsafe.Pointer(cprop)) - defer func() { - if cprop != nil { - C.free(unsafe.Pointer(cprop)) - } - }() - - nitems := C.int(0) - cdatas := C.get_prop(C.int(id), cprop, &nitems) - if cdatas == nil || nitems == 0 { + // nbytes now represents the actual byte length returned by C layer + nbytes := C.int(0) + cdatas := C.get_prop(C.int(id), cprop, &nbytes) + if cdatas == nil || nbytes == 0 { return nil, 0 } + // XIGetProperty allocates memory for data, caller must free it with XFree + defer C.free_prop_data(cdatas) - datas := ucharArrayToByte(cdatas, maxBufferLen) - return datas, int32(nitems) + datas := C.GoBytes(unsafe.Pointer(cdatas), nbytes) + return datas, int32(nbytes) } func SetInt8Prop(id int32, prop string, values []int8) error { @@ -196,25 +189,6 @@ func SetFloat32Prop(id int32, prop string, values []float32) error { return nil } -func ucharArrayToByte(cData *C.uchar, length int) []byte { - if cData == nil { - return nil - } - cItemSize := unsafe.Sizeof(*cData) - - var data []byte - for i := 0; i < length; i++ { - offset := uintptr(i) * cItemSize - addr := uintptr(unsafe.Pointer(cData)) + offset - cdata := (*C.uchar)(unsafe.Pointer(addr)) - if cdata == nil { - break - } - data = append(data, byte(*cdata)) - } - return data -} - func byteArrayToUChar(datas []byte) []C.uchar { var cdatas []C.uchar for i := 0; i < len(datas); i++ { diff --git a/dxinput/wacom.go b/dxinput/wacom.go index dd7bfa8..56312ac 100644 --- a/dxinput/wacom.go +++ b/dxinput/wacom.go @@ -6,11 +6,11 @@ package dxinput import ( "bytes" + "errors" "fmt" "os/exec" "strconv" "strings" - "errors" . "github.com/linuxdeepin/dde-api/dxinput/common" "github.com/linuxdeepin/dde-api/dxinput/utils" @@ -251,7 +251,7 @@ func doAction(cmd string) error { // #nosec G204 out, err := exec.Command("/bin/sh", "-c", cmd).CombinedOutput() if err != nil { - return fmt.Errorf(string(out)) + return errors.New(string(out)) } return nil }