From e08bc14634b1127cc6b8e4928a0d06e65fc519cd Mon Sep 17 00:00:00 2001 From: Zakary Timson Date: Sat, 12 Sep 2026 22:34:58 -0400 Subject: [PATCH 1/6] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d32b309..03476ba 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,8 @@ Homecast is a homebrew, Chromecast-style streaming appliance built around a Rasp The goal is to provide a simple, self-hosted casting and media experience while retaining complete control over the underlying hardware and software. Features include: +- Installs ontop of any Rasbian + Wayland OS + - Allows you to fully customize and use as your own media center - Remote desktop into the pi via the browser on any device - Mirrors to the TV - Install the PWA via the browser to get the remote control app From e24ad685fe59e1614ee0e1b9aa1b560b5cb019cb Mon Sep 17 00:00:00 2001 From: Zakary Timson Date: Sat, 12 Sep 2026 22:36:53 -0400 Subject: [PATCH 2/6] Add package.json --- package.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 package.json diff --git a/package.json b/package.json new file mode 100644 index 0000000..d26aff1 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "homecast", + "version": "0.1.0", + "description": "The Homebrew Chromecast", + "main": "src/server.mjs", + "scripts": { + "start": "node server.mjs" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "module", + "dependencies": { + "@novnc/novnc": "^1.7.0", + "ws": "^8.21.3" + } +} \ No newline at end of file From c84008cf5c8973fe15be431e76ecf8ffa60de485 Mon Sep 17 00:00:00 2001 From: Zakary Timson Date: Sat, 12 Sep 2026 22:38:26 -0400 Subject: [PATCH 3/6] Add src/server.mjs --- src/server.mjs | 129 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 src/server.mjs diff --git a/src/server.mjs b/src/server.mjs new file mode 100644 index 0000000..ac0c513 --- /dev/null +++ b/src/server.mjs @@ -0,0 +1,129 @@ +import http from 'node:http'; +import https from 'node:https'; +import net from 'node:net'; +import fs from 'node:fs'; +import {readFile} from 'node:fs/promises'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import {WebSocketServer} from 'ws'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const NOVNC = path.join(__dirname, 'node_modules/@novnc/novnc'); +const PORT = process.env['PORT'] || 3000; +const VNC_HOST = '127.0.0.1'; +const VNC_PORT = 5900; + +const MIME_TYPES = { + '.html': 'text/html; charset=utf-8', + '.js': 'text/javascript', + '.css': 'text/css', + '.json': 'application/json', + '.svg': 'image/svg+xml', + '.png': 'image/png', + '.ico': 'image/x-icon', + '.wasm': 'application/wasm' +}; + +const server = https.createServer({ + key: fs.readFileSync('./certs/key.pem'), + cert: fs.readFileSync('./certs/cert.pem') +}, async (req, res) => { + let file; + + if (req.url === '/') file = path.join(__dirname, '../public/index.html'); + else if (req.url.startsWith('/novnc/')) file = path.join(NOVNC, req.url.slice(7)); + else { + res.writeHead(404); + res.end('Not found'); + return; + } + + try { + const data = await readFile(file); + const type = MIME_TYPES[path.extname(file)] || 'application/octet-stream'; + + res.writeHead(200, {'Content-Type': type}); + res.end(data); + } catch (error) { + console.error('HTTP error:', error.message); + res.writeHead(404); + res.end('Not found'); + } +}); + +const wss = new WebSocketServer({ + server, + path: '/websockify', + handleProtocols: protocols => { + console.log('WebSocket protocols:', [...protocols]); + + if (protocols.has('binary')) return 'binary'; + return false; + } +}); + +wss.on('connection', ws => { + console.log('VNC client connected'); + + const socket = net.createConnection(VNC_PORT, VNC_HOST); + + socket.on('connect', () => { + console.log(`Connected to VNC ${VNC_HOST}:${VNC_PORT}`); + }); + + socket.on('data', data => { + console.log(`VNC -> browser: ${data.length} bytes`); + + if (data.length <= 64) { + console.log(` hex: ${data.toString('hex')}`); + console.log(` text: ${JSON.stringify(data.toString())}`); + } + + if (ws.readyState === ws.OPEN) { + ws.send(data, {binary: true}); + } + }); + + ws.on('message', (data, isBinary) => { + const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data); + + console.log(`Browser -> VNC: ${buffer.length} bytes (${isBinary ? 'binary' : 'text'})`); + + if (buffer.length <= 64) { + console.log(` hex: ${buffer.toString('hex')}`); + console.log(` text: ${JSON.stringify(buffer.toString())}`); + } + + if (socket.writable) { + socket.write(buffer); + } + }); + + ws.on('error', error => { + console.error('WebSocket error:', error.message); + socket.destroy(); + }); + + ws.on('close', (code, reason) => { + console.log(`Browser disconnected: ${code}${reason?.length ? ` ${reason}` : ''}`); + socket.destroy(); + }); + + socket.on('error', error => { + console.error('VNC socket error:', error.message); + if (ws.readyState === ws.OPEN) ws.close(); + }); + + socket.on('close', () => { + console.log('VNC socket closed'); + if (ws.readyState === ws.OPEN) ws.close(); + }); +}); + +server.on('error', error => { + console.error('Server error:', error); +}); + +server.listen(PORT, '0.0.0.0', () => { + console.log(`https://0.0.0.0:${PORT}`); +}) \ No newline at end of file From 4bfecdeacc9b7e50ed5f99983c91e6b826fe7d84 Mon Sep 17 00:00:00 2001 From: Zakary Timson Date: Sat, 12 Sep 2026 22:39:16 -0400 Subject: [PATCH 4/6] Add public/index.html --- public/index.html | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 public/index.html diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..1210801 --- /dev/null +++ b/public/index.html @@ -0,0 +1,46 @@ + + + + + + Remote Desktop + + + +
+ + + + \ No newline at end of file From 169ffad8639fce2346ccf69663cd766f61220cee Mon Sep 17 00:00:00 2001 From: Zakary Timson Date: Sat, 12 Sep 2026 22:40:19 -0400 Subject: [PATCH 5/6] Upload files to "public" --- public/homecast.png | Bin 0 -> 5244 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/homecast.png diff --git a/public/homecast.png b/public/homecast.png new file mode 100644 index 0000000000000000000000000000000000000000..ac3c3d6aed2828e515249db85ea6577327fb7bb2 GIT binary patch literal 5244 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 literal 0 HcmV?d00001 From 48170890563f23aa70806b1d1cc113d8266efb55 Mon Sep 17 00:00:00 2001 From: Zakary Timson Date: Sat, 12 Sep 2026 22:40:40 -0400 Subject: [PATCH 6/6] Update public/logo.png --- public/{homecast.png => logo.png} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename public/{homecast.png => logo.png} (100%) diff --git a/public/homecast.png b/public/logo.png similarity index 100% rename from public/homecast.png rename to public/logo.png