udev/udev-monitor | |||||
node_modules |
[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 |
[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 |
[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= |
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"); | |||||
} |
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 }); | |||||
} |
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; | |||||
} |
{ | |||||
"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" | |||||
} | |||||
} | |||||
} | |||||
} |
{ | |||||
"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" | |||||
} | |||||
} |
<?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> |
#!/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" |
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(); | |||||
}); | |||||
}); |
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) |
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"); | |||||
} |
#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, "]"); | |||||
} |
#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 |
#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); | |||||
} |
<!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> |
(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 }); | |||||
} | |||||
})(); |
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); | |||||
} |
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); | |||||
} |