⛏️ index : haiku.git

/*
 * Copyright 2004-2011 Haiku Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Sandor Vroemisse
 *		Jérôme Duval
 *		Axel Dörfler, axeld@pinc-software.de.
 */


#include "Keymap.h"

#include <new>
#include <stdio.h>
#include <string.h>

#include <ByteOrder.h>
#include <File.h>
#include <FindDirectory.h>
#include <Path.h>

#include <input_globals.h>


static const uint32 kModifierKeys = B_SHIFT_KEY | B_CAPS_LOCK | B_CONTROL_KEY
	| B_OPTION_KEY | B_COMMAND_KEY | B_MENU_KEY;


static void
print_key(char* chars, int32 offset, bool last = false)
{
	int size = chars[offset++];

	switch (size) {
		case 0:
			// Not mapped
			fputs("N/A", stdout);
			break;

		case 1:
			// single-byte UTF-8/ASCII character
			fputc(chars[offset], stdout);
			break;

		default:
		{
			// 2-, 3-, or 4-byte UTF-8 character
			char* str = new char[size + 1];
			strncpy(str, &chars[offset], size);
			str[size] = 0;
			fputs(str, stdout);
			delete[] str;
			break;
		}
	}

	if (!last)
		fputs("\t", stdout);
}


//	#pragma mark -


Keymap::Keymap()
	:
	fModificationMessage(NULL)
{
}


Keymap::~Keymap()
{
	delete fModificationMessage;
}


void
Keymap::SetTarget(BMessenger target, BMessage* modificationMessage)
{
	delete fModificationMessage;

	fTarget = target;
	fModificationMessage = modificationMessage;
}


void
Keymap::SetName(const char* name)
{
	strlcpy(fName, name, sizeof(fName));
}


void
Keymap::DumpKeymap()
{
	if (fKeys.version != 3)
		return;

	// Print a chart of the normal, shift, control, option, option+shift,
	// Caps, Caps+shift, Caps+option, and Caps+option+shift keys.
	puts("Key #\tn\ts\tc\to\tos\tC\tCs\tCo\tCos\n");

	for (uint8 i = 0; i < 128; i++) {
		printf(" 0x%02x\t", i);
		print_key(fChars, fKeys.normal_map[i]);
		print_key(fChars, fKeys.shift_map[i]);
		print_key(fChars, fKeys.control_map[i]);
		print_key(fChars, fKeys.option_map[i]);
		print_key(fChars, fKeys.option_shift_map[i]);
		print_key(fChars, fKeys.caps_map[i]);
		print_key(fChars, fKeys.caps_shift_map[i]);
		print_key(fChars, fKeys.option_caps_map[i]);
		print_key(fChars, fKeys.option_caps_shift_map[i], true);
		fputs("\n", stdout);
	}
}


//!	Load a map from a file
status_t
Keymap::Load(const entry_ref& ref)
{
	BEntry entry;
	status_t status = entry.SetTo(&ref, true);
	if (status != B_OK)
		return status;

	BFile file(&entry, B_READ_ONLY);
	status = SetTo(file);
	if (status != B_OK)
		return status;

	// fetch name from attribute and fall back to filename

	ssize_t bytesRead = file.ReadAttr("keymap:name", B_STRING_TYPE, 0, fName,
		sizeof(fName));
	if (bytesRead > 0)
		fName[bytesRead] = '\0';
	else
		strlcpy(fName, ref.name, sizeof(fName));

	return B_OK;
}


