diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..3912109 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey 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 2 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, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This 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 Library General +Public License instead of this License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2e239e7 --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +# Disable -Werror if there are compile errors +CFLAGS+=-Wall -Werror -g +all: loadmap reserve_js + +loadmap: loadmap.o dictionary.o mapparser.o programparser.o validkeys.o events.o vm.o devices.o config.o + $(CC) -g -o loadmap loadmap.o dictionary.o mapparser.o programparser.o validkeys.o events.o vm.o devices.o config.o + +reserve_js: reserve_js.o config.o + $(CC) -g -o reserve_js reserve_js.o config.o + +clean: + rm -f reserve_js reserve_js.o loadmap loadmap.o dictionary.o mapparser.o programparser.o validkeys.o events.o vm.o devices.o config.o diff --git a/README b/README new file mode 100644 index 0000000..b265646 --- /dev/null +++ b/README @@ -0,0 +1,21 @@ +1) Make sure that the module uinput is loaded, + I have + rmmod ehci_hcd + rmmod ohci_hcd + rmmod uinput + modprobe uinput + /sbin/reserve_js + modprobe ehci_hcd + modprobe ohci_hcd + in /etc/rc.d/rc.local. This ensures that js0 through 16 are reserved + for the program. The program /sbin/reserve_js waits 10 seconds + before releasing js0 through 16. + +2) You need permissions on /dev/uinput and /dev/input/event* +3) The uinput device can now be specified with --uinput_dev, + and the event device prefix with --event_dev +4) Run the program loadmap with your script, it will continue running + and provide the joystick events programmed until the program is + terminated. +5) Joystick selection by number is broken, the vendor and product + identifier must be used diff --git a/clock.h b/clock.h new file mode 100644 index 0000000..3bf19e2 --- /dev/null +++ b/clock.h @@ -0,0 +1,14 @@ +#include +#include + +static __uint64_t clock_millis() { + struct timeval tv; + static __uint64_t last = 0; + int result = gettimeofday(&tv, NULL); + if (result != 0) { + printf("Failed to read clock\n"); + return last; + } + last = (__uint64_t)tv.tv_sec * 1000 + (__uint64_t)tv.tv_usec / 1000; + return last; +} diff --git a/config.c b/config.c new file mode 100644 index 0000000..b2030e5 --- /dev/null +++ b/config.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include "config.h" + +char *current_config[CONFIG_MAX] = { + /* UINPUT_DEV */ + "/dev/uinput", + /* EVENT_DEV */ + "/dev/input/event", + NULL, +}; + +char *cmdline_arg[CONFIG_MAX] = { + /* UINPUT_DEV */ + "--uinput_dev", + /* EVENT_DEV */ + "--event_dev", + NULL, +}; + +char *get_config(int key) { + if ((key >= CONFIG_MAX) || (key < 0)) { + fprintf(stderr, "ERR: Invalid config key in code: %d\n", key); + exit(1); + } + return current_config[key]; +} + +char *set_config(int key, char *value) { + char *old; + if ((key >= CONFIG_MAX) || (key < 0)) { + fprintf(stderr, "ERR: Invalid config key in code: %d\n", key); + exit(1); + } + old = current_config[key]; + current_config[key] = value; + return old; +} + +int match_config(char *arg) { + int i=0; + while (cmdline_arg[i] != NULL) { + if (strcmp(cmdline_arg[i], arg) == 0) + return i; + i++; + } + return -1; +} + +void cmdline_config(int argc, char *argv[]) { + int i, index; + for (i=0; i= 0) { + if (i + 1 >= argc) { + fprintf(stderr, "Missing argument for: %s\n", argv[i]); + } + current_config[index] = argv[++i]; + } + } +} diff --git a/config.h b/config.h new file mode 100644 index 0000000..9e01a59 --- /dev/null +++ b/config.h @@ -0,0 +1,12 @@ +#ifndef __config_h +#define __config_h + +#define CONFIG_MAX 10 +#define UINPUT_DEV 0 +#define EVENT_DEV 1 + +char *get_config(int key); +char *set_config(int key, char *value); +void cmdline_config(int argc, char *argv[]); + +#endif diff --git a/devices.c b/devices.c new file mode 100644 index 0000000..e0d011a --- /dev/null +++ b/devices.c @@ -0,0 +1,468 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "clock.h" +#include "program.h" +#include "mapper.h" + +#define NUM_JOYSTICKS 10 +#define INTERVAL 50 + +struct jscal { + int min; + int max; + int stable; +}; + +struct joystick_device { + int axes; + int buttons; + int fd; + struct jscal cal; +}; + +static struct joystick_device devices[NUM_JOYSTICKS]={ + {0,0,-1, {0,0,1000}}, + {0,0,-1, {0,0,1000}}, + {0,0,-1, {0,0,1000}}, + {0,0,-1, {0,0,1000}}, + {0,0,-1, {0,0,1000}}, + {0,0,-1, {0,0,1000}}, + {0,0,-1, {0,0,1000}}, + {0,0,-1, {0,0,1000}}, + {0,0,-1, {0,0,1000}}, + {0,0,-1, {0,0,1000}} +}; + +static int mouse_fd; +static int kbd_fd; +static int code_fd; +static int njoysticks; +static int x = 0; +static int y = 0; +static int dx = 0; +static int dy = 0; +static int adx = 0; +static int ady = 0; +static int adw = 0; +static int dwheel = 0; + +void set_num_joysticks(int num) { + njoysticks=num; +} + +void set_num_axes(int js, int axes) { + if ((js<0)||(js>NUM_JOYSTICKS)) return; + devices[js].axes=axes; +} + +void set_num_buttons(int js, int buttons) { + if ((js<0)||(js>NUM_JOYSTICKS)) return; + devices[js].buttons=buttons; +} + +int valid_open(char *file, int flags) { + int fd = open(file, flags); + if (fd < 0) { + fprintf(stderr, "Error opening %s\n", file); + perror("Error opening file"); + exit(1); + } + return fd; +} + +int safe_ioctl3(int fd, int cmd, int arg) { + int r = ioctl(fd, cmd, arg); + if (r < 0) { + perror("Failed to execute ioctl"); + exit(1); + } + return r; +} + +int safe_ioctl2(int fd, int cmd) { + int r = ioctl(fd, cmd); + if (r < 0) { + perror("Failed to execute ioctl"); + exit(1); + } + return r; +} + +int safe_write(int fd, void *data, int size) { + int r = write(fd, data, size); + if (r != size) { + if (r < 0) { + perror("Failed to execute ioctl"); + exit(1); + } + fprintf(stderr, "Short write\n"); + exit(1); + } + return r; +} + +void register_devices() { + int i, j; + struct uinput_user_dev dev; + for (i=0; i= INTERVAL) { + mdx = adx / INTERVAL; + adx -= mdx * INTERVAL; + } + + if (abs(ady) >= INTERVAL) { + mdy = ady / INTERVAL; + ady -= mdy * INTERVAL; + } + + x += mdx; + y += mdy; + if ((mdx) || (mdy)) { + send_move_mouse_x(mdx); + send_move_mouse_y(mdy); + } + } + + if (dwheel) { + adw += delta * dwheel; + if (abs(adw) >= INTERVAL) { + mdw = adw / INTERVAL; + adw -= mdw * INTERVAL; + } + + if (mdw) + send_move_mouse_wheel(mdw); + } + + last = current; +} + +void release_mouse_buttons(void) { + press_mouse_button(BTN_LEFT,0); + press_mouse_button(BTN_RIGHT,0); + press_mouse_button(BTN_MIDDLE,0); +} + +void press_joy_button(int j, int code, int value) { + struct input_event event; + gettimeofday(&event.time, NULL); + if (j==255) { + event.type=EV_KEY; + event.code=code; + event.value=value; + safe_write(code_fd, &event, sizeof(event)); + event.type=EV_SYN; + event.code=SYN_REPORT; + event.value=0; + safe_write(code_fd, &event, sizeof(event)); + return; + } + if ((j<0)||(j>=NUM_JOYSTICKS)) return; + event.type=EV_KEY; + event.code=code; + event.value=value; + safe_write(devices[j].fd, &event, sizeof(event)); + event.type=EV_SYN; + event.code=SYN_REPORT; + event.value=0; + safe_write(devices[j].fd, &event, sizeof(event)); +} + +static int scale_multiplier=1; +static int scale_divider=1; +static int scale_offset=0; +static int dcal=0; +static int cal_error=6500; + +void set_scale_factor(int mult, int div, int ofs) { + scale_multiplier=mult; + scale_divider=div; + scale_offset=ofs; +} + +void set_dynamic_calibrate(int on) { + dcal=on; +} + +int rescale(int v) { + return v*scale_multiplier/scale_divider-scale_offset; +} + +int calibrate(int j, int v) { + if (!dcal) return v; + if (devices[j].cal.stable) { + devices[j].cal.stable--; + return v; + } + if ((devices[j].cal.min==devices[j].cal.max)&&(devices[j].cal.max==0)) { + devices[j].cal.min=devices[j].cal.max=v; + return v; + } + if (vdevices[j].cal.max) devices[j].cal.max=v; + if (devices[j].cal.min==devices[j].cal.max) + return v; + v=(v-devices[j].cal.min)*65536/devices[j].cal.max-devices[j].cal.min-32767; + v=(v*32768)/(32768-cal_error); + return v; +} + +void set_joy_axis(int j, int axis, int value) { + struct input_event event; + gettimeofday(&event.time, NULL); + value = rescale(value); + if (j==255) { + event.type=EV_ABS; + event.code=axis; + event.value=value; + safe_write(code_fd, &event, sizeof(event)); + event.type=EV_SYN; + event.code=SYN_REPORT; + event.value=0; + safe_write(code_fd, &event, sizeof(event)); + return; + } + if ((j<0)||(j>=NUM_JOYSTICKS)) return; + event.type=EV_ABS; + event.code=axis; + event.value=calibrate(j, value); + safe_write(devices[j].fd, &event, sizeof(event)); + event.type=EV_SYN; + event.code=SYN_REPORT; + event.value=0; + safe_write(devices[j].fd, &event, sizeof(event)); +} diff --git a/dictionary.c b/dictionary.c new file mode 100644 index 0000000..2d4e6e2 --- /dev/null +++ b/dictionary.c @@ -0,0 +1,66 @@ +#include +#include +#include "dictionary.h" + +static entry *lookup_entry(dictionary d, char *key) { + struct entry *e; + if (d==NULL) return NULL; + e=d; + while (e!=NULL) { + if (strcmp(e->key, key)==0) { + return e; + } + e=e->next; + } + return NULL; +} + +char *lookup_dictionary(dictionary d, char *key) { + struct entry *e; + if (d==NULL) return NULL; + e=lookup_entry(d, key); + if (e==NULL) return NULL; + else return e->value; +} + +dictionary add_entry(dictionary d, char *key, char *value) { + struct entry *e; + + e=lookup_entry(d, key); + if (e!=NULL) { + e->value=strdup(value); + return d; + } + + e=(entry *)malloc(sizeof(entry)); + e->key=strdup(key); + e->value=strdup(value); + e->next=d; + return e; +} + +void free_dictionary(dictionary d) { + struct entry *nd=NULL; + while (d!=NULL) { + nd=d->next; + free(d->key); + free(d->value); + free(d); + d=nd; + } +} + +static char key_value[1024]; +char *get_current(dictionary d) { + if (d==NULL) return NULL; + strcpy(key_value, d->key); + strcat(key_value, "=\""); + strcat(key_value, d->value); + strcat(key_value, "\""); + return key_value; +} + +dictionary next_entry(dictionary d) { + if (d==NULL) return NULL; + return d->next; +} diff --git a/dictionary.h b/dictionary.h new file mode 100644 index 0000000..8e103ff --- /dev/null +++ b/dictionary.h @@ -0,0 +1,18 @@ +#ifndef __dict +#define __dict +#include + +typedef struct entry { + char *key; + char *value; + struct entry * next; +} entry; + +typedef struct entry *dictionary; + +dictionary add_entry(dictionary d, char *key, char *value); +void free_dictionary(dictionary d); +char *lookup_dictionary(dictionary d, char *key); +char *get_current(dictionary d); +dictionary next_entry(dictionary d); +#endif diff --git a/doc/code/cargame.code b/doc/code/cargame.code new file mode 100644 index 0000000..f01906f --- /dev/null +++ b/doc/code/cargame.code @@ -0,0 +1,13 @@ + var val; + #get a positive value for acceleration + # >128 indicates acceleration + val=js2.a[1]/2+128; + #produce a braking value + # <128 indicates brakes + # we need to reverse the sense of the axis + val-=js2.a[0]/2; + a[0]=val; + #note that accelerating and braking at + # the same time results in no action + # but cannot be resolved here + diff --git a/doc/code/cargame.map b/doc/code/cargame.map new file mode 100644 index 0000000..8dd2c40 --- /dev/null +++ b/doc/code/cargame.map @@ -0,0 +1,12 @@ +axis vendor=0x00ff product=0x0000 src=0 target=joyaxis axis=0 + +#Mapper code device +button vendor=0x00ff product=0x0000 src=0 target=joybtn button=1 +button vendor=0x00ff product=0x0000 src=1 target=joybtn button=2 + +#assign a joystick number fo script purposes +script vendor=0x068e product=0x00f4 device=0 #Combatstick +script vendor=0x068e product=0x00f1 device=1 #Throttle +script vendor=0x068e product=0x00f2 device=2 #Pedals + +code "cargame.code" diff --git a/doc/code/countermeasures.code b/doc/code/countermeasures.code new file mode 100644 index 0000000..9625792 --- /dev/null +++ b/doc/code/countermeasures.code @@ -0,0 +1,14 @@ + var i; + thread { + if (js0.b[0]) { + i=5; + while (i>0) { + b[0]=1; + delay(1000); + b[0]=0; + delay(2000); + i--; + } + } + } + diff --git a/doc/code/countermeasures.map b/doc/code/countermeasures.map new file mode 100644 index 0000000..682f37f --- /dev/null +++ b/doc/code/countermeasures.map @@ -0,0 +1,10 @@ +#Mapper code device +axis vendor=0x00ff product=0x0000 src=0 target=joyaxis axis=0 +button vendor=0x00ff product=0x0000 src=0 target=joybtn button=1 + +#assign a joystick number fo script purposes +script vendor=0x068e product=0x00f4 device=0 #Combatstick +script vendor=0x068e product=0x00f1 device=1 #Throttle +script vendor=0x068e product=0x00f2 device=2 #Pedals + +code "countermeasures.code" diff --git a/doc/code/toebrakes.code b/doc/code/toebrakes.code new file mode 100644 index 0000000..4361da6 --- /dev/null +++ b/doc/code/toebrakes.code @@ -0,0 +1,3 @@ + b[0]=(js2.a[0]>128); + b[1]=(js2.a[1]>128); + diff --git a/doc/code/toebrakes.map b/doc/code/toebrakes.map new file mode 100644 index 0000000..6c8df4d --- /dev/null +++ b/doc/code/toebrakes.map @@ -0,0 +1,11 @@ +axis vendor=0x00ff product=0x0000 src=0 target=joyaxis axis=0 +#Mapper code device +button vendor=0x00ff product=0x0000 src=0 target=joybtn button=1 +button vendor=0x00ff product=0x0000 src=1 target=joybtn button=2 + +#assign a joystick number fo script purposes +script vendor=0x068e product=0x00f4 device=0 #Combatstick +script vendor=0x068e product=0x00f1 device=1 #Throttle +script vendor=0x068e product=0x00f2 device=2 #Pedals + +code "toebrakes.code" diff --git a/doc/code/trimming.code b/doc/code/trimming.code new file mode 100644 index 0000000..ad70dc3 --- /dev/null +++ b/doc/code/trimming.code @@ -0,0 +1,29 @@ + var trimx; + var trimy; + # the original values of trimx and trimy + var ox, oy; + if (firstscan) { + #center position is zero + trimx=128; + trimy=128; + ox=128; + oy=128; + } + #check for trimming button pressed + if (js0.b[0]) { + trimx=128-js0.a[0]+ox; + trimy=128-js0.a[1]+oy; + } else { + ox=trimx; + oy=trimy; + } + #check for reset button pressed + if (js0.b[1]) { + #reset center position is zero + trimx=128; + trimy=128; + ox=128; + oy=128; + } + a[0]=js0.a[0]-trimx+128; + a[1]=js0.a[1]-trimy+128; diff --git a/doc/code/trimming.map b/doc/code/trimming.map new file mode 100644 index 0000000..8af5428 --- /dev/null +++ b/doc/code/trimming.map @@ -0,0 +1,9 @@ +axis vendor=0x00ff product=0x0000 src=0 target=joyaxis axis=0 +axis vendor=0x00ff product=0x0000 src=1 target=joyaxis axis=1 + +#assign a joystick number fo script purposes +script vendor=0x068e product=0x00f4 device=0 #Combatstick +script vendor=0x068e product=0x00f1 device=1 #Throttle +script vendor=0x068e product=0x00f2 device=2 #Pedals + +code "trimming.code" diff --git a/doc/code/waitrelease.code b/doc/code/waitrelease.code new file mode 100644 index 0000000..c21a665 --- /dev/null +++ b/doc/code/waitrelease.code @@ -0,0 +1,12 @@ + thread { + #wait for first press of the button + wait(js0.b[1]); + #wait for release + wait(!js0.b[1]); + #press virtual button + b[0]=1; + #and release after a very short time + delay(1000); + b[0]=0; + } + diff --git a/doc/code/waitrelease.map b/doc/code/waitrelease.map new file mode 100644 index 0000000..950f826 --- /dev/null +++ b/doc/code/waitrelease.map @@ -0,0 +1,11 @@ +axis vendor=0x00ff product=0x0000 src=0 target=joyaxis axis=0 +#Mapper code device +button vendor=0x00ff product=0x0000 src=0 target=joybtn button=1 +button vendor=0x00ff product=0x0000 src=1 target=joybtn button=2 + +#assign a joystick number fo script purposes +script vendor=0x068e product=0x00f4 device=0 #Combatstick +script vendor=0x068e product=0x00f1 device=1 #Throttle +script vendor=0x068e product=0x00f2 device=2 #Pedals + +code "waitrelease.code" diff --git a/doc/config.aux b/doc/config.aux new file mode 100644 index 0000000..fc7cee0 --- /dev/null +++ b/doc/config.aux @@ -0,0 +1,8 @@ +\relax +\@writefile{toc}{\contentsline {section}{\numberline {1}Mapping directives}{1}} +\@writefile{toc}{\contentsline {subsection}{\numberline {1.1}Common keys}{1}} +\@writefile{toc}{\contentsline {subsection}{\numberline {1.2}{\tt axis}}{2}} +\@writefile{toc}{\contentsline {subsection}{\numberline {1.3}{\tt button}}{3}} +\@writefile{toc}{\contentsline {subsection}{\numberline {1.4}{\tt shift}}{4}} +\@writefile{toc}{\contentsline {subsection}{\numberline {1.5}{\tt code}}{5}} +\@writefile{toc}{\contentsline {subsection}{\numberline {1.6}{\tt script}}{5}} diff --git a/doc/config.dvi b/doc/config.dvi new file mode 100644 index 0000000..f6e8aad --- /dev/null +++ b/doc/config.dvi Binary files differ diff --git a/doc/config.log b/doc/config.log new file mode 100644 index 0000000..198e52f --- /dev/null +++ b/doc/config.log @@ -0,0 +1,99 @@ +This is pdfTeX, Version 3.14159265-2.6-1.40.16 (TeX Live 2015) (preloaded format=pdflatex 2015.10.11) 21 AUG 2016 13:55 +entering extended mode + restricted \write18 enabled. + %&-line parsing enabled. +**config.tex +(./config.tex +LaTeX2e <2015/01/01> +Babel <3.9l> and hyphenation patterns for 79 languages loaded. +(/usr/local/texlive/2015/texmf-dist/tex/latex/base/article.cls +Document Class: article 2014/09/29 v1.4h Standard LaTeX document class +(/usr/local/texlive/2015/texmf-dist/tex/latex/base/size10.clo +File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option) +) +\c@part=\count79 +\c@section=\count80 +\c@subsection=\count81 +\c@subsubsection=\count82 +\c@paragraph=\count83 +\c@subparagraph=\count84 +\c@figure=\count85 +\c@table=\count86 +\abovecaptionskip=\skip41 +\belowcaptionskip=\skip42 +\bibindent=\dimen102 +) (./config.aux) +\openout1 = `config.aux'. + +LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 5. +LaTeX Font Info: ... okay on input line 5. +LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 5. +LaTeX Font Info: ... okay on input line 5. +LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 5. +LaTeX Font Info: ... okay on input line 5. +LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 5. +LaTeX Font Info: ... okay on input line 5. +LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 5. +LaTeX Font Info: ... okay on input line 5. +LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 5. +LaTeX Font Info: ... okay on input line 5. +LaTeX Font Info: External font `cmex10' loaded for size +(Font) <12> on input line 6. +LaTeX Font Info: External font `cmex10' loaded for size +(Font) <8> on input line 6. +LaTeX Font Info: External font `cmex10' loaded for size +(Font) <6> on input line 6. +LaTeX Font Info: Try loading font information for OMS+cmr on input line 35. + +(/usr/local/texlive/2015/texmf-dist/tex/latex/base/omscmr.fd +File: omscmr.fd 2014/09/29 v2.5h Standard LaTeX font definitions +) +LaTeX Font Info: Font shape `OMS/cmr/m/n' in size <10> not available +(Font) Font shape `OMS/cmsy/m/n' tried instead on input line 35. + +Overfull \hbox (4.82469pt too wide) in paragraph at lines 43--45 +\OT1/cmr/m/n/10 Values are dec-i-mal/hexadecimal num-bers, pre-de-fined key-wor +ds or strings. Strings + [] + +[1 + +{/usr/local/texlive/2015/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] +LaTeX Font Info: External font `cmex10' loaded for size +(Font) <7> on input line 79. +LaTeX Font Info: External font `cmex10' loaded for size +(Font) <5> on input line 79. + +[2] [3] +Overfull \hbox (1.49698pt too wide) in paragraph at lines 145--154 +\OT1/cmtt/m/n/10 button vendor=0x068e product=0x00f4 src=2 target=kbd button="b + REL + [] + +[4] [5] (./config.aux) ) +Here is how much of TeX's memory you used: + 255 strings out of 493089 + 2863 string characters out of 6134841 + 58199 words of memory out of 5000000 + 3809 multiletter control sequences out of 15000+600000 + 8585 words of font info for 31 fonts, out of 8000000 for 9000 + 1141 hyphenation exceptions out of 8191 + 23i,6n,19p,168b,185s stack positions out of 5000i,500n,10000p,200000b,80000s + +Output written on config.pdf (5 pages, 126074 bytes). +PDF statistics: + 60 PDF objects out of 1000 (max. 8388607) + 42 compressed objects within 1 object stream + 0 named destinations out of 1000 (max. 500000) + 1 words of extra memory for PDF output out of 10000 (max. 10000000) + diff --git a/doc/config.pdf b/doc/config.pdf new file mode 100644 index 0000000..2cc2c5c --- /dev/null +++ b/doc/config.pdf Binary files differ diff --git a/doc/config.tex b/doc/config.tex new file mode 100644 index 0000000..89e0e8d --- /dev/null +++ b/doc/config.tex @@ -0,0 +1,208 @@ +\documentclass{article} +\title{Joystick Mapper for Linux -- Configuration} +\author{Alexandre Hardy} +\parindent=0cm +\begin{document} +\maketitle +\section*{Introduction} +This document describes configuration files for +the joystick mapper for Linux. The joystick mapper +allows multiple joystick devices to be combined +into one or more joystick devices. Joystick +events can also be mapped to mouse events +or keyboards events. The system is well suited +to applications that have limited configurability with +regard to joysticks. The joystick mapper kernel drivers +must be loaded before any joystick drivers to ensure +a consistent and sensible mapping. +\section*{Configuration file format} +The configuration file consists of several lines. +Each line is a mapping from some joystick event +to another event. Blank lines are ignored. +Comments are indicated by \#. The software +ignores everything from a \# until the end of a line. + +Each line begins with a keyword describing the +kind of mapping that will be performed. A number +of key-value pairs follows the keyword to describe +the mapping. + +All axes and button values begin at 0. That is the first button +is button 0, the second button is button 2 etc. +\section{Mapping directives} +\subsection{Common keys} +\begin{itemize} + \item {\bf id} -- Identifier of joystick for which the mapping is specified. This + identifier corresponds to the mapper device allocated to this + joystick (see {\tt /proc/bus/input/devices}). + \item {\bf vendor} -- Vendor identification of the device for which the mapping + is specified (see {\tt /proc/bus/input/devices}). + \item {\bf product} -- Product identification of the device for which the mapping + is specified (see {\tt /proc/bus/input/devices}). +\end{itemize} +Values are decimal/hexadecimal numbers, predefined keywords or strings. +Strings are indicated by placing the value in double quotation marks, eg. ''string''. +\subsection{{\tt axis}} +The {\tt axis} mapping directive maps a joystick axis event +to another event. The allowed keys are +\begin{itemize} + \item {\bf id} + \item {\bf vendor} + \item {\bf product} + \item {\bf src} -- This key specifies which axis of the joystick is to be mapped. + \item {\bf target} -- This key specifies which device event will be triggered + by an axis event on the specified axis. Valid + options are {\tt mouse}, {\tt joyaxis}, {\tt joybtn} or {\tt kbd}. + \item {\bf device} -- If the target event is a joystick, then the joystick + number can be specified with the {\tt device} key. + The joystick number corresponds to 0 if js0 is selected, 1 if js1 is selected etc. + \item {\bf axis} -- If the target has an axis, then the {\tt axis} key specifies + which axis the event must be mapped to. + \item {\bf plus} + \item {\bf minus} -- If the target has a button, then the {\tt plus} ({\tt minus}) + keys indicate which buttons/keys must be triggered + if the axis is moved in a positive (negative) direction. + \item {\bf flags} -- The {\tt invert} flag can be used to reverse the sense of the + axis. i.e. positive becomes negative and negative becomes positive. The {\tt + binary} flag can be used to trigger events only when the axis is moved + completely in the other direction. The {\tt trinary} flag is similar to the binary flag + but also triggers events when the axis is centered (the 0 input value resets state). + \item {\bf min} -- The reported minimum value on the axis. Used for determining trigger levels. + \item {\bf max} -- The reported maximum value on the axis. Used for determining trigger levels. + \item {\bf deadzone} -- The deadzone for the axis. + \item {\bf speed} -- for mouse axis targets, this is a speed setting from 0 (no movement) to 32767 (very fast and default) +\end{itemize} +Either the {\tt id} or the {\tt vendor} and {\tt product} keys must be specified to identify the +joystick. The {\tt kbd} target only supports button presses. + +\strut\\ +If {\tt min} and {\tt max} are specified, then the input will be remapped into the range $[-32767, 32767]$ using the +formula +$$ + o = \frac{65536\times(x-min)}{max-min} - 32767 +$$ +where $x$ is the input and $o$ is the reported axis position. +The value will be clamped if necessary. Note that if $min > max$ then the axis reporting is switched around by this formula. + +\strut\\ +If a deadzone is specified, then the deadzone is enforced {\bf after} mapping using the formula above so that any input $x$ such +that $-deadzone \leq x \leq deadzone$ will be reported as $0$. + +\strut\\ +{\bf Example.}\\ +\strut\\ +{\tt axis vendor=0x068e product=0x00f1 src=0 target=mouse axis=0} +\strut\\ +\strut\\ +This mapping maps from a CH Products PRO Throttle axis 0 to the $x$-axis of the mouse. + +\subsection{{\tt button}} +The {\tt button} mapping directive maps a joystick button event +to another event. The allowed keys are +\begin{itemize} + \item {\bf id} + \item {\bf vendor} + \item {\bf product} + \item {\bf src} -- This key specifies which button of the joystick is to be mapped. + \item {\bf target} -- This key specifies which device event will be triggered + by an axis event on the specified axis. Valid + options are {\tt mouse}, {\tt joyaxis}, {\tt joybtn} or {\tt kbd}. + \item {\bf device} -- If the target event is a joystick, then the joystick + number can be specified with the {\tt device} key. + The joystick number corresponds to 0 if js0 is selected, 1 if js1 is selected etc. + \item {\bf button} -- If the target has a button, then the {\tt button} key specifies + which button the event must be mapped to. + \item {\bf axis} -- If the target has an axis, then the {\tt axis} key specifies + which axis the event must be mapped to. The button + will trigger a move in the positive direction on the axis. + \item {\bf flags} -- The {\tt invert} flag can be used to reverse the sense of the + axis. i.e. the button will trigger a move in the negative + direction on the axis. + + The {\tt autorelease} flag specifies that the pressed button or key + must be released immediately after pressing, even if the joystick + button is held in. + + The {\tt release} flag specifies that the mapping only applies + to a release of the joystick button. + + The {\tt press} flag specifies that the mapping only applies + to a button being pressed, and not to a button being released. + + If neither {\tt press} nor {\tt release} is specified then + two mapping entries are created, one for press and one for + release so that pressing the button on the joystick will + press the target button which will only be released when + the joystick button is released. + + The {\tt shift} key specifies that this mapping is only + applied when in a shifted state (see the {\tt shift} statement below). + \item {\bf speed} -- for mouse axis targets, this is a speed setting from 1 (slow) to 1000 (very fast). 8 is the default. +\end{itemize} +Either the {\tt id} or the {\tt vendor} and {\tt product} keys must be specified to identify the +joystick. The {\tt kbd} target only supports button presses. + +If the target is {\tt kbd}, then a string can be provided which specifies a series +of keyboard events that must be executed. +{\bf Example.}\\ +\strut\\ +{\tt button vendor=0x068e product=0x00f4 src=2 target=kbd button="b REL b a REL a n REL n g REL g leftshift 1 REL 1 REL leftshift"} +\strut\\ +\strut\\ +This mapping maps from a CH Products Combatstick, button 2, to the key sequence {\sl bang!} (see the file {\tt keys.map} +for recognized keys). {\tt REL} indicates that the next key must be released. +\subsection{{\tt shift}} +The joystick mapper supports a {\sl shifted} mode in which the operation of buttons and axes +are modified. A single button on a joystick can be designated the shift button. If +this button is pressed then the joystick is in {\sl shifted} mode. Allowed keys are +\begin{itemize} + \item {\bf id} + \item {\bf vendor} + \item {\bf product} + \item {\bf src} -- This key specifies which button of the joystick is to be the shift button. +\end{itemize} + +{\bf Example.}\\ +\strut\\ +{\tt shift vendor=0x068e product=0x00f1 src=5} +\strut\\ +\strut\\ +This mapping designates button 5 on the CH Products PRO Throttle as the shift button. +\subsection{{\tt code}} +A program can be specified that is executed at regular intervals and can generate +joystick and keyboard events. The joystick events generated by the program can be remapped +through the joystick mapper device. The code joystick is identified by vendor 0x00ff and +produce 0x0000. The syntax of programs is described in a separate document. + +{\bf Example.}\\ +\strut\\ +{\tt code "myprogram"} +\strut\\ +\strut\\ +The joystick mapper will read and attempt to compile the program in the file {\tt myprogram}. +If all mapping statements are correct, then the program will be communicated to +the kernel driver along with the other mappings. + +\subsection{{\tt script}} +The program refers to existing joysticks in the system. To prevent the +program from failing to function correctly due to changes in the +joystick allocation (allocation to js0, js1, mapper0, mapper1 etc.), +for example if the joysticks have been unplugged and reinserted, +joysticks can be numbered based on vendor and product identifiers. Allowed +keys are +\begin{itemize} + \item {\bf id} + \item {\bf vendor} + \item {\bf product} + \item {\bf device} -- Specify the joystick number in the program file. +\end{itemize} +{\bf Example.}\\ +\strut\\ +{\tt script vendor=0x068e product=0x00f4 device=0} +\strut\\ +\strut\\ +This mapping specifies that the CH Products Combatstick will +be referred to as joystick 0 within the program specified +by the {\tt code} statement. + +\end{document} diff --git a/doc/program.aux b/doc/program.aux new file mode 100644 index 0000000..741716a --- /dev/null +++ b/doc/program.aux @@ -0,0 +1,34 @@ +\relax +\@writefile{toc}{\contentsline {section}{\numberline {1}Variables}{1}} +\@writefile{toc}{\contentsline {subsection}{\numberline {1.1}{\tt firstscan}}{2}} +\@writefile{toc}{\contentsline {subsection}{\numberline {1.2}{\tt clocktick}}{2}} +\@writefile{toc}{\contentsline {subsection}{\numberline {1.3}{\tt timestamp}}{2}} +\newlabel{sec:timestamp}{{1.3}{2}} +\@writefile{toc}{\contentsline {subsection}{\numberline {1.4}{\tt currentmode}}{2}} +\@writefile{toc}{\contentsline {subsection}{\numberline {1.5}{\tt js}}{2}} +\@writefile{toc}{\contentsline {subsection}{\numberline {1.6}{\tt a}}{3}} +\@writefile{toc}{\contentsline {subsection}{\numberline {1.7}{\tt b}}{3}} +\@writefile{toc}{\contentsline {section}{\numberline {2}Operators}{3}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.1}Arithmetic operators}{3}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.2}Boolean operators}{4}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3}Precedence}{4}} +\@writefile{toc}{\contentsline {section}{\numberline {3}Program statements}{4}} +\@writefile{toc}{\contentsline {subsection}{\numberline {3.1}Assignment}{5}} +\@writefile{toc}{\contentsline {subsection}{\numberline {3.2}Statement blocks}{5}} +\@writefile{toc}{\contentsline {subsection}{\numberline {3.3}{\tt if}}{5}} +\@writefile{toc}{\contentsline {subsection}{\numberline {3.4}{\tt while}}{6}} +\@writefile{toc}{\contentsline {subsection}{\numberline {3.5}{\tt signal}}{6}} +\@writefile{toc}{\contentsline {subsection}{\numberline {3.6}{\tt press}}{6}} +\@writefile{toc}{\contentsline {subsection}{\numberline {3.7}{\tt release}}{6}} +\@writefile{toc}{\contentsline {subsection}{\numberline {3.8}{\tt thread}}{6}} +\newlabel{sec:thread}{{3.8}{6}} +\@writefile{toc}{\contentsline {subsection}{\numberline {3.9}{\tt delay}}{7}} +\newlabel{sec:delay}{{3.9}{7}} +\@writefile{toc}{\contentsline {subsection}{\numberline {3.10}{\tt wait}}{7}} +\@writefile{toc}{\contentsline {subsection}{\numberline {3.11}{\tt halt}}{8}} +\@writefile{toc}{\contentsline {section}{\numberline {4}Examples}{8}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.1}Toe Brakes}{8}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2}Car Accelerator and Brakes}{9}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3}Delayed Release of Countermeasures}{9}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4}Trimming}{10}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.5}Waiting for Release of a Button}{10}} diff --git a/doc/program.dvi b/doc/program.dvi new file mode 100644 index 0000000..dbb13b6 --- /dev/null +++ b/doc/program.dvi Binary files differ diff --git a/doc/program.log b/doc/program.log new file mode 100644 index 0000000..bbee662 --- /dev/null +++ b/doc/program.log @@ -0,0 +1,136 @@ +This is pdfTeX, Version 3.14159265-2.6-1.40.16 (TeX Live 2015) (preloaded format=pdflatex 2015.10.11) 20 FEB 2016 20:37 +entering extended mode + restricted \write18 enabled. + %&-line parsing enabled. +**program.tex +(./program.tex +LaTeX2e <2015/01/01> +Babel <3.9l> and hyphenation patterns for 79 languages loaded. +(/usr/local/texlive/2015/texmf-dist/tex/latex/base/article.cls +Document Class: article 2014/09/29 v1.4h Standard LaTeX document class +(/usr/local/texlive/2015/texmf-dist/tex/latex/base/size10.clo +File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option) +) +\c@part=\count79 +\c@section=\count80 +\c@subsection=\count81 +\c@subsubsection=\count82 +\c@paragraph=\count83 +\c@subparagraph=\count84 +\c@figure=\count85 +\c@table=\count86 +\abovecaptionskip=\skip41 +\belowcaptionskip=\skip42 +\bibindent=\dimen102 +) (./program.aux) +\openout1 = `program.aux'. + +LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 6. +LaTeX Font Info: ... okay on input line 6. +LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 6. +LaTeX Font Info: ... okay on input line 6. +LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 6. +LaTeX Font Info: ... okay on input line 6. +LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 6. +LaTeX Font Info: ... okay on input line 6. +LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 6. +LaTeX Font Info: ... okay on input line 6. +LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 6. +LaTeX Font Info: ... okay on input line 6. +LaTeX Font Info: External font `cmex10' loaded for size +(Font) <12> on input line 7. +LaTeX Font Info: External font `cmex10' loaded for size +(Font) <8> on input line 7. +LaTeX Font Info: External font `cmex10' loaded for size +(Font) <6> on input line 7. + +Underfull \hbox (badness 10000) in paragraph at lines 8--15 + + [] + + +Underfull \hbox (badness 10000) in paragraph at lines 16--21 + + [] + + +Underfull \hbox (badness 10000) in paragraph at lines 34--40 + + [] + + +Underfull \hbox (badness 10000) in paragraph at lines 45--49 + + [] + +[1 + +{/usr/local/texlive/2015/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] +LaTeX Font Info: Try loading font information for OMS+cmr on input line 94. + +(/usr/local/texlive/2015/texmf-dist/tex/latex/base/omscmr.fd +File: omscmr.fd 2014/09/29 v2.5h Standard LaTeX font definitions +) +LaTeX Font Info: Font shape `OMS/cmr/m/n' in size <10> not available +(Font) Font shape `OMS/cmsy/m/n' tried instead on input line 94. + [2] +LaTeX Font Info: External font `cmex10' loaded for size +(Font) <7> on input line 145. +LaTeX Font Info: External font `cmex10' loaded for size +(Font) <5> on input line 145. + [3] [4] +LaTeX Font Info: Try loading font information for OMS+cmtt on input line 218 +. +LaTeX Font Info: No file OMScmtt.fd. on input line 218. + + +LaTeX Font Warning: Font shape `OMS/cmtt/m/n' undefined +(Font) using `OMS/cmsy/m/n' instead +(Font) for symbol `textbraceleft' on input line 218. + + +Underfull \hbox (badness 10000) in paragraph at lines 225--228 + + [] + +[5] +Underfull \hbox (badness 10000) in paragraph at lines 284--302 + + [] + +[6] [7] +Underfull \hbox (badness 10000) in paragraph at lines 353--357 + + [] + +[8] [9] [10] [11] (./program.aux) + +LaTeX Font Warning: Some font shapes were not available, defaults substituted. + + ) +Here is how much of TeX's memory you used: + 265 strings out of 493089 + 2995 string characters out of 6134841 + 59199 words of memory out of 5000000 + 3818 multiletter control sequences out of 15000+600000 + 8270 words of font info for 30 fonts, out of 8000000 for 9000 + 1141 hyphenation exceptions out of 8191 + 23i,6n,20p,140b,239s stack positions out of 5000i,500n,10000p,200000b,80000s + + +Output written on program.pdf (11 pages, 146525 bytes). +PDF statistics: + 84 PDF objects out of 1000 (max. 8388607) + 59 compressed objects within 1 object stream + 0 named destinations out of 1000 (max. 500000) + 1 words of extra memory for PDF output out of 10000 (max. 10000000) + diff --git a/doc/program.pdf b/doc/program.pdf new file mode 100644 index 0000000..a9887eb --- /dev/null +++ b/doc/program.pdf Binary files differ diff --git a/doc/program.tex b/doc/program.tex new file mode 100644 index 0000000..39f0964 --- /dev/null +++ b/doc/program.tex @@ -0,0 +1,500 @@ +\documentclass{article} +\def\pipe{$|$} +\title{Joystick Mapper for Linux -- Programming Guide} +\author{Alexandre Hardy} +\parindent=0cm +\begin{document} +\maketitle +This document describes programming language used by the +joystick mapping software. The driver creates a new joystick +device that can be mapped with the normal joystick mapping +software. To use the programming language effectively +the virtual joystick device must be mapped to some joystick +button or axis. The virtual joystick device is indicated +by a vendor id of {\tt 0x00ff} and a product id of {\tt 0x0000}.\\ + +At each execution cycle, the program is executed from the +beginning until termination (in sequence) or a maximum number of instructions +has been executed (to prevent the driver from looping indefinitely). +The exception to this rule is {\tt threads} described in +section \ref{sec:thread}.\\ + +Comments are indicated by a hash (\#) and continue to the end of the line. + +We now describe the basic components of a program. + +\section{Variables} +All variables in the programming language are 32-bit integers. +These variables can be used to store results from calculations +and can represent button values or axis values. A new variable +is declared with the {\tt var} statement: +\begin{center} + {\tt var} {\it variable\_name}; +\end{center} +where {\it variable\_name} is any identifier that begins with a letter +followed by letters, digits or the underscore. Variable names +are case-sensitive. These variables can be used for any purpose. +Variables are allocated to registers in the virtual machine. Since +there are 256 such registers, only 256 variables can be declared +in addition to the predefined variables.\\ + +Arrays can be declared as follows +\begin{center} + {\tt var} {\it variable\_name}{\tt [{\it constant}]}; +\end{center} +The size of the array must be constant. Each element of the array +is assigned to one register. Registers can be used up very +quickly by declaring arrays! Arrays are 0 based, that is the +first index of any array is 0.\\ + +A variable can be declared with the {\tt global} keyword instead, in +which case the variable is {\it shared} between all threads. This +variable can then be used for inter thread communication. + +There are a few predefined variables that have a special meaning. These +variables are described in the following sections. + +\subsection{{\tt firstscan}} +The {\tt firstscan} variable is set to 1 if this is the +first time the program is executed, and 0 otherwise. This +variable can be used to decide when to initialize program +variables. + +\subsection{{\tt clocktick}} +The {\tt clocktick} variable is set to 1 if the program +is executed as a result of a timer event, or 0 if the +program is executed due to some other event (such as +a joystick button being pressed). + +\subsection{{\tt timestamp}} +\label{sec:timestamp} +The {\tt timestamp} variable indicates the time since first +execution of the script measured in milliseconds. This variable can be used to +queue statements based on time. Examples of use include +the {\tt delay} statement (section \ref{sec:delay}). + +\subsection{{\tt currentmode}} +The {\tt currentmode} variable is defined by the +program to provide state referring to a mode of +operation. The variable is completely controlled +by the user and may be used for any purpose. +Unlike general variables, this variable is +accessible from all {\tt threads} (see section \ref{sec:thread}). + +\subsection{{\tt js}} +The {\tt js} variables give access to the current state of +joysticks attached to the system. There are 16 {\tt js} +variables, namely {\tt js0} through {\tt js15} that +provide access to a maximum of 16 joysticks. The joysticks +are numbered according to the specification in the +current mapping configuration file. That is, the joystick +numbers are explicitly assigned when the joystick is +mapped. Each {\tt js} variable has two fields: +\begin{itemize} + \item An {\tt a} field that is an array of integers. + Each element provides the current position + of that axis of that joystick. + For example {\tt js0.a[1]}, refers to + the second axis of the joystick designated + as 0~in the configuration file. + \item A {\tt b} field that is an array of integers. + Each element provides the current button status + of that button of that joystick. + For example {\tt js1.b[3]}, refers to + the fourth button of the joystick designated + as 1~in the configuration file. The value is + 1 for a depressed button and 0 otherwise. +\end{itemize} + +\subsection{{\tt a}} +The variable {\tt a} is an array of integers specifying the +position of the virtual axis of the virtual joystick +created by the program. For example the +statement +\begin{center} + {\tt a[1]=10;} +\end{center} +reports that the second axis of the virtual program joystick +is in position 10. The variable may also be read. + +\subsection{{\tt b}} +The {\tt b} variable is an array of integers indicating +whether a virtual button on the virtual program joystick +is depressed or not. For example, to indicate that +the first button on the virtual joystick is +depressed, we write +\begin{center} + {\tt b[0]=1;} +\end{center} +and to indicate (at a later time) that the button is released +we write +\begin{center} + {\tt b[0]=0;} +\end{center} +This variable may also be read. + +\section{Operators} +Operators are used to build expressions +to perform some calculation. The operators +are very similar to C operators with some +minor differences. Operators take constants, +variables, array elements or other expressions as parameters. +\subsection{Arithmetic operators} +The arithmetic operators are as follows: +\begin{itemize} + \item Unary +, eg. $+v$. + \item Unary -, eg. $-v$. + \item Binary + eg. $v_1+v_2$. + \item Binary - eg. $v_1+v_2$. + \item Binary $\ast$ eg. $v_1\ast v_2$. + \item Binary / -- integer division. eg. $v_1/v_2$. + \item Binary \% -- integer remainder. eg. $v_1\%v_2$. + This function is not implemented with + primitive instructions and is implemented as + $a\%b=a-(a/b)\ast b$. +\end{itemize} + +\subsection{Boolean operators} +Boolean operators allow certain conditions to be tested. +The result is an integer indicating the truth of the +statement. 0 is taken to mean {\it false}, and anything +else is taken to mean {\it true}. Thus integer values (variables, +results from a calculation) are also boolean values. +\begin{itemize} + \item {\tt a==b}: true if integer {\tt a} is equal to integer {\tt b}, false otherwise. + \item {\tt a!=b}: true if integer {\tt a} is NOT equal to integer {\tt b}, false otherwise. + \item {\tt ab}: true if integer {\tt a} is greater than integer {\tt b}, false otherwise. + \item {\tt a<=b}: true if integer {\tt a} is less than or equal to integer {\tt b}, false otherwise. + \item {\tt a>=b}: true if integer {\tt a} is greater than or equal to integer {\tt b}, false otherwise. + \item {\tt a\&\&b}: true if {\tt a} is true and {\tt b} is true, false otherwise. + \item {\tt a\pipe\pipe b}: true if {\tt a} is true or {\tt b} is true, false otherwise. + \item {\tt !a}: true if {\tt a} is false, false otherwise. +\end{itemize} +\subsection{Precedence} +Unary operators have the highest precedence. +The precedence of binary operators is similar to C. +The precedence from highest to lowest is: +\begin{itemize} + \item \&\& and \pipe\pipe + \item $\ast$, / and \% + \item + and - + \item {\tt ==, !=, <=, >=, <} and {\tt >} +\end{itemize} + +\section{Program statements} +All statements are terminated by a semi-colon, except the +statement block. +\subsection{Assignment} +Assignment is the most commonly used statement. +An example of assignment is +\begin{center} + {\tt currentmode=1;} +\end{center} +This sets the variable currentmode to have the value 1. Be careful +not to confuse the assignment operator = with the boolean equality +test ==. +A few shorthand notations exist for some common assignment operations: +\begin{itemize} + \item {\tt a++} $\rightarrow$ {\tt a=a+1;} + \item {\tt a--} $\rightarrow$ {\tt a=a-1;} + \item {\tt a+=b} $\rightarrow$ {\tt a=a+b;} + \item {\tt a-=b} $\rightarrow$ {\tt a=a-b;} + \item {\tt a*=b} $\rightarrow$ {\tt a=a*b;} + \item {\tt a/=b} $\rightarrow$ {\tt a=a/b;} +\end{itemize} +\subsection{Statement blocks} +If several statements need to be combined into a unit, +a statement block can be created as follows: +\begin{center} + \parbox{5cm}{ + {\tt \{\\ + \strut\ \ \ \ $statement_1$;\\ + \strut\ \ \ \ $statement_2$;\\ + \strut\ \ \ \ $statement_3$;\\ + \strut\ \ \ \ \vdots\\ + \strut\ \ \ \ $statement_n$;\\ + \} } + } +\end{center} +\subsection{{\tt if}} +An {\tt if} statement has the form +\begin{center} + {\tt if ({\it condition}) {\it statement}} +\end{center} +If the {\it statement} is a simple statement, then it is terminated +by a semi-colon. If it is a block statement, then no semi-colon +is necessary.\\ + +If the expression {\it condition} evaluates to 0 then the +{\it statement} is not executed, otherwise the {\it statement} +is executed. It is also possible to specify to alternatives +as in +\begin{center} + {\tt if ({\it condition}) {$statement_1$}} else $statement_2$ +\end{center} +where $statement_2$ is executed if {\it condition} evaluates +to 0, otherwise $statement_1$ is executed. + +\subsection{{\tt while}} +A {\tt while} loop has the form +\begin{center} + {\tt while ({\it condition}) {\it statement};} +\end{center} +As long as {\it condition} evaluates to a non-zero value +{\it statement} will be executed. The {\it condition} +is evaluated before the {\it statement} is executed +and is tested directly before the first (possible) +execution, and directly after execution of {\it statement}. +If {\it condition} evaluates to 0, then the loop +is terminated. +\subsection{{\tt signal}} +The {\tt signal} statement has the form +\begin{center} + {\tt signal({\it expression});} +\end{center} +This statement sends the result of expression +to the mapper device (the device used to program the joystick) +to be relayed to a client program. This can be +used to trigger events outside of the kernel +space. For example, the joystick could +be reprogrammed by the client program based +on the signal sent, or a particular program such +as an e-mail client or multimedia player could be executed. +\subsection{{\tt press}} +The {\tt press} statement allows keypresses to be sent +directly to the driver. {\tt press} has +the form +\begin{center} + {\tt press("{\it key}");} +\end{center} +where {\it key} is one of the constants listed in {\tt keys.txt}. +{\tt press} sends a key pressed event to the driver. +\subsection{{\tt release}} +The {\tt release} statement allows key release events to be sent +directly to the driver. {\tt release} has +the form +\begin{center} + {\tt release("{\it key}");} +\end{center} +where {\it key} is one of the constants listed in {\tt keys.txt}. +{\tt release} sends a key released event to the driver. +\subsection{{\tt thread}} +\label{sec:thread} +{\tt Threads} are threads of execution in the sense that each thread +will remember which statement was executing during the last execution +of this thread. Thus threads maintain state information in terms +of an instruction pointer and a collection of registers. {\tt Threads} +do not imply concurrent execution in any way whatsoever! The {\tt thread} +statement declares a thread with independent state, as well as +indicating that the thread must be executed at this point. The +main program will stop executing until execution of this thread completes +or the thread yields. If the thread halts, then the state information +of the thread is reset. If the thread yields, then the thread will +stop execution and save the instruction pointer so that the +thread can be resumed later. The first time the thread is executed (or after +the last halt), the current registers are stored in the state +of the thread and the instruction pointer is set to the +first instruction (statement) of the thread. Thereafter (until the next halt), the thread will use +its own copy of the registers (except for special registers such as {\tt timestamp} +and {\tt currentmode}). If the thread was not halted, then the instruction +pointer will be used to resume execution of the thread.\\ + +The thread statement has the form +\begin{center} + {\tt thread statements;} +\end{center} +The statement declares a new thread to the compiler. The compiler will also +generate instructions to begin execution of the thread (suspending execution +of the main program until the thread has halted or yielded). +Each thread is allocated a unique thread number. The maximum number of threads +in a program is 8. However, an alternative declaration +\begin{center} + {\tt thread {\it name} statements;} +\end{center} +can be used to provide a specific name to a thread. All threads in the +program with the same name will share the same thread number, thus increasing +the potential number of threads. If there are two or more such threads, then +the programmer is declaring to the compiler that only one such +thread will NOT be in the halted state at any time. +\subsection{{\tt delay}} +\label{sec:delay} +The {\tt delay} statement is only valid within a +{\tt thread} statement. The {\tt delay} statement has the form +\begin{center} + {\tt delay({\it expression});} +\end{center} +This statement will delay the executing {\tt thread} {\it expression} milliseconds +(see {\tt timestamp} in section \ref{sec:timestamp}). The mechanism used +to delay this period of time is a check of the amount of time delayed so +far, followed by a yield if the required time has not elapsed. +The thread will resume execution at this statement the next time +it is executed if the required time has not elapsed. Note that +the expression is reevaluated every time the delay is checked. + +\subsection{{\tt wait}} +The {\tt wait} statement is only valid within a +{\tt thread} statement. The {\tt wait} statement has +the form +\begin{center} + {\tt wait({\it condition});} +\end{center} +This statement halts the executing thread until {\it condition} becomes +true (that is non-zero). The {\tt wait} statement is implemented +by a yield if {\it condition} is false, which returns control to the +calling thread. The thread will resume execution at this statement +the next time it is executed. + +\subsection{{\tt halt}} +The halt statement has the form +\begin{center} + {\tt halt;} +\end{center} +and halts execution of the current thread or the main program. +Every program must halt. The compiler automatically adds a {\tt halt} +statement to the end of any program to ensure that the +program will halt.\\ + +It is also possible to halt a specific thread (possibly different to the current one) with +the statement +\begin{center} + {\tt halt {\it name};} +\end{center} +\section{Examples} +A few examples of programs are presented in this section. +In most of the examples it is necessary to provide a mapping +from the virtual joystick to a real joystick, or keyboard events. +\subsection{Toe Brakes} +Some flight simulators support toe brakes in the form of buttons +or keypresses, but do not support an axis for toe brakes. If this +is the case, we can use the toe brake axis on a set of rudder pedals +to simulate joystick button or key pressed. Assume that the +rudder pedals have been designated as joystick 2, and that the +toe brake axes are axes 0 and 1. The program to convert the +axis positions to button presses is: +\begin{verbatim} + b[0]=(js2.a[0]>128); + b[1]=(js2.a[1]>128); +\end{verbatim} +Remember that {\tt b} is the array of buttons for the +virtual joystick, and that boolean and integer expressions +are interchangeable. The typical range for any axis is 0--255, +hence the choice of comparison to 128. +The program's virtual joystick buttons should then be mapped +to real joystick buttons, or to key presses. + +\subsection{Car Accelerator and Brakes} +Most rudder pedals have to separate axes for the left and +right toe brake, but car simulators tend to use only one joystick +axis for both acceleration and braking. Assume the same setup +as above with axis 0 to be used for the brake, and +axis 1 to be used for the accelerator. The two axes can be mapped +to one axis with the program +\begin{verbatim} + var val; + #get a positive value for acceleration + # >128 indicates acceleration + val=js2.a[1]/2+128; + #produce a braking value + # <128 indicates brakes + # we need to reverse the sense of the axis + val-=js2.a[0]/2; + a[0]=val; + #note that accelerating and braking at + # the same time results in no action + # but cannot be resolved here +\end{verbatim} +\subsection{Delayed Release of Countermeasures} +In modern combat flight simulators it is necessary to +drop countermeasures such as flares or chaff to confuse +the seeker heads of missiles launched at the aircraft. +In such a situation it is standard practice to +release several countermeasures with a delay between +the release of each one. A thread should be used to +achieve this, so that other conditions may be checked +and other actions followed despite the presence +of countermeasures. +\begin{verbatim} + var i; + thread { + if (js0.b[5]) { + i=5; + while (i>0) { + b[0]=1; + delay(2); + b[0]=0; + delay(2000); + i--; + } + } + } +\end{verbatim} +This code will trigger the release of 5 countermeasures +with a delay of 2 seconds between each countermeasure. Countermeasures +will be launched as soon as button number 5 of joystick 0 +is pressed. They will continue to be launched even if the +button is immediately released. + +\subsection{Trimming} +It is sometimes necessary to trim the controls of an aircraft +so that straight and level flight can be maintained with +the joystick in a central position. The trim position may +change due to changes in air speed or other factors. +The program below trims the joystick according to the current +joystick position. +\begin{verbatim} + var trimx; + var trimy; + # the original values of trimx and trimy + var ox, oy; + if (firstscan) { + #center position is zero + trimx=128; + trimy=128; + ox=128; + oy=128; + } + #check for trimming button pressed + if (js0.b[5]) { + trimx=128-js0.a[0]+ox; + trimy=128-js0.a[1]+oy; + } else { + ox=trimx; + oy=trimy; + } + #check for reset button pressed + if (js0.b[6]) { + #reset center position is zero + trimx=128; + trimy=128; + ox=128; + oy=128; + } + a[0]=js0.a[0]-trimx+128; + a[1]=js0.a[1]-trimy+128; +\end{verbatim} + +\subsection{Waiting for Release of a Button} +Assume you want to launch exactly one missile +with a button press, and the simulation software +launches missiles in sequence according to +whether the button is depressed or not. +\begin{verbatim} + thread { + #wait for first press of the button + wait(js0.b[1]); + #wait for release + wait(!js0.b[1]); + #press virtual button + b[0]=1; + #and release after 1 second + delay(1000); + b[0]=0; + } +\end{verbatim} +Due to the input driver system it should be possible +to omit the delay, both the press and release should +be reported. However, the simulation software +may not work in entirely the same way. + +\end{document} diff --git a/events.c b/events.c new file mode 100644 index 0000000..62ddd4e --- /dev/null +++ b/events.c @@ -0,0 +1,500 @@ +/* + * Joystick remapper for the input driver suite. + * by Alexandre Hardy + * + * 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 2 of the License, or + * (at your option) any later version. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "mapper.h" + +#define MAX_SIGNALS 16 +#define MAX_EVENTS 32 + +#define MSECS(t) (1000 * ((t) / HZ) + 1000 * ((t) % HZ) / HZ) + +#define TIMEOUT 20 //(50 times a second) + +#undef press +#undef release + +struct shift_map { + struct program_button_remap *button_press[KEY_MAX+1]; + struct program_button_remap *button_release[KEY_MAX+1]; + struct program_axis_remap *axes[ABS_MAX+1]; +}; + +struct mapping { + int fd; /* /dev/input/event* file descriptor */ + __uint16_t vendor; + __uint16_t product; + int jsnum; + int mapped; + //two maps, one for shifted, and one for not shifted + struct shift_map map[2]; +}; + +static int shifted=0; +static __uint16_t shift_vendor; +static __uint16_t shift_product; +static __uint16_t shift_button; +struct mapping *events[MAX_EVENTS]; + +//install after creating joysticks and code joystick +//our joysticks are vendor 0xff and product 0x01 and must not be handled +//our mice are vendor 0xff and product 0x02 and must not be handled +//our kbd are vendor 0xff and product 0x03 and must not be handled +//the code joystick is 0xff and product 0x00 +//the code joystick must be handled by our code +void install_event_handlers() { + int i, j, r; + char name[256]; + struct input_id id; + for (i=0; i<=MAX_EVENTS; i++) { + sprintf(name, "%s%d", get_config(EVENT_DEV), i); + int fd=open(name, O_RDONLY|O_NONBLOCK); + if (fd<0) { + events[i]=NULL; + } else { + r = ioctl(fd, EVIOCGID, &id); + if (r < 0) { + perror("Failed to get vendor and product id"); + exit(1); + } + if ((id.vendor==0x00ff)&&(id.product!=0x0000)) { + close(fd); + events[i]=NULL; + continue; + } + char devname[256]; + memset(devname, 0, 256); + r = ioctl(fd, EVIOCGNAME(255), devname); + if (r < 0) { + perror("Failed to get input device name"); + exit(1); + } + fprintf(stderr, "Found device %s (vendor=0x%04x, product=0x%04x)\n", devname, id.vendor, id.product); + events[i]=(struct mapping *)malloc(sizeof(struct mapping)); + events[i]->fd=fd; + events[i]->vendor=id.vendor; + events[i]->product=id.product; + events[i]->jsnum=-1; + events[i]->mapped=0; + + for (j=0; jmap[0].button_press[j]=NULL; + events[i]->map[0].button_release[j]=NULL; + events[i]->map[1].button_press[j]=NULL; + events[i]->map[1].button_release[j]=NULL; + } + for (j=0; jmap[0].axes[j]=NULL; + events[i]->map[1].axes[j]=NULL; + } + } + } +} + + +static void process_key(struct mapping *mapper, int key, int value); +static void process_axis(struct mapping *mapper, int axis, int value); +void poll_joystick_loop() { + int i, j, n=0; + struct pollfd polled_fds[MAX_EVENTS]; + struct mapping *poll_mapper[MAX_EVENTS]; + struct input_event ev[16]; + int rb; + for (i=0; imapped)) { + poll_mapper[n]=events[i]; + polled_fds[n].fd=events[i]->fd; + polled_fds[n++].events=POLLIN; + } + } + + poll(polled_fds, n, TIMEOUT); + /* TODO: We may return early, we should indicate to program_run if a timertick is required */ + /* http://man7.org/linux/man-pages/man2/clock_gettime.2.html */ + for (i=0; i0) { + rb=read(polled_fds[i].fd, ev, sizeof(struct input_event)*16); + if (rb>0) + for (j=0; j<(int)(rb/sizeof(struct input_event)); j++) { + if (ev[j].type==EV_KEY) { + if ((ev[j].code >= BTN_MISC) && (ev[j].value != 2)) + process_key(poll_mapper[i], ev[j].code, ev[j].value); + } + if (ev[j].type==EV_ABS) + process_axis(poll_mapper[i], ev[j].code, ev[j].value); + } + } + } + } + program_run(); + repeat_mouse_move(); +} + +static int nsignals=0; +static int signals[MAX_SIGNALS]; +static int sighead=0; +static int sigtail=0; +int no_signal(void) { + return (nsignals==0); +} + +int goto_next_signal(void) { + int r=signals[sighead]; + nsignals--; + sighead++; + sighead=sighead%MAX_SIGNALS; + return r; +} + +void push_signal(int signal) { + //silently drop signals if too many are sent + if (nsignals>=MAX_SIGNALS) return; + nsignals++; + signals[sigtail]=signal; + sigtail++; + sigtail=sigtail%MAX_SIGNALS; +} + +static void process_key(struct mapping *mapper, int key, int value) { + int button=0; + int i, j; + struct program_button_remap **button_remap; + + if ((mapper->vendor==shift_vendor)||(mapper->product==shift_product)) { + if (key==shift_button) { + shifted=value; + if (shifted!=0) shifted=1; + } + } + + if ((mapper->vendor!=0x00ff)||(mapper->product!=0x0000)) + code_notify_button(mapper->jsnum, key, value); + if (value==1) button_remap=mapper->map[shifted].button_press; + else if (value==0) button_remap=mapper->map[shifted].button_release; + else return; + if (button_remap==NULL) return; + if (button_remap[key]==NULL) return; + + j=button_remap[key]->device&0x0F; + switch (button_remap[key]->device&0xF0) { + case DEVICE_JOYSTICK: + if (button_remap[key]->type==TYPE_BUTTON) { + for (i=0; (isequence[i]!=SEQUENCE_DONE); i++) { + button=button_remap[key]->sequence[i]&KEYMASK; + if (button_remap[key]->sequence[i]&RELEASEMASK) value=0; + else value=1; + press_joy_button(j, button, value); + if ((button_remap[key]->flags&FLAG_AUTO_RELEASE)&&(value==1)) { + press_joy_button(j, button, 0); + } + } + } else if (button_remap[key]->type==TYPE_AXIS) { + //it is an axis + if (value==1) value=32767; + if (button_remap[key]->flags&FLAG_INVERT) value=-value; + set_joy_axis(j, button_remap[key]->sequence[0], value); + } + break; + case DEVICE_MOUSE: + if (button_remap[key]->type==TYPE_BUTTON) { + for (i=0; (isequence[i]!=SEQUENCE_DONE); i++) { + button=button_remap[key]->sequence[i]&KEYMASK; + if (button_remap[key]->sequence[i]&RELEASEMASK) value=0; + else value=1; + press_mouse_button(button, value); + if ((button_remap[key]->flags&FLAG_AUTO_RELEASE)&&(value==1)) { + press_mouse_button(button, 0); + } + } + } else if (button_remap[key]->type==TYPE_AXIS) { + //it is an axis + if (button_remap[key]->flags&FLAG_INVERT) value=-value; + if (button_remap[key]->sequence[0]==ABS_X) + move_mouse_x(value * button_remap[key]->speed); + if (button_remap[key]->sequence[0]==ABS_Y) + move_mouse_y(value * button_remap[key]->speed); + if (button_remap[key]->sequence[0]==ABS_WHEEL) + move_mouse_wheel(value * button_remap[key]->speed); + } + break; + case DEVICE_KBD: + if (button_remap[key]->type==TYPE_BUTTON) { + for (i=0; (isequence[i]!=SEQUENCE_DONE); i++) { + button=button_remap[key]->sequence[i]&KEYMASK; + if (button_remap[key]->sequence[i]&RELEASEMASK) value=0; + else value=1; + press_key(button, value); + if ((button_remap[key]->flags&FLAG_AUTO_RELEASE)&&(value==1)) { + press_key(button, 0); + } + } + } + break; + } +} + +static void process_axis(struct mapping *mapper, int axis, int value) { + int button=0; + int i, j; + struct program_axis_remap **axes_remap; + __uint16_t *sequence, *off; + int release = 0; + + if ((mapper->vendor!=0x00ff)||(mapper->product!=0x0000)) + code_notify_axis(mapper->jsnum, axis, value); + + axes_remap=mapper->map[shifted].axes; + if (axes_remap==NULL) return; + if (axes_remap[axis]==NULL) return; + + if (axes_remap[axis]->min != axes_remap[axis]->max) { + if (axes_remap[axis]->min < axes_remap[axis]->max) { + if (value < axes_remap[axis]->min) + value = axes_remap[axis]->min; + if (value > axes_remap[axis]->max) + value = axes_remap[axis]->max; + } else { + if (value > axes_remap[axis]->min) + value = axes_remap[axis]->min; + if (value < axes_remap[axis]->max) + value = axes_remap[axis]->max; + } + // sigh. use floating point because I am too lazy (right now) to work out an overflow free integer version + value = ((value - axes_remap[axis]->min) * 65536.0) / (axes_remap[axis]->max - axes_remap[axis]->min) - 32767; + if (value < -32767) value = -32767; + if (value > 32767) value = 32767; + } + + if ((value >= -axes_remap[axis]->deadzone) && (value <= axes_remap[axis]->deadzone)) { + value = 0; + } else if (axes_remap[axis]->deadzone) { + // we don't want a sudden jump in values. rescale it. + if (value < 0) + value = (value + axes_remap[axis]->deadzone) * 32767.0 / (32767 - axes_remap[axis]->deadzone); + else + value = (value - axes_remap[axis]->deadzone) * 32767.0 / (32767 - axes_remap[axis]->deadzone); + } + + if (axes_remap[axis]->flags&FLAG_BINARY) { + // only process the event if there is a binary change (differs in sign and non-zero) + if ((axes_remap[axis]->saved_value<0) && (value<=0)) { + return; + } + if ((axes_remap[axis]->saved_value>0) && (value>=0)) { + return; + } + if ((axes_remap[axis]->saved_value==0) && (value==0)) { + return; + } + axes_remap[axis]->saved_value=value; + if (value == 0) + //release any keys pressed + release = 1; + } + + if (axes_remap[axis]->flags&FLAG_TRINARY) { + // only process the event if there is a binary change (differs in sign and non-zero) + if ((axes_remap[axis]->saved_value<0) && (value<0)) { + return; + } + if ((axes_remap[axis]->saved_value>0) && (value>0)) { + return; + } + axes_remap[axis]->saved_value=value; + if (value == 0) + //release any keys pressed + release = 1; + } + + j=axes_remap[axis]->device&0x0F; + + if (axes_remap[axis]->type==TYPE_BUTTON) { + if (value<0) { + sequence=axes_remap[axis]->minus; + off=axes_remap[axis]->plus; + } else { + sequence=axes_remap[axis]->plus; + off=axes_remap[axis]->minus; + } + } + + switch (axes_remap[axis]->device&0xF0) { + case DEVICE_JOYSTICK: + if (axes_remap[axis]->type==TYPE_BUTTON) { + //a joystick button + for (i=0; (iflags&FLAG_AUTO_RELEASE)&&(value==1)) { + press_joy_button(j, button, 0); + } + } + } else if (axes_remap[axis]->type==TYPE_AXIS) { + //it is an axis + if (axes_remap[axis]->flags&FLAG_INVERT) value=-value; + set_joy_axis(j, axes_remap[axis]->axis, value); + } + break; + case DEVICE_MOUSE: + if (axes_remap[axis]->type==TYPE_BUTTON) { + //a mouse button + for (i=0; (iflags&FLAG_AUTO_RELEASE)&&(value==1)) { + press_mouse_button(button, 0); + } + } + } else if (axes_remap[axis]->type==TYPE_AXIS) { + //it is an axis, assume -32767 to 32767. Use min and max if that is not the case + // make it a lot smaller + value = value * ((float)(axes_remap[axis]->speed) / 32767.0); + if (axes_remap[axis]->flags&FLAG_INVERT) value=-value; + //if (value>0) value=1; + //if (value<0) value=-1; + if (axes_remap[axis]->axis==ABS_X) + move_mouse_x(value); + if (axes_remap[axis]->axis==ABS_Y) + move_mouse_y(value); + if (axes_remap[axis]->axis==ABS_WHEEL) + move_mouse_wheel(value); + } + break; + case DEVICE_KBD: + if (axes_remap[axis]->type==TYPE_BUTTON) { + //a keypress + for (i=0; (iflags&FLAG_AUTO_RELEASE)&&(value==1)) { + press_key(button, 0); + } + } + } + break; + } +} + +void remap_axis(struct program_axis_remap *axis) { + struct mapping *mapper; + int i, shifted, r; + + if (axis->program!=PROGRAM_AXIS_REMAP) return; + if (axis->srcaxis>ABS_MAX) return; + + mapper=NULL; + for (i=0; ivendor==axis->vendor)&& + (events[i]->product==axis->product)) + mapper=events[i]; + } + + if (mapper==NULL) return; + if (mapper->mapped) + r = 0; + else + r = ioctl(mapper->fd, EVIOCGRAB, 1); + mapper->mapped=1; + if (r < 0) { + perror("Failed to grab device"); + fprintf(stderr, "Failed to lock device with vendor=0x%04x, product=0x%04x. Continuing anyway...\n", axis->vendor, axis->product); + } + + shifted=0; + if (axis->flags&FLAG_SHIFT) shifted=1; + mapper->map[shifted].axes[axis->srcaxis]=axis; +} + +void remap_button(struct program_button_remap *btn) { + struct mapping *mapper; + int i, shifted, r; + + if (btn->program!=PROGRAM_BUTTON_REMAP) return; + if (btn->srcbutton>KEY_MAX) return; + + mapper=NULL; + for (i=0; ivendor==btn->vendor)&& + (events[i]->product==btn->product)) + mapper=events[i]; + } + + if (mapper==NULL) return; + if (mapper->mapped) + r = 0; + else + r = ioctl(mapper->fd, EVIOCGRAB, 1); + mapper->mapped=1; + if (r < 0) { + perror("Failed to grab device"); + fprintf(stderr, "Failed to lock device with vendor=0x%04x, product=0x%04x. Continuing anyway...\n", btn->vendor, btn->product); + } + + shifted=0; + if (btn->flags&FLAG_SHIFT) shifted=1; + + if (btn->type==TYPE_SHIFT) { + shift_vendor=btn->vendor; + shift_product=btn->product; + shift_button=btn->srcbutton; + } else { + if (btn->flags&FLAG_RELEASE) + mapper->map[shifted].button_release[btn->srcbutton]=btn; + else + mapper->map[shifted].button_press[btn->srcbutton]=btn; + } +} + +void set_joystick_number(__uint16_t vendor, __uint16_t product, int device) { + struct mapping *mapper; + int i; + mapper=NULL; + for (i=0; ivendor==vendor)&& + (events[i]->product==product)) + mapper=events[i]; + } + + if (mapper==NULL) return; + mapper->mapped=1; + mapper->jsnum=device; +} diff --git a/keys.h b/keys.h new file mode 100644 index 0000000..f83aad5 --- /dev/null +++ b/keys.h @@ -0,0 +1,466 @@ +/* + * Keys and buttons + */ + +#define KEY_RESERVED 0 +#define KEY_ESC 1 +#define KEY_1 2 +#define KEY_2 3 +#define KEY_3 4 +#define KEY_4 5 +#define KEY_5 6 +#define KEY_6 7 +#define KEY_7 8 +#define KEY_8 9 +#define KEY_9 10 +#define KEY_0 11 +#define KEY_MINUS 12 +#define KEY_EQUAL 13 +#define KEY_BACKSPACE 14 +#define KEY_TAB 15 +#define KEY_Q 16 +#define KEY_W 17 +#define KEY_E 18 +#define KEY_R 19 +#define KEY_T 20 +#define KEY_Y 21 +#define KEY_U 22 +#define KEY_I 23 +#define KEY_O 24 +#define KEY_P 25 +#define KEY_LEFTBRACE 26 +#define KEY_RIGHTBRACE 27 +#define KEY_ENTER 28 +#define KEY_LEFTCTRL 29 +#define KEY_A 30 +#define KEY_S 31 +#define KEY_D 32 +#define KEY_F 33 +#define KEY_G 34 +#define KEY_H 35 +#define KEY_J 36 +#define KEY_K 37 +#define KEY_L 38 +#define KEY_SEMICOLON 39 +#define KEY_APOSTROPHE 40 +#define KEY_GRAVE 41 +#define KEY_LEFTSHIFT 42 +#define KEY_BACKSLASH 43 +#define KEY_Z 44 +#define KEY_X 45 +#define KEY_C 46 +#define KEY_V 47 +#define KEY_B 48 +#define KEY_N 49 +#define KEY_M 50 +#define KEY_COMMA 51 +#define KEY_DOT 52 +#define KEY_SLASH 53 +#define KEY_RIGHTSHIFT 54 +#define KEY_KPASTERISK 55 +#define KEY_LEFTALT 56 +#define KEY_SPACE 57 +#define KEY_CAPSLOCK 58 +#define KEY_F1 59 +#define KEY_F2 60 +#define KEY_F3 61 +#define KEY_F4 62 +#define KEY_F5 63 +#define KEY_F6 64 +#define KEY_F7 65 +#define KEY_F8 66 +#define KEY_F9 67 +#define KEY_F10 68 +#define KEY_NUMLOCK 69 +#define KEY_SCROLLLOCK 70 +#define KEY_KP7 71 +#define KEY_KP8 72 +#define KEY_KP9 73 +#define KEY_KPMINUS 74 +#define KEY_KP4 75 +#define KEY_KP5 76 +#define KEY_KP6 77 +#define KEY_KPPLUS 78 +#define KEY_KP1 79 +#define KEY_KP2 80 +#define KEY_KP3 81 +#define KEY_KP0 82 +#define KEY_KPDOT 83 + +#define KEY_ZENKAKUHANKAKU 85 +#define KEY_102ND 86 +#define KEY_F11 87 +#define KEY_F12 88 +#define KEY_RO 89 +#define KEY_KATAKANA 90 +#define KEY_HIRAGANA 91 +#define KEY_HENKAN 92 +#define KEY_KATAKANAHIRAGANA 93 +#define KEY_MUHENKAN 94 +#define KEY_KPJPCOMMA 95 +#define KEY_KPENTER 96 +#define KEY_RIGHTCTRL 97 +#define KEY_KPSLASH 98 +#define KEY_SYSRQ 99 +#define KEY_RIGHTALT 100 +#define KEY_LINEFEED 101 +#define KEY_HOME 102 +#define KEY_UP 103 +#define KEY_PAGEUP 104 +#define KEY_LEFT 105 +#define KEY_RIGHT 106 +#define KEY_END 107 +#define KEY_DOWN 108 +#define KEY_PAGEDOWN 109 +#define KEY_INSERT 110 +#define KEY_DELETE 111 +#define KEY_MACRO 112 +#define KEY_MUTE 113 +#define KEY_VOLUMEDOWN 114 +#define KEY_VOLUMEUP 115 +#define KEY_POWER 116 +#define KEY_KPEQUAL 117 +#define KEY_KPPLUSMINUS 118 +#define KEY_PAUSE 119 + +#define KEY_KPCOMMA 121 +#define KEY_HANGUEL 122 +#define KEY_HANJA 123 +#define KEY_YEN 124 +#define KEY_LEFTMETA 125 +#define KEY_RIGHTMETA 126 +#define KEY_COMPOSE 127 + +#define KEY_STOP 128 +#define KEY_AGAIN 129 +#define KEY_PROPS 130 +#define KEY_UNDO 131 +#define KEY_FRONT 132 +#define KEY_COPY 133 +#define KEY_OPEN 134 +#define KEY_PASTE 135 +#define KEY_FIND 136 +#define KEY_CUT 137 +#define KEY_HELP 138 +#define KEY_MENU 139 +#define KEY_CALC 140 +#define KEY_SETUP 141 +#define KEY_SLEEP 142 +#define KEY_WAKEUP 143 +#define KEY_FILE 144 +#define KEY_SENDFILE 145 +#define KEY_DELETEFILE 146 +#define KEY_XFER 147 +#define KEY_PROG1 148 +#define KEY_PROG2 149 +#define KEY_WWW 150 +#define KEY_MSDOS 151 +#define KEY_COFFEE 152 +#define KEY_DIRECTION 153 +#define KEY_CYCLEWINDOWS 154 +#define KEY_MAIL 155 +#define KEY_BOOKMARKS 156 +#define KEY_COMPUTER 157 +#define KEY_BACK 158 +#define KEY_FORWARD 159 +#define KEY_CLOSECD 160 +#define KEY_EJECTCD 161 +#define KEY_EJECTCLOSECD 162 +#define KEY_NEXTSONG 163 +#define KEY_PLAYPAUSE 164 +#define KEY_PREVIOUSSONG 165 +#define KEY_STOPCD 166 +#define KEY_RECORD 167 +#define KEY_REWIND 168 +#define KEY_PHONE 169 +#define KEY_ISO 170 +#define KEY_CONFIG 171 +#define KEY_HOMEPAGE 172 +#define KEY_REFRESH 173 +#define KEY_EXIT 174 +#define KEY_MOVE 175 +#define KEY_EDIT 176 +#define KEY_SCROLLUP 177 +#define KEY_SCROLLDOWN 178 +#define KEY_KPLEFTPAREN 179 +#define KEY_KPRIGHTPAREN 180 + +#define KEY_F13 183 +#define KEY_F14 184 +#define KEY_F15 185 +#define KEY_F16 186 +#define KEY_F17 187 +#define KEY_F18 188 +#define KEY_F19 189 +#define KEY_F20 190 +#define KEY_F21 191 +#define KEY_F22 192 +#define KEY_F23 193 +#define KEY_F24 194 + +#define KEY_PLAYCD 200 +#define KEY_PAUSECD 201 +#define KEY_PROG3 202 +#define KEY_PROG4 203 +#define KEY_SUSPEND 205 +#define KEY_CLOSE 206 +#define KEY_PLAY 207 +#define KEY_FASTFORWARD 208 +#define KEY_BASSBOOST 209 +#define KEY_PRINT 210 +#define KEY_HP 211 +#define KEY_CAMERA 212 +#define KEY_SOUND 213 +#define KEY_QUESTION 214 +#define KEY_EMAIL 215 +#define KEY_CHAT 216 +#define KEY_SEARCH 217 +#define KEY_CONNECT 218 +#define KEY_FINANCE 219 +#define KEY_SPORT 220 +#define KEY_SHOP 221 +#define KEY_ALTERASE 222 +#define KEY_CANCEL 223 +#define KEY_BRIGHTNESSDOWN 224 +#define KEY_BRIGHTNESSUP 225 +#define KEY_MEDIA 226 + +#define KEY_SWITCHVIDEOMODE 227 +#define KEY_KBDILLUMTOGGLE 228 +#define KEY_KBDILLUMDOWN 229 +#define KEY_KBDILLUMUP 230 + +#define KEY_UNKNOWN 240 + +#define BTN_MISC 0x100 +#define BTN_0 0x100 +#define BTN_1 0x101 +#define BTN_2 0x102 +#define BTN_3 0x103 +#define BTN_4 0x104 +#define BTN_5 0x105 +#define BTN_6 0x106 +#define BTN_7 0x107 +#define BTN_8 0x108 +#define BTN_9 0x109 + +#define BTN_MOUSE 0x110 +#define BTN_LEFT 0x110 +#define BTN_RIGHT 0x111 +#define BTN_MIDDLE 0x112 +#define BTN_SIDE 0x113 +#define BTN_EXTRA 0x114 +#define BTN_FORWARD 0x115 +#define BTN_BACK 0x116 +#define BTN_TASK 0x117 + +#define BTN_JOYSTICK 0x120 +#define BTN_TRIGGER 0x120 +#define BTN_THUMB 0x121 +#define BTN_THUMB2 0x122 +#define BTN_TOP 0x123 +#define BTN_TOP2 0x124 +#define BTN_PINKIE 0x125 +#define BTN_BASE 0x126 +#define BTN_BASE2 0x127 +#define BTN_BASE3 0x128 +#define BTN_BASE4 0x129 +#define BTN_BASE5 0x12a +#define BTN_BASE6 0x12b +#define BTN_DEAD 0x12f + +#define BTN_GAMEPAD 0x130 +#define BTN_A 0x130 +#define BTN_B 0x131 +#define BTN_C 0x132 +#define BTN_X 0x133 +#define BTN_Y 0x134 +#define BTN_Z 0x135 +#define BTN_TL 0x136 +#define BTN_TR 0x137 +#define BTN_TL2 0x138 +#define BTN_TR2 0x139 +#define BTN_SELECT 0x13a +#define BTN_START 0x13b +#define BTN_MODE 0x13c +#define BTN_THUMBL 0x13d +#define BTN_THUMBR 0x13e + +#define BTN_DIGI 0x140 +#define BTN_TOOL_PEN 0x140 +#define BTN_TOOL_RUBBER 0x141 +#define BTN_TOOL_BRUSH 0x142 +#define BTN_TOOL_PENCIL 0x143 +#define BTN_TOOL_AIRBRUSH 0x144 +#define BTN_TOOL_FINGER 0x145 +#define BTN_TOOL_MOUSE 0x146 +#define BTN_TOOL_LENS 0x147 +#define BTN_TOUCH 0x14a +#define BTN_STYLUS 0x14b +#define BTN_STYLUS2 0x14c +#define BTN_TOOL_DOUBLETAP 0x14d +#define BTN_TOOL_TRIPLETAP 0x14e + +#define BTN_WHEEL 0x150 +#define BTN_GEAR_DOWN 0x150 +#define BTN_GEAR_UP 0x151 + +#define KEY_OK 0x160 +#define KEY_SELECT 0x161 +#define KEY_GOTO 0x162 +#define KEY_CLEAR 0x163 +#define KEY_POWER2 0x164 +#define KEY_OPTION 0x165 +#define KEY_INFO 0x166 +#define KEY_TIME 0x167 +#define KEY_VENDOR 0x168 +#define KEY_ARCHIVE 0x169 +#define KEY_PROGRAM 0x16a +#define KEY_CHANNEL 0x16b +#define KEY_FAVORITES 0x16c +#define KEY_EPG 0x16d +#define KEY_PVR 0x16e +#define KEY_MHP 0x16f +#define KEY_LANGUAGE 0x170 +#define KEY_TITLE 0x171 +#define KEY_SUBTITLE 0x172 +#define KEY_ANGLE 0x173 +#define KEY_ZOOM 0x174 +#define KEY_MODE 0x175 +#define KEY_KEYBOARD 0x176 +#define KEY_SCREEN 0x177 +#define KEY_PC 0x178 +#define KEY_TV 0x179 +#define KEY_TV2 0x17a +#define KEY_VCR 0x17b +#define KEY_VCR2 0x17c +#define KEY_SAT 0x17d +#define KEY_SAT2 0x17e +#define KEY_CD 0x17f +#define KEY_TAPE 0x180 +#define KEY_RADIO 0x181 +#define KEY_TUNER 0x182 +#define KEY_PLAYER 0x183 +#define KEY_TEXT 0x184 +#define KEY_DVD 0x185 +#define KEY_AUX 0x186 +#define KEY_MP3 0x187 +#define KEY_AUDIO 0x188 +#define KEY_VIDEO 0x189 +#define KEY_DIRECTORY 0x18a +#define KEY_LIST 0x18b +#define KEY_MEMO 0x18c +#define KEY_CALENDAR 0x18d +#define KEY_RED 0x18e +#define KEY_GREEN 0x18f +#define KEY_YELLOW 0x190 +#define KEY_BLUE 0x191 +#define KEY_CHANNELUP 0x192 +#define KEY_CHANNELDOWN 0x193 +#define KEY_FIRST 0x194 +#define KEY_LAST 0x195 +#define KEY_AB 0x196 +#define KEY_NEXT 0x197 +#define KEY_RESTART 0x198 +#define KEY_SLOW 0x199 +#define KEY_SHUFFLE 0x19a +#define KEY_BREAK 0x19b +#define KEY_PREVIOUS 0x19c +#define KEY_DIGITS 0x19d +#define KEY_TEEN 0x19e +#define KEY_TWEN 0x19f + +#define KEY_DEL_EOL 0x1c0 +#define KEY_DEL_EOS 0x1c1 +#define KEY_INS_LINE 0x1c2 +#define KEY_DEL_LINE 0x1c3 + +#define KEY_FN 0x1d0 +#define KEY_FN_ESC 0x1d1 +#define KEY_FN_F1 0x1d2 +#define KEY_FN_F2 0x1d3 +#define KEY_FN_F3 0x1d4 +#define KEY_FN_F4 0x1d5 +#define KEY_FN_F5 0x1d6 +#define KEY_FN_F6 0x1d7 +#define KEY_FN_F7 0x1d8 +#define KEY_FN_F8 0x1d9 +#define KEY_FN_F9 0x1da +#define KEY_FN_F10 0x1db +#define KEY_FN_F11 0x1dc +#define KEY_FN_F12 0x1dd +#define KEY_FN_1 0x1de +#define KEY_FN_2 0x1df +#define KEY_FN_D 0x1e0 +#define KEY_FN_E 0x1e1 +#define KEY_FN_F 0x1e2 +#define KEY_FN_S 0x1e3 +#define KEY_FN_B 0x1e4 + +#define KEY_MAX 0x1ff + +/* + * Relative axes + */ + +#define REL_X 0x00 +#define REL_Y 0x01 +#define REL_Z 0x02 +#define REL_RX 0x03 +#define REL_RY 0x04 +#define REL_RZ 0x05 +#define REL_HWHEEL 0x06 +#define REL_DIAL 0x07 +#define REL_WHEEL 0x08 +#define REL_MISC 0x09 +#define REL_MAX 0x0f + +/* + * Absolute axes + */ + +#define ABS_X 0x00 +#define ABS_Y 0x01 +#define ABS_Z 0x02 +#define ABS_RX 0x03 +#define ABS_RY 0x04 +#define ABS_RZ 0x05 +#define ABS_THROTTLE 0x06 +#define ABS_RUDDER 0x07 +#define ABS_WHEEL 0x08 +#define ABS_GAS 0x09 +#define ABS_BRAKE 0x0a +#define ABS_HAT0X 0x10 +#define ABS_HAT0Y 0x11 +#define ABS_HAT1X 0x12 +#define ABS_HAT1Y 0x13 +#define ABS_HAT2X 0x14 +#define ABS_HAT2Y 0x15 +#define ABS_HAT3X 0x16 +#define ABS_HAT3Y 0x17 +#define ABS_PRESSURE 0x18 +#define ABS_DISTANCE 0x19 +#define ABS_TILT_X 0x1a +#define ABS_TILT_Y 0x1b +#define ABS_TOOL_WIDTH 0x1c +#define ABS_VOLUME 0x20 +#define ABS_MISC 0x28 +#define ABS_MAX 0x3f + +/* + * LEDs + */ + +#define LED_NUML 0x00 +#define LED_CAPSL 0x01 +#define LED_SCROLLL 0x02 +#define LED_COMPOSE 0x03 +#define LED_KANA 0x04 +#define LED_SLEEP 0x05 +#define LED_SUSPEND 0x06 +#define LED_MUTE 0x07 +#define LED_MISC 0x08 +#define LED_MAIL 0x09 +#define LED_CHARGING 0x0a +#define LED_MAX 0x0f + diff --git a/keys.txt b/keys.txt new file mode 100644 index 0000000..59428f2 --- /dev/null +++ b/keys.txt @@ -0,0 +1,103 @@ +esc 1 +num1 2 +num2 3 +num3 4 +num4 5 +num5 6 +num6 7 +num7 8 +num8 9 +num9 10 +num0 11 +minus 12 +equal 13 +backspace 14 +tab 15 +q 16 +w 17 +e 18 +r 19 +t 20 +y 21 +u 22 +i 23 +o 24 +p 25 +[ 26 +] 27 +enter 28 +leftctrl 29 +a 30 +s 31 +d 32 +f 33 +g 34 +h 35 +j 36 +k 37 +l 38 +; 39 +' 40 +` 41 +leftshift 42 +\ 43 +z 44 +x 45 +c 46 +v 47 +b 48 +n 49 +m 50 +, 51 +. 52 +/ 53 +rightshift 54 +kpstar 55 +leftalt 56 +space 57 +capslock 58 +f1 59 +f2 60 +f3 61 +f4 62 +f5 63 +f6 64 +f7 65 +f8 66 +f9 67 +f10 68 +numlock 69 +scrolllock 70 +kp7 71 +kp8 72 +kp9 73 +kpminus 74 +kp4 75 +kp5 76 +kp6 77 +kpplus 78 +kp1 79 +kp2 80 +kp3 81 +kp0 82 +kpdot 83 +f11 87 +f12 88 +kpenter 96 +rightctrl 97 +kpslash 98 +sysrq 99 +rightalt 100 +home 102 +up 103 +pgup 104 +left 105 +right 106 +end 107 +down 108 +pgdn 109 +insert 110 +delete 111 +pause 119 +leftmeta 125 +rightmeta 126 diff --git a/loadmap.c b/loadmap.c new file mode 100644 index 0000000..5423237 --- /dev/null +++ b/loadmap.c @@ -0,0 +1,97 @@ +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "dictionary.h" +#include "program.h" +#include "keys.h" +#include "validkeys.h" +#include "parser.h" +#include "mapper.h" +#include "clock.h" + +#define TIMEOUT 20 //(50 times a second) + +int main(int argc, char *argv[]) { + int i; + int parse_err; + int mult=1; + int div=1; + int ofs=0; + int timeout; + int start; + int stop; + + cmdline_config(argc, argv); + ++argv, --argc; /* skip over program name */ + + if (strcmp(argv[0], "-8")==0) { + ++argv, --argc; + mult=256; + div=1; + ofs=32767; + } + + if (strcmp(argv[0], "-d")==0) { + ++argv, --argc; + set_dynamic_calibrate(1); + } + + if ( argc > 0 ) + fmap = fopen(argv[0], "r"); + else + fmap = stdin; + + if (fmap==NULL) { + perror("Failed to open map"); + return 1; + } + + program.program=PROGRAM_CODE; + program.code[0]=HALT; + parse_err=parse_map(); + if (!parse_err) { + printf("%d joysticks.\n", njoysticks); + set_num_joysticks(njoysticks); + for (i=0; i validkeys.h +struct keymap_struct { + char key[32]; + int value; +}; + +extern struct keymap_struct keymap[]; +_EOF + +echo '#include "validkeys.h"' > validkeys.c +echo 'struct keymap_struct keymap[]={' >> validkeys.c +cat keys.txt | sed 's/\\/__slash__/g' | (while read key value; do +echo " {\"$key\", $value}," | sed 's/__slash__/\\\\/g' >> validkeys.c +done) +echo ' {"", -1},' >> validkeys.c +echo '};' >> validkeys.c diff --git a/man/loadmap.1 b/man/loadmap.1 new file mode 100644 index 0000000..e21a6b4 --- /dev/null +++ b/man/loadmap.1 @@ -0,0 +1,38 @@ +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.35. +.TH LOADMAP "1" "Feb 2008" "joymap 0.0.1" "User Commands" +.SH NAME +loadmap - load a joystick mapping +.SH SYNOPSIS +.B loadmap +[\fI-8\fR] [\fi-d\fR] [\fIFILE\fR] +.SH DESCRIPTION +.\" Add any additional description here +.PP +Load a joystick mapping, that is create the neccessary joysticks, +configure the number of axes and buttons and relay events from +the physical joysticks to the virtual joysticks. The mapping can +also map to mouse or keyboard events. Read the \fBConfiguration guide\fR +and \fBProgramming guide\fR for more details. +.PP +This program takes one argument, the file to read the configuration +from. If no argument is specified then \fBstdin\fR is used instead. +.PP +Additional flags: +.TP +-8 +Convert reported event range from 0-255 to -32767 to 32767 +.TP +-d +Dynamically (during use) compute calibration value so that events are reported in the range -32767 to 32767. +.PP +Exit status is 0 if OK, 1 otherwise. +.SH "SEE ALSO" +unmap (1) +.SH AUTHOR +Written by Alexandre Hardy. +.SH "REPORTING BUGS" +Report bugs to . +.SH "LICENSE" +This is free software. You may redistribute copies of it under the terms of +the GNU General Public License . +There is NO WARRANTY, to the extent permitted by law. diff --git a/man/unmap.1 b/man/unmap.1 new file mode 100644 index 0000000..c8e4876 --- /dev/null +++ b/man/unmap.1 @@ -0,0 +1,22 @@ +.TH UNMAP "1" "Feb 2008" "joymap 0.0.1" "User Commands" +.SH NAME +unmap \- remove all joystick mappings +.SH SYNOPSIS +.B unmap +.SH DESCRIPTION +.\" Add any additional description here +.PP +Remove all joystick mappings. No joysticks will be available +in the system, other than those physically plugged in. +.PP +Exit status is always 0, even after failure. +.SH "SEE ALSO" +loadmap (1) +.SH AUTHOR +Written by Alexandre Hardy. +.SH "REPORTING BUGS" +Report bugs to . +.SH "LICENSE" +This is free software. You may redistribute copies of it under the terms of +the GNU General Public License . +There is NO WARRANTY, to the extent permitted by law. diff --git a/mapparser.c b/mapparser.c new file mode 100644 index 0000000..bcdb333 --- /dev/null +++ b/mapparser.c @@ -0,0 +1,910 @@ +#include +#include +#include +#include +#include +#include "keys.h" +#include "program.h" +#include "parser.h" +#include "dictionary.h" +#include "validkeys.h" + +static int error=0; + +static struct reserved reserved[]={ + {"shift", SHIFT}, + {"button", BUTTON}, + {"axis", AXIS}, + {"code", CODE}, + {"script", SCRIPT}, + {"include", INCLUDE}, + {"joysticks", JOYSTICKS}, + {"joystick", JOYSTICK}, + {NULL, 0} +}; + +static char *known_keys[]={ + "id", + "vendor", + "product", + "src", + "target", + "axis", + "plus", + "minus", + "device", + "flags", + "button", + "axes", + "buttons", + "min", + "max", + "deadzone", + "speed", + NULL +}; + +FILE *pfile=NULL; +FILE *fmap=NULL; +int line=1; +int cpos=1; +static int nextchar=NONE; + +static dictionary dict=NULL; +static struct program_button_remap map; +static struct program_axis_remap amap; +static struct scriptmap script; +struct program_code program; +struct program_button_remap buttons[MAX_ASSIGN]; +struct program_axis_remap axes[MAX_ASSIGN]; +struct scriptmap scriptassign[MAX_ASSIGN]; +static int parse_err=0; +int nbuttons=0; +int naxes=0; +int nscript=0; +int njoysticks=0; +struct joystick joysticks[8]; + +static int base=0; + +static char *id, *vendor, *product, *src, *target, *button, *device, *flags, *axis, *plus, *minus, *min, *max, *deadzone, *speed; +static char compile[256]=""; + +static int ishex(char *s) { + char c; + if (*s!='0') return 0; + s++; + if (*s!='x') return 0; + s++; + while (*s!='\0') { + c=*s; + if ((('A'<=c)&&(c<='F'))|| + (('a'<=c)&&(c<='f'))|| + (('0'<=c)&&(c<='9'))) s++; + else return 0; + } + return 1; +} + +static int isnum(char *s) { + char c; + while (*s!='\0') { + c=*s; + if (('0'<=c)&&(c<='9')) s++; + else return 0; + } + return 1; +} + +int numeric(char *s) { + int r=0; + int sign = 1; + char msg[256]; + if (s==NULL) return 0; + if (s[0] == '-') { + sign = -1; + s++; + } + if (ishex(s)) { + r=strtol(s, NULL, 16); + } else if (isnum(s)) { + r=strtol(s, NULL, 10); + } else { + sprintf(msg, "Expected a number, got %s instead", s); + report(msg); + return 0; + } + return sign * r; +} + +static int get_device(char *s) { + char msg[256]; + if (s==NULL) report("Device expected!"); + if (strcmp(s, "joyaxis")==0) return DEVICE_JOYSTICK; + if (strcmp(s, "joybtn")==0) return DEVICE_JOYSTICK; + if (strcmp(s, "joystick")==0) return DEVICE_JOYSTICK; + if (strcmp(s, "kbd")==0) return DEVICE_KBD; + if (strcmp(s, "mouse")==0) return DEVICE_MOUSE; + sprintf(msg, "Expecting a device type:joyaxis,joybtn,joystick,kbd,mouse. Found %s instead", s); + report(msg); + return 255; +} + +static int get_type(char *s, dictionary d) { + char *button, *axis, *plus, *minus; + if (s==NULL) return 255; + if (strcmp(s, "joyaxis")==0) return TYPE_AXIS; + if (strcmp(s, "joybtn")==0) return TYPE_BUTTON; + button=lookup_dictionary(d, "button"); + axis=lookup_dictionary(d, "axis"); + plus=lookup_dictionary(d, "plus"); + minus=lookup_dictionary(d, "minus"); + if ((button!=NULL)&&(axis!=NULL)) { + report("Only one of the keys 'button' and 'axis' may be specified"); + return 255; + } + if ((plus!=NULL)&&(axis!=NULL)) { + report("Only one of the keys 'plus' and 'axis' may be specified"); + return 255; + } + if ((minus!=NULL)&&(axis!=NULL)) { + report("Only one of the keys 'minus' and 'axis' may be specified"); + return 255; + } + if (button!=NULL) return TYPE_BUTTON; + if (plus!=NULL) return TYPE_BUTTON; + if (minus!=NULL) return TYPE_BUTTON; + if (axis!=NULL) return TYPE_AXIS; + return 255; +} + +static int parse_flags(char *s) { + int flags=FLAG_NONE; + char *p=s; + int more=1; + char msg[256]; + if (s==NULL) return flags; + while (more) { + while ((*p!='\0')&&(*p!=',')&&(*p!=';')) p++; + if (*p=='\0') more=0; + *p='\0'; + if (strcmp(s, "autorelease")==0) flags|=FLAG_AUTO_RELEASE; + else if (strcmp(s, "release")==0) flags|=FLAG_RELEASE; + else if (strcmp(s, "press")==0) flags|=FLAG_PRESS; + else if (strcmp(s, "shift")==0) flags|=FLAG_SHIFT; + else if (strcmp(s, "invert")==0) flags|=FLAG_INVERT; + else if (strcmp(s, "binary")==0) flags|=FLAG_BINARY; + else if (strcmp(s, "trinary")==0) flags|=FLAG_TRINARY; + else { + sprintf(msg, "Unknown flag %s", s); + report(msg); + } + s=p+1; + } + return flags; +} + +static void parse_sequence(__uint16_t *sequence, char *s, int base, int type) { + char *p; + int releaseflag=0; + int value; + int i; + int n=0; + char msg[256]; + if (s==NULL) { + sequence[0]=SEQUENCE_DONE; + return; + } + if (isnum(s)) { + if ((base==DEVICE_JOYSTICK)&&(type==TYPE_BUTTON)) { + base=BTN_JOYSTICK; + } else if ((base==DEVICE_MOUSE)&&(type==TYPE_BUTTON)) { + base=BTN_MOUSE; + } else base=0; + sequence[0]=numeric(s)+base; + sequence[1]=SEQUENCE_DONE; + } else { + p=s; + while (*p!='\0') { + //skip whitespace + while ((*p!='\0')&&(isspace(*p))) p++; + s=p; + while ((*p!='\0')&&(!(isspace(*p)))) p++; + if (*p!='\0') { + *p='\0'; + p++; + } + value=-1; + if (strcmp(s, "REL")==0) releaseflag=RELEASEMASK; + else { + i=0; + while (keymap[i].value!=-1) { + if (strcmp(keymap[i].key, s)==0) + value=keymap[i].value; + i++; + } + if (value==-1) { + parse_err=1; + sprintf(msg, "Unknown key %s", s); + report(msg); + } else { + sequence[n]=value|releaseflag; + releaseflag=0; + n++; + } + } + s=p; + } + sequence[n]=SEQUENCE_DONE; + } +} + +static void show_dictionary(dictionary dict) { + dictionary d=dict; + char *entry; + entry=get_current(d); + while (entry!=NULL) { + printf("%s ", entry); + d=next_entry(d); + entry=get_current(d); + } +} + +static int has_required(dictionary dict, ...) { + va_list ap; + char *s; + char *entry; + char msg[256]; + + va_start(ap, dict); + s=va_arg(ap, char *); + while (s!=NULL) { + entry=lookup_dictionary(dict, s); + if (entry==NULL) { + sprintf(msg, "Missing key:%s", s); + report(msg); + printf("Defined keys: "); + show_dictionary(dict); + printf("\n"); + va_end(ap); + return 0; + } + s=va_arg(ap, char *); + } + va_end(ap); + return 1; +} + +void report(char *message) { + error=1; + fprintf(stderr, "%d:%d %s.\n", line, cpos, message); +} + +void reportline(int line, int cpos, char *message) { + error=1; + fprintf(stderr, "%d:%d %s.\n", line, cpos, message); +} + +int peekchar() { + if (nextchar!=NONE) return nextchar; + nextchar=fgetc(pfile); + return nextchar; +} + +void eatchar() { + if (nextchar=='\n') { + line++; + cpos=1; + } else cpos++; + if (nextchar!=EOF) + nextchar=NONE; +} + +int readchar() { + int c=peekchar(); + eatchar(); + return c; +} + +static struct token newline() { + struct token t; + t.line=line; + t.pos=cpos; + t.type=NL; + strcpy(t.value, "\n"); + eatchar(); + return t; +} + +static struct token eof() { + struct token t; + t.line=line; + t.pos=cpos; + t.type=EOF; + strcpy(t.value, "end of file"); + return t; +} + +static void parse_comment() { + //first character was # + //read until newline + int nc; + nc=peekchar(); + while ((nc!='\n')&&(nc!=EOF)) { + eatchar(); + nc=peekchar(); + } +} + +static void skipwhite() { + int nc=peekchar(); + while (isspace(nc)&&(nc!='\n')) { + eatchar(); + nc=peekchar(); + } +} + +static struct token parse_string() { + //opening quote has been read + //read until closing quote + struct token t; + int pos=0; + int nc; + t.line=line; + t.pos=cpos; + t.type=STRING; + + nc=readchar(); + nc=readchar(); + while (nc!='"') { + if (nc==EOF) { + report("Unexpected end of file while parsing a string literal"); + break; + } + t.value[pos++]=nc; + nc=readchar(); + } + t.value[pos]='\0'; + return t; +} + +static struct token parse_value(struct token t, int pos) { + int nc; + t.type=VALUE; + if (pos>=0) { + nc=peekchar(); + while (isalnum(nc)||(nc==',')||(nc==';')||(nc=='-')) { + t.value[pos++]=nc; + eatchar(); + nc=peekchar(); + } + t.value[pos]='\0'; + } + nc=0; + while (reserved[nc].token!=NULL) { + if (strcmp(reserved[nc].token, t.value)==0) { + t.type=reserved[nc].value; + break; + } + nc++; + } + return t; +} + +static struct token parse_id(struct token t, int pos) { + int nc; + t.type=ID; + nc=peekchar(); + while (isalnum(nc)||(nc=='_')) { + t.value[pos++]=nc; + eatchar(); + nc=peekchar(); + } + t.value[pos]='\0'; + nc=peekchar(); + if (nc!='=') { + report("Expected an \"=\""); + } else eatchar(); + + return t; +} + +static struct token parse_id_or_value() { + struct token t; + int pos=0; + int nc; + char message[256]; + t.line=line; + t.pos=cpos; + nc=peekchar(); + while (nc!=EOF) { + if (nc=='_') return parse_id(t, pos); + if (nc=='=') return parse_id(t, pos); + if (nc==',') return parse_value(t, pos); + if (nc==';') return parse_value(t, pos); + if (isspace(nc)) { + skipwhite(); + nc=peekchar(); + if (nc=='=') return parse_id(t, pos); + t.value[pos]='\0'; + pos=-1; + return parse_value(t, pos); + } + if (isalnum(nc)) { + t.value[pos++]=nc; + } else { + sprintf(message, "Unexpected character \"%c\".", nc); + report(message); + eatchar(); + break; + } + t.value[pos]='\0'; + eatchar(); + nc=peekchar(); + } + t.type=ID; + report("Missing ="); + return t; +} + +static struct token maptoken() { + int skip=1; + int nc; + struct token t; + char message[256]; + while (skip) { + nc=peekchar(); + if (nc=='#') + parse_comment(); + else if ((nc!='\n')&&isspace(nc)) + skipwhite(); + else skip=0; + } + if (nc=='\n') return newline(); + if (nc==EOF) return eof(); + if (nc=='"') return parse_string(); + if (nc==',') return parse_value(t,0); + if (nc==';') return parse_value(t,0); + if (nc=='_') return parse_value(t,0); + if (nc=='-') return parse_value(t,0); + if (isdigit(nc)) return parse_value(t,0); + if (isalnum(nc)) return parse_id_or_value(); + sprintf(message, "Unexpected character \"%c\".", nc); + report(message); + t.type=ERROR; + t.line=line; + t.pos=cpos; + t.value[0]=nc; + t.value[1]='\0'; + eatchar(); + return t; +} + +struct token (*tokenizer)()=NULL; +static struct token nexttoken; +struct token peektoken() { + if (nexttoken.type!=NONE) return nexttoken; + nexttoken=tokenizer(); + return nexttoken; +} + +void eattoken() { + if (nexttoken.type!=EOF) nexttoken.type=NONE; +} + +struct token readtoken() { + struct token t; + t=peektoken(); + eattoken(); + return t; +} + +void init_tokenizer() { + nextchar=NONE; + nexttoken.type=NONE; + line=1; + cpos=1; +} + +static int known_key(char *s) { + int i; + i=0; + while (known_keys[i]!=NULL) { + if (strcmp(known_keys[i], s)==0) return 1; + i++; + } + return 0; +} + +static void parse_valuepairs() { + struct token key; + struct token value; + char message[256]; + key=readtoken(); + while ((key.type!=NL)||(key.type!=EOF)) { + if (key.type==NL) break; + if (key.type==EOF) break; + if (key.type==ID) { + if (!known_key(key.value)) { + sprintf(message, "Unknown key \"%s\"", key.value); + reportline(key.line, key.pos, message); + } + value=readtoken(); + if ((value.type==STRING)||(value.type==VALUE)) { + dict=add_entry(dict, key.value, value.value); + } else { + sprintf(message, "Unexpected token \"%s\"", value.value); + reportline(value.line, value.pos, message); + } + } else { + sprintf(message, "Unexpected token \"%s\"", key.value); + reportline(key.line, key.pos, message); + } + key=readtoken(); + } +} + +static void parse_shift() { + struct token t; + t=readtoken(); + parse_valuepairs(); + //printf("shift "); + //show_dictionary(dict); + //printf("\n"); + id=lookup_dictionary(dict, "id"); + vendor=lookup_dictionary(dict, "vendor"); + product=lookup_dictionary(dict, "product"); + src=lookup_dictionary(dict, "src"); + if ((id==NULL)&&((vendor==NULL)||(product==NULL))) { + reportline(t.line, t.pos, "Must have id, or vendor and product"); + } else { + if (has_required(dict, "src", NULL)) { + map.program=PROGRAM_BUTTON_REMAP; + if (id!=NULL) + map.joystick=numeric(id); + else + map.joystick=255; + map.vendor=numeric(vendor); + map.product=numeric(product); + map.srcbutton=numeric(src); + map.type=TYPE_SHIFT; + map.flags=FLAG_NONE; + buttons[nbuttons]=map; + nbuttons++; + } + } + free_dictionary(dict); + dict=NULL; +} + +static void parse_script() { + struct token t; + t=readtoken(); + parse_valuepairs(); + //printf("script "); + //show_dictionary(dict); + //printf("\n"); + id=lookup_dictionary(dict, "id"); + vendor=lookup_dictionary(dict, "vendor"); + product=lookup_dictionary(dict, "product"); + device=lookup_dictionary(dict, "device"); + if ((id==NULL)&&((vendor==NULL)||(product==NULL))) { + reportline(t.line, t.pos, "Must have id, or vendor and product"); + } else { + if (has_required(dict, "device", NULL)) { + if (id!=NULL) + script.id=numeric(id); + else + script.id=-1; + script.vendor=numeric(vendor); + script.product=numeric(product); + script.device=numeric(device); + scriptassign[nscript]=script; + nscript++; + } + } + free_dictionary(dict); + dict=NULL; +} + +static void parse_button() { + struct token t; + int num; + t=readtoken(); + parse_valuepairs(); + id=lookup_dictionary(dict, "id"); + vendor=lookup_dictionary(dict, "vendor"); + product=lookup_dictionary(dict, "product"); + src=lookup_dictionary(dict, "src"); + target=lookup_dictionary(dict, "target"); + button=lookup_dictionary(dict, "button"); + axis=lookup_dictionary(dict, "axis"); + device=lookup_dictionary(dict, "device"); + flags=lookup_dictionary(dict, "flags"); + speed=lookup_dictionary(dict, "speed"); + if ((id==NULL)&&((vendor==NULL)||(product==NULL))) { + reportline(t.line, t.pos, "Must have id, or vendor and product"); + } else { + if (has_required(dict, "src", "target", NULL)) { + map.program=PROGRAM_BUTTON_REMAP; + if (id!=NULL) + map.joystick=numeric(id); + else + map.joystick=255; + map.vendor=numeric(vendor); + map.product=numeric(product); + base=get_device(target); + map.device=base+(numeric(device)&0xF); + map.type=get_type(target,dict); + if (base==DEVICE_JOYSTICK) { + num=numeric(device); + if (num>8) { + report("Maximum of 8 joysticks allowed"); + } else { + if (num>=njoysticks) { + njoysticks=num+1; + } + if (map.type==TYPE_AXIS) { + if (joysticks[num].axes<=numeric(axis)) + joysticks[num].axes=numeric(axis)+1; + } + if (map.type==TYPE_BUTTON) { + if (joysticks[num].buttons<=numeric(button)) + joysticks[num].buttons=numeric(button)+1; + } + } + } + map.srcbutton=numeric(src)+BTN_JOYSTICK; + map.speed=numeric(speed); + if (map.speed==0) + map.speed=8; + map.flags=parse_flags(flags); + if (button!=NULL) + parse_sequence(map.sequence, button, base, map.type); + else if (axis!=NULL) + parse_sequence(map.sequence, axis, base, map.type); + buttons[nbuttons]=map; + nbuttons++; + if (!((map.flags&FLAG_PRESS)||(map.flags&FLAG_RELEASE))&& + (map.sequence[1]==SEQUENCE_DONE)) { + if (map.sequence[0]&RELEASEMASK) { + map.sequence[0]&=~RELEASEMASK; + } else { + map.sequence[0]|=RELEASEMASK; + } + map.flags|=FLAG_RELEASE; + buttons[nbuttons]=map; + nbuttons++; + } + } + } + free_dictionary(dict); + dict=NULL; +} + +static void parse_axis() { + struct token t; + int num; + t=readtoken(); + parse_valuepairs(); + //printf("axis "); + //show_dictionary(dict); + //printf("\n"); + id=lookup_dictionary(dict, "id"); + vendor=lookup_dictionary(dict, "vendor"); + product=lookup_dictionary(dict, "product"); + src=lookup_dictionary(dict, "src"); + target=lookup_dictionary(dict, "target"); + axis=lookup_dictionary(dict, "axis"); + plus=lookup_dictionary(dict, "plus"); + minus=lookup_dictionary(dict, "minus"); + device=lookup_dictionary(dict, "device"); + flags=lookup_dictionary(dict, "flags"); + min=lookup_dictionary(dict, "min"); + max=lookup_dictionary(dict, "max"); + deadzone=lookup_dictionary(dict, "deadzone"); + speed=lookup_dictionary(dict, "speed"); + if ((id==NULL)&&((vendor==NULL)||(product==NULL))) { + reportline(t.line, t.pos, "Must have id, or vendor and product"); + } else { + if (has_required(dict, "src", "target", NULL)) { + amap.program=PROGRAM_AXIS_REMAP; + if (id!=NULL) + amap.joystick=numeric(id); + else + amap.joystick=255; + amap.vendor=numeric(vendor); + amap.product=numeric(product); + amap.srcaxis=numeric(src); + base=get_device(target); + amap.device=base+(numeric(device)&0xF); + amap.type=get_type(target,dict); + amap.saved_value = 0; + if (base==DEVICE_JOYSTICK) { + num=numeric(device); + if (num>8) { + report("Maximum of 8 joysticks allowed"); + } else { + if (num>=njoysticks) { + njoysticks=num+1; + } + if (amap.type==TYPE_AXIS) { + if (joysticks[num].axes<=numeric(axis)) + joysticks[num].axes=numeric(axis)+1; + } + if (amap.type==TYPE_BUTTON) { + if (joysticks[num].buttons<=numeric(plus)) + joysticks[num].buttons=numeric(plus)+1; + if (joysticks[num].buttons<=numeric(minus)) + joysticks[num].buttons=numeric(minus)+1; + } + } + } + if ((base==DEVICE_JOYSTICK)&&(amap.type==TYPE_BUTTON)) { + base=BTN_JOYSTICK; + } else if ((base==DEVICE_MOUSE)&&(amap.type==TYPE_BUTTON)) { + base=BTN_MOUSE; + } else base=0; + parse_sequence(amap.plus, plus, base, amap.type); + parse_sequence(amap.minus, minus, base, amap.type); + amap.axis=numeric(axis); + amap.flags=parse_flags(flags); + amap.min=numeric(min); + amap.max=numeric(max); + amap.deadzone=numeric(deadzone); + amap.speed=numeric(speed); + if (amap.speed==0) + amap.speed=32767; + axes[naxes]=amap; + naxes++; + } + } + free_dictionary(dict); + dict=NULL; +} + +static void parse_joystick() { + struct token t; + char *axes; char *buttons; + int num; + t=readtoken(); + parse_valuepairs(); + axes=lookup_dictionary(dict, "axes"); + buttons=lookup_dictionary(dict, "buttons"); + device=lookup_dictionary(dict, "device"); + if (device==NULL) { + reportline(t.line, t.pos, "Must have device"); + } else { + num=numeric(device); + if ((num<0)||(num>7)) reportline(t.line, t.pos, "Joystick must be 0-7"); + //printf("joystick "); + //show_dictionary(dict); + //printf("\n"); + if (num>=njoysticks) njoysticks=num+1; + if (axes!=NULL) + joysticks[num].axes=numeric(axes); + else + joysticks[num].axes=0; + if (buttons!=NULL) + joysticks[num].buttons=numeric(buttons); + else + joysticks[num].buttons=0; + } + free_dictionary(dict); + dict=NULL; +} + +static void parse_code() { + struct token t; + t=readtoken(); + t=readtoken(); + //printf("code "); + if (t.type!=STRING) { + reportline(t.line, t.pos, "String expected"); + } else strcpy(compile, t.value); + t=readtoken(); + while ((t.type!=NL)&&(t.type!=EOF)) { + reportline(t.line, t.pos, "No further token expected on this line"); + } + //printf("\"%s\"\n", compile); +} + +static void parse_joysticks() { + struct token t; + int num; + t=readtoken(); + t=readtoken(); + printf("joysticks "); + if (t.type!=VALUE) { + reportline(t.line, t.pos, "Value expected"); + } else num=numeric(t.value); + t=readtoken(); + while ((t.type!=NL)&&(t.type!=EOF)) { + reportline(t.line, t.pos, "No further token expected on this line"); + } + if (num<0) reportline(t.line, t.pos, "Only positive numbers allowed"); + if (num>8) reportline(t.line, t.pos, "Maximum 8 joysticks allowed"); + njoysticks=num; + printf("\"%d\"\n", num); +} + +static void parse_lines(); +static void parse_include() { + struct token t; + char include[256]=""; + FILE *backup; + t=readtoken(); + t=readtoken(); + //printf("include "); + if (t.type!=STRING) { + reportline(t.line, t.pos, "String expected"); + } else strcpy(include, t.value); + t=readtoken(); + while ((t.type!=NL)&&(t.type!=EOF)) { + reportline(t.line, t.pos, "No further token expected on this line"); + } + //printf("\"%s\"\n", include); + backup=pfile; + pfile=fopen(include, "r"); + if (pfile==NULL) reportline(t.line, t.pos, "Could not find specified file"); + else parse_lines(); + pfile=backup; +} + +static void parse_lines() { + struct token t; + char message[256]; + t=peektoken(); + while (t.type!=EOF) { + if (t.type==NL) { + t=readtoken(); + //do nothing + } else if (t.type==EOF) { + t=readtoken(); + //do nothing + } else if (t.type==CODE) { + parse_code(); + } else if (t.type==BUTTON) { + parse_button(); + } else if (t.type==AXIS) { + parse_axis(); + } else if (t.type==SHIFT) { + parse_shift(); + } else if (t.type==SCRIPT) { + parse_script(); + } else if (t.type==JOYSTICK) { + parse_joystick(); + } else if (t.type==JOYSTICKS) { + parse_joysticks(); + } else if (t.type==INCLUDE) { + parse_include(); + } else { + t=readtoken(); + error=1; + if (t.type!=ERROR) { + sprintf(message, "Unexpected token \"%s\"", t.value); + reportline(t.line, t.pos, message); + while ((t.type!=EOF)&&(t.type!=NL)) { + t=readtoken(); + } + } + } + t=peektoken(); + } +} + +int parse_map(void) { + pfile=fmap; + tokenizer=maptoken; + init_tokenizer(); + parse_lines(); + free_dictionary(dict); + dict=NULL; + if (fmap!=stdin) fclose(fmap); + if (strlen(compile)>0) { + fprintf(stderr, "parsing program %s\n", compile); + if (parse_program(compile, &program)!=0) error=1; + } + return error; +} + diff --git a/mapper.h b/mapper.h new file mode 100644 index 0000000..fa4a677 --- /dev/null +++ b/mapper.h @@ -0,0 +1,46 @@ +#ifndef __mapper_h +#define __mapper_h + +#include "program.h" +#define BUS_JOYMAP 0xEF + +void poll_joystick_loop(); +void install_event_handlers(); +void register_devices(); +void unregister_devices(); +void press_key(int code, int value); +void press_joy_button(int j, int code, int value); +void set_joy_axis(int j, int axis, int value); +void remove_all_joysticks(void); +void set_num_joysticks(int num); +void set_num_axes(int js, int axes); +void set_num_buttons(int js, int buttons); +void code_button(int code, int value); +void code_axis(int axis, int value); +void press_mouse_button(int code, int value); +void move_mouse(int dx, int dy); +void move_mouse_y(int dy); +void move_mouse_x(int dx); +void move_mouse_wheel(int rdw); +void set_mouse_pos(int cx, int cy); +void code_set_program(struct program_code *code); +void code_reset(void); +void code_notify_button(int js, int key, int value); +void code_notify_axis(int js, int axis, int value); +int no_signal(void); +int goto_next_signal(void); +void push_signal(int signal); +void release_mouse_buttons(void); +void release_keys(void); +void remap_button(struct program_button_remap *btn); +void remap_axis(struct program_axis_remap *axis); +void set_joystick_number(__uint16_t vendor, __uint16_t product, int device); +int mapper_code_install(void); +int mapper_code_uninstall(void); +void program_run(); +void printcode(); +void repeat_mouse_move(); +void set_scale_factor(int mult, int div, int ofs); +void set_dynamic_calibrate(int on); + +#endif diff --git a/oldexamples/basic.map b/oldexamples/basic.map new file mode 100644 index 0000000..741ccca --- /dev/null +++ b/oldexamples/basic.map @@ -0,0 +1,45 @@ +#sample using joystick number, id=4 implies use of js4 +axis id=0 src=4 target=joyaxis device=1 axis=1 + +#sample using vendor and product id. remap to joystick 0 (js0 if joymap is installed first) +#device defaults to 0 if not specified +#CH Products Combatstick +axis vendor=0x068e product=0x00f4 src=0 target=joyaxis device=0 axis=0 +axis vendor=0x068e product=0x00f4 src=1 target=joyaxis device=0 axis=1 +button vendor=0x068e product=0x00f4 src=0 target=joybtn device=0 button=0 +#flag can be: +# button: +# autorelease: release the key automatically +# release: only apply rule on releasing the button (otherwise only on pressing) +# press: only apply rule on pressing the button +# shift: apply this operation if shifted +# invert: reverse the axis (if an axis is the target) +#if press and release are not specified, then two rules are created, one with release +#and one with press in such a way that buttons will correspond precisely +# axis: +# invert: reverse the axis +# +button vendor=0x068e product=0x00f4 src=1 target=kbd button="a" flags=autorelease +#strings can be used to specify a sequence, REL indicates release +#see keys.map +button vendor=0x068e product=0x00f4 src=2 target=kbd button="b REL b a REL a n REL n g REL g leftshift num1 REL num1 REL leftshift" + +#CH Products PRO Throttle +axis vendor=0x068e product=0x00f1 src=2 target=joyaxis device=0 axis=2 +axis vendor=0x068e product=0x00f1 src=0 target=mouse axis=0 +axis vendor=0x068e product=0x00f1 src=1 target=mouse axis=1 +button vendor=0x068e product=0x00f1 src=1 target=mouse button=0 + +#CH Products Pedals +axis vendor=0x068e product=0x00f2 src=2 target=joyaxis device=0 axis=3 + +#specify a shift button, that gives access to extra functionality +shift vendor=0x068e product=0x00f1 src=5 + +#assign a joystick number fo script purposes +script vendor=0x068e product=0x00f4 device=0 #Combatstick +script vendor=0x068e product=0x00f1 device=1 #Throttle + +#for adding a custom program script +code "test.program" + diff --git a/oldexamples/quickmap.c b/oldexamples/quickmap.c new file mode 100644 index 0000000..fda946f --- /dev/null +++ b/oldexamples/quickmap.c @@ -0,0 +1,313 @@ +#include +#include +#include +#include +#include +#include "keys.h" +#include "program.h" + +//vendor=0x068e; //CH Products +//product=0x00f1; //PRO THROTTLE + +//vendor=0x068e; //CH Products +//product=0x00f4; //COMBATSTICK + +//vendor=0x068e; //CH Products +//product=0x00f2; //PEDALS + +int keys[]={ + KEY_A, + KEY_B, + KEY_C, + KEY_D, + KEY_E, + KEY_F, + KEY_G, + KEY_H, + KEY_I, + KEY_J, + KEY_K, + KEY_L, + KEY_M, + KEY_N, + KEY_O, + KEY_P, + KEY_Q, + KEY_R, + KEY_S, + KEY_T, + KEY_V, + KEY_W, + KEY_X, + KEY_Y, + KEY_Z, +}; + +int bang[MAX_SEQUENCE]={ + KEY_B, + KEY_B|RELEASEMASK, + KEY_A, + KEY_A|RELEASEMASK, + KEY_N, + KEY_N|RELEASEMASK, + KEY_G, + KEY_G|RELEASEMASK, + KEY_LEFTSHIFT, + KEY_1, + KEY_1|RELEASEMASK, + KEY_LEFTSHIFT|RELEASEMASK, + SEQUENCE_DONE, +}; + +int main(void) { + int fd; + int i; + char devname[256]; + int vendor=0; + int product=0; + struct program_button_remap map; + struct program_axis_remap amap; + + fd=open("/dev/mapper", O_RDWR); + if (fd<0) { + perror("Error programming joystick"); + return 0; + } + + for (i=0; i<128; i++) { + vendor=i; + if (ioctl(fd, JOYMAP_VENDOR, &vendor)<0) { + //perror("Failed to get vendor ID"); + continue; + } + product=i; + if (ioctl(fd, JOYMAP_PRODUCT, &product)<0) { + //perror("Failed to get product ID"); + continue; + } + sprintf(devname, " "); + devname[0]=i; + if (ioctl(fd, JOYMAP_NAME, devname)<0) { + //perror("Failed to get product name"); + continue; + } + fprintf(stderr, "%d: Vendor=%x Product=%x [%s]\n", i, vendor, product, devname); + } + + map.program=PROGRAM_BUTTON_REMAP; + map.joystick=255; + map.vendor=0x068e; //CH Products + map.product=0x00f4; //COMBATSTICK + map.srcbutton=BTN_JOYSTICK+0; + map.device=DEVICE_KBD; + map.type=TYPE_BUTTON; + map.flags=FLAG_NONE; + for (i=0; i +#include +#include +#include +#include +#include "keys.h" +#include "program.h" + +//vendor=0x068e; //CH Products +//product=0x00f1; //PRO THROTTLE + +//vendor=0x068e; //CH Products +//product=0x00f4; //COMBATSTICK + +//vendor=0x068e; //CH Products +//product=0x00f2; //PEDALS + +int keys[]={ + KEY_A, + KEY_B, + KEY_C, + KEY_D, + KEY_E, + KEY_F, + KEY_G, + KEY_H, + KEY_I, + KEY_J, + KEY_K, + KEY_L, + KEY_M, + KEY_N, + KEY_O, + KEY_P, + KEY_Q, + KEY_R, + KEY_S, + KEY_T, + KEY_V, + KEY_W, + KEY_X, + KEY_Y, + KEY_Z, +}; + +int bang[MAX_SEQUENCE]={ + KEY_B, + KEY_B|RELEASEMASK, + KEY_A, + KEY_A|RELEASEMASK, + KEY_N, + KEY_N|RELEASEMASK, + KEY_G, + KEY_G|RELEASEMASK, + KEY_LEFTSHIFT, + KEY_1, + KEY_1|RELEASEMASK, + KEY_LEFTSHIFT|RELEASEMASK, + SEQUENCE_DONE, +}; + +int main(void) { + int fd; + int i; + char devname[256]; + int vendor=0; + int product=0; + struct program_button_remap map; + struct program_axis_remap amap; + + fd=open("/dev/mapper", O_RDWR); + if (fd<0) { + perror("Error programming joystick"); + return 0; + } + + for (i=0; i<128; i++) { + vendor=i; + if (ioctl(fd, JOYMAP_VENDOR, &vendor)<0) { + //perror("Failed to get vendor ID"); + continue; + } + product=i; + if (ioctl(fd, JOYMAP_PRODUCT, &product)<0) { + //perror("Failed to get product ID"); + continue; + } + sprintf(devname, " "); + devname[0]=i; + if (ioctl(fd, JOYMAP_NAME, devname)<0) { + //perror("Failed to get product name"); + continue; + } + fprintf(stderr, "%d: Vendor=%x Product=%x [%s]\n", i, vendor, product, devname); + } + + for (i=0; i<10; i++) { + map.program=PROGRAM_BUTTON_REMAP; + map.joystick=255; + map.vendor=0x068e; //CH Products + map.product=0x00f4; //COMBATSTICK + map.srcbutton=BTN_JOYSTICK+i; + map.device=DEVICE_JOYSTICK; + map.type=TYPE_BUTTON; + map.flags=FLAG_NONE; + map.press=BTN_JOYSTICK+i; + map.sequence[1]=SEQUENCE_DONE; + write(fd, (char *)&map, sizeof(map)); + map.program=PROGRAM_BUTTON_REMAP; + map.joystick=255; + map.vendor=0x068e; //CH Products + map.product=0x00f4; //COMBATSTICK + map.srcbutton=BTN_JOYSTICK+i; + map.device=DEVICE_JOYSTICK; + map.type=TYPE_BUTTON; + map.flags=FLAG_RELEASE; + map.press=(BTN_JOYSTICK+i)|RELEASEMASK; + map.sequence[1]=SEQUENCE_DONE; + write(fd, (char *)&map, sizeof(map)); + } + + amap.program=PROGRAM_AXIS_REMAP; + amap.joystick=255; + amap.vendor=0x068e; //CH Products + amap.product=0x00f1; //PRO THROTTLE + amap.srcaxis=ABS_X; + amap.device=DEVICE_MOUSE; + amap.type=TYPE_AXIS; + amap.flags=FLAG_NONE; + amap.plus=ABS_X; + amap.minus=ABS_X; + amap.axis=ABS_X; + write(fd, (char *)&amap, sizeof(amap)); + + amap.program=PROGRAM_AXIS_REMAP; + amap.joystick=255; + amap.vendor=0x068e; //CH Products + amap.product=0x00f1; //PRO THROTTLE + amap.srcaxis=ABS_Y; + amap.device=DEVICE_MOUSE; + amap.type=TYPE_AXIS; + amap.flags=FLAG_NONE; + amap.plus=ABS_Y; + amap.minus=ABS_Y; + amap.axis=ABS_Y; + write(fd, (char *)&amap, sizeof(amap)); + + amap.program=PROGRAM_AXIS_REMAP; + amap.joystick=255; + amap.vendor=0x068e; //CH Products + amap.product=0x00f1; //PRO THROTTLE + amap.srcaxis=ABS_Z; + amap.device=DEVICE_JOYSTICK; + amap.type=TYPE_AXIS; + amap.flags=FLAG_NONE; + amap.plus=ABS_Z; + amap.minus=ABS_Z; + amap.axis=ABS_Z; + write(fd, (char *)&amap, sizeof(amap)); + + amap.program=PROGRAM_AXIS_REMAP; + amap.joystick=255; + amap.vendor=0x068e; //CH Products + amap.product=0x00f4; //COMBATSTICK + amap.srcaxis=ABS_X; + amap.device=DEVICE_JOYSTICK; + amap.type=TYPE_AXIS; + amap.flags=FLAG_NONE; + amap.plus=ABS_X; + amap.minus=ABS_X; + amap.axis=ABS_X; + write(fd, (char *)&amap, sizeof(amap)); + + amap.program=PROGRAM_AXIS_REMAP; + amap.joystick=255; + amap.vendor=0x068e; //CH Products + amap.product=0x00f4; //COMBATSTICK + amap.srcaxis=ABS_Y; + amap.device=DEVICE_JOYSTICK; + amap.type=TYPE_AXIS; + amap.flags=FLAG_NONE; + amap.plus=ABS_Y; + amap.minus=ABS_Y; + amap.axis=ABS_Y; + write(fd, (char *)&amap, sizeof(amap)); + + amap.program=PROGRAM_AXIS_REMAP; + amap.joystick=255; + amap.vendor=0x068e; //CH Products + amap.product=0x00f2; //PEDALS + amap.srcaxis=ABS_Z; + amap.device=DEVICE_JOYSTICK; + amap.type=TYPE_AXIS; + amap.flags=FLAG_NONE; + amap.plus=ABS_RX; + amap.minus=ABS_RX; + amap.axis=ABS_RX; + write(fd, (char *)&amap, sizeof(amap)); + + ioctl(fd, JOYMAP_MAP); + close(fd); +} + diff --git a/oldparser/map.lex b/oldparser/map.lex new file mode 100644 index 0000000..f39658b --- /dev/null +++ b/oldparser/map.lex @@ -0,0 +1,51 @@ +%{ +#include +#include +#include + +static int lineno=1; +%} + +digit [0-9] +id [a-zA-Z_][A-Za-z0-9_]*[=] +comment [#].*[\n] +white [ \r\t] +newline [\n] +int {digit}+ +float {int}"."{int} +num [\+\-]?({float}|{int}) +value [a-zA-Z,;0-9]* +string \"[^\"]*\" +%% +shift" " { return SHIFT;} +button" " { return BUTTON;} +axis" " { return AXIS;} +code" " { return CODE;} +{comment} { lineno++;} +{white} {} +{newline} { lineno++; return NL;} +{id} { + yytext[strlen(yytext)-1]='\0'; + strcpy(yylval.string,yytext); + return ID; + } +{string} { + yytext[strlen(yytext)-1]='\0'; + strcpy(yylval.string,yytext+1); + return STRING; + } +{value} { + strcpy(yylval.string,yytext); + return VALUE; + } +%% +//. { return yytext[0];} + +int yywrap(void) { + return 1; +} + +void yyerror(char *s) { + printf("Parse error(line %d):" , lineno); + printf("%s\n", s); +} diff --git a/oldparser/map.y b/oldparser/map.y new file mode 100644 index 0000000..8a909eb --- /dev/null +++ b/oldparser/map.y @@ -0,0 +1,431 @@ +%{ +#include +#include +#include +#include +#include +#include +#include "dictionary.h" +#include "dictionary.c" +#include "program.h" +#include "keys.h" +#include "validkeys.h" + +static dictionary dict=NULL; +static struct program_button_remap map; +static struct program_axis_remap amap; +static struct program_code program; +#define MAX_ASSIGN 1024 +static struct program_button_remap buttons[MAX_ASSIGN]; +static struct program_axis_remap axes[MAX_ASSIGN]; +static int parse_err=0; +static int nbuttons=0; +static int naxes=0; +static int base=0; + +static char *id, *vendor, *product, *src, *target, *button, *device, *flags, *axis, *plus, *minus; +static char compile[256]=""; + +int ishex(char *s) { + char c; + if (*s!='0') return 0; + s++; + if (*s!='x') return 0; + s++; + while (*s!='\0') { + c=*s; + if ((('A'<=c)&&(c<='F'))|| + (('a'<=c)&&(c<='f'))|| + (('0'<=c)&&(c<='9'))) s++; + else return 0; + } + return 1; +} + +int isnum(char *s) { + char c; + while (*s!='\0') { + c=*s; + if (('0'<=c)&&(c<='9')) s++; + else return 0; + } + return 1; +} + +int numeric(char *s) { + int r=0; + if (s==NULL) return 0; + if (ishex(s)) { + r=strtol(s, NULL, 16); + } else if (isnum(s)) { + r=strtol(s, NULL, 10); + } else { + printf("expected a number, got %s instead\n", s); + return 0; + } + return r; +} + +int get_device(char *s) { + if (s==NULL) printf("device expected!\n"); + if (strcmp(s, "joyaxis")==0) return DEVICE_JOYSTICK; + if (strcmp(s, "joybtn")==0) return DEVICE_JOYSTICK; + if (strcmp(s, "joystick")==0) return DEVICE_JOYSTICK; + if (strcmp(s, "kbd")==0) return DEVICE_KBD; + if (strcmp(s, "mouse")==0) return DEVICE_MOUSE; + printf("Expecting a device type:joyaxis,joybtn,joystick,kbd,mouse. Found %s instead.\n", s); + return 255; +} + +int get_type(char *s, dictionary d) { + char *button, *axis; + if (s==NULL) return 255; + if (strcmp(s, "joyaxis")==0) return TYPE_AXIS; + if (strcmp(s, "joybtn")==0) return TYPE_BUTTON; + button=lookup_dictionary(d, "button"); + axis=lookup_dictionary(d, "axis"); + if ((button!=NULL)&&(axis!=NULL)) { + printf("Only one of the keys 'button' and 'axis' may be specified.\n"); + return 255; + } + if (button!=NULL) return TYPE_BUTTON; + if (axis!=NULL) return TYPE_AXIS; + return 255; +} + +int parse_flags(char *s) { + int flags=FLAG_NONE; + char *p=s; + int more=1; + if (s==NULL) return flags; + while (more) { + while ((*p!='\0')&&(*p!=',')&&(*p!=';')) p++; + if (*p=='\0') more=0; + *p='\0'; + if (strcmp(s, "autorelease")==0) flags|=FLAG_AUTO_RELEASE; + else if (strcmp(s, "release")==0) flags|=FLAG_RELEASE; + else if (strcmp(s, "press")==0) flags|=FLAG_PRESS; + else if (strcmp(s, "shift")==0) flags|=FLAG_SHIFT; + else if (strcmp(s, "invert")==0) flags|=FLAG_INVERT; + else printf("Unknown flag %s.\n", s); + s=p+1; + } + return flags; +} + +void parse_sequence(__s16 *sequence, char *s, int base, int type) { + char *p; + int releaseflag=0; + int value; + int i; + int n=0; + if (s==NULL) return; + if (isnum(s)) { + if ((base==DEVICE_JOYSTICK)&&(type==TYPE_BUTTON)) { + base=BTN_JOYSTICK; + } else if ((base==DEVICE_MOUSE)&&(type==TYPE_BUTTON)) { + base=BTN_MOUSE; + } else base=0; + sequence[0]=numeric(s)+base; + sequence[1]=SEQUENCE_DONE; + } else { + p=s; + while (*p!='\0') { + //skip whitespace + while ((*p!='\0')&&(isspace(*p))) p++; + s=p; + while ((*p!='\0')&&(!(isspace(*p)))) p++; + if (*p!='\0') { + *p='\0'; + p++; + } + value=-1; + if (strcmp(s, "REL")==0) releaseflag=RELEASEMASK; + else { + i=0; + while (keymap[i].value!=-1) { + if (strcmp(keymap[i].key, s)==0) + value=keymap[i].value; + i++; + } + if (value==-1) { + parse_err=1; + printf("Unknown key %s\n", s); + } else { + sequence[n]=value|releaseflag; + releaseflag=0; + n++; + } + } + s=p; + } + sequence[n]=SEQUENCE_DONE; + } +} + +void show_dictionary(dictionary dict) { + dictionary d=dict; + char *entry; + entry=get_current(d); + while (entry!=NULL) { + printf("%s ", entry); + d=next_entry(d); + entry=get_current(d); + } +} + +int has_required(dictionary dict, ...) { + va_list ap; + char *s; + char *entry; + + va_start(ap, dict); + s=va_arg(ap, char *); + while (s!=NULL) { + entry=lookup_dictionary(dict, s); + if (entry==NULL) { + printf("Missing key:%s\n", s); + va_end(ap); + return 0; + } + s=va_arg(ap, char *); + } + va_end(ap); + return 1; +} +%} + +%union { + char string[256]; +} + +%token SHIFT +%token BUTTON +%token AXIS +%token CODE +%token ID +%token VALUE +%token STRING +%token NL +%% + +program: lines {} +; + +lines: /* empty */ + |lines axis {} + |lines code {} + |lines button {} + |lines shift {} + |lines NL {} + |error NL { parse_err=1; printf("error before newline!\n"); } +; + +shift: SHIFT valuepairs NL { + printf("shift "); + show_dictionary(dict); + printf("\n"); + id=lookup_dictionary(dict, "id"); + vendor=lookup_dictionary(dict, "vendor"); + product=lookup_dictionary(dict, "product"); + src=lookup_dictionary(dict, "src"); + if ((id==NULL)&&((vendor==NULL)||(product==NULL))) { + printf("Must have id, or vendor and product\n"); + } else { + if (has_required(dict, "src", NULL)) { + map.program=PROGRAM_BUTTON_REMAP; + if (id!=NULL) + map.joystick=numeric(id); + else + map.joystick=255; + map.vendor=numeric(vendor); + map.product=numeric(product); + map.srcbutton=numeric(src); + map.type=TYPE_SHIFT; + map.flags=FLAG_NONE; + buttons[nbuttons]=map; + nbuttons++; + } + } + free_dictionary(dict); + dict=NULL; + } +; + +button: BUTTON valuepairs NL { + id=lookup_dictionary(dict, "id"); + vendor=lookup_dictionary(dict, "vendor"); + product=lookup_dictionary(dict, "product"); + src=lookup_dictionary(dict, "src"); + target=lookup_dictionary(dict, "target"); + button=lookup_dictionary(dict, "button"); + axis=lookup_dictionary(dict, "axis"); + device=lookup_dictionary(dict, "device"); + flags=lookup_dictionary(dict, "flags"); + if ((id==NULL)&&((vendor==NULL)||(product==NULL))) { + printf("Must have id, or vendor and product\n"); + } else { + if (has_required(dict, "src", "target", NULL)) { + printf("button "); + show_dictionary(dict); + printf("\n"); + map.program=PROGRAM_BUTTON_REMAP; + if (id!=NULL) + map.joystick=numeric(id); + else + map.joystick=255; + map.vendor=numeric(vendor); + map.product=numeric(product); + base=get_device(target); + map.device=base+(numeric(device)&0xF); + map.type=get_type(target,dict); + map.srcbutton=numeric(src)+BTN_JOYSTICK; + map.flags=parse_flags(flags); + if (button!=NULL) + parse_sequence(map.sequence, button, base, map.type); + else if (axis!=NULL) + parse_sequence(map.sequence, axis, base, map.type); + buttons[nbuttons]=map; + nbuttons++; + if (!((map.flags&FLAG_PRESS)||(map.flags&FLAG_RELEASE))&& + (map.sequence[1]==SEQUENCE_DONE)) { + if (map.sequence[0]&RELEASEMASK) { + map.sequence[0]&=~RELEASEMASK; + } else { + map.sequence[0]|=RELEASEMASK; + } + map.flags|=FLAG_RELEASE; + buttons[nbuttons]=map; + nbuttons++; + } + } + } + free_dictionary(dict); + dict=NULL; + } +; + +axis: AXIS valuepairs NL { + printf("axis "); + show_dictionary(dict); + printf("\n"); + id=lookup_dictionary(dict, "id"); + vendor=lookup_dictionary(dict, "vendor"); + product=lookup_dictionary(dict, "product"); + src=lookup_dictionary(dict, "src"); + target=lookup_dictionary(dict, "target"); + axis=lookup_dictionary(dict, "axis"); + plus=lookup_dictionary(dict, "plus"); + minus=lookup_dictionary(dict, "minus"); + device=lookup_dictionary(dict, "device"); + flags=lookup_dictionary(dict, "flags"); + if ((id==NULL)&&((vendor==NULL)||(product==NULL))) { + printf("Must have id, or vendor and product\n"); + } else { + if (has_required(dict, "src", "target", NULL)) { + amap.program=PROGRAM_AXIS_REMAP; + if (id!=NULL) + amap.joystick=numeric(id); + else + amap.joystick=255; + amap.vendor=numeric(vendor); + amap.product=numeric(product); + amap.srcaxis=numeric(src); + base=get_device(target); + amap.device=base+(numeric(device)&0xF); + amap.type=get_type(target,dict); + if ((base==DEVICE_JOYSTICK)&&(amap.type==TYPE_BUTTON)) { + base=BTN_JOYSTICK; + } else if ((base==DEVICE_MOUSE)&&(amap.type==TYPE_BUTTON)) { + base=BTN_MOUSE; + } else base=0; + amap.plus=numeric(plus)+base; + amap.minus=numeric(minus)+base; + amap.axis=numeric(axis); + amap.flags=parse_flags(flags); + axes[naxes]=amap; + naxes++; + } + } + free_dictionary(dict); + dict=NULL; + } +; + +code: CODE STRING NL { + printf("code %s\n", $2); + strcpy(compile, $2); + } +; + +valuepairs: /* empty */ + |valuepairs valuepair {} +; + +valuepair: ID VALUE { + dict=add_entry(dict, $1, $2); + } + |ID STRING { + dict=add_entry(dict, $1, $2); + } +; + +%% + +#include "lex.map.c" + +#include "programparse.h" +int main(int argc, char *argv[]) { + int i, fd; + char devname[256]; + int vendor=0; + int product=0; + struct program_button_remap map; + struct program_axis_remap amap; + + ++argv, --argc; /* skip over program name */ + if ( argc > 0 ) + yyin = fopen( argv[0], "r" ); + else + yyin = stdin; + + fd=open("/dev/mapper", O_RDWR); + if (fd<0) { + perror("Error programming joystick"); + return 0; + } + + for (i=0; i<128; i++) { + vendor=i; + if (ioctl(fd, JOYMAP_VENDOR, &vendor)<0) { + continue; + } + product=i; + if (ioctl(fd, JOYMAP_PRODUCT, &product)<0) { + continue; + } + sprintf(devname, " "); + devname[0]=i; + if (ioctl(fd, JOYMAP_NAME, devname)<0) { + continue; + } + fprintf(stderr, "%d: Vendor=%x Product=%x [%s]\n", i, vendor, product, devname); + } + yyparse(); + if (strlen(compile)>0) { + if (parse_program(compile, &program)!=0) parse_err=1; + } + if (!parse_err) { + ioctl(fd, JOYMAP_UNMAP); + printf("%d button assignments.\n", nbuttons); + for (i=0; i +#include +#include + +static int lineno=1; +%} + +digit [0-9] +id [a-zA-Z_][A-Za-z0-9_]* +comment [#].*[\n] +white [ \r\t] +newline [\n] +int {digit}+ +float {int}"."{int} +num [\+\-]?({float}|{int}) +value {int} +string \"[^\"]*\" +%% +if { return IF;} +else { return ELSE;} +while { return WHILE;} +delay { return DELAY;} +wait { return WAIT;} +var { return VAR;} +signal { return PSIGNAL;} +press { return PRESS;} +release { return RELEASE;} +halt { return PHALT;} +thread { return PTHREAD;} +{comment} { lineno++;} +{white} {} +{newline} { lineno++;} +{id} { + yytext[strlen(yytext)-1]='\0'; + strcpy(yylval.string,yytext); + return ID; + } +{string} { + yytext[strlen(yytext)-1]='\0'; + strcpy(yylval.string,yytext+1); + return STRING; + } +{value} { + strcpy(yylval.string,yytext); + return INT; + } +. { return yytext[0];} +%% + +int yywrap(void) { + return 1; +} + +void yyerror(char *s) { + printf("Parse error(line %d):" , lineno); + printf("%s\n", s); +} diff --git a/oldparser/program.y b/oldparser/program.y new file mode 100644 index 0000000..11fe5b5 --- /dev/null +++ b/oldparser/program.y @@ -0,0 +1,162 @@ +%{ +#include +#include +#include +#include +#include +#include +#include "program.h" +#include "keys.h" +#include "validkeys.h" + +static int program_parse_err=0; +%} + +%union { + char string[256]; + int num; +} + +%token IF +%token ELSE +%token WHILE +%token DELAY +%token WAIT +%token VAR +%token PSIGNAL +%token PRESS +%token RELEASE +%token PHALT +%token PTHREAD +%token ID +%token INT +%token STRING +%% + +program: vardecs statements {} +; + +vardecs: /* empty */ + |vardecs vardec {} +; + +statements: /* empty */ + |statements statement {} +; + +vardec: VAR ID ";" {} + |VAR ID "[" INT "]" ";" {} +; + +statement: ifstmnt {} + |whilestmnt {} + |delay {} + |wait {} + |signal {} + |press {} + |release {} + |halt {} + |thread {} + |assign {} + |empty {} + |incr {} + |decr {} + |error ";" {} +; + +empty: ";" +; + +ifstmnt: IF "(" expr ")" "{" statements "}" elsepart {} + | IF "(" expr ")" statement elsepart {} +; + +elsepart: ELSE "{" statements "}" {} + | ELSE statement {} + | /*empty*/ {} +; + +whilestmnt: WHILE "(" expr ")" "{" statements "}" {} + | WHILE "(" expr ")" statement {} +; + +delay: DELAY "(" expr ")" ";" {} +; + +wait: WAIT "(" expr ")" ";" {} +; + +signal: PSIGNAL "(" expr ")" ";" {} +; + +press: PRESS "(" expr ")" ";" {} +; + +release: RELEASE "(" expr ")" ";" {} +; + +incr: ID"++;" {} +; + +decr: ID"--;" {} +; + +halt: PHALT ";" {} +; + +thread: PTHREAD "{" statements "}" {} + | PTHREAD ID "{" statements "}" {} +; + +assign: lvalue "=" expr ";" {} + |lvalue "+" "=" expr ";" {} + |lvalue "-" "=" expr ";" {} + |lvalue "*" "=" expr ";" {} + |lvalue "/" "=" expr ";" {} +; + +lvalue: ID {} + |ID "[" expr "]" {} +; + +expr: logic {} +; + +arithterm: arithterm "+" term {} + |arithterm "-" term {} + |term {} +; + +term: term "*" factor {} + | term "/" factor {} + | term "%" factor {} + | factor {} +; + +factor: factor "&" "&" unit {} + | factor "|" "|" unit {} + | unit {} +; + +logic: logic "=" "=" arithterm {} + | logic "!" "=" arithterm {} + | logic "<" "=" arithterm {} + | logic ">" "=" arithterm {} + | logic "<" arithterm {} + | logic ">" arithterm {} + | arithterm {} +; + +unit: ID {} + | ID "[" expr "]" {} + | "j" "s" INT "." "A" "[" expr "]" {} + | "j" "s" INT "." "B" "[" expr "]" {} + | INT {} + | "-" unit {} + | "+" unit {} + | "!" unit {} + | "(" expr ")" {} +%% + +#include "lex.program.c" + diff --git a/parser.h b/parser.h new file mode 100644 index 0000000..8e8e6cc --- /dev/null +++ b/parser.h @@ -0,0 +1,103 @@ +#ifndef __parser +#define __parser + +#include +#include +#include "program.h" + +#define NONE -255 +#define SHIFT 256 +#define BUTTON 257 +#define AXIS 258 +#define CODE 259 +#define STRING 260 +#define ID 261 +#define VALUE 262 +#define IF 263 +#define ELSE 264 +#define WHILE 265 +#define DELAY 266 +#define WAIT 267 +#define VAR 268 +#define PRESS 269 +#define RELEASE 270 +#define PHALT 271 +#define PTHREAD 272 +#define NL 273 +#define INCLUDE 274 +#define PSIGNAL 275 +#define PEQ 276 +#define PNE 277 +#define PLE 278 +#define PGE 279 +#define PAND 280 +#define POR 281 +#define PLUSE 282 +#define MINUSE 283 +#define MULTE 284 +#define DIVE 285 +#define PINC 286 +#define PDEC 287 +#define SCRIPT 288 +#define JOYSTICK 289 +#define JOYSTICKS 290 +#define GLOBALVAR 291 +#define ERROR 10000 + +#define MAX_SYMBOLS 512 + +struct token { + int line; + int pos; + int type; + char value[256]; +}; + +struct reserved { + char *token; + int value; +}; + +struct scriptmap { + int id; + int vendor; + int product; + int device; +}; + +extern FILE *fmap; +extern FILE *pfile; +extern int line; +extern int cpos; +extern struct token (*tokenizer)(); +void report(char *message); +void reportline(int line, int cpos, char *message); +int peekchar(); +void eatchar(); +int readchar(); +struct token peektoken(); +void eattoken(); +struct token readtoken(); +void init_tokenizer(); +int numeric(char *s); +int parse_map(void); +int parse_program(char *program, struct program_code *code); + +extern struct program_code program; +#define MAX_ASSIGN 1024 +extern struct program_button_remap buttons[MAX_ASSIGN]; +extern struct program_axis_remap axes[MAX_ASSIGN]; +extern struct scriptmap scriptassign[MAX_ASSIGN]; +extern int nbuttons; +extern int naxes; +extern int nscript; +extern int njoysticks; + +struct joystick { + int axes; + int buttons; +}; + +extern struct joystick joysticks[8]; + +#endif diff --git a/program.h b/program.h new file mode 100644 index 0000000..2bfc546 --- /dev/null +++ b/program.h @@ -0,0 +1,161 @@ +#ifndef __program_h +#define __program_h + +#include + +#define DEVICE_JOYSTICK 128 +#define DEVICE_KBD 64 +#define DEVICE_MOUSE 32 +#define DEVICE_NONE 0 + +#define TYPE_AXIS 1 +#define TYPE_BUTTON 2 +#define TYPE_SHIFT 3 //the shift button, target for button remap + +#define FLAG_NONE 0 +#define FLAG_AUTO_RELEASE 1 +#define FLAG_INVERT 2 +#define FLAG_RELEASE 4 +#define FLAG_SHIFT 8 //only if shifted +#define FLAG_PRESS 16 //not used by driver +#define FLAG_BINARY 32 +#define FLAG_TRINARY 64 +#define KEYMASK 0xfff +#define RELEASEMASK 32768 +#define SEQUENCE_DONE 65535 + +#define PROGRAM_AXIS_REMAP 1 +#define PROGRAM_BUTTON_REMAP 2 +#define PROGRAM_AXIS_PROPERTIES 3 +#define PROGRAM_CODE 4 + +#define MAX_SEQUENCE 16 //approximately 8 keys max +#define JOYSTICK_USE_VENDOR 255 +#define JOYSTICK_CODE 255 +#define JOYSTICK_CODE_VENDOR 0 +#define JOYSTICK_CODE_PRODUCT 0 + +#define MAX_CODE_SIZE 16384 +// sigh ... we get out of memory on opening the event stream +// if the number of axes is too large (64) +#define MAX_AXES 16 +#define MAX_BUTTONS 32 + +//virtual machine instructions +#define HALT 0 +#define JZ 1 +#define JUMP 2 +#define JUMPREL 3 +#define ADD 4 +#define SUB 5 +#define MUL 6 +#define DIV 7 +#define LT 8 +#define LE 9 +#define GT 10 +#define GE 11 +#define EQ 12 +#define NEQ 13 +#define AND 14 +#define OR 15 +#define NOT 16 +#define PUSH 17 +#define POP 18 +#define DEC 19 +#define INC 20 +#define KEY 21 //press a key +#define SIGNAL 22 //send a signal to a daemon +#define THREAD 23 //context switch, gets two jump targets from the stack: + //1-where to return when thread stops + //2-where to start the thread +#define YIELD 24 //return from a thread (back to main thread) +#define NEG 25 +#define JOIN 26 //stop an executing thread +#define PUSHA 27 //for arrays +#define POPA 28 //for arrays +#define DECA 29 //for arrays +#define INCA 30 //for arrays + +//address types +#define GP 0 +#define CODEB 1 +#define CODEA 2 +#define JS 3 +#define CONST 4 +#define GLOBAL 5 + +//registers, first 10 are reserved or fixed function +#define RESERVED 10 +#define FIRSTSCAN 0 +#define CLOCKTICK 1 +#define XREL 2 +#define YREL 3 +#define ZREL 4 +#define TIMESTAMP 5 +#define CURRENTMODE 6 +//the offset register, always added to any address +//registers, joystick etc) but not constants or stack +//the ofsreg is reset to zero after every instruction +#define OFSREG 7 + +//use the joystick if specified, if joystick is 255 +// then use the vendor and product to identify the +// joystick. This device should already be present in the +// system. Otherwise the map is ignored. +// if vendor=255 and product=0, then this refers to the virtual CODE joystick +struct program_axis_remap { + __uint8_t program; //PROGRAM_AXIS_REMAP + __uint8_t joystick; + __uint16_t vendor; + __uint16_t product; + __uint8_t srcaxis; + __uint8_t device; //low bits of device identify joystick + __uint8_t type; + __uint8_t flags; + __uint16_t axis; + __uint16_t plus[MAX_SEQUENCE]; //if top bit set, release + __uint16_t minus[MAX_SEQUENCE]; //if top bit set, release + __int32_t saved_value; // for implementing binary decisions + __int32_t min; + __int32_t max; + __int32_t deadzone; + __int32_t speed; +}; + +struct program_axis_properties { + __uint8_t program; //PROGRAM_AXIS_PROPERTIES + __uint8_t joystick; + __uint16_t vendor; + __uint16_t product; + __uint8_t srcaxis; + __uint8_t flags; + __int16_t scale; //8.8 fixed point + __int16_t bias; //shift after scale + __uint16_t threshhold; +}; + +#define press sequence[0] +#define release sequence[0] +struct program_button_remap { + __uint8_t program; //PROGRAM_BUTTON_REMAP + __uint8_t joystick; + __uint16_t vendor; + __uint16_t product; + __uint16_t srcbutton; + __uint8_t device; //low bits of device identify joystick + __uint8_t type; + __uint8_t flags; + __uint16_t sequence[MAX_SEQUENCE]; //if top bit set, release + __int32_t speed; +}; + +struct program_code { + __uint8_t program; //PROGRAM_CODE + unsigned char code[MAX_CODE_SIZE]; //1024 4 byte codes +}; + +void code_set_program(struct program_code *code); +void remap_button(struct program_button_remap *btn); +void remap_axis(struct program_axis_remap *axis); +#endif + diff --git a/programparser.c b/programparser.c new file mode 100644 index 0000000..2634466 --- /dev/null +++ b/programparser.c @@ -0,0 +1,1280 @@ +#include +#include +#include +#include +#include "program.h" +#include "parser.h" +#include "validkeys.h" + +static int program_parse_err=0; + +static struct reserved reserved[]={ + {"if", IF}, + {"else", ELSE}, + {"while", WHILE}, + {"delay", DELAY}, + {"wait", WAIT}, + {"signal", PSIGNAL}, + {"var", VAR}, + {"global", GLOBALVAR}, + {"press", PRESS}, + {"release", RELEASE}, + {"halt", PHALT}, + {"thread", PTHREAD}, + {NULL, 0} +}; + +//register assignments +static int assigned=RESERVED; + +struct symbol { + char name[256]; + int r; //assigned register + int num; //number of entries (for an array) + int type; //GP/CODEB/CODEA or JS +}; + +#define JS_FLAG (JS<<5) +#define GP_FLAG (GP<<5) +#define GLOBAL_FLAG (GLOBAL<<5) +#define CONST_FLAG (CONST<<5) +#define CODEA_FLAG (CODEA<<5) +#define CODEB_FLAG (CODEB<<5) +struct symbol symbol_table[MAX_SYMBOLS]={ + {"js0.a", 0x0000+0x1000, 64, JS_FLAG}, + {"js0.b", 0x0000, 128, JS_FLAG}, + {"js1.a", 0x0100+0x1000, 64, JS_FLAG}, + {"js1.b", 0x0100, 128, JS_FLAG}, + {"js2.a", 0x0200+0x1000, 64, JS_FLAG}, + {"js2.b", 0x0200, 128, JS_FLAG}, + {"js3.a", 0x0300+0x1000, 64, JS_FLAG}, + {"js3.b", 0x0300, 128, JS_FLAG}, + {"js4.a", 0x0400+0x1000, 64, JS_FLAG}, + {"js4.b", 0x0400, 128, JS_FLAG}, + {"js5.a", 0x0500+0x1000, 64, JS_FLAG}, + {"js5.b", 0x0500, 128, JS_FLAG}, + {"js6.a", 0x0600+0x1000, 64, JS_FLAG}, + {"js6.b", 0x0600, 128, JS_FLAG}, + {"js7.a", 0x0700+0x1000, 64, JS_FLAG}, + {"js7.b", 0x0700, 128, JS_FLAG}, + {"js8.a", 0x0800+0x1000, 64, JS_FLAG}, + {"js8.b", 0x0800, 128, JS_FLAG}, + {"js9.a", 0x0900+0x1000, 64, JS_FLAG}, + {"js9.b", 0x0900, 128, JS_FLAG}, + {"js10.a", 0x0A00+0x1000, 64, JS_FLAG}, + {"js10.b", 0x0A00, 128, JS_FLAG}, + {"js11.a", 0x0B00+0x1000, 64, JS_FLAG}, + {"js11.b", 0x0B00, 128, JS_FLAG}, + {"js12.a", 0x0C00+0x1000, 64, JS_FLAG}, + {"js12.b", 0x0C00, 128, JS_FLAG}, + {"js13.a", 0x0D00+0x1000, 64, JS_FLAG}, + {"js13.b", 0x0D00, 128, JS_FLAG}, + {"js14.a", 0x0E00+0x1000, 64, JS_FLAG}, + {"js14.b", 0x0E00, 128, JS_FLAG}, + {"js15.a", 0x0F00+0x1000, 64, JS_FLAG}, + {"js15.b", 0x0F00, 128, JS_FLAG}, + {"a", 0, MAX_AXES, CODEA_FLAG}, + {"b", 0, MAX_BUTTONS, CODEB_FLAG}, + {"firstscan", 0, 1, GP_FLAG}, + {"clocktick", 1, 1, GP_FLAG}, + {"xrel", 2, 1, GP_FLAG}, + {"yrel", 3, 1, GP_FLAG}, + {"zrel", 4, 1, GP_FLAG}, + {"timestamp", 5, 1, GP_FLAG}, + {"currentmode", 6, 1, GP_FLAG}, +}; + +static char threadnames[8][256]; + +static void name_thread(int thread, char *name) { + if (thread>=8) { + report("Too many threads"); + return; + } + strcpy(threadnames[thread], name); +} + +static int get_thread(char *name) { + int i; + char msg[256]; + for (i=0; i<8; i++) { + if (strcmp(name, threadnames[i])==0) return i; + } + sprintf(msg, "Unknown thread %s", name); + report(msg); + return 0; +} + +static int query_thread(char *name) { + int i; + for (i=0; i<8; i++) { + if (strcmp(name, threadnames[i])==0) return i; + } + return 0; +} + +static int nsymbols=41; + +static char *instructions[]={ + "HALT", + "JZ", + "JUMP", + "JUMPREL", + "ADD", + "SUB", + "MUL", + "DIV", + "LT", + "LE", + "GT", + "GE", + "EQ", + "NEQ", + "AND", + "OR", + "NOT", + "PUSH", + "POP", + "DEC", + "INC", + "KEY", + "SIGNAL", + "THREAD", + "YIELD", + "NEG", + "JOIN", + "PUSHA", + "POPA", + "DECA", + "INCA" +}; + +//for each instruction, does it have an operand +static char has_operand[]={ + 0, /* HALT */ + 1, /* JZ */ + 1, /* JUMP */ + 1, /* JUMPREL */ + 0, /* ADD */ + 0, /* SUB */ + 0, /* MUL */ + 0, /* DIV */ + 0, /* LT */ + 0, /* LE */ + 0, /* GT */ + 0, /* GE */ + 0, /* EQ */ + 0, /* NEQ */ + 0, /* AND */ + 0, /* OR */ + 0, /* NOT */ + 1, /* PUSH */ + 1, /* POP */ + 1, /* DEC */ + 1, /* INC */ + 0, /* KEY */ + 0, /* SIGNAL */ + 1, /* THREAD handles its own arguments */ + 0, /* YIELD */ + 0, /* NEG */ + 1, /* JOIN */ + 1, /* PUSHA */ + 1, /* POPA */ + 1, /* DECA */ + 1, /* INCA */ +}; + +static unsigned char *codeseg; +static int ip=0; +static void emit1(int instr) { + codeseg[ip]=instr; + ip++; +} + +static void emitc(int instr, int c) { + codeseg[ip]=instr|CONST_FLAG; + ip++; + *((unsigned short *)(codeseg+ip))=c; + ip+=2; +} + +static void emitaddress(int c) { + *((unsigned short *)(codeseg+ip))=c; + ip+=2; +} + +static void emitt(int instr, int c) { + codeseg[ip]=instr|GP_FLAG; + ip++; + *((unsigned short *)(codeseg+ip))=c; + ip+=2; +} + +static void emit2(int instr, int symbol, int value) { + if (value!=0) { + emitt(POP, OFSREG); + } + codeseg[ip]=instr|(symbol_table[symbol].type); + ip++; + *((unsigned short *)(codeseg+ip))=symbol_table[symbol].r; + ip+=2; +} + +void printcode() { + int lip=0; + int task; + int type; + int address; + int js; + int ret; + while (lip> 5; + task=task&31; + if ((task>=0)&&(task<=INCA)) { + if (has_operand[task]) { + address=*((unsigned short *)(codeseg+lip)); + lip+=2; + } else type=-1; + } else type=-1; + ret=-1; + if (task==THREAD) { + ret=*((unsigned short *)(codeseg+lip)); + lip+=2; + } + printf("%s ", instructions[task]); + if (type==GP) { + printf("r%d", address); + } + if (type==CONST) { + printf("%d", address); + } + if (type==CODEA) { + printf("A%d", address); + } + if (type==CODEB) { + printf("B%d", address); + } + if (type==JS) { + js=(address&0x0F00)>>8; + type=address&0xF000; + address=address&0x00FF; + if (type) + printf("js%d.A%d", js, address); + else + printf("js%d.B%d", js, address); + } + if (ret>0) printf(" ret=%d", ret); + printf("\n"); + } +} + +static void expect(int type, char *token) { + struct token t; + char msg[256]; + + t=readtoken(); + if (t.type!=type) { + sprintf(msg, "%s expected before %s", token, t.value); + reportline(t.line, t.pos, msg); + } +} + +static int symbol_exists(char *sym) { + int i; + for (i=0; i=MAX_SYMBOLS) { + reportline(symbol.line, symbol.pos, "Symbol table exhausted"); + return; + } + for (i=0; i=256) { + reportline(symbol.line, symbol.pos, "Registers exhausted"); + } + symbol_table[nsymbols].num=num; + if (global) { + symbol_table[nsymbols].type=GLOBAL_FLAG; + printf("Adding global %s to symbols with register r%d\n", symbol.value, symbol_table[nsymbols].r); + } else { + symbol_table[nsymbols].type=GP_FLAG; + printf("Adding %s to symbols with register r%d\n", symbol.value, symbol_table[nsymbols].r); + } + nsymbols++; +} + +static void parse_comment() { + //first character was # + //read until newline + int nc; + nc=readchar(); + while ((nc!='\n')&&(nc!=EOF)) + nc=readchar(); +} + +static void skipwhite() { + int nc=peekchar(); + while (isspace(nc)) { + eatchar(); + nc=peekchar(); + } +} + +static struct token parse_string() { + //opening quote has been read + //read until closing quote + struct token t; + int pos=0; + int nc; + t.line=line; + t.pos=cpos; + t.type=STRING; + + nc=readchar(); + nc=readchar(); + while (nc!='"') { + if (nc==EOF) { + report("Unexpected end of file while parsing a string literal"); + break; + } + t.value[pos++]=nc; + nc=readchar(); + } + t.value[pos]='\0'; + return t; +} + +static struct token parse_id() { + struct token t; + int pos=0; + int nc; + t.type=ID; + t.line=line; + t.pos=cpos; + nc=peekchar(); + while (isalnum(nc)||(nc=='_')||(nc=='.')) { + t.value[pos++]=nc; + eatchar(); + nc=peekchar(); + } + t.value[pos]='\0'; + + nc=0; + while (reserved[nc].token!=NULL) { + if (strcmp(reserved[nc].token, t.value)==0) { + t.type=reserved[nc].value; + break; + } + nc++; + } + return t; +} + +static struct token chartype(int nc) { + struct token t; + + t.type=nc; + t.line=line; + t.pos=cpos; + t.value[0]=nc; t.value[1]='\0'; + eatchar(); + return t; +} + +static struct token eqchartype(int nc) { + struct token t; + int pc; + + t.type=nc; + t.line=line; + t.pos=cpos; + t.value[0]=nc; t.value[1]='\0'; + eatchar(); + + pc=peekchar(); + if (pc=='=') { + t.value[1]='='; + t.value[2]='\0'; + eatchar(); + switch(nc) { + case '<': t.type=PLE; break; + case '>': t.type=PGE; break; + case '+': t.type=PLUSE; break; + case '-': t.type=MINUSE; break; + case '*': t.type=MULTE; break; + case '/': t.type=DIVE; break; + case '=': t.type=PEQ; break; + case '!': t.type=PNE; break; + } + } + if ((pc=='+')&&(nc=='+')) { + t.value[1]='+'; + t.value[2]='\0'; + t.type=PINC; + eatchar(); + } + if ((pc=='-')&&(nc=='-')) { + t.value[1]='-'; + t.value[2]='\0'; + t.type=PDEC; + eatchar(); + } + return t; +} + +static struct token logchartype(int nc) { + struct token t; + int pc; + + t.type=nc; + t.line=line; + t.pos=cpos; + t.value[0]=nc; t.value[1]='\0'; + eatchar(); + + pc=peekchar(); + if ((pc=='&')&&(nc=='&')) { + t.value[1]='+'; + t.value[2]='\0'; + t.type=PAND; + eatchar(); + } + if ((pc=='|')&&(nc=='|')) { + t.value[1]='-'; + t.value[2]='\0'; + t.type=PAND; + eatchar(); + } + return t; +} +static struct token parse_num() { + struct token t; + int pos=0; + int nc; + t.type=VALUE; + t.line=line; + t.pos=cpos; + nc=peekchar(); + while (isdigit(nc)||((pos==1)&&(nc=='x'))) { + t.value[pos++]=nc; + eatchar(); + nc=peekchar(); + } + t.value[pos]='\0'; + return t; +} + +static struct token eof() { + struct token t; + t.line=line; + t.pos=cpos; + t.type=EOF; + strcpy(t.value, "end of file"); + return t; +} + +static struct token programtoken() { + int skip=1; + int nc; + struct token t; + char message[256]; + while (skip) { + nc=peekchar(); + if (nc=='#') + parse_comment(); + else if (isspace(nc)) + skipwhite(); + else skip=0; + } + if (nc==EOF) return eof(); + if (nc=='"') return parse_string(); + if (nc=='_') return parse_id(); + if (isalpha(nc)) return parse_id(); + if (isdigit(nc)) return parse_num(); + if (nc=='{') return chartype(nc); + if (nc=='}') return chartype(nc); + if (nc=='[') return chartype(nc); + if (nc==']') return chartype(nc); + if (nc=='(') return chartype(nc); + if (nc==')') return chartype(nc); + if (nc=='%') return chartype(nc); + if (nc==';') return chartype(nc); + if (nc==',') return chartype(nc); + if (nc=='!') return eqchartype(nc); + if (nc=='=') return eqchartype(nc); + if (nc=='+') return eqchartype(nc); + if (nc=='-') return eqchartype(nc); + if (nc=='*') return eqchartype(nc); + if (nc=='/') return eqchartype(nc); + if (nc=='<') return eqchartype(nc); + if (nc=='>') return eqchartype(nc); + if (nc=='&') return logchartype(nc); + if (nc=='|') return logchartype(nc); + sprintf(message, "Unexpected character \"%c\".", nc); + eatchar(); + report(message); + t.type=ERROR; + t.line=line; + t.pos=cpos; + t.value[0]=nc; t.value[1]='\0'; + return t; +} + +static void seek_endstmt() { + struct token t; + t=peektoken(); + while ((t.type!=';')&&(t.type!='}')&&(t.type!=EOF)) { + eattoken(); + t=peektoken(); + } + eattoken(); +} + +static void seek_nextvar() { + struct token t; + t=peektoken(); + while ((t.type!=';')&&(t.type!=',')&&(t.type!=EOF)) { + eattoken(); + t=peektoken(); + } +} + +static int threaddepth=0; +static int threadnum=0; + +static void parse_expression(); +static void parse_statement(); +static void parse_statements(int block); +static void parse_unit() { + struct token t; + struct token id; + int sym; + char msg[256]; + t=readtoken(); + switch (t.type) { + case ID: + id=t; + sym=symbol_exists(id.value); + if (sym<0) { + sprintf(msg, "Unknown identifier %s", id.value); + reportline(id.line, id.pos, msg); + } + t=peektoken(); + if (t.type=='[') { + eattoken(); + parse_expression(); + expect(']', "]"); + emit2(PUSHA, sym, 1); + } else + emit2(PUSH, sym, 0); + break; + case VALUE: + emitc(PUSH, numeric(t.value)); + break; + case '-': + parse_unit(); + emit1(NEG); + break; + case '+': + parse_unit(); + break; + case '!': + parse_unit(); + emit1(NOT); + break; + case '(': + parse_expression(); + expect(')', ")"); + break; + } +} + +static int is_bin_operator(int type) { + if (type==PAND) return 1; + if (type==POR) return 1; + return 0; +} + +static void parse_factor() { + struct token t; + parse_unit(); + + t=peektoken(); + if (is_bin_operator(t.type)) { + eattoken(); + parse_factor(); + switch (t.type) { + case PAND: + emit1(AND); + break; + case POR: + emit1(OR); + break; + default: + break; + } + } +} + +static int is_mdm_operator(int type) { + if (type=='*') return 1; + if (type=='/') return 1; + if (type=='%') return 1; + return 0; +} + +static void parse_term() { + struct token t; + int x=8; + int y=9; + parse_factor(); + + t=peektoken(); + while (is_mdm_operator(t.type)) { + eattoken(); + parse_factor(); + switch (t.type) { + case '*': + emit1(MUL); + break; + case '/': + emit1(DIV); + break; + case '%': + emitt(POP,y); + emitt(POP,x); + emitt(PUSH,x); + emitt(PUSH,y); + emitt(PUSH,x); + emitt(PUSH,y); + emit1(DIV); + emit1(MUL); + emit1(SUB); + break; + default: + break; + } + t=peektoken(); + } +} + +static int is_pm_operator(int type) { + if (type=='+') return 1; + if (type=='-') return 1; + return 0; +} + +static void parse_arithterm() { + struct token t; + parse_term(); + + t=peektoken(); + while (is_pm_operator(t.type)) { + eattoken(); + parse_term(); + switch (t.type) { + case '+': + emit1(ADD); + break; + case '-': + emit1(SUB); + break; + default: + break; + } + t=peektoken(); + } +} + +static int is_logic_operator(int type) { + if (type=='<') return 1; + if (type=='>') return 1; + if (type==PEQ) return 1; + if (type==PNE) return 1; + if (type==PLE) return 1; + if (type==PGE) return 1; + return 0; +} + +static void parse_logic() { + struct token t; + parse_arithterm(); + + t=peektoken(); + if (is_logic_operator(t.type)) { + eattoken(); + parse_logic(); + switch (t.type) { + case '<': emit1(LT); + break; + case '>': emit1(GT); + break; + case PEQ: emit1(EQ); + break; + case PNE: emit1(NEQ); + break; + case PGE: emit1(GE); + break; + case PLE: emit1(LE); + break; + default: + break; + } + } +} + +static void parse_expression() { + parse_logic(); +} + +static void parse_condition() { + expect('(', "("); + parse_expression(); + expect(')', ")"); +} + +static void parse_var(int global) { + struct token t; + struct token id; + int size; + + eattoken(); + id=peektoken(); + if (id.type==ID) { + eattoken(); + t=peektoken(); + if (t.type=='[') { + eattoken(); + t=peektoken(); + if (t.type!=VALUE) { + reportline(t.line, t.pos, "Only constant array dimensions are allowed"); + size=1; + } else size=numeric(t.value); + if (t.type!=']') { + eattoken(); + t=peektoken(); + } + if (t.type!=']') { + reportline(t.line, t.pos, "] expected"); + seek_nextvar(); + } else eattoken(); + add_symbol(id, size, global); + t=peektoken(); + if (t.type==',') parse_var(global); + else if (t.type==';') { + eattoken(); + return; + } else { + reportline(t.line, t.pos, "Expected ;"); + seek_endstmt(); + return; + } + } else { + add_symbol(id, 1, global); + t=peektoken(); + if (t.type==',') parse_var(global); + else if (t.type==';') { + eattoken(); + return; + } else { + reportline(t.line, t.pos, "Expected ;"); + seek_endstmt(); + return; + } + } + } else { + reportline(id.line, id.pos, "Expected an identifier"); + seek_endstmt(); + } +} + +static void parse_declarations() { + struct token t; + + t=peektoken(); + while ((t.type==VAR) || (t.type==GLOBALVAR)) { + if (t.type == VAR) + parse_var(0); + else + parse_var(1); + t=peektoken(); + } +} + +static void parse_if() { + struct token t; + int start, f=0, fin=0, before; + + parse_condition(); + start=ip; emitc(JZ, f); + t=peektoken(); + if (t.type=='{') { + eattoken(); + parse_statements(1); + expect('}', "}"); + } else { + parse_statement(); + } + t=peektoken(); + if (t.type==ELSE) { + before=ip; + emitc(JUMP, fin); + f=ip; + eattoken(); + t=peektoken(); + if (t.type=='{') { + eattoken(); + parse_statements(1); + t=peektoken(); + expect('}', "}"); + } else { + parse_statement(); + } + fin=ip; + ip=start; + emitc(JZ, f); + ip=before; + emitc(JUMP, fin); + ip=fin; + } else { + fin=ip; + ip=start; + emitc(JZ, fin); + ip=fin; + } +} + +static void parse_while() { + struct token t; + int start, fin=0, cond; + start=ip; + parse_condition(); + cond=ip; + emitc(JZ, fin); + t=peektoken(); + if (t.type=='{') { + eattoken(); + parse_statements(1); + t=peektoken(); + expect('}', "}"); + } else { + parse_statement(); + } + emitc(JUMP, start); + fin=ip; + ip=cond; + emitc(JZ, fin); + ip=fin; +} + +static void parse_delay() { + struct token t; + char msg[256]; + int before=0, after=0, jump=0, reg=0; + t=readtoken(); + if (t.type!='(') { + sprintf(msg, "( expected before %s", t.value); + reportline(t.line, t.pos, msg); + seek_endstmt(); + return; + } + if (threaddepth==0) { + reportline(t.line, t.pos, "delay only valid in thread\n"); + parse_expression(); + } else { + reg=assigned++; + emitt(PUSH,TIMESTAMP); + emitt(POP,reg); + before=ip; + emitt(PUSH,TIMESTAMP); + emitt(PUSH,reg); + emit1(SUB); + parse_expression(); + emit1(LT); + jump=ip; + emitc(JZ,after); + emit1(YIELD); + emitc(JZ,before); + after=ip; + ip=jump; + emitc(JZ,after); + ip=after; + } + t=readtoken(); + if (t.type!=')') { + sprintf(msg, "( expected before %s", t.value); + reportline(t.line, t.pos, msg); + seek_endstmt(); + return; + } + expect(';', ";"); +} + +static void parse_signal() { + struct token t; + char msg[256]; + t=readtoken(); + if (t.type!='(') { + sprintf(msg, "( expected before %s", t.value); + reportline(t.line, t.pos, msg); + seek_endstmt(); + return; + } + parse_expression(); + emit1(SIGNAL); + t=readtoken(); + if (t.type!=')') { + sprintf(msg, "( expected before %s", t.value); + reportline(t.line, t.pos, msg); + seek_endstmt(); + return; + } + expect(';', ";"); +} + +static void parse_wait() { + struct token t; + char msg[256]; + int before=0, after=0, test=0; + t=readtoken(); + if (t.type!='(') { + sprintf(msg, "( expected before %s", t.value); + reportline(t.line, t.pos, msg); + seek_endstmt(); + return; + } + test=ip; + parse_expression(); + if (threaddepth==0) { + reportline(t.line, t.pos, "wait only valid in thread\n"); + } else { + emit1(NOT); + before=ip; + emitc(JZ,after); + emit1(YIELD); + emitc(JUMP,test); + after=ip; + ip=before; + emitc(JZ,after); + ip=after; + } + t=readtoken(); + if (t.type!=')') { + sprintf(msg, "( expected before %s", t.value); + reportline(t.line, t.pos, msg); + seek_endstmt(); + return; + } + expect(';', ";"); +} + +static void parse_press() { + struct token t; + char msg[256]; + int key, valid; + t=readtoken(); + if (t.type!='(') { + sprintf(msg, "( expected before %s", t.value); + reportline(t.line, t.pos, msg); + seek_endstmt(); + return; + } + t=readtoken(); + if (t.type!=STRING) { + reportline(t.line, t.pos, "Only strings are accepted as argument for press"); + seek_endstmt(); + return; + } + key=0; + valid=0; + while (keymap[key].value!=-1) { + if (strcmp(keymap[key].key, t.value)==0) { + valid=1; + break; + } + key++; + } + if (!valid) { + sprintf(msg, "Unknown key %s", t.value); + reportline(t.line, t.pos, msg); + } + emitc(PUSH,keymap[key].value); + emitc(PUSH,1); + emit1(KEY); + t=readtoken(); + if (t.type!=')') { + sprintf(msg, "( expected before %s", t.value); + reportline(t.line, t.pos, msg); + seek_endstmt(); + return; + } + expect(';', ";"); +} + +static void parse_release() { + struct token t; + char msg[256]; + t=readtoken(); + int key, valid; + if (t.type!='(') { + sprintf(msg, "( expected before %s", t.value); + reportline(t.line, t.pos, msg); + seek_endstmt(); + return; + } + t=readtoken(); + if (t.type!=STRING) { + reportline(t.line, t.pos, "Only strings are accepted as argument for release"); + seek_endstmt(); + return; + } + key=0; + valid=0; + while (keymap[key].value!=-1) { + if (strcmp(keymap[key].key, t.value)==0) { + valid=1; + break; + } + key++; + } + if (!valid) { + sprintf(msg, "Unknown key %s", t.value); + reportline(t.line, t.pos, msg); + } + emitc(PUSH,keymap[key].value); + emitc(PUSH,0); + emit1(KEY); + t=readtoken(); + if (t.type!=')') { + sprintf(msg, "( expected before %s", t.value); + reportline(t.line, t.pos, msg); + seek_endstmt(); + return; + } + expect(';', ";"); +} + +static void parse_halt() { + struct token name; + name=peektoken(); + if (name.type==ID) { + eattoken(); + get_thread(name.value); + emitc(JOIN,threadnum); + } else { + emit1(HALT); + } + expect(';', ";"); +} + +void parse_thread() { + struct token name; + int thread; + int end; + int this; + if (threaddepth>0) + report("Cannot create a thread within a thread."); + threaddepth++; + threadnum++; + this=threadnum; + name=peektoken(); + if (name.type==ID) { + eattoken(); + this=query_thread(name.value); + if (this==0) { + this=threadnum; + name_thread(this, name.value); + } + } + expect('{', "{"); + thread=ip; + emitc(THREAD,this); ip+=2; + parse_statements(1); + emit1(HALT); + end=ip; + ip=thread; + emitc(THREAD, this); + emitaddress(end); + ip=end; + expect('}', "}"); + threaddepth--; +} + +static void parse_assign(struct token id) { + struct token t; + char msg[256]; + int sym=symbol_exists(id.value); + int ofs=0; + if (sym<0) { + sprintf(msg, "Unknown identifier %s", id.value); + reportline(id.line, id.pos, msg); + } + t=readtoken(); + if (t.type=='[') { + parse_expression(); + expect(']', "]"); + t=readtoken(); + ofs=1; + } + + if (t.type==PINC) { + if (ofs) emit2(INCA,sym,ofs); + else emit2(INC,sym,ofs); + } else if (t.type==PDEC) { + if (ofs) emit2(DECA,sym,ofs); + else emit2(DEC,sym,ofs); + } else if (t.type==PLUSE) { + //save ofs into register 8 + if (ofs) {emitt(POP, 8); emitt(PUSH, 8);} + emit2(PUSH,sym,ofs); + parse_expression(); + emit1(ADD); + //restore from register 8 + if (ofs) emitt(PUSH,8); + if (ofs) emit2(POPA,sym,ofs); + else emit2(POP,sym,ofs); + } else if (t.type==MINUSE) { + //save ofs into register 8 + if (ofs) {emitt(POP,8); emitt(PUSH, 8);} + emit2(PUSH,sym,ofs); + parse_expression(); + emit1(SUB); + //restore from register 8 + if (ofs) emitt(PUSH,8); + if (ofs) emit2(POPA,sym,ofs); + else emit2(POP,sym,ofs); + } else if (t.type==MULTE) { + //save ofs into register 8 + if (ofs) {emitt(POP,8); emitt(PUSH,8);} + emit2(PUSH,sym,ofs); + parse_expression(); + emit1(MUL); + //restore from register 8 + if (ofs) emitt(PUSH,8); + if (ofs) emit2(POPA,sym,ofs); + else emit2(POP,sym,ofs); + } else if (t.type==DIVE) { + //save ofs into register 8 + if (ofs) {emitt(POP,8); emitt(PUSH,8);} + emit2(PUSH,sym,ofs); + parse_expression(); + emit1(DIV); + //restore from register 8 + if (ofs) emitt(PUSH,8); + if (ofs) emit2(POPA,sym,ofs); + else emit2(POP,sym,ofs); + } else if (t.type=='=') { + //save ofs into register 8 + if (ofs) emitt(POP, 8); + parse_expression(); + //restore from register 8 + if (ofs) emitt(PUSH, 8); + if (ofs) emit2(POPA,sym,ofs); + else emit2(POP,sym,ofs); + } else { + reportline(t.line, t.pos, "Expected \"=\", \"++\" or \"--\""); + seek_endstmt(); + return; + } + expect(';', ";"); +} + +static void parse_statement() { + struct token t; + char msg[256]; + + t=readtoken(); + if (t.type==';') { + //do nothing + } else if (t.type==IF) { + parse_if(); + } else if (t.type==WHILE) { + parse_while(); + } else if (t.type==DELAY) { + parse_delay(); + } else if (t.type==WAIT) { + parse_wait(); + } else if (t.type==VAR) { + reportline(t.line, t.pos, "Variable declarations must come before statements"); + seek_endstmt(); + } else if (t.type==PRESS) { + parse_press(); + } else if (t.type==RELEASE) { + parse_release(); + } else if (t.type==SIGNAL) { + parse_signal(); + } else if (t.type==PHALT) { + parse_halt(); + } else if (t.type==PTHREAD) { + parse_thread(); + } else if (t.type==ID) { + parse_assign(t); + } else { + sprintf(msg, "Unexpected token %s", t.value); + reportline(t.line, t.pos, msg); + seek_endstmt(); + } +} + +static void parse_statements(int block) { + struct token t; + + t=peektoken(); + if (block) { + while (t.type!='}') { + parse_statement(); + t=peektoken(); + if (t.type == EOF) { + expect('}', "}"); + break; + } + } + } else { + while (t.type!=EOF) { + parse_statement(); + t=peektoken(); + } + } +} + +static void parse_script() { + parse_declarations(); + parse_statements(0); + emit1(HALT); +} + +int parse_program(char *program, struct program_code *code) { + FILE *cfile=fopen(program, "r"); + tokenizer=programtoken; + if (cfile==NULL) { + printf("File not found %s.\n", program); + return 1; + } else { + pfile=cfile; + codeseg=code->code; + init_tokenizer(); + parse_script(); + fclose(pfile); + code->program=PROGRAM_CODE; + //printcode(); + } + return program_parse_err; +} diff --git a/reserve_js.c b/reserve_js.c new file mode 100644 index 0000000..54580d2 --- /dev/null +++ b/reserve_js.c @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include +#include +#include +#include "config.h" + +#define NUM_JOYSTICKS 10 +#define WAIT_TIME 10 + +#define check_ioctl(x, y, z)\ + r = ioctl(x, y, z);\ + if (r < 0) {\ + fprintf(stderr, "Failed to perform ioctl\n");\ + perror("Error");\ + exit(1);\ + } + +#define check_ioctl2(x, y)\ + r = ioctl(x, y);\ + if (r < 0) {\ + fprintf(stderr, "Failed to perform ioctl\n");\ + perror("Error");\ + exit(1);\ + } + +void register_devices() { + int i, j, r; + struct uinput_user_dev dev; + int fd[NUM_JOYSTICKS]; + for (i=0; i +#include +#include +#include +#include +#include + +void print_data(int aval[], int bval[], int axes, int btns) { + int i; + for (i=0; i=2) + fd=open(argv[1], O_RDONLY|O_NONBLOCK); + else + fd=open("/dev/input/js0", O_RDONLY|O_NONBLOCK); + if (fd<0) { + perror("Failed to open device"); + return 1; + } + ioctl(fd, JSIOCGAXES, &axes); + ioctl(fd, JSIOCGBUTTONS, &btns); + for (i=0; i<64; i++) { + cor[i].type=JS_CORR_NONE; + cor[i].coef[0]=128; + cor[i].coef[1]=128; + cor[i].coef[2]=65536*64; + cor[i].coef[3]=65536*64; + } + ioctl(fd, JSIOCSCORR, cor); + + printf("Leave all axes centred, press a button when ready\n"); + print_data_hdr(aval, bval, axes, btns); + + for (i=0; idmax[ev.number]) + dmax[ev.number]=aval[ev.number]; + } + } + print_data(aval, bval, axes, btns); + usleep(1000); + count++; + } + + + for (i=0; imax[ev.number]) + max[ev.number]=aval[ev.number]; + } + } + print_data(aval, bval, axes, btns); + nobutton=0; + for (i=0; i +#include +#include +#include +#include +#include + +void print_data(int aval[], int bval[], int axes, int btns) { + int i; + for (i=0; i=2) + fd=open(argv[1], O_RDONLY|O_NONBLOCK); + else + fd=open("/dev/input/js0", O_RDONLY|O_NONBLOCK); + if (fd<0) { + perror("Failed to open device"); + return 1; + } + ioctl(fd, JSIOCGAXES, &axes); + ioctl(fd, JSIOCGBUTTONS, &btns); + for (i=0; i<64; i++) { + cor[i].type=JS_CORR_NONE; + cor[i].coef[0]=128; + cor[i].coef[1]=128; + cor[i].coef[2]=65536*64; + cor[i].coef[3]=65536*64; + } + ioctl(fd, JSIOCSCORR, cor); + + printf("Leave all axes centred, press a button when ready\n"); + print_data_hdr(aval, bval, axes, btns); + + for (i=0; idmax[ev.number]) + dmax[ev.number]=aval[ev.number]; + } + } + print_data(aval, bval, axes, btns); + usleep(1000); + count++; + } + + + for (i=0; imax[ev.number]) + max[ev.number]=aval[ev.number]; + } + } + print_data(aval, bval, axes, btns); + nobutton=0; + for (i=0; i +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) { + int fd,i; + unsigned char axes,btns; + struct js_corr cor[64]; + if (argc>=2) + fd=open(argv[1], O_RDONLY|O_NONBLOCK); + else + fd=open("/dev/input/js0", O_RDONLY|O_NONBLOCK); + if (fd<0) { + perror("Failed to open device"); + return 1; + } + ioctl(fd, JSIOCGAXES, &axes); + ioctl(fd, JSIOCGBUTTONS, &btns); + ioctl(fd, JSIOCGCORR, cor); + for (i=0; i +#include +#include +#include +#include +#include + +void print_data(int aval[], int bval[], int axes, int btns) { + int i; + for (i=0; i=2) + fd=open(argv[1], O_RDONLY|O_NONBLOCK); + else + fd=open("/dev/input/js0", O_RDONLY|O_NONBLOCK); + if (fd<0) { + perror("Failed to open device"); + return 1; + } + ioctl(fd, JSIOCGAXES, &axes); + ioctl(fd, JSIOCGBUTTONS, &btns); + + print_data_hdr(aval, bval, axes, btns); + + for (i=0; i<100; i++) { + if (read(fd, &ev, sizeof(ev))==sizeof(ev)) { + if (ev.type&JS_EVENT_BUTTON) + bval[ev.number]=ev.value; + if (ev.type&JS_EVENT_AXIS) + aval[ev.number]=ev.value; + } + } + while (1) { + if (read(fd, &ev, sizeof(ev))==sizeof(ev)) { + if (ev.type&JS_EVENT_BUTTON) + bval[ev.number]=ev.value; + if (ev.type&JS_EVENT_AXIS) + aval[ev.number]=ev.value; + } + print_data(aval, bval, axes, btns); + } + printf("\n"); + return 0; +} diff --git a/tools/jsset16.c b/tools/jsset16.c new file mode 100644 index 0000000..4ba11f6 --- /dev/null +++ b/tools/jsset16.c @@ -0,0 +1,116 @@ +#include +#include +#include +#include +#include +#include + +void print_data(int aval[], int bval[], int axes, int btns) { + int i; + for (i=0; i=2) + fd=open(argv[1], O_RDONLY|O_NONBLOCK); + else + fd=open("/dev/input/js0", O_RDONLY|O_NONBLOCK); + if (fd<0) { + perror("Failed to open device"); + return 1; + } + ioctl(fd, JSIOCGAXES, &axes); + ioctl(fd, JSIOCGBUTTONS, &btns); + for (i=0; i<64; i++) { + cor[i].type=JS_CORR_NONE; + cor[i].coef[0]=128; + cor[i].coef[1]=128; + cor[i].coef[2]=65536*64; + cor[i].coef[3]=65536*64; + min[i]=max[i]=0; + } + ioctl(fd, JSIOCSCORR, cor); + while (read(fd, &ev, sizeof(ev))==sizeof(ev)) { + if (ev.type&JS_EVENT_BUTTON) + bval[ev.number]=ev.value; + if (ev.type&JS_EVENT_AXIS) { + aval[ev.number]=ev.value; + if (ev.valuemax[ev.number]) + max[ev.number]=ev.value; + } + } + usleep(100000); + count=0; + while (count++<100) { + while (read(fd, &ev, sizeof(ev))==sizeof(ev)) { + if (ev.type&JS_EVENT_BUTTON) + bval[ev.number]=ev.value; + if (ev.type&JS_EVENT_AXIS) { + aval[ev.number]=ev.value; + if (ev.valuemax[ev.number]) + max[ev.number]=ev.value; + } + } + + usleep(10000); + } + + for (i=0; i +#include +#include +#include +#include +#include + +void print_data(int aval[], int bval[], int axes, int btns) { + int i; + for (i=0; i=2) + fd=open(argv[1], O_RDONLY|O_NONBLOCK); + else + fd=open("/dev/input/js0", O_RDONLY|O_NONBLOCK); + if (fd<0) { + perror("Failed to open device"); + return 1; + } + ioctl(fd, JSIOCGAXES, &axes); + ioctl(fd, JSIOCGBUTTONS, &btns); + for (i=0; i<64; i++) { + cor[i].type=JS_CORR_NONE; + cor[i].coef[0]=128; + cor[i].coef[1]=128; + cor[i].coef[2]=65536*64; + cor[i].coef[3]=65536*64; + min[i]=max[i]=0; + } + ioctl(fd, JSIOCSCORR, cor); + + while (read(fd, &ev, sizeof(ev))==sizeof(ev)) { + if (ev.type&JS_EVENT_BUTTON) + bval[ev.number]=ev.value; + if (ev.type&JS_EVENT_AXIS) { + aval[ev.number]=ev.value; + if (ev.valuemax[ev.number]) + max[ev.number]=ev.value; + } + } + usleep(100000); + count=0; + while (count++<100) { + while (read(fd, &ev, sizeof(ev))==sizeof(ev)) { + if (ev.type&JS_EVENT_BUTTON) + bval[ev.number]=ev.value; + if (ev.type&JS_EVENT_AXIS) { + aval[ev.number]=ev.value; + if (ev.valuemax[ev.number]) + max[ev.number]=ev.value; + } + } + usleep(10000); + } + + for (i=0; i +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) { + int fd,i; + unsigned char axes,btns; + struct js_corr cor[64]; + if (argc>=2) + fd=open(argv[1], O_RDONLY|O_NONBLOCK); + else + fd=open("/dev/input/js0", O_RDONLY|O_NONBLOCK); + if (fd<0) { + perror("Failed to open device"); + return 1; + } + ioctl(fd, JSIOCGAXES, &axes); + ioctl(fd, JSIOCGBUTTONS, &btns); + for (i=0; i +#include +#include +#include "program.h" +#include "mapper.h" +#include "clock.h" +#include "keys.h" + +#define INTERVAL 50 +#define STACK_SIZE 1024 +#define MAX_THREADS 8 + +#define debug(...) +//#define debug printf + +static int installed=0; + +static int timestamp=0; +static int tick=0; +static __uint64_t started=0; +static int executing=0; + +//controller variables +static __int16_t code_analog[64]; +//controller variables +static __uint8_t code_bit[128]; +//controller variables +static __int16_t js_analog[16][64]; +//controller variables +static __uint8_t js_bit[16][128]; +static __int16_t currentmode=0; +static __int16_t firstscan=1; +static __int16_t clocktick=1; +static __int16_t xrel=0; +static __int16_t yrel=0; +static __int16_t zrel=0; +static __uint8_t code[MAX_CODE_SIZE]; +//registers inaccessible to user +//allocated by compiler +static int status=0; //0 = no valid program 1=valid program +static struct code_context { + unsigned int ip; + int registers[256]; //general purpose registers + int sp; + int stack[STACK_SIZE]; + int active; //is this thread active +} code_threads[MAX_THREADS]; + +//for each instruction, does it have an operand +static char has_operand[]={ + 0, /* HALT */ + 1, /* JZ */ + 1, /* JUMP */ + 1, /* JUMPREL */ + 0, /* ADD */ + 0, /* SUB */ + 0, /* MUL */ + 0, /* DIV */ + 0, /* LT */ + 0, /* LE */ + 0, /* GT */ + 0, /* GE */ + 0, /* EQ */ + 0, /* NEQ */ + 0, /* AND */ + 0, /* OR */ + 0, /* NOT */ + 1, /* PUSH */ + 1, /* POP */ + 1, /* DEC */ + 1, /* INC */ + 0, /* KEY */ + 0, /* SIGNAL */ + 1, /* THREAD handles its own arguments */ + 0, /* YIELD */ + 0, /* NEG */ + 1, /* JOIN */ + 1, /* PUSHA */ + 1, /* POPA */ + 1, /* DECA */ + 1, /* INCA */ +}; + +struct code_context *code_thread; + +void code_button(int code, int value); +void code_axis(int axis, int value); +void execute_script(void); + +void code_notify_button(int js, int key, int value) { + if ((js>=0)&&(js<16)) + if ((key-BTN_JOYSTICK>=0)&&(key-BTN_JOYSTICK<128)) { + if (js_bit[js][key-BTN_JOYSTICK]!=value) { + js_bit[js][key-BTN_JOYSTICK]=value; + program_run(); + } + } +} + +void code_notify_axis(int js, int axis, int value) { + if ((js>=0)&&(js<16)) + if ((axis>=0)&&(axis<64)) { + if (js_analog[js][axis]!=value) { + js_analog[js][axis]=value; + program_run(); + } + } +} + +void code_set_program(struct program_code *program) { + if (program==NULL) { + status=0; + return; + } + if (program->program!=PROGRAM_CODE) return; + memcpy(code, program->code, MAX_CODE_SIZE); + firstscan=1; + currentmode=0; + timestamp=0; + status=1; +} + +void code_reset(void) { + int i, j; + for (i=0; ispstack[code_thread->sp++]=x; +} + +static int pop(void) { + if (code_thread->sp<=0) return 0; + else return code_thread->stack[--code_thread->sp]; +} + +static int get_value(int address, int type, int array) { + int js; + int ofs=0; + if (array) + ofs=code_thread->registers[OFSREG]; + switch (type) { + case GP: + if (address+ofs==FIRSTSCAN) return firstscan; + if (address+ofs==CLOCKTICK) return clocktick; + if (address+ofs==XREL) return xrel; + if (address+ofs==YREL) return yrel; + if (address+ofs==ZREL) return zrel; + if (address+ofs==TIMESTAMP) return timestamp; + if (address+ofs==CURRENTMODE) return currentmode; + if ((address+ofs>=0)&&(address+ofs<256)) + return code_thread->registers[address+ofs]; + else + return 0; + case GLOBAL: + if (address+ofs==FIRSTSCAN) return firstscan; + if (address+ofs==CLOCKTICK) return clocktick; + if (address+ofs==XREL) return xrel; + if (address+ofs==YREL) return yrel; + if (address+ofs==ZREL) return zrel; + if (address+ofs==TIMESTAMP) return timestamp; + if (address+ofs==CURRENTMODE) return currentmode; + if ((address+ofs>=0)&&(address+ofs<256)) + return code_threads[0].registers[address+ofs]; + else + return 0; + case CODEA: + if ((address+ofs>=0)&&(address+ofs<64)) + return code_analog[address+ofs]; + else + return 0; + case CODEB: + if ((address+ofs>=0)&&(address+ofs<128)) + return code_bit[address+ofs]; + else + return 0; + case JS: + js=(address&0x0F00)>>8; + type=address&0xF000; + address=address&0x00FF; + if (type==0) { + if ((address+ofs>=0)&&(address+ofs<128)) + return js_bit[js][address+ofs]; + else + return 0; + } else { + if ((address+ofs>=0)&&(address+ofs<64)) + return js_analog[js][address+ofs]; + else + return 0; + } + case CONST: + return address; + default: + return 0; + } +} + +static void set_value(int address, int type, int value, int array) { + int ofs=0; + if (array) + ofs=code_thread->registers[OFSREG]; + switch (type) { + case GP: + if (address==XREL) xrel=value; + if (address==YREL) yrel=value; + if (address==ZREL) zrel=value; + if (address==CURRENTMODE) currentmode=value; + if ((address+ofs>=0)&&(address+ofs<256)) + code_thread->registers[address+ofs]=value; + break; + case GLOBAL: + if (address==XREL) xrel=value; + if (address==YREL) yrel=value; + if (address==ZREL) zrel=value; + if (address==CURRENTMODE) currentmode=value; + if ((address+ofs>=0)&&(address+ofs<256)) + code_threads[0].registers[address+ofs]=value; + break; + case CODEA: + if ((address+ofs>=0)&&(address+ofs<64)) + code_axis(address+ofs, value); + break; + case CODEB: + if ((address+ofs>=0)&&(address+ofs<128)) + code_button(address+ofs, value); + break; + case JS: + case CONST: + break; + default: + break; + } +} + +static int instr_exec=0; + +static void execute_script_thread(struct code_context *new_thread, unsigned int new_ip, unsigned int retip); +void execute_script(void) { + if (executing) return; + executing=1; + code_thread=&code_threads[0]; + code_thread->ip=0; + code_thread->sp=0; + code_thread->active=1; + instr_exec=0; + debug("\n\nstart thread\n"); + debug("timestamp %d\n", timestamp); + execute_script_thread(code_thread,0,MAX_CODE_SIZE); + firstscan=0; + clocktick=0; + executing=0; +} + +static void execute_script_thread(struct code_context *new_thread, unsigned int new_ip, unsigned int retip) { + int address=0, array=0; + int type; + int task; + int st; + int p1, p2; + int value=0; + unsigned int thread_start; + unsigned int thread_exit; + if (status!=1) return; + if (!new_thread->active) { + debug("Activating thread\n"); + *new_thread=*code_thread; + new_thread->ip=new_ip; + //code_thread->ip=retip; + code_thread=new_thread; + new_thread->active=1; + } else { + code_thread=new_thread; + } + while ((code_thread->ipip++]; + type=task >> 5; + task=task&31; + if (task>=PUSHA) array=1; + else array=0; + if ((task>=0)&&(task<=INCA)) + if (has_operand[task]) { + address=*((unsigned short *)(code+code_thread->ip)); + value=get_value(address, type, array); + code_thread->ip+=2; + debug("Got operand %d address=%d type=%d\n",value, address, type); + } + instr_exec++; + switch (task) { + case HALT: + debug("HALT\n"); + code_thread->ip=MAX_CODE_SIZE+1; + code_thread->active=0; + break; + case JZ: + debug("JZ\n"); + st=pop(); + if (st==0) code_thread->ip=value; + break; + case JUMP: + debug("JUMP\n"); + code_thread->ip=value; + break; + case JUMPREL: + debug("JUMPREL\n"); + code_thread->ip+=value; + break; + case ADD: + debug("ADD\n"); + p1=pop(); + p2=pop(); + push(p2+p1); + break; + case SUB: + debug("SUB\n"); + p1=pop(); + p2=pop(); + push(p2-p1); + break; + case MUL: + debug("MUL\n"); + p1=pop(); + p2=pop(); + push(p2*p1); + break; + case DIV: + debug("DIV\n"); + p1=pop(); + p2=pop(); + push(p2/p1); + break; + case LT: + debug("LT\n"); + p1=pop(); + p2=pop(); + push((p2p1)?1:0); + break; + case GE: + debug("GE\n"); + p1=pop(); + p2=pop(); + push((p2>=p1)?1:0); + break; + case EQ: + debug("EQ\n"); + p1=pop(); + p2=pop(); + push((p2==p1)?1:0); + break; + case NEQ: + debug("NEQ\n"); + p1=pop(); + p2=pop(); + push((p2!=p1)?1:0); + break; + case AND: + debug("AND\n"); + p1=pop(); + p2=pop(); + push((p2&&p1)?1:0); + break; + case OR: + debug("OR\n"); + p1=pop(); + p2=pop(); + push((p2||p1)?1:0); + break; + case NOT: + debug("NOT\n"); + p1=pop(); + push((!p1)?1:0); + break; + case NEG: + debug("NEG\n"); + p1=pop(); + push(-p1); + break; + case PUSH: + debug("PUSH %d\n", value); + push(value); + break; + case POP: + value=pop(); + debug("POP %d\n", value); + set_value(address, type, value, 0); + break; + case DEC: + debug("DEC\n"); + set_value(address, type, value-1, 0); + break; + case INC: + debug("INC\n"); + set_value(address, type, value+1, 0); + break; + case PUSHA: + debug("PUSHA %d\n", value); + push(value); + break; + case POPA: + value=pop(); + debug("POPA %d\n", value); + set_value(address, type, value, 1); + break; + case DECA: + debug("DECA\n"); + set_value(address, type, value-1, 1); + break; + case INCA: + debug("INCA\n"); + set_value(address, type, value+1, 1); + break; + case KEY: + p2=pop(); + p1=pop(); + debug("KEY %d %d\n", p1, p2); + press_key(p1, p2); + break; + case SIGNAL: + debug("SIGNAL\n"); + value=pop(); + push_signal(value); + break; + case THREAD: //context switch, value is new thread, two constant jump targets + //thread_start=*((unsigned short *)(code+code_thread->ip)); + //code_thread->ip+=2; + debug("THREAD %d\n", value); + thread_exit=*((unsigned short *)(code+code_thread->ip)); + code_thread->ip+=2; + thread_start=code_thread->ip; + if ((value>0)&&(valueip=thread_exit; + } else { + debug("Not a valid thread\n"); + code_thread=new_thread; + code_thread->ip=thread_exit; + } + break; + case YIELD: + debug("YIELD\n"); + return; + break; + case JOIN: //stop thread + debug("JOIN %d\n", value); + if ((value>0)&&(value