做个minecraft kiosk

📅 2026/7/17 2:51:19 👁️ 阅读次数 📝 编程学习
做个minecraft kiosk

水鱼拿起尘封已久的树莓派4B干了件大事。创建用户minecraft,给到需要的权限(id minecraft
uid=1001(minecraft) gid=1001(minecraft) groups=1001(minecraft),46(plugdev),100(users),102(input)),创建~/.xinitrc文件,给到以下。

#!/bin/bash xset s off xset -dpms exec /usr/bin/minecraft-pi-reborn

这个就是伪kiosk精髓了,开机默认minecraft用户,然后开游戏。

讲究点加个音效文件,记得找libminecraftpe06+08.so。其他在树莓派上是不灵光的,没有声音。

再讲究点加个70元xbox手柄,但是自己要写映射键位,据说可以不写映射直接玩,我是没试成功,游戏无响应。

#路径是/usr/local/bin/mcpi-pad #!/usr/bin/env bash set -euo pipefail ACTION="${1:-}" PID_FILE="/run/mcpi-pad.pid" LOG_FILE="/tmp/mcpi-pad.log" DAEMON="/usr/local/bin/mcpi-pad-daemon.py" is_running() { if [[ ! -f "$PID_FILE" ]]; then return 1 fi local pid pid="$(cat "$PID_FILE" 2>/dev/null || true)" [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null } start_pad() { if is_running; then echo "mcpi-pad daemon is already running" ps -fp "$(cat "$PID_FILE")" || true return 0 fi sudo pkill -9 -x xboxdrv 2>/dev/null || true nohup python3 "$DAEMON" >"$LOG_FILE" 2>&1 & echo $! | sudo tee "$PID_FILE" >/dev/null sleep 0.5 if is_running; then echo "mcpi-pad daemon started" ps -fp "$(cat "$PID_FILE")" else echo "mcpi-pad daemon failed to start" [[ -f "$LOG_FILE" ]] && tail -n 80 "$LOG_FILE" exit 1 fi } stop_pad() { if is_running; then local pid pid="$(cat "$PID_FILE")" kill "$pid" 2>/dev/null || true sleep 0.2 kill -9 "$pid" 2>/dev/null || true sudo rm -f "$PID_FILE" echo "mcpi-pad daemon stopped" else echo "mcpi-pad daemon is not running" fi } status_pad() { if is_running; then echo "mcpi-pad daemon is running" ps -fp "$(cat "$PID_FILE")" else echo "mcpi-pad daemon is not running" fi } case "$ACTION" in start) start_pad ;; stop) stop_pad ;; restart) stop_pad start_pad ;; status) status_pad ;; log) [[ -f "$LOG_FILE" ]] && tail -n 120 "$LOG_FILE" || echo "No log file: $LOG_FILE" ;; *) echo "Usage: sudo mcpi-pad {start|stop|restart|status|log}" exit 2 ;; esac

以及

