⌨️Пример Раскладки Для Dactyl Manuform

Описание моей раскладки для клавиатуры Dactyl Manuform 4x5

https://t.me/vlad_utyumov

Особенности раскладки

  1. Симметричная раскладка для возможности работать одной рукой (левой если в правой мышь, или правой, если в левой телефон).

  2. Два режима работы: ЙЦУКЕН для работы преимущественно двумя руками, и РИСАТЕНО (аккордовая раскладка) для возможности набирать текст одной рукой. Пока рисатено не освоена в полной мере, этот режим я использую для тренировки, а также в ситуациях, когда надо много работать мышью и вводить мало текста.

  3. Не используются тап-холды. Почему-то для меня тап-холды являются источником дополнительных ошибок, и как следствие - дополнительный стресс и ненормативная лексика. Это совсем не то, для чего мне нужна эргономичная клавиатура.

  4. Все символы (кроме букв) находятся на одних и тех же клавишах как в русской так и в английской раскладке. Это реализовано через Альт-коды. Работает только под Windows и не требует установки какого-либо софта в ОС.

  5. Не все клавиши присутствуют на клавиатуре. Не все хоткеи можно нажать. К примеру нет таких вредных клавиш как CapsLock, Insert и даже Alt. Тем не менее есть возможность использовать комбинацию клавиш Alt+Tab. Я добавил в раскладку только те возможности, которыми сам пользуюсь. Поэтому эта раскладка не является готовым решением, ее можно использовать как источник идей и не более того.

  • Раскладка сделана на QMK, в клавиатуре используется микроконтроллер Atmega32u4

Возможные проблемы

  • Некоторые приложения могут в определенных ситуациях напрямую работать со скан-кодами и не понимать те трюки, которые тут используются. Придется искать пути обхода проблемы.

  • Существует проблема при работе с удаленным компьютером по RDP. Когда клавиатура на одно нажатие клавиши отправляет зажатие модификатора, ввод символа и отпускание модификатора, модификатор может не прийти на удаленный компьютер. В связке QMK+Atmega32u4 эта проблема возникает редко, при активной работе можно наблюдать 2-3 случая за день, а можно и совсем не столкнуться. Но в других конфигурациях этот баг может стать серьезной проблемой.

Переключение режимов

Зеленый аккорд включает ЙЦУКЕН, синий аккорд РИСАТЕНО.

http://www.keyboard-layout-editor.com/#/gists/c2a368b82d47b45e139749ba492dcef3
Аккорды выбраны таким образом, чтобы исключить случайное нажатие. Повторное нажатие аккорда ничего не делает, поэтому мы можем не выяснять, в каком режиме находится клавиатура, а просто нажать нужный аккорд, и быть уверенными, что теперь клавиатура в нужном режиме.

Примеры кода

В файле rules.mk

COMBO_ENABLE           = yes

В файле config.h

#define COMBO_TERM 120

На период обучения поставил тайминг аккордов побольше.
В файле keymap.c

enum combo_events {
	CB_QZ
	, CB_W_LPR
	, PNC_N_UND
	, PNC_A_LPR
	//
	, COMBO_LENGTH
};

uint16_t COMBO_LEN = COMBO_LENGTH; // remove the COMBO_COUNT define and use this instead!

const uint16_t PROGMEM combo_QZ[] = {KC_Q, KC_Z, COMBO_END};
const uint16_t PROGMEM combo_W_LPR[] = {KC_W, KC_GRV, COMBO_END};
const uint16_t PROGMEM pnc_N_UND[] = {PNC_N, PNC_UNDSCR, COMBO_END};
const uint16_t PROGMEM pnc_A_LPR[] = {PNC_A, PNC_LBRKTL, COMBO_END};

combo_t key_combos[] = {
	[CB_QZ] = COMBO_ACTION(combo_QZ)
	, [CB_W_LPR] = COMBO_ACTION(combo_W_LPR)
	, [PNC_N_UND] = COMBO_ACTION(pnc_N_UND)
	, [PNC_A_LPR] = COMBO_ACTION(pnc_A_LPR)
};

