diff --git a/LICENSE.md b/LICENSE.md
index 3ae1225..e73c4eb 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -28,6 +28,7 @@ They licenses may be found in the following files:
 	- shared/stb_image.h, shared/stb_sprintf.h, shared/stb_ds.h and util/stb_truetype.h
 	- res/Fonts/Hack License.txt, res/Fonts/Inter License.txt
 	- res/Icons/elementary Icons License.txt
-	- res/Media/Licenses.txt
+	- res/Sample Images/Licenses.txt
+	- res/Keyboard Layouts/License.txt
 	- Ported applications have their licenses in their respective folders.
 Please tell me if I've forgotten something!
diff --git a/desktop/api.cpp b/desktop/api.cpp
index e401d4c..2ebf0e1 100644
--- a/desktop/api.cpp
+++ b/desktop/api.cpp
@@ -156,6 +156,9 @@ struct {
 	Array<Work> workQueue;
 	Array<EsHandle> workThreads;
 	volatile bool workFinish;
+
+	const uint16_t *keyboardLayout;
+	uint16_t keyboardLayoutIdentifier;
 } api;
 
 ptrdiff_t tlsStorageOffset;
diff --git a/desktop/desktop.cpp b/desktop/desktop.cpp
index acabb23..ae556e3 100644
--- a/desktop/desktop.cpp
+++ b/desktop/desktop.cpp
@@ -606,7 +606,7 @@ int ProcessGlobalKeyboardShortcuts(EsElement *, EsMessage *message) {
 		// Do not process global keyboard shortcuts if the installer is running.
 	} else if (message->type == ES_MSG_KEY_DOWN) {
 		bool ctrlOnly = message->keyboard.modifiers == ES_MODIFIER_CTRL;
-		int scancode = message->keyboard.scancode;
+		int scancode = ScancodeMapToLabel(message->keyboard.scancode);
 
 		if (ctrlOnly && scancode == ES_SCANCODE_N && !message->keyboard.repeat) {
 			ApplicationInstanceCreate(APPLICATION_ID_DESKTOP_BLANK_TAB, nullptr, nullptr);
@@ -651,7 +651,7 @@ int ContainerWindowMessage(EsElement *element, EsMessage *message) {
 		container->taskBarButton->MaybeRefreshStyle();
 	} else if (message->type == ES_MSG_KEY_DOWN) {
 		bool ctrlOnly = message->keyboard.modifiers == ES_MODIFIER_CTRL;
-		int scancode = message->keyboard.scancode;
+		int scancode = ScancodeMapToLabel(message->keyboard.scancode);
 
 		if (((message->keyboard.modifiers & ~ES_MODIFIER_SHIFT) == ES_MODIFIER_CTRL) && message->keyboard.scancode == ES_SCANCODE_TAB) {
 			int tab = -1;
diff --git a/desktop/gui.cpp b/desktop/gui.cpp
index 227b299..bdc93fb 100644
--- a/desktop/gui.cpp
+++ b/desktop/gui.cpp
@@ -343,7 +343,7 @@ struct ScrollPane {
 	void Setup(EsElement *parent, uint8_t xMode, uint8_t yMode, uint16_t flags);
 	void SetPosition(int axis, double newPosition, bool sendMovedMessage = true);
 	void Refresh();
-	void ReceivedMessage(EsMessage *message);
+	int ReceivedMessage(EsMessage *message);
 
 	inline void SetX(double scrollX, bool sendMovedMessage = true) { SetPosition(0, scrollX, sendMovedMessage); }
 	inline void SetY(double scrollY, bool sendMovedMessage = true) { SetPosition(1, scrollY, sendMovedMessage); }
@@ -2840,7 +2840,7 @@ void ScrollPane::Setup(EsElement *_parent, uint8_t _xMode, uint8_t _yMode, uint1
 	}
 }
 
-void ScrollPane::ReceivedMessage(EsMessage *message) {
+int ScrollPane::ReceivedMessage(EsMessage *message) {
 	if (message->type == ES_MSG_LAYOUT) {
 		Refresh();
 	} else if (message->type == ES_MSG_MOUSE_LEFT_DRAG || message->type == ES_MSG_MOUSE_RIGHT_DRAG || message->type == ES_MSG_MOUSE_MIDDLE_DRAG) {
@@ -2884,7 +2884,11 @@ void ScrollPane::ReceivedMessage(EsMessage *message) {
 	} else if (message->type == ES_MSG_SCROLL_WHEEL) {
 		SetPosition(0, position[0] + 60 * message->scrollWheel.dx / ES_SCROLL_WHEEL_NOTCH, true);
 		SetPosition(1, position[1] - 60 * message->scrollWheel.dy / ES_SCROLL_WHEEL_NOTCH, true);
+		if (message->scrollWheel.dx && mode[0]) return ES_HANDLED;
+		if (message->scrollWheel.dy && mode[1]) return ES_HANDLED;
 	}
+
+	return 0;
 }
 
 void ScrollPane::SetPosition(int axis, double newScroll, bool sendMovedMessage) {
@@ -2990,7 +2994,7 @@ struct EsScrollView : EsElement { ScrollPane scroll; };
 void EsScrollViewSetup(EsScrollView *view, uint8_t xMode, uint8_t yMode, uint16_t flags) { view->scroll.Setup(view, xMode, yMode, flags); }
 void EsScrollViewSetPosition(EsScrollView *view, int axis, double newPosition, bool notify) { view->scroll.SetPosition(axis, newPosition, notify); }
 void EsScrollViewRefresh(EsScrollView *view) { view->scroll.Refresh(); }
-void EsScrollViewReceivedMessage(EsScrollView *view, EsMessage *message) { view->scroll.ReceivedMessage(message); }
+int EsScrollViewReceivedMessage(EsScrollView *view, EsMessage *message) { return view->scroll.ReceivedMessage(message); }
 int64_t EsScrollViewGetPosition(EsScrollView *view, int axis) { return view->scroll.position[axis]; }
 int64_t EsScrollViewGetLimit(EsScrollView *view, int axis) { return view->scroll.limit[axis]; }
 void EsScrollViewSetFixedViewport(EsScrollView *view, int axis, int32_t value) { view->scroll.fixedViewport[axis] = value; }
@@ -3048,7 +3052,8 @@ int ProcessPanelMessage(EsElement *element, EsMessage *message) {
 	EsPanel *panel = (EsPanel *) element;
 	EsRectangle bounds = panel->GetBounds();
 
-	panel->scroll.ReceivedMessage(message);
+	int response = panel->scroll.ReceivedMessage(message);
+	if (response) return response;
 
 	if (message->type == ES_MSG_LAYOUT) {
 		if (panel->flags & ES_PANEL_TABLE) {
@@ -3748,7 +3753,8 @@ EsElement *CanvasPaneGetCanvas(EsElement *element) {
 int ProcessCanvasPaneMessage(EsElement *element, EsMessage *message) {
 	EsCanvasPane *pane = (EsCanvasPane *) element;
 
-	pane->scroll.ReceivedMessage(message);
+	int response = pane->scroll.ReceivedMessage(message);
+	if (response) return response;
 
 	if (message->type == ES_MSG_LAYOUT) {
 		EsElement *canvas = CanvasPaneGetCanvas(element);
@@ -6879,9 +6885,10 @@ void AccessKeyModeHandleKeyPress(EsMessage *message) {
 
 	EsWindow *window = gui.accessKeys.window;
 
-	int ic, isc;
-	ConvertScancodeToCharacter(message->keyboard.scancode, &ic, &isc, false, false);
-	ic = EsCRTtoupper(ic);
+	const char *inputString = KeyboardLayoutLookup(message->keyboard.scancode, 
+			message->keyboard.modifiers & ES_MODIFIER_SHIFT, message->keyboard.modifiers & ES_MODIFIER_ALT_GR, 
+			false, false);
+	int ic = EsCRTtoupper(inputString ? *inputString : 0);
 
 	bool keepAccessKeyModeActive = false;
 	bool regatherKeys = false;
@@ -6945,7 +6952,7 @@ bool UIHandleKeyMessage(EsWindow *window, EsMessage *message) {
 		if (message->keyboard.scancode == ES_SCANCODE_LEFT_SHIFT ) gui.leftModifiers  &= ~ES_MODIFIER_SHIFT;
 		if (message->keyboard.scancode == ES_SCANCODE_LEFT_FLAG  ) gui.leftModifiers  &= ~ES_MODIFIER_FLAG;
 		if (message->keyboard.scancode == ES_SCANCODE_RIGHT_CTRL ) gui.rightModifiers &= ~ES_MODIFIER_CTRL;
-		if (message->keyboard.scancode == ES_SCANCODE_RIGHT_ALT  ) gui.rightModifiers &= ~ES_MODIFIER_ALT;
+		if (message->keyboard.scancode == ES_SCANCODE_RIGHT_ALT  ) gui.rightModifiers &= ~ES_MODIFIER_ALT_GR;
 		if (message->keyboard.scancode == ES_SCANCODE_RIGHT_SHIFT) gui.rightModifiers &= ~ES_MODIFIER_SHIFT;
 		if (message->keyboard.scancode == ES_SCANCODE_RIGHT_FLAG ) gui.rightModifiers &= ~ES_MODIFIER_FLAG;
 
@@ -6973,7 +6980,7 @@ bool UIHandleKeyMessage(EsWindow *window, EsMessage *message) {
 	if (message->keyboard.scancode == ES_SCANCODE_LEFT_SHIFT ) gui.leftModifiers  |= ES_MODIFIER_SHIFT;
 	if (message->keyboard.scancode == ES_SCANCODE_LEFT_FLAG  ) gui.leftModifiers  |= ES_MODIFIER_FLAG;
 	if (message->keyboard.scancode == ES_SCANCODE_RIGHT_CTRL ) gui.rightModifiers |= ES_MODIFIER_CTRL;
-	if (message->keyboard.scancode == ES_SCANCODE_RIGHT_ALT  ) gui.rightModifiers |= ES_MODIFIER_ALT;
+	if (message->keyboard.scancode == ES_SCANCODE_RIGHT_ALT  ) gui.rightModifiers |= ES_MODIFIER_ALT_GR;
 	if (message->keyboard.scancode == ES_SCANCODE_RIGHT_SHIFT) gui.rightModifiers |= ES_MODIFIER_SHIFT;
 	if (message->keyboard.scancode == ES_SCANCODE_RIGHT_FLAG ) gui.rightModifiers |= ES_MODIFIER_FLAG;
 
@@ -7065,7 +7072,7 @@ bool UIHandleKeyMessage(EsWindow *window, EsMessage *message) {
 		// TODO Sort out what commands can be used from within dialogs and menus.
 
 		if (!gui.keyboardShortcutNames.itemCount) UIInitialiseKeyboardShortcutNamesTable();
-		const char *shortcutName = (const char *) HashTableGetShort(&gui.keyboardShortcutNames, message->keyboard.scancode);
+		const char *shortcutName = (const char *) HashTableGetShort(&gui.keyboardShortcutNames, ScancodeMapToLabel(message->keyboard.scancode));
 
 		if (shortcutName && window->instance && window->instance->_private) {
 			APIInstance *instance = (APIInstance *) window->instance->_private;
diff --git a/desktop/list_view.cpp b/desktop/list_view.cpp
index 6a84a19..26701e4 100644
--- a/desktop/list_view.cpp
+++ b/desktop/list_view.cpp
@@ -95,6 +95,11 @@ struct EsListView : EsElement {
 
 	int maximumItemsPerBand;
 
+	EsListViewIndex ensureVisibleGroupIndex;
+	EsListViewIndex ensureVisibleIndex;
+	uint8_t ensureVisibleAlign;
+	bool ensureVisibleQueued;
+
 	// Fixed item storage:
 	Array<ListViewFixedItem> fixedItems;
 	ptrdiff_t fixedItemSelection;
@@ -300,7 +305,15 @@ struct EsListView : EsElement {
 		*_itemSize = itemSize;
 	}
 
-	void EnsureItemVisible(EsListViewIndex groupIndex, EsListViewIndex index, bool alignTop) {
+	void EnsureItemVisible(EsListViewIndex groupIndex, EsListViewIndex index, uint8_t align) {
+		ensureVisibleQueued = true;
+		ensureVisibleGroupIndex = groupIndex;
+		ensureVisibleIndex = index;
+		ensureVisibleAlign = align;
+		EsElementRelayout(this);
+	}
+
+	void _EnsureItemVisible(EsListViewIndex groupIndex, EsListViewIndex index, uint8_t align) {
 		EsRectangle contentBounds = GetListBounds();
 
 		int64_t startInset = flags & ES_LIST_VIEW_HORIZONTAL ? style->insets.l : style->insets.t,
@@ -314,12 +327,18 @@ struct EsListView : EsElement {
 			return;
 		}
 
-		if (alignTop) {
+		if (align == 1) {
 			if (flags & ES_LIST_VIEW_HORIZONTAL) {
 				scroll.SetX(scroll.position[0] + position - startInset);
 			} else {
 				scroll.SetY(scroll.position[1] + position - startInset);
 			}
+		} else if (align == 2) {
+			if (flags & ES_LIST_VIEW_HORIZONTAL) {
+				scroll.SetX(scroll.position[0] + position + itemSize / 2 - contentSize / 2);
+			} else {
+				scroll.SetY(scroll.position[1] + position + itemSize / 2 - contentSize / 2);
+			}
 		} else {
 			if (flags & ES_LIST_VIEW_HORIZONTAL) {
 				scroll.SetX(scroll.position[0] + position + itemSize - contentSize + endInset);
@@ -1491,15 +1510,15 @@ struct EsListView : EsElement {
 			}
 
 			StartAnimating();
-			searchBufferLastKeyTime = currentTime;
-			int ic, isc;
-			ConvertScancodeToCharacter(scancode, &ic, &isc, false, false);
-			int character = shift ? isc : ic;
 
-			if (character != -1 && searchBufferBytes + 4 < sizeof(searchBuffer)) {
-				utf8_encode(character, searchBuffer + searchBufferBytes);
+			const char *inputString = KeyboardLayoutLookup(scancode, shift, false, false, false);
+			size_t inputStringBytes = EsCStringLength(inputString);
+
+			if (inputString && searchBufferBytes + inputStringBytes < sizeof(searchBuffer)) {
+				searchBufferLastKeyTime = currentTime;
+				EsMemoryCopy(searchBuffer + searchBufferBytes, inputString, inputStringBytes);
 				size_t previousSearchBufferBytes = searchBufferBytes;
-				searchBufferBytes += utf8_length_char(searchBuffer + searchBufferBytes);
+				searchBufferBytes += inputStringBytes;
 				if (!Search()) searchBufferBytes = previousSearchBufferBytes;
 				return true;
 			}
@@ -1574,7 +1593,8 @@ struct EsListView : EsElement {
 	}
 
 	int ProcessMessage(EsMessage *message) {
-		scroll.ReceivedMessage(message);
+		int response = scroll.ReceivedMessage(message);
+		if (response) return response;
 
 		if (message->type == ES_MSG_GET_WIDTH || message->type == ES_MSG_GET_HEIGHT) {
 			if (flags & ES_LIST_VIEW_HORIZONTAL) {
@@ -1590,6 +1610,13 @@ struct EsListView : EsElement {
 		} else if (message->type == ES_MSG_LAYOUT) {
 			firstLayout = true;
 			Wrap(message->layout.sizeChanged);
+
+			if (ensureVisibleQueued) {
+				ensureVisibleQueued = false;
+				_EnsureItemVisible(ensureVisibleGroupIndex, ensureVisibleIndex, ensureVisibleAlign);
+				// TODO _EnsureItemVisible may call Populate; if this happens, we don't need to call it below.
+			}
+
 			Populate();
 
 			if (columnHeader) {
@@ -2432,7 +2459,15 @@ bool EsListViewFixedItemSelect(EsListView *view, EsGeneric data) {
 	EsMessageMutexCheck();
 	EsListViewIndex index;
 	bool found = EsListViewFixedItemFindIndex(view, data, &index);
-	if (found) EsListViewSelect(view, 0, index);
+
+	if (found) {
+		EsListViewSelect(view, 0, index);
+
+		// TODO Maybe you should have to separately call EsListViewFocusItem to get this behaviour.
+		EsListViewFocusItem(view, 0, index);
+		view->EnsureItemVisible(0, index, 2 /* center */);
+	}
+
 	return found;
 }
 
diff --git a/desktop/os.header b/desktop/os.header
index bf8d5fa..5f5e955 100644
--- a/desktop/os.header
+++ b/desktop/os.header
@@ -707,6 +707,7 @@ define ES_MODIFIER_CTRL		(1 << 0)
 define ES_MODIFIER_SHIFT	(1 << 1)
 define ES_MODIFIER_ALT		(1 << 2)
 define ES_MODIFIER_FLAG		(1 << 3)
+define ES_MODIFIER_ALT_GR	(1 << 4)
 
 define ES_PROCESS_CREATE_PAUSED (1 << 0)
 
@@ -1556,7 +1557,7 @@ struct EsMessageMouseButton {
 };
 
 struct EsMessageKeyboard {
-	uint32_t scancode; 
+	uint16_t scancode; 
 	uint8_t modifiers;
 	bool repeat, numpad, numlock, single;
 };
@@ -2419,7 +2420,7 @@ function EsRectangle EsElementGetScreenBounds(EsElement *element, bool client =
 function EsElement *EsCustomElementCreate(EsElement *parent, uint64_t flags = ES_FLAGS_DEFAULT, const EsStyle *style = ES_NULL);
 function EsScrollView *EsCustomScrollViewCreate(EsElement *parent, uint64_t flags = ES_FLAGS_DEFAULT, const EsStyle *style = ES_NULL);
 
-function void EsScrollViewReceivedMessage(EsScrollView *view, EsMessage *message); // You *must* call this *before* handling any messages.
+function int EsScrollViewReceivedMessage(EsScrollView *view, EsMessage *message); // You *must* call this *before* handling any messages. If this returns non-zero, immediately return that value from your message handler.
 function void EsScrollViewSetup(EsScrollView *view, uint8_t xMode, uint8_t yMode, uint16_t flags);
 function void EsScrollViewSetPosition(EsScrollView *view, int axis, double newPosition, bool sendMovedMessage = true);
 function void EsScrollViewRefresh(EsScrollView *view);
diff --git a/desktop/prefix.h b/desktop/prefix.h
index a01a35d..c6265a0 100644
--- a/desktop/prefix.h
+++ b/desktop/prefix.h
@@ -289,6 +289,7 @@ struct GlobalData {
 	volatile float animationTimeMultiplier;
 	volatile uint64_t schedulerTimeMs;
 	volatile uint64_t schedulerTimeOffset;
+	volatile uint16_t keyboardLayout;
 };
 
 #ifdef KERNEL
diff --git a/desktop/settings.cpp b/desktop/settings.cpp
index 53e4c46..80b17c7 100644
--- a/desktop/settings.cpp
+++ b/desktop/settings.cpp
@@ -17,9 +17,11 @@ struct SettingsControl {
 #define SETTINGS_CONTROL_CHECKBOX (1)
 #define SETTINGS_CONTROL_NUMBER (2)
 #define SETTINGS_CONTROL_SLIDER (3)
+#define SETTINGS_CONTROL_CHOICE_LIST (4)
 	uint8_t type;
 	bool originalValueBool;
 	int32_t originalValueInt;
+	EsGeneric originalValueData;
 	int32_t minimumValue, maximumValue;
 	uint32_t steps;
 	double dragSpeed, dragValue, discreteStep;
@@ -193,6 +195,7 @@ void SettingsLoadDefaults() {
 	SettingsPutValue("general", "use_smart_quotes", EsLiteral("1"), nullptr, nullptr, true, false);
 	SettingsPutValue("general", "enable_hover_state", EsLiteral("1"), nullptr, nullptr, true, false);
 	SettingsPutValue("general", "enable_animations", EsLiteral("1"), nullptr, nullptr, true, false);
+	SettingsPutValue("general", "keyboard_layout", EsLiteral("us"), nullptr, nullptr, true, false);
 	SettingsPutValue("paths", "default_user_documents", EsLiteral("0:/"), nullptr, nullptr, true, false);
 }
 
@@ -204,6 +207,11 @@ void SettingsUpdateGlobalAndWindowManager() {
 	api.global->enableHoverState = EsSystemConfigurationReadInteger(EsLiteral("general"), EsLiteral("enable_hover_state"));
 	api.global->animationTimeMultiplier = EsSystemConfigurationReadInteger(EsLiteral("general"), EsLiteral("enable_animations")) ? 1.0f : 0.0f;
 
+	size_t keyboardLayoutBytes;
+	char *keyboardLayout = EsSystemConfigurationReadString(EsLiteral("general"), EsLiteral("keyboard_layout"), &keyboardLayoutBytes);
+	api.global->keyboardLayout = keyboardLayout && keyboardLayoutBytes == 2 ? (keyboardLayout[0] | ((uint16_t) keyboardLayout[1] << 8)) : 1;
+	EsHeapFree(keyboardLayout);
+
 	{
 		float newUIScale = EsSystemConfigurationReadInteger(EsLiteral("general"), EsLiteral("ui_scale")) * 0.01f;
 		bool changed = api.global->uiScale != newUIScale && api.global->uiScale;
@@ -289,6 +297,8 @@ void SettingsUndoButton(EsInstance *_instance, EsElement *, EsCommand *) {
 			SettingsNumberBoxSetValue(control->element, control->originalValueInt);
 		} else if (control->type == SETTINGS_CONTROL_SLIDER) {
 			EsSliderSetValue((EsSlider *) control->element, LinearMap(control->minimumValue, control->maximumValue, 0, 1, control->originalValueInt), true);
+		} else if (control->type == SETTINGS_CONTROL_CHOICE_LIST) {
+			EsListViewFixedItemSelect((EsListView *) control->element, control->originalValueData);
 		}
 	}
 
@@ -496,6 +506,65 @@ void SettingsAddSlider(EsElement *table, const char *string, ptrdiff_t stringByt
 	instance->controls.Add(control);
 }
 
+int SettingsChoiceListMessage(EsElement *element, EsMessage *message) {
+	EsListView *list = (EsListView *) element;
+	SettingsInstance *instance = (SettingsInstance *) list->instance;
+	SettingsControl *control = (SettingsControl *) list->userData.p;
+
+	if (message->type == ES_MSG_LIST_VIEW_SELECT) {
+		EsGeneric _newValue;
+
+		if (EsListViewFixedItemGetSelected(((EsListView *) element), &_newValue)) {
+			EsMutexAcquire(&api.systemConfigurationMutex);
+
+			const char *newValue = (const char *) _newValue.p;
+			size_t newValueBytes = 0;
+			while (newValue[newValueBytes] && newValue[newValueBytes] != '=') newValueBytes++;
+
+			char *value = (char *) EsHeapAllocate(newValueBytes, false), *_oldValue;
+			EsMemoryCopy(value, newValue, newValueBytes);
+			size_t _oldValueBytes;
+			bool changed = true;
+
+			if (SettingsPutValue(control->cConfigurationSection, control->cConfigurationKey, value, newValueBytes, 
+						&_oldValue, &_oldValueBytes, false, true)) {
+				changed = _oldValueBytes != newValueBytes || EsMemoryCompare(_oldValue, newValue, newValueBytes);
+				EsHeapFree(_oldValue);
+			}
+
+			EsMutexRelease(&api.systemConfigurationMutex);
+
+			if (changed) {
+				SettingsUpdateGlobalAndWindowManager();
+				EsElementSetDisabled(instance->undoButton, false);
+				desktop.configurationModified = true;
+			}
+		}
+	}
+
+	return 0;
+}
+
+EsListView *SettingsAddChoiceList(EsElement *table, const char *string, ptrdiff_t stringBytes, char accessKey, 
+		const char *cConfigurationSection, const char *cConfigurationKey) {
+	SettingsInstance *instance = (SettingsInstance *) table->instance;
+
+	SettingsControl *control = (SettingsControl *) EsHeapAllocate(sizeof(SettingsControl), true);
+	control->type = SETTINGS_CONTROL_CHOICE_LIST;
+	control->cConfigurationSection = cConfigurationSection;
+	control->cConfigurationKey = cConfigurationKey;
+
+	EsTextDisplayCreate(table, ES_CELL_H_RIGHT | ES_CELL_V_TOP, 0, string, stringBytes);
+	EsListView *list = EsListViewCreate(table, ES_CELL_H_EXPAND | ES_CELL_H_PUSH | ES_LIST_VIEW_CHOICE_SELECT | ES_LIST_VIEW_FIXED_ITEMS, ES_STYLE_LIST_CHOICE_BORDERED);
+	list->accessKey = accessKey;
+	list->userData = control;
+	list->messageUser = SettingsChoiceListMessage;
+
+	control->element = list;
+	instance->controls.Add(control);
+	return list;
+}
+
 int SettingsDoubleClickTestMessage(EsElement *element, EsMessage *message) {
 	if (message->type == ES_MSG_MOUSE_LEFT_DOWN) {
 		if (message->mouseDown.clickChainCount >= 2) {
@@ -562,15 +631,44 @@ void SettingsPageKeyboard(EsElement *element, SettingsPage *page) {
 	EsPanel *container = EsPanelCreate(content, ES_PANEL_VERTICAL | ES_CELL_H_SHRINK, &styleSettingsGroupContainer2);
 	SettingsAddTitle(container, page);
 
+	EsPanel *table;
+	EsTextbox *textbox;
+
+	table = EsPanelCreate(container, ES_CELL_H_FILL | ES_PANEL_TABLE | ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_FORM_TABLE);
+	EsPanelSetBands(table, 2);
+
+	EsListView *list = SettingsAddChoiceList(table, INTERFACE_STRING(DesktopSettingsKeyboardLayout), 'L', "general", "keyboard_layout");
+
+	EsINIState s = {};
+	s.buffer = (char *) EsBundleFind(&bundleDesktop, EsLiteral("Keyboard Layouts.ini"), &s.bytes);
+	void *activeKeyboardLayout = nullptr;
+
+	while (EsINIParse(&s)) {
+		if (s.key[0] != ';') {
+			EsListViewFixedItemInsert(list, s.value, s.valueBytes, s.key);
+			EsAssert(s.keyBytes == 2);
+
+			if (s.key[0] + ((uint16_t) s.key[1] << 8) == api.global->keyboardLayout) {
+				activeKeyboardLayout = s.key;
+				((SettingsControl *) list->userData.p)->originalValueData = activeKeyboardLayout;
+			}
+		}
+	}
+
+	EsListViewFixedItemSelect(list, activeKeyboardLayout);
+
+	table = EsPanelCreate(container, ES_CELL_H_FILL, &styleSettingsCheckboxGroup);
+	SettingsAddCheckbox(table, INTERFACE_STRING(DesktopSettingsKeyboardUseSmartQuotes), 'Q', "general", "use_smart_quotes");
+
+	EsSpacerCreate(container, ES_CELL_H_FILL, ES_STYLE_BUTTON_GROUP_SEPARATOR);
+
 	EsPanel *warningRow = EsPanelCreate(container, ES_CELL_H_CENTER | ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_FORM_TABLE);
 	EsIconDisplayCreate(warningRow, ES_FLAGS_DEFAULT, 0, ES_ICON_DIALOG_WARNING);
 	EsTextDisplayCreate(warningRow, ES_FLAGS_DEFAULT, 0, "Work in progress" ELLIPSIS);
 
-	EsPanel *table = EsPanelCreate(container, ES_CELL_H_FILL | ES_PANEL_TABLE | ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_FORM_TABLE);
+	table = EsPanelCreate(container, ES_CELL_H_FILL | ES_PANEL_TABLE | ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_FORM_TABLE);
 	EsPanelSetBands(table, 2);
 
-	EsTextbox *textbox;
-
 	EsTextDisplayCreate(table, ES_CELL_H_RIGHT | ES_CELL_H_PUSH, 0, INTERFACE_STRING(DesktopSettingsKeyboardKeyRepeatDelay)); // TODO.
 	textbox = EsTextboxCreate(table, ES_CELL_H_LEFT | ES_CELL_H_PUSH | ES_TEXTBOX_EDIT_BASED, &styleSettingsNumberTextbox);
 	textbox->accessKey = 'D';
@@ -593,9 +691,6 @@ void SettingsPageKeyboard(EsElement *element, SettingsPage *page) {
 	EsTextDisplayCreate(testBox, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(DesktopSettingsKeyboardTestTextboxIntroduction));
 	EsSpacerCreate(testBox, ES_FLAGS_DEFAULT, 0, 0, 5);
 	EsTextboxCreate(testBox, ES_CELL_H_LEFT)->accessKey = 'T';
-
-	table = EsPanelCreate(container, ES_CELL_H_FILL, &styleSettingsCheckboxGroup);
-	SettingsAddCheckbox(table, INTERFACE_STRING(DesktopSettingsKeyboardUseSmartQuotes), 'Q', "general", "use_smart_quotes");
 }
 
 void SettingsPageDisplay(EsElement *element, SettingsPage *page) {
diff --git a/desktop/text.cpp b/desktop/text.cpp
index 6e741ee..8b4b27b 100644
--- a/desktop/text.cpp
+++ b/desktop/text.cpp
@@ -2970,7 +2970,37 @@ void TextboxBufferResize(void **array, uintptr_t *allocated, uintptr_t needed, u
 	*array = newArray;
 }
 
-bool IsScancodeNonTypeable(unsigned scancode) {
+void KeyboardLayoutLoad() {
+	if (api.keyboardLayoutIdentifier != api.global->keyboardLayout) {
+		char buffer[64];
+		api.keyboardLayoutIdentifier = api.global->keyboardLayout;
+		api.keyboardLayout = (const uint16_t *) EsBundleFind(&bundleDesktop, buffer, EsStringFormat(buffer, sizeof(buffer), "Keyboard Layouts/%c%c.dat", 
+					(uint8_t) api.keyboardLayoutIdentifier, (uint8_t) (api.keyboardLayoutIdentifier >> 8)));
+
+		if (!api.keyboardLayout) {
+			// Fallback to the US layout if the specifier layout was not found.
+			api.keyboardLayout = (const uint16_t *) EsBundleFind(&bundleDesktop, buffer, EsStringFormat(buffer, sizeof(buffer), "Keyboard Layouts/us.dat"));
+		}
+	}
+}
+
+const char *KeyboardLayoutLookup(uint32_t scancode, bool isShiftHeld, bool isAltGrHeld, bool enableTabs, bool enableNewline) {
+	KeyboardLayoutLoad();
+	if (scancode >= 0x200) return nullptr;
+	if (scancode == ES_SCANCODE_ENTER || scancode == ES_SCANCODE_NUM_ENTER) return enableNewline ? "\n" : nullptr;
+	if (scancode == ES_SCANCODE_TAB) return enableTabs ? "\t" : nullptr;
+	if (scancode == ES_SCANCODE_BACKSPACE || scancode == ES_SCANCODE_DELETE) return nullptr;
+	uint16_t offset = api.keyboardLayout[scancode + (isShiftHeld ? 0x200 : 0) + (isAltGrHeld ? 0x400 : 0)];
+	return offset ? ((char *) api.keyboardLayout + 0x1000 + offset) : nullptr;
+}
+
+uint32_t ScancodeMapToLabel(uint32_t scancode) {
+	KeyboardLayoutLoad();
+	// TODO.
+	return scancode;
+}
+
+bool ScancodeIsNonTypeable(uint32_t scancode) {
 	switch (scancode) {
 		case ES_SCANCODE_CAPS_LOCK:
 		case ES_SCANCODE_SCROLL_LOCK:
@@ -3025,73 +3055,14 @@ bool IsScancodeNonTypeable(unsigned scancode) {
 	}
 }
 
-void ConvertScancodeToCharacter(unsigned scancode, int *_ic, int *_isc, bool enableTabs, bool enableNewline) {
-	int ic = -1, isc = -1;
-
-	switch (scancode) {
-		case ES_SCANCODE_A: ic = 'a'; isc = 'A'; break;
-		case ES_SCANCODE_B: ic = 'b'; isc = 'B'; break;
-		case ES_SCANCODE_C: ic = 'c'; isc = 'C'; break;
-		case ES_SCANCODE_D: ic = 'd'; isc = 'D'; break;
-		case ES_SCANCODE_E: ic = 'e'; isc = 'E'; break;
-		case ES_SCANCODE_F: ic = 'f'; isc = 'F'; break;
-		case ES_SCANCODE_G: ic = 'g'; isc = 'G'; break;
-		case ES_SCANCODE_H: ic = 'h'; isc = 'H'; break;
-		case ES_SCANCODE_I: ic = 'i'; isc = 'I'; break;
-		case ES_SCANCODE_J: ic = 'j'; isc = 'J'; break;
-		case ES_SCANCODE_K: ic = 'k'; isc = 'K'; break;
-		case ES_SCANCODE_L: ic = 'l'; isc = 'L'; break;
-		case ES_SCANCODE_M: ic = 'm'; isc = 'M'; break;
-		case ES_SCANCODE_N: ic = 'n'; isc = 'N'; break;
-		case ES_SCANCODE_O: ic = 'o'; isc = 'O'; break;
-		case ES_SCANCODE_P: ic = 'p'; isc = 'P'; break;
-		case ES_SCANCODE_Q: ic = 'q'; isc = 'Q'; break;
-		case ES_SCANCODE_R: ic = 'r'; isc = 'R'; break;
-		case ES_SCANCODE_S: ic = 's'; isc = 'S'; break;
-		case ES_SCANCODE_T: ic = 't'; isc = 'T'; break;
-		case ES_SCANCODE_U: ic = 'u'; isc = 'U'; break;
-		case ES_SCANCODE_V: ic = 'v'; isc = 'V'; break;
-		case ES_SCANCODE_W: ic = 'w'; isc = 'W'; break;
-		case ES_SCANCODE_X: ic = 'x'; isc = 'X'; break;
-		case ES_SCANCODE_Y: ic = 'y'; isc = 'Y'; break;
-		case ES_SCANCODE_Z: ic = 'z'; isc = 'Z'; break;
-		case ES_SCANCODE_0: ic = '0'; isc = ')'; break;
-		case ES_SCANCODE_1: ic = '1'; isc = '!'; break;
-		case ES_SCANCODE_2: ic = '2'; isc = '@'; break;
-		case ES_SCANCODE_3: ic = '3'; isc = '#'; break;
-		case ES_SCANCODE_4: ic = '4'; isc = '$'; break;
-		case ES_SCANCODE_5: ic = '5'; isc = '%'; break;
-		case ES_SCANCODE_6: ic = '6'; isc = '^'; break;
-		case ES_SCANCODE_7: ic = '7'; isc = '&'; break;
-		case ES_SCANCODE_8: ic = '8'; isc = '*'; break;
-		case ES_SCANCODE_9: ic = '9'; isc = '('; break;
-		case ES_SCANCODE_SLASH: 	ic = '/';  isc = '?'; break;
-		case ES_SCANCODE_PUNCTUATION_1: ic = '\\'; isc = '|'; break;
-		case ES_SCANCODE_LEFT_BRACE: 	ic = '[';  isc = '{'; break;
-		case ES_SCANCODE_RIGHT_BRACE: 	ic = ']';  isc = '}'; break;
-		case ES_SCANCODE_EQUALS: 	ic = '=';  isc = '+'; break;
-		case ES_SCANCODE_PUNCTUATION_5: ic = '`';  isc = '~'; break;
-		case ES_SCANCODE_HYPHEN: 	ic = '-';  isc = '_'; break;
-		case ES_SCANCODE_PUNCTUATION_3: ic = ';';  isc = ':'; break;
-		case ES_SCANCODE_PUNCTUATION_4: ic = '\''; isc = '"'; break;
-		case ES_SCANCODE_COMMA: 	ic = ',';  isc = '<'; break;
-		case ES_SCANCODE_PERIOD: 	ic = '.';  isc = '>'; break;
-		case ES_SCANCODE_SPACE: 	ic = ' ';  isc = ' '; break;
-		case ES_SCANCODE_ENTER:		if (enableNewline) { ic = '\n'; isc = '\n'; } break;
-		case ES_SCANCODE_TAB:		if (enableTabs) { ic = '\t'; isc = '\t'; } break;
-	}
-
-	*_ic = ic, *_isc = isc;
-}
-
 size_t EsMessageGetInputText(EsMessage *message, char *buffer) {
-	int ic, isc;
-	ConvertScancodeToCharacter(message->keyboard.scancode, &ic, &isc, true, true);
-
-	if (message->keyboard.modifiers & ES_MODIFIER_SHIFT) ic = isc;
-	if (ic == -1) return 0;
-
-	return utf8_encode(ic, buffer);
+	const char *string = KeyboardLayoutLookup(message->keyboard.scancode, 
+			message->keyboard.modifiers & ES_MODIFIER_SHIFT, message->keyboard.modifiers & ES_MODIFIER_ALT_GR, 
+			true, true);
+	size_t bytes = string ? EsCStringLength(string) : 0;
+	EsAssert(bytes < 64);
+	EsMemoryCopy(buffer, string, bytes);
+	return bytes;
 }
 
 enum CharacterType {
@@ -4318,9 +4289,9 @@ int ProcessTextboxMessage(EsElement *element, EsMessage *message) {
 		if (response != 0 && message->type != ES_MSG_DESTROY) return response;
 	}
 
-	textbox->scroll.ReceivedMessage(message);
-
-	int response = ES_HANDLED;
+	int response = textbox->scroll.ReceivedMessage(message);
+	if (response) return response;
+	response = ES_HANDLED;
 
 	if (message->type == ES_MSG_PAINT) {
 		EsPainter *painter = message->painter;
@@ -4418,11 +4389,11 @@ int ProcessTextboxMessage(EsElement *element, EsMessage *message) {
 		EsHeapFree(textbox->activeLine);
 		EsHeapFree(textbox->data);
 		EsHeapFree(textbox->editStartContent);
-	} else if (message->type == ES_MSG_KEY_TYPED && !IsScancodeNonTypeable(message->keyboard.scancode)) {
+	} else if (message->type == ES_MSG_KEY_TYPED && !ScancodeIsNonTypeable(message->keyboard.scancode)) {
 		bool verticalMotion = false;
 		bool ctrl = message->keyboard.modifiers & ES_MODIFIER_CTRL;
 
-		if (message->keyboard.modifiers & ~(ES_MODIFIER_CTRL | ES_MODIFIER_ALT | ES_MODIFIER_SHIFT)) {
+		if (message->keyboard.modifiers & ~(ES_MODIFIER_CTRL | ES_MODIFIER_ALT | ES_MODIFIER_SHIFT | ES_MODIFIER_ALT_GR)) {
 			// Unused modifier.
 			return 0;
 		}
@@ -4498,27 +4469,26 @@ int ProcessTextboxMessage(EsElement *element, EsMessage *message) {
 				EsTextboxStartEdit(textbox);
 			}
 
-			int ic, isc;
-			ConvertScancodeToCharacter(message->keyboard.scancode, &ic, &isc, true, textbox->flags & ES_TEXTBOX_MULTILINE); 
-			int character = (message->keyboard.modifiers & ES_MODIFIER_SHIFT) ? isc : ic;
+			const char *inputString = KeyboardLayoutLookup(message->keyboard.scancode, 
+					message->keyboard.modifiers & ES_MODIFIER_SHIFT, message->keyboard.modifiers & ES_MODIFIER_ALT_GR, 
+					true, textbox->flags & ES_TEXTBOX_MULTILINE);
 
-			if (ic != -1 && (message->keyboard.modifiers & ~ES_MODIFIER_SHIFT) == 0) {
+			if (inputString && (message->keyboard.modifiers & ~(ES_MODIFIER_SHIFT | ES_MODIFIER_ALT_GR)) == 0) {
 				if (textbox->smartQuotes && api.global->useSmartQuotes) {
 					DocumentLine *currentLine = &textbox->lines[textbox->carets[0].line];
 					const char *buffer = currentLine->GetBuffer(textbox);
 					bool left = !textbox->carets[0].byte || buffer[textbox->carets[0].byte - 1] == ' ';
 
-					if (character == '"') {
-						character = left ? 0x201C : 0x201D;
-					} else if (character == '\'') {
-						character = left ? 0x2018 : 0x2019;
+					if (inputString[0] == '"' && inputString[1] == 0) {
+						inputString = left ? "\u201C" : "\u201D";
+					} else if (inputString[0] == '\'' && inputString[1] == 0) {
+						inputString = left ? "\u2018" : "\u2019";
 					}
 				}
 
-				char buffer[4];
-				EsTextboxInsert(textbox, buffer, utf8_encode(character, buffer));
+				EsTextboxInsert(textbox, inputString, -1);
 
-				if (buffer[0] == '\n' && textbox->carets[0].line) {
+				if (inputString[0] == '\n' && inputString[1] == 0 && textbox->carets[0].line) {
 					// Copy the indentation from the previous line.
 
 					DocumentLine *previousLine = &textbox->lines[textbox->carets[0].line - 1];
diff --git a/kernel/windows.cpp b/kernel/windows.cpp
index a126058..d42f3ff 100644
--- a/kernel/windows.cpp
+++ b/kernel/windows.cpp
@@ -321,7 +321,8 @@ void WindowManager::PressKey(unsigned scancode) {
 	if (scancode == ES_SCANCODE_RIGHT_FLAG) flag2 = true;
 	if (scancode == (ES_SCANCODE_RIGHT_FLAG | K_SCANCODE_KEY_RELEASED)) flag2 = false;
 
-	modifiers = ((alt | alt2) ? ES_MODIFIER_ALT : 0) 
+	modifiers = (alt ? ES_MODIFIER_ALT : 0) 
+		| (alt2 ? ES_MODIFIER_ALT_GR : 0) 
 		| ((ctrl | ctrl2) ? ES_MODIFIER_CTRL : 0) 
 		| ((shift | shift2) ? ES_MODIFIER_SHIFT : 0)
 		| ((flag | flag2) ? ES_MODIFIER_FLAG : 0);
diff --git a/res/Keyboard Layouts/License.txt b/res/Keyboard Layouts/License.txt
new file mode 100644
index 0000000..8c16795
--- /dev/null
+++ b/res/Keyboard Layouts/License.txt	
@@ -0,0 +1,193 @@
+Extracted from the XKB data by nakst.
+License of the XKB data (MIT) follows:
+
+Copyright 1996 by Joseph Moss
+Copyright (C) 2002-2007 Free Software Foundation, Inc.
+Copyright (C) Dmitry Golubev <lastguru@mail.ru>, 2003-2004
+Copyright (C) 2004, Gregory Mokhin <mokhin@bog.msu.ru>
+Copyright (C) 2006 Erdal Ronahî
+
+Permission to use, copy, modify, distribute, and sell this software and its
+documentation for any purpose is hereby granted without fee, provided that
+the above copyright notice appear in all copies and that both that
+copyright notice and this permission notice appear in supporting
+documentation, and that the name of the copyright holder(s) not be used in
+advertising or publicity pertaining to distribution of the software without
+specific, written prior permission.  The copyright holder(s) makes no
+representations about the suitability of this software for any purpose.  It
+is provided "as is" without express or implied warranty.
+
+THE COPYRIGHT HOLDER(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+EVENT SHALL THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+
+
+Copyright (c) 1996  Digital Equipment Corporation
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL DIGITAL EQUIPMENT CORPORATION BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
+THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Except as contained in this notice, the name of the Digital Equipment 
+Corporation shall not be used in advertising or otherwise to promote
+the sale, use or other dealings in this Software without prior written
+authorization from Digital Equipment Corporation.
+
+
+Copyright 1996, 1998  The Open Group
+
+Permission to use, copy, modify, distribute, and sell this software and its
+documentation for any purpose is hereby granted without fee, provided that
+the above copyright notice appear in all copies and that both that
+copyright notice and this permission notice appear in supporting
+documentation.
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+Except as contained in this notice, the name of The Open Group shall
+not be used in advertising or otherwise to promote the sale, use or
+other dealings in this Software without prior written authorization
+from The Open Group.
+
+
+Copyright 2004-2005 Sun Microsystems, Inc.  All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice (including the next
+paragraph) shall be included in all copies or substantial portions of the
+Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+
+Copyright (c) 1996 by Silicon Graphics Computer Systems, Inc.
+
+Permission to use, copy, modify, and distribute this
+software and its documentation for any purpose and without
+fee is hereby granted, provided that the above copyright
+notice appear in all copies and that both that copyright
+notice and this permission notice appear in supporting
+documentation, and that the name of Silicon Graphics not be 
+used in advertising or publicity pertaining to distribution 
+of the software without specific prior written permission.
+Silicon Graphics makes no representation about the suitability 
+of this software for any purpose. It is provided "as is"
+without any express or implied warranty.
+
+SILICON GRAPHICS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS 
+SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 
+AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON
+GRAPHICS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL 
+DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, 
+DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 
+OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION  WITH
+THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+
+Copyright (c) 1996  X Consortium
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+Except as contained in this notice, the name of the X Consortium shall
+not be used in advertising or otherwise to promote the sale, use or
+other dealings in this Software without prior written authorization
+from the X Consortium.
+
+
+Copyright (C) 2004, 2006 Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+
+Permission to use, copy, modify, distribute, and sell this software and its
+documentation for any purpose is hereby granted without fee, provided that
+the above copyright notice appear in all copies and that both that
+copyright notice and this permission notice appear in supporting
+documentation.
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+Except as contained in this notice, the name of a copyright holder shall
+not be used in advertising or otherwise to promote the sale, use or
+other dealings in this Software without prior written authorization of
+the copyright holder.
+
+
+Copyright (C) 1999, 2000 by Anton Zinoviev <anton@lml.bas.bg>
+
+This software may be used, modified, copied, distributed, and sold,
+in both source and binary form provided that the above copyright
+and these terms are retained. Under no circumstances is the author
+responsible for the proper functioning of this software, nor does
+the author assume any responsibility for damages incurred with its
+use.
+
+Permission is granted to anyone to use, distribute and modify
+this file in any way, provided that the above copyright notice
+is left intact and the author of the modification summarizes
+the changes in this header.
+
+This file is distributed without any expressed or implied warranty.
diff --git a/res/Keyboard Layouts/af.dat b/res/Keyboard Layouts/af.dat
new file mode 100644
index 0000000..ed1b0bd
Binary files /dev/null and b/res/Keyboard Layouts/af.dat differ
diff --git a/res/Keyboard Layouts/al.dat b/res/Keyboard Layouts/al.dat
new file mode 100644
index 0000000..00efdc8
Binary files /dev/null and b/res/Keyboard Layouts/al.dat differ
diff --git a/res/Keyboard Layouts/am.dat b/res/Keyboard Layouts/am.dat
new file mode 100644
index 0000000..977f1f6
Binary files /dev/null and b/res/Keyboard Layouts/am.dat differ
diff --git a/res/Keyboard Layouts/at.dat b/res/Keyboard Layouts/at.dat
new file mode 100644
index 0000000..e34763c
Binary files /dev/null and b/res/Keyboard Layouts/at.dat differ
diff --git a/res/Keyboard Layouts/au.dat b/res/Keyboard Layouts/au.dat
new file mode 100644
index 0000000..18f4c16
Binary files /dev/null and b/res/Keyboard Layouts/au.dat differ
diff --git a/res/Keyboard Layouts/az.dat b/res/Keyboard Layouts/az.dat
new file mode 100644
index 0000000..4fc4bd9
Binary files /dev/null and b/res/Keyboard Layouts/az.dat differ
diff --git a/res/Keyboard Layouts/bd.dat b/res/Keyboard Layouts/bd.dat
new file mode 100644
index 0000000..7fb05e2
Binary files /dev/null and b/res/Keyboard Layouts/bd.dat differ
diff --git a/res/Keyboard Layouts/be.dat b/res/Keyboard Layouts/be.dat
new file mode 100644
index 0000000..11bfb53
Binary files /dev/null and b/res/Keyboard Layouts/be.dat differ
diff --git a/res/Keyboard Layouts/bg.dat b/res/Keyboard Layouts/bg.dat
new file mode 100644
index 0000000..285ace1
Binary files /dev/null and b/res/Keyboard Layouts/bg.dat differ
diff --git a/res/Keyboard Layouts/br.dat b/res/Keyboard Layouts/br.dat
new file mode 100644
index 0000000..b5c938d
Binary files /dev/null and b/res/Keyboard Layouts/br.dat differ
diff --git a/res/Keyboard Layouts/bt.dat b/res/Keyboard Layouts/bt.dat
new file mode 100644
index 0000000..95d9e24
Binary files /dev/null and b/res/Keyboard Layouts/bt.dat differ
diff --git a/res/Keyboard Layouts/bw.dat b/res/Keyboard Layouts/bw.dat
new file mode 100644
index 0000000..805cb06
Binary files /dev/null and b/res/Keyboard Layouts/bw.dat differ
diff --git a/res/Keyboard Layouts/by.dat b/res/Keyboard Layouts/by.dat
new file mode 100644
index 0000000..cd1edd5
Binary files /dev/null and b/res/Keyboard Layouts/by.dat differ
diff --git a/res/Keyboard Layouts/ca.dat b/res/Keyboard Layouts/ca.dat
new file mode 100644
index 0000000..9300409
Binary files /dev/null and b/res/Keyboard Layouts/ca.dat differ
diff --git a/res/Keyboard Layouts/cd.dat b/res/Keyboard Layouts/cd.dat
new file mode 100644
index 0000000..354ac14
Binary files /dev/null and b/res/Keyboard Layouts/cd.dat differ
diff --git a/res/Keyboard Layouts/ch.dat b/res/Keyboard Layouts/ch.dat
new file mode 100644
index 0000000..76f044a
Binary files /dev/null and b/res/Keyboard Layouts/ch.dat differ
diff --git a/res/Keyboard Layouts/cm.dat b/res/Keyboard Layouts/cm.dat
new file mode 100644
index 0000000..18f4c16
Binary files /dev/null and b/res/Keyboard Layouts/cm.dat differ
diff --git a/res/Keyboard Layouts/cn.dat b/res/Keyboard Layouts/cn.dat
new file mode 100644
index 0000000..18f4c16
Binary files /dev/null and b/res/Keyboard Layouts/cn.dat differ
diff --git a/res/Keyboard Layouts/cz.dat b/res/Keyboard Layouts/cz.dat
new file mode 100644
index 0000000..d5ef361
Binary files /dev/null and b/res/Keyboard Layouts/cz.dat differ
diff --git a/res/Keyboard Layouts/de.dat b/res/Keyboard Layouts/de.dat
new file mode 100644
index 0000000..e34763c
Binary files /dev/null and b/res/Keyboard Layouts/de.dat differ
diff --git a/res/Keyboard Layouts/dk.dat b/res/Keyboard Layouts/dk.dat
new file mode 100644
index 0000000..923462b
Binary files /dev/null and b/res/Keyboard Layouts/dk.dat differ
diff --git a/res/Keyboard Layouts/dz.dat b/res/Keyboard Layouts/dz.dat
new file mode 100644
index 0000000..63502e8
Binary files /dev/null and b/res/Keyboard Layouts/dz.dat differ
diff --git a/res/Keyboard Layouts/ee.dat b/res/Keyboard Layouts/ee.dat
new file mode 100644
index 0000000..1d81f07
Binary files /dev/null and b/res/Keyboard Layouts/ee.dat differ
diff --git a/res/Keyboard Layouts/es.dat b/res/Keyboard Layouts/es.dat
new file mode 100644
index 0000000..b6afe2a
Binary files /dev/null and b/res/Keyboard Layouts/es.dat differ
diff --git a/res/Keyboard Layouts/et.dat b/res/Keyboard Layouts/et.dat
new file mode 100644
index 0000000..e5556fd
Binary files /dev/null and b/res/Keyboard Layouts/et.dat differ
diff --git a/res/Keyboard Layouts/extract_keymaps.c b/res/Keyboard Layouts/extract_keymaps.c
new file mode 100644
index 0000000..9839ffd
--- /dev/null
+++ b/res/Keyboard Layouts/extract_keymaps.c	
@@ -0,0 +1,329 @@
+#include <X11/Xlib.h>
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// It looks like ES_SCANCODE_PUNCTUATION_1 and ES_SCANCODE_PUNCTUATION_2 are mutually exclusive 
+// (_1 only on US keyboards, and _2 on everything else),
+// and X11 merges them into key code 0x33.
+
+#define ES_SCANCODE_A				(0x04)
+#define ES_SCANCODE_B				(0x05)
+#define ES_SCANCODE_C				(0x06)
+#define ES_SCANCODE_D				(0x07)
+#define ES_SCANCODE_E				(0x08)
+#define ES_SCANCODE_F				(0x09)
+#define ES_SCANCODE_G				(0x0A)
+#define ES_SCANCODE_H				(0x0B)
+#define ES_SCANCODE_I				(0x0C)
+#define ES_SCANCODE_J				(0x0D)
+#define ES_SCANCODE_K				(0x0E)
+#define ES_SCANCODE_L				(0x0F)
+#define ES_SCANCODE_M				(0x10)
+#define ES_SCANCODE_N				(0x11)
+#define ES_SCANCODE_O				(0x12)
+#define ES_SCANCODE_P				(0x13)
+#define ES_SCANCODE_Q				(0x14)
+#define ES_SCANCODE_R				(0x15)
+#define ES_SCANCODE_S				(0x16)
+#define ES_SCANCODE_T				(0x17)
+#define ES_SCANCODE_U				(0x18)
+#define ES_SCANCODE_V				(0x19)
+#define ES_SCANCODE_W				(0x1A)
+#define ES_SCANCODE_X				(0x1B)
+#define ES_SCANCODE_Y				(0x1C)
+#define ES_SCANCODE_Z				(0x1D)
+
+#define ES_SCANCODE_1				(0x1E)
+#define ES_SCANCODE_2				(0x1F)
+#define ES_SCANCODE_3				(0x20)
+#define ES_SCANCODE_4				(0x21)
+#define ES_SCANCODE_5				(0x22)
+#define ES_SCANCODE_6				(0x23)
+#define ES_SCANCODE_7				(0x24)
+#define ES_SCANCODE_8				(0x25)
+#define ES_SCANCODE_9				(0x26)
+#define ES_SCANCODE_0				(0x27)
+
+#define ES_SCANCODE_ENTER 			(0x28)
+#define ES_SCANCODE_ESCAPE			(0x29)
+#define ES_SCANCODE_BACKSPACE			(0x2A)
+#define ES_SCANCODE_TAB				(0x2B)
+#define ES_SCANCODE_SPACE			(0x2C)
+
+#define ES_SCANCODE_HYPHEN			(0x2D)
+#define ES_SCANCODE_EQUALS			(0x2E)
+#define ES_SCANCODE_LEFT_BRACE			(0x2F)
+#define ES_SCANCODE_RIGHT_BRACE			(0x30)
+#define ES_SCANCODE_COMMA			(0x36)
+#define ES_SCANCODE_PERIOD			(0x37)
+#define ES_SCANCODE_SLASH			(0x38)
+#define ES_SCANCODE_PUNCTUATION_1		(0x31) // On US keyboard, \|
+#define ES_SCANCODE_PUNCTUATION_2		(0x32) // Not on US keyboard
+#define ES_SCANCODE_PUNCTUATION_3		(0x33) // On US keyboard, ;:
+#define ES_SCANCODE_PUNCTUATION_4		(0x34) // On US keyboard, '"
+#define ES_SCANCODE_PUNCTUATION_5		(0x35) // On US keyboard, `~
+#define ES_SCANCODE_PUNCTUATION_6		(0x64) // Not on US keyboard
+
+#define ES_SCANCODE_F1				(0x3A)
+#define ES_SCANCODE_F2				(0x3B)
+#define ES_SCANCODE_F3				(0x3C)
+#define ES_SCANCODE_F4				(0x3D)
+#define ES_SCANCODE_F5				(0x3E)
+#define ES_SCANCODE_F6				(0x3F)
+#define ES_SCANCODE_F7				(0x40)
+#define ES_SCANCODE_F8				(0x41)
+#define ES_SCANCODE_F9				(0x42)
+#define ES_SCANCODE_F10				(0x43)
+#define ES_SCANCODE_F11				(0x44)
+#define ES_SCANCODE_F12				(0x45)
+#define ES_SCANCODE_F13				(0x68)
+#define ES_SCANCODE_F14				(0x69)
+#define ES_SCANCODE_F15				(0x6A)
+#define ES_SCANCODE_F16				(0x6B)
+#define ES_SCANCODE_F17				(0x6C)
+#define ES_SCANCODE_F18				(0x6D)
+#define ES_SCANCODE_F19				(0x6E)
+#define ES_SCANCODE_F20				(0x6F)
+#define ES_SCANCODE_F21				(0x70)
+#define ES_SCANCODE_F22				(0x71)
+#define ES_SCANCODE_F23				(0x72)
+#define ES_SCANCODE_F24				(0x73)
+
+#define ES_SCANCODE_CAPS_LOCK			(0x39)
+#define ES_SCANCODE_PRINT_SCREEN			(0x46)
+#define ES_SCANCODE_SCROLL_LOCK			(0x47)
+#define ES_SCANCODE_PAUSE			(0x48)
+#define ES_SCANCODE_INSERT			(0x49)
+#define ES_SCANCODE_HOME				(0x4A)
+#define ES_SCANCODE_PAGE_UP			(0x4B)
+#define ES_SCANCODE_DELETE			(0x4C)
+#define ES_SCANCODE_END				(0x4D)
+#define ES_SCANCODE_PAGE_DOWN			(0x4E)
+#define ES_SCANCODE_RIGHT_ARROW			(0x4F)
+#define ES_SCANCODE_LEFT_ARROW			(0x50)
+#define ES_SCANCODE_DOWN_ARROW			(0x51)
+#define ES_SCANCODE_UP_ARROW			(0x52)
+#define ES_SCANCODE_NUM_LOCK			(0x53)
+#define ES_SCANCODE_CONTEXT_MENU 		(0x65)
+#define ES_SCANCODE_SYSTEM_REQUEST		(0x9A)
+
+#define ES_SCANCODE_ACTION_EXECUTE		(0x74)
+#define ES_SCANCODE_ACTION_HELP			(0x75)
+#define ES_SCANCODE_ACTION_MENU			(0x76)
+#define ES_SCANCODE_ACTION_SELECT		(0x77)
+#define ES_SCANCODE_ACTION_STOP			(0x78)
+#define ES_SCANCODE_ACTION_AGAIN			(0x79)
+#define ES_SCANCODE_ACTION_UNDO			(0x7A)
+#define ES_SCANCODE_ACTION_CUT			(0x7B)
+#define ES_SCANCODE_ACTION_COPY			(0x7C)
+#define ES_SCANCODE_ACTION_PASTE			(0x7D)
+#define ES_SCANCODE_ACTION_FIND			(0x7E)
+#define ES_SCANCODE_ACTION_CANCEL		(0x9B)
+#define ES_SCANCODE_ACTION_CLEAR			(0x9C)
+#define ES_SCANCODE_ACTION_PRIOR			(0x9D)
+#define ES_SCANCODE_ACTION_RETURN		(0x9E)
+#define ES_SCANCODE_ACTION_SEPARATOR		(0x9F)
+
+#define ES_SCANCODE_MM_MUTE			(0x7F)
+#define ES_SCANCODE_MM_LOUDER			(0x80)
+#define ES_SCANCODE_MM_QUIETER			(0x81)
+#define ES_SCANCODE_MM_NEXT			(0x103)
+#define ES_SCANCODE_MM_PREVIOUS			(0x104)
+#define ES_SCANCODE_MM_STOP			(0x105)
+#define ES_SCANCODE_MM_PAUSE			(0x106)
+#define ES_SCANCODE_MM_SELECT			(0x107)
+#define ES_SCANCODE_MM_EMAIL			(0x108)
+#define ES_SCANCODE_MM_CALC			(0x109)
+#define ES_SCANCODE_MM_FILES			(0x10A)
+
+#define ES_SCANCODE_INTERNATIONAL_1		(0x87)
+#define ES_SCANCODE_INTERNATIONAL_2		(0x88)
+#define ES_SCANCODE_INTERNATIONAL_3		(0x89)
+#define ES_SCANCODE_INTERNATIONAL_4		(0x8A)
+#define ES_SCANCODE_INTERNATIONAL_5		(0x8B)
+#define ES_SCANCODE_INTERNATIONAL_6		(0x8C)
+#define ES_SCANCODE_INTERNATIONAL_7		(0x8D)
+#define ES_SCANCODE_INTERNATIONAL_8		(0x8E)
+#define ES_SCANCODE_INTERNATIONAL_9		(0x8F)
+
+#define ES_SCANCODE_HANGUL_ENGLISH_TOGGLE	(0x90)
+#define ES_SCANCODE_HANJA_CONVERSION		(0x91)
+#define ES_SCANCODE_KATAKANA			(0x92)
+#define ES_SCANCODE_HIRAGANA			(0x93)
+#define ES_SCANCODE_HANKAKU_ZENKAKU_TOGGLE	(0x94)
+#define ES_SCANCODE_ALTERNATE_ERASE		(0x99)
+
+#define ES_SCANCODE_THOUSANDS_SEPARATOR		(0xB2)
+#define ES_SCANCODE_DECIMAL_SEPARATOR		(0xB3)
+#define ES_SCANCODE_CURRENCY_UNIT		(0xB4)
+#define ES_SCANCODE_CURRENCY_SUBUNIT		(0xB5)
+
+#define ES_SCANCODE_NUM_DIVIDE			(0x54)
+#define ES_SCANCODE_NUM_MULTIPLY			(0x55)
+#define ES_SCANCODE_NUM_SUBTRACT			(0x56)
+#define ES_SCANCODE_NUM_ADD			(0x57)
+#define ES_SCANCODE_NUM_ENTER			(0x58)
+#define ES_SCANCODE_NUM_1			(0x59)
+#define ES_SCANCODE_NUM_2			(0x5A)
+#define ES_SCANCODE_NUM_3			(0x5B)
+#define ES_SCANCODE_NUM_4			(0x5C)
+#define ES_SCANCODE_NUM_5			(0x5D)
+#define ES_SCANCODE_NUM_6			(0x5E)
+#define ES_SCANCODE_NUM_7			(0x5F)
+#define ES_SCANCODE_NUM_8			(0x60)
+#define ES_SCANCODE_NUM_9			(0x61)
+#define ES_SCANCODE_NUM_0			(0x62)
+#define ES_SCANCODE_NUM_POINT			(0x63)
+#define ES_SCANCODE_NUM_EQUALS			(0x67)
+#define ES_SCANCODE_NUM_COMMA			(0x82)
+#define ES_SCANCODE_NUM_00			(0xB0)
+#define ES_SCANCODE_NUM_000			(0xB1)
+#define ES_SCANCODE_NUM_LEFT_PAREN		(0xB6)
+#define ES_SCANCODE_NUM_RIGHT_PAREN		(0xB7)
+#define ES_SCANCODE_NUM_LEFT_BRACE		(0xB8)
+#define ES_SCANCODE_NUM_RIGHT_BRACE		(0xB9)
+#define ES_SCANCODE_NUM_TAB			(0xBA)
+#define ES_SCANCODE_NUM_BACKSPACE		(0xBB)
+#define ES_SCANCODE_NUM_A			(0xBC)
+#define ES_SCANCODE_NUM_B			(0xBD)
+#define ES_SCANCODE_NUM_C			(0xBE)
+#define ES_SCANCODE_NUM_D			(0xBF)
+#define ES_SCANCODE_NUM_E			(0xC0)
+#define ES_SCANCODE_NUM_F			(0xC1)
+#define ES_SCANCODE_NUM_XOR			(0xC2)
+#define ES_SCANCODE_NUM_CARET			(0xC3)
+#define ES_SCANCODE_NUM_PERCENT			(0xC4)
+#define ES_SCANCODE_NUM_LESS_THAN		(0xC5)
+#define ES_SCANCODE_NUM_GREATER_THAN		(0xC6)
+#define ES_SCANCODE_NUM_AMPERSAND		(0xC7)
+#define ES_SCANCODE_NUM_DOUBLE_AMPERSAND		(0xC8)
+#define ES_SCANCODE_NUM_BAR			(0xC9)
+#define ES_SCANCODE_NUM_DOUBLE_BAR		(0xCA)
+#define ES_SCANCODE_NUM_COLON			(0xCB)
+#define ES_SCANCODE_NUM_HASH			(0xCC)
+#define ES_SCANCODE_NUM_SPACE			(0xCD)
+#define ES_SCANCODE_NUM_AT			(0xCE)
+#define ES_SCANCODE_NUM_EXCLAMATION_MARK		(0xCF)
+#define ES_SCANCODE_NUM_MEMORY_STORE		(0xD0)
+#define ES_SCANCODE_NUM_MEMORY_RECALL		(0xD1)
+#define ES_SCANCODE_NUM_MEMORY_CLEAR		(0xD2)
+#define ES_SCANCODE_NUM_MEMORY_ADD		(0xD3)
+#define ES_SCANCODE_NUM_MEMORY_SUBTRACT		(0xD4)
+#define ES_SCANCODE_NUM_MEMORY_MULTIPLY		(0xD5)
+#define ES_SCANCODE_NUM_MEMORY_DIVIDE		(0xD6)
+#define ES_SCANCODE_NUM_NEGATE			(0xD7)
+#define ES_SCANCODE_NUM_CLEAR_ALL		(0xD8)
+#define ES_SCANCODE_NUM_CLEAR			(0xD9)
+#define ES_SCANCODE_NUM_BINARY			(0xDA)
+#define ES_SCANCODE_NUM_OCTAL			(0xDB)
+#define ES_SCANCODE_NUM_DECIMAL			(0xDC)
+#define ES_SCANCODE_NUM_HEXADECIMAL		(0xDD)
+
+#define ES_SCANCODE_LEFT_CTRL			(0xE0)
+#define ES_SCANCODE_LEFT_SHIFT			(0xE1)
+#define ES_SCANCODE_LEFT_ALT			(0xE2)
+#define ES_SCANCODE_LEFT_FLAG			(0xE3)
+#define ES_SCANCODE_RIGHT_CTRL			(0xE4)
+#define ES_SCANCODE_RIGHT_SHIFT			(0xE5)
+#define ES_SCANCODE_RIGHT_ALT			(0xE6)
+#define ES_SCANCODE_RIGHT_FLAG			(0xE7)
+
+#define ES_SCANCODE_ACPI_POWER 			(0x100)
+#define ES_SCANCODE_ACPI_SLEEP 			(0x101)
+#define ES_SCANCODE_ACPI_WAKE  			(0x102)
+
+#define ES_SCANCODE_WWW_SEARCH			(0x10B)
+#define ES_SCANCODE_WWW_HOME			(0x10C)
+#define ES_SCANCODE_WWW_BACK			(0x10D)
+#define ES_SCANCODE_WWW_FORWARD			(0x10E)
+#define ES_SCANCODE_WWW_STOP			(0x10F)
+#define ES_SCANCODE_WWW_REFRESH			(0x110)
+#define ES_SCANCODE_WWW_STARRED			(0x111)
+
+uint32_t remap[] = {
+	0, 0, 0, 0, 0, 0, 0, 0,
+	0, ES_SCANCODE_ESCAPE, ES_SCANCODE_1, ES_SCANCODE_2, ES_SCANCODE_3, ES_SCANCODE_4, ES_SCANCODE_5, ES_SCANCODE_6,
+	ES_SCANCODE_7, ES_SCANCODE_8, ES_SCANCODE_9, ES_SCANCODE_0, ES_SCANCODE_HYPHEN, ES_SCANCODE_EQUALS, ES_SCANCODE_BACKSPACE, ES_SCANCODE_TAB,
+	ES_SCANCODE_Q, ES_SCANCODE_W, ES_SCANCODE_E, ES_SCANCODE_R, ES_SCANCODE_T, ES_SCANCODE_Y, ES_SCANCODE_U, ES_SCANCODE_I,
+	ES_SCANCODE_O, ES_SCANCODE_P, ES_SCANCODE_LEFT_BRACE, ES_SCANCODE_RIGHT_BRACE, ES_SCANCODE_ENTER, ES_SCANCODE_LEFT_CTRL, ES_SCANCODE_A, ES_SCANCODE_S,
+	ES_SCANCODE_D, ES_SCANCODE_F, ES_SCANCODE_G, ES_SCANCODE_H, ES_SCANCODE_J, ES_SCANCODE_K, ES_SCANCODE_L, ES_SCANCODE_PUNCTUATION_3, 
+	ES_SCANCODE_PUNCTUATION_4, ES_SCANCODE_PUNCTUATION_5, ES_SCANCODE_LEFT_SHIFT, ES_SCANCODE_PUNCTUATION_1, ES_SCANCODE_Z, ES_SCANCODE_X, ES_SCANCODE_C, ES_SCANCODE_V,
+	ES_SCANCODE_B, ES_SCANCODE_N, ES_SCANCODE_M, ES_SCANCODE_COMMA, ES_SCANCODE_PERIOD, ES_SCANCODE_SLASH, ES_SCANCODE_RIGHT_SHIFT, ES_SCANCODE_NUM_MULTIPLY,
+	ES_SCANCODE_LEFT_ALT, ES_SCANCODE_SPACE, ES_SCANCODE_CAPS_LOCK, ES_SCANCODE_F1, ES_SCANCODE_F2, ES_SCANCODE_F3, ES_SCANCODE_F4, ES_SCANCODE_F5,
+	ES_SCANCODE_F6, ES_SCANCODE_F7, ES_SCANCODE_F8, ES_SCANCODE_F9, ES_SCANCODE_F10, ES_SCANCODE_NUM_LOCK, ES_SCANCODE_SCROLL_LOCK, ES_SCANCODE_NUM_7,
+	ES_SCANCODE_NUM_8, ES_SCANCODE_NUM_9, ES_SCANCODE_NUM_SUBTRACT, ES_SCANCODE_NUM_4, ES_SCANCODE_NUM_5, ES_SCANCODE_NUM_6, ES_SCANCODE_NUM_ADD, ES_SCANCODE_NUM_1, 
+	ES_SCANCODE_NUM_2, ES_SCANCODE_NUM_3, ES_SCANCODE_NUM_0, ES_SCANCODE_NUM_POINT, 0, 0, ES_SCANCODE_PUNCTUATION_6, ES_SCANCODE_F11,
+	ES_SCANCODE_F12, 0, ES_SCANCODE_KATAKANA, ES_SCANCODE_HIRAGANA, 0, 0, 0, 0,
+	ES_SCANCODE_NUM_ENTER, ES_SCANCODE_RIGHT_CTRL, ES_SCANCODE_NUM_DIVIDE, ES_SCANCODE_PRINT_SCREEN, ES_SCANCODE_RIGHT_ALT, 0, ES_SCANCODE_HOME, ES_SCANCODE_UP_ARROW,
+	ES_SCANCODE_PAGE_UP, ES_SCANCODE_LEFT_ARROW, ES_SCANCODE_RIGHT_ARROW, ES_SCANCODE_END, ES_SCANCODE_DOWN_ARROW, ES_SCANCODE_PAGE_DOWN, ES_SCANCODE_INSERT, ES_SCANCODE_DELETE,
+	0, ES_SCANCODE_MM_MUTE, ES_SCANCODE_MM_QUIETER, ES_SCANCODE_MM_LOUDER, ES_SCANCODE_ACPI_POWER, ES_SCANCODE_NUM_EQUALS, 0, ES_SCANCODE_PAUSE,
+};
+
+int main(int argc, char **argv) {
+	if (argc != 2) {
+		fprintf(stderr, "Usage: %s <layout>\n", argv[0]);
+		return 1;
+	}
+
+	FILE *f = popen("setxkbmap -query | grep layout", "r");
+	char oldLayout[64] = {};
+	fread(oldLayout, 1, sizeof(oldLayout) - 1, f);
+	pclose(f);
+
+	char setLayout[128];
+	snprintf(setLayout, sizeof(setLayout), "setxkbmap %s", argv[1]);
+	system(setLayout);
+
+	Display *display = XOpenDisplay(NULL);
+	XIM xim = XOpenIM(display, 0, 0, 0);
+	XSetLocaleModifiers("");
+	XSetWindowAttributes attributes = {};
+	Window window = XCreateWindow(display, DefaultRootWindow(display), 0, 0, 800, 600, 0, 0, 
+			InputOutput, CopyFromParent, CWOverrideRedirect, &attributes);
+	XIC xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, window, XNFocusWindow, window, NULL);
+
+	uint16_t table[512 * 4] = {};
+	char stringBuffer[65536] = {};
+	size_t stringBufferPosition = 0;
+
+	for (uintptr_t state = 0; state < 4; state++) {
+		for (uintptr_t i = 0; i < 0x80; i++) {
+			XEvent event = {};
+			event.xkey.type = KeyPress;
+			event.xkey.display = display;
+			event.xkey.window = window;
+			event.xkey.state = state == 0 ? 0x00 : state == 1 ? 0x01 /* shift */ : state == 2 ? 0x80 /* alt gr */ : state == 3 ? 0x81 : 0;
+			event.xkey.keycode = i;
+			char text[32];
+			KeySym symbol = NoSymbol;
+			Status status;
+			size_t textBytes = Xutf8LookupString(xic, &event.xkey, text, sizeof(text) - 1, &symbol, &status); 
+			uint16_t offset = 0;
+
+			if (textBytes) {
+				offset = stringBufferPosition;
+				assert(stringBufferPosition + textBytes + 1 < sizeof(stringBuffer));
+				memcpy(stringBuffer + stringBufferPosition, text, textBytes);
+				stringBuffer[stringBufferPosition + textBytes] = 0;
+				stringBufferPosition += textBytes + 1;
+			}
+
+			table[remap[i] + state * 512] = offset;
+
+			if (remap[i] == ES_SCANCODE_PUNCTUATION_1) {
+				table[ES_SCANCODE_PUNCTUATION_2 + state * 512] = offset;
+			}
+		}
+	}
+
+	snprintf(setLayout, sizeof(setLayout), "setxkbmap %s", oldLayout + 7);
+	system(setLayout);
+
+	fwrite(table, 1, sizeof(table), stdout);
+	fwrite(stringBuffer, 1, stringBufferPosition, stdout);
+
+	return 0;
+}
diff --git a/res/Keyboard Layouts/fi.dat b/res/Keyboard Layouts/fi.dat
new file mode 100644
index 0000000..ce811d2
Binary files /dev/null and b/res/Keyboard Layouts/fi.dat differ
diff --git a/res/Keyboard Layouts/fo.dat b/res/Keyboard Layouts/fo.dat
new file mode 100644
index 0000000..3d74e19
Binary files /dev/null and b/res/Keyboard Layouts/fo.dat differ
diff --git a/res/Keyboard Layouts/fr.dat b/res/Keyboard Layouts/fr.dat
new file mode 100644
index 0000000..6f2ec91
Binary files /dev/null and b/res/Keyboard Layouts/fr.dat differ
diff --git a/res/Keyboard Layouts/gb.dat b/res/Keyboard Layouts/gb.dat
new file mode 100644
index 0000000..bafd438
Binary files /dev/null and b/res/Keyboard Layouts/gb.dat differ
diff --git a/res/Keyboard Layouts/ge.dat b/res/Keyboard Layouts/ge.dat
new file mode 100644
index 0000000..8e3bac3
Binary files /dev/null and b/res/Keyboard Layouts/ge.dat differ
diff --git a/res/Keyboard Layouts/gh.dat b/res/Keyboard Layouts/gh.dat
new file mode 100644
index 0000000..b3d9f38
Binary files /dev/null and b/res/Keyboard Layouts/gh.dat differ
diff --git a/res/Keyboard Layouts/gr.dat b/res/Keyboard Layouts/gr.dat
new file mode 100644
index 0000000..b9cecbd
Binary files /dev/null and b/res/Keyboard Layouts/gr.dat differ
diff --git a/res/Keyboard Layouts/hr.dat b/res/Keyboard Layouts/hr.dat
new file mode 100644
index 0000000..f64a0bb
Binary files /dev/null and b/res/Keyboard Layouts/hr.dat differ
diff --git a/res/Keyboard Layouts/hu.dat b/res/Keyboard Layouts/hu.dat
new file mode 100644
index 0000000..a95f048
Binary files /dev/null and b/res/Keyboard Layouts/hu.dat differ
diff --git a/res/Keyboard Layouts/id.dat b/res/Keyboard Layouts/id.dat
new file mode 100644
index 0000000..18f4c16
Binary files /dev/null and b/res/Keyboard Layouts/id.dat differ
diff --git a/res/Keyboard Layouts/ie.dat b/res/Keyboard Layouts/ie.dat
new file mode 100644
index 0000000..36443bc
Binary files /dev/null and b/res/Keyboard Layouts/ie.dat differ
diff --git a/res/Keyboard Layouts/il.dat b/res/Keyboard Layouts/il.dat
new file mode 100644
index 0000000..b962e39
Binary files /dev/null and b/res/Keyboard Layouts/il.dat differ
diff --git a/res/Keyboard Layouts/in.dat b/res/Keyboard Layouts/in.dat
new file mode 100644
index 0000000..19d4f10
Binary files /dev/null and b/res/Keyboard Layouts/in.dat differ
diff --git a/res/Keyboard Layouts/index.ini b/res/Keyboard Layouts/index.ini
new file mode 100644
index 0000000..792ef43
--- /dev/null
+++ b/res/Keyboard Layouts/index.ini	
@@ -0,0 +1,89 @@
+af=Afghani
+al=Albanian
+et=Amharic
+sy=Arabic (Syria)
+am=Armenian
+az=Azerbaijani
+ml=Bambara
+bd=Bangla
+by=Belarusian
+be=Belgian
+dz=Berber (Algeria, Latin)
+bg=Bulgarian
+mm=Burmese
+cn=Chinese
+hr=Croatian
+cz=Czech
+dk=Danish
+mv=Dhivehi
+nl=Dutch
+bt=Dzongkha
+au=English (Australian)
+cm=English (Cameroon)
+gh=English (Ghana)
+ng=English (Nigeria)
+za=English (South Africa)
+gb=English (UK)
+us=English (US)
+ee=Estonian
+fo=Faroese
+ph=Filipino
+fi=Finnish
+ca=French (Canada)
+cd=French (Democratic Republic of the Congo)
+tg=French (Togo)
+fr=French
+ge=Georgian
+at=German (Austria)
+ch=German (Switzerland)
+de=German
+gr=Greek
+il=Hebrew
+hu=Hungarian
+is=Icelandic
+in=Indian
+jv=Indonesian (Javanese)
+id=Indonesian (Latin)
+iq=Iraqi
+ie=Irish
+it=Italian
+jp=Japanese
+kz=Kazakh
+kh=Khmer (Cambodia)
+kr=Korean
+kg=Kyrgyz
+la=Lao
+lv=Latvian
+lt=Lithuanian
+mk=Macedonian
+my=Malay (Jawi, Arabic Keyboard)
+mt=Maltese
+md=Moldavian
+mn=Mongolian
+me=Montenegrin
+np=Nepali
+no=Norwegian
+ir=Persian
+pl=Polish
+br=Portuguese (Brazil)
+pt=Portuguese
+ro=Romanian
+ru=Russian
+rs=Serbian
+lk=Sinhala (phonetic)
+sk=Slovak
+si=Slovenian
+es=Spanish
+ke=Swahili (Kenya)
+tz=Swahili (Tanzania)
+se=Swedish
+tw=Taiwanese
+tj=Tajik
+th=Thai
+bw=Tswana
+tr=Turkish
+ua=Ukrainian
+pk=Urdu (Pakistan)
+uz=Uzbek
+vn=Vietnamese
+sn=Wolof
diff --git a/res/Keyboard Layouts/iq.dat b/res/Keyboard Layouts/iq.dat
new file mode 100644
index 0000000..a84fd57
Binary files /dev/null and b/res/Keyboard Layouts/iq.dat differ
diff --git a/res/Keyboard Layouts/ir.dat b/res/Keyboard Layouts/ir.dat
new file mode 100644
index 0000000..e7f7166
Binary files /dev/null and b/res/Keyboard Layouts/ir.dat differ
diff --git a/res/Keyboard Layouts/is.dat b/res/Keyboard Layouts/is.dat
new file mode 100644
index 0000000..dcc8447
Binary files /dev/null and b/res/Keyboard Layouts/is.dat differ
diff --git a/res/Keyboard Layouts/it.dat b/res/Keyboard Layouts/it.dat
new file mode 100644
index 0000000..4b36e8e
Binary files /dev/null and b/res/Keyboard Layouts/it.dat differ
diff --git a/res/Keyboard Layouts/jp.dat b/res/Keyboard Layouts/jp.dat
new file mode 100644
index 0000000..02b807f
Binary files /dev/null and b/res/Keyboard Layouts/jp.dat differ
diff --git a/res/Keyboard Layouts/jv.dat b/res/Keyboard Layouts/jv.dat
new file mode 100644
index 0000000..3d4efc7
Binary files /dev/null and b/res/Keyboard Layouts/jv.dat differ
diff --git a/res/Keyboard Layouts/ke.dat b/res/Keyboard Layouts/ke.dat
new file mode 100644
index 0000000..805cb06
Binary files /dev/null and b/res/Keyboard Layouts/ke.dat differ
diff --git a/res/Keyboard Layouts/kg.dat b/res/Keyboard Layouts/kg.dat
new file mode 100644
index 0000000..36faec0
Binary files /dev/null and b/res/Keyboard Layouts/kg.dat differ
diff --git a/res/Keyboard Layouts/kh.dat b/res/Keyboard Layouts/kh.dat
new file mode 100644
index 0000000..7c10e7d
Binary files /dev/null and b/res/Keyboard Layouts/kh.dat differ
diff --git a/res/Keyboard Layouts/kr.dat b/res/Keyboard Layouts/kr.dat
new file mode 100644
index 0000000..18f4c16
Binary files /dev/null and b/res/Keyboard Layouts/kr.dat differ
diff --git a/res/Keyboard Layouts/kz.dat b/res/Keyboard Layouts/kz.dat
new file mode 100644
index 0000000..4385c25
Binary files /dev/null and b/res/Keyboard Layouts/kz.dat differ
diff --git a/res/Keyboard Layouts/la.dat b/res/Keyboard Layouts/la.dat
new file mode 100644
index 0000000..f4b6651
Binary files /dev/null and b/res/Keyboard Layouts/la.dat differ
diff --git a/res/Keyboard Layouts/lk.dat b/res/Keyboard Layouts/lk.dat
new file mode 100644
index 0000000..2b778d6
Binary files /dev/null and b/res/Keyboard Layouts/lk.dat differ
diff --git a/res/Keyboard Layouts/lt.dat b/res/Keyboard Layouts/lt.dat
new file mode 100644
index 0000000..d8c0257
Binary files /dev/null and b/res/Keyboard Layouts/lt.dat differ
diff --git a/res/Keyboard Layouts/lv.dat b/res/Keyboard Layouts/lv.dat
new file mode 100644
index 0000000..2e4de2d
Binary files /dev/null and b/res/Keyboard Layouts/lv.dat differ
diff --git a/res/Keyboard Layouts/md.dat b/res/Keyboard Layouts/md.dat
new file mode 100644
index 0000000..d500293
Binary files /dev/null and b/res/Keyboard Layouts/md.dat differ
diff --git a/res/Keyboard Layouts/me.dat b/res/Keyboard Layouts/me.dat
new file mode 100644
index 0000000..b3c38ff
Binary files /dev/null and b/res/Keyboard Layouts/me.dat differ
diff --git a/res/Keyboard Layouts/mk.dat b/res/Keyboard Layouts/mk.dat
new file mode 100644
index 0000000..df755b0
Binary files /dev/null and b/res/Keyboard Layouts/mk.dat differ
diff --git a/res/Keyboard Layouts/ml.dat b/res/Keyboard Layouts/ml.dat
new file mode 100644
index 0000000..f6bb06c
Binary files /dev/null and b/res/Keyboard Layouts/ml.dat differ
diff --git a/res/Keyboard Layouts/mm.dat b/res/Keyboard Layouts/mm.dat
new file mode 100644
index 0000000..a0a7410
Binary files /dev/null and b/res/Keyboard Layouts/mm.dat differ
diff --git a/res/Keyboard Layouts/mn.dat b/res/Keyboard Layouts/mn.dat
new file mode 100644
index 0000000..4f7b00a
Binary files /dev/null and b/res/Keyboard Layouts/mn.dat differ
diff --git a/res/Keyboard Layouts/mt.dat b/res/Keyboard Layouts/mt.dat
new file mode 100644
index 0000000..724ecbb
Binary files /dev/null and b/res/Keyboard Layouts/mt.dat differ
diff --git a/res/Keyboard Layouts/mv.dat b/res/Keyboard Layouts/mv.dat
new file mode 100644
index 0000000..69a416b
Binary files /dev/null and b/res/Keyboard Layouts/mv.dat differ
diff --git a/res/Keyboard Layouts/my.dat b/res/Keyboard Layouts/my.dat
new file mode 100644
index 0000000..f2a42a2
Binary files /dev/null and b/res/Keyboard Layouts/my.dat differ
diff --git a/res/Keyboard Layouts/ng.dat b/res/Keyboard Layouts/ng.dat
new file mode 100644
index 0000000..e4f1057
Binary files /dev/null and b/res/Keyboard Layouts/ng.dat differ
diff --git a/res/Keyboard Layouts/nl.dat b/res/Keyboard Layouts/nl.dat
new file mode 100644
index 0000000..471eb77
Binary files /dev/null and b/res/Keyboard Layouts/nl.dat differ
diff --git a/res/Keyboard Layouts/no.dat b/res/Keyboard Layouts/no.dat
new file mode 100644
index 0000000..3813b8c
Binary files /dev/null and b/res/Keyboard Layouts/no.dat differ
diff --git a/res/Keyboard Layouts/np.dat b/res/Keyboard Layouts/np.dat
new file mode 100644
index 0000000..7b7be29
Binary files /dev/null and b/res/Keyboard Layouts/np.dat differ
diff --git a/res/Keyboard Layouts/ph.dat b/res/Keyboard Layouts/ph.dat
new file mode 100644
index 0000000..314272f
Binary files /dev/null and b/res/Keyboard Layouts/ph.dat differ
diff --git a/res/Keyboard Layouts/pk.dat b/res/Keyboard Layouts/pk.dat
new file mode 100644
index 0000000..fc87e24
Binary files /dev/null and b/res/Keyboard Layouts/pk.dat differ
diff --git a/res/Keyboard Layouts/pl.dat b/res/Keyboard Layouts/pl.dat
new file mode 100644
index 0000000..f946107
Binary files /dev/null and b/res/Keyboard Layouts/pl.dat differ
diff --git a/res/Keyboard Layouts/pt.dat b/res/Keyboard Layouts/pt.dat
new file mode 100644
index 0000000..9834164
Binary files /dev/null and b/res/Keyboard Layouts/pt.dat differ
diff --git a/res/Keyboard Layouts/ro.dat b/res/Keyboard Layouts/ro.dat
new file mode 100644
index 0000000..d500293
Binary files /dev/null and b/res/Keyboard Layouts/ro.dat differ
diff --git a/res/Keyboard Layouts/rs.dat b/res/Keyboard Layouts/rs.dat
new file mode 100644
index 0000000..941ba1c
Binary files /dev/null and b/res/Keyboard Layouts/rs.dat differ
diff --git a/res/Keyboard Layouts/ru.dat b/res/Keyboard Layouts/ru.dat
new file mode 100644
index 0000000..d67fd45
Binary files /dev/null and b/res/Keyboard Layouts/ru.dat differ
diff --git a/res/Keyboard Layouts/se.dat b/res/Keyboard Layouts/se.dat
new file mode 100644
index 0000000..28ea8ac
Binary files /dev/null and b/res/Keyboard Layouts/se.dat differ
diff --git a/res/Keyboard Layouts/si.dat b/res/Keyboard Layouts/si.dat
new file mode 100644
index 0000000..4b8d34b
Binary files /dev/null and b/res/Keyboard Layouts/si.dat differ
diff --git a/res/Keyboard Layouts/sk.dat b/res/Keyboard Layouts/sk.dat
new file mode 100644
index 0000000..9c3033f
Binary files /dev/null and b/res/Keyboard Layouts/sk.dat differ
diff --git a/res/Keyboard Layouts/sn.dat b/res/Keyboard Layouts/sn.dat
new file mode 100644
index 0000000..5e3ccc8
Binary files /dev/null and b/res/Keyboard Layouts/sn.dat differ
diff --git a/res/Keyboard Layouts/sy.dat b/res/Keyboard Layouts/sy.dat
new file mode 100644
index 0000000..a84fd57
Binary files /dev/null and b/res/Keyboard Layouts/sy.dat differ
diff --git a/res/Keyboard Layouts/tg.dat b/res/Keyboard Layouts/tg.dat
new file mode 100644
index 0000000..16423f3
Binary files /dev/null and b/res/Keyboard Layouts/tg.dat differ
diff --git a/res/Keyboard Layouts/th.dat b/res/Keyboard Layouts/th.dat
new file mode 100644
index 0000000..bc037cf
Binary files /dev/null and b/res/Keyboard Layouts/th.dat differ
diff --git a/res/Keyboard Layouts/tj.dat b/res/Keyboard Layouts/tj.dat
new file mode 100644
index 0000000..e8858bf
Binary files /dev/null and b/res/Keyboard Layouts/tj.dat differ
diff --git a/res/Keyboard Layouts/tr.dat b/res/Keyboard Layouts/tr.dat
new file mode 100644
index 0000000..c2b28df
Binary files /dev/null and b/res/Keyboard Layouts/tr.dat differ
diff --git a/res/Keyboard Layouts/tw.dat b/res/Keyboard Layouts/tw.dat
new file mode 100644
index 0000000..44526a6
Binary files /dev/null and b/res/Keyboard Layouts/tw.dat differ
diff --git a/res/Keyboard Layouts/tz.dat b/res/Keyboard Layouts/tz.dat
new file mode 100644
index 0000000..74d240a
Binary files /dev/null and b/res/Keyboard Layouts/tz.dat differ
diff --git a/res/Keyboard Layouts/ua.dat b/res/Keyboard Layouts/ua.dat
new file mode 100644
index 0000000..c3c4fe0
Binary files /dev/null and b/res/Keyboard Layouts/ua.dat differ
diff --git a/res/Keyboard Layouts/us.dat b/res/Keyboard Layouts/us.dat
new file mode 100644
index 0000000..18f4c16
Binary files /dev/null and b/res/Keyboard Layouts/us.dat differ
diff --git a/res/Keyboard Layouts/uz.dat b/res/Keyboard Layouts/uz.dat
new file mode 100644
index 0000000..5599cc8
Binary files /dev/null and b/res/Keyboard Layouts/uz.dat differ
diff --git a/res/Keyboard Layouts/vn.dat b/res/Keyboard Layouts/vn.dat
new file mode 100644
index 0000000..f0c5016
Binary files /dev/null and b/res/Keyboard Layouts/vn.dat differ
diff --git a/res/Keyboard Layouts/za.dat b/res/Keyboard Layouts/za.dat
new file mode 100644
index 0000000..2b6d2d3
Binary files /dev/null and b/res/Keyboard Layouts/za.dat differ
diff --git a/res/Theme Source.dat b/res/Theme Source.dat
index 16cbbd8..753b84b 100644
Binary files a/res/Theme Source.dat and b/res/Theme Source.dat differ
diff --git a/res/Theme.dat b/res/Theme.dat
index f0d371b..0c27b20 100644
Binary files a/res/Theme.dat and b/res/Theme.dat differ
diff --git a/shared/strings.cpp b/shared/strings.cpp
index 12da177..cf96aa3 100644
--- a/shared/strings.cpp
+++ b/shared/strings.cpp
@@ -132,6 +132,7 @@ DEFINE_INTERFACE_STRING(DesktopSettingsKeyboardKeyRepeatRate, "Key repeat rate:"
 DEFINE_INTERFACE_STRING(DesktopSettingsKeyboardCaretBlinkRate, "Caret blink rate:");
 DEFINE_INTERFACE_STRING(DesktopSettingsKeyboardTestTextboxIntroduction, "Try your settings in the textbox below:");
 DEFINE_INTERFACE_STRING(DesktopSettingsKeyboardUseSmartQuotes, "Use smart quotes when typing");
+DEFINE_INTERFACE_STRING(DesktopSettingsKeyboardLayout, "Keyboard layout:");
 
 DEFINE_INTERFACE_STRING(DesktopSettingsMouseDoubleClickSpeed, "Double click time:");
 DEFINE_INTERFACE_STRING(DesktopSettingsMouseSpeed, "Cursor movement speed:");
diff --git a/util/build_core.c b/util/build_core.c
index 951b132..ca68504 100644
--- a/util/build_core.c
+++ b/util/build_core.c
@@ -575,6 +575,23 @@ void BuildDesktop(Application *application) {
 		}
 	}
 
+	EsINIState s = {};
+	s.buffer = (char *) LoadFile("res/Keyboard Layouts/index.ini", &s.bytes);
+
+	while (EsINIParse(&s)) {
+		EsINIZeroTerminate(&s);
+
+		if (s.key[0] != ';') {
+			char in[128];
+			char name[128];
+			snprintf(in, sizeof(in), "res/Keyboard Layouts/%s.dat", s.key);
+			snprintf(name, sizeof(name), "Keyboard Layouts/%s.dat", s.key);
+			ADD_BUNDLE_INPUT(strdup(in), strdup(name), 16);
+		}
+	}
+
+	ADD_BUNDLE_INPUT("res/Keyboard Layouts/index.ini", "Keyboard Layouts.ini", 16);
+	ADD_BUNDLE_INPUT("res/Keyboard Layouts/License.txt", "Keyboard Layouts License.txt", 16);
 	ADD_BUNDLE_INPUT("res/Theme.dat", "Theme.dat", 16);
 	ADD_BUNDLE_INPUT("res/elementary Icons.dat", "Icons.dat", 16);
 	ADD_BUNDLE_INPUT("res/elementary Icons License.txt", "Icons License.txt", 16);
diff --git a/util/designer2.cpp b/util/designer2.cpp
index 673d741..3241961 100644
--- a/util/designer2.cpp
+++ b/util/designer2.cpp
@@ -3691,6 +3691,7 @@ int main(int argc, char **argv) {
 	UIWindowRegisterShortcut(window, UI_SHORTCUT(UI_KEYCODE_LETTER('Y'), 1 /* ctrl */, 0, 0, DocumentRedoStep, 0));
 	UIWindowRegisterShortcut(window, UI_SHORTCUT(UI_KEYCODE_LETTER('D'), 1 /* ctrl */, 0, 0, ObjectDuplicateCommand, 0));
 	UIWindowRegisterShortcut(window, UI_SHORTCUT(UI_KEYCODE_FKEY(2), 0, 0, 0, CanvasSwitchView, 0));
+	UIWindowRegisterShortcut(window, UI_SHORTCUT(UI_KEYCODE_FKEY(3), 0, 0, 0, CanvasSwitchSelectorIndex, 0));
 	UIWindowRegisterShortcut(window, UI_SHORTCUT(UI_KEYCODE_DIGIT('1'), 1 /* ctrl */, 0, 0, CanvasZoom100, 0));
 	UIWindowRegisterShortcut(window, UI_SHORTCUT(UI_KEYCODE_DELETE, 0, 0, 0, ObjectDeleteCommand, 0));