libspnav protocol v1 partial support and extended API

pull/15/head
John Tsiombikas 2022-02-12 16:49:26 +02:00
rodzic 22f354a083
commit c4b098a02c
4 zmienionych plików z 287 dodań i 10 usunięć

Wyświetl plik

@ -4,6 +4,10 @@
#include <X11/Xlib.h>
#include <spnav.h>
#if defined(BUILD_AF_UNIX)
void print_dev_info(void);
#endif
void sig(int s)
{
spnav_close();
@ -42,9 +46,11 @@ int main(void)
#elif defined(BUILD_AF_UNIX)
if(spnav_open()==-1) {
fprintf(stderr, "failed to connect to the space navigator daemon\n");
fprintf(stderr, "failed to connect to the space navigator daemon\n");
return 1;
}
print_dev_info();
#else
#error Unknown build type!
#endif
@ -67,3 +73,25 @@ int main(void)
spnav_close();
return 0;
}
#if defined(BUILD_AF_UNIX)
void print_dev_info(void)
{
int proto;
if((proto = spnav_protocol()) == -1) {
fprintf(stderr, "failed to query protocol version\n");
return;
}
printf("spacenav AF_UNIX protocol version: %d\n", proto);
if(proto >= 1) {
printf("Device: %s\n", spnav_dev_name(0, 0));
printf("Path: %s\n", spnav_dev_path(0, 0));
printf("Buttons: %d\n", spnav_dev_buttons());
printf("Axes: %d\n", spnav_dev_axes());
}
putchar('\n');
}
#endif

48
proto.h 100644
Wyświetl plik

@ -0,0 +1,48 @@
#ifndef PROTO_H_
#define PROTO_H_
/* maximum supported protocol version */
#define MAX_PROTO_VER 1
struct reqresp {
int type;
int data[7];
};
#define REQ_TAG 0x7faa0000
#define REQ_BASE 0x1000
/* REQ_S* are set, REQ_G* are get requests.
* Quick-reference for request-response data in the comments next to each
* request: Q[n] defines request data item n, R[n] defines response data item n
*
* status responses are 0 for success, non-zero for failure
*/
enum {
/* per-client settings */
REQ_SET_SENS = REQ_BASE,/* set client sensitivity: Q[0] float - R[6] status */
REQ_GET_SENS, /* get client sensitivity: R[0] float R[6] status */
/* device queries */
REQ_DEV_NAME = 0x2000, /* get device name: R[0] length R[6] status followed
by <length> bytes */
REQ_DEV_PATH, /* get device path: same as above */
REQ_DEV_NAXES, /* get number of axes: R[0] num axes R[6] status */
REQ_DEV_NBUTTONS, /* get number of buttons: same as above */
/* TODO: features like LCD, LEDs ... */
/* configuration settings */
REQ_SCFG_SENS = 0x3000, /* set global sensitivity: Q[0] float - R[6] status */
REQ_GCFG_SENS, /* get global sens: R[0] float R[6] status */
REQ_SCFG_SENS_AXIS, /* set per-axis sens/ty: Q[0-5] values - R[6] status */
REQ_GCFG_SENS_AXIS, /* get per-axis sens/ty: R[0-5] values R[6] status */
REQ_SCFG_DEADZONE, /* set deadzones: Q[0-5] values - R[6] status */
REQ_GCFG_DEADZONE, /* get deadzones: R[0-5] values R[6] status */
REQ_SCFG_INVERT, /* set invert axes: Q[0-5] invert - R[6] status */
REQ_GCFG_INVERT, /* get invert axes: R[0-5] invert R[6] status */
/* TODO ... more */
REQ_CHANGE_PROTO = 0x5500
};
#endif /* PROTO_H_ */

182
spnav.c
Wyświetl plik

