Skip to content

Instantly share code, notes, and snippets.

@Hermann-SW
Forked from Gadgetoid/Makefile
Last active January 4, 2022 02:06
Show Gist options
  • Save Hermann-SW/19c7522220a4ec3c8d6ecc89de2b0a60 to your computer and use it in GitHub Desktop.
Save Hermann-SW/19c7522220a4ec3c8d6ecc89de2b0a60 to your computer and use it in GitHub Desktop.
Pi 400 KB

Raspberry Pi 400 as a USB HID Keyboard

Hook your Pi 400 up to your PC somehow, using a USB Type-C cable into the power port. Anker make good ones- I used a 3m white one for my tests.

Setup

Add dtoverlay=dwc2 to /boot/config.txt

Reboot!

Clone libusbgx somewhere and build according INSTALL, including "sudo make install"
https://github.com/linux-usb-gadgets/libusbgx

Run sudo modprobe libcomposite

Run make

Run sudo ./pi400

YOUR PI 400 IS NOW A FREAKING KEYBOARD FOR YOUR PC WHAAAAT!?

Your keyboard input will be detached from your Pi while it's forwarded to your host computer.

Press Ctrl + Raspberry to exit and restore your keyboard on the Pi.

I did copy self-built "pi400" to "/usr/local/bin" and use now "laptop" alias for using Pi400 as laptop keyboard:

pi@raspberrypi400:~ $ tail -1 ~/.bashrc
alias laptop="sudo pi400 2>/dev/null >/dev/null"
pi@raspberrypi400:~ $ 
#include "gadget-hid.h"
#include <errno.h>
#include <stdio.h>
#include <usbg/function/midi.h>
static char report_desc[] = {
0x05, 0x01,
0x09, 0x06,
0xA1, 0x01,
0x05, 0x07,
0x19, 0xe0,
0x29, 0xe7,
0x15, 0x00,
0x25, 0x01,
0x75, 0x01,
0x95, 0x08,
0x81, 0x02,
0x95, 0x01,
0x75, 0x08,
0x81, 0x01,
0x95, 0x03,
0x75, 0x01,
0x05, 0x08,
0x19, 0x01,
0x29, 0x03,
0x91, 0x02,
0x95, 0x05,
0x75, 0x01,
0x91, 0x01,
0x95, 0x06,
0x75, 0x08,
0x15, 0x00,
0x26, 0xff,
0x00, 0x05,
0x07, 0x19,
0x00, 0x2a,
0xff, 0x00,
0x81, 0x00, // Input (Data, Array, Abs)
0xc0 // End collection
};
int initUSB() {
int ret = -EINVAL;
int usbg_ret;
struct usbg_gadget_attrs g_attrs = {
.bcdUSB = 0x0200,
.bDeviceClass = USB_CLASS_PER_INTERFACE,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = 64, /* Max allowed ep0 packet size */
.idVendor = VENDOR,
.idProduct = PRODUCT,
.bcdDevice = 0x0001, /* Verson of device */
};
struct usbg_gadget_strs g_strs = {
.serial = "0123456789", /* Serial number */
.manufacturer = "Pimoroni", /* Manufacturer */
.product = "Keybow" /* Product string */
};
struct usbg_config_strs c_strs = {
.configuration = "1xHID"
};
struct usbg_f_midi_attrs midi_attrs = {
.index = 1,
.id = "usb1",
.buflen = 128,
.qlen = 16,
.in_ports = 1,
.out_ports = 1
};
struct usbg_f_hid_attrs f_attrs = {
.protocol = 1,
.report_desc = {
.desc = report_desc,
.len = sizeof(report_desc),
},
.report_length = 16,
.subclass = 0,
};
usbg_ret = usbg_init("/sys/kernel/config", &s);
if (usbg_ret != USBG_SUCCESS) {
fprintf(stderr, "Error on usbg init\n");
fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret),
usbg_strerror(usbg_ret));
goto out1;
}
usbg_ret = usbg_create_gadget(s, "g1", &g_attrs, &g_strs, &g);
if (usbg_ret != USBG_SUCCESS) {
fprintf(stderr, "Error creating gadget\n");
fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret),
usbg_strerror(usbg_ret));
goto out2;
}
usbg_ret = usbg_create_function(g, USBG_F_HID, "usb0", &f_attrs, &f_hid);
if (usbg_ret != USBG_SUCCESS) {
fprintf(stderr, "Error creating function: USBG_F_HID\n");
fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret),
usbg_strerror(usbg_ret));
goto out2;
}
usbg_ret = usbg_create_config(g, 1, "config", NULL, &c_strs, &c);
if (usbg_ret != USBG_SUCCESS) {
fprintf(stderr, "Error creating config\n");
fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret),
usbg_strerror(usbg_ret));
goto out2;
}
usbg_ret = usbg_add_config_function(c, "keyboard", f_hid);
if (usbg_ret != USBG_SUCCESS) {
fprintf(stderr, "Error adding function: keyboard\n");
fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret),
usbg_strerror(usbg_ret));
goto out2;
}
usbg_ret = usbg_enable_gadget(g, DEFAULT_UDC);
if (usbg_ret != USBG_SUCCESS) {
fprintf(stderr, "Error enabling gadget\n");
fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret),
usbg_strerror(usbg_ret));
goto out2;
}
ret = 0;
out2:
usbg_cleanup(s);
s = NULL;
out1:
return ret;
}
int cleanupUSB(){
if(g){
usbg_disable_gadget(g);
usbg_rm_gadget(g, USBG_RM_RECURSE);
}
if(s){
usbg_cleanup(s);
}
return 0;
}
#include <linux/usb/ch9.h>
#include <usbg/usbg.h>
#include <usbg/function/hid.h>
#define VENDOR 0x04d9
#define PRODUCT 0x0007
#define HID_REPORT_SIZE 8
usbg_state *s;
usbg_gadget *g;
usbg_config *c;
usbg_function *f_hid;
int initUSB();
int cleanupUSB();
pi400: CFLAGS+=-static -lusbgx -lconfig -DPI400_USB
pi400: pi400.c gadget-hid.c
$(CC) $^ $(CFLAGS) -o $@
clean:
-rm pi400
#include "pi400.h"
#include "gadget-hid.h"
#include <sys/ioctl.h>
#include <linux/hidraw.h>
#include <linux/input.h>
#include <signal.h>
#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define KEYBOARD_DEV "/dev/input/by-id/usb-_Raspberry_Pi_Internal_Keyboard-event-kbd"
#define HID_REPORT_SIZE 8
#define GRAB 1
#define UNGRAB 0
int hid_output;
volatile int running = 0;
int key_index = 0;
void signal_handler(int dummy) {
running = 0;
}
int find_hidraw_device() {
int fd;
int ret;
struct hidraw_devinfo hidinfo;
char path[20];
for(int x = 0; x < 16; x++){
sprintf(path, "/dev/hidraw%d", x);
if ((fd = open(path, O_RDWR | O_NONBLOCK)) == -1) {
continue;
}
ret = ioctl(fd, HIDIOCGRAWINFO, &hidinfo);
if(hidinfo.vendor == VENDOR && hidinfo.product == PRODUCT) {
printf("Found keyboard at: %s\n", path);
return fd;
}
close(fd);
}
return -1;
}
int main() {
int ret;
int fd;
int uinput_fd;
unsigned char buf[HID_REPORT_SIZE];
fd = find_hidraw_device();
if(fd == -1) {
printf("Failed to open keyboard device\n");
return 1;
}
ret = initUSB();
uinput_fd = open(KEYBOARD_DEV, O_RDONLY);
ioctl(uinput_fd, EVIOCGRAB, UNGRAB);
usleep(500000);
ioctl(uinput_fd, EVIOCGRAB, GRAB);
do {
hid_output = open("/dev/hidg0", O_WRONLY | O_NDELAY);
} while (hid_output == -1 && errno == EINTR);
if (hid_output == -1){
printf("Error opening /dev/hidg0 for writing.\n");
return 1;
}
printf("Running...\n");
running = 1;
system("irplay 3");
signal(SIGINT, signal_handler);
while (running){
int c = read(fd, buf, HID_REPORT_SIZE);
if(c != HID_REPORT_SIZE){
continue;
}
for(int x = 0; x < HID_REPORT_SIZE; x++)
{
printf("%x ", buf[x]);
}
printf("\n");
write(hid_output, buf, HID_REPORT_SIZE);
usleep(1000);
if(buf[0] == 0x09){
running = 0;
system("irplay 1");
break;
}
}
for(int x = 0; x < HID_REPORT_SIZE; x++){
buf[x] = 0;
};
write(hid_output, buf, HID_REPORT_SIZE);
ioctl(uinput_fd, EVIOCGRAB, UNGRAB);
close(uinput_fd);
printf("Cleanup USB\n");
cleanupUSB();
return 0;
}
#include <pthread.h>
int initUSB();
int main();
void sendHIDReport();
@Hermann-SW
Copy link
Author

Much more space with Pi400 keyboard instead of big wireless Logitech keyboard for working with laptop (and Pi400):
(HDMI switch remote control bottom left allows to easily switch between laptop and Pi400 on HDMI display)

For comparison situation before:

@Hermann-SW2
Copy link

Hermann-SW2 commented Oct 27, 2021

This project is retired now.
Gadgetoid moved his project to github repo and added many new features, allows to compile as well.

I use his pi400kb every day as KVM switch (Keyboard Video Mouse) together with a cheap IR controller HDMI switch, which is controlled from Pi400 with R+IRled. Allows to switch Pi400 keyboard+mouse and HDMI display for work with Pi400/laptop with just pressing CTRL+RASPBERRY! Details in this thread:
https://forums.raspberrypi.com/viewtopic.php?t=321840

Pi400 KVM switch operation:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment