3 Commits

Author SHA1 Message Date
4b39024091 Update randopix.nimble 2020-05-28 21:43:47 +02:00
9b8323349c Update build.yml 2020-05-23 19:02:11 +02:00
82232f0ccf Update build.yml 2020-05-23 18:57:21 +02:00
15 changed files with 190 additions and 476 deletions

30
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: Build randopix
# This workflow is triggered on pushes to the repository.
on:
push:
branches:
- build
jobs:
build:
name: Build 64 Bit Linux
# This job runs on Linux
runs-on: ubuntu-latest
steps:
- name: Install GTK packages
run: sudo apt install gir1.2-gtk-3.0 gir1.2-graphene-1.0 gir1.2-gtksource-3.0 gir1.2-vte-2.91 gir1.2-notify-0.7 gir1.2-gst-plugins-bad-1.0
- uses: actions/checkout@master
- name: Cache choosenim
id: cache-choosenim
uses: actions/cache@v1
with:
path: ~/.choosenim
key: ${{ runner.os }}-choosenim-stable
- name: Cache nimble
id: cache-nimble
uses: actions/cache@v1
with:
path: ~/.nimble
key: ${{ runner.os }}-nimble-stable
- uses: jiro4989/setup-nim-action@v1.0.2
- run: nimble build -Y

10
.gitignore vendored
View File

@@ -1,4 +1,12 @@
# Ignore all
*
# Unignore all with extensions
!*.*
# Unignore all dirs
!*/
### Above combination will ignore all files without extension ### ### Above combination will ignore all files without extension ###
src/resources/pixctrl.js
bin/ bin/
.vscode/ .vscode/

View File

@@ -1,9 +0,0 @@
[Unit]
Description=randopix image display
[Service]
Environment=DISPLAY=:0
ExecStart=randopix -d /mnt/pix -m file
[Install]
WantedBy=default.target

View File

@@ -1,29 +1,18 @@
import strformat
# Package # Package
version = "1.0.0" version = "0.1.0"
author = "luxick" author = "luxick"
description = "Play an image slide show from different sources" description = "Play an image slide show from different sources"
license = "GPL-2.0" license = "GPL-2.0"
srcDir = "src" srcDir = "src"
binDir = "bin"
bin = @["randopix", "pixctrl"] bin = @["randopix", "pixctrl"]
# Dependencies # Dependencies
requires "nim >= 1.0.0", "gintro", "argparse", "jester", "ajax" requires "nim >= 1.0.0", "gintro <= 0.5.5", "argparse >=0.10.1"
proc genJS =
echo "Generating JS Client"
exec("nim js -o:src/resources/pixctrl.js src/pixctrl.nim")
task genJS, "Generate the Javascript client":
genJS()
task buildAll, "Generate JS and run build":
genJS()
exec "nimble build"
task debug, "Compile debug version": task debug, "Compile debug version":
exec "nim c -d:debug --debugger:native -o:bin/randopix src/randopix.nim" exec "nim c -d:debug --debugger:native --out:randopix src/randopix.nim"
before install: task release, "Compile release version":
genJS() exec fmt"nim c -d:release --out:randopix-{version} src/randopix.nim"

View File

