Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c6b3f65b35 | |||
| 427a872811 | |||
| 1e5527f629 | |||
| 6d5404337e | |||
| 8c06248c32 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,4 +1,4 @@
|
||||
### Above combination will ignore all files without extension ###
|
||||
src/resources/pixctrl.js
|
||||
bin/
|
||||
.vscode/
|
||||
pixctrl.js
|
||||
@@ -1,6 +1,6 @@
|
||||
# Package
|
||||
|
||||
version = "1.0.0"
|
||||
version = "1.1.0"
|
||||
author = "luxick"
|
||||
description = "Play an image slide show from different sources"
|
||||
license = "GPL-2.0"
|
||||
@@ -13,7 +13,7 @@ requires "nim >= 1.0.0", "gintro", "argparse", "jester", "ajax"
|
||||
|
||||
proc genJS =
|
||||
echo "Generating JS Client"
|
||||
exec("nim js -o:src/resources/pixctrl.js src/pixctrl.nim")
|
||||
exec("nim js -o:src/resources/www/pixctrl.js src/pixctrl.nim")
|
||||
|
||||
task genJS, "Generate the Javascript client":
|
||||
genJS()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import os, sets, random, httpClient, json, strutils, strformat, options, deques, times
|
||||
from lenientops import `*`
|
||||
import gintro/[gdkpixbuf, gobject]
|
||||
import common
|
||||
|
||||
@@ -57,6 +58,19 @@ proc log(ip: ImageProvider, things: varargs[string, `$`]) =
|
||||
if ip.verbose:
|
||||
echo things.join()
|
||||
|
||||
func calcImageSize(maxWidth, maxHeight, imgWidth, imgHeight: int): tuple[width: int, height: int] =
|
||||
## Calculate the best fit for an image on the give screen size.
|
||||
## This should keep the image aspect ratio
|
||||
let
|
||||
ratioMax = maxWidth / maxHeight
|
||||
ratioImg = imgWidth / imgHeight
|
||||
if (ratioMax > ratioImg):
|
||||
result.width = (imgWidth * (maxHeight / imgHeight)).toInt
|
||||
result.height = maxHeight
|
||||
else:
|
||||
result.width = maxWidth
|
||||
result.height = (imgHeight * (maxWidth / imgWidth)).toInt
|
||||
|
||||
########################
|
||||
# Image Provider procs
|
||||
########################
|
||||
@@ -130,7 +144,7 @@ proc getFileName(ip: var ImageProvider): FileOpResult =
|
||||
# Exported procs
|
||||
########################
|
||||
|
||||
proc next*(ip: var ImageProvider, width, height: int): FileOpResult =
|
||||
proc next*(ip: var ImageProvider, maxWidth, maxHeight: int): FileOpResult =
|
||||
## Uses the image provider to get a new image ready to display.
|
||||
## `width` and `height` should be the size of the window.
|
||||
if ip.mode == Mode.None:
|
||||
@@ -140,16 +154,11 @@ proc next*(ip: var ImageProvider, width, height: int): FileOpResult =
|
||||
if not op.success: return op
|
||||
|
||||
var rawPixbuf = newPixbufFromFile(op.file)
|
||||
# resize the pixbuf to best fit on screen
|
||||
var w, h: int
|
||||
if (width > height):
|
||||
h = height
|
||||
w = ((rawPixbuf.width * h) / rawPixbuf.height).toInt
|
||||
else:
|
||||
w = width
|
||||
h = ((rawPixbuf.height * w) / rawPixbuf.width).toInt
|
||||
# Resize the pixbuf to best fit on screen
|
||||
let size = calcImageSize(maxWidth, maxHeight, rawPixbuf.width, rawPixbuf.height)
|
||||
ip.log "Scale image to: ", size
|
||||
let then = now()
|
||||
var pixbuf = rawPixbuf.scaleSimple(w, h, InterpType.nearest)
|
||||
var pixbuf = rawPixbuf.scaleSimple(size.width, size.height, 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
|
||||
|
||||
@@ -193,12 +193,18 @@ proc quit(action: SimpleAction; parameter: Variant; app: Application) =
|
||||
## Application quit event
|
||||
cleanUp(window, app)
|
||||
|
||||
proc hidePointer(window: ApplicationWindow): void =
|
||||
proc realizeWindow(window: ApplicationWindow, image: Image): void =
|
||||
## Hides the mouse pointer for the application.
|
||||
if args.fullscreen:
|
||||
window.fullscreen
|
||||
let cur = window.getDisplay().newCursorForDisplay(CursorType.blankCursor)
|
||||
let win = window.getWindow()
|
||||
win.setCursor(cur)
|
||||
|
||||
# Setting the inital image
|
||||
# Fix 1 second timeout to make sure all other initialization has finished
|
||||
updateTimeout = int(timeoutAdd(1000, timedUpdate, image))
|
||||
|
||||
proc appActivate(app: Application) =
|
||||
# Parse arguments from the command line
|
||||
let parsed = newArgs()
|
||||
@@ -243,9 +249,6 @@ proc appActivate(app: Application) =
|
||||
let image = newImage()
|
||||
container.add(image)
|
||||
|
||||
if args.fullscreen:
|
||||
window.fullscreen
|
||||
|
||||
## Connect the GTK signals to the procs
|
||||
var action: SimpleAction
|
||||
|
||||
@@ -270,16 +273,12 @@ proc appActivate(app: Application) =
|
||||
window.actionMap.addAction(action)
|
||||
|
||||
window.connect("destroy", cleanUp, app)
|
||||
window.connect("realize", hidePointer)
|
||||
window.connect("realize", realizeWindow, image)
|
||||
|
||||
window.showAll
|
||||
# Help is only shown on demand
|
||||
helpBox.hide
|
||||
|
||||
# Setting the inital image
|
||||
# Fix 1 second timeout to make sure all other initialization has finished
|
||||
updateTimeout = int(timeoutAdd(1000, timedUpdate, image))
|
||||
|
||||
## open communication channel from the control server
|
||||
chan.open()
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
BIN
src/resources/www/favicon.ico
Normal file
BIN
src/resources/www/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/resources/www/github.png
Normal file
BIN
src/resources/www/github.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
@@ -17,6 +17,8 @@
|
||||
<main>
|
||||
<h1 class="center">Randopix Remote</h1>
|
||||
|
||||
<p>Control the image that is shown on this randopix.</p>
|
||||
|
||||
<div class="control">
|
||||
<div class="control-info">Load the next image</div>
|
||||
<div class="control-body">
|
||||
@@ -40,6 +42,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="social">
|
||||
<a class="btn btn-mini" href="https://github.com/luxick/randopix">
|
||||
<img src="/github.png"></img>
|
||||
Source code and usage
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
BIN
src/resources/www/microfab.gif
Normal file
BIN
src/resources/www/microfab.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
src/resources/www/not-comic-sans.woff
Normal file
BIN
src/resources/www/not-comic-sans.woff
Normal file
Binary file not shown.
157
src/resources/www/site.css
Normal file
157
src/resources/www/site.css
Normal file
@@ -0,0 +1,157 @@
|
||||
@charset "utf-8";
|
||||
/* CSS Document */
|
||||
|
||||
@font-face {
|
||||
font-family: notcomicsans;
|
||||
src: url("/not-comic-sans.woff") format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.star-bg {
|
||||
background-color: #1c1d1b;
|
||||
background-image: url("/stars.gif")
|
||||
}
|
||||
|
||||
/* Chrome, Safari, Opera */
|
||||
@-webkit-keyframes rainbow {
|
||||
0%{color: orange;}
|
||||
10%{color: purple;}
|
||||
20%{color: red;}
|
||||
30%{color: CadetBlue;}
|
||||
40%{color: yellow;}
|
||||
50%{color: coral;}
|
||||
60%{color: green;}
|
||||
70%{color: cyan;}
|
||||
80%{color: DeepPink;}
|
||||
90%{color: DodgerBlue;}
|
||||
100%{color: orange;}
|
||||
}
|
||||
|
||||
/* Internet Explorer */
|
||||
@-ms-keyframes rainbow {
|
||||
0%{color: orange;}
|
||||
10%{color: purple;}
|
||||
20%{color: red;}
|
||||
30%{color: CadetBlue;}
|
||||
40%{color: yellow;}
|
||||
50%{color: coral;}
|
||||
60%{color: green;}
|
||||
70%{color: cyan;}
|
||||
80%{color: DeepPink;}
|
||||
90%{color: DodgerBlue;}
|
||||
100%{color: orange;}
|
||||
}
|
||||
|
||||
/* Standard Syntax */
|
||||
@keyframes rainbow {
|
||||
0%{color: orange;}
|
||||
10%{color: purple;}
|
||||
20%{color: red;}
|
||||
30%{color: CadetBlue;}
|
||||
40%{color: yellow;}
|
||||
50%{color: coral;}
|
||||
60%{color: green;}
|
||||
70%{color: cyan;}
|
||||
80%{color: DeepPink;}
|
||||
90%{color: DodgerBlue;}
|
||||
100%{color: orange;}
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: notcomicsans;
|
||||
color:#FFFF00;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 900px;
|
||||
align-self: center;
|
||||
border: 1px solid gray;
|
||||
border-radius: 5px;
|
||||
background-image: url("/microfab.gif");
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.control {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.control-info {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.control-body {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
}
|
||||
.control-body > * {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
/* Chrome, Safari, Opera */
|
||||
-webkit-animation: rainbow 5s infinite;
|
||||
/* Internet Explorer */
|
||||
-ms-animation: rainbow 5s infinite;
|
||||
/* Standar Syntax */
|
||||
animation: rainbow 5s infinite;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0;
|
||||
border: none;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
background-color: transparent;
|
||||
/* show a hand cursor on hover; some argue that we
|
||||
should keep the default arrow cursor for buttons */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #ff0;
|
||||
}
|
||||
|
||||
.btn {
|
||||
text-align: center;
|
||||
font-size: 1.3rem;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
border: 2px solid darkgray;
|
||||
border-radius: 5px;
|
||||
padding: 0px 10px;
|
||||
background-color: #1c1d1b;
|
||||
}
|
||||
.btn:hover {
|
||||
color: #ff0;
|
||||
fill: #ff0;
|
||||
background-color: #0b0c0b;
|
||||
}
|
||||
|
||||
.btn-mini {
|
||||
font-size: 0.75rem;
|
||||
border: 1px solid darkgray;
|
||||
padding: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.btn-mini > img {
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.social {
|
||||
margin-top: 20px;
|
||||
display: inline-block;
|
||||
}
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
BIN
src/resources/www/stars.gif
Normal file
BIN
src/resources/www/stars.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@@ -1,12 +1,22 @@
|
||||
import asyncdispatch, strutils, json, logging
|
||||
import asyncdispatch, strutils, json, logging, os
|
||||
import jester
|
||||
import common
|
||||
|
||||
when defined(release):
|
||||
proc slurpResources(): Table[string, string] {.compileTime.} =
|
||||
## Include everything from the www dir into the binary.
|
||||
## This way the final executable will not need an static web folder.
|
||||
for item in walkDir("src/resources/www/", true):
|
||||
if item.kind == pcFile:
|
||||
result[item.path] = slurp("resources/www/" & item.path)
|
||||
|
||||
const resources = slurpResources()
|
||||
|
||||
const
|
||||
index = slurp("resources/index.html")
|
||||
style = slurp("resources/site.css")
|
||||
pixctrlJs = slurp("resources/pixctrl.js")
|
||||
script = slurp("resources/script.js")
|
||||
contentTypes = {
|
||||
".js": "text/javascript",
|
||||
".css": "text/css"
|
||||
}.toTable
|
||||
|
||||
type
|
||||
ServerArgs* = object of RootObj
|
||||
@@ -21,19 +31,37 @@ proc log(things: varargs[string, `$`]) =
|
||||
if verbose:
|
||||
echo things.join()
|
||||
|
||||
router randopixRouter:
|
||||
when defined(release):
|
||||
## When in release mode, use resources includes in the binary.
|
||||
## When developing use the files directly.
|
||||
router getRouter:
|
||||
get "/":
|
||||
resp index
|
||||
resp resources["index.html"]
|
||||
|
||||
get "/site.css":
|
||||
resp(style, contentType="text/css")
|
||||
|
||||
get "/pixctrl.js":
|
||||
resp(pixctrlJs, contentType="text/javascript")
|
||||
|
||||
get "/script.js":
|
||||
resp(script, contentType="text/javascript")
|
||||
get "/@resource":
|
||||
try:
|
||||
var cType: string
|
||||
if contentTypes.hasKey(@"resource".splitFile.ext):
|
||||
cType = contentTypes[@"resource".splitFile.ext]
|
||||
resp resources[@"resource"], contentType=cType
|
||||
except KeyError:
|
||||
log "Resource not found: ", @"resource"
|
||||
resp Http404
|
||||
else:
|
||||
router getRouter:
|
||||
get "/":
|
||||
resp readFile("src/resources/www/index.html")
|
||||
get "/@resource":
|
||||
try:
|
||||
var cType: string
|
||||
if contentTypes.hasKey(@"resource".splitFile.ext):
|
||||
cType = contentTypes[@"resource".splitFile.ext]
|
||||
resp readFile("src/resources/www/" & @"resource"), contentType=cType
|
||||
except KeyError:
|
||||
log "Resource not found: ", @"resource"
|
||||
resp Http404
|
||||
|
||||
router postRouter:
|
||||
post "/":
|
||||
try:
|
||||
log "Command from ", request.ip
|
||||
@@ -46,6 +74,10 @@ router randopixRouter:
|
||||
except:
|
||||
log "Error: ", getCurrentExceptionMsg()
|
||||
|
||||
router randopixRouter:
|
||||
extend postRouter, ""
|
||||
extend getRouter, ""
|
||||
|
||||
proc runServer*[ServerArgs](arg: ServerArgs) {.thread, nimcall.} =
|
||||
verbose = arg.verbose
|
||||
logging.setLogFilter(lvlInfo)
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.0.0
|
||||
1.1.0
|
||||
Reference in New Issue
Block a user