[ADD] hw_screen: implement support for customer facing displays (#15303)
This adds the hw_screen module which is responsible for receiving rendered HTML from a POS client and sending it to a running Chromium browser. This is done by sending Remote Debugging Protocol messages over a WebSocket provided by Chromium. This allows hw_screen to evaluate arbitrary JS. This is used to seamlessly (without flickering) update the browser. This also includes changes to the POSBox. X, Chromium and some miscellaneous other programs were added. Functionality was added that automatically displays a fullscreen, UI-less Chromium browser when the POSBox starts up. This is accomplished through x_application.sh, which gets executed only when a display is detected. After the browser starts, it will display a specific screen until it receives the first order update from a POS client. Creates a public controller to be able to display the interface on an external device not connected to the posbox (e.g. tablet) Courtesy of JOV, LPE, ODO and MAT
This commit is contained in:
parent
a09e4aa7aa
commit
1600216c1d
|
@ -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>
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2015 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import controllers
|
|
@ -0,0 +1,42 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2015 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
|
||||
{
|
||||
'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.
|
||||
""",
|
||||
'author': 'OpenERP SA',
|
||||
'depends': ['hw_proxy'],
|
||||
'installable': False,
|
||||
'auto_install': False,
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2015 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import main
|
|
@ -0,0 +1,185 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2015 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
|
||||
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;
|
||||
}
|
|
@ -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=
|
||||
|
Binary file not shown.
|
@ -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-simplejson python-tz python-unittest2 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-simplejson python-tz python-unittest2 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
|
||||
|
||||
|
|
Loading…
Reference in New Issue