@@ -2,8 +2,8 @@ window {
background: black; background: black;
font-size: 30px; font-size: 30px;
} }
box { label {
background-color: rgba(255, 255, 255, .75); background-color: #fff;
border: 2px solid gray; border: 2px solid gray;
border-radius: 5px; border-radius: 5px;
margin: 10px; margin: 10px;

View File

@@ -1,5 +1,8 @@
import json import json
const
defaultPort* = 5555 ## Default port at which the control server will run
type type
OpResult* = object of RootObj ## Result object for signalling failure state across proc calls OpResult* = object of RootObj ## Result object for signalling failure state across proc calls
success*: bool ## Indicating if the opration was successfull success*: bool ## Indicating if the opration was successfull
@@ -21,18 +24,18 @@ type
command*: Command ## Command that the application should execute command*: Command ## Command that the application should execute
parameter*: string ## Optional parameter for the command parameter*: string ## Optional parameter for the command
proc `$`(cMsg: CommandMessage): string =
$(%cMsg)
proc newOpResult*(): OpResult = proc newOpResult*(): OpResult =
OpResult(success: true) OpResult(success: true)
proc newOpResult*(msg: string): OpResult = proc newOpResult*(msg: string): OpResult =
OpResult(success: false, errorMsg: msg) OpResult(success: false, errorMsg: msg)
proc newCommandMessage*(c: Command, p: string = ""): CommandMessage = proc newCommand*(c: Command, p: string = ""): CommandMessage =
CommandMessage(command: c, parameter: p) CommandMessage(command: c, parameter: p)
proc wrap*(msg: CommandMessage): string =
$(%msg) & "\r\L"
proc enumToStrings*(en: typedesc): seq[string] = proc enumToStrings*(en: typedesc): seq[string] =
for x in en: for x in en:
result.add $x result.add $x

View File

@@ -1,94 +1,52 @@
import strutils, json import strutils, net
import argparse
import common import common
when defined(js): const modeHelp = "Change the display mode. Possible values: [$1]" % Mode.enumToStrings().join(", ")
import ajax, jsconsole, dom
else:
import httpClient, strformat
import argparse
var randopixServer* {.exportc.}: string ## URL for the randopix server
proc sendCommand(msg: CommandMessage) = var socket = newSocket()
when defined(js):
console.log("Sending:", $msg, "to URL:", document.URL)
var req = newXMLHttpRequest()
proc processSend(e:Event) = proc sendCommand*(server, port: string, msg: CommandMessage) =
if req.readyState == rsDONE: socket.connect(server, Port(port.parseInt))
if req.status != 200: if not socket.trySend(msg.wrap):
console.log("There was a problem with the request.") echo "Cannot send command: ", msg
console.log($req.status, req.statusText) socket.close()
req.onreadystatechange = processSend proc switchMode*(server, port: string, mode: string) =
req.open("POST", document.URL)
req.send(cstring($(%*msg)))
else:
let client = newHttpClient()
let resp = client.post(randopixServer, $(%msg))
if not resp.status.contains("200"):
echo "Error while sending command: ", resp.status
proc sendCommand(cmd: Command) =
sendCommand(newCommandMessage(cmd))
proc switchMode*(mode: string) =
## Update the display mode
try: try:
discard parseEnum[Mode](mode) discard parseEnum[Mode](mode)
except ValueError: except ValueError:
echo "Invalid mode: ", mode echo "Invalid mode: ", mode
echo "Possible values: [$1]" % Mode.enumToStrings().join(", ") echo "Possible values: [$1]" % Mode.enumToStrings().join(", ")
return return
sendCommand(newCommandMessage(cMode, mode)) let c = newCommand(cMode, mode)
sendCommand(server, port, c)
proc refresh*() {.exportc.} = when isMainModule:
## Force refresh of the current image var p = newParser("pixctrl"):
sendCommand(cRefresh) help("Control utilitiy for randopix")
option("-s", "--server", help="Host running the randopix server", default="127.0.0.1")
option("-p", "--port", help="Port to connect to the randopix server", default = $defaultPort)
proc setTimeout*(seconds: string) = command($cRefresh):
## Set the image timeout to this value help("Force image refresh now")
sendCommand(newCommandMessage(cTimeout, seconds))
when defined(js):
proc getModes(): seq[cstring] {.exportc.} =
for mode in enumToStrings(Mode):
result.add cstring(mode)
proc switchMode*(mode: cstring) {.exportc.} =
switchMode($mode)
proc setTimeout*(seconds: cstring) {.exportc.} =
setTimeout($seconds)
else:
when isMainModule:
const modeHelp = "Change the display mode. Possible values: [$1]" % Mode.enumToStrings().join(", ")
var p = newParser("pixctrl"):
help("Control utilitiy for randopix")
option("-s", "--server", help="Host running the randopix server", default="http://localhost:8080/")
run: run:
if opts.server.startsWith("http://"): let c = newCommand(cRefresh)
randopixServer = opts.server sendCommand(opts.parentOpts.server, opts.parentOpts.port, c)
else:
randopixServer = fmt"http://{opts.server}"
command($cRefresh): command($cTimeout):
## Force refresh command help("Set timeout in seconds before a new image is displayed")
help("Force image refresh now") arg("seconds", default = "300")
run: run:
refresh() let c = newCommand(cTimeout, opts.seconds)
sendCommand(opts.parentOpts.server, opts.parentOpts.port, c)
command($cTimeout): command($cMode):
## Timeout Command help(modeHelp)
help("Set timeout in seconds before a new image is displayed") arg("mode")
arg("seconds", default = "300") run:
run: switchMode(opts.parentOpts.server, opts.parentOpts.port, opts.mode)
setTimeout(opts.seconds) try:
p.run(commandLineParams())
command($cMode): except:
## Mode switch command echo p.help
help(modeHelp)
arg("mode")
run:
switchMode(opts.mode)
try:
p.run(commandLineParams())
except:
echo getCurrentExceptionMsg()

View File

@@ -1,4 +1,4 @@
import os, sets, random, httpClient, json, strutils, strformat, options, deques, times import os, sets, random, httpClient, json, strformat, options
import gintro/[gdkpixbuf, gobject] import gintro/[gdkpixbuf, gobject]
import common import common
@@ -6,6 +6,7 @@ const
supportedExts = @[".png", ".jpg", ".jpeg"] supportedExts = @[".png", ".jpg", ".jpeg"]
foxesUrl = "https://randomfox.ca/floof/" foxesUrl = "https://randomfox.ca/floof/"
inspiroUrl = "http://inspirobot.me/api?generate=true" inspiroUrl = "http://inspirobot.me/api?generate=true"
tmpFile = "/tmp/randopix_tmp.png"
type type
FileOpResult* = object of OpResult FileOpResult* = object of OpResult
@@ -17,18 +18,17 @@ type
mode* : Mode ## Selects the API that is used to get images mode* : Mode ## Selects the API that is used to get images
path*: Option[string] ## Path on the local file syetem that will be used in `file` mode path*: Option[string] ## Path on the local file syetem that will be used in `file` mode
exts: HashSet[string] ## Allowed extensions that the `file` mode will display exts: HashSet[string] ## Allowed extensions that the `file` mode will display
files: seq[string] ## Currently loaded list of images in `file` mode
var var
client = newHttpClient() ## For loading images from the web client = newHttpClient() ## For loading images from the web
tmpDir = getTempDir() / "randopix"
tmpFile = tmpDir / "tmp.png"
fileList = initDeque[string]()
######################## ########################
# Constructors # Constructors
######################## ########################
proc newImageProvider(verbose: bool, mode: Mode, path: Option[string]): ImageProvider = proc newImageProvider(verbose: bool, mode: Mode, path: Option[string]): ImageProvider =
randomize()
ImageProvider(verbose: verbose, mode: mode, path: path, exts: supportedExts.toHashSet) ImageProvider(verbose: verbose, mode: mode, path: path, exts: supportedExts.toHashSet)
proc newImageProvider*(verbose: bool): ImageProvider = proc newImageProvider*(verbose: bool): ImageProvider =
@@ -53,9 +53,8 @@ proc newFileOpResult(file: string): FileOpResult =
# Utilities # Utilities
######################## ########################
proc log(ip: ImageProvider, things: varargs[string, `$`]) = proc log(ip: ImageProvider, msg: string) =
if ip.verbose: if ip.verbose: echo msg
echo things.join()
######################## ########################
# Image Provider procs # Image Provider procs
@@ -95,24 +94,20 @@ proc getLocalFile(ip: var ImageProvider): FileOpResult =
# First, check if there are still images left to be loaded. # First, check if there are still images left to be loaded.
# If not reread all files from the path # If not reread all files from the path
if fileList.len == 0: if ip.files.len < 1:
var tmp: seq[string] if ip.path.isNone:
var split: tuple[dir, name, ext: string] return newFileOpResultError("No path for image loading")
ip.log "Reloading file list..."
for file in walkDirRec(ip.path.get): for file in walkDirRec(ip.path.get):
split = splitFile(file) let split = splitFile(file)
if ip.exts.contains(split.ext): if ip.exts.contains(split.ext):
tmp.add($file) ip.files.add(file)
ip.log fmt"Loaded {ip.files.len} files"
shuffle(ip.files)
ip.log fmt"Loaded {tmp.len} files"
shuffle(tmp)
for file in tmp:
fileList.addLast(file)
if fileList.len == 0:
return newFileOpResultError("No files found")
let next = fileList.popFirst()
# Remove the current file after # Remove the current file after
result = newFileOpResult(next) result = newFileOpResult(ip.files[0])
ip.files.delete(0)
proc getFileName(ip: var ImageProvider): FileOpResult = proc getFileName(ip: var ImageProvider): FileOpResult =
## Get the temporary file name of the next file to display ## Get the temporary file name of the next file to display
@@ -148,10 +143,7 @@ proc next*(ip: var ImageProvider, width, height: int): FileOpResult =
else: else:
w = width w = width
h = ((rawPixbuf.height * w) / rawPixbuf.width).toInt h = ((rawPixbuf.height * w) / rawPixbuf.width).toInt
let then = now() var pixbuf = rawPixbuf.scaleSimple(w, h, InterpType.bilinear)
var pixbuf = rawPixbuf.scaleSimple(w, h, InterpType.nearest)
let now = now()
ip.log "Image scaled. Time: ", (now - then).inMilliseconds, "ms"
# The pixbuf is written to disk and loaded again once because # The pixbuf is written to disk and loaded again once because
# directly setting the image from a pixbuf will leak memory # directly setting the image from a pixbuf will leak memory
let saved = pixbuf.savev(tmpFile, "png", @[]) let saved = pixbuf.savev(tmpFile, "png", @[])
@@ -159,10 +151,7 @@ proc next*(ip: var ImageProvider, width, height: int): FileOpResult =
return newFileOpResultError("Error while saving temporary image") return newFileOpResultError("Error while saving temporary image")
# GTK pixbuf leaks memory when not manually decreasing reference count # GTK pixbuf leaks memory when not manually decreasing reference count
pixbuf.unref() pixbuf.genericGObjectUnref()
rawPixbuf.unref() rawPixbuf.genericGObjectUnref()
newFileOpResult(tmpFile) newFileOpResult(tmpFile)
createDir(tmpDir)
randomize()

View File

@@ -5,21 +5,14 @@ import argparse except run
import providers, server, common import providers, server, common
const const
css = slurp("resources/app.css") css = slurp("app.css")
version = slurp("version") version = "0.1"
helpString = [
"ESC\tClose program",
"H\tShow/Hide this help",
"F\tToggle fullscreen",
"U\tForce refresh"
].join("\n")
type type
Args = ref object Args = ref object
fullscreen: bool ## Applicaion is show in fullscreen mode fullscreen: bool ## Applicaion is show in fullscreen mode
verbose: bool ## More debug information in notification label verbose: bool ## More debug information in notification label
timeout: int ## Milliseconds between image refreshes timeout: int ## Milliseconds between image refreshes
port: int ## Port to host the control server
var var
imageProvider: ImageProvider ## Gets images from the chosen source imageProvider: ImageProvider ## Gets images from the chosen source
@@ -28,7 +21,6 @@ var
# Widgets # Widgets
window: ApplicationWindow window: ApplicationWindow
label: Label label: Label
box: Box
# Server vor recieving commands from external tools # Server vor recieving commands from external tools
serverWorker: system.Thread[ServerArgs] serverWorker: system.Thread[ServerArgs]
@@ -36,22 +28,21 @@ proc log(things: varargs[string, `$`]) =
if args.verbose: if args.verbose:
echo things.join() echo things.join()
proc notify(label: Label, things: varargs[string, `$`]) = proc notify(label: Label, message: string = "") =
## Shows the notification box in the lower left corner. ## Shows the notification box in the lower left corner.
## If no message is passed, the box will be hidden ## If no message is passed, the box will be hidden
label.text = things.join() label.text = message
if (label.text == ""): if (message == ""):
box.hide label.hide
else: else:
box.show label.show
proc newArgs(): Option[Args] = proc newArgs(): Option[Args] =
let p = newParser("randopix"): let p = newParser("randopix"):
help(fmt"Version {version} - Display random images from different sources") help(fmt"Version {version} - Display random images from different sources")
option("-m", "--mode", help="The image source mode.", choices=enumToStrings(Mode)) option("-m", "--mode", help="The image source mode.", choices=enumToStrings(Mode))
option("-d", "--directoy", help="Path to a directory with images for the 'file' mode") option("-p", "--path", help="Path to a directory with images for the 'file' mode")
option("-t", "--timeout", help="Seconds before the image is refreshed", default="300") option("-t", "--timeout", help="Seconds before the image is refreshed", default="300")
option("-p", "--port", help="Port over which the control server should be accessible", default="8080")
flag("-w", "--windowed", help="Do not start in fullscreen mode") flag("-w", "--windowed", help="Do not start in fullscreen mode")
flag("-v", "--verbose", help="Show more information") flag("-v", "--verbose", help="Show more information")
@@ -70,8 +61,8 @@ proc newArgs(): Option[Args] =
startMode = Mode.None startMode = Mode.None
# Create the image provider # Create the image provider
if opts.directoy != "": if opts.path != "":
imageProvider = newImageProvider(opts.verbose, startMode, opts.directoy) imageProvider = newImageProvider(opts.verbose, startMode, opts.path)
else: else:
imageProvider = newImageProvider(opts.verbose, startMode) imageProvider = newImageProvider(opts.verbose, startMode)
@@ -85,8 +76,7 @@ proc newArgs(): Option[Args] =
return some(Args( return some(Args(
fullscreen: not opts.windowed, fullscreen: not opts.windowed,
verbose: opts.verbose, verbose: opts.verbose,
timeout: timeout, timeout: timeout))
port: opts.port.parseInt))
except: except:
echo p.help echo p.help
@@ -116,19 +106,17 @@ proc updateImage(image: Image): bool =
e = getCurrentException() e = getCurrentException()
msg = getCurrentExceptionMsg() msg = getCurrentExceptionMsg()
log "Got exception ", repr(e), " with message ", msg log "Got exception ", repr(e), " with message ", msg
label.notify "Error while refreshing, retrying..." label.notify "Error while refreshing image, retrying..."
return false return false
proc timedUpdate(image: Image): bool = proc timedUpdate(image: Image): bool =
discard updateImage(image) discard updateImage(image);
# Force garbage collection now. Otherwise the RAM will fill up until the GC is triggered.
GC_fullCollect()
updateTimeout = int(timeoutAdd(uint32(args.timeout), timedUpdate, image)) updateTimeout = int(timeoutAdd(uint32(args.timeout), timedUpdate, image))
return false return false
proc forceUpdate(action: SimpleAction; parameter: Variant; image: Image): void = proc forceUpdate(action: SimpleAction; parameter: Variant; image: Image): void =
log "Refreshing..." log "Refreshing image..."
label.notify "Refreshing..." label.notify "Refreshing image..."
if updateTimeout > 0: if updateTimeout > 0:
discard updateTimeout.remove discard updateTimeout.remove
updateTimeout = int(timeoutAdd(500, timedUpdate, image)) updateTimeout = int(timeoutAdd(500, timedUpdate, image))
@@ -149,17 +137,14 @@ proc checkServerChannel(image: Image): bool =
let val = msg.parameter.parseInt * 1000 let val = msg.parameter.parseInt * 1000
log "Setting timeout to ", val log "Setting timeout to ", val
args.timeout = val args.timeout = val
if updateTimeout > 0: discard updateTimeout.remove
discard updateTimeout.remove
updateTimeout = int(timeoutAdd(uint32(args.timeout), timedUpdate, image)) updateTimeout = int(timeoutAdd(uint32(args.timeout), timedUpdate, image))
of cMode: of cMode:
try: try:
let mode = parseEnum[Mode](msg.parameter) let mode = parseEnum[Mode](msg.parameter)
imageProvider.mode = mode
forceUpdate(nil, nil, image)
log "Switching mode: ", mode log "Switching mode: ", mode
label.notify fmt"Switch Mode: {msg.parameter.capitalizeAscii()}" imageProvider.mode = mode
except ValueError: except ValueError:
log "Invalid mode: ", msg.parameter log "Invalid mode: ", msg.parameter
@@ -177,28 +162,19 @@ proc toggleFullscreen(action: SimpleAction; parameter: Variant; window: Applicat
window.fullscreen window.fullscreen
args.fullscreen = not args.fullscreen args.fullscreen = not args.fullscreen
proc toggleHelp(action: SimpleAction; parameter: Variant; box: Box) =
if box.visible:
box.hide
else:
box.show
proc cleanUp(w: ApplicationWindow, app: Application) = proc cleanUp(w: ApplicationWindow, app: Application) =
## Stop the control server and exit the GTK application ## Stop the control server and exit the GTK application
log "Stopping control server..."
closeServer()
serverWorker.joinThread()
chan.close() chan.close()
log "Server channel closed." log "Server stopped."
app.quit() app.quit()
proc quit(action: SimpleAction; parameter: Variant; app: Application) = proc quit(action: SimpleAction; parameter: Variant; app: Application) =
## Application quit event ## Application quit event
cleanUp(window, app) cleanUp(window, app)
proc hidePointer(window: ApplicationWindow): void =
## Hides the mouse pointer for the application.
let cur = window.getDisplay().newCursorForDisplay(CursorType.blankCursor)
let win = window.getWindow()
win.setCursor(cur)
proc appActivate(app: Application) = proc appActivate(app: Application) =
# Parse arguments from the command line # Parse arguments from the command line
let parsed = newArgs() let parsed = newArgs()
@@ -218,26 +194,12 @@ proc appActivate(app: Application) =
addProviderForScreen(getDefaultScreen(), provider, STYLE_PROVIDER_PRIORITY_USER) addProviderForScreen(getDefaultScreen(), provider, STYLE_PROVIDER_PRIORITY_USER)
# Create all windgets we are gonna use # Create all windgets we are gonna use
label = newLabel(fmt"Starting ('H' for help)...") label = newLabel("Starting...")
label.halign = Align.`end`
let spinner = newSpinner() label.valign = Align.`end`
spinner.start()
box = newBox(Orientation.horizontal, 2)
box.halign = Align.`end`
box.valign = Align.`end`
box.packStart(spinner, true, true, 10)
box.packStart(label, true, true, 0)
let helpText = newLabel(helpString)
let helpBox = newBox(Orientation.vertical, 0)
helpBox.packStart(helpText, true, true, 0)
helpBox.halign = Align.start
helpBox.valign = Align.start
let container = newOverlay() let container = newOverlay()
container.addOverlay(box) container.addOverlay(label)
container.addOverlay(helpBox)
window.add(container) window.add(container)
let image = newImage() let image = newImage()
@@ -247,34 +209,24 @@ proc appActivate(app: Application) =
window.fullscreen window.fullscreen
## Connect the GTK signals to the procs ## Connect the GTK signals to the procs
var action: SimpleAction let fullscreenAction = newSimpleAction("fullscreen")
discard fullscreenAction.connect("activate", toggleFullscreen, window)
action = newSimpleAction("fullscreen")
discard action.connect("activate", toggleFullscreen, window)
app.setAccelsForAction("win.fullscreen", "F") app.setAccelsForAction("win.fullscreen", "F")
window.actionMap.addAction(action) window.actionMap.addAction(fullscreenAction)
action = newSimpleAction("quit") let quitAction = newSimpleAction("quit")
discard action.connect("activate", quit, app) discard quitAction.connect("activate", quit, app)
app.setAccelsForAction("win.quit", "Escape") app.setAccelsForAction("win.quit", "Escape")
window.actionMap.addAction(action) window.actionMap.addAction(quitAction)
action = newSimpleAction("update") let updateImageAction = newSimpleAction("update")
discard action.connect("activate", forceUpdate, image) discard updateImageAction.connect("activate", forceUpdate, image)
app.setAccelsForAction("win.update", "U") app.setAccelsForAction("win.update", "U")
window.actionMap.addAction(action) window.actionMap.addAction(updateImageAction)
action = newSimpleAction("help")
discard action.connect("activate", toggleHelp, helpBox)
app.setAccelsForAction("win.help", "H")
window.actionMap.addAction(action)
window.connect("destroy", cleanUp, app) window.connect("destroy", cleanUp, app)
window.connect("realize", hidePointer)
window.showAll window.showAll
# Help is only shown on demand
helpBox.hide
# Setting the inital image # Setting the inital image
# Fix 1 second timeout to make sure all other initialization has finished # Fix 1 second timeout to make sure all other initialization has finished
@@ -284,7 +236,7 @@ proc appActivate(app: Application) =
chan.open() chan.open()
## Start the server for handling incoming commands ## Start the server for handling incoming commands
let serverArgs = ServerArgs(verbose: args.verbose, port: args.port) let serverArgs = newServerArgs(args.verbose)
createThread(serverWorker, runServer, serverArgs) createThread(serverWorker, runServer, serverArgs)
discard idleAdd(checkServerChannel, image) discard idleAdd(checkServerChannel, image)

View File

@@ -1,3 +1,3 @@
threads:on threads:on
d:ssl d:ssl
# gc:arc gc:arc

View File

@@ -1,45 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>randopix</title>
<link rel="stylesheet" type="text/css" href="/site.css">
<script src="/pixctrl.js" type="text/javascript"></script>
<script src="/script.js" type="text/javascript"></script>
</head>
<body class="star-bg">
<main>
<h1 class="center">Randopix Remote</h1>
<div class="control">
<div class="control-info">Load the next image</div>
<div class="control-body">
<button type="button" class="btn" onclick="refresh()">Refresh Image Now!</button>
</div>
</div>
<div class="control">
<div class="control-info">Switch the image mode</div>
<div class="control-body">
<select id="modeselect"></select>
<button type="button" class="btn" onclick="js_setMode()">Set Mode</button>
</div>
</div>
<div class="control">
<div class="control-info">Adjust timespan between images</div>
<div class="control-body">
<input id="timeout" type="number">
<button type="button" class="btn" onclick="js_setTimeout()">Set Timeout</button>
</div>
</div>
</main>
</body>
</html>

View File

@@ -1,30 +0,0 @@
function capitalize(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
window.onload = () => {
const modes = getModes();
const modeselect = document.getElementById("modeselect");
modes.forEach(mode => {
let opt = document.createElement('option');
opt.value = mode;
opt.innerHTML = capitalize(mode)
modeselect.appendChild(opt);
});
}
function js_setTimeout() {
const elem = document.getElementById("timeout");
const timeout = parseInt(elem.value);
if (timeout < 1) {
console.error("timeout must be positive");
return;
}
setTimeout(timeout.toString());
elem.value = null;
}
function js_setMode() {
const modeselect = document.getElementById("modeselect");
switchMode(modeselect.value)
}

File diff suppressed because one or more lines are too long

View File

@@ -1,55 +1,60 @@
import asyncdispatch, strutils, json, logging import net, json, strutils
import jester
import common import common
const
index = slurp("resources/index.html")
style = slurp("resources/site.css")
pixctrlJs = slurp("resources/pixctrl.js")
script = slurp("resources/script.js")
type type
ServerArgs* = object of RootObj ServerArgs* = object of RootObj
verbose*: bool verbose: bool
port*: int
var var
chan*: Channel[CommandMessage] chan*: Channel[CommandMessage]
verbose: bool verbose: bool
proc newServerArgs*(verbose: bool): ServerArgs =
ServerArgs(verbose: verbose)
proc log(things: varargs[string, `$`]) = proc log(things: varargs[string, `$`]) =
if verbose: if verbose:
echo things.join() echo things.join()
router randopixRouter: proc closeServer*() =
get "/": ## Sends a "Close" command to the server
resp index var socket = newSocket()
socket.connect("127.0.0.1", Port(defaultPort))
get "/site.css": let c = newCommand(cClose)
resp(style, contentType="text/css") socket.send(c.wrap)
socket.close()
get "/pixctrl.js":
resp(pixctrlJs, contentType="text/javascript")
get "/script.js":
resp(script, contentType="text/javascript")
post "/":
try:
log "Command from ", request.ip
let json = request.body.parseJson
let msg = json.to(CommandMessage)
log "Got message: ", $msg
# Pass command from client to main applicaiton
chan.send(msg)
resp Http200
except:
log "Error: ", getCurrentExceptionMsg()
proc runServer*[ServerArgs](arg: ServerArgs) {.thread, nimcall.} = proc runServer*[ServerArgs](arg: ServerArgs) {.thread, nimcall.} =
verbose = arg.verbose verbose = arg.verbose
logging.setLogFilter(lvlInfo) var server = net.newSocket()
let port = Port(arg.port) server.bindAddr(Port(defaultPort))
let settings = newSettings(port=port) server.listen()
var server = initJester(randopixRouter, settings=settings) log "Control server is listening"
server.serve()
while true:
# Process client requests
var client = net.newSocket()
server.accept(client)
log "Client connected"
try:
var line = client.recvLine()
if line == "":
log "No data from client"
continue
var jsonData = parseJson(line)
let msg = jsonData.to(CommandMessage)
case msg.command
of cClose:
log "Server recieved termination command. Exiting."
break
else:
# Pass command from client to main applicaiton
chan.send(msg)
except OSError:
log "Server error: ", getCurrentExceptionMsg()
except:
log "Invalid command from client: ", getCurrentExceptionMsg()
log repr(getCurrentException())
server.close()

View File

@@ -1 +0,0 @@
1.0.0