//!	We save a map to a file
status_t
Keymap::Save(const entry_ref& ref)
{
	BFile file;
	status_t status = file.SetTo(&ref,
		B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
	if (status != B_OK) {
		printf("error %s\n", strerror(status));
		return status;
	}

	for (uint32 i = 0; i < sizeof(fKeys) / 4; i++)
		((uint32*)&fKeys)[i] = B_HOST_TO_BENDIAN_INT32(((uint32*)&fKeys)[i]);

	ssize_t bytesWritten = file.Write(&fKeys, sizeof(fKeys));
	if (bytesWritten < (ssize_t)sizeof(fKeys))
		status = bytesWritten < 0 ? bytesWritten : B_IO_ERROR;

	for (uint32 i = 0; i < sizeof(fKeys) / 4; i++)
		((uint32*)&fKeys)[i] = B_BENDIAN_TO_HOST_INT32(((uint32*)&fKeys)[i]);

	if (status == B_OK) {
		fCharsSize = B_HOST_TO_BENDIAN_INT32(fCharsSize);

		bytesWritten = file.Write(&fCharsSize, sizeof(uint32));
		if (bytesWritten < (ssize_t)sizeof(uint32))
			status = bytesWritten < 0 ? bytesWritten : B_IO_ERROR;

		fCharsSize = B_BENDIAN_TO_HOST_INT32(fCharsSize);
	}

	if (status == B_OK) {
		bytesWritten = file.Write(fChars, fCharsSize);
		if (bytesWritten < (ssize_t)fCharsSize)
			status = bytesWritten < 0 ? bytesWritten : B_IO_ERROR;
	}

	if (status == B_OK) {
		const BString name(fName);
		file.WriteAttrString("keymap:name", &name);
			// Failing would be non-fatal
	}

	return status;
}


status_t
Keymap::SetModifier(uint32 keyCode, uint32 modifier)
{
	const uint32 kSingleModifierKeys = B_LEFT_SHIFT_KEY | B_RIGHT_SHIFT_KEY
		| B_LEFT_COMMAND_KEY | B_RIGHT_COMMAND_KEY | B_LEFT_CONTROL_KEY
		| B_RIGHT_CONTROL_KEY | B_LEFT_OPTION_KEY | B_RIGHT_OPTION_KEY;

	if ((modifier & kSingleModifierKeys) != 0)
		modifier &= kSingleModifierKeys;
	else if ((modifier & kModifierKeys) != 0)
		modifier &= kModifierKeys;

	if (modifier == B_CAPS_LOCK)
		fKeys.caps_key = keyCode;
	else if (modifier == B_NUM_LOCK)
		fKeys.num_key = keyCode;
	else if (modifier == B_SCROLL_LOCK)
		fKeys.scroll_key = keyCode;
	else if (modifier == B_LEFT_SHIFT_KEY)
		fKeys.left_shift_key = keyCode;
	else if (modifier == B_RIGHT_SHIFT_KEY)
		fKeys.right_shift_key = keyCode;
	else if (modifier == B_LEFT_COMMAND_KEY)
		fKeys.left_command_key = keyCode;
	else if (modifier == B_RIGHT_COMMAND_KEY)
		fKeys.right_command_key = keyCode;
	else if (modifier == B_LEFT_CONTROL_KEY)
		fKeys.left_control_key = keyCode;
	else if (modifier == B_RIGHT_CONTROL_KEY)
		fKeys.right_control_key = keyCode;
	else if (modifier == B_LEFT_OPTION_KEY)
		fKeys.left_option_key = keyCode;
	else if (modifier == B_RIGHT_OPTION_KEY)
		fKeys.right_option_key = keyCode;
	else if (modifier == B_MENU_KEY)
		fKeys.menu_key = keyCode;
	else
		return B_BAD_VALUE;

	if (fModificationMessage != NULL)
		fTarget.SendMessage(fModificationMessage);

	return B_OK;
}


//! Enables/disables the "deadness" of the given keycode/modifier combo.
void
Keymap::SetDeadKeyEnabled(uint32 keyCode, uint32 modifiers, bool enabled)
{
	uint32 tableMask = 0;
	int32 offset = Offset(keyCode, modifiers, &tableMask);
	uint8 deadKeyIndex = DeadKeyIndex(offset);
	if (deadKeyIndex > 0) {
		uint32* deadTables[] = {
			&fKeys.acute_tables,
			&fKeys.grave_tables,
			&fKeys.circumflex_tables,
			&fKeys.dieresis_tables,
			&fKeys.tilde_tables
		};

		if (enabled)
			(*deadTables[deadKeyIndex - 1]) |= tableMask;
		else
			(*deadTables[deadKeyIndex - 1]) &= ~tableMask;

		if (fModificationMessage != NULL)
			fTarget.SendMessage(fModificationMessage);
	}
}


/*! Returns the trigger character string that is currently set for the dead
	key with the given index (which is 1..5).
*/
void
Keymap::GetDeadKeyTrigger(dead_key_index deadKeyIndex, BString& outTrigger)
{
	outTrigger = "";
	if (deadKeyIndex < 1 || deadKeyIndex > 5)
		return;

	int32 deadOffsets[] = {
		fKeys.acute_dead_key[1],
		fKeys.grave_dead_key[1],
		fKeys.circumflex_dead_key[1],
		fKeys.dieresis_dead_key[1],
		fKeys.tilde_dead_key[1]
	};

	int32 offset = deadOffsets[deadKeyIndex - 1];
	if (offset < 0 || offset >= (int32)fCharsSize)
		return;

	uint32 deadNumBytes = fChars[offset];
	if (!deadNumBytes)
		return;

	outTrigger.SetTo(&fChars[offset + 1], deadNumBytes);
}


/*! Sets the trigger character string that shall be used for the dead key
	with the given index (which is 1..5).
*/
void
Keymap::SetDeadKeyTrigger(dead_key_index deadKeyIndex, const BString& trigger)
{
	if (deadKeyIndex < 1 || deadKeyIndex > 5)
		return;

	int32 deadOffsets[] = {
		fKeys.acute_dead_key[1],
		fKeys.grave_dead_key[1],
		fKeys.circumflex_dead_key[1],
		fKeys.dieresis_dead_key[1],
		fKeys.tilde_dead_key[1]
	};

	int32 offset = deadOffsets[deadKeyIndex - 1];
	if (offset < 0 || offset >= (int32)fCharsSize)
		return;

	if (_SetChars(offset, trigger.String(), trigger.Length())) {
		// reset modifier table such that new dead key is enabled wherever
		// it is available
		uint32* deadTables[] = {
			&fKeys.acute_tables,
			&fKeys.grave_tables,
			&fKeys.circumflex_tables,
			&fKeys.dieresis_tables,
			&fKeys.tilde_tables
		};
		*deadTables[deadKeyIndex - 1]
			= B_NORMAL_TABLE | B_SHIFT_TABLE | B_CONTROL_TABLE | B_OPTION_TABLE
				| B_OPTION_SHIFT_TABLE | B_CAPS_TABLE | B_CAPS_SHIFT_TABLE
				| B_OPTION_CAPS_TABLE | B_OPTION_CAPS_SHIFT_TABLE;

		if (fModificationMessage != NULL)
			fTarget.SendMessage(fModificationMessage);
	}
}


status_t
Keymap::RestoreSystemDefault()
{
	BPath path;
	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
	if (status != B_OK)
		return status;

	path.Append("Key_map");

	BEntry entry(path.Path());
	entry.Remove();

	return Use();
}


//! We make our input server use the map in /boot/home/config/settings/Keymap
status_t
Keymap::Use()
{
	status_t result = _restore_key_map_();
	if (result == B_OK)
		set_keyboard_locks(modifiers());
	return result;
}


void
Keymap::SetKey(uint32 keyCode, uint32 modifiers, int8 deadKey,
	const char* bytes, int32 numBytes)
{
	int32 offset = Offset(keyCode, modifiers);
	if (offset < 0)
		return;

	if (numBytes < 0)
		numBytes = strlen(bytes);
	if (numBytes > 6)
		return;

	if (_SetChars(offset, bytes, numBytes)) {
		if (fModificationMessage != NULL)
			fTarget.SendMessage(fModificationMessage);
	}
}


Keymap&
Keymap::operator=(const Keymap& other)
{
	if (this == &other)
		return *this;

	delete[] fChars;
	delete fModificationMessage;

	fChars = new(std::nothrow) char[other.fCharsSize];
	if (fChars != NULL) {
		memcpy(fChars, other.fChars, other.fCharsSize);
		fCharsSize = other.fCharsSize;
	} else
		fCharsSize = 0;

	memcpy(&fKeys, &other.fKeys, sizeof(key_map));
	strlcpy(fName, other.fName, sizeof(fName));

	fTarget = other.fTarget;

	if (other.fModificationMessage != NULL)
		fModificationMessage = new BMessage(*other.fModificationMessage);

	return *this;
}


bool
Keymap::_SetChars(int32 offset, const char* bytes, int32 numBytes)
{
	int32 oldNumBytes = fChars[offset];

	if (oldNumBytes == numBytes
		&& !memcmp(&fChars[offset + 1], bytes, numBytes)) {
		// nothing to do
		return false;
	}

	int32 diff = numBytes - oldNumBytes;
	if (diff != 0) {
		fCharsSize += diff;

		if (diff > 0) {
			// make space for the new data
			char* chars = new(std::nothrow) char[fCharsSize];
			if (chars != NULL) {
				memcpy(chars, fChars, offset + oldNumBytes + 1);
				memcpy(&chars[offset + 1 + numBytes],
					&fChars[offset + 1 + oldNumBytes],
					fCharsSize - 2 - offset - diff);
				delete[] fChars;
				fChars = chars;
			} else
				return false;
		} else if (diff < 0) {
			// shrink table
			memmove(&fChars[offset + numBytes], &fChars[offset + oldNumBytes],
				fCharsSize - offset - 2 - diff);
		}

		// update offsets
		int32* data = fKeys.control_map;
		int32 size = sizeof(fKeys.control_map) / 4 * 9
			+ sizeof(fKeys.acute_dead_key) / 4 * 5;
		for (int32 i = 0; i < size; i++) {
			if (data[i] > offset)
				data[i] += diff;
		}
	}

	memcpy(&fChars[offset + 1], bytes, numBytes);
	fChars[offset] = numBytes;

	return true;
}