mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2025-12-12 01:00:29 +08:00
v2 beta
This commit is contained in:
parent
6e9a3222ce
commit
940989b6e9
@ -15,6 +15,6 @@ search = version="{current_version}"
|
|||||||
replace = version="{new_version}"
|
replace = version="{new_version}"
|
||||||
|
|
||||||
[bumpversion:file:PKGBUILD]
|
[bumpversion:file:PKGBUILD]
|
||||||
search = pkgver="{current_version}"
|
search = pkgver={current_version}
|
||||||
replace = pkgver="{new_version}"
|
replace = pkgver={new_version}
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,8 @@ all:
|
|||||||
|
|
||||||
|
|
||||||
run:
|
run:
|
||||||
docker build --rm --tag $(TESTENV_IMAGE) -f testenv/Dockerfile .
|
sudo modprobe loop
|
||||||
|
docker build $(TESTENV_OPTS) --rm --tag $(TESTENV_IMAGE) -f testenv/Dockerfile .
|
||||||
- docker run --rm \
|
- docker run --rm \
|
||||||
--volume `pwd`/kvmd:/kvmd:ro \
|
--volume `pwd`/kvmd:/kvmd:ro \
|
||||||
--volume `pwd`/web:/usr/share/kvmd/web:ro \
|
--volume `pwd`/web:/usr/share/kvmd/web:ro \
|
||||||
|
|||||||
@ -2,26 +2,26 @@
|
|||||||
# Author: Maxim Devaev <mdevaev@gmail.com>
|
# Author: Maxim Devaev <mdevaev@gmail.com>
|
||||||
|
|
||||||
|
|
||||||
pkgname="kvmd"
|
pkgname=kvmd
|
||||||
pkgver="0.66"
|
pkgver=0.66
|
||||||
pkgrel="1"
|
pkgrel=1
|
||||||
pkgdesc="The main Pi-KVM daemon"
|
pkgdesc="The main Pi-KVM daemon"
|
||||||
arch=("any")
|
|
||||||
url="https://github.com/pi-kvm/pi-kvm"
|
url="https://github.com/pi-kvm/pi-kvm"
|
||||||
license=("GPL")
|
license=(GPL)
|
||||||
|
arch=(any)
|
||||||
depends=(
|
depends=(
|
||||||
"python"
|
python
|
||||||
"python-yaml"
|
python-yaml
|
||||||
"python-aiohttp"
|
python-aiohttp
|
||||||
"python-aiofiles"
|
python-aiofiles
|
||||||
"python-pyudev"
|
python-pyudev
|
||||||
"python-raspberry-gpio"
|
python-raspberry-gpio
|
||||||
"python-pyserial"
|
python-pyserial
|
||||||
"python-setproctitle"
|
python-setproctitle
|
||||||
)
|
)
|
||||||
makedepends=("python-setuptools")
|
makedepends=(python-setuptools)
|
||||||
source=("$url/archive/v$pkgver.tar.gz")
|
source=("$url/archive/v$pkgver.tar.gz")
|
||||||
md5sums=("SKIP")
|
md5sums=(SKIP)
|
||||||
|
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
@ -34,9 +34,9 @@ build() {
|
|||||||
|
|
||||||
package() {
|
package() {
|
||||||
cd $srcdir/$pkgname-build
|
cd $srcdir/$pkgname-build
|
||||||
python setup.py install --root=$pkgdir
|
python setup.py install --root="$pkgdir"
|
||||||
install -Dm644 configs/kvmd.service "$pkgdir"/usr/lib/systemd/system/kvmd.service
|
install -Dm644 configs/kvmd.service "$pkgdir/usr/lib/systemd/system/kvmd.service"
|
||||||
mkdir -p "$pkgdir"/usr/share/kvmd
|
mkdir -p "$pkgdir/usr/share/kvmd"
|
||||||
cp -r web "$pkgdir"/usr/share/kvmd
|
cp -r web "$pkgdir/usr/share/kvmd"
|
||||||
cp -r configs "$pkgdir"/usr/share/kvmd
|
cp -r configs "$pkgdir/usr/share/kvmd"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,15 +36,21 @@ kvmd:
|
|||||||
init_restart_after: 1.0
|
init_restart_after: 1.0
|
||||||
shutdown_delay: 10.0
|
shutdown_delay: 10.0
|
||||||
|
|
||||||
resolutions:
|
quality: 80
|
||||||
- 800x600 - 720x576
|
|
||||||
|
|
||||||
cmd:
|
cmd:
|
||||||
- "/usr/bin/mjpg_streamer"
|
- "/usr/bin/ustreamer"
|
||||||
- "-i"
|
- "--device=/dev/kvmd-streamer"
|
||||||
- "input_uvc.so -d /dev/kvmd-streamer -e 2 -t pal -y -n -r {resolution}"
|
- "--tv-standard=pal"
|
||||||
- "-o"
|
- "--format=yuyv"
|
||||||
- "output_http.so -l localhost -p 8082"
|
- "--encoder=omx"
|
||||||
|
- "--jpeg-quality={quality}"
|
||||||
|
- "--width=720"
|
||||||
|
- "--height=576"
|
||||||
|
- "--fake-width=800"
|
||||||
|
- "--fake-height=600"
|
||||||
|
- "--host=localhost"
|
||||||
|
- "--port=8082"
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
version: 1
|
version: 1
|
||||||
|
|||||||
72
kvmd/configs/kvmd/v2.yaml
Normal file
72
kvmd/configs/kvmd/v2.yaml
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
kvmd:
|
||||||
|
server:
|
||||||
|
host: localhost
|
||||||
|
port: 8081
|
||||||
|
heartbeat: 3.0
|
||||||
|
|
||||||
|
hid:
|
||||||
|
device: /dev/ttyAMA0
|
||||||
|
speed: 115200
|
||||||
|
|
||||||
|
atx:
|
||||||
|
pinout:
|
||||||
|
power_led: 16
|
||||||
|
hdd_led: 12
|
||||||
|
power_switch: 26
|
||||||
|
reset_switch: 20
|
||||||
|
|
||||||
|
click_delay: 0.1
|
||||||
|
long_click_delay: 5.5
|
||||||
|
|
||||||
|
state_poll: 0.1
|
||||||
|
|
||||||
|
msd:
|
||||||
|
device: "/dev/kvmd-msd"
|
||||||
|
init_delay: 2.0
|
||||||
|
write_meta: true
|
||||||
|
chunk_size: 65536
|
||||||
|
|
||||||
|
streamer:
|
||||||
|
pinout:
|
||||||
|
cap: -1
|
||||||
|
conv: -1
|
||||||
|
|
||||||
|
sync_delay: 0.0
|
||||||
|
init_delay: 1.0
|
||||||
|
init_restart_after: 0.0
|
||||||
|
shutdown_delay: 10.0
|
||||||
|
|
||||||
|
quality: 80
|
||||||
|
|
||||||
|
cmd:
|
||||||
|
- "/usr/bin/ustreamer"
|
||||||
|
- "--device=/dev/kvmd-streamer"
|
||||||
|
- "--format=uyvy"
|
||||||
|
- "--encoder=omx"
|
||||||
|
- "--jpeg-quality={quality}"
|
||||||
|
- "--dv-timings"
|
||||||
|
- "--host=localhost"
|
||||||
|
- "--port=8082"
|
||||||
|
|
||||||
|
logging:
|
||||||
|
version: 1
|
||||||
|
disable_existing_loggers: false
|
||||||
|
|
||||||
|
formatters:
|
||||||
|
console:
|
||||||
|
(): logging.Formatter
|
||||||
|
style: "{"
|
||||||
|
datefmt: "%H:%M:%S"
|
||||||
|
format: "[{asctime}] {name:20.20} {levelname:>7} --- {message}"
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
console:
|
||||||
|
level: DEBUG
|
||||||
|
class: logging.StreamHandler
|
||||||
|
stream: ext://sys.stdout
|
||||||
|
formatter: console
|
||||||
|
|
||||||
|
root:
|
||||||
|
level: INFO
|
||||||
|
handlers:
|
||||||
|
- console
|
||||||
@ -31,7 +31,7 @@ http {
|
|||||||
server localhost:8081 fail_timeout=0s max_fails=0;
|
server localhost:8081 fail_timeout=0s max_fails=0;
|
||||||
}
|
}
|
||||||
|
|
||||||
upstream mjpg_streamer {
|
upstream ustreamer {
|
||||||
server localhost:8082 fail_timeout=0s max_fails=0;
|
server localhost:8082 fail_timeout=0s max_fails=0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,9 +112,9 @@ http {
|
|||||||
include /etc/nginx/proxy-params.conf;
|
include /etc/nginx/proxy-params.conf;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ ^/streamer/(snapshot|stream)(?:/(.*))?$ {
|
location /streamer {
|
||||||
rewrite /streamer/?(.*)(?:/(.*))?$ /?action=$1 break;
|
rewrite /streamer/?(.*) /$1 break;
|
||||||
proxy_pass http://mjpg_streamer;
|
proxy_pass http://ustreamer;
|
||||||
include /etc/nginx/proxy-params.conf;
|
include /etc/nginx/proxy-params.conf;
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
proxy_ignore_headers X-Accel-Buffering;
|
proxy_ignore_headers X-Accel-Buffering;
|
||||||
|
|||||||
@ -49,7 +49,7 @@ def main() -> None:
|
|||||||
sync_delay=float(config["streamer"]["sync_delay"]),
|
sync_delay=float(config["streamer"]["sync_delay"]),
|
||||||
init_delay=float(config["streamer"]["init_delay"]),
|
init_delay=float(config["streamer"]["init_delay"]),
|
||||||
init_restart_after=float(config["streamer"]["init_restart_after"]),
|
init_restart_after=float(config["streamer"]["init_restart_after"]),
|
||||||
resolutions=config["streamer"]["resolutions"],
|
quality=int(config["streamer"]["quality"]),
|
||||||
cmd=list(map(str, config["streamer"]["cmd"])),
|
cmd=list(map(str, config["streamer"]["cmd"])),
|
||||||
loop=loop,
|
loop=loop,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -128,7 +128,7 @@ class Server: # pylint: disable=too-many-instance-attributes
|
|||||||
self.__system_tasks: List[asyncio.Task] = []
|
self.__system_tasks: List[asyncio.Task] = []
|
||||||
|
|
||||||
self.__reset_streamer = False
|
self.__reset_streamer = False
|
||||||
self.__streamer_resolution = streamer.get_current_resolution()
|
self.__streamer_quality = streamer.get_current_quality()
|
||||||
|
|
||||||
def run(self, host: str, port: int) -> None:
|
def run(self, host: str, port: int) -> None:
|
||||||
self.__hid.start()
|
self.__hid.start()
|
||||||
@ -166,7 +166,7 @@ class Server: # pylint: disable=too-many-instance-attributes
|
|||||||
|
|
||||||
# ===== INFO
|
# ===== INFO
|
||||||
|
|
||||||
async def __info_handler(self, _: aiohttp.web.Request) -> aiohttp.web.WebSocketResponse:
|
async def __info_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
|
||||||
return _json(_get_system_info())
|
return _json(_get_system_info())
|
||||||
|
|
||||||
# ===== WEBSOCKET
|
# ===== WEBSOCKET
|
||||||
@ -305,12 +305,15 @@ class Server: # pylint: disable=too-many-instance-attributes
|
|||||||
|
|
||||||
@_wrap_exceptions_for_web("Can't set stream params")
|
@_wrap_exceptions_for_web("Can't set stream params")
|
||||||
async def __streamer_set_params_handler(self, request: aiohttp.web.Request) -> aiohttp.web.Response:
|
async def __streamer_set_params_handler(self, request: aiohttp.web.Request) -> aiohttp.web.Response:
|
||||||
resolution = request.query.get("resolution")
|
quality = request.query.get("quality")
|
||||||
if resolution:
|
if quality:
|
||||||
if resolution in self.__streamer.get_available_resolutions():
|
try:
|
||||||
self.__streamer_resolution = resolution
|
quality_int = int(quality)
|
||||||
else:
|
if not (1 <= quality_int <= 100):
|
||||||
raise BadRequest("Unknown resolution %r" % (resolution))
|
raise ValueError()
|
||||||
|
except Exception:
|
||||||
|
raise BadRequest("Invalid quality %r" % (quality))
|
||||||
|
self.__streamer_quality = quality_int
|
||||||
return _json()
|
return _json()
|
||||||
|
|
||||||
async def __streamer_reset_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
|
async def __streamer_reset_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
|
||||||
@ -356,7 +359,7 @@ class Server: # pylint: disable=too-many-instance-attributes
|
|||||||
cur = len(self.__sockets)
|
cur = len(self.__sockets)
|
||||||
if prev == 0 and cur > 0:
|
if prev == 0 and cur > 0:
|
||||||
if not self.__streamer.is_running():
|
if not self.__streamer.is_running():
|
||||||
await self.__streamer.start(self.__streamer_resolution)
|
await self.__streamer.start(self.__streamer_quality)
|
||||||
await self.__broadcast_event("streamer_state", **self.__streamer.get_state())
|
await self.__broadcast_event("streamer_state", **self.__streamer.get_state())
|
||||||
elif prev > 0 and cur == 0:
|
elif prev > 0 and cur == 0:
|
||||||
shutdown_at = time.time() + self.__streamer_shutdown_delay
|
shutdown_at = time.time() + self.__streamer_shutdown_delay
|
||||||
@ -365,10 +368,10 @@ class Server: # pylint: disable=too-many-instance-attributes
|
|||||||
await self.__streamer.stop()
|
await self.__streamer.stop()
|
||||||
await self.__broadcast_event("streamer_state", **self.__streamer.get_state())
|
await self.__broadcast_event("streamer_state", **self.__streamer.get_state())
|
||||||
|
|
||||||
if self.__reset_streamer or self.__streamer_resolution != self.__streamer.get_current_resolution():
|
if self.__reset_streamer or self.__streamer_quality != self.__streamer.get_current_quality():
|
||||||
if self.__streamer.is_running():
|
if self.__streamer.is_running():
|
||||||
await self.__streamer.stop()
|
await self.__streamer.stop()
|
||||||
await self.__streamer.start(self.__streamer_resolution, no_init_restart=True)
|
await self.__streamer.start(self.__streamer_quality, no_init_restart=True)
|
||||||
await self.__broadcast_event("streamer_state", **self.__streamer.get_state())
|
await self.__broadcast_event("streamer_state", **self.__streamer.get_state())
|
||||||
self.__reset_streamer = False
|
self.__reset_streamer = False
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import asyncio.subprocess
|
import asyncio.subprocess
|
||||||
|
|
||||||
from collections import OrderedDict as odict
|
|
||||||
|
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
@ -21,10 +19,8 @@ class Streamer: # pylint: disable=too-many-instance-attributes
|
|||||||
sync_delay: float,
|
sync_delay: float,
|
||||||
init_delay: float,
|
init_delay: float,
|
||||||
init_restart_after: float,
|
init_restart_after: float,
|
||||||
|
quality: int,
|
||||||
resolutions: List[str],
|
|
||||||
cmd: List[str],
|
cmd: List[str],
|
||||||
|
|
||||||
loop: asyncio.AbstractEventLoop,
|
loop: asyncio.AbstractEventLoop,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
@ -33,26 +29,18 @@ class Streamer: # pylint: disable=too-many-instance-attributes
|
|||||||
self.__sync_delay = sync_delay
|
self.__sync_delay = sync_delay
|
||||||
self.__init_delay = init_delay
|
self.__init_delay = init_delay
|
||||||
self.__init_restart_after = init_restart_after
|
self.__init_restart_after = init_restart_after
|
||||||
|
self.__quality = quality
|
||||||
self.__resolutions = odict([
|
|
||||||
(display, (real or display))
|
|
||||||
for (display, real) in [
|
|
||||||
(tuple(map(str.lower, map(str.strip, resolution.split("-", maxsplit=1)))) + ("",))[:2]
|
|
||||||
for resolution in resolutions
|
|
||||||
]
|
|
||||||
])
|
|
||||||
self.__resolution = list(self.__resolutions)[0]
|
|
||||||
self.__cmd = cmd
|
self.__cmd = cmd
|
||||||
|
|
||||||
self.__loop = loop
|
self.__loop = loop
|
||||||
|
|
||||||
self.__proc_task: Optional[asyncio.Task] = None
|
self.__proc_task: Optional[asyncio.Task] = None
|
||||||
|
|
||||||
async def start(self, resolution: str, no_init_restart: bool=False) -> None:
|
async def start(self, quality: int, no_init_restart: bool=False) -> None:
|
||||||
logger = get_logger()
|
logger = get_logger()
|
||||||
logger.info("Starting streamer ...")
|
logger.info("Starting streamer ...")
|
||||||
assert resolution in self.__resolutions, (resolution, self.__resolutions)
|
assert 1 <= quality <= 100
|
||||||
self.__resolution = resolution
|
self.__quality = quality
|
||||||
await self.__inner_start()
|
await self.__inner_start()
|
||||||
if self.__init_restart_after > 0.0 and not no_init_restart:
|
if self.__init_restart_after > 0.0 and not no_init_restart:
|
||||||
logger.info("Stopping streamer to restart ...")
|
logger.info("Stopping streamer to restart ...")
|
||||||
@ -67,22 +55,13 @@ class Streamer: # pylint: disable=too-many-instance-attributes
|
|||||||
def is_running(self) -> bool:
|
def is_running(self) -> bool:
|
||||||
return bool(self.__proc_task)
|
return bool(self.__proc_task)
|
||||||
|
|
||||||
def get_current_resolution(self) -> str:
|
def get_current_quality(self) -> int:
|
||||||
return self.__resolution
|
return self.__quality
|
||||||
|
|
||||||
def get_available_resolutions(self) -> List[str]:
|
|
||||||
return list(self.__resolutions)
|
|
||||||
|
|
||||||
def get_state(self) -> Dict:
|
def get_state(self) -> Dict:
|
||||||
(width, height) = tuple(map(int, self.__resolution.split("x")))
|
|
||||||
return {
|
return {
|
||||||
"is_running": self.is_running(),
|
"is_running": self.is_running(),
|
||||||
"size": {
|
"quality": self.__quality,
|
||||||
"width": width,
|
|
||||||
"height": height,
|
|
||||||
},
|
|
||||||
"resolution": self.__resolution,
|
|
||||||
"resolutions": list(self.__resolutions),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async def cleanup(self) -> None:
|
async def cleanup(self) -> None:
|
||||||
@ -118,7 +97,7 @@ class Streamer: # pylint: disable=too-many-instance-attributes
|
|||||||
while True: # pylint: disable=too-many-nested-blocks
|
while True: # pylint: disable=too-many-nested-blocks
|
||||||
proc: Optional[asyncio.subprocess.Process] = None # pylint: disable=no-member
|
proc: Optional[asyncio.subprocess.Process] = None # pylint: disable=no-member
|
||||||
try:
|
try:
|
||||||
cmd = [part.format(resolution=self.__resolutions[self.__resolution]) for part in self.__cmd]
|
cmd = [part.format(quality=self.__quality) for part in self.__cmd]
|
||||||
proc = await asyncio.create_subprocess_exec(
|
proc = await asyncio.create_subprocess_exec(
|
||||||
*cmd,
|
*cmd,
|
||||||
stdout=asyncio.subprocess.PIPE,
|
stdout=asyncio.subprocess.PIPE,
|
||||||
|
|||||||
@ -33,7 +33,7 @@ RUN pacman -Syy \
|
|||||||
python-pip \
|
python-pip \
|
||||||
nginx-mainline \
|
nginx-mainline \
|
||||||
nginx-mainline-mod-lua \
|
nginx-mainline-mod-lua \
|
||||||
mjpg-streamer \
|
ustreamer \
|
||||||
socat \
|
socat \
|
||||||
&& pacman -Sc --noconfirm
|
&& pacman -Sc --noconfirm
|
||||||
|
|
||||||
|
|||||||
@ -36,17 +36,16 @@ kvmd:
|
|||||||
init_restart_after: 1.0
|
init_restart_after: 1.0
|
||||||
shutdown_delay: 10.0
|
shutdown_delay: 10.0
|
||||||
|
|
||||||
resolutions:
|
quality: 80
|
||||||
- 640x480
|
|
||||||
- 800x600
|
|
||||||
- 1024x768
|
|
||||||
|
|
||||||
cmd:
|
cmd:
|
||||||
- "/usr/bin/mjpg_streamer"
|
- "/usr/bin/ustreamer"
|
||||||
- "-i"
|
- "--device=/dev/kvmd-streamer"
|
||||||
- "input_uvc.so -d /dev/kvmd-streamer -e 2 -y -n -r {resolution}"
|
- "--jpeg-quality={quality}"
|
||||||
- "-o"
|
- "--width=800"
|
||||||
- "output_http.so -l 0.0.0.0 -p 8082"
|
- "--height=600"
|
||||||
|
- "--host=0.0.0.0"
|
||||||
|
- "--port=8082"
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
version: 1
|
version: 1
|
||||||
|
|||||||
@ -35,7 +35,7 @@ div.stream-box-mouse-enabled {
|
|||||||
cursor: url("../svg/stream-mouse-cursor.svg"), pointer;
|
cursor: url("../svg/stream-mouse-cursor.svg"), pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
select#stream-resolution-select {
|
select#stream-quality-select {
|
||||||
margin: 8px 0 8px 0;
|
margin: 8px 0 8px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -75,9 +75,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div data-dont-hide-menu class="ctl-dropdown-content-text">
|
<div data-dont-hide-menu class="ctl-dropdown-content-text">
|
||||||
Resolution:
|
Quality:
|
||||||
<select disabled id="stream-resolution-select">
|
<select disabled id="stream-quality-select">
|
||||||
<option>640x480</option>
|
<option>80%</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
|
|||||||
@ -5,8 +5,7 @@ function Stream() {
|
|||||||
|
|
||||||
var __prev_state = false;
|
var __prev_state = false;
|
||||||
|
|
||||||
var __resolution = "640x480";
|
var __quality = 80;
|
||||||
var __resolutions = ["640x480"];
|
|
||||||
|
|
||||||
var __normal_size = {width: 640, height: 480};
|
var __normal_size = {width: 640, height: 480};
|
||||||
var __size_factor = 1;
|
var __size_factor = 1;
|
||||||
@ -14,8 +13,13 @@ function Stream() {
|
|||||||
var __init__ = function() {
|
var __init__ = function() {
|
||||||
$("stream-led").title = "Stream inactive";
|
$("stream-led").title = "Stream inactive";
|
||||||
|
|
||||||
|
var quality = 10;
|
||||||
|
for (; quality <= 100; quality += 10) {
|
||||||
|
$("stream-quality-select").innerHTML += "<option value=\"" + quality + "\">" + quality + "%</option>";
|
||||||
|
}
|
||||||
|
|
||||||
tools.setOnClick($("stream-reset-button"), __clickResetButton);
|
tools.setOnClick($("stream-reset-button"), __clickResetButton);
|
||||||
$("stream-resolution-select").onchange = __changeResolution;
|
$("stream-quality-select").onchange = __changeQuality;
|
||||||
$("stream-size-slider").oninput = __resize;
|
$("stream-size-slider").oninput = __resize;
|
||||||
$("stream-size-slider").onchange = __resize;
|
$("stream-size-slider").onchange = __resize;
|
||||||
|
|
||||||
@ -27,12 +31,10 @@ function Stream() {
|
|||||||
// XXX: In current implementation we don't need this event because Stream() has own state poller
|
// XXX: In current implementation we don't need this event because Stream() has own state poller
|
||||||
|
|
||||||
var __startPoller = function() {
|
var __startPoller = function() {
|
||||||
var http = tools.makeRequest("GET", "/streamer/snapshot", function() {
|
var http = tools.makeRequest("GET", "/streamer/ping", function() {
|
||||||
if (http.readyState === 2 || http.readyState === 4) {
|
if (http.readyState === 4) {
|
||||||
var status = http.status;
|
var response = (http.status === 200 ? JSON.parse(http.responseText) : null);
|
||||||
http.onreadystatechange = null;
|
if (http.status !== 200 || !response.stream.online) {
|
||||||
http.abort();
|
|
||||||
if (status !== 200) {
|
|
||||||
tools.info("Refreshing stream ...");
|
tools.info("Refreshing stream ...");
|
||||||
__prev_state = false;
|
__prev_state = false;
|
||||||
$("stream-image").className = "stream-image-inactive";
|
$("stream-image").className = "stream-image-inactive";
|
||||||
@ -40,8 +42,9 @@ function Stream() {
|
|||||||
$("stream-led").className = "led-off";
|
$("stream-led").className = "led-off";
|
||||||
$("stream-led").title = "Stream inactive";
|
$("stream-led").title = "Stream inactive";
|
||||||
$("stream-reset-button").disabled = true;
|
$("stream-reset-button").disabled = true;
|
||||||
$("stream-resolution-select").disabled = true;
|
$("stream-quality-select").disabled = true;
|
||||||
} else if (!__prev_state) {
|
} else if (http.status === 200 && !__prev_state) {
|
||||||
|
__normal_size = response.stream.resolution;
|
||||||
__refreshImage();
|
__refreshImage();
|
||||||
__prev_state = true;
|
__prev_state = true;
|
||||||
$("stream-image").className = "stream-image-active";
|
$("stream-image").className = "stream-image-active";
|
||||||
@ -49,6 +52,7 @@ function Stream() {
|
|||||||
$("stream-led").className = "led-on";
|
$("stream-led").className = "led-on";
|
||||||
$("stream-led").title = "Stream is active";
|
$("stream-led").title = "Stream is active";
|
||||||
$("stream-reset-button").disabled = false;
|
$("stream-reset-button").disabled = false;
|
||||||
|
$("stream-quality-select").disabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -66,11 +70,11 @@ function Stream() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var __changeResolution = function() {
|
var __changeQuality = function() {
|
||||||
var resolution = $("stream-resolution-select").value;
|
var quality = parseInt($("stream-quality-select").value);
|
||||||
if (__resolution != resolution) {
|
if (__quality != quality) {
|
||||||
$("stream-resolution-select").disabled = true;
|
$("stream-quality-select").disabled = true;
|
||||||
var http = tools.makeRequest("POST", "/kvmd/streamer/set_params?resolution=" + resolution, function() {
|
var http = tools.makeRequest("POST", "/kvmd/streamer/set_params?quality=" + quality, function() {
|
||||||
if (http.readyState === 4) {
|
if (http.readyState === 4) {
|
||||||
if (http.status !== 200) {
|
if (http.status !== 200) {
|
||||||
ui.error("Can't configure stream:<br>", http.responseText);
|
ui.error("Can't configure stream:<br>", http.responseText);
|
||||||
@ -99,25 +103,14 @@ function Stream() {
|
|||||||
if (http.readyState === 4 && http.status === 200) {
|
if (http.readyState === 4 && http.status === 200) {
|
||||||
var result = JSON.parse(http.responseText).result;
|
var result = JSON.parse(http.responseText).result;
|
||||||
|
|
||||||
if (__resolutions != result.resolutions) {
|
if (__quality != result.quality) {
|
||||||
tools.info("Resolutions list changed:", result.resolutions);
|
tools.info("Quality changed:", result.quality);
|
||||||
$("stream-resolution-select").innerHTML = "";
|
document.querySelector("#stream-quality-select [value=\"" + result.quality + "\"]").selected = true;
|
||||||
result.resolutions.forEach(function(resolution) {
|
__quality = result.quality;
|
||||||
$("stream-resolution-select").innerHTML += "<option value=\"" + resolution + "\">" + resolution + "</option>";
|
|
||||||
});
|
|
||||||
$("stream-resolution-select").disabled = (result.resolutions.length == 1);
|
|
||||||
__resolutions = result.resolutions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (__resolution != result.resolution) {
|
|
||||||
tools.info("Resolution changed:", result.resolution);
|
|
||||||
document.querySelector("#stream-resolution-select [value=\"" + result.resolution + "\"]").selected = true;
|
|
||||||
__resolution = result.resolution;
|
|
||||||
}
|
|
||||||
|
|
||||||
__normal_size = result.size;
|
|
||||||
__applySizeFactor();
|
__applySizeFactor();
|
||||||
$("stream-image").src = "/streamer/stream/" + new Date().getTime();
|
$("stream-image").src = "/streamer/stream?t=" + new Date().getTime();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
21
os/Makefile
21
os/Makefile
@ -9,7 +9,12 @@ WEBUI_ADMIN_PASSWD ?= admin
|
|||||||
|
|
||||||
# =====
|
# =====
|
||||||
_BUILD_DIR = ./.build
|
_BUILD_DIR = ./.build
|
||||||
_KVMD_VERSION = $(shell bash -c 'source ../kvmd/PKGBUILD; echo $$pkgver')
|
|
||||||
|
define fetch_version
|
||||||
|
curl --silent "https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=$(1)" \
|
||||||
|
| grep "^pkgver=" \
|
||||||
|
| grep -Po "\d+\.\d+[^\"']*"
|
||||||
|
endef
|
||||||
|
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
@ -21,24 +26,30 @@ v1:
|
|||||||
make _pikvm PIKVM_PLATFORM=v1 PI_BOARD=rpi-2
|
make _pikvm PIKVM_PLATFORM=v1 PI_BOARD=rpi-2
|
||||||
|
|
||||||
|
|
||||||
|
v2:
|
||||||
|
make _pikvm PIKVM_PLATFORM=v2 PI_BOARD=rpi-2
|
||||||
|
|
||||||
|
|
||||||
shell:
|
shell:
|
||||||
cd $(_BUILD_DIR) && make shell
|
cd $(_BUILD_DIR) && make shell
|
||||||
|
|
||||||
|
|
||||||
_pikvm: $(_BUILD_DIR)
|
_pikvm: $(_BUILD_DIR)
|
||||||
rm -rf $(_BUILD_DIR)/stages/pikvm
|
rm -rf $(_BUILD_DIR)/stages/pikvm-*
|
||||||
rm -rf $(_BUILD_DIR)/builder/scripts/pikvm
|
rm -rf $(_BUILD_DIR)/builder/scripts/pikvm
|
||||||
cp -a platforms/$(PIKVM_PLATFORM) $(_BUILD_DIR)/stages/pikvm
|
cp -a platforms/common $(_BUILD_DIR)/stages/pikvm-common
|
||||||
|
cp -a platforms/$(PIKVM_PLATFORM) $(_BUILD_DIR)/stages/pikvm-$(PIKVM_PLATFORM)
|
||||||
cd $(_BUILD_DIR) && make binfmt && make _rpi \
|
cd $(_BUILD_DIR) && make binfmt && make _rpi \
|
||||||
BUILD_OPTS=" $(BUILD_OPTS) \
|
BUILD_OPTS=" $(BUILD_OPTS) \
|
||||||
--build-arg KVMD_VERSION=$(_KVMD_VERSION) \
|
--build-arg USTREAMER_VERSION=$(call fetch_version, ustreamer) \
|
||||||
|
--build-arg KVMD_VERSION=$(call fetch_version, kvmd) \
|
||||||
--build-arg NEW_SSH_KEYGEN=$(shell uuidgen) \
|
--build-arg NEW_SSH_KEYGEN=$(shell uuidgen) \
|
||||||
--build-arg WEBUI_ADMIN_PASSWD='$(WEBUI_ADMIN_PASSWD)' \
|
--build-arg WEBUI_ADMIN_PASSWD='$(WEBUI_ADMIN_PASSWD)' \
|
||||||
--build-arg NEW_HTTPS_CERT=$(shell uuidgen) \
|
--build-arg NEW_HTTPS_CERT=$(shell uuidgen) \
|
||||||
" \
|
" \
|
||||||
PROJECT=pikvm \
|
PROJECT=pikvm \
|
||||||
BOARD=$(PI_BOARD) \
|
BOARD=$(PI_BOARD) \
|
||||||
STAGES="__init__ os watchdog ro pikvm rootssh __cleanup__" \
|
STAGES="__init__ os watchdog ro pikvm-common pikvm-$(PIKVM_PLATFORM) rootssh __cleanup__" \
|
||||||
LOCALE=$(LOCALE) \
|
LOCALE=$(LOCALE) \
|
||||||
TIMEZONE=$(TIMEZONE)
|
TIMEZONE=$(TIMEZONE)
|
||||||
|
|
||||||
|
|||||||
51
os/platforms/common/Dockerfile.part
Normal file
51
os/platforms/common/Dockerfile.part
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
RUN pkg-install \
|
||||||
|
nginx-mainline \
|
||||||
|
apache-tools \
|
||||||
|
raspberrypi-firmware \
|
||||||
|
v4l-utils \
|
||||||
|
python \
|
||||||
|
python-raspberry-gpio \
|
||||||
|
customizepkg
|
||||||
|
RUN systemctl enable nginx
|
||||||
|
|
||||||
|
COPY stages/pikvm-common/customizepkg.nginx /etc/customizepkg.d/nginx-mainline-mod-ndk
|
||||||
|
COPY stages/pikvm-common/customizepkg.nginx /etc/customizepkg.d/nginx-mainline-mod-lua
|
||||||
|
RUN env MAKEPKGOPTS="--skipchecksums --skippgpcheck" pkg-install nginx-mainline-mod-lua
|
||||||
|
|
||||||
|
ARG USTREAMER_VERSION
|
||||||
|
ENV USTREAMER_VERSION $USTREAMER_VERSION
|
||||||
|
RUN echo $USTREAMER_VERSION
|
||||||
|
RUN pkg-install ustreamer
|
||||||
|
|
||||||
|
ARG KVMD_VERSION
|
||||||
|
ENV KVMD_VERSION $KVMD_VERSION
|
||||||
|
RUN echo $KVMD_VERSION
|
||||||
|
RUN pkg-install kvmd
|
||||||
|
RUN systemctl enable kvmd
|
||||||
|
|
||||||
|
COPY stages/pikvm-common/sysctl.conf /etc/sysctl.d/99-pikvm.conf
|
||||||
|
COPY stages/pikvm-common/motd /etc/
|
||||||
|
|
||||||
|
RUN sed -i -e "s/console=ttyAMA0\,115200//g" /boot/cmdline.txt \
|
||||||
|
&& sed -i -e "s/kgdboc=ttyAMA0\,115200//g" /boot/cmdline.txt
|
||||||
|
RUN systemctl mask serial-getty@ttyAMA0.service
|
||||||
|
|
||||||
|
RUN rm -rf /etc/nginx/* \
|
||||||
|
&& cp /usr/share/kvmd/configs/nginx/* /etc/nginx/ \
|
||||||
|
&& sed -i -e "s/^#PROD//g" /etc/nginx/nginx.conf
|
||||||
|
|
||||||
|
ARG WEBUI_ADMIN_PASSWD
|
||||||
|
ENV WEBUI_ADMIN_PASSWD $WEBUI_ADMIN_PASSWD
|
||||||
|
RUN echo "$WEBUI_ADMIN_PASSWD" | htpasswd -ci /etc/nginx/htpasswd admin
|
||||||
|
|
||||||
|
ARG NEW_HTTPS_CERT
|
||||||
|
ENV NEW_HTTPS_CERT $NEW_HTTPS_CERT
|
||||||
|
RUN echo $NEW_HTTPS_CERT
|
||||||
|
RUN mkdir /etc/nginx/ssl \
|
||||||
|
&& cd /etc/nginx/ssl \
|
||||||
|
&& openssl req -new -x509 -nodes -newkey rsa:4096 -keyout server.key -out server.crt -days 3650 \
|
||||||
|
-subj "/C=RU/ST=Moscow/L=Moscow/O=Pi-KVM/OU=Pi-KVM/CN=localhost" \
|
||||||
|
&& chmod 400 server.key \
|
||||||
|
&& chmod 444 server.crt \
|
||||||
|
&& chmod 750 /etc/nginx/ssl \
|
||||||
|
&& chown -R root:http /etc/nginx/ssl
|
||||||
@ -1,48 +1,4 @@
|
|||||||
RUN pkg-install \
|
COPY stages/pikvm-v1/config.txt /boot/
|
||||||
nginx-mainline \
|
COPY stages/pikvm-v1/udev.rules /etc/udev/rules.d/pikvm.rules
|
||||||
apache-tools \
|
|
||||||
mjpg-streamer \
|
|
||||||
python \
|
|
||||||
python-raspberry-gpio \
|
|
||||||
customizepkg
|
|
||||||
RUN systemctl enable nginx
|
|
||||||
|
|
||||||
COPY stages/pikvm/customizepkg.nginx /etc/customizepkg.d/nginx-mainline-mod-ndk
|
|
||||||
COPY stages/pikvm/customizepkg.nginx /etc/customizepkg.d/nginx-mainline-mod-lua
|
|
||||||
RUN env MAKEPKGOPTS="--skipchecksums --skippgpcheck" pkg-install nginx-mainline-mod-lua
|
|
||||||
|
|
||||||
ARG KVMD_VERSION
|
|
||||||
ENV KVMD_VERSION $KVMD_VERSION
|
|
||||||
RUN echo $KVMD_VERSION
|
|
||||||
RUN pkg-install kvmd
|
|
||||||
RUN systemctl enable kvmd
|
|
||||||
|
|
||||||
COPY stages/pikvm/config.txt /boot/
|
|
||||||
COPY stages/pikvm/sysctl.conf /etc/sysctl.d/99-pikvm.conf
|
|
||||||
COPY stages/pikvm/udev.rules /etc/udev/rules.d/pikvm.rules
|
|
||||||
COPY stages/pikvm/motd /etc/
|
|
||||||
|
|
||||||
RUN sed -i -e "s/console=ttyAMA0\,115200//g" /boot/cmdline.txt \
|
|
||||||
&& sed -i -e "s/kgdboc=ttyAMA0\,115200//g" /boot/cmdline.txt
|
|
||||||
RUN systemctl mask serial-getty@ttyAMA0.service
|
|
||||||
|
|
||||||
RUN rm -rf /etc/nginx/* \
|
|
||||||
&& cp /usr/share/kvmd/configs/nginx/* /etc/nginx/ \
|
|
||||||
&& sed -i -e "s/^#PROD//g" /etc/nginx/nginx.conf
|
|
||||||
RUN cp /usr/share/kvmd/configs/kvmd/v1.yaml /etc/kvmd.yaml
|
RUN cp /usr/share/kvmd/configs/kvmd/v1.yaml /etc/kvmd.yaml
|
||||||
|
|
||||||
ARG WEBUI_ADMIN_PASSWD
|
|
||||||
ENV WEBUI_ADMIN_PASSWD $WEBUI_ADMIN_PASSWD
|
|
||||||
RUN echo "$WEBUI_ADMIN_PASSWD" | htpasswd -ci /etc/nginx/htpasswd admin
|
|
||||||
|
|
||||||
ARG NEW_HTTPS_CERT
|
|
||||||
ENV NEW_HTTPS_CERT $NEW_HTTPS_CERT
|
|
||||||
RUN echo $NEW_HTTPS_CERT
|
|
||||||
RUN mkdir /etc/nginx/ssl \
|
|
||||||
&& cd /etc/nginx/ssl \
|
|
||||||
&& openssl req -new -x509 -nodes -newkey rsa:4096 -keyout server.key -out server.crt -days 3650 \
|
|
||||||
-subj "/C=RU/ST=Moscow/L=Moscow/O=Pi-KVM/OU=Pi-KVM/CN=localhost" \
|
|
||||||
&& chmod 400 server.key \
|
|
||||||
&& chmod 444 server.crt \
|
|
||||||
&& chmod 750 /etc/nginx/ssl \
|
|
||||||
&& chown -R root:http /etc/nginx/ssl
|
|
||||||
|
|||||||
11
os/platforms/v2/Dockerfile.part
Normal file
11
os/platforms/v2/Dockerfile.part
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
RUN pkg-install \
|
||||||
|
dkms \
|
||||||
|
tc358743-dkms
|
||||||
|
|
||||||
|
RUN sed -i -e "s/rootwait/cma=128M rootwait/g" /boot/cmdline.txt
|
||||||
|
|
||||||
|
COPY stages/pikvm-v2/config.txt /boot/
|
||||||
|
COPY stages/pikvm-v2/udev.rules /etc/udev/rules.d/pikvm.rules
|
||||||
|
COPY stages/pikvm-v2/modules.load /etc/modules-load.d/pikvm.conf
|
||||||
|
|
||||||
|
RUN cp /usr/share/kvmd/configs/kvmd/v2.yaml /etc/kvmd.yaml
|
||||||
5
os/platforms/v2/config.txt
Normal file
5
os/platforms/v2/config.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
gpu_mem=16
|
||||||
|
start_x=1
|
||||||
|
enable_uart=1
|
||||||
|
dtoverlay=tc358743,i2c_pins_28_29=1
|
||||||
|
dtparam=act_led_gpio=27
|
||||||
1
os/platforms/v2/modules.load
Normal file
1
os/platforms/v2/modules.load
Normal file
@ -0,0 +1 @@
|
|||||||
|
tc358743
|
||||||
4
os/platforms/v2/udev.rules
Normal file
4
os/platforms/v2/udev.rules
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# https://unix.stackexchange.com/questions/66901/how-to-bind-usb-device-under-a-static-name
|
||||||
|
# https://wiki.archlinux.org/index.php/Udev#Setting_static_device_names
|
||||||
|
KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", KERNELS=="soc", SYMLINK+="kvmd-streamer"
|
||||||
|
KERNEL=="sd[a-z]", SUBSYSTEM=="block", KERNELS=="1-1.4:1.0", SYMLINK+="kvmd-msd"
|
||||||
Loading…
x
Reference in New Issue
Block a user