[MERGE] forward port branch saas-6 up to 9d04d14669

This commit is contained in:
Christophe Simonis
2017-03-24 17:11:48 +01:00
30 changed files with 711 additions and 29 deletions
+6 -1
View File
@@ -86,10 +86,15 @@ class EscposDriver(Thread):
printers = usb.core.find(find_all=True, idVendor=0x0519)
for printer in printers:
try:
description = usb.util.get_string(printer, 256, printer.iManufacturer) + " " + usb.util.get_string(printer, 256, printer.iProduct)
except Exception as e:
_logger.error("Can not get printer description: %s" % (e.message or repr(e)))
description = 'Unknown printer'
connected.append({
'vendor': printer.idVendor,
'product': printer.idProduct,
'name': usb.util.get_string(printer, 256, printer.iManufacturer) + " " + usb.util.get_string(printer, 256, printer.iProduct)
'name': description
})
return connected
@@ -58,7 +58,10 @@ index_template = """
If you need to grant remote debugging access to a developer, you can do it <a href='/remote_connect'>here</a>.
</p>
<p>
The PosBox software installed on this posbox is <b>version 15</b>,
If you need to display the current customer basket on another device, you can do it <a href='/point_of_sale/display'>here</a>.
</p>
<p>
The PosBox software installed on this posbox is <b>version 16</b>,
the posbox version number is independent from Odoo. You can upgrade
the software on the <a href='/hw_proxy/upgrade/'>upgrade page</a>.
</p>
+4
View File
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import controllers
+21
View File
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Screen Driver',
'version': '1.0',
'category': 'Hardware Drivers',
'sequence': 6,
'summary': 'Provides support for customer facing displays',
'website': 'https://www.odoo.com/page/point-of-sale',
'description': """
Screen Driver
=============
This module allows the POS client to send rendered HTML to a remotely
installed screen. This module then displays this HTML using a web
browser.
""",
'depends': ['hw_proxy'],
'installable': False,
'auto_install': False,
}
+4
View File
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import main
+166
View File
@@ -0,0 +1,166 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp import http
from openerp.tools import config
from openerp.addons.web.controllers import main as web
import logging
import netifaces as ni
import os
from subprocess import call
import time
import threading
self_port = str(config['xmlrpc_port'] or 8069)
_logger = logging.getLogger(__name__)
class HardwareScreen(web.Home):
event_data = threading.Event()
pos_client_data = {'rendered_html': False,
'ip_from': False}
display_in_use = ''
failure_count = {}
def _call_xdotools(self, keystroke):
os.environ['DISPLAY'] = ":0.0"
os.environ['XAUTHORITY'] = "/run/lightdm/pi/xauthority"
try:
call(['xdotool', 'key', keystroke])
return "xdotool succeeded in stroking " + keystroke
except:
return "xdotool threw an error, maybe it is not installed on the posbox"
@http.route('/hw_proxy/display_refresh', type='json', auth='none', cors='*')
def display_refresh(self):
return self._call_xdotools('F5')
# POS CASHIER'S ROUTES
@http.route('/hw_proxy/customer_facing_display', type='json', auth='none', cors='*')
def update_user_facing_display(self, html=None):
request_ip = http.request.httprequest.remote_addr
if request_ip == HardwareScreen.pos_client_data.get('ip_from', ''):
HardwareScreen.pos_client_data['rendered_html'] = html
HardwareScreen.event_data.set()
return {'status': 'updated'}
else:
return {'status': 'failed'}
@http.route('/hw_proxy/take_control', type='json', auth='none', cors='*')
def take_control(self, html=None):
# ALLOW A CASHIER TO TAKE CONTROL OVER THE POSBOX, IN CASE OF MULTIPLE CASHIER PER POSBOX
HardwareScreen.pos_client_data['rendered_html'] = html
HardwareScreen.pos_client_data['ip_from'] = http.request.httprequest.remote_addr
HardwareScreen.event_data.set()
return {'status': 'success',
'message': 'You now have access to the display'}
@http.route('/hw_proxy/test_ownership', type='json', auth='none', cors='*')
def test_ownership(self):
if HardwareScreen.pos_client_data.get('ip_from') == http.request.httprequest.remote_addr:
return {'status': 'OWNER'}
else:
return {'status': 'NOWNER'}
# POSBOX ROUTES (SELF)
@http.route('/point_of_sale/display', type='http', auth='none')
def render_main_display(self):
return self._get_html()
@http.route('/point_of_sale/get_serialized_order', type='json', auth='none')
def get_serialized_order(self):
request_addr = http.request.httprequest.remote_addr
result = HardwareScreen.pos_client_data
if HardwareScreen.display_in_use and request_addr != HardwareScreen.display_in_use:
if not HardwareScreen.failure_count.get(request_addr):
HardwareScreen.failure_count[request_addr] = 0
if HardwareScreen.failure_count[request_addr] > 0:
time.sleep(10)
HardwareScreen.failure_count[request_addr] += 1
return {'rendered_html': """<div class="pos-customer_facing_display"><p>Not Authorized. Another browser is in use to display for the client. Please refresh.</p></div> """,
'stop_longpolling': True,
'ip_from': request_addr}
# IMPLEMENTATION OF LONGPOLLING
# Times out 2 seconds before the JS request does
if HardwareScreen.event_data.wait(28):
HardwareScreen.event_data.clear()
HardwareScreen.failure_count[request_addr] = 0
return result
return {'rendered_html': False,
'ip_from': HardwareScreen.pos_client_data['ip_from']}
def _get_html(self):
cust_js = None
interfaces = ni.interfaces()
my_ip = '127.0.0.1'
HardwareScreen.display_in_use = http.request.httprequest.remote_addr
with open(os.path.join(os.path.dirname(__file__), "../static/src/js/worker.js")) as js:
cust_js = js.read()
with open(os.path.join(os.path.dirname(__file__), "../static/src/css/cust_css.css")) as css:
cust_css = css.read()
display_ifaces = ""
for iface_id in interfaces:
iface_obj = ni.ifaddresses(iface_id)
ifconfigs = iface_obj.get(ni.AF_INET, [])
for conf in ifconfigs:
if conf.get('addr'):
display_ifaces += "<tr><td>" + iface_id + "</td>"
display_ifaces += "<td>" + conf.get('addr') + "</td>"
display_ifaces += "<td>" + conf.get('netmask') + "</td></tr>"
# What is my external IP ?
if iface_id != 'lo':
my_ip = conf.get('addr')
my_ip_port = my_ip + ":" + self_port
html = """
<!DOCTYPE html>
<html>
<head>
<title class="origin">Odoo -- Point of Sale</title>
<script type="text/javascript" class="origin" src="http://""" + my_ip_port + """/web/static/lib/jquery/jquery.js" >
</script>
<script type="text/javascript" class="origin">
""" + cust_js + """
</script>
<link rel="stylesheet" class="origin" href="http://""" + my_ip_port + """/web/static/lib/bootstrap/css/bootstrap.css" >
</link>
<script class="origin" src="http://""" + my_ip_port + """/web/static/lib/bootstrap/js/bootstrap.min.js"></script>
<style class="origin">
""" + cust_css + """
</style>
</head>
<body class="original_body">
<div hidden class="shadow"></div>
<div class="container">
<div class="row">
<div class="col-md-4 col-md-offset-4">
<h1>Odoo Point of Sale</h1>
<h2>POSBox Client display</h2>
<h3>My IPs</h3>
<table id="table_ip" class="table table-condensed">
<tr>
<th>Interface</th>
<th>IP</th>
<th>Netmask</th>
</tr>
""" + display_ifaces + """
</table>
<p>The customer cart will be displayed here once a Point of Sale session is started.</p>
<p>Odoo version 11 or above is required.</p>
</div>
</div>
</div>
</body>
</html>
"""
return html
@@ -0,0 +1,21 @@
html {
width: 100%;
height: 100%;
font-size: 11px;
}
body {
width: 100%;
height: 100%;
font-size: 14px;
margin: 0;
}
.original_body {
background-color: #797083;
color: white;
}
.ajax_got_body {
color: black;
}
+60
View File
@@ -0,0 +1,60 @@
$(function() {
"use strict";
// mergedHead will be turned to true the first time we receive something from a new host
// It allows to transform the <head> only once
var mergedHead = false;
var current_client_url = "";
var stop_longpolling = false;
function longpolling() {
$.ajax({
type: 'POST',
url: 'http://'+window.location.host+'/point_of_sale/get_serialized_order',
dataType: 'json',
beforeSend: function(xhr){xhr.setRequestHeader('Content-Type', 'application/json');},
data: JSON.stringify({jsonrpc: '2.0'}),
success: function(data) {
if (typeof data.result.stop_longpolling !== 'undefined') {
stop_longpolling = data.result.stop_longpolling;
}
if (data.result.ip_from && data.result.rendered_html) {
var trimmed = $.trim(data.result.rendered_html);
var $parsedHTML = $('<div>').html($.parseHTML(trimmed,true)); // WARNING: the true here will executes any script present in the string to parse
var new_client_url = $parsedHTML.find(".resources > base").attr('href');
if (!mergedHead || (current_client_url !== new_client_url)) {
mergedHead = true;
current_client_url = new_client_url;
$("body").removeClass('original_body').addClass('ajax_got_body');
$("head").children().not('.origin').remove();
$("head").append($parsedHTML.find(".resources").html());
}
$(".container").html($parsedHTML.find('.pos-customer_facing_display').html());
$(".container").attr('class', 'container').addClass($parsedHTML.find('.pos-customer_facing_display').attr('class'));
var d = $('.pos_orderlines_list');
d.scrollTop(d.prop("scrollHeight"));
// Here we execute the code coming from the pos, apparently $.parseHTML() executes scripts right away,
// Since we modify the dom afterwards, the script might not have any effect
if (typeof foreign_js !== 'undefined' && $.isFunction(foreign_js)) {
foreign_js();
}
}
},
complete: function(jqXHR,err) {
if (!stop_longpolling) {
longpolling();
}
},
timeout: 30000,
});
};
longpolling();
});
@@ -11,7 +11,7 @@ create_ramdisk () {
echo "Creating ramdisk for ${1} of size ${SIZE}..."
mount -t tmpfs -o size="${SIZE}" tmpfs "${RAMDISK}"
rsync -a --exclude="swap" --exclude="apt" --exclude="dpkg" "${ORIGINAL}/" "${RAMDISK}/"
rsync -a --exclude="swap" --exclude="apt" --exclude="dpkg" --exclude=".mozilla" "${ORIGINAL}/" "${RAMDISK}/"
mount --bind "${RAMDISK}" "${ORIGINAL}"
}
@@ -0,0 +1,8 @@
[LightDM]
user-authority-in-system-dir=true
[SeatDefaults]
xserver-command=/usr/bin/X -s 0 dpms -nolisten tcp
greeter-hide-users=false
autologin-user=pi
@@ -0,0 +1,6 @@
#!/bin/bash
xset s off
xset -dpms
export HOME=/tmp
/usr/bin/firefox http://localhost:8069/point_of_sale/display &
@@ -0,0 +1,32 @@
Manifest-Version: 1.0
Name: install.rdf
Digest-Algorithms: MD5 SHA1
MD5-Digest: xnFKNbJgc33dy/FJ+4cmHA==
SHA1-Digest: 4YLgHgdgCw4lGCfsX7+r4zVmKJ4=
Name: chrome.manifest
Digest-Algorithms: MD5 SHA1
MD5-Digest: O5ce4bajAqUxG6iVul0K2w==
SHA1-Digest: wb6VleWnV5GgLypmg1ij9TfmPLE=
Name: content/rkioskbrowser.js
Digest-Algorithms: MD5 SHA1
MD5-Digest: /cF15tfVnbkfbxRVTL5N+g==
SHA1-Digest: lvbY92PbsjwY+0TguEYoXWw0lxQ=
Name: content/rkioskbrowser.xul
Digest-Algorithms: MD5 SHA1
MD5-Digest: NFOIpAmZtn20Y+XfTo4cpw==
SHA1-Digest: VVIo7GdG+4mvAOtgR0DJUsSc/yg=
Name: content/rkioskunknownContentType.xul
Digest-Algorithms: MD5 SHA1
MD5-Digest: 6cNGoHJS9Xvm+wmvo4IesQ==
SHA1-Digest: T8i1MkMKjmn5HICqWv+iXSPRKIU=
Name: content/rkioskxpinstallConfirm.xul
Digest-Algorithms: MD5 SHA1
MD5-Digest: SxN1YnRJiuood2WhU85v9g==
SHA1-Digest: Msj21u4bTAKUi496xld5YCGdjiA=
@@ -0,0 +1,4 @@
Signature-Version: 1.0
MD5-Digest-Manifest: 7hFyhNW67+H+rf9OhWGDkA==
SHA1-Digest-Manifest: HvfAQQLXphSZ9m8KBorJMX8MUnE=
@@ -0,0 +1,3 @@
The R-kiosk module has been downloaded from https://addons.mozilla.org/en-US/firefox/addon/r-kiosk/
It is in the public domain as stated on https://addons.mozilla.org/en-US/firefox/addon/r-kiosk/eula/
This mozilla module is shipped by Odoo to provide out of the box Client facing display to Odoo's Posbox
@@ -0,0 +1,4 @@
content RKiosk content/
overlay chrome://browser/content/browser.xul chrome://rkiosk/content/rkioskbrowser.xul
overlay chrome://mozapps/content/xpinstall/xpinstallConfirm.xul chrome://rkiosk/content/rkioskxpinstallConfirm.xul
overlay chrome://mozapps/content/downloads/unknownContentType.xul chrome://rkiosk/content/rkioskunknownContentType.xul
@@ -0,0 +1,34 @@
function Rkiosk_donothing()
{
}
function rkioskclose()
{
close();
}
function Rkiosk_navbar_setting()
{
var rkiosk_navbar_enable="true";
var prefs = Components.classes["@mozilla.org/preferences-service;1"].
getService(Components.interfaces.nsIPrefBranch);
if (prefs.getPrefType("rkiosk.navbar") == prefs.PREF_BOOL){
if (prefs.getBoolPref("rkiosk.navbar")) rkiosk_navbar_enable = "false";
}
var rkiosk_element = document.getElementById("navigator-toolbox");
rkiosk_element.setAttribute("hidden", rkiosk_navbar_enable);
}
function RkioskBrowserStartup()
{
Rkiosk_navbar_setting();
BrowserStartup();
setTimeout(RkioskdelayedStartup, 1000);
}
function RkioskdelayedStartup()
{
window.fullScreen = true;
}
@@ -0,0 +1,175 @@
<?xml version="1.0"?>
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<window id="main-window"
onload="RkioskBrowserStartup()">
</window>
<script type="application/x-javascript" src="rkioskbrowser.js"/>
<menubar id="main-menubar" hidden="true" fullscreentoolbar="false" disabled="true">
<menuitem id="menu_preferences" disabled="true"/>
</menubar>
<popup id="contentAreaContextMenu"
onpopupshowing="if (event.target != this) return true; return false;"
onpopuphiding="if (event.target == this) gContextMenu = null;">
</popup>
<commandset id="mainCommandSet">
<command id="cmd_newNavigator" disabled="true"/>
<command id="cmd_handleBackspace" disabled="true"/>
<command id="cmd_handleShiftBackspace" disabled="true"/>
<command id="cmd_newNavigatorTab" disabled="true"/>
<command id="Browser:OpenFile" disabled="true"/>
<command id="Browser:SavePage" disabled="true"/>
<command id="Browser:SaveFrame" disabled="true"/>
<command id="Browser:SendLink" disabled="true"/>
<command id="cmd_pageSetup" disabled="true"/>
<command id="cmd_print" oncommand="PrintUtils.print();"/>
<command id="cmd_printPreview" disabled="true"/>
<command id="cmd_close" disabled="true"/>
<command id="cmd_closeWindow" disabled="true"/>
<command id="cmd_ToggleTabsOnTop" disabled="true"/>
<command id="cmd_quitApplication" disabled="true"/>
<command id="cmd_toggleTaskbar" disabled="true"/>
<command id="cmd_CustomizeToolbars" disabled="true"/>
<commandset id="editMenuCommands"/>
<command id="View:PageSource" disabled="true"/>
<command id="View:PageInfo" disabled="true"/>
<command id="View:FullScreen" disabled="true"/>
<command id="cmd_find" disabled="true"/>
<command id="cmd_findAgain" disabled="true"/>
<command id="cmd_findPrevious" disabled="true"/>
<command id="Browser:AddBookmarkAs" disabled="true"/>
<command id="Browser:BookmarkAllTabs" disabled="true"/>
<command id="Browser:Home" oncommand="BrowserHome();"/>
<command id="Browser:Back" disabled="true"/>
<command id="Browser:Forward" disabled="true"/>
<command id="Browser:Stop" disabled="true"/>
<command id="Browser:Reload" disabled="true"/>
<command id="Browser:ReloadSkipCache" disabled="true"/>
<command id="Browser:BackOrBackDuplicate" disabled="true"/>
<command id="Browser:Forward" disabled="true"/>
<command id="Browser:ForwardOrForwardDuplicate" disabled="true"/>
<command id="Browser:ReloadOrDuplicate" disabled="true"/>
<command id="Browser:NextTab" disabled="true"/>
<command id="Browser:PrevTab" disabled="true"/>
<command id="Browser:ShowAllTabs" disabled="true"/>
<command id="Browser:ToggleTabView" disabled="true"/>
<command id="Browser:ToggleAddonBar" disabled="true"/>
<command id="cmd_fullZoomReduce" disabled="true"/>
<command id="cmd_fullZoomEnlarge" disabled="true"/>
<command id="cmd_fullZoomReset" disabled="true"/>
<command id="cmd_fullZoomToggle" disabled="true"/>
<command id="Browser:OpenLocation" disabled="true"/>
<command id="Tools:Search" disabled="true"/>
<command id="Tools:Downloads" disabled="true"/>
<command id="Tools:Addons" disabled="true"/>
<command id="Tools:Sanitize" disabled="true"/>
<command id="Tools:Inspect" disabled="true"/>
<command id="Tools:PrivateBrowsing" disabled="true"/>
<command id="History:UndoCloseTab" disabled="true"/>
<command id="History:UndoCloseWindow" disabled="true"/>
<command id="cmd_bm_open" disabled="true"/>
<command id="cmd_bm_openinnewwindow" disabled="true"/>
<command id="cmd_bm_openinnewtab" disabled="true"/>
<command id="cmd_bm_openfolder" disabled="true"/>
<command id="cmd_bm_managefolder" disabled="true"/>
<command id="cmd_bm_newfolder" disabled="true"/>
<command id="cmd_bm_newbookmark" disabled="true"/>
<command id="cmd_bm_newseparator" disabled="true"/>
<command id="cmd_bm_properties" disabled="true"/>
<command id="cmd_bm_refreshlivemark" disabled="true"/>
<command id="cmd_bm_refreshmicrosummary" disabled="true"/>
<command id="cmd_bm_rename" disabled="true"/>
<command id="cmd_bm_moveBookmark" disabled="true"/>
<command id="cmd_bm_sortbyname" disabled="true"/>
<command id="cmd_copyLink" disabled="true"/>
<command id="cmd_copyImageLocation" disabled="true"/>
<command id="cmd_copyImageContents" disabled="true"/>
<command id="cmd_undo" disabled="true"/>
<command id="cmd_redo" disabled="true"/>
<command id="cmd_cut" disabled="true"/>
<command id="cmd_copy" disabled="true"/>
<command id="cmd_paste" disabled="true"/>
<command id="cmd_delete" disabled="true"/>
<command id="cmd_selectAll" disabled="true"/>
<command id="cmd_switchTextDirection" disabled="true"/>
<command id="cmd_textZoomReduce" disabled="true"/>
<command id="cmd_textZoomEnlarge" disabled="true"/>
<command id="cmd_textZoomReset" disabled="true"/>
</commandset>
<commandset id="placesCommands">
<command id="Browser:ShowAllBookmarks" disabled="true"/>
<command id="Browser:ShowAllHistory" disabled="true"/>
</commandset>
<keyset id="mainKeyset">
<key id="rkiosk_f1" keycode="VK_F1" oncommand="Rkiosk_donothing();"/>
<key id="key_newNavigator" disabled="true"/>
<key id="key_newNavigatorTab" disabled="true"/>
<key id="focusURLBar" disabled="true"/>
<key id="focusURLBar2" disabled="true"/>
<key id="key_search" disabled="true"/>
<key id="key_search2" disabled="true"/>
<key id="key_openDownloads" disabled="true"/>
<key id="openFileKb" disabled="true"/>
<key id="key_savePage" disabled="true"/>
<key id="printKb" disabled="true"/>
<key id="key_close" disabled="true"/>
<key id="key_closeWindow" disabled="true"/>
<key id="key_undo" disabled="true"/>
<key id="key_redo" disabled="true"/>
<key id="key_cut" disabled="true"/>
<key id="key_copy" disabled="true"/>
<key id="key_paste" disabled="true"/>
<key id="key_delete" disabled="true"/>
<key id="key_selectAll" disabled="true"/>
<key id="goBackKb" disabled="true"/>
<key id="goForwardKb" disabled="true"/>
<key id="goHome" keycode="VK_HOME" command="Browser:Home" modifiers="alt"/>
<key id="key_viewSource" disabled="true"/>
<key id="key_find" disabled="true"/>
<key id="key_findAgain" disabled="true"/>
<key id="key_findPrevious" disabled="true"/>
<key id="addBookmarkAsKb" disabled="true"/>
<key id="bookmarkAllTabsKb" disabled="true"/>
<key id="key_stop" disabled="true"/>
<key id="key_gotoHistory" command="Rkiosk_donothing();"/>
<key id="key_switchTextDirection" disabled="true"/>
<key id="key_sanitize" disabled="true"/>
<key id="key_undoCloseTab" disabled="true"/>
<key id="viewBookmarksSidebarKb" command="Rkiosk_donothing();"/>
<key id="viewBookmarksSidebarWinKb" command="Rkiosk_donothing();"/>
<key id="key_fullScreen" disabled="true"/>
<key id="key_textZoomReduce" disabled="true"/>
<key id="key_textZoomEnlarge" disabled="true"/>
<key id="key_textZoomReset" disabled="true"/>
<key id="showAllHistoryKb" disabled="true"/>
<key id="key_errorConsole" disabled="true"/>
<key id="manBookmarkKb" disabled="true"/>
<key id="key_fullZoomReduce" disabled="true"/>
<key id="key_fullZoomEnlarge" disabled="true"/>
<key id="key_fullZoomReset" disabled="true"/>
<key id="key_openAddons" disabled="true"/>
<key id="key_webConsole" disabled="true"/>
<key id="key_inspect" disabled="true"/>
<key id="key_scratchpad" disabled="true"/>
<key id="key_showAllTabs" disabled="true"/>
<key id="key_tabview" disabled="true"/>
<key id="key_privatebrowsing" disabled="true"/>
<key id="key_undoCloseWindow" disabled="true"/>
<key id="key_selectTab1" disabled="true"/>
<key id="key_selectTab2" disabled="true"/>
<key id="key_selectTab3" disabled="true"/>
<key id="key_selectTab4" disabled="true"/>
<key id="key_selectTab5" disabled="true"/>
<key id="key_selectTab6" disabled="true"/>
<key id="key_selectTab7" disabled="true"/>
<key id="key_selectTab8" disabled="true"/>
<key id="key_selectLastTab" disabled="true"/>
<key id="key_toggleAddonBar" disabled="true"/>
</keyset>
</overlay>
@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<overlay>
<dialog id="unknownContentType"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="rkioskclose();">
<script src="rkioskbrowser.js"/>
</dialog>
</overlay>
@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<overlay>
<dialog id="xpinstallConfirm"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="rkioskclose();">
<script src="rkioskbrowser.js"/>
</dialog>
</overlay>
@@ -0,0 +1,19 @@
<?xml version='1.0' encoding='utf-8'?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>{4D498D0A-05AD-4fdb-97B5-8A0AABC1FC5B}</em:id>
<em:name>R-kiosk</em:name>
<em:version>0.9.0.1-signed.1-signed</em:version>
<em:description>RKiosk (Real Kiosk), fullscreen kiosk mode: all menus, keys etc. disabled</em:description>
<em:creator>Kimmo Heinaaro</em:creator>
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>2.0</em:minVersion>
<em:maxVersion>6.*</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>
@@ -0,0 +1,7 @@
// Preferences to allow unattended install of R-Kiosk extension
// Needed for Odoo posbox Client display
pref("app.update.checkInstallTime", false);
pref("devtools.webide.widget.autoinstall", false);
pref("xpinstall.customConfirmationUI", false);
pref("xpinstall.signatures.required", false);
pref("browser.shell.checkDefaultBrowser",false)
@@ -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_scale,hw_scanner,hw_escpos,hw_blackbox_be
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_scale,hw_scanner,hw_escpos,hw_blackbox_be,hw_screen
}
function _stop() {
@@ -8,17 +8,25 @@ __dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
__file="${__dir}/$(basename "${BASH_SOURCE[0]}")"
__base="$(basename ${__file} .sh)"
# Since we are emulating, the real /boot is not mounted,
# leading to mismatch between kernel image and modules.
mount /dev/sda1 /boot
# Recommends: antiword, graphviz, ghostscript, postgresql, python-gevent, poppler-utils
export DEBIAN_FRONTEND=noninteractive
echo "nameserver 8.8.8.8" >> /etc/resolv.conf
mount /dev/sda1 /boot
apt-get update
apt-get -y dist-upgrade
# Do not be too fast to upgrade to more recent firmware and kernel than 4.38
# Firmware 4.44 seems to prevent the LED mechanism from working
PKGS_TO_INSTALL="adduser postgresql-client python python-dateutil python-decorator python-docutils python-feedparser python-imaging python-jinja2 python-ldap python-libxslt1 python-lxml python-mako python-mock python-openid python-passlib python-psutil python-psycopg2 python-pybabel python-pychart python-pydot python-pyparsing python-pypdf python-reportlab python-requests python-tz python-vatnumber python-vobject python-werkzeug python-xlwt python-yaml postgresql python-gevent python-serial python-pip python-dev localepurge vim mc mg screen iw hostapd isc-dhcp-server git rsync console-data"
PKGS_TO_INSTALL="adduser postgresql-client python python-dateutil python-decorator python-docutils python-feedparser python-imaging python-jinja2 python-ldap python-libxslt1 python-lxml python-mako python-mock python-openid python-passlib python-psutil python-psycopg2 python-pybabel python-pychart python-pydot python-pyparsing python-pypdf python-reportlab python-requests python-tz python-vatnumber python-vobject python-werkzeug python-xlwt python-yaml postgresql python-gevent python-serial python-pip python-dev localepurge vim mc mg screen iw hostapd isc-dhcp-server git rsync console-data lightdm xserver-xorg-video-fbdev xserver-xorg-input-evdev iceweasel xdotool unclutter x11-utils openbox python-netifaces rpi-update"
apt-get -y install ${PKGS_TO_INSTALL}
# KEEP OWN CONFIG FILES DURING PACKAGE CONFIGURATION
# http://serverfault.com/questions/259226/automatically-keep-current-version-of-config-files-when-apt-get-install
apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" --force-yes install ${PKGS_TO_INSTALL}
apt-get clean
localepurge
@@ -32,13 +40,20 @@ pip install pyusb==1.0.0b1
pip install qrcode
pip install evdev
# --upgrade because websocket_client in wheezy is bad:
# https://github.com/docker/compose/issues/1288
pip install --upgrade websocket_client
groupadd usbusers
usermod -a -G usbusers pi
usermod -a -G lp pi
usermod -a -G input lightdm
sudo -u postgres createuser -s pi
mkdir /var/log/odoo
chown pi:pi /var/log/odoo
chown pi:pi -R /home/pi/odoo/
chmod 770 -R /home/pi/odoo/
# logrotate is very picky when it comes to file permissions
chown -R root:root /etc/logrotate.d/
@@ -54,6 +69,31 @@ update-rc.d -f isc-dhcp-server remove
systemctl daemon-reload
systemctl enable ramdisks.service
systemctl disable dphys-swapfile.service
systemctl enable ssh
# USER PI AUTO LOGIN (from nano raspi-config)
# We take the whole algorithm from raspi-config in order to stay compatible with raspbian infrastructure
if command -v systemctl > /dev/null && systemctl | grep -q '\-\.mount'; then
SYSTEMD=1
elif [ -f /etc/init.d/cron ] && [ ! -h /etc/init.d/cron ]; then
SYSTEMD=0
else
echo "Unrecognised init system"
return 1
fi
if [ $SYSTEMD -eq 1 ]; then
systemctl set-default graphical.target
ln -fs /etc/systemd/system/autologin@.service /etc/systemd/system/getty.target.wants/getty@tty1.service
else
update-rc.d lightdm enable 2
fi
# disable overscan in /boot/config.txt, we can't use
# overwrite_after_init because it's on a different device
# (/dev/mmcblk0p1) and we don't mount that afterwards.
# This option disables any black strips around the screen
# cf: https://www.raspberrypi.org/documentation/configuration/raspi-config.md
echo "disable_overscan=1" >> /boot/config.txt
# https://www.raspberrypi.org/forums/viewtopic.php?p=79249
# to not have "setting up console font and keymap" during boot take ages
@@ -68,5 +108,6 @@ create_ramdisk_dir "/var"
create_ramdisk_dir "/etc"
create_ramdisk_dir "/tmp"
mkdir /root_bypass_ramdisks
umount /dev/sda1
reboot
@@ -0,0 +1,2 @@
SUBSYSTEM=="input", GROUP="input", MODE="0660"
KERNEL=="tty[0-9]*", GROUP="tty", MODE="0660"
@@ -36,20 +36,25 @@ fi
cp -a *raspbian*.img posbox.img
CLONE_DIR="${OVERWRITE_FILES_BEFORE_INIT_DIR}/home/pi/odoo"
rm -rf "${CLONE_DIR}"
mkdir "${CLONE_DIR}"
git clone -b 8.0 --no-checkout --depth 1 https://github.com/odoo/odoo.git "${CLONE_DIR}"
cd "${CLONE_DIR}"
git config core.sparsecheckout true
echo "addons/web
if [ ! -d $CLONE_DIR ]; then
echo "Clone Github repo"
mkdir -p "${CLONE_DIR}"
git clone -b 8.0 --no-local --no-checkout --depth 1 https://github.com/odoo/odoo.git "${CLONE_DIR}"
cd "${CLONE_DIR}"
git config core.sparsecheckout true
echo "addons/web
addons/web_kanban
addons/hw_*
addons/point_of_sale/tools/posbox/configuration
openerp/
odoo.py" | tee --append .git/info/sparse-checkout > /dev/null
git read-tree -mu HEAD
cd "${__dir}"
git read-tree -mu HEAD
fi
cd "${__dir}"
USR_BIN="${OVERWRITE_FILES_BEFORE_INIT_DIR}/usr/bin/"
mkdir -p "${USR_BIN}"
cd "/tmp"
@@ -60,9 +65,11 @@ cd "${__dir}"
mv /tmp/ngrok "${USR_BIN}"
# zero pad the image to be around 3.5 GiB, by default the image is only ~1.3 GiB
echo "Enlarging the image..."
dd if=/dev/zero bs=1M count=2048 >> posbox.img
# resize partition table
echo "Fdisking"
START_OF_ROOT_PARTITION=$(fdisk -l posbox.img | tail -n 1 | awk '{print $2}')
(echo 'p'; # print
echo 'd'; # delete
@@ -75,7 +82,7 @@ START_OF_ROOT_PARTITION=$(fdisk -l posbox.img | tail -n 1 | awk '{print $2}')
echo 'p'; # print
echo 'w') | fdisk posbox.img # write and quit
LOOP_MAPPER_PATH=$(kpartx -av posbox.img | tail -n 1 | cut -d ' ' -f 3)
LOOP_MAPPER_PATH=$(kpartx -avs posbox.img | tail -n 1 | cut -d ' ' -f 3)
LOOP_MAPPER_PATH="/dev/mapper/${LOOP_MAPPER_PATH}"
sleep 5
+37 -7
View File
@@ -850,31 +850,61 @@ function eval_contexts (contexts, evaluation_context) {
function eval_domains (domains, evaluation_context) {
evaluation_context = _.extend(pycontext(), evaluation_context || {});
var result_domain = [];
// Normalize only if the first domain is the array ["|"] or ["!"]
var need_normalization = (
domains.length > 0 &&
domains[0].length === 1 &&
(domains[0][0] === "|" || domains[0][0] === "!")
);
_(domains).each(function (domain) {
if (_.isString(domain)) {
// wrap raw strings in domain
domain = { __ref: 'domain', __debug: domain };
}
var domain_array_to_combine;
switch(domain.__ref) {
case 'domain':
evaluation_context.context = evaluation_context;
result_domain.push.apply(
result_domain, py.eval(domain.__debug, wrap_context(evaluation_context)));
domain_array_to_combine = py.eval(domain.__debug, wrap_context(evaluation_context));
break;
case 'compound_domain':
var eval_context = eval_contexts([domain.__eval_context]);
result_domain.push.apply(
result_domain, eval_domains(
domain.__domains, _.extend(
{}, evaluation_context, eval_context)));
domain_array_to_combine = eval_domains(
domain.__domains, _.extend({}, evaluation_context, eval_context)
);
break;
default:
result_domain.push.apply(result_domain, domain);
domain_array_to_combine = domain;
}
if (need_normalization) {
domain_array_to_combine = get_normalized_domain(domain_array_to_combine);
}
result_domain.push.apply(result_domain, domain_array_to_combine);
});
return result_domain;
}
/**
* Returns a normalized copy of the given domain array. Normalization is
* is making the implicit "&" at the start of the domain explicit, e.g.
* [A, B, C] would become ["&", "&", A, B, C].
*
* @param {Array} domain_array
* @returns {Array} normalized copy of the given array
*/
function get_normalized_domain(domain_array) {
var expected = 1; // Holds the number of expected domain expressions
_.each(domain_array, function (item) {
if (item === "&" || item === "|") {
expected++;
} else if (item !== "!") {
expected--;
}
});
var new_explicit_ands = _.times(-expected, _.constant("&"));
return new_explicit_ands.concat(domain_array);
}
function eval_groupbys (contexts, evaluation_context) {
evaluation_context = _.extend(pycontext(), evaluation_context || {});
var result_group = [];
+3 -2
View File
@@ -536,7 +536,7 @@ odoo.define_section('eval.edc.nonliterals', ['web.pyeval', 'web.session'], funct
var result = pyeval.sync_eval_domains_and_contexts({
domains: [
[['type', '=', 'contract']],
{ "__domains": [["|"], [["state", "in", ["open", "draft"]]], [["state", "=", "pending"]]],
{ "__domains": [["|"], [["state", "in", ["open", "draft"]]], [["type", "=", "contract"], ["state", "=", "pending"]]],
"__eval_context": null,
"__ref": "compound_domain"
},
@@ -552,7 +552,8 @@ odoo.define_section('eval.edc.nonliterals', ['web.pyeval', 'web.session'], funct
assert.deepEqual(result.domain, [
["type", "=", "contract"],
"|", ["state", "in", ["open", "draft"]],
["state", "=", "pending"],
"&", ["type", "=", "contract"],
["state", "=", "pending"],
"|",
"&", ["date", "!=", false],
["date", "<=", today],
@@ -334,7 +334,7 @@ var CalendarView = View.extend({
self.proxy('update_record')(event._id, data);
},
eventRender: function (event, element, view) {
element.find('.fc-event-title').html(event.title);
element.find('.fc-event-title').html(event.title + event.attendee_avatars);
},
eventAfterRender: function (event, element, view) {
if ((view.name !== 'month') && (((event.end-event.start)/60000)<=30)) {
@@ -630,7 +630,6 @@ var CalendarView = View.extend({
if (attendee_other.length>2) {
the_title_avatar += '<span class="o_attendee_head" title="' + attendee_other.slice(0, -2) + '">+</span>';
}
the_title = the_title_avatar + the_title;
}
}
@@ -642,6 +641,7 @@ var CalendarView = View.extend({
'start': moment(date_start).toString(),
'end': moment(date_stop).toString(),
'title': the_title,
'attendee_avatars': the_title_avatar,
'allDay': (this.fields[this.date_start].type == 'date' || (this.all_day && evt[this.all_day]) || false),
'id': evt.id,
'attendees':attendees
+10 -3
View File
@@ -1555,9 +1555,16 @@ class Root(object):
httprequest.session.db = db_monodb(httprequest)
def setup_lang(self, httprequest):
if not "lang" in httprequest.session.context:
lang = httprequest.accept_languages.best or "en_US"
lang = babel.core.LOCALE_ALIASES.get(lang, lang).replace('-', '_')
if "lang" not in httprequest.session.context:
alang = httprequest.accept_languages.best or "en-US"
try:
code, territory, _, _ = babel.core.parse_locale(alang, sep='-')
if territory:
lang = '%s_%s' % (code, territory)
else:
lang = babel.core.LOCALE_ALIASES[code]
except (ValueError, KeyError):
lang = 'en_US'
httprequest.session.context["lang"] = lang
def get_request(self, httprequest):