mirror of
https://github.com/hrvach/deskhop.git
synced 2024-11-25 01:04:02 +01:00
Features, bugfixes and configuration.
- Support for storing config in flash - Support for keyboard and mouse in any port (hopefully) - Single-sided operation should work with a USB hub - Added mouse switch cursor height/offset simple calibration - Added per-screen settings support - Mouse speed is configurable per-screen and per-axis - Small fixes and cleanup - Added LED feedback - Updated documentation for usage guide
This commit is contained in:
parent
2711b911ee
commit
99a6e3bf20
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
build
|
@ -33,6 +33,7 @@ target_include_directories(Pico-PIO-USB PRIVATE ${PICO_PIO_USB_DIR})
|
|||||||
|
|
||||||
set(COMMON_SOURCES
|
set(COMMON_SOURCES
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/usb_descriptors.c
|
${CMAKE_CURRENT_LIST_DIR}/src/usb_descriptors.c
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/defaults.c
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/hid_parser.c
|
${CMAKE_CURRENT_LIST_DIR}/src/hid_parser.c
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/utils.c
|
${CMAKE_CURRENT_LIST_DIR}/src/utils.c
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/handlers.c
|
${CMAKE_CURRENT_LIST_DIR}/src/handlers.c
|
||||||
@ -54,6 +55,7 @@ set(COMMON_INCLUDES
|
|||||||
|
|
||||||
set(COMMON_LINK_LIBRARIES
|
set(COMMON_LINK_LIBRARIES
|
||||||
pico_stdlib
|
pico_stdlib
|
||||||
|
hardware_flash
|
||||||
hardware_uart
|
hardware_uart
|
||||||
hardware_gpio
|
hardware_gpio
|
||||||
hardware_pio
|
hardware_pio
|
||||||
@ -96,3 +98,6 @@ target_link_options(board_B PRIVATE
|
|||||||
-Xlinker
|
-Xlinker
|
||||||
--print-memory-usage
|
--print-memory-usage
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pico_set_linker_script(board_A ${CMAKE_SOURCE_DIR}/memory_map.ld)
|
||||||
|
pico_set_linker_script(board_B ${CMAKE_SOURCE_DIR}/memory_map.ld)
|
56
README.md
56
README.md
@ -67,10 +67,7 @@ It should appear as a USB drive on your system. Copy the corresponding board_A.u
|
|||||||
|
|
||||||
Option 1 - Open the case, hold the button while connecting each Pico and copy the right uf2 to it.
|
Option 1 - Open the case, hold the button while connecting each Pico and copy the right uf2 to it.
|
||||||
|
|
||||||
Option 2 - Switch a board to BOOTSEL mode by using a special key combination. (**hold down** all of these keys).
|
Option 2 - Switch a board to BOOTSEL mode by using a special key combination (listed below).
|
||||||
|
|
||||||
- board A: ```Right Shift, F12, A, Left Shift```
|
|
||||||
- board B: ```Right Shift, F12, B, Left Shift```
|
|
||||||
|
|
||||||
This will make the corresponding Pico board enter the bootloader upgrade mode and act as USB flash drive. Now you can drag-and-drop the .uf2 file to it (you might need to plug in your mouse directly).
|
This will make the corresponding Pico board enter the bootloader upgrade mode and act as USB flash drive. Now you can drag-and-drop the .uf2 file to it (you might need to plug in your mouse directly).
|
||||||
|
|
||||||
@ -153,6 +150,43 @@ The standard process to do that is using isopropyl alcohol and an old toothbrush
|
|||||||
|
|
||||||
[![PCB Assembly Guide](img/yt-video-s.jpg)](https://www.youtube.com/watch?v=LxI9NYi_oOU)
|
[![PCB Assembly Guide](img/yt-video-s.jpg)](https://www.youtube.com/watch?v=LxI9NYi_oOU)
|
||||||
|
|
||||||
|
## Usage guide
|
||||||
|
|
||||||
|
#### Keyboard shortcuts
|
||||||
|
|
||||||
|
_Firmware upgrade_
|
||||||
|
```Right Shift + F12 + Left Shift + A``` - put board A in FW upgrade mode
|
||||||
|
```Right Shift + F12 + Left Shift + B``` - put board B in FW upgrade mode
|
||||||
|
|
||||||
|
_Usage_
|
||||||
|
```Right ALT``` - mouse slows down while it's pressed
|
||||||
|
```Right CTRL + L``` - Lock/Unlock mouse desktop switching
|
||||||
|
```Caps Lock``` - Switch between outputs
|
||||||
|
|
||||||
|
_Config_
|
||||||
|
```Right Shift + F12 + D``` - remove flash config
|
||||||
|
```Right Shift + F12 + Y``` - save screen switch offset
|
||||||
|
|
||||||
|
#### Switch cursor height calibration
|
||||||
|
|
||||||
|
This step is not required, but it can be handy if your screens are not perfectly aligned or differ in size. The objective is to have the mouse pointer come out at exactly the same height.
|
||||||
|
|
||||||
|
![Image](img/border_top_s.png)
|
||||||
|
|
||||||
|
Just park your mouse on the LARGER screen at the height of the smaller/lower screen (illustrated) and press ```Right Shift + F12 + Y```. Your LED (and caps lock) should flash in confirmation.
|
||||||
|
|
||||||
|
Repeat for the bottom border (if it's above the larger screen's border). This will get saved to flash and it should keep this calibration value from now on.
|
||||||
|
|
||||||
|
#### Other configuration
|
||||||
|
|
||||||
|
Mouse speed can now be configured per output screen and per axis. If you have multiple displays under Linux, your X speed is probably too fast, so you need to configure it in user_config.h and rebuild. In the future, this will be configurable without having to do that.
|
||||||
|
|
||||||
|
#### Functional verification
|
||||||
|
|
||||||
|
When you connect a new USB peripheral, the board will flash the led twice, and instruct the other board to do the same. This way you can test if USB and outgoing communication works for each board.
|
||||||
|
|
||||||
|
Do this test by first plugging the keyboard on one side and then on the other. If everything is OK, leds will flash quickly back and forth in both cases.
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
1. I just have two Picos, can I do without a PCB and isolator?
|
1. I just have two Picos, can I do without a PCB and isolator?
|
||||||
@ -177,7 +211,7 @@ Yes, the idea was to make it behave like it was one single computer.
|
|||||||
|
|
||||||
5. Will this work with keyboard/mouse combo dongles, like the Logitech Unifying receiver?
|
5. Will this work with keyboard/mouse combo dongles, like the Logitech Unifying receiver?
|
||||||
|
|
||||||
Not with the current version, but there is work ongoing to add support. Testing is one of the main problems, so if there is anyone with the device and some Logitech gear, let me know.
|
Not tested yet, but the latest version might actually work (please provide feedback).
|
||||||
|
|
||||||
6. Will this work with wireless mice and keyboards that have separate wireless receivers (one for the mouse, another for the keyboard)?
|
6. Will this work with wireless mice and keyboards that have separate wireless receivers (one for the mouse, another for the keyboard)?
|
||||||
|
|
||||||
@ -199,12 +233,12 @@ There are several software alternatives you can use if that works in your partic
|
|||||||
|
|
||||||
## Shortcomings
|
## Shortcomings
|
||||||
|
|
||||||
- Slow mouse movement with some devices.
|
|
||||||
- Windows 10 broke HID absolute coordinates behavior in KB5003637, so you can't use more than 1 screen on Windows (mouse will stay on the main screen).
|
- Windows 10 broke HID absolute coordinates behavior in KB5003637, so you can't use more than 1 screen on Windows (mouse will stay on the main screen).
|
||||||
- If you have more than one display, the mouse is faster in the X direction on that machine. Will get fixed with per-output configurable speed settings.
|
|
||||||
- Code needs cleanup, some refactoring etc.
|
- Code needs cleanup, some refactoring etc.
|
||||||
- Occasional bugs and weird behavior.
|
- Occasional bugs and weird behavior.
|
||||||
- Not tested with a wide variety of devices, I don't know how it will work with your hardware. There is a reasonable chance things might not work out-of-the-box.
|
- Not tested with a wide variety of devices, I don't know how it will work with your hardware. There is a reasonable chance things might not work out-of-the-box.
|
||||||
|
- Advanced keyboards (with knobs, extra buttons or sliders) will probably face issues where this additional hardware doesn't work.
|
||||||
|
- Super-modern mice with 300 buttons might see some buttons not work as expected.
|
||||||
- NOTE: Both computers need to be connected and powered on for this to work (as each board gets powered by the computer it plugs into).
|
- NOTE: Both computers need to be connected and powered on for this to work (as each board gets powered by the computer it plugs into).
|
||||||
|
|
||||||
## Progress
|
## Progress
|
||||||
@ -214,10 +248,10 @@ So, what's the deal with all the enthusiasm? I can't believe it - please allow m
|
|||||||
Planned features:
|
Planned features:
|
||||||
- ~~Proper TinyUSB host integration~~ (done)
|
- ~~Proper TinyUSB host integration~~ (done)
|
||||||
- ~~HID report protocol parsing, not just boot protocol~~ (mostly done)
|
- ~~HID report protocol parsing, not just boot protocol~~ (mostly done)
|
||||||
- Support for unified dongle receivers
|
- ~~Support for unified dongle receivers~~
|
||||||
- ~~Support for USB hubs~~ and single-sided operation
|
- ~~Support for USB hubs and single-sided operation~~
|
||||||
- Configurable screens
|
- Configurable screens (partially)
|
||||||
- Permament configuration stored in flash
|
- ~~Permament configuration stored in flash~~
|
||||||
- Unified firmware for both Picos
|
- Unified firmware for both Picos
|
||||||
- ... and more!
|
- ... and more!
|
||||||
|
|
||||||
|
Binary file not shown.
Binary file not shown.
BIN
img/border_top_s.png
Normal file
BIN
img/border_top_s.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
262
memory_map.ld
Normal file
262
memory_map.ld
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
/* Based on GCC ARM embedded samples.
|
||||||
|
Defines the following symbols for use by code:
|
||||||
|
__exidx_start
|
||||||
|
__exidx_end
|
||||||
|
__etext
|
||||||
|
__data_start__
|
||||||
|
__preinit_array_start
|
||||||
|
__preinit_array_end
|
||||||
|
__init_array_start
|
||||||
|
__init_array_end
|
||||||
|
__fini_array_start
|
||||||
|
__fini_array_end
|
||||||
|
__data_end__
|
||||||
|
__bss_start__
|
||||||
|
__bss_end__
|
||||||
|
__end__
|
||||||
|
end
|
||||||
|
__HeapLimit
|
||||||
|
__StackLimit
|
||||||
|
__StackTop
|
||||||
|
__stack (== StackTop)
|
||||||
|
*/
|
||||||
|
|
||||||
|
__CONFIG_STORAGE_LEN = 4k;
|
||||||
|
|
||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 2048k - __CONFIG_STORAGE_LEN
|
||||||
|
FLASH_CONFIG(rw) : ORIGIN = 0x10000000 + (2048k - __CONFIG_STORAGE_LEN), LENGTH = __CONFIG_STORAGE_LEN
|
||||||
|
RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k
|
||||||
|
SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k
|
||||||
|
SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k
|
||||||
|
}
|
||||||
|
|
||||||
|
ENTRY(_entry_point)
|
||||||
|
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
/* Second stage bootloader is prepended to the image. It must be 256 bytes big
|
||||||
|
and checksummed. It is usually built by the boot_stage2 target
|
||||||
|
in the Raspberry Pi Pico SDK
|
||||||
|
*/
|
||||||
|
|
||||||
|
.flash_begin : {
|
||||||
|
__flash_binary_start = .;
|
||||||
|
} > FLASH
|
||||||
|
|
||||||
|
.boot2 : {
|
||||||
|
__boot2_start__ = .;
|
||||||
|
KEEP (*(.boot2))
|
||||||
|
__boot2_end__ = .;
|
||||||
|
} > FLASH
|
||||||
|
|
||||||
|
ASSERT(__boot2_end__ - __boot2_start__ == 256,
|
||||||
|
"ERROR: Pico second stage bootloader must be 256 bytes in size")
|
||||||
|
|
||||||
|
/* The second stage will always enter the image at the start of .text.
|
||||||
|
The debugger will use the ELF entry point, which is the _entry_point
|
||||||
|
symbol if present, otherwise defaults to start of .text.
|
||||||
|
This can be used to transfer control back to the bootrom on debugger
|
||||||
|
launches only, to perform proper flash setup.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.flashtext : {
|
||||||
|
__logical_binary_start = .;
|
||||||
|
KEEP (*(.vectors))
|
||||||
|
KEEP (*(.binary_info_header))
|
||||||
|
__binary_info_header_end = .;
|
||||||
|
KEEP (*(.reset))
|
||||||
|
}
|
||||||
|
|
||||||
|
.rodata : {
|
||||||
|
/* segments not marked as .flashdata are instead pulled into .data (in RAM) to avoid accidental flash accesses */
|
||||||
|
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*)))
|
||||||
|
. = ALIGN(4);
|
||||||
|
} > FLASH
|
||||||
|
|
||||||
|
.ARM.extab :
|
||||||
|
{
|
||||||
|
*(.ARM.extab* .gnu.linkonce.armextab.*)
|
||||||
|
} > FLASH
|
||||||
|
|
||||||
|
__exidx_start = .;
|
||||||
|
.ARM.exidx :
|
||||||
|
{
|
||||||
|
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
|
||||||
|
} > FLASH
|
||||||
|
__exidx_end = .;
|
||||||
|
|
||||||
|
/* Machine inspectable binary information */
|
||||||
|
. = ALIGN(4);
|
||||||
|
__binary_info_start = .;
|
||||||
|
.binary_info :
|
||||||
|
{
|
||||||
|
KEEP(*(.binary_info.keep.*))
|
||||||
|
*(.binary_info.*)
|
||||||
|
} > FLASH
|
||||||
|
__binary_info_end = .;
|
||||||
|
. = ALIGN(4);
|
||||||
|
|
||||||
|
/* Vector table goes first in RAM, to avoid large alignment hole */
|
||||||
|
.ram_vector_table (NOLOAD): {
|
||||||
|
*(.ram_vector_table)
|
||||||
|
} > RAM
|
||||||
|
|
||||||
|
.text : {
|
||||||
|
__ram_text_start__ = .;
|
||||||
|
*(.init)
|
||||||
|
*(.text*)
|
||||||
|
*(.fini)
|
||||||
|
/* Pull all c'tors into .text */
|
||||||
|
*crtbegin.o(.ctors)
|
||||||
|
*crtbegin?.o(.ctors)
|
||||||
|
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
|
||||||
|
*(SORT(.ctors.*))
|
||||||
|
*(.ctors)
|
||||||
|
/* Followed by destructors */
|
||||||
|
*crtbegin.o(.dtors)
|
||||||
|
*crtbegin?.o(.dtors)
|
||||||
|
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
|
||||||
|
*(SORT(.dtors.*))
|
||||||
|
*(.dtors)
|
||||||
|
|
||||||
|
*(.eh_frame*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
__ram_text_end__ = .;
|
||||||
|
} > RAM AT> FLASH
|
||||||
|
__ram_text_source__ = LOADADDR(.text);
|
||||||
|
. = ALIGN(4);
|
||||||
|
|
||||||
|
.data : {
|
||||||
|
__data_start__ = .;
|
||||||
|
*(vtable)
|
||||||
|
|
||||||
|
*(.time_critical*)
|
||||||
|
|
||||||
|
. = ALIGN(4);
|
||||||
|
*(.rodata*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
|
||||||
|
*(.data*)
|
||||||
|
|
||||||
|
. = ALIGN(4);
|
||||||
|
*(.after_data.*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
/* preinit data */
|
||||||
|
PROVIDE_HIDDEN (__mutex_array_start = .);
|
||||||
|
KEEP(*(SORT(.mutex_array.*)))
|
||||||
|
KEEP(*(.mutex_array))
|
||||||
|
PROVIDE_HIDDEN (__mutex_array_end = .);
|
||||||
|
|
||||||
|
. = ALIGN(4);
|
||||||
|
/* preinit data */
|
||||||
|
PROVIDE_HIDDEN (__preinit_array_start = .);
|
||||||
|
KEEP(*(SORT(.preinit_array.*)))
|
||||||
|
KEEP(*(.preinit_array))
|
||||||
|
PROVIDE_HIDDEN (__preinit_array_end = .);
|
||||||
|
|
||||||
|
. = ALIGN(4);
|
||||||
|
/* init data */
|
||||||
|
PROVIDE_HIDDEN (__init_array_start = .);
|
||||||
|
KEEP(*(SORT(.init_array.*)))
|
||||||
|
KEEP(*(.init_array))
|
||||||
|
PROVIDE_HIDDEN (__init_array_end = .);
|
||||||
|
|
||||||
|
. = ALIGN(4);
|
||||||
|
/* finit data */
|
||||||
|
PROVIDE_HIDDEN (__fini_array_start = .);
|
||||||
|
*(SORT(.fini_array.*))
|
||||||
|
*(.fini_array)
|
||||||
|
PROVIDE_HIDDEN (__fini_array_end = .);
|
||||||
|
|
||||||
|
*(.jcr)
|
||||||
|
. = ALIGN(4);
|
||||||
|
/* All data end */
|
||||||
|
__data_end__ = .;
|
||||||
|
} > RAM AT> FLASH
|
||||||
|
/* __etext is (for backwards compatibility) the name of the .data init source pointer (...) */
|
||||||
|
__etext = LOADADDR(.data);
|
||||||
|
|
||||||
|
.uninitialized_data (NOLOAD): {
|
||||||
|
. = ALIGN(4);
|
||||||
|
*(.uninitialized_data*)
|
||||||
|
} > RAM
|
||||||
|
|
||||||
|
/* Start and end symbols must be word-aligned */
|
||||||
|
.scratch_x : {
|
||||||
|
__scratch_x_start__ = .;
|
||||||
|
*(.scratch_x.*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
__scratch_x_end__ = .;
|
||||||
|
} > SCRATCH_X AT > FLASH
|
||||||
|
__scratch_x_source__ = LOADADDR(.scratch_x);
|
||||||
|
|
||||||
|
.scratch_y : {
|
||||||
|
__scratch_y_start__ = .;
|
||||||
|
*(.scratch_y.*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
__scratch_y_end__ = .;
|
||||||
|
} > SCRATCH_Y AT > FLASH
|
||||||
|
__scratch_y_source__ = LOADADDR(.scratch_y);
|
||||||
|
|
||||||
|
.bss : {
|
||||||
|
. = ALIGN(4);
|
||||||
|
__bss_start__ = .;
|
||||||
|
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*)))
|
||||||
|
*(COMMON)
|
||||||
|
. = ALIGN(4);
|
||||||
|
__bss_end__ = .;
|
||||||
|
} > RAM
|
||||||
|
|
||||||
|
.heap (NOLOAD):
|
||||||
|
{
|
||||||
|
__end__ = .;
|
||||||
|
end = __end__;
|
||||||
|
KEEP(*(.heap*))
|
||||||
|
__HeapLimit = .;
|
||||||
|
} > RAM
|
||||||
|
|
||||||
|
/* Configuration flash section (4k in size, end of flash) */
|
||||||
|
|
||||||
|
.section_config : {
|
||||||
|
"ADDR_CONFIG" = .;
|
||||||
|
} > FLASH_CONFIG
|
||||||
|
|
||||||
|
/* .stack*_dummy section doesn't contains any symbols. It is only
|
||||||
|
* used for linker to calculate size of stack sections, and assign
|
||||||
|
* values to stack symbols later
|
||||||
|
*
|
||||||
|
* stack1 section may be empty/missing if platform_launch_core1 is not used */
|
||||||
|
|
||||||
|
/* by default we put core 0 stack at the end of scratch Y, so that if core 1
|
||||||
|
* stack is not used then all of SCRATCH_X is free.
|
||||||
|
*/
|
||||||
|
.stack1_dummy (NOLOAD):
|
||||||
|
{
|
||||||
|
*(.stack1*)
|
||||||
|
} > SCRATCH_X
|
||||||
|
.stack_dummy (NOLOAD):
|
||||||
|
{
|
||||||
|
KEEP(*(.stack*))
|
||||||
|
} > SCRATCH_Y
|
||||||
|
|
||||||
|
.flash_end : {
|
||||||
|
__flash_binary_end = .;
|
||||||
|
} > FLASH
|
||||||
|
|
||||||
|
/* stack limit is poorly named, but historically is maximum heap ptr */
|
||||||
|
__StackLimit = ORIGIN(RAM) + LENGTH(RAM);
|
||||||
|
__StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X);
|
||||||
|
__StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y);
|
||||||
|
__StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy);
|
||||||
|
__StackBottom = __StackTop - SIZEOF(.stack_dummy);
|
||||||
|
PROVIDE(__stack = __StackTop);
|
||||||
|
|
||||||
|
/* Check if data + heap + stack exceeds RAM limit */
|
||||||
|
ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed")
|
||||||
|
|
||||||
|
ASSERT( __binary_info_header_end - __logical_binary_start <= 256, "Binary info must be in first 256 bytes of the binary")
|
||||||
|
/* todo assert on extra code */
|
||||||
|
}
|
||||||
|
|
30
src/defaults.c
Normal file
30
src/defaults.c
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#include "main.h"
|
||||||
|
|
||||||
|
/* Default configuration */
|
||||||
|
const config_t default_config = {
|
||||||
|
.magic_header = 0x0B00B1E5,
|
||||||
|
.output[ACTIVE_OUTPUT_A] =
|
||||||
|
{
|
||||||
|
.number = ACTIVE_OUTPUT_A,
|
||||||
|
.speed_x = MOUSE_SPEED_A_FACTOR_X,
|
||||||
|
.speed_y = MOUSE_SPEED_A_FACTOR_Y,
|
||||||
|
.border = {
|
||||||
|
.top = 0,
|
||||||
|
.bottom = MAX_SCREEN_COORD,
|
||||||
|
},
|
||||||
|
.screen_count = 1,
|
||||||
|
.screen_index = 0,
|
||||||
|
},
|
||||||
|
.output[ACTIVE_OUTPUT_B] =
|
||||||
|
{
|
||||||
|
.number = ACTIVE_OUTPUT_B,
|
||||||
|
.speed_x = MOUSE_SPEED_B_FACTOR_X,
|
||||||
|
.speed_y = MOUSE_SPEED_B_FACTOR_Y,
|
||||||
|
.border = {
|
||||||
|
.top = 0,
|
||||||
|
.bottom = MAX_SCREEN_COORD,
|
||||||
|
},
|
||||||
|
.screen_count = 1,
|
||||||
|
.screen_index = 0,
|
||||||
|
},
|
||||||
|
};
|
@ -30,6 +30,20 @@ void output_toggle_hotkey_handler(device_state_t* state) {
|
|||||||
switch_output(state->active_output);
|
switch_output(state->active_output);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* This key combo records switch y top coordinate for different-size monitors */
|
||||||
|
void screen_border_hotkey_handler(device_state_t* state) {
|
||||||
|
border_size_t *border = &state->config.output[state->active_output].border;
|
||||||
|
|
||||||
|
/* To deal away with 2 different keys, if we're above half, it's the top coord and vice versa */
|
||||||
|
if (state->mouse_y > (MAX_SCREEN_COORD / 2))
|
||||||
|
border->bottom = state->mouse_y;
|
||||||
|
else
|
||||||
|
border->top = state->mouse_y;
|
||||||
|
|
||||||
|
send_packet((uint8_t*)border, SYNC_BORDERS_MSG, sizeof(border_size_t));
|
||||||
|
save_config();
|
||||||
|
};
|
||||||
|
|
||||||
/* This key combo puts board A in firmware upgrade mode */
|
/* This key combo puts board A in firmware upgrade mode */
|
||||||
void fw_upgrade_hotkey_handler_A(device_state_t* state) {
|
void fw_upgrade_hotkey_handler_A(device_state_t* state) {
|
||||||
reset_usb_boot(1 << PICO_DEFAULT_LED_PIN, 0);
|
reset_usb_boot(1 << PICO_DEFAULT_LED_PIN, 0);
|
||||||
@ -40,12 +54,17 @@ void fw_upgrade_hotkey_handler_B(device_state_t* state) {
|
|||||||
send_value(ENABLE, FIRMWARE_UPGRADE_MSG);
|
send_value(ENABLE, FIRMWARE_UPGRADE_MSG);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* This key combo prevents mouse from switching outputs */
|
||||||
void switchlock_hotkey_handler(device_state_t* state) {
|
void switchlock_hotkey_handler(device_state_t* state) {
|
||||||
state->switch_lock ^= 1;
|
state->switch_lock ^= 1;
|
||||||
send_value(state->switch_lock, SWITCH_LOCK_MSG);
|
send_value(state->switch_lock, SWITCH_LOCK_MSG);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* When pressed, erases stored config in flash and loads defaults */
|
||||||
|
void wipe_config_hotkey_handler(device_state_t* state) {
|
||||||
|
wipe_config();
|
||||||
|
load_config();
|
||||||
|
}
|
||||||
|
|
||||||
void mouse_zoom_hotkey_handler(device_state_t* state) {
|
void mouse_zoom_hotkey_handler(device_state_t* state) {
|
||||||
if (state->mouse_zoom)
|
if (state->mouse_zoom)
|
||||||
@ -67,16 +86,18 @@ void all_keys_released_handler(device_state_t* state) {
|
|||||||
* ==================================================== */
|
* ==================================================== */
|
||||||
|
|
||||||
void handle_keyboard_uart_msg(uart_packet_t* packet, device_state_t* state) {
|
void handle_keyboard_uart_msg(uart_packet_t* packet, device_state_t* state) {
|
||||||
if (state->active_output == ACTIVE_OUTPUT_B) {
|
queue_kbd_report((hid_keyboard_report_t*)packet->data, state);
|
||||||
queue_kbd_report((hid_keyboard_report_t*)packet->data, state);
|
state->last_activity[BOARD_ROLE] = time_us_64();
|
||||||
state->last_activity[ACTIVE_OUTPUT_B] = time_us_64();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void handle_mouse_abs_uart_msg(uart_packet_t* packet, device_state_t* state) {
|
void handle_mouse_abs_uart_msg(uart_packet_t* packet, device_state_t* state) {
|
||||||
hid_abs_mouse_report_t* mouse_report = (hid_abs_mouse_report_t*)packet->data;
|
hid_abs_mouse_report_t* mouse_report = (hid_abs_mouse_report_t*)packet->data;
|
||||||
queue_mouse_report(mouse_report, state);
|
queue_mouse_report(mouse_report, state);
|
||||||
state->last_activity[ACTIVE_OUTPUT_A] = time_us_64();
|
|
||||||
|
state->mouse_x = mouse_report->x;
|
||||||
|
state->mouse_y = mouse_report->y;
|
||||||
|
|
||||||
|
state->last_activity[BOARD_ROLE] = time_us_64();
|
||||||
}
|
}
|
||||||
|
|
||||||
void handle_output_select_msg(uart_packet_t* packet, device_state_t* state) {
|
void handle_output_select_msg(uart_packet_t* packet, device_state_t* state) {
|
||||||
@ -84,7 +105,7 @@ void handle_output_select_msg(uart_packet_t* packet, device_state_t* state) {
|
|||||||
if (state->tud_connected)
|
if (state->tud_connected)
|
||||||
stop_pressing_any_keys(&global_state);
|
stop_pressing_any_keys(&global_state);
|
||||||
|
|
||||||
update_leds(state);
|
restore_leds(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* On firmware upgrade message, reboot into the BOOTSEL fw upgrade mode */
|
/* On firmware upgrade message, reboot into the BOOTSEL fw upgrade mode */
|
||||||
@ -98,18 +119,30 @@ void handle_mouse_zoom_msg(uart_packet_t* packet, device_state_t* state) {
|
|||||||
|
|
||||||
void handle_set_report_msg(uart_packet_t* packet, device_state_t* state) {
|
void handle_set_report_msg(uart_packet_t* packet, device_state_t* state) {
|
||||||
/* Only board B sends LED state through this message type */
|
/* Only board B sends LED state through this message type */
|
||||||
state->keyboard_leds[ACTIVE_OUTPUT_B] = packet->data[0];
|
state->keyboard_leds[BOARD_ROLE] = packet->data[0];
|
||||||
update_leds(state);
|
restore_leds(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handle_switch_lock_msg(uart_packet_t* packet, device_state_t* state) {
|
void handle_switch_lock_msg(uart_packet_t* packet, device_state_t* state) {
|
||||||
state->switch_lock = packet->data[0];
|
state->switch_lock = packet->data[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Handle border syncing message that lets the other device know about monitor height offset */
|
||||||
|
void handle_sync_borders_msg(uart_packet_t* packet, device_state_t* state) {
|
||||||
|
border_size_t *border = &state->config.output[state->active_output].border;
|
||||||
|
memcpy(border, packet->data, sizeof(border_size_t));
|
||||||
|
save_config();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When this message is received, flash the locally attached LED to verify serial comms */
|
||||||
|
void handle_flash_led_msg(uart_packet_t* packet, device_state_t* state) {
|
||||||
|
blink_led(state);
|
||||||
|
}
|
||||||
|
|
||||||
/* Update output variable, set LED on/off and notify the other board so they are in sync. */
|
/* Update output variable, set LED on/off and notify the other board so they are in sync. */
|
||||||
void switch_output(uint8_t new_output) {
|
void switch_output(uint8_t new_output) {
|
||||||
global_state.active_output = new_output;
|
global_state.active_output = new_output;
|
||||||
update_leds(&global_state);
|
restore_leds(&global_state);
|
||||||
send_value(new_output, OUTPUT_SELECT_MSG);
|
send_value(new_output, OUTPUT_SELECT_MSG);
|
||||||
|
|
||||||
/* If we were holding a key down and drag the mouse to another screen, the key gets stuck.
|
/* If we were holding a key down and drag the mouse to another screen, the key gets stuck.
|
||||||
|
@ -32,33 +32,49 @@ hotkey_combo_t hotkeys[] = {
|
|||||||
{.modifier = KEYBOARD_MODIFIER_RIGHTALT,
|
{.modifier = KEYBOARD_MODIFIER_RIGHTALT,
|
||||||
.keys = {},
|
.keys = {},
|
||||||
.key_count = 0,
|
.key_count = 0,
|
||||||
|
.pass_to_os = true,
|
||||||
.action_handler = &mouse_zoom_hotkey_handler},
|
.action_handler = &mouse_zoom_hotkey_handler},
|
||||||
|
|
||||||
/* Switch lock */
|
/* Switch lock */
|
||||||
{.modifier = KEYBOARD_MODIFIER_RIGHTCTRL,
|
{.modifier = KEYBOARD_MODIFIER_RIGHTCTRL,
|
||||||
.keys = {HID_KEY_L},
|
.keys = {HID_KEY_L},
|
||||||
.key_count = 1,
|
.key_count = 1,
|
||||||
|
.acknowledge = true,
|
||||||
.action_handler = &switchlock_hotkey_handler},
|
.action_handler = &switchlock_hotkey_handler},
|
||||||
|
|
||||||
|
/* Erase stored config */
|
||||||
|
{.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT,
|
||||||
|
.keys = {HID_KEY_F12, HID_KEY_D},
|
||||||
|
.key_count = 2,
|
||||||
|
.acknowledge = true,
|
||||||
|
.action_handler = &wipe_config_hotkey_handler},
|
||||||
|
|
||||||
|
/* Record switch y coordinate */
|
||||||
|
{.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT,
|
||||||
|
.keys = {HID_KEY_F12, HID_KEY_Y},
|
||||||
|
.key_count = 2,
|
||||||
|
.acknowledge = true,
|
||||||
|
.action_handler = &screen_border_hotkey_handler},
|
||||||
|
|
||||||
/* Hold down left shift + right shift + F12 + A ==> firmware upgrade mode for board A (kbd) */
|
/* Hold down left shift + right shift + F12 + A ==> firmware upgrade mode for board A (kbd) */
|
||||||
{.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT | KEYBOARD_MODIFIER_LEFTSHIFT,
|
{.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT | KEYBOARD_MODIFIER_LEFTSHIFT,
|
||||||
.keys = {HID_KEY_F12, HID_KEY_A},
|
.keys = {HID_KEY_F12, HID_KEY_A},
|
||||||
.key_count = 2,
|
.key_count = 2,
|
||||||
|
.acknowledge = true,
|
||||||
.action_handler = &fw_upgrade_hotkey_handler_A},
|
.action_handler = &fw_upgrade_hotkey_handler_A},
|
||||||
|
|
||||||
/* Hold down left shift + right shift + F12 + B ==> firmware upgrade mode for board B (mouse) */
|
/* Hold down left shift + right shift + F12 + B ==> firmware upgrade mode for board B (mouse) */
|
||||||
{.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT | KEYBOARD_MODIFIER_LEFTSHIFT,
|
{.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT | KEYBOARD_MODIFIER_LEFTSHIFT,
|
||||||
.keys = {HID_KEY_F12, HID_KEY_B},
|
.keys = {HID_KEY_F12, HID_KEY_B},
|
||||||
.key_count = 2,
|
.key_count = 2,
|
||||||
.action_handler = &fw_upgrade_hotkey_handler_B}
|
.acknowledge = true,
|
||||||
};
|
.action_handler = &fw_upgrade_hotkey_handler_B}};
|
||||||
|
|
||||||
|
|
||||||
/* ==================================================== *
|
/* ==================================================== *
|
||||||
* Keyboard Queue Section
|
* Keyboard Queue Section
|
||||||
* ==================================================== */
|
* ==================================================== */
|
||||||
|
|
||||||
void process_kbd_queue_task(device_state_t *state) {
|
void process_kbd_queue_task(device_state_t* state) {
|
||||||
hid_keyboard_report_t report;
|
hid_keyboard_report_t report;
|
||||||
|
|
||||||
/* If we're not connected, we have nowhere to send reports to. */
|
/* If we're not connected, we have nowhere to send reports to. */
|
||||||
@ -77,7 +93,7 @@ void process_kbd_queue_task(device_state_t *state) {
|
|||||||
queue_try_remove(&state->kbd_queue, &report);
|
queue_try_remove(&state->kbd_queue, &report);
|
||||||
}
|
}
|
||||||
|
|
||||||
void queue_kbd_report(hid_keyboard_report_t *report, device_state_t *state) {
|
void queue_kbd_report(hid_keyboard_report_t* report, device_state_t* state) {
|
||||||
/* It wouldn't be fun to queue up a bunch of messages and then dump them all on host */
|
/* It wouldn't be fun to queue up a bunch of messages and then dump them all on host */
|
||||||
if (!state->tud_connected)
|
if (!state->tud_connected)
|
||||||
return;
|
return;
|
||||||
@ -85,11 +101,21 @@ void queue_kbd_report(hid_keyboard_report_t *report, device_state_t *state) {
|
|||||||
queue_try_add(&state->kbd_queue, report);
|
queue_try_add(&state->kbd_queue, report);
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop_pressing_any_keys(device_state_t *state) {
|
void stop_pressing_any_keys(device_state_t* state) {
|
||||||
static hid_keyboard_report_t no_keys_pressed_report = {0, 0, {0}};
|
static hid_keyboard_report_t no_keys_pressed_report = {0, 0, {0}};
|
||||||
queue_try_add(&state->kbd_queue, &no_keys_pressed_report);
|
queue_try_add(&state->kbd_queue, &no_keys_pressed_report);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If keys need to go locally, queue packet to kbd queue, else send them through UART */
|
||||||
|
void send_key(hid_keyboard_report_t* report, device_state_t* state) {
|
||||||
|
if (state->active_output == BOARD_ROLE) {
|
||||||
|
queue_kbd_report(report, state);
|
||||||
|
state->last_activity[BOARD_ROLE] = time_us_64();
|
||||||
|
} else {
|
||||||
|
send_packet((uint8_t*)report, KEYBOARD_REPORT_MSG, KBD_REPORT_LENGTH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ==================================================== *
|
/* ==================================================== *
|
||||||
* Parse and interpret the keys pressed on the keyboard
|
* Parse and interpret the keys pressed on the keyboard
|
||||||
* ==================================================== */
|
* ==================================================== */
|
||||||
@ -105,42 +131,51 @@ bool no_keys_are_pressed(const hid_keyboard_report_t* report) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hotkey_combo_t* check_hotkeys(hid_keyboard_report_t* report, int length, device_state_t* state) {
|
||||||
|
/* Go through the list of hotkeys, check if any are pressed, then execute their handler */
|
||||||
|
for (int n = 0; n < sizeof(hotkeys) / sizeof(hotkeys[0]); n++) {
|
||||||
|
if (is_key_pressed(hotkeys[n], report)) {
|
||||||
|
return &hotkeys[n];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
void process_keyboard_report(uint8_t* raw_report, int length, device_state_t* state) {
|
void process_keyboard_report(uint8_t* raw_report, int length, device_state_t* state) {
|
||||||
hid_keyboard_report_t* keyboard_report = (hid_keyboard_report_t*)raw_report;
|
hid_keyboard_report_t* keyboard_report = (hid_keyboard_report_t*)raw_report;
|
||||||
|
hotkey_combo_t* hotkey = NULL;
|
||||||
|
|
||||||
if (length < KBD_REPORT_LENGTH)
|
if (length < KBD_REPORT_LENGTH)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* Go through the list of hotkeys, check if any are pressed, then execute their handler */
|
|
||||||
for (int n = 0; n < sizeof(hotkeys) / sizeof(hotkeys[0]); n++) {
|
|
||||||
if (keypress_check(hotkeys[n], keyboard_report)) {
|
|
||||||
hotkeys[n].action_handler(state);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
state->key_pressed = !no_keys_are_pressed(keyboard_report);
|
|
||||||
|
|
||||||
/* If no keys are pressed anymore, take care of checking and deactivating stuff */
|
/* If no keys are pressed anymore, take care of checking and deactivating stuff */
|
||||||
if (!state->key_pressed) {
|
if(no_keys_are_pressed(keyboard_report))
|
||||||
all_keys_released_handler(state);
|
all_keys_released_handler(state);
|
||||||
|
else
|
||||||
|
/* Check if it was a hotkey, makes sense only if a key is pressed */
|
||||||
|
hotkey = check_hotkeys(keyboard_report, length, state);
|
||||||
|
|
||||||
|
/* ... and take appropriate action */
|
||||||
|
if(hotkey != NULL) {
|
||||||
|
/* Provide visual feedback we received the action */
|
||||||
|
if (hotkey->acknowledge)
|
||||||
|
blink_led(state);
|
||||||
|
|
||||||
|
hotkey->action_handler(state);
|
||||||
|
if (!hotkey->pass_to_os)
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If keys need to go to output B, send them through UART, otherwise send a HID report directly */
|
/* This method will decide if the key gets queued locally or sent through UART */
|
||||||
if (state->active_output == ACTIVE_OUTPUT_B) {
|
send_key(keyboard_report, state);
|
||||||
send_packet(raw_report, KEYBOARD_REPORT_MSG, KBD_REPORT_LENGTH);
|
|
||||||
} else {
|
|
||||||
queue_kbd_report(keyboard_report, state);
|
|
||||||
state->last_activity[ACTIVE_OUTPUT_A] = time_us_64();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ============================================================ *
|
/* ============================================================ *
|
||||||
* Check if a specific key combination is present in the report
|
* Check if a specific key combination is present in the report
|
||||||
* ============================================================ */
|
* ============================================================ */
|
||||||
|
|
||||||
bool keypress_check(hotkey_combo_t keypress, const hid_keyboard_report_t* report) {
|
bool is_key_pressed(hotkey_combo_t keypress, const hid_keyboard_report_t* report) {
|
||||||
int matches = 0;
|
int matches = 0;
|
||||||
|
|
||||||
/* We expect all modifiers specified to be detected in the report */
|
/* We expect all modifiers specified to be detected in the report */
|
||||||
|
61
src/led.c
61
src/led.c
@ -21,13 +21,60 @@
|
|||||||
* ========== Update pico and keyboard LEDs ========== *
|
* ========== Update pico and keyboard LEDs ========== *
|
||||||
* ==================================================== */
|
* ==================================================== */
|
||||||
|
|
||||||
void update_leds(device_state_t* state) {
|
void set_keyboard_leds(uint8_t requested_led_state, device_state_t* state) {
|
||||||
gpio_put(GPIO_LED_PIN, state->active_output == BOARD_ROLE);
|
static uint8_t new_led_value;
|
||||||
|
|
||||||
// TODO: Will be done in a callback
|
new_led_value = requested_led_state;
|
||||||
if (BOARD_ROLE == PICO_A) {
|
if (state->keyboard_connected) {
|
||||||
uint8_t* leds = &(state->keyboard_leds[state->active_output]);
|
tuh_hid_set_report(state->kbd_dev_addr, state->kbd_instance, 0, HID_REPORT_TYPE_OUTPUT,
|
||||||
tuh_hid_set_report(global_state.kbd_dev_addr, global_state.kbd_instance, 0,
|
&new_led_value, sizeof(uint8_t));
|
||||||
HID_REPORT_TYPE_OUTPUT, leds, sizeof(uint8_t));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void restore_leds(device_state_t* state) {
|
||||||
|
state->onboard_led_state = (state->active_output == BOARD_ROLE);
|
||||||
|
gpio_put(GPIO_LED_PIN, state->onboard_led_state);
|
||||||
|
|
||||||
|
if (state->keyboard_connected) {
|
||||||
|
uint8_t leds = state->keyboard_leds[state->active_output];
|
||||||
|
set_keyboard_leds(leds, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void blink_led(device_state_t* state) {
|
||||||
|
/* Since LEDs might be ON previously, we go OFF, ON, OFF, ON, OFF */
|
||||||
|
state->blinks_left = 5;
|
||||||
|
state->last_led_change = time_us_32();
|
||||||
|
}
|
||||||
|
|
||||||
|
void led_blinking_task(device_state_t* state) {
|
||||||
|
/* 50 ms off, 50 ms on */
|
||||||
|
const int blink_interval_us = 80000;
|
||||||
|
static uint8_t leds;
|
||||||
|
|
||||||
|
/* If there is no more blinking to be done, exit immediately */
|
||||||
|
if (state->blinks_left == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* We have some blinks left to do, check if they are due, exit if not */
|
||||||
|
if ((time_us_32()) - state->last_led_change < blink_interval_us)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Toggle the LED state */
|
||||||
|
uint8_t new_led_state = gpio_get(GPIO_LED_PIN) ^ 1;
|
||||||
|
gpio_put(GPIO_LED_PIN, new_led_state);
|
||||||
|
|
||||||
|
/* Also keyboard leds (if it's connected locally) since on-board leds are not visible */
|
||||||
|
leds = new_led_state * 0x07; /* Numlock, capslock, scrollock */
|
||||||
|
|
||||||
|
if (state->keyboard_connected)
|
||||||
|
set_keyboard_leds(leds, state);
|
||||||
|
|
||||||
|
/* Decrement the counter and update the last-changed timestamp */
|
||||||
|
state->blinks_left--;
|
||||||
|
state->last_led_change = time_us_32();
|
||||||
|
|
||||||
|
/* Restore LEDs in the last pass */
|
||||||
|
if (state->blinks_left == 0)
|
||||||
|
restore_leds(state);
|
||||||
|
}
|
@ -63,6 +63,9 @@ void core1_main() {
|
|||||||
|
|
||||||
// Receives data over serial from the other board
|
// Receives data over serial from the other board
|
||||||
receive_char(&in_packet, &global_state);
|
receive_char(&in_packet, &global_state);
|
||||||
|
|
||||||
|
// Check if LED needs blinking
|
||||||
|
led_blinking_task(&global_state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
113
src/main.h
113
src/main.h
@ -19,11 +19,15 @@
|
|||||||
|
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "hardware/flash.h"
|
||||||
|
#include "hardware/sync.h"
|
||||||
#include "hardware/watchdog.h"
|
#include "hardware/watchdog.h"
|
||||||
|
#include "hid_parser.h"
|
||||||
#include "pico/bootrom.h"
|
#include "pico/bootrom.h"
|
||||||
#include "pico/multicore.h"
|
#include "pico/multicore.h"
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
@ -31,10 +35,9 @@
|
|||||||
#include "pio_usb.h"
|
#include "pio_usb.h"
|
||||||
#include "tusb.h"
|
#include "tusb.h"
|
||||||
#include "usb_descriptors.h"
|
#include "usb_descriptors.h"
|
||||||
#include "hid_parser.h"
|
|
||||||
#include "user_config.h"
|
#include "user_config.h"
|
||||||
|
|
||||||
/********* Misc definitions **********/
|
/********* Misc definitions for better readability **********/
|
||||||
#define PICO_A 0
|
#define PICO_A 0
|
||||||
#define PICO_B 1
|
#define PICO_B 1
|
||||||
|
|
||||||
@ -50,6 +53,8 @@
|
|||||||
#define MAX_REPORT_ITEMS 16
|
#define MAX_REPORT_ITEMS 16
|
||||||
#define MOUSE_BOOT_REPORT_LEN 4
|
#define MOUSE_BOOT_REPORT_LEN 4
|
||||||
|
|
||||||
|
#define NUM_SCREENS 2 // Will be more in the future
|
||||||
|
|
||||||
/********* Pinout definitions **********/
|
/********* Pinout definitions **********/
|
||||||
#define PIO_USB_DP_PIN 14 // D+ is pin 14, D- is pin 15
|
#define PIO_USB_DP_PIN 14 // D+ is pin 14, D- is pin 15
|
||||||
#define GPIO_LED_PIN 25 // LED is connected to pin 25 on a PICO
|
#define GPIO_LED_PIN 25 // LED is connected to pin 25 on a PICO
|
||||||
@ -85,7 +90,7 @@
|
|||||||
* - checksum is simply calculated by XORing all bytes together
|
* - checksum is simply calculated by XORing all bytes together
|
||||||
*/
|
*/
|
||||||
|
|
||||||
enum packet_type_e : uint8_t {
|
enum packet_type_e {
|
||||||
KEYBOARD_REPORT_MSG = 1,
|
KEYBOARD_REPORT_MSG = 1,
|
||||||
MOUSE_REPORT_MSG = 2,
|
MOUSE_REPORT_MSG = 2,
|
||||||
OUTPUT_SELECT_MSG = 3,
|
OUTPUT_SELECT_MSG = 3,
|
||||||
@ -93,6 +98,8 @@ enum packet_type_e : uint8_t {
|
|||||||
MOUSE_ZOOM_MSG = 5,
|
MOUSE_ZOOM_MSG = 5,
|
||||||
KBD_SET_REPORT_MSG = 6,
|
KBD_SET_REPORT_MSG = 6,
|
||||||
SWITCH_LOCK_MSG = 7,
|
SWITCH_LOCK_MSG = 7,
|
||||||
|
SYNC_BORDERS_MSG = 8,
|
||||||
|
FLASH_LED_MSG = 9,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -133,6 +140,37 @@ typedef struct {
|
|||||||
/********* Screen **********/
|
/********* Screen **********/
|
||||||
#define MAX_SCREEN_COORD 32767
|
#define MAX_SCREEN_COORD 32767
|
||||||
|
|
||||||
|
/********* Configuration storage definitions **********/
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int top; // When jumping from a smaller to a bigger screen, go to THIS top height
|
||||||
|
int bottom; // When jumping from a smaller to a bigger screen, go to THIS bottom
|
||||||
|
// height
|
||||||
|
} border_size_t;
|
||||||
|
|
||||||
|
/* Define output parameters */
|
||||||
|
typedef struct {
|
||||||
|
int number; // Number of this output (e.g. ACTIVE_OUTPUT_A = 0 etc)
|
||||||
|
int screen_count; // How many monitors per output (e.g. Output A is Windows with 3 monitors)
|
||||||
|
int screen_index; // Current active screen
|
||||||
|
int speed_x; // Mouse speed per output, in direction X
|
||||||
|
int speed_y; // Mouse speed per output, in direction Y
|
||||||
|
border_size_t border; // Screen border size/offset to keep cursor at same height when switching
|
||||||
|
} output_t;
|
||||||
|
|
||||||
|
/* Data structure defining how configuration is stored */
|
||||||
|
typedef struct {
|
||||||
|
uint32_t magic_header;
|
||||||
|
uint8_t force_mouse_boot_mode;
|
||||||
|
output_t output[NUM_SCREENS];
|
||||||
|
uint32_t checksum;
|
||||||
|
} config_t;
|
||||||
|
|
||||||
|
extern const config_t default_config;
|
||||||
|
|
||||||
|
extern config_t ADDR_CONFIG[];
|
||||||
|
#define ADDR_CONFIG_BASE_ADDR (ADDR_CONFIG)
|
||||||
|
|
||||||
// -------------------------------------------------------+
|
// -------------------------------------------------------+
|
||||||
|
|
||||||
typedef void (*action_handler_t)();
|
typedef void (*action_handler_t)();
|
||||||
@ -142,6 +180,8 @@ typedef struct {
|
|||||||
uint8_t keys[6]; // Which keys need to be pressed
|
uint8_t keys[6]; // Which keys need to be pressed
|
||||||
uint8_t key_count; // How many keys are pressed
|
uint8_t key_count; // How many keys are pressed
|
||||||
action_handler_t action_handler; // What to execute when the key combination is detected
|
action_handler_t action_handler; // What to execute when the key combination is detected
|
||||||
|
bool pass_to_os; // True if we are to pass the key to the OS too
|
||||||
|
bool acknowledge; // True if we are to notify the user about registering keypress
|
||||||
} hotkey_combo_t;
|
} hotkey_combo_t;
|
||||||
|
|
||||||
typedef struct TU_ATTR_PACKED {
|
typedef struct TU_ATTR_PACKED {
|
||||||
@ -155,12 +195,12 @@ typedef struct TU_ATTR_PACKED {
|
|||||||
typedef enum { IDLE, READING_PACKET, PROCESSING_PACKET } receiver_state_t;
|
typedef enum { IDLE, READING_PACKET, PROCESSING_PACKET } receiver_state_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t kbd_dev_addr; // Address of the keyboard device
|
uint8_t kbd_dev_addr; // Address of the keyboard device
|
||||||
uint8_t kbd_instance; // Keyboard instance (d'uh - isn't this a useless comment)
|
uint8_t kbd_instance; // Keyboard instance (d'uh - isn't this a useless comment)
|
||||||
|
|
||||||
uint8_t keyboard_leds[2]; // State of keyboard LEDs (index 0 = A, index 1 = B)
|
uint8_t keyboard_leds[NUM_SCREENS]; // State of keyboard LEDs (index 0 = A, index 1 = B)
|
||||||
uint64_t last_activity[2]; // Timestamp of the last input activity (-||-)
|
uint64_t last_activity[NUM_SCREENS]; // Timestamp of the last input activity (-||-)
|
||||||
receiver_state_t receiver_state; // Storing the state for the simple receiver state machine
|
receiver_state_t receiver_state; // Storing the state for the simple receiver state machine
|
||||||
|
|
||||||
uint64_t core1_last_loop_pass; // Timestamp of last core1 loop execution
|
uint64_t core1_last_loop_pass; // Timestamp of last core1 loop execution
|
||||||
uint8_t active_output; // Currently selected output (0 = A, 1 = B)
|
uint8_t active_output; // Currently selected output (0 = A, 1 = B)
|
||||||
@ -168,35 +208,40 @@ typedef struct {
|
|||||||
int16_t mouse_x; // Store and update the location of our mouse pointer
|
int16_t mouse_x; // Store and update the location of our mouse pointer
|
||||||
int16_t mouse_y;
|
int16_t mouse_y;
|
||||||
|
|
||||||
mouse_t mouse_dev; // Mouse device specifics, e.g. stores locations for keys in report
|
config_t config; // Device configuration, loaded from flash or defaults used
|
||||||
queue_t kbd_queue; // Queue that stores keyboard reports
|
|
||||||
queue_t mouse_queue; // Queue that stores mouse reports
|
|
||||||
|
|
||||||
bool tud_connected; // True when TinyUSB device successfully connects
|
mouse_t mouse_dev; // Mouse device specifics, e.g. stores locations for keys in report
|
||||||
bool keyboard_connected; // True when our keyboard is connected locally
|
queue_t kbd_queue; // Queue that stores keyboard reports
|
||||||
bool mouse_connected; // True when a mouse is connected locally
|
queue_t mouse_queue; // Queue that stores mouse reports
|
||||||
bool mouse_zoom; // True when "mouse zoom" is enabled
|
|
||||||
bool switch_lock; // True when device is prevented from switching
|
|
||||||
|
|
||||||
bool key_pressed; // We are holding down a key (from the PCs point of view)
|
/* Connection status flags */
|
||||||
|
bool tud_connected; // True when TinyUSB device successfully connects
|
||||||
|
bool keyboard_connected; // True when our keyboard is connected locally
|
||||||
|
bool mouse_connected; // True when a mouse is connected locally
|
||||||
|
|
||||||
|
/* Feature flags */
|
||||||
|
bool mouse_zoom; // True when "mouse zoom" is enabled
|
||||||
|
bool switch_lock; // True when device is prevented from switching
|
||||||
|
bool onboard_led_state; // True when LED is ON
|
||||||
|
|
||||||
|
/* Onboard LED blinky (provide feedback when e.g. mouse connected) */
|
||||||
|
int32_t blinks_left; // How many blink transitions are left
|
||||||
|
int32_t last_led_change; // Timestamp of the last time led state transitioned
|
||||||
|
|
||||||
} device_state_t;
|
} device_state_t;
|
||||||
|
|
||||||
/******* USB Host *********/
|
|
||||||
void process_mouse_report(uint8_t*, int, device_state_t*);
|
|
||||||
void check_endpoints(device_state_t* state);
|
|
||||||
|
|
||||||
/********* Setup **********/
|
/********* Setup **********/
|
||||||
void initial_setup(void);
|
void initial_setup(void);
|
||||||
void serial_init(void);
|
void serial_init(void);
|
||||||
void core1_main(void);
|
void core1_main(void);
|
||||||
|
|
||||||
/********* Keyboard **********/
|
/********* Keyboard **********/
|
||||||
bool keypress_check(hotkey_combo_t, const hid_keyboard_report_t*);
|
bool is_key_pressed(hotkey_combo_t, const hid_keyboard_report_t*);
|
||||||
void process_keyboard_report(uint8_t*, int, device_state_t*);
|
void process_keyboard_report(uint8_t*, int, device_state_t*);
|
||||||
void stop_pressing_any_keys(device_state_t*);
|
void stop_pressing_any_keys(device_state_t*);
|
||||||
void queue_kbd_report(hid_keyboard_report_t*, device_state_t*);
|
void queue_kbd_report(hid_keyboard_report_t*, device_state_t*);
|
||||||
void process_kbd_queue_task(device_state_t*);
|
void process_kbd_queue_task(device_state_t*);
|
||||||
|
void send_key(hid_keyboard_report_t*, device_state_t*);
|
||||||
|
|
||||||
/********* Mouse **********/
|
/********* Mouse **********/
|
||||||
bool tud_hid_abs_mouse_report(uint8_t report_id,
|
bool tud_hid_abs_mouse_report(uint8_t report_id,
|
||||||
@ -206,10 +251,15 @@ bool tud_hid_abs_mouse_report(uint8_t report_id,
|
|||||||
int8_t vertical,
|
int8_t vertical,
|
||||||
int8_t horizontal);
|
int8_t horizontal);
|
||||||
|
|
||||||
uint8_t parse_report_descriptor(mouse_t* mouse, uint8_t arr_count, uint8_t const* desc_report, uint16_t desc_len);
|
void process_mouse_report(uint8_t*, int, device_state_t*);
|
||||||
int32_t get_report_value(uint8_t* report, report_val_t *val);
|
uint8_t parse_report_descriptor(mouse_t* mouse,
|
||||||
|
uint8_t arr_count,
|
||||||
|
uint8_t const* desc_report,
|
||||||
|
uint16_t desc_len);
|
||||||
|
int32_t get_report_value(uint8_t* report, report_val_t* val);
|
||||||
void process_mouse_queue_task(device_state_t*);
|
void process_mouse_queue_task(device_state_t*);
|
||||||
void queue_mouse_report(hid_abs_mouse_report_t*, device_state_t*);
|
void queue_mouse_report(hid_abs_mouse_report_t*, device_state_t*);
|
||||||
|
void send_mouse(hid_abs_mouse_report_t*, device_state_t*);
|
||||||
|
|
||||||
/********* UART **********/
|
/********* UART **********/
|
||||||
void receive_char(uart_packet_t*, device_state_t*);
|
void receive_char(uart_packet_t*, device_state_t*);
|
||||||
@ -217,7 +267,9 @@ void send_packet(const uint8_t*, enum packet_type_e, int);
|
|||||||
void send_value(const uint8_t, enum packet_type_e);
|
void send_value(const uint8_t, enum packet_type_e);
|
||||||
|
|
||||||
/********* LEDs **********/
|
/********* LEDs **********/
|
||||||
void update_leds(device_state_t*);
|
void restore_leds(device_state_t*);
|
||||||
|
void blink_led(device_state_t*);
|
||||||
|
void led_blinking_task(device_state_t*);
|
||||||
|
|
||||||
/********* Checksum **********/
|
/********* Checksum **********/
|
||||||
uint8_t calc_checksum(const uint8_t*, int);
|
uint8_t calc_checksum(const uint8_t*, int);
|
||||||
@ -226,21 +278,30 @@ bool verify_checksum(const uart_packet_t*);
|
|||||||
/********* Watchdog **********/
|
/********* Watchdog **********/
|
||||||
void kick_watchdog(void);
|
void kick_watchdog(void);
|
||||||
|
|
||||||
|
/********* Configuration **********/
|
||||||
|
void load_config(void);
|
||||||
|
void save_config(void);
|
||||||
|
void wipe_config(void);
|
||||||
|
|
||||||
/********* Handlers **********/
|
/********* Handlers **********/
|
||||||
void output_toggle_hotkey_handler(device_state_t*);
|
void output_toggle_hotkey_handler(device_state_t*);
|
||||||
|
void screen_border_hotkey_handler(device_state_t*);
|
||||||
void fw_upgrade_hotkey_handler_A(device_state_t*);
|
void fw_upgrade_hotkey_handler_A(device_state_t*);
|
||||||
void fw_upgrade_hotkey_handler_B(device_state_t*);
|
void fw_upgrade_hotkey_handler_B(device_state_t*);
|
||||||
void mouse_zoom_hotkey_handler(device_state_t*);
|
void mouse_zoom_hotkey_handler(device_state_t*);
|
||||||
void all_keys_released_handler(device_state_t*);
|
void all_keys_released_handler(device_state_t*);
|
||||||
void switchlock_hotkey_handler(device_state_t*);
|
void switchlock_hotkey_handler(device_state_t*);
|
||||||
|
void wipe_config_hotkey_handler(device_state_t*);
|
||||||
|
|
||||||
void handle_keyboard_uart_msg(uart_packet_t*, device_state_t*);
|
void handle_keyboard_uart_msg(uart_packet_t*, device_state_t*);
|
||||||
void handle_mouse_abs_uart_msg(uart_packet_t*, device_state_t*);
|
void handle_mouse_abs_uart_msg(uart_packet_t*, device_state_t*);
|
||||||
void handle_output_select_msg(uart_packet_t*, device_state_t*);
|
void handle_output_select_msg(uart_packet_t*, device_state_t*);
|
||||||
void handle_fw_upgrade_msg(void);
|
|
||||||
void handle_mouse_zoom_msg(uart_packet_t*, device_state_t*);
|
void handle_mouse_zoom_msg(uart_packet_t*, device_state_t*);
|
||||||
void handle_set_report_msg(uart_packet_t*, device_state_t*);
|
void handle_set_report_msg(uart_packet_t*, device_state_t*);
|
||||||
void handle_switch_lock_msg(uart_packet_t*, device_state_t*);
|
void handle_switch_lock_msg(uart_packet_t*, device_state_t*);
|
||||||
|
void handle_sync_borders_msg(uart_packet_t*, device_state_t*);
|
||||||
|
void handle_flash_led_msg(uart_packet_t*, device_state_t*);
|
||||||
|
void handle_fw_upgrade_msg(void);
|
||||||
|
|
||||||
void switch_output(uint8_t);
|
void switch_output(uint8_t);
|
||||||
|
|
||||||
|
282
src/mouse.c
282
src/mouse.c
@ -18,158 +18,204 @@
|
|||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
|
||||||
int get_mouse_offset(int32_t movement, const int direction) {
|
int get_mouse_offset(int32_t movement, const int direction) {
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
|
output_t *active_output =
|
||||||
|
&global_state.config.output[global_state.active_output];
|
||||||
|
|
||||||
if (direction == DIRECTION_X)
|
if (direction == DIRECTION_X)
|
||||||
offset = movement * MOUSE_SPEED_FACTOR_X;
|
offset = movement * active_output->speed_x;
|
||||||
else
|
else
|
||||||
offset = movement * MOUSE_SPEED_FACTOR_Y;
|
offset = movement * active_output->speed_y;
|
||||||
|
|
||||||
/* Holding a special hotkey enables mouse to slow down as much as possible
|
/* Holding a special hotkey enables mouse to slow down as much as possible
|
||||||
when you need that extra precision */
|
when you need that extra precision */
|
||||||
if (global_state.mouse_zoom)
|
if (global_state.mouse_zoom)
|
||||||
offset = offset >> 2;
|
offset = offset >> 2;
|
||||||
|
|
||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
void keep_cursor_on_screen(int16_t* position, const int32_t* movement, const int direction) {
|
void keep_cursor_on_screen(int16_t *position, const int32_t *movement,
|
||||||
int16_t offset = get_mouse_offset(*movement, direction);
|
const int direction) {
|
||||||
|
int16_t offset = get_mouse_offset(*movement, direction);
|
||||||
|
|
||||||
/* Lowest we can go is 0 */
|
/* Lowest we can go is 0 */
|
||||||
if (*position + offset < 0)
|
if (*position + offset < 0)
|
||||||
*position = 0;
|
*position = 0;
|
||||||
|
|
||||||
/* Highest we can go is MAX_SCREEN_COORD */
|
/* Highest we can go is MAX_SCREEN_COORD */
|
||||||
else if (*position + offset > MAX_SCREEN_COORD)
|
else if (*position + offset > MAX_SCREEN_COORD)
|
||||||
*position = MAX_SCREEN_COORD;
|
*position = MAX_SCREEN_COORD;
|
||||||
|
|
||||||
/* We're still on screen, all good */
|
/* We're still on screen, all good */
|
||||||
else
|
else
|
||||||
*position += offset;
|
*position += offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If mouse needs to go locally, queue packet to mouse queue, else send them
|
||||||
void check_mouse_switch(const mouse_values_t* values, device_state_t* state) {
|
* through UART */
|
||||||
hid_abs_mouse_report_t report = {.y = 0, .x = MAX_SCREEN_COORD};
|
void send_mouse(hid_abs_mouse_report_t *report, device_state_t *state) {
|
||||||
|
if (state->active_output == BOARD_ROLE) {
|
||||||
/* No switching allowed if explicitly disabled */
|
queue_mouse_report(report, state);
|
||||||
if (state->switch_lock)
|
state->last_activity[BOARD_ROLE] = time_us_64();
|
||||||
return;
|
} else {
|
||||||
|
send_packet((uint8_t *)report, MOUSE_REPORT_MSG, MOUSE_REPORT_LENGTH);
|
||||||
/* End of screen left switches screen A->B */
|
}
|
||||||
bool jump_from_A_to_B = (state->mouse_x + values->move_x < -MOUSE_JUMP_THRESHOLD &&
|
|
||||||
state->active_output == ACTIVE_OUTPUT_A);
|
|
||||||
|
|
||||||
/* End of screen right switches screen B->A */
|
|
||||||
bool jump_from_B_to_A = (state->mouse_x + values->move_x > MAX_SCREEN_COORD + MOUSE_JUMP_THRESHOLD &&
|
|
||||||
state->active_output == ACTIVE_OUTPUT_B);
|
|
||||||
|
|
||||||
if (jump_from_A_to_B || jump_from_B_to_A) {
|
|
||||||
/* Hide mouse pointer in the upper right corner on the system we are switching FROM
|
|
||||||
If the mouse is locally attached to the current board or notify other board if not */
|
|
||||||
if (state->active_output == state->mouse_connected)
|
|
||||||
queue_mouse_report(&report, state);
|
|
||||||
else
|
|
||||||
send_packet((const uint8_t*)&report, MOUSE_REPORT_MSG, MOUSE_REPORT_LENGTH);
|
|
||||||
|
|
||||||
if (jump_from_A_to_B) {
|
|
||||||
switch_output(ACTIVE_OUTPUT_B);
|
|
||||||
state->mouse_x = MAX_SCREEN_COORD;
|
|
||||||
} else {
|
|
||||||
switch_output(ACTIVE_OUTPUT_A);
|
|
||||||
state->mouse_x = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void extract_values_report_protocol(uint8_t* report,
|
int16_t scale_y_coord(output_t *from, output_t *to, device_state_t *state) {
|
||||||
device_state_t* state,
|
int size_to = to->border.bottom - to->border.top;
|
||||||
mouse_values_t* values) {
|
int size_from = from->border.bottom - from->border.top;
|
||||||
/* If Report ID is used, the report is prefixed by the report ID so we have to move by 1 byte */
|
|
||||||
if (state->mouse_dev.uses_report_id) {
|
|
||||||
/* Move past the ID to parse the report */
|
|
||||||
report++;
|
|
||||||
}
|
|
||||||
|
|
||||||
values->move_x = get_report_value(report, &state->mouse_dev.move_x);
|
/* If sizes match, there is nothing to do */
|
||||||
values->move_y = get_report_value(report, &state->mouse_dev.move_y);
|
if (size_from == size_to)
|
||||||
values->wheel = get_report_value(report, &state->mouse_dev.wheel);
|
return state->mouse_y;
|
||||||
values->buttons = get_report_value(report, &state->mouse_dev.buttons);
|
|
||||||
|
/* Moving from bigger ==> smaller screen */
|
||||||
|
if (size_from < size_to) {
|
||||||
|
if (state->mouse_y < from->border.top)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (state->mouse_y > from->border.bottom)
|
||||||
|
return MAX_SCREEN_COORD;
|
||||||
|
|
||||||
|
/* y_b = ((y_a - top) * HEIGHT) / (bottom - top) */
|
||||||
|
return ((state->mouse_y - from->border.top) * MAX_SCREEN_COORD) /
|
||||||
|
size_from;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Moving from smaller ==> bigger screen,
|
||||||
|
y_a = top + (((bottom - top) * y_b) / HEIGHT) */
|
||||||
|
return to->border.top + ((size_to * state->mouse_y) / MAX_SCREEN_COORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
void extract_values_boot_protocol(uint8_t* report, device_state_t* state, mouse_values_t* values) {
|
void check_mouse_switch(const mouse_values_t *values, device_state_t *state) {
|
||||||
hid_mouse_report_t* mouse_report = (hid_mouse_report_t*)report;
|
hid_abs_mouse_report_t report = {.y = 0, .x = MAX_SCREEN_COORD};
|
||||||
values->move_x = mouse_report->x;
|
|
||||||
values->move_y = mouse_report->y;
|
|
||||||
values->wheel = mouse_report->wheel;
|
|
||||||
values->buttons = mouse_report->buttons;
|
|
||||||
}
|
|
||||||
|
|
||||||
void process_mouse_report(uint8_t* raw_report, int len, device_state_t* state) {
|
/* No switching allowed if explicitly disabled */
|
||||||
mouse_values_t values = {0};
|
if (state->switch_lock)
|
||||||
|
return;
|
||||||
|
|
||||||
/* Interpret values depending on the current protocol used */
|
/* End of screen left switches screen A->B */
|
||||||
if (state->mouse_dev.protocol == HID_PROTOCOL_BOOT)
|
bool jump_from_A_to_B =
|
||||||
extract_values_boot_protocol(raw_report, state, &values);
|
(state->mouse_x + values->move_x < -MOUSE_JUMP_THRESHOLD &&
|
||||||
else
|
state->active_output == ACTIVE_OUTPUT_A);
|
||||||
extract_values_report_protocol(raw_report, state, &values);
|
|
||||||
|
|
||||||
/* We need to enforce the cursor doesn't go off-screen, that would be bad. */
|
/* End of screen right switches screen B->A */
|
||||||
keep_cursor_on_screen(&state->mouse_x, &values.move_x, DIRECTION_X);
|
bool jump_from_B_to_A = (state->mouse_x + values->move_x >
|
||||||
keep_cursor_on_screen(&state->mouse_y, &values.move_y, DIRECTION_Y);
|
MAX_SCREEN_COORD + MOUSE_JUMP_THRESHOLD &&
|
||||||
|
state->active_output == ACTIVE_OUTPUT_B);
|
||||||
|
|
||||||
hid_abs_mouse_report_t abs_mouse_report = {
|
if (jump_from_A_to_B || jump_from_B_to_A) {
|
||||||
.buttons = values.buttons,
|
/* Hide mouse pointer in the upper right corner on the system we are
|
||||||
.x = state->mouse_x,
|
switching FROM If the mouse is locally attached to the current board or
|
||||||
.y = state->mouse_y,
|
notify other board if not */
|
||||||
.wheel = values.wheel,
|
send_mouse(&report, state);
|
||||||
.pan = 0
|
|
||||||
};
|
if (jump_from_A_to_B) {
|
||||||
|
switch_output(ACTIVE_OUTPUT_B);
|
||||||
|
state->mouse_x = MAX_SCREEN_COORD;
|
||||||
|
|
||||||
|
state->mouse_y = scale_y_coord(&state->config.output[ACTIVE_OUTPUT_A],
|
||||||
|
&state->config.output[ACTIVE_OUTPUT_B],
|
||||||
|
state);
|
||||||
|
|
||||||
if (state->active_output == ACTIVE_OUTPUT_A) {
|
|
||||||
send_packet((const uint8_t*)&abs_mouse_report, MOUSE_REPORT_MSG, MOUSE_REPORT_LENGTH);
|
|
||||||
} else {
|
} else {
|
||||||
queue_mouse_report(&abs_mouse_report, state);
|
switch_output(ACTIVE_OUTPUT_A);
|
||||||
state->last_activity[ACTIVE_OUTPUT_B] = time_us_64();
|
state->mouse_x = 0;
|
||||||
}
|
|
||||||
|
|
||||||
/* We use the mouse to switch outputs, the logic is in check_mouse_switch() */
|
state->mouse_y = scale_y_coord(&state->config.output[ACTIVE_OUTPUT_B],
|
||||||
check_mouse_switch(&values, state);
|
&state->config.output[ACTIVE_OUTPUT_A],
|
||||||
|
state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void extract_values_report_protocol(uint8_t *report, device_state_t *state,
|
||||||
|
mouse_values_t *values) {
|
||||||
|
/* If Report ID is used, the report is prefixed by the report ID so we have to
|
||||||
|
* move by 1 byte */
|
||||||
|
if (state->mouse_dev.uses_report_id) {
|
||||||
|
/* Move past the ID to parse the report */
|
||||||
|
report++;
|
||||||
|
}
|
||||||
|
|
||||||
|
values->move_x = get_report_value(report, &state->mouse_dev.move_x);
|
||||||
|
values->move_y = get_report_value(report, &state->mouse_dev.move_y);
|
||||||
|
values->wheel = get_report_value(report, &state->mouse_dev.wheel);
|
||||||
|
values->buttons = get_report_value(report, &state->mouse_dev.buttons);
|
||||||
|
}
|
||||||
|
|
||||||
|
void extract_values_boot_protocol(uint8_t *report, device_state_t *state,
|
||||||
|
mouse_values_t *values) {
|
||||||
|
hid_mouse_report_t *mouse_report = (hid_mouse_report_t *)report;
|
||||||
|
|
||||||
|
values->move_x = mouse_report->x;
|
||||||
|
values->move_y = mouse_report->y;
|
||||||
|
values->wheel = mouse_report->wheel;
|
||||||
|
values->buttons = mouse_report->buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
void process_mouse_report(uint8_t *raw_report, int len, device_state_t *state) {
|
||||||
|
mouse_values_t values = {0};
|
||||||
|
|
||||||
|
/* Interpret values depending on the current protocol used */
|
||||||
|
if (state->mouse_dev.protocol == HID_PROTOCOL_BOOT)
|
||||||
|
extract_values_boot_protocol(raw_report, state, &values);
|
||||||
|
else
|
||||||
|
extract_values_report_protocol(raw_report, state, &values);
|
||||||
|
|
||||||
|
/* We need to enforce the cursor doesn't go off-screen, that would be bad. */
|
||||||
|
keep_cursor_on_screen(&state->mouse_x, &values.move_x, DIRECTION_X);
|
||||||
|
keep_cursor_on_screen(&state->mouse_y, &values.move_y, DIRECTION_Y);
|
||||||
|
|
||||||
|
hid_abs_mouse_report_t abs_mouse_report = {.buttons = values.buttons,
|
||||||
|
.x = state->mouse_x,
|
||||||
|
.y = state->mouse_y,
|
||||||
|
.wheel = values.wheel,
|
||||||
|
.pan = 0};
|
||||||
|
|
||||||
|
/* Move the mouse, depending where the output is supposed to go */
|
||||||
|
send_mouse(&abs_mouse_report, state);
|
||||||
|
|
||||||
|
/* We use the mouse to switch outputs, the logic is in check_mouse_switch() */
|
||||||
|
check_mouse_switch(&values, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================================================== *
|
/* ==================================================== *
|
||||||
* Mouse Queue Section
|
* Mouse Queue Section
|
||||||
* ==================================================== */
|
* ==================================================== */
|
||||||
|
|
||||||
void process_mouse_queue_task(device_state_t* state) {
|
void process_mouse_queue_task(device_state_t *state) {
|
||||||
hid_abs_mouse_report_t report = {0};
|
hid_abs_mouse_report_t report = {0};
|
||||||
|
|
||||||
/* We need to be connected to the host to send messages */
|
/* We need to be connected to the host to send messages */
|
||||||
if (!state->tud_connected)
|
if (!state->tud_connected)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* Peek first, if there is anything there... */
|
/* Peek first, if there is anything there... */
|
||||||
if (!queue_try_peek(&state->mouse_queue, &report))
|
if (!queue_try_peek(&state->mouse_queue, &report))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* If we are suspended, let's wake the host up */
|
/* If we are suspended, let's wake the host up */
|
||||||
if(tud_suspended())
|
if (tud_suspended())
|
||||||
tud_remote_wakeup();
|
tud_remote_wakeup();
|
||||||
|
|
||||||
/* ... try sending it to the host, if it's successful */
|
/* ... try sending it to the host, if it's successful */
|
||||||
bool succeeded = tud_hid_abs_mouse_report(REPORT_ID_MOUSE, report.buttons, report.x, report.y,
|
bool succeeded =
|
||||||
report.wheel, report.pan);
|
tud_hid_abs_mouse_report(REPORT_ID_MOUSE, report.buttons, report.x,
|
||||||
|
report.y, report.wheel, report.pan);
|
||||||
|
|
||||||
/* ... then we can remove it from the queue */
|
/* ... then we can remove it from the queue */
|
||||||
if (succeeded)
|
if (succeeded)
|
||||||
queue_try_remove(&state->mouse_queue, &report);
|
queue_try_remove(&state->mouse_queue, &report);
|
||||||
}
|
}
|
||||||
|
|
||||||
void queue_mouse_report(hid_abs_mouse_report_t* report, device_state_t* state) {
|
void queue_mouse_report(hid_abs_mouse_report_t *report, device_state_t *state) {
|
||||||
/* It wouldn't be fun to queue up a bunch of messages and then dump them all on host */
|
/* It wouldn't be fun to queue up a bunch of messages and then dump them all
|
||||||
if (!state->tud_connected)
|
* on host */
|
||||||
return;
|
if (!state->tud_connected)
|
||||||
|
return;
|
||||||
|
|
||||||
queue_try_add(&state->mouse_queue, report);
|
queue_try_add(&state->mouse_queue, report);
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ void serial_init() {
|
|||||||
void pio_usb_host_config(void) {
|
void pio_usb_host_config(void) {
|
||||||
/* tuh_configure() must be called before tuh_init() */
|
/* tuh_configure() must be called before tuh_init() */
|
||||||
static pio_usb_configuration_t config = PIO_USB_DEFAULT_CONFIG;
|
static pio_usb_configuration_t config = PIO_USB_DEFAULT_CONFIG;
|
||||||
config.pin_dp = PIO_USB_DP_PIN;
|
config.pin_dp = PIO_USB_DP_PIN_DEFAULT;
|
||||||
tuh_configure(BOARD_TUH_RHPORT, TUH_CFGID_RPI_PIO_USB_CONFIGURATION, &config);
|
tuh_configure(BOARD_TUH_RHPORT, TUH_CFGID_RPI_PIO_USB_CONFIGURATION, &config);
|
||||||
|
|
||||||
/* Initialize and configure TinyUSB Host */
|
/* Initialize and configure TinyUSB Host */
|
||||||
@ -69,6 +69,9 @@ void initial_setup(void) {
|
|||||||
/* PIO USB requires a clock multiple of 12 MHz, setting to 120 MHz */
|
/* PIO USB requires a clock multiple of 12 MHz, setting to 120 MHz */
|
||||||
set_sys_clock_khz(120000, true);
|
set_sys_clock_khz(120000, true);
|
||||||
|
|
||||||
|
/* Search the persistent storage sector in flash for valid config or use defaults */
|
||||||
|
load_config();
|
||||||
|
|
||||||
/* Init and enable the on-board LED GPIO as output */
|
/* Init and enable the on-board LED GPIO as output */
|
||||||
gpio_init(GPIO_LED_PIN);
|
gpio_init(GPIO_LED_PIN);
|
||||||
gpio_set_dir(GPIO_LED_PIN, GPIO_OUT);
|
gpio_set_dir(GPIO_LED_PIN, GPIO_OUT);
|
||||||
|
16
src/uart.c
16
src/uart.c
@ -44,7 +44,6 @@ void send_value(const uint8_t value, enum packet_type_e packet_type) {
|
|||||||
* =============== Parsing Packets ================ *
|
* =============== Parsing Packets ================ *
|
||||||
* ================================================== */
|
* ================================================== */
|
||||||
|
|
||||||
|
|
||||||
void process_packet(uart_packet_t* packet, device_state_t* state) {
|
void process_packet(uart_packet_t* packet, device_state_t* state) {
|
||||||
if (!verify_checksum(packet)) {
|
if (!verify_checksum(packet)) {
|
||||||
return;
|
return;
|
||||||
@ -78,6 +77,14 @@ void process_packet(uart_packet_t* packet, device_state_t* state) {
|
|||||||
case SWITCH_LOCK_MSG:
|
case SWITCH_LOCK_MSG:
|
||||||
handle_switch_lock_msg(packet, state);
|
handle_switch_lock_msg(packet, state);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case SYNC_BORDERS_MSG:
|
||||||
|
handle_sync_borders_msg(packet, state);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FLASH_LED_MSG:
|
||||||
|
handle_flash_led_msg(packet, state);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,10 +99,11 @@ void receive_char(uart_packet_t* packet, device_state_t* state) {
|
|||||||
switch (state->receiver_state) {
|
switch (state->receiver_state) {
|
||||||
case IDLE:
|
case IDLE:
|
||||||
if (uart_is_readable(SERIAL_UART)) {
|
if (uart_is_readable(SERIAL_UART)) {
|
||||||
raw_packet[0] = raw_packet[1]; /* Remember the previous byte received */
|
raw_packet[0] = raw_packet[1]; /* Remember the previous byte received */
|
||||||
raw_packet[1] = uart_getc(SERIAL_UART); /* ... and try to match packet start */
|
raw_packet[1] = uart_getc(SERIAL_UART); /* ... and try to match packet start */
|
||||||
|
|
||||||
/* If we found 0xAA 0x55, we're in sync and can move on to read/process the packet */
|
/* If we found 0xAA 0x55, we're in sync and can move on to read/process the packet
|
||||||
|
*/
|
||||||
if (raw_packet[0] == START1 && raw_packet[1] == START2) {
|
if (raw_packet[0] == START1 && raw_packet[1] == START2) {
|
||||||
state->receiver_state = READING_PACKET;
|
state->receiver_state = READING_PACKET;
|
||||||
}
|
}
|
||||||
|
10
src/usb.c
10
src/usb.c
@ -63,7 +63,7 @@ void tud_hid_set_report_cb(uint8_t instance,
|
|||||||
/* If we are board without the keyboard hooked up directly, we need to send this information
|
/* If we are board without the keyboard hooked up directly, we need to send this information
|
||||||
to the other one since that one has the keyboard connected to it (and LEDs you can turn on :)) */
|
to the other one since that one has the keyboard connected to it (and LEDs you can turn on :)) */
|
||||||
if (global_state.keyboard_connected)
|
if (global_state.keyboard_connected)
|
||||||
update_leds(&global_state);
|
restore_leds(&global_state);
|
||||||
else
|
else
|
||||||
send_value(leds, KBD_SET_REPORT_MSG);
|
send_value(leds, KBD_SET_REPORT_MSG);
|
||||||
}
|
}
|
||||||
@ -110,7 +110,6 @@ void tuh_hid_mount_cb(uint8_t dev_addr,
|
|||||||
/* Keeping this is needed for setting leds from device set_report callback */
|
/* Keeping this is needed for setting leds from device set_report callback */
|
||||||
global_state.kbd_dev_addr = dev_addr;
|
global_state.kbd_dev_addr = dev_addr;
|
||||||
global_state.kbd_instance = instance;
|
global_state.kbd_instance = instance;
|
||||||
|
|
||||||
global_state.keyboard_connected = true;
|
global_state.keyboard_connected = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -120,12 +119,17 @@ void tuh_hid_mount_cb(uint8_t dev_addr,
|
|||||||
if (tuh_hid_get_protocol(dev_addr, instance) == HID_PROTOCOL_BOOT) {
|
if (tuh_hid_get_protocol(dev_addr, instance) == HID_PROTOCOL_BOOT) {
|
||||||
tuh_hid_set_protocol(dev_addr, instance, HID_PROTOCOL_REPORT);
|
tuh_hid_set_protocol(dev_addr, instance, HID_PROTOCOL_REPORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_report_descriptor(&global_state.mouse_dev, MAX_REPORTS, desc_report, desc_len);
|
parse_report_descriptor(&global_state.mouse_dev, MAX_REPORTS, desc_report, desc_len);
|
||||||
|
|
||||||
global_state.mouse_connected = true;
|
global_state.mouse_connected = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
/* Flash local led to indicate a device was connected */
|
||||||
|
blink_led(&global_state);
|
||||||
|
|
||||||
|
/* Also signal the other board to flash LED, to enable easy verification if serial works */
|
||||||
|
send_value(ENABLE, FLASH_LED_MSG);
|
||||||
|
|
||||||
|
|
||||||
/* Kick off the report querying */
|
/* Kick off the report querying */
|
||||||
tuh_hid_receive_report(dev_addr, instance);
|
tuh_hid_receive_report(dev_addr, instance);
|
||||||
|
@ -33,18 +33,24 @@
|
|||||||
*
|
*
|
||||||
* This affects how fast the mouse moves.
|
* This affects how fast the mouse moves.
|
||||||
*
|
*
|
||||||
* MOUSE_SPEED_FACTOR_X: [1-128], mouse moves at this speed in X direction
|
* MOUSE_SPEED_A_FACTOR_X: [1-128], mouse moves at this speed in X direction
|
||||||
* MOUSE_SPEED_FACTOR_Y: [1-128], mouse moves at this speed in Y direction
|
* MOUSE_SPEED_A_FACTOR_Y: [1-128], mouse moves at this speed in Y direction
|
||||||
*
|
*
|
||||||
* MOUSE_JUMP_THRESHOLD: [0-32768], sets the "force" you need to use to drag the
|
* MOUSE_JUMP_THRESHOLD: [0-32768], sets the "force" you need to use to drag the
|
||||||
* mouse to another screen, 0 meaning no force needed at all, and ~500 some force
|
* mouse to another screen, 0 meaning no force needed at all, and ~500 some force
|
||||||
* needed, ~1000 no accidental jumps, you need to really mean it.
|
* needed, ~1000 no accidental jumps, you need to really mean it.
|
||||||
*
|
*
|
||||||
* TODO: make this configurable per-screen.
|
* This is now configurable per-screen.
|
||||||
*
|
*
|
||||||
* */
|
* */
|
||||||
|
|
||||||
#define MOUSE_SPEED_FACTOR_X 16
|
/* Output A values */
|
||||||
#define MOUSE_SPEED_FACTOR_Y 16
|
#define MOUSE_SPEED_A_FACTOR_X 16
|
||||||
|
#define MOUSE_SPEED_A_FACTOR_Y 16
|
||||||
|
|
||||||
|
/* Output B values */
|
||||||
|
#define MOUSE_SPEED_B_FACTOR_X 16
|
||||||
|
#define MOUSE_SPEED_B_FACTOR_Y 16
|
||||||
|
|
||||||
#define MOUSE_JUMP_THRESHOLD 0
|
#define MOUSE_JUMP_THRESHOLD 0
|
||||||
|
|
||||||
|
32
src/utils.c
32
src/utils.c
@ -51,3 +51,35 @@ void kick_watchdog(void) {
|
|||||||
if (current_time - core1_last_loop_pass < CORE1_HANG_TIMEOUT_US)
|
if (current_time - core1_last_loop_pass < CORE1_HANG_TIMEOUT_US)
|
||||||
watchdog_update();
|
watchdog_update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ================================================== *
|
||||||
|
* Flash and config functions
|
||||||
|
* ================================================== */
|
||||||
|
|
||||||
|
void wipe_config(void) {
|
||||||
|
uint32_t ints = save_and_disable_interrupts();
|
||||||
|
flash_range_erase(PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE);
|
||||||
|
restore_interrupts(ints);
|
||||||
|
}
|
||||||
|
|
||||||
|
void load_config(void) {
|
||||||
|
config_t* config = ADDR_CONFIG_BASE_ADDR;
|
||||||
|
|
||||||
|
/* If no config is detected, copy default values to our struct. TODO checksum */
|
||||||
|
if (config->magic_header != 0x0B00B1E5) {
|
||||||
|
config = (config_t*)&default_config;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(&global_state.config, config, sizeof(config_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
void save_config(void) {
|
||||||
|
uint8_t buf[FLASH_PAGE_SIZE];
|
||||||
|
memcpy(buf, &global_state.config, sizeof(config_t));
|
||||||
|
|
||||||
|
wipe_config();
|
||||||
|
|
||||||
|
uint32_t ints = save_and_disable_interrupts();
|
||||||
|
flash_range_program(PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE, buf, FLASH_PAGE_SIZE);
|
||||||
|
restore_interrupts(ints);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user