diff --git a/xcb/c.go b/xcb/c.go index 8fe59d8..3138c1d 100644 --- a/xcb/c.go +++ b/xcb/c.go @@ -1,33 +1,124 @@ package xcb import ( - "errors" + "runtime" + "unsafe" ) -//#include -//#include -//#cgo linux LDFLAGS: -lxcb +/* +#cgo linux pkg-config: xcb + +#include +#include + +static int _go_xcb_change_hosts_checked(xcb_connection_t *c, uint8_t mode, uint8_t family, uint16_t address_len, const uint8_t *address) { + xcb_void_cookie_t cookie = xcb_change_hosts_checked(c, mode, family, address_len, address); + free((void *)address); + + int errno = xcb_connection_has_error(c); + if (errno != 0) + return errno; + + xcb_generic_error_t *e = xcb_request_check(c, cookie); + if (e != NULL) { + // don't want to deal with xcb errors + free((void *)e); + return -1; + } + + return 0; +} +*/ import "C" -func xcbHandleConnectionError(c *C.xcb_connection_t) error { - if errno := C.xcb_connection_has_error(c); errno != 0 { - switch errno { - case C.XCB_CONN_ERROR: - return errors.New("connection error") - case C.XCB_CONN_CLOSED_EXT_NOTSUPPORTED: - return errors.New("extension not supported") - case C.XCB_CONN_CLOSED_MEM_INSUFFICIENT: - return errors.New("memory not available") - case C.XCB_CONN_CLOSED_REQ_LEN_EXCEED: - return errors.New("request length exceeded") - case C.XCB_CONN_CLOSED_PARSE_ERR: - return errors.New("invalid display string") - case C.XCB_CONN_CLOSED_INVALID_SCREEN: - return errors.New("server has no screen matching display") - default: - return errors.New("generic X11 failure") - } - } else { +const ( + HostModeInsert = C.XCB_HOST_MODE_INSERT + HostModeDelete = C.XCB_HOST_MODE_DELETE + + FamilyInternet = C.XCB_FAMILY_INTERNET + FamilyDecnet = C.XCB_FAMILY_DECNET + FamilyChaos = C.XCB_FAMILY_CHAOS + FamilyServerInterpreted = C.XCB_FAMILY_SERVER_INTERPRETED + FamilyInternet6 = C.XCB_FAMILY_INTERNET_6 +) + +type ( + HostMode = C.xcb_host_mode_t + Family = C.xcb_family_t +) + +func (conn *connection) changeHostsChecked(mode HostMode, family Family, address string) error { + errno := C._go_xcb_change_hosts_checked( + conn.c, + C.uint8_t(mode), + C.uint8_t(family), + C.uint16_t(len(address)), + (*C.uint8_t)(unsafe.Pointer(C.CString(address))), + ) + switch errno { + case 0: return nil + case -1: + return ErrChangeHosts + default: + return &ConnectionError{errno} } } + +type connection struct{ c *C.xcb_connection_t } + +func connect() (*connection, error) { + conn := newConnection(C.xcb_connect(nil, nil)) + return conn, conn.hasError() +} + +func newConnection(c *C.xcb_connection_t) *connection { + conn := &connection{c} + runtime.SetFinalizer(conn, (*connection).disconnect) + return conn +} + +const ( + ConnError = C.XCB_CONN_ERROR + ConnClosedExtNotSupported = C.XCB_CONN_CLOSED_EXT_NOTSUPPORTED + ConnClosedMemInsufficient = C.XCB_CONN_CLOSED_MEM_INSUFFICIENT + ConnClosedReqLenExceed = C.XCB_CONN_CLOSED_REQ_LEN_EXCEED + ConnClosedParseErr = C.XCB_CONN_CLOSED_PARSE_ERR + ConnClosedInvalidScreen = C.XCB_CONN_CLOSED_INVALID_SCREEN +) + +type ConnectionError struct{ errno C.int } + +func (ce *ConnectionError) Error() string { + switch ce.errno { + case ConnError: + return "connection error" + case ConnClosedExtNotSupported: + return "extension not supported" + case ConnClosedMemInsufficient: + return "memory not available" + case ConnClosedReqLenExceed: + return "request length exceeded" + case ConnClosedParseErr: + return "invalid display string" + case ConnClosedInvalidScreen: + return "server has no screen matching display" + default: + return "generic X11 failure" + } +} + +func (conn *connection) hasError() error { + errno := C.xcb_connection_has_error(conn.c) + if errno == 0 { + return nil + } + return &ConnectionError{errno} +} + +func (conn *connection) disconnect() { + C.xcb_disconnect(conn.c) + + // no need for a finalizer anymore + runtime.SetFinalizer(conn, nil) +} diff --git a/xcb/export.go b/xcb/export.go index 35af9aa..1ed9997 100644 --- a/xcb/export.go +++ b/xcb/export.go @@ -1,63 +1,22 @@ // Package xcb implements X11 ChangeHosts via libxcb. package xcb -//#include -//#include -//#cgo linux LDFLAGS: -lxcb -import "C" import ( "errors" - "unsafe" ) -const ( - HostModeInsert = C.XCB_HOST_MODE_INSERT - HostModeDelete = C.XCB_HOST_MODE_DELETE +var ErrChangeHosts = errors.New("xcb_change_hosts() failed") - FamilyInternet = C.XCB_FAMILY_INTERNET - FamilyDecnet = C.XCB_FAMILY_DECNET - FamilyChaos = C.XCB_FAMILY_CHAOS - FamilyServerInterpreted = C.XCB_FAMILY_SERVER_INTERPRETED - FamilyInternet6 = C.XCB_FAMILY_INTERNET_6 -) +func ChangeHosts(mode HostMode, family Family, address string) error { + var conn *connection -type ConnectionError struct { - err error -} - -func (e *ConnectionError) Error() string { - return e.err.Error() -} - -func (e *ConnectionError) Unwrap() error { - return e.err -} - -var ( - ErrChangeHosts = errors.New("xcb_change_hosts() failed") -) - -func ChangeHosts(mode, family C.uint8_t, address string) error { - c := C.xcb_connect(nil, nil) - defer C.xcb_disconnect(c) - - if err := xcbHandleConnectionError(c); err != nil { - return &ConnectionError{err} + if c, err := connect(); err != nil { + c.disconnect() + return err + } else { + defer c.disconnect() + conn = c } - addr := C.CString(address) - cookie := C.xcb_change_hosts_checked(c, mode, family, C.ushort(len(address)), (*C.uchar)(unsafe.Pointer(addr))) - C.free(unsafe.Pointer(addr)) - - if err := xcbHandleConnectionError(c); err != nil { - return &ConnectionError{err} - } - - e := C.xcb_request_check(c, cookie) - if e != nil { - defer C.free(unsafe.Pointer(e)) - return ErrChangeHosts - } - - return nil + return conn.changeHostsChecked(mode, family, address) }