* Initial work for consolidation of board files and default ChibiOS configs.
* Migrate F401/F411 black pills for testing.
* Add early init bootloader jump flag.
* Add support for I2C in order to use i2c_scanner keymap.
* Add F401/F411 HSE bypass to get things booting.
* Exempt "hooked" ChibiOS conf files from updater script.
* Fix up ordering for bootloader_defs file check.
* Match previous $(KEYBOARD_PATHS) value for Proton-C, updated for all board configs.
Fixes the handling for the oneshot cleanup, so it only cleans up if it is active. It should not cleanup of SHO is off (eg using a normal oneshot key), nor if it's actively pressed or used.
Previous behavior BROKE swap hand key.
* I2C_TIMEOUT is not defined on arm teensy
* Work round teensy having different ChibiOS config options
* Stash OLED conf files
* update comment
* update comment
* Remove stm32 alias to allow teensy alt mode
Process mouse movement in the keymap before it is sent to the host. Example uses
include filtering noise, adding acceleration, and automatically activating a
layer. To use, define the following function in your keymap:
void ps2_mouse_moved_user(report_mouse_t *mouse_report);
With this change, when ps2_mouse is disabled, mousekeys works as usual. With
ps2_mouse enabled, mousekeys button state is shared with ps2_mouse for clicking,
dragging, and scrolling, mousekeys clicks are produced by ps2_mouse only, and
mouskeys button state is transferred to mousekeys without generating clicks to
enable mousekeys dragging.
Co-authored-by: Drashna Jaelre <drashna@live.com>
Co-authored-by: Drashna Jaelre <drashna@live.com>
* init
* add RETRO_TAP; tap anyway after TAP_TERM, if no interruption
* RETRO_TAP works for other types of taps
* revert to upstream/master
* explain this fork in readme
* use one readme.md file instaed
* fix the error if NO_ACTION_ONESHOT is defined
* restore readme.md to upstream master
Co-authored-by: Tsan-Kuang Lee <tsan.kuang.lee@gmail.com>
* Branch point for 2020 May 30 Breaking Change
* Migrate `ACTION_LAYER_TOGGLE` to `TG()` (#8954)
* Migrate `ACTION_MODS_ONESHOT` to `OSM()` (#8957)
* Migrate `ACTION_DEFAULT_LAYER_SET` to `DF()` (#8958)
* Migrate `ACTION_LAYER_MODS` to `LM()` (#8959)
* Migrate `ACTION_MODS_TAP_KEY` to `MT()` (#8968)
* Convert V-USB usbdrv to a submodule (#8321)
* Unify Tap Hold functions and documentation (#8348)
* Changing board names to prevent confusion (#8412)
* Move the Keyboardio Model01 to a keyboardio/ subdir (#8499)
* Move spaceman keyboards (#8830)
* Migrate miscellaneous `fn_actions` entries (#8977)
* Migrate `ACTION_MODS_KEY` to chained mod keycodes (#8979)
* Organizing my keyboards (plaid, tartan, ergoinu) (#8537)
* Refactor Lily58 to use split_common (#6260)
* Refactor zinc to use split_common (#7114)
* Add a message if bin/qmk doesn't work (#9000)
* Fix conflicting types for 'tfp_printf' (#8269)
* Fixed RGB_DISABLE_AFTER_TIMEOUT to be seconds based & small internals cleanup (#6480)
* Refactor and updates to TKC1800 code (#8472)
* Switch to qmk forks for everything (#9019)
* audio refactor: replace deprecated PLAY_NOTE_ARRAY (#8484)
* Audio enable corrections (2/3) (#8903)
* Split HHKB to ANSI and JP layouts and Add VIA support for each (#8582)
* Audio enable corrections (Part 4) (#8974)
* Fix typo from PR7114 (#9171)
* Augment future branch Changelogs (#8978)
* Revert "Branch point for 2020 May 30 Breaking Change"
This commits add the SH_OS keycode, which works similarly to one shot
layers:
* while pressed, the keyboard is swapped
* if no keys were pressed while it was pressed, the next key press is
swapped
SH_OS also supports chaining with one shot layers:
OSL(x) + SH_OS + key interprets the key press on the oneshot layer.
The ONESHOT_TIMEOUT setting used by one shot keys and layers is also
used by oneshot swap hands. In the above chaining scenario the timeout
of the oneshot layer is reset when swap hands is activated.
Resolves#2682
* Improve security by eliminating the use of well-known names.
* Add an additional $ so the shell expands $TMP1 and $TMP2
Co-authored-by: Joel Challis <git@zvecr.com>
Co-authored-by: Joel Challis <git@zvecr.com>
* Refactor to use mpaland/printf
* trim firmware size
* remove keymap changes
* run clang format
* Fixup after rebase
* fix up git-submodule command for printf
* Add support for hardware and board initialisation overrides.
* qmk cformat.
* Add some documentation.
* Docs clarity.
* Make early_hardware_init_pre a no-op for now, until migrations occur.
* Doco update
* Make distinction between keyboard and ChibiOS board in docs
* Doc anchors.
* Update tmk_core/protocol/chibios/main.c
Co-Authored-By: Joel Challis <git@zvecr.com>
* Rework bootloader entry to be off by default, allow opting-in.
Co-authored-by: Joel Challis <git@zvecr.com>
* tmk_core/common: Fixing TIMER_DIFF macro to calculate difference correctly after the timer wraps.
Let's go through an example, using the following macro:
If the first timer read is 0xe4 and the second one is 0x32, the timer wrapped.
If the timer would have had more bits, it's new value would have been 0x132,
and the correct difference in time is 0x132 - 0xe4 = 0x4e
old code TIMER_DIFF_8(0x32, 0xe4) = 0xff - 0xe4 + 0x32 = 0x4d, which is wrong.
new code TIMER_DIFF_8(0x32, 0xe4) = 0xff + 1 - 0xe4 + 0x32 = 0x4e, which is correct.
This also gives a chance for a smart compiler to optimize the code using normal
integer overflow.
For example on AVR, the following C code:
uint8_t __attribute__ ((noinline)) test(uint8_t current_timer, uint8_t start_timer)
{
return TIMER_DIFF_8(current_timer, start_timer);
}
With the original code, it gets translated to the following list of instructions:
00004c6e <test>:
4c6e: 98 2f mov r25, r24
4c70: 86 1b sub r24, r22
4c72: 96 17 cp r25, r22
4c74: 08 f4 brcc .+2 ; 0x4c78 <test+0xa>
4c76: 81 50 subi r24, 0x01 ; 1
4c78: 08 95 ret
But with this commit, it gets translated to a single instruction:
00004c40 <test>:
4c40: 86 1b sub r24, r22
4c42: 08 95 ret
This unfortunately doesn't always work so nicely, for example the following C code:
int __attribute__ ((noinline)) test(uint8_t current_timer, uint8_t start_timer)
{
return TIMER_DIFF_8(current_timer, start_timer);
}
(Note: return type changed to int)
With the original code it gets translated to:
00004c6e <test>:
4c6e: 28 2f mov r18, r24
4c70: 30 e0 ldi r19, 0x00 ; 0
4c72: 46 2f mov r20, r22
4c74: 50 e0 ldi r21, 0x00 ; 0
4c76: 86 17 cp r24, r22
4c78: 20 f0 brcs .+8 ; 0x4c82 <test+0x14>
4c7a: c9 01 movw r24, r18
4c7c: 84 1b sub r24, r20
4c7e: 95 0b sbc r25, r21
4c80: 08 95 ret
4c82: c9 01 movw r24, r18
4c84: 84 1b sub r24, r20
4c86: 95 0b sbc r25, r21
4c88: 81 50 subi r24, 0x01 ; 1
4c8a: 9f 4f sbci r25, 0xFF ; 255
4c8c: 08 95 ret
Wth this commit it gets translated to:
00004c40 <test>:
4c40: 28 2f mov r18, r24
4c42: 30 e0 ldi r19, 0x00 ; 0
4c44: 46 2f mov r20, r22
4c46: 50 e0 ldi r21, 0x00 ; 0
4c48: 86 17 cp r24, r22
4c4a: 20 f0 brcs .+8 ; 0x4c54 <test+0x14>
4c4c: c9 01 movw r24, r18
4c4e: 84 1b sub r24, r20
4c50: 95 0b sbc r25, r21
4c52: 08 95 ret
4c54: c9 01 movw r24, r18
4c56: 84 1b sub r24, r20
4c58: 95 0b sbc r25, r21
4c5a: 93 95 inc r25
4c5c: 08 95 ret
There is not much performance improvement in this case, however at least with this
commit it functions correctly.
Note: The following commit will improve compiler output for the latter example.
* tmk_core/common: Improve code generation for TIMER_DIFF* macros
Because of integer promotion the compiler is having a hard time generating
efficient code to calculate TIMER_DIFF* macros in some situations.
In the below example, the return value is "int", and this is causing the
trouble.
Example C code:
int __attribute__ ((noinline)) test(uint8_t current_timer, uint8_t start_timer)
{
return TIMER_DIFF_8(current_timer, start_timer);
}
BEFORE: (with -Os)
00004c40 <test>:
4c40: 28 2f mov r18, r24
4c42: 30 e0 ldi r19, 0x00 ; 0
4c44: 46 2f mov r20, r22
4c46: 50 e0 ldi r21, 0x00 ; 0
4c48: 86 17 cp r24, r22
4c4a: 20 f0 brcs .+8 ; 0x4c54 <test+0x14>
4c4c: c9 01 movw r24, r18
4c4e: 84 1b sub r24, r20
4c50: 95 0b sbc r25, r21
4c52: 08 95 ret
4c54: c9 01 movw r24, r18
4c56: 84 1b sub r24, r20
4c58: 95 0b sbc r25, r21
4c5a: 93 95 inc r25
4c5c: 08 95 ret
AFTER: (with -Os)
00004c40 <test>:
4c40: 86 1b sub r24, r22
4c42: 90 e0 ldi r25, 0x00 ; 0
4c44: 08 95 ret
Note: the example is showing -Os but improvements can be seen at all optimization levels,
including -O0. We never use -O0, but I tested it to make sure that no extra code is
generated in that case.OA
* quantum/debounce: Fix custom wrapping timers in eager_pr and eager_pk debounce algorithms
Please see the below simulated sequence of events:
Column A is the 16-bit value returned by read_timer();
Column B is the value returned by custom_wrap_timer_read();
Column C is the original code: (timer_read() % MAX_DEBOUNCE)
A, B, C
65530, 19, 30
65531, 20, 31
65532, 21, 32
65533, 22, 33
65534, 23, 34
65535, 24, 35
0 25, 0
1, 26, 1
2, 27, 2
3, 28, 3
4, 29, 4
5, 30, 5
read_timer() wraps about every 1.09 seconds, and so debouncing might
fail at these times without this commit.
* quantum/debounce/eager_pr and eager_pk: modifications for code readability according to code review.
* quantum/debounce/eager_pr and eager_pk: modifications for code readability according to code review. (2)
* Add PS2_MOUSE_ROTATE to compensate for device orientation
* fixup! Add PS2_MOUSE_ROTATE to compensate for device orientation
* Reformat with IndentPPDirectives: AfterHash as per #6316
* Define NO_ACTION_MACRO/FUNCTION in header instead of makefile when LTO is enabled
Currently, boards and keymaps that define NO_ACTION_MACRO/FUNCTION unconditionally
will not compile with LTO_ENABLE (#8604). This fixes the issue by moving the
definitions from common.mk to action.h, which enables us to check for previous
definitions of those macros (this cannot be done in a makefile).
* Remove LTO checks in templates
Since now NO_ACTION_MACRO/FUNCTION are defined as needed in action.h (which is
included by quantum.h), checking for LTO in keyboard and user code is no
longer required.
* Update LTO_ENABLE docs
* Change _delay_ms/us() to wait_ms/us()
* Switch to platform-agnostic GPIO macros
* Add AVR spi_master and migrate Adafruit BLE code
* Set verbose back to false
* Add clock divisor, bit order and SPI mode configuration for init
* Add start and stop functions
* Move configuration of mode, endianness and speed to `spi_start()`
* Some breaks here would be good
* Default Adafruit BLE clock divisor to 4 (2MHz on the Feather 32U4)
* Remove mode and divisor enums
* Add some docs
* No hr at EOF
* Add links in sidebar
* rewrite usbhid feature on vusb
* Apply suggestions from code review
Co-Authored-By: Ryan <fauxpark@gmail.com>
* fix typo
* fix typo again
* Update tmk_core/protocol/vusb/vusb.c
Co-Authored-By: Ryan <fauxpark@gmail.com>
* clean up defines
Co-authored-by: Ryan <fauxpark@gmail.com>
* Update tmk_core/common/progmem.h
Co-Authored-By: Ryan <fauxpark@gmail.com>
* Update quantum/rgblight.c
Co-Authored-By: Ryan <fauxpark@gmail.com>
* fixed problem with implicit declaration in quantum/rgblight.c (#8381)
Co-authored-by: Ryan <fauxpark@gmail.com>
* Port over some AVR backlight logic to SLEEP_LED
* Port over some AVR backlight logic to SLEEP_LED - add timer 3
* Port over some AVR backlight logic to SLEEP_LED - clang format
* Enable SLEEP_LED within vusb protocol
* Add support for RAW endpoint for arm_atsam
This the excellent work from helluvamatt/qmk_firmware in bb6eeb93b.
* Reformat arm_atsam RAW endpoint code
Co-authored-by: Matt Schneeberger <helluvamatt@gmail.com>
* Improve process_record system
Code based on @colinta's
* Rename and better handle functions
* Fix incorrect function call to process_record_user
* Add documentation for post_process_record
* Add both get_event_keycode and get_record_keycode functions
And add some comments about these functions
* Update code format
* Cleanup merge artifacts
* Add support for Bootmagic lite when using SPLIT_HAND_PIN
* Deduplicate bootmagic_lite logic from within via
* Revert location of defaults so that user overrides still work for now
* Tidy up code slightly
* Remove unnecessary import of rgblight.h in tmk_core/protocol/*/*.c
* tmk_core/protocol/chibios/main.c
* tmk_core/protocol/lufa/lufa.c
see #8380 for tmk_core/protocol/vusb/main.c.
* Remove '#include "rgblight.h"' from tmk_core/protocol/vusb/main.c.
* Fix extra keyboard report during test_fixture teardown
* Add tests for pressing two keys with only different modifers
* Fix#1708
When two keys that use the same keycode, but different modifiers were
pressed at the same time, the second keypress wasn't registered. This is
fixed by forcing a key release when we detect a new press for the same
keycode.
* Fix the NKRO version of is_key_pressed
* Fix uninitalized loop variable
Co-authored-by: Jack Humbert <jack.humb@gmail.com>
* Moved contents of rgblight_reconfig.h to rgblight_post_config.h.
In #3582, rgblight_reconfig.h had to be newly created. Now, the build system of qmk_firmware has a post_cofig feature, so that what was done in rgblight_reconfig.h can now be realized in rgblight_post_config.h.
**This commit does not change the build result.**
Testing script
```shell
# build on master
git checkout master
echo master > /tmp/master_md5.txt
# RGBLIGHT_ENABLE = no
make HELIX=verbose helix/rev2:default:clean
make HELIX=verbose helix/rev2:default
md5 helix_rev2_default.hex >> /tmp/master_md5.txt
# RGBLIGHT_ENABLE = yes, with animations
make HELIX=verbose helix/rev2/back:default:clean
make HELIX=verbose helix/rev2/back:default
md5 helix_rev2_back_default.hex >> /tmp/master_md5.txt
# RGBLIGHT_ENABLE = yes, without animations
make HELIX=verbose,no_ani helix/rev2/back:default:clean
make HELIX=verbose,no_ani helix/rev2/back:default
md5 helix_rev2_back_default.hex >> /tmp/master_md5.txt
# build on refactor_rgblight_reconfig.h
git checkout refactor_rgblight_reconfig.h
echo refactor_rgblight_reconfig.h > /tmp/branch_md5.txt
# RGBLIGHT_ENABLE = no
make HELIX=verbose helix/rev2:default:clean
make HELIX=verbose helix/rev2:default
md5 helix_rev2_default.hex >> /tmp/branch_md5.txt
# RGBLIGHT_ENABLE = yes, with animations
make HELIX=verbose helix/rev2/back:default:clean
make HELIX=verbose helix/rev2/back:default
md5 helix_rev2_back_default.hex >> /tmp/branch_md5.txt
# RGBLIGHT_ENABLE = yes, without animations
make HELIX=verbose,no_ani helix/rev2/back:default:clean
make HELIX=verbose,no_ani helix/rev2/back:default
md5 helix_rev2_back_default.hex >> /tmp/branch_md5.txt
diff -u /tmp/master_md5.txt /tmp/branch_md5.txt
```
Test result:
```
--- /tmp/master_md5.txt 2020-01-03 15:42:22.000000000 +0900
+++ /tmp/branch_md5.txt 2020-01-03 15:42:42.000000000 +0900
@@ -1,4 +1,4 @@
-master
+refactor_rgblight_reconfig.h
MD5 (helix_rev2_default.hex) = f360032edd522448366d471d8f4f8181
MD5 (helix_rev2_back_default.hex) = 0c663acc6cccc44476b3b969ad22a48f
MD5 (helix_rev2_back_default.hex) = e66b1195ff6d38e6e22c975b8ae42fd3
```
* Expressions that are too long are difficult to read, so wrap them.
* Edit the expression again
* remove `defined(RGBLIGHT_ANIMATIONS)` in `tmk_core/common/*/suspend.c`, `tmk_core/protocol/*/main.c`
move contents of rgblight_reconfig.h to rgblight.h.
The following changes were made to rgblight.h.
```diff
+#ifdef RGBLIGHT_USE_TIMER
void rgblight_task(void);
void rgblight_timer_init(void);
void rgblight_timer_enable(void);
void rgblight_timer_disable(void);
void rgblight_timer_toggle(void);
+#else
+#define rgblight_task()
+#define rgblight_timer_init()
+#define rgblight_timer_enable()
+#define rgblight_timer_disable()
+#define rgblight_timer_toggle()
+#endif
```
The following changes were made to tmk_core/common/avr/suspend.c, tmk_core/common/chibios/suspend.c, tmk_core/protocol/chibios/main.c, tmk_core/protocol/lufa/lufa.c, tmk_core/protocol/vusb/main.c.
```diff
-# ifdef RGBLIGHT_ANIMATIONS
rgblight_timer_enable();
-# endif
```
```diff
-#if defined(RGBLIGHT_ANIMATIONS) && defined(RGBLIGHT_ENABLE)
+#if defined(RGBLIGHT_ENABLE)
rgblight_task();
#endif
```
* remove 'defined(RGBLIGHT_ANIMATIONS)' in tmk_core/common/keyboard.c
Co-authored-by: Joel Challis <git@zvecr.com>
* Compile error if ONESHOT_TIMEOUT defined but oneshot disabled
When ONESHOT_TIMEOUT and NO_ACTION_ONESHOT are both defined, this code
fails to compile.
Wrap the one usage of ONESHOT_TIMEOUT that is inconsistent with the
rest so all usages are properly wrapped by a check of NO_ACTION_ONESHOT.
* Run file through clang-format
Co-authored-by: Ted M Lin <tedmlin@gmail.com>
* Option to use function for KEYCODE2 routines.
Convert the KEYCODE2SYSTEM and KEYCODE2CONSUMER macros to functions,
defaulting to using the macros. The function form allows the compiler
to optimize the switch statement itself, over the macro nested
ternaries.
To enable this feature, #define USE_KEYCODE2_FUNCTION.
Testing against a random selection of avr-based keyboards, this
increased available flash by ~500 bytes. For arm-based keyboards,
the available flash increased by ~400 bytes.
* Replace macro with function entirely.
As zvecr states, go bold and just commit to using the function instead
of the macro.
* Reformat whitespace now that functional review is done
Verified against clang-format output.
* Dedupe extrakey report struct, and send functions in V-USB & LUFA
* Doc comment for consistency
* Wrap it in ifdef to prevent unused function error
* Do the same for ATSAM
- uprintf -> dprintf
- Fix atsam "vendor" eeprom.
- Bump Kinetis K20x to 64 bytes, too.
- Rollback Kinetis to 32 bytes as partitioning can only be done once. Add warning about changing the value.
- Change RAM-backed "fake" EEPROM implementations to match eeconfig's current usage.
- Add 24LC128 by request.
* Run clang-format manually to fix recently changed files
* Run clang-format manually to fix recently changed files - revert template files
* Run clang-format manually to fix recently changed files - format off for ascii_to_keycode_lut
* Implement IGNORE_MOD_TAP_INTERRUPT_PER_KEY
- Add configurable option IGNORE_MOD_TAP_INTERRUPT_PER_KEY
- Add function get_ignore_mod_tap_interrupt iff the option is enabled
Unless IGNORE_MOD_TAP_INTERRUPT_PER_KEY is defined, this patch does not affect the resulting binary.
* Add documentation for IGNORE_MOD_TAP_INTERRUPT_PER_KEY
* Move some common matrix code to a common location
* Refactor some 'custom_matrix_helper' logic to use custom matrix lite
* Fix build for kinesis/stapelberg - abuse of vpath was picking up matrix.c from core when custom matrix was enabled
* Add validation for CUSTOM_MATRIX
* VIA Refactor
* Remove old code
* review changes
* review changes
* Fix cannonkeys/satisfaction75/prototype:via build
* Add via.h to quantum.h
* Move backlight init to after backlight config load
* Merge branch 'master' into via_refactor_pr
* Update user's rules.mk to new way of enabling VIA
* Added id_switch_matrix_state
* Review changes
* Add central location for chibios defines
* Add central location for chibios defines - actually add files this time....
* Add Copyright header
* Update include order to resolve i2cv1 build errors
* Align endpoint config as per rest of file (fixes#4783)
* Add comments about explicit order use
* Update tmk_core/protocol/chibios/usb_main.c
Co-Authored-By: fauxpark <fauxpark@gmail.com>
* Set proper AVR part for USBasp avrdude flashing
* Remove `PROGRAM_CMD` stuff from rules.mk as they should not be needed anymore
* Missed the Plaid readme
* Remove PROGRAM_CMD from Mysterium
* Initial ARM bitbang ws2812 driver
* Unify chibios platform to run rgblight_task
* Remove 'avr only' comments from ws2812 docs
* Remove 'avr only' comments from ws2812 docs
* Unify chibios platform to run rgblight_task - review comments
* Remove debug flags from keymap
* Add comments from review
* Add defines for STM32L0XX
* Attempt to get arm ws2812 working on multiple gcc versions
* Move tmk_core/common/backlight to quantum/backlight
* Add guards to backlight inclusion
* Add guards to backlight inclusion
* Update backlight guards on clueboard/60
* Use full paths to avoid vpath issues
* Initial stab at some fake dfu-util-split-left behaviour
* Apply suggestions from code review
Co-Authored-By: fauxpark <fauxpark@gmail.com>
* Clang format fixes
* Fake eeprom init for both left and right hand
* Fix battery level code in adafruit_ble.cpp
The code in tsk_core/protocol/lufa/adafluit_ble.cpp that polls the
battery level for the Adafruit feather BLE controller reads the
regulated voltage, not the raw voltage coming from the battery. To do
that, the Adafruit Feather docs say you should read from pin A9:
https://learn.adafruit.com/adafruit-feather-32u4-basic-proto/power-management#measuring-battery-4-9.
(See also
https://learn.adafruit.com/adafruit-feather-32u4-bluefruit-le/pinouts#logic-pins-2-9.)
I'm not sure why, but analogRead(9); doesn't read the correct pin.
Checking all available analog pins experimentally, it turns out that
analogRead(7); returns the correct value. So the code above should read:
state.vbat = analogRead(7);
* Update tmk_core/protocol/lufa/adafruit_ble.cpp
Co-Authored-By: Drashna Jaelre <drashna@live.com>
* Remove old comment
* Fix linking error
* Remove `#ifdef` around `#include analog.h`.
* Really fix linking error
* Fix indentation
* Fix braces
* Expand descriptor headers
* Align descriptor elements
* Nicer formatting
* Tidy up preprocessor statements
* Remove VERSION_BCD redefine - LUFA_VERSION_INTEGER is currently 0x170418
* Tidy up comments
* Tweak ordering of HID report elements (no functional changes)
* We don't need all of these newlines
* Move default USB_MAX_POWER_CONSUMPTION closer to where it makes sense
* Ask nicely
* Add some more comments
* Change indentation back to 4 spaces
* Add changelog entry
This will manually wipe the EEPROM. This is a hacky solution for when new ranges are added or moved around.
A better (more complicated) solution would be to zero out everything, not just known ranges. But for now, this is a hacky solution that will work.
* Additional changes for Layer State typedef compatibility
* Replace biton32 with get_highest_layer in docs
* Change additional layer structure code
* Fix uGFX reference issue
* Remove dynamic_keymap check
* Where did all these extra spaces come from
Co-Authored-By: fauxpark <fauxpark@gmail.com>