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 "StringOp.hh"
9 #include <cstring>
10 
11 using std::string_view;
12 
13 namespace openmsx {
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.remove_prefix(1);
65  break;
66  }
67  str.remove_prefix(1);
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.remove_prefix(1);
81  }
82  return string_view(&*tokenBegin, str.begin() - tokenBegin);
83 }
84 
85 
86 UnicodeKeymap::UnicodeKeymap(string_view keyboardType)
87 {
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.remove_prefix(1);
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 = StringOp::startsWith(token, "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 > 0x1FBAF) {
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  if (token == "--") {
161  // Skip -- for now, it means the character cannot be typed.
162  continue;
163  }
164  bool ok;
165  unsigned rowcol = parseHex(token, ok);
166  if (!ok || rowcol >= 0x100) {
167  throw MSXException(
168  (token.empty() ? "Missing" : "Wrong"),
169  " <ROW><COL> value in keymap file");
170  }
171  if ((rowcol >> 4) >= KeyMatrixPosition::NUM_ROWS) {
172  throw MSXException("Too high row value in keymap file");
173  }
174  if ((rowcol & 0x0F) >= KeyMatrixPosition::NUM_COLS) {
175  throw MSXException("Too high column value in keymap file");
176  }
177  auto pos = KeyMatrixPosition(rowcol);
178 
179  // Parse remaining tokens. It is an optional list of modifier keywords.
180  byte modmask = 0;
181  while (true) {
182  token = nextToken(data);
183  if (token.empty()) {
184  break;
185  } else if (token == "SHIFT") {
186  modmask |= KeyInfo::SHIFT_MASK;
187  } else if (token == "CTRL") {
188  modmask |= KeyInfo::CTRL_MASK;
189  } else if (token == "GRAPH") {
190  modmask |= KeyInfo::GRAPH_MASK;
191  } else if (token == "CAPSLOCK") {
192  modmask |= KeyInfo::CAPS_MASK;
193  } else if (token == "CODE") {
194  modmask |= KeyInfo::CODE_MASK;
195  } else {
196  throw MSXException(
197  "Invalid modifier \"", token, "\" in keymap file");
198  }
199  }
200 
201  if (isDeadKey) {
202  if (modmask != 0) {
203  throw MSXException(
204  "DEADKEY entry in keymap file cannot have modifiers");
205  }
206  deadKeys[deadKeyIndex] = KeyInfo(pos, 0);
207  } else {
208  mapdata.emplace_back(unicode, KeyInfo(pos, modmask));
209  // Note: getRowCol() uses 3 bits for column, rowcol uses 4.
210  relevantMods[pos.getRowCol()] |= modmask;
211  }
212  }
213 
214  ranges::sort(mapdata, LessTupleElement<0>());
215 }
216 
217 } // namespace openmsx
A position (row, column) in a keyboard matrix.
FileContext systemFileContext()
Definition: FileContext.cc:149
uint8_t byte
8 bit unsigned integer
Definition: openmsx.hh:26
UnicodeKeymap(std::string_view keyboardType)
bool startsWith(string_view total, string_view part)
Definition: StringOp.cc:71
UnicodeKeymap::KeyInfo KeyInfo
Definition: Keyboard.cc:40
std::string resolve(std::string_view filename) const
Definition: FileContext.cc:77
constexpr const char *const filename
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
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:573
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).