1
0
Fork 0

signage: bring in from external repo

This is b28e6f07aa48f1e2f01eb37bffa180f97a7b03bd from
https://code.hackerspace.pl/q3k/love2d-signage/. We only keep code
commited by inf and q3k, and we're both now licensing this code under
the ISC license, as per COPYING in the root of hscloud.

Change-Id: Ibeee2e6923605e4b1a17a1d295867c056863ef59
Reviewed-on: https://gerrit.hackerspace.pl/c/hscloud/+/1335
Reviewed-by: informatic <informatic@hackerspace.pl>
Reviewed-by: q3k <q3k@hackerspace.pl>
master
q3k 2022-07-07 14:24:53 +02:00 committed by q3k
parent 9c5d866105
commit 18c1a263cf
35 changed files with 3162 additions and 48 deletions

View File

@ -1,6 +1,6 @@
# RPi4 as TV kiosk.
{ config, pkgs, ... }:
{ config, pkgs, workspace, ... }:
let
nixos-hardware = builtins.fetchGit {
@ -32,12 +32,6 @@ in {
networking.domain = "waw.hackerspace.pl";
time.timeZone = "Europe/Warsaw";
nixpkgs.overlays = [
(self: super: {
signage = self.callPackage ./signage.nix {};
})
];
sound.enable = true;
services.pipewire = {
enable = true;
@ -107,7 +101,7 @@ in {
'';
in pkgs.writeScriptBin "signage-wrapped" ''
#!/usr/bin/env bash
SIGNAGE_CONFIG=${config} ${signage}/bin/signage
SIGNAGE_CONFIG=${config} ${workspace.hswaw.signage.prod}/bin/signage
'')
firefox foot wayvnc
vim rxvt-unicode-unwrapped.terminfo

View File

@ -1,40 +0,0 @@
{ love, curl, fetchFromGitHub, fetchgit, stdenv, lib, ... }:
let
love12 = (love.overrideAttrs (oa: {
version = "12.0-dev";
src = fetchFromGitHub {
owner = "love2d";
repo = "love";
rev = "d586d1847446f5212d5f7e9efb94e50fcfba7d77";
sha256 = "sha256-gTpVtyqXV6/GsezuCpooaY+x5tPfOF9p1b83v4kKR4E=";
};
makeFlags = [
"CPPFLAGS=-DHTTPS_BACKEND_CURL"
];
buildInputs = oa.buildInputs ++ [ curl ];
NIX_LDFLAGS = "-lcurl";
enableParallelBuilding = true;
}));
signage = stdenv.mkDerivation {
name = "signage";
src = fetchgit {
url = "https://code.hackerspace.pl/q3k/love2d-signage";
rev = "6c14716222e28b004861b3926560bf21d519fb00";
sha256 = "sha256-dfZ6Q320+ukMt9Q2igcARBM72LRbW5ltEvxrngSW8fQ=";
};
installPhase = ''
mkdir -p $out/share/signage
cp -rv $src/* $out/share/signage/
mkdir -p $out/bin/
cat <<EOF >$out/bin/signage
#!/usr/bin/env bash
${love12}/bin/love $out/share/signage
EOF
chmod +x $out/bin/signage
'';
};
in signage

24
hswaw/signage/README Normal file
View File

@ -0,0 +1,24 @@
love2d signage
===
As used in digital signage displays in HSWAW.
Originally hosted on code.hackerspace.pl/informatic/love2d-signage, now in hscloud.
Building & Running
---
$ nix-build -A hswaw.signage.prod
$ result/bin/signage
If the result doesn't run and complains about OpenGL not being available, try building the 'local' target instead, which will use your host `<nixpkgs>`:
$ nix-build -A hswaw.signage.local
For non-NixOS systems you'll need something like `nixgl` to work around Nixpkgs-GL-on-Non-NixOS badness anyways.
Custom configs
---
You can either modify config.lua and rebuild, or specify a custom config at runtime by settin gthe `SIGNAGE_CONFIG` environment variable.

13
hswaw/signage/config.lua Normal file
View File

@ -0,0 +1,13 @@
return {
displayTime = 2,
transitionTime = 0.5,
showProgress = true,
nodes = {
{'nodes.weather'},
{'nodes.newdash', displayTime=10},
{'nodes.misery', displayTime = 7},
},
environment = os.getenv('ENV') or 'prod',
renderWidth = 1280,
renderHeight = 720,
}

View File

@ -0,0 +1,114 @@
local NodeManager = class('NodeManager', {
state = 'running',
stateTime = 0,
currentNode = nil,
})
function NodeManager:init(config)
self.config = config
self.nodes = {}
self.nodeConfs = {}
self.currentNode = nil
print('Initializing NodeManager')
end
function NodeManager:load()
self:configChanged()
end
function NodeManager:resize(w, h)
self.secondaryCanvas = love.graphics.newCanvas(w, h)
end
function NodeManager:configChanged()
local cnt = {}
local newNodes = {}
local newNodeConfs = {}
for _, c_ in ipairs(self.config.nodes) do
local nodeConfig = lume.clone(c_)
local hash = inspect(nodeConfig)
if cnt[hash] == nil then cnt[hash] = 0 end
cnt[hash] = cnt[hash] + 1
hash = hash .. '-' .. tostring(cnt[hash])
local nodeName = nodeConfig[1]
table.remove(nodeConfig, 1)
if self.nodeConfs[hash] then
print('Using existing node:', self.nodeConfs[hash], hash)
newNodes[#newNodes + 1] = self.nodeConfs[hash]
else
print('Creating new node.', nodeName, inspect(nodeConfig))
local status, err = pcall(function()
newNodes[#newNodes + 1] = require(nodeName)(nodeConfig)
end)
if err then
print("Error occured while loading", nodeName, err)
return
end
end
newNodeConfs[hash] = newNodes[#newNodes]
end
self.nodes = newNodes
self.nodeConfs = newNodeConfs
end
function NodeManager:render()
if not self.currentNode then self.currentNode = self.nodes[1] end
if not self.currentNode then return end
-- love.graphics.print('state: ' .. self.state .. '; counter: ' .. tostring(self.stateTime), 50, 50)
self.currentNode:render()
if self.state == 'transitioning' and self.currentNode ~= self.nextNode and self.nextNode then
self.secondaryCanvas:renderTo(function()
self.nextNode:render()
end)
love.graphics.setColor(1.0, 1.0, 1.0, 1.0 * (self.stateTime / self.config.transitionTime))
love.graphics.draw(self.secondaryCanvas, 0, 0)
end
love.graphics.setColor(1.0, 1.0, 1.0, 0.3)
if self.config.showProgress and self.state == 'running' then
local stateTime
stateTime = self.currentNode.displayTime or self.config.displayTime
local h = 5
love.graphics.rectangle("fill", 0, love.graphics.getHeight() - h, (self.stateTime / stateTime) * love.graphics.getWidth(), h)
end
end
function NodeManager:update(dt)
if not self.currentNode then self.currentNode = self.nodes[1] end
if not self.currentNode then return end
self.stateTime = self.stateTime + dt
if self.state == 'transitioning' and self.stateTime >= self.config.transitionTime then
self.stateTime = 0
self.state = 'running'
self.currentNode:afterExit()
-- self.currentNode, self.nextNode = self.nextNode, self.nodes[(lume.find(self.nodes, self.nextNode) or 1) % #self.nodes + 1]
self.currentNode = self.nextNode
self.currentNode:afterEnter()
elseif self.state == 'running' and self.stateTime >= (self.currentNode.displayTime or self.config.displayTime) then
self.stateTime = 0
self.state = 'transitioning'
self.currentNode:beforeExit()
self.nextNode = self.nodes[(lume.find(self.nodes, self.currentNode) or 1) % #self.nodes + 1]
self.nextNode:beforeEnter()
end
self.currentNode:update(dt)
if self.state == 'transitioning' and self.currentNode ~= self.nextNode and self.nextNode then
self.nextNode:update(dt)
end
end
return NodeManager

View File

@ -0,0 +1,16 @@
local Node = class('Node', {})
function Node:init(opts)
for i, n in pairs(opts) do
self[i] = n
end
end
function Node:update(dt) end
function Node:beforeEnter() end
function Node:afterEnter() end
function Node:beforeExit() end
function Node:afterExit() end
return Node

View File

@ -0,0 +1,40 @@
local ThreadNode = Node:extend('ThreadNode', {
threadFile = nil,
threadChannel = nil,
updateInterval = 5,
lastUpdate = 0,
state = nil,
})
function ThreadNode:update(dt)
if not self.state then self:checkUpdate() end
end
function ThreadNode:checkUpdate()
if self.threadFile and self.threadChannel then
if self.lastUpdate < love.timer.getTime() - self.updateInterval or
(not self.state and self.lastUpdate < love.timer.getTime() - 5) then
self.lastUpdate = love.timer.getTime()
print(self.threadChannel, "Updating...")
local updateThread = love.thread.newThread(self.threadFile)
updateThread:start()
end
local v = love.thread.getChannel(self.threadChannel):pop()
if v then
self:onUpdate(v)
end
end
end
function ThreadNode:afterExit()
print('exit')
self:checkUpdate()
end
function ThreadNode:onUpdate(v)
self.state = v
end
return ThreadNode

43
hswaw/signage/default.nix Normal file
View File

@ -0,0 +1,43 @@
{ pkgs, ... }: let
signageForPkgs = pkgs: with { inherit (pkgs) love fetchFromGitHub stdenv curl; }; let
# Build LÖVE2D 12, currently still in development. This gives us https
# support.
love12 = (love.overrideAttrs (oa: {
version = "12.0-dev";
src = fetchFromGitHub {
owner = "love2d";
repo = "love";
rev = "d586d1847446f5212d5f7e9efb94e50fcfba7d77";
sha256 = "sha256-gTpVtyqXV6/GsezuCpooaY+x5tPfOF9p1b83v4kKR4E=";
};
makeFlags = [
"CPPFLAGS=-DHTTPS_BACKEND_CURL"
];
buildInputs = oa.buildInputs ++ [ curl ];
NIX_LDFLAGS = "-lcurl";
enableParallelBuilding = true;
}));
signage = stdenv.mkDerivation {
name = "signage";
src = ./.;
installPhase = ''
mkdir -p $out/share/signage
cp -rv $src/* $out/share/signage/
mkdir -p $out/bin/
cat <<EOF >$out/bin/signage
#!/usr/bin/env bash
${love12}/bin/love $out/share/signage
EOF
chmod +x $out/bin/signage
'';
};
in signage;
in {
prod = signageForPkgs pkgs;
local = signageForPkgs (import <nixpkgs> {});
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

104
hswaw/signage/main.lua Normal file
View File

@ -0,0 +1,104 @@
class = require('vendor.30log')
inspect = require('vendor.inspect')
lume = require('vendor.lume')
Node = require('core.node')
ThreadNode = require('core.thread-node')
NodeManager = require('core.node-manager')
local config = nil
if os.getenv("SIGNAGE_CONFIG") ~= nil then
local f = loadfile(os.getenv("SIGNAGE_CONFIG"))
if f ~= nil then
config = f()
else
error("SIGNAGE_CONFIG given but could not be loaded")
end
else
config = require('config')
end
local push = require('vendor.push')
local lurker = require('vendor.lurker')
lurker.quiet = true
lurker.interval = 3
local debugGraph = require('vendor.debugGraph')
local fpsGraph = debugGraph:new('fps', 0, 0)
local memGraph = debugGraph:new('mem', 0, 30)
local gameWidth, gameHeight = config.renderWidth, config.renderHeight
local windowWidth, windowHeight = love.window.getDesktopDimensions()
windowWidth, windowHeight = windowWidth*.5, windowHeight*.5 --make the window a bit smaller than the screen itself
if config.environment == 'dev' then
push:setupScreen(gameWidth, gameHeight, windowWidth, windowHeight, {fullscreen = false, resizable = true})
else
push:setupScreen(gameWidth, gameHeight, gameWidth, gameHeight, {fullscreen = true})
end
function love.resize(w, h)
push:resize(w, h)
manager:resize(w, h)
end
function love.load()
manager = NodeManager(config)
manager:load()
manager:resize(push:getWidth(), push:getHeight())
love.mouse.setVisible( false )
end
function getw() return push:getWidth() end
function geth() return push:getHeight() end
function love.draw()
push:start()
-- Patch love.graphics.getWidth/Height to account for push
oldw, oldh = love.graphics.getWidth, love.graphics.getHeight
love.graphics.getWidth, love.graphics.getHeight = getw, geth
manager:render()
-- Draw graphs
love.graphics.setColor(1.0, 1.0, 1.0, 0.5)
-- love.graphics.setNewFont(10)
-- love.graphics.print(inspect(state), 0, 60, 0)
fpsGraph:draw()
memGraph:draw()
love.graphics.getWidth, love.graphics.getHeight = oldw, oldh
push:finish()
end
function love.keypressed( key, scancode, isrepeat )
if key == "right" then
-- Cycle to next state
manager.stateTime = 2137
end
end
function love.update(dt)
manager:update(dt)
-- Update the graphs
fpsGraph:update(dt)
memGraph:update(dt)
lurker.update()
end
function lurker.preswap(f)
if f == 'config.lua' then
print('config reloaded, notifying nodemanager')
package.loaded['config'] = nil
manager.config = require('config');
manager:configChanged();
elseif f:match('_thread.lua') then
return false
end
end

View File

@ -0,0 +1,46 @@
local node = ThreadNode:extend('nodes.at', {
threadFile = 'nodes/at_thread.lua',
threadChannel = 'at',
api = 'http://at.hackerspace.pl/api',
})
local bigFont = love.graphics.newFont('fonts/Lato-Light.ttf', 80)
local textFont = love.graphics.newFont('fonts/Lato-Light.ttf', 50)
local smallFont = love.graphics.newFont('fonts/Lato-Light.ttf', 30)
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
if self.state then
love.graphics.setColor( 1.0, 1.0, 1.0, 0.4 )
love.graphics.setFont(bigFont)
love.graphics.printf('Currently at hackerspace:', 50, 100, love.graphics.getWidth() - 100, 'center')
usersList = (table.concat(lume.map(self.state.users, function(v) return v.login end), ', ') or 'nobody...') .. '\n'
if self.state.unknown > 0 then
usersList = usersList .. '\n...and ' .. tostring(self.state.unknown) .. ' unknown creatures'
end
if self.state.kektops > 0 then
usersList = usersList .. '\n...and ' .. tostring(self.state.kektops) .. ' kektops'
end
if self.state.esps > 0 then
usersList = usersList .. '\n...and ' .. tostring(self.state.esps) .. ' ESPs'
end
love.graphics.setColor( 1.0, 1.0, 1.0 )
love.graphics.setFont(textFont)
love.graphics.printf(usersList, 50, 220, love.graphics.getWidth() - 100, 'center')
else
love.graphics.setColor( 1.0, 1.0, 1.0, 0.4 )
love.graphics.setFont(smallFont)
love.graphics.printf("Loading at...", 0, love.graphics.getHeight() - 200, love.graphics.getWidth(), 'center')
end
end
return node

View File

@ -0,0 +1,14 @@
local socket = require("socket")
local http = require("socket.http")
local json = require("vendor.json")
local lume = require("vendor.lume")
local miseryURL = 'http://at.hackerspace.pl/api'
local r, c, h = http.request(miseryURL)
if c == 200 then
love.thread.getChannel('at'):push(json.decode(r))
print("Update finished")
else
print("Update failed")
end

View File

@ -0,0 +1,55 @@
local node = Node:extend('nodes.countdown', {
target = 1498780800,
description = 'to get the fuck out of here',
precision = 3,
})
local textFont = love.graphics.newFont('fonts/Lato-Thin.ttf', 100)
local smallFont = love.graphics.newFont('fonts/Lato-Light.ttf', 60)
function node:init(config)
node.super.init(self, config)
end
function timefmt(time, precision)
precision = precision or 3
local p = {
{60, "seconds"},
{60, "minutes"},
{24, "hours"},
{7, "days"},
{nil, "weeks"},
}
local parts = {}
local v
for i, e in ipairs(p) do
if e[1] == nil then
v = time
else
v = time % e[1]
time = math.floor(time / e[1])
end
if v ~= 0 then
table.insert(parts, 1, tostring(v) .. " " .. e[2])
end
end
return table.concat(lume.slice(parts, 1, precision), " ")
end
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
love.graphics.setColor( 1.0, 1.0, 1.0 )
love.graphics.setFont(textFont);
love.graphics.printf(timefmt(math.abs(self.target - os.time()), self.precision), 0, 0.3*love.graphics.getHeight(), love.graphics.getWidth(), 'center');
love.graphics.setFont(smallFont);
love.graphics.printf(self.description, 0, 0.7*love.graphics.getHeight(), love.graphics.getWidth(), 'center');
end
return node

View File

@ -0,0 +1,64 @@
local node = Node:extend('nodes.at', {
})
-- local papa = love.graphics.newImage("papa.png")
local h = 25.0
local v = {
{-h, -h, -h},
{ h, -h, -h},
{ h, h, -h},
{-h, h, -h},
{-h, -h, h},
{ h, -h, h},
{ h, h, h},
{-h, h, h}
}
local c = {
{0, 1, 31},
{1, 2, 31},
{2, 3, 31},
{3, 0, 31},
{0, 4, 34},
{1, 5, 34},
{2, 6, 34},
{3, 7, 34},
{4, 5, 32},
{5, 6,32},
{6, 7,32},
{7, 4,32}
}
local E = 100 * math.tan(2*math.pi/3)
function to2d(p) return p[1] * E / (p[3]+E), p[2] * E / (p[3]+E) end
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
love.graphics.setColor( 1.0, 1.0, 1.0 )
local w = love.graphics.getWidth() / 2
local h = love.graphics.getHeight() / 2
local scl = 6
for _, p in ipairs(c) do
x1, y1 = to2d(v[p[1]+1])
x2, y2 = to2d(v[p[2]+1])
love.graphics.line(x1 * scl + w, y1 * scl + h, x2 * scl + w, y2 * scl + h)
end
end
function node:update(dt)
for _, vec in ipairs(v) do
angle = -dt/3
nv0 = math.cos(angle) * vec[1] + math.sin(angle) * vec[3]
nv2 = -math.sin(angle) * vec[1] + math.cos(angle) * vec[3]
vec[1] = nv0
vec[3] = nv2
angle = dt
nv1 = math.cos(angle) * vec[2] - math.sin(angle) * vec[3]
nv2 = math.sin(angle) * vec[2] + math.cos(angle) * vec[3]
vec[2] = nv1
vec[3] = nv2
end
end
return node

View File

@ -0,0 +1,64 @@
local node = ThreadNode:extend('node.currency', {
threadFile = 'nodes/currency_thread.lua',
threadChannel = 'currency',
updateInterval = 10,
state = {
values = {},
changes = {},
},
})
local inspect = require('vendor.inspect')
local textFont = love.graphics.newFont('fonts/Lato-Light.ttf', 90)
local headFont = love.graphics.newFont('fonts/Lato-Regular.ttf', 90)
local smallFont = love.graphics.newFont('fonts/Lato-Light.ttf', 30)
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
if self.state.values and self.state.values[2] then
local pad = 20
love.graphics.setColor( 1.0, 1.0, 1.0 )
love.graphics.setFont(headFont)
love.graphics.printf("BTCPLN", 0, 180, love.graphics.getWidth()/2, 'right')
love.graphics.printf("TRYIDR", 0, 350, love.graphics.getWidth()/2, 'right')
love.graphics.setFont(textFont)
if self.state.changes[1] then
love.graphics.setColor( 0, 1.0, 0 )
else
love.graphics.setColor( 1.0, 0, 0 )
end
love.graphics.printf(self.state.values[1], love.graphics.getWidth()/2 + 2*pad, 180, love.graphics.getWidth()/2 - 2*pad, 'left')
if self.state.changes[2] then
love.graphics.setColor( 0, 1.0, 0 )
else
love.graphics.setColor( 1.0, 0, 0 )
end
love.graphics.printf(self.state.values[2], love.graphics.getWidth()/2 + 2*pad, 350, love.graphics.getWidth()/2 - 2*pad, 'left')
else
love.graphics.setColor( 1.0, 1.0, 1.0, 0.4 )
love.graphics.setFont(smallFont)
love.graphics.printf("Loading currency...", 0, love.graphics.getHeight() - 200, love.graphics.getWidth(), 'center')
end
end
function node:onUpdate(v)
for n in ipairs(v.values) do
if self.state.values[n] then
if self.state.values[n] ~= v.values[n] then
self.state.changes[n] = self.state.values[n] > v.values[n]
end
else
self.state.changes[n] = true
end
end
self.state.values = v.values
end
return node

View File

@ -0,0 +1,22 @@
local socket = require("socket")
local http = require("socket.http")
local json = require("vendor.json")
local lume = require("vendor.lume")
local btcURL = 'http://www.bitmarket.pl/json/BTCPLN/ticker.json'
local tryURL = 'http://api.fixer.io/latest?base=TRY'
local r, c, h = http.request(btcURL)
if c == 200 then
btcpln = json.decode(r)['last']
local r, c, h = http.request(tryURL)
if c == 200 then
tryidr = json.decode(r)['rates']['IDR']
end
love.thread.getChannel('currency'):push({
values = {math.floor(btcpln), math.floor(tryidr)},
})
print("Update finished")
else
print("Update failed")
end

View File

@ -0,0 +1,34 @@
local node = ThreadNode:extend('node.misery', {
threadFile = 'nodes/misery_thread.lua',
threadChannel = 'misery',
updateInterval = 10,
})
local inspect = require('vendor.inspect')
local textFont = love.graphics.newFont('fonts/Lato-Light.ttf', 50)
local smallFont = love.graphics.newFont('fonts/Lato-Light.ttf', 30)
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
if self.state then
love.graphics.setColor( 1.0, 1.0, 1.0 )
love.graphics.setFont(textFont)
love.graphics.printf(self.state.entry, 50, 180, love.graphics.getWidth() - 100, 'center')
love.graphics.setColor( 1.0, 1.0, 1.0, 0.8 )
love.graphics.setFont(smallFont)
local description = 'added by ' .. self.state.author .. ' on ' .. os.date('%Y/%m/%d %X', self.state.added)
love.graphics.printf(description, 200, love.graphics.getHeight() - 100, love.graphics.getWidth() - 400, 'center')
else
love.graphics.setColor( 1.0, 1.0, 1.0, 0.4 )
love.graphics.setFont(smallFont)
love.graphics.printf("Loading misery...", 0, love.graphics.getHeight() - 200, love.graphics.getWidth(), 'center')
end
end
return node

View File

@ -0,0 +1,14 @@
local socket = require("socket")
local https = require("https")
local json = require("vendor.json")
local lume = require("vendor.lume")
local miseryURL = 'https://oodviewer.q3k.me/randomterm.json/_,'
local c, r, h = https.request(miseryURL)
if c == 200 then
love.thread.getChannel('misery'):push(json.decode(r))
print("Update finished")
else
print("Update failed: " .. tostring(c) .. ": " .. tostring(r))
end

View File

@ -0,0 +1,123 @@
local node = ThreadNode:extend('nodes.newdash', {
threadFile = 'nodes/newdash_thread.lua',
threadChannel = 'newdash',
updateInterval = 60,
})
local weatherFont = love.graphics.newFont('fonts/weathericons-regular-webfont.ttf', 165)
local tempFont = love.graphics.newFont('fonts/Lato-Light.ttf', 120)
local timeFont = love.graphics.newFont('fonts/Lato-Thin.ttf', 330)
local dateFont = love.graphics.newFont('fonts/Lato-Light.ttf', 90)
local headerFont = love.graphics.newFont('fonts/Lato-Regular.ttf', 40)
local valueFont = love.graphics.newFont('fonts/Lato-Light.ttf', 45)
local atFont = love.graphics.newFont('fonts/Lato-Light.ttf', 35)
local weatherGlyphs = {
snow = "",
mist = "",
clear = "",
-- clouds = "",
clouds = "", -- x---DDD
drizzle = "",
}
function node:spejsiotData(node_id, endpoint, parameter)
if self.state.spejsiot[node_id] and self.state.spejsiot[node_id]["$online"] and self.state.spejsiot[node_id][endpoint] and self.state.spejsiot[node_id][endpoint][parameter] ~= nil then
return self.state.spejsiot[node_id][endpoint][parameter]
else
return nil
end
end
function node:renderIOTState(node_id, endpoint, parameter, x, y)
local rawValue = self:spejsiotData(node_id, endpoint, parameter)
if rawValue == true then
love.graphics.setColor( 0, 1.0, 0 )
love.graphics.printf("ON", x, y, 400, 'left')
elseif rawValue == false then
love.graphics.setColor( 1.0, 0, 0 )
love.graphics.printf("OFF", x, y, 400, 'left')
elseif rawValue == nil then
love.graphics.setColor( 1.0, 0, 0 )
love.graphics.printf("OFFLINE", x, y, 400, 'left')
else
love.graphics.setColor( 1.0, 1.0, 1.0 )
love.graphics.printf(rawValue, x, y, 400, 'left')
end
end
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
if self.state then
love.graphics.setColor( 1.0, 1.0, 1.0 )
if weatherGlyphs[self.state.weather] then
love.graphics.setFont(weatherFont)
love.graphics.print(weatherGlyphs[self.state.weather], 100, 340)
else
love.graphics.setFont(atFont)
love.graphics.print(self.state.weather, 100, 370)
end
love.graphics.setFont(tempFont)
love.graphics.printf(string.format("%d°", self.state.temperature), 350, 390, 270, 'center')
love.graphics.setFont(headerFont)
love.graphics.printf("Ambient:", 720, 380, 160, 'right')
love.graphics.printf("Exhaust:", 720, 440, 160, 'right')
love.graphics.printf("Pope:", 720, 500, 160, 'right')
love.graphics.setFont(valueFont)
if self:spejsiotData("d106e1", "environment", "degree") then
love.graphics.printf(string.format(
"%d° / %d%%RH",
self:spejsiotData("d106e1", "environment", "degree"),
self:spejsiotData("d106e1", "environment", "humidity")
), 900, 378, 400, 'left')
else
love.graphics.printf("?!", 900, 378, 400, 'left')
end
self:renderIOTState("c0dbe7", "relay", "on", 900, 438)
self:renderIOTState("0eac42", "spin and blink", "on", 900, 498)
love.graphics.setColor( 1.0, 1.0, 1.0 )
love.graphics.setFont(headerFont)
love.graphics.printf("at hackerspace:", 50, 593, 300, 'left')
love.graphics.setFont(atFont)
users = {}
if self.state.at then
users = lume.map(self.state.at.users, function(v) return v.login end)
if self.state.at.unknown > 0 then
users[#users + 1] = string.format("%d unknown creatures", self.state.at.unknown)
end
if self.state.at.kektops > 0 then
users[#users + 1] = string.format("%d kektops", self.state.at.kektops)
end
if self.state.at.esps > 0 then
users[#users + 1] = string.format("%d ESPs", self.state.at.esps)
end
end
usersList = (table.concat(users, ', ') or 'nobody...')
love.graphics.printf(usersList, 350, 598, 900, 'left')
else
love.graphics.setColor(1.0, 1.0, 1.0, 0.5)
love.graphics.setFont(valueFont)
love.graphics.printf("Loading...", 0, 530, love.graphics.getWidth(), "center")
end
love.graphics.setColor( 1.0, 1.0, 1.0 )
love.graphics.setFont(timeFont)
love.graphics.printf(os.date("%H:%M"), 50, -10, 850, 'center')
love.graphics.setFont(dateFont)
love.graphics.printf(os.date("%Y\n%m/%d"), 960, 80, 270, 'center')
end
return node

View File

@ -0,0 +1,36 @@
local socket = require("socket")
local https = require("https")
local json = require("vendor.json")
local weatherURL = 'https://openweathermap.org/data/2.5/weather?id=6695624&units=metric&appid=439d4b804bc8187953eb36d2a8c26a02'
local spejsiotURL = 'https://spejsiot.waw.hackerspace.pl/api/1/devices'
local atURL = 'https://at.hackerspace.pl/api'
local spejsiotData = {}
local atData = nil
local c, r, h = https.request(weatherURL)
if c == 200 then
local data = json.decode(r)
local c, r, h = https.request(spejsiotURL)
if c == 200 then
spejsiotData = json.decode(r)
end
local c, r, h = https.request(atURL)
if c == 200 then
atData = json.decode(r)
end
love.thread.getChannel('newdash'):push({
weather = data.weather[1].main:lower(),
temperature = data.main.temp,
lastUpdate = data.dt,
spejsiot = spejsiotData,
at = atData,
})
print("Update finished")
else
print("Update failed")
end

View File

@ -0,0 +1,16 @@
local node = Node:extend('nodes.screen1', {})
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
for x = 0, love.graphics.getWidth() / 50, 1 do
for y = 0, love.graphics.getHeight() / 50, 1 do
local r = math.sin(x + love.timer.getTime() * 2) + math.cos(y + love.timer.getTime() * math.sin(x));
love.graphics.setColor(1.0, 1.0, 1.0, (r + 2) * 1.0)
love.graphics.circle("line", x * 50, y * 50, r * 10)
end
end
end
return node

View File

@ -0,0 +1,117 @@
local node = Node:extend('nodes.shadertoy', {})
local smallFont = love.graphics.newFont('fonts/Lato-Light.ttf', 20)
function node:init(config)
node.super.init(self, config)
self.path = self.path or "test.glsl"
self.resolution = self.resolution or {1280, 720}
self:loadShader()
end
function node:beforeEnter()
self:loadShader()
end
function node:loadShader()
local iSystem = {}
local header = ""
local ender=[[
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) {
vec2 fragCoord = texture_coords * iResolution.xy;
mainImage( color, fragCoord );
return color;
}
]]
local file = io.open(self.path, "r")
local shaderData = file:read("*all")
shaderData = string.gsub(shaderData,"texture2D","Texel")
shaderData = string.gsub(shaderData,"iTime","iGlobalTime")
shaderData = string.gsub(shaderData,"precision highp float;","")
if string.find(shaderData,"iGlobalTime") then
iSystem.iGlobalTime=0
if not string.find(shaderData,"number iGlobalTime") then
header="extern number iGlobalTime;\n"..header
end
end
-- TODO
-- if string.find(shaderData,"iChannel") then
-- iSystem.iChannel={}
-- for k,v in pairs(shaderChannel) do
-- header="extern Image iChannel"..k..";\n"..header
-- end
-- end
if string.find(shaderData,"iMouse") then
iSystem.iMouse = {0, 0, -1, -1}
header = "extern vec4 iMouse;\n"..header
end
if string.find(shaderData,"iResolution") then
iSystem.iResolution = {self.resolution[1], self.resolution[2],1}
header = "extern vec3 iResolution;\n"..header
end
shaderData = header..shaderData
if not string.find(shaderData,"vec4 effect") then
shaderData = shaderData.."\n"..ender
end
print('Shader loaded')
self.shaderLoadError = nil
shaderLoaded, self.shader = pcall(love.graphics.newShader, shaderData)
if not shaderLoaded then
print('Shader load failed:', self.shader)
self.shaderLoadError = self.shader
self.shader = nil
else
print(shaderLoaded, self.shader)
if iSystem.iResolution then
self.shader:send("iResolution",iSystem.iResolution)
end
self.iSystem = iSystem
self.canvas = love.graphics.newCanvas(self.resolution[1], self.resolution[2])
self.renderCanvas = love.graphics.newCanvas(self.resolution[1], self.resolution[2])
end
end
function node:update(dt)
if self.shader ~= nil then
if self.iSystem.iGlobalTime then
self.iSystem.iGlobalTime=self.iSystem.iGlobalTime+dt
self.shader:send("iGlobalTime", self.iSystem.iGlobalTime)
end
end
end
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
if self.shaderLoadError ~= nil then
print('render!')
love.graphics.setColor(1.0, 0.0, 0.0, 1.0)
love.graphics.setFont(smallFont)
love.graphics.printf(self.shaderLoadError, 0, 0.1*love.graphics.getHeight(), love.graphics.getWidth(), 'left');
elseif self.shader ~= nil then
oldCanvas = love.graphics.getCanvas( )
love.graphics.setColor( 1.0, 1.0, 1.0 )
self.canvas:renderTo(function ()
love.graphics.setShader(self.shader)
love.graphics.draw(self.renderCanvas)
love.graphics.setShader()
end)
love.graphics.setCanvas(oldCanvas)
love.graphics.draw(self.canvas,0,0,math.pi,love.graphics.getWidth() / self.resolution[1], love.graphics.getHeight() / self.resolution[2], self.resolution[1], self.resolution[2])
end
end
return node

View File

@ -0,0 +1,25 @@
local node = Node:extend('nodes.time', {})
local textFont = love.graphics.newFont('fonts/Lato-Thin.ttf', 400)
local smallFont = love.graphics.newFont('fonts/Lato-Light.ttf', 60)
function node:init(config)
node.super.init(self, config)
self.timeFormat = self.timeFormat or '%H:%M'
self.dateFormat = self.dateFormat or '%Y/%m/%d'
end
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
love.graphics.setColor( 1.0, 1.0, 1.0 )
love.graphics.setFont(textFont);
love.graphics.printf(os.date(self.timeFormat), 0, 0.14*love.graphics.getHeight(), love.graphics.getWidth(), 'center');
love.graphics.setFont(smallFont);
love.graphics.printf(os.date(self.dateFormat), 0, 0.8*love.graphics.getHeight(), love.graphics.getWidth(), 'center');
end
return node

View File

@ -0,0 +1,88 @@
local node = ThreadNode:extend('nodes.weather', {
threadFile = 'nodes/weather_thread.lua',
threadChannel = 'weather',
updateInterval = 5 * 60,
})
local weatherFont = love.graphics.newFont('fonts/weathericons-regular-webfont.ttf', 400)
local textFont = love.graphics.newFont('fonts/Lato-Thin.ttf', 300)
local smallFont = love.graphics.newFont('fonts/Lato-Light.ttf', 30)
local weatherGlyphs = {}
local weatherGlyphsSet = {
day = {
snow = "",
mist = "",
clear = "",
-- clouds = "",
clouds = "", -- x---DDD
drizzle = "",
},
night = {
snow = "",
mist = "",
clear = "",
clouds = "",
drizzle = "",
}
}
function node:timeOfDay()
local sunRise = tonumber(os.date("%H%M", self.state.sunRise))
local sunSet = tonumber(os.date("%H%M", self.state.sunSet))
local now = tonumber(os.date("%H%M"))
if sunRise == nil or sunSet == nil then
return weatherGlyphsSet["day"] -- smth gone wrong. assume daylight
end
if now < sunSet and now > sunRise then
print('day')
return weatherGlyphsSet["day"]
else
print('night')
return weatherGlyphsSet["night"]
end
end
function node:beforeEnter()
if self.state then
weatherGlyphs = self:timeOfDay()
else
weatherGlyphs = weatherGlyphsSet["day"] -- do not know sunraise and sunset yet. assume daylight
end
end
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
if self.state then
love.graphics.setColor( 1.0, 1.0, 1.0 )
if weatherGlyphs[self.state.weather] then
love.graphics.setFont(weatherFont)
love.graphics.print(weatherGlyphs[self.state.weather], 120, 35)
else
love.graphics.setFont(smallFont)
love.graphics.print(self.state.weather, 150, love.graphics.getHeight()/2 - 60)
end
love.graphics.setFont(textFont)
love.graphics.printf(tostring(math.floor(self.state.temperature + 0.5)) .. "°", 600, 150, 650, 'center')
love.graphics.setFont(smallFont)
love.graphics.printf(os.date("Last update: %Y/%m/%d %H:%M", self.state.lastUpdate), 0, love.graphics.getHeight() - 60, love.graphics.getWidth(), 'center')
if self.state.insideTemperature then
love.graphics.printf("Room: " .. tostring(self.state.insideTemperature) .. "° / " .. tostring(self.state.insideHumidity) .. "%RH", 0, love.graphics.getHeight() - 100, love.graphics.getWidth(), 'center')
end
else
love.graphics.setColor( 1.0, 1.0, 1.0, 0.4 )
love.graphics.setFont(smallFont)
love.graphics.printf("Loading weather...", 0, love.graphics.getHeight() - 200, love.graphics.getWidth(), 'center')
end
end
return node

View File

@ -0,0 +1,32 @@
local socket = require("socket")
local https = require("https")
local json = require("vendor.json")
local weatherURL = 'https://openweathermap.org/data/2.5/weather?id=6695624&units=metric&appid=439d4b804bc8187953eb36d2a8c26a02'
--local insideURL = 'https://dht01.waw.hackerspace.pl/'
--local insideData = {}
local c, r, h = https.request(weatherURL)
if c == 200 then
local data = json.decode(r)
--local r, c, h = http.request(insideURL)
--if c == 200 then
-- for n in string.gmatch(string.gsub(r, ",", "."), ":%s*(%S+)[*%%]") do
-- insideData[#insideData+1] = n
-- end
--end
love.thread.getChannel('weather'):push({
weather = data.weather[1].main:lower(),
temperature = data.main.temp,
lastUpdate = data.dt,
sunRise = data.sys.sunrise,
sunSet = data.sys.sunset,
insideTemperature = nil,
insideHumidity = nil,
})
print("Update finished")
else
print("Update failed")
end

31
hswaw/signage/vendor/30log.lua vendored Normal file
View File

@ -0,0 +1,31 @@
local next, assert, pairs, type, tostring, setmetatable, baseMt, _instances, _classes, _class = next, assert, pairs, type, tostring, setmetatable, {}, setmetatable({},{__mode = 'k'}), setmetatable({},{__mode = 'k'})
local function assert_call_from_class(class, method) assert(_classes[class], ('Wrong method call. Expected class:%s.'):format(method)) end; local function assert_call_from_instance(instance, method) assert(_instances[instance], ('Wrong method call. Expected instance:%s.'):format(method)) end
local function bind(f, v) return function(...) return f(v, ...) end end
local default_filter = function() return true end
local function deep_copy(t, dest, aType) t = t or {}; local r = dest or {}; for k,v in pairs(t) do if aType ~= nil and type(v) == aType then r[k] = (type(v) == 'table') and ((_classes[v] or _instances[v]) and v or deep_copy(v)) or v elseif aType == nil then r[k] = (type(v) == 'table') and k~= '__index' and ((_classes[v] or _instances[v]) and v or deep_copy(v)) or v end; end return r end
local function instantiate(call_init,self,...) assert_call_from_class(self, 'new(...) or class(...)'); local instance = {class = self}; _instances[instance] = tostring(instance); deep_copy(self, instance, 'table')
instance.__index, instance.__subclasses, instance.__instances, instance.mixins = nil, nil, nil, nil; setmetatable(instance,self); if call_init and self.init then if type(self.init) == 'table' then deep_copy(self.init, instance) else self.init(instance, ...) end end; return instance
end
local function extend(self, name, extra_params)
assert_call_from_class(self, 'extend(...)'); local heir = {}; _classes[heir] = tostring(heir); self.__subclasses[heir] = true; deep_copy(extra_params, deep_copy(self, heir))
heir.name, heir.__index, heir.super, heir.mixins = extra_params and extra_params.name or name, heir, self, {}; return setmetatable(heir,self)
end
baseMt = { __call = function (self,...) return self:new(...) end, __tostring = function(self,...)
if _instances[self] then return ("instance of '%s' (%s)"):format(rawget(self.class,'name') or '?', _instances[self]) end; return _classes[self] and ("class '%s' (%s)"):format(rawget(self,'name') or '?', _classes[self]) or self end
}; _classes[baseMt] = tostring(baseMt); setmetatable(baseMt, {__tostring = baseMt.__tostring})
local class = {isClass = function(t) return not not _classes[t] end, isInstance = function(t) return not not _instances[t] end}
_class = function(name, attr) local c = deep_copy(attr); _classes[c] = tostring(c)
c.name, c.__tostring, c.__call, c.new, c.create, c.extend, c.__index, c.mixins, c.__instances, c.__subclasses = name or c.name, baseMt.__tostring, baseMt.__call, bind(instantiate, true), bind(instantiate, false), extend, c, setmetatable({},{__mode = 'k'}), setmetatable({},{__mode = 'k'}), setmetatable({},{__mode = 'k'})
c.subclasses = function(self, filter, ...) assert_call_from_class(self, 'subclasses(class)'); filter = filter or default_filter; local subclasses = {}; for class in pairs(_classes) do if class ~= baseMt and class:subclassOf(self) and filter(class,...) then subclasses[#subclasses + 1] = class end end; return subclasses end
c.instances = function(self, filter, ...) assert_call_from_class(self, 'instances(class)'); filter = filter or default_filter; local instances = {}; for instance in pairs(_instances) do if instance:instanceOf(self) and filter(instance, ...) then instances[#instances + 1] = instance end end; return instances end
c.subclassOf = function(self, superclass) assert_call_from_class(self, 'subclassOf(superclass)'); assert(class.isClass(superclass), 'Wrong argument given to method "subclassOf()". Expected a class.'); local super = self.super; while super do if super == superclass then return true end; super = super.super end; return false end
c.classOf = function(self, subclass) assert_call_from_class(self, 'classOf(subclass)'); assert(class.isClass(subclass), 'Wrong argument given to method "classOf()". Expected a class.'); return subclass:subclassOf(self) end
c.instanceOf = function(self, fromclass) assert_call_from_instance(self, 'instanceOf(class)'); assert(class.isClass(fromclass), 'Wrong argument given to method "instanceOf()". Expected a class.'); return ((self.class == fromclass) or (self.class:subclassOf(fromclass))) end
c.cast = function(self, toclass) assert_call_from_instance(self, 'instanceOf(class)'); assert(class.isClass(toclass), 'Wrong argument given to method "cast()". Expected a class.'); setmetatable(self, toclass); self.class = toclass; return self end
c.with = function(self,...) assert_call_from_class(self, 'with(mixin)'); for _, mixin in ipairs({...}) do assert(self.mixins[mixin] ~= true, ('Attempted to include a mixin which was already included in %s'):format(tostring(self))); self.mixins[mixin] = true; deep_copy(mixin, self, 'function') end return self end
c.includes = function(self, mixin) assert_call_from_class(self,'includes(mixin)'); return not not (self.mixins[mixin] or (self.super and self.super:includes(mixin))) end
c.without = function(self, ...) assert_call_from_class(self, 'without(mixin)'); for _, mixin in ipairs({...}) do
assert(self.mixins[mixin] == true, ('Attempted to remove a mixin which is not included in %s'):format(tostring(self))); local classes = self:subclasses(); classes[#classes + 1] = self
for _, class in ipairs(classes) do for method_name, method in pairs(mixin) do if type(method) == 'function' then class[method_name] = nil end end end; self.mixins[mixin] = nil end; return self end; return setmetatable(c, baseMt) end
class._DESCRIPTION = '30 lines library for object orientation in Lua'; class._VERSION = '30log v1.2.0'; class._URL = 'http://github.com/Yonaba/30log'; class._LICENSE = 'MIT LICENSE <http://www.opensource.org/licenses/mit-license.php>'
return setmetatable(class,{__call = function(_,...) return _class(...) end })

132
hswaw/signage/vendor/debugGraph.lua vendored Normal file
View File

@ -0,0 +1,132 @@
--[[
UNLICENSE
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTBILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT, IN NO EVENT SHALL THE AUTHORS BE
LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
]]
-- Code based on https://github.com/icrawler/FPSGraph
local debugGraph = {}
function debugGraph:new(type, x, y, width, height, delay, label, font)
if ({mem=0, fps=0, custom=0})[type] == nil then
error('Acceptable types: mem, fps, custom')
end
local instance = {
x = x or 0, -- X position
y = y or 0, -- Y position
width = width or 50, -- Graph width
height = height or 30, -- Graph height
delay = delay or 0.5, -- Update delay
label = label or type, -- Graph label
font = font or love.graphics.newFont(8),
data = {},
_max = 0,
_time = 0,
_type = type,
}
-- Build base data
for i = 0, math.floor(instance.width / 2) do
table.insert(instance.data, 0)
end
-- Updating the graph
function instance:update(dt, val)
local lastTime = self._time
self._time = (self._time + dt) % self.delay
-- Check if the minimum amount of time has past
if dt > self.delay or lastTime > self._time then
-- Fetch data if needed
if val == nil then
if self._type == 'fps' then
-- Collect fps info and update the label
val = 0.75 * 1 / dt + 0.25 * love.timer.getFPS()
self.label = "FPS: " .. math.floor(val * 10) / 10
elseif self._type == 'mem' then
-- Collect memory info and update the label
val = collectgarbage('count')
self.label = "Memory (KB): " .. math.floor(val * 10) / 10
else
-- If the val is nil then we'll just skip this time
return
end
end
-- pop the old data and push new data
table.remove(self.data, 1)
table.insert(self.data, val)
-- Find the highest value
local max = 0
for i=1, #self.data do
local v = self.data[i]
if v > max then
max = v
end
end
self._max = max
end
end
function instance:draw()
-- Store the currently set font and change the font to our own
local fontCache = love.graphics.getFont()
love.graphics.setFont(self.font)
local max = math.ceil(self._max/10) * 10 + 20
local len = #self.data
local steps = self.width / len
-- Build the line data
local lineData = {}
for i=1, len do
-- Build the X and Y of the point
local x = steps * (i - 1) + self.x
local y = self.height * (-self.data[i] / max + 1) + self.y
-- Append it to the line
table.insert(lineData, x)
table.insert(lineData, y)
end
-- Draw the line
love.graphics.line(unpack(lineData))
-- Print the label
if self.label ~= '' then
love.graphics.print(self.label, self.x, self.y + self.height - self.font:getHeight())
end
-- Reset the font
love.graphics.setFont(fontCache)
end
return instance
end
return debugGraph

341
hswaw/signage/vendor/inspect.lua vendored Normal file
View File

@ -0,0 +1,341 @@
local inspect ={
_VERSION = 'inspect.lua 3.1.0',
_URL = 'http://github.com/kikito/inspect.lua',
_DESCRIPTION = 'human-readable representations of tables',
_LICENSE = [[
MIT LICENSE
Copyright (c) 2013 Enrique García Cota
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
}
local tostring = tostring
inspect.KEY = setmetatable({}, {__tostring = function() return 'inspect.KEY' end})
inspect.METATABLE = setmetatable({}, {__tostring = function() return 'inspect.METATABLE' end})
-- Apostrophizes the string if it has quotes, but not aphostrophes
-- Otherwise, it returns a regular quoted string
local function smartQuote(str)
if str:match('"') and not str:match("'") then
return "'" .. str .. "'"
end
return '"' .. str:gsub('"', '\\"') .. '"'
end
-- \a => '\\a', \0 => '\\0', 31 => '\31'
local shortControlCharEscapes = {
["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n",
["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v"
}
local longControlCharEscapes = {} -- \a => nil, \0 => \000, 31 => \031
for i=0, 31 do
local ch = string.char(i)
if not shortControlCharEscapes[ch] then
shortControlCharEscapes[ch] = "\\"..i
longControlCharEscapes[ch] = string.format("\\%03d", i)
end
end
local function escape(str)
return (str:gsub("\\", "\\\\")
:gsub("(%c)%f[0-9]", longControlCharEscapes)
:gsub("%c", shortControlCharEscapes))
end
local function isIdentifier(str)
return type(str) == 'string' and str:match( "^[_%a][_%a%d]*$" )
end
local function isSequenceKey(k, sequenceLength)
return type(k) == 'number'
and 1 <= k
and k <= sequenceLength
and math.floor(k) == k
end
local defaultTypeOrders = {
['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4,
['function'] = 5, ['userdata'] = 6, ['thread'] = 7
}
local function sortKeys(a, b)
local ta, tb = type(a), type(b)
-- strings and numbers are sorted numerically/alphabetically
if ta == tb and (ta == 'string' or ta == 'number') then return a < b end
local dta, dtb = defaultTypeOrders[ta], defaultTypeOrders[tb]
-- Two default types are compared according to the defaultTypeOrders table
if dta and dtb then return defaultTypeOrders[ta] < defaultTypeOrders[tb]
elseif dta then return true -- default types before custom ones
elseif dtb then return false -- custom types after default ones
end
-- custom types are sorted out alphabetically
return ta < tb
end
-- For implementation reasons, the behavior of rawlen & # is "undefined" when
-- tables aren't pure sequences. So we implement our own # operator.
local function getSequenceLength(t)
local len = 1
local v = rawget(t,len)
while v ~= nil do
len = len + 1
v = rawget(t,len)
end
return len - 1
end
local function getNonSequentialKeys(t)
local keys = {}
local sequenceLength = getSequenceLength(t)
for k,_ in pairs(t) do
if not isSequenceKey(k, sequenceLength) then table.insert(keys, k) end
end
table.sort(keys, sortKeys)
return keys, sequenceLength
end
local function getToStringResultSafely(t, mt)
local __tostring = type(mt) == 'table' and rawget(mt, '__tostring')
local str, ok
if type(__tostring) == 'function' then
ok, str = pcall(__tostring, t)
str = ok and str or 'error: ' .. tostring(str)
end
if type(str) == 'string' and #str > 0 then return str end
end
local function countTableAppearances(t, tableAppearances)
tableAppearances = tableAppearances or {}
if type(t) == 'table' then
if not tableAppearances[t] then
tableAppearances[t] = 1
for k,v in pairs(t) do
countTableAppearances(k, tableAppearances)
countTableAppearances(v, tableAppearances)
end
countTableAppearances(getmetatable(t), tableAppearances)
else
tableAppearances[t] = tableAppearances[t] + 1
end
end
return tableAppearances
end
local copySequence = function(s)
local copy, len = {}, #s
for i=1, len do copy[i] = s[i] end
return copy, len
end
local function makePath(path, ...)
local keys = {...}
local newPath, len = copySequence(path)
for i=1, #keys do
newPath[len + i] = keys[i]
end
return newPath
end
local function processRecursive(process, item, path, visited)
if item == nil then return nil end
if visited[item] then return visited[item] end
local processed = process(item, path)
if type(processed) == 'table' then
local processedCopy = {}
visited[item] = processedCopy
local processedKey
for k,v in pairs(processed) do
processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited)
if processedKey ~= nil then
processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited)
end
end
local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited)
setmetatable(processedCopy, mt)
processed = processedCopy
end
return processed
end
-------------------------------------------------------------------
local Inspector = {}
local Inspector_mt = {__index = Inspector}
function Inspector:puts(...)
local args = {...}
local buffer = self.buffer
local len = #buffer
for i=1, #args do
len = len + 1
buffer[len] = args[i]
end
end
function Inspector:down(f)
self.level = self.level + 1
f()
self.level = self.level - 1
end
function Inspector:tabify()
self:puts(self.newline, string.rep(self.indent, self.level))
end
function Inspector:alreadyVisited(v)
return self.ids[v] ~= nil
end
function Inspector:getId(v)
local id = self.ids[v]
if not id then
local tv = type(v)
id = (self.maxIds[tv] or 0) + 1
self.maxIds[tv] = id
self.ids[v] = id
end
return tostring(id)
end
function Inspector:putKey(k)
if isIdentifier(k) then return self:puts(k) end
self:puts("[")
self:putValue(k)
self:puts("]")
end
function Inspector:putTable(t)
if t == inspect.KEY or t == inspect.METATABLE then
self:puts(tostring(t))
elseif self:alreadyVisited(t) then
self:puts('<table ', self:getId(t), '>')
elseif self.level >= self.depth then
self:puts('{...}')
else
if self.tableAppearances[t] > 1 then self:puts('<', self:getId(t), '>') end
local nonSequentialKeys, sequenceLength = getNonSequentialKeys(t)
local mt = getmetatable(t)
local toStringResult = getToStringResultSafely(t, mt)
self:puts('{')
self:down(function()
if toStringResult then
self:puts(' -- ', escape(toStringResult))
if sequenceLength >= 1 then self:tabify() end
end
local count = 0
for i=1, sequenceLength do
if count > 0 then self:puts(',') end
self:puts(' ')
self:putValue(t[i])
count = count + 1
end
for _,k in ipairs(nonSequentialKeys) do
if count > 0 then self:puts(',') end
self:tabify()
self:putKey(k)
self:puts(' = ')
self:putValue(t[k])
count = count + 1
end
if mt then
if count > 0 then self:puts(',') end
self:tabify()
self:puts('<metatable> = ')
self:putValue(mt)
end
end)
if #nonSequentialKeys > 0 or mt then -- result is multi-lined. Justify closing }
self:tabify()
elseif sequenceLength > 0 then -- array tables have one extra space before closing }
self:puts(' ')
end
self:puts('}')
end
end
function Inspector:putValue(v)
local tv = type(v)
if tv == 'string' then
self:puts(smartQuote(escape(v)))
elseif tv == 'number' or tv == 'boolean' or tv == 'nil' then
self:puts(tostring(v))
elseif tv == 'table' then
self:putTable(v)
else
self:puts('<',tv,' ',self:getId(v),'>')
end
end
-------------------------------------------------------------------
function inspect.inspect(root, options)
options = options or {}
local depth = options.depth or math.huge
local newline = options.newline or '\n'
local indent = options.indent or ' '
local process = options.process
if process then
root = processRecursive(process, root, {}, {})
end
local inspector = setmetatable({
depth = depth,
level = 0,
buffer = {},
ids = {},
maxIds = {},
newline = newline,
indent = indent,
tableAppearances = countTableAppearances(root)
}, Inspector_mt)
inspector:putValue(root)
return table.concat(inspector.buffer)
end
setmetatable(inspect, { __call = function(_, ...) return inspect.inspect(...) end })
return inspect

380
hswaw/signage/vendor/json.lua vendored Normal file
View File

@ -0,0 +1,380 @@
--
-- json.lua
--
-- Copyright (c) 2015 rxi
--
-- This library is free software; you can redistribute it and/or modify it
-- under the terms of the MIT license. See LICENSE for details.
--
local json = { _version = "0.1.0" }
-------------------------------------------------------------------------------
-- Encode
-------------------------------------------------------------------------------
local encode
local escape_char_map = {
[ "\\" ] = "\\\\",
[ "\"" ] = "\\\"",
[ "\b" ] = "\\b",
[ "\f" ] = "\\f",
[ "\n" ] = "\\n",
[ "\r" ] = "\\r",
[ "\t" ] = "\\t",
}
local escape_char_map_inv = { [ "\\/" ] = "/" }
for k, v in pairs(escape_char_map) do
escape_char_map_inv[v] = k
end
local function escape_char(c)
return escape_char_map[c] or string.format("\\u%04x", c:byte())
end
local function encode_nil(val)
return "null"
end
local function encode_table(val, stack)
local res = {}
stack = stack or {}
-- Circular reference?
if stack[val] then error("circular reference") end
stack[val] = true
if val[1] ~= nil or next(val) == nil then
-- Treat as array -- check keys are valid and it is not sparse
local n = 0
for k in pairs(val) do
if type(k) ~= "number" then
error("invalid table: mixed or invalid key types")
end
n = n + 1
end
if n ~= #val then
error("invalid table: sparse array")
end
-- Encode
for i, v in ipairs(val) do
table.insert(res, encode(v, stack))
end
stack[val] = nil
return "[" .. table.concat(res, ",") .. "]"
else
-- Treat as an object
for k, v in pairs(val) do
if type(k) ~= "string" then
error("invalid table: mixed or invalid key types")
end
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
end
stack[val] = nil
return "{" .. table.concat(res, ",") .. "}"
end
end
local function encode_string(val)
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
end
local function encode_number(val)
-- Check for NaN, -inf and inf
if val ~= val or val <= -math.huge or val >= math.huge then
error("unexpected number value '" .. tostring(val) .. "'")
end
return string.format("%.14g", val)
end
local type_func_map = {
[ "nil" ] = encode_nil,
[ "table" ] = encode_table,
[ "string" ] = encode_string,
[ "number" ] = encode_number,
[ "boolean" ] = tostring,
}
encode = function(val, stack)
local t = type(val)
local f = type_func_map[t]
if f then
return f(val, stack)
end
error("unexpected type '" .. t .. "'")
end
function json.encode(val)
return ( encode(val) )
end
-------------------------------------------------------------------------------
-- Decode
-------------------------------------------------------------------------------
local parse
local function create_set(...)
local res = {}
for i = 1, select("#", ...) do
res[ select(i, ...) ] = true
end
return res
end
local space_chars = create_set(" ", "\t", "\r", "\n")
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
local literals = create_set("true", "false", "null")
local literal_map = {
[ "true" ] = true,
[ "false" ] = false,
[ "null" ] = nil,
}
local function next_char(str, idx, set, negate)
for i = idx, #str do
if set[str:sub(i, i)] ~= negate then
return i
end
end
return #str + 1
end
local function decode_error(str, idx, msg)
local line_count = 1
local col_count = 1
for i = 1, idx - 1 do
col_count = col_count + 1
if str:sub(i, i) == "\n" then
line_count = line_count + 1
col_count = 1
end
end
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
end
local function codepoint_to_utf8(n)
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
local f = math.floor
if n <= 0x7f then
return string.char(n)
elseif n <= 0x7ff then
return string.char(f(n / 64) + 192, n % 64 + 128)
elseif n <= 0xffff then
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
elseif n <= 0x10ffff then
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
f(n % 4096 / 64) + 128, n % 64 + 128)
end
error( string.format("invalid unicode codepoint '%x'", n) )
end
local function parse_unicode_escape(s)
local n1 = tonumber( s:sub(3, 6), 16 )
local n2 = tonumber( s:sub(9, 12), 16 )
-- Surrogate pair?
if n2 then
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
else
return codepoint_to_utf8(n1)
end
end
local function parse_string(str, i)
local has_unicode_escape = false
local has_surrogate_escape = false
local has_escape = false
local last
for j = i + 1, #str do
local x = str:byte(j)
if x < 32 then
decode_error(str, j, "control character in string")
end
if last == 92 then -- "\\" (escape char)
if x == 117 then -- "u" (unicode escape sequence)
local hex = str:sub(j + 1, j + 5)
if not hex:find("%x%x%x%x") then
decode_error(str, j, "invalid unicode escape in string")
end
if hex:find("^[dD][89aAbB]") then
has_surrogate_escape = true
else
has_unicode_escape = true
end
else
local c = string.char(x)
if not escape_chars[c] then
decode_error(str, j, "invalid escape char '" .. c .. "' in string")
end
has_escape = true
end
last = nil
elseif x == 34 then -- '"' (end of string)
local s = str:sub(i + 1, j - 1)
if has_surrogate_escape then
s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
end
if has_unicode_escape then
s = s:gsub("\\u....", parse_unicode_escape)
end
if has_escape then
s = s:gsub("\\.", escape_char_map_inv)
end
return s, j + 1
else
last = x
end
end
decode_error(str, i, "expected closing quote for string")
end
local function parse_number(str, i)
local x = next_char(str, i, delim_chars)
local s = str:sub(i, x - 1)
local n = tonumber(s)
if not n then
decode_error(str, i, "invalid number '" .. s .. "'")
end
return n, x
end
local function parse_literal(str, i)
local x = next_char(str, i, delim_chars)
local word = str:sub(i, x - 1)
if not literals[word] then
decode_error(str, i, "invalid literal '" .. word .. "'")
end
return literal_map[word], x
end
local function parse_array(str, i)
local res = {}
local n = 1
i = i + 1
while 1 do
local x
i = next_char(str, i, space_chars, true)
-- Empty / end of array?
if str:sub(i, i) == "]" then
i = i + 1
break
end
-- Read token
x, i = parse(str, i)
res[n] = x
n = n + 1
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "]" then break end
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
end
return res, i
end
local function parse_object(str, i)
local res = {}
i = i + 1
while 1 do
local key, val
i = next_char(str, i, space_chars, true)
-- Empty / end of object?
if str:sub(i, i) == "}" then
i = i + 1
break
end
-- Read key
if str:sub(i, i) ~= '"' then
decode_error(str, i, "expected string for key")
end
key, i = parse(str, i)
-- Read ':' delimiter
i = next_char(str, i, space_chars, true)
if str:sub(i, i) ~= ":" then
decode_error(str, i, "expected ':' after key")
end
i = next_char(str, i + 1, space_chars, true)
-- Read value
val, i = parse(str, i)
-- Set
res[key] = val
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "}" then break end
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
end
return res, i
end
local char_func_map = {
[ '"' ] = parse_string,
[ "0" ] = parse_number,
[ "1" ] = parse_number,
[ "2" ] = parse_number,
[ "3" ] = parse_number,
[ "4" ] = parse_number,
[ "5" ] = parse_number,
[ "6" ] = parse_number,
[ "7" ] = parse_number,
[ "8" ] = parse_number,
[ "9" ] = parse_number,
[ "-" ] = parse_number,
[ "t" ] = parse_literal,
[ "f" ] = parse_literal,
[ "n" ] = parse_literal,
[ "[" ] = parse_array,
[ "{" ] = parse_object,
}
parse = function(str, idx)
local chr = str:sub(idx, idx)
local f = char_func_map[chr]
if f then
return f(str, idx)
end
decode_error(str, idx, "unexpected character '" .. chr .. "'")
end
function json.decode(str)
if type(str) ~= "string" then
error("expected argument of type string, got " .. type(str))
end
return ( parse(str, next_char(str, 1, space_chars, true)) )
end
return json

772
hswaw/signage/vendor/lume.lua vendored Normal file
View File

@ -0,0 +1,772 @@
--
-- lume
--
-- Copyright (c) 2016 rxi
--
-- This library is free software; you can redistribute it and/or modify it
-- under the terms of the MIT license. See LICENSE for details.
--
local lume = { _version = "2.2.3" }
local pairs, ipairs = pairs, ipairs
local type, assert, unpack = type, assert, unpack or table.unpack
local tostring, tonumber = tostring, tonumber
local math_floor = math.floor
local math_ceil = math.ceil
local math_atan2 = math.atan2 or math.atan
local math_sqrt = math.sqrt
local math_abs = math.abs
local noop = function()
end
local identity = function(x)
return x
end
local patternescape = function(str)
return str:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%1")
end
local absindex = function(len, i)
return i < 0 and (len + i + 1) or i
end
local iscallable = function(x)
if type(x) == "function" then return true end
local mt = getmetatable(x)
return mt and mt.__call ~= nil
end
local getiter = function(x)
if lume.isarray(x) then
return ipairs
elseif type(x) == "table" then
return pairs
end
error("expected table", 3)
end
local iteratee = function(x)
if x == nil then return identity end
if iscallable(x) then return x end
if type(x) == "table" then
return function(z)
for k, v in pairs(x) do
if z[k] ~= v then return false end
end
return true
end
end
return function(z) return z[x] end
end
function lume.clamp(x, min, max)
return x < min and min or (x > max and max or x)
end
function lume.round(x, increment)
if increment then return lume.round(x / increment) * increment end
return x >= 0 and math_floor(x + .5) or math_ceil(x - .5)
end
function lume.sign(x)
return x < 0 and -1 or 1
end
function lume.lerp(a, b, amount)
return a + (b - a) * lume.clamp(amount, 0, 1)
end
function lume.smooth(a, b, amount)
local t = lume.clamp(amount, 0, 1)
local m = t * t * (3 - 2 * t)
return a + (b - a) * m
end
function lume.pingpong(x)
return 1 - math_abs(1 - x % 2)
end
function lume.distance(x1, y1, x2, y2, squared)
local dx = x1 - x2
local dy = y1 - y2
local s = dx * dx + dy * dy
return squared and s or math_sqrt(s)
end
function lume.angle(x1, y1, x2, y2)
return math_atan2(y2 - y1, x2 - x1)
end
function lume.vector(angle, magnitude)
return math.cos(angle) * magnitude, math.sin(angle) * magnitude
end
function lume.random(a, b)
if not a then a, b = 0, 1 end
if not b then b = 0 end
return a + math.random() * (b - a)
end
function lume.randomchoice(t)
return t[math.random(#t)]
end
function lume.weightedchoice(t)
local sum = 0
for _, v in pairs(t) do
assert(v >= 0, "weight value less than zero")
sum = sum + v
end
assert(sum ~= 0, "all weights are zero")
local rnd = lume.random(sum)
for k, v in pairs(t) do
if rnd < v then return k end
rnd = rnd - v
end
end
function lume.isarray(x)
return (type(x) == "table" and x[1] ~= nil) and true or false
end
function lume.push(t, ...)
local n = select("#", ...)
for i = 1, n do
t[#t + 1] = select(i, ...)
end
return ...
end
function lume.remove(t, x)
local iter = getiter(t)
for i, v in iter(t) do
if v == x then
if lume.isarray(t) then
table.remove(t, i)
break
else
t[i] = nil
break
end
end
end
return x
end
function lume.clear(t)
local iter = getiter(t)
for k in iter(t) do
t[k] = nil
end
return t
end
function lume.extend(t, ...)
for i = 1, select("#", ...) do
local x = select(i, ...)
if x then
for k, v in pairs(x) do
t[k] = v
end
end
end
return t
end
function lume.shuffle(t)
local rtn = {}
for i = 1, #t do
local r = math.random(i)
if r ~= i then
rtn[i] = rtn[r]
end
rtn[r] = t[i]
end
return rtn
end
function lume.sort(t, comp)
local rtn = lume.clone(t)
if comp then
if type(comp) == "string" then
table.sort(rtn, function(a, b) return a[comp] < b[comp] end)
else
table.sort(rtn, comp)
end
else
table.sort(rtn)
end
return rtn
end
function lume.array(...)
local t = {}
for x in ... do t[#t + 1] = x end
return t
end
function lume.each(t, fn, ...)
local iter = getiter(t)
if type(fn) == "string" then
for _, v in iter(t) do v[fn](v, ...) end
else
for _, v in iter(t) do fn(v, ...) end
end
return t
end
function lume.map(t, fn)
fn = iteratee(fn)
local iter = getiter(t)
local rtn = {}
for k, v in iter(t) do rtn[k] = fn(v) end
return rtn
end
function lume.all(t, fn)
fn = iteratee(fn)
local iter = getiter(t)
for _, v in iter(t) do
if not fn(v) then return false end
end
return true
end
function lume.any(t, fn)
fn = iteratee(fn)
local iter = getiter(t)
for _, v in iter(t) do
if fn(v) then return true end
end
return false
end
function lume.reduce(t, fn, first)
local acc = first
local started = first and true or false
local iter = getiter(t)
for _, v in iter(t) do
if started then
acc = fn(acc, v)
else
acc = v
started = true
end
end
assert(started, "reduce of an empty table with no first value")
return acc
end
function lume.set(t)
local rtn = {}
for k in pairs(lume.invert(t)) do
rtn[#rtn + 1] = k
end
return rtn
end
function lume.filter(t, fn, retainkeys)
fn = iteratee(fn)
local iter = getiter(t)
local rtn = {}
if retainkeys then
for k, v in iter(t) do
if fn(v) then rtn[k] = v end
end
else
for _, v in iter(t) do
if fn(v) then rtn[#rtn + 1] = v end
end
end
return rtn
end
function lume.reject(t, fn, retainkeys)
fn = iteratee(fn)
local iter = getiter(t)
local rtn = {}
if retainkeys then
for k, v in iter(t) do
if not fn(v) then rtn[k] = v end
end
else
for _, v in iter(t) do
if not fn(v) then rtn[#rtn + 1] = v end
end
end
return rtn
end
function lume.merge(...)
local rtn = {}
for i = 1, select("#", ...) do
local t = select(i, ...)
local iter = getiter(t)
for k, v in iter(t) do
rtn[k] = v
end
end
return rtn
end
function lume.concat(...)
local rtn = {}
for i = 1, select("#", ...) do
local t = select(i, ...)
if t ~= nil then
local iter = getiter(t)
for _, v in iter(t) do
rtn[#rtn + 1] = v
end
end
end
return rtn
end
function lume.find(t, value)
local iter = getiter(t)
for k, v in iter(t) do
if v == value then return k end
end
return nil
end
function lume.match(t, fn)
fn = iteratee(fn)
local iter = getiter(t)
for k, v in iter(t) do
if fn(v) then return v, k end
end
return nil
end
function lume.count(t, fn)
local count = 0
local iter = getiter(t)
if fn then
fn = iteratee(fn)
for _, v in iter(t) do
if fn(v) then count = count + 1 end
end
else
if lume.isarray(t) then
return #t
end
for _ in iter(t) do count = count + 1 end
end
return count
end
function lume.slice(t, i, j)
i = i and absindex(#t, i) or 1
j = j and absindex(#t, j) or #t
local rtn = {}
for x = i < 1 and 1 or i, j > #t and #t or j do
rtn[#rtn + 1] = t[x]
end
return rtn
end
function lume.first(t, n)
if not n then return t[1] end
return lume.slice(t, 1, n)
end
function lume.last(t, n)
if not n then return t[#t] end
return lume.slice(t, -n, -1)
end
function lume.invert(t)
local rtn = {}
for k, v in pairs(t) do rtn[v] = k end
return rtn
end
function lume.pick(t, ...)
local rtn = {}
for i = 1, select("#", ...) do
local k = select(i, ...)
rtn[k] = t[k]
end
return rtn
end
function lume.keys(t)
local rtn = {}
local iter = getiter(t)
for k in iter(t) do rtn[#rtn + 1] = k end
return rtn
end
function lume.clone(t)
local rtn = {}
for k, v in pairs(t) do rtn[k] = v end
return rtn
end
function lume.fn(fn, ...)
assert(iscallable(fn), "expected a function as the first argument")
local args = { ... }
return function(...)
local a = lume.concat(args, { ... })
return fn(unpack(a))
end
end
function lume.once(fn, ...)
local f = lume.fn(fn, ...)
local done = false
return function(...)
if done then return end
done = true
return f(...)
end
end
local memoize_fnkey = {}
local memoize_nil = {}
function lume.memoize(fn)
local cache = {}
return function(...)
local c = cache
for i = 1, select("#", ...) do
local a = select(i, ...) or memoize_nil
c[a] = c[a] or {}
c = c[a]
end
c[memoize_fnkey] = c[memoize_fnkey] or {fn(...)}
return unpack(c[memoize_fnkey])
end
end
function lume.combine(...)
local n = select('#', ...)
if n == 0 then return noop end
if n == 1 then
local fn = select(1, ...)
if not fn then return noop end
assert(iscallable(fn), "expected a function or nil")
return fn
end
local funcs = {}
for i = 1, n do
local fn = select(i, ...)
if fn ~= nil then
assert(iscallable(fn), "expected a function or nil")
funcs[#funcs + 1] = fn
end
end
return function(...)
for _, f in ipairs(funcs) do f(...) end
end
end
function lume.call(fn, ...)
if fn then
return fn(...)
end
end
function lume.time(fn, ...)
local start = os.clock()
local rtn = {fn(...)}
return (os.clock() - start), unpack(rtn)
end
local lambda_cache = {}
function lume.lambda(str)
if not lambda_cache[str] then
local args, body = str:match([[^([%w,_ ]-)%->(.-)$]])
assert(args and body, "bad string lambda")
local s = "return function(" .. args .. ")\nreturn " .. body .. "\nend"
lambda_cache[str] = lume.dostring(s)
end
return lambda_cache[str]
end
local serialize
local serialize_map = {
[ "boolean" ] = tostring,
[ "nil" ] = tostring,
[ "string" ] = function(v) return string.format("%q", v) end,
[ "number" ] = function(v)
if v ~= v then return "0/0" -- nan
elseif v == 1 / 0 then return "1/0" -- inf
elseif v == -1 / 0 then return "-1/0" end -- -inf
return tostring(v)
end,
[ "table" ] = function(t, stk)
stk = stk or {}
if stk[t] then error("circular reference") end
local rtn = {}
stk[t] = true
for k, v in pairs(t) do
rtn[#rtn + 1] = "[" .. serialize(k, stk) .. "]=" .. serialize(v, stk)
end
stk[t] = nil
return "{" .. table.concat(rtn, ",") .. "}"
end
}
setmetatable(serialize_map, {
__index = function(_, k) error("unsupported serialize type: " .. k) end
})
serialize = function(x, stk)
return serialize_map[type(x)](x, stk)
end
function lume.serialize(x)
return serialize(x)
end
function lume.deserialize(str)
return lume.dostring("return " .. str)
end
function lume.split(str, sep)
if not sep then
return lume.array(str:gmatch("([%S]+)"))
else
assert(sep ~= "", "empty separator")
local psep = patternescape(sep)
return lume.array((str..sep):gmatch("(.-)("..psep..")"))
end
end
function lume.trim(str, chars)
if not chars then return str:match("^[%s]*(.-)[%s]*$") end
chars = patternescape(chars)
return str:match("^[" .. chars .. "]*(.-)[" .. chars .. "]*$")
end
function lume.wordwrap(str, limit)
limit = limit or 72
local check
if type(limit) == "number" then
check = function(s) return #s >= limit end
else
check = limit
end
local rtn = {}
local line = ""
for word, spaces in str:gmatch("(%S+)(%s*)") do
local s = line .. word
if check(s) then
table.insert(rtn, line .. "\n")
line = word
else
line = s
end
for c in spaces:gmatch(".") do
if c == "\n" then
table.insert(rtn, line .. "\n")
line = ""
else
line = line .. c
end
end
end
table.insert(rtn, line)
return table.concat(rtn)
end
function lume.format(str, vars)
if not vars then return str end
local f = function(x)
return tostring(vars[x] or vars[tonumber(x)] or "{" .. x .. "}")
end
return (str:gsub("{(.-)}", f))
end
function lume.trace(...)
local info = debug.getinfo(2, "Sl")
local t = { info.short_src .. ":" .. info.currentline .. ":" }
for i = 1, select("#", ...) do
local x = select(i, ...)
if type(x) == "number" then
x = string.format("%g", lume.round(x, .01))
end
t[#t + 1] = tostring(x)
end
print(table.concat(t, " "))
end
function lume.dostring(str)
return assert((loadstring or load)(str))()
end
function lume.uuid()
local fn = function(x)
local r = math.random(16) - 1
r = (x == "x") and (r + 1) or (r % 4) + 9
return ("0123456789abcdef"):sub(r, r)
end
return (("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"):gsub("[xy]", fn))
end
function lume.hotswap(modname)
local oldglobal = lume.clone(_G)
local updated = {}
local function update(old, new)
if updated[old] then return end
updated[old] = true
local oldmt, newmt = getmetatable(old), getmetatable(new)
if oldmt and newmt then update(oldmt, newmt) end
for k, v in pairs(new) do
if type(v) == "table" then update(old[k], v) else old[k] = v end
end
end
local err = nil
local function onerror(e)
for k in pairs(_G) do _G[k] = oldglobal[k] end
err = lume.trim(e)
end
local ok, oldmod = pcall(require, modname)
oldmod = ok and oldmod or nil
xpcall(function()
package.loaded[modname] = nil
local newmod = require(modname)
if type(oldmod) == "table" then update(oldmod, newmod) end
for k, v in pairs(oldglobal) do
if v ~= _G[k] and type(v) == "table" then
update(v, _G[k])
_G[k] = v
end
end
end, onerror)
package.loaded[modname] = oldmod
if err then return nil, err end
return oldmod
end
local ripairs_iter = function(t, i)
i = i - 1
local v = t[i]
if v then return i, v end
end
function lume.ripairs(t)
return ripairs_iter, t, (#t + 1)
end
function lume.color(str, mul)
mul = mul or 1
local r, g, b, a
r, g, b = str:match("#(%x%x)(%x%x)(%x%x)")
if r then
r = tonumber(r, 16) / 0xff
g = tonumber(g, 16) / 0xff
b = tonumber(b, 16) / 0xff
a = 1
elseif str:match("rgba?%s*%([%d%s%.,]+%)") then
local f = str:gmatch("[%d.]+")
r = (f() or 0) / 0xff
g = (f() or 0) / 0xff
b = (f() or 0) / 0xff
a = f() or 1
else
error(("bad color string '%s'"):format(str))
end
return r * mul, g * mul, b * mul, a * mul
end
function lume.rgba(color)
local a = math_floor((color / 16777216) % 256)
local r = math_floor((color / 65536) % 256)
local g = math_floor((color / 256) % 256)
local b = math_floor((color) % 256)
return r, g, b, a
end
local chain_mt = {}
chain_mt.__index = lume.map(lume.filter(lume, iscallable, true),
function(fn)
return function(self, ...)
self._value = fn(self._value, ...)
return self
end
end)
chain_mt.__index.result = function(x) return x._value end
function lume.chain(value)
return setmetatable({ _value = value }, chain_mt)
end
setmetatable(lume, {
__call = function(_, ...)
return lume.chain(...)
end
})
return lume

269
hswaw/signage/vendor/lurker.lua vendored Normal file
View File

@ -0,0 +1,269 @@
--
-- lurker
--
-- Copyright (c) 2018 rxi
--
-- This library is free software; you can redistribute it and/or modify it
-- under the terms of the MIT license. See LICENSE for details.
--
-- Assumes lume is in the same directory as this file if it does not exist
-- as a global
local lume = rawget(_G, "lume") or require((...):gsub("[^/.\\]+$", "lume"))
local lurker = { _version = "1.0.1" }
local dir = love.filesystem.enumerate or love.filesystem.getDirectoryItems
local time = love.timer.getTime or os.time
local function isdir(path)
local info = love.filesystem.getInfo(path)
return info.type == "directory"
end
local function lastmodified(path)
local info = love.filesystem.getInfo(path, "file")
return info.modtime
end
local lovecallbacknames = {
"update",
"load",
"draw",
"mousepressed",
"mousereleased",
"keypressed",
"keyreleased",
"focus",
"quit",
}
function lurker.init()
lurker.print("Initing lurker")
lurker.path = "."
lurker.preswap = function() end
lurker.postswap = function() end
lurker.interval = .5
lurker.protected = true
lurker.quiet = false
lurker.lastscan = 0
lurker.lasterrorfile = nil
lurker.files = {}
lurker.funcwrappers = {}
lurker.lovefuncs = {}
lurker.state = "init"
lume.each(lurker.getchanged(), lurker.resetfile)
return lurker
end
function lurker.print(...)
print("[lurker] " .. lume.format(...))
end
function lurker.listdir(path, recursive, skipdotfiles)
path = (path == ".") and "" or path
local function fullpath(x) return path .. "/" .. x end
local t = {}
for _, f in pairs(lume.map(dir(path), fullpath)) do
if not skipdotfiles or not f:match("/%.[^/]*$") then
if recursive and isdir(f) then
t = lume.concat(t, lurker.listdir(f, true, true))
else
table.insert(t, lume.trim(f, "/"))
end
end
end
return t
end
function lurker.initwrappers()
for _, v in pairs(lovecallbacknames) do
lurker.funcwrappers[v] = function(...)
local args = {...}
xpcall(function()
return lurker.lovefuncs[v] and lurker.lovefuncs[v](unpack(args))
end, lurker.onerror)
end
lurker.lovefuncs[v] = love[v]
end
lurker.updatewrappers()
end
function lurker.updatewrappers()
for _, v in pairs(lovecallbacknames) do
if love[v] ~= lurker.funcwrappers[v] then
lurker.lovefuncs[v] = love[v]
love[v] = lurker.funcwrappers[v]
end
end
end
function lurker.onerror(e, nostacktrace)
lurker.print("An error occurred; switching to error state")
lurker.state = "error"
-- Release mouse
local setgrab = love.mouse.setGrab or love.mouse.setGrabbed
setgrab(false)
-- Set up callbacks
for _, v in pairs(lovecallbacknames) do
love[v] = function() end
end
love.update = lurker.update
love.keypressed = function(k)
if k == "escape" then
lurker.print("Exiting...")
love.event.quit()
end
end
local stacktrace = nostacktrace and "" or
lume.trim((debug.traceback("", 2):gsub("\t", "")))
local msg = lume.format("{1}\n\n{2}", {e, stacktrace})
local colors = {
{ lume.color("#1e1e2c", 256) },
{ lume.color("#f0a3a3", 256) },
{ lume.color("#92b5b0", 256) },
{ lume.color("#66666a", 256) },
{ lume.color("#cdcdcd", 256) },
}
love.graphics.reset()
love.graphics.setFont(love.graphics.newFont(12))
love.draw = function()
local pad = 25
local width = love.graphics.getWidth()
local function drawhr(pos, color1, color2)
local animpos = lume.smooth(pad, width - pad - 8, lume.pingpong(time()))
if color1 then love.graphics.setColor(color1) end
love.graphics.rectangle("fill", pad, pos, width - pad*2, 1)
if color2 then love.graphics.setColor(color2) end
love.graphics.rectangle("fill", animpos, pos, 8, 1)
end
local function drawtext(str, x, y, color, limit)
love.graphics.setColor(color)
love.graphics[limit and "printf" or "print"](str, x, y, limit)
end
love.graphics.setBackgroundColor(colors[1])
love.graphics.clear()
drawtext("An error has occurred", pad, pad, colors[2])
drawtext("lurker", width - love.graphics.getFont():getWidth("lurker") -
pad, pad, colors[4])
drawhr(pad + 32, colors[4], colors[5])
drawtext("If you fix the problem and update the file the program will " ..
"resume", pad, pad + 46, colors[3])
drawhr(pad + 72, colors[4], colors[5])
drawtext(msg, pad, pad + 90, colors[5], width - pad * 2)
love.graphics.reset()
end
end
function lurker.exitinitstate()
lurker.state = "normal"
if lurker.protected then
lurker.initwrappers()
end
end
function lurker.exiterrorstate()
lurker.state = "normal"
for _, v in pairs(lovecallbacknames) do
love[v] = lurker.funcwrappers[v]
end
end
function lurker.update()
if lurker.state == "init" then
lurker.exitinitstate()
end
local diff = time() - lurker.lastscan
if diff > lurker.interval then
lurker.lastscan = lurker.lastscan + diff
local changed = lurker.scan()
if #changed > 0 and lurker.lasterrorfile then
local f = lurker.lasterrorfile
lurker.lasterrorfile = nil
lurker.hotswapfile(f)
end
end
end
function lurker.getchanged()
local function fn(f)
return f:match("%.lua$") and lurker.files[f] ~= lastmodified(f)
end
return lume.filter(lurker.listdir(lurker.path, true, true), fn)
end
function lurker.modname(f)
return (f:gsub("%.lua$", ""):gsub("[/\\]", "."))
end
function lurker.resetfile(f)
lurker.files[f] = lastmodified(f)
end
function lurker.hotswapfile(f)
lurker.print("Hotswapping '{1}'...", {f})
if lurker.state == "error" then
lurker.exiterrorstate()
end
if lurker.preswap(f) then
lurker.print("Hotswap of '{1}' aborted by preswap", {f})
lurker.resetfile(f)
return
end
local modname = lurker.modname(f)
local t, ok, err = lume.time(lume.hotswap, modname)
if ok then
lurker.print("Swapped '{1}' in {2} secs", {f, t})
else
lurker.print("Failed to swap '{1}' : {2}", {f, err})
if not lurker.quiet and lurker.protected then
lurker.lasterrorfile = f
lurker.onerror(err, true)
lurker.resetfile(f)
return
end
end
lurker.resetfile(f)
lurker.postswap(f)
if lurker.protected then
lurker.updatewrappers()
end
end
function lurker.scan()
if lurker.state == "init" then
lurker.exitinitstate()
end
local changed = lurker.getchanged()
lume.each(changed, lurker.hotswapfile)
return changed
end
return lurker.init()

131
hswaw/signage/vendor/push.lua vendored Normal file
View File

@ -0,0 +1,131 @@
-- push.lua v0.1
-- Copyright (c) 2016 Ulysse Ramage
-- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
local push = {}
setmetatable(push, push)
function push:setupScreen(WWIDTH, WHEIGHT, RWIDTH, RHEIGHT, f)
f = f or {}
self._WWIDTH, self._WHEIGHT = WWIDTH, WHEIGHT
self._RWIDTH, self._RHEIGHT = RWIDTH, RHEIGHT
self._fullscreen = f.fullscreen or self._fullscreen or false
self._resizable = f.resizable or self._resizable or false
if f.canvas == nil then f.canvas = true end
love.window.setMode( self._RWIDTH, self._RHEIGHT, {fullscreen = self._fullscreen, borderless = false, resizable = self._resizable} )
self:initValues()
if f.canvas then self:createCanvas() end
self._borderColor = {0, 0, 0}
self._drawFunctions = {["start"]=push.start, ["end"]=push.finish}
end
function push:createCanvas()
self._canvas = love.graphics.newCanvas(self._WWIDTH, self._WHEIGHT)
end
function push:initValues()
self._SCALEX, self._SCALEY = self._RWIDTH/self._WWIDTH, self._RHEIGHT/self._WHEIGHT
self._SCALE = math.min(self._SCALEX, self._SCALEY)
self._OFFSET = {x = (self._SCALEX - self._SCALE) * (self._WWIDTH/2), y = (self._SCALEY - self._SCALE) * (self._WHEIGHT/2)}
self._GWIDTH, self._GHEIGHT = self._RWIDTH-self._OFFSET.x*2, self._RHEIGHT-self._OFFSET.y*2
self._INV_SCALE = 1/self._SCALE
end
function push:setShader(shader)
self._shader = shader
end
--[[ DEPRECATED ]]--
function push:apply(operation, shader)
if operation == "start" then
self:start()
elseif operation == "finish" or operation == "end" then
self:finish(shader)
end
end
function push:start()
if self._canvas then
love.graphics.push()
love.graphics.setCanvas(self._canvas)
else
love.graphics.translate(self._OFFSET.x, self._OFFSET.y)
love.graphics.setScissor(self._OFFSET.x, self._OFFSET.y, self._WWIDTH*self._SCALE, self._WHEIGHT*self._SCALE)
love.graphics.push()
love.graphics.scale(self._SCALE)
end
end
function push:finish(shader)
love.graphics.setBackgroundColor(unpack(self._borderColor))
if self._canvas then
love.graphics.pop()
love.graphics.setCanvas()
love.graphics.translate(self._OFFSET.x, self._OFFSET.y)
love.graphics.setColor(1.0, 1.0, 1.0)
love.graphics.setShader(shader or self._shader)
love.graphics.draw(self._canvas, 0, 0, 0, self._SCALE, self._SCALE)
love.graphics.setCanvas(self._canvas)
love.graphics.clear()
love.graphics.setCanvas()
love.graphics.setShader()
else
love.graphics.pop()
love.graphics.setScissor()
end
end
function push:calculateScale(offset)
self._SCALEX, self._SCALEY = self._RWIDTH/self._WWIDTH, self._RHEIGHT/self._WHEIGHT
self._SCALE = math.min(self._SCALEX, self._SCALEY)+offset
self._OFFSET = {x = (self._SCALEX - self._SCALE) * (self._WWIDTH/2), y = (self._SCALEY - self._SCALE) * (self._WHEIGHT/2)}
end
function push:setBorderColor(color, g, b)
self._borderColor = g and {color, g, b} or color
end
function push:toGame(x, y)
x, y = x-self._OFFSET.x, y-self._OFFSET.y
local normalX, normalY = x/self._GWIDTH, y/self._GHEIGHT
x, y = (x>=0 and x<=self._WWIDTH*self._SCALE) and normalX*self._WWIDTH or nil, (y>=0 and y<=self._WHEIGHT*self._SCALE) and normalY*self._WHEIGHT or nil
return x, y
end
--doesn't work - TODO
function push:toReal(x, y)
return x+self._OFFSET.x, y+self._OFFSET.y
end
function push:switchFullscreen(winw, winh)
self._fullscreen = not self._fullscreen
local windowWidth, windowHeight = love.window.getDesktopDimensions()
self._RWIDTH = self._fullscreen and windowWidth or winw or windowWidth*.5
self._RHEIGHT = self._fullscreen and windowHeight or winh or windowHeight*.5
self:initValues()
love.window.setFullscreen(self._fullscreen, "desktop")
end
function push:resize(w, h)
self._RWIDTH = w
self._RHEIGHT = h
self:initValues()
end
function push:getWidth() return self._WWIDTH end
function push:getHeight() return self._WHEIGHT end
function push:getDimensions() return self._WWIDTH, self._WHEIGHT end
return push