주변기기

Sofle

작성일 수정일

그렇다. 결국에는 스플릿 키보드에 입문해버리고 말았다. 원래 생각하던 키보드는 Bastard Keyboards에서 판매하는 Charybdis였다. 그러다가 Sofle로 첫 스플릿 키보드를 시작하게 된 데에는 몇 가지 이유가 있다.

  1. 내가 스플릿 키보드를 잘 쓸 수 있을지 아닐지도 모르는 상황에서 바로 구매하기에는 가격대가 있는 편이었다. 거기다가 공식적으로는 무선을 지원하지 않아서 이를 무선으로 쓰려면 부품들 몇 가지를 따로 사서 조립해야 한다.
  2. Charybdis는 어차피 Bastard Keyboards에서 도면을 공유하고 있어서 어디서 사든 폼팩터 자체는 같을 수밖에 없다. 따라서 ZMK로 무선을 돌리는 타오바오산 키보드를 사는 것도 꽤나 괜찮은 방법인 것 같았다.
  3. 만일 스플릿 키보드를 산다면 나는 Magic Trackpad가 있으므로 두 스플릿 키보드 사이에 트랙패드를 놓고 쓸 수 있고, 그럼 Charybdis의 가장 큰 특징인 트랙볼이 사실상 있을 필요가 없어진다.

그래서 나는 우선 상대적으로 저렴한 Sofle을 산 후, Sofle과 트랙패드 조합을 우선 사용해보고 쓸만하면 Charybdis를 휴대용을 겸하여 타오바오에서 사기로 했다.

taobao

내가 산 제품은 Sofle v2 기반으로, 여기서 thumb cluster의 1.5u 키캡을 1u키캡으로 바꾸고 오른손의 EC11 인코더를 조이스틱으로 바꾼 모델이다. 개인적으로 양쪽 모두에 인코더가 있는 오리지널 Sofle은 두 인코더를 모두 사용하기가 상당히 난해하다고 생각하던 차라서 고민없이 구매했다.

taobao

가운데는 Magic Trackpad이고 장패드는 GMK Euler이다.

며칠 사용해 본 소감으로는, 우선 ZMK는 사용하기 편하다. QMK는 시작하려면 QMK MSYS를 깔거나 Homebrew에서 QMK를 깔아야 하는데 ZMK는 GitHub Action으로 모든 것이 이루어져 새로운 무언가를 설치할 필요가 없다. 물론 환경은 한 번 구축하면 되는 것이라 이것만으로 ZMK가 QMK보다 낫다고 할 수는 없겠지만.

키보드가 스플릿인 것은 보통 B키 문제가 가장 큰데, 나는 이를 위해 키보드가 오기 전부터 세벌식을 연습하고 있었다. 또 그러다보니 타자 속도가 잘 나오지 않아서 한글타자의 경우에는 문제를 느끼기 힘들었다. 오히려 문제는 영어타자에서 발생했는데, 영어타자는 스플릿 자체는 별 문제가 없었지만 ortholinear 배열이다보니 이 부분에서 오타가 많이 났다.

키의 경우는 우선 thumb cluster를 제외하고 알파열만 보면 절대적으로 부족한 것은 맞다. 왼쪽부터 차례로 배열한다면 위의 두 열에서는 두 개의 키가 빠지게 되고, 셋째 열에서는 하나의 키가 빠지게 된다. 이는 불편한 일이기는 하지만 참지 못할 정도는 아니다. 가령, 나중에 키맵에서 ZMK를 다룰 때 제대로 살펴보겠지만, 나는 숫자 키들에 auto-shift를 켠 상태로, 9 키와 0 키를 각각 빠르게 두 번 누르면 []이 나오도록 했고, 가장 오른쪽 위에는 위의 사진과 같이 -를 넣되 마찬가지로 빠르게 두 번 누르면 =이 나오도록 했다. 이 자리에 있어야 하는 백스페이스는 왼손 thumb cluster로 옮겼다. 그 외에도 여러 수정이 있고, 지금도 써 가면서 바꾸고 있지만 어쨌든 핵심은 이 정도는 그렇게까지는 불편하지 않다는 것이다.

