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 "narrow.hh"
7#include "one_of.hh"
8#include "ranges.hh"
9#include "stl.hh"
10#include <optional>
11
12using std::string_view;
13
14namespace openmsx {
15
18[[nodiscard]] static constexpr std::optional<unsigned> parseHex(string_view str)
19{
20 if (str.empty()) {
21 return {};
22 }
23 unsigned value = 0;
24 for (const char c : str) {
25 value *= 16;
26 if ('0' <= c && c <= '9') {
27 value += c - '0';
28 } else if ('A' <= c && c <= 'F') {
29 value += c - 'A' + 10;
30 } else if ('a' <= c && c <= 'f') {
31 value += c - 'a' + 10;
32 } else {
33 return {};
34 }
35 }
36 return value;
37}
38
43[[nodiscard]] static constexpr bool isSep(char c)
44{
45 return c == one_of(',', // comma
46 ' ', '\t', '\r', // whitespace
47 '#'); // comment
48}
49
53static constexpr void skipSep(string_view& str)
54{
55 while (!str.empty()) {
56 const char c = str.front();
57 if (!isSep(c)) break;
58 if (c == '#') {
59 // Skip till end of line.
60 while (!str.empty() && str.front() != '\n') str.remove_prefix(1);
61 break;
62 }
63 str.remove_prefix(1);
64 }
65}
66
70[[nodiscard]] static constexpr string_view nextToken(string_view& str)
71{
72 skipSep(str);
73 const auto* tokenBegin = str.data();
74 while (!str.empty() && str.front() != '\n' && !isSep(str.front())) {
75 // Pop non-separator character.
76 str.remove_prefix(1);
77 }
78 return {tokenBegin, size_t(str.data() - tokenBegin)};
79}
80
81
82UnicodeKeymap::UnicodeKeymap(string_view keyboardType)
83{
84 auto filename = systemFileContext().resolve(
85 tmpStrCat("unicodemaps/unicodemap.", keyboardType));
86 try {
87 File file(filename);
88 auto buf = file.mmap();
89 parseUnicodeKeyMapFile(
90 string_view(reinterpret_cast<const char*>(buf.data()), buf.size()));
91 // TODO in the future we'll require the presence of
92 // "MSX-Video-Characterset" in the keyboard information
93 // file, then we don't need this fallback.
94 if (!msxChars.has_value()) {
95 msxChars.emplace("MSXVID.TXT");
96 }
97 } catch (FileException&) {
98 throw MSXException("Couldn't load unicode keymap file: ", filename);
99 }
100}
101
103{
104 auto m = binary_find(mapData, unicode, {}, &Entry::unicode);
105 return m ? m->keyInfo : KeyInfo();
106}
107
109{
110 assert(n < NUM_DEAD_KEYS);
111 return deadKeys[n];
112}
113
114void UnicodeKeymap::parseUnicodeKeyMapFile(string_view data)
115{
116 ranges::fill(relevantMods, 0);
117
118 while (!data.empty()) {
119 if (data.front() == '\n') {
120 // Next line.
121 data.remove_prefix(1);
122 }
123
124 string_view token = nextToken(data);
125 if (token.empty()) {
126 // Skip empty line.
127 continue;
128 }
129
130 if (token == "MSX-Video-Characterset:") {
131 auto vidFileName = nextToken(data);
132 if (vidFileName.empty()) {
133 throw MSXException("Missing filename for MSX-Video-Characterset");
134 }
135 msxChars.emplace(vidFileName);
136 continue;
137 }
138
139 // Parse first token: a unicode value or the keyword DEADKEY.
140 unsigned unicode = 0;
141 unsigned deadKeyIndex = 0;
142 bool isDeadKey = token.starts_with("DEADKEY");
143 if (isDeadKey) {
144 token.remove_prefix(strlen("DEADKEY"));
145 if (token.empty()) {
146 // The normal keywords are
147 // DEADKEY1 DEADKEY2 DEADKEY3
148 // but for backwards compatibility also still recognize
149 // DEADKEY
150 } else {
151 auto d = parseHex(token);
152 if (!d || *d > NUM_DEAD_KEYS) {
153 throw MSXException(
154 "Wrong deadkey number in keymap file. "
155 "It must be 1..", NUM_DEAD_KEYS);
156 }
157 deadKeyIndex = *d - 1; // Make index 0 based instead of 1 based
158 }
159 } else {
160 auto u = parseHex(token);
161 if (!u || *u > 0x1FBFF) {
162 throw MSXException("Wrong unicode value in keymap file");
163 }
164 unicode = *u;
165 }
166
167 // Parse second token. It must be <ROW><COL>
168 token = nextToken(data);
169 if (token == "--") {
170 // Skip -- for now, it means the character cannot be typed.
171 continue;
172 }
173 auto rowcol = parseHex(token);
174 if (!rowcol || *rowcol >= 0x100) {
175 throw MSXException(
176 (token.empty() ? "Missing" : "Wrong"),
177 " <ROW><COL> value in keymap file");
178 }
179 if ((*rowcol >> 4) >= KeyMatrixPosition::NUM_ROWS) {
180 throw MSXException("Too high row value in keymap file");
181 }
182 if ((*rowcol & 0x0F) >= KeyMatrixPosition::NUM_COLS) {
183 throw MSXException("Too high column value in keymap file");
184 }
185 auto pos = KeyMatrixPosition(narrow_cast<uint8_t>(*rowcol));
186
187 // Parse remaining tokens. It is an optional list of modifier keywords.
188 uint8_t modMask = 0;
189 while (true) {
190 token = nextToken(data);
191 if (token.empty()) {
192 break;
193 } else if (token == "SHIFT") {
194 modMask |= KeyInfo::SHIFT_MASK;
195 } else if (token == "CTRL") {
196 modMask |= KeyInfo::CTRL_MASK;
197 } else if (token == "GRAPH") {
198 modMask |= KeyInfo::GRAPH_MASK;
199 } else if (token == "CAPSLOCK") {
200 modMask |= KeyInfo::CAPS_MASK;
201 } else if (token == "CODE") {
202 modMask |= KeyInfo::CODE_MASK;
203 } else {
204 throw MSXException(
205 "Invalid modifier \"", token, "\" in keymap file");
206 }
207 }
208
209 if (isDeadKey) {
210 if (modMask != 0) {
211 throw MSXException(
212 "DEADKEY entry in keymap file cannot have modifiers");
213 }
214 deadKeys[deadKeyIndex] = KeyInfo(pos, 0);
215 } else {
216 mapData.emplace_back(Entry{unicode, KeyInfo(pos, modMask)});
217 // Note: getRowCol() uses 3 bits for column, rowcol uses 4.
218 relevantMods[pos.getRowCol()] |= modMask;
219 }
220 }
221
222 ranges::sort(mapData, {}, &Entry::unicode);
223}
224
225} // namespace openmsx
std::string resolve(std::string_view filename) const
std::span< const uint8_t > mmap()
Map file in memory.
Definition File.cc:102
static constexpr unsigned NUM_COLS
Columns are in the range [0..NUM_COLS).
static constexpr unsigned NUM_ROWS
Rows are in the range [0..NUM_ROWS).
KeyInfo getDeadKey(unsigned n) const
KeyInfo get(unsigned unicode) const
UnicodeKeymap(std::string_view keyboardType)
This file implemented 3 utility functions:
Definition Autofire.cc:9
const FileContext & systemFileContext()
UnicodeKeymap::KeyInfo KeyInfo
Definition Keyboard.cc:60
constexpr void fill(ForwardRange &&range, const T &value)
Definition ranges.hh:305
constexpr void sort(RandomAccessRange &&range)
Definition ranges.hh:49
auto * binary_find(ForwardRange &&range, const T &value, Compare comp={}, Proj proj={})
Definition ranges.hh:438
TemporaryString tmpStrCat(Ts &&... ts)
Definition strCat.hh:742
static constexpr uint8_t CAPS_MASK
static constexpr uint8_t SHIFT_MASK
static constexpr uint8_t GRAPH_MASK
static constexpr uint8_t CODE_MASK
static constexpr uint8_t CTRL_MASK