void process_combo_event(uint16_t combo_index, bool pressed) {
	if (pressed) {
		case CB_QZ:
			// DEF
		break;
		case CB_W_LPR:
			// PNCATEHO
			layer_clear();
			layer_on(_PNC);
		break;
		case PNC_N_UND:
			// DEF
			layer_clear();
			layer_on(_DEF);
		break;
		case PNC_A_LPR:
			// PNCATEHO
		break;
		}
	}
}	

Тамб кластер

http://www.keyboard-layout-editor.com/#/gists/b51caa94916d0108c9a1aa6b57c84e63

  • nav - слой навигации

  • shft = Shift

  • num - цифровой слой

  • spc - пробел

  • bkspc = Backspace

  • esc = Escape

  • nav+shft - стрелки с шифтом, выделение текста

  • spc+shft - Enter

  • spc+num - Ctrl

  • esc+bkspc - Ctrl+Backspace, удаление целого слова

Нижние кнопки (esc и bkspc) не очень удобно нажимать. Все зависит от размера руки. В моем случае эти клавиши пригодны для единичных нажатий, но не годятся для того чтобы их удерживать и нажимать той же рукой что-то еще.
На четырех нижних кнопках есть еще место для двух аккордов (spc+esc, num+bkspc), на которые можно повесить какие-нибудь хоткеи.

Знаки препинания

В разряд знаков препинания я постарался включить символы, которые часто используются в наборе текста, в противоположность спецсимволам. Получился такой список .,;:"'!?()<>[]{}. Символы '<>[]{} в этом списке скорее лишние, они попали сюда за компанию со скобками и кавычкой. С другой сторны не хватает дефиса, но дефис оказался среди спецсимволов в одной компании с "плюсом" и "равно".
В двуруком и одноруком режимах знаки препинания сделаны по разному.
В режиме ЙЦУКЕН знаки препинания вводятся в тамб кластере, при этом одна рука зажимает слоефикатор NUM, а другая вводит символ.

http://www.keyboard-layout-editor.com/#/gists/9007726697f131c0142263a946d7da19

  • двоеточие вводится тап-денсом: надо нажать точку два раза подряд. Если нажать точку третий раз то получится троеточие.

  • точка с запятой вводится аккордом (точка + запятая)

http://www.keyboard-layout-editor.com/#/gists/acf023ded36bcd85b32d4881da12eb66

  • Вопросительный знак вводится тап-денсом

  • Одиночная кавычка вводится аккордом

В режиме РИСАТЕНО знаки препинания вводятся на основном слое фингерами.

http://www.keyboard-layout-editor.com/#/gists/a9226bd793dbbdcd19448fba3559fe79

  • Тап-денс и аккорды работают точно так же

  • Угловые и фигурные скобки вводятся с шифтом

  • Скобки на половинках зекально не отражаются левая скобка всегда слева, правая справа

Примеры кода

Этот пример демонстрирует использование альт-кодов и переопределение поведения клавиши с шифтом:

enum custom_keycodes { // эти кейкоды расмещаем в слои лэйаут вместо стандартых
	M_UNDSCR = SAFE_RANGE // начало диапазона кастом-кодов
	, PNC_LPRNTHL // наш пример: левая скобка на левой половине слоя РИСАТЕНО
};

// для двузначных альт-кодов
void tap_alt_code2(uint16_t mods, uint8_t n1, uint8_t n2) {
	unregister_mods(mods);
	register_mods(MOD_BIT(KC_LEFT_ALT));
	if (n1 == 0) {
		tap_code(KC_KP_0);
	} else {
		tap_code(KC_KP_1 + n1 - 1);
	}
	if (n2 == 0) {
		tap_code(KC_KP_0);
	} else {
		tap_code(KC_KP_1 + n2 - 1);
	}
	unregister_mods(MOD_BIT(KC_LEFT_ALT));
	register_mods(mods);
}