마지막으로 조이스틱이 예상보다 쓸만하다. 조이스틱을 마우스 커서로 쓰는 레이어도 만들어두기는 했지만 트랙패드도 있고, 또 마우스를 대체하기에는 급한대로 쓸 수는 있지만 권장할 만하지는 않다. 오히려 이를 방향키로 쓰는 것이 너무 편했는데, HHKB같은 미니배열에서는 보통 Fn + ], Fn + ;, Fn + ', Fn + /을 방향키로 대체하는지라 새끼손가락의 부담이 조금 있는 반면 조이스틱은 엄지로 사용하게 되므로 그럴 일이 없었다.

이후 여러 수정을 거쳐 현재의 키맵은 다음과 같다.

#define ZMK_POINTING_DEFAULT_MOVE_VAL 1200  // 600
#define ZMK_POINTING_DEFAULT_SCRL_VAL 25   // 10

#include <input/processors.dtsi>
#include <zephyr/dt-bindings/input/input-event-codes.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/bt.h>
#include <dt-bindings/zmk/keys.h>
#include <dt-bindings/zmk/rgb.h>
#include <dt-bindings/zmk/pointing.h>
#include <dt-bindings/zmk/outputs.h>

#define AS(keycode) &as LS(keycode) keycode     // Autoshift Macro

&mmv_input_listener { input-processors = <&zip_xy_scaler 2 1>; };

&msc_input_listener { input-processors = <&zip_scroll_scaler 2 1>; };

&msc {
    acceleration-exponent = <1>;      // 0
    time-to-max-speed-ms = <100>;       // 300
    delay-ms = <0>;                   // 0
};

&mmv {
    time-to-max-speed-ms = <1000>;
    acceleration-exponent = <1>;
    trigger-period-ms = <16>;
};

