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 @@ +