Barcode scanners as IoT Devices
This PR is linked to: odoo/enterprise#4913 We updated the hw_scanner module to transform it into an IoT Driver. Since barcode scanners work the same way than keyboards, we will use a single Driver for both of them. Here are the different changes that are introduced : - Move all the js code that is related to barcode readers to a separate file for clarity. - Move KeyboardUSBDriver to Community. Barcode scanners should be available in Community. - Remove hw_scanner. We use the same route in the new driver (for Community) so backward compatibility is ensured. In Enterprise, we now use the longpolling to read the barcodes. - Send real characters instead of the key's name. For example, we now send '_' instead of 'UNDERSCORE'. - Add the possibility to select the keyboard/scanner layout from the IoT Device form view. An action is called on the IoT Box. - Add support for modifiers: Shift, Ctrl, Shift Lock, Num Lock... -- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr closes odoo/odoo#35084 Signed-off-by: Quentin Lejeune (qle) <qle@odoo.com>
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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))
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import controllers
|
||||
@@ -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,
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import main
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
});
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
+771
@@ -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 €
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/models.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/widget_base.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/keyboard.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/barcode_reader.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/chrome.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/devices.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/gui.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user