/ {
    scroll_encoder: scroll_encoder {
        compatible = "zmk,behavior-sensor-rotate";
        #sensor-binding-cells = <0>;
        bindings = <&msc SCRL_DOWN>, <&msc SCRL_UP>;
        tap-ms = <100>;
    };

    rgb_encoder: rgb_encoder {
            compatible = "zmk,behavior-sensor-rotate";
            #sensor-binding-cells = <0>;
            bindings = <&rgb_ug RGB_BRI>, <&rgb_ug RGB_BRD>;
    };

    macros {
        leader: snippet_leader {
            compatible = "zmk,behavior-macro";
            #binding-cells = <0>;
            tap-ms = <1>;
            wait-ms = <1>;
            bindings = <&macro_tap &kp LC(LG(LA(SPACE))) &kp LC(LG(LS(RBKT)))>;
        };
        leader_alt: snippet_leader_alternative {
            compatible = "zmk,behavior-macro";
            #binding-cells = <0>;
            tap-ms = <1>;
            wait-ms = <1>;
            bindings = <&macro_tap &kp LC(LG(LA(SPACE))) &kp LC(LG(LS(LA(RBKT))))>;
        };
    };

    combos {
        compatible = "zmk,combos";
        combo_enter {
            timeout-ms = <200>;
            key-positions = <56 60>;
            bindings = <&kp ENTER>;
        };
        combo_leader_alternative {
            timeout-ms = <200>;
            key-positions = <57 59>;
            bindings = <&leader_alt>;
        };
        combo_vivaldi {
            timeout-ms = <150>;
            key-positions = <62 63>;
            bindings = <&kp LG(LALT)>;
        };
        combo_left_rectangle {
            timeout-ms = <150>;
            key-positions = <53 54>;
            bindings = <&kp LC(LALT)>;
        };
        combo_lbkt {
            timeout-ms = <150>;
            key-positions = <9 10>;
            bindings = <&kp LBKT>;
        };
        combo_win {
            timeout-ms = <150>;
            key-positions = <53 63>;
            bindings = <&tog_on 2>;
            layers = <0>;
        };
        combo_mac {
            timeout-ms = <150>;
            key-positions = <53 63>;
            bindings = <&tog_off 2>;
            layers = <2>;
        };
    };

    behaviors {
        as: auto_shift {
            compatible = "zmk,behavior-hold-tap";
            #binding-cells = <2>;
            tapping_term_ms = <275>;
            quick_tap_ms = <0>;
            flavor = "tap-preferred";
            bindings = <&kp>, <&kp>;
        };

        mta: mod_tap_alternative {
            compatible = "zmk,behavior-hold-tap";
            #binding-cells = <2>;
            tapping_term_ms = <250>;
            quick_tap_ms = <0>;
            flavor = "tap-preferred";
            bindings = <&kp>, <&kp>;
        };

        lta: layer_tap_alternative {
            compatible = "zmk,behavior-hold-tap";
            #binding-cells = <2>;
            flavor = "hold-preferred";
            tapping-term-ms = <200>;
            bindings = <&mo>, <&kp>;
            display-name = "Layer-Tap-Alt";
        };

        lts: layer_tap_sticky {
            compatible = "zmk,behavior-hold-tap";
            #binding-cells = <2>;
            flavor = "hold-preferred";
            tapping-term-ms = <200>;
            bindings = <&kp>, <&slq>;
            display-name = "Layer-Tap";
        };

        slq: sticky_layer_non_quick_release {
            compatible = "zmk,behavior-sticky-key";
            #binding-cells = <1>;
            bindings = <&mo>;
            release-after-ms = <1000>;
            ignore-modifiers;
          };

        gtesc: grave_tilde_escape {
            compatible = "zmk,behavior-mod-morph";
            #binding-cells = <0>;
            bindings = <&kp ESC>, <&kp GRAVE>;
            mods = <(MOD_LGUI|MOD_LALT|MOD_LSFT|MOD_RGUI|MOD_RALT|MOD_RSFT)>;
            keep-mods = <(MOD_LSFT|MOD_RSFT)>;
        };

        bspc_del: backspace_delete {
            compatible = "zmk,behavior-mod-morph";
            #binding-cells = <0>;
            bindings = <&kp BACKSPACE>, <&kp DELETE>;
            mods = <(MOD_LALT|MOD_LSFT|MOD_RALT|MOD_RSFT)>;
        };

        tog_on: toggle_layer_on_only {
            compatible = "zmk,behavior-toggle-layer";
            #binding-cells = <1>;
            display-name = "Toggle Layer On";
            toggle-mode = "on";
        };

        tog_off: toggle_layer_off_only {
            compatible = "zmk,behavior-toggle-layer";
            #binding-cells = <1>;
            display-name = "Toggle Layer Off";
            toggle-mode = "off";
        };
    };

    keymap {
        compatible = "zmk,keymap";

        MacBase {
            bindings = <
&gtesc          &kp N1     &kp N2        &kp N3        &kp N4     &kp N5     &kp UP_ARROW     &kp N6   &kp N7     &kp N8     &kp N9    &kp N0    &kp MINUS
&kp TAB         &kp Q      &kp W         &kp E         &kp R      &kp T      &kp DOWN_ARROW   &kp Y    &kp U      &kp I      &kp O     &kp P     &kp BACKSLASH
&mta CAPS RALT  &kp A      &kp S         &kp D         &kp F      &kp G      &kp LEFT_ARROW   &kp H    &kp J      &kp K      &kp L     &kp SEMI  &kp APOS
&lts LSHFT 5    &kp Z      &kp X         &kp C         &kp V      &kp B      &kp RIGHT_ARROW  &kp N    &kp M      &kp COMMA  &kp DOT   &kp FSLH  &lta 4 ENTER
&kp C_MUTE      &tog_on 4  &kp LEFT_ALT  &kp LEFT_GUI  &kp SPACE  &bspc_del  &none            &leader  &kp SPACE  &kp LCTRL  &kp RALT  &mo 1
            >;

            sensor-bindings = <&inc_dec_kp C_VOLUME_UP C_VOL_DN>;
            display-name = "MacBase";
        };

        MacFn {
            bindings = <
&kp C_BRI_DEC    &kp C_BRI_INC    &kp LC(UP)       &kp LG(SPACE)      &kp LG(LS(N4))     &kp LG(LS(N5))     &trans  &kp C_PREV         &kp C_PP  &kp C_NEXT  &kp C_MUTE  &kp C_VOL_DN          &kp C_VOL_UP
&out OUT_TOG     &bt BT_NXT       &bt BT_CLR       &trans             &kp LC(LG(LA(R)))  &trans             &trans  &trans             &trans    &trans      &trans      &kp LC(LG(LA(P)))     &trans
&rgb_ug RGB_SPI  &rgb_ug RGB_HUI  &rgb_ug RGB_SAI  &trans             &trans             &trans             &trans  &trans             &trans    &trans      &trans      &trans                &trans
&rgb_ug RGB_SPD  &rgb_ug RGB_HUD  &rgb_ug RGB_SAD  &kp LC(LG(LA(C)))  &trans             &kp LC(LG(LA(B)))  &trans  &kp LC(LG(LA(N)))  &trans    &trans      &trans      &kp LC(LG(LA(FSLH)))  &trans
&rgb_ug RGB_TOG  &rgb_ug RGB_EFF  &trans           &trans             &trans             &trans             &trans  &tog_on 2          &trans    &trans      &trans      &trans
            >;
            sensor-bindings = <&rgb_encoder>;
            display-name = "MacFn";
        };

        WinBase {
            bindings = <
&gtesc          &kp N1     &kp N2        &kp N3        &kp N4     &kp N5     &kp UP_ARROW     &kp N6   &kp N7     &kp N8     &kp N9    &kp N0    &kp MINUS
&kp TAB         &kp Q      &kp W         &kp E         &kp R      &kp T      &kp DOWN_ARROW   &kp Y    &kp U      &kp I      &kp O     &kp P     &kp BACKSLASH
&mta CAPS RALT  &kp A      &kp S         &kp D         &kp F      &kp G      &kp LEFT_ARROW   &kp H    &kp J      &kp K      &kp L     &kp SEMI  &kp APOS
&lts LSHFT 5    &kp Z      &kp X         &kp C         &kp V      &kp B      &kp RIGHT_ARROW  &kp N    &kp M      &kp COMMA  &kp DOT   &kp FSLH  &mt RSHFT ENTER
&kp C_MUTE      &tog_on 4  &kp LEFT_ALT  &kp LEFT_GUI  &kp SPACE  &bspc_del  &none            &leader  &kp SPACE  &kp LCTRL  &kp RALT  &mo 3
            >;

            sensor-bindings = <&inc_dec_kp C_VOLUME_UP C_VOL_DN>;
            display-name = "WinBase";
        };

        WinFn {
            bindings = <
&kp F1           &kp F2           &kp F3           &kp F4  &kp F5  &kp F6  &trans  &kp F7  &kp F8  &kp F9  &kp F10   &kp F11   &kp F12
&out OUT_TOG     &bt BT_NXT       &bt BT_CLR       &trans  &trans  &trans  &trans  &trans  &trans  &trans  &kp LBKT  &kp RBKT  &trans
&rgb_ug RGB_SPI  &rgb_ug RGB_HUI  &rgb_ug RGB_SAI  &trans  &trans  &trans  &trans  &trans  &trans  &trans  &trans    &trans    &trans
&rgb_ug RGB_SPD  &rgb_ug RGB_HUD  &rgb_ug RGB_SAD  &trans  &trans  &trans  &trans  &trans  &trans  &trans  &trans    &trans    &trans
&rgb_ug RGB_TOG  &rgb_ug RGB_EFF  &trans           &trans  &trans  &trans  &trans  &trans  &trans  &trans  &trans    &trans
            >;
            sensor-bindings = <&rgb_encoder>;
            display-name = "WinFn";
        };

        Navigation {
            bindings = <
&trans  &trans          &trans          &trans           &trans  &trans  &mmv MOVE_UP     &trans  &trans     &trans     &trans  &trans  &trans
&trans  &trans          &kp UP_ARROW    &trans           &trans  &trans  &mmv MOVE_DOWN   &trans  &mkp LCLK  &mkp RCLK  &trans  &trans  &trans
&trans  &kp LEFT_ARROW  &kp DOWN_ARROW  &kp RIGHT_ARROW  &trans  &trans  &mmv MOVE_LEFT   &trans  &trans     &trans     &trans  &trans  &trans
&trans  &trans          &trans          &trans           &trans  &trans  &mmv MOVE_RIGHT  &trans  &trans     &trans     &trans  &trans  &trans
&trans  &tog_off 4      &trans          &trans           &trans  &trans  &trans           &trans  &trans     &trans     &trans  &trans 
            >;

            label = "Nav";
            sensor-bindings = <&scroll_encoder>;
        };

        Symbol {
            bindings = <
&trans     &trans  &trans  &trans  &trans  &trans  &mmv MOVE_UP     &trans  &trans  &trans  &kp LBKT  &kp RBKT  &kp EQUAL
&trans     &trans  &trans  &trans  &trans  &trans  &mmv MOVE_DOWN   &trans  &trans  &trans  &trans    &trans    &trans
&trans     &trans  &trans  &trans  &trans  &trans  &mmv MOVE_LEFT   &trans  &trans  &trans  &trans    &trans    &trans
&kp LSHFT  &trans  &trans  &trans  &trans  &trans  &mmv MOVE_RIGHT  &trans  &trans  &trans  &trans    &trans    &trans
&trans     &trans  &trans  &trans  &trans  &trans  &trans           &trans  &trans  &trans  &trans    &trans 
            >;

            label = "Sym";
            sensor-bindings = <&rgb_encoder>;
        };
    };
};

