mirror of
https://github.com/Julow/Unexpected-Keyboard.git
synced 2025-06-26 04:32:25 +02:00
Sinhala phonetic layout (#965)
This commit is contained in:
parent
d6dfb5c18c
commit
de5c5b5b60
524
gen_sinhala_phonetic_layout.py
Executable file
524
gen_sinhala_phonetic_layout.py
Executable file
@ -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)
|
@ -80,6 +80,7 @@
|
|||||||
<item>latn_qwertz_sq</item>
|
<item>latn_qwertz_sq</item>
|
||||||
<item>latn_workman_us</item>
|
<item>latn_workman_us</item>
|
||||||
<item>shaw_imperial_en</item>
|
<item>shaw_imperial_en</item>
|
||||||
|
<item>sinhala_phonetic</item>
|
||||||
<item>tamil_default</item>
|
<item>tamil_default</item>
|
||||||
<item>urdu_phonetic_ur</item>
|
<item>urdu_phonetic_ur</item>
|
||||||
<item>custom</item>
|
<item>custom</item>
|
||||||
@ -163,6 +164,7 @@
|
|||||||
<item>QWERTZ (Albanian)</item>
|
<item>QWERTZ (Albanian)</item>
|
||||||
<item>WORKMAN (US)</item>
|
<item>WORKMAN (US)</item>
|
||||||
<item>Shaw Imperial</item>
|
<item>Shaw Imperial</item>
|
||||||
|
<item>සිංහල</item>
|
||||||
<item>தமிழ்</item>
|
<item>தமிழ்</item>
|
||||||
<item>Urdu Phonetic</item>
|
<item>Urdu Phonetic</item>
|
||||||
<item>@string/pref_layout_e_custom</item>
|
<item>@string/pref_layout_e_custom</item>
|
||||||
@ -246,6 +248,7 @@
|
|||||||
<item>@xml/latn_qwertz_sq</item>
|
<item>@xml/latn_qwertz_sq</item>
|
||||||
<item>@xml/latn_workman_us</item>
|
<item>@xml/latn_workman_us</item>
|
||||||
<item>@xml/shaw_imperial_en</item>
|
<item>@xml/shaw_imperial_en</item>
|
||||||
|
<item>@xml/sinhala_phonetic</item>
|
||||||
<item>@xml/tamil_default</item>
|
<item>@xml/tamil_default</item>
|
||||||
<item>@xml/urdu_phonetic_ur</item>
|
<item>@xml/urdu_phonetic_ur</item>
|
||||||
<item>-1</item>
|
<item>-1</item>
|
||||||
|
@ -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="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="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="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="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="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"/>
|
<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"/>
|
||||||
|
@ -769,6 +769,38 @@ public final class KeyValue implements Comparable<KeyValue>
|
|||||||
case "௲": case "௳":
|
case "௲": case "௳":
|
||||||
return makeStringKey(name, FLAG_SMALLER_FONT);
|
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 */
|
/* Internal keys */
|
||||||
case "selection_mode": return makeInternalModifier(Modifier.SELECTION_MODE);
|
case "selection_mode": return makeInternalModifier(Modifier.SELECTION_MODE);
|
||||||
|
|
||||||
|
107
srcs/layouts/sinhala_phonetic.xml
Normal file
107
srcs/layouts/sinhala_phonetic.xml
Normal file
@ -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="&" 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="ඃ" />
|
||||||
|
<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="<" c="ච" sw="." ne="ඡ" />
|
||||||
|
<key c="ව" ne=">" sw="," />
|
||||||
|
<key nw="\?" c="බ" sw="/" ne="භ" />
|
||||||
|
<key nw=":" c="න" sw=";" ne="ණ" />
|
||||||
|
<key nw=""" c="ම" sw="'" se="zwj" ne="ඹ" />
|
||||||
|
<key width="1.5" c="backspace" ne="delete" />
|
||||||
|
</row>
|
||||||
|
<modmap>
|
||||||
|
<shift a="ඍ" b="ෘ" />
|
||||||
|
<shift a="ඎ" b="ෲ" />
|
||||||
|
<shift a="ඇ" b="ැ" />
|
||||||
|
<shift a="ඈ" b="ෑ" />
|
||||||
|
<shift a="එ" b="ෙ" />
|
||||||
|
<shift a="ඒ" b="ේ" />
|
||||||
|
<shift a="ත" b="ට" />
|
||||||
|
<shift a="ථ" b="ඨ" />
|
||||||
|
<shift a="උ" b="ු" />
|
||||||
|
<shift a="ඌ" b="ූ" />
|
||||||
|
<shift a="ඉ" b="ි" />
|
||||||
|
<shift a="ඊ" b="ී" />
|
||||||
|
<shift a="ඔ" b="ො" />
|
||||||
|
<shift a="ඕ" b="ෝ" />
|
||||||
|
<shift a="අ" b="්" />
|
||||||
|
<shift a="ආ" b="ා" />
|
||||||
|
<shift a="ස" b="ෂ" />
|
||||||
|
<shift a="ද" b="ඩ" />
|
||||||
|
<shift a="ධ" b="ඪ" />
|
||||||
|
<shift a="ඓ" b="ෛ" />
|
||||||
|
<shift a="ග" b="ඟ" />
|
||||||
|
<shift a="හ" b="ෞ" />
|
||||||
|
<shift a="ඃ" b="ඖ" />
|
||||||
|
<shift a="ජ" b="ඦ" />
|
||||||
|
<shift a="ක" b="ඦ" />
|
||||||
|
<shift a="ඛ" b="ඐ" />
|
||||||
|
<shift a="ල" b="ෟ" />
|
||||||
|
<shift a="ළ" b="ෳ" />
|
||||||
|
<shift a="න" b="ං" />
|
||||||
|
<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="ඁ" />
|
||||||
|
</modmap>
|
||||||
|
</keyboard>
|
Loading…
x
Reference in New Issue
Block a user