| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2003-2009 Marcel Holtmann <marcel@holtmann.org> |
| * |
| * |
| * 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 |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #define _GNU_SOURCE |
| #include <stdio.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <signal.h> |
| #include <sys/poll.h> |
| #include <sys/ioctl.h> |
| #include <sys/socket.h> |
| |
| #include <bluetooth/bluetooth.h> |
| #include <bluetooth/rfcomm.h> |
| #include <bluetooth/hidp.h> |
| |
| #include "hidd.h" |
| #include "uinput.h" |
| |
| #include <math.h> |
| |
| #ifdef NEED_PPOLL |
| #include "ppoll.h" |
| #endif |
| |
| static volatile sig_atomic_t __io_canceled = 0; |
| |
| static void sig_hup(int sig) |
| { |
| } |
| |
| static void sig_term(int sig) |
| { |
| __io_canceled = 1; |
| } |
| |
| static void send_event(int fd, uint16_t type, uint16_t code, int32_t value) |
| { |
| struct uinput_event event; |
| int len; |
| |
| if (fd <= fileno(stderr)) |
| return; |
| |
| memset(&event, 0, sizeof(event)); |
| event.type = type; |
| event.code = code; |
| event.value = value; |
| |
| len = write(fd, &event, sizeof(event)); |
| } |
| |
| static int uinput_create(char *name, int keyboard, int mouse) |
| { |
| struct uinput_dev dev; |
| int fd, aux; |
| |
| fd = open("/dev/uinput", O_RDWR); |
| if (fd < 0) { |
| fd = open("/dev/input/uinput", O_RDWR); |
| if (fd < 0) { |
| fd = open("/dev/misc/uinput", O_RDWR); |
| if (fd < 0) { |
| fprintf(stderr, "Can't open input device: %s (%d)\n", |
| strerror(errno), errno); |
| return -1; |
| } |
| } |
| } |
| |
| memset(&dev, 0, sizeof(dev)); |
| |
| if (name) |
| strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE - 1); |
| |
| dev.id.bustype = BUS_BLUETOOTH; |
| dev.id.vendor = 0x0000; |
| dev.id.product = 0x0000; |
| dev.id.version = 0x0000; |
| |
| if (write(fd, &dev, sizeof(dev)) < 0) { |
| fprintf(stderr, "Can't write device information: %s (%d)\n", |
| strerror(errno), errno); |
| close(fd); |
| return -1; |
| } |
| |
| if (mouse) { |
| ioctl(fd, UI_SET_EVBIT, EV_REL); |
| |
| for (aux = REL_X; aux <= REL_MISC; aux++) |
| ioctl(fd, UI_SET_RELBIT, aux); |
| } |
| |
| if (keyboard) { |
| ioctl(fd, UI_SET_EVBIT, EV_KEY); |
| ioctl(fd, UI_SET_EVBIT, EV_LED); |
| ioctl(fd, UI_SET_EVBIT, EV_REP); |
| |
| for (aux = KEY_RESERVED; aux <= KEY_UNKNOWN; aux++) |
| ioctl(fd, UI_SET_KEYBIT, aux); |
| |
| //for (aux = LED_NUML; aux <= LED_MISC; aux++) |
| // ioctl(fd, UI_SET_LEDBIT, aux); |
| } |
| |
| if (mouse) { |
| ioctl(fd, UI_SET_EVBIT, EV_KEY); |
| |
| for (aux = BTN_LEFT; aux <= BTN_BACK; aux++) |
| ioctl(fd, UI_SET_KEYBIT, aux); |
| } |
| |
| ioctl(fd, UI_DEV_CREATE); |
| |
| return fd; |
| } |
| |
| static int rfcomm_connect(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel) |
| { |
| struct sockaddr_rc addr; |
| int sk; |
| |
| sk = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); |
| if (sk < 0) { |
| fprintf(stderr, "Can't create socket: %s (%d)\n", |
| strerror(errno), errno); |
| return -1; |
| } |
| |
| memset(&addr, 0, sizeof(addr)); |
| addr.rc_family = AF_BLUETOOTH; |
| bacpy(&addr.rc_bdaddr, src); |
| |
| if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { |
| fprintf(stderr, "Can't bind socket: %s (%d)\n", |
| strerror(errno), errno); |
| close(sk); |
| return -1; |
| } |
| |
| memset(&addr, 0, sizeof(addr)); |
| addr.rc_family = AF_BLUETOOTH; |
| bacpy(&addr.rc_bdaddr, dst); |
| addr.rc_channel = channel; |
| |
| if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { |
| fprintf(stderr, "Can't connect: %s (%d)\n", |
| strerror(errno), errno); |
| close(sk); |
| return -1; |
| } |
| |
| return sk; |
| } |
| |
| static void func(int fd) |
| { |
| } |
| |
| static void back(int fd) |
| { |
| } |
| |
| static void next(int fd) |
| { |
| } |
| |
| static void button(int fd, unsigned int button, int is_press) |
| { |
| switch (button) { |
| case 1: |
| send_event(fd, EV_KEY, BTN_LEFT, is_press); |
| break; |
| case 3: |
| send_event(fd, EV_KEY, BTN_RIGHT, is_press); |
| break; |
| } |
| |
| send_event(fd, EV_SYN, SYN_REPORT, 0); |
| } |
| |
| static void move(int fd, unsigned int direction) |
| { |
| double angle; |
| int32_t x, y; |
| |
| angle = (direction * 22.5) * 3.1415926 / 180; |
| x = (int) (sin(angle) * 8); |
| y = (int) (cos(angle) * -8); |
| |
| send_event(fd, EV_REL, REL_X, x); |
| send_event(fd, EV_REL, REL_Y, y); |
| |
| send_event(fd, EV_SYN, SYN_REPORT, 0); |
| } |
| |
| static inline void epox_decode(int fd, unsigned char event) |
| { |
| switch (event) { |
| case 48: |
| func(fd); break; |
| case 55: |
| back(fd); break; |
| case 56: |
| next(fd); break; |
| case 53: |
| button(fd, 1, 1); break; |
| case 121: |
| button(fd, 1, 0); break; |
| case 113: |
| break; |
| case 54: |
| button(fd, 3, 1); break; |
| case 120: |
| button(fd, 3, 0); break; |
| case 112: |
| break; |
| case 51: |
| move(fd, 0); break; |
| case 97: |
| move(fd, 1); break; |
| case 65: |
| move(fd, 2); break; |
| case 98: |
| move(fd, 3); break; |
| case 50: |
| move(fd, 4); break; |
| case 99: |
| move(fd, 5); break; |
| case 67: |
| move(fd, 6); break; |
| case 101: |
| move(fd, 7); break; |
| case 52: |
| move(fd, 8); break; |
| case 100: |
| move(fd, 9); break; |
| case 66: |
| move(fd, 10); break; |
| case 102: |
| move(fd, 11); break; |
| case 49: |
| move(fd, 12); break; |
| case 103: |
| move(fd, 13); break; |
| case 57: |
| move(fd, 14); break; |
| case 104: |
| move(fd, 15); break; |
| case 69: |
| break; |
| default: |
| printf("Unknown event code %d\n", event); |
| break; |
| } |
| } |
| |
| int epox_presenter(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel) |
| { |
| unsigned char buf[16]; |
| struct sigaction sa; |
| struct pollfd p; |
| sigset_t sigs; |
| char addr[18]; |
| int i, fd, sk, len; |
| |
| sk = rfcomm_connect(src, dst, channel); |
| if (sk < 0) |
| return -1; |
| |
| fd = uinput_create("Bluetooth Presenter", 0, 1); |
| if (fd < 0) { |
| close(sk); |
| return -1; |
| } |
| |
| ba2str(dst, addr); |
| |
| printf("Connected to %s on channel %d\n", addr, channel); |
| printf("Press CTRL-C for hangup\n"); |
| |
| memset(&sa, 0, sizeof(sa)); |
| sa.sa_flags = SA_NOCLDSTOP; |
| sa.sa_handler = SIG_IGN; |
| sigaction(SIGCHLD, &sa, NULL); |
| sigaction(SIGPIPE, &sa, NULL); |
| |
| sa.sa_handler = sig_term; |
| sigaction(SIGTERM, &sa, NULL); |
| sigaction(SIGINT, &sa, NULL); |
| |
| sa.sa_handler = sig_hup; |
| sigaction(SIGHUP, &sa, NULL); |
| |
| sigfillset(&sigs); |
| sigdelset(&sigs, SIGCHLD); |
| sigdelset(&sigs, SIGPIPE); |
| sigdelset(&sigs, SIGTERM); |
| sigdelset(&sigs, SIGINT); |
| sigdelset(&sigs, SIGHUP); |
| |
| p.fd = sk; |
| p.events = POLLIN | POLLERR | POLLHUP; |
| |
| while (!__io_canceled) { |
| p.revents = 0; |
| if (ppoll(&p, 1, NULL, &sigs) < 1) |
| continue; |
| |
| len = read(sk, buf, sizeof(buf)); |
| if (len < 0) |
| break; |
| |
| for (i = 0; i < len; i++) |
| epox_decode(fd, buf[i]); |
| } |
| |
| printf("Disconnected\n"); |
| |
| ioctl(fd, UI_DEV_DESTROY); |
| |
| close(fd); |
| close(sk); |
| |
| return 0; |
| } |
| |
| int headset_presenter(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel) |
| { |
| printf("Not implemented\n"); |
| return -1; |
| } |
| |
| /* The strange meta key close to Ctrl has been assigned to Esc, |
| Fn key to CtrlR and the left space to Alt*/ |
| |
| static unsigned char jthree_keycodes[63] = { |
| KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, |
| KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, |
| KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, |
| KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, |
| KEY_LEFTALT, KEY_TAB, KEY_CAPSLOCK, KEY_ESC, |
| KEY_7, KEY_8, KEY_9, KEY_0, KEY_MINUS, KEY_EQUAL, KEY_BACKSPACE, |
| KEY_Y, KEY_U, KEY_I, KEY_O, KEY_P, KEY_LEFTBRACE, KEY_RIGHTBRACE, |
| KEY_H, KEY_J, KEY_K, KEY_L, KEY_SEMICOLON, KEY_APOSTROPHE, KEY_ENTER, |
| KEY_N, KEY_M, KEY_COMMA, KEY_DOT, KEY_SLASH, KEY_UP, |
| KEY_SPACE, KEY_COMPOSE, KEY_LEFT, KEY_DOWN, KEY_RIGHT, |
| KEY_LEFTCTRL, KEY_RIGHTSHIFT, KEY_LEFTSHIFT, KEY_DELETE, KEY_RIGHTCTRL, KEY_RIGHTALT, |
| }; |
| |
| static inline void jthree_decode(int fd, unsigned char event) |
| { |
| if (event > 63) |
| send_event(fd, EV_KEY, jthree_keycodes[event & 0x3f], 0); |
| else |
| send_event(fd, EV_KEY, jthree_keycodes[event - 1], 1); |
| } |
| |
| int jthree_keyboard(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel) |
| { |
| unsigned char buf[16]; |
| struct sigaction sa; |
| struct pollfd p; |
| sigset_t sigs; |
| char addr[18]; |
| int i, fd, sk, len; |
| |
| sk = rfcomm_connect(src, dst, channel); |
| if (sk < 0) |
| return -1; |
| |
| fd = uinput_create("J-Three Keyboard", 1, 0); |
| if (fd < 0) { |
| close(sk); |
| return -1; |
| } |
| |
| ba2str(dst, addr); |
| |
| printf("Connected to %s on channel %d\n", addr, channel); |
| printf("Press CTRL-C for hangup\n"); |
| |
| memset(&sa, 0, sizeof(sa)); |
| sa.sa_flags = SA_NOCLDSTOP; |
| sa.sa_handler = SIG_IGN; |
| sigaction(SIGCHLD, &sa, NULL); |
| sigaction(SIGPIPE, &sa, NULL); |
| |
| sa.sa_handler = sig_term; |
| sigaction(SIGTERM, &sa, NULL); |
| sigaction(SIGINT, &sa, NULL); |
| |
| sa.sa_handler = sig_hup; |
| sigaction(SIGHUP, &sa, NULL); |
| |
| sigfillset(&sigs); |
| sigdelset(&sigs, SIGCHLD); |
| sigdelset(&sigs, SIGPIPE); |
| sigdelset(&sigs, SIGTERM); |
| sigdelset(&sigs, SIGINT); |
| sigdelset(&sigs, SIGHUP); |
| |
| p.fd = sk; |
| p.events = POLLIN | POLLERR | POLLHUP; |
| |
| while (!__io_canceled) { |
| p.revents = 0; |
| if (ppoll(&p, 1, NULL, &sigs) < 1) |
| continue; |
| |
| len = read(sk, buf, sizeof(buf)); |
| if (len < 0) |
| break; |
| |
| for (i = 0; i < len; i++) |
| jthree_decode(fd, buf[i]); |
| } |
| |
| printf("Disconnected\n"); |
| |
| ioctl(fd, UI_DEV_DESTROY); |
| |
| close(fd); |
| close(sk); |
| |
| return 0; |
| } |
| |
| static const int celluon_xlate_num[10] = { |
| KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9 |
| }; |
| |
| static const int celluon_xlate_char[26] = { |
| 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_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z |
| }; |
| |
| static int celluon_xlate(int c) |
| { |
| if (c >= '0' && c <= '9') |
| return celluon_xlate_num[c - '0']; |
| |
| if (c >= 'A' && c <= 'Z') |
| return celluon_xlate_char[c - 'A']; |
| |
| switch (c) { |
| case 0x08: |
| return KEY_BACKSPACE; |
| case 0x09: |
| return KEY_TAB; |
| case 0x0d: |
| return KEY_ENTER; |
| case 0x11: |
| return KEY_LEFTCTRL; |
| case 0x14: |
| return KEY_CAPSLOCK; |
| case 0x20: |
| return KEY_SPACE; |
| case 0x25: |
| return KEY_LEFT; |
| case 0x26: |
| return KEY_UP; |
| case 0x27: |
| return KEY_RIGHT; |
| case 0x28: |
| return KEY_DOWN; |
| case 0x2e: |
| return KEY_DELETE; |
| case 0x5b: |
| return KEY_MENU; |
| case 0xa1: |
| return KEY_RIGHTSHIFT; |
| case 0xa0: |
| return KEY_LEFTSHIFT; |
| case 0xba: |
| return KEY_SEMICOLON; |
| case 0xbd: |
| return KEY_MINUS; |
| case 0xbc: |
| return KEY_COMMA; |
| case 0xbb: |
| return KEY_EQUAL; |
| case 0xbe: |
| return KEY_DOT; |
| case 0xbf: |
| return KEY_SLASH; |
| case 0xc0: |
| return KEY_GRAVE; |
| case 0xdb: |
| return KEY_LEFTBRACE; |
| case 0xdc: |
| return KEY_BACKSLASH; |
| case 0xdd: |
| return KEY_RIGHTBRACE; |
| case 0xde: |
| return KEY_APOSTROPHE; |
| case 0xff03: |
| return KEY_HOMEPAGE; |
| case 0xff04: |
| return KEY_TIME; |
| case 0xff06: |
| return KEY_OPEN; |
| case 0xff07: |
| return KEY_LIST; |
| case 0xff08: |
| return KEY_MAIL; |
| case 0xff30: |
| return KEY_CALC; |
| case 0xff1a: /* Map FN to ALT */ |
| return KEY_LEFTALT; |
| case 0xff2f: |
| return KEY_INFO; |
| default: |
| printf("Unknown key %x\n", c); |
| return c; |
| } |
| } |
| |
| struct celluon_state { |
| int len; /* Expected length of current packet */ |
| int count; /* Number of bytes received */ |
| int action; |
| int key; |
| }; |
| |
| static void celluon_decode(int fd, struct celluon_state *s, uint8_t c) |
| { |
| if (s->count < 2 && c != 0xa5) { |
| /* Lost Sync */ |
| s->count = 0; |
| return; |
| } |
| |
| switch (s->count) { |
| case 0: |
| /* New packet - Reset state */ |
| s->len = 30; |
| s->key = 0; |
| break; |
| case 1: |
| break; |
| case 6: |
| s->action = c; |
| break; |
| case 28: |
| s->key = c; |
| if (c == 0xff) |
| s->len = 31; |
| break; |
| case 29: |
| case 30: |
| if (s->count == s->len - 1) { |
| /* TODO: Verify checksum */ |
| if (s->action < 2) { |
| send_event(fd, EV_KEY, celluon_xlate(s->key), |
| s->action); |
| } |
| s->count = -1; |
| } else { |
| s->key = (s->key << 8) | c; |
| } |
| break; |
| } |
| |
| s->count++; |
| |
| return; |
| } |
| |
| int celluon_keyboard(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel) |
| { |
| unsigned char buf[16]; |
| struct sigaction sa; |
| struct pollfd p; |
| sigset_t sigs; |
| char addr[18]; |
| int i, fd, sk, len; |
| struct celluon_state s; |
| |
| sk = rfcomm_connect(src, dst, channel); |
| if (sk < 0) |
| return -1; |
| |
| fd = uinput_create("Celluon Keyboard", 1, 0); |
| if (fd < 0) { |
| close(sk); |
| return -1; |
| } |
| |
| ba2str(dst, addr); |
| |
| printf("Connected to %s on channel %d\n", addr, channel); |
| printf("Press CTRL-C for hangup\n"); |
| |
| memset(&sa, 0, sizeof(sa)); |
| sa.sa_flags = SA_NOCLDSTOP; |
| sa.sa_handler = SIG_IGN; |
| sigaction(SIGCHLD, &sa, NULL); |
| sigaction(SIGPIPE, &sa, NULL); |
| |
| sa.sa_handler = sig_term; |
| sigaction(SIGTERM, &sa, NULL); |
| sigaction(SIGINT, &sa, NULL); |
| |
| sa.sa_handler = sig_hup; |
| sigaction(SIGHUP, &sa, NULL); |
| |
| sigfillset(&sigs); |
| sigdelset(&sigs, SIGCHLD); |
| sigdelset(&sigs, SIGPIPE); |
| sigdelset(&sigs, SIGTERM); |
| sigdelset(&sigs, SIGINT); |
| sigdelset(&sigs, SIGHUP); |
| |
| p.fd = sk; |
| p.events = POLLIN | POLLERR | POLLHUP; |
| |
| memset(&s, 0, sizeof(s)); |
| |
| while (!__io_canceled) { |
| p.revents = 0; |
| if (ppoll(&p, 1, NULL, &sigs) < 1) |
| continue; |
| |
| len = read(sk, buf, sizeof(buf)); |
| if (len < 0) |
| break; |
| |
| for (i = 0; i < len; i++) |
| celluon_decode(fd, &s, buf[i]); |
| } |
| |
| printf("Disconnected\n"); |
| |
| ioctl(fd, UI_DEV_DESTROY); |
| |
| close(fd); |
| close(sk); |
| |
| return 0; |
| } |