각 레이어에서 가운데 있는 것들 (첫째 레이어 기준 &kp ARROW 시리즈가 있는 열)은 조이스틱이고, 가장 아래 열의 좌측 1번은 왼쪽 스플릿의 노브 클릭에 해당되는 키다. 이 노브의 다이얼은 각 레이어의 sensor-bindings로 지정해주면 되고, 대부분은 volume으로 뒀지만 예외적으로 Navigation 레이어에서는 스크롤 역할을 하게 해 보았다. 어차피 조이스틱으로 마우스 대체하는 것은 너무 힘든 일이라 이 레이어 자체를 쓸 일은 없지만, 최후의 보험 같은 것이다.

레이어는 총 여섯 개다. MacBaseWinBase가 각각 macOS와 Windows용 기본 레이어이고 그 위에 MacFnWinFn이 하나씩 붙으며, 나머지 둘은 방금 전, 마우스 역할을 하는 Navigation과, 9, 0 키에 중괄호, 대괄호를 배정해주는 Symbol 레이어다. Mac과 Windows 사이의 전환은 맨 아랫줄 왼쪽에서 두 번째 키와 오른쪽 끝 키를 함께 누르는 combo가 맡는데, MacBase에서 누르면 tog_on 2WinBase를 켜고 WinBase에서 같은 자리를 누르면 tog_off 2로 끄기 때문에 하나의 조합으로 왕복이 된다. 반면 Fn 레이어는 오른쪽 맨 끝 키에 &mo로 걸어두어 누르고 있는 동안만 올라간다. WinFn은 그냥 숫자 키를 펑션 키로 바꿔주는 것에 불과하고, MacFn은 매직키보드의 기능을 따라하도록 넣어놨다.

