Zidoo V-12 Remote

  • Here I got AI to write it.


    Tested working on debian with Zidoo V12.

    Run as root with the Zidoo V12 Keyboard device as arg 1: sudo ./uinput-forwarder /dev/input/eventX

    I had to chmod /dev/uinput

    sudo chmod +0666 /dev/uinput

    since libre is running as root already drop the sudo for libre


  • the issue I had, and/or found was libinput tablet-pad misclassification, Kodi sees the remote as a tablet-pad. And no amount of wrngling would change it. This approach worked for me.
    It's been updated a bit due to libreelec turning off bluetooth every 5 hours too. I'll post the full lot in the next post


    Zidoo V12 Remote on LibreELEC/Kodi — Full Setup Guide

    Background

    The Zidoo V12 is a BLE (Bluetooth Low Energy) HID remote. On LibreELEC, libinput misclassifies the V12's keyboard input node (event10) as a tablet-pad device, causing Kodi to grab it but ignore all input. The solution bypasses libinput entirely using a Python daemon that reads event10 directly and forwards keypresses to Kodi via its JSON-RPC API.


    Prerequisites

    • LibreELEC installed and running
    • Kodi webserver enabled: Settings → Services → Control → Allow remote control via HTTP
      • Port: 8080
      • Authentication: disabled
    • V12 remote powered on and in pairing mode

    Step 1: Pair the Remote

    Code
    bluetoothctl
    agent on
    default-agent
    scan on
    # Wait for "Remote-V12" to appear and note the MAC address
    pair XX:XX:XX:XX:XX:XX
    trust XX:XX:XX:XX:XX:XX
    connect XX:XX:XX:XX:XX:XX
    exit
    Quote

    The MAC address for this remote is 25:04:27:11:28:95 — if re-paired it may change. Update all references accordingly.


    Step 2: Configure BlueZ for HID Reconnection

    /etc is read-only squashfs on LibreELEC. We override the bluetooth service via a systemd drop-in and point it at a writable config in /storage.

    Verify it worked:

    Code
    ps | grep bluetoothd
    # Should show: /usr/lib/bluetooth/bluetoothd --configfile=/storage/.config/bluetooth/main.conf

    Step 3: udev Rule

    Tells libinput to ignore event10 entirely, preventing Kodi from grabbing it and leaving it free for our daemon.

    Code
    mkdir -p /storage/.config/udev.rules.d
    
    cat > /storage/.config/udev.rules.d/99-zidoo-v12.rules << 'EOF'
    KERNEL=="event*", SUBSYSTEM=="input", ATTRS{name}=="Remote-V12 Keyboard", ENV{LIBINPUT_IGNORE_DEVICE}="1", TAG+="uaccess"
    EOF
    
    udevadm control --reload-rules
    udevadm trigger

    Step 4: The Daemon Script

    Reads /dev/input/event10 directly via Python's struct module (no external dependencies) and forwards keypresses to Kodi's JSON-RPC API.

    Quote

    Note: Volume down is remapped by the kernel hwdb to KEY_COMMA (code 51) — this is a V12 firmware quirk, not a bug in the script. The yellow button (KEY_YELLOW / code 400) is not present on this remote's physical layout.


    Step 5: Daemon Service


    Step 6: Boot Connect Service

    Connects the remote automatically at boot without blocking the boot sequence. The & backgrounds the bluetoothctl call so boot continues immediately.


    Step 7: Bluetooth Watchdog

    Detects and recovers from BlueZ lockups and dropped connections. Runs every 2 minutes. Only intervenes if event10 is missing or the daemon has died.


    Verification

    Code
    # Check all services are running
    systemctl status v12-remote
    systemctl status bt-reconnect.timer
    systemctl status bt-connect-boot.service
    
    # Watch the daemon live
    journalctl -u v12-remote -f
    
    # Check watchdog history
    journalctl -u bt-reconnect.service --since "1 hour ago"

    Troubleshooting

    SymptomCheckFix
    Remote not responding after bootls /dev/input/event10Wait 10–15s, or bluetoothctl connect 25:04:27:11:28:95
    Daemon not runningsystemctl status v12-remotesystemctl restart v12-remote
    BlueZ unresponsivebluetoothctl show returns nothingsystemctl restart bluetooth
    Wrong event nodecat /proc/bus/input/devices | grep -A2 "V12"Update DEVICE in v12-remote.py
    bluetoothd missing --configfileps | grep bluetoothdRe-apply Step 2

    File Summary

    FilePurpose
    /storage/.config/bluetooth/main.confBlueZ config with HID reconnect UUIDs
    /storage/.config/system.d/bluetooth.service.d/configfile.confSystemd drop-in to pass --configfile to bluetoothd
    /storage/.config/udev.rules.d/99-zidoo-v12.rulesudev rule to hide event10 from libinput
    /storage/v12-remote.pyMain daemon script
    /storage/.config/system.d/v12-remote.serviceSystemd service for the daemon
    /storage/.config/system.d/bt-connect-boot.serviceBoot-time connect service
    /storage/bt-reconnect.shWatchdog script
    /storage/.config/system.d/bt-reconnect.serviceWatchdog service unit
    /storage/.config/system.d/bt-reconnect.timerWatchdog timer (every 2 min)

    Notes

    • All config lives in /storage and survives LibreELEC updates and reboots
    • /etc is read-only squashfs — never edit it directly
    • event10 = Remote-V12 Keyboard, event11 = Remote-V12 Mouse
    • BlueZ logs HOG profile errors on V12 reconnect — these are harmless and normal for this device
    • The V12 MAC address is 25:04:27:11:28:95 — update all references if re-paired
    • LibreELEC nightly builds may clear /storage/.cache/services/bluez.conf on boot — this is why the systemd drop-in override is used instead

    Removal Guide

    To completely remove all V12 remote configuration and return the system to its default state.

    Step 1: Stop and Disable Services

    Code
    systemctl stop v12-remote
    systemctl disable v12-remote
    
    systemctl stop bt-reconnect.timer
    systemctl disable bt-reconnect.timer
    
    systemctl stop bt-connect-boot.service
    systemctl disable bt-connect-boot.service

    Step 2: Remove Service and Timer Files

    Code
    rm /storage/.config/system.d/v12-remote.service
    rm /storage/.config/system.d/bt-connect-boot.service
    rm /storage/.config/system.d/bt-reconnect.service
    rm /storage/.config/system.d/bt-reconnect.timer
    rm -rf /storage/.config/system.d/multi-user.target.wants/bt-connect-boot.service
    rm -rf /storage/.config/system.d/multi-user.target.wants/v12-remote.service
    rm -rf /storage/.config/system.d/timers.target.wants/bt-reconnect.timer

    Step 3: Remove Scripts

    Code
    rm /storage/v12-remote.py
    rm /storage/bt-reconnect.sh

    Step 4: Remove udev Rule

    Code
    rm /storage/.config/udev.rules.d/99-zidoo-v12.rules
    udevadm control --reload-rules
    udevadm trigger

    Step 5: Remove BlueZ Override

    Code
    rm /storage/.config/system.d/bluetooth.service.d/configfile.conf
    rmdir /storage/.config/system.d/bluetooth.service.d 2>/dev/null
    rm /storage/.config/bluetooth/main.conf
    rmdir /storage/.config/bluetooth 2>/dev/null

    Restart bluetooth to pick up the removal:

    Code
    systemctl daemon-reload
    systemctl restart bluetooth

    Verify bluetoothd is back to default (no --configfile:(

    Code
    ps | grep bluetoothd
    # Should show: /usr/lib/bluetooth/bluetoothd  (no --configfile)

    Step 6: Unpair the Remote (optional)

    Code
    bluetoothctl
    remove 25:04:27:11:28:95
    exit

    Step 7: Reload systemd

    Code
    systemctl daemon-reload
    systemctl reset-failed

    That's it — the system is back to its default state with no trace of the V12 configuration.


    LibreELEC solution requires the libinput misclassification fix first regardless of whether you use uinput, JSON-RPC or LIRC
    Libreelec deoesn't have in itself a c++ compiler, as far as I'm aware. But Debian does. But the issue still seems to stem from the misclassing of the remote as a touchpad

    Edited 2 times, last by lusephur: Merged a post created by lusephur into this post. (May 2, 2026 at 1:03 PM).

  • LibreELEC solution requires the libinput misclassification fix first regardless of whether you use uinput, JSON-RPC or LIRC
    Libreelec deoesn't have in itself a c++ compiler, as far as I'm aware. But Debian does. But the issue still seems to stem from the misclassing of the remote as a touchpad

    I think the misclassification is also the issue in debian.

    The code provided creates a NEW generic usb keyboard device which IS NOT misclassified and Kodi will respond to.

    The keycodes Kodi is responding to from that device are just captured from the Zidoo device and re-sent (forwarded).

    Libre doesn't need to compile the code, compile the code elsewhere and copy the binary to Libre

    gcc uinput-forwarder.c -static -o uinput-forwarder


    or cross compile to aarch64

    aarch64-linux-gnu-gcc uinput-forwarder.c -static -o uinput-forwarder

    or cross compile to arm32 (the print_event function needs rework for arm32 but it's only for debugging so can be remove)

    arm-linux-gnueabihf-gcc uinput-forwarder.c -static -o uinput-forwarder


    3 Solutions to the same problem, each with merit