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