/* * QEMU Baum Braille Device * * Copyright (c) 2008 Samuel Thibault * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "qemu-common.h" #include "sysemu/char.h" #include "qemu/timer.h" #include "hw/usb.h" #include <brlapi.h> #include <brlapi_constants.h> #include <brlapi_keycodes.h> #ifdef CONFIG_SDL #include <SDL_syswm.h> #endif #if 0 #define DPRINTF(fmt, ...) \ printf(fmt, ## __VA_ARGS__) #else #define DPRINTF(fmt, ...) #endif #define ESC 0x1B #define BAUM_REQ_DisplayData 0x01 #define BAUM_REQ_GetVersionNumber 0x05 #define BAUM_REQ_GetKeys 0x08 #define BAUM_REQ_SetMode 0x12 #define BAUM_REQ_SetProtocol 0x15 #define BAUM_REQ_GetDeviceIdentity 0x84 #define BAUM_REQ_GetSerialNumber 0x8A #define BAUM_RSP_CellCount 0x01 #define BAUM_RSP_VersionNumber 0x05 #define BAUM_RSP_ModeSetting 0x11 #define BAUM_RSP_CommunicationChannel 0x16 #define BAUM_RSP_PowerdownSignal 0x17 #define BAUM_RSP_HorizontalSensors 0x20 #define BAUM_RSP_VerticalSensors 0x21 #define BAUM_RSP_RoutingKeys 0x22 #define BAUM_RSP_Switches 0x23 #define BAUM_RSP_TopKeys 0x24 #define BAUM_RSP_HorizontalSensor 0x25 #define BAUM_RSP_VerticalSensor 0x26 #define BAUM_RSP_RoutingKey 0x27 #define BAUM_RSP_FrontKeys6 0x28 #define BAUM_RSP_BackKeys6 0x29 #define BAUM_RSP_CommandKeys 0x2B #define BAUM_RSP_FrontKeys10 0x2C #define BAUM_RSP_BackKeys10 0x2D #define BAUM_RSP_EntryKeys 0x33 #define BAUM_RSP_JoyStick 0x34 #define BAUM_RSP_ErrorCode 0x40 #define BAUM_RSP_InfoBlock 0x42 #define BAUM_RSP_DeviceIdentity 0x84 #define BAUM_RSP_SerialNumber 0x8A #define BAUM_RSP_BluetoothName 0x8C #define BAUM_TL1 0x01 #define BAUM_TL2 0x02 #define BAUM_TL3 0x04 #define BAUM_TR1 0x08 #define BAUM_TR2 0x10 #define BAUM_TR3 0x20 #define BUF_SIZE 256 typedef struct { CharDriverState *chr; brlapi_handle_t *brlapi; int brlapi_fd; unsigned int x, y; uint8_t in_buf[BUF_SIZE]; uint8_t in_buf_used; uint8_t out_buf[BUF_SIZE]; uint8_t out_buf_used, out_buf_ptr; QEMUTimer *cellCount_timer; } BaumDriverState; /* Let's assume NABCC by default */ static const uint8_t nabcc_translation[256] = { [0] = ' ', #ifndef BRLAPI_DOTS #define BRLAPI_DOTS(d1,d2,d3,d4,d5,d6,d7,d8) \ ((d1?BRLAPI_DOT1:0)|\ (d2?BRLAPI_DOT2:0)|\ (d3?BRLAPI_DOT3:0)|\ (d4?BRLAPI_DOT4:0)|\ (d5?BRLAPI_DOT5:0)|\ (d6?BRLAPI_DOT6:0)|\ (d7?BRLAPI_DOT7:0)|\ (d8?BRLAPI_DOT8:0)) #endif [BRLAPI_DOTS(1,0,0,0,0,0,0,0)] = 'a', [BRLAPI_DOTS(1,1,0,0,0,0,0,0)] = 'b', [BRLAPI_DOTS(1,0,0,1,0,0,0,0)] = 'c', [BRLAPI_DOTS(1,0,0,1,1,0,0,0)] = 'd', [BRLAPI_DOTS(1,0,0,0,1,0,0,0)] = 'e', [BRLAPI_DOTS(1,1,0,1,0,0,0,0)] = 'f', [BRLAPI_DOTS(1,1,0,1,1,0,0,0)] = 'g', [BRLAPI_DOTS(1,1,0,0,1,0,0,0)] = 'h', [BRLAPI_DOTS(0,1,0,1,0,0,0,0)] = 'i', [BRLAPI_DOTS(0,1,0,1,1,0,0,0)] = 'j', [BRLAPI_DOTS(1,0,1,0,0,0,0,0)] = 'k', [BRLAPI_DOTS(1,1,1,0,0,0,0,0)] = 'l', [BRLAPI_DOTS(1,0,1,1,0,0,0,0)] = 'm', [BRLAPI_DOTS(1,0,1,1,1,0,0,0)] = 'n', [BRLAPI_DOTS(1,0,1,0,1,0,0,0)] = 'o', [BRLAPI_DOTS(1,1,1,1,0,0,0,0)] = 'p', [BRLAPI_DOTS(1,1,1,1,1,0,0,0)] = 'q', [BRLAPI_DOTS(1,1,1,0,1,0,0,0)] = 'r', [BRLAPI_DOTS(0,1,1,1,0,0,0,0)] = 's', [BRLAPI_DOTS(0,1,1,1,1,0,0,0)] = 't', [BRLAPI_DOTS(1,0,1,0,0,1,0,0)] = 'u', [BRLAPI_DOTS(1,1,1,0,0,1,0,0)] = 'v', [BRLAPI_DOTS(0,1,0,1,1,1,0,0)] = 'w', [BRLAPI_DOTS(1,0,1,1,0,1,0,0)] = 'x', [BRLAPI_DOTS(1,0,1,1,1,1,0,0)] = 'y', [BRLAPI_DOTS(1,0,1,0,1,1,0,0)] = 'z', [BRLAPI_DOTS(1,0,0,0,0,0,1,0)] = 'A', [BRLAPI_DOTS(1,1,0,0,0,0,1,0)] = 'B', [BRLAPI_DOTS(1,0,0,1,0,0,1,0)] = 'C', [BRLAPI_DOTS(1,0,0,1,1,0,1,0)] = 'D', [BRLAPI_DOTS(1,0,0,0,1,0,1,0)] = 'E', [BRLAPI_DOTS(1,1,0,1,0,0,1,0)] = 'F', [BRLAPI_DOTS(1,1,0,1,1,0,1,0)] = 'G', [BRLAPI_DOTS(1,1,0,0,1,0,1,0)] = 'H', [BRLAPI_DOTS(0,1,0,1,0,0,1,0)] = 'I', [BRLAPI_DOTS(0,1,0,1,1,0,1,0)] = 'J', [BRLAPI_DOTS(1,0,1,0,0,0,1,0)] = 'K', [BRLAPI_DOTS(1,1,1,0,0,0,1,0)] = 'L', [BRLAPI_DOTS(1,0,1,1,0,0,1,0)] = 'M', [BRLAPI_DOTS(1,0,1,1,1,0,1,0)] = 'N', [BRLAPI_DOTS(1,0,1,0,1,0,1,0)] = 'O', [BRLAPI_DOTS(1,1,1,1,0,0,1,0)] = 'P', [BRLAPI_DOTS(1,1,1,1,1,0,1,0)] = 'Q', [BRLAPI_DOTS(1,1,1,0,1,0,1,0)] = 'R', [BRLAPI_DOTS(0,1,1,1,0,0,1,0)] = 'S', [BRLAPI_DOTS(0,1,1,1,1,0,1,0)] = 'T', [BRLAPI_DOTS(1,0,1,0,0,1,1,0)] = 'U', [BRLAPI_DOTS(1,1,1,0,0,1,1,0)] = 'V', [BRLAPI_DOTS(0,1,0,1,1,1,1,0)] = 'W', [BRLAPI_DOTS(1,0,1,1,0,1,1,0)] = 'X', [BRLAPI_DOTS(1,0,1,1,1,1,1,0)] = 'Y', [BRLAPI_DOTS(1,0,1,0,1,1,1,0)] = 'Z', [BRLAPI_DOTS(0,0,1,0,1,1,0,0)] = '0', [BRLAPI_DOTS(0,1,0,0,0,0,0,0)] = '1', [BRLAPI_DOTS(0,1,1,0,0,0,0,0)] = '2', [BRLAPI_DOTS(0,1,0,0,1,0,0,0)] = '3', [BRLAPI_DOTS(0,1,0,0,1,1,0,0)] = '4', [BRLAPI_DOTS(0,1,0,0,0,1,0,0)] = '5', [BRLAPI_DOTS(0,1,1,0,1,0,0,0)] = '6', [BRLAPI_DOTS(0,1,1,0,1,1,0,0)] = '7', [BRLAPI_DOTS(0,1,1,0,0,1,0,0)] = '8', [BRLAPI_DOTS(0,0,1,0,1,0,0,0)] = '9', [BRLAPI_DOTS(0,0,0,1,0,1,0,0)] = '.', [BRLAPI_DOTS(0,0,1,1,0,1,0,0)] = '+', [BRLAPI_DOTS(0,0,1,0,0,1,0,0)] = '-', [BRLAPI_DOTS(1,0,0,0,0,1,0,0)] = '*', [BRLAPI_DOTS(0,0,1,1,0,0,0,0)] = '/', [BRLAPI_DOTS(1,1,1,0,1,1,0,0)] = '(', [BRLAPI_DOTS(0,1,1,1,1,1,0,0)] = ')', [BRLAPI_DOTS(1,1,1,1,0,1,0,0)] = '&', [BRLAPI_DOTS(0,0,1,1,1,1,0,0)] = '#', [BRLAPI_DOTS(0,0,0,0,0,1,0,0)] = ',', [BRLAPI_DOTS(0,0,0,0,1,1,0,0)] = ';', [BRLAPI_DOTS(1,0,0,0,1,1,0,0)] = ':', [BRLAPI_DOTS(0,1,1,1,0,1,0,0)] = '!', [BRLAPI_DOTS(1,0,0,1,1,1,0,0)] = '?', [BRLAPI_DOTS(0,0,0,0,1,0,0,0)] = '"', [BRLAPI_DOTS(0,0,1,0,0,0,0,0)] ='\'', [BRLAPI_DOTS(0,0,0,1,0,0,0,0)] = '`', [BRLAPI_DOTS(0,0,0,1,1,0,1,0)] = '^', [BRLAPI_DOTS(0,0,0,1,1,0,0,0)] = '~', [BRLAPI_DOTS(0,1,0,1,0,1,1,0)] = '[', [BRLAPI_DOTS(1,1,0,1,1,1,1,0)] = ']', [BRLAPI_DOTS(0,1,0,1,0,1,0,0)] = '{', [BRLAPI_DOTS(1,1,0,1,1,1,0,0)] = '}', [BRLAPI_DOTS(1,1,1,1,1,1,0,0)] = '=', [BRLAPI_DOTS(1,1,0,0,0,1,0,0)] = '<', [BRLAPI_DOTS(0,0,1,1,1,0,0,0)] = '>', [BRLAPI_DOTS(1,1,0,1,0,1,0,0)] = '$', [BRLAPI_DOTS(1,0,0,1,0,1,0,0)] = '%', [BRLAPI_DOTS(0,0,0,1,0,0,1,0)] = '@', [BRLAPI_DOTS(1,1,0,0,1,1,0,0)] = '|', [BRLAPI_DOTS(1,1,0,0,1,1,1,0)] ='\\', [BRLAPI_DOTS(0,0,0,1,1,1,0,0)] = '_', }; /* The serial port can receive more of our data */ static void baum_accept_input(struct CharDriverState *chr) { BaumDriverState *baum = chr->opaque; int room, first; if (!baum->out_buf_used) return; room = qemu_chr_be_can_write(chr); if (!room) return; if (room > baum->out_buf_used) room = baum->out_buf_used; first = BUF_SIZE - baum->out_buf_ptr; if (room > first) { qemu_chr_be_write(chr, baum->out_buf + baum->out_buf_ptr, first); baum->out_buf_ptr = 0; baum->out_buf_used -= first; room -= first; } qemu_chr_be_write(chr, baum->out_buf + baum->out_buf_ptr, room); baum->out_buf_ptr += room; baum->out_buf_used -= room; } /* We want to send a packet */ static void baum_write_packet(BaumDriverState *baum, const uint8_t *buf, int len) { uint8_t io_buf[1 + 2 * len], *cur = io_buf; int room; *cur++ = ESC; while (len--) if ((*cur++ = *buf++) == ESC) *cur++ = ESC; room = qemu_chr_be_can_write(baum->chr); len = cur - io_buf; if (len <= room) { /* Fits */ qemu_chr_be_write(baum->chr, io_buf, len); } else { int first; uint8_t out; /* Can't fit all, send what can be, and store the rest. */ qemu_chr_be_write(baum->chr, io_buf, room); len -= room; cur = io_buf + room; if (len > BUF_SIZE - baum->out_buf_used) { /* Can't even store it, drop the previous data... */ assert(len <= BUF_SIZE); baum->out_buf_used = 0; baum->out_buf_ptr = 0; } out = baum->out_buf_ptr; baum->out_buf_used += len; first = BUF_SIZE - baum->out_buf_ptr; if (len > first) { memcpy(baum->out_buf + out, cur, first); out = 0; len -= first; cur += first; } memcpy(baum->out_buf + out, cur, len); } } /* Called when the other end seems to have a wrong idea of our display size */ static void baum_cellCount_timer_cb(void *opaque) { BaumDriverState *baum = opaque; uint8_t cell_count[] = { BAUM_RSP_CellCount, baum->x * baum->y }; DPRINTF("Timeout waiting for DisplayData, sending cell count\n"); baum_write_packet(baum, cell_count, sizeof(cell_count)); } /* Try to interpret a whole incoming packet */ static int baum_eat_packet(BaumDriverState *baum, const uint8_t *buf, int len) { const uint8_t *cur = buf; uint8_t req = 0; if (!len--) return 0; if (*cur++ != ESC) { while (*cur != ESC) { if (!len--) return 0; cur++; } DPRINTF("Dropped %d bytes!\n", cur - buf); } #define EAT(c) do {\ if (!len--) \ return 0; \ if ((c = *cur++) == ESC) { \ if (!len--) \ return 0; \ if (*cur++ != ESC) { \ DPRINTF("Broken packet %#2x, tossing\n", req); \ if (qemu_timer_pending(baum->cellCount_timer)) { \ qemu_del_timer(baum->cellCount_timer); \ baum_cellCount_timer_cb(baum); \ } \ return (cur - 2 - buf); \ } \ } \ } while (0) EAT(req); switch (req) { case BAUM_REQ_DisplayData: { uint8_t cells[baum->x * baum->y], c; uint8_t text[baum->x * baum->y]; uint8_t zero[baum->x * baum->y]; int cursor = BRLAPI_CURSOR_OFF; int i; /* Allow 100ms to complete the DisplayData packet */ qemu_mod_timer(baum->cellCount_timer, qemu_get_clock_ns(vm_clock) + get_ticks_per_sec() / 10); for (i = 0; i < baum->x * baum->y ; i++) { EAT(c); cells[i] = c; if ((c & (BRLAPI_DOT7|BRLAPI_DOT8)) == (BRLAPI_DOT7|BRLAPI_DOT8)) { cursor = i + 1; c &= ~(BRLAPI_DOT7|BRLAPI_DOT8); } if (!(c = nabcc_translation[c])) c = '?'; text[i] = c; } qemu_del_timer(baum->cellCount_timer); memset(zero, 0, sizeof(zero)); brlapi_writeArguments_t wa = { .displayNumber = BRLAPI_DISPLAY_DEFAULT, .regionBegin = 1, .regionSize = baum->x * baum->y, .text = (char *)text, .textSize = baum->x * baum->y, .andMask = zero, .orMask = cells, .cursor = cursor, .charset = (char *)"ISO-8859-1", }; if (brlapi__write(baum->brlapi, &wa) == -1) brlapi_perror("baum brlapi_write"); break; } case BAUM_REQ_SetMode: { uint8_t mode, setting; DPRINTF("SetMode\n"); EAT(mode); EAT(setting); /* ignore */ break; } case BAUM_REQ_SetProtocol: { uint8_t protocol; DPRINTF("SetProtocol\n"); EAT(protocol); /* ignore */ break; } case BAUM_REQ_GetDeviceIdentity: { uint8_t identity[17] = { BAUM_RSP_DeviceIdentity, 'B','a','u','m',' ','V','a','r','i','o' }; DPRINTF("GetDeviceIdentity\n"); identity[11] = '0' + baum->x / 10; identity[12] = '0' + baum->x % 10; baum_write_packet(baum, identity, sizeof(identity)); break; } case BAUM_REQ_GetVersionNumber: { uint8_t version[] = { BAUM_RSP_VersionNumber, 1 }; /* ? */ DPRINTF("GetVersionNumber\n"); baum_write_packet(baum, version, sizeof(version)); break; } case BAUM_REQ_GetSerialNumber: { uint8_t serial[] = { BAUM_RSP_SerialNumber, '0','0','0','0','0','0','0','0' }; DPRINTF("GetSerialNumber\n"); baum_write_packet(baum, serial, sizeof(serial)); break; } case BAUM_REQ_GetKeys: { DPRINTF("Get%0#2x\n", req); /* ignore */ break; } default: DPRINTF("unrecognized request %0#2x\n", req); do if (!len--) return 0; while (*cur++ != ESC); cur--; break; } return cur - buf; } /* The other end is writing some data. Store it and try to interpret */ static int baum_write(CharDriverState *chr, const uint8_t *buf, int len) { BaumDriverState *baum = chr->opaque; int tocopy, cur, eaten, orig_len = len; if (!len) return 0; if (!baum->brlapi) return len; while (len) { /* Complete our buffer as much as possible */ tocopy = len; if (tocopy > BUF_SIZE - baum->in_buf_used) tocopy = BUF_SIZE - baum->in_buf_used; memcpy(baum->in_buf + baum->in_buf_used, buf, tocopy); baum->in_buf_used += tocopy; buf += tocopy; len -= tocopy; /* Interpret it as much as possible */ cur = 0; while (cur < baum->in_buf_used && (eaten = baum_eat_packet(baum, baum->in_buf + cur, baum->in_buf_used - cur))) cur += eaten; /* Shift the remainder */ if (cur) { memmove(baum->in_buf, baum->in_buf + cur, baum->in_buf_used - cur); baum->in_buf_used -= cur; } /* And continue if any data left */ } return orig_len; } /* Send the key code to the other end */ static void baum_send_key(BaumDriverState *baum, uint8_t type, uint8_t value) { uint8_t packet[] = { type, value }; DPRINTF("writing key %x %x\n", type, value); baum_write_packet(baum, packet, sizeof(packet)); } /* We got some data on the BrlAPI socket */ static void baum_chr_read(void *opaque) { BaumDriverState *baum = opaque; brlapi_keyCode_t code; int ret; if (!baum->brlapi) return; while ((ret = brlapi__readKey(baum->brlapi, 0, &code)) == 1) { DPRINTF("got key %"BRLAPI_PRIxKEYCODE"\n", code); /* Emulate */ switch (code & BRLAPI_KEY_TYPE_MASK) { case BRLAPI_KEY_TYPE_CMD: switch (code & BRLAPI_KEY_CMD_BLK_MASK) { case BRLAPI_KEY_CMD_ROUTE: baum_send_key(baum, BAUM_RSP_RoutingKey, (code & BRLAPI_KEY_CMD_ARG_MASK)+1); baum_send_key(baum, BAUM_RSP_RoutingKey, 0); break; case 0: switch (code & BRLAPI_KEY_CMD_ARG_MASK) { case BRLAPI_KEY_CMD_FWINLT: baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TL2); baum_send_key(baum, BAUM_RSP_TopKeys, 0); break; case BRLAPI_KEY_CMD_FWINRT: baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TR2); baum_send_key(baum, BAUM_RSP_TopKeys, 0); break; case BRLAPI_KEY_CMD_LNUP: baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TR1); baum_send_key(baum, BAUM_RSP_TopKeys, 0); break; case BRLAPI_KEY_CMD_LNDN: baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TR3); baum_send_key(baum, BAUM_RSP_TopKeys, 0); break; case BRLAPI_KEY_CMD_TOP: baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TL1|BAUM_TR1); baum_send_key(baum, BAUM_RSP_TopKeys, 0); break; case BRLAPI_KEY_CMD_BOT: baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TL3|BAUM_TR3); baum_send_key(baum, BAUM_RSP_TopKeys, 0); break; case BRLAPI_KEY_CMD_TOP_LEFT: baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TL2|BAUM_TR1); baum_send_key(baum, BAUM_RSP_TopKeys, 0); break; case BRLAPI_KEY_CMD_BOT_LEFT: baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TL2|BAUM_TR3); baum_send_key(baum, BAUM_RSP_TopKeys, 0); break; case BRLAPI_KEY_CMD_HOME: baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TL2|BAUM_TR1|BAUM_TR3); baum_send_key(baum, BAUM_RSP_TopKeys, 0); break; case BRLAPI_KEY_CMD_PREFMENU: baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TL1|BAUM_TL3|BAUM_TR1); baum_send_key(baum, BAUM_RSP_TopKeys, 0); break; } } break; case BRLAPI_KEY_TYPE_SYM: break; } } if (ret == -1 && (brlapi_errno != BRLAPI_ERROR_LIBCERR || errno != EINTR)) { brlapi_perror("baum: brlapi_readKey"); brlapi__closeConnection(baum->brlapi); g_free(baum->brlapi); baum->brlapi = NULL; } } static void baum_close(struct CharDriverState *chr) { BaumDriverState *baum = chr->opaque; qemu_free_timer(baum->cellCount_timer); if (baum->brlapi) { brlapi__closeConnection(baum->brlapi); g_free(baum->brlapi); } g_free(baum); } CharDriverState *chr_baum_init(void) { BaumDriverState *baum; CharDriverState *chr; brlapi_handle_t *handle; #ifdef CONFIG_SDL SDL_SysWMinfo info; #endif int tty; baum = g_malloc0(sizeof(BaumDriverState)); baum->chr = chr = g_malloc0(sizeof(CharDriverState)); chr->opaque = baum; chr->chr_write = baum_write; chr->chr_accept_input = baum_accept_input; chr->chr_close = baum_close; handle = g_malloc0(brlapi_getHandleSize()); baum->brlapi = handle; baum->brlapi_fd = brlapi__openConnection(handle, NULL, NULL); if (baum->brlapi_fd == -1) { brlapi_perror("baum_init: brlapi_openConnection"); goto fail_handle; } baum->cellCount_timer = qemu_new_timer_ns(vm_clock, baum_cellCount_timer_cb, baum); if (brlapi__getDisplaySize(handle, &baum->x, &baum->y) == -1) { brlapi_perror("baum_init: brlapi_getDisplaySize"); goto fail; } #ifdef CONFIG_SDL memset(&info, 0, sizeof(info)); SDL_VERSION(&info.version); if (SDL_GetWMInfo(&info)) tty = info.info.x11.wmwindow; else #endif tty = BRLAPI_TTY_DEFAULT; if (brlapi__enterTtyMode(handle, tty, NULL) == -1) { brlapi_perror("baum_init: brlapi_enterTtyMode"); goto fail; } qemu_set_fd_handler(baum->brlapi_fd, baum_chr_read, NULL, baum); return chr; fail: qemu_free_timer(baum->cellCount_timer); brlapi__closeConnection(handle); fail_handle: g_free(handle); g_free(chr); g_free(baum); return NULL; } static void register_types(void) { register_char_driver_qapi("braille", CHARDEV_BACKEND_KIND_BRAILLE, NULL); } type_init(register_types);