pax_global_header00006660000000000000000000000064147721276050014525gustar00rootroot0000000000000052 comment=d641e5cbbe0d6d2381a2ee8d95c8c7273d005272 libspnav-1.2/000077500000000000000000000000001477212760500132055ustar00rootroot00000000000000libspnav-1.2/.github/000077500000000000000000000000001477212760500145455ustar00rootroot00000000000000libspnav-1.2/.github/workflows/000077500000000000000000000000001477212760500166025ustar00rootroot00000000000000libspnav-1.2/.github/workflows/build_freebsd.yml000066400000000000000000000015621477212760500221220ustar00rootroot00000000000000name: FreeBSD build on: push: pull_request: workflow_dispatch: jobs: build: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: FreeBSD build uses: vmactions/freebsd-vm@v1 with: prepare: | pkg install -y gmake libX11 mesa-libs libglvnd libGLU run: | ./configure gmake gmake DESTDIR=libspnav-freebsd install gmake examples mkdir libspnav-freebsd/examples cp examples/simple/simple_af_unix libspnav-freebsd/examples cp examples/simple/simple_x11 libspnav-freebsd/examples cp examples/cube/cube libspnav-freebsd/examples cp examples/fly/fly libspnav-freebsd/examples - uses: actions/upload-artifact@v4 with: name: libspnav-freebsd path: libspnav-freebsd # vi:ts=2 sts=2 sw=2 expandtab: libspnav-1.2/.github/workflows/build_gnulinux.yml000066400000000000000000000034321477212760500223570ustar00rootroot00000000000000name: GNU/Linux build on: push: pull_request: workflow_dispatch: jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: install dependencies run: | sudo apt-get update sudo apt-get install libx11-dev libgl1-mesa-dev libglu1-mesa-dev - name: configure run: ./configure - name: build run: make - name: build examples run: make examples - name: stage install run: | DESTDIR=libspnav-gnulinux make install mkdir libspnav-gnulinux/examples cp examples/simple/simple_af_unix libspnav-gnulinux/examples cp examples/simple/simple_x11 libspnav-gnulinux/examples cp examples/cube/cube libspnav-gnulinux/examples cp examples/fly/fly libspnav-gnulinux/examples - uses: actions/upload-artifact@v4 with: name: libspnav-gnulinux path: libspnav-gnulinux build-nox11: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: install dependencies run: | sudo apt-get update sudo apt-get install libx11-dev libgl1-mesa-dev libglu1-mesa-dev - name: configure run: ./configure --disable-x11 - name: build run: make - name: build examples run: | make -C examples/simple simple_af_unix make -C examples/fly - name: stage install run: | DESTDIR=libspnav-gnulinux-nox11 make install mkdir libspnav-gnulinux-nox11/examples cp examples/simple/simple_af_unix libspnav-gnulinux-nox11/examples cp examples/fly/fly libspnav-gnulinux-nox11/examples - uses: actions/upload-artifact@v4 with: name: libspnav-gnulinux-nox11 path: libspnav-gnulinux-nox11 # vi:ts=2 sts=2 sw=2 expandtab: libspnav-1.2/.github/workflows/build_macosx.yml000066400000000000000000000032031477212760500217740ustar00rootroot00000000000000name: MacOS X build on: push: pull_request: workflow_dispatch: jobs: build: runs-on: macos-latest steps: - uses: actions/checkout@v4 - name: install dependencies run: | brew install libx11 mesa mesa-glu - name: configure run: ./configure - name: build run: make - name: build examples run: make examples - name: stage install run: | DESTDIR=libspnav-macosx make install mkdir libspnav-macosx/examples cp examples/simple/simple_af_unix libspnav-macosx/examples cp examples/simple/simple_x11 libspnav-macosx/examples cp examples/cube/cube libspnav-macosx/examples cp examples/fly/fly libspnav-macosx/examples - uses: actions/upload-artifact@v4 with: name: libspnav-macosx path: libspnav-macosx build-nox11: runs-on: macos-latest steps: - uses: actions/checkout@v4 - name: install dependencies run: | brew install libx11 mesa mesa-glu - name: configure run: ./configure --disable-x11 - name: build run: make - name: build examples run: | make -C examples/simple simple_af_unix make -C examples/fly - name: stage install run: | DESTDIR=libspnav-macosx-nox11 make install mkdir libspnav-macosx-nox11/examples cp examples/simple/simple_af_unix libspnav-macosx-nox11/examples cp examples/fly/fly libspnav-macosx-nox11/examples - uses: actions/upload-artifact@v4 with: name: libspnav-macosx-nox11 path: libspnav-macosx-nox11 # vi:ts=2 sts=2 sw=2 expandtab: libspnav-1.2/.gitignore000066400000000000000000000002551477212760500151770ustar00rootroot00000000000000*.o *.d *.swp *.a *.so *.so.* spnav_config.h Makefile examples/cube/cube examples/fly/fly examples/simple/simple_af_unix examples/simple/simple_x11 spnav.pc doc/manual.html libspnav-1.2/LICENSE000066400000000000000000000025741477212760500142220ustar00rootroot00000000000000Copyright (C) 2007-2018 John Tsiombikas Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. libspnav-1.2/Makefile.in000066400000000000000000000041341477212760500152540ustar00rootroot00000000000000 obj = src/spnav.o src/proto.o src/util.o $(magellan_obj) hdr = src/spnav.h src/spnav_magellan.h src/spnav_config.h name = spnav lib_a = lib$(name).a incpaths = -I. -I/usr/local/include -I/usr/X11R6/include -I/opt/homebrew/include libpaths = -L/usr/local/lib -L/usr/X11R6/lib -L/opt/homebrew/lib CC ?= gcc AR ?= ar CFLAGS = $(opt) $(dbg) -std=c89 $(pic) -pedantic -Wall -fno-strict-aliasing $(incpaths) $(user_cflags) LDFLAGS = $(libpaths) $(user_ldflags) $(xlib) -lm ifeq ($(shell uname -s), Darwin) lib_so = libspnav.dylib shared = -dynamiclib else so_major = 0 so_minor = 4 devlink = lib$(name).so soname = $(devlink).$(so_major) lib_so = $(soname).$(so_minor) shared = -shared -Wl,-soname,$(soname) pic = -fPIC endif .PHONY: all all: $(lib_a) $(lib_so) $(lib_a): $(obj) $(AR) rcs $@ $(obj) $(lib_so): $(obj) $(CC) $(shared) -o $@ $(obj) $(LDFLAGS) %.o: $(srcdir)/%.c $(CC) $(CFLAGS) -c $< -o $@ .PHONY: clean clean: rm -f $(obj) .PHONY: distclean distclean: rm -f $(obj) $(lib_a) $(lib_so) Makefile .PHONY: install install: $(lib_a) $(lib_so) $(hdr) mkdir -p $(DESTDIR)$(PREFIX)/$(libdir) $(DESTDIR)$(PREFIX)/include cp $(lib_a) $(DESTDIR)$(PREFIX)/$(libdir)/$(lib_a) cp $(lib_so) $(DESTDIR)$(PREFIX)/$(libdir)/$(lib_so) [ -n "$(soname)" ] && \ rm -f $(DESTDIR)$(PREFIX)/$(libdir)/$(soname) $(DESTDIR)$(PREFIX)/$(libdir)/$(devlink) && \ cd $(DESTDIR)$(PREFIX)/$(libdir) && \ ln -s $(lib_so) $(soname) && \ ln -s $(soname) $(devlink) || \ true for h in $(hdr); do cp -p $(srcdir)/$$h $(DESTDIR)$(PREFIX)/include/; done mkdir -p $(DESTDIR)$(PREFIX)/share/pkgconfig cp spnav.pc $(DESTDIR)$(PREFIX)/share/pkgconfig/spnav.pc .PHONY: uninstall uninstall: rm -f $(DESTDIR)$(PREFIX)/$(libdir)/$(lib_a) rm -f $(DESTDIR)$(PREFIX)/$(libdir)/$(lib_so) [ -n "$(soname)" ] && \ rm -f $(DESTDIR)$(PREFIX)/$(libdir)/$(soname) $(DESTDIR)$(PREFIX)/$(libdir)/$(devlink) || \ true for i in $(hdr); do rm -f $(DESTDIR)$(PREFIX)/include/$$i; done rm -f $(DESTDIR)$(PREFIX)/share/pkgconfig/spnav.pc .PHONY: examples examples: $(MAKE) -C examples/simple $(MAKE) -C examples/cube $(MAKE) -C examples/fly libspnav-1.2/README.md000066400000000000000000000067511477212760500144750ustar00rootroot00000000000000libspnav ======== ![GNU/Linux build status](https://github.com/FreeSpacenav/libspnav/actions/workflows/build_gnulinux.yml/badge.svg) ![FreeBSD build status](https://github.com/FreeSpacenav/libspnav/actions/workflows/build_freebsd.yml/badge.svg) ![MacOS X build status](https://github.com/FreeSpacenav/libspnav/actions/workflows/build_macosx.yml/badge.svg) About ----- Libspnav is a C library for receiving input from 6 degrees-of-freedom (6dof) input devices, also known as spacemice, spaceballs, etc. 6dof input is very useful for fluidly manipulating objects or viewpoints in 3D space, and fundamendally comprises of relative movement (translation) across 3 axes (TX/TY/TZ), and rotation about 3 axes (RX/RY/RZ). Libspnav is a counterpart to the free spacenav driver (spacenavd), which runs as a system daemon, and handles all the low level interfacing with the actual devices. However, it can also communicate with the proprietary 3Dconnexion driver (3dxsrv), with reduced functionality. There are two modes of operation supported by libspnav: 1. It can use the native spacenav protocol, which works over UNIX domain sockets, supports the whole feature set exposed by libspnav, but is only compatible with spacenavd. This is used when `spnav_open` is called to connect to the driver. 2. It can use the X11 magellan protocol, based on X `ClientMessage` events, which is compatible with both spacenavd and the proprietary 3dxsrv, but only supports a limited subset of features. Basically just receiving input events and changing sensitivity, no queries, device information, or configuration management. This is used when `spnav_x11_open` is called to connect to the driver. Also, libspnav provides a magellan API wrapper on top of the libspnav API, which allows existing applications written for the official SDK to be transparently recompiled against libspnav and be free of the 3Dconnexion licensing conditions. Documentation ------------- To learn how to use libspnav in your programs, refer to the [manual](doc/manual.md), which is available in markdown format under the `doc` directory of the libspnav distribution. The manual is also available online in HTML format on the free spacenav project website: http://spacenav.sourceforge.net/man_libspnav Also make sure to check out the example programs which come with libspnav under the `examples` directory. ![examples](http://spacenav.sourceforge.net/images/exbar.png) Installation ------------ To build and install libspnav, simply run: ./configure make make install Most likely the `make install` part will need to be executed as root, if you're installing libspnav system-wide, which is the common case (default prefix is: `/usr/local`). Run `./configure --help` for a list of available build options. By default libspnav will be compiled with X11 support (and will require Xlib), which is necessary for compatibility with the proprietary driver. If you don't need that, and would rather drop the Xlib dependency, you can pass `--disable-x11` to `configure`, to build without X11 support. To build the example programs, change into their directory and run `make`. The "cube" and "fly" examples use OpenGL and Xlib, so make sure to have `libGL` and `libX11` installed, before attempting to build them. License ------- Copyright (C) 2007-2025 John Tsiombikas libspnav is free software. Feel free to use, modify, and/or redistibute it under the terms of the 3-clause BSD license. See LICENSE for details. libspnav-1.2/configure000077500000000000000000000057531477212760500151260ustar00rootroot00000000000000#!/bin/sh echo 'configuring spacenav library...' PREFIX=/usr/local OPT=yes DBG=yes X11=yes VER=`git describe --tags 2>/dev/null` if [ -z "$VER" ]; then VER=`git rev-parse --short HEAD 2>/dev/null` if [ -z "$VER" ]; then VER=v`pwd | grep 'libspnav-[0-9]\+\.' | sed 's/.*libspnav-\(\([0-9]\+\.\)\+[0-9]\+\).*$/\1/'` if [ $VER = v ]; then VER='' fi fi fi echo "configuring libspnav - $VER" srcdir="`dirname "$0"`" libdir=lib #if [ "`uname -m`" = 'x86_64' ]; then # libdir=lib64 #fi for arg; do case "$arg" in --prefix=*) value=`echo $arg | sed 's/--prefix=//'` PREFIX=${value:-$prefix} ;; --enable-opt) OPT=yes;; --disable-opt) OPT=no;; --enable-debug) DBG=yes;; --disable-debug) DBG=no;; --enable-x11) X11=yes;; --disable-x11) X11=no;; --help) echo 'usage: ./configure [options]' echo 'options:' echo ' --prefix=: installation path (default: /usr/local)' echo ' --enable-x11: enable X11 communication mode (default)' echo ' --disable-x11: disable X11 communication mode' echo ' --enable-opt: enable speed optimizations (default)' echo ' --disable-opt: disable speed optimizations' echo ' --enable-debug: include debugging symbols (default)' echo ' --disable-debug: do not include debugging symbols' echo 'all invalid options are silently ignored' exit 0 ;; esac done echo " prefix: $PREFIX" echo " optimize for speed: $OPT" echo " include debugging symbols: $DBG" echo " x11 communication method: $X11" if [ -n "$CFLAGS" ]; then echo " cflags: $CFLAGS" fi if [ -n "$LDFLAGS" ]; then echo " ldflags: $LDFLAGS" fi echo "" if [ "$X11" = "no" ]; then echo "WARNING: you have disabled the X11 interface, the resulting library won't be compatible with the proprietary 3Dconnexion daemon (3dxserv)!" echo "" fi # create Makefile echo 'creating Makefile ...' echo "PREFIX = $PREFIX" >Makefile echo "srcdir = $srcdir" >>Makefile echo "libdir = $libdir" >>Makefile if [ -n "$CFLAGS" ]; then echo "user_cflags = $CFLAGS" >>Makefile fi if [ -n "$LDFLAGS" ]; then echo "user_ldflags = $LDFLAGS" >>Makefile fi if [ "$DBG" = 'yes' ]; then echo 'dbg = -g' >>Makefile fi if [ "$OPT" = 'yes' ]; then echo 'opt = -O3' >>Makefile fi if [ "$X11" = 'yes' ]; then echo 'magellan_obj = src/spnav_magellan.o' >>Makefile echo 'xlib = -lX11' >>Makefile fi cat "$srcdir/Makefile.in" >>Makefile # create spnav_config.h echo 'creating spnav_config.h ...' echo '#ifndef SPNAV_CONFIG_H_' >src/spnav_config.h echo '#define SPNAV_CONFIG_H_' >>src/spnav_config.h echo '' >>src/spnav_config.h if [ "$X11" = 'yes' ]; then echo '#define SPNAV_USE_X11' >>src/spnav_config.h echo '' >>src/spnav_config.h fi echo '#endif /* SPNAV_CONFIG_H_ */' >>src/spnav_config.h # create pkgconfig file echo 'creating spnav.pc ...' pcver=`echo $VER | sed 's/^v//'` echo "PREFIX=$PREFIX" >spnav.pc cat "$srcdir/spnav.pc.in" | sed "s/@VERSION@/$pcver/; s/@LIBDIR@/$libdir/" >>spnav.pc #done echo '' echo 'Done. You can now type make (or gmake) to compile libspnav.' echo '' libspnav-1.2/doc/000077500000000000000000000000001477212760500137525ustar00rootroot00000000000000libspnav-1.2/doc/Makefile000066400000000000000000000001761477212760500154160ustar00rootroot00000000000000.PHONY: all all: manual.html %.html: %.md man_head.html cat man_head.html >$@ markdown $< >>$@ echo '' >>$@ libspnav-1.2/doc/man_head.html000066400000000000000000000001761477212760500164000ustar00rootroot00000000000000 libspnav manual libspnav-1.2/doc/manual.css000066400000000000000000000024021477212760500157370ustar00rootroot00000000000000body { background-color: #222; color: #ddd; font-family: "Palatino", Georgia, serif; max-width: 56em; padding: 1em; margin: 0 auto; font-size: 110%; } p { text-align: justify; } h1, h2 { font-family: sans-serif; /*text-shadow: 3px 3px 3px black;*/ } /*img { display: block; margin: 0 auto; max-width: 100%; height: auto; }*/ img.fullsize { max-width: initial; } img.inline { display: inline; } pre, code { background-color: #191919; color: #edb; font-size: 0.95em; font-family: noto mono, droid mono, monaco, consolas, fixed; } tt { color: #fff; font-size: 1.1em; } code .comment { color: #9db; } code .keyword { color: #d93; } code .literal.string { color: #d88; } a:link { color: #8af; } a:visited { color: #88f; } a:hover { color: #f8f; } a:active { color: #faf; } h1 { color: #db9; } h2 { color: #9bd; } h3 { color: #79b; } h4 { color: #bd9; } .center { text-align: center; } blockquote { border-radius: 1em; padding-left: 1em; padding-right: 1em; padding-top: 0.1em; padding-bottom: 0.1em; color: #aaa; background: #111; font-style: italic; } tr:nth-child(even) { background: #191919 } tr:nth-child(odd) { background: #222 } td { font-family: sans-serif; padding: 0.25em; } table { margin: 0 auto; } table.left { margin: 0 0; } libspnav-1.2/doc/manual.md000066400000000000000000000634001477212760500155540ustar00rootroot00000000000000libspnav manual =============== Table of contents ----------------- - [About libspnav](#about-libspnav) - [Quick start](#quick-start) - [Libspnav API](#libspnav-api) - [Initialization and cleanup](#initialization-and-cleanup) - [Application requests](#application-requests) - [Input events](#input-events) - [Device information](#device-information) - [Utility functions](#utility-functions) - [Configuration management](#configuration-management) - [Magellan API](#magellan-api) About libspnav -------------- Libspnav is a C library for receiving input from 6 degrees-of-freedom (6dof) input devices, also known as spacemice, spaceballs, etc. 6dof input is very useful for fluidly manipulating objects or viewpoints in 3D space, and fundamendally comprises of relative movement (translation) across 3 axes (TX/TY/TZ), and rotation about 3 axes (RX/RY/RZ). Libspnav is a counterpart to the free spacenav driver (spacenavd), which runs as a system daemon, and handles all the low level interfacing with the actual devices. However, it can also communicate with the proprietary 3Dconnexion driver (3dxsrv), with reduced functionality. There are two modes of operation supported by libspnav: 1. It can use the native spacenav protocol, which works over UNIX domain sockets, supports the whole feature set exposed by libspnav, but is only compatible with spacenavd. This is used when `spnav_open` is called to connect to the driver. 2. It can use the X11 magellan protocol, based on X `ClientMessage` events, which is compatible with both spacenavd and the proprietary 3dxsrv, but only supports a limited subset of features. Basically just receiving input events and changing sensitivity, no queries, device information, or configuration management. This is used when `spnav_x11_open` is called to connect to the driver. Since the 3Dconnexion driver is practically unmaintained for the last decade, and never really worked very well to begin with (which was what led to the development of spacenavd), and also since the magellan protocol introduces an unnecessary dependency to the X window system, and provides very rudimentary functionality, new applications are encouraged to use `spnav_open`. Also, libspnav provides a magellan API wrapper on top of the libspnav API, which allows existing applications written for the official SDK to be transparently recompiled against libspnav and be free of the 3Dconnexion licensing conditions. Quick start ----------- Libspnav, as of version 1.0 has been expanded, but really most applications will only ever need to call a handful of functions to receive input events. A good starting point is to read the heavily commented `spnav.h` header file, and the simple example programs which come with libspnav, but this section will cover the basics which most applications will need. To use libspnav you need to include the header file `` and link with `-lspnav`. The first thing to do, is to connect to the driver, which is done by calling `spnav_open`: if(spnav_open() == -1) { /* failed to connect to spacenavd */ } When it's time to stop talking to spacenavd, call `spnav_close` to close the connection. After connecting you can go straight to your event loop, or you can query some information about the device used by spacenavd: char buf[256]; if(spnav_dev_name(buf, sizeof buf) != -1) { printf("Device: %s\n", buf); } num_buttons = spnav_dev_buttons(); If you only want to receive input from libspnav, or if you have a dedicated thread for it, you can simply wait for events like so: spnav_event sev; while(spnav_wait_event(&sev)) { switch(sev.type) { case SPNAV_EVENT_MOTION: /* translation in sev.motion.x, sev.motion.y, sev.motion.z. * rotation in sev.motion.rx, sev.motion.ry, sev.motion.rz. */ break; case SPNAV_EVENT_BUTTON: /* 0-based button number in sev.button.bnum. * button state in sev.button.press (non-zero means pressed). */ break; } } > Note that there is a set of helper utility functions provided by libspnav, for > accumulating motion inputs into a position vector and an orientation quaternion, > and for extracting a matrix from them suitable for manipulating 3D objects, or > for manipulating the view to fly through a 3D scene. See the "cube" and "fly" > example programs. Alternatively an application can check for new events without blocking, by calling `spnav_poll_event`. It's also worth noting that both functions work exactly the same regardless of which protocol is in use. Applications which need to integrate libspnav input to their main event loop, can call `spnav_fd()` to retrieve the file descriptor corresponding to the communication socket. This can be used in a regular `select` loop: fd_set rdset; int sock, count; spnav_event sev; FD_ZERO(&rdset); FD_SET(sock, &rdset); /* ... other file descriptors ... */ if(select(maxfd + 1, &rdset, 0, 0, 0) <= 0) { return; } if(FD_ISSET(sock, &rdset)) { while(spnav_poll_event(&sev)) { /* ... handle spacenav event ... */ } } > The "fly" example program also demonstrates how to integrate libspnav events > in a select loop, which is also handling X events coming from the Xlib socket. The level of functionality described in this section (minus the device queries), can be achieved and works exactly the same with both protocols. The only difference is in calling `spnav_open` for the native protocol, or calling `spnav_x11_open` and passing your `Window` id for the magellan X11 protocol. To see the differences clearly, check out the "simple" example program, and look for the conditionally compiled blocks of code marked with `#if defined(BUILD_AF_UNIX)` and `#if defined(BUILD_X11)`. Libspnav API ------------ This section will describe all the functions and structures in the libspnav API in detail. ### Initialization and cleanup #### spnav\_open Function prototype: `int spnav_open(void)` Open connection to the driver via the native spacenav protocol (`AF_UNIX` socket). The native protocol is not compatible with the proprietary `3dxsrv` driver, but provides the maximum level of functionality, and does not depend on X11 to function. If you wish to remain compatible with `3dxsrv` with a reduced feature set, use `spnav_x11_open` instead. Returns: 0 on success, -1 on failure. #### spnav\_close Function prototype: `int spnav_close(void)` Close the connection to the driver. No further events will be received. Returns: 0 on success, -1 on failure. #### spnav\_fd Function prototype: `int spnav_fd(void)` Returns the file descriptor of the socket used to communicate with the driver. It can be used to integrate libspnav events in a standard select loop. If the native protocol is in use, the spacenavd socket is returned. If the X11 magellan protocol is in use, the socket used to communicate with the X server is returned. In either case the file descriptor returned by `spnav_fd` can be used to wait for events. If no connection has been established, or the connection has been closed, -1 is returned. Returns: file descriptor on success, -1 on failure. #### spnav\_x11\_open Function prototype: `int spnav_x11_open(Display *dpy, Window win)` Open a connection to the driver via the X11 magellan protocol. Events are delivered to the window registered by this call. More windows can be registered by calling `spnav_x11_window`. Returns: 0 on success, -1 on failure. #### spnav\_protocol Function prototype: `int spnav_protocol(void)` Returns the spacenav protocol version used in the currently established connection. If there is no established connection, or if the connection is using the X11 magellan protocol, -1 is returned. Returns: protocol version on success, -1 on failure. ### Application requests #### spnav\_client\_name Function prototype: `int spnav_client_name(const char *name)` This is an optional request, which can be used to set the application name to spacenavd. Currently it's only used for logging. Only works over the spacenav protocol v1 or later. Returns: 0 on success, -1 on failure. #### spnav\_evmask Function prototype: `int spnav_evmask(unsigned int mask)` Select the types of events the application is interested in receiving. Available options, which can be or-ed together, are: - `SPNAV_EVMASK_MOTION`: 6dof motion events (enabled by defualt). - `SPNAV_EVMASK_BUTTON`: button press/release events (enabled by default). - `SPNAV_EVMASK_DEV`: device change events (enabled by default for protocol v1 clients). - `SPNAV_EVMASK_CFG`: configuration change events. - `SPNAV_EVMASK_RAWAXIS`: raw device axis events (not useful for most apps). - `SPNAV_EVMASK_RAWBUTTON`: raw device button events (not useful for most apps). Event mask selection only works over the spacenav protocol v1 or later. Returns: 0 on success, -1 on failure. #### spnav\_sensitivity Function prototype: `int spnav_sensitivity(double sens)` Sets the client sensitivity, which is used to scale all reported motion values to this application. Returns: 0 on success, -1 on failure. #### spnav\_x11\_window Function prototype: `int spnav_x11_window(Window win)` For applications using the X11 magellan protocol (`spnav_x11_open`). Register a window which will receive events by the driver. Returns: 0 on success, -1 on failure. ### Input events #### spnav\_wait\_event Function prototype: `int spnav_wait_event(spnav_event *ev)` Blocks waiting for spnav events. When an event is pending, it writes the event through the `ev` pointer and returns a non-zero value. `spnav_event` is defined as a union of structures, of all possible event types: typedef union spnav_event { int type; struct spnav_event_motion motion; struct spnav_event_button button; struct spnav_event_dev dev; struct spnav_event_cfg cfg; struct spnav_event_axis axis; } spnav_event; See the heavily commented `spnav.h` file for details of all the different event structures. Returns: non-zero when it successfully received an event, 0 on failure. #### spnav\_poll\_event Function prototype: `int spnav_poll_event(spnav_event *ev)` Checks if there is a pending spnav event without blocking. If there are any pending events, it removes one from the queue, writes it through the `ev` pointer, and returns the event type. If there is no event pending, it returns 0. See `spnav_wait_event` above for the definition of `spnav_event`. Returns: event type if an event is pending, 0 if there are no available events. #### spnav\_remove\_events Function prototype: `int spnav_remove_events(int type)` Drops any pending events of the specified type, or all pending events if `SPNAV_EVENT_ANY` is passed. Returns: number of events removed from the queue. #### spnav\_x11\_event Function prototype: `int spnav_x11_event(const XEvent *xev, spnav_event *sev)` For applications using the X11 magellan protocol (`spnav_x11_open`). Examines an X11 event to determine if it's an spnav event or not. Returns: event type (`SPNAV_EVENT_MOTION`/`SPNAV_EVENT_BUTTON`) if it is an spnav event, or 0 if it's not an spnav event. ### Device information The functions in this section only work over the native spacenav protocol v1 or later, and will fail if the connection is using the X11 magellan protocol, or the spacenav protocol v0 (spacenavd <= 0.8). #### spnav\_dev\_name Function prototype: `int spnav_dev_name(char *buf, int bufsz)` Retrieve a descriptive name for the currently active device used by spacenavd. The name is written to the buffer passed as the first argument. No more than `bufsz` bytes are written, including the zero terminator. The total length of the complete device name is returned, even if it was truncated to fit in the provided buffer. So to make sure you get the complete name you can use the following code: int len = spnav_dev_name(0, 0); if(len > 0) { char *buf = malloc(len + 1); spnav_dev_name(buf, len + 1); } Returns: length of the device name, or -1 on failure. #### spnav\_dev\_path Function protoype: `int spnav_dev_path(char *buf, int bufsz)` Retrieve the path of the device file used by spacenavd to interface with the actual device. See `spnav_dev_name` for a description of how retrieving strings work in libspnav. Returns: length of the path string, or -1 on failure. #### spnav\_dev\_buttons Function prototype: `int spnav_dev_buttons(void)` Returns the number of physical buttons present on the currently used device. If the query fails (due to incorrect protocol), the default value of 2 buttons is returned. Returns: number of device buttons, or 2 (default) on failure. #### spnav\_dev\_axes Function prototype: `int spnav_dev_axes(void)` Returns the number of physical device axes on the currently used device. If the query fails (due to incorrect protocol), the default value of 6 axes is returned. Returns: number of device axes, or 6 (default) on failure. #### spnav\_dev\_usbid Function prototype: `int spnav_dev_usbid(unsigned int *vendor, unsigned int *product)` If the currently used device is a USB device, its USB vendor:product id is written through the `vendor` and `product` pointers. Fails for non-USB devices, of if no device is detected. Returns: 0 on success, -1 on failure. #### spnav\_dev\_type Function prototype: `int spnav_dev_type(void)` Returns the device type (see `SPNAV_DEV_*` enumeration in `spnav.h`) for the currently used device. If the device is not known, `SPNAV_DEV_UNKNOWN` is returned. Returns: device type on success, -1 on failure. ### Utility functions A number of helper utility functions for common tasks needed by most 3D programs using 6dof input. #### spnav\_posrot\_init Function prototype: `void spnav_posrot_init(struct spnav_posrot *pr)` The `spnav_posrot` structure is used by a set of helper functions to accumulate motion input from spnav motion events, and convert the result into a matrix. It contains a position vector, and a rotation quaternion: struct spnav_posrot { float pos[3]; /* position vector (x, y, z) */ float rot[4]; /* orientation quaternion (x, y, z, w) w:real xyz:imaginary */ }; This function initializes the vector to 0, and the quaternion to an identity unit quaternion (1 + 0i + 0j + 0k), ready to start accumulating motion inputs. #### spnav\_posrot\_moveobj Function prototype: `void spnav_posrot_moveobj(struct spnav_posrot *pr, struct spnav_event_motion *ev)` Apply position and rotation inputs from an spnav motion event, in a way suitable for manipulating a 3D object. Use in conjunction with `spnav_matrix_obj` to later extract a model matrix from the accumulated position/rotation. #### spnav\_posrot\_moveview Function prototype: `void spnav_posrot_moveview(struct spnav_posrot *pr, struct spnav_event_motion *ev)` Apply position and rotation inputs from an spnav motion event, in a way suitable for manipulating the view for flying through a 3D scene. Use in conjunction with `spnav_matrix_view` to later extract a view matrix from the accumulated position/rotation. #### spnav\_matrix\_obj Function prototype: `void spnav_matrix_obj(float *mat, struct spnav_posrot *pr)` Construct a 4x4 homogeneous transformation matrix from the `spnav_posrot` structure, suitable for use as a model/world matrix to position and orient a 3D object. Use in conjunction with `spnav_posrot_moveobj` to accumulate motion inputs. The first argument is a pointer to an array of 16 floats, where the matrix is written. The matrix is in the order expected by OpenGL. #### spnav\_matrix\_view Function prototype: `void spnav_matrix_view(float *mat, struct spnav_posrot *pr)` Construct a 4x4 homogeneous transformation matrix from the `spnav_posrot` structure, suitable for use as a view matrix for 6dof-controllef flight in 3D space. Use in conjunction with `spnav_posrot_moveview` to accumulate motion inputs. The first argument is a pointer to an array of 16 floats, where the matrix is written. The matrix is in the order expected by OpenGL. ### Configuration management The configuration API is of no use to regular libspnav applications. It's only intended for writing configuration management tools like the `spnavcfg` program. It is useful to facilitate writing better configuration management programs, which is why these calls are part of libspnav, but it would be quite rude towards the user for most 3D applications to modify spacenavd settings through this interface. Please think twice before calling any of the `spnav_cfg_*` functions. The configuration functions change the active settings in the currently running spacenavd. To make the changes persistent, they should be written back to the spacenavd configuration file, which is done by calling `spnav_cfg_save`. Finally these functions, similarly to the device queries above, only work over the native spacenav protocol v1 or later. #### spnav\_cfg\_reset Function prototype: `int spnav_cfg_reset(void)` Reset all settings to their default values. Returns: 0 on success, -1 on failure. #### spnav\_cfg\_restore Function prototype: `int spnav_cfg_restore(void)` Re-read the spacenavd configuration file, and revert all settings to their values defined there. Returns: 0 on success, -1 on failure. #### spnav\_cfg\_save Function prototype: `int spnav_cfg_save(void)` Save all the current settings to the spacenavd configuration file. This is required to make any changes perists across spacenavd restarts. Returns: 0 on success, -1 on failure. #### spnav\_cfg\_set\_sens Function prototype: `int spnav_cfg_set_sens(float s)` Set the global sensitivity for all motion axes (default: 1.0). Returns: 0 on success, -1 on failure. #### spnav\_cfg\_get\_sens Function prototype: `float spnav_cfg_get_sens(void)` Get the global sensitivity for all motion axes. Returns: sensitivity on success, < 0.0 on failure #### spnav\_cfg\_set\_axis\_sens Function prototype: `int spnav_cfg_set_axis_sens(const float *svec)` Set the per-axis sensitivity for all 6 input axes (TX, TY, TZ, RX, RY, RZ). The function expects an array of 6 floats. The values are multiplied, so 1.0 means default sensitivity, values higher than 1 increase sensitivity, while values less than 1 decrease it. Returns: 0 on success, -1 on failure. #### spnav\_cfg\_get\_axis\_sens Function prototype: `int spnav_cfg_get_axis_sens(float *svec)` Expects an array of 6 floats, and writes the current per-axis sensitivity values for all 6 input axes there. Returns: 0 on success, -1 on failure. #### spnav\_cfg\_set\_invert Function prototype: `int spnav_cfg_set_invert(int invbits)` Expects a bitmask where bits 0 to 5 define which of the 6 input axes to invert. Bit 0 corresponds to TX, bit 1 to TY, and so on. Returns: 0 on success, -1 on failure. #### spnav\_cfg\_get\_invert Function prototype: `int spnav_cfg_get_invert(void)` Returns a bitmask where bits 0 to 5 define which of the 6 input axes are inverted. Bit 0 corresponds to TX, bit 1 to TY and so on. Returns: invert bitmaks on success, -1 on failure. #### spnav\_cfg\_set\_deadzone Function prototype: `int spnav_cfg_set_deadzone(int devaxis, int delta)` Sets the deadzone threshold (under which motion is considered to be noise and discarded) for a specific *device axis*. Device axis numbers range from 0 to the value returned by `spnav_dev_axes`-1, and are not affected by remapping. Returns: 0 on success, -1 on failure. #### spnav\_cfg\_get\_deadzone Function prototype: `int spnav_cfg_get_deadzone(int devaxis)` Returns the deadzone threshold for a specific *device axis*. Device axis numbers range from 0 to the value returned by `spnav_dev_axes`-1, and are not affected by remapping. Returns: deadzone on success, -1 on failure. #### spnav\_cfg\_set\_axismap Function prototype: `int spnav_cfg_set_axismap(int devaxis, int map)` Maps a *device axis* to an input axis. Both values are zero-based, device axes range from 0 to the value returned by `spnav_dev_axes`-1, while input axes range from 0 to 5 and correspond to the sequence: TX, TY, TZ, RX, RY, RZ. To unmap an axis, you can map it to -1. Returns: 0 on success, -1 on failure. #### spnav\_cfg\_get\_axismap Function prototype: `int spnav_cfg_get_axismap(int devaxis)` Returns the input axis mapping for the *device axis* `devaxis`. See `spnav_cfg_set_axismap` for a description of axis numbers. Returns: axis mapping on success, -1 on failure or if the axis in not mapped. #### spnav\_cfg\_set\_bnmap Function prototype: `int spnav_cfg_set_bnmap(int devbn, int map)` Maps a *device button* to an input button. Both values range from 0 to the value returned by `spnav_dev_buttons`-1. To unmap a button you can map it to -1. Action mappings (`spnav_cfg_set_kbmap`) or keyboard mappings (`spnav_cfg_set_bnaction`) for a given device button, override the regular button mapping. Returns: 0 on success, -1 on failure. #### spnav\_cfg\_get\_bnmap Function prototype: `int spnav_cfg_get_bnmap(int devbn)` Returns the input button mapped to the device button `devbn`. See `spnav_cfg_set_bnmap` for a description of button numbers. Returns: button mapping on success, -1 on failure or if unmapped. #### spnav\_cfg\_set\_bnaction Function prototype: `int spnav_cfg_set_bnaction(int devbn, int act)` Maps a *device button* to an action. Device button numbers range from 0 to the value returned by `spnav_dev_buttons`-1. Actions are one of the following: - `SPNAV_BNACT_NONE`: No action mapped, use as regular button (default). - `SPNAV_BNACT_SENS_RESET`: reset sensitivity to 1. - `SPNAV_BNACT_SENS_INC`: increase sensitivity. - `SPNAV_BNACT_SENS_DEC`: decrease sensitivity. - `SPNAV_BNACT_DISABLE_ROT`: disable rotation while held down. - `SPNAV_BNACT_DISABLE_TRANS`: disable translation while held down. Returns: 0 on success, -1 on failure. #### spnav\_cfg\_get\_bnaction Function prototype: `int spnav_cfg_get_bnaction(int devbn)` Returns the action mapped to the device button `devbn`. See `spnav_cfg_set_bnaction` for details. Returns: action mapping on success, -1 on failure. #### spnav\_cfg\_set\_kbmap Function prototype: `int spnav_cfg_set_kbmap(int devbn, int key)` Maps a *device button* to a keyboard key. Device button numbers range from 0 to the value returned by `spnav_dev_buttons`-1. Key values are X11 `KeySym`s defined in `X11/keysymdef.h`. You can use `XStringToKeysym` and `XKeysymToString` to convert between a key name and a keysym if necessary. Keyboard mappings only work spacenavd is compiled with X11 support (which is the default), and will be unreliable if spacenavd is compiled without XTEST support. Returns: 0 on success, -1 on failure. #### spnav\_cfg\_get\_kbmap Function prototype: `int spnav_cfg_get_kbmap(int devbn)` Returns the X11 keysym mapped to the device button `devbn`. See `spnav_cfg_set_kbmap` for details. Returns: keysym mapping on success, -1 on failure. #### spnav\_cfg\_set\_swapyz Function prototype: `int spnav_cfg_set_swapyz(int swap)` Enable or disable swapping of the translation and rotation Y and Z axes. Returns: 0 on success, -1 on failure. #### spnav\_cfg\_get\_swapyz Function prototype: `int spnav_cfg_get_swapyz(void)` Returns: 0 if the Y-Z axes are not swapped, 1 if they are, -1 on failure. #### spnav\_cfg\_set\_led Function prototype: `int spnav_cfg_set_led(int mode)` Sets the LED mode for devices with LEDs around the puck. The mode value can be: - `SPNAV_CFG_LED_OFF` - `SPNAV_CFG_LED_ON` - `SPNAV_CFG_LED_AUTO` In "auto" mode, the LED is off by default, and turns on when one or more clients are connected to spacenavd. Returns: 0 on success, -1 on failure. #### spnav\_cfg\_get\_led Function prototype: `int spnav_cfg_get_led(void)` Returns the LED mode for devices with LEDs around the puck. See `spnav_cfg_set_led` for the possible modes. Returns: LED mode on success, -1 on failure. #### spnav\_cfg\_set\_grab Function prototype: `int spnav_cfg_set_grab(int grab)` Enable or disable exclusive device access. When grab is enabled, no other program can use the device. This is useful for stopping the X server for trying to use it as a regular mouse, but will interfere with programs which attempt to use 6dof devices directly, like google earth. Pass non-zero to enable the exclusive access grab setting, or 0 to disable it. Returns: 0 on success, -1 on failure. #### spnav\_cfg\_get\_grab Function prototype: `int spnav_cfg_get_grab(void)` Returns the exclusive device access grab setting. See `spnav_cfg_set_grab` for details. Returns: exclusive device grab setting on success, -1 on failure. #### spnav\_cfg\_set\_serial Function prototype: `int spnav_cfg_set_serial(const char *devpath)` Set the device file path to use for opening serial 6dof devices. Serial devices cannot be detected automatically, the user needs to provide the device path explicitly. To not attempt to open a serial device, pass an empty string. Note: for security reasons, the path set through this setting must correspond to a TTY character device, otherwise it is ignored. Returns: 0 on success, -1 on failure. #### spnav\_cfg\_get\_serial Function prototype: `int spnav_cfg_get_serial(char *buf, int bufsz)` Retrieve the currently configured serial device path. See `spnav_dev_name` for a description of how to use string query functions like this. Returns: serial device path length on success, -1 on failure. Magellan API ------------ No detailed documentation is currently available for the Magellan API wrapper. It is however rather simple, so go ahead and browse through the `spnav_magellan.h` header file if you want to know more. New programs are discouraged from using the Magellan API wrapper. Everything that can be done with it, can also be done with the regular libspnav API; it is after all just as thin compatibility layer on top of the libspnav API. It is **not** necessary to use the Magellan API to achieve compatibility with the proprietary 3Dconnexion driver; using the libspnav API with `spnav_x11_open` makes your program compatible with it. libspnav-1.2/examples/000077500000000000000000000000001477212760500150235ustar00rootroot00000000000000libspnav-1.2/examples/cube/000077500000000000000000000000001477212760500157415ustar00rootroot00000000000000libspnav-1.2/examples/cube/Makefile000066400000000000000000000005701477212760500174030ustar00rootroot00000000000000obj = cube.o bin = cube incdir = -I../.. -I../../src -I/usr/local/include -I/usr/X11R6/include \ -I/opt/homebrew/include libdir = -L../.. -L/usr/local/lib -L/usr/X11R6/lib -L/opt/homebrew/lib CFLAGS = -pedantic -Wall -g $(incdir) LDFLAGS = $(libdir) -lGL -lGLU -lspnav -lX11 -lm $(bin): $(obj) $(CC) -o $@ $(obj) $(LDFLAGS) .PHONY: clean clean: rm -f $(obj) $(bin) libspnav-1.2/examples/cube/cube.c000066400000000000000000000154401477212760500170270ustar00rootroot00000000000000/* This example demonstrates how to use libspnav to get 6dof input, * and use that to rotate and translate a 3D cube. The magellan X11 protocol is * used (spnav_x11_open) which is compatible with both spacenavd and * 3Dconnexion's 3dxsrv. * * The code is a bit cluttered with X11 and GLX calls, so the interesting bits * are marked with XXX comments. */ #include #include #include #include #include #include #include #include #define SQ(x) ((x) * (x)) int create_gfx(int xsz, int ysz); void destroy_gfx(void); void set_window_title(const char *title); void redraw(void); void draw_cube(void); int handle_event(XEvent *xev); Display *dpy; Atom wm_prot, wm_del_win; GLXContext ctx; Window win; /* XXX: posrot contains a position vector and an orientation quaternion, and * can be used with the spnav_posrot_moveobj function to accumulate input * motions, and then with spnav_matrix_obj to create a transformation matrix. * See util.c in the libspnav source code for implementation details. */ struct spnav_posrot posrot; int redisplay; int main(void) { if(!(dpy = XOpenDisplay(0))) { fprintf(stderr, "failed to connect to the X server"); return 1; } if(create_gfx(512, 512) == -1) { return 1; } /* XXX: spnav_x11_open registers our window with the driver for receiving * motion/button events through the 3dxsrv-compatible X11 magellan protocol. */ if(spnav_x11_open(dpy, win) == -1) { fprintf(stderr, "failed to connect to the space navigator daemon\n"); return 1; } /* XXX: initialize the position vector & orientation quaternion */ spnav_posrot_init(&posrot); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); for(;;) { XEvent xev; XNextEvent(dpy, &xev); if(handle_event(&xev) != 0) { destroy_gfx(); XCloseDisplay(dpy); return 0; } if(redisplay) { redraw(); redisplay = 0; } } return 0; } int create_gfx(int xsz, int ysz) { int scr; Window root; XVisualInfo *vis; XSetWindowAttributes xattr; unsigned int events; XClassHint class_hint; int attr[] = { GLX_RGBA, GLX_DOUBLEBUFFER, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1, GLX_DEPTH_SIZE, 16, None }; wm_prot = XInternAtom(dpy, "WM_PROTOCOLS", False); wm_del_win = XInternAtom(dpy, "WM_DELETE_WINDOW", False); scr = DefaultScreen(dpy); root = RootWindow(dpy, scr); if(!(vis = glXChooseVisual(dpy, scr, attr))) { fprintf(stderr, "requested GLX visual is not available\n"); return -1; } if(!(ctx = glXCreateContext(dpy, vis, 0, True))) { fprintf(stderr, "failed to create GLX context\n"); XFree(vis); return -1; } xattr.background_pixel = xattr.border_pixel = BlackPixel(dpy, scr); xattr.colormap = XCreateColormap(dpy, root, vis->visual, AllocNone); if(!(win = XCreateWindow(dpy, root, 0, 0, xsz, ysz, 0, vis->depth, InputOutput, vis->visual, CWColormap | CWBackPixel | CWBorderPixel, &xattr))) { fprintf(stderr, "failed to create X window\n"); return -1; } XFree(vis); /* set the window event mask */ events = ExposureMask | StructureNotifyMask | KeyPressMask | KeyReleaseMask | ButtonReleaseMask | ButtonPressMask | PointerMotionMask; XSelectInput(dpy, win, events); XSetWMProtocols(dpy, win, &wm_del_win, 1); set_window_title("libspnav cube"); class_hint.res_name = "cube"; class_hint.res_class = "cube"; XSetClassHint(dpy, win, &class_hint); if(glXMakeCurrent(dpy, win, ctx) == False) { fprintf(stderr, "glXMakeCurrent failed\n"); glXDestroyContext(dpy, ctx); XDestroyWindow(dpy, win); return -1; } XMapWindow(dpy, win); XFlush(dpy); return 0; } void destroy_gfx(void) { glXDestroyContext(dpy, ctx); XDestroyWindow(dpy, win); glXMakeCurrent(dpy, None, 0); } void set_window_title(const char *title) { XTextProperty wm_name; XStringListToTextProperty((char**)&title, 1, &wm_name); XSetWMName(dpy, win, &wm_name); XSetWMIconName(dpy, win, &wm_name); XFree(wm_name.value); } void redraw(void) { float xform[16]; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(0, 0, -6); /* view matrix, push back to see the cube */ /* XXX convert the accumulated position/rotation into a 4x4 model matrix */ spnav_matrix_obj(xform, &posrot); glMultMatrixf(xform); /* concatenate our computed model matrix */ draw_cube(); glXSwapBuffers(dpy, win); } void draw_cube(void) { glBegin(GL_QUADS); /* face +Z */ glNormal3f(0, 0, 1); glColor3f(1, 0, 0); glVertex3f(-1, -1, 1); glVertex3f(1, -1, 1); glVertex3f(1, 1, 1); glVertex3f(-1, 1, 1); /* face +X */ glNormal3f(1, 0, 0); glColor3f(0, 1, 0); glVertex3f(1, -1, 1); glVertex3f(1, -1, -1); glVertex3f(1, 1, -1); glVertex3f(1, 1, 1); /* face -Z */ glNormal3f(0, 0, -1); glColor3f(0, 0, 1); glVertex3f(1, -1, -1); glVertex3f(-1, -1, -1); glVertex3f(-1, 1, -1); glVertex3f(1, 1, -1); /* face -X */ glNormal3f(-1, 0, 0); glColor3f(1, 1, 0); glVertex3f(-1, -1, -1); glVertex3f(-1, -1, 1); glVertex3f(-1, 1, 1); glVertex3f(-1, 1, -1); /* face +Y */ glNormal3f(0, 1, 0); glColor3f(0, 1, 1); glVertex3f(-1, 1, 1); glVertex3f(1, 1, 1); glVertex3f(1, 1, -1); glVertex3f(-1, 1, -1); /* face -Y */ glNormal3f(0, -1, 0); glColor3f(1, 0, 1); glVertex3f(-1, -1, -1); glVertex3f(1, -1, -1); glVertex3f(1, -1, 1); glVertex3f(-1, -1, 1); glEnd(); } int handle_event(XEvent *xev) { static int win_mapped; KeySym sym; spnav_event spev; switch(xev->type) { case MapNotify: win_mapped = 1; break; case UnmapNotify: win_mapped = 0; break; case Expose: if(win_mapped && xev->xexpose.count == 0) { redraw(); } break; case ClientMessage: /* XXX check if the event is a spacenav event */ if(spnav_x11_event(xev, &spev)) { /* if so deal with motion and button events */ if(spev.type == SPNAV_EVENT_MOTION) { /* XXX use the spnav_posrot_moveobj utility function to * accumulate motion inputs into the cube's position vector and * orientation quaternion. */ spnav_posrot_moveobj(&posrot, &spev.motion); redisplay = 1; } else { /* XXX on button press, reset the cube position/orientation */ if(spev.button.press) { spnav_posrot_init(&posrot); redisplay = 1; } } /* finally remove any other queued motion events */ spnav_remove_events(SPNAV_EVENT_MOTION); } else if(xev->xclient.message_type == wm_prot) { if(xev->xclient.data.l[0] == wm_del_win) { return 1; } } break; case KeyPress: sym = XLookupKeysym((XKeyEvent*)&xev->xkey, 0); if((sym & 0xff) == 27) { return 1; } break; case ConfigureNotify: { int x = xev->xconfigure.width; int y = xev->xconfigure.height; glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45.0, (float)x / (float)y, 1.0, 1000.0); glViewport(0, 0, x, y); } break; default: break; } return 0; } libspnav-1.2/examples/fly/000077500000000000000000000000001477212760500156155ustar00rootroot00000000000000libspnav-1.2/examples/fly/Makefile000066400000000000000000000005761477212760500172650ustar00rootroot00000000000000obj = fly.o xwin.o bin = fly incdir = -I../.. -I../../src -I/usr/local/include -I/usr/X11R6/include \ -I/opt/homebrew/include libdir = -L../.. -L/usr/local/lib -L/usr/X11R6/lib -L/opt/homebrew/lib CFLAGS = -pedantic -Wall -O3 $(incdir) LDFLAGS = $(libdir) -lGL -lGLU -lspnav -lX11 -lm $(bin): $(obj) $(CC) -o $@ $(obj) $(LDFLAGS) .PHONY: clean clean: rm -f $(obj) $(bin) libspnav-1.2/examples/fly/fly.c000066400000000000000000000232171477212760500165600ustar00rootroot00000000000000/* This example demonstrates how to use 6-dof input for controlling the camera * to fly around a scene. The native spacenav protocol is used, to also * demonstrate how to integrate input from libspnav in an event loop. */ #include #include #include #include #include #include #include #include #include #include #include #include "xwin.h" #define GRID_REP 60 #define GRID_SZ 200 #define GRID_RES 7 void gen_textures(void); void gen_scene(void); void redraw(void); void draw_scene(void); void handle_spnav_event(spnav_event *ev); int handle_xevent(XEvent *xev); void draw_box(float xsz, float ysz, float zsz); /* XXX: posrot contains a position vector and an orientation quaternion, and * can be used with the spnav_posrot_moveview function to accumulate input * motions, and then with spnav_matrix_view to create a view matrix. * See util.c in the libspnav source code for implementation details. */ struct spnav_posrot posrot; unsigned int grid_tex, box_tex; unsigned int scene, skydome; float apex_color[] = {0.07, 0.1, 0.4, 1.0f}; float horiz_color[] = {0.5, 0.2, 0.05, 1.0f}; int main(void) { int xsock, ssock, maxfd; if(!(dpy = XOpenDisplay(0))) { fprintf(stderr, "failed to connect to the X server"); return 1; } if(create_xwin("libspnav fly", 1024, 768) == -1) { return 1; } /* XXX: open connection to the spacenav driver */ if(spnav_open() == -1) { fprintf(stderr, "failed to connect to the spacenav driver\n"); return 1; } /* XXX: initialize the position vector & orientation quaternion */ spnav_posrot_init(&posrot); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); glFogi(GL_FOG_MODE, GL_LINEAR); glFogf(GL_FOG_START, GRID_SZ / 4.0f); glFogf(GL_FOG_END, GRID_SZ); glFogfv(GL_FOG_COLOR, horiz_color); gen_textures(); gen_scene(); /* XXX: grab the Xlib socket and the libspnav socket. we'll need them in the * select loop to wait for input from either source. */ xsock = ConnectionNumber(dpy); /* Xlib socket */ ssock = spnav_fd(); /* libspnav socket */ maxfd = xsock > ssock ? xsock : ssock; for(;;) { fd_set rdset; /* XXX: add both sockets to the file descriptor set, to monitor both */ FD_ZERO(&rdset); FD_SET(xsock, &rdset); FD_SET(ssock, &rdset); while(select(maxfd + 1, &rdset, 0, 0, 0) == -1 && errno == EINTR); /* XXX: handle any pending X events */ if(FD_ISSET(xsock, &rdset)) { while(XPending(dpy)) { XEvent xev; XNextEvent(dpy, &xev); if(handle_xevent(&xev) != 0) { goto end; } } } /* XXX: handle any pending spacenav events */ if(FD_ISSET(ssock, &rdset)) { spnav_event sev; while(spnav_poll_event(&sev)) { handle_spnav_event(&sev); } } if(redisplay_pending) { redisplay_pending = 0; redraw(); } } end: glDeleteTextures(1, &grid_tex); destroy_xwin(); spnav_close(); return 0; } void gen_textures(void) { int i, j, r, g, b; static unsigned char pixels[128 * 128 * 3]; unsigned char *pptr; pptr = pixels; for(i=0; i<128; i++) { float dy = abs(i - 64) / 64.0f; for(j=0; j<128; j++) { float dx = abs(j - 64) / 64.0f; float d = dx > dy ? dx : dy; float val = pow(d * 1.04f, 10.0) * 1.4f; r = (int)(214.0f * val); g = (int)(76.0f * val); b = (int)(255.0f * val); *pptr++ = r > 255 ? 255 : r; *pptr++ = g > 255 ? 255 : g; *pptr++ = b > 255 ? 255 : b; } } glGenTextures(1, &grid_tex); glBindTexture(GL_TEXTURE_2D, grid_tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, 128, 128, GL_RGB, GL_UNSIGNED_BYTE, pixels); pptr = pixels; for(i=0; i<128; i++) { int row = i >> 5; float dy = ((i - 12) & 0x1f) / 16.0f; for(j=0; j<128; j++) { int col = j >> 5; float dx = ((j - 12) & 0x1f) / 16.0f; float d = dx > dy ? dx : dy; int xor = (col ^ row) & 0xf; r = d < 0.5f ? (xor << 4) + 20 : 0; g = d < 0.5f ? (xor << 4) + 20 : 0; b = d < 0.5f ? (xor << 4) + 20 : 0; *pptr++ = r > 255 ? 255 : r; *pptr++ = g > 255 ? 255 : g; *pptr++ = b > 255 ? 255 : b; } } glGenTextures(1, &box_tex); glBindTexture(GL_TEXTURE_2D, box_tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, 128, 128, GL_RGB, GL_UNSIGNED_BYTE, pixels); } void gen_scene(void) { int i, j, k; float x, y, h, u, v, du; srand(0); scene = glGenLists(1); glNewList(scene, GL_COMPILE); /* grid */ glBindTexture(GL_TEXTURE_2D, grid_tex); glBegin(GL_QUADS); glColor3f(1, 1, 1); du = 1.0f / (float)GRID_RES; for(i=0; i> 1); float tu = (u + (gc & 1) * du) * GRID_REP; float tv = (v + (gc >> 1) * du) * GRID_REP; x = ((u - 0.5f) + (gc & 1) * du) * GRID_SZ * 2.0f; y = ((v - 0.5f) + (gc >> 1) * du) * GRID_SZ * 2.0f; glTexCoord2f(tu, tv); glVertex3f(x, 0, -y); } } } glEnd(); /* buildings */ glBindTexture(GL_TEXTURE_2D, box_tex); for(i=0; i<8; i++) { for(j=0; j<8; j++) { x = (j - 4.0f + 0.5f * (float)rand() / RAND_MAX) * 20.0f; y = (i - 4.0f + 0.5f * (float)rand() / RAND_MAX) * 20.0f; h = (3.0f + (float)rand() / RAND_MAX) * 6.0f; glPushMatrix(); glTranslatef(x, h/2, y); glMatrixMode(GL_TEXTURE); glLoadIdentity(); glScalef(3, h/4, 1); draw_box(6, h, 6); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); } } glEndList(); skydome = glGenLists(1); glNewList(skydome, GL_COMPILE); /* skydome */ glBegin(GL_TRIANGLE_FAN); glColor3fv(apex_color); glVertex3f(0, GRID_SZ / 5.0f, 0); glColor3fv(horiz_color); glVertex3f(-GRID_SZ, 0, -GRID_SZ); glVertex3f(GRID_SZ, 0, -GRID_SZ); glVertex3f(GRID_SZ, 0, GRID_SZ); glVertex3f(-GRID_SZ, 0, GRID_SZ); glVertex3f(-GRID_SZ, 0, -GRID_SZ); glEnd(); glBegin(GL_TRIANGLE_FAN); glColor3fv(horiz_color); glVertex3f(0, -GRID_SZ / 5.0f, 0); glVertex3f(-GRID_SZ, 0, -GRID_SZ); glVertex3f(-GRID_SZ, 0, GRID_SZ); glVertex3f(GRID_SZ, 0, GRID_SZ); glVertex3f(GRID_SZ, 0, -GRID_SZ); glVertex3f(-GRID_SZ, 0, -GRID_SZ); glEnd(); glEndList(); } void redraw(void) { float xform[16]; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); /* XXX convert the accumulated position/rotation into a 4x4 view matrix */ spnav_matrix_view(xform, &posrot); glMultMatrixf(xform); /* concatenate our computed view matrix */ glTranslatef(0, -5, 0); /* move the default view a bit higher above the ground */ glPushMatrix(); xform[12] = xform[13] = xform[14] = 0.0f; glLoadMatrixf(xform); glTranslatef(0, -5, 0); glDisable(GL_DEPTH_TEST); glCallList(skydome); glEnable(GL_DEPTH_TEST); glPopMatrix(); glEnable(GL_TEXTURE_2D); glEnable(GL_FOG); glCallList(scene); glDisable(GL_TEXTURE_2D); glDisable(GL_FOG); glXSwapBuffers(dpy, win); } void reshape(int x, int y) { glViewport(0, 0, x, y); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(50.0, (float)x / (float)y, 0.5, 500.0); } void handle_spnav_event(spnav_event *ev) { switch(ev->type) { case SPNAV_EVENT_MOTION: /* XXX use the spnav_posrot_moveview utility function to modify the view * position vector and orientation quaternion, based on the motion input * we received. * * We'll also divide rotation values by two, to make rotation less * sensitive. In this scale, it feels better that way in fly mode. The * ideal ratio of sensitivities will vary depending on the scene scale. */ ev->motion.rx /= 2; ev->motion.ry /= 2; ev->motion.rz /= 2; spnav_posrot_moveview(&posrot, &ev->motion); /* XXX: Drop any further pending motion events. This can make our input * more responsive on slow or heavily loaded machines. We don't gain * anything by processing a whole queue of relative motions. */ spnav_remove_events(SPNAV_EVENT_MOTION); redisplay_pending = 1; break; case SPNAV_EVENT_BUTTON: /* XXX reset position and orientation to identity on button presses to * reset the view */ spnav_posrot_init(&posrot); redisplay_pending = 1; break; default: break; } } void draw_box(float xsz, float ysz, float zsz) { xsz /= 2; ysz /= 2; zsz /= 2; glBegin(GL_QUADS); /* face +Z */ glNormal3f(0, 0, 1); glTexCoord2f(0, 0); glVertex3f(-xsz, -ysz, zsz); glTexCoord2f(1, 0); glVertex3f(xsz, -ysz, zsz); glTexCoord2f(1, 1); glVertex3f(xsz, ysz, zsz); glTexCoord2f(0, 1); glVertex3f(-xsz, ysz, zsz); /* face +X */ glNormal3f(1, 0, 0); glTexCoord2f(0, 0); glVertex3f(xsz, -ysz, zsz); glTexCoord2f(1, 0); glVertex3f(xsz, -ysz, -zsz); glTexCoord2f(1, 1); glVertex3f(xsz, ysz, -zsz); glTexCoord2f(0, 1); glVertex3f(xsz, ysz, zsz); /* face -Z */ glNormal3f(0, 0, -1); glTexCoord2f(0, 0); glVertex3f(xsz, -ysz, -zsz); glTexCoord2f(1, 0); glVertex3f(-xsz, -ysz, -zsz); glTexCoord2f(1, 1); glVertex3f(-xsz, ysz, -zsz); glTexCoord2f(0, 1); glVertex3f(xsz, ysz, -zsz); /* face -X */ glNormal3f(-1, 0, 0); glTexCoord2f(0, 0); glVertex3f(-xsz, -ysz, -zsz); glTexCoord2f(1, 0); glVertex3f(-xsz, -ysz, zsz); glTexCoord2f(1, 1); glVertex3f(-xsz, ysz, zsz); glTexCoord2f(0, 1); glVertex3f(-xsz, ysz, -zsz); /* face +Y */ glNormal3f(0, 1, 0); glTexCoord2f(0, 0); glVertex3f(-xsz, ysz, zsz); glVertex3f(xsz, ysz, zsz); glVertex3f(xsz, ysz, -zsz); glVertex3f(-xsz, ysz, -zsz); /* face -Y */ glNormal3f(0, -1, 0); glTexCoord2f(0, 0); glVertex3f(-xsz, -ysz, -zsz); glVertex3f(xsz, -ysz, -zsz); glVertex3f(xsz, -ysz, zsz); glVertex3f(-xsz, -ysz, zsz); glEnd(); } libspnav-1.2/examples/fly/xwin.c000066400000000000000000000055041477212760500167520ustar00rootroot00000000000000#include #include #include #include #include "xwin.h" void reshape(int x, int y); void set_window_title(const char *title); Display *dpy; Atom wm_prot, wm_del_win; GLXContext ctx; Window win; int redisplay_pending; int create_xwin(const char *title, int xsz, int ysz) { int scr; Window root; XVisualInfo *vis; XSetWindowAttributes xattr; unsigned int events; XClassHint class_hint; int attr[] = { GLX_RGBA, GLX_DOUBLEBUFFER, GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_DEPTH_SIZE, 24, None }; wm_prot = XInternAtom(dpy, "WM_PROTOCOLS", False); wm_del_win = XInternAtom(dpy, "WM_DELETE_WINDOW", False); scr = DefaultScreen(dpy); root = RootWindow(dpy, scr); if(!(vis = glXChooseVisual(dpy, scr, attr))) { fprintf(stderr, "requested GLX visual is not available\n"); return -1; } if(!(ctx = glXCreateContext(dpy, vis, 0, True))) { fprintf(stderr, "failed to create GLX context\n"); XFree(vis); return -1; } xattr.background_pixel = xattr.border_pixel = BlackPixel(dpy, scr); xattr.colormap = XCreateColormap(dpy, root, vis->visual, AllocNone); if(!(win = XCreateWindow(dpy, root, 0, 0, xsz, ysz, 0, vis->depth, InputOutput, vis->visual, CWColormap | CWBackPixel | CWBorderPixel, &xattr))) { fprintf(stderr, "failed to create X window\n"); return -1; } XFree(vis); /* set the window event mask */ events = ExposureMask | StructureNotifyMask | KeyPressMask | KeyReleaseMask | ButtonReleaseMask | ButtonPressMask | PointerMotionMask; XSelectInput(dpy, win, events); XSetWMProtocols(dpy, win, &wm_del_win, 1); set_window_title(title); class_hint.res_name = "libspnav_example"; class_hint.res_class = "libspnav_example"; XSetClassHint(dpy, win, &class_hint); if(glXMakeCurrent(dpy, win, ctx) == False) { fprintf(stderr, "glXMakeCurrent failed\n"); glXDestroyContext(dpy, ctx); XDestroyWindow(dpy, win); return -1; } XMapWindow(dpy, win); XFlush(dpy); return 0; } void destroy_xwin(void) { glXDestroyContext(dpy, ctx); XDestroyWindow(dpy, win); glXMakeCurrent(dpy, None, 0); } void set_window_title(const char *title) { XTextProperty wm_name; XStringListToTextProperty((char**)&title, 1, &wm_name); XSetWMName(dpy, win, &wm_name); XSetWMIconName(dpy, win, &wm_name); XFree(wm_name.value); } int handle_xevent(XEvent *xev) { int x, y; KeySym sym; switch(xev->type) { case Expose: redisplay_pending = 1; break; case ClientMessage: if(xev->xclient.message_type == wm_prot) { if(xev->xclient.data.l[0] == wm_del_win) { return 1; } } break; case KeyPress: sym = XLookupKeysym((XKeyEvent*)&xev->xkey, 0); if((sym & 0xff) == 27) { return 1; } break; case ConfigureNotify: x = xev->xconfigure.width; y = xev->xconfigure.height; reshape(x, y); break; default: break; } return 0; } libspnav-1.2/examples/fly/xwin.h000066400000000000000000000004061477212760500167530ustar00rootroot00000000000000#ifndef XWIN_H_ #define XWIN_H_ #include extern Display *dpy; extern Window win; extern int win_width, win_height; extern int redisplay_pending; int create_xwin(const char *title, int xsz, int ysz); void destroy_xwin(void); #endif /* XWIN_H_ */ libspnav-1.2/examples/simple/000077500000000000000000000000001477212760500163145ustar00rootroot00000000000000libspnav-1.2/examples/simple/Makefile000066400000000000000000000007621477212760500177610ustar00rootroot00000000000000incdir = -I../.. -I../../src -I/usr/local/include -I/usr/X11R6/include \ -I/opt/homebrew/include libdir = -L../.. -L/usr/local/lib -L/usr/X11R6/lib -L/opt/homebrew/lib CFLAGS = -pedantic -Wall -g $(incdir) LDFLAGS = $(libdir) -lspnav -lX11 -lm .PHONY: all all: simple_x11 simple_af_unix simple_x11: simple.c $(CC) $(CFLAGS) -DBUILD_X11 -o $@ $< $(LDFLAGS) simple_af_unix: simple.c $(CC) $(CFLAGS) -DBUILD_AF_UNIX -o $@ $< $(LDFLAGS) .PHONY: clean clean: rm -f simple_x11 simple_af_unix libspnav-1.2/examples/simple/simple.c000066400000000000000000000044631477212760500177600ustar00rootroot00000000000000#include #include #include #include #include #if defined(BUILD_AF_UNIX) void print_dev_info(void); #endif void sig(int s) { spnav_close(); exit(0); } int main(void) { #if defined(BUILD_X11) Display *dpy; Window win; unsigned long bpix; #endif spnav_event sev; signal(SIGINT, sig); #if defined(BUILD_X11) if(!(dpy = XOpenDisplay(0))) { fprintf(stderr, "failed to connect to the X server\n"); return 1; } bpix = BlackPixel(dpy, DefaultScreen(dpy)); win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0, 1, 1, 0, bpix, bpix); /* This actually registers our window with the driver for receiving * motion/button events through the 3dxsrv-compatible X11 protocol. */ if(spnav_x11_open(dpy, win) == -1) { fprintf(stderr, "failed to connect to spacenavd\n"); return 1; } #elif defined(BUILD_AF_UNIX) if(spnav_open()==-1) { fprintf(stderr, "failed to connect to spacenavd\n"); return 1; } print_dev_info(); #else #error Unknown build type! #endif /* spnav_wait_event() and spnav_poll_event(), will silently ignore any non-spnav X11 events. * * If you need to handle other X11 events you will have to use a regular XNextEvent() loop, * and pass any ClientMessage events to spnav_x11_event, which will return the event type or * zero if it's not an spnav event (see spnav.h). */ while(spnav_wait_event(&sev)) { if(sev.type == SPNAV_EVENT_MOTION) { printf("got motion event: t(%d, %d, %d) ", sev.motion.x, sev.motion.y, sev.motion.z); printf("r(%d, %d, %d)\n", sev.motion.rx, sev.motion.ry, sev.motion.rz); } else { /* SPNAV_EVENT_BUTTON */ printf("got button %s event b(%d)\n", sev.button.press ? "press" : "release", sev.button.bnum); } } spnav_close(); return 0; } #if defined(BUILD_AF_UNIX) void print_dev_info(void) { int proto; char buf[256]; if((proto = spnav_protocol()) == -1) { fprintf(stderr, "failed to query protocol version\n"); return; } printf("spacenav AF_UNIX protocol version: %d\n", proto); spnav_client_name("simple example"); if(proto >= 1) { spnav_dev_name(buf, sizeof buf); printf("Device: %s\n", buf); spnav_dev_path(buf, sizeof buf); printf("Path: %s\n", buf); printf("Buttons: %d\n", spnav_dev_buttons()); printf("Axes: %d\n", spnav_dev_axes()); } putchar('\n'); } #endif libspnav-1.2/spnav.pc.in000066400000000000000000000003601477212760500152640ustar00rootroot00000000000000Name: libspnav Description: Library to access 6-DoF input devices managed by spacenavd or the proprietary 3dxsrv driver. URL: http://spacenav.sourceforge.net Version: @VERSION@ Cflags: -I${PREFIX}/include Libs: -L${PREFIX}/@LIBDIR@ -lspnav libspnav-1.2/src/000077500000000000000000000000001477212760500137745ustar00rootroot00000000000000libspnav-1.2/src/proto.c000066400000000000000000000025601477212760500153060ustar00rootroot00000000000000#include #include #include #define DEF_PROTO_REQ_NAMES #include "proto.h" int spnav_send_str(int fd, int req, const char *str) { int len; struct reqresp rr = {0}; if(fd == -1) { return -1; } len = str ? strlen(str) : 0; rr.type = req; rr.data[6] = len; do { if(str) { memcpy(rr.data, str, len > REQSTR_CHUNK_SIZE ? REQSTR_CHUNK_SIZE : len); } write(fd, &rr, sizeof rr); str += REQSTR_CHUNK_SIZE; len -= REQSTR_CHUNK_SIZE; rr.data[6] = len | REQSTR_CONT_BIT; } while(len > 0); return 0; } int spnav_recv_str(struct reqresp_strbuf *sbuf, struct reqresp *rr) { int len; if(rr->data[6] < 0) return -1; len = REQSTR_REMLEN(rr); if(REQSTR_FIRST(rr)) { /* first packet, allocate buffer */ free(sbuf->buf); sbuf->expect = len; sbuf->size = sbuf->expect + 1; if(!(sbuf->buf = malloc(sbuf->size))) { return -1; } sbuf->endp = sbuf->buf; } if(!sbuf->size || !sbuf->buf || !sbuf->endp) { return -1; } if(sbuf->endp < sbuf->buf || sbuf->endp >= sbuf->buf + sbuf->size) { return -1; } if(sbuf->expect > sbuf->size) return -1; if(len != sbuf->expect) return -1; if(len > REQSTR_CHUNK_SIZE) { len = REQSTR_CHUNK_SIZE; } memcpy(sbuf->endp, rr->data, len); sbuf->endp += len; sbuf->expect -= len; if(sbuf->expect < 0) return -1; if(!sbuf->expect) { *sbuf->endp = 0; return 1; } return 0; } libspnav-1.2/src/proto.h000066400000000000000000000154741477212760500153230ustar00rootroot00000000000000#ifndef PROTO_H_ #define PROTO_H_ #if defined(__sgi) || defined(__sun) #include #else #include #endif /* maximum supported protocol version */ #define MAX_PROTO_VER 1 enum { UEV_MOTION, UEV_PRESS, UEV_RELEASE, UEV_DEV, UEV_CFG, UEV_RAWAXIS, UEV_RAWBUTTON, MAX_UEV }; struct reqresp { int32_t type; int32_t data[7]; }; struct reqresp_strbuf { char *buf, *endp; int size; int expect; }; #define REQ_TAG 0x7faa0000 #define REQ_BASE 0x1000 /* REQ_S* are set requests, 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 * * "remaining length" fields in string transfers have 16 valid bits. Bit 16 is * used as a flag: 0 for the first packet, 1 for continuation packets. */ enum { /* per-client settings */ REQ_SET_NAME = REQ_BASE,/* set client name: Q[0-5] next 24 bytes Q[6] remaining length - R[6] status */ REQ_SET_SENS, /* set client sensitivity: Q[0] float - R[6] status */ REQ_GET_SENS, /* get client sensitivity: R[0] float R[6] status */ REQ_SET_EVMASK, /* set event mask: Q[0] mask - R[6] status */ REQ_GET_EVMASK, /* get event mask: R[0] mask R[6] status */ /* device queries */ REQ_DEV_NAME = 0x2000, /* get device name: R[0-5] next 24 bytes R[6] remaining length or -1 for failure */ 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 */ REQ_DEV_USBID, /* get USB id: R[0] vend R[1] prod R[6] status */ REQ_DEV_TYPE, /* get device type: R[0] type enum R[6] status */ /* 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] dev axis Q[1] deadzone - R[6] status */ REQ_GCFG_DEADZONE, /* get deadzones: R[0] dev axis - R[0] dev axis R[1] deadzone 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 */ REQ_SCFG_AXISMAP, /* set axis mapping: Q[0] dev axis Q[1] mapping - R[6] status */ REQ_GCFG_AXISMAP, /* get axis mapping: Q[0] dev axis - R[0] dev axis R[1] mapping R[6] status */ REQ_SCFG_BNMAP, /* set button mapping: Q[0] dev bidx Q[1] map bidx - R[6] status */ REQ_GCFG_BNMAP, /* get button mapping: Q[0] dev bidx - R[0] dev bidx R[1] map bidx R[6] status */ REQ_SCFG_BNACTION, /* set button action: Q[0] bidx Q[1] action - R[6] status */ REQ_GCFG_BNACTION, /* get button action: Q[0] bidx - R[0] bidx R[1] action R[6] status */ REQ_SCFG_KBMAP, /* set keyboard mapping: Q[0] bidx Q[1] keysym - R[6] status */ REQ_GCFG_KBMAP, /* get keyboard mapping: Q[0] bidx - R[0] bidx R[1] keysym R[6] status */ REQ_SCFG_SWAPYZ, /* set Y-Z axis swap: Q[0] swap - R[6] status */ REQ_GCFG_SWAPYZ, /* get Y-Z axis swap: R[0] swap R[6] status */ REQ_SCFG_LED, /* set LED state: Q[0] state - R[6] status */ REQ_GCFG_LED, /* get LED state: R[0] state R[6] status */ REQ_SCFG_GRAB, /* set device grabbing: Q[0] state - R[6] status */ REQ_GCFG_GRAB, /* get device grabbing: R[0] state R[6] status */ REQ_SCFG_SERDEV, /* set serial device path: Q[0-5] next 24 bytes Q[6] remaining length - R[6] status */ REQ_GCFG_SERDEV, /* get serial device path: R[0-5] next 24 bytes R[6] remaining length or -1 for failure */ REQ_SCFG_REPEAT, /* set repeat interval: Q[0] interval (msec) - R[6] status */ REQ_GCFG_REPEAT, /* get repeat interval: R[0] interval (msec) R[6] status */ /* TODO ... more */ REQ_CFG_SAVE = 0x3ffe, /* save config file: R[6] status */ REQ_CFG_RESTORE, /* load config from file: R[6] status */ REQ_CFG_RESET, /* reset to default config: R[6] status */ REQ_CHANGE_PROTO = 0x5500 }; /* XXX keep in sync with SPNAV_DEV_* in spnav.h (libspnav) */ enum { DEV_UNKNOWN, /* serial devices */ DEV_SB2003 = 0x100, /* Spaceball 1003/2003/2003C */ DEV_SB3003, /* Spaceball 3003/3003C */ DEV_SB4000, /* Spaceball 4000FLX/5000FLX */ DEV_SM, /* Magellan SpaceMouse */ DEV_SM5000, /* Spaceball 5000 (spacemouse protocol) */ DEV_SMCADMAN, /* 3Dconnexion CadMan (spacemouse protocol) */ /* USB devices */ DEV_PLUSXT = 0x200, /* SpaceMouse Plus XT */ DEV_CADMAN, /* 3Dconnexion CadMan (USB version) */ DEV_SMCLASSIC, /* SpaceMouse Classic */ DEV_SB5000, /* Spaceball 5000 (USB version) */ DEV_STRAVEL, /* Space Traveller */ DEV_SPILOT, /* Space Pilot */ DEV_SNAV, /* Space Navigator */ DEV_SEXP, /* Space Explorer */ DEV_SNAVNB, /* Space Navigator for Notebooks */ DEV_SPILOTPRO, /* Space Pilot pro */ DEV_SMPRO, /* SpaceMouse Pro */ DEV_NULOOQ, /* Nulooq */ DEV_SMW, /* SpaceMouse Wireless */ DEV_SMPROW, /* SpaceMouse Pro Wireless */ DEV_SMENT, /* SpaceMouse Enterprise */ DEV_SMCOMP, /* SpaceMouse Compact */ DEV_SMMOD /* SpaceMouse Module */ }; #define REQSTR_CHUNK_SIZE 24 #define REQSTR_CONT_BIT 0x10000 #define REQSTR_FIRST(rr) (((rr)->data[6] & REQSTR_CONT_BIT) == 0) #define REQSTR_REMLEN(rr) ((rr)->data[6] & 0xffff) int spnav_send_str(int fd, int req, const char *str); int spnav_recv_str(struct reqresp_strbuf *sbuf, struct reqresp *rr); #ifdef DEF_PROTO_REQ_NAMES const char *spnav_reqnames_1000[] = { "SET_NAME", "SET_SENS", "GET_SENS", "SET_EVMASK", "GET_EVMASK" }; const char *spnav_reqnames_2000[] = { "DEV_NAME", "DEV_PATH", "DEV_NAXES", "DEV_NBUTTONS", "DEV_USBID", "DEV_TYPE" }; const char *spnav_reqnames_3000[] = { "SCFG_SENS", "GCFG_SENS", "SCFG_SENS_AXIS", "GCFG_SENS_AXIS", "SCFG_DEADZONE", "GCFG_DEADZONE", "SCFG_INVERT", "GCFG_INVERT", "SCFG_AXISMAP", "GCFG_AXISMAP", "SCFG_BNMAP", "GCFG_BNMAP", "SCFG_BNACTION", "GCFG_BNACTION", "SCFG_KBMAP", "GCFG_KBMAP", "SCFG_SWAPYZ", "GCFG_SWAPYZ", "SCFG_LED", "GCFG_LED", "SCFG_GRAB", "GCFG_GRAB", "SCFG_SERDEV", "GCFG_SERDEV", "SCFG_REPEAT", "GCFG_REPEAT" }; const int spnav_reqnames_1000_size = sizeof spnav_reqnames_1000 / sizeof *spnav_reqnames_1000; const int spnav_reqnames_2000_size = sizeof spnav_reqnames_2000 / sizeof *spnav_reqnames_2000; const int spnav_reqnames_3000_size = sizeof spnav_reqnames_3000 / sizeof *spnav_reqnames_3000; #else extern const char *spnav_reqnames_1000[]; extern const char *spnav_reqnames_2000[]; extern const char *spnav_reqnames_3000[]; extern const int spnav_reqnames_1000_size; extern const int spnav_reqnames_2000_size; extern const int spnav_reqnames_3000_size; #endif /* DEF_PROTO_REQ_NAMES */ #endif /* PROTO_H_ */ libspnav-1.2/src/spnav.c000066400000000000000000000531301477212760500152710ustar00rootroot00000000000000/* This file is part of libspnav, part of the spacenav project (spacenav.sf.net) Copyright (C) 2007-2023 John Tsiombikas Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include "spnav.h" #include "proto.h" /* default timeout for request responses*/ #define TIMEOUT 400 /* default socket path */ #define SPNAV_SOCK_PATH "/var/run/spnav.sock" #ifdef SPNAV_USE_X11 #include #include static Window get_daemon_window(Display *dpy); static int catch_badwin(Display *dpy, XErrorEvent *err); static Display *dpy; static Window app_win; static Atom motion_event, button_press_event, button_release_event, command_event; enum { CMD_APP_WINDOW = 27695, CMD_APP_SENS }; #define IS_OPEN (dpy || (sock != -1)) #else #define IS_OPEN (sock != -1) #endif static int read_event(int s, spnav_event *event); static int proc_event(int *data, spnav_event *event); static void flush_resp(void); static int wait_resp(void *buf, int sz, int timeout_ms); static int request(int req, struct reqresp *rr, int timeout_ms); static int request_str(int req, char *buf, int bufsz, int timeout_ms); struct event_node { spnav_event event; struct event_node *next; }; /* only used for non-X mode, with spnav_remove_events */ 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) { struct sockaddr_un addr = {0}; addr.sun_family = AF_UNIX; strncpy(addr.sun_path, path, sizeof addr.sun_path - 1); return connect(s, (struct sockaddr*)&addr, sizeof addr); } int spnav_open(void) { int s, cmd; char *path; FILE *fp; char buf[256], *ptr; if(IS_OPEN) { return -1; } if(!(ev_queue = malloc(sizeof *ev_queue))) { return -1; } ev_queue->next = 0; ev_queue_tail = ev_queue; if((s = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) { return -1; } /* heed SPNAV_SOCKET environment variable if it's defined */ if((path = getenv("SPNAV_SOCKET"))) { if(connect_afunix(s, path) == 0) goto success; } /* hacky config file parser, to look for socket = in /etc/spnavrc */ if((fp = fopen("/etc/spnavrc", "rb"))) { path = 0; while(fgets(buf, sizeof buf, fp)) { ptr = buf; while(*ptr && isspace(*ptr)) ptr++; if(!*ptr || *ptr == '#') continue; /* comment or empty line */ if(memcmp(ptr, "socket", 6) == 0 && (ptr = strchr(ptr, '='))) { while(*++ptr && isspace(*ptr)); if(!*ptr) continue; path = ptr; ptr += strlen(ptr) - 1; while(ptr > path && isspace(*ptr)) *ptr-- = 0; break; } } if(path && connect_afunix(s, path) == 0) goto success; } /* by default use SPNAV_SOCK_PATH (see top of this file) */ if(connect_afunix(s, SPNAV_SOCK_PATH) == -1) { close(s); return -1; } 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, 300) == -1) { spnav_sensitivity(1.0f); } else { proto = cmd & 0xff; } return 0; } #ifdef SPNAV_USE_X11 int spnav_x11_open(Display *display, Window win) { if(IS_OPEN) { return -1; } dpy = display; motion_event = XInternAtom(dpy, "MotionEvent", True); button_press_event = XInternAtom(dpy, "ButtonPressEvent", True); button_release_event = XInternAtom(dpy, "ButtonReleaseEvent", True); command_event = XInternAtom(dpy, "CommandEvent", True); if(!motion_event || !button_press_event || !button_release_event || !command_event) { dpy = 0; return -1; /* daemon not started */ } if(spnav_x11_window(win) == -1) { dpy = 0; return -1; /* daemon not started */ } app_win = win; return 0; } #endif int spnav_close(void) { if(!IS_OPEN) { return -1; } if(sock) { while(ev_queue) { void *tmp = ev_queue; ev_queue = ev_queue->next; free(tmp); } close(sock); sock = -1; return 0; } #ifdef SPNAV_USE_X11 if(dpy) { spnav_x11_window(DefaultRootWindow(dpy)); app_win = 0; dpy = 0; return 0; } #endif return -1; } #ifdef SPNAV_USE_X11 int spnav_x11_window(Window win) { int (*prev_xerr_handler)(Display*, XErrorEvent*); XEvent xev; Window daemon_win; if(!IS_OPEN) { return -1; } if(!(daemon_win = get_daemon_window(dpy))) { return -1; } prev_xerr_handler = XSetErrorHandler(catch_badwin); xev.type = ClientMessage; xev.xclient.send_event = False; xev.xclient.display = dpy; xev.xclient.window = win; xev.xclient.message_type = command_event; xev.xclient.format = 16; xev.xclient.data.s[0] = ((unsigned int)win & 0xffff0000) >> 16; xev.xclient.data.s[1] = (unsigned int)win & 0xffff; xev.xclient.data.s[2] = CMD_APP_WINDOW; XSendEvent(dpy, daemon_win, False, 0, &xev); XSync(dpy, False); XSetErrorHandler(prev_xerr_handler); return 0; } static int x11_sensitivity(double sens) { int (*prev_xerr_handler)(Display*, XErrorEvent*); XEvent xev; Window daemon_win; float fsens; unsigned int isens; if(!(daemon_win = get_daemon_window(dpy))) { return -1; } fsens = sens; isens = *(unsigned int*)&fsens; prev_xerr_handler = XSetErrorHandler(catch_badwin); xev.type = ClientMessage; xev.xclient.send_event = False; xev.xclient.display = dpy; xev.xclient.window = app_win; xev.xclient.message_type = command_event; xev.xclient.format = 16; xev.xclient.data.s[0] = isens & 0xffff; xev.xclient.data.s[1] = (isens & 0xffff0000) >> 16; xev.xclient.data.s[2] = CMD_APP_SENS; XSendEvent(dpy, daemon_win, False, 0, &xev); XSync(dpy, False); XSetErrorHandler(prev_xerr_handler); return 0; } #endif int spnav_sensitivity(double sens) { float fval; struct reqresp rr; #ifdef SPNAV_USE_X11 if(dpy) { return x11_sensitivity(sens); } #endif fval = sens; if(proto == 0) { if(sock) { ssize_t bytes; while((bytes = write(sock, &fval, sizeof fval)) <= 0 && errno == EINTR); if(bytes <= 0) { return -1; } return 0; } return -1; } rr.data[0] = *(int*)&fval; if(request(REQ_SET_SENS, &rr, TIMEOUT) == -1) { return -1; } return 0; } int spnav_fd(void) { #ifdef SPNAV_USE_X11 if(dpy) { return ConnectionNumber(dpy); } #endif return sock; } /* Checks both the event queue and the daemon socket for pending events. * In either case, it returns immediately with true/false values (doesn't block). */ static int event_pending(int s) { fd_set rd_set; struct timeval tv; if(ev_queue->next) { return 1; } FD_ZERO(&rd_set); FD_SET(s, &rd_set); /* don't block, just poll */ tv.tv_sec = tv.tv_usec = 0; if(select(s + 1, &rd_set, 0, 0, &tv) > 0) { return 1; } return 0; } /* If there are events waiting in the event queue, dequeue one and * return that, otherwise read one from the daemon socket. * This might block unless we called event_pending() first and it returned true. */ static int read_event(int s, spnav_event *event) { int rd; int32_t data[8]; /* if we have a queued event, deliver that one */ if(ev_queue->next) { struct event_node *node = ev_queue->next; ev_queue->next = ev_queue->next->next; /* dequeued the last event, must update tail pointer */ if(ev_queue_tail == node) { ev_queue_tail = ev_queue; } memcpy(event, &node->event, sizeof *event); free(node); return event->type; } /* otherwise read one from the connection */ do { rd = read(s, data, sizeof data); } while(rd == -1 && errno == EINTR); if(rd <= 0) { return 0; } return proc_event(data, event); } static int proc_event(int32_t *data, spnav_event *event) { int i; if(data[0] < 0 || data[0] >= MAX_UEV) { return 0; } switch(data[0]) { case UEV_MOTION: event->type = SPNAV_EVENT_MOTION; break; case UEV_PRESS: case UEV_RELEASE: event->type = SPNAV_EVENT_BUTTON; break; default: event->type = data[0]; } switch(event->type) { case SPNAV_EVENT_MOTION: event->motion.data = &event->motion.x; for(i=0; i<6; i++) { event->motion.data[i] = data[i + 1]; } event->motion.period = data[7]; break; case SPNAV_EVENT_RAWAXIS: event->axis.idx = data[1]; event->axis.value = data[2]; break; case SPNAV_EVENT_BUTTON: event->button.press = data[0] == UEV_PRESS ? 1 : 0; event->button.bnum = data[1]; break; case SPNAV_EVENT_RAWBUTTON: event->button.bnum = data[1]; event->button.press = data[2]; break; case SPNAV_EVENT_DEV: event->dev.op = data[1]; event->dev.id = data[2]; event->dev.devtype = data[3]; event->dev.usbid[0] = data[4]; event->dev.usbid[1] = data[5]; break; case SPNAV_EVENT_CFG: event->cfg.cfg = data[1]; memcpy(event->cfg.data, data + 2, sizeof event->cfg.data); break; } return event->type; } int spnav_wait_event(spnav_event *event) { #ifdef SPNAV_USE_X11 if(dpy) { for(;;) { XEvent xev; XNextEvent(dpy, &xev); if(spnav_x11_event(&xev, event) > 0) { return event->type; } } } #endif if(sock) { if(read_event(sock, event) > 0) { return event->type; } } return 0; } int spnav_poll_event(spnav_event *event) { #ifdef SPNAV_USE_X11 if(dpy) { if(XPending(dpy)) { XEvent xev; XNextEvent(dpy, &xev); return spnav_x11_event(&xev, event); } return 0; } #endif if(sock) { if(event_pending(sock)) { if(read_event(sock, event) > 0) { return event->type; } } } return 0; } #ifdef SPNAV_USE_X11 static Bool match_events(Display *dpy, XEvent *xev, char *arg) { int evtype = *(int*)arg; if(xev->type != ClientMessage) { return False; } if(xev->xclient.message_type == motion_event) { return !evtype || evtype == SPNAV_EVENT_MOTION ? True : False; } if(xev->xclient.message_type == button_press_event || xev->xclient.message_type == button_release_event) { return !evtype || evtype == SPNAV_EVENT_BUTTON ? True : False; } return False; } #endif /* Appends an event to an event list. * Tailptr must be a pointer to the tail pointer of the list. NULL means * append to the global event queue. */ static int enqueue_event(spnav_event *event, struct event_node **tailptr) { struct event_node *node; if(!(node = malloc(sizeof *node))) { return -1; } node->event = *event; node->next = 0; if(!tailptr) { tailptr = &ev_queue_tail; } (*tailptr)->next = node; *tailptr = node; return 0; } int spnav_remove_events(int type) { int rm_count = 0; #ifdef SPNAV_USE_X11 if(dpy) { XEvent xev; while(XCheckIfEvent(dpy, &xev, match_events, (char*)&type)) { rm_count++; } return rm_count; } #endif if(sock) { struct event_node *tmplist, *tmptail; if(!(tmplist = tmptail = malloc(sizeof *tmplist))) { return -1; } tmplist->next = 0; /* while there are events in the event queue, or the daemon socket */ while(event_pending(sock)) { spnav_event event; read_event(sock, &event); /* remove next event */ if(event.type != type) { /* We don't want to drop this one, wrong type. Keep the event * in the temporary list, for deferred reinsertion */ enqueue_event(&event, &tmptail); } else { rm_count++; } } /* reinsert any events we removed that we didn't mean to */ while(tmplist->next) { struct event_node *node = tmplist->next; enqueue_event(&node->event, 0); free(tmplist); tmplist = node; } free(tmplist); return rm_count; } return 0; } #ifdef SPNAV_USE_X11 int spnav_x11_event(const XEvent *xev, spnav_event *event) { int i; int xmsg_type; if(xev->type != ClientMessage) { return 0; } xmsg_type = xev->xclient.message_type; if(xmsg_type != motion_event && xmsg_type != button_press_event && xmsg_type != button_release_event) { return 0; } if(xmsg_type == motion_event) { event->type = SPNAV_EVENT_MOTION; event->motion.data = &event->motion.x; for(i=0; i<6; i++) { event->motion.data[i] = xev->xclient.data.s[i + 2]; } event->motion.period = xev->xclient.data.s[8]; } else { event->type = SPNAV_EVENT_BUTTON; event->button.press = xmsg_type == button_press_event ? 1 : 0; event->button.bnum = xev->xclient.data.s[2]; } return event->type; } static Window get_daemon_window(Display *dpy) { Window win, root_win; XTextProperty wname; Atom type; int fmt; unsigned long nitems, bytes_after; unsigned char *prop; root_win = DefaultRootWindow(dpy); XGetWindowProperty(dpy, root_win, command_event, 0, 1, False, AnyPropertyType, &type, &fmt, &nitems, &bytes_after, &prop); if(!prop) { return 0; } win = *(Window*)prop; XFree(prop); if(!XGetWMName(dpy, win, &wname) || strcmp("Magellan Window", (char*)wname.value) != 0) { return 0; } return win; } int catch_badwin(Display *dpy, XErrorEvent *err) { char buf[256]; if(err->error_code == BadWindow) { /* do nothing? */ } else { XGetErrorText(dpy, err->error_code, buf, sizeof buf); fprintf(stderr, "libspnav: caught unexpected X error: %s\n", buf); } return 0; } #endif static void flush_resp(void) { int res; char buf[256]; fd_set rdset; struct timeval tv = {0}; FD_ZERO(&rdset); FD_SET(sock, &rdset); while((res = select(sock + 1, &rdset, 0, 0, &tv)) > 0 || (res == -1 && errno == EINTR)) { read(sock, buf, sizeof buf); } } 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 || proto < 1) return -1; flush_resp(); req |= REQ_TAG; rr->type = req; write(sock, rr, sizeof *rr); if(wait_resp(rr, sizeof *rr, TIMEOUT) == -1) { return -1; } /* XXX assuming data[6] is always status */ if(rr->type != req || rr->data[6] < 0) return -1; return 0; } static int request_str(int req, char *buf, int bufsz, int timeout_ms) { int res = -1; struct reqresp rr = {0}; struct reqresp_strbuf sbuf = {0}; if(request(req, &rr, timeout_ms) == -1) { return -1; } while((res = spnav_recv_str(&sbuf, &rr)) == 0) { if(wait_resp(&rr, sizeof rr, timeout_ms) == -1) { free(sbuf.buf); return -1; } } if(res == -1) { free(sbuf.buf); return -1; } if(buf) { strncpy(buf, sbuf.buf, bufsz - 1); buf[bufsz - 1] = 0; } free(sbuf.buf); return sbuf.size - 1; } int spnav_protocol(void) { return proto; } int spnav_client_name(const char *name) { return spnav_send_str(sock, REQ_SET_NAME, name); } int spnav_evmask(unsigned int mask) { struct reqresp rr = {0}; rr.data[0] = mask; if(request(REQ_SET_EVMASK, &rr, TIMEOUT) == -1) { return -1; } return 0; } int spnav_dev_name(char *buf, int bufsz) { return request_str(REQ_DEV_NAME, buf, bufsz, TIMEOUT); } int spnav_dev_path(char *buf, int bufsz) { return request_str(REQ_DEV_PATH, buf, bufsz, TIMEOUT); } int spnav_dev_buttons(void) { struct reqresp rr = {0}; if(request(REQ_DEV_NBUTTONS, &rr, TIMEOUT) == -1) { return 2; /* default */ } return rr.data[0]; } int spnav_dev_axes(void) { struct reqresp rr = {0}; if(request(REQ_DEV_NAXES, &rr, TIMEOUT) == -1) { return 6; /* default */ } return rr.data[0]; } int spnav_dev_usbid(unsigned int *vend, unsigned int *prod) { struct reqresp rr = {0}; if(request(REQ_DEV_USBID, &rr, TIMEOUT) == -1) { return -1; } if(vend) *vend = rr.data[0]; if(prod) *prod = rr.data[1]; return 0; } int spnav_dev_type(void) { struct reqresp rr = {0}; if(request(REQ_DEV_TYPE, &rr, TIMEOUT) == -1) { return -1; } return rr.data[0]; } /* configuation api */ int spnav_cfg_reset(void) { struct reqresp rr = {0}; return request(REQ_CFG_RESET, &rr, TIMEOUT); } int spnav_cfg_restore(void) { struct reqresp rr = {0}; return request(REQ_CFG_RESTORE, &rr, TIMEOUT); } int spnav_cfg_save(void) { struct reqresp rr = {0}; return request(REQ_CFG_SAVE, &rr, TIMEOUT); } int spnav_cfg_set_sens(float s) { struct reqresp rr = {0}; rr.data[0] = *(int*)&s; return request(REQ_SCFG_SENS, &rr, TIMEOUT); } float spnav_cfg_get_sens(void) { struct reqresp rr = {0}; if(request(REQ_GCFG_SENS, &rr, TIMEOUT) == -1) { return -1.0f; } return *(float*)&rr.data[0]; } int spnav_cfg_set_axis_sens(const float *svec) { struct reqresp rr; memcpy(rr.data, svec, 6 * sizeof *svec); return request(REQ_SCFG_SENS_AXIS, &rr, TIMEOUT); } int spnav_cfg_get_axis_sens(float *svec) { struct reqresp rr = {0}; if(request(REQ_GCFG_SENS_AXIS, &rr, TIMEOUT) == -1) { return -1; } memcpy(svec, rr.data, 6 * sizeof *svec); return 0; } int spnav_cfg_set_deadzone(int axis, int delta) { struct reqresp rr = {0}; rr.data[0] = axis; rr.data[1] = delta; return request(REQ_SCFG_DEADZONE, &rr, TIMEOUT); } int spnav_cfg_get_deadzone(int axis) { struct reqresp rr = {0}; rr.data[0] = axis; if(request(REQ_GCFG_DEADZONE, &rr, TIMEOUT) == -1) { return -1; } return rr.data[1]; } int spnav_cfg_set_invert(int invbits) { int i; struct reqresp rr; for(i=0; i<6; i++) { rr.data[i] = invbits & 1; invbits >>= 1; } return request(REQ_SCFG_INVERT, &rr, TIMEOUT); } int spnav_cfg_get_invert(void) { int i, res = 0; struct reqresp rr = {0}; if(request(REQ_GCFG_INVERT, &rr, TIMEOUT) == -1) { return -1; } for(i=0; i<6; i++) { res = (res >> 1) | (rr.data[i] ? 0x20 : 0); } return res; } int spnav_cfg_set_axismap(int devaxis, int map) { struct reqresp rr = {0}; rr.data[0] = devaxis; rr.data[1] = map; return request(REQ_SCFG_AXISMAP, &rr, TIMEOUT); } int spnav_cfg_get_axismap(int devaxis) { struct reqresp rr = {0}; rr.data[0] = devaxis; if(request(REQ_GCFG_AXISMAP, &rr, TIMEOUT) == -1) { return -1; } return rr.data[1]; } int spnav_cfg_set_bnmap(int devbn, int map) { struct reqresp rr = {0}; rr.data[0] = devbn; rr.data[1] = map; return request(REQ_SCFG_BNMAP, &rr, TIMEOUT); } int spnav_cfg_get_bnmap(int devbn) { struct reqresp rr = {0}; rr.data[0] = devbn; if(request(REQ_GCFG_BNMAP, &rr, TIMEOUT) == -1) { return -1; } return rr.data[1]; } int spnav_cfg_set_bnaction(int bn, int act) { struct reqresp rr = {0}; rr.data[0] = bn; rr.data[1] = act; return request(REQ_SCFG_BNACTION, &rr, TIMEOUT); } int spnav_cfg_get_bnaction(int bn) { struct reqresp rr = {0}; rr.data[0] = bn; if(request(REQ_GCFG_BNACTION, &rr, TIMEOUT) == -1) { return -1; } return rr.data[1]; } int spnav_cfg_set_kbmap(int bn, int key) { struct reqresp rr = {0}; rr.data[0] = bn; rr.data[1] = key; return request(REQ_SCFG_KBMAP, &rr, TIMEOUT); } int spnav_cfg_get_kbmap(int bn) { struct reqresp rr = {0}; rr.data[0] = bn; if(request(REQ_GCFG_KBMAP, &rr, TIMEOUT) == -1) { return -1; } return rr.data[1]; } int spnav_cfg_set_swapyz(int swap) { struct reqresp rr = {0}; rr.data[0] = swap; return request(REQ_SCFG_SWAPYZ, &rr, TIMEOUT); } int spnav_cfg_get_swapyz(void) { struct reqresp rr = {0}; if(request(REQ_GCFG_SWAPYZ, &rr, TIMEOUT) == -1) { return -1; } return rr.data[0]; } int spnav_cfg_set_led(int state) { struct reqresp rr = {0}; if(state < 0 || state >= 3) return -1; rr.data[0] = state; return request(REQ_SCFG_LED, &rr, TIMEOUT); } int spnav_cfg_get_led(void) { struct reqresp rr = {0}; if(request(REQ_GCFG_LED, &rr, TIMEOUT) == -1) { return -1; } return rr.data[0]; } int spnav_cfg_set_grab(int state) { struct reqresp rr = {0}; rr.data[0] = state ? 1 : 0; return request(REQ_SCFG_GRAB, &rr, TIMEOUT); } int spnav_cfg_get_grab(void) { struct reqresp rr = {0}; if(request(REQ_GCFG_GRAB, &rr, TIMEOUT) == -1) { return -1; } return rr.data[0]; } int spnav_cfg_set_serial(const char *devpath) { return spnav_send_str(sock, REQ_SCFG_SERDEV, devpath); } int spnav_cfg_get_serial(char *buf, int bufsz) { return request_str(REQ_GCFG_SERDEV, buf, bufsz, TIMEOUT); } int spnav_cfg_set_repeat(int msec) { struct reqresp rr = {0}; if(msec < 0) msec = -1; rr.data[0] = msec; return request(REQ_SCFG_REPEAT, &rr, TIMEOUT); } int spnav_cfg_get_repeat(void) { struct reqresp rr = {0}; if(request(REQ_GCFG_REPEAT, &rr, TIMEOUT) == -1) { return -1; } return rr.data[0]; } libspnav-1.2/src/spnav.h000066400000000000000000000323771477212760500153100ustar00rootroot00000000000000/* This file is part of libspnav, part of the spacenav project (spacenav.sf.net) Copyright (C) 2007-2023 John Tsiombikas Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef SPACENAV_H_ #define SPACENAV_H_ #include "spnav_config.h" #ifdef SPNAV_USE_X11 #include #endif enum { SPNAV_EVENT_ANY, /* used by spnav_remove_events() */ SPNAV_EVENT_MOTION, SPNAV_EVENT_BUTTON, /* includes both press and release */ SPNAV_EVENT_DEV, /* add/remove device event */ SPNAV_EVENT_CFG, /* configuration change event */ SPNAV_EVENT_RAWAXIS, SPNAV_EVENT_RAWBUTTON }; enum { SPNAV_DEV_ADD, SPNAV_DEV_RM }; struct spnav_event_motion { int type; /* SPNAV_EVENT_MOTION */ int x, y, z; int rx, ry, rz; unsigned int period; int *data; }; struct spnav_event_button { int type; /* SPNAV_EVENT_BUTTON or SPNAV_EVENT_RAWBUTTON */ int press; int bnum; }; struct spnav_event_dev { int type; /* SPNAV_EVENT_DEV */ int op; /* SPNAV_DEV_ADD / SPNAV_DEV_RM */ int id; int devtype; /* see spnav_dev_type() */ int usbid[2]; /* USB id if it's a USB device, 0:0 if it's a serial device */ }; struct spnav_event_cfg { int type; /* SPNAV_EVENT_CFG */ int cfg; /* same as protocol REQ_GCFG* enum */ int data[6]; /* same as protocol response data 0-5 */ }; struct spnav_event_axis { int type; /* SPNAV_EVENT_RAWAXIS */ int idx; /* axis number */ int value; /* value */ }; typedef union spnav_event { int type; struct spnav_event_motion motion; struct spnav_event_button button; struct spnav_event_dev dev; struct spnav_event_cfg cfg; struct spnav_event_axis axis; } spnav_event; #ifdef __cplusplus extern "C" { #endif /* Open connection to the daemon via AF_UNIX socket. * The unix domain socket interface is an alternative to the original magellan * protocol, and it is *NOT* compatible with the 3D connexion driver. If you wish * to remain compatible, use the X11 protocol (spnav_x11_open, see below). * Returns -1 on failure. */ int spnav_open(void); /* Close connection to the daemon. Use it for X11 or AF_UNIX connections. * Returns -1 on failure */ int spnav_close(void); /* Retrieves the file descriptor used for communication with the daemon, for * use with select() by the application, if so required. * If the X11 mode is used, the socket used to communicate with the X server is * returned, so the result of this function is always reliable. * If AF_UNIX mode is used, the fd of the socket is returned or -1 if * no connection is open / failure occurred. */ int spnav_fd(void); /* TODO: document */ int spnav_sensitivity(double sens); /* blocks waiting for space-nav events. returns 0 if an error occurs */ int spnav_wait_event(spnav_event *event); /* checks the availability of space-nav events (non-blocking) * returns the event type if available, or 0 otherwise. */ int spnav_poll_event(spnav_event *event); /* Removes any pending events from the specified type, or all pending events * events if the type argument is SPNAV_EVENT_ANY. Returns the number of * removed events. */ int spnav_remove_events(int type); #ifdef SPNAV_USE_X11 /* Opens a connection to the daemon, using the original magellan X11 protocol. * Any application using this protocol should be compatible with the proprietary * 3D connexion driver too. */ int spnav_x11_open(Display *dpy, Window win); /* Sets the application window, that is to receive events by the driver. * * NOTE: Any number of windows can be registered for events, when using the * free spnavd daemon. I suspect that the proprietary 3D connexion daemon only * sends events to one window at a time, thus this function replaces the window * that receives events. If compatibility with 3dxsrv is required, do not * assume that you can register multiple windows. */ int spnav_x11_window(Window win); /* Examines an arbitrary X11 event. If it's a spnav event, it returns the event * type (SPNAV_EVENT_MOTION or SPNAV_EVENT_BUTTON) and fills in the spnav_event * structure passed through "event" accordingly. Otherwise, it returns 0. */ 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(). */ /* Set client name */ int spnav_client_name(const char *name); /* Select the types of events the client is interested in receiving */ enum { SPNAV_EVMASK_MOTION = 0x01, /* 6dof motion events */ SPNAV_EVMASK_BUTTON = 0x02, /* button events */ SPNAV_EVMASK_DEV = 0x04, /* device change events */ SPNAV_EVMASK_CFG = 0x08, /* configuration change events */ SPNAV_EVMASK_RAWAXIS = 0x10, /* raw device axis events */ SPNAV_EVMASK_RAWBUTTON = 0x20, /* raw device button events */ SPNAV_EVMASK_INPUT = SPNAV_EVMASK_MOTION | SPNAV_EVMASK_BUTTON, SPNAV_EVMASK_DEFAULT = SPNAV_EVMASK_INPUT | SPNAV_EVMASK_DEV, SPNAV_EVMASK_ALL = 0xffff }; int spnav_evmask(unsigned int mask); /* 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. * If buf is not null, the name is copied into buf. No more than bufsz bytes are * written, including the zero terminator. * The number of bytes that would have been written assuming enough space in buf * are returned, excluding the zero terminator. */ int spnav_dev_name(char *buf, int bufsz); /* Returns the path to the device file if applicable. * Usage same as spnav_dev_name. */ int 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); /* Writes the USB vendor:device ID through the vend/prod pointers. * Returns 0 for success, or -1 on failure (for instance if there is no open * device, or if it's not a USB device). */ int spnav_dev_usbid(unsigned int *vend, unsigned int *prod); enum { SPNAV_DEV_UNKNOWN, /* serial devices */ SPNAV_DEV_SB2003 = 0x100, /* Spaceball 1003/2003/2003C */ SPNAV_DEV_SB3003, /* Spaceball 3003/3003C */ SPNAV_DEV_SB4000, /* Spaceball 4000FLX/5000FLX */ SPNAV_DEV_SM, /* Magellan SpaceMouse */ SPNAV_DEV_SM5000, /* Spaceball 5000 (spacemouse protocol) */ SPNAV_DEV_SMCADMAN, /* 3Dconnexion CadMan (spacemouse protocol) */ /* USB devices */ SPNAV_DEV_PLUSXT = 0x200, /* SpaceMouse Plus XT */ SPNAV_DEV_CADMAN, /* 3Dconnexion CadMan (USB version) */ SPNAV_DEV_SMCLASSIC, /* SpaceMouse Classic */ SPNAV_DEV_SB5000, /* Spaceball 5000 (USB version) */ SPNAV_DEV_STRAVEL, /* Space Traveller */ SPNAV_DEV_SPILOT, /* Space Pilot */ SPNAV_DEV_SNAV, /* Space Navigator */ SPNAV_DEV_SEXP, /* Space Explorer */ SPNAV_DEV_SNAVNB, /* Space Navigator for Notebooks */ SPNAV_DEV_SPILOTPRO, /* Space Pilot pro */ SPNAV_DEV_SMPRO, /* SpaceMouse Pro */ SPNAV_DEV_NULOOQ, /* NuLOOQ */ SPNAV_DEV_SMW, /* SpaceMouse Wireless */ SPNAV_DEV_SMPROW, /* SpaceMouse Pro Wireless */ SPNAV_DEV_SMENT, /* SpaceMouse Enterprise */ SPNAV_DEV_SMCOMP, /* SpaceMouse Compact */ SPNAV_DEV_SMMOD /* SpaceMouse Module */ }; /* Returns the device type (see enumeration above) or -1 on failure */ int spnav_dev_type(void); /* Utility functions * ----------------------------------------------------------------------------- * These are optional helper functions which perform tasks commonly needed by 3D * programs using 6dof input. */ struct spnav_posrot { float pos[3]; /* position vector (x, y, z) */ float rot[4]; /* orientation quaternion (x, y, z, w) w:real xyz:imaginary */ }; void spnav_posrot_init(struct spnav_posrot *pr); void spnav_posrot_moveobj(struct spnav_posrot *pr, const struct spnav_event_motion *ev); void spnav_posrot_moveview(struct spnav_posrot *pr, const struct spnav_event_motion *ev); /* Construct a 4x4 homogeneous transformation matrix from the `spnav_posrot` * structure, suitable for use as a model/world matrix to position and orient a * 3D object. Use in conjunction with `spnav_posrot_moveobj` to accumulate * motion inputs. * The first argument is a pointer to an array of 16 floats, where the matrix is * written. The matrix is in the order expected by OpenGL. */ void spnav_matrix_obj(float *mat, const struct spnav_posrot *pr); /* Construct a 4x4 homogeneous transformation matrix from the `spnav_posrot` * structure, suitable for use as a view matrix for 6dof-controllef flight in 3D * space. Use in conjunction with `spnav_posrot_moveview` to accumulate motion * inputs. * The first argument is a pointer to an array of 16 floats, where the matrix is * written. The matrix is in the order expected by OpenGL. */ void spnav_matrix_view(float *mat, const struct spnav_posrot *pr); /* Configuration API * ----------------------------------------------------------------------------- * This API is mostly targeted towards configuration management tools (like * spnavcfg). Normal clients should avoid changing the spacenavd configuration. * They should use the non-persistent client-specific settings instead. * * All functions with return 0 on success, -1 on failure, unless noted otherwise */ enum { SPNAV_CFG_LED_OFF, SPNAV_CFG_LED_ON, SPNAV_CFG_LED_AUTO }; enum { SPNAV_BNACT_NONE, SPNAV_BNACT_SENS_RESET, SPNAV_BNACT_SENS_INC, SPNAV_BNACT_SENS_DEC, SPNAV_BNACT_DISABLE_ROT, SPNAV_BNACT_DISABLE_TRANS, SPNAV_MAX_BNACT }; /* Reset all settings to their default values. This change will not persist * between spacenavd restarts, unless followed by a call to spnav_cfg_save. */ int spnav_cfg_reset(void); /* Revert all the session settings to the values defined in the spacenavd * configuration file. This change will not persist between spacenavd restarts, * unless followed by a call to spnav_cfg_save. */ int spnav_cfg_restore(void); /* Save all the current settings to the spacenavd configuration file. */ int spnav_cfg_save(void); /* Set the global sensitivity. * cfgfile option: sensitivity */ int spnav_cfg_set_sens(float s); float spnav_cfg_get_sens(void); /* returns the sensitivity or <0.0 on error */ /* Set the per-axis sensitivity for all 6 axes. svec should point to an array of * 6 floats. * cfgfile options: sensitivity-translation-[x|y|z], sensitivity-rotation-[x|y|z] */ int spnav_cfg_set_axis_sens(const float *svec); int spnav_cfg_get_axis_sens(float *svecret); /* writes 6 floats through svec */ /* Set deadzone for a device axis * cfgfile options: dead-zoneN, */ int spnav_cfg_set_deadzone(int devaxis, int delta); int spnav_cfg_get_deadzone(int devaxis); /* returns deadzone, -1 on error */ /* Set the axis invert state * 0: normal, 1: inverted. order: MSB [ ... RZ|RY|RX|TZ|TY|TX] LSB * cfgfile options: invert-trans, invert-rot */ int spnav_cfg_set_invert(int invbits); int spnav_cfg_get_invert(void); /* returns invert bits, -1 on error */ int spnav_cfg_set_axismap(int devaxis, int map); int spnav_cfg_get_axismap(int devaxis); int spnav_cfg_set_bnmap(int devbn, int map); int spnav_cfg_get_bnmap(int devbn); int spnav_cfg_set_bnaction(int devbn, int act); int spnav_cfg_get_bnaction(int devbn); int spnav_cfg_set_kbmap(int devbn, int key); int spnav_cfg_get_kbmap(int devbn); int spnav_cfg_set_swapyz(int swap); int spnav_cfg_get_swapyz(void); /* Control device LED * SPNAV_CFG_LED_OFF | SPNAV_CFG_LED_ON | SPNAV_CFG_LED_AUTO * cfgfile option: led */ int spnav_cfg_set_led(int state); int spnav_cfg_get_led(void); /* returns led setting, -1 on error */ /* Device exclusive access grabbing */ int spnav_cfg_set_grab(int state); int spnav_cfg_get_grab(void); /* returns 0:no-grab/1:grab, or -1 on error */ int spnav_cfg_set_serial(const char *devpath); int spnav_cfg_get_serial(char *buf, int bufsz); int spnav_cfg_set_repeat(int msec); int spnav_cfg_get_repeat(void); #ifdef __cplusplus } #endif #endif /* SPACENAV_H_ */ libspnav-1.2/src/spnav_magellan.c000066400000000000000000000071051477212760500171320ustar00rootroot00000000000000/* This file is part of libspnav, part of the spacenav project (spacenav.sf.net) Copyright (C) 2007-2010 John Tsiombikas Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* spnav_magellan.h and spnav_magellan.c provide source-level compatibility * with the original proprietary Magellan SDK provided by 3Dconnexion. * It is actually implemented as a wrapper on top of the new spnav interface * (see spnav.h). */ #include "spnav_magellan.h" #include "spnav.h" int MagellanInit(Display *dpy, Window win) { return spnav_x11_open(dpy, win) == -1 ? 0 : 1; } int MagellanClose(Display *dpy) { return spnav_close() == -1 ? 0 : 1; } int MagellanSetWindow(Display *dpy, Window win) { return spnav_x11_window(win) == -1 ? 0 : 1; } int MagellanApplicationSensitivity(Display *dpy, double sens) { return spnav_sensitivity(sens) == -1 ? 0 : 1; } int MagellanInputEvent(Display *dpy, XEvent *xev, MagellanIntEvent *mev) { int i; spnav_event event; if(!spnav_x11_event(xev, &event)) { return 0; } if(event.type == SPNAV_EVENT_MOTION) { mev->type = MagellanInputMotionEvent; for(i=0; i<6; i++) { mev->u.data[i] = event.motion.data[i]; } mev->u.data[6] = event.motion.period * 1000 / 60; } else { mev->type = event.button.press ? MagellanInputButtonPressEvent : MagellanInputButtonReleaseEvent; mev->u.button = event.button.bnum; } return mev->type; } int MagellanTranslateEvent(Display *dpy, XEvent *xev, MagellanFloatEvent *mev, double tscale, double rscale) { int i; spnav_event event; if(!spnav_x11_event(xev, &event)) { return 0; } if(event.type == SPNAV_EVENT_MOTION) { mev->MagellanType = MagellanInputMotionEvent; for(i=0; i<6; i++) { mev->MagellanData[i] = (double)event.motion.data[i] * (i < 3 ? tscale : rscale); } mev->MagellanPeriod = event.motion.period; } else { mev->MagellanType = event.button.press ? MagellanInputButtonPressEvent : MagellanInputButtonReleaseEvent; mev->MagellanButton = event.button.bnum; } return mev->MagellanType; } int MagellanRemoveMotionEvents(Display *dpy) { return spnav_remove_events(SPNAV_EVENT_MOTION); } int MagellanRotationMatrix(double mat[4][4], double x, double y, double z) { return 0; /* TODO */ } int MagellanMultiplicationMatrix(double mat_a[4][4], double mat_b[4][4], double mat_c[4][4]) { return 0; /* TODO */ } libspnav-1.2/src/spnav_magellan.h000066400000000000000000000056531477212760500171450ustar00rootroot00000000000000/* This file is part of libspnav, part of the spacenav project (spacenav.sf.net) Copyright (C) 2007-2010 John Tsiombikas Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* spnav_magellan.h and spnav_magellan.c provide source-level compatibility * with the original proprietary Magellan SDK provided by 3Dconnexion. * It is actually implemented as a wrapper on top of the new spnav interface * (see spnav.h). */ #ifndef SPACENAV_MAGELLAN_H_ #define SPACENAV_MAGELLAN_H_ #include enum { MagellanInputMotionEvent = 1, MagellanInputButtonPressEvent = 2, MagellanInputButtonReleaseEvent = 3 }; enum { MagellanX, MagellanY, MagellanZ, MagellanA, MagellanB, MagellanC }; typedef union { int data[7]; int button; } MagellanIntUnion; typedef struct { int type; MagellanIntUnion u; } MagellanIntEvent; typedef struct { int MagellanType; int MagellanButton; double MagellanData[6]; int MagellanPeriod; } MagellanFloatEvent; #ifdef __cplusplus extern "C" { #endif int MagellanInit(Display *dpy, Window win); int MagellanClose(Display *dpy); int MagellanSetWindow(Display *dpy, Window win); int MagellanApplicationSensitivity(Display *dpy, double sens); int MagellanInputEvent(Display *dpy, XEvent *event, MagellanIntEvent *mag_event); int MagellanTranslateEvent(Display *dpy, XEvent *event, MagellanFloatEvent *mag_event, double tscale, double rscale); int MagellanRemoveMotionEvents(Display *dpy); int MagellanRotationMatrix(double mat[4][4], double x, double y, double z); int MagellanMultiplicationMatrix(double mat_a[4][4], double mat_b[4][4], double mat_c[4][4]); #ifdef __cplusplus } #endif #endif /* SPACENAV_MAGELLAN_H_ */ libspnav-1.2/src/util.c000066400000000000000000000142331477212760500151200ustar00rootroot00000000000000/* This file is part of libspnav, part of the spacenav project (spacenav.sf.net) Copyright (C) 2007-2022 John Tsiombikas Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include "spnav.h" static void vec3_cross(float *res, const float *va, const float *vb); static void vec3_qrot(float *res, const float *vec, const float *quat); static void quat_mul(float *qa, const float *qb); static void quat_invert(float *q); static void quat_rotate(float *q, float angle, float x, float y, float z); static void mat4_mul(float *ma, const float *mb); static void mat4_translation(float *mat, float x, float y, float z); static void mat4_quat(float *mat, const float *quat); void spnav_posrot_init(struct spnav_posrot *pr) { pr->pos[0] = pr->pos[1] = pr->pos[2] = 0.0f; pr->rot[0] = pr->rot[1] = pr->rot[2] = 0.0f; pr->rot[3] = 1.0f; } void spnav_posrot_moveobj(struct spnav_posrot *pr, const struct spnav_event_motion *ev) { float len; pr->pos[0] += ev->x * 0.001; pr->pos[1] += ev->y * 0.001; pr->pos[2] -= ev->z * 0.001; if((len = sqrt(ev->rx * ev->rx + ev->ry * ev->ry + ev->rz * ev->rz)) != 0.0f) { float x = ev->rx / len; float y = ev->ry / len; float z = -ev->rz / len; quat_rotate(pr->rot, len * 0.001, x, y, z); } } void spnav_posrot_moveview(struct spnav_posrot *pr, const struct spnav_event_motion *ev) { float trans[3], len; if((len = sqrt(ev->rx * ev->rx + ev->ry * ev->ry + ev->rz * ev->rz)) != 0.0f) { float x = -ev->rx / len; float y = -ev->ry / len; float z = ev->rz / len; quat_rotate(pr->rot, len * 0.001, x, y, z); } trans[0] = -ev->x * 0.001; trans[1] = -ev->y * 0.001; trans[2] = ev->z * 0.001; vec3_qrot(trans, trans, pr->rot); pr->pos[0] += trans[0]; pr->pos[1] += trans[1]; pr->pos[2] += trans[2]; } void spnav_matrix_obj(float *mat, const struct spnav_posrot *pr) { float tmp[16]; mat4_quat(mat, pr->rot); mat4_translation(tmp, pr->pos[0], pr->pos[1], pr->pos[2]); mat4_mul(mat, tmp); } void spnav_matrix_view(float *mat, const struct spnav_posrot *pr) { float tmp[16]; mat4_translation(mat, pr->pos[0], pr->pos[1], pr->pos[2]); mat4_quat(tmp, pr->rot); mat4_mul(mat, tmp); } /* ---- vector/matrix/quaternion math operations ---- */ static void vec3_cross(float *res, const float *va, const float *vb) { res[0] = va[1] * vb[2] - va[2] * vb[1]; res[1] = va[2] * vb[0] - va[0] * vb[2]; res[2] = va[0] * vb[1] - va[1] * vb[0]; } static void vec3_qrot(float *res, const float *vec, const float *quat) { float vq[4], inv_q[4], tmp_q[4]; inv_q[0] = tmp_q[0] = quat[0]; inv_q[1] = tmp_q[1] = quat[1]; inv_q[2] = tmp_q[2] = quat[2]; inv_q[3] = tmp_q[3] = quat[3]; vq[0] = vec[0]; vq[1] = vec[1]; vq[2] = vec[2]; vq[3] = 0.0f; quat_invert(inv_q); quat_mul(tmp_q, vq); quat_mul(tmp_q, inv_q); res[0] = tmp_q[0]; res[1] = tmp_q[1]; res[2] = tmp_q[2]; } static void quat_mul(float *qa, const float *qb) { float x, y, z, dot, cross[3]; dot = qa[0] * qb[0] + qa[1] * qb[1] + qa[2] * qb[2]; vec3_cross(cross, qb, qa); x = qa[3] * qb[0] + qb[3] * qa[0] + cross[0]; y = qa[3] * qb[1] + qb[3] * qa[1] + cross[1]; z = qa[3] * qb[2] + qb[3] * qa[2] + cross[2]; qa[3] = qa[3] * qb[3] - dot; qa[0] = x; qa[1] = y; qa[2] = z; } static void quat_invert(float *q) { float s, len_sq = q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]; /* conjugate */ q[0] = -q[0]; q[1] = -q[1]; q[2] = -q[2]; if(len_sq != 0.0f) { s = 1.0f / len_sq; q[0] *= s; q[1] *= s; q[2] *= s; q[3] *= s; } } static void quat_rotate(float *q, float angle, float x, float y, float z) { float rq[4]; float half = angle * 0.5f; float sin_half = sin(half); rq[3] = cos(half); rq[0] = x * sin_half; rq[1] = y * sin_half; rq[2] = z * sin_half; quat_mul(q, rq); } static void mat4_mul(float *ma, const float *mb) { int i, j; float res[16]; float *resptr = res; float *arow = ma; for(i=0; i<4; i++) { for(j=0; j<4; j++) { *resptr++ = arow[0] * mb[j] + arow[1] * mb[4 + j] + arow[2] * mb[8 + j] + arow[3] * mb[12 + j]; } arow += 4; } memcpy(ma, res, sizeof res); } static void mat4_translation(float *mat, float x, float y, float z) { static const float id[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; memcpy(mat, id, sizeof id); mat[12] = x; mat[13] = y; mat[14] = z; } static void mat4_quat(float *m, const float *q) { float xsq2 = 2.0f * q[0] * q[0]; float ysq2 = 2.0f * q[1] * q[1]; float zsq2 = 2.0f * q[2] * q[2]; float sx = 1.0f - ysq2 - zsq2; float sy = 1.0f - xsq2 - zsq2; float sz = 1.0f - xsq2 - ysq2; m[3] = m[7] = m[11] = m[12] = m[13] = m[14] = 0.0f; m[15] = 1.0f; m[0] = sx; m[1] = 2.0f * q[0] * q[1] + 2.0f * q[3] * q[2]; m[2] = 2.0f * q[2] * q[0] - 2.0f * q[3] * q[1]; m[4] = 2.0f * q[0] * q[1] - 2.0f * q[3] * q[2]; m[5] = sy; m[6] = 2.0f * q[1] * q[2] + 2.0f * q[3] * q[0]; m[8] = 2.0f * q[2] * q[0] + 2.0f * q[3] * q[1]; m[9] = 2.0f * q[1] * q[2] - 2.0f * q[3] * q[0]; m[10] = sz; }