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