@@ -0,0 +1,2 @@ | |||
udev/udev-monitor | |||
node_modules |
@@ -0,0 +1,16 @@ | |||
[KEYS] | |||
A=65293 | |||
B=65288 | |||
Select=101 | |||
Start=116 | |||
Right=65363 | |||
Left=65361 | |||
Up=65362 | |||
Down=65364 | |||
R=65366 | |||
L=65365 | |||
X=120 | |||
Y=121 | |||
Debug=49 | |||
Boost=50 | |||
Lid=51 |
@@ -0,0 +1,33 @@ | |||
[View] | |||
ScreenLayout=2 | |||
SwapScreens=false | |||
Rotation=0 | |||
ScreenGap=false | |||
Filter=0 | |||
SecondaryFilter=3 | |||
ShowMenu=true | |||
ShowToolbar=true | |||
ShowStatusbar=true | |||
[Window] | |||
Scale2x=0 | |||
Fullscreen=true | |||
[HudDisplay] | |||
Fps=false | |||
FrameCounter=false | |||
LagCounter=false | |||
Input=false | |||
GraphicalInput=false | |||
RTC=false | |||
Mic=false | |||
[Config] | |||
FpsLimiter=true | |||
AudoFrameskip=true | |||
Frameskip=0 | |||
[Audio] | |||
Enabled=true | |||
Synchronization=0 | |||
Interpolation=1 |
@@ -0,0 +1,191 @@ | |||
[Display] | |||
Bilinear=1 | |||
Filter=none | |||
FilterPlugin= | |||
IFB=none | |||
KeepOnTop=0 | |||
MaxThreads=4 | |||
RenderMethod=simple | |||
Scale=3 | |||
Stretch=1 | |||
[GB] | |||
BiosFile= | |||
GBCBiosFile= | |||
GBCROMDir= | |||
PrintAutoPage=1 | |||
PrintScreenCap=0 | |||
ROMDir= | |||
Palette0=7FFF,56B5,318C,0000,7FFF,56B5,318C,0000 | |||
Palette1=7FFF,56B5,318C,0000,7FFF,56B5,318C,0000 | |||
Palette2=7FFF,56B5,318C,0000,7FFF,56B5,318C,0000 | |||
[GBA] | |||
BiosFile= | |||
LinkAuto=0 | |||
LinkFast=0 | |||
LinkHost= | |||
LinkProto=0 | |||
LinkTimeout=1 | |||
LinkType=0 | |||
ROMDir= | |||
[General] | |||
AutoLoadLastState=0 | |||
BatteryDir= | |||
FreezeRecent=0 | |||
OnlineUpdates=daily | |||
RecordingDir= | |||
RewindInterval=0 | |||
ScreenshotDir= | |||
StateDir= | |||
StatusBar=0 | |||
[Joypad] | |||
AutofireThrottle=1 | |||
Default=1 | |||
[Joypad/1] | |||
Up=Up | |||
Down=Down | |||
Left=Left | |||
Right=Right | |||
A=X | |||
B=Z | |||
L=A | |||
R=S | |||
Select= | |||
Start=Enter | |||
MotionUp= | |||
MotionDown= | |||
MotionLeft= | |||
MotionRight= | |||
MotionIn= | |||
MotionOut= | |||
AutoA=W | |||
AutoB=Q | |||
Speed=Space | |||
Capture=F11 | |||
GS= | |||
[Joypad/2] | |||
Up= | |||
Down= | |||
Left= | |||
Right= | |||
A= | |||
B= | |||
L= | |||
R= | |||
Select= | |||
Start= | |||
MotionUp= | |||
MotionDown= | |||
MotionLeft= | |||
MotionRight= | |||
MotionIn= | |||
MotionOut= | |||
AutoA= | |||
AutoB= | |||
Speed= | |||
Capture= | |||
GS= | |||
[Joypad/3] | |||
Up= | |||
Down= | |||
Left= | |||
Right= | |||
A= | |||
B= | |||
L= | |||
R= | |||
Select= | |||
Start= | |||
MotionUp= | |||
MotionDown= | |||
MotionLeft= | |||
MotionRight= | |||
MotionIn= | |||
MotionOut= | |||
AutoA= | |||
AutoB= | |||
Speed= | |||
Capture= | |||
GS= | |||
[Joypad/4] | |||
Up= | |||
Down= | |||
Left= | |||
Right= | |||
A= | |||
B= | |||
L= | |||
R= | |||
Select= | |||
Start= | |||
MotionUp= | |||
MotionDown= | |||
MotionLeft= | |||
MotionRight= | |||
MotionIn= | |||
MotionOut= | |||
AutoA= | |||
AutoB= | |||
Speed= | |||
Capture= | |||
GS= | |||
[Sound] | |||
AudioAPI=sdl | |||
Buffers=5 | |||
Enable=783 | |||
GBAFiltering=50 | |||
GBAInterpolation=1 | |||
GBDeclicking=1 | |||
GBEcho=20 | |||
GBEnableEffects=0 | |||
GBStereo=15 | |||
GBSurround=0 | |||
Quality=44 | |||
Volume=100 | |||
[preferences] | |||
LinkNumPlayers=2 | |||
agbPrint=0 | |||
autoFrameSkip=0 | |||
autoPatch=1 | |||
autoSaveCheatList=1 | |||
borderAutomatic=0 | |||
borderOn=0 | |||
captureFormat=0 | |||
cheatsEnabled=0 | |||
disableStatus=0 | |||
emulatorType=1 | |||
flashSize=0 | |||
frameSkip=0 | |||
fsColorDepth=32 | |||
fsFrequency=60 | |||
fsHeight=600 | |||
fsWidth=800 | |||
fullScreen=1 | |||
gbPaletteOption=0 | |||
gbPrinter=0 | |||
gdbBreakOnLoad=0 | |||
gdbPort=55555 | |||
maxScale=0 | |||
pauseWhenInactive=1 | |||
rtcEnabled=0 | |||
saveType=0 | |||
showSpeed=0 | |||
showSpeedTransparent=1 | |||
skipBios=0 | |||
skipSaveGameBattery=0 | |||
skipSaveGameCheats=0 | |||
throttle=100 | |||
useBiosGB=0 | |||
useBiosGBA=0 | |||
useBiosGBC=0 | |||
vsync=0 | |||
[Recent] | |||
file1=roms/1986 - Pokemon Emerald (U)(TrashMan).gba | |||
file2=/home/martin/Downloads/1986 - Pokemon Emerald (U)(TrashMan).gba | |||
file3= | |||
file4= | |||
file5= | |||
file6= | |||
file7= | |||
file8= | |||
file9= | |||
file10= |
@@ -0,0 +1,42 @@ | |||
var shell = require("./shell"); | |||
var pathlib = require("path"); | |||
var os = require("os"); | |||
var fs = require("fs"); | |||
var EventEmitter = require("events"); | |||
exports.run = run; | |||
// Reset to default config | |||
async function cpconf(from, to) { | |||
to = pathlib.join(os.homedir(), to); | |||
from = pathlib.join("emuconf", from); | |||
await shell.safe("rm", "-rf", "--", to); | |||
await shell.safe("cp", "-r", "--", from, to); | |||
} | |||
async function run_visualboy(path, saves) { | |||
await cpconf("vbam", ".vbam"); | |||
await shell.safe("vbam", "-F", path); | |||
} | |||
async function run_desmume(path, saves) { | |||
await cpconf("desmume", ".config/desmume"); | |||
await shell.safe("desmume", path); | |||
} | |||
var emus = { | |||
nds: run_desmume, | |||
gba: run_visualboy, | |||
gbc: run_visualboy, | |||
}; | |||
async function run(path, cb) { | |||
var ext = pathlib.extname(path).substr(1); | |||
var fn = emus[ext]; | |||
if (!fn) | |||
throw new Error("Can't run games with extension "+ext+"."); | |||
shell.safe("mkdir", "-p", "--", path+".save"); | |||
await fn(path, path+".save"); | |||
} |
@@ -0,0 +1,58 @@ | |||
var WebSocket = require("ws"); | |||
var EventEmitter = require("events"); | |||
exports = module.exports = new EventEmitter(); | |||
exports.init = init; | |||
exports.error = error; | |||
exports.sendGameStopped = sendGameStopped; | |||
exports.sendGameStart = sendGameStart; | |||
exports.sendGameList = sendGameList; | |||
var wss; | |||
function onmessage(msg) { | |||
console.log("Received", msg); | |||
var obj = JSON.parse(msg); | |||
switch (obj.cmd) { | |||
case "run": | |||
return exports.emit("run", obj.name); | |||
default: | |||
console.error("Received unknown command:", obj.cmd); | |||
} | |||
} | |||
function send(obj) { | |||
var msg = JSON.stringify(obj); | |||
console.log("Sending", msg); | |||
wss.clients.forEach(client => { | |||
if (client.readyState === WebSocket.OPEN) | |||
client.send(msg); | |||
}); | |||
} | |||
function init(port, cb) { | |||
wss = new WebSocket.Server({ host: "127.0.0.1", port }, cb); | |||
wss.on("connection", sock => { | |||
exports.emit("connection"); | |||
sock.on("message", onmessage); | |||
}); | |||
} | |||
function sendGameStopped() { | |||
send({ cmd: "ongamestopped" }); | |||
} | |||
function sendGameStart() { | |||
send({ cmd: "ongamestart" }); | |||
} | |||
function sendGameList(games) { | |||
send({ cmd: "ongamelist", games }); | |||
} | |||
function error(msg) { | |||
send({ cmd: "onerror", msg }); | |||
} |
@@ -0,0 +1,32 @@ | |||
var spawn = require("child_process").spawn; | |||
module.exports = shell; | |||
module.exports.safe = safe; | |||
function shell(cmd, ...args) { | |||
console.log("running shell '"+cmd+" "+args.join(" ")+"'"); | |||
return new Promise((resolve, reject) => { | |||
var child = spawn(cmd, args); | |||
function print(out, name) { | |||
return function(d) { | |||
var s = d.toString(); | |||
if (s.trim() === "") | |||
return; | |||
out.write(cmd+": "+name+": "+s); | |||
} | |||
} | |||
child.stderr.on("data", print(process.stderr, "err")); | |||
child.stdout.on("data", print(process.stdout, "out")); | |||
child.on("error", reject); | |||
child.on("exit", resolve); | |||
}); | |||
} | |||
async function safe(...args) { | |||
var code = await shell.apply(null, args); | |||
if (code !== 0 && code !== null) | |||
throw new Error("Command "+args[0]+" failed, exit code "+code); | |||
return code; | |||
} |
@@ -0,0 +1,27 @@ | |||
{ | |||
"name": "emuboy", | |||
"version": "1.0.0", | |||
"lockfileVersion": 1, | |||
"requires": true, | |||
"dependencies": { | |||
"safe-buffer": { | |||
"version": "5.1.1", | |||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", | |||
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" | |||
}, | |||
"ultron": { | |||
"version": "1.1.0", | |||
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.0.tgz", | |||
"integrity": "sha1-sHoualQagV/Go0zNRTO67DB8qGQ=" | |||
}, | |||
"ws": { | |||
"version": "3.1.0", | |||
"resolved": "https://registry.npmjs.org/ws/-/ws-3.1.0.tgz", | |||
"integrity": "sha512-TU4/qKFlyQFqNITNWiqPCUY9GqlAhEotlzfcZcve6VT1YEngQl1dDMqwQQS3eMYruJ5r/UD3lcsWib6iVMDGDw==", | |||
"requires": { | |||
"safe-buffer": "5.1.1", | |||
"ultron": "1.1.0" | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,15 @@ | |||
{ | |||
"name": "emuboy", | |||
"version": "1.0.0", | |||
"description": "", | |||
"main": "index.js", | |||
"scripts": { | |||
"test": "echo \"Error: no test specified\" && exit 1", | |||
"postinstall": "make -C udev" | |||
}, | |||
"author": "Martin Dørum <martid0311@gmail.com> (http://mort.coffee)", | |||
"license": "ISC", | |||
"dependencies": { | |||
"ws": "^3.1.0" | |||
} | |||
} |
@@ -0,0 +1,114 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<joystick configversion="19" appversion="2.23"> | |||
<!--The SDL name for a joystick is included for informational purposes only.--> | |||
<sdlname>usb gamepad </sdlname> | |||
<!--The GUID for a joystick is included for informational purposes only.--> | |||
<guid>030000001008000001e5000010010000</guid> | |||
<sets> | |||
<set index="1"> | |||
<axis index="1"> | |||
<throttle>normal</throttle> | |||
<axisbutton index="1"> | |||
<slots> | |||
<slot> | |||
<code>0x1000012</code> | |||
<mode>keyboard</mode> | |||
</slot> | |||
</slots> | |||
</axisbutton> | |||
<axisbutton index="2"> | |||
<slots> | |||
<slot> | |||
<code>0x1000014</code> | |||
<mode>keyboard</mode> | |||
</slot> | |||
</slots> | |||
</axisbutton> | |||
</axis> | |||
<axis index="2"> | |||
<maxZone>32737</maxZone> | |||
<throttle>normal</throttle> | |||
<axisbutton index="1"> | |||
<slots> | |||
<slot> | |||
<code>0x1000013</code> | |||
<mode>keyboard</mode> | |||
</slot> | |||
</slots> | |||
</axisbutton> | |||
<axisbutton index="2"> | |||
<slots> | |||
<slot> | |||
<code>0x1000015</code> | |||
<mode>keyboard</mode> | |||
</slot> | |||
</slots> | |||
</axisbutton> | |||
</axis> | |||
<button index="1"> | |||
<slots> | |||
<slot> | |||
<code>0x58</code> | |||
<mode>keyboard</mode> | |||
</slot> | |||
</slots> | |||
</button> | |||
<button index="2"> | |||
<slots> | |||
<slot> | |||
<code>0x1000004</code> | |||
<mode>keyboard</mode> | |||
</slot> | |||
</slots> | |||
</button> | |||
<button index="3"> | |||
<slots> | |||
<slot> | |||
<code>0x1000003</code> | |||
<mode>keyboard</mode> | |||
</slot> | |||
</slots> | |||
</button> | |||
<button index="4"> | |||
<slots> | |||
<slot> | |||
<code>0x59</code> | |||
<mode>keyboard</mode> | |||
</slot> | |||
</slots> | |||
</button> | |||
<button index="5"> | |||
<slots> | |||
<slot> | |||
<code>0x1000016</code> | |||
<mode>keyboard</mode> | |||
</slot> | |||
</slots> | |||
</button> | |||
<button index="6"> | |||
<slots> | |||
<slot> | |||
<code>0x1000017</code> | |||
<mode>keyboard</mode> | |||
</slot> | |||
</slots> | |||
</button> | |||
<button index="9"> | |||
<slots> | |||
<slot> | |||
<code>0x1000001</code> | |||
<mode>keyboard</mode> | |||
</slot> | |||
</slots> | |||
</button> | |||
<button index="10"> | |||
<slots> | |||
<slot> | |||
<code>0x1000000</code> | |||
<mode>keyboard</mode> | |||
</slot> | |||
</slots> | |||
</button> | |||
</set> | |||
</sets> | |||
</joystick> |
@@ -0,0 +1,13 @@ | |||
#!/bin/sh | |||
# Left/Up/Down/Right: left/up/down/right | |||
# A: Enter | |||
# B: Backspace | |||
# X: X | |||
# Y: Y | |||
# Start: T | |||
# Select: E | |||
# LBumper: PgUp | |||
# RBumper: PgDn | |||
dir=$(dirname "$0") | |||
antimicro --hidden "$dir/keymap.joystick.amgp" |
@@ -0,0 +1,81 @@ | |||
var emu = require("./js/emulate"); | |||
var ipc = require("./js/ipc"); | |||
var shell = require("./js/shell"); | |||
var udev = require("./udev"); | |||
var pathlib = require("path"); | |||
var fs = require("fs"); | |||
var mountdir = "/tmp/emuboy.mnt" | |||
process.on("unhandledRejection", evt => { | |||
console.trace(evt); | |||
process.exit(1); | |||
}); | |||
var rompath = mountdir+"/roms"; | |||
var games = []; | |||
function updateGames() { | |||
if (driveMounted) { | |||
var rx = /\.(gba|gbc|nds)$/; | |||
fs.readdir(rompath, (err, list) => { | |||
if (err) | |||
throw err; | |||
games = list.filter(x => rx.test(x)); | |||
ipc.sendGameList(games); | |||
}); | |||
} else { | |||
games = []; | |||
ipc.sendGameList(games); | |||
} | |||
} | |||
var driveMounted = false; | |||
async function onblock(evt) { | |||
if (evt.DEVTYPE !== "partition") | |||
return; | |||
if (evt.PARTN !== "1") | |||
return; | |||
if (evt.ID_BUS !== "usb") | |||
return; | |||
if (!evt.ACTION || evt.ACTION === "add") { | |||
await shell.safe("mkdir", "-p", mountdir); | |||
if (await shell("mountpoint", "-q", mountdir) === 0) | |||
await shell.safe("sudo", "umount", mountdir); | |||
await shell.safe( | |||
"sudo", "mount", "-o", "uid="+process.env.USER, | |||
evt.DEVNAME, mountdir); | |||
await shell.safe("mkdir", "-p", rompath); | |||
driveMounted = true; | |||
updateGames(); | |||
} else if (evt.ACTION === "remove") { | |||
driveMounted = false; | |||
console.log("Unmounted drive", evt.DEVNAME); | |||
updateGames(); | |||
} | |||
} | |||
udev.init(); | |||
udev.monitor("block", onblock); | |||
udev.list("block", x => x.forEach(onblock)); | |||
ipc.init(8085, () => { | |||
console.log("Server started"); | |||
ipc.on("run", async function(name) { | |||
try { | |||
ipc.sendGameStart(); | |||
await emu.run(pathlib.join(rompath, name)); | |||
} catch (err) { | |||
ipc.error(err.toString()); | |||
console.trace(err); | |||
} | |||
ipc.sendGameStopped(); | |||
}); | |||
ipc.on("connection", () => { | |||
updateGames(); | |||
}); | |||
}); |
@@ -0,0 +1,10 @@ | |||
TARGET=udev-monitor | |||
SRC=src/main.c src/json.c | |||
HDR=src/json.h | |||
$(TARGET): $(SRC) $(HDR) | |||
$(CC) -o $(TARGET) -g $(shell pkg-config --cflags --libs libudev) $(SRC) | |||
clean: | |||
rm -f $(TARGET) |
@@ -0,0 +1,83 @@ | |||
var spawn = require("child_process").spawn; | |||
exports.list = list; | |||
exports.monitor = monitor; | |||
exports.unmonitor = unmonitor; | |||
exports.exit = exit; | |||
exports.init = init; | |||
var child; | |||
function init(cb) { | |||
child = spawn(__dirname+"/udev-monitor"); | |||
var currstr = ""; | |||
child.stdout.setEncoding("utf8"); | |||
child.stdout.on("data", d => { | |||
var lines = d.toString().split("\n"); | |||
currstr += lines[0]; | |||
if (lines.length === 1) | |||
return; | |||
ondata(JSON.parse(currstr)); | |||
for (var i = 1; i < lines.length -1; ++i) { | |||
ondata(JSON.parse(lines[i])); | |||
} | |||
currstr = lines[lines.length - 1]; | |||
}); | |||
child.stderr.on("data", d => console.error("udev error:", d.toString())); | |||
} | |||
var listq = []; | |||
var monitors = {}; | |||
function ondata(obj) { | |||
if (obj instanceof Array) { | |||
if (listq.length === 0) | |||
return; | |||
var cb = listq.shift(); | |||
cb(obj); | |||
} else { | |||
var ss = obj.SUBSYSTEM; | |||
if (monitors[ss]) { | |||
monitors[ss].forEach(cb => cb(obj)); | |||
} | |||
if (monitors["*"]) { | |||
monitors["*"].forEach(cb => cb(obj)); | |||
} | |||
} | |||
} | |||
function list(ss, cb) { | |||
listq.push(cb); | |||
child.stdin.write("list:"+ss+"\n"); | |||
} | |||
function monitor(ss, cb) { | |||
if (monitors[ss] == null) { | |||
child.stdin.write("monitor:"+ss+"\n"); | |||
monitors[ss] = []; | |||
} | |||
monitors[ss].push(cb); | |||
} | |||
function unmonitor(ss, cb) { | |||
if (monitors[ss] == null) | |||
throw new Error("Callback function for "+ss+" not registered"); | |||
var removed = false; | |||
for (var i in monitors[ss]) { | |||
if (monitors[ss][i] === cb) { | |||
monitors[ss].splice(i, 1); | |||
removed = true; | |||
} | |||
} | |||
} | |||
function exit() { | |||
child.kill("SIGTERM"); | |||
} |
@@ -0,0 +1,59 @@ | |||
#include "json.h" | |||
#include <stdio.h> | |||
void json_str(FILE *f, const char *str) | |||
{ | |||
int start, i; | |||
char c; | |||
fprintf(f, "\""); | |||
start = 0; | |||
i = 0; | |||
while (1) | |||
{ | |||
c = str[i]; | |||
if (c == '\0') | |||
{ | |||
fwrite(str + start, 1, i - start, f); | |||
break; | |||
} | |||
else if (c == '\\' || c == '\"') | |||
{ | |||
fwrite(str + start, 1, i - start, f); | |||
fprintf(f, "\\%c", str[i]); | |||
start = i + 1; | |||
} | |||
i += 1; | |||
} | |||
fprintf(f, "\""); | |||
} | |||
void json_sep(FILE *f) | |||
{ | |||
fprintf(f, ","); | |||
} | |||
void json_obj_start(FILE *f) | |||
{ | |||
fprintf(f, "{"); | |||
} | |||
void json_obj_key(FILE *f, const char *key) | |||
{ | |||
json_str(f, key); | |||
fprintf(f, ":"); | |||
} | |||
void json_obj_end(FILE *f) | |||
{ | |||
fprintf(f, "}"); | |||
} | |||
void json_arr_start(FILE *f) | |||
{ | |||
fprintf(f, "["); | |||
} | |||
void json_arr_end(FILE *f) | |||
{ | |||
fprintf(f, "]"); | |||
} |
@@ -0,0 +1,16 @@ | |||
#ifndef JSON_H | |||
#define JSON_H | |||
#include <stdio.h> | |||
void json_str(FILE *f, const char *str); | |||
void json_sep(FILE *f); | |||
void json_obj_start(FILE *f); | |||
void json_obj_key(FILE *f, const char *key); | |||
void json_obj_end(FILE *f); | |||
void json_arr_start(FILE *f); | |||
void json_arr_end(FILE *f); | |||
#endif |
@@ -0,0 +1,287 @@ | |||
#include "json.h" | |||
#include <libudev.h> | |||
#include <stdio.h> | |||
#include <sys/select.h> | |||
#include <unistd.h> | |||
#include <errno.h> | |||
#include <string.h> | |||
#include <fcntl.h> | |||
#include <stdlib.h> | |||
struct udev *udev; | |||
FILE *out; | |||
struct listener | |||
{ | |||
struct udev_monitor *mon; | |||
int fd; | |||
}; | |||
struct listener listeners[32]; | |||
int listenerc = 0; | |||
struct udev_monitor *mons[32]; | |||
static void devinfo_print_json( | |||
FILE *f, | |||
struct udev *udev, | |||
struct udev_device *dev) | |||
{ | |||
struct udev_list_entry *entry, *sysattrs; | |||
int first; | |||
const char *key, *val; | |||
json_obj_start(f); | |||
sysattrs = udev_device_get_properties_list_entry(dev); | |||
first = 1; | |||
udev_list_entry_foreach(entry, sysattrs) | |||
{ | |||
key = udev_list_entry_get_name(entry); | |||
val = udev_list_entry_get_value(entry); | |||
if (!first) | |||
json_sep(f); | |||
json_obj_key(f, key); | |||
json_str(f, val); | |||
first = 0; | |||
} | |||
json_obj_end(f); | |||
udev_device_unref(dev); | |||
} | |||
static void enumerate(FILE *f, const char *subsystem) | |||
{ | |||
struct udev_enumerate *en; | |||
struct udev_list_entry *device, *devices; | |||
struct udev_device *dev; | |||
int first; | |||
const char *name; | |||
// Create enumerator | |||
en = udev_enumerate_new(udev); | |||
if (subsystem != NULL) | |||
udev_enumerate_add_match_subsystem(en, subsystem); | |||
json_arr_start(f); | |||
// Enumerate | |||
udev_enumerate_scan_devices(en); | |||
devices = udev_enumerate_get_list_entry(en); | |||
first = 1; | |||
udev_list_entry_foreach(device, devices) | |||
{ | |||
if (!first) | |||
json_sep(f); | |||
name = udev_list_entry_get_name(device); | |||
dev = udev_device_new_from_syspath(udev, name); | |||
devinfo_print_json(f, udev, dev); | |||
first = 0; | |||
} | |||
json_arr_end(f); | |||
printf("\n"); | |||
fflush(f); | |||
udev_enumerate_unref(en); | |||
} | |||
int monitor(const char *subsystem) | |||
{ | |||
struct udev_monitor *mon; | |||
struct listener *l; | |||
mon = udev_monitor_new_from_netlink(udev, "udev"); | |||
if (!mon) | |||
return -1; | |||
if (subsystem != NULL) | |||
{ | |||
udev_monitor_filter_add_match_subsystem_devtype( | |||
mon, subsystem, NULL); | |||
} | |||
udev_monitor_enable_receiving(mon); | |||
l = &listeners[listenerc++]; | |||
l->mon = mon; | |||
l->fd = udev_monitor_get_fd(mon); | |||
return 0; | |||
} | |||
void oncommand(struct listener *l) | |||
{ | |||
char buf[1024]; | |||
char *cmd, *arg; | |||
int cnt, i; | |||
char c; | |||
memset(buf, 0, sizeof(buf)); | |||
cnt = read(l->fd, buf, sizeof(buf)); | |||
if (cnt == -1) | |||
{ | |||
perror("stdin"); | |||
return; | |||
} | |||
else if (cnt == 0) | |||
{ | |||
// don't try to read from stdin anymore if it's closed | |||
l->fd = -1; | |||
return; | |||
} | |||
else if (cnt == sizeof(buf)) | |||
{ | |||
fprintf(stderr, "input buffer too big\n"); | |||
// drain, so we don't end up reading stale data next time | |||
do { | |||
cnt = read(l->fd, buf, sizeof(buf)); | |||
if (cnt == -1) | |||
{ | |||
perror("stdin"); | |||
return; | |||
} | |||
} while (cnt == sizeof(buf)); | |||
return; | |||
} | |||
// parse input into command and argument | |||
cmd = buf; | |||
arg = NULL; | |||
i = 0; | |||
while (1) | |||
{ | |||
c = buf[i]; | |||
// colon: replace with \0 to terminate cmd, | |||
// update arg to point to the charcater after : | |||
if (c == ':') | |||
{ | |||
buf[i] = '\0'; | |||
arg = buf + i + 1; | |||
} | |||
// newline: replace with \0 to terminate arg, | |||
// respond to request, then update cmd and | |||
// continue reading the buffer until we reach a \0 | |||
else if (c == '\n' || c == '\0') | |||
{ | |||
buf[i] = '\0'; | |||
if (*cmd == '\0') | |||
break; | |||
// respond to request | |||
if (strcmp(cmd, "list") == 0) | |||
{ | |||
if (strcmp(arg, "*") == 0) | |||
enumerate(out, NULL); | |||
else | |||
enumerate(out, arg); | |||
} | |||
else if (strcmp(cmd, "monitor") == 0) | |||
{ | |||
if (strcmp(arg, "*") == 0) | |||
monitor(NULL); | |||
else | |||
monitor(arg); | |||
} | |||
else | |||
{ | |||
fprintf(stderr, "Unknown command: %s\n", cmd); | |||
} | |||
if (c == '\0') | |||
break; | |||
cmd = buf + i + 1; | |||
} | |||
i += 1; | |||
} | |||
} | |||
void onevent(struct listener *l) | |||
{ | |||
struct udev_device *dev = udev_monitor_receive_device(l->mon); | |||
if (!dev) | |||
{ | |||
fprintf(stderr, "No device even though an event occurred.\n"); | |||
return; | |||
} | |||
devinfo_print_json(out, udev, dev); | |||
printf("\n"); | |||
fflush(out); | |||
} | |||
void readinput() | |||
{ | |||
fd_set set; | |||
FD_ZERO(&set); | |||
int max = -1; | |||
for (int i = 0; i < listenerc; ++i) | |||
{ | |||
int fd = listeners[i].fd; | |||
if (fd == -1) | |||
continue; | |||
FD_SET(fd, &set); | |||
if (fd > max) | |||
max = fd; | |||
} | |||
int activity; | |||
activity = select(max + 1, &set, NULL, NULL, NULL); | |||
if (activity < 0) | |||
{ | |||
if (errno != EINTR) | |||
perror("select"); | |||
return; | |||
} | |||
for (int i = 0; i < listenerc; ++i) | |||
{ | |||
struct listener *l = &listeners[i]; | |||
int fd = l->fd; | |||
if (!FD_ISSET(fd, &set)) | |||
continue; | |||
if (fd == STDIN_FILENO) | |||
oncommand(l); | |||
else | |||
onevent(l); | |||
} | |||
} | |||
int main() | |||
{ | |||
out = stdout; | |||
memset(listeners, 0, sizeof(listeners)); | |||
// read stdin | |||
listeners[listenerc++].fd = STDIN_FILENO; | |||
// create udev | |||
udev = udev_new(); | |||
if (!udev) | |||
{ | |||
fprintf(stderr, "Failed to craete udev\n"); | |||
return 1; | |||
} | |||
// main loop | |||
while (1) { | |||
readinput(); | |||
} | |||
udev_unref(udev); | |||
} |
@@ -0,0 +1,17 @@ | |||
<!DOCTYPE html> | |||
<html> | |||
<head> | |||
<meta charset="utf-8"> | |||
<link rel="stylesheet" href="style.css"> | |||
<title></title> | |||
</head> | |||
<body> | |||
<div id="games"></div> | |||
<div id="overlay"> | |||
Game running. | |||
</div> | |||
<script src="ipc.js"></script> | |||
<script src="script.js"></script> | |||
</body> | |||
</html> |
@@ -0,0 +1,62 @@ | |||
(function() { | |||
window.ipc = {}; | |||
ipc.ongamestopped = null; | |||
ipc.ongamelist = null; | |||
ipc.onerror = null; | |||
var ws; | |||
var connected = false; | |||
function send(obj) { | |||
var msg = JSON.stringify(obj); | |||
if (!connected) | |||
return console.error( | |||
"Attempt to send message, but socket is closed."); | |||
console.log("Sending", msg); | |||
ws.send(msg); | |||
} | |||
ipc.init = (port, cb) => { | |||
console.log("Connecting..."); | |||
ws = new WebSocket("ws://127.0.0.1:"+port); | |||
ws.onopen = () => { | |||
connected = true; | |||
console.log("Connected."); | |||
cb && cb(); | |||
} | |||
ws.onmessage = msg => { | |||
console.log("Received", msg.data); | |||
var obj = JSON.parse(msg.data); | |||
switch (obj.cmd) { | |||
case "ongamestopped": | |||
return ipc.ongamestopped(); | |||
case "ongamestart": | |||
return ipc.ongamestart(); | |||
case "ongamelist": | |||
return ipc.ongamelist(obj.games); | |||
case "onerror": | |||
return ipc.onerror(obj.msg); | |||
default: | |||
console.error("Received unknown command:", obj.cmd); | |||
} | |||
} | |||
ws.onclose = () => { | |||
connected = false; | |||
console.log("Disconnected, retrying in 1 second..."); | |||
setTimeout(ipc.init.bind(null, port, cb), 1000); | |||
} | |||
} | |||
ipc.run = name => { | |||
send({ cmd: "run", name: name }); | |||
} | |||
})(); |
@@ -0,0 +1,35 @@ | |||
var elems = { | |||
games: document.getElementById("games"), | |||
overlay: document.getElementById("overlay"), | |||
}; | |||
function makeGame(game) { | |||
var a = document.createElement("a"); | |||
a.innerText = game; | |||
a.href = "javascript:void(0)"; | |||
a.onclick = ipc.run.bind(null, game); | |||
return a; | |||
} | |||
ipc.init(8085, () => { | |||
elems.overlay.className = ""; | |||
}); | |||
ipc.ongamestopped = () => { | |||
console.log("Game stopped."); | |||
elems.overlay.className = ""; | |||
} | |||
ipc.ongamestart = () => { | |||
console.log("Game started."); | |||
elems.overlay.className = "active"; | |||
} | |||
ipc.ongamelist = games => { | |||
elems.games.innerHTML = ""; | |||
games.forEach(g => elems.games.appendChild(makeGame(g))); | |||
} | |||
ipc.onerror = msg => { | |||
alert(msg); | |||
} |
@@ -0,0 +1,39 @@ | |||
html, body { | |||
overflow: hidden; | |||
position: fixed; | |||
width: 100%; | |||
height: 100%; | |||
margin: 0px; | |||
} | |||
#games { | |||
padding: 24px; | |||
} | |||
#games a { | |||
margin: 12px; | |||
display: block; | |||
} | |||
#overlay { | |||
transition: opacity 0.5s, transform 0.5s; | |||
opacity: 0; | |||
pointer-events: none; | |||
position: absolute; | |||
top: 0px; | |||
left: 0px; | |||
width: 100%; | |||
height: 100%; | |||
background-color: black; | |||
color: white; | |||
transform: scale(2); | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
} | |||
#overlay.active { | |||
opacity: 1; | |||
transform: scale(1); | |||
} |