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
  • Select an option

  • Save Hermann-SW/19c7522220a4ec3c8d6ecc89de2b0a60 to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown
Author

Fixed issues with tabs in Makefile, removed binary pi400kb not needed anymore.

@Hermann-SW

Copy link
Copy Markdown
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

Hermann-SW2 commented Oct 27, 2021

Copy link
Copy Markdown

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