[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:
Lucas Perais 2017-03-21 11:50:12 +01:00 committed by GitHub
parent a09e4aa7aa
commit 1600216c1d
25 changed files with 729 additions and 14 deletions

View File

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

View File

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

View File

@ -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,
}

View File

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

View File

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

View File

@ -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;
}

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();
});

View File

@ -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}"
}

View File

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

View File

@ -0,0 +1,6 @@
#!/bin/bash
xset s off
xset -dpms
export HOME=/tmp
/usr/bin/firefox http://localhost:8069/point_of_sale/display &

View File

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

View File

@ -0,0 +1,4 @@
Signature-Version: 1.0
MD5-Digest-Manifest: 7hFyhNW67+H+rf9OhWGDkA==
SHA1-Digest-Manifest: HvfAQQLXphSZ9m8KBorJMX8MUnE=

View File

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

View File

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

View File

@ -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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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() {

View File

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

View File

@ -0,0 +1,2 @@
SUBSYSTEM=="input", GROUP="input", MODE="0660"
KERNEL=="tty[0-9]*", GROUP="tty", MODE="0660"

View File

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