@ -36,7 +36,11 @@ OF SUCH DAMAGE.
#include <sys/un.h>
#include <sys/select.h>
#include "spnav.h"
#include "proto.h"
/* default timeout for request responses*/
#define TIMEOUT 256
/* default socket path */
#define SPNAV_SOCK_PATH "/var/run/spnav.sock"
#ifdef USE_X11
@ -46,6 +50,10 @@ OF SUCH DAMAGE.
static Window get_daemon_window(Display *dpy);
static int catch_badwin(Display *dpy, XErrorEvent *err);
static int wait_resp(void *buf, int sz, int timeout_ms);
static int request(int req, struct reqresp *rr, int timeout_ms);
static Display *dpy;
static Window app_win;
static Atom motion_event, button_press_event, button_release_event, command_event;
@ -70,6 +78,7 @@ static struct event_node *ev_queue, *ev_queue_tail;
/* AF_UNIX socket used for alternative communication with daemon */
static int sock = -1;
static int proto;
static int connect_afunix(int s, const char *path)
{
@ -83,7 +92,7 @@ static int connect_afunix(int s, const char *path)
int spnav_open(void)
{
int s;
int s, cmd;
char *path;
FILE *fp;
char buf[256], *ptr;
@ -135,6 +144,20 @@ int spnav_open(void)
success:
sock = s;
proto = 0;
/* send protocol change request and wait for a response.
* if we time out, assume we're talking with an old version of spacenavd,
* which took our packet as a sensitivity value, so restore sensitivity to
* 1.0 and continue with protocol v0
*/
cmd = REQ_TAG | REQ_CHANGE_PROTO | MAX_PROTO_VER;
write(s, &cmd, sizeof cmd);
if(wait_resp(&cmd, sizeof cmd, TIMEOUT) == -1) {
spnav_sensitivity(1.0f);
} else {
proto = cmd & 0xff;
}
return 0;
}
@ -269,24 +292,33 @@ static int x11_sensitivity(double sens)
int spnav_sensitivity(double sens)
{
struct reqresp rr;
#ifdef USE_X11
if(dpy) {
return x11_sensitivity(sens);
}
#endif
if(sock) {
ssize_t bytes;
float fval = sens;
if(proto == 0) {
if(sock) {
ssize_t bytes;
float fval = sens;
while((bytes = write(sock, &fval, sizeof fval)) <= 0 && errno == EINTR);
if(bytes <= 0) {
return -1;
while((bytes = write(sock, &fval, sizeof fval)) <= 0 && errno == EINTR);
if(bytes <= 0) {
return -1;
}
return 0;
}
return 0;
return -1;
}
return -1;
rr.data[0] = *(int*)&sens;
if(request(REQ_SET_SENS, &rr, TIMEOUT) == -1) {
return -1;
}
return 0;
}
int spnav_fd(void)
@ -595,3 +627,135 @@ int catch_badwin(Display *dpy, XErrorEvent *err)
return 0;
}
#endif
static int wait_resp(void *buf, int sz, int timeout_ms)
{
int res;
fd_set rdset;
struct timeval tv;
char *ptr;
if(timeout_ms) {
FD_ZERO(&rdset);
FD_SET(sock, &rdset);
if(timeout_ms > 0) {
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
}
while((res = select(sock + 1, &rdset, 0, 0, timeout_ms < 0 ? 0 : &tv)) == -1 && errno == EINTR);
}
if(!timeout_ms || (res > 0 && FD_ISSET(sock, &rdset))) {
ptr = buf;
while(sz > 0) {
if((res = read(sock, ptr, sz)) <= 0 && errno != EINTR) {
return -1;
}
ptr += res;
sz -= res;
}
return 0;
}
return -1;
}
static int request(int req, struct reqresp *rr, int timeout_ms)
{
if(sock < 0) return -1;
req |= REQ_TAG;
rr->type = req;
write(sock, rr, sizeof *rr);
if(wait_resp(rr, sizeof *rr, TIMEOUT) == -1) {
return -1;
}
if(rr->type != req) return -1;
return 0;
}
int spnav_protocol(void)
{
return proto;
}
const char *query_str(char *buf, int bufsz, int req)
{
struct reqresp rr = {0};
static char strbuf[512];
char *str;
if(!buf) {
buf = strbuf;
bufsz = sizeof strbuf;
}
/* if we get a huge name, something is probably wrong, assume it's a mistake */
if(request(req, &rr, TIMEOUT) == -1 || rr.data[6] != 0 || rr.data[0] >= 512 || rr.data[0] <= 0) {
return 0;
}
if(bufsz < rr.data[0]) {
if(!(str = malloc(rr.data[0]))) {
str = strbuf;
bufsz = sizeof strbuf;
}
} else {
str = buf;
}
if(wait_resp(str, rr.data[0], TIMEOUT) == -1) {
if(str != strbuf && str != buf) {
free(str);
}
return 0;
}
str[rr.data[0]] = 0;
if(str != buf) {
strncpy(buf, str, bufsz);
buf[bufsz - 1] = 0;
}
if(str != strbuf && str != buf) {
free(str);
}
return buf;
}
const char *spnav_dev_name(char *buf, int bufsz)
{
if(proto < 1) return 0;
return query_str(buf, bufsz, REQ_DEV_NAME);
}
const char *spnav_dev_path(char *buf, int bufsz)
{
if(proto < 1) return 0;
return query_str(buf, bufsz, REQ_DEV_PATH);
}
int spnav_dev_buttons(void)
{
struct reqresp rr = {0};
if(proto < 1 || request(REQ_DEV_NBUTTONS, &rr, TIMEOUT) == -1 || rr.data[6] != 0) {
return 2; /* default */
}
return rr.data[0];
}
int spnav_dev_axes(void)
{
struct reqresp rr = {0};
if(proto < 1 || request(REQ_DEV_NAXES, &rr, TIMEOUT) == -1 || rr.data[6] != 0) {
return 6; /* default */
}
return rr.data[0];
}

37
spnav.h
Wyświetl plik

@ -130,6 +130,43 @@ int spnav_x11_window(Window win);
int spnav_x11_event(const XEvent *xev, spnav_event *event);
#endif
/* returns the protocol version understood by the running spacenavd
* -1 on error, or if the connection was established using the X11
* protocol (spnav_x11_open).
*/
int spnav_protocol(void);
/*
* Everything from this point on will either fail, or return hardcoded default
* values when communicating over the X11 protocol (spnav_x11_open), or when
* using a spacenav protocol version less than 1. See spnav_protocol().
*/
/* TODO multi-device support and device selection not implemented yet
int spnav_num_devices(void);
int spnav_select_device(int dev);
*/
/* Returns a descriptive device name.
* The name is copied into buf, and a pointer to it is returned. No more than
* bufsz bytes are written including the zero terminator.
* buf can be null, in which case a pointer to a static string is returned.
*/
const char *spnav_dev_name(char *buf, int bufsz);
/* Returns the path to the device file if applicable.
* Usage same as spnav_dev_name.
*/
const char *spnav_dev_path(char *buf, int bufsz);
/* returns the number of buttons (defaults to 2 if unable to query) */
int spnav_dev_buttons(void);
/* returns the number of axes (defaults to 6 if unable to query) */
int spnav_dev_axes(void);
#ifdef __cplusplus
}
#endif