키가 모자란 자리는 대부분 hold-tap과 mod-morph로 메웠다. 왼쪽 Shift는 길게 누르면 Shift지만 Tap behavior는 Symbol 레이어가 1초간 sticky로 걸린다. 따라서, 가령 대괄호 [를 누르고 싶으면 이를 tap하고 9 키를 누르면 되고, 중괄호 {를 누르고 싶으면 이를 double tap하되, 나중 tap은 hold한 상태로 (첫째 tap에서 Symbol 위치로 이동하고, 둘째 tap은 Symbol 레이어에서 해당 위치가 LSHFT이므로 이를 누르고 있는 것이다) 9 키를 누르면 된다. 오른손 새끼손가락의 Enter는 길게 누르면 Navigation이 된다. Esc 자리는 mod-morph여서 그냥 누르면 Esc, 아무 modifier나 얹으면 `가 나오되 Shift는 그대로 통과해 ~가 된다. 왼손 thumb cluster로 옮긴 Backspace도 같은 방식이라 Alt나 Shift를 얹으면 Delete가 되고, 홈열 맨 앞의 Caps Lock 자리는 톡 치면 오른쪽 Option (한영키), 길게 누르면 Caps Lock이다.

Combo는 몇 개 되지 않는다. 양쪽 스페이스를 동시에 누르면 Enter가 나오는 콤보는 우하단 shift의 Tap을 엔터로 설정하면서 잘 쓰지 않는다. 세벌식에서 중요한 키인 [를 매번 Symbol 레이어에서 불러올 수는 없으니 숫자열의 89를 같이 누르면 레이어를 거치지 않고도 [가 나온다. 나머지는 macOS 쪽 앱을 겨냥한 것으로, Rectangle과 Vivaldi가 쓰는 modifier 조합을 그대로 combo에 얹어두었다. &leader 매크로는 hyper 조합 두 개를 연달아 보내 스니펫을 부르는 데 쓰고 있고, 그 옆의 두 키를 같이 누르면 변형인 &leader_alt가 나간다. 현재는 VS Code로 갈아타기는 했는데, 어쨌든 아이디어와 키 자체는 서브라임 텍스트에서 소개한 것과 동일하다.

댓글남기기