diff --git a/hackpads/winpad-67/firmware/config.h b/hackpads/winpad-67/firmware/config.h index bfef076228..22abcfff30 100644 --- a/hackpads/winpad-67/firmware/config.h +++ b/hackpads/winpad-67/firmware/config.h @@ -6,7 +6,13 @@ #define WS2812_DI_PIN GP1 #define RGBLIGHT_LED_COUNT 3 +#define RGBLIGHT_EFFECT_RAINBOW_MOOD +//#define RGBLIGHT_MODE_RAINBOW_MOOD 0 +//#define RGBLIGHT_DEFAULT_MODE RGBLIGHT_MODE_RAINBOW_MOOD +#define RGBLIGHT_LIMIT_VAL 64 #define I2C_DRIVER I2CD1 #define I2C1_SDA_PIN GP6 #define I2C1_SCL_PIN GP7 + +#define OLED_FONT_H "font.c" diff --git a/hackpads/winpad-67/firmware/font.c b/hackpads/winpad-67/firmware/font.c new file mode 100644 index 0000000000..4c5de413f1 --- /dev/null +++ b/hackpads/winpad-67/firmware/font.c @@ -0,0 +1,230 @@ +#include "progmem.h" + +// Includes sway, ddc, and gear icons + +static const unsigned char PROGMEM font[] = { + 0x07, 0x08, 0x7F, 0x08, 0x07, 0x00, + 0x3E, 0x5B, 0x4F, 0x5B, 0x3E, 0x00, + 0x3E, 0x6B, 0x4F, 0x6B, 0x3E, 0x00, + 0x1C, 0x3E, 0x7C, 0x3E, 0x1C, 0x00, + 0x18, 0x3C, 0x7E, 0x3C, 0x18, 0x00, + 0x1C, 0x57, 0x7D, 0x57, 0x1C, 0x00, + 0x1C, 0x5E, 0x7F, 0x5E, 0x1C, 0x00, + 0x00, 0x18, 0x3C, 0x18, 0x00, 0x00, + 0xFF, 0xE7, 0xC3, 0xE7, 0xFF, 0x00, + 0x00, 0x18, 0x24, 0x18, 0x00, 0x00, + 0xFF, 0xE7, 0xDB, 0xE7, 0xFF, 0x00, + 0x30, 0x48, 0x3A, 0x06, 0x0E, 0x00, + 0x26, 0x29, 0x79, 0x29, 0x26, 0x00, + 0x40, 0x7F, 0x05, 0x05, 0x07, 0x00, + 0x40, 0x7F, 0x05, 0x25, 0x3F, 0x00, + 0x5A, 0x3C, 0xE7, 0x3C, 0x5A, 0x00, + 0x7F, 0x3E, 0x1C, 0x1C, 0x08, 0x00, + 0x08, 0x1C, 0x1C, 0x3E, 0x7F, 0x00, + 0x14, 0x22, 0x7F, 0x22, 0x14, 0x00, + 0x5F, 0x5F, 0x00, 0x5F, 0x5F, 0x00, + 0x06, 0x09, 0x7F, 0x01, 0x7F, 0x00, + 0x00, 0x66, 0x89, 0x95, 0x6A, 0x00, + 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, + 0x94, 0xA2, 0xFF, 0xA2, 0x94, 0x00, + 0x08, 0x04, 0x7E, 0x04, 0x08, 0x00, + 0x10, 0x20, 0x7E, 0x20, 0x10, 0x00, + 0x08, 0x08, 0x2A, 0x1C, 0x08, 0x00, + 0x08, 0x1C, 0x2A, 0x08, 0x08, 0x00, + 0x1E, 0x10, 0x10, 0x10, 0x10, 0x00, + 0x0C, 0x1E, 0x0C, 0x1E, 0x0C, 0x00, + 0x30, 0x38, 0x3E, 0x38, 0x30, 0x00, + 0x06, 0x0E, 0x3E, 0x0E, 0x06, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, + 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, + 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00, + 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00, + 0x23, 0x13, 0x08, 0x64, 0x62, 0x00, + 0x36, 0x49, 0x56, 0x20, 0x50, 0x00, + 0x00, 0x08, 0x07, 0x03, 0x00, 0x00, + 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00, + 0x00, 0x41, 0x22, 0x1C, 0x00, 0x00, + 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 0x00, + 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, + 0x00, 0x80, 0x70, 0x30, 0x00, 0x00, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, + 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, + 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, + 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, + 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00, + 0x72, 0x49, 0x49, 0x49, 0x46, 0x00, + 0x21, 0x41, 0x49, 0x4D, 0x33, 0x00, + 0x18, 0x14, 0x12, 0x7F, 0x10, 0x00, + 0x27, 0x45, 0x45, 0x45, 0x39, 0x00, + 0x3C, 0x4A, 0x49, 0x49, 0x31, 0x00, + 0x41, 0x21, 0x11, 0x09, 0x07, 0x00, + 0x36, 0x49, 0x49, 0x49, 0x36, 0x00, + 0x46, 0x49, 0x49, 0x29, 0x1E, 0x00, + 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x40, 0x34, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x14, 0x22, 0x41, 0x00, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, + 0x00, 0x41, 0x22, 0x14, 0x08, 0x00, + 0x02, 0x01, 0x59, 0x09, 0x06, 0x00, + 0x3E, 0x41, 0x5D, 0x59, 0x4E, 0x00, + 0x7C, 0x12, 0x11, 0x12, 0x7C, 0x00, + 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00, + 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00, + 0x7F, 0x41, 0x41, 0x41, 0x3E, 0x00, + 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00, + 0x7F, 0x09, 0x09, 0x09, 0x01, 0x00, + 0x3E, 0x41, 0x41, 0x51, 0x73, 0x00, + 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, + 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00, + 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00, + 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00, + 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00, + 0x7F, 0x02, 0x1C, 0x02, 0x7F, 0x00, + 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00, + 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00, + 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00, + 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00, + 0x7F, 0x09, 0x19, 0x29, 0x46, 0x00, + 0x26, 0x49, 0x49, 0x49, 0x32, 0x00, + 0x03, 0x01, 0x7F, 0x01, 0x03, 0x00, + 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00, + 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00, + 0x3F, 0x40, 0x3C, 0x40, 0x3F, 0x00, + 0x63, 0x14, 0x08, 0x14, 0x63, 0x00, + 0x03, 0x04, 0x78, 0x04, 0x03, 0x00, + 0x61, 0x59, 0x49, 0x4D, 0x43, 0x00, + 0x00, 0x7F, 0x41, 0x41, 0x41, 0x00, + 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, + 0x00, 0x41, 0x41, 0x41, 0x7F, 0x00, + 0x04, 0x02, 0x01, 0x02, 0x04, 0x00, + 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, + 0x00, 0x03, 0x07, 0x08, 0x00, 0x00, + 0x20, 0x54, 0x54, 0x78, 0x40, 0x00, + 0x7F, 0x28, 0x44, 0x44, 0x38, 0x00, + 0x38, 0x44, 0x44, 0x44, 0x28, 0x00, + 0x38, 0x44, 0x44, 0x28, 0x7F, 0x00, + 0x38, 0x54, 0x54, 0x54, 0x18, 0x00, + 0x00, 0x08, 0x7E, 0x09, 0x02, 0x00, + 0x18, 0xA4, 0xA4, 0x9C, 0x78, 0x00, + 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00, + 0x00, 0x44, 0x7D, 0x40, 0x00, 0x00, + 0x20, 0x40, 0x40, 0x3D, 0x00, 0x00, + 0x7F, 0x10, 0x28, 0x44, 0x00, 0x00, + 0x00, 0x41, 0x7F, 0x40, 0x00, 0x00, + 0x7C, 0x04, 0x78, 0x04, 0x78, 0x00, + 0x7C, 0x08, 0x04, 0x04, 0x78, 0x00, + 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, + 0xFC, 0x18, 0x24, 0x24, 0x18, 0x00, + 0x18, 0x24, 0x24, 0x18, 0xFC, 0x00, + 0x7C, 0x08, 0x04, 0x04, 0x08, 0x00, + 0x48, 0x54, 0x54, 0x54, 0x24, 0x00, + 0x04, 0x04, 0x3F, 0x44, 0x24, 0x00, + 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00, + 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00, + 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00, + 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, + 0x4C, 0x90, 0x90, 0x90, 0x7C, 0x00, + 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00, + 0x00, 0x08, 0x36, 0x41, 0x00, 0x00, + 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, + 0x00, 0x41, 0x36, 0x08, 0x00, 0x00, + 0x02, 0x01, 0x02, 0x04, 0x02, 0x00, + 0x06, 0x09, 0x09, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x80, 0xB0, 0x68, 0x24, 0x34, 0xF4, + 0xDE, 0xCE, 0xCC, 0x84, 0x84, 0x84, + 0x84, 0x44, 0x74, 0x78, 0x00, 0x00, + 0xF0, 0xF8, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0xF8, 0xF0, 0x00, 0x00, + 0xC0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, + 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFE, + 0xFE, 0xFC, 0xF8, 0xF0, 0xE0, 0xE0, + 0xE0, 0xE0, 0xE0, 0xE0, 0xC0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xC0, 0xF0, 0xF8, 0xFC, 0x3E, + 0x1E, 0x06, 0x01, 0x00, 0x00, 0x00, + 0x7F, 0x41, 0x41, 0x41, 0x7F, 0x00, + 0x7F, 0x41, 0x41, 0x41, 0x7F, 0x00, + 0x00, 0x80, 0xC0, 0xE0, 0x7E, 0x5B, + 0x4F, 0x5B, 0xFE, 0xC0, 0x00, 0x00, + 0xC0, 0x00, 0xDC, 0xD7, 0xDE, 0xDE, + 0xDE, 0xD7, 0xDC, 0x00, 0xC0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0xC0, 0xE1, 0xB1, + 0x99, 0x9B, 0x9E, 0x9F, 0x9F, 0xDF, + 0x4F, 0x4B, 0x49, 0x49, 0xDD, 0x97, + 0x97, 0x04, 0x0C, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x00, 0x7F, 0x41, 0x41, + 0x41, 0x3E, 0x00, 0x7F, 0x41, 0x41, + 0x41, 0x3E, 0x00, 0x3E, 0x41, 0x41, + 0x22, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x81, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x83, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x83, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xC3, 0x81, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, + 0x49, 0x48, 0x01, 0x01, 0x48, 0x48, + 0x01, 0x01, 0x49, 0x48, 0x00, 0x09, + 0x08, 0x40, 0x08, 0x00, 0x10, 0x50, + 0x10, 0x09, 0x00, 0x10, 0x59, 0x49, + 0x49, 0x19, 0x5A, 0x00, 0x7B, 0x7E, + 0x03, 0x03, 0x00, 0x1E, 0x3F, 0x69, + 0x69, 0x6F, 0x26, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x0F, 0x1F, 0x3F, 0x3C, + 0x78, 0x70, 0x60, 0x00, 0x00, 0x00, + 0x7F, 0x41, 0x41, 0x41, 0x7F, 0x00, + 0x7F, 0x41, 0x41, 0x41, 0x7F, 0x00, + 0x30, 0x7B, 0x7F, 0x78, 0x30, 0x20, + 0x20, 0x30, 0x78, 0x7F, 0x3B, 0x00, + 0x03, 0x00, 0x0F, 0x7F, 0x0F, 0x0F, + 0x0F, 0x7F, 0x0F, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0E, 0x0F, 0x1F, 0x1F, 0x38, 0x38, + 0x38, 0x70, 0x70, 0x70, 0xE0, 0xE0, + 0xE0, 0x70, 0x70, 0x78, 0x38, 0x38, + 0x1D, 0x1D, 0x1D, 0x0F, 0x0E, 0x00, + 0x07, 0x0F, 0x0C, 0x0C, 0x0C, 0x6C, + 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, + 0x7C, 0x7C, 0x7C, 0x7C, 0x6C, 0x0C, + 0x0C, 0x0C, 0x0F, 0x07, 0x00, 0x00, + 0x03, 0x07, 0x0F, 0x0F, 0x0F, 0x0F, + 0x0F, 0x1F, 0x3F, 0x7E, 0xFE, 0xFE, + 0xFE, 0x7E, 0x3F, 0x1F, 0x0F, 0x0F, + 0x0F, 0x0F, 0x0F, 0x07, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; diff --git a/hackpads/winpad-67/firmware/keymaps/default/keymap.c b/hackpads/winpad-67/firmware/keymaps/default/keymap.c index 922be53947..e2aa400f08 100644 --- a/hackpads/winpad-67/firmware/keymaps/default/keymap.c +++ b/hackpads/winpad-67/firmware/keymaps/default/keymap.c @@ -4,15 +4,19 @@ #include QMK_KEYBOARD_H #include "raw_hid.h" -// 3 chars for brightness, 12 for "% - Source: ", 6 for input name, 1 null term -#define MONITOR_STATE_MAX 22 +// 12 for "Brightness: ", 3 chars for brightness, 1 '%', null term +#define MONITOR_BRIGHTNESS_MAX 17 +// 8 for "Source: ", 8 for input name, 1 null term +#define MONITOR_SOURCE_MAX 17 +#define MONITOR_NAME_MAX 17 #ifndef RAW_EPSIZE # define RAW_EPSIZE 32 #endif enum layer_names { SWAY, - MONITOR + MONITOR, + CONTROL }; enum my_keycodes { @@ -36,19 +40,37 @@ struct { uint8_t input_source; } monitor_state = {.brightness = 0, .input_source = 0}; -#define NUM_INPUT_NAMES 6 +#define NUM_INPUT_NAMES 0x13 -const char input_names[NUM_INPUT_NAMES][7] = { - "DP-1", - "DP-2", - "HDMI-1", - "HDMI-2", - "VGA", - "DVI" +// taken from the MCCS spec +const char input_names[NUM_INPUT_NAMES][8] = { + "", + "VGA 1", + "VGA 2", + "DVI 1", + "DVI 2", + "Cmps 1", + "Cmps 2", + "S-vid 1", + "S-vid 2", + "Tuner 1", + "Tuner 2", + "Tuner 3", + "Cmpt 1", + "Cmpt 2", + "Cmpt 3", + "DP 1", + "DP 2", + "HDMI 1", + "HDMI 2", }; char sway_title[RAW_EPSIZE - 1]; -char monitor_status_title[MONITOR_STATE_MAX]; +char monitor_source_title[MONITOR_SOURCE_MAX] = "Source: ?"; +char monitor_brightness_title[MONITOR_BRIGHTNESS_MAX] = "Brightness: ?%"; +char monitor_name_title[MONITOR_NAME_MAX] = "?"; +// "Color: xxx*\0" +char hue_str[12]; const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { /* @@ -61,23 +83,29 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { * └───┴───┴───┘ */ [SWAY] = LAYOUT( - // mode mute select up - MT(MOD_LSFT, MODE), KC_MUTE, LSG(KC_A), + //stacked tabbed fullscreen + G(KC_S), G(KC_W), G(KC_F), //tabbed vertical horizontal - G(KC_E), G(KC_V), G(KC_B), - //stacked tabbed fullscreen - G(KC_S), G(KC_W), G(KC_F) - ), + G(KC_E), G(KC_V), G(KC_B), + // mode mute select up + MODE, KC_MUTE, LSG(KC_UP) + ), [MONITOR] = LAYOUT( - MODE, BRIGHT_DEFAULT, CYCLE_MONITORS, - HDMI_1, DP_1, VGA, - HDMI_2, DP_2, DVI - ) + HDMI_2, DP_2, DVI, + HDMI_1, DP_1, VGA, + MODE, BRIGHT_DEFAULT, CYCLE_MONITORS + ), + [CONTROL] = LAYOUT( + QK_BOOTLOADER, QK_REBOOT, KC_NO, + QK_UNDERGLOW_MODE_NEXT, QK_UNDERGLOW_MODE_PREVIOUS, KC_NO, + MODE, QK_UNDERGLOW_TOGGLE, KC_NO + ) }; const uint16_t PROGMEM encoder_map[][1][2] = { - [SWAY] = { ENCODER_CCW_CW(KC_VOLD, KC_VOLU) }, - [MONITOR] = { ENCODER_CCW_CW(BRIGHT_DOWN, BRIGHT_UP) } + [SWAY] = { ENCODER_CCW_CW(KC_VOLU, KC_VOLD) }, + [MONITOR] = { ENCODER_CCW_CW(BRIGHT_UP, BRIGHT_DOWN) }, + [CONTROL] = { ENCODER_CCW_CW(QK_UNDERGLOW_HUE_UP, QK_UNDERGLOW_HUE_DOWN) } }; bool process_record_user(uint16_t keycode, keyrecord_t *record) { @@ -87,29 +115,29 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) { // only on press if (!record->event.pressed) return false; uint8_t cur = get_highest_layer(layer_state); - uint8_t new = cur == SWAY ? MONITOR : SWAY; + uint8_t new = cur == SWAY ? MONITOR : cur == MONITOR ? CONTROL : SWAY; layer_move(new); + return false; } else if (keycode >= BRIGHT_UP && keycode <= DVI) { + // only on press + if (!record->event.pressed) return false; + // send as HID command uint8_t buf[RAW_EPSIZE]; + memset(buf, 0, RAW_EPSIZE); buf[0] = keycode - BRIGHT_UP; raw_hid_send(buf, RAW_EPSIZE); + if (keycode == BRIGHT_UP) monitor_state.brightness++; + else if (keycode == BRIGHT_DOWN) monitor_state.brightness--; return false; } return true; } layer_state_t layer_state_set_user(layer_state_t state) { - switch (get_highest_layer(state)) { - case MONITOR: - // I'll make these fancier once I actually get the hackpad - rgblight_setrgb(255, 0, 0); - break; - case SWAY: - rgblight_setrgb(0, 255, 0); - break; - } + oled_clear(); + return state; } @@ -117,14 +145,14 @@ layer_state_t layer_state_set_user(layer_state_t state) { void raw_hid_receive(uint8_t *data, uint8_t length) { switch (data[0]) { case SWAY_TITLE: - for (int i = 1; i < RAW_EPSIZE - 1; i++) { + for (int i = 1; i < length - 1; i++) { sway_title[i - 1] = (char) data[i]; } // force a null terminator at the end - sway_title[RAW_EPSIZE - 1] = 0; + sway_title[length - 1] = 0; break; case MONITOR_STATUS: - // first byte is brightness, second byte is input source + // first byte is brightness, second byte is input source, rest is monitor name monitor_state.brightness = data[1]; // make sure it's within range if (data[2] >= NUM_INPUT_NAMES) { @@ -133,23 +161,111 @@ void raw_hid_receive(uint8_t *data, uint8_t length) { monitor_state.input_source = data[2]; } snprintf( - monitor_status_title, - MONITOR_STATE_MAX, - "%d%% - Source: %s", - monitor_state.brightness, + monitor_brightness_title, + MONITOR_BRIGHTNESS_MAX, + "Brightness: %3d%%", + monitor_state.brightness + ); + snprintf( + monitor_source_title, + MONITOR_SOURCE_MAX, + "Source: %-8s", input_names[monitor_state.input_source] ); + snprintf( + monitor_name_title, + MONITOR_NAME_MAX, + "%-17s", + &data[3] + ); break; } } -bool oled_task_user(void) { +/*bool oled_task_user(void) { + oled_clear(); switch (get_highest_layer(layer_state)) { case SWAY: oled_write(sway_title, false); break; case MONITOR: oled_write(monitor_status_title, false); + oled_set_brightness(monitor_state.brightness); + break; + } + //oled_write("hello there", false); + oled_set_brightness(monitor_state.brightness); + return false; +}*/ + + +static const char PROGMEM SWAY_LOGO[] = { + 0x80, 0x81, 0x82, 0x83, 0x00, + 0xA0, 0xA1, 0xA2, 0xA3, 0x00, + 0xC0, 0xC1, 0xC2, 0xC3, 0x00 +}; + +static const char PROGMEM DDC_LOGO[] = { + 0x84, 0x85, 0x86, 0x87, 0x00, + 0xA4, 0xA5, 0xA6, 0xA7, 0x00, + 0xC4, 0xC5, 0xC6, 0xC7, 0x00 +}; +static const char PROGMEM GEAR_LOGO[] = { + 0x88, 0x89, 0x8A, 0x8B, 0x00, + 0xA8, 0xA9, 0xAA, 0xAB, 0x00, + 0xC8, 0xC9, 0xCA, 0xCB, 0x00 +}; + +bool oled_task_user(void) { + switch (get_highest_layer(layer_state)) { + case SWAY: + // all three rows + oled_write_P(SWAY_LOGO, false); + oled_set_cursor(0, 1); + oled_write_P(SWAY_LOGO + 5, false); + oled_set_cursor(0, 2); + oled_write_P(SWAY_LOGO + 10, false); + + oled_set_cursor(5, 1); + oled_write_P("Sway WM", false); + break; + case MONITOR: + // all three rows + oled_write_P(DDC_LOGO, false); + oled_set_cursor(0, 1); + oled_write_P(DDC_LOGO + 5, false); + oled_set_cursor(0, 2); + oled_write_P(DDC_LOGO + 10, false); + + oled_set_cursor(5, 0); + oled_write_P("Monitor:", false); + + oled_set_cursor(5, 1); + oled_write(monitor_name_title, false); + oled_set_cursor(5, 2); + oled_write(monitor_brightness_title, false); + oled_set_cursor(5, 3); + oled_write(monitor_source_title, false); + break; + case CONTROL: + oled_write_P(GEAR_LOGO, false); + oled_set_cursor(0, 1); + oled_write_P(GEAR_LOGO + 5, false); + oled_set_cursor(0, 2); + oled_write_P(GEAR_LOGO + 10, false); + + oled_set_cursor(5, 0); + oled_write_P("Settings:", false); + + // RGB light color + oled_set_cursor(5, 2); + snprintf( + hue_str, + 12, + "Color: %3d\x7F", + rgblight_get_hue() + ); + oled_write(hue_str, false); break; } return false; diff --git a/hackpads/winpad-67/firmware/keymaps/default/rules.mk b/hackpads/winpad-67/firmware/keymaps/default/rules.mk index ee32568148..7bbffb52e1 100644 --- a/hackpads/winpad-67/firmware/keymaps/default/rules.mk +++ b/hackpads/winpad-67/firmware/keymaps/default/rules.mk @@ -1 +1,2 @@ ENCODER_MAP_ENABLE = yes +RAW_ENABLE = yes diff --git a/hackpads/winpad-67/production/firmware.uf2 b/hackpads/winpad-67/production/firmware.uf2 index 7b62842a58..7a4fc51d3a 100644 Binary files a/hackpads/winpad-67/production/firmware.uf2 and b/hackpads/winpad-67/production/firmware.uf2 differ diff --git a/hackpads/winpad-67/winpadd/.gitignore b/hackpads/winpad-67/winpadd/.gitignore new file mode 100644 index 0000000000..9f970225ad --- /dev/null +++ b/hackpads/winpad-67/winpadd/.gitignore @@ -0,0 +1 @@ +target/ \ No newline at end of file diff --git a/hackpads/winpad-67/winpadd/50-winpad.rules b/hackpads/winpad-67/winpadd/50-winpad.rules new file mode 100644 index 0000000000..2df0117dd7 --- /dev/null +++ b/hackpads/winpad-67/winpadd/50-winpad.rules @@ -0,0 +1 @@ +KERNEL=="hidraw*", ATTRS{idVendor}=="feed", ATTRS{idProduct}=="534b", MODE="0660", GROUP="input" \ No newline at end of file diff --git a/hackpads/winpad-67/winpadd/Cargo.lock b/hackpads/winpad-67/winpadd/Cargo.lock new file mode 100644 index 0000000000..38fb140812 --- /dev/null +++ b/hackpads/winpad-67/winpadd/Cargo.lock @@ -0,0 +1,501 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4730490333d58093109dc02c23174c3f4d490998c3fed3cc8e82d57afedb9cf" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "ddc" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba69f2c53e320fc4abad17cb02bbbf04d1a36f18e9907f347589ec5991b3c6c5" +dependencies = [ + "mccs 0.1.3", +] + +[[package]] +name = "ddc-i2c" +version = "0.2.1" +source = "git+https://github.com/grimsteel/ddc-i2c-rs.git#988c1b53c1ad3d8f12dd61b9d8b19b41bc960619" +dependencies = [ + "ddc", + "i2c", + "i2c-linux", + "resize-slice", +] + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + +[[package]] +name = "hidapi" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b876ecf37e86b359573c16c8366bc3eba52b689884a0fc42ba3f67203d2a8b" +dependencies = [ + "cc", + "cfg-if", + "libc", + "pkg-config", + "windows-sys 0.48.0", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "i2c" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60c7b7bdd7b3a985fdcf94a0d7d98e7a47fde8b7f22fb55ce1a91cc104a2ce9a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "i2c-linux" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0268a871aaa071221d6c2875ebedcf64710e59b0d87c68c8faf5e98b87dd2a4" +dependencies = [ + "bitflags", + "i2c", + "i2c-linux-sys", + "resize-slice", + "udev", +] + +[[package]] +name = "i2c-linux-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cd060ed0016621d3da4ed3a23b0158084de90d1f3a8e59f3d391aacd3bbcf8" +dependencies = [ + "bitflags", + "byteorder", + "libc", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "log" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" + +[[package]] +name = "mccs" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6090d6b3ded42fed158b660a6b9cdaa1924f3eef6c6598e82a9ca9b70a1988cd" +dependencies = [ + "void", +] + +[[package]] +name = "mccs" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a63ab5297c9a7d5f8298a076b5f858c3c51ce84bf2f57a302d1d67ff9323360" + +[[package]] +name = "mccs-caps" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47729c9dbef15d10c6a7ebb3157034265ccc2559e76ac10afb45ecc69ce09288" +dependencies = [ + "mccs 0.2.0", + "nom", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "resize-slice" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a3cb2f74a9891e76958b9e0ccd269a25b466c3ae3bb3efd71db157248308c4a" +dependencies = [ + "uninitialized", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "udev" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47504d1a49b2ea1b133e7ddd1d9f0a83cf03feb9b440c2c470d06db4589cf301" +dependencies = [ + "libc", + "libudev-sys", +] + +[[package]] +name = "uninitialized" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74c1aa4511c38276c548406f0b1f5f8b793f000cfb51e18f278a102abd057e81" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winpadd" +version = "0.1.0" +dependencies = [ + "ddc", + "ddc-i2c", + "env_logger", + "hidapi", + "log", + "mccs 0.2.0", + "mccs-caps", +] diff --git a/hackpads/winpad-67/winpadd/Cargo.toml b/hackpads/winpad-67/winpadd/Cargo.toml new file mode 100644 index 0000000000..81a8ed2a58 --- /dev/null +++ b/hackpads/winpad-67/winpadd/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "winpadd" +version = "0.1.0" +edition = "2021" + +[dependencies] +env_logger = "0.11.6" +hidapi = "2.6.3" +log = "0.4.25" +ddc-i2c = { git = 'https://github.com/grimsteel/ddc-i2c-rs.git', features = ['with-linux', 'with-linux-enumerate'] } +ddc = "0.2.2" +mccs = "0.2.0" +mccs-caps = "0.2.0" diff --git a/hackpads/winpad-67/winpadd/src/main.rs b/hackpads/winpad-67/winpadd/src/main.rs new file mode 100644 index 0000000000..601d4da018 --- /dev/null +++ b/hackpads/winpad-67/winpadd/src/main.rs @@ -0,0 +1,302 @@ +use log::{info, error}; +use hidapi::{HidApi, HidError}; +use std::{process::ExitCode, error::Error, fmt, thread::sleep, time::Duration, process::Command, mem::transmute}; +use env_logger::{Builder as LogBuilder, Env}; +use ddc::Ddc; +use mccs_caps::parse_capabilities; +use ddc_i2c::from_i2c_device; + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[allow(dead_code)] +enum WinpadKeycodes { + BrightUp, + BrightDown, + BrightDefault, + NextMonitor, + DP1, + DP2, + HDMI1, + HDMI2, + VGA1, + DVI1 +} + +impl WinpadKeycodes { + fn to_ddc_input_source(&self) -> Option { + // taken from mccs spec + match self { + Self::DP1 => Some(0x0f), + Self::DP2 => Some(0x10), + Self::HDMI1 => Some(0x11), + Self::HDMI2 => Some(0x12), + Self::VGA1 => Some(0x01), + Self::DVI1 => Some(0x03), + _ => None + } + } +} + +impl TryFrom<&[u8; RAW_HID_MSG_LENGTH]> for WinpadKeycodes { + fn try_from(value: &[u8; RAW_HID_MSG_LENGTH]) -> Result { + let val = value[0]; + // check bounds + if val >= WinpadKeycodes::BrightUp as u8 && val <= WinpadKeycodes::DVI1 as u8 { + Ok(unsafe { transmute(val) }) + } else { + Err(WinpaddError::InvalidHidCommand(val)) + } + } + + type Error = WinpaddError; +} + +#[derive(Debug, Clone)] +#[allow(dead_code)] +enum WinpadHostMessage<'a> { + SwayTitle(&'a str), + MonitorStatus { + brightness: u8, + input_source: u8, + monitor_name: &'a str + } +} + +impl<'a> Into<[u8; RAW_HID_MSG_LENGTH]> for WinpadHostMessage<'a> { + fn into(self) -> [u8; RAW_HID_MSG_LENGTH] { + let mut buf = [0; RAW_HID_MSG_LENGTH]; + match self { + Self::SwayTitle(s) => { + buf[0] = 0; + // 1 byte for msg type, 1 for null term + let bytes_to_copy = (RAW_HID_MSG_LENGTH - 2).min(s.len()); + buf[1..1 + bytes_to_copy].copy_from_slice(s.as_bytes()); + }, + Self::MonitorStatus { brightness, input_source, monitor_name } => { + buf[0] = 1; + buf[1] = brightness; + buf[2] = input_source; + // 3 bytes for msg type/brightness/input, 1 for null term + let bytes_to_copy = (RAW_HID_MSG_LENGTH - 4).min(monitor_name.len()); + buf[3..3 + bytes_to_copy].copy_from_slice(monitor_name.as_bytes()); + } + } + buf + } +} + +#[derive(Debug, Copy, Clone)] +enum WinpaddError { + NoMonitors, + InvalidHidCommand(u8), +} + +impl fmt::Display for WinpaddError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::NoMonitors => write!(f, "No monitors found!"), + Self::InvalidHidCommand(c) => write!(f, "Invalid hid command: {:02x}", c), + //Self::DdcError => write!(f, "DDC transmission error") + } + } +} + +impl Error for WinpaddError {} + +const WINPAD_VID: u16 = 0xfeed; +const WINPAD_PID: u16 = 0x534b; +const WINPAD_USAGE_ID: u16 = 0x61; +const VCP_BRIGHTNESS: u8 = 0x10; +const VCP_INPUT: u8 = 0x60; +const RAW_HID_MSG_LENGTH: usize = 32; +const DEFAULT_BRIGHTNESS: u8 = 20; + +#[derive(Debug, Clone)] +struct Caps { + brightness: bool, + input_source: Vec +} + +#[derive(Debug, Clone)] +struct Monitor +where T: Ddc +{ + name: String, + capabilities: Caps, + ddc: T +} + +// For some reason, ddcutil is way better at this than the rust implementation +fn detect_monitors() -> Vec> { + for i in 0..7 { + sleep(Duration::from_millis(4_u64.pow(i))); + + if let Ok(result) = Command::new("ddcutil") + .arg("detect") + .arg("--terse") + .output() + { + if result.status.success() { + // parse output + let out = String::from_utf8(result.stdout).unwrap(); + return out.split("\n\n").filter_map(|d| { + let mut lines = d.lines(); + // parse path and name out of output + let i2c_line = lines.nth(1)?; + let i2c_file = i2c_line.split_whitespace().last()?; + + let caps = get_capabilities(i2c_file)?; + let caps = parse_capabilities(caps).ok()?; + + // get the name from the ddcutil output or from the caps string + let name = lines.nth(1) + .and_then(|l| l.split(':').nth_back(1)) + .map(|v| v.into()) + .or(caps.model) + .unwrap_or_else(|| "Unknown".into()); + + let caps = Caps { + brightness: caps.vcp_features.contains_key(&VCP_BRIGHTNESS), + input_source: caps.vcp_features.get(&VCP_INPUT) + .map(|v| v.values.keys().copied().collect()) + .unwrap_or_else(|| vec![]) + }; + + + Some(Monitor { + capabilities: caps, + name, + ddc: from_i2c_device(i2c_file).ok()? + }) + }).collect(); + } + } + } + vec![] +} + +fn get_capabilities(i2c_file: &str) -> Option { + let bus_num = i2c_file.split_once('-')?.1; + for i in 0..7 { + sleep(Duration::from_millis(4_u64.pow(i))); + + if let Ok(result) = Command::new("ddcutil") + .arg("capabilities") + .arg("--terse") + .arg("--bus") + .arg(bus_num) + .output() + { + if result.status.success() { + // parse output + let out = String::from_utf8(result.stdout).unwrap(); + if let Some(caps_start) = out.find('(') { + return Some(out[caps_start..].trim().into()); + } + } + } + } + None +} + +fn run() -> Result<(), Box> { + info!("loading monitors..."); + + // find monitors + let mut monitors: Vec<_> = detect_monitors(); + + if monitors.len() == 0 { + Err(WinpaddError::NoMonitors)?; + } + + let mut current_monitor = 0; + + info!("found {} monitors", monitors.len()); + + let mut hid = HidApi::new()?; + let mut hid_buf = [0; RAW_HID_MSG_LENGTH]; + + loop { + hid.reset_devices()?; + hid.add_devices(WINPAD_VID, WINPAD_PID)?; + + // find the raw hid device + // https://docs.qmk.fm/features/rawhid#basic-configuration + if let Some(winpad) = hid.device_list() + .find(|v| v.usage() == WINPAD_USAGE_ID) + .and_then(|d| d.open_device(&hid).ok()) + { + info!("winpad connected"); + loop { + match winpad.read(&mut hid_buf) { + Err(HidError::HidApiError { .. }) => { + // disconnect + break; + }, + e @ Err(_) => { + e?; + }, + Ok(_) => { + let command: WinpadKeycodes = (&hid_buf).try_into()?; + if command == WinpadKeycodes::NextMonitor { + current_monitor = (current_monitor + 1) % monitors.len(); + } + let mon = &mut monitors[current_monitor]; + // ignore ddc transmission errors + let Ok(mut brightness) = mon.ddc.get_vcp_feature(VCP_BRIGHTNESS).map(|v| v.value() as u8) else { + continue; + }; + let Ok(mut input_source) = mon.ddc.get_vcp_feature(VCP_INPUT).map(|v| v.value() as u8) else { + continue; + }; + if mon.capabilities.brightness { + let new_brightness = match command { + WinpadKeycodes::BrightDefault => DEFAULT_BRIGHTNESS, + WinpadKeycodes::BrightUp => (brightness + 5).min(100), + WinpadKeycodes::BrightDown => brightness.saturating_sub(5), + _ => brightness + }; + + // set brightness + if new_brightness != brightness { + brightness = new_brightness; + let _ = mon.ddc.set_vcp_feature(VCP_BRIGHTNESS, brightness as u16); + } + } + + if let Some(new_input) = command.to_ddc_input_source() { + if mon.capabilities.input_source.contains(&new_input) { + // appply this input + input_source = new_input; + let _ = mon.ddc.set_vcp_feature(VCP_INPUT, input_source as u16); + } + } + + // report back to winpad + let msg = WinpadHostMessage::MonitorStatus { + brightness, + input_source, + monitor_name: &mon.name + }; + let raw_msg: [u8; RAW_HID_MSG_LENGTH] = msg.into(); + winpad.write(&raw_msg)?; + } + } + } + info!("winpad disconnected"); + } + + sleep(Duration::from_secs(10)); + } +} + +fn main() -> ExitCode { + LogBuilder::from_env(Env::new().filter_or("RUST_LOG", "info")).init(); + + // log any errors + if let Err(err) = run() { + error!("{}", err); + ExitCode::FAILURE + } else { + ExitCode::SUCCESS + } +} diff --git a/website/src/main.tsx b/website/src/main.tsx index f6f14249d7..fd43aa11cb 100644 --- a/website/src/main.tsx +++ b/website/src/main.tsx @@ -24,6 +24,7 @@ import "./index.css"; import OrpheusPad from "./pages/submissions/Orpheuspad/OrpheusPad.mdx" import CyaoPad from "./pages/submissions/Cyaopad/CyaoPad.mdx" import Wang01 from "./pages/submissions/Wang01/Wang01.mdx" +import Winpad from "./pages/submissions/winpad/winpad.mdx" const router = createBrowserRouter([ { @@ -81,6 +82,10 @@ const router = createBrowserRouter([ { path: "/projects/wang01", element: + }, + { + path: "/projects/winpad", + element: } ]); diff --git a/website/src/pages/submissions/winpad/cad.png b/website/src/pages/submissions/winpad/cad.png new file mode 100644 index 0000000000..7011ef8dd5 Binary files /dev/null and b/website/src/pages/submissions/winpad/cad.png differ diff --git a/website/src/pages/submissions/winpad/glow.png b/website/src/pages/submissions/winpad/glow.png new file mode 100644 index 0000000000..2c78688d48 Binary files /dev/null and b/website/src/pages/submissions/winpad/glow.png differ diff --git a/website/src/pages/submissions/winpad/monitor.png b/website/src/pages/submissions/winpad/monitor.png new file mode 100644 index 0000000000..100b823a90 Binary files /dev/null and b/website/src/pages/submissions/winpad/monitor.png differ diff --git a/website/src/pages/submissions/winpad/pcb.png b/website/src/pages/submissions/winpad/pcb.png new file mode 100644 index 0000000000..19ec45b653 Binary files /dev/null and b/website/src/pages/submissions/winpad/pcb.png differ diff --git a/website/src/pages/submissions/winpad/schematic.png b/website/src/pages/submissions/winpad/schematic.png new file mode 100644 index 0000000000..d1f2901832 Binary files /dev/null and b/website/src/pages/submissions/winpad/schematic.png differ diff --git a/website/src/pages/submissions/winpad/winpad.mdx b/website/src/pages/submissions/winpad/winpad.mdx new file mode 100644 index 0000000000..164357b902 --- /dev/null +++ b/website/src/pages/submissions/winpad/winpad.mdx @@ -0,0 +1,83 @@ +import schematicImage from './schematic.png'; +import pcbImage from './pcb.png'; +import cadImage from './cad.png'; +import monitorImage from './monitor.png'; +import glowImage from './glow.png'; + +# winpad + +winpad is a 3 layer, 8 key macropad with a rotary encoder and an OLED display. It has 3 WS2812B leds and uses QMK firmware. It's for controlling window arrangement and display attributes. + +## Features: +- 128x32 OLED Display +- 8 keys +- Rotary encoder +- 3 WS2812B RGB LEDs + +## CAD Model: +The case and plate fits together using 4 M2 Screws. It has two optional side stands which require two additional M2 screws. + +It has 3 laser cut pieces - the plate and the two stands, and two 3d printed pieces - the case top and bottom. + +Schematic + +Case was made in Onshape. + +## PCB + +PCB was made in Kicad. + +Schematic +Schematic + +PCB +Schematic + +## Firmware Overview +This hackpad uses [QMK](https://qmk.fm/) firmware for everything. + +The top left button changes the layer. + +### Layer 1 - i3/Sway Window Arrangement + +- **Rotary encoder**: Volume/mute +- **OLED**: Sway icon +- **Keys**: Tiling layout/arrangement + +### Layer 2 - DDC Monitor/Display Control + +***Note***: This mode requires `winpadd`, a host-side daemon for relaying DDC controls. (Source code can be found in `hackpads/winpad-67` folder). An HID udev rule is provided as well. + +- **Rotary encoder**: Brightness (press to reset to 20%) +- **OLED**: Display details +- **Top right key**: Change current display +- **Other keys**: Change input source + +OLED monitor details + +### Layer 3 - Settings + +- **Rotary encoder**: Change underglow hue in static mode +- **OLED**: Current hue +- **Middle keys**: Change current underglow effect +- **Lower keys**: Enter bootloder (so I don't have to do RST + BOOT), reboot + +Orange underglow + +## BOM: +Here should be everything you need to make this hackpad + +- 8x Cherry MX Switches +- 8x Keycaps +- 8x M2 screws +- 9x 1N4148 Diodes. +- 3x WS2812B LEDs +- 1x 0.91" 128x32 OLED Display +- 1x EC11 Rotary Encoder +- 1x XIAO RP2040 +- 1x Case (2 printed parts, 3 laser cut parts) + + +## Extra stuff + +The USB slot on the initial case didn't account for the pin headers. The rotary encoder cap in the pictures is from a random model I found online.