openMSX
UnicodeKeymap.cc
Go to the documentation of this file.
1 #include "UnicodeKeymap.hh"
2 #include "MSXException.hh"
3 #include "File.hh"
4 #include "FileContext.hh"
5 #include "FileException.hh"
6 #include "ranges.hh"
7 #include "stl.hh"
8 #include <cstring>
9 
10 namespace openmsx {
11 
12 const unsigned UnicodeKeymap::NUM_DEAD_KEYS;
13 
14 
19 static unsigned parseHex(string_view str, bool& ok)
20 {
21  if (str.empty()) {
22  ok = false;
23  return 0;
24  }
25  unsigned value = 0;
26  for (const char c : str) {
27  value *= 16;
28  if ('0' <= c && c <= '9') {
29  value += c - '0';
30  } else if ('A' <= c && c <= 'F') {
31  value += c - 'A' + 10;
32  } else if ('a' <= c && c <= 'f') {
33  value += c - 'a' + 10;
34  } else {
35  ok = false;
36  return 0;
37  }
38  }
39  ok = true;
40  return value;
41 }
42 
47 static inline bool isSep(char c)
48 {
49  return c == ',' // comma
50  || c == ' ' || c == '\t' || c == '\r' // whitespace
51  || c == '#'; // comment
52 }
53 
57 static void skipSep(string_view& str)
58 {
59  while (!str.empty()) {
60  const char c = str.front();
61  if (!isSep(c)) break;
62  if (c == '#') {
63  // Skip till end of line.
64  while (!str.empty() && str.front() != '\n') str.pop_front();
65  break;
66  }
67  str.pop_front();
68  }
69 }
70 
74 static string_view nextToken(string_view& str)
75 {
76  skipSep(str);
77  auto tokenBegin = str.begin();
78  while (!str.empty() && str.front() != '\n' && !isSep(str.front())) {
79  // Pop non-separator character.
80  str.pop_front();
81  }
82  return string_view(tokenBegin, str.begin());
83 }
84 
85 
87 {
88  auto filename = systemFileContext().resolve(
89  strCat("unicodemaps/unicodemap.", keyboardType));
90  try {
91  File file(filename);
92  auto buf = file.mmap();
93  parseUnicodeKeymapfile(
94  string_view(reinterpret_cast<const char*>(buf.data()), buf.size()));
95  } catch (FileException&) {
96  throw MSXException("Couldn't load unicode keymap file: ", filename);
97  }
98 }
99 
101 {
102  auto it = ranges::lower_bound(mapdata, unicode, LessTupleElement<0>());
103  return ((it != end(mapdata)) && (it->first == unicode))
104  ? it->second : KeyInfo();
105 }
106 
108 {
109  assert(n < NUM_DEAD_KEYS);
110  return deadKeys[n];
111 }
112 
113 void UnicodeKeymap::parseUnicodeKeymapfile(string_view data)
114 {
115  memset(relevantMods, 0, sizeof(relevantMods));
116 
117  while (!data.empty()) {
118  if (data.front() == '\n') {
119  // Next line.
120  data.pop_front();
121  }
122 
123  string_view token = nextToken(data);
124  if (token.empty()) {
125  // Skip empty line.
126  continue;
127  }
128 
129  // Parse first token: a unicode value or the keyword DEADKEY.
130  unsigned unicode = 0;
131  unsigned deadKeyIndex = 0;
132  bool isDeadKey = token.starts_with("DEADKEY");
133  if (isDeadKey) {
134  token.remove_prefix(strlen("DEADKEY"));
135  if (token.empty()) {
136  // The normal keywords are
137  // DEADKEY1 DEADKEY2 DEADKEY3
138  // but for backwards compatibility also still recognize
139  // DEADKEY
140  } else {
141  bool ok;
142  deadKeyIndex = parseHex(token, ok);
143  deadKeyIndex--; // Make index 0 based instead of 1 based
144  if (!ok || deadKeyIndex >= NUM_DEAD_KEYS) {
145  throw MSXException(
146  "Wrong deadkey number in keymap file. "
147  "It must be 1..", NUM_DEAD_KEYS);
148  }
149  }
150  } else {
151  bool ok;
152  unicode = parseHex(token, ok);
153  if (!ok || unicode > 0xFFFF) {
154  throw MSXException("Wrong unicode value in keymap file");
155  }
156  }
157 
158  // Parse second token. It must be <ROW><COL>
159  token = nextToken(data);
160  bool ok;
161  unsigned rowcol = parseHex(token, ok);
162  if (!ok || rowcol >= 0x100) {
163  throw MSXException(
164  (token.empty() ? "Missing" : "Wrong"),
165  " <ROW><COL> value in keymap file");
166  }
167  if ((rowcol >> 4) >= KeyMatrixPosition::NUM_ROWS) {
168  throw MSXException("Too high row value in keymap file");
169  }
170  if ((rowcol & 0x0F) >= KeyMatrixPosition::NUM_COLS) {
171  throw MSXException("Too high column value in keymap file");
172  }
173  auto pos = KeyMatrixPosition(rowcol);
174 
175  // Parse remaining tokens. It is an optional list of modifier keywords.
176  byte modmask = 0;
177  while (true) {
178  token = nextToken(data);
179  if (token.empty()) {
180  break;
181  } else if (token == "SHIFT") {
182  modmask |= KeyInfo::SHIFT_MASK;
183  } else if (token == "CTRL") {
184  modmask |= KeyInfo::CTRL_MASK;
185  } else if (token == "GRAPH") {
186  modmask |= KeyInfo::GRAPH_MASK;
187  } else if (token == "CAPSLOCK") {
188  modmask |= KeyInfo::CAPS_MASK;
189  } else if (token == "CODE") {
190  modmask |= KeyInfo::CODE_MASK;
191  } else {
192  throw MSXException(
193  "Invalid modifier \"", token, "\" in keymap file");
194  }
195  }
196 
197  if (isDeadKey) {
198  if (modmask != 0) {
199  throw MSXException(
200  "DEADKEY entry in keymap file cannot have modifiers");
201  }
202  deadKeys[deadKeyIndex] = KeyInfo(pos, 0);
203  } else {
204  mapdata.emplace_back(unicode, KeyInfo(pos, modmask));
205  // Note: getRowCol() uses 3 bits for column, rowcol uses 4.
206  relevantMods[pos.getRowCol()] |= modmask;
207  }
208  }
209 
210  ranges::sort(mapdata, LessTupleElement<0>());
211 }
212 
213 } // namespace openmsx
bool starts_with(string_view x) const
Definition: string_view.cc:116
A position (row, column) in a keyboard matrix.
FileContext systemFileContext()
Definition: FileContext.cc:148
void pop_front()
Definition: string_view.hh:75
uint8_t byte
8 bit unsigned integer
Definition: openmsx.hh:26
UnicodeKeymap::KeyInfo KeyInfo
Definition: Keyboard.cc:38
std::string resolve(string_view filename) const
Definition: FileContext.cc:76
UnicodeKeymap(string_view keyboardType)
constexpr auto data(C &c) -> decltype(c.data())
Definition: span.hh:69
char front() const
Definition: string_view.hh:55
void remove_prefix(size_type n)
Definition: string_view.hh:65
constexpr size_t strlen(const char *s) noexcept
Definition: cstd.hh:135
static constexpr byte CODE_MASK
static constexpr byte GRAPH_MASK
void sort(RandomAccessRange &&range)
Definition: ranges.hh:35
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
static constexpr byte CTRL_MASK
static constexpr unsigned NUM_ROWS
Rows are in the range [0..NUM_ROWS).
KeyInfo getDeadkey(unsigned n) const
static constexpr byte CAPS_MASK
bool empty() const
Definition: string_view.hh:45
This class implements a (close approximation) of the std::string_view class.
Definition: string_view.hh:16
auto begin() const
Definition: string_view.hh:38
auto lower_bound(ForwardRange &&range, const T &value)
Definition: ranges.hh:71
KeyInfo get(unsigned unicode) const
std::string strCat(Ts &&...ts)
Definition: strCat.hh:577
span< uint8_t > mmap()
Map file in memory.
Definition: File.cc:93
static constexpr byte SHIFT_MASK
static constexpr unsigned NUM_COLS
Columns are in the range [0..NUM_COLS).
auto end(const string_view &x)
Definition: string_view.hh:152