abouttreesummaryrefslogcommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xgen_sinhala_phonetic_layout.py524
-rw-r--r--res/values/layouts.xml3
-rw-r--r--res/xml/method.xml1
-rw-r--r--srcs/juloo.keyboard2/KeyValue.java32
-rw-r--r--srcs/layouts/sinhala_phonetic.xml107
5 files changed, 667 insertions, 0 deletions
diff --git a/gen_sinhala_phonetic_layout.py b/gen_sinhala_phonetic_layout.py
new file mode 100755
index 0000000..44cc39f
--- /dev/null
+++ b/gen_sinhala_phonetic_layout.py
@@ -0,0 +1,524 @@
+#! /bin/env python3
+
+"""
+Script to generate a layout based on an existing.
+
+Tuned to create Sinhala phonetic layout based on qwerty (US), but may be adoped
+for other scripts. Look at dicts before the LayoutBuilder code.
+
+Usage:
+ python3 gen_sinhala_phonetic_layout [-h|--help] [-v|--verbose] [-o|--output]
+
+By default with no args will write to corresponding file in `srcs/layouts/`.
+
+Script uses central symbol (in direction "c") to identify a key, which may not
+be appropriate for base (reference) layouts other, than qwerty (US).
+
+Warning will be printed to stderr if new symbol overrides some symbol of the
+reference layout in directions other, than "c".
+
+Exception will be rised on other
+conflicts e. g. when trying to move a symbol into occupied position.
+
+ - Made with latn_qwerty_us.xml from commit `6b1551d`
+ - Made with Python 3.13
+ - Requires Python >= 3.11
+"""
+
+import argparse
+import logging
+
+from enum import StrEnum
+from pathlib import Path
+from xml.etree import ElementTree
+
+
+class Placement(StrEnum):
+ C = 'c'
+ NW = 'nw'
+ N = 'n'
+ NE = 'ne'
+ E = 'e'
+ SE = 'se'
+ S = 's'
+ SW = 'sw'
+ W = 'w'
+
+
+# Based on XKB Sinhala (phonetic)
+KEYS_MAP: dict[str, tuple[str, str, str, str]] = {
+ # Row 1 ###########################################
+ 'q': ('ඍ', 'ඎ', '\u0DD8', '\u0DF2'),
+ 'w': ('ඇ', 'ඈ', '\u0DD0', '\u0DD1'),
+ 'e': ('එ', 'ඒ', '\u0DD9', '\u0DDA'),
+ 'r': ('ර', '', '', ''), # In XKB virama is on layer 2
+ 't': ('ත', 'ථ', 'ට', 'ඨ'),
+ 'y': ('ය', '', '', ''), # In XKB virama is on layer 2
+ 'u': ('උ', 'ඌ', '\u0DD4', '\u0DD6'),
+ 'i': ('ඉ', 'ඊ', '\u0DD2', '\u0DD3'),
+ 'o': ('ඔ', 'ඕ', '\u0DDC', '\u0DDD'),
+ 'p': ('ප', 'ඵ', '', ''),
+ # Row 2 ###########################################
+ 'a': ('අ', 'ආ', '\u0DCA', '\u0DCF'),
+ 's': ('ස', 'ශ', 'ෂ', ''),
+ 'd': ('ද', 'ධ', 'ඩ', 'ඪ'),
+ 'f': ('ෆ', '\u0D93', '', '\u0DDB'), # In XKB aiyanna placed otherwise
+ 'g': ('ග', 'ඝ', 'ඟ', ''),
+ 'h': ('හ', '\u0D83', '\u0DDE', 'ඖ'),
+ 'j': ('ජ', 'ඣ', 'ඦ', ''),
+ 'k': ('ක', 'ඛ', 'ඦ', 'ඐ'),
+ 'l': ('ල', 'ළ', '\u0DDF', '\u0DF3'),
+ # Row 3 ###########################################
+ 'z': ('ඤ', 'ඥ', '', ''), # In XKB contains bar, broken bar
+ 'x': ('ඳ', 'ඬ', '', ''),
+ 'c': ('ච', 'ඡ', '', ''),
+ 'v': ('ව', '', '', ''),
+ 'b': ('බ', 'භ', '', ''),
+ 'n': ('න', 'ණ', '\u0D82', 'ඞ'),
+ 'm': ('ම', 'ඹ', '', ''),
+}
+
+# How to place four levels of Key.
+# Syntax: LEVEL: PLACEMENT | 'FROM_LEVEL+MODIFIER'
+# The last means symbol on level FROM_LEVEL with modifier key MODIFIER gives
+# key on level LEVEL
+#
+LEVELS_MAP = {
+ 0: Placement.C,
+ 1: Placement.NE,
+ 2: '0+shift',
+ 3: '1+shift',
+}
+
+# Additional modify keys combinations.
+# Syntax:
+# MODKEY: { A: B }
+#
+MODMAP_EXTRA: dict[str, dict[str, str]] = {
+ 'shift': {
+ # Astrological numbers
+ '1': '෧',
+ '2': '෨',
+ '3': '෩',
+ '4': '෪',
+ '5': '෫',
+ '6': '෬',
+ '7': '෭',
+ '8': '෮',
+ '9': '෯',
+ '0': '෦',
+ # Kunddaliya
+ '.': '෴',
+ # Extra broken bar intead z key in XKB
+ '\u007C': '\u00A6',
+ # Special whitespaces
+ 'zwj': 'zwnj',
+ },
+ 'fn': {
+ # Sinhala archaic digits
+ 'ඍ': '𑇡', # 1
+ 'ඇ': '𑇢', # 2
+ 'එ': '𑇣', # 3
+ 'ර': '𑇤', # 4
+ 'ත': '𑇥', # 5
+ 'ය': '𑇦', # 6
+ 'උ': '𑇧', # 7
+ 'ඉ': '𑇨', # 8
+ 'ඔ': '𑇩', # 9
+ 'ප': '𑇪', # 10
+ 'අ': '𑇫', # 20
+ 'ස': '𑇬', # 30
+ 'ද': '𑇭', # 40
+ 'ෆ': '𑇮', # 50
+ 'ග': '𑇯', # 60
+ 'හ': '𑇰', # 70
+ 'ජ': '𑇱', # 80
+ 'ක': '𑇲', # 90
+ 'ල': '𑇳', # 100
+ 'ළ': '𑇴', # 1000
+ # Sinhala candrabindu for Sanskrit
+ 'ණ': '\u0D81',
+ },
+}
+
+# Table to move additional characters in reference layout.
+# Format is (CENTRAL_CHAR, PLACEMENT): (CENTRAL_CHAR, PLACEMENT). E. g. to move
+# char from key with central character "q", direction "se" to key with central
+# character "w", direction "sw", add line:
+# ('q', Placement.SE): ('w', Placement.SW),
+#
+# To delete a char, use None as destination placement. E.g.:
+# ('q', Placment.SE): ('q', None)
+#
+# Moving of main char in central placement is not supported.
+#
+TRANSITIONS_MAP: dict[tuple[str, Placement], tuple[str, Placement | None]] = {
+ ('q', Placement.SE): ('q', Placement.SW), # loc esc
+ ('q', Placement.NE): ('q', Placement.SE), # 1
+
+ ('w', Placement.NE): ('w', Placement.SE), # 2
+
+ ('e', Placement.SE): ('r', Placement.NW), # loc €
+ ('e', Placement.NE): ('e', Placement.SE), # 3
+
+ ('r', Placement.NE): ('r', Placement.SE), # 4
+ ('t', Placement.NE): ('t', Placement.SE), # 5
+ ('y', Placement.NE): ('y', Placement.SE), # 6
+ ('u', Placement.NE): ('u', Placement.SE), # 7
+ ('i', Placement.NE): ('i', Placement.SE), # 8
+
+ ('o', Placement.SE): ('p', Placement.SW), # )
+ ('o', Placement.NE): ('o', Placement.SE), # 9
+
+ ('p', Placement.NE): ('p', Placement.SE), # 0
+
+ ('a', Placement.NE): ('a', Placement.NW), # `
+ ('a', Placement.NW): ('a', Placement.SW), # loc tab
+
+ ('s', Placement.NE): ('s', Placement.NW), # loc §
+
+ ('g', Placement.SW): ('g', Placement.NW), # _
+ ('g', Placement.NE): ('g', Placement.SW), # -
+
+ ('h', Placement.SW): ('h', Placement.NW), # +
+ ('h', Placement.NE): ('h', Placement.SW), # =
+
+ ('l', Placement.NE): ('l', Placement.NW), # |
+
+ ('x', Placement.NE): ('x', Placement.NW), # loc †
+ ('c', Placement.NE): ('c', Placement.NW), # <
+ ('b', Placement.NE): ('b', Placement.NW), # ?
+ ('n', Placement.NE): ('n', Placement.NW), # :
+ ('m', Placement.NE): ('m', Placement.NW), # "
+}
+
+# Add additional characters to arbitrary places.
+# Syntax is CHAR: POSITION, where POSITION is a pari as in TRANSITIONS_MAP.
+#
+CHARS_EXTRA = {
+ # In XKB ZWJ is on `/` key, and ZWNJ is on spacebar
+ 'zwj': ('m', Placement.SE),
+}
+
+
+# List of char unicode numbers and inclusive ranges of numbers to encode as XML
+# numeric character references.
+# Good for combining signs to not mess with quotes.
+# Characters in line of the keyboard tag will not be escaped.
+#
+ESCAPE_LIST: list[int | tuple[int, int]] = [
+ # Sinhalese diacritics
+ (0xD81, 0xD83),
+ (0xDCA, 0xDDF),
+]
+
+# Default filename. Output path can be overrided with `-o` flag also.
+LAYOUT_FILENAME = 'sinhala_phonetic.xml'
+
+# Will be placed after XML declaration. Need to have proper <!-- --> tags.
+COMMENT = '''
+<!-- This file defines Sinhala layout.
+
+Based on XKB Sinhala (phonetic) layout.
+-->
+'''
+
+BASE_DIR = Path(__file__).parent
+REFERENCE_LAYOUT_FILE = BASE_DIR / 'srcs/layouts/latn_qwerty_us.xml'
+
+LOGGER = logging.getLogger(__name__)
+KeysMapType = list[list[ElementTree.Element]]
+
+
+class LayoutGenError(RuntimeError):
+ ...
+
+
+def xml_elem_to_str(element: ElementTree.Element) -> str:
+ return ElementTree.tostring(
+ element,
+ xml_declaration=False,
+ encoding='unicode').strip()
+
+
+def keys_map_to_str(keys_map: KeysMapType) -> str:
+ """ Make laout rows map printable for debug purposes """
+ result = '[\n'
+ for row in keys_map:
+ result += ' ' * 4
+ for key in row:
+ result += str(key.attrib) + ', '
+ result += '\n'
+ result += ']'
+ return result
+
+
+def is_in_escape_list(char: str | int) -> bool:
+ if isinstance(char, str):
+ char = ord(char)
+ for item in ESCAPE_LIST:
+ if isinstance(item, tuple) and char >= item[0] and char <= item[1]:
+ return True
+ elif isinstance(item, int):
+ if char == item:
+ return True
+ else:
+ TypeError(f'Unexpected item {item} of ESCAPE_LIST')
+ return False
+
+
+def xml_encode_char(ch: str | int) -> str:
+ if isinstance(ch, str):
+ ch = ord(ch)
+ hex_val = hex(ch).split('x')[-1]
+ return f'&#x{hex_val.upper().zfill(4)};'
+
+
+class LayoutBuilder:
+ XML_DECLARATION = "<?xml version='1.0' encoding='utf-8'?>"
+
+ def __init__(
+ self,
+ name: str = '',
+ script: str = '',
+ numpad_script: str = '',
+ comment: str = '',
+ ) -> None:
+ """
+ :param comment: MUST be a valid XML comment wrapped in <!-- tags -->
+ """
+ attrs = {}
+ if name:
+ attrs['name'] = name
+ if script:
+ attrs['script'] = script
+ if numpad_script:
+ attrs['numpad_script'] = numpad_script
+ self._comment = None
+ if comment:
+ self._comment = comment.strip() or None
+ self._xml_keyboard = ElementTree.Element('keyboard', attrib=attrs)
+ self._modmap = ElementTree.Element('modmap')
+
+ @staticmethod
+ def _parse_reference_layout() -> list[ElementTree.Element]:
+ return ElementTree.parse(REFERENCE_LAYOUT_FILE).findall('row')
+
+ @staticmethod
+ def _move_untransited_to_new_map(
+ ref_map: KeysMapType,
+ new_map: KeysMapType
+ ) -> None:
+ coordinates = [
+ (row_num, key_num)
+ for row_num in range(len(ref_map))
+ for key_num in range(len(ref_map[row_num]))
+ ]
+
+ for row_num, key_num in coordinates:
+ old_key = ref_map[row_num][key_num]
+ new_key = new_map[row_num][key_num]
+ for k, val in old_key.attrib.items():
+ if (transited := new_key.attrib.get(k)) is not None:
+ msg = (
+ f'Transition of {transited} to'
+ f' {new_key.get(Placement.C)}:{k} conflictls with'
+ f' existing value "{val}"')
+ raise LayoutGenError(msg)
+ new_key.set(k, val)
+
+ @staticmethod
+ def _add_extra_chars_to_ref_map(
+ coord_map: dict[str, tuple[int, int]],
+ new_map: KeysMapType
+ ) -> None:
+ for char, (to_key_name, to_plc) in CHARS_EXTRA.items():
+ if not (to_coord := coord_map.get(to_key_name)):
+ msg = f'Trying to add "{char}" to missing key "{to_key_name}"'
+ raise LayoutGenError(msg)
+ row_num, key_num = to_coord
+ key = new_map[row_num][key_num]
+ if (existing := key.get(to_plc)) is not None:
+ msg = f'Trying to add char to <{to_key_name}:{to_plc}>, but already contains "{existing}"'
+ raise LayoutGenError(msg)
+ key.set(to_plc, char)
+ LOGGER.info(
+ 'Added "%s" to <%s:%s>',
+ char, to_key_name, to_plc)
+
+ @classmethod
+ def _apply_transitions(cls, ref_map: list) -> list:
+ coord_map: dict[str, tuple[int, int]] = {}
+
+ coordinates = [
+ (row_num, key_num)
+ for row_num in range(len(ref_map))
+ for key_num in range(len(ref_map[row_num]))
+ ]
+
+ for row_num, key_num in coordinates:
+ row = ref_map[row_num]
+ key = row[key_num]
+ key_name = key.get(Placement.C)
+ if key_name in coord_map:
+ msg = f'Duplicated value "{key_name}" in central position'
+ raise LayoutGenError(msg)
+ coord_map[key_name] = (row_num, key_num)
+
+ # Make new map with empty keys
+ result_map = [[ElementTree.Element('key') for key in row] for row in ref_map]
+
+ # Place by transitions map on new places
+ for (from_key_name, from_plc), (to_key_name, to_plc) in TRANSITIONS_MAP.items():
+ if Placement.C in (from_plc, to_plc):
+ raise NotImplementedError('Transition from or to placment "c"')
+ if not (from_coord := coord_map.get(from_key_name)):
+ raise LayoutGenError(f'Transition from missing key {from_key_name}')
+ if not (to_coord := coord_map.get(to_key_name)):
+ raise LayoutGenError(f'Transition to missing key {to_key_name}')
+ from_key = ref_map[from_coord[0]][from_coord[1]]
+ to_key = result_map[to_coord[0]][to_coord[1]]
+ try:
+ val = from_key.attrib.pop(from_plc)
+ except KeyError:
+ msg = f'No value in key {from_key_name}, placement {from_plc} to move'
+ raise LayoutGenError(msg)
+ if to_plc is not None:
+ if to_key.get(to_plc):
+ msg = f'Second transition to key {to_key_name}, placement {to_plc}'
+ raise LayoutGenError(msg)
+ to_key.set(to_plc, val)
+ LOGGER.info(
+ 'Moved "%s" from <%s:%s> to <%s:%s>',
+ val, from_key_name, from_plc, to_key_name, to_plc)
+ else:
+ LOGGER.info(
+ 'Deleted "%s" from <%s:%s>',
+ val, from_key_name, from_plc)
+
+ # Fill new map with other values
+ cls._move_untransited_to_new_map(ref_map, new_map=result_map)
+
+ # Add additional characters
+ cls._add_extra_chars_to_ref_map(coord_map, new_map=result_map)
+
+ return result_map
+
+ @staticmethod
+ def _resolve_placement(
+ key: ElementTree.Element,
+ placement: Placement,
+ new_char: str
+ ) -> None:
+ if placement != Placement.C:
+ central_char = key.get(Placement.C)
+ existing = key.get(placement)
+ if existing:
+ LOGGER.warning(
+ 'Placement %s of key %s already occupied with %s',
+ placement, central_char, existing)
+ key.set(placement, new_char)
+
+ def _process_key(self, key: ElementTree.Element) -> ElementTree.Element:
+ central_char = key.get(Placement.C)
+ if not central_char:
+ return key
+ new_key_entry = KEYS_MAP.get(central_char)
+ if new_key_entry is None:
+ return key
+
+ for level, placement_spec in LEVELS_MAP.items():
+ if not (new_char := new_key_entry[level]):
+ continue
+ if '+' in placement_spec:
+ pair = placement_spec.split('+')
+ from_level, modkey = int(pair[0]), pair[1]
+ key_a = new_key_entry[from_level]
+ key_b = new_char
+ if key_a is None:
+ raise LayoutGenError(f'Tried to modife {key_a} to {key_b}')
+ ElementTree.SubElement(self._modmap, modkey, a=key_a, b=key_b)
+ else:
+ placement = Placement(placement_spec)
+ self._resolve_placement(key, placement=placement, new_char=new_char)
+ return key
+
+ @staticmethod
+ def _make_extra_modmap(modmap: ElementTree.Element) -> ElementTree.Element:
+ for modkey, ab_map in MODMAP_EXTRA.items():
+ for a, b in ab_map.items():
+ LOGGER.info('Adding modmap %s "%s" -> "%s"', modkey, a, b)
+ ElementTree.SubElement(modmap, modkey, a=a, b=b)
+ return modmap
+
+ @staticmethod
+ def _post_escape(data: str) -> str:
+ buf = ''
+ lines = data.splitlines(keepends=True)
+ for line in lines:
+ # Skip keyboard tag line to keep attributes
+ if '<keyboard ' in line:
+ buf += line
+ continue
+ for ch in line:
+ if is_in_escape_list(ch):
+ ch = xml_encode_char(ch)
+ buf += ch
+ return buf
+
+ def build(self) -> None:
+ raw_ref_rows = self._parse_reference_layout()
+ ref_rows = self._apply_transitions(raw_ref_rows)
+ for row in ref_rows:
+ new_row = ElementTree.SubElement(self._xml_keyboard, 'row')
+ for key in row:
+ LOGGER.debug(
+ 'Processing reference entry %s',
+ xml_elem_to_str(key))
+ new_row.append(self._process_key(key))
+ self._modmap = self._make_extra_modmap(self._modmap)
+ self._xml_keyboard.append(self._modmap)
+
+ def get_xml(self) -> str:
+ ElementTree.indent(self._xml_keyboard)
+ body_raw = xml_elem_to_str(self._xml_keyboard)
+ body = self._post_escape(body_raw)
+
+ result = self.XML_DECLARATION + '\n'
+ if self._comment:
+ result += self._comment + '\n'
+ result += body + '\n'
+
+ return result
+
+
+def get_args() -> argparse.Namespace:
+ parser = argparse.ArgumentParser(
+ prog='gen_sinhala_phonetic_layout',
+ description='Generate XKB-based Sinhala layout',)
+ parser.add_argument(
+ '-o',
+ '--output',
+ default=BASE_DIR / f'srcs/layouts/{LAYOUT_FILENAME}',
+ help='File to write result, `-` for stdout')
+ parser.add_argument(
+ '-v',
+ '--verbose',
+ help='More verbose logging',
+ action='store_true')
+ return parser.parse_args()
+
+
+if __name__ == '__main__':
+ args = get_args()
+ logging.basicConfig(
+ level=logging.DEBUG if args.verbose else logging.WARNING,
+ format='%(levelname)s: %(message)s')
+ builder = LayoutBuilder(name='සිංහල', script='sinhala', comment=COMMENT)
+ builder.build()
+ content = builder.get_xml()
+ if args.output == '-':
+ print(content)
+ else:
+ with open(args.output, 'w') as file:
+ file.write(content)
diff --git a/res/values/layouts.xml b/res/values/layouts.xml
index e2bec86..417b497 100644
--- a/res/values/layouts.xml
+++ b/res/values/layouts.xml
@@ -80,6 +80,7 @@
<item>latn_qwertz_sq</item>
<item>latn_workman_us</item>
<item>shaw_imperial_en</item>
+ <item>sinhala_phonetic</item>
<item>tamil_default</item>
<item>urdu_phonetic_ur</item>
<item>custom</item>
@@ -163,6 +164,7 @@
<item>QWERTZ (Albanian)</item>
<item>WORKMAN (US)</item>
<item>Shaw Imperial</item>
+ <item>සිංහල</item>
<item>தமிழ்</item>
<item>Urdu Phonetic</item>
<item>@string/pref_layout_e_custom</item>
@@ -246,6 +248,7 @@
<item>@xml/latn_qwertz_sq</item>
<item>@xml/latn_workman_us</item>
<item>@xml/shaw_imperial_en</item>
+ <item>@xml/sinhala_phonetic</item>
<item>@xml/tamil_default</item>
<item>@xml/urdu_phonetic_ur</item>
<item>-1</item>
diff --git a/res/xml/method.xml b/res/xml/method.xml
index 8d738b9..8b2599f 100644
--- a/res/xml/method.xml
+++ b/res/xml/method.xml
@@ -51,6 +51,7 @@
<subtype android:label="%s" android:languageTag="pt" android:imeSubtypeLocale="pt_BR" android:imeSubtypeMode="keyboard" android:isAsciiCapable="true" android:imeSubtypeExtraValue="script=latin,default_layout=latn_qwerty_pt,extra_keys=accent_aigu:á:é:í:ó:ú@d|accent_cedille:ç@c|accent_circonflexe:â:ê:ô@f|accent_grave:à:ò@f|accent_tilde:ã:õ@n|€|ª|º"/>
<subtype android:label="%s" android:languageTag="ro" android:imeSubtypeLocale="ro_RO" android:imeSubtypeMode="keyboard" android:isAsciiCapable="true" android:imeSubtypeExtraValue="script=latin,default_layout=latn_qwerty_ro,extra_keys=ă|â|î|ș|ț|€|$"/>
<subtype android:label="%s" android:languageTag="ru" android:imeSubtypeLocale="ru_RU" android:imeSubtypeMode="keyboard" android:isAsciiCapable="true" android:imeSubtypeExtraValue="script=latin,default_layout=cyrl_jcuken_ru"/>
+ <subtype android:label="%s" android:languageTag="si" android:imeSubtypeLocale="si_LK" android:imeSubtypeMode="keyboard" android:isAsciiCapable="true" android:imeSubtypeExtraValue="script=sinhala,default_layout=sinhala_phonetic,extra_keys=₨"/>
<subtype android:label="%s" android:languageTag="sk" android:imeSubtypeLocale="sk_SK" android:imeSubtypeMode="keyboard" android:isAsciiCapable="true" android:imeSubtypeExtraValue="script=latin,default_layout=latn_qwertz_sk,extra_keys=accent_caron:ě:ř:ž:š:č:ň:ď:ľ:ť@f|accent_ring:ů@s|accent_circonflexe:ô@f|accent_trema:ä:ü:ö@u|accent_aigu:á:é:í:ó:ú:ŕ:ś:ĺ:ý@d"/>
<subtype android:label="%s" android:languageTag="sq" android:imeSubtypeLocale="sq_AL" android:imeSubtypeMode="keyboard" android:isAsciiCapable="true" android:imeSubtypeExtraValue="script=latin,default_layout=latn_qwertz_sq"/>
<subtype android:label="%s" android:languageTag="sr" android:imeSubtypeLocale="sr_" android:imeSubtypeMode="keyboard" android:isAsciiCapable="true" android:imeSubtypeExtraValue="script=latin,default_layout=cyrl_lynyertz_sr"/>
diff --git a/srcs/juloo.keyboard2/KeyValue.java b/srcs/juloo.keyboard2/KeyValue.java
index ecfdd94..ee7b415 100644
--- a/srcs/juloo.keyboard2/KeyValue.java
+++ b/srcs/juloo.keyboard2/KeyValue.java
@@ -769,6 +769,38 @@ public final class KeyValue implements Comparable<KeyValue>
case "௲": case "௳":
return makeStringKey(name, FLAG_SMALLER_FONT);
+ /* Sinhala letters to reduced size */
+ case "අ": case "ආ": case "ඇ": case "ඈ": case "ඉ":
+ case "ඊ": case "උ": case "ඌ": case "ඍ": case "ඎ":
+ case "ඏ": case "ඐ": case "එ": case "ඒ": case "ඓ":
+ case "ඔ": case "ඕ": case "ඖ": case "ක": case "ඛ":
+ case "ග": case "ඝ": case "ඞ": case "ඟ": case "ච":
+ case "ඡ": case "ජ": case "ඣ": case "ඤ": case "ඥ":
+ case "ඦ": case "ට": case "ඨ": case "ඩ": case "ඪ":
+ case "ණ": case "ඬ": case "ත": case "ථ": case "ද":
+ case "ධ": case "න": case "ඳ": case "ප": case "ඵ":
+ case "බ": case "භ": case "ම": case "ඹ": case "ය":
+ case "ර": case "ල": case "ව": case "ශ": case "ෂ":
+ case "ස": case "හ": case "ළ": case "ෆ":
+ /* Astrological numbers */
+ case "෦": case "෧": case "෨": case "෩": case "෪":
+ case "෫": case "෬": case "෭": case "෮": case "෯":
+ case "ෲ": case "ෳ":
+ /* Diacritics */
+ case "\u0d81": case "\u0d82": case "\u0d83": case "\u0dca":
+ case "\u0dcf": case "\u0dd0": case "\u0dd1": case "\u0dd2":
+ case "\u0dd3": case "\u0dd4": case "\u0dd6": case "\u0dd8":
+ case "\u0dd9": case "\u0dda": case "\u0ddb": case "\u0ddc":
+ case "\u0ddd": case "\u0dde": case "\u0ddf":
+ /* Archaic digits */
+ case "𑇡": case "𑇢": case "𑇣": case "𑇤": case "𑇥":
+ case "𑇦": case "𑇧": case "𑇨": case "𑇩": case "𑇪":
+ case "𑇫": case "𑇬": case "𑇭": case "𑇮": case "𑇯":
+ case "𑇰": case "𑇱": case "𑇲": case "𑇳": case "𑇴":
+ /* Exta */
+ case "෴": case "₨": // Rupee is not exclusively Sinhala sign
+ return makeStringKey(name, FLAG_SMALLER_FONT);
+
/* Internal keys */
case "selection_mode": return makeInternalModifier(Modifier.SELECTION_MODE);
diff --git a/srcs/layouts/sinhala_phonetic.xml b/srcs/layouts/sinhala_phonetic.xml
new file mode 100644
index 0000000..d9ae23f
--- /dev/null
+++ b/srcs/layouts/sinhala_phonetic.xml
@@ -0,0 +1,107 @@
+<?xml version='1.0' encoding='utf-8'?>
+<!-- This file defines Sinhala layout.
+
+Based on XKB Sinhala (phonetic) layout.
+-->
+<keyboard name="සිංහල" script="sinhala">
+ <row>
+ <key sw="loc esc" se="1" c="ඍ" ne="ඎ" />
+ <key se="2" c="ඇ" nw="~" sw="\@" ne="ඈ" />
+ <key se="3" c="එ" nw="!" sw="\#" ne="ඒ" />
+ <key nw="loc €" se="4" c="ර" sw="$" />
+ <key se="5" c="ත" sw="%" ne="ථ" />
+ <key se="6" c="ය" sw="^" />
+ <key se="7" c="උ" sw="&amp;" ne="ඌ" />
+ <key se="8" c="ඉ" sw="*" ne="ඊ" />
+ <key se="9" c="ඔ" sw="(" ne="ඕ" />
+ <key sw=")" se="0" c="ප" ne="ඵ" />
+ </row>
+ <row>
+ <key nw="`" sw="loc tab" shift="0.5" c="අ" ne="ආ" />
+ <key nw="loc §" c="ස" sw="loc ß" ne="ශ" />
+ <key c="ද" ne="ධ" />
+ <key c="ෆ" ne="ඓ" />
+ <key nw="_" sw="-" c="ග" ne="ඝ" />
+ <key nw="+" sw="=" c="හ" ne="&#x0D83;" />
+ <key c="ජ" se="}" sw="{" ne="ඣ" />
+ <key c="ක" sw="[" se="]" ne="ඛ" />
+ <key nw="|" c="ල" sw="\\" ne="ළ" />
+ </row>
+ <row>
+ <key width="1.5" c="shift" ne="loc capslock" />
+ <key c="ඤ" ne="ඥ" />
+ <key nw="loc †" c="ඳ" ne="ඬ" />
+ <key nw="&lt;" c="ච" sw="." ne="ඡ" />
+ <key c="ව" ne="&gt;" sw="," />
+ <key nw="\?" c="බ" sw="/" ne="භ" />
+ <key nw=":" c="න" sw=";" ne="ණ" />
+ <key nw="&quot;" c="ම" sw="'" se="zwj" ne="ඹ" />
+ <key width="1.5" c="backspace" ne="delete" />
+ </row>
+ <modmap>
+ <shift a="ඍ" b="&#x0DD8;" />
+ <shift a="ඎ" b="ෲ" />
+ <shift a="ඇ" b="&#x0DD0;" />
+ <shift a="ඈ" b="&#x0DD1;" />
+ <shift a="එ" b="&#x0DD9;" />
+ <shift a="ඒ" b="&#x0DDA;" />
+ <shift a="ත" b="ට" />
+ <shift a="ථ" b="ඨ" />
+ <shift a="උ" b="&#x0DD4;" />
+ <shift a="ඌ" b="&#x0DD6;" />
+ <shift a="ඉ" b="&#x0DD2;" />
+ <shift a="ඊ" b="&#x0DD3;" />
+ <shift a="ඔ" b="&#x0DDC;" />
+ <shift a="ඕ" b="&#x0DDD;" />
+ <shift a="අ" b="&#x0DCA;" />
+ <shift a="ආ" b="&#x0DCF;" />
+ <shift a="ස" b="ෂ" />
+ <shift a="ද" b="ඩ" />
+ <shift a="ධ" b="ඪ" />
+ <shift a="ඓ" b="&#x0DDB;" />
+ <shift a="ග" b="ඟ" />
+ <shift a="හ" b="&#x0DDE;" />
+ <shift a="&#x0D83;" b="ඖ" />
+ <shift a="ජ" b="ඦ" />
+ <shift a="ක" b="ඦ" />
+ <shift a="ඛ" b="ඐ" />
+ <shift a="ල" b="&#x0DDF;" />
+ <shift a="ළ" b="ෳ" />
+ <shift a="න" b="&#x0D82;" />
+ <shift a="ණ" b="ඞ" />
+ <shift a="1" b="෧" />
+ <shift a="2" b="෨" />
+ <shift a="3" b="෩" />
+ <shift a="4" b="෪" />
+ <shift a="5" b="෫" />
+ <shift a="6" b="෬" />
+ <shift a="7" b="෭" />
+ <shift a="8" b="෮" />
+ <shift a="9" b="෯" />
+ <shift a="0" b="෦" />
+ <shift a="." b="෴" />
+ <shift a="|" b="¦" />
+ <shift a="zwj" b="zwnj" />
+ <fn a="ඍ" b="𑇡" />
+ <fn a="ඇ" b="𑇢" />
+ <fn a="එ" b="𑇣" />
+ <fn a="ර" b="𑇤" />
+ <fn a="ත" b="𑇥" />
+ <fn a="ය" b="𑇦" />
+ <fn a="උ" b="𑇧" />
+ <fn a="ඉ" b="𑇨" />
+ <fn a="ඔ" b="𑇩" />
+ <fn a="ප" b="𑇪" />
+ <fn a="අ" b="𑇫" />
+ <fn a="ස" b="𑇬" />
+ <fn a="ද" b="𑇭" />
+ <fn a="ෆ" b="𑇮" />
+ <fn a="ග" b="𑇯" />
+ <fn a="හ" b="𑇰" />
+ <fn a="ජ" b="𑇱" />
+ <fn a="ක" b="𑇲" />
+ <fn a="ල" b="𑇳" />
+ <fn a="ළ" b="𑇴" />
+ <fn a="ණ" b="&#x0D81;" />
+ </modmap>
+</keyboard>