// для трехзначных альт-кодов
void tap_alt_code3(uint16_t mods, uint8_t n1, uint8_t n2, uint8_t n3) {
	unregister_mods(mods);
	register_mods(MOD_BIT(KC_LEFT_ALT));
	if (n1 == 0) {
		tap_code(KC_KP_0);
	} else {
		tap_code(KC_KP_1 + n1 - 1);
	}
	if (n2 == 0) {
		tap_code(KC_KP_0);
	} else {
		tap_code(KC_KP_1 + n2 - 1);
	}
	if (n3 == 0) {
		tap_code(KC_KP_0);
	} else {
		tap_code(KC_KP_1 + n3 - 1);
	}
	unregister_mods(MOD_BIT(KC_LEFT_ALT));
	register_mods(mods);
}

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
	uint16_t mods = get_mods();
	if (record->event.pressed) {
 		switch (keycode) {
		case PNC_LPRNTHL:
		case PNC_LPRNTHR:
			if (mods & MOD_MASK_SHIFT) {
				// <
				tap_alt_code2(mods, 6, 0);
			} else {
				// (
				tap_code16(S(KC_9));
			}
		break;
	return true;
}

Тап-денс у меня реализован велосипедным методом, по причинам, которые уже не актуальны. Поэтому примеров кода нет. Возможно в будущем перепишу раскладку и выложу пример реализации тап-денса, но в документации QMK есть примеры.

При вводе запятой в двуруком режиме возникла проблема. Быстрое нажатие NUM + Space отрабатывается как аккорд, на который у меня повешен Ctrl. При том что аккорд я всегда ввожу на одной половинке, а для ввода запятой задействуются клавиши с разных половин. Для решения этой проблемы я назначил на клавиши NUM и Space кастомные кей-коды, разные для правой и левой половин. Пришлось прописывать обработку включения/выключения слоя, ввода пробела, что в итоге вылилось в довольно сложный код. Тем не менее я его выложу, чтобы продемонстрировать программное включение-выключение слоев и программное нажатие-отжатие клавиши.

// если нажаты оба NUM и один из них отпускаем, как определить, надо ли выключать слой?
// запомним какой из них слой включил
bool is_lnum = false;
bool is_rnum = false;
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
	uint16_t mods = get_mods();
	if (record->event.pressed) {
		switch (keycode) {
		case M_LNUM: // нажали левый NUM
			if (IS_LAYER_ON(_NUM)) {
				// слой уже включен правым NUM, выведем точку
				tap_alt_code2(mods, 4, 6);
			} else { // слой еще не включен, надо включить
				layer_on(_NUM);
				is_lnum = true;
			}
		break;
		case M_RNUM:
			if (IS_LAYER_ON(_NUM)) {
				// слой уже включен левым NUM, выведем кавычку
				tap_alt_code2(mods, 3, 4);
			} else { // слой еще не включен, надо включить
				layer_on(_NUM);
				is_rnum = true;
			}
		break;
		case M_LSPC: // нажали левый пробел
			if (IS_LAYER_ON(_NUM) && is_rnum) {
				// слой включен правым NUM, выведем запятую
				tap_alt_code2(mods, 4, 4);
			} else {
 				// выполним нажатие пробела без отжатия, чтобы можно было напечатать много пробелов, удерживая клавишу
				register_code(KC_SPC); 
			}	
		break;
		case M_RSPC: // нажали правый пробел
			if (IS_LAYER_ON(_NUM) && is_lnum) {
				// слой включен левым NUM, выведем восклицательный знак
				tap_code16(S(KC_1));
			} else {
				register_code(KC_SPC);
			}	
		break;
		}
	} else {
		switch (keycode) {
		case M_LNUM: // отжали левый нум
			if (IS_LAYER_ON(_NUM) && is_lnum) {
				// слой включен левым нумом, выключим слой
				layer_off(_NUM);
				is_lnum = false;
				}
			}
				
		break;
		case M_RNUM: // отжали правый нум
			if (IS_LAYER_ON(_NUM) && is_rnum) {
				// слой включен правым нумом, выключим слой
				layer_off(_NUM);
				is_rnum = false;
			}
		break;
		case M_LSPC: // отжали пробел
			if (IS_LAYER_OFF(_NUM)) {
				// слой выключен, значит это не запятая и не восклицательный знак
				// отжимаем пробел
				unregister_code(KC_SPC);
			}
		break;
		case M_RSPC:
			if (IS_LAYER_OFF(_NUM)) {
				unregister_code(KC_SPC);
			}
		break;
		}
	}
	return true;
}

