summaryrefslogtreecommitdiff
path: root/src/dged/keyboard.c
blob: 63b7b6ea9e59de9db653853c76552e71963b8f75 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#define _DEFAULT_SOURCE
#include "keyboard.h"
#include "reactor.h"
#include "stdio.h"
#include "utf8.h"

#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

struct keyboard keyboard_create(struct reactor *reactor) {
  struct termios term;
  tcgetattr(0, &term);

  // set input to non-blocking
  term.c_cc[VMIN] = 0;
  term.c_cc[VTIME] = 0;
  tcsetattr(0, TCSADRAIN, &term);
  return keyboard_create_fd(reactor, STDIN_FILENO);
}

struct keyboard keyboard_create_fd(struct reactor *reactor, int fd) {
  return (struct keyboard){
      .fd = fd,
      .reactor_event_id = reactor_register_interest(reactor, fd, ReadInterest),
  };
}

void parse_keys(uint8_t *bytes, uint32_t nbytes, struct key *out_keys,
                uint32_t *out_nkeys) {
  // TODO: can be optimized if "bytes" contains no special chars
  uint32_t nkps = 0;
  for (uint32_t bytei = 0; bytei < nbytes; ++bytei) {
    uint8_t b = bytes[bytei];
    bool has_more = bytei + 1 < nbytes;
    uint8_t next = has_more ? bytes[bytei + 1] : 0;

    struct key *kp = &out_keys[nkps];
    if (b == 0x1b) { // meta
      kp->start = bytei;
      kp->mod = Meta;
    } else if (has_more && isalnum(next) && kp->mod & Meta &&
               (b == '[' ||
                b == '0')) { // special char (function keys, pgdn, etc)
      kp->mod = Spec;
    } else if (b == 0x7f) { // ?
      kp->mod |= Ctrl;
      kp->key = '?';
      kp->start = bytei;
      kp->end = bytei + 1;
      ++nkps;
    } else if (iscntrl(b)) { // ctrl char
      kp->mod |= Ctrl;
      kp->key = b | 0x40;
      kp->start = bytei;
      kp->end = bytei + 1;
      ++nkps;
    } else {
      if (kp->mod & Spec && b == '~') {
        // skip tilde in special chars
        kp->end = bytei + 1;
        ++nkps;
      } else if (kp->mod & Meta || kp->mod & Spec) {
        kp->key = b;
        kp->end = bytei + 1;

        if (kp->mod & Meta || (kp->mod & Spec && next != '~')) {
          ++nkps;
        }
      } else if (utf8_byte_is_unicode_continuation(b)) {
        // do nothing for these
      } else if (utf8_byte_is_unicode_start(b)) { // unicode char
        kp->mod = None;
        kp->key = 0;
        kp->start = bytei;
        kp->end = bytei + utf8_nbytes(bytes + bytei, nbytes - bytei, 1);
        ++nkps;
      } else { // normal ASCII char
        kp->mod = None;
        kp->key = b;
        kp->start = bytei;
        kp->end = bytei + 1;
        ++nkps;
      }
    }
  }

  *out_nkeys = nkps;
}

struct keyboard_update keyboard_update(struct keyboard *kbd,
                                       struct reactor *reactor,
                                       void *(*frame_alloc)(size_t)) {

  struct keyboard_update upd = (struct keyboard_update){
      .keys = NULL,
      .nkeys = 0,
      .nbytes = 0,
      .raw = NULL,
  };

  // check if there is anything to do
  if (!reactor_poll_event(reactor, kbd->reactor_event_id)) {
    return upd;
  }

  // read all input in chunks of `bufsize` bytes
  const uint32_t bufsize = 128;
  uint8_t *buf = malloc(bufsize), *writepos = buf;
  int nbytes = 0, nread = 0;
  while ((nread = read(kbd->fd, writepos, bufsize)) == bufsize) {
    nbytes += bufsize;
    buf = realloc(buf, nbytes + bufsize);
    writepos = buf + nbytes;
  }

  nbytes += nread;

  if (nbytes > 0) {
    upd.raw = frame_alloc(nbytes);

    if (upd.raw == NULL) {
      fprintf(stderr, "failed to allocate buffer of %d bytes\n", nbytes);
      free(buf);
      return upd;
    }

    upd.nbytes = nbytes;
    memcpy(upd.raw, buf, nbytes);
    upd.keys = frame_alloc(sizeof(struct key) * nbytes);
    memset(upd.keys, 0, sizeof(struct key) * nbytes);

    parse_keys(upd.raw, upd.nbytes, upd.keys, &upd.nkeys);
  }

  free(buf);
  return upd;
}

bool key_equal_char(struct key *key, uint8_t mod, uint8_t c) {
  return key->key == c && key->mod == mod;
}

bool key_equal(struct key *key1, struct key *key2) {
  return key_equal_char(key1, key2->mod, key2->key);
}

uint32_t key_name(struct key *key, char *buf, size_t capacity) {
  const char *mod = "";
  switch (key->mod) {
  case Ctrl:
    mod = "c-";
    break;
  case Meta:
    mod = "m-";
    break;
  case Spec:
    mod = "special-";
    break;
  }

  snprintf(buf, capacity, "%s%c", mod, tolower(key->key));
}