#路径/usr/local/bin/mcpi-pad-daemon.py #!/usr/bin/env python3 import signal import sys import time from collections import defaultdict from evdev import InputDevice, UInput, ecodes DEVICE_NAME = "Xbox Wireless Controller" THRESHOLD = 12000 CENTER = 32768 MOUSE_DEADZONE = 5000 MOUSE_MAX_STEP = 25 def find_event_device() -> str: with open('/proc/bus/input/devices', 'r', encoding='utf-8') as f: blocks = f.read().split('\n\n') for block in blocks: if DEVICE_NAME in block: for tok in block.split(): if tok.startswith('event') and tok[5:].isdigit(): return f'/dev/input/{tok}' raise RuntimeError('Xbox controller event device not found') pressed = defaultdict(set) def press(ui: UInput, src: str, key: int) -> None: if key in pressed[src]: return pressed[src].add(key) if not any(key in v for k, v in pressed.items() if k != src): ui.write(ecodes.EV_KEY, key, 1) def release(ui: UInput, src: str, key: int) -> None: if key not in pressed[src]: return pressed[src].remove(key) if not any(key in v for v in pressed.values()): ui.write(ecodes.EV_KEY, key, 0) def set_axis(ui: UInput, src: str, neg: int, pos: int, value: int) -> None: delta = value - CENTER if delta < -THRESHOLD: press(ui, src, neg) release(ui, src, pos) elif delta > THRESHOLD: press(ui, src, pos) release(ui, src, neg) else: release(ui, src, neg) release(ui, src, pos) def set_hat(ui: UInput, src: str, neg: int, pos: int, value: int) -> None: if value < 0: press(ui, src, neg) release(ui, src, pos) elif value > 0: press(ui, src, pos) release(ui, src, neg) else: release(ui, src, neg) release(ui, src, pos) def emit_mouse_rel(ui: UInput, rel_code: int, value: int, invert: bool = False) -> bool: delta = value - CENTER if abs(delta) <= MOUSE_DEADZONE: return False span = 32767 - MOUSE_DEADZONE magnitude = int(((abs(delta) - MOUSE_DEADZONE) / span) * MOUSE_MAX_STEP) magnitude = max(1, min(MOUSE_MAX_STEP, magnitude)) direction = -1 if delta < 0 else 1 if invert: direction *= -1 ui.write(ecodes.EV_REL, rel_code, direction * magnitude) return True def release_all(ui: UInput) -> None: keys = set() for ks in pressed.values(): keys.update(ks) for key in keys: ui.write(ecodes.EV_KEY, key, 0) ui.syn() def main() -> int: event_dev = find_event_device() pad = InputDevice(event_dev) kb_ui = UInput( { ecodes.EV_KEY: [ ecodes.KEY_W, ecodes.KEY_A, ecodes.KEY_S, ecodes.KEY_D, ecodes.KEY_SPACE, ecodes.KEY_LEFTCTRL, ecodes.KEY_ESC, ecodes.KEY_E, ] }, name='MCPI Pad Keyboard', bustype=0x03, ) mouse_ui = UInput( { ecodes.EV_KEY: [ecodes.BTN_LEFT, ecodes.BTN_RIGHT], ecodes.EV_REL: [ecodes.REL_X, ecodes.REL_Y], }, name='MCPI Pad Mouse', bustype=0x03, ) btn_map = { ecodes.BTN_SOUTH: ecodes.KEY_SPACE, ecodes.BTN_EAST: ecodes.KEY_LEFTCTRL, ecodes.BTN_C: ecodes.KEY_ESC, ecodes.BTN_Z: ecodes.KEY_E, } stop = False def _stop(_sig, _frm): nonlocal stop stop = True signal.signal(signal.SIGTERM, _stop) signal.signal(signal.SIGINT, _stop) while not stop: try: for ev in pad.read_loop(): if stop: break if ev.type == ecodes.EV_KEY and ev.code in btn_map: key = btn_map[ev.code] if ev.value: press(kb_ui, 'btn', key) else: release(kb_ui, 'btn', key) kb_ui.syn() elif ev.type == ecodes.EV_ABS: if ev.code == ecodes.ABS_X: set_axis(kb_ui, 'lx', ecodes.KEY_A, ecodes.KEY_D, ev.value) kb_ui.syn() elif ev.code == ecodes.ABS_Y: set_axis(kb_ui, 'ly', ecodes.KEY_W, ecodes.KEY_S, ev.value) kb_ui.syn() elif ev.code == ecodes.ABS_RX: if emit_mouse_rel(mouse_ui, ecodes.REL_X, ev.value): mouse_ui.syn() elif ev.code == ecodes.ABS_RY: if emit_mouse_rel(mouse_ui, ecodes.REL_Y, ev.value, invert=True): mouse_ui.syn() elif ev.code == ecodes.ABS_HAT0X: set_hat(kb_ui, 'hx', ecodes.KEY_A, ecodes.KEY_D, ev.value) kb_ui.syn() elif ev.code == ecodes.ABS_HAT0Y: set_hat(kb_ui, 'hy', ecodes.KEY_W, ecodes.KEY_S, ev.value) kb_ui.syn() except OSError: time.sleep(0.2) try: event_dev = find_event_device() pad = InputDevice(event_dev) except Exception: time.sleep(0.5) release_all(kb_ui) pad.close() kb_ui.close() mouse_ui.close() return 0 if __name__ == '__main__': sys.exit(main())

反正最后游戏柄还是很难调试,还是键鼠套餐香。