Слой навигации

Слой навигации доступен для работы одной рукой.

http://www.keyboard-layout-editor.com/#/gists/79ce66b0f58dccba811fd6a52ef47102

  • Стрелка влево и стрелка вверх двигают курсор к началу текста. Поэтому стрелка вверх расположена слева, а стрелка вниз - вправо.

  • Клавиша Home двигает курсор дальше, чем стрелка. Поэтому стрелки внутри, а Home/End - снаружи.

  • Стрелки с контролом перемещают курсор по словам.

  • На синих клавишах размещены хоткеи IDE для навигации по коду: перейти к определению, вернуться назад, поиск парных скобок.

  • Альт-таб работает полностью нативно. Когда первый раз нажимается клавиша "Альт-таб", клавиатура посылает в систему зажатие альта и однократное нажатие таба. На экране появляются миниатюры приложений. При последующих нажатиях кнопки посылаются только нажатия таба - на экране происходит выбор приложения. После отпускания кнопки NAV, в систему посылается отжатие альта, выбор приложения завершается.

  • В слое есть аккорды для переключения языка и вызова контекстного меню. Стрелки отображаются с одной половинки на другую не зеркально. Чтобы аккорды нажимались на двух половинках одинаково, для них выбраны симметрично расположенные клавиши.

Примеры кода

Этот пример иллюстрирует работу Alt-Tab. Ctrl-Tab делается аналогично.

bool is_atab_active = false;
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
	uint16_t mods = get_mods();
	// alt tab cleanup
	if (is_atab_active && keycode != M_ATAB) { 
		// отжали слоефикатор или нажали другую клавишу в слое
		unregister_mods(MOD_BIT(KC_LALT)); // отжимаем альт
		is_atab_active = false;
	}
	if (record->event.pressed) {
		switch (keycode) {
		case M_ATAB:
			if (!is_atab_active) { // первое нажатие - зажимаем альт
				register_mods(MOD_BIT(KC_LALT));
				is_atab_active = true;
			}
			tap_code(KC_TAB); // нажали и отпустили таб
		break;
	}
	return true;
}

Цифровой слой

Цифровой слой доступен для работы одной рукой.

http://www.keyboard-layout-editor.com/#/gists/1d4b7a9f529fc47c7d2f6e25f81378dc

  • За основу взят цифровой слой РИСАТЕНО. Цифры старше 6 и 0 вводятся аккордами.

  • Верхняя клавиша второго ряда указа не задействована, потому что до нее сложно тянуться, если тамб удерживает NUM

  • Некоторые символы вводятся тап-денсом, некоторые аккордом

Для ввода дробных чисел и дат удобно иметь в цифровом слое точку и запятую. Для этого в слой добавлены аккорды:

http://www.keyboard-layout-editor.com/#/gists/62182e2b8c3b1b845886283e2b11fa0f

Слой Ctrl

Модификатор Ctrl зажимается аккордом в тамб-кластере. Он работает как обычный модификатор, например, влияет на поведение нажатий левой кнопки мыши. Но также меняет работу почти всех клавиш, т.е. по сути включает слой. У меня это реализовано не через слой, переопределено поведение каждой клавиши в двух слоях. Но, наверное, через слой это было бы проще сделать.
Все функции слоя Ctrl доступны для работы одной рукой.

http://www.keyboard-layout-editor.com/#/gists/17234cd347f88b73d924f6cc8424e7eb
Shft2 - это слой в слое. Продолжая удерживать тамбом аккорд, зажимаем средним пальцем Shft2, и получаем возможность выполнить еще несколько действий:

http://www.keyboard-layout-editor.com/#/gists/ecfb0b1eaf30bed1066bbd7c00792774

Ссылки

http://www.keyboard-layout-editor.com/#/gists/cd7791577b534d8fd26ed35210f549e5
http://www.keyboard-layout-editor.com/#/gists/803320870a706259504871c9ab27acdb
http://www.keyboard-layout-editor.com/#/gists/d84f5e80f60aa40f7e377dad88784ca2