xref: /haiku/src/preferences/keymap/Keymap.cpp (revision 4b7e219688450694efc9d1890f83f816758c16d3)
1 /*
2  * Copyright 2004-2011 Haiku Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Sandor Vroemisse
7  *		Jérôme Duval
8  *		Axel Dörfler, axeld@pinc-software.de.
9  */
10 
11 
12 #include "Keymap.h"
13 
14 #include <new>
15 #include <stdio.h>
16 #include <string.h>
17 
18 #include <ByteOrder.h>
19 #include <File.h>
20 #include <FindDirectory.h>
21 #include <Path.h>
22 
23 #include <input_globals.h>
24 
25 
26 static const uint32 kModifierKeys = B_SHIFT_KEY | B_CAPS_LOCK | B_CONTROL_KEY
27 	| B_OPTION_KEY | B_COMMAND_KEY | B_MENU_KEY;
28 
29 
30 static void
31 print_key(char *chars, int32 offset, bool last = false)
32 {
33 	int size = chars[offset++];
34 
35 	switch (size) {
36 		case 0:
37 			// Not mapped
38 			fputs("N/A", stdout);
39 			break;
40 
41 		case 1:
42 			// single-byte UTF-8/ASCII character
43 			fputc(chars[offset], stdout);
44 			break;
45 
46 		default:
47 		{
48 			// 2-, 3-, or 4-byte UTF-8 character
49 			char *str = new char[size + 1];
50 			strncpy(str, &chars[offset], size);
51 			str[size] = 0;
52 			fputs(str, stdout);
53 			delete[] str;
54 			break;
55 		}
56 	}
57 
58 	if (!last)
59 		fputs("\t", stdout);
60 }
61 
62 
63 //	#pragma mark -
64 
65 
66 Keymap::Keymap()
67 	:
68 	fModificationMessage(NULL)
69 {
70 }
71 
72 
73 Keymap::~Keymap()
74 {
75 	delete fModificationMessage;
76 }
77 
78 
79 void
80 Keymap::SetTarget(BMessenger target, BMessage* modificationMessage)
81 {
82 	delete fModificationMessage;
83 
84 	fTarget = target;
85 	fModificationMessage = modificationMessage;
86 }
87 
88 
89 void
90 Keymap::SetName(const char* name)
91 {
92 	strlcpy(fName, name, sizeof(fName));
93 }
94 
95 
96 void
97 Keymap::DumpKeymap()
98 {
99 	if (fKeys.version != 3)
100 		return;
101 
102 	// Print a chart of the normal, shift, control, option, option+shift,
103 	// Caps, Caps+shift, Caps+option, and Caps+option+shift keys.
104 	puts("Key #\tn\ts\tc\to\tos\tC\tCs\tCo\tCos\n");
105 
106 	for (uint8 i = 0; i < 128; i++) {
107 		printf(" 0x%02x\t", i);
108 		print_key(fChars, fKeys.normal_map[i]);
109 		print_key(fChars, fKeys.shift_map[i]);
110 		print_key(fChars, fKeys.control_map[i]);
111 		print_key(fChars, fKeys.option_map[i]);
112 		print_key(fChars, fKeys.option_shift_map[i]);
113 		print_key(fChars, fKeys.caps_map[i]);
114 		print_key(fChars, fKeys.caps_shift_map[i]);
115 		print_key(fChars, fKeys.option_caps_map[i]);
116 		print_key(fChars, fKeys.option_caps_shift_map[i], true);
117 		fputs("\n", stdout);
118 	}
119 }
120 
121 
122 //!	Load a map from a file
123 status_t
124 Keymap::Load(const entry_ref& ref)
125 {
126 	BEntry entry;
127 	status_t status = entry.SetTo(&ref, true);
128 	if (status != B_OK)
129 		return status;
130 
131 	BFile file(&entry, B_READ_ONLY);
132 	status = SetTo(file);
133 	if (status != B_OK)
134 		return status;
135 
136 	// fetch name from attribute and fall back to filename
137 
138 	ssize_t bytesRead = file.ReadAttr("keymap:name", B_STRING_TYPE, 0, fName,
139 		sizeof(fName));
140 	if (bytesRead > 0)
141 		fName[bytesRead] = '\0';
142 	else
143 		strlcpy(fName, ref.name, sizeof(fName));
144 
145 	return B_OK;
146 }
147 
148 
149 //!	We save a map to a file
150 status_t
151 Keymap::Save(const entry_ref& ref)
152 {
153 	BFile file;
154 	status_t status = file.SetTo(&ref,
155 		B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
156 	if (status != B_OK) {
157 		printf("error %s\n", strerror(status));
158 		return status;
159 	}
160 
161 	for (uint32 i = 0; i < sizeof(fKeys) / 4; i++)
162 		((uint32*)&fKeys)[i] = B_HOST_TO_BENDIAN_INT32(((uint32*)&fKeys)[i]);
163 
164 	ssize_t bytesWritten = file.Write(&fKeys, sizeof(fKeys));
165 	if (bytesWritten < (ssize_t)sizeof(fKeys))
166 		status = bytesWritten < 0 ? bytesWritten : B_IO_ERROR;
167 
168 	for (uint32 i = 0; i < sizeof(fKeys) / 4; i++)
169 		((uint32*)&fKeys)[i] = B_BENDIAN_TO_HOST_INT32(((uint32*)&fKeys)[i]);
170 
171 	if (status == B_OK) {
172 		fCharsSize = B_HOST_TO_BENDIAN_INT32(fCharsSize);
173 
174 		bytesWritten = file.Write(&fCharsSize, sizeof(uint32));
175 		if (bytesWritten < (ssize_t)sizeof(uint32))
176 			status = bytesWritten < 0 ? bytesWritten : B_IO_ERROR;
177 
178 		fCharsSize = B_BENDIAN_TO_HOST_INT32(fCharsSize);
179 	}
180 
181 	if (status == B_OK) {
182 		bytesWritten = file.Write(fChars, fCharsSize);
183 		if (bytesWritten < (ssize_t)fCharsSize)
184 			status = bytesWritten < 0 ? bytesWritten : B_IO_ERROR;
185 	}
186 
187 	if (status == B_OK) {
188 		file.WriteAttr("keymap:name", B_STRING_TYPE, 0, fName, strlen(fName));
189 			// Failing would be non-fatal
190 	}
191 
192 	return status;
193 }
194 
195 
196 status_t
197 Keymap::SetModifier(uint32 keyCode, uint32 modifier)
198 {
199 	const uint32 kSingleModifierKeys = B_LEFT_SHIFT_KEY | B_RIGHT_SHIFT_KEY
200 		| B_LEFT_COMMAND_KEY | B_RIGHT_COMMAND_KEY | B_LEFT_CONTROL_KEY
201 		| B_RIGHT_CONTROL_KEY | B_LEFT_OPTION_KEY | B_RIGHT_OPTION_KEY;
202 
203 	if ((modifier & kSingleModifierKeys) != 0)
204 		modifier &= kSingleModifierKeys;
205 	else if ((modifier & kModifierKeys) != 0)
206 		modifier &= kModifierKeys;
207 
208 	if (modifier == B_CAPS_LOCK)
209 		fKeys.caps_key = keyCode;
210 	else if (modifier == B_NUM_LOCK)
211 		fKeys.num_key = keyCode;
212 	else if (modifier == B_SCROLL_LOCK)
213 		fKeys.scroll_key = keyCode;
214 	else if (modifier == B_LEFT_SHIFT_KEY)
215 		fKeys.left_shift_key = keyCode;
216 	else if (modifier == B_RIGHT_SHIFT_KEY)
217 		fKeys.right_shift_key = keyCode;
218 	else if (modifier == B_LEFT_COMMAND_KEY)
219 		fKeys.left_command_key = keyCode;
220 	else if (modifier == B_RIGHT_COMMAND_KEY)
221 		fKeys.right_command_key = keyCode;
222 	else if (modifier == B_LEFT_CONTROL_KEY)
223 		fKeys.left_control_key = keyCode;
224 	else if (modifier == B_RIGHT_CONTROL_KEY)
225 		fKeys.right_control_key = keyCode;
226 	else if (modifier == B_LEFT_OPTION_KEY)
227 		fKeys.left_option_key = keyCode;
228 	else if (modifier == B_RIGHT_OPTION_KEY)
229 		fKeys.right_option_key = keyCode;
230 	else if (modifier == B_MENU_KEY)
231 		fKeys.menu_key = keyCode;
232 	else
233 		return B_BAD_VALUE;
234 
235 	if (fModificationMessage != NULL)
236 		fTarget.SendMessage(fModificationMessage);
237 
238 	return B_OK;
239 }
240 
241 
242 //! Enables/disables the "deadness" of the given keycode/modifier combo.
243 void
244 Keymap::SetDeadKeyEnabled(uint32 keyCode, uint32 modifiers, bool enabled)
245 {
246 	uint32 tableMask = 0;
247 	int32 offset = Offset(keyCode, modifiers, &tableMask);
248 	uint8 deadKeyIndex = DeadKeyIndex(offset);
249 	if (deadKeyIndex > 0) {
250 		uint32* deadTables[] = {
251 			&fKeys.acute_tables,
252 			&fKeys.grave_tables,
253 			&fKeys.circumflex_tables,
254 			&fKeys.dieresis_tables,
255 			&fKeys.tilde_tables
256 		};
257 
258 		if (enabled)
259 			(*deadTables[deadKeyIndex - 1]) |= tableMask;
260 		else
261 			(*deadTables[deadKeyIndex - 1]) &= ~tableMask;
262 
263 		if (fModificationMessage != NULL)
264 			fTarget.SendMessage(fModificationMessage);
265 	}
266 }
267 
268 
269 /*! Returns the trigger character string that is currently set for the dead
270 	key with the given index (which is 1..5).
271 */
272 void
273 Keymap::GetDeadKeyTrigger(dead_key_index deadKeyIndex, BString& outTrigger)
274 {
275 	outTrigger = "";
276 	if (deadKeyIndex < 1 || deadKeyIndex > 5)
277 		return;
278 
279 	int32 deadOffsets[] = {
280 		fKeys.acute_dead_key[1],
281 		fKeys.grave_dead_key[1],
282 		fKeys.circumflex_dead_key[1],
283 		fKeys.dieresis_dead_key[1],
284 		fKeys.tilde_dead_key[1]
285 	};
286 
287 	int32 offset = deadOffsets[deadKeyIndex - 1];
288 	if (offset < 0 || offset >= (int32)fCharsSize)
289 		return;
290 
291 	uint32 deadNumBytes = fChars[offset];
292 	if (!deadNumBytes)
293 		return;
294 
295 	outTrigger.SetTo(&fChars[offset + 1], deadNumBytes);
296 }
297 
298 
299 /*! Sets the trigger character string that shall be used for the dead key
300 	with the given index (which is 1..5).
301 */
302 void
303 Keymap::SetDeadKeyTrigger(dead_key_index deadKeyIndex, const BString& trigger)
304 {
305 	if (deadKeyIndex < 1 || deadKeyIndex > 5)
306 		return;
307 
308 	int32 deadOffsets[] = {
309 		fKeys.acute_dead_key[1],
310 		fKeys.grave_dead_key[1],
311 		fKeys.circumflex_dead_key[1],
312 		fKeys.dieresis_dead_key[1],
313 		fKeys.tilde_dead_key[1]
314 	};
315 
316 	int32 offset = deadOffsets[deadKeyIndex - 1];
317 	if (offset < 0 || offset >= (int32)fCharsSize)
318 		return;
319 
320 	if (_SetChars(offset, trigger.String(), trigger.Length())) {
321 		// reset modifier table such that new dead key is enabled wherever
322 		// it is available
323 		uint32* deadTables[] = {
324 			&fKeys.acute_tables,
325 			&fKeys.grave_tables,
326 			&fKeys.circumflex_tables,
327 			&fKeys.dieresis_tables,
328 			&fKeys.tilde_tables
329 		};
330 		*deadTables[deadKeyIndex - 1]
331 			= B_NORMAL_TABLE | B_SHIFT_TABLE | B_CONTROL_TABLE | B_OPTION_TABLE
332 				| B_OPTION_SHIFT_TABLE | B_CAPS_TABLE | B_CAPS_SHIFT_TABLE
333 				| B_OPTION_CAPS_TABLE | B_OPTION_CAPS_SHIFT_TABLE;
334 
335 		if (fModificationMessage != NULL)
336 			fTarget.SendMessage(fModificationMessage);
337 	}
338 }
339 
340 
341 status_t
342 Keymap::RestoreSystemDefault()
343 {
344 	BPath path;
345 	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
346 	if (status != B_OK)
347 		return status;
348 
349 	path.Append("Key_map");
350 
351 	BEntry entry(path.Path());
352 	entry.Remove();
353 
354 	return Use();
355 }
356 
357 
358 //! We make our input server use the map in /boot/home/config/settings/Keymap
359 status_t
360 Keymap::Use()
361 {
362 	status_t result = _restore_key_map_();
363 	if (result == B_OK)
364 		set_keyboard_locks(modifiers());
365 	return result;
366 }
367 
368 
369 void
370 Keymap::SetKey(uint32 keyCode, uint32 modifiers, int8 deadKey,
371 	const char* bytes, int32 numBytes)
372 {
373 	int32 offset = Offset(keyCode, modifiers);
374 	if (offset < 0)
375 		return;
376 
377 	if (numBytes == -1)
378 		numBytes = strlen(bytes);
379 	if (numBytes > 6)
380 		return;
381 
382 	if (_SetChars(offset, bytes, numBytes)) {
383 		if (fModificationMessage != NULL)
384 			fTarget.SendMessage(fModificationMessage);
385 	}
386 }
387 
388 
389 Keymap&
390 Keymap::operator=(const Keymap& other)
391 {
392 	if (this == &other)
393 		return *this;
394 
395 	delete[] fChars;
396 	delete fModificationMessage;
397 
398 	fChars = new(std::nothrow) char[other.fCharsSize];
399 	if (fChars != NULL) {
400 		memcpy(fChars, other.fChars, other.fCharsSize);
401 		fCharsSize = other.fCharsSize;
402 	} else
403 		fCharsSize = 0;
404 
405 	memcpy(&fKeys, &other.fKeys, sizeof(key_map));
406 	strlcpy(fName, other.fName, sizeof(fName));
407 
408 	fTarget = other.fTarget;
409 
410 	if (other.fModificationMessage != NULL)
411 		fModificationMessage = new BMessage(*other.fModificationMessage);
412 
413 	return *this;
414 }
415 
416 
417 bool
418 Keymap::_SetChars(int32 offset, const char* bytes, int32 numBytes)
419 {
420 	int32 oldNumBytes = fChars[offset];
421 
422 	if (oldNumBytes == numBytes
423 		&& !memcmp(&fChars[offset + 1], bytes, numBytes)) {
424 		// nothing to do
425 		return false;
426 	}
427 
428 	int32 diff = numBytes - oldNumBytes;
429 	if (diff != 0) {
430 		fCharsSize += diff;
431 
432 		if (diff > 0) {
433 			// make space for the new data
434 			char* chars = new(std::nothrow) char[fCharsSize];
435 			if (chars != NULL) {
436 				memcpy(chars, fChars, offset + oldNumBytes + 1);
437 				memcpy(&chars[offset + 1 + numBytes],
438 					&fChars[offset + 1 + oldNumBytes],
439 					fCharsSize - 2 - offset - diff);
440 				delete[] fChars;
441 				fChars = chars;
442 			} else
443 				return false;
444 		} else if (diff < 0) {
445 			// shrink table
446 			memmove(&fChars[offset + numBytes], &fChars[offset + oldNumBytes],
447 				fCharsSize - offset - 2 - diff);
448 		}
449 
450 		// update offsets
451 		int32* data = fKeys.control_map;
452 		int32 size = sizeof(fKeys.control_map) / 4 * 9
453 			+ sizeof(fKeys.acute_dead_key) / 4 * 5;
454 		for (int32 i = 0; i < size; i++) {
455 			if (data[i] > offset)
456 				data[i] += diff;
457 		}
458 	}
459 
460 	memcpy(&fChars[offset + 1], bytes, numBytes);
461 	fChars[offset] = numBytes;
462 
463 	return true;
464 }
465