diff --git a/addons/hw_drivers/controllers/driver.py b/addons/hw_drivers/controllers/driver.py
index 0bd0286c3d5..9e0b478dadc 100755
--- a/addons/hw_drivers/controllers/driver.py
+++ b/addons/hw_drivers/controllers/driver.py
@@ -166,7 +166,7 @@ class Driver(Thread, metaclass=DriverMetaClass):
"""
On specific driver override this method to give type of device
return string
- possible value : printer - camera - device
+ possible value : printer - camera - keyboard - scanner - device
"""
return self._device_type
diff --git a/addons/hw_drivers/drivers/KeyboardUSBDriver.py b/addons/hw_drivers/drivers/KeyboardUSBDriver.py
new file mode 100644
index 00000000000..a9a67615857
--- /dev/null
+++ b/addons/hw_drivers/drivers/KeyboardUSBDriver.py
@@ -0,0 +1,303 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import ctypes
+import evdev
+import json
+from keysym_to_ucs import keysym_mapping
+import logging
+from lxml import etree
+import os
+from pathlib import Path
+import subprocess
+import time
+from threading import Lock
+from usb import util
+try:
+ from queue import Queue, Empty
+except ImportError:
+ from Queue import Queue, Empty # pylint: disable=deprecated-module
+
+from odoo import http, _
+from odoo.addons.hw_proxy.controllers.main import drivers as old_drivers
+from odoo.addons.hw_drivers.controllers.driver import event_manager, Driver, iot_devices
+
+_logger = logging.getLogger(__name__)
+xlib = ctypes.cdll.LoadLibrary('libX11.so.6')
+
+
+class KeyboardUSBDriver(Driver):
+ connection_type = 'usb'
+ keyboard_layout_groups = []
+
+ def __init__(self, device):
+ if not hasattr(KeyboardUSBDriver, 'display'):
+ os.environ['XAUTHORITY'] = "/run/lightdm/pi/xauthority"
+ KeyboardUSBDriver.display = xlib.XOpenDisplay(bytes(":0.0", "utf-8"))
+
+ super(KeyboardUSBDriver, self).__init__(device)
+ self._device_type = 'keyboard'
+ self._device_connection = 'direct'
+ self._device_name = self._set_name()
+ self.load_layout()
+
+ for device in [evdev.InputDevice(path) for path in evdev.list_devices()]:
+ if (self.dev.idVendor == device.info.vendor) and (self.dev.idProduct == device.info.product):
+ self.input_device = device
+
+ if 'barcode' in self._device_name.lower() or 'scanner' in self._device_name.lower():
+ self._device_type = 'scanner'
+ self._barcodes = Queue()
+ self._current_barcode = ''
+ self.input_device.grab()
+ self.read_barcode_lock = Lock()
+
+ @classmethod
+ def supported(cls, device):
+ for cfg in device:
+ for itf in cfg:
+ if itf.bInterfaceClass == 3 and itf.bInterfaceProtocol == 1:
+ return True
+ return False
+
+ @classmethod
+ def get_status(self):
+ """Allows `hw_proxy.Proxy` to retrieve the status of the scanners"""
+ status = 'connected' if any(iot_devices[d].device_type == "scanner" for d in iot_devices) else 'disconnected'
+ return {'status': status, 'messages': ''}
+
+ def _set_name(self):
+ try:
+ manufacturer = util.get_string(self.dev, 256, self.dev.iManufacturer)
+ product = util.get_string(self.dev, 256, self.dev.iProduct)
+ return ("%s - %s") % (manufacturer, product)
+ except ValueError as e:
+ _logger.warning(e)
+ return _('Unknow keyboard or scanner')
+
+ def action(self, data):
+ if data.get('action', False) == 'update_layout':
+ layout = {
+ 'layout': data.get('layout'),
+ 'variant': data.get('variant'),
+ }
+ self._change_keyboard_layout(layout)
+ self.save_layout(layout)
+ else:
+ self.data['value'] = ''
+ event_manager.device_changed(self)
+
+ def run(self):
+ key_input = self._barcode_scanner_input if self._device_type == "scanner" else self._keyboard_input
+ try:
+ for event in self.input_device.read_loop():
+ if event.type == evdev.ecodes.EV_KEY:
+ data = evdev.categorize(event)
+ if data.keystate == 1:
+ key_input(data.scancode)
+ except Exception as err:
+ _logger.warning(err)
+
+ def _change_keyboard_layout(self, new_layout):
+ """Change the layout of the current device to what is specified in
+ new_layout.
+
+ Args:
+ new_layout (dict): A dict containing two keys:
+ - layout (str): The layout code
+ - variant (str): An optional key to represent the variant of the
+ selected layout
+ """
+ if hasattr(self, 'keyboard_layout'):
+ KeyboardUSBDriver.keyboard_layout_groups.remove(self.keyboard_layout)
+
+ if new_layout:
+ self.keyboard_layout = new_layout.get('layout') or 'us'
+ if new_layout.get('variant'):
+ self.keyboard_layout += "(%s)" % new_layout['variant']
+ else:
+ self.keyboard_layout = 'us'
+
+ KeyboardUSBDriver.keyboard_layout_groups.append(self.keyboard_layout)
+ subprocess.call(["setxkbmap", "-display", ":0.0", ",".join(KeyboardUSBDriver.keyboard_layout_groups)])
+
+ # Close then re-open display to refresh the mapping
+ xlib.XCloseDisplay(KeyboardUSBDriver.display)
+ KeyboardUSBDriver.display = xlib.XOpenDisplay(bytes(":0.0", "utf-8"))
+
+ def save_layout(self, layout):
+ """Save the layout to a file on the box to read it when restarting it.
+ We need that in order to keep the selected layout after a reboot.
+
+ Args:
+ new_layout (dict): A dict containing two keys:
+ - layout (str): The layout code
+ - variant (str): An optional key to represent the variant of the
+ selected layout
+ """
+ file_path = Path.home() / 'odoo-keyboard-layouts.conf'
+ if file_path.exists():
+ data = json.loads(file_path.read_text())
+ else:
+ data = {}
+ data[self.device_identifier] = layout
+ subprocess.check_call(["sudo", "mount", "-o", "remount,rw", "/"])
+ file_path.write_text(json.dumps(data))
+ subprocess.check_call(["sudo", "mount", "-o", "remount,ro", "/"])
+
+ def load_layout(self):
+ """Read the layout from the saved filed and set it as current layout.
+ If no file or no layout is found we use 'us' by default.
+ """
+ file_path = Path.home() / 'odoo-keyboard-layouts.conf'
+ if file_path.exists():
+ data = json.loads(file_path.read_text())
+ layout = data.get(self.device_identifier, {'layout': 'us'})
+ else:
+ layout = {'layout': 'us'}
+ self._change_keyboard_layout(layout)
+
+ def _keyboard_input(self, scancode):
+ """Deal with a keyboard input. Send the character corresponding to the
+ pressed key represented by its scancode to the connected Odoo instance.
+
+ Args:
+ scancode (int): The scancode of the pressed key.
+ """
+ self.data['value'] = self._scancode_to_char(scancode)
+ if self.data['value']:
+ event_manager.device_changed(self)
+
+ def _barcode_scanner_input(self, scancode):
+ """Deal with a barcode scanner input. Add the new character scanned to
+ the current barcode or complete the barcode if "Return" is pressed.
+ When a barcode is completed, two tasks are performed:
+ - Send a device_changed update to the event manager to notify the
+ listeners that the value has changed (used in Enterprise).
+ - Add the barcode to the list barcodes that are being queried in
+ Community.
+
+ Args:
+ scancode (int): The scancode of the pressed key.
+ """
+ if scancode == 28: # Return
+ self.data['value'] = self._current_barcode
+ event_manager.device_changed(self)
+ self._barcodes.put((time.time(), self._current_barcode))
+ self._current_barcode = ''
+ else:
+ self._current_barcode += self._scancode_to_char(scancode)
+
+ def _scancode_to_char(self, scancode):
+ """Translate a received scancode to a character depending on the
+ selected keyboard layout and the current state of the keyboard's
+ modifiers.
+
+ Args:
+ scancode (int): The scancode of the pressed key, to be translated to
+ a character
+
+ Returns:
+ str: The translated scancode.
+ """
+ # Scancode -> Keysym : Depends on the keyboard layout
+ group = KeyboardUSBDriver.keyboard_layout_groups.index(self.keyboard_layout)
+ modifiers = self._get_active_modifiers(scancode)
+ keysym = ctypes.c_int(xlib.XkbKeycodeToKeysym(KeyboardUSBDriver.display, scancode + 8, group, modifiers))
+
+ # Translate Keysym to a character
+ key_pressed = ctypes.create_string_buffer(5)
+ xlib.XkbTranslateKeySym(KeyboardUSBDriver.display, ctypes.byref(keysym), 0, ctypes.byref(key_pressed), 5, ctypes.byref(ctypes.c_int()))
+ if key_pressed.value:
+ # Latin-1
+ return key_pressed.value.decode('latin1')
+ elif (keysym.value & 0xff000000) == 0x01000000:
+ # Already UCS
+ return chr(keysym.value & 0x00ffffff)
+ elif keysym.value in keysym_mapping:
+ # Other encodings
+ return chr(keysym_mapping[keysym.value])
+ return ''
+
+ def _get_active_modifiers(self, scancode):
+ """Get the state of currently active modifiers.
+
+ Args:
+ scancode (int): The scancode of the key being translated
+
+ Returns:
+ int: The current state of the modifiers:
+ 0 -- Lowercase
+ 1 -- Highercase or (NumLock + key pressed on keypad)
+ 2 -- AltGr
+ 3 -- Highercase + AltGr
+ """
+ keyboard_leds = self.input_device.leds()
+ num_lock = 0 in keyboard_leds # Led Num 0
+ caps_lock = 1 in keyboard_leds # Led Num 1
+
+ keyboard_pressed = self.input_device.active_keys()
+ shift = bool([x for x in [42, 54] if x in keyboard_pressed]) # 42 = Left Shift, 54 = Right Shift
+ alt_gr = 100 in keyboard_pressed
+
+ modifiers = 0
+ if (scancode in [71, 72, 73, 75, 76, 77, 79, 80, 81, 82, 83] and num_lock) or (shift ^ caps_lock):
+ modifiers += 1
+ if alt_gr:
+ modifiers += 2
+ return modifiers
+
+ def read_next_barcode(self):
+ """Get the value of the last barcode that was scanned but not sent yet
+ and not older than 5 seconds. This function is used in Community, when
+ we don't have access to the IoTLongpolling.
+
+ Returns:
+ str: The next barcode to be read or an empty string.
+ """
+
+ # Previous query still running, stop it by sending a fake barcode
+ if self.read_barcode_lock.locked():
+ self._barcodes.put((time.time(), ""))
+
+ with self.read_barcode_lock:
+ try:
+ timestamp, barcode = self._barcodes.get(True, 55)
+ if timestamp > time.time() - 5:
+ return barcode
+ except Empty:
+ return ''
+
+
+old_drivers['scanner'] = KeyboardUSBDriver
+
+class KeyboardUSBController(http.Controller):
+ @http.route('/hw_proxy/scanner', type='json', auth='none', cors='*')
+ def get_barcode(self):
+ scanners = [iot_devices[d] for d in iot_devices if iot_devices[d].device_type == "scanner"]
+ if scanners:
+ return scanners[0].read_next_barcode()
+ time.sleep(5)
+ return None
+
+ @http.route('/hw_proxy/load_keyboard_layouts', type='json', auth='none', cors='*')
+ def load_keyboard_layouts(self):
+ available_layouts = []
+ tree = etree.parse("/usr/share/X11/xkb/rules/base.xml", etree.XMLParser(ns_clean=True, recover=True))
+ layouts = tree.xpath("//layout")
+ for layout in layouts:
+ layout_name = layout.xpath("./configItem/name")[0].text
+ layout_description = layout.xpath("./configItem/description")[0].text
+ available_layouts.append({
+ 'name': layout_description,
+ 'layout': layout_name,
+ })
+ for variant in layout.xpath("./variantList/variant"):
+ variant_name = variant.xpath("./configItem/name")[0].text
+ variant_description = variant.xpath("./configItem/description")[0].text
+ available_layouts.append({
+ 'name': variant_description,
+ 'layout': layout_name,
+ 'variant': variant_name,
+ })
+ return available_layouts
diff --git a/addons/hw_proxy/controllers/main.py b/addons/hw_proxy/controllers/main.py
index 33e20f8a76a..f67babef3d0 100644
--- a/addons/hw_proxy/controllers/main.py
+++ b/addons/hw_proxy/controllers/main.py
@@ -209,17 +209,6 @@ class Proxy(http.Controller):
def print_receipt(self, receipt):
print('print_receipt %s', receipt)
- @http.route('/hw_proxy/is_scanner_connected', type='json', auth='none', cors='*')
- def is_scanner_connected(self, receipt):
- print('is_scanner_connected?')
- return False
-
- @http.route('/hw_proxy/scanner', type='json', auth='none', cors='*')
- def scanner(self, receipt):
- print('scanner')
- time.sleep(10)
- return ''
-
@http.route('/hw_proxy/log', type='json', auth='none', cors='*')
def log(self, arguments):
_logger.info(' '.join(str(v) for v in arguments))
diff --git a/addons/hw_scanner/__init__.py b/addons/hw_scanner/__init__.py
deleted file mode 100644
index dd15b5c32e5..00000000000
--- a/addons/hw_scanner/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from . import controllers
diff --git a/addons/hw_scanner/__manifest__.py b/addons/hw_scanner/__manifest__.py
deleted file mode 100644
index 7dbe14d191c..00000000000
--- a/addons/hw_scanner/__manifest__.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-{
- 'name': 'Barcode Scanner Hardware Driver',
- 'category': 'Point of Sale',
- 'sequence': 6,
- 'summary': 'Hardware Driver for Barcode Scanners',
- 'website': 'https://www.odoo.com/page/point-of-sale-hardware',
- 'description': """
-Barcode Scanner Hardware Driver
-================================
-
-This module allows the web client to access a remotely installed barcode
-scanner, and is used by the IoTBox to provide barcode scanner support to the
-point of sale module.
-
-""",
- 'depends': ['hw_proxy'],
- 'external_dependencies': {'python': ['evdev']},
- 'installable': False,
-}
diff --git a/addons/hw_scanner/controllers/__init__.py b/addons/hw_scanner/controllers/__init__.py
deleted file mode 100644
index 5d4b25db9c0..00000000000
--- a/addons/hw_scanner/controllers/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from . import main
diff --git a/addons/hw_scanner/controllers/main.py b/addons/hw_scanner/controllers/main.py
deleted file mode 100644
index 86a01497b46..00000000000
--- a/addons/hw_scanner/controllers/main.py
+++ /dev/null
@@ -1,240 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-import logging
-import time
-from os import listdir
-from os.path import join, isdir
-try:
- from queue import Queue, Empty
-except ImportError:
- from Queue import Queue, Empty # pylint: disable=deprecated-module
-from select import select
-from threading import Thread, Lock
-
-from odoo import http
-
-from odoo.addons.hw_proxy.controllers import main as hw_proxy
-
-_logger = logging.getLogger(__name__)
-
-try:
- import evdev
-except ImportError:
- _logger.error('Odoo module hw_scanner depends on the evdev python module')
- evdev = None
-
-class ScannerDevice():
- def __init__(self, path):
- self.evdev = evdev.InputDevice(path)
- self.evdev.grab()
-
- self.barcode = []
- self.shift = False
-
-class Scanner(Thread):
- def __init__(self):
- Thread.__init__(self)
- self.lock = Lock()
- self.status = {'status':'connecting', 'messages':[]}
- self.input_dir = '/dev/input/by-id/'
- self.open_devices = []
- self.barcodes = Queue()
- self.keymap = {
- 2: ("1","!"),
- 3: ("2","@"),
- 4: ("3","#"),
- 5: ("4","$"),
- 6: ("5","%"),
- 7: ("6","^"),
- 8: ("7","&"),
- 9: ("8","*"),
- 10:("9","("),
- 11:("0",")"),
- 12:("-","_"),
- 13:("=","+"),
- # 14 BACKSPACE
- # 15 TAB
- 16:("q","Q"),
- 17:("w","W"),
- 18:("e","E"),
- 19:("r","R"),
- 20:("t","T"),
- 21:("y","Y"),
- 22:("u","U"),
- 23:("i","I"),
- 24:("o","O"),
- 25:("p","P"),
- 26:("[","{"),
- 27:("]","}"),
- # 28 ENTER
- # 29 LEFT_CTRL
- 30:("a","A"),
- 31:("s","S"),
- 32:("d","D"),
- 33:("f","F"),
- 34:("g","G"),
- 35:("h","H"),
- 36:("j","J"),
- 37:("k","K"),
- 38:("l","L"),
- 39:(";",":"),
- 40:("'","\""),
- 41:("`","~"),
- # 42 LEFT SHIFT
- 43:("\\","|"),
- 44:("z","Z"),
- 45:("x","X"),
- 46:("c","C"),
- 47:("v","V"),
- 48:("b","B"),
- 49:("n","N"),
- 50:("m","M"),
- 51:(",","<"),
- 52:(".",">"),
- 53:("/","?"),
- # 54 RIGHT SHIFT
- 57:(" "," "),
- }
-
- def lockedstart(self):
- with self.lock:
- if not self.isAlive():
- self.daemon = True
- self.start()
-
- def set_status(self, status, message = None):
- if status == self.status['status']:
- if message != None and message != self.status['messages'][-1]:
- self.status['messages'].append(message)
- else:
- self.status['status'] = status
- if message:
- self.status['messages'] = [message]
- else:
- self.status['messages'] = []
-
- if status == 'error' and message:
- _logger.error('Barcode Scanner Error: '+message)
- elif status == 'disconnected' and message:
- _logger.info('Disconnected Barcode Scanner: %s', message)
-
- def get_devices(self):
- try:
- if not evdev:
- return []
-
- if not isdir(self.input_dir):
- return []
-
- new_devices = [device for device in listdir(self.input_dir)
- if join(self.input_dir, device) not in [dev.evdev.fn for dev in self.open_devices]]
- scanners = [device for device in new_devices
- if (('kbd' in device) and ('keyboard' not in device.lower()))
- or ('barcode' in device.lower()) or ('scanner' in device.lower())]
-
- for device in scanners:
- _logger.debug('opening device %s', join(self.input_dir,device))
- self.open_devices.append(ScannerDevice(join(self.input_dir,device)))
-
- if self.open_devices:
- self.set_status('connected','Connected to '+ str([dev.evdev.name for dev in self.open_devices]))
- else:
- self.set_status('disconnected','Barcode Scanner Not Found')
-
- return self.open_devices
- except Exception as e:
- self.set_status('error',str(e))
- return []
-
- def release_device(self, dev):
- self.open_devices.remove(dev)
-
- def get_barcode(self):
- """ Returns a scanned barcode. Will wait at most 5 seconds to get a barcode, and will
- return barcode scanned in the past if they are not older than 5 seconds and have not
- been returned before. This is necessary to catch barcodes scanned while the POS is
- busy reading another barcode
- """
-
- self.lockedstart()
-
- while True:
- try:
- timestamp, barcode = self.barcodes.get(True, 5)
- if timestamp > time.time() - 5:
- return barcode
- except Empty:
- return ''
-
- def get_status(self):
- self.lockedstart()
- return self.status
-
- def _get_open_device_by_fd(self, fd):
- for dev in self.open_devices:
- if dev.evdev.fd == fd:
- return dev
-
- def run(self):
- """ This will start a loop that catches all keyboard events, parse barcode
- sequences and put them on a timestamped queue that can be consumed by
- the point of sale's requests for barcode events
- """
-
- self.barcodes = Queue()
-
- barcode = []
- shift = False
- devices = None
-
- while True: # barcodes loop
- devices = self.get_devices()
-
- try:
- while True: # keycode loop
- r,w,x = select({dev.fd: dev for dev in [d.evdev for d in devices]},[],[],5)
- if len(r) == 0: # timeout
- break
-
- for fd in r:
- device = self._get_open_device_by_fd(fd)
-
- if not evdev.util.is_device(device.evdev.fn):
- _logger.info('%s disconnected', str(device.evdev))
- self.release_device(device)
- break
-
- events = device.evdev.read()
-
- for event in events:
- if event.type == evdev.ecodes.EV_KEY:
- # _logger.debug('Evdev Keyboard event %s',evdev.categorize(event))
- if event.value == 1: # keydown events
- if event.code in self.keymap:
- if device.shift:
- device.barcode.append(self.keymap[event.code][1])
- else:
- device.barcode.append(self.keymap[event.code][0])
- elif event.code == 42 or event.code == 54: # SHIFT
- device.shift = True
- elif event.code == 28: # ENTER, end of barcode
- _logger.debug('pushing barcode %s from %s', ''.join(device.barcode), str(device.evdev))
- self.barcodes.put( (time.time(),''.join(device.barcode)) )
- device.barcode = []
- elif event.value == 0: #keyup events
- if event.code == 42 or event.code == 54: # LEFT SHIFT
- device.shift = False
-
- except Exception as e:
- self.set_status('error',str(e))
-
-scanner_thread = None
-if evdev:
- scanner_thread = Scanner()
- hw_proxy.drivers['scanner'] = scanner_thread
-
-class ScannerDriver(hw_proxy.Proxy):
- @http.route('/hw_proxy/scanner', type='json', auth='none', cors='*')
- def scanner(self):
- return scanner_thread.get_barcode() if scanner_thread else None
diff --git a/addons/hw_scanner/i18n/hw_scanner.pot b/addons/hw_scanner/i18n/hw_scanner.pot
deleted file mode 100644
index 5d61a0a94b5..00000000000
--- a/addons/hw_scanner/i18n/hw_scanner.pot
+++ /dev/null
@@ -1,16 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server 9.0\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-09-07 14:39+0000\n"
-"PO-Revision-Date: 2015-09-07 14:39+0000\n"
-"Last-Translator: <>\n"
-"Language-Team: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Plural-Forms: \n"
-
diff --git a/addons/point_of_sale/static/src/js/barcode_reader.js b/addons/point_of_sale/static/src/js/barcode_reader.js
new file mode 100644
index 00000000000..c332591b715
--- /dev/null
+++ b/addons/point_of_sale/static/src/js/barcode_reader.js
@@ -0,0 +1,133 @@
+odoo.define('point_of_sale.BarcodeReader', function (require) {
+"use strict";
+
+var core = require('web.core');
+
+// this module interfaces with the barcode reader. It assumes the barcode reader
+// is set-up to act like a keyboard. Use connect() and disconnect() to activate
+// and deactivate the barcode reader. Use set_action_callbacks to tell it
+// what to do when it reads a barcode.
+var BarcodeReader = core.Class.extend({
+ actions:[
+ 'product',
+ 'cashier',
+ 'client',
+ ],
+
+ init: function (attributes) {
+ this.pos = attributes.pos;
+ this.action_callback = {};
+ this.proxy = attributes.proxy;
+ this.remote_scanning = false;
+ this.remote_active = 0;
+
+ this.barcode_parser = attributes.barcode_parser;
+
+ this.action_callback_stack = [];
+
+ core.bus.on('barcode_scanned', this, function (barcode) {
+ this.scan(barcode);
+ });
+ },
+
+ set_barcode_parser: function (barcode_parser) {
+ this.barcode_parser = barcode_parser;
+ },
+
+ save_callbacks: function () {
+ var callbacks = {};
+ for (var name in this.action_callback) {
+ callbacks[name] = this.action_callback[name];
+ }
+ this.action_callback_stack.push(callbacks);
+ },
+
+ restore_callbacks: function () {
+ if (this.action_callback_stack.length) {
+ var callbacks = this.action_callback_stack.pop();
+ this.action_callback = callbacks;
+ }
+ },
+
+ // when a barcode is scanned and parsed, the callback corresponding
+ // to its type is called with the parsed_barcode as a parameter.
+ // (parsed_barcode is the result of parse_barcode(barcode))
+ //
+ // callbacks is a Map of 'actions' : callback(parsed_barcode)
+ // that sets the callback for each action. if a callback for the
+ // specified action already exists, it is replaced.
+ //
+ // possible actions include :
+ // 'product' | 'cashier' | 'client' | 'discount'
+ set_action_callback: function (action, callback) {
+ if (arguments.length == 2) {
+ this.action_callback[action] = callback;
+ } else {
+ var actions = arguments[0];
+ for (var action in actions) {
+ this.set_action_callback(action,actions[action]);
+ }
+ }
+ },
+
+ //remove all action callbacks
+ reset_action_callbacks: function () {
+ for (var action in this.action_callback) {
+ this.action_callback[action] = undefined;
+ }
+ },
+
+ scan: function (code) {
+ if (!code) {
+ return;
+ }
+ var parsed_result = this.barcode_parser.parse_barcode(code);
+ if (this.action_callback[parsed_result.type]) {
+ this.action_callback[parsed_result.type](parsed_result);
+ } else if (this.action_callback.error) {
+ this.action_callback.error(parsed_result);
+ } else {
+ console.warn("Ignored Barcode Scan:", parsed_result);
+ }
+ },
+
+ // the barcode scanner will listen on the hw_proxy/scanner interface for
+ // scan events until disconnect_from_proxy is called
+ connect_to_proxy: function () {
+ var self = this;
+ this.remote_scanning = true;
+ if (this.remote_active >= 1) {
+ return;
+ }
+ this.remote_active = 1;
+
+ function waitforbarcode(){
+ return self.proxy.connection.rpc('/hw_proxy/scanner',{},{shadow: true, timeout:7500})
+ .then(function (barcode) {
+ if (!self.remote_scanning) {
+ self.remote_active = 0;
+ return;
+ }
+ self.scan(barcode);
+ waitforbarcode();
+ },
+ function () {
+ if (!self.remote_scanning) {
+ self.remote_active = 0;
+ return;
+ }
+ setTimeout(waitforbarcode,5000);
+ });
+ }
+ waitforbarcode();
+ },
+
+ // the barcode scanner will stop listening on the hw_proxy/scanner remote interface
+ disconnect_from_proxy: function () {
+ this.remote_scanning = false;
+ },
+});
+
+return BarcodeReader;
+
+});
diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js
index a018882cac9..ff012a1679d 100644
--- a/addons/point_of_sale/static/src/js/devices.js
+++ b/addons/point_of_sale/static/src/js/devices.js
@@ -483,135 +483,9 @@ var ProxyDevice = core.Class.extend(mixins.PropertiesMixin,{
});
-// this module interfaces with the barcode reader. It assumes the barcode reader
-// is set-up to act like a keyboard. Use connect() and disconnect() to activate
-// and deactivate the barcode reader. Use set_action_callbacks to tell it
-// what to do when it reads a barcode.
-var BarcodeReader = core.Class.extend({
- actions:[
- 'product',
- 'cashier',
- 'client',
- ],
-
- init: function(attributes){
- this.pos = attributes.pos;
- this.action_callback = {};
- this.proxy = attributes.proxy;
- this.remote_scanning = false;
- this.remote_active = 0;
-
- this.barcode_parser = attributes.barcode_parser;
-
- this.action_callback_stack = [];
-
- core.bus.on('barcode_scanned', this, function (barcode) {
- this.scan(barcode);
- });
- },
-
- set_barcode_parser: function(barcode_parser) {
- this.barcode_parser = barcode_parser;
- },
-
- save_callbacks: function(){
- var callbacks = {};
- for(var name in this.action_callback){
- callbacks[name] = this.action_callback[name];
- }
- this.action_callback_stack.push(callbacks);
- },
-
- restore_callbacks: function(){
- if(this.action_callback_stack.length){
- var callbacks = this.action_callback_stack.pop();
- this.action_callback = callbacks;
- }
- },
-
- // when a barcode is scanned and parsed, the callback corresponding
- // to its type is called with the parsed_barcode as a parameter.
- // (parsed_barcode is the result of parse_barcode(barcode))
- //
- // callbacks is a Map of 'actions' : callback(parsed_barcode)
- // that sets the callback for each action. if a callback for the
- // specified action already exists, it is replaced.
- //
- // possible actions include :
- // 'product' | 'cashier' | 'client' | 'discount'
- set_action_callback: function(action, callback){
- if(arguments.length == 2){
- this.action_callback[action] = callback;
- }else{
- var actions = arguments[0];
- for(var action in actions){
- this.set_action_callback(action,actions[action]);
- }
- }
- },
-
- //remove all action callbacks
- reset_action_callbacks: function(){
- for(var action in this.action_callback){
- this.action_callback[action] = undefined;
- }
- },
-
- scan: function(code){
- if (!code) {
- return;
- }
- var parsed_result = this.barcode_parser.parse_barcode(code);
- if (this.action_callback[parsed_result.type]) {
- this.action_callback[parsed_result.type](parsed_result);
- } else if (this.action_callback.error) {
- this.action_callback.error(parsed_result);
- } else {
- console.warn("Ignored Barcode Scan:", parsed_result);
- }
- },
-
- // the barcode scanner will listen on the hw_proxy/scanner interface for
- // scan events until disconnect_from_proxy is called
- connect_to_proxy: function(){
- var self = this;
- this.remote_scanning = true;
- if(this.remote_active >= 1){
- return;
- }
- this.remote_active = 1;
-
- function waitforbarcode(){
- return self.proxy.connection.rpc('/hw_proxy/scanner',{},{shadow: true, timeout:7500})
- .then(function(barcode){
- if(!self.remote_scanning){
- self.remote_active = 0;
- return;
- }
- self.scan(barcode);
- waitforbarcode();
- },
- function(){
- if(!self.remote_scanning){
- self.remote_active = 0;
- return;
- }
- setTimeout(waitforbarcode,5000);
- });
- }
- waitforbarcode();
- },
-
- // the barcode scanner will stop listening on the hw_proxy/scanner remote interface
- disconnect_from_proxy: function(){
- this.remote_scanning = false;
- },
-});
-
return {
JobQueue: JobQueue,
ProxyDevice: ProxyDevice,
- BarcodeReader: BarcodeReader,
};
});
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index ca290812484..25fcaf028e5 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -3,6 +3,7 @@ odoo.define('point_of_sale.models', function (require) {
var ajax = require('web.ajax');
var BarcodeParser = require('barcodes.BarcodeParser');
+var BarcodeReader = require('point_of_sale.BarcodeReader');
var PosDB = require('point_of_sale.DB');
var devices = require('point_of_sale.devices');
var concurrency = require('web.concurrency');
@@ -41,7 +42,7 @@ exports.PosModel = Backbone.Model.extend({
this.gui = attributes.gui;
this.proxy = new devices.ProxyDevice(this); // used to communicate to the hardware devices via a local proxy
- this.barcode_reader = new devices.BarcodeReader({'pos': this, proxy:this.proxy});
+ this.barcode_reader = new BarcodeReader({'pos': this, proxy:this.proxy});
this.proxy_queue = new devices.JobQueue(); // used to prevent parallels communications to the proxy
this.db = new PosDB(); // a local database used to search trough products and categories & store pending orders
diff --git a/addons/point_of_sale/tools/posbox/overwrite_after_init/usr/local/lib/python3.5/dist-packages/keysym_to_ucs.py b/addons/point_of_sale/tools/posbox/overwrite_after_init/usr/local/lib/python3.5/dist-packages/keysym_to_ucs.py
new file mode 100644
index 00000000000..73c3d559523
--- /dev/null
+++ b/addons/point_of_sale/tools/posbox/overwrite_after_init/usr/local/lib/python3.5/dist-packages/keysym_to_ucs.py
@@ -0,0 +1,771 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+
+"""
+ The Keysym -> Character mapping provided by Xlib only supports latin-1
+ encoded characters. For keysyms corresponding to special characters, X11
+ won't return anything. This file provides a Keysym to Unicode mapping for
+ most of the characters not supported by X11.
+"""
+
+keysym_mapping = {
+ 0x01a1: 0x0104, # Aogonek Ą
+ 0x01a2: 0x02d8, # breve ˘
+ 0x01a3: 0x0141, # Lstroke Ł
+ 0x01a5: 0x013d, # Lcaron Ľ
+ 0x01a6: 0x015a, # Sacute Ś
+ 0x01a9: 0x0160, # Scaron Š
+ 0x01aa: 0x015e, # Scedilla Ş
+ 0x01ab: 0x0164, # Tcaron Ť
+ 0x01ac: 0x0179, # Zacute Ź
+ 0x01ae: 0x017d, # Zcaron Ž
+ 0x01af: 0x017b, # Zabovedot Ż
+ 0x01b1: 0x0105, # aogonek ą
+ 0x01b2: 0x02db, # ogonek ˛
+ 0x01b3: 0x0142, # lstroke ł
+ 0x01b5: 0x013e, # lcaron ľ
+ 0x01b6: 0x015b, # sacute ś
+ 0x01b7: 0x02c7, # caron ˇ
+ 0x01b9: 0x0161, # scaron š
+ 0x01ba: 0x015f, # scedilla ş
+ 0x01bb: 0x0165, # tcaron ť
+ 0x01bc: 0x017a, # zacute ź
+ 0x01bd: 0x02dd, # doubleacute ˝
+ 0x01be: 0x017e, # zcaron ž
+ 0x01bf: 0x017c, # zabovedot ż
+ 0x01c0: 0x0154, # Racute Ŕ
+ 0x01c3: 0x0102, # Abreve Ă
+ 0x01c5: 0x0139, # Lacute Ĺ
+ 0x01c6: 0x0106, # Cacute Ć
+ 0x01c8: 0x010c, # Ccaron Č
+ 0x01ca: 0x0118, # Eogonek Ę
+ 0x01cc: 0x011a, # Ecaron Ě
+ 0x01cf: 0x010e, # Dcaron Ď
+ 0x01d0: 0x0110, # Dstroke Đ
+ 0x01d1: 0x0143, # Nacute Ń
+ 0x01d2: 0x0147, # Ncaron Ň
+ 0x01d5: 0x0150, # Odoubleacute Ő
+ 0x01d8: 0x0158, # Rcaron Ř
+ 0x01d9: 0x016e, # Uring Ů
+ 0x01db: 0x0170, # Udoubleacute Ű
+ 0x01de: 0x0162, # Tcedilla Ţ
+ 0x01e0: 0x0155, # racute ŕ
+ 0x01e3: 0x0103, # abreve ă
+ 0x01e5: 0x013a, # lacute ĺ
+ 0x01e6: 0x0107, # cacute ć
+ 0x01e8: 0x010d, # ccaron č
+ 0x01ea: 0x0119, # eogonek ę
+ 0x01ec: 0x011b, # ecaron ě
+ 0x01ef: 0x010f, # dcaron ď
+ 0x01f0: 0x0111, # dstroke đ
+ 0x01f1: 0x0144, # nacute ń
+ 0x01f2: 0x0148, # ncaron ň
+ 0x01f5: 0x0151, # odoubleacute ő
+ 0x01f8: 0x0159, # rcaron ř
+ 0x01f9: 0x016f, # uring ů
+ 0x01fb: 0x0171, # udoubleacute ű
+ 0x01fe: 0x0163, # tcedilla ţ
+ 0x01ff: 0x02d9, # abovedot ˙
+ 0x02a1: 0x0126, # Hstroke Ħ
+ 0x02a6: 0x0124, # Hcircumflex Ĥ
+ 0x02a9: 0x0130, # Iabovedot İ
+ 0x02ab: 0x011e, # Gbreve Ğ
+ 0x02ac: 0x0134, # Jcircumflex Ĵ
+ 0x02b1: 0x0127, # hstroke ħ
+ 0x02b6: 0x0125, # hcircumflex ĥ
+ 0x02b9: 0x0131, # idotless ı
+ 0x02bb: 0x011f, # gbreve ğ
+ 0x02bc: 0x0135, # jcircumflex ĵ
+ 0x02c5: 0x010a, # Cabovedot Ċ
+ 0x02c6: 0x0108, # Ccircumflex Ĉ
+ 0x02d5: 0x0120, # Gabovedot Ġ
+ 0x02d8: 0x011c, # Gcircumflex Ĝ
+ 0x02dd: 0x016c, # Ubreve Ŭ
+ 0x02de: 0x015c, # Scircumflex Ŝ
+ 0x02e5: 0x010b, # cabovedot ċ
+ 0x02e6: 0x0109, # ccircumflex ĉ
+ 0x02f5: 0x0121, # gabovedot ġ
+ 0x02f8: 0x011d, # gcircumflex ĝ
+ 0x02fd: 0x016d, # ubreve ŭ
+ 0x02fe: 0x015d, # scircumflex ŝ
+ 0x03a2: 0x0138, # kra ĸ
+ 0x03a3: 0x0156, # Rcedilla Ŗ
+ 0x03a5: 0x0128, # Itilde Ĩ
+ 0x03a6: 0x013b, # Lcedilla Ļ
+ 0x03aa: 0x0112, # Emacron Ē
+ 0x03ab: 0x0122, # Gcedilla Ģ
+ 0x03ac: 0x0166, # Tslash Ŧ
+ 0x03b3: 0x0157, # rcedilla ŗ
+ 0x03b5: 0x0129, # itilde ĩ
+ 0x03b6: 0x013c, # lcedilla ļ
+ 0x03ba: 0x0113, # emacron ē
+ 0x03bb: 0x0123, # gcedilla ģ
+ 0x03bc: 0x0167, # tslash ŧ
+ 0x03bd: 0x014a, # ENG Ŋ
+ 0x03bf: 0x014b, # eng ŋ
+ 0x03c0: 0x0100, # Amacron Ā
+ 0x03c7: 0x012e, # Iogonek Į
+ 0x03cc: 0x0116, # Eabovedot Ė
+ 0x03cf: 0x012a, # Imacron Ī
+ 0x03d1: 0x0145, # Ncedilla Ņ
+ 0x03d2: 0x014c, # Omacron Ō
+ 0x03d3: 0x0136, # Kcedilla Ķ
+ 0x03d9: 0x0172, # Uogonek Ų
+ 0x03dd: 0x0168, # Utilde Ũ
+ 0x03de: 0x016a, # Umacron Ū
+ 0x03e0: 0x0101, # amacron ā
+ 0x03e7: 0x012f, # iogonek į
+ 0x03ec: 0x0117, # eabovedot ė
+ 0x03ef: 0x012b, # imacron ī
+ 0x03f1: 0x0146, # ncedilla ņ
+ 0x03f2: 0x014d, # omacron ō
+ 0x03f3: 0x0137, # kcedilla ķ
+ 0x03f9: 0x0173, # uogonek ų
+ 0x03fd: 0x0169, # utilde ũ
+ 0x03fe: 0x016b, # umacron ū
+ 0x047e: 0x203e, # overline ‾
+ 0x04a1: 0x3002, # kana_fullstop 。
+ 0x04a2: 0x300c, # kana_openingbracket 「
+ 0x04a3: 0x300d, # kana_closingbracket 」
+ 0x04a4: 0x3001, # kana_comma 、
+ 0x04a5: 0x30fb, # kana_conjunctive ・
+ 0x04a6: 0x30f2, # kana_WO ヲ
+ 0x04a7: 0x30a1, # kana_a ァ
+ 0x04a8: 0x30a3, # kana_i ィ
+ 0x04a9: 0x30a5, # kana_u ゥ
+ 0x04aa: 0x30a7, # kana_e ェ
+ 0x04ab: 0x30a9, # kana_o ォ
+ 0x04ac: 0x30e3, # kana_ya ャ
+ 0x04ad: 0x30e5, # kana_yu ュ
+ 0x04ae: 0x30e7, # kana_yo ョ
+ 0x04af: 0x30c3, # kana_tsu ッ
+ 0x04b0: 0x30fc, # prolongedsound ー
+ 0x04b1: 0x30a2, # kana_A ア
+ 0x04b2: 0x30a4, # kana_I イ
+ 0x04b3: 0x30a6, # kana_U ウ
+ 0x04b4: 0x30a8, # kana_E エ
+ 0x04b5: 0x30aa, # kana_O オ
+ 0x04b6: 0x30ab, # kana_KA カ
+ 0x04b7: 0x30ad, # kana_KI キ
+ 0x04b8: 0x30af, # kana_KU ク
+ 0x04b9: 0x30b1, # kana_KE ケ
+ 0x04ba: 0x30b3, # kana_KO コ
+ 0x04bb: 0x30b5, # kana_SA サ
+ 0x04bc: 0x30b7, # kana_SHI シ
+ 0x04bd: 0x30b9, # kana_SU ス
+ 0x04be: 0x30bb, # kana_SE セ
+ 0x04bf: 0x30bd, # kana_SO ソ
+ 0x04c0: 0x30bf, # kana_TA タ
+ 0x04c1: 0x30c1, # kana_CHI チ
+ 0x04c2: 0x30c4, # kana_TSU ツ
+ 0x04c3: 0x30c6, # kana_TE テ
+ 0x04c4: 0x30c8, # kana_TO ト
+ 0x04c5: 0x30ca, # kana_NA ナ
+ 0x04c6: 0x30cb, # kana_NI ニ
+ 0x04c7: 0x30cc, # kana_NU ヌ
+ 0x04c8: 0x30cd, # kana_NE ネ
+ 0x04c9: 0x30ce, # kana_NO ノ
+ 0x04ca: 0x30cf, # kana_HA ハ
+ 0x04cb: 0x30d2, # kana_HI ヒ
+ 0x04cc: 0x30d5, # kana_FU フ
+ 0x04cd: 0x30d8, # kana_HE ヘ
+ 0x04ce: 0x30db, # kana_HO ホ
+ 0x04cf: 0x30de, # kana_MA マ
+ 0x04d0: 0x30df, # kana_MI ミ
+ 0x04d1: 0x30e0, # kana_MU ム
+ 0x04d2: 0x30e1, # kana_ME メ
+ 0x04d3: 0x30e2, # kana_MO モ
+ 0x04d4: 0x30e4, # kana_YA ヤ
+ 0x04d5: 0x30e6, # kana_YU ユ
+ 0x04d6: 0x30e8, # kana_YO ヨ
+ 0x04d7: 0x30e9, # kana_RA ラ
+ 0x04d8: 0x30ea, # kana_RI リ
+ 0x04d9: 0x30eb, # kana_RU ル
+ 0x04da: 0x30ec, # kana_RE レ
+ 0x04db: 0x30ed, # kana_RO ロ
+ 0x04dc: 0x30ef, # kana_WA ワ
+ 0x04dd: 0x30f3, # kana_N ン
+ 0x04de: 0x309b, # voicedsound ゛
+ 0x04df: 0x309c, # semivoicedsound ゜
+ 0x05ac: 0x060c, # Arabic_comma ،
+ 0x05bb: 0x061b, # Arabic_semicolon ؛
+ 0x05bf: 0x061f, # Arabic_question_mark ؟
+ 0x05c1: 0x0621, # Arabic_hamza ء
+ 0x05c2: 0x0622, # Arabic_maddaonalef آ
+ 0x05c3: 0x0623, # Arabic_hamzaonalef أ
+ 0x05c4: 0x0624, # Arabic_hamzaonwaw ؤ
+ 0x05c5: 0x0625, # Arabic_hamzaunderalef إ
+ 0x05c6: 0x0626, # Arabic_hamzaonyeh ئ
+ 0x05c7: 0x0627, # Arabic_alef ا
+ 0x05c8: 0x0628, # Arabic_beh ب
+ 0x05c9: 0x0629, # Arabic_tehmarbuta ة
+ 0x05ca: 0x062a, # Arabic_teh ت
+ 0x05cb: 0x062b, # Arabic_theh ث
+ 0x05cc: 0x062c, # Arabic_jeem ج
+ 0x05cd: 0x062d, # Arabic_hah ح
+ 0x05ce: 0x062e, # Arabic_khah خ
+ 0x05cf: 0x062f, # Arabic_dal د
+ 0x05d0: 0x0630, # Arabic_thal ذ
+ 0x05d1: 0x0631, # Arabic_ra ر
+ 0x05d2: 0x0632, # Arabic_zain ز
+ 0x05d3: 0x0633, # Arabic_seen س
+ 0x05d4: 0x0634, # Arabic_sheen ش
+ 0x05d5: 0x0635, # Arabic_sad ص
+ 0x05d6: 0x0636, # Arabic_dad ض
+ 0x05d7: 0x0637, # Arabic_tah ط
+ 0x05d8: 0x0638, # Arabic_zah ظ
+ 0x05d9: 0x0639, # Arabic_ain ع
+ 0x05da: 0x063a, # Arabic_ghain غ
+ 0x05e0: 0x0640, # Arabic_tatweel ـ
+ 0x05e1: 0x0641, # Arabic_feh ف
+ 0x05e2: 0x0642, # Arabic_qaf ق
+ 0x05e3: 0x0643, # Arabic_kaf ك
+ 0x05e4: 0x0644, # Arabic_lam ل
+ 0x05e5: 0x0645, # Arabic_meem م
+ 0x05e6: 0x0646, # Arabic_noon ن
+ 0x05e7: 0x0647, # Arabic_ha ه
+ 0x05e8: 0x0648, # Arabic_waw و
+ 0x05e9: 0x0649, # Arabic_alefmaksura ى
+ 0x05ea: 0x064a, # Arabic_yeh ي
+ 0x05eb: 0x064b, # Arabic_fathatan ً
+ 0x05ec: 0x064c, # Arabic_dammatan ٌ
+ 0x05ed: 0x064d, # Arabic_kasratan ٍ
+ 0x05ee: 0x064e, # Arabic_fatha َ
+ 0x05ef: 0x064f, # Arabic_damma ُ
+ 0x05f0: 0x0650, # Arabic_kasra ِ
+ 0x05f1: 0x0651, # Arabic_shadda ّ
+ 0x05f2: 0x0652, # Arabic_sukun ْ
+ 0x06a1: 0x0452, # Serbian_dje ђ
+ 0x06a2: 0x0453, # Macedonia_gje ѓ
+ 0x06a3: 0x0451, # Cyrillic_io ё
+ 0x06a4: 0x0454, # Ukrainian_ie є
+ 0x06a5: 0x0455, # Macedonia_dse ѕ
+ 0x06a6: 0x0456, # Ukrainian_i і
+ 0x06a7: 0x0457, # Ukrainian_yi ї
+ 0x06a8: 0x0458, # Cyrillic_je ј
+ 0x06a9: 0x0459, # Cyrillic_lje љ
+ 0x06aa: 0x045a, # Cyrillic_nje њ
+ 0x06ab: 0x045b, # Serbian_tshe ћ
+ 0x06ac: 0x045c, # Macedonia_kje ќ
+ 0x06ae: 0x045e, # Byelorussian_shortu ў
+ 0x06af: 0x045f, # Cyrillic_dzhe џ
+ 0x06b0: 0x2116, # numerosign №
+ 0x06b1: 0x0402, # Serbian_DJE Ђ
+ 0x06b2: 0x0403, # Macedonia_GJE Ѓ
+ 0x06b3: 0x0401, # Cyrillic_IO Ё
+ 0x06b4: 0x0404, # Ukrainian_IE Є
+ 0x06b5: 0x0405, # Macedonia_DSE Ѕ
+ 0x06b6: 0x0406, # Ukrainian_I І
+ 0x06b7: 0x0407, # Ukrainian_YI Ї
+ 0x06b8: 0x0408, # Cyrillic_JE Ј
+ 0x06b9: 0x0409, # Cyrillic_LJE Љ
+ 0x06ba: 0x040a, # Cyrillic_NJE Њ
+ 0x06bb: 0x040b, # Serbian_TSHE Ћ
+ 0x06bc: 0x040c, # Macedonia_KJE Ќ
+ 0x06be: 0x040e, # Byelorussian_SHORTU Ў
+ 0x06bf: 0x040f, # Cyrillic_DZHE Џ
+ 0x06c0: 0x044e, # Cyrillic_yu ю
+ 0x06c1: 0x0430, # Cyrillic_a а
+ 0x06c2: 0x0431, # Cyrillic_be б
+ 0x06c3: 0x0446, # Cyrillic_tse ц
+ 0x06c4: 0x0434, # Cyrillic_de д
+ 0x06c5: 0x0435, # Cyrillic_ie е
+ 0x06c6: 0x0444, # Cyrillic_ef ф
+ 0x06c7: 0x0433, # Cyrillic_ghe г
+ 0x06c8: 0x0445, # Cyrillic_ha х
+ 0x06c9: 0x0438, # Cyrillic_i и
+ 0x06ca: 0x0439, # Cyrillic_shorti й
+ 0x06cb: 0x043a, # Cyrillic_ka к
+ 0x06cc: 0x043b, # Cyrillic_el л
+ 0x06cd: 0x043c, # Cyrillic_em м
+ 0x06ce: 0x043d, # Cyrillic_en н
+ 0x06cf: 0x043e, # Cyrillic_o о
+ 0x06d0: 0x043f, # Cyrillic_pe п
+ 0x06d1: 0x044f, # Cyrillic_ya я
+ 0x06d2: 0x0440, # Cyrillic_er р
+ 0x06d3: 0x0441, # Cyrillic_es с
+ 0x06d4: 0x0442, # Cyrillic_te т
+ 0x06d5: 0x0443, # Cyrillic_u у
+ 0x06d6: 0x0436, # Cyrillic_zhe ж
+ 0x06d7: 0x0432, # Cyrillic_ve в
+ 0x06d8: 0x044c, # Cyrillic_softsign ь
+ 0x06d9: 0x044b, # Cyrillic_yeru ы
+ 0x06da: 0x0437, # Cyrillic_ze з
+ 0x06db: 0x0448, # Cyrillic_sha ш
+ 0x06dc: 0x044d, # Cyrillic_e э
+ 0x06dd: 0x0449, # Cyrillic_shcha щ
+ 0x06de: 0x0447, # Cyrillic_che ч
+ 0x06df: 0x044a, # Cyrillic_hardsign ъ
+ 0x06e0: 0x042e, # Cyrillic_YU Ю
+ 0x06e1: 0x0410, # Cyrillic_A А
+ 0x06e2: 0x0411, # Cyrillic_BE Б
+ 0x06e3: 0x0426, # Cyrillic_TSE Ц
+ 0x06e4: 0x0414, # Cyrillic_DE Д
+ 0x06e5: 0x0415, # Cyrillic_IE Е
+ 0x06e6: 0x0424, # Cyrillic_EF Ф
+ 0x06e7: 0x0413, # Cyrillic_GHE Г
+ 0x06e8: 0x0425, # Cyrillic_HA Х
+ 0x06e9: 0x0418, # Cyrillic_I И
+ 0x06ea: 0x0419, # Cyrillic_SHORTI Й
+ 0x06eb: 0x041a, # Cyrillic_KA К
+ 0x06ec: 0x041b, # Cyrillic_EL Л
+ 0x06ed: 0x041c, # Cyrillic_EM М
+ 0x06ee: 0x041d, # Cyrillic_EN Н
+ 0x06ef: 0x041e, # Cyrillic_O О
+ 0x06f0: 0x041f, # Cyrillic_PE П
+ 0x06f1: 0x042f, # Cyrillic_YA Я
+ 0x06f2: 0x0420, # Cyrillic_ER Р
+ 0x06f3: 0x0421, # Cyrillic_ES С
+ 0x06f4: 0x0422, # Cyrillic_TE Т
+ 0x06f5: 0x0423, # Cyrillic_U У
+ 0x06f6: 0x0416, # Cyrillic_ZHE Ж
+ 0x06f7: 0x0412, # Cyrillic_VE В
+ 0x06f8: 0x042c, # Cyrillic_SOFTSIGN Ь
+ 0x06f9: 0x042b, # Cyrillic_YERU Ы
+ 0x06fa: 0x0417, # Cyrillic_ZE З
+ 0x06fb: 0x0428, # Cyrillic_SHA Ш
+ 0x06fc: 0x042d, # Cyrillic_E Э
+ 0x06fd: 0x0429, # Cyrillic_SHCHA Щ
+ 0x06fe: 0x0427, # Cyrillic_CHE Ч
+ 0x06ff: 0x042a, # Cyrillic_HARDSIGN Ъ
+ 0x07a1: 0x0386, # Greek_ALPHAaccent Ά
+ 0x07a2: 0x0388, # Greek_EPSILONaccent Έ
+ 0x07a3: 0x0389, # Greek_ETAaccent Ή
+ 0x07a4: 0x038a, # Greek_IOTAaccent Ί
+ 0x07a5: 0x03aa, # Greek_IOTAdiaeresis Ϊ
+ 0x07a7: 0x038c, # Greek_OMICRONaccent Ό
+ 0x07a8: 0x038e, # Greek_UPSILONaccent Ύ
+ 0x07a9: 0x03ab, # Greek_UPSILONdieresis Ϋ
+ 0x07ab: 0x038f, # Greek_OMEGAaccent Ώ
+ 0x07ae: 0x0385, # Greek_accentdieresis ΅
+ 0x07af: 0x2015, # Greek_horizbar ―
+ 0x07b1: 0x03ac, # Greek_alphaaccent ά
+ 0x07b2: 0x03ad, # Greek_epsilonaccent έ
+ 0x07b3: 0x03ae, # Greek_etaaccent ή
+ 0x07b4: 0x03af, # Greek_iotaaccent ί
+ 0x07b5: 0x03ca, # Greek_iotadieresis ϊ
+ 0x07b6: 0x0390, # Greek_iotaaccentdieresis ΐ
+ 0x07b7: 0x03cc, # Greek_omicronaccent ό
+ 0x07b8: 0x03cd, # Greek_upsilonaccent ύ
+ 0x07b9: 0x03cb, # Greek_upsilondieresis ϋ
+ 0x07ba: 0x03b0, # Greek_upsilonaccentdieresis ΰ
+ 0x07bb: 0x03ce, # Greek_omegaaccent ώ
+ 0x07c1: 0x0391, # Greek_ALPHA Α
+ 0x07c2: 0x0392, # Greek_BETA Β
+ 0x07c3: 0x0393, # Greek_GAMMA Γ
+ 0x07c4: 0x0394, # Greek_DELTA Δ
+ 0x07c5: 0x0395, # Greek_EPSILON Ε
+ 0x07c6: 0x0396, # Greek_ZETA Ζ
+ 0x07c7: 0x0397, # Greek_ETA Η
+ 0x07c8: 0x0398, # Greek_THETA Θ
+ 0x07c9: 0x0399, # Greek_IOTA Ι
+ 0x07ca: 0x039a, # Greek_KAPPA Κ
+ 0x07cb: 0x039b, # Greek_LAMBDA Λ
+ 0x07cc: 0x039c, # Greek_MU Μ
+ 0x07cd: 0x039d, # Greek_NU Ν
+ 0x07ce: 0x039e, # Greek_XI Ξ
+ 0x07cf: 0x039f, # Greek_OMICRON Ο
+ 0x07d0: 0x03a0, # Greek_PI Π
+ 0x07d1: 0x03a1, # Greek_RHO Ρ
+ 0x07d2: 0x03a3, # Greek_SIGMA Σ
+ 0x07d4: 0x03a4, # Greek_TAU Τ
+ 0x07d5: 0x03a5, # Greek_UPSILON Υ
+ 0x07d6: 0x03a6, # Greek_PHI Φ
+ 0x07d7: 0x03a7, # Greek_CHI Χ
+ 0x07d8: 0x03a8, # Greek_PSI Ψ
+ 0x07d9: 0x03a9, # Greek_OMEGA Ω
+ 0x07e1: 0x03b1, # Greek_alpha α
+ 0x07e2: 0x03b2, # Greek_beta β
+ 0x07e3: 0x03b3, # Greek_gamma γ
+ 0x07e4: 0x03b4, # Greek_delta δ
+ 0x07e5: 0x03b5, # Greek_epsilon ε
+ 0x07e6: 0x03b6, # Greek_zeta ζ
+ 0x07e7: 0x03b7, # Greek_eta η
+ 0x07e8: 0x03b8, # Greek_theta θ
+ 0x07e9: 0x03b9, # Greek_iota ι
+ 0x07ea: 0x03ba, # Greek_kappa κ
+ 0x07eb: 0x03bb, # Greek_lambda λ
+ 0x07ec: 0x03bc, # Greek_mu μ
+ 0x07ed: 0x03bd, # Greek_nu ν
+ 0x07ee: 0x03be, # Greek_xi ξ
+ 0x07ef: 0x03bf, # Greek_omicron ο
+ 0x07f0: 0x03c0, # Greek_pi π
+ 0x07f1: 0x03c1, # Greek_rho ρ
+ 0x07f2: 0x03c3, # Greek_sigma σ
+ 0x07f3: 0x03c2, # Greek_finalsmallsigma ς
+ 0x07f4: 0x03c4, # Greek_tau τ
+ 0x07f5: 0x03c5, # Greek_upsilon υ
+ 0x07f6: 0x03c6, # Greek_phi φ
+ 0x07f7: 0x03c7, # Greek_chi χ
+ 0x07f8: 0x03c8, # Greek_psi ψ
+ 0x07f9: 0x03c9, # Greek_omega ω
+ 0x08a1: 0x23b7, # leftradical ⎷
+ 0x08a2: 0x250c, # topleftradical ┌
+ 0x08a3: 0x2500, # horizconnector ─
+ 0x08a4: 0x2320, # topintegral ⌠
+ 0x08a5: 0x2321, # botintegral ⌡
+ 0x08a6: 0x2502, # vertconnector │
+ 0x08a7: 0x23a1, # topleftsqbracket ⎡
+ 0x08a8: 0x23a3, # botleftsqbracket ⎣
+ 0x08a9: 0x23a4, # toprightsqbracket ⎤
+ 0x08aa: 0x23a6, # botrightsqbracket ⎦
+ 0x08ab: 0x239b, # topleftparens ⎛
+ 0x08ac: 0x239d, # botleftparens ⎝
+ 0x08ad: 0x239e, # toprightparens ⎞
+ 0x08ae: 0x23a0, # botrightparens ⎠
+ 0x08af: 0x23a8, # leftmiddlecurlybrace ⎨
+ 0x08b0: 0x23ac, # rightmiddlecurlybrace ⎬
+ 0x08bc: 0x2264, # lessthanequal ≤
+ 0x08bd: 0x2260, # notequal ≠
+ 0x08be: 0x2265, # greaterthanequal ≥
+ 0x08bf: 0x222b, # integral ∫
+ 0x08c0: 0x2234, # therefore ∴
+ 0x08c1: 0x221d, # variation ∝
+ 0x08c2: 0x221e, # infinity ∞
+ 0x08c5: 0x2207, # nabla ∇
+ 0x08c8: 0x223c, # approximate ∼
+ 0x08c9: 0x2243, # similarequal ≃
+ 0x08cd: 0x21d4, # ifonlyif ⇔
+ 0x08ce: 0x21d2, # implies ⇒
+ 0x08cf: 0x2261, # identical ≡
+ 0x08d6: 0x221a, # radical √
+ 0x08da: 0x2282, # includedin ⊂
+ 0x08db: 0x2283, # includes ⊃
+ 0x08dc: 0x2229, # intersection ∩
+ 0x08dd: 0x222a, # union ∪
+ 0x08de: 0x2227, # logicaland ∧
+ 0x08df: 0x2228, # logicalor ∨
+ 0x08ef: 0x2202, # partialderivative ∂
+ 0x08f6: 0x0192, # function ƒ
+ 0x08fb: 0x2190, # leftarrow ←
+ 0x08fc: 0x2191, # uparrow ↑
+ 0x08fd: 0x2192, # rightarrow →
+ 0x08fe: 0x2193, # downarrow ↓
+ 0x09e0: 0x25c6, # soliddiamond ◆
+ 0x09e1: 0x2592, # checkerboard ▒
+ 0x09e2: 0x2409, # ht ␉
+ 0x09e3: 0x240c, # ff ␌
+ 0x09e4: 0x240d, # cr ␍
+ 0x09e5: 0x240a, # lf ␊
+ 0x09e8: 0x2424, # nl 
+ 0x09e9: 0x240b, # vt ␋
+ 0x09ea: 0x2518, # lowrightcorner ┘
+ 0x09eb: 0x2510, # uprightcorner ┐
+ 0x09ec: 0x250c, # upleftcorner ┌
+ 0x09ed: 0x2514, # lowleftcorner └
+ 0x09ee: 0x253c, # crossinglines ┼
+ 0x09ef: 0x23ba, # horizlinescan1 ⎺
+ 0x09f0: 0x23bb, # horizlinescan3 ⎻
+ 0x09f1: 0x2500, # horizlinescan5 ─
+ 0x09f2: 0x23bc, # horizlinescan7 ⎼
+ 0x09f3: 0x23bd, # horizlinescan9 ⎽
+ 0x09f4: 0x251c, # leftt ├
+ 0x09f5: 0x2524, # rightt ┤
+ 0x09f6: 0x2534, # bott ┴
+ 0x09f7: 0x252c, # topt ┬
+ 0x09f8: 0x2502, # vertbar │
+ 0x0aa1: 0x2003, # emspace
+ 0x0aa2: 0x2002, # enspace
+ 0x0aa3: 0x2004, # em3space
+ 0x0aa4: 0x2005, # em4space
+ 0x0aa5: 0x2007, # digitspace
+ 0x0aa6: 0x2008, # punctspace
+ 0x0aa7: 0x2009, # thinspace
+ 0x0aa8: 0x200a, # hairspace
+ 0x0aa9: 0x2014, # emdash —
+ 0x0aaa: 0x2013, # endash –
+ 0x0aae: 0x2026, # ellipsis …
+ 0x0aaf: 0x2025, # doubbaselinedot ‥
+ 0x0ab0: 0x2153, # onethird ⅓
+ 0x0ab1: 0x2154, # twothirds ⅔
+ 0x0ab2: 0x2155, # onefifth ⅕
+ 0x0ab3: 0x2156, # twofifths ⅖
+ 0x0ab4: 0x2157, # threefifths ⅗
+ 0x0ab5: 0x2158, # fourfifths ⅘
+ 0x0ab6: 0x2159, # onesixth ⅙
+ 0x0ab7: 0x215a, # fivesixths ⅚
+ 0x0ab8: 0x2105, # careof ℅
+ 0x0abb: 0x2012, # figdash ‒
+ 0x0abc: 0x2329, # leftanglebracket 〈
+ 0x0abe: 0x232a, # rightanglebracket 〉
+ 0x0ac3: 0x215b, # oneeighth ⅛
+ 0x0ac4: 0x215c, # threeeighths ⅜
+ 0x0ac5: 0x215d, # fiveeighths ⅝
+ 0x0ac6: 0x215e, # seveneighths ⅞
+ 0x0ac9: 0x2122, # trademark ™
+ 0x0aca: 0x2613, # signaturemark ☓
+ 0x0acc: 0x25c1, # leftopentriangle ◁
+ 0x0acd: 0x25b7, # rightopentriangle ▷
+ 0x0ace: 0x25cb, # emopencircle ○
+ 0x0acf: 0x25af, # emopenrectangle ▯
+ 0x0ad0: 0x2018, # leftsinglequotemark ‘
+ 0x0ad1: 0x2019, # rightsinglequotemark ’
+ 0x0ad2: 0x201c, # leftdoublequotemark “
+ 0x0ad3: 0x201d, # rightdoublequotemark ”
+ 0x0ad4: 0x211e, # prescription ℞
+ 0x0ad6: 0x2032, # minutes ′
+ 0x0ad7: 0x2033, # seconds ″
+ 0x0ad9: 0x271d, # latincross ✝
+ 0x0adb: 0x25ac, # filledrectbullet ▬
+ 0x0adc: 0x25c0, # filledlefttribullet ◀
+ 0x0add: 0x25b6, # filledrighttribullet ▶
+ 0x0ade: 0x25cf, # emfilledcircle ●
+ 0x0adf: 0x25ae, # emfilledrect ▮
+ 0x0ae0: 0x25e6, # enopencircbullet ◦
+ 0x0ae1: 0x25ab, # enopensquarebullet ▫
+ 0x0ae2: 0x25ad, # openrectbullet ▭
+ 0x0ae3: 0x25b3, # opentribulletup △
+ 0x0ae4: 0x25bd, # opentribulletdown ▽
+ 0x0ae5: 0x2606, # openstar ☆
+ 0x0ae6: 0x2022, # enfilledcircbullet •
+ 0x0ae7: 0x25aa, # enfilledsqbullet ▪
+ 0x0ae8: 0x25b2, # filledtribulletup ▲
+ 0x0ae9: 0x25bc, # filledtribulletdown ▼
+ 0x0aea: 0x261c, # leftpointer ☜
+ 0x0aeb: 0x261e, # rightpointer ☞
+ 0x0aec: 0x2663, # club ♣
+ 0x0aed: 0x2666, # diamond ♦
+ 0x0aee: 0x2665, # heart ♥
+ 0x0af0: 0x2720, # maltesecross ✠
+ 0x0af1: 0x2020, # dagger †
+ 0x0af2: 0x2021, # doubledagger ‡
+ 0x0af3: 0x2713, # checkmark ✓
+ 0x0af4: 0x2717, # ballotcross ✗
+ 0x0af5: 0x266f, # musicalsharp ♯
+ 0x0af6: 0x266d, # musicalflat ♭
+ 0x0af7: 0x2642, # malesymbol ♂
+ 0x0af8: 0x2640, # femalesymbol ♀
+ 0x0af9: 0x260e, # telephone ☎
+ 0x0afa: 0x2315, # telephonerecorder ⌕
+ 0x0afb: 0x2117, # phonographcopyright ℗
+ 0x0afc: 0x2038, # caret ‸
+ 0x0afd: 0x201a, # singlelowquotemark ‚
+ 0x0afe: 0x201e, # doublelowquotemark „
+ 0x0ba3: 0x003c, # leftcaret <
+ 0x0ba6: 0x003e, # rightcaret >
+ 0x0ba8: 0x2228, # downcaret ∨
+ 0x0ba9: 0x2227, # upcaret ∧
+ 0x0bc0: 0x00af, # overbar ¯
+ 0x0bc2: 0x22a5, # downtack ⊥
+ 0x0bc3: 0x2229, # upshoe ∩
+ 0x0bc4: 0x230a, # downstile ⌊
+ 0x0bc6: 0x005f, # underbar _
+ 0x0bca: 0x2218, # jot ∘
+ 0x0bcc: 0x2395, # quad ⎕
+ 0x0bce: 0x22a4, # uptack ⊤
+ 0x0bcf: 0x25cb, # circle ○
+ 0x0bd3: 0x2308, # upstile ⌈
+ 0x0bd6: 0x222a, # downshoe ∪
+ 0x0bd8: 0x2283, # rightshoe ⊃
+ 0x0bda: 0x2282, # leftshoe ⊂
+ 0x0bdc: 0x22a2, # lefttack ⊢
+ 0x0bfc: 0x22a3, # righttack ⊣
+ 0x0cdf: 0x2017, # hebrew_doublelowline ‗
+ 0x0ce0: 0x05d0, # hebrew_aleph א
+ 0x0ce1: 0x05d1, # hebrew_bet ב
+ 0x0ce2: 0x05d2, # hebrew_gimel ג
+ 0x0ce3: 0x05d3, # hebrew_dalet ד
+ 0x0ce4: 0x05d4, # hebrew_he ה
+ 0x0ce5: 0x05d5, # hebrew_waw ו
+ 0x0ce6: 0x05d6, # hebrew_zain ז
+ 0x0ce7: 0x05d7, # hebrew_chet ח
+ 0x0ce8: 0x05d8, # hebrew_tet ט
+ 0x0ce9: 0x05d9, # hebrew_yod י
+ 0x0cea: 0x05da, # hebrew_finalkaph ך
+ 0x0ceb: 0x05db, # hebrew_kaph כ
+ 0x0cec: 0x05dc, # hebrew_lamed ל
+ 0x0ced: 0x05dd, # hebrew_finalmem ם
+ 0x0cee: 0x05de, # hebrew_mem מ
+ 0x0cef: 0x05df, # hebrew_finalnun ן
+ 0x0cf0: 0x05e0, # hebrew_nun נ
+ 0x0cf1: 0x05e1, # hebrew_samech ס
+ 0x0cf2: 0x05e2, # hebrew_ayin ע
+ 0x0cf3: 0x05e3, # hebrew_finalpe ף
+ 0x0cf4: 0x05e4, # hebrew_pe פ
+ 0x0cf5: 0x05e5, # hebrew_finalzade ץ
+ 0x0cf6: 0x05e6, # hebrew_zade צ
+ 0x0cf7: 0x05e7, # hebrew_qoph ק
+ 0x0cf8: 0x05e8, # hebrew_resh ר
+ 0x0cf9: 0x05e9, # hebrew_shin ש
+ 0x0cfa: 0x05ea, # hebrew_taw ת
+ 0x0da1: 0x0e01, # Thai_kokai ก
+ 0x0da2: 0x0e02, # Thai_khokhai ข
+ 0x0da3: 0x0e03, # Thai_khokhuat ฃ
+ 0x0da4: 0x0e04, # Thai_khokhwai ค
+ 0x0da5: 0x0e05, # Thai_khokhon ฅ
+ 0x0da6: 0x0e06, # Thai_khorakhang ฆ
+ 0x0da7: 0x0e07, # Thai_ngongu ง
+ 0x0da8: 0x0e08, # Thai_chochan จ
+ 0x0da9: 0x0e09, # Thai_choching ฉ
+ 0x0daa: 0x0e0a, # Thai_chochang ช
+ 0x0dab: 0x0e0b, # Thai_soso ซ
+ 0x0dac: 0x0e0c, # Thai_chochoe ฌ
+ 0x0dad: 0x0e0d, # Thai_yoying ญ
+ 0x0dae: 0x0e0e, # Thai_dochada ฎ
+ 0x0daf: 0x0e0f, # Thai_topatak ฏ
+ 0x0db0: 0x0e10, # Thai_thothan ฐ
+ 0x0db1: 0x0e11, # Thai_thonangmontho ฑ
+ 0x0db2: 0x0e12, # Thai_thophuthao ฒ
+ 0x0db3: 0x0e13, # Thai_nonen ณ
+ 0x0db4: 0x0e14, # Thai_dodek ด
+ 0x0db5: 0x0e15, # Thai_totao ต
+ 0x0db6: 0x0e16, # Thai_thothung ถ
+ 0x0db7: 0x0e17, # Thai_thothahan ท
+ 0x0db8: 0x0e18, # Thai_thothong ธ
+ 0x0db9: 0x0e19, # Thai_nonu น
+ 0x0dba: 0x0e1a, # Thai_bobaimai บ
+ 0x0dbb: 0x0e1b, # Thai_popla ป
+ 0x0dbc: 0x0e1c, # Thai_phophung ผ
+ 0x0dbd: 0x0e1d, # Thai_fofa ฝ
+ 0x0dbe: 0x0e1e, # Thai_phophan พ
+ 0x0dbf: 0x0e1f, # Thai_fofan ฟ
+ 0x0dc0: 0x0e20, # Thai_phosamphao ภ
+ 0x0dc1: 0x0e21, # Thai_moma ม
+ 0x0dc2: 0x0e22, # Thai_yoyak ย
+ 0x0dc3: 0x0e23, # Thai_rorua ร
+ 0x0dc4: 0x0e24, # Thai_ru ฤ
+ 0x0dc5: 0x0e25, # Thai_loling ล
+ 0x0dc6: 0x0e26, # Thai_lu ฦ
+ 0x0dc7: 0x0e27, # Thai_wowaen ว
+ 0x0dc8: 0x0e28, # Thai_sosala ศ
+ 0x0dc9: 0x0e29, # Thai_sorusi ษ
+ 0x0dca: 0x0e2a, # Thai_sosua ส
+ 0x0dcb: 0x0e2b, # Thai_hohip ห
+ 0x0dcc: 0x0e2c, # Thai_lochula ฬ
+ 0x0dcd: 0x0e2d, # Thai_oang อ
+ 0x0dce: 0x0e2e, # Thai_honokhuk ฮ
+ 0x0dcf: 0x0e2f, # Thai_paiyannoi ฯ
+ 0x0dd0: 0x0e30, # Thai_saraa ะ
+ 0x0dd1: 0x0e31, # Thai_maihanakat ั
+ 0x0dd2: 0x0e32, # Thai_saraaa า
+ 0x0dd3: 0x0e33, # Thai_saraam ำ
+ 0x0dd4: 0x0e34, # Thai_sarai ิ
+ 0x0dd5: 0x0e35, # Thai_saraii ี
+ 0x0dd6: 0x0e36, # Thai_saraue ึ
+ 0x0dd7: 0x0e37, # Thai_sarauee ื
+ 0x0dd8: 0x0e38, # Thai_sarau ุ
+ 0x0dd9: 0x0e39, # Thai_sarauu ู
+ 0x0dda: 0x0e3a, # Thai_phinthu ฺ
+ 0x0ddf: 0x0e3f, # Thai_baht ฿
+ 0x0de0: 0x0e40, # Thai_sarae เ
+ 0x0de1: 0x0e41, # Thai_saraae แ
+ 0x0de2: 0x0e42, # Thai_sarao โ
+ 0x0de3: 0x0e43, # Thai_saraaimaimuan ใ
+ 0x0de4: 0x0e44, # Thai_saraaimaimalai ไ
+ 0x0de5: 0x0e45, # Thai_lakkhangyao ๅ
+ 0x0de6: 0x0e46, # Thai_maiyamok ๆ
+ 0x0de7: 0x0e47, # Thai_maitaikhu ็
+ 0x0de8: 0x0e48, # Thai_maiek ่
+ 0x0de9: 0x0e49, # Thai_maitho ้
+ 0x0dea: 0x0e4a, # Thai_maitri ๊
+ 0x0deb: 0x0e4b, # Thai_maichattawa ๋
+ 0x0dec: 0x0e4c, # Thai_thanthakhat ์
+ 0x0ded: 0x0e4d, # Thai_nikhahit ํ
+ 0x0df0: 0x0e50, # Thai_leksun ๐
+ 0x0df1: 0x0e51, # Thai_leknung ๑
+ 0x0df2: 0x0e52, # Thai_leksong ๒
+ 0x0df3: 0x0e53, # Thai_leksam ๓
+ 0x0df4: 0x0e54, # Thai_leksi ๔
+ 0x0df5: 0x0e55, # Thai_lekha ๕
+ 0x0df6: 0x0e56, # Thai_lekhok ๖
+ 0x0df7: 0x0e57, # Thai_lekchet ๗
+ 0x0df8: 0x0e58, # Thai_lekpaet ๘
+ 0x0df9: 0x0e59, # Thai_lekkao ๙
+ 0x0ea1: 0x3131, # Hangul_Kiyeog ㄱ
+ 0x0ea2: 0x3132, # Hangul_SsangKiyeog ㄲ
+ 0x0ea3: 0x3133, # Hangul_KiyeogSios ㄳ
+ 0x0ea4: 0x3134, # Hangul_Nieun ㄴ
+ 0x0ea5: 0x3135, # Hangul_NieunJieuj ㄵ
+ 0x0ea6: 0x3136, # Hangul_NieunHieuh ㄶ
+ 0x0ea7: 0x3137, # Hangul_Dikeud ㄷ
+ 0x0ea8: 0x3138, # Hangul_SsangDikeud ㄸ
+ 0x0ea9: 0x3139, # Hangul_Rieul ㄹ
+ 0x0eaa: 0x313a, # Hangul_RieulKiyeog ㄺ
+ 0x0eab: 0x313b, # Hangul_RieulMieum ㄻ
+ 0x0eac: 0x313c, # Hangul_RieulPieub ㄼ
+ 0x0ead: 0x313d, # Hangul_RieulSios ㄽ
+ 0x0eae: 0x313e, # Hangul_RieulTieut ㄾ
+ 0x0eaf: 0x313f, # Hangul_RieulPhieuf ㄿ
+ 0x0eb0: 0x3140, # Hangul_RieulHieuh ㅀ
+ 0x0eb1: 0x3141, # Hangul_Mieum ㅁ
+ 0x0eb2: 0x3142, # Hangul_Pieub ㅂ
+ 0x0eb3: 0x3143, # Hangul_SsangPieub ㅃ
+ 0x0eb4: 0x3144, # Hangul_PieubSios ㅄ
+ 0x0eb5: 0x3145, # Hangul_Sios ㅅ
+ 0x0eb6: 0x3146, # Hangul_SsangSios ㅆ
+ 0x0eb7: 0x3147, # Hangul_Ieung ㅇ
+ 0x0eb8: 0x3148, # Hangul_Jieuj ㅈ
+ 0x0eb9: 0x3149, # Hangul_SsangJieuj ㅉ
+ 0x0eba: 0x314a, # Hangul_Cieuc ㅊ
+ 0x0ebb: 0x314b, # Hangul_Khieuq ㅋ
+ 0x0ebc: 0x314c, # Hangul_Tieut ㅌ
+ 0x0ebd: 0x314d, # Hangul_Phieuf ㅍ
+ 0x0ebe: 0x314e, # Hangul_Hieuh ㅎ
+ 0x0ebf: 0x314f, # Hangul_A ㅏ
+ 0x0ec0: 0x3150, # Hangul_AE ㅐ
+ 0x0ec1: 0x3151, # Hangul_YA ㅑ
+ 0x0ec2: 0x3152, # Hangul_YAE ㅒ
+ 0x0ec3: 0x3153, # Hangul_EO ㅓ
+ 0x0ec4: 0x3154, # Hangul_E ㅔ
+ 0x0ec5: 0x3155, # Hangul_YEO ㅕ
+ 0x0ec6: 0x3156, # Hangul_YE ㅖ
+ 0x0ec7: 0x3157, # Hangul_O ㅗ
+ 0x0ec8: 0x3158, # Hangul_WA ㅘ
+ 0x0ec9: 0x3159, # Hangul_WAE ㅙ
+ 0x0eca: 0x315a, # Hangul_OE ㅚ
+ 0x0ecb: 0x315b, # Hangul_YO ㅛ
+ 0x0ecc: 0x315c, # Hangul_U ㅜ
+ 0x0ecd: 0x315d, # Hangul_WEO ㅝ
+ 0x0ece: 0x315e, # Hangul_WE ㅞ
+ 0x0ecf: 0x315f, # Hangul_WI ㅟ
+ 0x0ed0: 0x3160, # Hangul_YU ㅠ
+ 0x0ed1: 0x3161, # Hangul_EU ㅡ
+ 0x0ed2: 0x3162, # Hangul_YI ㅢ
+ 0x0ed3: 0x3163, # Hangul_I ㅣ
+ 0x0ed4: 0x11a8, # Hangul_J_Kiyeog ᆨ
+ 0x0ed5: 0x11a9, # Hangul_J_SsangKiyeog ᆩ
+ 0x0ed6: 0x11aa, # Hangul_J_KiyeogSios ᆪ
+ 0x0ed7: 0x11ab, # Hangul_J_Nieun ᆫ
+ 0x0ed8: 0x11ac, # Hangul_J_NieunJieuj ᆬ
+ 0x0ed9: 0x11ad, # Hangul_J_NieunHieuh ᆭ
+ 0x0eda: 0x11ae, # Hangul_J_Dikeud ᆮ
+ 0x0edb: 0x11af, # Hangul_J_Rieul ᆯ
+ 0x0edc: 0x11b0, # Hangul_J_RieulKiyeog ᆰ
+ 0x0edd: 0x11b1, # Hangul_J_RieulMieum ᆱ
+ 0x0ede: 0x11b2, # Hangul_J_RieulPieub ᆲ
+ 0x0edf: 0x11b3, # Hangul_J_RieulSios ᆳ
+ 0x0ee0: 0x11b4, # Hangul_J_RieulTieut ᆴ
+ 0x0ee1: 0x11b5, # Hangul_J_RieulPhieuf ᆵ
+ 0x0ee2: 0x11b6, # Hangul_J_RieulHieuh ᆶ
+ 0x0ee3: 0x11b7, # Hangul_J_Mieum ᆷ
+ 0x0ee4: 0x11b8, # Hangul_J_Pieub ᆸ
+ 0x0ee5: 0x11b9, # Hangul_J_PieubSios ᆹ
+ 0x0ee6: 0x11ba, # Hangul_J_Sios ᆺ
+ 0x0ee7: 0x11bb, # Hangul_J_SsangSios ᆻ
+ 0x0ee8: 0x11bc, # Hangul_J_Ieung ᆼ
+ 0x0ee9: 0x11bd, # Hangul_J_Jieuj ᆽ
+ 0x0eea: 0x11be, # Hangul_J_Cieuc ᆾ
+ 0x0eeb: 0x11bf, # Hangul_J_Khieuq ᆿ
+ 0x0eec: 0x11c0, # Hangul_J_Tieut ᇀ
+ 0x0eed: 0x11c1, # Hangul_J_Phieuf ᇁ
+ 0x0eee: 0x11c2, # Hangul_J_Hieuh ᇂ
+ 0x0eef: 0x316d, # Hangul_RieulYeorinHieuh ㅭ
+ 0x0ef0: 0x3171, # Hangul_SunkyeongeumMieum ㅱ
+ 0x0ef1: 0x3178, # Hangul_SunkyeongeumPieub ㅸ
+ 0x0ef2: 0x317f, # Hangul_PanSios ㅿ
+ 0x0ef3: 0x3181, # Hangul_KkogjiDalrinIeung ㆁ
+ 0x0ef4: 0x3184, # Hangul_SunkyeongeumPhieuf ㆄ
+ 0x0ef5: 0x3186, # Hangul_YeorinHieuh ㆆ
+ 0x0ef6: 0x318d, # Hangul_AraeA ㆍ
+ 0x0ef7: 0x318e, # Hangul_AraeAE ㆎ
+ 0x0ef8: 0x11eb, # Hangul_J_PanSios ᇫ
+ 0x0ef9: 0x11f0, # Hangul_J_KkogjiDalrinIeung ᇰ
+ 0x0efa: 0x11f9, # Hangul_J_YeorinHieuh ᇹ
+ 0x0eff: 0x20a9, # Korean_Won ₩
+ 0x13a4: 0x20ac, # Euro €
+ 0x13bc: 0x0152, # OE Œ
+ 0x13bd: 0x0153, # oe œ
+ 0x13be: 0x0178, # Ydiaeresis Ÿ
+ 0x20ac: 0x20ac, # EuroSign €
+}
diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init.d/odoo b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init.d/odoo
index 5db7e33939d..f633be2b7c7 100755
--- a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init.d/odoo
+++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init.d/odoo
@@ -26,7 +26,7 @@ test -x $DAEMON || exit 0
set -e
function _start() {
- start-stop-daemon --start --quiet --pidfile $PIDFILE --chuid $USER:$USER --background --make-pidfile --exec $DAEMON -- --config $CONFIG --logfile $LOGFILE --load=web,hw_proxy,hw_posbox_homepage,hw_posbox_upgrade,hw_scanner,hw_escpos,hw_blackbox_be,hw_screen,hw_drivers
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --chuid $USER:$USER --background --make-pidfile --exec $DAEMON -- --config $CONFIG --logfile $LOGFILE --load=web,hw_proxy,hw_posbox_homepage,hw_posbox_upgrade,hw_escpos,hw_blackbox_be,hw_screen,hw_drivers
}
function _stop() {
diff --git a/addons/point_of_sale/views/point_of_sale.xml b/addons/point_of_sale/views/point_of_sale.xml
index a38a6997095..6704e47a98e 100644
--- a/addons/point_of_sale/views/point_of_sale.xml
+++ b/addons/point_of_sale/views/point_of_sale.xml
@@ -18,6 +18,7 @@
+