mirror of
https://git.rwth-aachen.de/acs/public/villas/node/
synced 2025-03-09 00:00:00 +01:00
Merge branch 'node-websocket' into develop
This commit is contained in:
commit
556aaf48b2
25 changed files with 29686 additions and 51 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -2,3 +2,6 @@
|
|||
path = clients/rtds
|
||||
url = git@github.com:RWTH-ACS/GTFPGA.git
|
||||
branch = master
|
||||
[submodule "contrib/websocket/flot"]
|
||||
path = contrib/websocket/flot
|
||||
url = https://github.com/flot/flot.git
|
||||
|
|
7
Makefile
7
Makefile
|
@ -60,6 +60,13 @@ ifeq ($(shell pkg-config libcurl jansson uuid; echo $$?),0)
|
|||
LIB_LDLIBS += $(shell pkg-config --libs libcurl jansson uuid)
|
||||
endif
|
||||
|
||||
# Enable WebSocket support
|
||||
ifeq ($(shell pkg-config libwebsockets; echo $$?),0)
|
||||
LIB_OBJS += websocket.o
|
||||
LIB_CFLAGS += $(shell pkg-config --cflags libwebsockets)
|
||||
LIB_LDLIBS += $(shell pkg-config --libs libwebsockets)
|
||||
endif
|
||||
|
||||
# Enable OPAL-RT Asynchronous Process support (will result in 32bit binary!!!)
|
||||
ifneq (,$(wildcard $(OPALDIR)/include_target/AsyncApi.h))
|
||||
LIB_OBJS += opal.o
|
||||
|
|
149
contrib/websocket/app.js
Normal file
149
contrib/websocket/app.js
Normal file
|
@ -0,0 +1,149 @@
|
|||
// global variables
|
||||
var connection;
|
||||
var timer;
|
||||
|
||||
var seq = 0;
|
||||
|
||||
var node = 'websocket'
|
||||
var url = 'ws://10.211.55.6:8080/' + node;
|
||||
var protocol = ['live'];
|
||||
|
||||
var plotData = [];
|
||||
var plotOptions = {
|
||||
xaxis: {
|
||||
mode: 'time'
|
||||
},
|
||||
legend: {
|
||||
show: true
|
||||
}
|
||||
};
|
||||
|
||||
var xDelta = 10*1000;
|
||||
|
||||
$(document).on('ready', function() {
|
||||
$.getJSON('/nodes.json', function(data) {
|
||||
$(data).each(function(index, node) {
|
||||
$(".node-selector").append($("<li>").text(node));
|
||||
});
|
||||
});
|
||||
|
||||
$('#slider').slider({
|
||||
min : 0,
|
||||
max : 100,
|
||||
slide : function(event, ui) {
|
||||
sendMsg(Date.now(), [ ui.value ]);
|
||||
}
|
||||
});
|
||||
|
||||
wsConnect();
|
||||
});
|
||||
|
||||
$(window).on('beforeunload', wsDisconnect);
|
||||
|
||||
function plotUpdate() {
|
||||
var data = [];
|
||||
|
||||
// add data to arrays
|
||||
for (var i = 0; i < plotData.length; i++) {
|
||||
// remove old values
|
||||
//while (plotData[i].length > 0 && plotData[i][0][0] < (Date.now() - xDelta))
|
||||
// plotData[i].shift()
|
||||
|
||||
data[i] = {
|
||||
data : plotData[i],
|
||||
shadowSize : 0,
|
||||
label : "Index " + String(i),
|
||||
lines : {
|
||||
lineWidth: 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var options = {
|
||||
xaxis: {
|
||||
min: Date.now() - xDelta,
|
||||
max: Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
/* update plot */
|
||||
$.plot('#placeholder', data, $.extend(true, options, plotOptions));
|
||||
}
|
||||
|
||||
function wsDisconnect() {
|
||||
connection.close();
|
||||
|
||||
$('#connectionStatus')
|
||||
.text('Disconnected')
|
||||
.css('color', 'red');
|
||||
|
||||
plotData = [];
|
||||
clearInterval(timer);
|
||||
}
|
||||
|
||||
function wsConnect() {
|
||||
connection = new WebSocket(url, protocol);
|
||||
|
||||
connection.onopen = function() {
|
||||
$('#connectionStatus')
|
||||
.text('Connected')
|
||||
.css('color', 'green');
|
||||
|
||||
timer = setInterval(plotUpdate, 1.0 / 25);
|
||||
};
|
||||
|
||||
connection.onclose = function() {
|
||||
wsDisconnect();
|
||||
|
||||
setTimeout(wsConnect, 3000); // retry
|
||||
};
|
||||
|
||||
connection.onerror = function(error) {
|
||||
$('#connectionStatus').text(function() {
|
||||
return 'Status: Error: ' + error.message;
|
||||
});
|
||||
};
|
||||
|
||||
connection.onmessage = function(e) {
|
||||
console.log(e.data);
|
||||
|
||||
var res = e.data.split(/\s+/).map(Number);
|
||||
var timestamp = res[0] * 1000;
|
||||
var values = res.slice(1)
|
||||
|
||||
// add empty arrays for data
|
||||
while (plotData.length < values.length)
|
||||
plotData.push([]);
|
||||
|
||||
// add data to arrays
|
||||
for (var i = 0; i < values.length; i++)
|
||||
plotData[i].push([timestamp, values[i]]);
|
||||
};
|
||||
};
|
||||
|
||||
function sendMsg(ts, values) {
|
||||
connection.send(ts / 1000 + "(" + seq + ") " + values.join(" "));
|
||||
|
||||
seq += 1;
|
||||
}
|
||||
|
||||
/* Control event handlers */
|
||||
function onButtonClick(value) {
|
||||
sendMsg(Date.now(), [ value ]);
|
||||
}
|
||||
|
||||
function onTextChange() {
|
||||
sendMsg(Date.now(), [ 1 ]);
|
||||
}
|
||||
|
||||
/* Helpers */
|
||||
function wsUrl(endpoint) {
|
||||
var l = window.location;
|
||||
|
||||
var protocol = (l.protocol === "https:") ? "wss://" : "ws://";
|
||||
var port = ((l.port != 80) && (l.port != 443)) ? ":" + l.port : "";
|
||||
|
||||
return protocol + l.hostname + port + endpoint;
|
||||
}
|
||||
|
||||
|
1
contrib/websocket/eonerc_logo.png
Symbolic link
1
contrib/websocket/eonerc_logo.png
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../doc/pictures/eonerc_logo.png
|
1
contrib/websocket/flot
Submodule
1
contrib/websocket/flot
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 958e5fd43c6dff4bab3e1fd5cb6109df5c1e8003
|
61
contrib/websocket/index.html
Normal file
61
contrib/websocket/index.html
Normal file
|
@ -0,0 +1,61 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>S2SS / WebSocket Mockup</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<link rel="stylesheet" href="jquery-ui/jquery-ui.theme.css" />
|
||||
<link rel="stylesheet" href="jquery-ui/jquery-ui.structure.css" />
|
||||
|
||||
<script src="jquery/jquery.js"></script>
|
||||
<script src="jquery-ui/jquery-ui.js"></script>
|
||||
<script src="flot/jquery.flot.js"></script>
|
||||
<script src="flot/jquery.flot.time.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrapper">
|
||||
<div id="header">
|
||||
<img class="logo" alt="EONERC" src="eonerc_logo.png" />
|
||||
<h1>S2SS / WebSocket Mockup</h1>
|
||||
</div>
|
||||
<div id="container">
|
||||
<ul class="node-selector"></ul>
|
||||
|
||||
<div class="plot-container">
|
||||
<div id="placeholder" class="plot-placeholder"></div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<p>
|
||||
<span>Trigger:</span>
|
||||
<button onclick="onButtonClick(1)">On</button>
|
||||
<button onclick="onButtonClick(0)">Off</button>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span>Slider:</span>
|
||||
<div id="slider"></div>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span>Value:</span>
|
||||
<input type="text" onchange="onTextChange()" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="connection">
|
||||
<span>Status:</span> <span id="connectionStatus"></span>
|
||||
<button id="pause" onclick="wsDisconnect()">Pause</button>
|
||||
<button id="pause" onclick="wsConnect()">Play</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="footer">
|
||||
<p>Copyright 2015: Institute for Automation of Complex Power Systems,
|
||||
EON Energy Research Center, RWTH Aachen University, Germany</p>
|
||||
<p>Authors: <a href="mailto:mgrigul@eonerc.rwth-aachen.de">Markus Grigul</a>,
|
||||
<a href="mailto:stvogel@eonerc.rwth-aachen.de">Steffen Vogel</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
BIN
contrib/websocket/jquery-ui/images/ui-icons_444444_256x240.png
Normal file
BIN
contrib/websocket/jquery-ui/images/ui-icons_444444_256x240.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.8 KiB |
BIN
contrib/websocket/jquery-ui/images/ui-icons_555555_256x240.png
Normal file
BIN
contrib/websocket/jquery-ui/images/ui-icons_555555_256x240.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.8 KiB |
BIN
contrib/websocket/jquery-ui/images/ui-icons_777620_256x240.png
Normal file
BIN
contrib/websocket/jquery-ui/images/ui-icons_777620_256x240.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
BIN
contrib/websocket/jquery-ui/images/ui-icons_777777_256x240.png
Normal file
BIN
contrib/websocket/jquery-ui/images/ui-icons_777777_256x240.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.8 KiB |
BIN
contrib/websocket/jquery-ui/images/ui-icons_cc0000_256x240.png
Normal file
BIN
contrib/websocket/jquery-ui/images/ui-icons_cc0000_256x240.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
BIN
contrib/websocket/jquery-ui/images/ui-icons_ffffff_256x240.png
Normal file
BIN
contrib/websocket/jquery-ui/images/ui-icons_ffffff_256x240.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.2 KiB |
1225
contrib/websocket/jquery-ui/jquery-ui.css
vendored
Normal file
1225
contrib/websocket/jquery-ui/jquery-ui.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
16617
contrib/websocket/jquery-ui/jquery-ui.js
vendored
Normal file
16617
contrib/websocket/jquery-ui/jquery-ui.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
833
contrib/websocket/jquery-ui/jquery-ui.structure.css
vendored
Normal file
833
contrib/websocket/jquery-ui/jquery-ui.structure.css
vendored
Normal file
|
@ -0,0 +1,833 @@
|
|||
/*!
|
||||
* jQuery UI CSS Framework 1.11.4
|
||||
* http://jqueryui.com
|
||||
*
|
||||
* Copyright jQuery Foundation and other contributors
|
||||
* Released under the MIT license.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://api.jqueryui.com/category/theming/
|
||||
*/
|
||||
|
||||
/* Layout helpers
|
||||
----------------------------------*/
|
||||
.ui-helper-hidden {
|
||||
display: none;
|
||||
}
|
||||
.ui-helper-hidden-accessible {
|
||||
border: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
.ui-helper-reset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
line-height: 1.3;
|
||||
text-decoration: none;
|
||||
font-size: 100%;
|
||||
list-style: none;
|
||||
}
|
||||
.ui-helper-clearfix:before,
|
||||
.ui-helper-clearfix:after {
|
||||
content: "";
|
||||
display: table;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.ui-helper-clearfix:after {
|
||||
clear: both;
|
||||
}
|
||||
.ui-helper-clearfix {
|
||||
min-height: 0; /* support: IE7 */
|
||||
}
|
||||
.ui-helper-zfix {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
filter:Alpha(Opacity=0); /* support: IE8 */
|
||||
}
|
||||
|
||||
.ui-front {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
|
||||
/* Interaction Cues
|
||||
----------------------------------*/
|
||||
.ui-state-disabled {
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
|
||||
/* Icons
|
||||
----------------------------------*/
|
||||
|
||||
/* states and images */
|
||||
.ui-icon {
|
||||
display: block;
|
||||
text-indent: -99999px;
|
||||
overflow: hidden;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
|
||||
/* Misc visuals
|
||||
----------------------------------*/
|
||||
|
||||
/* Overlays */
|
||||
.ui-widget-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.ui-draggable-handle {
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
}
|
||||
.ui-resizable {
|
||||
position: relative;
|
||||
}
|
||||
.ui-resizable-handle {
|
||||
position: absolute;
|
||||
font-size: 0.1px;
|
||||
display: block;
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
}
|
||||
.ui-resizable-disabled .ui-resizable-handle,
|
||||
.ui-resizable-autohide .ui-resizable-handle {
|
||||
display: none;
|
||||
}
|
||||
.ui-resizable-n {
|
||||
cursor: n-resize;
|
||||
height: 7px;
|
||||
width: 100%;
|
||||
top: -5px;
|
||||
left: 0;
|
||||
}
|
||||
.ui-resizable-s {
|
||||
cursor: s-resize;
|
||||
height: 7px;
|
||||
width: 100%;
|
||||
bottom: -5px;
|
||||
left: 0;
|
||||
}
|
||||
.ui-resizable-e {
|
||||
cursor: e-resize;
|
||||
width: 7px;
|
||||
right: -5px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
}
|
||||
.ui-resizable-w {
|
||||
cursor: w-resize;
|
||||
width: 7px;
|
||||
left: -5px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
}
|
||||
.ui-resizable-se {
|
||||
cursor: se-resize;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
right: 1px;
|
||||
bottom: 1px;
|
||||
}
|
||||
.ui-resizable-sw {
|
||||
cursor: sw-resize;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
left: -5px;
|
||||
bottom: -5px;
|
||||
}
|
||||
.ui-resizable-nw {
|
||||
cursor: nw-resize;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
left: -5px;
|
||||
top: -5px;
|
||||
}
|
||||
.ui-resizable-ne {
|
||||
cursor: ne-resize;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
right: -5px;
|
||||
top: -5px;
|
||||
}
|
||||
.ui-selectable {
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
}
|
||||
.ui-selectable-helper {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
border: 1px dotted black;
|
||||
}
|
||||
.ui-sortable-handle {
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
}
|
||||
.ui-accordion .ui-accordion-header {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
margin: 2px 0 0 0;
|
||||
padding: .5em .5em .5em .7em;
|
||||
min-height: 0; /* support: IE7 */
|
||||
font-size: 100%;
|
||||
}
|
||||
.ui-accordion .ui-accordion-icons {
|
||||
padding-left: 2.2em;
|
||||
}
|
||||
.ui-accordion .ui-accordion-icons .ui-accordion-icons {
|
||||
padding-left: 2.2em;
|
||||
}
|
||||
.ui-accordion .ui-accordion-header .ui-accordion-header-icon {
|
||||
position: absolute;
|
||||
left: .5em;
|
||||
top: 50%;
|
||||
margin-top: -8px;
|
||||
}
|
||||
.ui-accordion .ui-accordion-content {
|
||||
padding: 1em 2.2em;
|
||||
border-top: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
.ui-autocomplete {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
cursor: default;
|
||||
}
|
||||
.ui-button {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
line-height: normal;
|
||||
margin-right: .1em;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
overflow: visible; /* removes extra width in IE */
|
||||
}
|
||||
.ui-button,
|
||||
.ui-button:link,
|
||||
.ui-button:visited,
|
||||
.ui-button:hover,
|
||||
.ui-button:active {
|
||||
text-decoration: none;
|
||||
}
|
||||
/* to make room for the icon, a width needs to be set here */
|
||||
.ui-button-icon-only {
|
||||
width: 2.2em;
|
||||
}
|
||||
/* button elements seem to need a little more width */
|
||||
button.ui-button-icon-only {
|
||||
width: 2.4em;
|
||||
}
|
||||
.ui-button-icons-only {
|
||||
width: 3.4em;
|
||||
}
|
||||
button.ui-button-icons-only {
|
||||
width: 3.7em;
|
||||
}
|
||||
|
||||
/* button text element */
|
||||
.ui-button .ui-button-text {
|
||||
display: block;
|
||||
line-height: normal;
|
||||
}
|
||||
.ui-button-text-only .ui-button-text {
|
||||
padding: .4em 1em;
|
||||
}
|
||||
.ui-button-icon-only .ui-button-text,
|
||||
.ui-button-icons-only .ui-button-text {
|
||||
padding: .4em;
|
||||
text-indent: -9999999px;
|
||||
}
|
||||
.ui-button-text-icon-primary .ui-button-text,
|
||||
.ui-button-text-icons .ui-button-text {
|
||||
padding: .4em 1em .4em 2.1em;
|
||||
}
|
||||
.ui-button-text-icon-secondary .ui-button-text,
|
||||
.ui-button-text-icons .ui-button-text {
|
||||
padding: .4em 2.1em .4em 1em;
|
||||
}
|
||||
.ui-button-text-icons .ui-button-text {
|
||||
padding-left: 2.1em;
|
||||
padding-right: 2.1em;
|
||||
}
|
||||
/* no icon support for input elements, provide padding by default */
|
||||
input.ui-button {
|
||||
padding: .4em 1em;
|
||||
}
|
||||
|
||||
/* button icon element(s) */
|
||||
.ui-button-icon-only .ui-icon,
|
||||
.ui-button-text-icon-primary .ui-icon,
|
||||
.ui-button-text-icon-secondary .ui-icon,
|
||||
.ui-button-text-icons .ui-icon,
|
||||
.ui-button-icons-only .ui-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -8px;
|
||||
}
|
||||
.ui-button-icon-only .ui-icon {
|
||||
left: 50%;
|
||||
margin-left: -8px;
|
||||
}
|
||||
.ui-button-text-icon-primary .ui-button-icon-primary,
|
||||
.ui-button-text-icons .ui-button-icon-primary,
|
||||
.ui-button-icons-only .ui-button-icon-primary {
|
||||
left: .5em;
|
||||
}
|
||||
.ui-button-text-icon-secondary .ui-button-icon-secondary,
|
||||
.ui-button-text-icons .ui-button-icon-secondary,
|
||||
.ui-button-icons-only .ui-button-icon-secondary {
|
||||
right: .5em;
|
||||
}
|
||||
|
||||
/* button sets */
|
||||
.ui-buttonset {
|
||||
margin-right: 7px;
|
||||
}
|
||||
.ui-buttonset .ui-button {
|
||||
margin-left: 0;
|
||||
margin-right: -.3em;
|
||||
}
|
||||
|
||||
/* workarounds */
|
||||
/* reset extra padding in Firefox, see h5bp.com/l */
|
||||
input.ui-button::-moz-focus-inner,
|
||||
button.ui-button::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.ui-datepicker {
|
||||
width: 17em;
|
||||
padding: .2em .2em 0;
|
||||
display: none;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-header {
|
||||
position: relative;
|
||||
padding: .2em 0;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-prev,
|
||||
.ui-datepicker .ui-datepicker-next {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
width: 1.8em;
|
||||
height: 1.8em;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-prev-hover,
|
||||
.ui-datepicker .ui-datepicker-next-hover {
|
||||
top: 1px;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-prev {
|
||||
left: 2px;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-next {
|
||||
right: 2px;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-prev-hover {
|
||||
left: 1px;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-next-hover {
|
||||
right: 1px;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-prev span,
|
||||
.ui-datepicker .ui-datepicker-next span {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
margin-left: -8px;
|
||||
top: 50%;
|
||||
margin-top: -8px;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-title {
|
||||
margin: 0 2.3em;
|
||||
line-height: 1.8em;
|
||||
text-align: center;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-title select {
|
||||
font-size: 1em;
|
||||
margin: 1px 0;
|
||||
}
|
||||
.ui-datepicker select.ui-datepicker-month,
|
||||
.ui-datepicker select.ui-datepicker-year {
|
||||
width: 45%;
|
||||
}
|
||||
.ui-datepicker table {
|
||||
width: 100%;
|
||||
font-size: .9em;
|
||||
border-collapse: collapse;
|
||||
margin: 0 0 .4em;
|
||||
}
|
||||
.ui-datepicker th {
|
||||
padding: .7em .3em;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
border: 0;
|
||||
}
|
||||
.ui-datepicker td {
|
||||
border: 0;
|
||||
padding: 1px;
|
||||
}
|
||||
.ui-datepicker td span,
|
||||
.ui-datepicker td a {
|
||||
display: block;
|
||||
padding: .2em;
|
||||
text-align: right;
|
||||
text-decoration: none;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-buttonpane {
|
||||
background-image: none;
|
||||
margin: .7em 0 0 0;
|
||||
padding: 0 .2em;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-buttonpane button {
|
||||
float: right;
|
||||
margin: .5em .2em .4em;
|
||||
cursor: pointer;
|
||||
padding: .2em .6em .3em .6em;
|
||||
width: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current {
|
||||
float: left;
|
||||
}
|
||||
|
||||
/* with multiple calendars */
|
||||
.ui-datepicker.ui-datepicker-multi {
|
||||
width: auto;
|
||||
}
|
||||
.ui-datepicker-multi .ui-datepicker-group {
|
||||
float: left;
|
||||
}
|
||||
.ui-datepicker-multi .ui-datepicker-group table {
|
||||
width: 95%;
|
||||
margin: 0 auto .4em;
|
||||
}
|
||||
.ui-datepicker-multi-2 .ui-datepicker-group {
|
||||
width: 50%;
|
||||
}
|
||||
.ui-datepicker-multi-3 .ui-datepicker-group {
|
||||
width: 33.3%;
|
||||
}
|
||||
.ui-datepicker-multi-4 .ui-datepicker-group {
|
||||
width: 25%;
|
||||
}
|
||||
.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,
|
||||
.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header {
|
||||
border-left-width: 0;
|
||||
}
|
||||
.ui-datepicker-multi .ui-datepicker-buttonpane {
|
||||
clear: left;
|
||||
}
|
||||
.ui-datepicker-row-break {
|
||||
clear: both;
|
||||
width: 100%;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
/* RTL support */
|
||||
.ui-datepicker-rtl {
|
||||
direction: rtl;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-prev {
|
||||
right: 2px;
|
||||
left: auto;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-next {
|
||||
left: 2px;
|
||||
right: auto;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-prev:hover {
|
||||
right: 1px;
|
||||
left: auto;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-next:hover {
|
||||
left: 1px;
|
||||
right: auto;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-buttonpane {
|
||||
clear: right;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-buttonpane button {
|
||||
float: left;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,
|
||||
.ui-datepicker-rtl .ui-datepicker-group {
|
||||
float: right;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,
|
||||
.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header {
|
||||
border-right-width: 0;
|
||||
border-left-width: 1px;
|
||||
}
|
||||
.ui-dialog {
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: .2em;
|
||||
outline: 0;
|
||||
}
|
||||
.ui-dialog .ui-dialog-titlebar {
|
||||
padding: .4em 1em;
|
||||
position: relative;
|
||||
}
|
||||
.ui-dialog .ui-dialog-title {
|
||||
float: left;
|
||||
margin: .1em 0;
|
||||
white-space: nowrap;
|
||||
width: 90%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.ui-dialog .ui-dialog-titlebar-close {
|
||||
position: absolute;
|
||||
right: .3em;
|
||||
top: 50%;
|
||||
width: 20px;
|
||||
margin: -10px 0 0 0;
|
||||
padding: 1px;
|
||||
height: 20px;
|
||||
}
|
||||
.ui-dialog .ui-dialog-content {
|
||||
position: relative;
|
||||
border: 0;
|
||||
padding: .5em 1em;
|
||||
background: none;
|
||||
overflow: auto;
|
||||
}
|
||||
.ui-dialog .ui-dialog-buttonpane {
|
||||
text-align: left;
|
||||
border-width: 1px 0 0 0;
|
||||
background-image: none;
|
||||
margin-top: .5em;
|
||||
padding: .3em 1em .5em .4em;
|
||||
}
|
||||
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
|
||||
float: right;
|
||||
}
|
||||
.ui-dialog .ui-dialog-buttonpane button {
|
||||
margin: .5em .4em .5em 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.ui-dialog .ui-resizable-se {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
right: -5px;
|
||||
bottom: -5px;
|
||||
background-position: 16px 16px;
|
||||
}
|
||||
.ui-draggable .ui-dialog-titlebar {
|
||||
cursor: move;
|
||||
}
|
||||
.ui-menu {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: block;
|
||||
outline: none;
|
||||
}
|
||||
.ui-menu .ui-menu {
|
||||
position: absolute;
|
||||
}
|
||||
.ui-menu .ui-menu-item {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 3px 1em 3px .4em;
|
||||
cursor: pointer;
|
||||
min-height: 0; /* support: IE7 */
|
||||
/* support: IE10, see #8844 */
|
||||
list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
|
||||
}
|
||||
.ui-menu .ui-menu-divider {
|
||||
margin: 5px 0;
|
||||
height: 0;
|
||||
font-size: 0;
|
||||
line-height: 0;
|
||||
border-width: 1px 0 0 0;
|
||||
}
|
||||
.ui-menu .ui-state-focus,
|
||||
.ui-menu .ui-state-active {
|
||||
margin: -1px;
|
||||
}
|
||||
|
||||
/* icon support */
|
||||
.ui-menu-icons {
|
||||
position: relative;
|
||||
}
|
||||
.ui-menu-icons .ui-menu-item {
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
/* left-aligned */
|
||||
.ui-menu .ui-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: .2em;
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
/* right-aligned */
|
||||
.ui-menu .ui-menu-icon {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
.ui-progressbar {
|
||||
height: 2em;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
}
|
||||
.ui-progressbar .ui-progressbar-value {
|
||||
margin: -1px;
|
||||
height: 100%;
|
||||
}
|
||||
.ui-progressbar .ui-progressbar-overlay {
|
||||
background: url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==");
|
||||
height: 100%;
|
||||
filter: alpha(opacity=25); /* support: IE8 */
|
||||
opacity: 0.25;
|
||||
}
|
||||
.ui-progressbar-indeterminate .ui-progressbar-value {
|
||||
background-image: none;
|
||||
}
|
||||
.ui-selectmenu-menu {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: none;
|
||||
}
|
||||
.ui-selectmenu-menu .ui-menu {
|
||||
overflow: auto;
|
||||
/* Support: IE7 */
|
||||
overflow-x: hidden;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup {
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
line-height: 1.5;
|
||||
padding: 2px 0.4em;
|
||||
margin: 0.5em 0 0 0;
|
||||
height: auto;
|
||||
border: 0;
|
||||
}
|
||||
.ui-selectmenu-open {
|
||||
display: block;
|
||||
}
|
||||
.ui-selectmenu-button {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.ui-selectmenu-button span.ui-icon {
|
||||
right: 0.5em;
|
||||
left: auto;
|
||||
margin-top: -8px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
}
|
||||
.ui-selectmenu-button span.ui-selectmenu-text {
|
||||
text-align: left;
|
||||
padding: 0.4em 2.1em 0.4em 1em;
|
||||
display: block;
|
||||
line-height: 1.4;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ui-slider {
|
||||
position: relative;
|
||||
text-align: left;
|
||||
}
|
||||
.ui-slider .ui-slider-handle {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
cursor: default;
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
}
|
||||
.ui-slider .ui-slider-range {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
font-size: .7em;
|
||||
display: block;
|
||||
border: 0;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
/* support: IE8 - See #6727 */
|
||||
.ui-slider.ui-state-disabled .ui-slider-handle,
|
||||
.ui-slider.ui-state-disabled .ui-slider-range {
|
||||
filter: inherit;
|
||||
}
|
||||
|
||||
.ui-slider-horizontal {
|
||||
height: .8em;
|
||||
}
|
||||
.ui-slider-horizontal .ui-slider-handle {
|
||||
top: -.3em;
|
||||
margin-left: -.6em;
|
||||
}
|
||||
.ui-slider-horizontal .ui-slider-range {
|
||||
top: 0;
|
||||
height: 100%;
|
||||
}
|
||||
.ui-slider-horizontal .ui-slider-range-min {
|
||||
left: 0;
|
||||
}
|
||||
.ui-slider-horizontal .ui-slider-range-max {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.ui-slider-vertical {
|
||||
width: .8em;
|
||||
height: 100px;
|
||||
}
|
||||
.ui-slider-vertical .ui-slider-handle {
|
||||
left: -.3em;
|
||||
margin-left: 0;
|
||||
margin-bottom: -.6em;
|
||||
}
|
||||
.ui-slider-vertical .ui-slider-range {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.ui-slider-vertical .ui-slider-range-min {
|
||||
bottom: 0;
|
||||
}
|
||||
.ui-slider-vertical .ui-slider-range-max {
|
||||
top: 0;
|
||||
}
|
||||
.ui-spinner {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.ui-spinner-input {
|
||||
border: none;
|
||||
background: none;
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
margin: .2em 0;
|
||||
vertical-align: middle;
|
||||
margin-left: .4em;
|
||||
margin-right: 22px;
|
||||
}
|
||||
.ui-spinner-button {
|
||||
width: 16px;
|
||||
height: 50%;
|
||||
font-size: .5em;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
cursor: default;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
right: 0;
|
||||
}
|
||||
/* more specificity required here to override default borders */
|
||||
.ui-spinner a.ui-spinner-button {
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
border-right: none;
|
||||
}
|
||||
/* vertically center icon */
|
||||
.ui-spinner .ui-icon {
|
||||
position: absolute;
|
||||
margin-top: -8px;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
}
|
||||
.ui-spinner-up {
|
||||
top: 0;
|
||||
}
|
||||
.ui-spinner-down {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
/* TR overrides */
|
||||
.ui-spinner .ui-icon-triangle-1-s {
|
||||
/* need to fix icons sprite */
|
||||
background-position: -65px -16px;
|
||||
}
|
||||
.ui-tabs {
|
||||
position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
|
||||
padding: .2em;
|
||||
}
|
||||
.ui-tabs .ui-tabs-nav {
|
||||
margin: 0;
|
||||
padding: .2em .2em 0;
|
||||
}
|
||||
.ui-tabs .ui-tabs-nav li {
|
||||
list-style: none;
|
||||
float: left;
|
||||
position: relative;
|
||||
top: 0;
|
||||
margin: 1px .2em 0 0;
|
||||
border-bottom-width: 0;
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ui-tabs .ui-tabs-nav .ui-tabs-anchor {
|
||||
float: left;
|
||||
padding: .5em 1em;
|
||||
text-decoration: none;
|
||||
}
|
||||
.ui-tabs .ui-tabs-nav li.ui-tabs-active {
|
||||
margin-bottom: -1px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,
|
||||
.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,
|
||||
.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor {
|
||||
cursor: text;
|
||||
}
|
||||
.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor {
|
||||
cursor: pointer;
|
||||
}
|
||||
.ui-tabs .ui-tabs-panel {
|
||||
display: block;
|
||||
border-width: 0;
|
||||
padding: 1em 1.4em;
|
||||
background: none;
|
||||
}
|
||||
.ui-tooltip {
|
||||
padding: 8px;
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
max-width: 300px;
|
||||
-webkit-box-shadow: 0 0 5px #aaa;
|
||||
box-shadow: 0 0 5px #aaa;
|
||||
}
|
||||
body .ui-tooltip {
|
||||
border-width: 2px;
|
||||
}
|
410
contrib/websocket/jquery-ui/jquery-ui.theme.css
vendored
Normal file
410
contrib/websocket/jquery-ui/jquery-ui.theme.css
vendored
Normal file
|
@ -0,0 +1,410 @@
|
|||
/*!
|
||||
* jQuery UI CSS Framework 1.11.4
|
||||
* http://jqueryui.com
|
||||
*
|
||||
* Copyright jQuery Foundation and other contributors
|
||||
* Released under the MIT license.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://api.jqueryui.com/category/theming/
|
||||
*
|
||||
* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Arial%2CHelvetica%2Csans-serif&fsDefault=1em&fwDefault=normal&cornerRadius=3px&bgColorHeader=e9e9e9&bgTextureHeader=flat&borderColorHeader=dddddd&fcHeader=333333&iconColorHeader=444444&bgColorContent=ffffff&bgTextureContent=flat&borderColorContent=dddddd&fcContent=333333&iconColorContent=444444&bgColorDefault=f6f6f6&bgTextureDefault=flat&borderColorDefault=c5c5c5&fcDefault=454545&iconColorDefault=777777&bgColorHover=ededed&bgTextureHover=flat&borderColorHover=cccccc&fcHover=2b2b2b&iconColorHover=555555&bgColorActive=007fff&bgTextureActive=flat&borderColorActive=003eff&fcActive=ffffff&iconColorActive=ffffff&bgColorHighlight=fffa90&bgTextureHighlight=flat&borderColorHighlight=dad55e&fcHighlight=777620&iconColorHighlight=777620&bgColorError=fddfdf&bgTextureError=flat&borderColorError=f1a899&fcError=5f3f3f&iconColorError=cc0000&bgColorOverlay=aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=666666&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=5px&offsetTopShadow=0px&offsetLeftShadow=0px&cornerRadiusShadow=8px
|
||||
*/
|
||||
|
||||
|
||||
/* Component containers
|
||||
----------------------------------*/
|
||||
.ui-widget {
|
||||
font-family: Arial,Helvetica,sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
.ui-widget .ui-widget {
|
||||
font-size: 1em;
|
||||
}
|
||||
.ui-widget input,
|
||||
.ui-widget select,
|
||||
.ui-widget textarea,
|
||||
.ui-widget button {
|
||||
font-family: Arial,Helvetica,sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
.ui-widget-content {
|
||||
border: 1px solid #dddddd;
|
||||
background: #ffffff;
|
||||
color: #333333;
|
||||
}
|
||||
.ui-widget-content a {
|
||||
color: #333333;
|
||||
}
|
||||
.ui-widget-header {
|
||||
border: 1px solid #dddddd;
|
||||
background: #e9e9e9;
|
||||
color: #333333;
|
||||
font-weight: bold;
|
||||
}
|
||||
.ui-widget-header a {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
/* Interaction states
|
||||
----------------------------------*/
|
||||
.ui-state-default,
|
||||
.ui-widget-content .ui-state-default,
|
||||
.ui-widget-header .ui-state-default {
|
||||
border: 1px solid #c5c5c5;
|
||||
background: #f6f6f6;
|
||||
font-weight: normal;
|
||||
color: #454545;
|
||||
}
|
||||
.ui-state-default a,
|
||||
.ui-state-default a:link,
|
||||
.ui-state-default a:visited {
|
||||
color: #454545;
|
||||
text-decoration: none;
|
||||
}
|
||||
.ui-state-hover,
|
||||
.ui-widget-content .ui-state-hover,
|
||||
.ui-widget-header .ui-state-hover,
|
||||
.ui-state-focus,
|
||||
.ui-widget-content .ui-state-focus,
|
||||
.ui-widget-header .ui-state-focus {
|
||||
border: 1px solid #cccccc;
|
||||
background: #ededed;
|
||||
font-weight: normal;
|
||||
color: #2b2b2b;
|
||||
}
|
||||
.ui-state-hover a,
|
||||
.ui-state-hover a:hover,
|
||||
.ui-state-hover a:link,
|
||||
.ui-state-hover a:visited,
|
||||
.ui-state-focus a,
|
||||
.ui-state-focus a:hover,
|
||||
.ui-state-focus a:link,
|
||||
.ui-state-focus a:visited {
|
||||
color: #2b2b2b;
|
||||
text-decoration: none;
|
||||
}
|
||||
.ui-state-active,
|
||||
.ui-widget-content .ui-state-active,
|
||||
.ui-widget-header .ui-state-active {
|
||||
border: 1px solid #003eff;
|
||||
background: #007fff;
|
||||
font-weight: normal;
|
||||
color: #ffffff;
|
||||
}
|
||||
.ui-state-active a,
|
||||
.ui-state-active a:link,
|
||||
.ui-state-active a:visited {
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Interaction Cues
|
||||
----------------------------------*/
|
||||
.ui-state-highlight,
|
||||
.ui-widget-content .ui-state-highlight,
|
||||
.ui-widget-header .ui-state-highlight {
|
||||
border: 1px solid #dad55e;
|
||||
background: #fffa90;
|
||||
color: #777620;
|
||||
}
|
||||
.ui-state-highlight a,
|
||||
.ui-widget-content .ui-state-highlight a,
|
||||
.ui-widget-header .ui-state-highlight a {
|
||||
color: #777620;
|
||||
}
|
||||
.ui-state-error,
|
||||
.ui-widget-content .ui-state-error,
|
||||
.ui-widget-header .ui-state-error {
|
||||
border: 1px solid #f1a899;
|
||||
background: #fddfdf;
|
||||
color: #5f3f3f;
|
||||
}
|
||||
.ui-state-error a,
|
||||
.ui-widget-content .ui-state-error a,
|
||||
.ui-widget-header .ui-state-error a {
|
||||
color: #5f3f3f;
|
||||
}
|
||||
.ui-state-error-text,
|
||||
.ui-widget-content .ui-state-error-text,
|
||||
.ui-widget-header .ui-state-error-text {
|
||||
color: #5f3f3f;
|
||||
}
|
||||
.ui-priority-primary,
|
||||
.ui-widget-content .ui-priority-primary,
|
||||
.ui-widget-header .ui-priority-primary {
|
||||
font-weight: bold;
|
||||
}
|
||||
.ui-priority-secondary,
|
||||
.ui-widget-content .ui-priority-secondary,
|
||||
.ui-widget-header .ui-priority-secondary {
|
||||
opacity: .7;
|
||||
filter:Alpha(Opacity=70); /* support: IE8 */
|
||||
font-weight: normal;
|
||||
}
|
||||
.ui-state-disabled,
|
||||
.ui-widget-content .ui-state-disabled,
|
||||
.ui-widget-header .ui-state-disabled {
|
||||
opacity: .35;
|
||||
filter:Alpha(Opacity=35); /* support: IE8 */
|
||||
background-image: none;
|
||||
}
|
||||
.ui-state-disabled .ui-icon {
|
||||
filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */
|
||||
}
|
||||
|
||||
/* Icons
|
||||
----------------------------------*/
|
||||
|
||||
/* states and images */
|
||||
.ui-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.ui-icon,
|
||||
.ui-widget-content .ui-icon {
|
||||
background-image: url("images/ui-icons_444444_256x240.png");
|
||||
}
|
||||
.ui-widget-header .ui-icon {
|
||||
background-image: url("images/ui-icons_444444_256x240.png");
|
||||
}
|
||||
.ui-state-default .ui-icon {
|
||||
background-image: url("images/ui-icons_777777_256x240.png");
|
||||
}
|
||||
.ui-state-hover .ui-icon,
|
||||
.ui-state-focus .ui-icon {
|
||||
background-image: url("images/ui-icons_555555_256x240.png");
|
||||
}
|
||||
.ui-state-active .ui-icon {
|
||||
background-image: url("images/ui-icons_ffffff_256x240.png");
|
||||
}
|
||||
.ui-state-highlight .ui-icon {
|
||||
background-image: url("images/ui-icons_777620_256x240.png");
|
||||
}
|
||||
.ui-state-error .ui-icon,
|
||||
.ui-state-error-text .ui-icon {
|
||||
background-image: url("images/ui-icons_cc0000_256x240.png");
|
||||
}
|
||||
|
||||
/* positioning */
|
||||
.ui-icon-blank { background-position: 16px 16px; }
|
||||
.ui-icon-carat-1-n { background-position: 0 0; }
|
||||
.ui-icon-carat-1-ne { background-position: -16px 0; }
|
||||
.ui-icon-carat-1-e { background-position: -32px 0; }
|
||||
.ui-icon-carat-1-se { background-position: -48px 0; }
|
||||
.ui-icon-carat-1-s { background-position: -64px 0; }
|
||||
.ui-icon-carat-1-sw { background-position: -80px 0; }
|
||||
.ui-icon-carat-1-w { background-position: -96px 0; }
|
||||
.ui-icon-carat-1-nw { background-position: -112px 0; }
|
||||
.ui-icon-carat-2-n-s { background-position: -128px 0; }
|
||||
.ui-icon-carat-2-e-w { background-position: -144px 0; }
|
||||
.ui-icon-triangle-1-n { background-position: 0 -16px; }
|
||||
.ui-icon-triangle-1-ne { background-position: -16px -16px; }
|
||||
.ui-icon-triangle-1-e { background-position: -32px -16px; }
|
||||
.ui-icon-triangle-1-se { background-position: -48px -16px; }
|
||||
.ui-icon-triangle-1-s { background-position: -64px -16px; }
|
||||
.ui-icon-triangle-1-sw { background-position: -80px -16px; }
|
||||
.ui-icon-triangle-1-w { background-position: -96px -16px; }
|
||||
.ui-icon-triangle-1-nw { background-position: -112px -16px; }
|
||||
.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
|
||||
.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
|
||||
.ui-icon-arrow-1-n { background-position: 0 -32px; }
|
||||
.ui-icon-arrow-1-ne { background-position: -16px -32px; }
|
||||
.ui-icon-arrow-1-e { background-position: -32px -32px; }
|
||||
.ui-icon-arrow-1-se { background-position: -48px -32px; }
|
||||
.ui-icon-arrow-1-s { background-position: -64px -32px; }
|
||||
.ui-icon-arrow-1-sw { background-position: -80px -32px; }
|
||||
.ui-icon-arrow-1-w { background-position: -96px -32px; }
|
||||
.ui-icon-arrow-1-nw { background-position: -112px -32px; }
|
||||
.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
|
||||
.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
|
||||
.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
|
||||
.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
|
||||
.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
|
||||
.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
|
||||
.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
|
||||
.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
|
||||
.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
|
||||
.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
|
||||
.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
|
||||
.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
|
||||
.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
|
||||
.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
|
||||
.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
|
||||
.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
|
||||
.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
|
||||
.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
|
||||
.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
|
||||
.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
|
||||
.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
|
||||
.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
|
||||
.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
|
||||
.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
|
||||
.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
|
||||
.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
|
||||
.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
|
||||
.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
|
||||
.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
|
||||
.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
|
||||
.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
|
||||
.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
|
||||
.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
|
||||
.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
|
||||
.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
|
||||
.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
|
||||
.ui-icon-arrow-4 { background-position: 0 -80px; }
|
||||
.ui-icon-arrow-4-diag { background-position: -16px -80px; }
|
||||
.ui-icon-extlink { background-position: -32px -80px; }
|
||||
.ui-icon-newwin { background-position: -48px -80px; }
|
||||
.ui-icon-refresh { background-position: -64px -80px; }
|
||||
.ui-icon-shuffle { background-position: -80px -80px; }
|
||||
.ui-icon-transfer-e-w { background-position: -96px -80px; }
|
||||
.ui-icon-transferthick-e-w { background-position: -112px -80px; }
|
||||
.ui-icon-folder-collapsed { background-position: 0 -96px; }
|
||||
.ui-icon-folder-open { background-position: -16px -96px; }
|
||||
.ui-icon-document { background-position: -32px -96px; }
|
||||
.ui-icon-document-b { background-position: -48px -96px; }
|
||||
.ui-icon-note { background-position: -64px -96px; }
|
||||
.ui-icon-mail-closed { background-position: -80px -96px; }
|
||||
.ui-icon-mail-open { background-position: -96px -96px; }
|
||||
.ui-icon-suitcase { background-position: -112px -96px; }
|
||||
.ui-icon-comment { background-position: -128px -96px; }
|
||||
.ui-icon-person { background-position: -144px -96px; }
|
||||
.ui-icon-print { background-position: -160px -96px; }
|
||||
.ui-icon-trash { background-position: -176px -96px; }
|
||||
.ui-icon-locked { background-position: -192px -96px; }
|
||||
.ui-icon-unlocked { background-position: -208px -96px; }
|
||||
.ui-icon-bookmark { background-position: -224px -96px; }
|
||||
.ui-icon-tag { background-position: -240px -96px; }
|
||||
.ui-icon-home { background-position: 0 -112px; }
|
||||
.ui-icon-flag { background-position: -16px -112px; }
|
||||
.ui-icon-calendar { background-position: -32px -112px; }
|
||||
.ui-icon-cart { background-position: -48px -112px; }
|
||||
.ui-icon-pencil { background-position: -64px -112px; }
|
||||
.ui-icon-clock { background-position: -80px -112px; }
|
||||
.ui-icon-disk { background-position: -96px -112px; }
|
||||
.ui-icon-calculator { background-position: -112px -112px; }
|
||||
.ui-icon-zoomin { background-position: -128px -112px; }
|
||||
.ui-icon-zoomout { background-position: -144px -112px; }
|
||||
.ui-icon-search { background-position: -160px -112px; }
|
||||
.ui-icon-wrench { background-position: -176px -112px; }
|
||||
.ui-icon-gear { background-position: -192px -112px; }
|
||||
.ui-icon-heart { background-position: -208px -112px; }
|
||||
.ui-icon-star { background-position: -224px -112px; }
|
||||
.ui-icon-link { background-position: -240px -112px; }
|
||||
.ui-icon-cancel { background-position: 0 -128px; }
|
||||
.ui-icon-plus { background-position: -16px -128px; }
|
||||
.ui-icon-plusthick { background-position: -32px -128px; }
|
||||
.ui-icon-minus { background-position: -48px -128px; }
|
||||
.ui-icon-minusthick { background-position: -64px -128px; }
|
||||
.ui-icon-close { background-position: -80px -128px; }
|
||||
.ui-icon-closethick { background-position: -96px -128px; }
|
||||
.ui-icon-key { background-position: -112px -128px; }
|
||||
.ui-icon-lightbulb { background-position: -128px -128px; }
|
||||
.ui-icon-scissors { background-position: -144px -128px; }
|
||||
.ui-icon-clipboard { background-position: -160px -128px; }
|
||||
.ui-icon-copy { background-position: -176px -128px; }
|
||||
.ui-icon-contact { background-position: -192px -128px; }
|
||||
.ui-icon-image { background-position: -208px -128px; }
|
||||
.ui-icon-video { background-position: -224px -128px; }
|
||||
.ui-icon-script { background-position: -240px -128px; }
|
||||
.ui-icon-alert { background-position: 0 -144px; }
|
||||
.ui-icon-info { background-position: -16px -144px; }
|
||||
.ui-icon-notice { background-position: -32px -144px; }
|
||||
.ui-icon-help { background-position: -48px -144px; }
|
||||
.ui-icon-check { background-position: -64px -144px; }
|
||||
.ui-icon-bullet { background-position: -80px -144px; }
|
||||
.ui-icon-radio-on { background-position: -96px -144px; }
|
||||
.ui-icon-radio-off { background-position: -112px -144px; }
|
||||
.ui-icon-pin-w { background-position: -128px -144px; }
|
||||
.ui-icon-pin-s { background-position: -144px -144px; }
|
||||
.ui-icon-play { background-position: 0 -160px; }
|
||||
.ui-icon-pause { background-position: -16px -160px; }
|
||||
.ui-icon-seek-next { background-position: -32px -160px; }
|
||||
.ui-icon-seek-prev { background-position: -48px -160px; }
|
||||
.ui-icon-seek-end { background-position: -64px -160px; }
|
||||
.ui-icon-seek-start { background-position: -80px -160px; }
|
||||
/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
|
||||
.ui-icon-seek-first { background-position: -80px -160px; }
|
||||
.ui-icon-stop { background-position: -96px -160px; }
|
||||
.ui-icon-eject { background-position: -112px -160px; }
|
||||
.ui-icon-volume-off { background-position: -128px -160px; }
|
||||
.ui-icon-volume-on { background-position: -144px -160px; }
|
||||
.ui-icon-power { background-position: 0 -176px; }
|
||||
.ui-icon-signal-diag { background-position: -16px -176px; }
|
||||
.ui-icon-signal { background-position: -32px -176px; }
|
||||
.ui-icon-battery-0 { background-position: -48px -176px; }
|
||||
.ui-icon-battery-1 { background-position: -64px -176px; }
|
||||
.ui-icon-battery-2 { background-position: -80px -176px; }
|
||||
.ui-icon-battery-3 { background-position: -96px -176px; }
|
||||
.ui-icon-circle-plus { background-position: 0 -192px; }
|
||||
.ui-icon-circle-minus { background-position: -16px -192px; }
|
||||
.ui-icon-circle-close { background-position: -32px -192px; }
|
||||
.ui-icon-circle-triangle-e { background-position: -48px -192px; }
|
||||
.ui-icon-circle-triangle-s { background-position: -64px -192px; }
|
||||
.ui-icon-circle-triangle-w { background-position: -80px -192px; }
|
||||
.ui-icon-circle-triangle-n { background-position: -96px -192px; }
|
||||
.ui-icon-circle-arrow-e { background-position: -112px -192px; }
|
||||
.ui-icon-circle-arrow-s { background-position: -128px -192px; }
|
||||
.ui-icon-circle-arrow-w { background-position: -144px -192px; }
|
||||
.ui-icon-circle-arrow-n { background-position: -160px -192px; }
|
||||
.ui-icon-circle-zoomin { background-position: -176px -192px; }
|
||||
.ui-icon-circle-zoomout { background-position: -192px -192px; }
|
||||
.ui-icon-circle-check { background-position: -208px -192px; }
|
||||
.ui-icon-circlesmall-plus { background-position: 0 -208px; }
|
||||
.ui-icon-circlesmall-minus { background-position: -16px -208px; }
|
||||
.ui-icon-circlesmall-close { background-position: -32px -208px; }
|
||||
.ui-icon-squaresmall-plus { background-position: -48px -208px; }
|
||||
.ui-icon-squaresmall-minus { background-position: -64px -208px; }
|
||||
.ui-icon-squaresmall-close { background-position: -80px -208px; }
|
||||
.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
|
||||
.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
|
||||
.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
|
||||
.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
|
||||
.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
|
||||
.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
|
||||
|
||||
|
||||
/* Misc visuals
|
||||
----------------------------------*/
|
||||
|
||||
/* Corner radius */
|
||||
.ui-corner-all,
|
||||
.ui-corner-top,
|
||||
.ui-corner-left,
|
||||
.ui-corner-tl {
|
||||
border-top-left-radius: 3px;
|
||||
}
|
||||
.ui-corner-all,
|
||||
.ui-corner-top,
|
||||
.ui-corner-right,
|
||||
.ui-corner-tr {
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
.ui-corner-all,
|
||||
.ui-corner-bottom,
|
||||
.ui-corner-left,
|
||||
.ui-corner-bl {
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
.ui-corner-all,
|
||||
.ui-corner-bottom,
|
||||
.ui-corner-right,
|
||||
.ui-corner-br {
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
|
||||
/* Overlays */
|
||||
.ui-widget-overlay {
|
||||
background: #aaaaaa;
|
||||
opacity: .3;
|
||||
filter: Alpha(Opacity=30); /* support: IE8 */
|
||||
}
|
||||
.ui-widget-shadow {
|
||||
margin: 0px 0 0 0px;
|
||||
padding: 5px;
|
||||
background: #666666;
|
||||
opacity: .3;
|
||||
filter: Alpha(Opacity=30); /* support: IE8 */
|
||||
border-radius: 8px;
|
||||
}
|
9789
contrib/websocket/jquery/jquery.js
vendored
Normal file
9789
contrib/websocket/jquery/jquery.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
50
contrib/websocket/style.css
Normal file
50
contrib/websocket/style.css
Normal file
|
@ -0,0 +1,50 @@
|
|||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
#wrapper {
|
||||
|
||||
}
|
||||
|
||||
#header {
|
||||
padding: 20px;
|
||||
border-bottom: 2px solid gray;
|
||||
}
|
||||
|
||||
#header h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#footer {
|
||||
padding: 1em;
|
||||
border-top: 2px solid gray;
|
||||
}
|
||||
|
||||
#container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#slider {
|
||||
width: 300px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.logo {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.plot-container {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.plot-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
|
@ -18,8 +18,7 @@
|
|||
|
||||
/** Static list initialization */
|
||||
#define LIST_INIT(dtor) { \
|
||||
.start = NULL, \
|
||||
.end = NULL, \
|
||||
.array = NULL, \
|
||||
.length = 0, \
|
||||
.capacity = 0, \
|
||||
.lock = PTHREAD_MUTEX_INITIALIZER, \
|
||||
|
@ -27,12 +26,12 @@
|
|||
}
|
||||
|
||||
#define list_length(list) ((list)->length)
|
||||
#define list_at(list, index) ((list)->length > index ? (list)->start[index] : NULL)
|
||||
#define list_at(list, index) ((list)->length > index ? (list)->array[index] : NULL)
|
||||
|
||||
#define list_first(list) list_at(list, 0)
|
||||
#define list_last(list) list_at(list, (list)->length-1)
|
||||
#define list_foreach(ptr, list) for (int _i = 0, _p; _p = 1, _i < (list)->length; _i++) \
|
||||
for (ptr = (list)->start[_i]; _p--; )
|
||||
for (ptr = (list)->array[_i]; _p--; )
|
||||
|
||||
/** Callback to destroy list elements.
|
||||
*
|
||||
|
@ -44,10 +43,9 @@ typedef void (*dtor_cb_t)(void *);
|
|||
typedef int (*cmp_cb_t)(const void *, const void *);
|
||||
|
||||
struct list {
|
||||
void **start; /**< Array of pointers to list elements */
|
||||
void **end; /**< Array of pointers to list elements */
|
||||
size_t capacity; /**< Size of list::start in elements */
|
||||
size_t length; /**< Number of elements of list::start which are in use */
|
||||
void **array; /**< Array of pointers to list elements */
|
||||
size_t capacity; /**< Size of list::array in elements */
|
||||
size_t length; /**< Number of elements of list::array which are in use */
|
||||
dtor_cb_t destructor; /**< A destructor which gets called for every list elements during list_destroy() */
|
||||
pthread_mutex_t lock; /**< A mutex to allow thread-safe accesses */
|
||||
};
|
||||
|
@ -61,6 +59,9 @@ void list_destroy(struct list *l);
|
|||
/** Append an element to the end of the list */
|
||||
void list_push(struct list *l, void *p);
|
||||
|
||||
/** Remove all occurences of a list item */
|
||||
void list_remove(struct list *l, void *p);
|
||||
|
||||
/** Search the list for an element whose first element is a character array which matches name.
|
||||
*
|
||||
* @see Only possible because of §1424 of http://c0x.coding-guidelines.com/6.7.2.1.html
|
||||
|
|
|
@ -55,12 +55,11 @@ int msg_verify(struct msg *m);
|
|||
* @param m A pointer to the message.
|
||||
* @param flags See msg_flags.
|
||||
* @param offset A optional offset in seconds. Only used if flags contains MSG_PRINT_OFFSET.
|
||||
* @retval 0 Success. Everything went well.
|
||||
* @retval <0 Error. Something went wrong.
|
||||
* @return Number of bytes written to buf.
|
||||
*/
|
||||
int msg_fprint(FILE *f, struct msg *m, int flags, double offset);
|
||||
int msg_print(char *buf, size_t len, struct msg *m, int flags, double offset);
|
||||
|
||||
/** Read a message from a file stream.
|
||||
/** Read a message from a character buffer.
|
||||
*
|
||||
* @param f The file handle from fopen() or stdin.
|
||||
* @param m A pointer to the message.
|
||||
|
@ -69,6 +68,18 @@ int msg_fprint(FILE *f, struct msg *m, int flags, double offset);
|
|||
* @retval 0 Success. Everything went well.
|
||||
* @retval <0 Error. Something went wrong.
|
||||
*/
|
||||
int msg_scan(const char *line, struct msg *m, int *fl, double *off);
|
||||
|
||||
/** Print a raw message in human readable form to a file stream.
|
||||
*
|
||||
* @see msg_print()
|
||||
*/
|
||||
int msg_fprint(FILE *f, struct msg *m, int flags, double offset);
|
||||
|
||||
/** Read a message from a file stream.
|
||||
*
|
||||
* @see msg_scan()
|
||||
*/
|
||||
int msg_fscan(FILE *f, struct msg *m, int *flags, double *offset);
|
||||
|
||||
#endif /* _MSG_H_ */
|
||||
|
|
|
@ -127,11 +127,13 @@ int version_compare(struct version *a, struct version *b);
|
|||
int version_parse(const char *s, struct version *v);
|
||||
|
||||
/** Check assertion and exit if failed. */
|
||||
#define assert(exp) do { \
|
||||
#ifndef assert
|
||||
#define assert(exp) do { \
|
||||
if (!EXPECT(exp, 0)) \
|
||||
error("Assertion failed: '%s' in %s(), %s:%d", \
|
||||
XSTR(exp), __FUNCTION__, __BASE_FILE__, __LINE__); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#endif /* _UTILS_H_ */
|
||||
|
||||
|
|
56
include/websocket.h
Normal file
56
include/websocket.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
/** Node type: WebSockets
|
||||
*
|
||||
* This file implements the websocket type for nodes.
|
||||
* It's based on the libwebsockets library.
|
||||
*
|
||||
* @file
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2014-2015, Institute for Automation of Complex Power Systems, EONERC
|
||||
* This file is part of S2SS. All Rights Reserved. Proprietary and confidential.
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
*/
|
||||
/**
|
||||
* @addtogroup websockets WebSockets node type
|
||||
* @ingroup node
|
||||
* @{
|
||||
*********************************************************************************/
|
||||
|
||||
|
||||
#ifndef _WEBSOCKET_H_
|
||||
#define _WEBSOCKET_H_
|
||||
|
||||
#include "node.h"
|
||||
|
||||
/* Forward declarations */
|
||||
struct msg;
|
||||
struct libwebsocket_context;
|
||||
|
||||
struct websocket {
|
||||
struct {
|
||||
pthread_cond_t cond;
|
||||
pthread_mutex_t mutex;
|
||||
struct msg *m;
|
||||
} read, write;
|
||||
|
||||
struct list connections; /**< List of struct libwebsockets sockets */
|
||||
};
|
||||
|
||||
/** @see node_vtable::init */
|
||||
int websocket_init(int argc, char * argv[], struct settings *set);
|
||||
|
||||
/** @see node_vtable::deinit */
|
||||
int websocket_deinit();
|
||||
|
||||
/** @see node_vtable::open */
|
||||
int websocket_open(struct node *n);
|
||||
|
||||
/** @see node_vtable::close */
|
||||
int websocket_close(struct node *n);
|
||||
|
||||
/** @see node_vtable::read */
|
||||
int websocket_read(struct node *n, struct msg *pool, int poolsize, int first, int cnt);
|
||||
|
||||
/** @see node_vtable::write */
|
||||
int websocket_write(struct node *n, struct msg *pool, int poolsize, int first, int cnt);
|
||||
|
||||
#endif /* _WEBSOCKET_H_ */
|
51
lib/list.c
51
lib/list.c
|
@ -21,8 +21,7 @@ void list_init(struct list *l, dtor_cb_t dtor)
|
|||
l->length = 0;
|
||||
l->capacity = 0;
|
||||
|
||||
l->start = NULL;
|
||||
l->end = NULL;
|
||||
l->array = NULL;
|
||||
}
|
||||
|
||||
void list_destroy(struct list *l)
|
||||
|
@ -34,10 +33,9 @@ void list_destroy(struct list *l)
|
|||
l->destructor(p);
|
||||
}
|
||||
|
||||
free(l->start);
|
||||
free(l->array);
|
||||
|
||||
l->start =
|
||||
l->end = NULL;
|
||||
l->array = NULL;
|
||||
|
||||
l->length =
|
||||
l->capacity = 0;
|
||||
|
@ -51,36 +49,51 @@ void list_push(struct list *l, void *p)
|
|||
pthread_mutex_lock(&l->lock);
|
||||
|
||||
/* Resize array if out of capacity */
|
||||
if (l->end == l->start + l->capacity) {
|
||||
if (l->length >= l->capacity) {
|
||||
l->capacity += LIST_CHUNKSIZE;
|
||||
l->start = realloc(l->start, l->capacity * sizeof(void *));
|
||||
l->array = realloc(l->array, l->capacity * sizeof(void *));
|
||||
}
|
||||
|
||||
l->start[l->length] = p;
|
||||
l->array[l->length] = p;
|
||||
l->length++;
|
||||
l->end = &l->start[l->length];
|
||||
|
||||
pthread_mutex_unlock(&l->lock);
|
||||
}
|
||||
|
||||
void * list_lookup(struct list *l, const char *needle)
|
||||
void list_remove(struct list *l, void *p)
|
||||
{
|
||||
int cmp(const void *elm, const void *needle) {
|
||||
char *name = * (char **) elm; /* Ugly hack: the lookup key (name) must be the first element in the list element struct */
|
||||
|
||||
return strcmp(name, needle);
|
||||
int removed = 0;
|
||||
|
||||
pthread_mutex_lock(&l->lock);
|
||||
|
||||
for (int i = 0; i < l->length; i++) {
|
||||
if (l->array[i] == p)
|
||||
removed++;
|
||||
else
|
||||
l->array[i - removed] = l->array[i];
|
||||
}
|
||||
|
||||
l->length -= removed;
|
||||
|
||||
pthread_mutex_unlock(&l->lock);
|
||||
}
|
||||
|
||||
void * list_lookup(struct list *l, const char *name)
|
||||
{
|
||||
int cmp_helper(const void *a, const void *b) {
|
||||
return strcmp(* (char **) a, b);
|
||||
}
|
||||
|
||||
return list_search(l, cmp, (void *) needle);
|
||||
return list_search(l, cmp_helper, (void *) name);
|
||||
}
|
||||
|
||||
int list_contains(struct list *l, void *p)
|
||||
{
|
||||
int cmp(const void *a, const void *b) {
|
||||
int cmp_helper(const void *a, const void *b) {
|
||||
return a == b;
|
||||
}
|
||||
|
||||
return list_count(l, cmp, p);
|
||||
return list_count(l, cmp_helper, p);
|
||||
}
|
||||
|
||||
int list_count(struct list *l, cmp_cb_t cmp, void *ctx)
|
||||
|
@ -123,6 +136,8 @@ void list_sort(struct list *l, cmp_cb_t cmp)
|
|||
}
|
||||
|
||||
pthread_mutex_lock(&l->lock);
|
||||
qsort(l->start, l->length, sizeof(void *), cmp_helper);
|
||||
|
||||
qsort(l->array, l->length, sizeof(void *), cmp_helper);
|
||||
|
||||
pthread_mutex_unlock(&l->lock);
|
||||
}
|
58
lib/msg.c
58
lib/msg.c
|
@ -49,34 +49,34 @@ int msg_verify(struct msg *m)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int msg_fprint(FILE *f, struct msg *m, int flags, double offset)
|
||||
int msg_print(char *buf, size_t len, struct msg *m, int flags, double offset)
|
||||
{
|
||||
fprintf(f, "%u", m->ts.sec);
|
||||
size_t off = snprintf(buf, len, "%u", m->ts.sec);
|
||||
|
||||
if (flags & MSG_PRINT_NANOSECONDS)
|
||||
fprintf(f, ".%09u", m->ts.nsec);
|
||||
off += snprintf(buf + off, len - off, ".%09u", m->ts.nsec);
|
||||
|
||||
if (flags & MSG_PRINT_OFFSET)
|
||||
fprintf(f, "%+g", offset);
|
||||
off += snprintf(buf + off, len - off, "%+g", offset);
|
||||
|
||||
if (flags & MSG_PRINT_SEQUENCE)
|
||||
fprintf(f, "(%u)", m->sequence);
|
||||
off += snprintf(buf + off, len - off, "(%u)", m->sequence);
|
||||
|
||||
if (flags & MSG_PRINT_VALUES) {
|
||||
for (int i = 0; i < m->length; i++)
|
||||
fprintf(f, "\t%.6f", m->data[i].f);
|
||||
off += snprintf(buf + off, len - off, "\t%.6f", m->data[i].f);
|
||||
}
|
||||
|
||||
fprintf(f, "\n");
|
||||
off += snprintf(buf + off, len - off, "\n");
|
||||
|
||||
return 0;
|
||||
return off + 1; /* trailing '\0' */
|
||||
}
|
||||
|
||||
/** @todo Currently only floating point values are supported */
|
||||
int msg_fscan(FILE *f, struct msg *m, int *fl, double *off)
|
||||
int msg_scan(const char *line, struct msg *m, int *fl, double *off)
|
||||
{
|
||||
char line[MSG_VALUES * 16];
|
||||
char *end, *ptr = line;
|
||||
char *end;
|
||||
const char *ptr = line;
|
||||
|
||||
int flags = 0;
|
||||
double offset;
|
||||
|
||||
|
@ -90,14 +90,6 @@ int msg_fscan(FILE *f, struct msg *m, int *fl, double *off)
|
|||
* Please note that only the seconds and at least one value are mandatory
|
||||
*/
|
||||
|
||||
skip: if (fgets(line, sizeof(line), f) == NULL)
|
||||
return -1; /* An error occured */
|
||||
|
||||
/* Skip whitespaces, empty and comment lines */
|
||||
for (ptr = line; isspace(*ptr); ptr++);
|
||||
if (*ptr == '\0' || *ptr == '#')
|
||||
goto skip;
|
||||
|
||||
/* Mandatory: seconds */
|
||||
m->ts.sec = (uint32_t) strtoul(ptr, &end, 10);
|
||||
if (ptr == end)
|
||||
|
@ -165,3 +157,29 @@ skip: if (fgets(line, sizeof(line), f) == NULL)
|
|||
|
||||
return m->length;
|
||||
}
|
||||
|
||||
int msg_fprint(FILE *f, struct msg *m, int flags, double offset)
|
||||
{
|
||||
char line[4096];
|
||||
|
||||
int len = msg_print(line, sizeof(line), m, flags, offset);
|
||||
|
||||
fputs(line, f);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int msg_fscan(FILE *f, struct msg *m, int *fl, double *off)
|
||||
{
|
||||
char *ptr, line[4096];
|
||||
|
||||
skip: if (fgets(line, sizeof(line), f) == NULL)
|
||||
return -1; /* An error occured */
|
||||
|
||||
/* Skip whitespaces, empty and comment lines */
|
||||
for (ptr = line; isspace(*ptr); ptr++);
|
||||
if (*ptr == '\0' || *ptr == '#')
|
||||
goto skip;
|
||||
|
||||
return msg_scan(line, m, fl, off);
|
||||
}
|
||||
|
|
386
lib/websocket.c
Normal file
386
lib/websocket.c
Normal file
|
@ -0,0 +1,386 @@
|
|||
/** Node type: Websockets (libwebsockets)
|
||||
*
|
||||
* This file implements the weboscket subtype for nodes.
|
||||
*
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2014-2015, Institute for Automation of Complex Power Systems, EONERC
|
||||
* This file is part of S2SS. All Rights Reserved. Proprietary and confidential.
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
*********************************************************************************/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#include <libwebsockets.h>
|
||||
#include <config.h>
|
||||
|
||||
#include "websocket.h"
|
||||
#include "timing.h"
|
||||
#include "utils.h"
|
||||
#include "msg.h"
|
||||
|
||||
/* Forward declarations */
|
||||
static int protocol_cb_http(struct lws_context *, struct lws *, enum lws_callback_reasons, void *, void *, size_t);
|
||||
static int protocol_cb_live(struct lws_context *, struct lws *, enum lws_callback_reasons, void *, void *, size_t);
|
||||
|
||||
/* Static storage */
|
||||
static struct websocket_global {
|
||||
pthread_t thread; /**< All nodes are served by a single websocket server. This server is running in a dedicated thread. */
|
||||
struct lws_context *context; /**< The libwebsockets server context. */
|
||||
|
||||
struct list nodes; /**< List of websocket node instances. */
|
||||
|
||||
int port; /**< Port of the build in HTTP / WebSocket server */
|
||||
|
||||
const char *ssl_cert; /**< Path to the SSL certitifcate for HTTPS / WSS */
|
||||
const char *ssl_private_key; /**< Path to the SSL private key for HTTPS / WSS */
|
||||
const char *htdocs; /**< Path to the directory which should be served by build in HTTP server */
|
||||
} global;
|
||||
|
||||
static struct lws_protocols protocols[] = {
|
||||
{ "http-only", protocol_cb_http, 0, 0 },
|
||||
{ "live", protocol_cb_live, sizeof(struct node *), 10 },
|
||||
{ 0 } /* terminator */
|
||||
};
|
||||
|
||||
/* Choose mime type based on the file extension */
|
||||
static char * get_mimetype(const char *resource_path)
|
||||
{
|
||||
char *extension = strrchr(resource_path, '.');
|
||||
|
||||
if (extension == NULL)
|
||||
return "text/plain";
|
||||
else if (!strcmp(extension, ".png"))
|
||||
return "image/png";
|
||||
else if (!strcmp(extension, ".jpg"))
|
||||
return "image/jpg";
|
||||
else if (!strcmp(extension, ".gif"))
|
||||
return "image/gif";
|
||||
else if (!strcmp(extension, ".html"))
|
||||
return "text/html";
|
||||
else if (!strcmp(extension, ".css"))
|
||||
return "text/css";
|
||||
else if (!strcmp(extension, ".js"))
|
||||
return "application/javascript";
|
||||
else
|
||||
return "text/plain";
|
||||
}
|
||||
|
||||
static int protocol_cb_http(struct lws_context *context, struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
|
||||
{
|
||||
struct websocket_global *global = lws_context_user(context);
|
||||
|
||||
switch (reason) {
|
||||
case LWS_CALLBACK_HTTP:
|
||||
if (!global->htdocs) {
|
||||
lws_return_http_status(context, wsi, HTTP_STATUS_SERVICE_UNAVAILABLE, NULL);
|
||||
goto try_to_reuse;
|
||||
}
|
||||
|
||||
if (len < 1) {
|
||||
lws_return_http_status(context, wsi, HTTP_STATUS_BAD_REQUEST, NULL);
|
||||
goto try_to_reuse;
|
||||
}
|
||||
|
||||
char *requested_uri = (char *) in;
|
||||
|
||||
debug(3, "Got new lws HTTP request: %s", requested_uri);
|
||||
|
||||
/* Handle default path */
|
||||
if (!strcmp(requested_uri, "/")) {
|
||||
char *response = "HTTP/1.1 302 Found\r\n"
|
||||
"Content-Length: 0\r\n"
|
||||
"Location: /index.html\r\n"
|
||||
"\r\n";
|
||||
|
||||
lws_write(wsi, (void *) response, strlen(response), LWS_WRITE_HTTP);
|
||||
|
||||
goto try_to_reuse;
|
||||
}
|
||||
/* Return list of websocket nodes */
|
||||
else if (!strcmp(requested_uri, "/nodes.json")) {
|
||||
char response[4096];
|
||||
char *body = NULL;
|
||||
|
||||
list_foreach(struct node *n, &global->nodes)
|
||||
strcatf(&body, "'%s', ", n->name);
|
||||
|
||||
snprintf(response, sizeof(response),
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n"
|
||||
"[ %.*s ]", (int) strlen(body) - 2, body);
|
||||
|
||||
lws_write(wsi, (void *) response, strlen(response), LWS_WRITE_HTTP);
|
||||
free(body);
|
||||
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
char path[4069];
|
||||
snprintf(path, sizeof(path), "%s%s", global->htdocs, requested_uri);
|
||||
|
||||
/* refuse to serve files we don't understand */
|
||||
char *mimetype = get_mimetype(path);
|
||||
if (!mimetype) {
|
||||
warn("HTTP: Unknown mimetype for %s", path);
|
||||
lws_return_http_status(context, wsi, HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, NULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int n = lws_serve_http_file(context, wsi, path, mimetype, NULL, 0);
|
||||
if (n < 0)
|
||||
return -1;
|
||||
else if (n == 0)
|
||||
break;
|
||||
else
|
||||
goto try_to_reuse;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
try_to_reuse:
|
||||
if (lws_http_transaction_completed(wsi))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int protocol_cb_live(struct lws_context *context, struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
|
||||
{
|
||||
struct node *n;
|
||||
struct websocket *w;
|
||||
struct websocket_global *global = lws_context_user(context);
|
||||
|
||||
char *buf, uri[1024];
|
||||
|
||||
switch (reason) {
|
||||
case LWS_CALLBACK_ESTABLISHED:
|
||||
lws_hdr_copy(wsi, uri, sizeof(uri), WSI_TOKEN_GET_URI); /* The path component of the*/
|
||||
|
||||
/* Search for node which matches uri */
|
||||
list_foreach(n, &global->nodes) {
|
||||
if (!strcmp(n->name, uri + 1)) /* we skip leading '/' */
|
||||
goto found;
|
||||
}
|
||||
|
||||
warn("WebSocket: New Connection for non-exsitent node: %s", uri + 1);
|
||||
|
||||
return -1;
|
||||
|
||||
found: * (void **) user = n;
|
||||
w = n->_vd;
|
||||
|
||||
list_push(&w->connections, wsi);
|
||||
|
||||
info("WebSocket: New Connection for node: %s", n->name);
|
||||
|
||||
return 0;
|
||||
|
||||
case LWS_CALLBACK_CLOSED:
|
||||
n = * (struct node **) user;
|
||||
w = n->_vd;
|
||||
|
||||
list_remove(&w->connections, wsi);
|
||||
|
||||
info("WebSocket: Connection closed for node: %s", n->name);
|
||||
|
||||
return 0;
|
||||
|
||||
case LWS_CALLBACK_SERVER_WRITEABLE:
|
||||
n = * (struct node **) user;
|
||||
if (!n)
|
||||
return 0;
|
||||
|
||||
w = n->_vd;
|
||||
if (!w->write.m)
|
||||
return 0;
|
||||
|
||||
buf = malloc(LWS_SEND_BUFFER_PRE_PADDING + LWS_SEND_BUFFER_POST_PADDING + 4096);
|
||||
|
||||
pthread_mutex_lock(&w->write.mutex);
|
||||
|
||||
len = msg_print(buf + LWS_SEND_BUFFER_PRE_PADDING, 4096, w->write.m, MSG_PRINT_NANOSECONDS | MSG_PRINT_VALUES, 0);
|
||||
|
||||
pthread_mutex_unlock(&w->write.mutex);
|
||||
|
||||
lws_write(wsi, (unsigned char *) (buf + LWS_SEND_BUFFER_PRE_PADDING), len, LWS_WRITE_TEXT);
|
||||
|
||||
return 0;
|
||||
|
||||
case LWS_CALLBACK_RECEIVE:
|
||||
n = * (struct node **) user;
|
||||
w = n->_vd;
|
||||
|
||||
pthread_mutex_lock(&w->read.mutex);
|
||||
|
||||
msg_scan(in, w->read.m, NULL, NULL);
|
||||
|
||||
pthread_mutex_unlock(&w->read.mutex);
|
||||
pthread_cond_broadcast(&w->read.cond);
|
||||
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void logger(int level, const char *msg) {
|
||||
int len = strlen(msg);
|
||||
if (strchr(msg, '\n'))
|
||||
len -= 1;
|
||||
|
||||
switch (level) {
|
||||
case LLL_ERR: error("WebSocket: %.*s", len, msg); break;
|
||||
case LLL_WARN: warn("WebSocket: %.*s", len, msg); break;
|
||||
case LLL_INFO: info("WebSocket: %.*s", len, msg); break;
|
||||
default: debug(1, "WebSocket: %.*s", len, msg); break;
|
||||
}
|
||||
}
|
||||
|
||||
static void * server_thread(void *ctx)
|
||||
{
|
||||
debug(3, "WebSocket: Started server thread");
|
||||
|
||||
while (lws_service(global.context, 10) >= 0);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int websocket_init(int argc, char * argv[], struct settings *set)
|
||||
{
|
||||
lws_set_log_level((1 << LLL_COUNT) - 1, logger);
|
||||
|
||||
/* Parse global config */
|
||||
/*
|
||||
config_setting_lookup_string(cfg, "ssl_cert", &global.ssl_cert);
|
||||
config_setting_lookup_string(cfg, "ssl_private_key", &global.ssl_private_key);
|
||||
config_setting_lookup_string(cfg, "htdocs", &global.htdocs);
|
||||
|
||||
if (!config_setting_lookup_int(cfg, "port", &global.port))
|
||||
port = 80;
|
||||
*/
|
||||
|
||||
/* @todo Fake settings */
|
||||
global.port = 8080;
|
||||
global.htdocs = "/s2ss/contrib/websocket";
|
||||
|
||||
/* Initialize list of nodes */
|
||||
list_init(&global.nodes, NULL);
|
||||
|
||||
/* Start server */
|
||||
struct lws_context_creation_info info = {
|
||||
.port = global.port,
|
||||
.protocols = protocols,
|
||||
.extensions = lws_get_internal_extensions(),
|
||||
.ssl_cert_filepath = global.ssl_cert,
|
||||
.ssl_private_key_filepath = global.ssl_private_key,
|
||||
.gid = -1,
|
||||
.uid = -1,
|
||||
.user = &global
|
||||
};
|
||||
|
||||
global.context = lws_create_context(&info);
|
||||
if (global.context == NULL)
|
||||
error("WebSocket: failed to initialize server");
|
||||
|
||||
pthread_create(&global.thread, NULL, server_thread, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int websocket_deinit()
|
||||
{
|
||||
pthread_join(global.thread, NULL);
|
||||
|
||||
lws_cancel_service(global.context);
|
||||
lws_context_destroy(global.context);
|
||||
|
||||
list_destroy(&global.nodes);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int websocket_open(struct node *n)
|
||||
{
|
||||
struct websocket *w = n->_vd;
|
||||
|
||||
list_init(&w->connections, NULL);
|
||||
list_push(&global.nodes, n);
|
||||
|
||||
pthread_mutex_init(&w->read.mutex, NULL);
|
||||
pthread_mutex_init(&w->write.mutex, NULL);
|
||||
pthread_cond_init(&w->read.cond, NULL);
|
||||
|
||||
/* pthread_cond_wait() expects the mutex to be already locked */
|
||||
pthread_mutex_lock(&w->read.mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int websocket_close(struct node *n)
|
||||
{
|
||||
struct websocket *w = n->_vd;
|
||||
|
||||
list_remove(&global.nodes, n);
|
||||
|
||||
pthread_mutex_destroy(&w->read.mutex);
|
||||
pthread_mutex_destroy(&w->write.mutex);
|
||||
pthread_cond_destroy(&w->read.cond);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int websocket_read(struct node *n, struct msg *pool, int poolsize, int first, int cnt)
|
||||
{
|
||||
struct websocket *w = n->_vd;
|
||||
struct msg *m = pool + (first % poolsize);
|
||||
|
||||
w->read.m = m;
|
||||
|
||||
pthread_cond_wait(&w->read.cond, &w->read.mutex);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int websocket_write(struct node *n, struct msg *pool, int poolsize, int first, int cnt)
|
||||
{
|
||||
struct websocket *w = n->_vd;
|
||||
struct msg *m = pool + (first % poolsize);
|
||||
|
||||
pthread_mutex_lock(&w->write.mutex);
|
||||
|
||||
w->write.m = m;
|
||||
|
||||
/* Notify all active websocket connections to send new data */
|
||||
list_foreach(struct lws *wsi, &w->connections) {
|
||||
lws_callback_on_writable(global.context, wsi);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&w->write.mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct node_type vt = {
|
||||
.name = "websocket",
|
||||
.description = "Send and receive samples of a WebSocket connection (libwebsockets)",
|
||||
.size = sizeof(struct websocket),
|
||||
.open = websocket_open,
|
||||
.close = websocket_close,
|
||||
.read = websocket_read,
|
||||
.write = websocket_write,
|
||||
.init = websocket_init,
|
||||
.deinit = websocket_deinit
|
||||
};
|
||||
|
||||
REGISTER_NODE_TYPE(&vt)
|
Loading…
Add table
Reference in a new issue