spacenavd-1.3.1/0000775000175000017500000000000014737545525013347 5ustar nuclearnuclearspacenavd-1.3.1/README.md0000664000175000017500000001070214737545500014617 0ustar nuclearnuclearspacenavd ========= ![GNU/Linux build status](https://github.com/FreeSpacenav/spacenavd/actions/workflows/build_gnulinux.yml/badge.svg) ![FreeBSD build status](https://github.com/FreeSpacenav/spacenavd/actions/workflows/build_freebsd.yml/badge.svg) ![MacOS X build status](https://github.com/FreeSpacenav/spacenavd/actions/workflows/build_macosx.yml/badge.svg) About ----- Spacenavd is a free software user-space driver (daemon), for 6-dof input devices, like 3Dconnexion's space-mice. It's compatible with the original 3dxsrv proprietary daemon provided by 3Dconnexion, and works as a drop-in replacement with any program that was written for the 3Dconnexion driver, but also provides an improved communication mechanism for programs designed specifically to work with spacenavd. For more info on the spacenav project, visit: http://spacenav.sourceforge.net > We are currently in the process of documenting how button numbers relate to > physical buttons, across all 6dof devices: > https://github.com/FreeSpacenav/spacenavd/wiki/Device-button-names > > If you have a device which is missing from that wiki page, please help us > expand the database by adding it, or send us the information through email. License ------- Copyright (C) 2007-2025 John Tsiombikas This program is free software. Feel free to copy, modify and/or redistribute it under the terms of the GNU General Public License version 3, or at your option, any later version published by the Free Software Foundation. See COPYING for details. Dependencies ------------ In order to compile the spacenavd daemon, you'll need the following: - GNU C Compiler - GNU make - Xlib (libX11, optional) - XInput2 (libXi, optional) - Xtest (libXtst, optional) You can compile the daemon without Xlib, but it won't be compatible with applications that where written for the original proprietary 3Dconnexion driver (e.g. maya, houdini, etc). The 3dxsrv compatibility interface needs to communicate with clients through the X window system. Programs designed to work with the alternative spacenavd-specific interface however (e.g. blender) will work fine even when spacenavd is built without X11 support. Installation ------------ If you have the dependencies installed, just run `./configure` and then `make` to compile the daemon, and then `make install`, to install it.The default installation prefix is `/usr/local`. If you wish to install somewhere else, you may pass `--prefix=/whatever` to the configure script. Running spacenavd ----------------- Spacenavd is designed to run during startup as a system daemon. If your system uses SysV init, then you may run `setup_init` as root, to install the spacenavd init script, and have spacenavd start automatically during startup. To start the daemon right after installing it, without having to reboot your system, just type `/etc/init.d/spacenavd start` as root. If your system uses BSD init or some other init system, then you'll have to follow your init documentation to set this up yourself. You may be able to use the provided `init_script` file as a starting point. For systems running systemd, there is a spacenavd.service file under `contrib/systemd`. Follow your system documentation for how to use it. Configuration ------------- The spacenavd daemon reads a number of options from `/etc/spnavrc`. If that file doesn't exist, then it will use default values for everything. An example configuration file is included in the doc subdirectory, which you may copy to `/etc` and tweak. You may use the graphical spnavcfg program to interactively set and tweak any of the configuration options. Troubleshooting --------------- If you're having trouble running spacenavd, read the up to date FAQ on the spacenav website: http://spacenav.sourceforge.net/faq.html If you're not sure if spacenavd is set up correctly and works with your device, a good first step is to try and run the "simple" example program which comes with libspnav. It builds into two variants: `simple_af_unix` and `simple_x11`, which is helpful for testing both supported communication protocols. If either or both fail to work, there's something wrong with your setup. If you're still having trouble, send a description of your problem to the spacenav-users mailing list: spacenav-users@lists.sourceforge.net along with a copy of your /var/log/spnavd.log and any other relevant information. If you have encountered a bug, please file a bug report in our bug tracker: https://github.com/FreeSpacenav/spacenavd/issues spacenavd-1.3.1/spnavd_ctl0000775000175000017500000000221514737545500015423 0ustar nuclearnuclear#!/bin/sh # this script, starts and stops the communication between spacenavd and the # local X server. (:0). if [ "$1" != 'x11' ]; then echo "the only valid control for the moment is x11 ($0 x11 start/stop)." exit 1 fi if [ -z "$2" ]; then echo 'you must specify either "start" or "stop".' exit 1 fi if [ "$2" = 'start' ]; then # check to see there is a local X server running. DISPLAY=":0" xdpyinfo >/dev/null 2>/dev/null if [ $? != 0 ]; then echo "You must have an X server running before starting up spacenavd-X11 events." exit 1 fi sig=-usr1 elif [ "$2" = "stop" ]; then sig=-usr2 else echo 'you must specify either "start" or "stop".' exit 1 fi # detect daemon's process id pid=`cat /var/run/spnavd.pid 2>/dev/null` if [ $? != 0 ]; then pid=`ps -e | grep spacenavd | awk '{ print $1 }'` if [ -z "$pid" ]; then echo 'spacenavd daemon is not running, nothing to do.' exit 1 fi fi kill $sig $pid if [ $? = 0 ]; then if [ $sig = '-usr1' ]; then echo 'signalled spacenavd, it should now start sending X events.' else echo 'signalled spacenavd to stop sending X events.' fi else echo 'sending signal to spacenavd failed.' fi spacenavd-1.3.1/.gitignore0000664000175000017500000000005214737545500015325 0ustar nuclearnuclear*.o *.d *.swp Makefile spacenavd config.h spacenavd-1.3.1/.github/0000775000175000017500000000000014737545500014700 5ustar nuclearnuclearspacenavd-1.3.1/.github/workflows/0000775000175000017500000000000014737545500016735 5ustar nuclearnuclearspacenavd-1.3.1/.github/workflows/build_macosx.yml0000664000175000017500000000165414737545500022137 0ustar nuclearnuclearname: 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 libxi libxtst - name: configure run: ./configure - name: build run: make - name: stage install run: DESTDIR=spacenavd-macosx make install - uses: actions/upload-artifact@v4 with: name: spacenavd-macosx path: spacenavd-macosx build-nox11: runs-on: macos-latest steps: - uses: actions/checkout@v4 - name: configure run: ./configure --disable-x11 - name: build run: make - name: stage install run: DESTDIR=spacenavd-macosx-nox11 make install - uses: actions/upload-artifact@v4 with: name: spacenavd-macosx-nox11 path: spacenavd-macosx-nox11 # vi:ts=2 sts=2 sw=2 expandtab: spacenavd-1.3.1/.github/workflows/build_gnulinux.yml0000664000175000017500000000175514737545500022520 0ustar nuclearnuclearname: 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 libxi-dev libxtst-dev - name: configure run: ./configure - name: build run: make - name: stage install run: DESTDIR=spacenavd-gnulinux make install - uses: actions/upload-artifact@v4 with: name: spacenavd-gnulinux path: spacenavd-gnulinux build-nox11: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: configure run: ./configure --disable-x11 - name: build run: make - name: stage install run: DESTDIR=spacenavd-gnulinux-nox11 make install - uses: actions/upload-artifact@v4 with: name: spacenavd-gnulinux-nox11 path: spacenavd-gnulinux-nox11 # vi:ts=2 sts=2 sw=2 expandtab: spacenavd-1.3.1/.github/workflows/build_freebsd.yml0000664000175000017500000000106014737545500022246 0ustar nuclearnuclearname: 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 git gmake libX11 libXi libXtst run: | ./configure gmake gmake DESTDIR=spacenavd-freebsd install - uses: actions/upload-artifact@v4 with: name: spacenavd-freebsd path: spacenavd-freebsd # vi:ts=2 sts=2 sw=2 expandtab: spacenavd-1.3.1/AUTHORS0000664000175000017500000000110614737545500014406 0ustar nuclearnuclearMain author and maintainer: John Tsiombikas Contributors: Doug LaRue Krister Svanlun Hans Meine Jaroslaw Bulat Pavel Frolov, Robert Haschke David Lister Stephen Hurd Gaël Écorchard Alberto Fanjul Serial device support in early versions (before the v0.8 rewrite): John E. Stone NOTE: This file has been left untended for quite a while. There are bound to be contributions from people not listed here. If you ever contributed code to this project and your name is missing from this list, please contact me and I'll add it. spacenavd-1.3.1/COPYING0000664000175000017500000010451314737545500014377 0ustar nuclearnuclear GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . spacenavd-1.3.1/doc/0000775000175000017500000000000014737545500014105 5ustar nuclearnuclearspacenavd-1.3.1/doc/example-spnavrc0000664000175000017500000000757414737545500017152 0ustar nuclearnuclear# This is an example configuration file for spacenavd. Uncomment and change # any settings you need and copy it to /etc/spnavrc. # # Note that it's much easier to configure sensitivities and figure out axis # reversals by using the interactive configure utility "spnavcfg", which lets # you fiddle with all the settings and see the result immediately. # # Lines that start with a `#' are comments, and are ignored by spacenavd. # Sensitivity is multiplied with every motion (1.0 normal). # #sensitivity = 1.0 # Separate sensitivity for rotation and translation. # #sensitivity-translation = 1.0 #sensitivity-rotation = 1.0 # Separate sensitivity for each rotation and translation axis. # #sensitivity-translation-x = 1.0 #sensitivity-translation-y = 1.0 #sensitivity-translation-z = 1.0 #sensitivity-rotation-x = 1.0 #sensitivity-rotation-y = 1.0 #sensitivity-rotation-z = 1.0 # Dead zone; any motion less than this number is discarded as noise. # #dead-zone = 2 # Separate dead-zone for each rotation and translation axis. # This is still supported, but unclear in face of device axes mapping, and # therefore you are encouraged to use dead-zoneN instead. # #dead-zone-translation-x = 2 #dead-zone-translation-y = 2 #dead-zone-translation-z = 2 #dead-zone-rotation-x = 2 #dead-zone-rotation-y = 2 #dead-zone-rotation-z = 2 # Separate dead-zone for each device axis. # #dead-zone0 = 2 #dead-zone1 = 2 # ... #dead-zoneN = 2 # Selectively invert translation and rotation axes. Valid values are # combinations of the letters x, y, and z. # #invert-rot = yz #invert-trans = yz # Swap Y and Z axes # #swap-yz = false # Axis remapping (zero-based) # #axismap0 = 0 #axismap1 = 1 #... #axismapN = N (N < 64) # Button remapping (zero-based) # #bnmap0 = 0 #bnmap1 = 1 #... #bnmapN = N (N < 64) # Button action mapping # Use the following syntax: # bnactN = # Where N is the button number (zero-based), and action is one of the following # available actions: # none, # sensitivity-up, sensitivity-down, sensitivity-reset # disable-rotation # disable-translation # dominant-axis # #bnact16 = sensitivity-up #bnact17 = sensitivity-down #bnact18 = sensitivity-reset # Map buttons to keyboard keys (experimental) # By default no such mappings are active. Use the following syntax: # kbmapN = # where N is the button number (zero-based), and is a keysym name from # /usr/include/X11/keysymdef.h, without the leading XK_ # Example: # kbmap0 = Escape # Serial device # Set this only if you have a serial device, and make sure you specify the # correct device file. If you do set this option, any USB devices will be # ignored! # #serial = /dev/ttyS0 # Enable/disable LED light (for devices that have one). # #led = on # Device grab (USB) # # When set to true (default): grab the USB device to prevent other programs # (such as the X server) from also using it. # # Grabbing the device ensures that other programs won't be able to use it without # talking to spacenavd. For instance some versions of Xorg will use the device to move # the mouse pointer if we don't grab it. # Set this to false if you want to use programs that try to talk to the device directly # such as google earth, then follow FAQ 11 http://spacenav.sourceforge.net/faq.html#faq11 # to force the X server to ignore the device # #grab = true # List of additional USB devices to use (multiple devices can be listed) # # example: # device-id = 046d:c625 # Repeat interval (milliseconds) # Non-deadzone events are repeated every so many milliseconds (-1 to disable). # # You probably don't need this setting. Set it to something like 250 if you # find that your apps stop moving the view while you hold the puck/ball at a # fixed off-center position (like bottomed out on the vertical axis). # Might cause inadvertent continuous motion if the deadzone is too small for # the re-centering power of the device. # #repeat-interval = -1 spacenavd-1.3.1/doc/spnavrc_smouse_ent0000664000175000017500000000047214737545500017750 0ustar nuclearnuclear# Sensible button mappings for the SpaceMouse Enterprise # Copy to /etc/spnavrc to use bnact2 = sensitivity-reset bnact8 = sensitivity-down bnact9 = sensitivity-up kbmap18 = Escape bnact22 = disable-rotation kbmap13 = KP_Delete kbmap20 = Shift_L kbmap21 = Control_L kbmap24 = Delete kbmap25 = Tab kbmap26 = space spacenavd-1.3.1/doc/spnavrc_spilot0000664000175000017500000000047214737545500017101 0ustar nuclearnuclear# Sensible button mappings for the original SpacePilot # Copy to /etc/spnavrc to use kbmap6 = KP_7 kbmap8 = KP_3 kbmap9 = KP_1 kbmap11 = Alt_L kbmap12 = Shift_L kbmap13 = Control_L kbmap14 = KP_Delete kbmap19 = KP_5 bnact16 = sensitivity-up bnact17 = sensitivity-down bnact18 = sensitivity-reset kbmap10 = Escape spacenavd-1.3.1/configure0000775000175000017500000001347214737545525015265 0ustar nuclearnuclear#!/bin/sh test_kver() { req_major=`echo $1 | awk -F . '{ print $1 }'` req_minor=`echo $1 | awk -F . '{ print $2 }'` req_rev=`echo $1 | awk -F . '{ print $3 }'` linux_rev=`uname -r | sed 's/-.*//'` kver_major=`echo $linux_rev | awk -F . '{ print $1 }'` kver_minor=`echo $linux_rev | awk -F . '{ print $2 }'` kver_rev=`echo $linux_rev | awk -F . '{ print $3 }'` if [ "$kver_major" -lt "$req_major" ]; then return 1 fi if [ "$kver_major" = "$req_major" ]; then if [ "$kver_minor" -lt "$req_minor" ]; then return 1 fi if [ "$kver_minor" = "$req_minor" -a "$kver_rev" -lt "$req_rev" ]; then return 1 fi fi return 0 } check_header() { printf "Looking for header: $1 ... " >&2 echo "#include <$1>" >.chkhdr.c if cpp -I/usr/local/include $x11inc .chkhdr.c >/dev/null 2>&1; then echo found >&2 echo "#define HAVE_`basename $1 | tr '[:lower:]' '[:upper:]' | sed 's/\./_/g'`" else echo not found >&2 fi rm -f .chkhdr.c } PREFIX=/usr/local OPT=yes DBG=yes X11=yes HOTPLUG=yes XINPUT=yes VER=1.3.1 CFGDIR=/etc if [ -z "$VER" ]; then VER=`git rev-parse --short HEAD` if [ -z "$VER" ]; then VER=v`pwd | grep 'spacenavd-[0-9]\+\.' | sed 's/.*spacenavd-\(\([0-9]\+\.\)\+[0-9]\+\).*$/\1/'` if [ $VER = v ]; then VER='' fi fi fi echo "configuring spacenavd - $VER" sys=`uname -s` if [ "$sys" = Linux ]; then # NETLINK_KOBJECT_UEVENT used for hotplug detection requires 2.6.10 if test_kver 2.6.10; then HOTPLUG=yes else HOTPLUG=no fi elif [ "$sys" = Darwin ]; then LDFLAGS='-framework CoreFoundation -framework IOKit' else # TODO implement hotplug for other systems then switch this on HOTPLUG=no fi srcdir="`dirname "$0"`" # process arguments for arg; do case "$arg" in --prefix=*) value=`echo $arg | sed 's/--prefix=//'` PREFIX=${value:-$prefix} ;; --cfgdir=*) value=`echo $arg | sed 's/--cfgdir=//'` CFGDIR=${value:-$cfgdir} ;; --enable-opt) OPT=yes;; --disable-opt) OPT=no;; --enable-debug) DBG=yes;; --disable-debug) DBG=no;; --enable-x11) X11=yes;; --disable-x11) X11=no;; --enable-hotplug) HOTPLUG=yes;; --disable-hotplug) HOTPLUG=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-hotplug: enable hotplug using NETLINK_KOBJECT_UEVENT (default)' echo ' --disable-hotplug: disable hotplug, fallback to polling for the device' 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 " config dir: $CFGDIR" echo " optimize for speed: $OPT" echo " include debugging symbols: $DBG" echo " x11 communication method: $X11" echo " use hotplug: $HOTPLUG" echo "" HAVE_ALLOCA_H=`check_header alloca.h` HAVE_MALLOC_H=`check_header malloc.h` HAVE_STDINT_H=`check_header stdint.h` HAVE_INTTYPES_H=`check_header inttypes.h` if [ "$X11" = "no" ]; then echo "WARNING: you have disabled the X11 interface, the resulting daemon \ won't be compatible with applications written for the proprietary 3Dconnexion \ daemon (3dxserv)!" echo else # find alternate X11 header/lib paths if [ -e /usr/local/include/X11/Xlib.h ]; then x11prefix='/usr/local' elif [ -e /usr/X11/include/X11/Xlib.h ]; then x11prefix='/usr/X11' elif [ -e /usr/X11R6/include/X11/Xlib.h ]; then x11prefix='/usr/X11R6' elif [ -e /opt/homebrew/include/X11/Xlib.h ]; then x11prefix='/opt/homebrew' fi if [ -n "$x11prefix" ]; then echo "X11 prefix: $x11prefix" x11inc=-I$x11prefix/include x11lib=-L$x11prefix/lib fi HAVE_XINPUT2_H=`check_header X11/extensions/XInput2.h` HAVE_XTEST_H=`check_header X11/extensions/XTest.h` if [ -z "$HAVE_XTEST_H" ]; then echo "WARNING: building without XTEST support, makes keyboard emulation \ less reliable (fallback to XSendEvent)." fi fi # create Makefile echo 'creating Makefile ...' echo "PREFIX = $PREFIX" >Makefile echo "srcdir = $srcdir" >>Makefile echo "ver = $VER" >>Makefile if [ "$DBG" = 'yes' ]; then echo 'dbg = -g' >>Makefile fi if [ "$OPT" = 'yes' ]; then echo 'opt = -O2' >>Makefile fi if [ "$X11" = 'yes' ]; then echo "xinc = $x11inc" >>Makefile echo "xlib = $x11lib" >>Makefile if [ -n "$HAVE_XINPUT2_H" ]; then echo 'xlib += -lXi' >>Makefile fi if [ -n "$HAVE_XTEST_H" ]; then echo xlib += -lXtst >>Makefile fi echo 'xlib += -lX11 -lXext' >>Makefile fi if [ -n "$CFLAGS" ]; then echo "add_cflags = $CFLAGS" >>Makefile fi if [ -n "$LDFLAGS" ]; then echo "add_ldflags = $LDFLAGS" >>Makefile fi cat "$srcdir/Makefile.in" >>Makefile # create config.h cfgheader=$srcdir/src/config.h echo 'creating config.h' echo '#ifndef CONFIG_H_' >$cfgheader echo '#define CONFIG_H_' >>$cfgheader echo >>$cfgheader if [ "$X11" = yes ]; then echo '#define USE_X11' >>$cfgheader echo >>$cfgheader fi if [ "$HOTPLUG" = yes ]; then echo '#define USE_NETLINK' >>$cfgheader echo >>$cfgheader fi echo '#define VERSION "'$VER'"' >>$cfgheader echo >>$cfgheader # check for alloca.h [ -n "$HAVE_ALLOCA_H" ] && echo $HAVE_ALLOCA_H >>$cfgheader [ -n "$HAVE_MALLOC_H" ] && echo $HAVE_MALLOC_H >>$cfgheader [ -n "$HAVE_STDINT_H" ] && echo $HAVE_STDINT_H >>$cfgheader [ -n "$HAVE_INTTYPES_H" ] && echo $HAVE_INTTYPES_H >>$cfgheader [ -n "$HAVE_XINPUT2_H" ] && echo $HAVE_XINPUT2_H >>$cfgheader [ -n "$HAVE_XTEST_H" ] && echo $HAVE_XTEST_H >>$cfgheader echo >>$cfgheader echo "#define CFGDIR \"$CFGDIR\"" >>$cfgheader echo >>$cfgheader echo '#endif /* CONFIG_H_ */' >>$cfgheader echo '' echo 'Done. You can now type make (or gmake) to compile spacenavd.' echo '' spacenavd-1.3.1/init_script0000775000175000017500000000215514737545500015620 0ustar nuclearnuclear#!/bin/sh # # spacenavd free driver for 3Dconnexion 6dof devices # # chkconfig: 2345 99 99 # description: A free user space driver for 3Dconnexion input devices,\ # compatible with the proprietary 3dxsrv daemon. # # ### BEGIN INIT INFO # Provides: spacenavd # Required-Start: $remote_fs $syslog # Required-Stop: $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: free driver for 3Dconnexion 6dof devices # Description: A free user space driver for 3Dconnexion input devices, compatible with the proprietary 3dxsrv daemon. ### END INIT INFO DAEMON=/usr/local/bin/spacenavd [ -x "$DAEMON" ] || exit 0 case "$1" in start) echo 'Starting spacenavd daemon' $DAEMON -v ;; stop) echo 'Stopping spacenavd daemon' # detect daemon's process id pid=`cat /var/run/spnavd.pid 2>/dev/null` if [ $? != 0 ]; then pid=`ps -e | grep spacenavd | awk '{ print $1 }'` if [ -z "$pid" ]; then echo 'spacenavd daemon is not running, nothing to do.' exit 1 fi fi kill $pid ;; reload|restart|force-reload) $0 stop && sleep 1 && $0 start ;; esac spacenavd-1.3.1/src/0000775000175000017500000000000014737545500014127 5ustar nuclearnuclearspacenavd-1.3.1/src/xdetect_freebsd.c0000664000175000017500000000726614737545500017440 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #if defined(__FreeBSD__) || defined(__APPLE__) #include "config.h" #ifdef USE_X11 #include #include #include #include #include #include #include #include #include "proto_x11.h" #include "spnavd.h" static int kq = -1; static int fd_x11 = -1; static int fd_tmp = -1; int xdet_start(void) { struct timespec ts = {0, 0}; struct kevent kev; if((kq = kqueue()) == -1) { logmsg(LOG_ERR, "failed to create kqueue: %s\n", strerror(errno)); return -1; } if((fd_x11 = open("/tmp/.X11-unix", O_RDONLY)) == -1) { if((fd_tmp = open("/tmp", O_RDONLY)) == -1) { logmsg(LOG_ERR, "failed to open /tmp: %s\n", strerror(errno)); goto err; } } EV_SET(&kev, fd_x11 != -1 ? fd_x11 : fd_tmp, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_WRITE, 0, 0); if(kevent(kq, &kev, 1, 0, 0, &ts) == -1) { logmsg(LOG_ERR, "failed to register kqueue event notification: %s\n", strerror(errno)); goto err; } if(verbose) { logmsg(LOG_INFO, "waiting for the X socket file to appear\n"); } return kq; err: if(fd_x11 != -1) close(fd_x11); if(fd_tmp != -1) close(fd_tmp); if(kq != -1) close(kq); kq = -1; return -1; } void xdet_stop(void) { if(kq != -1) { if(verbose) { logmsg(LOG_INFO, "stopping X watch\n"); } if(fd_x11 != -1) close(fd_x11); if(fd_tmp != -1) close(fd_tmp); close(kq); kq = fd_x11 = fd_tmp = -1; } } int xdet_get_fd(void) { return kq; } int handle_xdet_events(fd_set *rset) { struct kevent kev; struct timespec ts = {0, 0}; if(kq == -1 || !FD_ISSET(kq, rset)) { return -1; } if(kevent(kq, 0, 0, &kev, 1, &ts) <= 0) { return -1; } if(kev.ident == fd_tmp) { assert(fd_x11 == -1); /* try to open the socket dir, see if that was what was added to /tmp */ if((fd_x11 = open("/tmp/.X11-unix", O_RDONLY)) == -1) { return -1; } EV_SET(&kev, fd_x11, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_WRITE, 0, 0); if(kevent(kq, &kev, 1, 0, 0, &ts) == -1) { logmsg(LOG_ERR, "failed to register kqueue event notification for /tmp/.X11-unix: %s\n", strerror(errno)); close(fd_x11); fd_x11 = -1; return -1; } /* successfully added the notification for /tmp/.X11-unix, now we * don't need the /tmp notification anymore. by closing the fd it's * automatically removed from the kqueue. */ close(fd_tmp); fd_tmp = -1; } else if(kev.ident == fd_x11) { int i; if(verbose) { logmsg(LOG_INFO, "found X socket, will now attempt to connect to the X server\n"); } /* poll for approximately 30 seconds (well a bit more than that) */ for(i=0; i<30; i++) { sleep(1); if(init_x11() != -1) { /* done, we don't need the X socket notification any more */ close(fd_x11); fd_x11 = -1; return 0; /* success */ } } logmsg(LOG_ERR, "found X socket yet failed to connect\n"); } return -1; } #endif /* USE_X11 */ #else int spacenavd_xdetect_freebsd_shut_up_empty_source_warning; #endif /* __FreeBSD__ */ spacenavd-1.3.1/src/hotplug_linux.c0000664000175000017500000000764014737545500017203 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef __linux__ #include "config.h" #include #include #include #include #include #include #ifdef USE_NETLINK #include #include #include #endif #include "hotplug.h" #include "dev.h" #include "spnavd.h" #include "cfgfile.h" static int con_hotplug(void); static void delay_timeout(int sig); static void poll_timeout(int sig); static int hotplug_fd = -1; static int poll_time, poll_pipe = -1; static int delay_pending, delay_pipe[2] = {-1, -1}; int init_hotplug(void) { if(hotplug_fd != -1) { logmsg(LOG_WARNING, "WARNING: calling init_hotplug while hotplug is running!\n"); return hotplug_fd; } if((hotplug_fd = con_hotplug()) == -1) { int pfd[2]; if(verbose) { logmsg(LOG_WARNING, "hotplug failed will resort to polling\n"); } if(pipe(pfd) == -1) { logmsg(LOG_ERR, "failed to open polling self-pipe: %s\n", strerror(errno)); return -1; } poll_pipe = pfd[1]; hotplug_fd = pfd[0]; poll_time = 1; signal(SIGALRM, poll_timeout); alarm(poll_time); } else { if(pipe(delay_pipe) == -1) { logmsg(LOG_ERR, "failed to open hotplug delay self-pipe: %s\n", strerror(errno)); return -1; } } return hotplug_fd; } void shutdown_hotplug(void) { if(hotplug_fd != -1) { close(hotplug_fd); hotplug_fd = -1; } if(poll_pipe != -1) { close(poll_pipe); poll_pipe = -1; } if(delay_pipe[0] != -1) { close(delay_pipe[0]); close(delay_pipe[1]); delay_pipe[0] = delay_pipe[1] = -1; } } int get_hotplug_fd(void) { return delay_pending ? delay_pipe[0] : hotplug_fd; } int handle_hotplug(void) { char buf[64]; if(poll_pipe != -1 || delay_pending) { delay_pending = 0; read(delay_pipe[0], buf, sizeof buf); if(verbose > 1) { logmsg(LOG_DEBUG, "handle_hotplug: init_devices_usb\n"); } if(init_devices_usb() == -1) { return -1; } return 0; } while(read(hotplug_fd, buf, sizeof buf) > 0); if(verbose > 1) { logmsg(LOG_DEBUG, "handle_hotplug: schedule delayed activation in 1 sec\n"); } /* schedule a delayed trigger to avoid multiple hotplug activations in a row */ delay_pending = 1; signal(SIGALRM, delay_timeout); alarm(1); return 0; } static int con_hotplug(void) { #ifdef USE_NETLINK int s; struct sockaddr_nl addr; if((s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT)) == -1) { logmsg(LOG_ERR, "failed to open hotplug netlink socket: %s\n", strerror(errno)); return -1; } fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK); memset(&addr, 0, sizeof addr); addr.nl_family = AF_NETLINK; addr.nl_pid = getpid(); addr.nl_groups = -1; if(bind(s, (struct sockaddr*)&addr, sizeof addr) == -1) { logmsg(LOG_ERR, "failed to bind to hotplug netlink socket: %s\n", strerror(errno)); close(s); return -1; } return s; #else return -1; #endif /* USE_NETLINK */ } static void delay_timeout(int sig) { write(delay_pipe[1], &sig, 1); } static void poll_timeout(int sig) { signal(sig, poll_timeout); if(sig == SIGALRM) { if(poll_pipe != -1) { write(poll_pipe, &sig, 1); poll_time *= 2; alarm(poll_time); } } } #else int spacenavd_hotplug_linux_shut_up_empty_source_warning; #endif /* __linux__ */ spacenavd-1.3.1/src/dev_serial.h0000664000175000017500000000161114737545500016414 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SPNAV_DEV_SERIAL_H_ #define SPNAV_DEV_SERIAL_H_ struct device; int open_dev_serial(struct device *dev); #endif /* SPNAV_DEV_SERIAL_H_ */ spacenavd-1.3.1/src/logger.c0000664000175000017500000000262014737545500015552 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include "logger.h" static FILE *logfile; static int use_syslog; int start_logfile(const char *fname) { if(!(logfile = fopen(fname, "w"))) { fprintf(stderr, "failed to open log file: %s\n", fname); return -1; } setvbuf(logfile, 0, _IONBF, 0); return fileno(logfile); } int start_syslog(const char *id) { openlog(id, LOG_NDELAY, LOG_DAEMON); use_syslog = 1; return 0; } void logmsg(int prio, const char *fmt, ...) { va_list ap; if(use_syslog) { va_start(ap, fmt); vsyslog(prio, fmt, ap); va_end(ap); } va_start(ap, fmt); vfprintf(logfile ? logfile : stdout, fmt, ap); va_end(ap); } spacenavd-1.3.1/src/xdetect.c0000664000175000017500000000221714737545500015735 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* this must be the inverse of all the other xdetect_*.c ifdefs */ #if !defined(__linux__) && !defined(__FreeBSD__) && !defined(__APPLE__) #include #include "xdetect.h" int xdet_start(void) { return -1; } void xdet_stop(void) { } int xdet_get_fd(void) { return -1; } int handle_xdet_events(fd_set *rset) { return -1; } #else int spacenav_xdetect_none_shut_up_empty_source_warning; #endif spacenavd-1.3.1/src/spnavd_win32.c0000664000175000017500000000262214737545500016612 0ustar nuclearnuclear#ifdef WIN32 #include #include #include "logger.h" static void WINAPI svc_main(unsigned long argc, char **argv); static unsigned long WINAPI svc_handler(unsigned long op, unsigned long evtype, void *evdata, void *cls); static SERVICE_STATUS_HANDLE svc; int main(int argc, char **argv) { SERVICE_TABLE_ENTRY serv_tbl[] = { {"", svc_main}, {0, 0} }; if(!StartServiceCtrlDispatcher(serv_tbl)) { logmsg(LOG_ERR, "failed to start the spacenavd service\n"); return 1; } return 0; } static void WINAPI svc_main(unsigned long argc, char **argv) { SERVICE_STATUS status; svc = RegisterServiceCtrlHandlerEx("spacenavd", svc_handler, 0); status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; status.dwCurrentState = SERVICE_START_PENDING; status.dwControlsAccepted = SERVICE_ACCEPT_STOP; status.dwWin32ExitCode = 0; status.dwCheckPoint = 0; status.dwWaitHint = 2000; SetServiceStatus(svc, &status); /* init */ status.dwCurrentState = SERVICE_RUNNING; SetServiceStatus(svc, &status); } static unsigned long WINAPI svc_handler(unsigned long op, unsigned long evtype, void *evdata, void *cls) { logmsg(LOG_ERR, "received service control message: %ld\n", op); switch(op) { case SERVICE_CONTROL_INTERROGATE: return 0; case SERVICE_CONTROL_STOP: exit(0); default: break; } return ERROR_CALL_NOT_IMPLEMENTED; } #else int spnavd_win32_silcence_empty_file_warnings = 1; #endif spacenavd-1.3.1/src/xdetect_linux.c0000664000175000017500000000741714737545500017163 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef __linux__ #include "config.h" #ifdef USE_X11 #include #include #include #include #include #include #include #include #include "proto_x11.h" #include "spnavd.h" /* TODO implement fallback to polling if inotify is not available */ static int try_xconnect(void); static int fd = -1; static int watch_tmp = -1, watch_x11 = -1; int xdet_start(void) { if((fd = inotify_init()) == -1) { logmsg(LOG_ERR, "failed to create inotify queue: %s\n", strerror(errno)); return -1; } fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); if((watch_x11 = inotify_add_watch(fd, "/tmp/.X11-unix", IN_CREATE)) == -1) { if((watch_tmp = inotify_add_watch(fd, "/tmp", IN_CREATE)) == -1) { logmsg(LOG_ERR, "failed to watch /tmp for file events: %s\n", strerror(errno)); close(fd); fd = -1; return -1; } } if(verbose) { logmsg(LOG_INFO, "waiting for the X socket file to appear\n"); } return fd; } /* this is called by init_x11 if it's successful */ void xdet_stop(void) { if(fd != -1) { if(verbose) { logmsg(LOG_INFO, "stopping X watch\n"); } close(fd); fd = watch_tmp = watch_x11 = -1; } } int xdet_get_fd(void) { return fd; } int handle_xdet_events(fd_set *rset) { char buf[512]; struct inotify_event *ev = (struct inotify_event*)buf; ssize_t res; if(fd == -1 || !FD_ISSET(fd, rset)) { return -1; } for(;;) { if((res = read(fd, buf, sizeof buf)) <= 0) { if(res == 0) { /* kernels before 2.6.14 returned 0 for not enough space */ errno = EINVAL; } if(errno == EINTR) continue; if(errno != EAGAIN) { logmsg(LOG_ERR, "failed to read inotify event: %s\n", strerror(errno)); } return -1; } if(ev->wd == watch_tmp) { if(watch_x11 != -1) { inotify_rm_watch(fd, watch_tmp); continue; } if(ev->len > 0 && strcmp(ev->name, ".X11-unix") == 0) { if((watch_x11 = inotify_add_watch(fd, "/tmp/.X11-unix", IN_CREATE)) == -1) { logmsg(LOG_ERR, "failed to add /tmp/.X11-unix to the watch queue: %s\n", strerror(errno)); continue; } if(try_xconnect() == 0) { return 0; } } } else if(ev->wd == watch_x11) { char *dpystr, sock_file[64]; int dpynum = 0; if((dpystr = getenv("DISPLAY"))) { char *tmp = strchr(dpystr, ':'); if(tmp && isdigit(tmp[1])) { dpynum = atoi(tmp + 1); } } sprintf(sock_file, "X%d", dpynum); if(ev->len > 0 && strcmp(ev->name, sock_file) == 0) { if(verbose) { logmsg(LOG_INFO, "found X socket, will now attempt to connect to the X server\n"); } if(try_xconnect() == 0) { return 0; } logmsg(LOG_ERR, "found X socket yet failed to connect\n"); } } } return -1; } static int try_xconnect(void) { int i; /* poll for approximately 15 seconds (well a bit more than that) */ for(i=0; i<15; i++) { sleep(1); if(init_x11() != -1) { return 0; /* success */ } } return -1; } #endif /* USE_X11 */ #else int spacenavd_xdetect_linux_shut_up_empty_source_warning; #endif /* __linux__ */ spacenavd-1.3.1/src/client.h0000664000175000017500000000457114737545500015565 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef CLIENT_H_ #define CLIENT_H_ #include "config.h" #ifdef USE_X11 #include #endif #include "proto.h" /* client types */ enum { CLIENT_X11, /* through the magellan X11 protocol */ CLIENT_UNIX /* through the new UNIX domain socket */ }; /* event selection (must match SPNAV_EVMASK* in libspnav/spnav.h) */ enum { EVMASK_MOTION = 0x01, EVMASK_BUTTON = 0x02, EVMASK_DEV = 0x04, EVMASK_CFG = 0x08, EVMASK_RAWAXIS = 0x10, EVMASK_RAWBUTTON = 0x20 }; struct device; struct client { int type; int sock; /* UNIX domain socket */ int proto; /* protocol version */ #ifdef USE_X11 Window win; /* X11 client window */ #endif float sens; /* sensitivity */ struct device *dev; char *name; /* client name (not unique) */ unsigned int evmask; /* event selection mask */ char reqbuf[64]; int reqbytes; /* protocol buffer for handling reception of strings in multiple packets */ struct reqresp_strbuf strbuf; struct client *next; }; struct client *add_client(int type, void *cdata); void remove_client(struct client *client); void free_client(struct client *client); int get_client_type(struct client *client); int get_client_socket(struct client *client); #ifdef USE_X11 Window get_client_window(struct client *client); #endif void set_client_sensitivity(struct client *client, float sens); float get_client_sensitivity(struct client *client); void set_client_device(struct client *client, struct device *dev); struct device *get_client_device(struct client *client); /* these two can be used to iterate over all clients */ struct client *first_client(void); struct client *next_client(void); #endif /* CLIENT_H_ */ spacenavd-1.3.1/src/dev_usb.c0000664000175000017500000000336014737545500015724 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #include #include #include #include "dev_usb.h" #ifdef HAVE_ALLOCA_H #include #endif #ifdef HAVE_MALLOC_H #include #endif #include "logger.h" void free_usb_devices_list(struct usb_dev_info *list) { while(list) { int i; struct usb_dev_info *tmp = list; list = list->next; free(tmp->name); for(i=0; inum_devfiles; i++) { free(tmp->devfiles[i]); } free(tmp); } } void print_usb_device_info(struct usb_dev_info *devinfo) { int i, sz = 64; char *devname, *buf, *s; devname = devinfo->name ? devinfo->name : "unknown"; sz += strlen(devname); for(i=0; inum_devfiles; i++) { sz += strlen(devinfo->devfiles[i]) + 1; } s = buf = alloca(sz); s += sprintf(s, "[%x:%x]: \"%s\" (", devinfo->vendorid, devinfo->productid, devinfo->name ? devinfo->name : "unknown"); for(i=0; inum_devfiles; i++) { s += sprintf(s, "%s ", devinfo->devfiles[i]); } sprintf(s, ")\n"); logmsg(LOG_INFO, buf); } spacenavd-1.3.1/src/dev_usb_linux.c0000664000175000017500000004000014737545500017133 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef __linux__ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "dev.h" #include "dev_usb.h" #include "spnavd.h" #include "event.h" #include "hotplug.h" #include "client.h" #define DEF_MINVAL (-500) #define DEF_MAXVAL 500 #define DEF_RANGE (DEF_MAXVAL - DEF_MINVAL) #define IS_DEV_OPEN(dev) ((dev)->fd >= 0) /* sometimes the rotation events are missing from linux/input.h */ #ifndef REL_RX #define REL_RX 3 #endif #ifndef REL_RY #define REL_RY 4 #endif #ifndef REL_RZ #define REL_RZ 5 #endif /* apparently some old versions of input.h do not define EV_SYN */ #ifndef EV_SYN #define EV_SYN 0 #endif static void close_evdev(struct device *dev); static int read_evdev(struct device *dev, struct dev_input *inp); static void set_led_evdev(struct device *dev, int state); int open_dev_usb(struct device *dev) { int i, axes_rel = 0, axes_abs = 0; struct input_absinfo absinfo; unsigned char evtype_mask[((EV_MAX | KEY_MAX) + 7) / 8]; if((dev->fd = open(dev->path, O_RDWR)) == -1) { if((dev->fd = open(dev->path, O_RDONLY)) == -1) { logmsg(LOG_ERR, "failed to open device: %s\n", strerror(errno)); return -1; } logmsg(LOG_WARNING, "opened device read-only, LEDs won't work\n"); } if(ioctl(dev->fd, EVIOCGNAME(sizeof dev->name), dev->name) == -1) { logmsg(LOG_WARNING, "EVIOCGNAME ioctl failed: %s\n", strerror(errno)); strcpy(dev->name, "unknown"); } logmsg(LOG_INFO, "device name: %s\n", dev->name); /* get number of axes */ if(ioctl(dev->fd, EVIOCGBIT(EV_ABS, sizeof evtype_mask), evtype_mask) != -1) { for(i=0; ifd, EVIOCGBIT(EV_REL, sizeof evtype_mask), evtype_mask) != -1) { for(i=0; inum_axes = axes_rel + axes_abs; if(!dev->num_axes) { if(dev->usbid[0] == VID_3DCONN && dev->usbid[1] == PID_WIRELESS) { /* a wireless 3Dconnexion device without axes is probably one of the * CadMouse products, drop it. */ logmsg(LOG_DEBUG, "No axes detected, probably a CadMouse, dropping\n"); close(dev->fd); dev->fd = -1; return -1; } logmsg(LOG_WARNING, "failed to retrieve number of axes. assuming 6\n"); dev->num_axes = 6; } else { if(verbose) { logmsg(LOG_INFO, " Number of axes: %d (%da %dr)\n", dev->num_axes, axes_abs, axes_rel); } } /* get number of buttons */ dev->bnbase = -1; dev->num_buttons = 0; if(ioctl(dev->fd, EVIOCGBIT(EV_KEY, sizeof evtype_mask), evtype_mask) != -1) { for(i=0; ibnbase < 0) { dev->bnbase = idx * 8 + bit; } dev->num_buttons++; } } } else { logmsg(LOG_DEBUG, "EVIOCGBIT(EV_KEY) ioctl failed: %s\n", strerror(errno)); } if(dev->bnbase == -1) { dev->bnbase = 0; } /* for devices marked for disjointed button range remapping, log the button * count reported before applying the remapping function */ if(dev->bnhack) { logmsg(LOG_DEBUG, "Device %04x:%04x reports %d buttons before disjointed button remapping\n", dev->usbid[0], dev->usbid[1], dev->num_buttons); } if(dev->bnhack) { dev->num_buttons = dev->bnhack(-1); } else { if(dev->usbid[0] == VID_3DCONN && dev->usbid[1] == PID_WIRELESS) { /* Wireless devices use the same dongle, try to guess which actual * device this is, and apply the button hack if it's a SpcMouse Pro */ if(dev->num_buttons > 2) { dev->type = DEV_SMPROW; dev->bnhack = bnhack_smpro; dev->num_buttons = bnhack_smpro(-1); strcpy(dev->name, "3Dconnexion SpaceMouse Pro Wireless (guess)"); } else { dev->type = DEV_SMW; strcpy(dev->name, "3Dconnexion SpaceMouse Wireless (guess)"); } } } if(!dev->num_buttons) { logmsg(LOG_WARNING, "failed to retrieve number of buttons, will default to 2\n"); dev->num_buttons = 2; } else { if(verbose) { logmsg(LOG_INFO, " Number of buttons: %d (evdev offset: %d)\n", dev->num_buttons, dev->bnbase); } } dev->minval = malloc(dev->num_axes * sizeof *dev->minval); dev->maxval = malloc(dev->num_axes * sizeof *dev->maxval); dev->fuzz = malloc(dev->num_axes * sizeof *dev->fuzz); if(!dev->minval || !dev->maxval || !dev->fuzz) { logmsg(LOG_ERR, "failed to allocate memory: %s\n", strerror(errno)); return -1; } /* if the device is an absolute device, find the minimum and maximum axis values */ for(i=0; inum_axes; i++) { dev->minval[i] = DEF_MINVAL; dev->maxval[i] = DEF_MAXVAL; dev->fuzz[i] = 0; if(ioctl(dev->fd, EVIOCGABS(i), &absinfo) == 0) { dev->minval[i] = absinfo.minimum; dev->maxval[i] = absinfo.maximum; dev->fuzz[i] = absinfo.fuzz; if(verbose) { logmsg(LOG_INFO, " Axis %d value range: %d - %d (fuzz: %d)\n", i, dev->minval[i], dev->maxval[i], dev->fuzz[i]); } } } if(cfg.grab_device) { int grab = 1; /* try to grab the device */ if(ioctl(dev->fd, EVIOCGRAB, &grab) == -1) { logmsg(LOG_WARNING, "failed to grab the device: %s\n", strerror(errno)); } } /* set non-blocking */ fcntl(dev->fd, F_SETFL, fcntl(dev->fd, F_GETFL) | O_NONBLOCK); if(cfg.led == LED_ON || (cfg.led == LED_AUTO && first_client())) { set_led_evdev(dev, 1); } else { /* Some devices start with the LED enabled, make sure to turn it off * explicitly if necessary. * * XXX G.Ebner reports that some devices (SpaceMouse Compact at least) * fail to turn their LED off at startup if it's not turned explicitly * on first. We'll need to investigate further, but it doesn't seem to * cause any visible blinking, so let's leave the redundant call to * enable it first for now. See github pull request #39: * https://github.com/FreeSpacenav/spacenavd/pull/39 */ set_led_evdev(dev, 1); set_led_evdev(dev, 0); } /* fill the device function pointers */ dev->close = close_evdev; dev->read = read_evdev; dev->set_led = set_led_evdev; return 0; } static void close_evdev(struct device *dev) { if(IS_DEV_OPEN(dev)) { dev->set_led(dev, 0); close(dev->fd); dev->fd = -1; free(dev->minval); free(dev->maxval); free(dev->fuzz); } } static INLINE int map_range(struct device *dev, int axidx, int val) { int range = dev->maxval[axidx] - dev->minval[axidx]; if(range <= 0) { return val; } return (val - dev->minval[axidx]) * DEF_RANGE / range + DEF_MINVAL; } static int read_evdev(struct device *dev, struct dev_input *inp) { struct input_event iev; /* linux evdev event */ int rdbytes; if(!IS_DEV_OPEN(dev)) return -1; do { rdbytes = read(dev->fd, &iev, sizeof iev); } while(rdbytes == -1 && errno == EINTR); /* disconnect? */ if(rdbytes == -1) { if(errno != EAGAIN) { logmsg(LOG_ERR, "read error: %s\n", strerror(errno)); remove_device(dev); } return -1; } if(rdbytes > 0) { switch(iev.type) { case EV_REL: inp->type = INP_MOTION; inp->idx = iev.code - REL_X; inp->val = iev.value; /*printf("[%s] EV_REL(%d): %d\n", dev->name, inp->idx, iev.value);*/ break; case EV_ABS: inp->type = INP_MOTION; inp->idx = iev.code - ABS_X; inp->val = map_range(dev, inp->idx, iev.value); /*printf("[%s] EV_ABS(%d): %d (orig: %d)\n", dev->name, inp->idx, inp->val, iev.value);*/ break; case EV_KEY: inp->type = INP_BUTTON; if(dev->bnhack) { /* for problematic devices, remap button numbers to a contiguous range */ if((inp->idx = dev->bnhack(iev.code)) == -1) { return -1; } } else { inp->idx = iev.code - dev->bnbase; } inp->val = iev.value; /*logmsg(LOG_DEBUG, "EV_KEY c:%d (%d) v:%d\n", iev.code, inp->idx, iev.value);*/ break; case EV_SYN: inp->type = INP_FLUSH; /*printf("[%s] EV_SYN\n", dev->name);*/ break; case EV_MSC: /* don't know what to do with these MSC events, the spacemouse enterprise * sends them on every button press. Silently ignore them for now. */ return -1; default: if(verbose > 1) { logmsg(LOG_DEBUG, "unhandled event: %d\n", iev.type); } return -1; } } return 0; } static void set_led_evdev(struct device *dev, int state) { struct input_event ev; if(!IS_DEV_OPEN(dev)) return; memset(&ev, 0, sizeof ev); ev.type = EV_LED; ev.code = LED_MISC; ev.value = state; if(write(dev->fd, &ev, sizeof ev) == -1) { logmsg(LOG_WARNING, "failed to turn LED %s\n", state ? "on" : "off"); } } #define PROC_DEV "/proc/bus/input/devices" struct usb_dev_info *find_usb_devices(int (*match)(const struct usb_dev_info*)) { struct usb_dev_info *devlist = 0, devinfo; int i, buf_used, buf_len, bytes_read; char buf[1024]; char *buf_pos, *section_start, *next_section = 0, *cur_line, *next_line; FILE *fp; DIR *dir; struct dirent *dent; if(verbose > 1) { logmsg(LOG_INFO, "Device detection, parsing " PROC_DEV "\n"); } devlist = 0; buf_pos = buf; buf_len = sizeof(buf) - 1; if(!(fp = fopen(PROC_DEV, "r"))) { if(verbose) { logmsg(LOG_ERR, "failed to open " PROC_DEV ": %s\n", strerror(errno)); } goto alt_detect; } while((bytes_read = fread(buf_pos, 1, buf_len, fp)) >= 0) { buf_pos[bytes_read] = '\0'; section_start = buf; for(;;) { char *keyptr; next_section = strstr(section_start, "\n\n"); if(next_section == NULL) { /* move last (partial) section to start of buf */ /* sizeof(buf) - 1 because the last one is '\0' */ buf_used = strlen(section_start); memmove(buf, section_start, buf_used); buf[buf_used] = '\0'; /* point to end of last section and calc remaining space in buf */ buf_pos = buf + buf_used; buf_len = sizeof(buf) - buf_used; /* break to read from file again */ break; } /* set second newline to terminating null */ next_section[1] = 0; /* point to start of next section */ next_section += 2; memset(&devinfo, 0, sizeof devinfo); cur_line = section_start; while (*cur_line) { next_line = strchr(cur_line, '\n'); *next_line = 0; next_line++; switch (*cur_line) { case 'I': keyptr = strstr(cur_line, "Vendor="); if(keyptr) { char *endp, *valptr = keyptr + strlen("Vendor="); devinfo.vendorid = strtol(valptr, &endp, 16); } keyptr = strstr(cur_line, "Product="); if(keyptr) { char *endp, *valptr = keyptr + strlen("Product="); devinfo.productid = strtol(valptr, &endp, 16); } break; case 'N': keyptr = strstr(cur_line, "Name=\""); if(keyptr) { char *valptr = keyptr + strlen("Name=\""); char *endp = strrchr(cur_line, '"'); if(endp) { *endp = 0; } if(!(devinfo.name = strdup(valptr))) { logmsg(LOG_ERR, "failed to allocate the device name buffer for: %s: %s\n", valptr, strerror(errno)); } } break; case 'H': keyptr = strstr(cur_line, "Handlers="); if(keyptr) { char *devfile = 0, *valptr = keyptr + strlen("Handlers="); static const char *prefix = "/dev/input/"; int idx = 0; while((devfile = strtok(devfile ? 0 : valptr, " \t\v\n\r"))) { if(strstr(devfile, "event") != devfile) { /* ignore everything which isn't an event interface device */ continue; } if(!(devinfo.devfiles[idx] = malloc(strlen(devfile) + strlen(prefix) + 1))) { logmsg(LOG_ERR, "failed to allocate device filename buffer: %s\n", strerror(errno)); continue; } sprintf(devinfo.devfiles[idx++], "%s%s", prefix, devfile); } devinfo.num_devfiles = idx; } break; } cur_line = next_line; } /* check with the user-supplied matching callback to see if we should include * this device in the returned list or not... */ if(devinfo.num_devfiles > 0 && (!match || match(&devinfo))) { /* add it to the list */ struct usb_dev_info *node = malloc(sizeof *node); if(node) { if(verbose > 1 || (verbose && !dev_path_in_use(devinfo.devfiles[0]))) { logmsg(LOG_INFO, "found usb device [%x:%x]: \"%s\" (%s) \n", devinfo.vendorid, devinfo.productid, devinfo.name ? devinfo.name : "unknown", devinfo.devfiles[0]); } *node = devinfo; memset(&devinfo, 0, sizeof devinfo); node->next = devlist; devlist = node; } else { logmsg(LOG_ERR, "failed to allocate usb device info node: %s\n", strerror(errno)); } } else { /* cleanup devinfo before moving to the next line */ for(i=0; i 1) { logmsg(LOG_INFO, "trying alternative detection, querying /dev/input/ devices...\n"); } /* if for some reason we can't open the /proc/bus/input/devices file, or we * couldn't find our device there, we'll try opening all /dev/input/ * devices, and see if anyone matches our predicate */ if(!(dir = opendir("/dev/input"))) { logmsg(LOG_ERR, "failed to open /dev/input/ directory: %s\n", strerror(errno)); return 0; } while((dent = readdir(dir))) { int fd; struct stat st; struct input_id id; memset(&devinfo, 0, sizeof devinfo); if(!(devinfo.devfiles[0] = malloc(strlen(dent->d_name) + strlen("/dev/input/") + 1))) { logmsg(LOG_ERR, "failed to allocate device file name: %s\n", strerror(errno)); continue; } sprintf(devinfo.devfiles[0], "/dev/input/%s", dent->d_name); devinfo.num_devfiles = 1; if(verbose > 1) { logmsg(LOG_INFO, " trying \"%s\" ... \n", devinfo.devfiles[0]); } if(stat(devinfo.devfiles[0], &st) == -1 || !S_ISCHR(st.st_mode)) { free(devinfo.devfiles[0]); continue; } if((fd = open(devinfo.devfiles[0], O_RDONLY)) == -1) { logmsg(LOG_ERR, "failed to open %s: %s\n", devinfo.devfiles[0], strerror(errno)); free(devinfo.devfiles[0]); continue; } if(ioctl(fd, EVIOCGID, &id) != -1) { devinfo.vendorid = id.vendor; devinfo.productid = id.product; } if(ioctl(fd, EVIOCGNAME(sizeof buf), buf) != -1) { if(!(devinfo.name = strdup(buf))) { logmsg(LOG_ERR, "failed to allocate device name buffer: %s\n", strerror(errno)); close(fd); free(devinfo.devfiles[0]); continue; } } if(!match || match(&devinfo)) { struct usb_dev_info *node = malloc(sizeof *node); if(node) { if(verbose > 1 || (verbose && !dev_path_in_use(devinfo.devfiles[0]))) { logmsg(LOG_INFO, "found usb device [%x:%x]: \"%s\" (%s) \n", devinfo.vendorid, devinfo.productid, devinfo.name ? devinfo.name : "unknown", devinfo.devfiles[0]); } *node = devinfo; node->next = devlist; devlist = node; } else { free(devinfo.name); free(devinfo.devfiles[0]); logmsg(LOG_ERR, "failed to allocate usb device info: %s\n", strerror(errno)); } } else { free(devinfo.name); free(devinfo.devfiles[0]); } close(fd); } closedir(dir); return devlist; } #else int spacenavd_dev_usb_linux_silence_empty_warning; #endif /* __linux__ */ spacenavd-1.3.1/src/kbemu.c0000664000175000017500000000432014737545500015375 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #ifdef USE_X11 #include #include #include "logger.h" #include "kbemu.h" #ifdef HAVE_XTEST_H #include static int use_xtest; #endif static Display *dpy; void kbemu_set_display(Display *d) { dpy = d; if(d) { #ifdef HAVE_XTEST_H int tmp; use_xtest = XTestQueryExtension(dpy, &tmp, &tmp, &tmp, &tmp); if(use_xtest) logmsg(LOG_DEBUG, "Using XTEST to send key events\n"); else #endif logmsg(LOG_DEBUG, "Using XSendEvent to send key events\n"); } } KeySym kbemu_keysym(const char *str) { return XStringToKeysym(str); } const char *kbemu_keyname(KeySym sym) { return XKeysymToString(sym); } void send_kbevent(KeySym key, int press) { XEvent xevent; Window win; int rev_state; if(!dpy) return; #ifdef HAVE_XTEST_H if(use_xtest) { XTestFakeKeyEvent(dpy, XKeysymToKeycode(dpy, key), press, 0); XFlush(dpy); return; } #endif XGetInputFocus(dpy, &win, &rev_state); xevent.type = press ? KeyPress : KeyRelease; xevent.xkey.display = dpy; xevent.xkey.root = DefaultRootWindow(dpy); xevent.xkey.window = win; xevent.xkey.subwindow = None; xevent.xkey.keycode = XKeysymToKeycode(dpy, key); xevent.xkey.state = 0; xevent.xkey.time = CurrentTime; xevent.xkey.x = xevent.xkey.y = 1; xevent.xkey.x_root = xevent.xkey.y_root = 1; XSendEvent(dpy, win, True, press ? KeyPressMask : KeyReleaseMask, &xevent); XFlush(dpy); } #else int spacenavd_kbemu_shut_up_empty_source_warning; #endif /* USE_X11 */ spacenavd-1.3.1/src/dev_usb_freebsd.c0000664000175000017500000001572114737545500017422 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef __FreeBSD__ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "spnavd.h" #include "client.h" #include "dev.h" #include "dev_usb.h" #include "event.h" #define AXES 6 #define IS_DEV_OPEN(dev) ((dev)->fd >= 0) static void close_hid(struct device *dev) { if(IS_DEV_OPEN(dev)) { dev->set_led(dev, 0); close(dev->fd); dev->fd = -1; } } static void set_led_hid(struct device *dev, int state) { struct usb_gen_descriptor d = {0}; uint8_t buf[2]; /* * TODO: Get stuff from report descriptor: * - Is there an LED? * - How big is the message? * - What bits should be set? */ if(!IS_DEV_OPEN(dev)) return; buf[0] = 4; buf[1] = state ? 15 : 0; d.ugd_data = buf; d.ugd_maxlen = sizeof buf; d.ugd_report_type = UHID_OUTPUT_REPORT; if(ioctl(dev->fd, USB_SET_REPORT, &d) == -1) { logmsg(LOG_ERR, "Unable to set LED: %s\n", strerror(errno)); } } static uint32_t button_event(struct dev_input *inp, uint32_t last, uint32_t curr) { uint32_t new; int b; new = last ^ curr; b = ffs(new) - 1; if(new) { inp->type = INP_BUTTON; inp->idx = b; inp->val = (curr >> b) & 1; last ^= (1 << b); } return last; } static uint32_t axis_event(struct dev_input *inp, int16_t *curr, unsigned int *flush) { int axis; if((axis = ffs(*flush)) > 0) { axis--; *flush &= ~(1 << axis); if(axis < AXES) { inp->type = INP_MOTION; inp->idx = axis; inp->val = curr[axis]; return 0; } inp->type = INP_FLUSH; return 0; } return -1; } static int read_hid(struct device *dev, struct dev_input *inp) { uint8_t iev[1024]; int rdbytes; int i; static uint32_t last_buttons; static uint32_t curr_buttons; static int16_t curr_pos[AXES]; static unsigned int flush = 0; if(!IS_DEV_OPEN(dev)) return -1; if(last_buttons != curr_buttons) { last_buttons = button_event(inp, last_buttons, curr_buttons); return 0; } if(axis_event(inp, curr_pos, &flush) == 0) { return 0; } do { rdbytes = read(dev->fd, &iev, sizeof iev); } while(rdbytes == -1 && errno == EINTR); /* disconnect? */ if(rdbytes == -1) { if(errno != EAGAIN) { logmsg(LOG_ERR, "read error: %s\n", strerror(errno)); remove_device(dev); } return -1; } if(rdbytes > 0) { switch(iev[0]) { case 1: /* Three axis... X, Y, Z */ flush = 0x40; for(i=0; ifd = open(dev->path, O_RDWR | O_NONBLOCK)) == -1) { if((dev->fd = open(dev->path, O_RDONLY | O_NONBLOCK)) == -1) { logmsg(LOG_ERR, "failed to open device: %s\n", strerror(errno)); return -1; } logmsg(LOG_WARNING, "opened device read-only, LEDs won't work\n"); } if(cfg.led == LED_ON || (cfg.led == LED_AUTO && first_client())) { set_led_hid(dev, 1); } else { /* Some devices start with the LED enabled, make sure to turn it off * explicitly if necessary. * * XXX G.Ebner reports that some devices (SpaceMouse Compact at least) * fail to turn their LED off at startup if it's not turned explicitly * on first. We'll need to investigate further, but it doesn't seem to * cause any visible blinking, so let's leave the redundant call to * enable it first for now. See github pull request #39: * https://github.com/FreeSpacenav/spacenavd/pull/39 */ set_led_hid(dev, 1); set_led_hid(dev, 0); } /* fill the device function pointers */ dev->close = close_hid; dev->read = read_hid; dev->set_led = set_led_hid; /* TODO until we flesh out the USB code on FreeBSD, let's fill the structure * with fake but plausible information. */ dev->bnbase = 0; dev->num_buttons = 2; dev->num_axes = 6; return 0; } struct usb_dev_info *find_usb_devices(int (*match)(const struct usb_dev_info*)) { struct usb_dev_info *node, *devlist = 0; struct usb_device_info devinfo; glob_t gl; size_t si; int fd; if(verbose) { logmsg(LOG_INFO, "Device detection, checking \"/dev/uhid*\"\n"); } if(glob("/dev/uhid*", 0, NULL, &gl) != 0) { return devlist; } for(si=0; sivendorid = devinfo.udi_vendorNo; node->productid = devinfo.udi_productNo; node->name = strdup(devinfo.udi_product); if(node->name != NULL) { node->devfiles[0] = strdup(gl.gl_pathv[si]); if(node->devfiles[0] != NULL) { node->num_devfiles = 1; } } } if(!node || !node->num_devfiles) { logmsg(LOG_ERR, "failed to allocate usb device info node: %s\n", strerror(errno)); free_usb_devices_list(node); } else if(verbose) { logmsg(LOG_INFO, "found usb device [%x:%x]: \"%s\" (%s) \n", node->vendorid, node->productid, node->name ? node->name : "unknown", node->devfiles[0]); } if(!match || match(node)) { if(verbose) { logmsg(LOG_INFO, "found usb device: "); print_usb_device_info(node); } node->next = devlist; devlist = node; } } close(fd); } globfree(&gl); return devlist; } #else int spacenavd_dev_usb_freebsd_silence_empty_warning; #endif /* __FreeBSD__ */ spacenavd-1.3.1/src/dev_usb_darwin.c0000664000175000017500000000540114737545500017266 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #if defined(__APPLE__) && defined(__MACH__) #include "config.h" #include #include #include #include #include #include "spnavd.h" #include "dev.h" #include "dev_usb.h" int open_dev_usb(struct device *dev) { return -1; } struct usb_dev_info *find_usb_devices(int (*match)(const struct usb_dev_info*)) { struct usb_dev_info *devlist = 0; struct usb_dev_info devinfo; /*static const int vendor_id = 1133;*/ /* 3dconnexion */ static char dev_path[512]; io_object_t dev; io_iterator_t iter; CFMutableDictionaryRef match_dict; CFNumberRef number_ref; match_dict = IOServiceMatching(kIOHIDDeviceKey); /* add filter rule: vendor-id should be 3dconnexion's */ /*number_ref = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &vendor_id); CFDictionarySetValue(match_dict, CFSTR(kIOHIDVendorIDKey), number_ref); CFRelease(number_ref); */ /* fetch... */ if(IOServiceGetMatchingServices(kIOMasterPortDefault, match_dict, &iter) != kIOReturnSuccess) { logmsg(LOG_ERR, "failed to retrieve USB HID devices\n"); return 0; } while((dev = IOIteratorNext(iter))) { memset(&devinfo, 0, sizeof devinfo); IORegistryEntryGetPath(dev, kIOServicePlane, dev_path); if(!(devinfo.devfiles[0] = strdup(dev_path))) { logmsg(LOG_ERR, "failed to allocate device file path buffer: %s\n", strerror(errno)); continue; } devinfo.num_devfiles = 1; /* TODO retrieve vendor id and product id */ if(!match || match(&devinfo)) { struct usb_dev_info *node = malloc(sizeof *node); if(node) { if(verbose) { logmsg(LOG_INFO, "found usb device: "); print_usb_device_info(&devinfo); } *node = devinfo; node->next = devlist; devlist = node; } else { free(devinfo.devfiles[0]); logmsg(LOG_ERR, "failed to allocate usb device info node: %s\n", strerror(errno)); } } } IOObjectRelease(dev); IOObjectRelease(iter); return devlist; } #else int spacenavd_dev_usb_darwin_silence_empty_warning; #endif /* __APPLE__ && __MACH__ */ spacenavd-1.3.1/src/spnavd.c0000664000175000017500000002644114737545500015575 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "spnavd.h" #include "logger.h" #include "dev.h" #include "hotplug.h" #include "client.h" #include "proto_unix.h" #ifdef USE_X11 #include "proto_x11.h" #endif static void print_usage(const char *argv0); static void cleanup(void); static void redir_log(int fallback_syslog); static void daemonize(void); static int write_pid_file(void); static int find_running_daemon(void); static void handle_events(fd_set *rset); static void sig_handler(int s); static char *fix_path(char *str); char *cfgfile = DEF_CFGFILE; static char *logfile = DEF_LOGFILE; static int pfd[2]; int main(int argc, char **argv) { int i, pid, ret, become_daemon = 1; int force_logfile = 0; for(i=1; i max_fd) max_fd = fd; } dev = dev->next; } if((fd = get_hotplug_fd()) != -1) { FD_SET(fd, &rset); if(fd > max_fd) max_fd = fd; } /* the UNIX domain socket listening for connections */ if((fd = get_unix_socket()) != -1) { FD_SET(fd, &rset); if(fd > max_fd) max_fd = fd; } /* all the UNIX socket clients */ client_iter = first_client(); while(client_iter) { if(get_client_type(client_iter) == CLIENT_UNIX) { int s = get_client_socket(client_iter); assert(s >= 0); FD_SET(s, &rset); if(s > max_fd) max_fd = s; } client_iter = next_client(); } /* and the X server socket */ #ifdef USE_X11 if((fd = get_x11_socket()) != -1) { FD_SET(fd, &rset); if(fd > max_fd) max_fd = fd; } #endif /* also the self-pipe read-end for safe SIGHUP handling */ FD_SET(pfd[0], &rset); if(pfd[0] > max_fd) max_fd = fd; do { /* if there is at least one device out of the deadzone and repeat is enabled * wait for only as long as specified in cfg.repeat_msec */ struct timeval tv, *timeout = 0; if(cfg.repeat_msec >= 0) { dev = get_devices(); while(dev) { if(is_device_valid(dev) && !in_deadzone(dev)) { tv.tv_sec = cfg.repeat_msec / 1000; tv.tv_usec = cfg.repeat_msec % 1000; timeout = &tv; break; } dev = dev->next; } } ret = select(max_fd + 1, &rset, 0, 0, timeout); } while(ret == -1 && errno == EINTR); if(ret > 0) { handle_events(&rset); } else { if(cfg.repeat_msec >= 0) { dev = get_devices(); while(dev) { if(!in_deadzone(dev)) { repeat_last_event(dev); } dev = dev->next; } } } } return 0; /* unreachable */ } static void print_usage(const char *argv0) { printf("usage: %s [options]\n", argv0); printf("options:\n"); printf(" -d: do not daemonize\n"); printf(" -c : config file path (default: " DEF_CFGFILE ")\n"); printf(" -l |syslog: log file path or log to syslog (default: " DEF_LOGFILE ")\n"); printf(" -v: verbose output (use multiple times for greater effect)\n"); printf(" -V,-version: print version number and exit\n"); printf(" -h,-help: print usage information and exit\n"); } static void cleanup(void) { struct device *dev; #ifdef USE_X11 close_x11(); /* call to avoid leaving garbage in the X server's root windows */ #endif close_unix(); shutdown_hotplug(); dev = get_devices(); while(dev) { struct device *tmp = dev; dev = dev->next; remove_device(tmp); } remove(PIDFILE); } static void redir_log(int fallback_syslog) { int i, fd = -1; if(logfile) { fd = start_logfile(logfile); } if(fd >= 0 || fallback_syslog) { /* redirect standard input/output/error * best effort attempt to make either the logfile or the syslog socket * accessible through stdout/stderr, just in case any printfs survived * the logmsg conversion. */ for(i=0; i<3; i++) { close(i); } open("/dev/zero", O_RDONLY); if(fd == -1) { fd = start_syslog(SYSLOG_ID); dup(1); /* not guaranteed to work */ } else { dup(fd); } } setvbuf(stdout, 0, _IOLBF, 0); setvbuf(stderr, 0, _IONBF, 0); } static void daemonize(void) { int pid; chdir("/"); redir_log(1); /* release controlling terminal */ if((pid = fork()) == -1) { perror("failed to fork"); exit(1); } else if(pid) { exit(0); } setsid(); } static int write_pid_file(void) { FILE *fp; int pid = getpid(); if(!(fp = fopen(PIDFILE, "w"))) { return -1; } fprintf(fp, "%d\n", pid); fclose(fp); return 0; } static int find_running_daemon(void) { FILE *fp; int s, pid; struct sockaddr_un addr; /* try to open the pid-file */ if(!(fp = fopen(PIDFILE, "r"))) { return -1; } if(fscanf(fp, "%d\n", &pid) != 1) { fclose(fp); return -1; } fclose(fp); /* make sure it's not just a stale pid-file */ if((s = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) { return -1; } memset(&addr, 0, sizeof addr); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, SOCK_NAME, sizeof addr.sun_path); if(connect(s, (struct sockaddr*)&addr, sizeof addr) == -1) { close(s); return -1; } /* managed to connect alright, it's running... */ close(s); return pid; } static void handle_events(fd_set *rset) { int dev_fd, hotplug_fd; struct device *dev; struct dev_input inp; /* handle signal pipe */ if(FD_ISSET(pfd[0], rset)) { int tmp; read(pfd[0], &tmp, sizeof tmp); /* eat up the junk char */ read_cfg(cfgfile, &cfg); cfg_changed(); } /* handle anything coming through the UNIX socket */ handle_uevents(rset); #ifdef USE_X11 /* handle any X11 events (magellan protocol) */ handle_xevents(rset); #endif /* finally read any pending device input data */ dev = get_devices(); while(dev) { /* keep the next pointer because read_device can potentially destroy * the device node if the read fails. */ struct device *next = dev->next; if((dev_fd = get_device_fd(dev)) != -1 && FD_ISSET(dev_fd, rset)) { /* read an event from the device ... */ while(read_device(dev, &inp) != -1) { /* ... and process it, possibly dispatching a spacenav event to clients */ process_input(dev, &inp); } /* flush any pending events if we run out of input */ inp.type = INP_FLUSH; process_input(dev, &inp); } dev = next; } if((hotplug_fd = get_hotplug_fd()) != -1) { if(FD_ISSET(hotplug_fd, rset)) { handle_hotplug(); } } } void cfg_changed(void) { if(cfg.led != prev_cfg.led) { struct device *dev = get_devices(); while(dev) { if(is_device_valid(dev)) { if(verbose) { logmsg(LOG_INFO, "led %s, device: %s\n", cfg.led ? (cfg.led == LED_AUTO ? "auto" : "on"): "off", dev->name); } if(cfg.led == LED_ON || (cfg.led == LED_AUTO && first_client())) { set_device_led(dev, 1); } else { set_device_led(dev, 0); } } dev = dev->next; } } if(strcmp(cfg.serial_dev, prev_cfg.serial_dev) != 0) { struct device *dev, *iter = get_devices(); while(iter) { dev = iter; iter = iter->next; if(strcmp(dev->path, prev_cfg.serial_dev) == 0) { remove_device(dev); } } init_devices_serial(); } prev_cfg = cfg; } /* signals usr1 & usr2 are sent by the spnav_x11 script to start/stop the * daemon's connection to the X server. */ static void sig_handler(int s) { switch(s) { case SIGHUP: write(pfd[1], &s, 1); /* write *something* to the pipe to trigger a re-read */ break; case SIGSEGV: logmsg(LOG_ERR, "Segmentation fault caught, trying to exit gracefully\n"); case SIGINT: case SIGTERM: exit(0); #ifdef USE_X11 case SIGUSR1: init_x11(); break; case SIGUSR2: close_x11(); break; #endif default: break; } } static char *fix_path(char *str) { char *buf, *tmp; int sz, len; if(str[0] == '/') return str; len = strlen(str) + 1; /* +1 for the path separator */ sz = PATH_MAX; if(!(buf = malloc(sz + len))) { perror("failed to allocate path buffer"); return 0; } while(!getcwd(buf, sz)) { if(errno == ERANGE) { sz *= 2; if(!(tmp = realloc(buf, sz + len))) { perror("failed to reallocate path buffer"); free(buf); return 0; } buf = tmp; } else { perror("getcwd failed"); free(buf); return 0; } } sprintf(buf + strlen(buf), "/%s", str); return buf; } spacenavd-1.3.1/src/proto_unix.c0000664000175000017500000003654014737545500016511 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "proto.h" #include "proto_unix.h" #include "spnavd.h" #ifdef USE_X11 #include "kbemu.h" #endif #ifndef isfinite #define isfinite(x) (!isnan(x)) #endif static int lsock = -1; static int handle_request(struct client *c, struct reqresp *req); static const char *reqstr(int req); int init_unix(void) { int s; mode_t prev_umask; struct sockaddr_un addr; if(lsock >= 0) return 0; if((s = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) { logmsg(LOG_ERR, "failed to create socket: %s\n", strerror(errno)); return -1; } unlink(SOCK_NAME); /* in case it already exists */ memset(&addr, 0, sizeof addr); addr.sun_family = AF_UNIX; strcpy(addr.sun_path, SOCK_NAME); prev_umask = umask(0); if(bind(s, (struct sockaddr*)&addr, sizeof addr) == -1) { logmsg(LOG_ERR, "failed to bind unix socket: %s: %s\n", SOCK_NAME, strerror(errno)); close(s); return -1; } umask(prev_umask); if(listen(s, 8) == -1) { logmsg(LOG_ERR, "listen failed: %s\n", strerror(errno)); close(s); unlink(SOCK_NAME); return -1; } lsock = s; return 0; } void close_unix(void) { if(lsock != -1) { close(lsock); lsock = -1; unlink(SOCK_NAME); } } int get_unix_socket(void) { return lsock; } void send_uevent(spnav_event *ev, struct client *c) { int i; int32_t data[8] = {0}; float motion_mul; if(lsock == -1) return; switch(ev->type) { case EVENT_MOTION: if(!(c->evmask & EVMASK_MOTION)) return; data[0] = UEV_MOTION; motion_mul = get_client_sensitivity(c); for(i=0; i<6; i++) { float val = (float)ev->motion.data[i] * motion_mul; data[i + 1] = (int32_t)val; } data[7] = ev->motion.period; break; case EVENT_RAWAXIS: if(!(c->evmask & EVMASK_RAWAXIS)) return; data[0] = UEV_RAWAXIS; data[1] = ev->axis.idx; data[2] = ev->axis.value; break; case EVENT_BUTTON: if(!(c->evmask & EVMASK_BUTTON)) return; data[0] = ev->button.press ? UEV_PRESS : UEV_RELEASE; data[1] = ev->button.bnum; data[2] = ev->button.press; break; case EVENT_RAWBUTTON: if(!(c->evmask & EVMASK_RAWBUTTON)) return; data[0] = UEV_RAWBUTTON; data[1] = ev->button.bnum; data[2] = ev->button.press; break; case EVENT_DEV: if(!(c->evmask & EVMASK_DEV)) return; data[0] = UEV_DEV; data[1] = ev->dev.op; data[2] = ev->dev.id; data[3] = ev->dev.devtype; data[4] = ev->dev.usbid[0]; data[5] = ev->dev.usbid[1]; break; case EVENT_CFG: if(!(c->evmask & EVMASK_CFG)) return; data[0] = UEV_CFG; data[1] = ev->cfg.cfg; memcpy(data + 2, ev->cfg.data, sizeof ev->cfg.data); break; default: return; } while(write(get_client_socket(c), data, sizeof data) == -1 && errno == EINTR); } int handle_uevents(fd_set *rset) { struct client *citer; struct reqresp *req; if(lsock == -1) { return -1; } if(FD_ISSET(lsock, rset)) { /* got an incoming connection */ int s; if((s = accept(lsock, 0, 0)) == -1) { logmsg(LOG_ERR, "error while accepting connection on the UNIX socket: %s\n", strerror(errno)); } else { /* set socket as non-blocking and add client to the list */ fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK); if(!add_client(CLIENT_UNIX, &s)) { logmsg(LOG_ERR, "failed to add client: %s\n", strerror(errno)); } } } /* all the UNIX socket clients */ citer = first_client(); while(citer) { struct client *c = citer; citer = next_client(); if(get_client_type(c) == CLIENT_UNIX) { int s = get_client_socket(c); if(FD_ISSET(s, rset)) { int rdbytes; int32_t msg; float sens; /* handle client requests */ switch(c->proto) { case 0: while((rdbytes = read(s, &msg, sizeof msg)) < 0 && errno == EINTR); if(rdbytes <= 0) { /* something went wrong... disconnect client */ close(get_client_socket(c)); remove_client(c); continue; } /* handle magic NaN protocol change requests */ if((msg & 0xffffff00) == (REQ_TAG | REQ_CHANGE_PROTO)) { c->proto = msg & 0xff; /* if the client requests a protocol version higher than the * daemon supports, return the maximum supported version and * switch to that. */ if(c->proto > MAX_PROTO_VER) { c->proto = MAX_PROTO_VER; msg = REQ_TAG | REQ_CHANGE_PROTO | MAX_PROTO_VER; } write(s, &msg, sizeof msg); if(c->proto > 0) { /* set default event mask for proto-v1 clients */ c->evmask = EVMASK_MOTION | EVMASK_BUTTON | EVMASK_DEV; } continue; } /* protocol v0: only sensitivity comes from clients */ sens = *(float*)&msg; if(isfinite(sens)) { set_client_sensitivity(c, sens); } break; case 1: /* protocol v1: accumulate request bytes, and process */ while((rdbytes = read(s, c->reqbuf + c->reqbytes, sizeof *req - c->reqbytes)) < 0 && errno == EINTR); if(rdbytes <= 0) { close(s); remove_client(c); continue; } c->reqbytes += rdbytes; if(c->reqbytes >= sizeof *req) { req = (struct reqresp*)c->reqbuf; c->reqbytes = 0; if(handle_request(c, req) == -1) { close(s); remove_client(c); } } break; } } } } return 0; } static int sendresp(struct client *c, struct reqresp *rr, int status) { rr->data[6] = status; return write(get_client_socket(c), rr, sizeof *rr); } #define AXIS_VALID(x) ((x) >= 0 && (x) < MAX_AXES) #define BN_VALID(x) ((x) >= 0 && (x) < MAX_BUTTONS) #define BNACT_VALID(x) ((x) >= 0 && (x) < MAX_BNACT) static int handle_request(struct client *c, struct reqresp *req) { int i, idx, res; float fval, fvec[6]; struct device *dev; const char *str = 0; logmsg(LOG_DEBUG, "request %s - %x %x %x %x %x %x\n", reqstr(req->type), req->data[0], req->data[1], req->data[2], req->data[3], req->data[4], req->data[5], req->data[6]); switch(req->type & 0xffff) { case REQ_SET_NAME: if((res = spnav_recv_str(&c->strbuf, req)) == -1) { logmsg(LOG_ERR, "SET_NAME: failed to receive string\n"); break; } if(res) { c->name = c->strbuf.buf; c->strbuf.buf = 0; logmsg(LOG_INFO, "client name: %s\n", c->name); } break; case REQ_SET_SENS: fval = *(float*)req->data; if(isfinite(fval)) { set_client_sensitivity(c, fval); sendresp(c, req, 0); } else { logmsg(LOG_WARNING, "client attempted to set invalid client sensitivity\n"); sendresp(c, req, -1); } break; case REQ_GET_SENS: fval = get_client_sensitivity(c); req->data[0] = *(int*)&fval; sendresp(c, req, 0); break; case REQ_SET_EVMASK: c->evmask = req->data[0]; sendresp(c, req, 0); break; case REQ_GET_EVMASK: req->data[0] = c->evmask; sendresp(c, req, 0); break; case REQ_DEV_NAME: if((dev = get_client_device(c))) { spnav_send_str(get_client_socket(c), req->type, dev->name); } else { sendresp(c, req, -1); } break; case REQ_DEV_PATH: if((dev = get_client_device(c))) { spnav_send_str(get_client_socket(c), req->type, dev->path); } else { sendresp(c, req, -1); } break; case REQ_DEV_NAXES: if((dev = get_client_device(c))) { req->data[0] = dev->num_axes; sendresp(c, req, 0); } else { sendresp(c, req, -1); } break; case REQ_DEV_NBUTTONS: if((dev = get_client_device(c))) { req->data[0] = dev->num_buttons; sendresp(c, req, 0); } else { sendresp(c, req, -1); } break; case REQ_DEV_USBID: if((dev = get_client_device(c)) && dev->usbid[0] && dev->usbid[1]) { req->data[0] = dev->usbid[0]; req->data[1] = dev->usbid[1]; sendresp(c, req, 0); } else { sendresp(c, req, -1); } break; case REQ_DEV_TYPE: if((dev = get_client_device(c))) { req->data[0] = dev->type; sendresp(c, req, 0); } else { sendresp(c, req, -1); } break; case REQ_SCFG_SENS: fval = *(float*)req->data; if(isfinite(fval)) { cfg.sensitivity = fval; sendresp(c, req, 0); } else { logmsg(LOG_WARNING, "client attempted to set invalid global sensitivity\n"); sendresp(c, req, -1); } break; case REQ_GCFG_SENS: req->data[0] = *(int*)&cfg.sensitivity; sendresp(c, req, 0); break; case REQ_SCFG_SENS_AXIS: for(i=0; i<6; i++) { fvec[i] = ((float*)req->data)[i]; if(!isfinite(fvec[i])) { logmsg(LOG_WARNING, "client attempted to set invalid axis %d sensitivity\n", i); sendresp(c, req, -1); return 0; } } for(i=0; i<3; i++) { cfg.sens_trans[i] = fvec[i]; cfg.sens_rot[i] = fvec[i + 3]; } sendresp(c, req, 0); break; case REQ_GCFG_SENS_AXIS: for(i=0; i<3; i++) { req->data[i] = *(int*)(cfg.sens_trans + i); req->data[i + 3] = *(int*)(cfg.sens_rot + i); } sendresp(c, req, 0); break; case REQ_SCFG_DEADZONE: if(!AXIS_VALID(req->data[0])) { logmsg(LOG_WARNING, "client attempted to set invalid axis deadzone: %d\n", req->data[0]); sendresp(c, req, -1); return 0; } cfg.dead_threshold[req->data[0]] = req->data[1]; sendresp(c, req, 0); break; case REQ_GCFG_DEADZONE: if(!AXIS_VALID(req->data[0])) { logmsg(LOG_WARNING, "client requested invalid axis deadzone: %d\n", req->data[0]); sendresp(c, req, -1); return 0; } req->data[1] = cfg.dead_threshold[req->data[0]]; sendresp(c, req, 0); break; case REQ_SCFG_INVERT: for(i=0; i<6; i++) { cfg.invert[i] = req->data[i] ? 1 : 0; } sendresp(c, req, 0); break; case REQ_GCFG_INVERT: memcpy(req->data, cfg.invert, 6 * sizeof(int)); sendresp(c, req, 0); break; case REQ_SCFG_AXISMAP: if(!AXIS_VALID(req->data[0]) || req->data[1] < -1 || req->data[1] >= 6) { logmsg(LOG_WARNING, "client attempted to set invalid axis mapping: %d -> %d\n", req->data[0], req->data[1]); sendresp(c, req, -1); return 0; } cfg.map_axis[req->data[0]] = req->data[1]; sendresp(c, req, 0); break; case REQ_GCFG_AXISMAP: if(!AXIS_VALID(req->data[0])) { logmsg(LOG_WARNING, "client queried mapping of invalid axis: %d\n", req->data[0]); sendresp(c, req, -1); return 0; } req->data[1] = cfg.map_axis[req->data[0]]; sendresp(c, req, 0); break; case REQ_SCFG_BNMAP: if(!BN_VALID(req->data[0]) || !BN_VALID(req->data[1])) { logmsg(LOG_WARNING, "client attempted to set invalid button mapping: %d -> %d\n", req->data[0], req->data[1]); sendresp(c, req, -1); return 0; } cfg.map_button[req->data[0]] = req->data[1]; sendresp(c, req, 0); break; case REQ_GCFG_BNMAP: if(!BN_VALID(req->data[0])) { logmsg(LOG_WARNING, "client queried mapping of invalid button: %d\n", req->data[0]); sendresp(c, req, -1); return 0; } req->data[1] = cfg.map_button[req->data[0]]; sendresp(c, req, 0); break; case REQ_SCFG_BNACTION: if(!BN_VALID(req->data[0]) || !BNACT_VALID(req->data[1])) { logmsg(LOG_WARNING, "client attempted to set invalid button action: %d -> %d\n", req->data[0], req->data[1]); sendresp(c, req, -1); return 0; } cfg.bnact[req->data[0]] = req->data[1]; sendresp(c, req, 0); break; case REQ_GCFG_BNACTION: if(!BN_VALID(req->data[0])) { logmsg(LOG_WARNING, "client queried action bound to invalid button: %d\n", req->data[0]); sendresp(c, req, -1); return 0; } req->data[1] = cfg.bnact[req->data[0]]; sendresp(c, req, 0); break; case REQ_SCFG_KBMAP: #ifdef USE_X11 idx = req->data[0]; if(!BN_VALID(idx) || (req->data[1] > 0 && !(str = kbemu_keyname(req->data[1])))) { logmsg(LOG_WARNING, "client attempted to set invalid key map: %d -> %x\n", idx, (unsigned int)req->data[1]); sendresp(c, req, -1); return 0; } cfg.kbmap[idx] = req->data[1]; free(cfg.kbmap_str[idx]); cfg.kbmap_str[idx] = req->data[1] > 0 ? strdup(str) : 0; sendresp(c, req, 0); #else logmsg(LOG_WARNING, "unable to set keyboard mappings, daemon compiled without X11 support\n"); sendresp(c, req, -1); #endif break; case REQ_GCFG_KBMAP: #ifdef USE_X11 idx = req->data[0]; if(!BN_VALID(idx)) { logmsg(LOG_WARNING, "client queried keyboard mapping for invalid button: %d\n", idx); sendresp(c, req, -1); return 0; } if(cfg.kbmap_str[idx]) { if(!cfg.kbmap[idx]) { cfg.kbmap[idx] = kbemu_keysym(cfg.kbmap_str[idx]); } req->data[1] = cfg.kbmap[idx]; } else { req->data[1] = 0; } sendresp(c, req, 0); #else logmsg(LOG_WARNING, "unable to query keyboard mappings, daemon compiled without X11 support\n"); sendresp(c, req, -1); #endif break; case REQ_SCFG_SWAPYZ: cfg.swapyz = req->data[0] ? 1 : 0; sendresp(c, req, 0); case REQ_GCFG_SWAPYZ: req->data[0] = cfg.swapyz; sendresp(c, req, 0); break; case REQ_SCFG_LED: if(req->data[0] < 0 || req->data[0] >= 3) { sendresp(c, req, -1); break; } cfg.led = req->data[0]; cfg_changed(); sendresp(c, req, 0); break; case REQ_GCFG_LED: req->data[0] = cfg.led; sendresp(c, req, 0); break; case REQ_SCFG_GRAB: cfg.grab_device = req->data[0] ? 1 : 0; sendresp(c, req, 0); break; case REQ_GCFG_GRAB: req->data[0] = cfg.grab_device; sendresp(c, req, 0); break; case REQ_SCFG_SERDEV: if((res = spnav_recv_str(&c->strbuf, req)) == -1) { logmsg(LOG_ERR, "SCFG_SERDEV: failed to receive string\n"); break; } if(res) { strncpy(cfg.serial_dev, c->strbuf.buf, sizeof cfg.serial_dev - 1); cfg.serial_dev[sizeof cfg.serial_dev - 1] = 0; cfg_changed(); } break; case REQ_GCFG_SERDEV: spnav_send_str(c->sock, req->type, cfg.serial_dev); break; case REQ_SCFG_REPEAT: cfg.repeat_msec = req->data[0]; sendresp(c, req, 0); break; case REQ_GCFG_REPEAT: req->data[0] = cfg.repeat_msec; sendresp(c, req, 0); break; case REQ_CFG_SAVE: sendresp(c, req, write_cfg(cfgfile, &cfg)); break; case REQ_CFG_RESTORE: if(read_cfg(cfgfile, &cfg) == -1) { logmsg(LOG_INFO, "config restore requested but failed to read %s, restoring defaults instead\n", cfgfile); default_cfg(&cfg); } cfg_changed(); sendresp(c, req, 0); break; case REQ_CFG_RESET: default_cfg(&cfg); cfg_changed(); sendresp(c, req, 0); break; default: logmsg(LOG_WARNING, "invalid client request: %s\n", reqstr(req->type)); sendresp(c, req, -1); } return 0; } static const char *reqstr(int req) { static char buf[8]; req &= 0xffff; if(req >= 0x1000 && req < 0x1000 + spnav_reqnames_1000_size) { return spnav_reqnames_1000[req - 0x1000]; } if(req >= 0x2000 && req < 0x2000 + spnav_reqnames_2000_size) { return spnav_reqnames_2000[req - 0x2000]; } if(req >= 0x3000 && req < 0x3000 + spnav_reqnames_3000_size) { return spnav_reqnames_3000[req - 0x3000]; } switch(req) { case REQ_CFG_SAVE: return "CFG_SAVE"; case REQ_CFG_RESTORE: return "CFG_RESTORE"; case REQ_CFG_RESET: return "CFG_RESET"; default: break; } sprintf(buf, "0x%04x", req); return buf; } spacenavd-1.3.1/src/proto_x11.c0000664000175000017500000002463514737545500016141 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #ifdef USE_X11 #include #include #include #include #include #ifdef HAVE_ALLOCA_H #include #endif #ifdef HAVE_MALLOC_H #include #endif #include #include #include "proto_x11.h" #include "client.h" #include "spnavd.h" #include "dev.h" #include "xdetect.h" #include "kbemu.h" #ifdef HAVE_XINPUT2_H #include #include #endif enum cmd_msg { CMD_NONE, CMD_APP_WINDOW = 27695, /* set client window */ CMD_APP_SENS /* set app sensitivity */ }; static int xerr(Display *dpy, XErrorEvent *err); static int xioerr(Display *dpy); static Display *dpy; static Window win; static Atom xa_event_motion, xa_event_bpress, xa_event_brelease; static Atom xa_event_devdisc, xa_event_cmd; /* XXX This stands in for the client sensitivity. Due to the * bad design of the original magellan protocol, we can't know * which client requested the sensitivity change, so we have * to keep it global for all X clients. */ static float x11_sens = 1.0; static jmp_buf jbuf; int init_x11(void) { int i, screen, scr_count; Window root; XSetWindowAttributes xattr; Atom wm_delete, cmd_type; XTextProperty tp_wname; XClassHint class_hint; char *win_title = "Magellan Window"; if(dpy) return 0; /* if the server started from init, it probably won't have a DISPLAY env var * so let's add a default one. */ if(!getenv("DISPLAY")) { putenv("DISPLAY=:0.0"); } /* ... also there won't be an XAUTHORITY env var, so set one up */ if(!getenv("XAUTHORITY")) { struct passwd *p = getpwuid(getuid()); char *home, *buf; if(!p || !p->pw_dir) { if(!p) { logmsg(LOG_ERR, "getpwuid failed: %s\n", strerror(errno)); } logmsg(LOG_WARNING, "falling back to getting the home directory from the HOME env var...\n"); if(!(home = getenv("HOME"))) { logmsg(LOG_WARNING, "HOME env var not found, using /tmp as a home directory...\n"); home = "/tmp"; } } else { home = p->pw_dir; } buf = alloca(strlen("XAUTHORITY=") + strlen(home) + strlen("/.Xauthority") + 1); sprintf(buf, "XAUTHORITY=%s/.Xauthority", home); putenv(buf); } if(verbose) { logmsg(LOG_INFO, "trying to open X11 display \"%s\"\n", getenv("DISPLAY")); logmsg(LOG_INFO, " XAUTHORITY=%s\n", getenv("XAUTHORITY")); } if(!(dpy = XOpenDisplay(0))) { logmsg(LOG_ERR, "failed to open X11 display \"%s\"\n", getenv("DISPLAY")); xdet_start(); return -1; } XSetErrorHandler(xerr); XSetIOErrorHandler(xioerr); if(setjmp(jbuf)) { return -1; } scr_count = ScreenCount(dpy); screen = DefaultScreen(dpy); root = RootWindow(dpy, screen); /* intern the various atoms used for communicating with the magellan clients */ xa_event_motion = XInternAtom(dpy, "MotionEvent", False); xa_event_bpress = XInternAtom(dpy, "ButtonPressEvent", False); xa_event_brelease = XInternAtom(dpy, "ButtonReleaseEvent", False); xa_event_devdisc = XInternAtom(dpy, "DeviceDisconnectEvent", False); xa_event_cmd = XInternAtom(dpy, "CommandEvent", False); /* Create a dummy window, so that clients are able to send us events * through the magellan API. No need to map the window. */ xattr.background_pixel = xattr.border_pixel = BlackPixel(dpy, screen); xattr.colormap = DefaultColormap(dpy, screen); win = XCreateWindow(dpy, root, 0, 0, 10, 10, 0, CopyFromParent, InputOutput, DefaultVisual(dpy, screen), CWColormap | CWBackPixel | CWBorderPixel, &xattr); wm_delete = XInternAtom(dpy, "WM_DELETE_WINDOW", False); XSetWMProtocols(dpy, win, &wm_delete, 1); XStringListToTextProperty(&win_title, 1, &tp_wname); XSetWMName(dpy, win, &tp_wname); XFree(tp_wname.value); class_hint.res_name = "magellan"; class_hint.res_class = "magellan_win"; XSetClassHint(dpy, win, &class_hint); /* I believe this is a bit hackish, but the magellan API expects to find the CommandEvent * property on the root window, containing our window id. * The API doesn't look for a specific property type, so I made one up here (MagellanCmdType). */ cmd_type = XInternAtom(dpy, "MagellanCmdType", False); for(i=0; itype) { case EVENT_MOTION: xevent.xclient.message_type = xa_event_motion; xevent.xclient.format = 16; for(i=0; i<6; i++) { float val = (float)ev->motion.data[i] * x11_sens; xevent.xclient.data.s[i + 2] = (short)val; } xevent.xclient.data.s[0] = xevent.xclient.data.s[1] = 0; xevent.xclient.data.s[8] = ev->motion.period; break; case EVENT_BUTTON: xevent.xclient.message_type = ev->button.press ? xa_event_bpress : xa_event_brelease; xevent.xclient.format = 16; xevent.xclient.data.s[2] = ev->button.bnum; break; default: return; } XSendEvent(dpy, get_client_window(c), False, 0, &xevent); XFlush(dpy); } int handle_xevents(fd_set *rset) { if(!dpy) { if(xdet_get_fd() != -1) { handle_xdet_events(rset); } return -1; } /* process any pending X events */ if(FD_ISSET(ConnectionNumber(dpy), rset)) { if(setjmp(jbuf)) { return 0; } while(XPending(dpy)) { XEvent xev; XNextEvent(dpy, &xev); if(xev.type == ClientMessage && xev.xclient.message_type == xa_event_cmd) { unsigned int win_id; switch(xev.xclient.data.s[2]) { case CMD_APP_WINDOW: win_id = xev.xclient.data.s[1]; win_id |= (unsigned int)xev.xclient.data.s[0] << 16; set_client_window((Window)win_id); break; case CMD_APP_SENS: x11_sens = *(float*)xev.xclient.data.s; /* see decl of x11_sens for details */ break; default: break; } } } } return 0; } /* adds a new X11 client to the list, IF it does not already exist */ void set_client_window(Window win) { int i, scr_count; struct client *cnode; /* When a magellan application exits, the SDK sets another window to avoid * crashing the original proprietary daemon. The new free SDK will set * consistently the root window for that purpose, which we can ignore here * easily. */ scr_count = ScreenCount(dpy); for(i=0; iname, xidevs[i].name) == 0 && xidevs[i].enabled) { unsigned char zero = 0; logmsg(LOG_INFO, "Removing device \"%s\" from X server\n", dev->name); XIChangeProperty(dpy, xidevs[i].deviceid, atom_enabled, XA_INTEGER, 8, PropModeReplace, &zero, 1); break; } dev = dev->next; } } XIFreeDeviceInfo(xidevs); #endif /* HAVE_XINPUT2_H */ } /* X11 error handler */ static int xerr(Display *dpy, XErrorEvent *err) { char buf[512]; if(err->error_code == BadWindow) { if(verbose) { logmsg(LOG_INFO, "Caught BadWindow, dropping client with window: %x\n", (unsigned int)err->resourceid); } /* we may get a BadWindow error when trying to send events to * clients that have disconnected in the meanwhile. */ remove_client_window((Window)err->resourceid); } else { XGetErrorText(dpy, err->error_code, buf, sizeof buf); logmsg(LOG_ERR, "Caught unexpected X error: %s [op: %d,%d, res: %u]\n", buf, (int)err->request_code, (int)err->minor_code, (unsigned int)err->resourceid); } return 0; } /* X11 I/O error handler * This function must not return or xlib will abort. */ static int xioerr(Display *display) { logmsg(LOG_ERR, "Lost the X server!\n"); dpy = 0; close_x11(); xdet_start(); longjmp(jbuf, 1); return 0; } #else int spacenavd_proto_x11_shut_up_empty_source_warning; #endif /* USE_X11 */ spacenavd-1.3.1/src/hotplug_freebsd.c0000664000175000017500000000177614737545500017462 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef __FreeBSD__ int init_hotplug(void) { return -1; } void shutdown_hotplug(void) { } int get_hotplug_fd(void) { return -1; } int handle_hotplug(void) { return -1; } #else int spacenavd_hotplug_freebsd_shut_up_empty_source_warning; #endif /* __FreeBSD */ spacenavd-1.3.1/src/proto_x11.h0000664000175000017500000000225014737545500016133 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PROTO_X11_H_ #define PROTO_X11_H_ #include "config.h" #include #include #include "event.h" #include "client.h" int init_x11(void); void close_x11(void); int get_x11_socket(void); void send_xevent(spnav_event *ev, struct client *c); int handle_xevents(fd_set *rset); void set_client_window(Window win); void remove_client_window(Window win); void drop_xinput(void); #endif /* PROTO_X11_H_ */ spacenavd-1.3.1/src/client.c0000664000175000017500000000671614737545500015563 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #include #include #include "client.h" #include "dev.h" #include "spnavd.h" #ifdef USE_X11 #include #include #endif static struct client *client_list = NULL; static struct client *client_iter; /* iterator (used by first/next calls) */ /* add a client to the list * cdata points to the socket fd for new-protocol clients, or the * window XID for clients talking to us through the magellan protocol */ struct client *add_client(int type, void *cdata) { struct client *client; #ifdef USE_X11 if(!cdata || (type != CLIENT_UNIX && type != CLIENT_X11)) #else if(!cdata || type != CLIENT_UNIX) #endif { return 0; } if(!(client = calloc(1, sizeof *client))) { return 0; } client->type = type; if(type == CLIENT_UNIX) { client->sock = *(int*)cdata; #ifdef USE_X11 } else { client->win = *(Window*)cdata; #endif } /* default to protocol version 0 until the client changes it */ client->proto = 0; /* evmask for proto-v0 clients is just input events */ client->evmask = EVMASK_MOTION | EVMASK_BUTTON; client->sens = 1.0f; client->dev = 0; /* default/first device */ if(!client_list && cfg.led == LED_AUTO) { /* on first client, turn the led on */ set_devices_led(1); } client->next = client_list; client_list = client; return client; } void remove_client(struct client *client) { struct client *iter = client_list; if(!iter) return; if(iter == client) { client_list = iter->next; free_client(iter); iter = client_list; if(!iter) { if(cfg.led == LED_AUTO) { set_devices_led(0); /* no more clients, turn off led */ } return; } } while(iter->next) { if(iter->next == client) { struct client *tmp = iter->next; iter->next = tmp->next; free_client(tmp); } else { iter = iter->next; } } } void free_client(struct client *client) { if(client) { free(client->name); free(client->strbuf.buf); free(client); } } int get_client_type(struct client *client) { return client->type; } int get_client_socket(struct client *client) { return client->sock; } #ifdef USE_X11 Window get_client_window(struct client *client) { return client->win; } #endif void set_client_sensitivity(struct client *client, float sens) { client->sens = sens; } float get_client_sensitivity(struct client *client) { return client->sens; } void set_client_device(struct client *client, struct device *dev) { client->dev = dev; } struct device *get_client_device(struct client *client) { return client->dev ? client->dev : get_devices(); } struct client *first_client(void) { client_iter = client_list; return client_iter; } struct client *next_client(void) { if(client_iter) { client_iter = client_iter->next; } return client_iter; } spacenavd-1.3.1/src/event.c0000664000175000017500000002226314737545500015421 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #include #include #include #include "event.h" #include "client.h" #include "proto_unix.h" #include "spnavd.h" #ifdef USE_X11 #include "proto_x11.h" #include "kbemu.h" #endif enum { MOT_X, MOT_Y, MOT_Z, MOT_RX, MOT_RY, MOT_RZ }; enum { BTN_RELEASE = 0, BTN_PRESS = 1 }; struct dev_event { spnav_event event; struct timeval timeval; struct device *dev; int pending; struct dev_event *next; }; static struct dev_event *add_dev_event(struct device *dev); static struct dev_event *device_event_in_use(struct device *dev); static void handle_button_action(int act, int val); static void dispatch_event(struct dev_event *dev); static void send_event(spnav_event *ev, struct client *c); static unsigned int msec_dif(struct timeval tv1, struct timeval tv2); static struct dev_event *dev_ev_list = NULL; static int disable_translation, disable_rotation, dom_axis_mode; static int cur_axis_mag[6], cur_dom_axis; static struct dev_event *add_dev_event(struct device *dev) { struct dev_event *dev_ev, *iter; int i; if((dev_ev = malloc(sizeof *dev_ev)) == NULL) { return NULL; } dev_ev->event.motion.data = (int*)&dev_ev->event.motion.x; for(i=0; i<6; i++) dev_ev->event.motion.data[i] = 0; gettimeofday(&dev_ev->timeval, 0); dev_ev->dev = dev; dev_ev->next = NULL; if(dev_ev_list == NULL) return dev_ev_list = dev_ev; iter = dev_ev_list; while(iter->next) { iter = iter->next; } iter->next = dev_ev; return dev_ev; } /* remove_dev_event takes a device pointer as argument so that upon removal of * a device the pending event (if any) can be removed. */ void remove_dev_event(struct device *dev) { struct dev_event dummy; struct dev_event *iter; dummy.next = dev_ev_list; iter = &dummy; while(iter->next) { if(iter->next->dev == dev) { struct dev_event *ev = iter->next; iter->next = ev->next; if(verbose) { logmsg(LOG_INFO, "removing pending device event of: %s\n", dev->path); } free(ev); } else { iter = iter->next; } } dev_ev_list = dummy.next; } static struct dev_event *device_event_in_use(struct device *dev) { struct dev_event *iter = dev_ev_list; while(iter) { if(iter->dev == dev) { return iter; } iter = iter->next; } return NULL; } static inline int map_axis(int devaxis) { const static int swaptab[] = {0, 2, 1, 3, 5, 4}; int axis = cfg.map_axis[devaxis]; if(axis < 0 || axis >= 6) { return -1; } if(cfg.swapyz) { return swaptab[axis]; } return axis; } /* process_input processes an device input event, and dispatches * spacenav events to the clients by calling dispatch_event. * relative inputs (INP_MOTION) are accumulated, and dispatched when * we get an INP_FLUSH event. Button events are dispatched immediately * and they implicitly flush any pending motion event. */ void process_input(struct device *dev, struct dev_input *inp) { int sign, axis, abs_val; struct dev_event *dev_ev; float sens_rot, sens_trans, axis_sens; spnav_event ev; switch(inp->type) { case INP_MOTION: ev.type = EVENT_RAWAXIS; ev.axis.idx = inp->idx; ev.axis.value = inp->val; broadcast_event(&ev); abs_val = abs(inp->val); if(abs_val < cfg.dead_threshold[inp->idx] ) { inp->val = 0; } if((axis = map_axis(inp->idx)) == -1) { break; } sign = cfg.invert[axis] ? -1 : 1; sens_rot = disable_rotation ? 0 : cfg.sens_rot[axis - 3]; sens_trans = disable_translation ? 0 : cfg.sens_trans[axis]; axis_sens = axis < 3 ? sens_trans : sens_rot; if(dom_axis_mode && axis < 6) { if(abs_val > cur_axis_mag[cur_dom_axis]) { cur_dom_axis = axis; } else { inp->val = 0; } cur_axis_mag[axis] = abs_val; } inp->val = (int)((float)inp->val * cfg.sensitivity * axis_sens); dev_ev = device_event_in_use(dev); if(verbose && dev_ev == NULL) logmsg(LOG_INFO, "adding dev event for device: %s\n", dev->path); if(dev_ev == NULL && (dev_ev = add_dev_event(dev)) == NULL) { logmsg(LOG_ERR, "failed to get dev_event\n"); break; } dev_ev->event.type = EVENT_MOTION; dev_ev->event.motion.data = (int*)&dev_ev->event.motion.x; dev_ev->event.motion.data[axis] = sign * inp->val; dev_ev->pending = 1; break; case INP_BUTTON: ev.type = EVENT_RAWBUTTON; ev.button.press = inp->val; ev.button.bnum = inp->idx; broadcast_event(&ev); /* check to see if the button has been bound to an action */ if(cfg.bnact[inp->idx] > 0) { handle_button_action(cfg.bnact[inp->idx], inp->val); break; } #ifdef USE_X11 /* check to see if we must emulate a keyboard event instead of a * retular button event for this button */ if(cfg.kbmap_str[inp->idx]) { if(!cfg.kbmap[inp->idx]) { cfg.kbmap[inp->idx] = kbemu_keysym(cfg.kbmap_str[inp->idx]); if(verbose) { logmsg(LOG_DEBUG, "mapping ``%s'' to keysym %d\n", cfg.kbmap_str[inp->idx], (int)cfg.kbmap[inp->idx]); } } send_kbevent(cfg.kbmap[inp->idx], inp->val); break; } #endif dev_ev = device_event_in_use(dev); if(dev_ev && dev_ev->pending) { dispatch_event(dev_ev); dev_ev->pending = 0; } inp->idx = cfg.map_button[inp->idx]; /* button events are not queued */ { struct dev_event dev_button_event; dev_button_event.dev = dev; dev_button_event.event.type = EVENT_BUTTON; dev_button_event.event.button.press = inp->val; dev_button_event.event.button.bnum = inp->idx; dispatch_event(&dev_button_event); } /* to have them replace motion events in the queue uncomment next section */ /* dev_ev = add_dev_event(dev); * dev_ev->event.type = EVENT_BUTTON; * dev_ev->event.button.press = inp->val; * dev_ev->event.button.bnum = inp->idx; * dispatch_event(dev_ev); */ break; case INP_FLUSH: dev_ev = device_event_in_use(dev); if(dev_ev && dev_ev->pending) { dispatch_event(dev_ev); dev_ev->pending = 0; } break; default: break; } } static void handle_button_action(int act, int pressed) { if(pressed) return; /* perform all actions on release */ switch(act) { case BNACT_SENS_INC: cfg.sensitivity *= 1.1f; broadcast_cfg_event(REQ_GCFG_SENS, *(int*)&cfg.sensitivity); break; case BNACT_SENS_DEC: cfg.sensitivity *= 0.9f; broadcast_cfg_event(REQ_GCFG_SENS, *(int*)&cfg.sensitivity); break; case BNACT_SENS_RESET: cfg.sensitivity = 1.0f; broadcast_cfg_event(REQ_GCFG_SENS, *(int*)&cfg.sensitivity); break; case BNACT_DISABLE_ROTATION: disable_rotation = !disable_rotation; if(disable_rotation) { disable_translation = 0; } break; case BNACT_DISABLE_TRANSLATION: disable_translation = !disable_translation; if(disable_translation) { disable_rotation = 0; } break; case BNACT_DOMINANT_AXIS: dom_axis_mode = !dom_axis_mode; break; } } int in_deadzone(struct device *dev) { int i; struct dev_event *dev_ev; if((dev_ev = device_event_in_use(dev)) == NULL) return -1; for(i=0; i<6; i++) { if(dev_ev->event.motion.data[i] != 0) return 0; } return 1; } void repeat_last_event(struct device *dev) { struct dev_event *dev_ev; if((dev_ev = device_event_in_use(dev)) == NULL) return; dispatch_event(dev_ev); } static void dispatch_event(struct dev_event *dev_ev) { struct client *c, *client_iter; struct device *client_dev; if(dev_ev->event.type == EVENT_MOTION) { struct timeval tv; gettimeofday(&tv, 0); dev_ev->event.motion.period = msec_dif(tv, dev_ev->timeval); dev_ev->timeval = tv; } client_iter = first_client(); while(client_iter) { c = client_iter; client_iter = next_client(); /* if the client has selected a single device to get input from, then * don't send the event if it originates from a different device */ client_dev = get_client_device(c); if(!client_dev || client_dev == dev_ev->dev) { send_event(&dev_ev->event, c); } } } void broadcast_event(spnav_event *ev) { struct client *c; c = first_client(); while(c) { /* event masks will be checked at the protocol level (send_uevent) */ send_event(ev, c); c = c->next; } } void broadcast_cfg_event(int cfg, int val) { spnav_event ev = {0}; ev.type = EVENT_CFG; ev.cfg.cfg = cfg; ev.cfg.data[0] = val; broadcast_event(&ev); } static void send_event(spnav_event *ev, struct client *c) { switch(get_client_type(c)) { #ifdef USE_X11 case CLIENT_X11: send_xevent(ev, c); break; #endif case CLIENT_UNIX: send_uevent(ev, c); break; default: break; } } static unsigned int msec_dif(struct timeval tv1, struct timeval tv2) { unsigned int ms1, ms2; ms1 = tv1.tv_sec * 1000 + tv1.tv_usec / 1000; ms2 = tv2.tv_sec * 1000 + tv2.tv_usec / 1000; return ms1 - ms2; } spacenavd-1.3.1/src/xdetect.h0000664000175000017500000000166514737545500015750 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef XDETECT_H_ #define XDETECT_H_ #include "config.h" int xdet_start(void); void xdet_stop(void); int xdet_get_fd(void); int handle_xdet_events(fd_set *rset); #endif /* XDETECT_H_ */ spacenavd-1.3.1/src/event.h0000664000175000017500000000420114737545500015416 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef EVENT_H_ #define EVENT_H_ #include "config.h" #include #include "dev.h" enum { EVENT_MOTION, EVENT_BUTTON, /* includes both press and release */ /* protocol v1 events */ EVENT_DEV, /* device change */ EVENT_CFG, /* configuration change */ EVENT_RAWAXIS, EVENT_RAWBUTTON }; enum { DEV_ADD, DEV_RM }; struct event_motion { int type; int x, y, z; int rx, ry, rz; unsigned int period; int *data; }; struct event_button { int type; int press; int bnum; }; struct event_dev { int type; int op; int id; int devtype; int usbid[2]; }; struct event_cfg { int type; int cfg; int data[6]; }; struct event_axis { int type; int idx; int value; }; typedef union spnav_event { int type; struct event_motion motion; struct event_button button; struct event_dev dev; struct event_cfg cfg; struct event_axis axis; } spnav_event; enum { INP_MOTION, INP_BUTTON, INP_FLUSH }; struct dev_input { int type; int idx; int val; }; void remove_dev_event(struct device *dev); void process_input(struct device *dev, struct dev_input *inp); /* non-zero if the last processed motion event was in the deadzone */ int in_deadzone(struct device *dev); /* dispatches the last event */ void repeat_last_event(struct device *dev); /* broadcasts an event to all clients */ void broadcast_event(spnav_event *ev); void broadcast_cfg_event(int cfg, int val); #endif /* EVENT_H_ */ spacenavd-1.3.1/src/dev.c0000664000175000017500000003312614737545500015056 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #include #include #include #include #include #include #include "dev.h" #include "dev_usb.h" #include "dev_serial.h" #include "event.h" /* remove pending events upon device removal */ #include "spnavd.h" #include "proto.h" #include "proto_unix.h" #ifdef USE_X11 #include "proto_x11.h" #endif /* The device flags are introduced to normalize input across all known * supported 6dof devices. Newer USB devices seem to use axis 1 as fwd/back and * axis 2 as up/down, while older serial devices (and possibly also the early * USB ones?) do the opposite. This discrepancy would mean the user has to * change the configuration back and forth when changing devices. With these * flags we attempt to make all known devices use the same input axes at the * lowest level, and let the user remap based on preference, and have their * choice persist across all known devices. */ enum { DF_SWAPYZ = 1, DF_INVYZ = 2 }; /* The bnmap function pointer in the device table was introduced to deal with * certain USB devices which report a huge amount of buttons, and strange * disjointed button numbers. The function is expected to return the number of * actual buttons when a negative number is passed as argument, and the button * mapping otherwise. */ static struct usbdb_entry { int usbid[2]; int type; unsigned int flags; int (*bnmap)(int); /* remap buttons on problematic devices */ } usbdb[] = { {{0x046d, 0xc603}, DEV_PLUSXT, 0, 0}, /* spacemouse plus XT */ {{0x046d, 0xc605}, DEV_CADMAN, DF_SWAPYZ | DF_INVYZ, 0}, /* cadman */ {{0x046d, 0xc606}, DEV_SMCLASSIC, 0, 0}, /* spacemouse classic */ {{0x046d, 0xc621}, DEV_SB5000, DF_SWAPYZ | DF_INVYZ, 0}, /* spaceball 5000 */ {{0x046d, 0xc623}, DEV_STRAVEL, DF_SWAPYZ | DF_INVYZ, 0}, /* space traveller */ {{0x046d, 0xc625}, DEV_SPILOT, DF_SWAPYZ | DF_INVYZ, 0}, /* space pilot */ {{0x046d, 0xc626}, DEV_SNAV, DF_SWAPYZ | DF_INVYZ, 0}, /* space navigator */ {{0x046d, 0xc627}, DEV_SEXP, DF_SWAPYZ | DF_INVYZ, 0}, /* space explorer */ {{0x046d, 0xc628}, DEV_SNAVNB, DF_SWAPYZ | DF_INVYZ, 0}, /* space navigator for notebooks*/ {{0x046d, 0xc629}, DEV_SPILOTPRO, DF_SWAPYZ | DF_INVYZ, 0}, /* space pilot pro*/ {{0x046d, 0xc62b}, DEV_SMPRO, DF_SWAPYZ | DF_INVYZ, bnhack_smpro}, /* space mouse pro*/ {{0x046d, 0xc640}, DEV_NULOOQ, 0, 0}, /* nulooq */ {{0x256f, 0xc62e}, DEV_SMW, DF_SWAPYZ | DF_INVYZ, 0}, /* spacemouse wireless (USB cable) */ {{0x256f, 0xc62f}, DEV_SMW, DF_SWAPYZ | DF_INVYZ, 0}, /* spacemouse wireless receiver */ {{0x256f, 0xc631}, DEV_SMPROW, DF_SWAPYZ | DF_INVYZ, bnhack_smpro}, /* spacemouse pro wireless */ {{0x256f, 0xc632}, DEV_SMPROW, DF_SWAPYZ | DF_INVYZ, bnhack_smpro}, /* spacemouse pro wireless receiver */ {{0x256f, 0xc633}, DEV_SMENT, DF_SWAPYZ | DF_INVYZ, bnhack_sment}, /* spacemouse enterprise */ {{0x256f, 0xc635}, DEV_SMCOMP, DF_SWAPYZ | DF_INVYZ, 0}, /* spacemouse compact */ {{0x256f, 0xc636}, DEV_SMMOD, DF_SWAPYZ | DF_INVYZ, 0}, /* spacemouse module */ {{0x256f, 0xc63a}, DEV_SMW, DF_SWAPYZ | DF_INVYZ, 0}, /* spacemouse wireless (Bluetooth) */ {{-1, -1}, DEV_UNKNOWN, 0} }; /* 3Dconnexion devices which we don't want to match, because they are * not 6dof space-mice. reported by: Herbert Graeber in github pull request #4 */ static int devid_blacklist[][2] = { {0x256f, 0xc650}, /* cadmouse */ {0x256f, 0xc651}, /* cadmouse wireless */ {0x256f, 0xc654}, /* CadMouse Pro Wireless */ {0x256f, 0xc655}, /* CadMouse Compact */ {0x256f, 0xc656}, /* CadMouse Pro */ {0x256f, 0xc657}, /* CadMouse Pro Wireless Left */ {0x256f, 0xc658}, /* CadMouse Compact Wireless */ {0x256f, 0xc664}, /* Keyboard Pro */ {0x256f, 0xc668}, /* Keyboard Pro (newer version)*/ {0x256f, 0xc665}, /* Numpad Pro */ {0x256f, 0xc62c}, /* lipari(?) */ {0x256f, 0xc641}, /* scout(?) */ {-1, -1} }; static struct device *add_device(void); static int match_usbdev(const struct usb_dev_info *devinfo); static struct usbdb_entry *find_usbdb_entry(unsigned int vid, unsigned int pid); static struct device *dev_list = NULL; static unsigned short last_id; void init_devices(void) { init_devices_serial(); init_devices_usb(); } void init_devices_serial(void) { struct stat st; struct device *dev; spnav_event ev = {0}; /* try to open a serial device if specified in the config file */ if(cfg.serial_dev[0]) { if(!dev_path_in_use(cfg.serial_dev)) { if(stat(cfg.serial_dev, &st) == -1) { logmsg(LOG_ERR, "Failed to stat serial device %s: %s\n", cfg.serial_dev, strerror(errno)); return; } if(!S_ISCHR(st.st_mode)) { logmsg(LOG_ERR, "Ignoring configured serial device: %s: %s\n", cfg.serial_dev, "not a character device"); return; } dev = add_device(); strcpy(dev->path, cfg.serial_dev); if(open_dev_serial(dev) == -1) { remove_device(dev); return; } logmsg(LOG_INFO, "using device: %s\n", cfg.serial_dev); /* new serial device added, send device change event */ ev.dev.type = EVENT_DEV; ev.dev.op = DEV_ADD; ev.dev.id = dev->id; ev.dev.devtype = dev->type; broadcast_event(&ev); } } } int init_devices_usb(void) { int i; struct device *dev; struct usb_dev_info *usblist, *usbdev; struct usbdb_entry *uent; spnav_event ev = {0}; char buf[256]; /* detect any supported USB devices */ usblist = find_usb_devices(match_usbdev); usbdev = usblist; while(usbdev) { for(i=0; inum_devfiles; i++) { if((dev = dev_path_in_use(usbdev->devfiles[i]))) { if(verbose > 1) { logmsg(LOG_WARNING, "already using device: %s (%s) (id: %d)\n", dev->name, dev->path, dev->id); } break; } uent = find_usbdb_entry(usbdev->vendorid, usbdev->productid); dev = add_device(); strcpy(dev->path, usbdev->devfiles[i]); dev->type = uent ? uent->type : DEV_UNKNOWN; dev->flags = uent ? uent->flags : 0; dev->bnhack = uent ? uent->bnmap : 0; dev->usbid[0] = usbdev->vendorid; dev->usbid[1] = usbdev->productid; if(open_dev_usb(dev) == -1) { remove_device(dev); } else { /* add the 6dof remapping flags to every future 3dconnexion device */ if(dev->usbid[0] == VID_3DCONN) { dev->flags |= DF_SWAPYZ | DF_INVYZ; } /* sanity-check the device flags */ if((dev->flags & (DF_SWAPYZ | DF_INVYZ)) && dev->num_axes != 6) { logmsg(LOG_WARNING, "BUG: Tried to add 6dof device flags to a device with %d axes. Please report this as a bug\n", dev->num_axes); dev->flags &= ~(DF_SWAPYZ | DF_INVYZ); } logmsg(LOG_INFO, "using device: %s (%s)\n", dev->name, dev->path); if(dev->flags) { strcpy(buf, " device flags:"); if(dev->flags & DF_SWAPYZ) strcat(buf, " swap y-z"); if(dev->flags & DF_INVYZ) strcat(buf, " invert y-z"); logmsg(LOG_INFO, "%s\n", buf); } /* new USB device added, send device change event */ ev.dev.type = EVENT_DEV; ev.dev.op = DEV_ADD; ev.dev.id = dev->id; ev.dev.devtype = dev->type; ev.dev.usbid[0] = dev->usbid[0]; ev.dev.usbid[1] = dev->usbid[1]; broadcast_event(&ev); break; } } usbdev = usbdev->next; } free_usb_devices_list(usblist); if(!usblist) { if(verbose > 1) { logmsg(LOG_ERR, "failed to find any supported USB devices\n"); } return -1; } #ifdef USE_X11 drop_xinput(); #endif return 0; } static struct device *add_device(void) { struct device *dev; if(!(dev = malloc(sizeof *dev))) { return 0; } memset(dev, 0, sizeof *dev); dev->fd = -1; dev->id = last_id++; dev->next = dev_list; dev_list = dev; logmsg(LOG_INFO, "adding device (id: %d).\n", dev->id); return dev_list; } void remove_device(struct device *dev) { struct device dummy; struct device *iter; spnav_event ev; logmsg(LOG_INFO, "removing device: %s (id: %d path: %s)\n", dev->name, dev->id, dev->path); dummy.next = dev_list; iter = &dummy; while(iter->next) { if(iter->next == dev) { iter->next = dev->next; break; } iter = iter->next; } dev_list = dummy.next; remove_dev_event(dev); if(dev->close) { dev->close(dev); } /* send device change event to clients */ ev.dev.type = EVENT_DEV; ev.dev.op = DEV_RM; ev.dev.id = dev->id; ev.dev.devtype = dev->type; ev.dev.usbid[0] = dev->usbid[0]; ev.dev.usbid[1] = dev->usbid[1]; broadcast_event(&ev); free(dev); } struct device *dev_path_in_use(const char *dev_path) { struct device *iter = dev_list; while(iter) { if(strcmp(iter->path, dev_path) == 0) { return iter; } iter = iter->next; } return 0; } int get_device_fd(struct device *dev) { return dev ? dev->fd : -1; } int get_device_index(struct device *dev) { struct device *iter = dev_list; int index = 0; while(iter) { if(dev == iter) { return index; } index++; iter = iter->next; } return -1; } int read_device(struct device *dev, struct dev_input *inp) { if(dev->read == NULL) { return -1; } if(dev->read(dev, inp) == -1) { return -1; } if(inp->type == INP_MOTION) { if(dev->flags & DF_SWAPYZ) { static const int swap[] = {0, 2, 1, 3, 5, 4}; inp->idx = swap[inp->idx]; } if((dev->flags & DF_INVYZ) && inp->idx != 0 && inp->idx != 3) { inp->val = -inp->val; } } return 0; } void set_device_led(struct device *dev, int state) { if(dev->set_led) { dev->set_led(dev, state); } } void set_devices_led(int state) { struct device *dev = get_devices(); while(dev) { set_device_led(dev, state); dev = dev->next; } } struct device *get_devices(void) { return dev_list; } static int match_usbdev(const struct usb_dev_info *devinfo) { int i; /* match any USB devices listed in the config file */ for(i=0; ivendorid && (unsigned int)cfg.devid[i][1] == devinfo->productid) { return 1; } if(cfg.devname[i] && devinfo->name && strcmp(cfg.devname[i], devinfo->name) == 0) { return 1; } } if(devinfo->vendorid != -1 && devinfo->productid != -1) { int vid = devinfo->vendorid; int pid = devinfo->productid; /* ignore any device in the devid_blacklist */ for(i=0; devid_blacklist[i][0] > 0; i++) { if(vid == devid_blacklist[i][0] && pid == devid_blacklist[i][1]) { return 0; } } /* match any device with the new 3Dconnexion device id */ if(vid == VID_3DCONN) { /* avoid matching and trying to grab the CAD mouse, when connected * on the same universal receiver as the spacemouse. */ if(pid == 0xc652 && strstr(devinfo->name, "Universal Receiver Mouse")) { return 0; } return 1; } /* match any device in the usbdb */ for(i=0; usbdb[i].usbid[0] > 0; i++) { if(vid == usbdb[i].usbid[0] && pid == usbdb[i].usbid[1]) { return 1; } } } /* if it's a 3Dconnexion device match it immediately */ if((devinfo->name && strstr(devinfo->name, "3Dconnexion"))) { return 1; } return 0; /* no match */ } static struct usbdb_entry *find_usbdb_entry(unsigned int vid, unsigned int pid) { int i; for(i=0; usbdb[i].usbid[0] != -1; i++) { if(usbdb[i].usbid[0] == vid && usbdb[i].usbid[1] == pid) { return usbdb + i; } } return 0; } /* --- button remapping hack functions --- */ /* SpaceMouse Pro */ int bnhack_smpro(int bn) { if(bn < 0) return 15; /* button count */ switch(bn) { case 256: return 4; /* menu */ case 257: return 5; /* fit */ case 258: return 6; /* [T] */ case 260: return 7; /* [R] */ case 261: return 8; /* [F] */ case 264: return 9; /* [ ] */ case 268: return 0; /* 1 */ case 269: return 1; /* 2 */ case 270: return 2; /* 3 */ case 271: return 3; /* 4 */ case 278: return 11; /* esc */ case 279: return 12; /* alt */ case 280: return 13; /* shift */ case 281: return 14; /* ctrl */ case 282: return 10; /* lock */ default: break; } return -1; /* ignore all other events */ } /* SpaceMouse Enterprise */ int bnhack_sment(int bn) { if(bn < 0) return 31; /* button count */ switch(bn) { case 256: return 12; /* menu */ case 257: return 13; /* fit */ case 258: return 14; /* [T] */ case 260: return 15; /* [R] */ case 261: return 16; /* [F] */ case 264: return 17; /* [ ] */ case 266: return 30; /* iso */ case 268: return 0; /* 1 */ case 269: return 1; /* 2 */ case 270: return 2; /* 3 */ case 271: return 3; /* 4 */ case 272: return 4; /* 5 */ case 273: return 5; /* 6 */ case 274: return 6; /* 7 */ case 275: return 7; /* 8 */ case 276: return 8; /* 9 */ case 277: return 9; /* 10 */ case 278: return 18; /* esc */ case 279: return 19; /* alt */ case 280: return 20; /* shift */ case 281: return 21; /* ctrl */ case 282: return 22; /* lock */ case 291: return 23; /* enter */ case 292: return 24; /* delete */ case 332: return 10; /* 11 */ case 333: return 11; /* 12 */ case 358: return 27; /* V1 */ case 359: return 28; /* V2 */ case 360: return 29; /* V3 */ case 430: return 25; /* tab */ case 431: return 26; /* space */ default: break; } return -1; /* ignore all other events */ } spacenavd-1.3.1/src/dev_serial.c0000664000175000017500000004256314737545500016422 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #include #include #include #include #include #include #ifdef HAVE_ALLOCA_H #include #endif #ifdef HAVE_MALLOC_H #include #endif #include #include #include #include #include #include #include "dev_serial.h" #include "dev.h" #include "event.h" #include "logger.h" #include "proto.h" #if defined(__i386__) || defined(__ia64__) || defined(WIN32) || \ (defined(__alpha__) || defined(__alpha)) || \ defined(__arm__) || \ (defined(__mips__) && defined(__MIPSEL__)) || \ defined(__SYMBIAN32__) || \ defined(__x86_64__) || \ defined(__LITTLE_ENDIAN__) #define SBALL_LITTLE_ENDIAN #else #define SBALL_BIG_ENDIAN #endif #define INP_BUF_SZ 256 #define EVQUEUE_SZ 64 enum { SB4000 = 1, FLIPXY = 2 }; struct sball { int fd; unsigned int flags; char buf[INP_BUF_SZ]; int len; short mot[6]; unsigned int keystate, keymask; struct termios saved_term; int saved_mstat; struct dev_input evqueue[EVQUEUE_SZ]; int evq_rd, evq_wr; struct device *dev; int (*parse)(struct sball*, int, char*, int); }; static void close_dev_serial(struct device *dev); static int read_dev_serial(struct device *dev, struct dev_input *inp); static int stty_sball(struct sball *sb); static int stty_mag(struct sball *sb); static void stty_save(struct sball *sb); static void stty_restore(struct sball *sb); static int proc_input(struct sball *sb); static int mag_parsepkt(struct sball *sb, int id, char *data, int len); static int sball_parsepkt(struct sball *sb, int id, char *data, int len); static int guess_num_buttons(struct device *dev, const char *verstr); static void make_printable(char *buf, int len); static int read_timeout(int fd, char *buf, int bufsz, long tm_usec); static void enqueue_motion(struct sball *sb, int axis, int val); static void gen_button_events(struct sball *sb, unsigned int prev); static char *memstr(char *buf, int len, const char *str); int open_dev_serial(struct device *dev) { int fd, sz; char buf[128]; struct sball *sb = 0; if((fd = open(dev->path, O_RDWR | O_NOCTTY | O_NONBLOCK)) == -1) { logmsg(LOG_ERR, "open_dev_serial: failed to open device: %s: %s\n", dev->path, strerror(errno)); return -1; } if(!isatty(fd)) { logmsg(LOG_ERR, "open_dev_serial: refusing to use %s: not a TTY\n", dev->path); close(fd); return -1; } if(!(sb = calloc(1, sizeof *sb))) { logmsg(LOG_ERR, "open_dev_serial: failed to allocate sball object\n"); goto err; } sb->dev = dev; dev->data = sb; dev->fd = sb->fd = fd; dev->num_axes = 6; dev->close = close_dev_serial; dev->read = read_dev_serial; stty_save(sb); if(stty_sball(sb) == -1) { goto err; } /* Apparently some spaceballs take some time to initialize, and it's * necessary to wait for a little while before we start sending commands. */ sleep(1); write(fd, "\r@RESET\r", 8); if((sz = read_timeout(fd, buf, sizeof buf - 1, 2000000)) > 0 && memstr(buf, sz, "@1")) { /* we got a response, so it's a spaceball */ make_printable(buf, sz); logmsg(LOG_INFO, "Spaceball detected: %s\n", buf); strcpy(dev->name, "Spaceball"); dev->num_buttons = guess_num_buttons(dev, buf); sb->keymask = 0xffff >> (16 - dev->num_buttons); logmsg(LOG_INFO, "%d buttons\n", dev->num_buttons); /* set binary mode and enable automatic data packet sending. also request * a key event to find out as soon as possible if this is a 4000flx with * 12 buttons */ write(fd, "\rCB\rMSSV\rk\r", 11); sb->parse = sball_parsepkt; return 0; } /* try as a magellan spacemouse */ if(stty_mag(sb) == -1) { goto err; } write(fd, "vQ\r", 3); if((sz = read_timeout(fd, buf, sizeof buf - 1, 250000)) > 0 && buf[0] == 'v') { make_printable(buf, sz); logmsg(LOG_INFO, "Magellan SpaceMouse detected:\n%s\n", buf); strcpy(dev->name, "Magellan SpaceMouse"); dev->num_buttons = guess_num_buttons(dev, buf); sb->keymask = 0xffff >> (16 - dev->num_buttons); logmsg(LOG_INFO, "%d buttons\n", dev->num_buttons); /* set 3D mode, not-dominant-axis, pass through motion and button packets */ write(fd, "m3\r", 3); /* also attempt the compress mode-set command with extended keys enabled */ write(fd, "c3B\r", 4); sb->parse = mag_parsepkt; return 0; } err: stty_restore(sb); close(fd); free(sb); return -1; } static void close_dev_serial(struct device *dev) { if(dev->data) { stty_restore(dev->data); close(dev->fd); } dev->data = 0; } static int read_dev_serial(struct device *dev, struct dev_input *inp) { int sz; struct sball *sb = dev->data; if(!sb) return -1; while((sz = read(sb->fd, sb->buf + sb->len, INP_BUF_SZ - sb->len - 1)) > 0) { sb->len += sz; proc_input(sb); } /* if we fill the input buffer, make a last attempt to parse it, and discard * it so we can receive more */ if(sb->len >= INP_BUF_SZ) { proc_input(sb); sb->len = 0; } if(sb->evq_rd != sb->evq_wr) { *inp = sb->evqueue[sb->evq_rd]; sb->evq_rd = (sb->evq_rd + 1) & (EVQUEUE_SZ - 1); return 0; } return -1; } /* Labtec spaceball: 9600 8n1 XON/XOFF * Can't use canonical mode to assemble input into lines for the spaceball, * because binary data received for motion events can include newlines which * would be eaten up by the line discipline. Therefore we'll rely on VTIME=1 to * hopefully get more than 1 byte at a time. Alternatively we could request * printable reports, but I don't feel like implementing that. */ static int stty_sball(struct sball *sb) { int mstat; struct termios term; term = sb->saved_term; term.c_oflag = 0; term.c_lflag = 0; term.c_cc[VMIN] = 0; term.c_cc[VTIME] = 1; term.c_cflag = CLOCAL | CREAD | CS8 | HUPCL; term.c_iflag = IGNBRK | IGNPAR | IXON | IXOFF; cfsetispeed(&term, B9600); cfsetospeed(&term, B9600); if(tcsetattr(sb->fd, TCSAFLUSH, &term) == -1) { perror("open_dev_serial: tcsetattr"); return -1; } tcflush(sb->fd, TCIOFLUSH); mstat = sb->saved_mstat | TIOCM_DTR | TIOCM_RTS; ioctl(sb->fd, TIOCMGET, &mstat); return 0; } /* Logicad magellan spacemouse: 9600 8n2 CTS/RTS * Since the magellan devices don't seem to send any newlines, we can rely on * canonical mode to feed us nice whole lines at a time. */ static int stty_mag(struct sball *sb) { int mstat; struct termios term; term = sb->saved_term; term.c_oflag = 0; term.c_lflag = ICANON; term.c_cc[VMIN] = 0; term.c_cc[VTIME] = 0; term.c_cc[VEOF] = 0; term.c_cc[VEOL] = '\r'; term.c_cc[VEOL2] = 0; term.c_cc[VERASE] = 0; term.c_cc[VKILL] = 0; term.c_cflag = CLOCAL | CREAD | CS8 | CSTOPB | HUPCL; #ifdef CCTS_OFLOW term.c_cflag |= CCTS_OFLOW; #elif defined(CRTSCTS) term.c_cflag |= CRTSCTS; #endif term.c_iflag = IGNBRK | IGNPAR; cfsetispeed(&term, B9600); cfsetospeed(&term, B9600); if(tcsetattr(sb->fd, TCSAFLUSH, &term) == -1) { perror("open_dev_serial: tcsetattr"); return -1; } tcflush(sb->fd, TCIOFLUSH); mstat = sb->saved_mstat | TIOCM_DTR | TIOCM_RTS; ioctl(sb->fd, TIOCMGET, &mstat); return 0; } static void stty_save(struct sball *sb) { tcgetattr(sb->fd, &sb->saved_term); ioctl(sb->fd, TIOCMGET, &sb->saved_mstat); } static void stty_restore(struct sball *sb) { tcsetattr(sb->fd, TCSAFLUSH, &sb->saved_term); tcflush(sb->fd, TCIOFLUSH); ioctl(sb->fd, TIOCMSET, &sb->saved_mstat); } static int proc_input(struct sball *sb) { int sz; char *bptr = sb->buf; char *start = sb->buf; char *end = sb->buf + sb->len; /* see if we have a CR in the buffer */ while(bptr < end) { if(*bptr == '\r') { *bptr = 0; sb->parse(sb, *start, start + 1, bptr - start - 1); start = ++bptr; } else { bptr++; } } sz = start - sb->buf; if(sz > 0) { memmove(sb->buf, start, sz); sb->len -= sz; } return 0; } static int mag_parsepkt(struct sball *sb, int id, char *data, int len) { int i, prev, motion_pending = 0; unsigned int prev_key; /*logmsg(LOG_DEBUG, "magellan packet: %c - %s (%d bytes)\n", (char)id, data, len);*/ switch(id) { case 'd': if(len != 24) { logmsg(LOG_WARNING, "magellan: invalid data packet, expected 24 bytes, got: %d\n", len); return -1; } for(i=0; i<6; i++) { prev = sb->mot[i]; sb->mot[i] = ((((int)data[0] & 0xf) << 12) | (((int)data[1] & 0xf) << 8) | (((int)data[2] & 0xf) << 4) | (data[3] & 0xf)) - 0x8000; data += 4; /* flip the Z axis sign to match the spaceball */ if(i == 2 || i == 5) { sb->mot[i] = -sb->mot[i]; } if(sb->mot[i] != prev) { enqueue_motion(sb, i, sb->mot[i]); motion_pending++; } } if(motion_pending) { enqueue_motion(sb, -1, 0); } break; case 'k': if(len < 3) { logmsg(LOG_WARNING, "magellan: invalid keyboard pakcet, expected 3 bytes, got: %d\n", len); return -1; } prev_key = sb->keystate; sb->keystate = (data[0] & 0xf) | ((data[1] & 0xf) << 4) | (((unsigned int)data[2] & 0xf) << 8); if(len > 3) { sb->keystate |= ((unsigned int)data[3] & 0xf) << 12; } if(sb->keystate != prev_key) { gen_button_events(sb, prev_key); } break; case 'e': if(data[0] == 1) { logmsg(LOG_WARNING, "magellan error: illegal command: %c%c\n", data[1], data[2]); } else if(data[0] == 2) { logmsg(LOG_WARNING, "magellan error: framing error\n"); } else { logmsg(LOG_WARNING, "magellan error: unknown device error\n"); } return -1; default: break; } return 0; } static int sball_parsepkt(struct sball *sb, int id, char *data, int len) { int i, prev, motion_pending = 0; char c, *rd, *wr; unsigned int prev_key; char *errbuf, *errbuf_end; errbuf = alloca(len * 16 + 32); /* decode data packet, replacing escaped values with the correct ones */ rd = wr = data; while(rd < data + len) { if((c = *rd++) == '^') { switch(*rd++) { case 'Q': *wr++ = 0x11; /* XON */ break; case 'S': *wr++ = 0x13; /* XOFF */ break; case 'M': *wr++ = 13; /* CR */ break; case '^': *wr++ = '^'; break; default: logmsg(LOG_WARNING, "sball decode: ignoring invalid escape code: %xh\n", (unsigned int)c); } } else { *wr++ = c; } } len = wr - data; /* update the decoded length */ switch(id) { case 'D': if(len != 14) { logmsg(LOG_WARNING, "sball: invalid data packet, expected 14 bytes, got: %d\n", len); return -1; } #ifndef SBALL_BIG_ENDIAN rd = data; for(i=0; i<6; i++) { rd += 2; c = rd[0]; rd[0] = rd[1]; rd[1] = c; } #endif for(i=0; i<6; i++) { char *dest = (char*)(sb->mot + i); data += 2; prev = sb->mot[i]; *dest++ = data[0]; *dest++ = data[1]; if(sb->mot[i] != prev) { enqueue_motion(sb, i, sb->mot[i]); motion_pending++; } } if(motion_pending) { enqueue_motion(sb, -1, 0); } break; case 'K': if(len != 2) { logmsg(LOG_WARNING, "sball: invalid key packet, expected 2 bytes, got: %d\n", len); return -1; } if(sb->flags & SB4000) break; /* ignore K packets from spaceball 4000 devices */ prev_key = sb->keystate; /* data[1] bits 0-3 -> buttons 0,1,2,3 * data[1] bits 4,5 (3003 L/R) -> buttons 0, 1 * data[0] bits 0-2 -> buttons 4,5,6 * data[0] bit 4 is (2003 pick) -> button 7 */ sb->keystate = ((data[1] & 0xf) | ((data[1] >> 4) & 3) | ((data[0] & 7) << 4) | ((data[0] & 0x10) << 3)) & sb->keymask; if(sb->keystate != prev_key) { gen_button_events(sb, prev_key); } break; case '.': if(len != 2) { logmsg(LOG_WARNING, "sball: invalid sb4k key packet, expected 2 bytes, got: %d\n", len); return -1; } /* spaceball 4000 key packet */ if(!(sb->flags & SB4000)) { logmsg(LOG_INFO, "Switching to spaceball 4000flx/5000flx-a mode (12 buttons) \n"); sb->flags |= SB4000; sb->dev->num_buttons = 12; /* might have guessed 8 before */ sb->keymask = 0xfff; strcpy(sb->dev->name, "Spaceball 4000FLX"); sb->dev->type = DEV_SB4000; } /* update orientation flag (actually don't bother) */ /* if(data[0] & 0x20) { sb->flags |= FLIPXY; } else { sb->flags &= ~FLIPXY; } */ prev_key = sb->keystate; /* data[1] bits 0-5 -> buttons 0,1,2,3,4,5 * data[1] bit 7 -> button 6 * data[0] bits 0-4 -> buttons 7,8,9,10,11 */ sb->keystate = (data[1] & 0x3f) | ((data[1] & 0x80) >> 1) | ((data[0] & 0x1f) << 7); if(sb->keystate != prev_key) { gen_button_events(sb, prev_key); } break; case 'E': strcpy(errbuf, "sball: error:"); errbuf_end = errbuf + 13; for(i=0; itype = DEV_SB3003; strcpy(dev->name, "Spaceball 3003/3003C"); return 2; /* spaceball 3003/3003C */ } if(minor == 43 || minor == 45) { dev->type = DEV_SB4000; strcpy(dev->name, "Spaceball 4000FLX/5000FLX-A"); return 12; /* spaceball 4000flx/5000flx-a */ } if(minor == 2 || minor == 13 || minor == 15 || minor == 42) { /* 2.42 is also used by spaceball 4000flx. we'll guess 2003c for * now, and change the buttons to 12 first time we get a '.' * packet. I'll also request a key report during init to make * sure this happens as soon as possible, before clients have a * chance to connect. */ dev->type = DEV_SB2003; strcpy(dev->name, "Spaceball 1003/2003/2003C"); return 8; /* spaceball 1003/2003/2003c */ } } } if(strstr(verstr, "MAGELLAN")) { dev->type = DEV_SM; strcpy(dev->name, "Magellan SpaceMouse"); return 11; /* magellan spacemouse (assume ext buttons on plus/xt) */ } if(strstr(verstr, "SPACEBALL")) { dev->type = DEV_SM5000; strcpy(dev->name, "Spaceball 5000"); return 12; /* spaceball 5000 */ } if(strstr(verstr, "CadMan")) { dev->type = DEV_SMCADMAN; strcpy(dev->name, "CadMan"); return 4; } logmsg(LOG_DEBUG, "Can't guess number of buttons, default to 8, report this as a bug!\n"); return 8; } static void make_printable(char *buf, int len) { int i, c; char *wr = buf; for(i=0; i 0) { tv.tv_sec = usec / 1000000; tv.tv_usec = usec % 1000000; FD_ZERO(&rdset); FD_SET(fd, &rdset); if((res = select(fd + 1, &rdset, 0, 0, &tv)) > 0 && FD_ISSET(fd, &rdset)) { sz += read(fd, buf + sz, bufsz - sz); buf[sz] = 0; tm_usec = usec = 128000; /* wait 128ms for the rest of the message to appear */ gettimeofday(&tv0, 0); continue; } if(res == -1 && (errno == EWOULDBLOCK || errno == EAGAIN)) { break; } gettimeofday(&tv, 0); usec = tm_usec - ((tv.tv_sec - tv0.tv_sec) * 1000000 + (tv.tv_usec - tv0.tv_usec)); } return sz > 0 ? sz : -1; } static void enqueue_motion(struct sball *sb, int axis, int val) { struct dev_input *inp = sb->evqueue + sb->evq_wr; sb->evq_wr = (sb->evq_wr + 1) & (EVQUEUE_SZ - 1); if(sb->evq_wr == sb->evq_rd) { /* overflow, drop the oldest event */ sb->evq_rd = (sb->evq_rd + 1) & (EVQUEUE_SZ - 1); } if(axis >= 0) { inp->type = INP_MOTION; inp->idx = axis; inp->val = val; } else { inp->type = INP_FLUSH; } } static void gen_button_events(struct sball *sb, unsigned int prev) { int i; unsigned int bit = 1; unsigned int diff = sb->keystate ^ prev; struct dev_input *inp; for(i=0; i<16; i++) { if(diff & bit) { inp = sb->evqueue + sb->evq_wr; sb->evq_wr = (sb->evq_wr + 1) & (EVQUEUE_SZ - 1); if(sb->evq_wr == sb->evq_rd) { /* overflow, drop the oldest event */ sb->evq_rd = (sb->evq_rd + 1) & (EVQUEUE_SZ - 1); } inp->type = INP_BUTTON; inp->idx = i; inp->val = sb->keystate & bit ? 1 : 0; } bit <<= 1; } } static char *memstr(char *buf, int len, const char *str) { int i, slen = strlen(str); for(i=0; i This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SPNAV_DEV_USB_H_ #define SPNAV_DEV_USB_H_ #define VID_3DCONN 0x256f #define PID_WIRELESS 0xc652 struct device; int open_dev_usb(struct device *dev); /* USB device enumeration and matching */ #define MAX_USB_DEV_FILES 16 struct usb_dev_info { char *name; int num_devfiles; char *devfiles[MAX_USB_DEV_FILES]; int vendorid, productid; struct usb_dev_info *next; }; struct usb_dev_info *find_usb_devices(int (*match)(const struct usb_dev_info*)); void free_usb_devices_list(struct usb_dev_info *list); void print_usb_device_info(struct usb_dev_info *devinfo); /* see usbdb_entry table bnmap field in dev.c */ int bnhack_smpro(int bn); int bnhack_sment(int bn); #endif /* SPNAV_DEV_USB_H_ */ spacenavd-1.3.1/src/proto.h0000664000175000017500000001553214737545500015451 0ustar nuclearnuclear#ifndef PROTO_H_ #define PROTO_H_ #include "config.h" #ifdef HAVE_STDINT_H #include #elif defined(HAVE_INTTYPES_H) #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_ */ spacenavd-1.3.1/src/logger.h0000664000175000017500000000034614737545500015562 0ustar nuclearnuclear#ifndef LOGGER_H_ #define LOGGER_H_ #include /* for log priority levels */ int start_logfile(const char *fname); int start_syslog(const char *id); void logmsg(int prio, const char *fmt, ...); #endif /* LOGGER_H_ */ spacenavd-1.3.1/src/dummy_usb.c0000664000175000017500000000307514737545500016304 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #if !defined(__linux__) && !(defined(__APPLE__) && defined(__MACH__)) && !defined(__FreeBSD__) #include #include "logger.h" #include "dev.h" static const char *message = "Unfortunately this version of spacenavd does not support USB devices on your " "platform yet. Make sure you are using the latest version of spacenavd.\n"; struct usb_dev_info *find_usb_devices(int (*match)(const struct usb_dev_info*)) { logmsg(LOG_ERR, message); return 0; } int open_dev_usb(struct device *dev) { return -1; } /* the hotplug functions will also be missing on unsupported platforms */ int init_hotplug(void) { return -1; } void shutdown_hotplug(void) { } int get_hotplug_fd(void) { return -1; } int handle_hotplug(void) { return -1; } #else int dummy_usb_c_avoid_stupid_compiler_warnings = 1; #endif /* unsupported platform */ spacenavd-1.3.1/src/dev.h0000664000175000017500000000366514737545500015070 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SPNAV_DEV_H_ #define SPNAV_DEV_H_ #include #include "config.h" struct dev_input; #define MAX_DEV_NAME 256 struct device { int id; int fd; void *data; char name[MAX_DEV_NAME]; char path[PATH_MAX]; int type; unsigned int usbid[2]; /* vendor:product for USB devices */ unsigned int flags; int num_axes, num_buttons; int bnbase; /* button base (reported number of first button) */ int *minval, *maxval; /* input value range (default: -500, 500) */ int *fuzz; /* noise threshold */ void (*close)(struct device*); int (*read)(struct device*, struct dev_input*); void (*set_led)(struct device*, int); int (*bnhack)(int bn); struct device *next; }; void init_devices(void); void init_devices_serial(void); int init_devices_usb(void); void remove_device(struct device *dev); int get_device_fd(struct device *dev); #define is_device_valid(dev) (get_device_fd(dev) >= 0) int get_device_index(struct device *dev); int read_device(struct device *dev, struct dev_input *inp); void set_device_led(struct device *dev, int state); void set_devices_led(int state); struct device *get_devices(void); struct device *dev_path_in_use(const char *dev_path); #endif /* SPNAV_DEV_H_ */ spacenavd-1.3.1/src/cfgfile.c0000664000175000017500000005370214737545500015701 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include "cfgfile.h" #include "logger.h" #include "spnavd.h" /* all parsable config options... some of them might map to the same cfg field */ enum { CFG_REPEAT, CFG_DEADZONE, CFG_DEADZONE_N, CFG_DEADZONE_TX, CFG_DEADZONE_TY, CFG_DEADZONE_TZ, CFG_DEADZONE_RX, CFG_DEADZONE_RY, CFG_DEADZONE_RZ, CFG_SENS, CFG_SENS_TRANS, CFG_SENS_TX, CFG_SENS_TY, CFG_SENS_TZ, CFG_SENS_ROT, CFG_SENS_RX, CFG_SENS_RY, CFG_SENS_RZ, CFG_INVROT, CFG_INVTRANS, CFG_SWAPYZ, CFG_AXISMAP_N, CFG_BNMAP_N, CFG_BNACT_N, CFG_KBMAP_N, CFG_LED, CFG_GRAB, CFG_SERIAL, CFG_DEVID, NUM_CFG_OPTIONS }; enum { RMCFG_ALL, RMCFG_OWN }; /* number of lines to add to the cfglines allocation, in order to allow for * adding any number of additional options if necessary */ #define NUM_EXTRA_LINES (NUM_CFG_OPTIONS + MAX_CUSTOM + MAX_BUTTONS * 3 + MAX_AXES + 16) static int parse_bnact(const char *s); static const char *bnact_name(int bnact); static int add_cfgopt(int opt, int idx, const char *fmt, ...); static int add_cfgopt_devid(int vid, int pid); static int rm_cfgopt(const char *name, int mode); enum {TX, TY, TZ, RX, RY, RZ}; struct cfgline { char *str; /* actual line text */ int opt; /* CFG_* item */ int idx; int own; /* added and owned by spacenavd, not in the original user config */ }; static struct cfgline *cfglines; static int num_lines; void default_cfg(struct cfg *cfg) { int i; memset(cfg, 0, sizeof *cfg); cfg->sensitivity = 1.0; for(i=0; i<3; i++) { cfg->sens_trans[i] = cfg->sens_rot[i] = 1.0; } for(i=0; i<6; i++) { cfg->dead_threshold[i] = 2; } cfg->led = LED_ON; cfg->grab_device = 1; for(i=0; i<6; i++) { cfg->map_axis[i] = i; } for(i=0; imap_button[i] = i; cfg->kbmap_str[i] = 0; cfg->kbmap[i] = 0; } cfg->repeat_msec = -1; for(i=0; idevname[i] = 0; cfg->devid[i][0] = cfg->devid[i][1] = -1; } } void unlock_cfgfile(int fd) { struct flock flk; flk.l_type = F_UNLCK; flk.l_start = flk.l_len = 0; flk.l_whence = SEEK_SET; fcntl(fd, F_SETLK, &flk); } #define EXPECT(cond) \ do { \ if(!(cond)) { \ logmsg(LOG_ERR, "%s: invalid value for %s\n", __func__, key_str); \ continue; \ } \ } while(0) int read_cfg(const char *fname, struct cfg *cfg) { FILE *fp; int i, c, fd; char buf[512]; struct flock flk; int num_devid = 0; struct cfgline *lptr; default_cfg(cfg); logmsg(LOG_INFO, "reading config file: %s\n", fname); if(!(fp = fopen(fname, "r"))) { logmsg(LOG_WARNING, "failed to open config file %s: %s. using defaults.\n", fname, strerror(errno)); return -1; } fd = fileno(fp); /* acquire shared read lock */ flk.l_type = F_RDLCK; flk.l_start = flk.l_len = 0; flk.l_whence = SEEK_SET; while(fcntl(fd, F_SETLKW, &flk) == -1); /* count newlines and populate lines array */ num_lines = 0; while((c = fgetc(fp)) != -1) { if(c == '\n') num_lines++; } rewind(fp); if(!num_lines) num_lines = 1; /* add enough lines to be able to append any number of new options */ free(cfglines); if(!(cfglines = calloc(num_lines + NUM_EXTRA_LINES, sizeof *cfglines))) { logmsg(LOG_WARNING, "failed to allocate config lines buffer (%d lines)\n", num_lines); unlock_cfgfile(fd); fclose(fp); return -1; } /* parse config file */ num_lines = 0; while(fgets(buf, sizeof buf, fp)) { int isint, isfloat, ival, bnidx, axisidx; float fval; char *endp, *key_str, *val_str, *line = buf; lptr = cfglines + num_lines++; if((endp = strchr(buf, '\r')) || (endp = strchr(buf, '\n'))) { *endp = 0; } if(!(lptr->str = strdup(buf))) { logmsg(LOG_WARNING, "failed to allocate config line buffer, skipping line %d.\n", num_lines); continue; } while(*line == ' ' || *line == '\t') line++; if(!*line || *line == '\n' || *line == '\r' || *line == '#') { continue; /* ignore comments and empty lines */ } if(!(key_str = strtok(line, " =\n\t\r"))) { logmsg(LOG_WARNING, "invalid config line: %s, skipping.\n", line); continue; } if(!(val_str = strtok(0, " =\n\t\r"))) { logmsg(LOG_WARNING, "missing value for config key: %s\n", key_str); continue; } ival = strtol(val_str, &endp, 10); isint = (endp > val_str); fval = strtod(val_str, &endp); isfloat = (endp > val_str); if(strcmp(key_str, "repeat-interval") == 0) { lptr->opt = CFG_REPEAT; EXPECT(isint); cfg->repeat_msec = ival; } else if(strcmp(key_str, "dead-zone") == 0) { lptr->opt = CFG_DEADZONE; EXPECT(isint); for(i=0; idead_threshold[i] = ival; } } else if(sscanf(key_str, "dead-zone%d", &axisidx) == 1) { if(axisidx < 0 || axisidx >= MAX_AXES) { logmsg(LOG_WARNING, "invalid option %s, valid input axis numbers 0 - %d\n", key_str, MAX_AXES - 1); continue; } lptr->opt = CFG_DEADZONE_N; lptr->idx = axisidx; cfg->dead_threshold[axisidx] = ival; } else if(strcmp(key_str, "dead-zone-translation-x") == 0) { logmsg(LOG_WARNING, "Deprecated option: %s. You are encouraged to use dead-zoneN instead\n", key_str); lptr->opt = CFG_DEADZONE_TX; EXPECT(isint); cfg->dead_threshold[0] = ival; } else if(strcmp(key_str, "dead-zone-translation-y") == 0) { logmsg(LOG_WARNING, "Deprecated option: %s. You are encouraged to use dead-zoneN instead\n", key_str); lptr->opt = CFG_DEADZONE_TY; EXPECT(isint); cfg->dead_threshold[1] = ival; } else if(strcmp(key_str, "dead-zone-translation-z") == 0) { logmsg(LOG_WARNING, "Deprecated option: %s. You are encouraged to use dead-zoneN instead\n", key_str); lptr->opt = CFG_DEADZONE_TZ; EXPECT(isint); cfg->dead_threshold[2] = ival; } else if(strcmp(key_str, "dead-zone-rotation-x") == 0) { logmsg(LOG_WARNING, "Deprecated option: %s. You are encouraged to use dead-zoneN instead\n", key_str); lptr->opt = CFG_DEADZONE_RX; EXPECT(isint); cfg->dead_threshold[3] = ival; } else if(strcmp(key_str, "dead-zone-rotation-y") == 0) { logmsg(LOG_WARNING, "Deprecated option: %s. You are encouraged to use dead-zoneN instead\n", key_str); lptr->opt = CFG_DEADZONE_RY; EXPECT(isint); cfg->dead_threshold[4] = ival; } else if(strcmp(key_str, "dead-zone-rotation-z") == 0) { logmsg(LOG_WARNING, "Deprecated option: %s. You are encouraged to use dead-zoneN instead\n", key_str); lptr->opt = CFG_DEADZONE_RZ; EXPECT(isint); cfg->dead_threshold[5] = ival; } else if(strcmp(key_str, "sensitivity") == 0) { lptr->opt = CFG_SENS; EXPECT(isfloat); cfg->sensitivity = fval; } else if(strcmp(key_str, "sensitivity-translation") == 0) { lptr->opt = CFG_SENS_TRANS; EXPECT(isfloat); cfg->sens_trans[0] = cfg->sens_trans[1] = cfg->sens_trans[2] = fval; } else if(strcmp(key_str, "sensitivity-translation-x") == 0) { lptr->opt = CFG_SENS_TX; EXPECT(isfloat); cfg->sens_trans[0] = fval; } else if(strcmp(key_str, "sensitivity-translation-y") == 0) { lptr->opt = CFG_SENS_TY; EXPECT(isfloat); cfg->sens_trans[1] = fval; } else if(strcmp(key_str, "sensitivity-translation-z") == 0) { lptr->opt = CFG_SENS_TZ; EXPECT(isfloat); cfg->sens_trans[2] = fval; } else if(strcmp(key_str, "sensitivity-rotation") == 0) { lptr->opt = CFG_SENS_ROT; EXPECT(isfloat); cfg->sens_rot[0] = cfg->sens_rot[1] = cfg->sens_rot[2] = fval; } else if(strcmp(key_str, "sensitivity-rotation-x") == 0) { lptr->opt = CFG_SENS_RX; EXPECT(isfloat); cfg->sens_rot[0] = fval; } else if(strcmp(key_str, "sensitivity-rotation-y") == 0) { lptr->opt = CFG_SENS_RY; EXPECT(isfloat); cfg->sens_rot[1] = fval; } else if(strcmp(key_str, "sensitivity-rotation-z") == 0) { lptr->opt = CFG_SENS_RZ; EXPECT(isfloat); cfg->sens_rot[2] = fval; } else if(strcmp(key_str, "invert-rot") == 0) { lptr->opt = CFG_INVROT; if(strchr(val_str, 'x')) { cfg->invert[RX] = 1; } if(strchr(val_str, 'y')) { cfg->invert[RY] = 1; } if(strchr(val_str, 'z')) { cfg->invert[RZ] = 1; } } else if(strcmp(key_str, "invert-trans") == 0) { lptr->opt = CFG_INVTRANS; if(strchr(val_str, 'x')) { cfg->invert[TX] = 1; } if(strchr(val_str, 'y')) { cfg->invert[TY] = 1; } if(strchr(val_str, 'z')) { cfg->invert[TZ] = 1; } } else if(strcmp(key_str, "swap-yz") == 0) { lptr->opt = CFG_SWAPYZ; if(isint) { cfg->swapyz = ival; } else { if(strcmp(val_str, "true") == 0 || strcmp(val_str, "on") == 0 || strcmp(val_str, "yes") == 0) { cfg->swapyz = 1; } else if(strcmp(val_str, "false") == 0 || strcmp(val_str, "off") == 0 || strcmp(val_str, "no") == 0) { cfg->swapyz = 0; } else { logmsg(LOG_WARNING, "invalid configuration value for %s, expected a boolean value.\n", key_str); continue; } } } else if(sscanf(key_str, "axismap%d", &axisidx) == 1) { EXPECT(isint); if(axisidx < 0 || axisidx >= MAX_AXES) { logmsg(LOG_WARNING, "invalid option %s, valid input axis numbers 0 - %d\n", key_str, MAX_AXES - 1); continue; } if(ival < 0 || ival >= 6) { logmsg(LOG_WARNING, "invalid config value for %s, expected a number from 0 to 6\n", key_str); continue; } lptr->opt = CFG_AXISMAP_N; lptr->idx = axisidx; cfg->map_axis[axisidx] = ival; } else if(sscanf(key_str, "bnmap%d", &bnidx) == 1) { EXPECT(isint); if(bnidx < 0 || bnidx >= MAX_BUTTONS || ival < 0 || ival >= MAX_BUTTONS) { logmsg(LOG_WARNING, "invalid configuration value for %s, expected a number from 0 to %d\n", key_str, MAX_BUTTONS); continue; } if(cfg->map_button[bnidx] != bnidx) { logmsg(LOG_WARNING, "warning: multiple mappings for button %d\n", bnidx); } lptr->opt = CFG_BNMAP_N; lptr->idx = bnidx; cfg->map_button[bnidx] = ival; } else if(sscanf(key_str, "bnact%d", &bnidx) == 1) { if(bnidx < 0 || bnidx >= MAX_BUTTONS) { logmsg(LOG_WARNING, "invalid configuration value for %s, expected a number from 0 to %d\n", key_str, MAX_BUTTONS); continue; } lptr->opt = CFG_BNACT_N; lptr->idx = bnidx; if((cfg->bnact[bnidx] = parse_bnact(val_str)) == -1) { cfg->bnact[bnidx] = BNACT_NONE; logmsg(LOG_WARNING, "invalid button action: \"%s\"\n", val_str); continue; } } else if(sscanf(key_str, "kbmap%d", &bnidx) == 1) { if(bnidx < 0 || bnidx >= MAX_BUTTONS) { logmsg(LOG_WARNING, "invalid configuration value for %s, expected a number from 0 to %d\n", key_str, MAX_BUTTONS); continue; } lptr->opt = CFG_KBMAP_N; lptr->idx = bnidx; if(cfg->kbmap_str[bnidx]) { logmsg(LOG_WARNING, "warning: multiple keyboard mappings for button %d: %s -> %s\n", bnidx, cfg->kbmap_str[bnidx], val_str); free(cfg->kbmap_str[bnidx]); } cfg->kbmap_str[bnidx] = strdup(val_str); } else if(strcmp(key_str, "led") == 0) { lptr->opt = CFG_LED; if(isint) { cfg->led = ival; } else { if(strcmp(val_str, "auto") == 0) { cfg->led = LED_AUTO; } else if(strcmp(val_str, "true") == 0 || strcmp(val_str, "on") == 0 || strcmp(val_str, "yes") == 0) { cfg->led = LED_ON; } else if(strcmp(val_str, "false") == 0 || strcmp(val_str, "off") == 0 || strcmp(val_str, "no") == 0) { cfg->led = LED_OFF; } else { logmsg(LOG_WARNING, "invalid configuration value for %s, expected a boolean value.\n", key_str); continue; } } } else if(strcmp(key_str, "grab") == 0) { lptr->opt = CFG_GRAB; if(isint) { cfg->grab_device = ival; } else { if(strcmp(val_str, "true") == 0 || strcmp(val_str, "on") == 0 || strcmp(val_str, "yes") == 0) { cfg->grab_device = 1; } else if(strcmp(val_str, "false") == 0 || strcmp(val_str, "off") == 0 || strcmp(val_str, "no") == 0) { cfg->grab_device = 0; } else { logmsg(LOG_WARNING, "invalid configuration value for %s, expected a boolean value.\n", key_str); continue; } } } else if(strcmp(key_str, "serial") == 0) { lptr->opt = CFG_SERIAL; strncpy(cfg->serial_dev, val_str, PATH_MAX - 1); } else if(strcmp(key_str, "device-id") == 0) { unsigned int vendor, prod; lptr->opt = CFG_DEVID; if(sscanf(val_str, "%x:%x", &vendor, &prod) == 2) { cfg->devid[num_devid][0] = (int)vendor; cfg->devid[num_devid][1] = (int)prod; num_devid++; } else { logmsg(LOG_WARNING, "invalid configuration value for %s, expected a vendorid:productid pair\n", key_str); continue; } } else { logmsg(LOG_WARNING, "unrecognized config option: %s\n", key_str); } } unlock_cfgfile(fd); fclose(fp); return 0; } int write_cfg(const char *fname, struct cfg *cfg) { int i, same; FILE *fp; struct flock flk; struct cfg def; char buf[128]; if(!(fp = fopen(fname, "w"))) { logmsg(LOG_ERR, "failed to write config file %s: %s\n", fname, strerror(errno)); return -1; } if(!cfglines) { if(!(cfglines = calloc(NUM_EXTRA_LINES, sizeof *cfglines))) { logmsg(LOG_WARNING, "failed to allocate config lines buffer\n"); fclose(fp); return -1; } } default_cfg(&def); /* default config for comparisons */ if(cfg->sensitivity != def.sensitivity) { add_cfgopt(CFG_SENS, 0, "sensitivity = %.3f", cfg->sensitivity); } if(cfg->sens_trans[0] == cfg->sens_trans[1] && cfg->sens_trans[1] == cfg->sens_trans[2]) { rm_cfgopt("sensitivity-translation-x", RMCFG_ALL); rm_cfgopt("sensitivity-translation-y", RMCFG_ALL); rm_cfgopt("sensitivity-translation-z", RMCFG_ALL); if(cfg->sens_trans[0] != def.sens_trans[0]) { add_cfgopt(CFG_SENS_TRANS, 0, "sensitivity-translation = %.3f", cfg->sens_trans[0]); } else { rm_cfgopt("sensitivity-translation", RMCFG_OWN); } } else { if(cfg->sens_trans[0] != def.sens_trans[0]) { add_cfgopt(CFG_SENS_TX, 0, "sensitivity-translation-x = %.3f", cfg->sens_trans[0]); rm_cfgopt("sensitivity-translation", RMCFG_ALL); } else { rm_cfgopt("sensitivity-translation-x", RMCFG_OWN); } if(cfg->sens_trans[1] != def.sens_trans[1]) { add_cfgopt(CFG_SENS_TY, 0, "sensitivity-translation-y = %.3f", cfg->sens_trans[1]); rm_cfgopt("sensitivity-translation", RMCFG_ALL); } else { rm_cfgopt("sensitivity-translation-y", RMCFG_OWN); } if(cfg->sens_trans[2] != def.sens_trans[2]) { add_cfgopt(CFG_SENS_TZ, 0, "sensitivity-translation-z = %.3f", cfg->sens_trans[2]); rm_cfgopt("sensitivity-translation", RMCFG_ALL); } else { rm_cfgopt("sensitivity-translation-z", RMCFG_OWN); } } if(cfg->sens_rot[0] == cfg->sens_rot[1] && cfg->sens_rot[1] == cfg->sens_rot[2]) { rm_cfgopt("sensitivity-rotation-x", RMCFG_ALL); rm_cfgopt("sensitivity-rotation-y", RMCFG_ALL); rm_cfgopt("sensitivity-rotation-z", RMCFG_ALL); if(cfg->sens_rot[0] != def.sens_rot[0]) { add_cfgopt(CFG_SENS_ROT, 0, "sensitivity-rotation = %.3f", cfg->sens_rot[0]); } else { rm_cfgopt("sensitivity-rotation", RMCFG_OWN); } } else { if(cfg->sens_rot[0] != def.sens_rot[0]) { add_cfgopt(CFG_SENS_RX, 0, "sensitivity-rotation-x = %.3f", cfg->sens_rot[0]); rm_cfgopt("sensitivity-rotation", RMCFG_ALL); } else { rm_cfgopt("sensitivity-rotation-x", RMCFG_OWN); } if(cfg->sens_rot[1] != def.sens_rot[1]) { add_cfgopt(CFG_SENS_RY, 0, "sensitivity-rotation-y = %.3f", cfg->sens_rot[1]); rm_cfgopt("sensitivity-rotation", RMCFG_ALL); } else { rm_cfgopt("sensitivity-rotation-y", RMCFG_OWN); } if(cfg->sens_rot[2] != def.sens_rot[2]) { add_cfgopt(CFG_SENS_RZ, 0, "sensitivity-rotation-z = %.3f", cfg->sens_rot[2]); rm_cfgopt("sensitivity-rotation", RMCFG_ALL); } else { rm_cfgopt("sensitivity-rotation-z", RMCFG_OWN); } } same = 1; for(i=1; idead_threshold[i] != cfg->dead_threshold[i - 1]) { same = 0; break; } } if(same) { if(cfg->dead_threshold[0] != def.dead_threshold[0]) { add_cfgopt(CFG_DEADZONE, 0, "dead-zone = %d", cfg->dead_threshold[0]); for(i=0; idead_threshold[i] != def.dead_threshold[i]) { add_cfgopt(CFG_DEADZONE_N, i, "dead-zone%d = %d", i, cfg->dead_threshold[i]); rm_cfgopt("dead-zone", RMCFG_ALL); } else { sprintf(buf, "dead-zone%d", i); rm_cfgopt(buf, RMCFG_OWN); } } } if(cfg->repeat_msec != def.repeat_msec) { add_cfgopt(CFG_REPEAT, 0, "repeat-interval = %d\n", cfg->repeat_msec); } else { rm_cfgopt("repeat-interval", RMCFG_ALL); } if(cfg->invert[0] || cfg->invert[1] || cfg->invert[2]) { char flags[4] = {0}, *p = flags; if(cfg->invert[0]) *p++ = 'x'; if(cfg->invert[1]) *p++ = 'y'; if(cfg->invert[2]) *p = 'z'; add_cfgopt(CFG_INVTRANS, 0, "invert-trans = %s", flags); } else { rm_cfgopt("invert-trans", RMCFG_ALL); } if(cfg->invert[3] || cfg->invert[4] || cfg->invert[5]) { char flags[4] = {0}, *p = flags; if(cfg->invert[3]) *p++ = 'x'; if(cfg->invert[4]) *p++ = 'y'; if(cfg->invert[5]) *p = 'z'; add_cfgopt(CFG_INVROT, 0, "invert-rot = %s", flags); } else { rm_cfgopt("invert-rot", RMCFG_ALL); } if(cfg->swapyz) { add_cfgopt(CFG_SWAPYZ, 0, "swap-yz = true"); } else { rm_cfgopt("swap-yz", RMCFG_ALL); } for(i=0; imap_button[i] != i) { add_cfgopt(CFG_BNMAP_N, i, "bnmap%d = %d", i, cfg->map_button[i]); } else { sprintf(buf, "bnmap%d", i); rm_cfgopt(buf, RMCFG_ALL); } } for(i=0; ibnact[i] != BNACT_NONE) { add_cfgopt(CFG_BNACT_N, i, "bnact%d = %s", i, bnact_name(cfg->bnact[i])); } else { sprintf(buf, "bnact%d", i); rm_cfgopt(buf, RMCFG_ALL); } } for(i=0; ikbmap_str[i]) { add_cfgopt(CFG_KBMAP_N, i, "kbmap%d = %s", i, cfg->kbmap_str[i]); } else { sprintf(buf, "kbmap%d", i); rm_cfgopt(buf, RMCFG_ALL); } } if(cfg->led != def.led) { add_cfgopt(CFG_LED, 0, "led = %s", (cfg->led ? (cfg->led == LED_AUTO ? "auto" : "on") : "off")); } else { rm_cfgopt("led", RMCFG_OWN); } if(cfg->grab_device != def.grab_device) { add_cfgopt(CFG_GRAB, 0, "grab = %s", cfg->grab_device ? "true" : "false"); } else { rm_cfgopt("grab", RMCFG_OWN); } if(cfg->serial_dev[0]) { add_cfgopt(CFG_SERIAL, 0, "serial = %s", cfg->serial_dev); } else { rm_cfgopt("serial", RMCFG_ALL); } for(i=0; idevid[i][0] != -1 && cfg->devid[i][1] != -1) { add_cfgopt_devid(cfg->devid[i][0], cfg->devid[i][1]); } } /* acquire exclusive write lock */ flk.l_type = F_WRLCK; flk.l_start = flk.l_len = 0; flk.l_whence = SEEK_SET; while(fcntl(fileno(fp), F_SETLKW, &flk) == -1); for(i=0; iown = 1; } free(lptr->str); lptr->str = str; lptr->opt = opt; lptr->idx = idx; return 0; } static int add_cfgopt_devid(int vid, int pid) { int i; unsigned int dev[2]; struct cfgline *lptr = 0; char *str, *val; if(!(str = malloc(64))) return -1; sprintf(str, "device-id = %04x:%04x", vid, pid); for(i=0; i ptr && isspace(*--endp)) *endp = 0; if(strcmp(ptr, name) == 0) { if(mode != RMCFG_OWN || cfglines[i].own) { free(cfglines[i].str); cfglines[i].str = 0; } return 0; } } return -1; } spacenavd-1.3.1/src/proto_unix.h0000664000175000017500000000203214737545500016503 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PROTO_UNIX_H_ #define PROTO_UNIX_H_ #include "config.h" #include "event.h" #include "client.h" int init_unix(void); void close_unix(void); int get_unix_socket(void); void send_uevent(spnav_event *ev, struct client *c); int handle_uevents(fd_set *rset); #endif /* PROTO_UNIX_H_ */ spacenavd-1.3.1/src/hotplug_darwin.c0000664000175000017500000000220614737545500017321 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #if defined(__APPLE__) && defined(__MACH__) #include "config.h" #include #include #include int init_hotplug(void) { return -1; } void shutdown_hotplug(void) { } int get_hotplug_fd(void) { return -1; } int handle_hotplug(void) { return -1; } #else int spacenavd_hotplug_darwin_shut_up_empty_source_warning; #endif /* __APPLE__ && __MACH__ */ spacenavd-1.3.1/src/cfgfile.h0000664000175000017500000000341514737545500015702 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef CFGFILE_H_ #define CFGFILE_H_ #include #define MAX_AXES 64 #define MAX_BUTTONS 64 #define MAX_CUSTOM 64 enum { LED_OFF = 0, LED_ON = 1, LED_AUTO = 2 }; /* button actions (XXX: must correspond to SPNAV_BNACT_* in libspnav) */ enum { BNACT_NONE, BNACT_SENS_RESET, BNACT_SENS_INC, BNACT_SENS_DEC, BNACT_DISABLE_ROTATION, BNACT_DISABLE_TRANSLATION, BNACT_DOMINANT_AXIS, MAX_BNACT }; struct cfg { float sensitivity, sens_trans[3], sens_rot[3]; int dead_threshold[MAX_AXES]; int invert[MAX_AXES]; int map_axis[MAX_AXES]; int map_button[MAX_BUTTONS]; int bnact[MAX_BUTTONS]; int kbmap[MAX_BUTTONS]; char *kbmap_str[MAX_BUTTONS]; int swapyz; int led, grab_device; char serial_dev[PATH_MAX]; int repeat_msec; char *devname[MAX_CUSTOM]; /* custom USB device name list */ int devid[MAX_CUSTOM][2]; /* custom USB vendor/product id list */ }; void default_cfg(struct cfg *cfg); int read_cfg(const char *fname, struct cfg *cfg); int write_cfg(const char *fname, struct cfg *cfg); #endif /* CFGFILE_H_ */ spacenavd-1.3.1/src/kbemu.h0000664000175000017500000000177314737545500015413 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KBEMU_H_ #define KBEMU_H_ #include #include void kbemu_set_display(Display *dpy); KeySym kbemu_keysym(const char *str); const char *kbemu_keyname(KeySym sym); void send_kbevent(KeySym key, int press); #endif /* KBEMU_H_ */ spacenavd-1.3.1/src/hotplug.h0000664000175000017500000000165714737545500015773 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SPNAV_HOTPLUG_H_ #define SPNAV_HOTPLUG_H_ int init_hotplug(void); void shutdown_hotplug(void); int get_hotplug_fd(void); int handle_hotplug(void); #endif /* SPNAV_HOTPLUG_H_ */ spacenavd-1.3.1/src/spnavd.h0000664000175000017500000000265614737545500015604 0ustar nuclearnuclear/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2025 John Tsiombikas This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SPNAVD_H_ #define SPNAVD_H_ #include "config.h" #include "cfgfile.h" #include "logger.h" #define DEF_CFGFILE CFGDIR "/spnavrc" #define DEF_LOGFILE "/var/log/spnavd.log" #define SOCK_NAME "/var/run/spnav.sock" #define PIDFILE "/var/run/spnavd.pid" #define SYSLOG_ID "spnavd" /* Multiple devices support */ #ifndef MAX_DEVICES #define MAX_DEVICES 8 #endif #if defined(__cplusplus) || (__STDC_VERSION__ >= 199901L) #define INLINE inline #else /* not C++ or C99 */ #ifdef __GNUC__ #define INLINE __inline__ #else #define INLINE #endif #endif struct cfg cfg, prev_cfg; extern char *cfgfile; /* defined in spnavd.c */ int verbose; void cfg_changed(void); #endif /* SPNAVD_H_ */ spacenavd-1.3.1/src/proto.c0000664000175000017500000000256014737545500015441 0ustar nuclearnuclear#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; } spacenavd-1.3.1/contrib/0000775000175000017500000000000014737545500015000 5ustar nuclearnuclearspacenavd-1.3.1/contrib/README0000664000175000017500000000026314737545500015661 0ustar nuclearnuclearFiles in the contrib subdirectory are not maintained by the spacenav project maintainers. They are just 3rd party contributions included just in case they prove useful to anyone. spacenavd-1.3.1/contrib/systemd/0000775000175000017500000000000014737545500016470 5ustar nuclearnuclearspacenavd-1.3.1/contrib/systemd/spacenavd.service0000664000175000017500000000034314737545500022016 0ustar nuclearnuclear[Unit] Description=3Dconnexion Input Devices Userspace Driver After=syslog.target [Service] Type=forking PIDFile=/var/run/spnavd.pid ExecStart=/usr/local/bin/spacenavd StandardError=syslog [Install] WantedBy=graphical.target spacenavd-1.3.1/setup_init0000775000175000017500000000257614737545500015463 0ustar nuclearnuclear#!/bin/sh setup_sysv_init() { rlvl=`cat /etc/inittab | grep :initdefault: | sed 's/^i.://; s/:init.*$//'` if [ $? != 0 -o -z "$rlvl" ]; then echo 'default runlevel detection failed.' rlvl=2 fi echo "selected runlevel: $rlvl" if [ "$1" = 'remove' ]; then echo 'removing sysv init script' rm -f /etc/init.d/spacenavd rm -f /etc/rc${rlvl}.d/S99spacenavd else echo 'setting up sysv init script' install -m 755 init_script /etc/init.d/spacenavd cd /etc/rc${rlvl}.d rm -f S99spacenavd ln -s ../init.d/spacenavd S99spacenavd fi } setup_bsd_init() { echo 'setting up bsd init' echo "BSD init setup not implemented yet, you'll have to do it manually." } if [ "$1" = '--no-install' ]; then echo echo --- Spacenavd installation complete --- echo To have spacenavd start automatically at bootup, you must add an appropriate echo "init script. Refer to your system's manual for details on how to do that." echo An example init script is available in the spacenavd source directory. echo If you wish to attempt and install an init script automatically, run ./setup_init echo exit 0 fi if [ -d /etc/init.d -a -d /etc/rc0.d ]; then setup_sysv_init $* exit 0 fi if [ -f /etc/rc -a -d /etc/rc.d ]; then setup_bsd_init $* exit 0 fi echo "You're either using a non-standard init or this detection failed." echo "You'll have to setup your init, to start spacenavd, manually." spacenavd-1.3.1/Makefile.in0000664000175000017500000000153614737545500015412 0ustar nuclearnuclearsrc = $(sort $(wildcard src/*.c)) hdr = $(wildcard src/*.h) obj = $(src:.c=.o) dep = $(obj:.o=.d) bin = spacenavd ctl = spnavd_ctl warn = -pedantic -Wall CC ?= gcc CFLAGS = $(warn) $(dbg) $(opt) -fno-strict-aliasing -fcommon \ -I$(srcdir)/src $(xinc) -MMD $(add_cflags) LDFLAGS = $(xlib) $(add_ldflags) -lm $(bin): $(obj) $(CC) -o $@ $(obj) $(LDFLAGS) -include $(dep) tags: $(src) $(hdr) ctags $(src) $(hdr) %.o: $(srcdir)/%.c $(CC) $(CFLAGS) -c $< -o $@ .PHONY: clean clean: rm -f $(obj) $(bin) .PHONY: cleandep cleandep: rm -f $(dep) .PHONY: install install: $(bin) mkdir -p $(DESTDIR)$(PREFIX)/bin cp $(bin) $(DESTDIR)$(PREFIX)/bin/$(bin) cp $(srcdir)/$(ctl) $(DESTDIR)$(PREFIX)/bin/$(ctl) cd $(srcdir) && ./setup_init --no-install .PHONY: uninstall uninstall: rm -f $(DESTDIR)$(PREFIX)/bin/$(bin) rm -f $(DESTDIR)$(PREFIX)/bin/$(ctl)