Skip to content

Instantly share code, notes, and snippets.

@Onefabis
Last active May 31, 2022 15:59
Show Gist options
  • Save Onefabis/d8d27f8c02d03071f1d987c76686cbd8 to your computer and use it in GitHub Desktop.
Save Onefabis/d8d27f8c02d03071f1d987c76686cbd8 to your computer and use it in GitHub Desktop.
layout switcher for Qmk keyboard
'''
1. Open src.c in lang shift lib and add this function right next to the 'lang_activate' function
########################################
void lang_toggle(int externalLang) {
if (lang_current == lang_should_be && timer_read() - lang_timer >= 200) {
if (externalLang == 0){
layer_off(2);
} else if (externalLang == 1){
layer_on(2);
}
lang_current = externalLang;
lang_should_be = externalLang;
uint8_t response[RAW_EPSIZE];
memset(response, 0, RAW_EPSIZE);
response[0] = lang_current;
raw_hid_send(response, RAW_EPSIZE);
}
}
########################################
2. After that enable RAW_ENABLE in rules.mk in your keyboard
3. Then add this code in your keymap.c fils
########################################
#ifdef RAW_ENABLE
void raw_hid_receive(uint8_t *data, uint8_t length) {
if(data[0] == 0) {
lang_toggle(1);
} else if(data[0] == 1) {
lang_toggle(0);
};
};
#endif
########################################
4. Create the 'keyboard.txt' file near to the 'currentLayer.exe' the it should look like this
vendor_id = 0x0000
product_id = 0x0000
usage_page = 0xFF60
usage = 0x61
copy and paste vendor_id and product_id from your qmk config.h file
#####################################################################
####### IF YOU WISH TO COMPILE EXE FILS BY YOURSELF ##############
#####################################################################
1. Save the code below as 'currentLayer.py'
2. Install next libraries:
pip install hid
pip install wxPython
3. Get 'hidapi.dll' from 'hidapi-win.zip' archive here https://github.com/libusb/hidapi/releases and put it near to 'C:\Windows\System32\'
4. Install pip install git+https://github.com/pyinstaller/pyinstaller, delete the old one if already installed
5. Put the icon.gif attached in comments and place it near to the 'currentLayer.py' and don't forget to change its extension to .ico
6. In command line change the path to where in placed and run the code
pyinstaller -F --add-data "hidapi.dll;." --icon=icon.ico --onefile --windowed currentLayer.py
7. Get 'currentLayer.exe' in dist subfolder
8. Don't forget to always put 'keyboard.txt' near to the .exe with the text described above
'''
import ctypes
import hid
import time
import wx.adv
import wx
import threading
import queue
from base64 import b64decode
from zlib import decompress
from io import BytesIO
imgIconData = b'eJxjYGAEQgUFBhDJsEKAgUGMgYFBA4iBQgwODBBxEGgQYBgFo2AU4AHzXTgtFrryxtMCg8zGZ/cCN+45C915/9MSL3DjnYPT3zS2G4axhcNCV654fHq2JSv/vz3Jgyi8HagWrxuAcUGq/QcKdP9/25pCFD4IVDvc7KcqHgL2rwmTBIcjCK8Fsinlk2o/cnyDzKCUP9TsB4XfAag5a6DhSQl/NP6Hlv30jv8FblxxA1r+uHCa0Mt+XO0Q+rQ/uLG2P2AA0v4CpgVaYHcOS3x2j4JRMNLAfxzgAwMD/wMGBvYDDAzMlGCQGSCzcNkDAOK2rRo='
image_data = decompress(b64decode(imgIconData))
stream = BytesIO(bytearray(image_data))
TRAY_TOOLTIP = 'Language changer'
keyboard_file = 'keyboard.txt'
try:
input = raw_input
except NameError:
pass
def create_menu_item(menu, label, func):
item = wx.MenuItem(menu, -1, label)
menu.Bind(wx.EVT_MENU, func, id=item.GetId())
menu.Append(item)
return item
class TaskBarIcon(wx.adv.TaskBarIcon):
def __init__(self, frame):
self.frame = frame
super(TaskBarIcon, self).__init__()
self.trayIcon = wx.Image(stream, wx.BITMAP_TYPE_ANY)
self.trayImage = wx.Bitmap(self.trayIcon)
self.set_icon(self.trayImage, 0)
self.Bind(wx.adv.EVT_TASKBAR_LEFT_DOWN, self.on_left_down)
self.pauseStatus = 0
self.speed_queue = queue.Queue()
self.blink_thread = MonitorLang(2)
self.blink_thread.start()
def CreatePopupMenu(self):
self.menu = wx.Menu()
self.pauseMenuItem = create_menu_item(self.menu, 'Toggle monitoring', self.toggle)
self.menu.AppendSeparator()
create_menu_item(self.menu, 'Exit', self.on_exit)
return self.menu
def set_icon(self, path, status):
if status == 0:
icon = wx.Icon(path)
self.SetIcon(icon, TRAY_TOOLTIP + ' running')
else:
icon = wx.Icon(path)
self.SetIcon(icon, TRAY_TOOLTIP + ' paused')
def on_left_down(self, event):
pass
def toggle(self, event):
if self.pauseStatus == 1:
print('Resume')
self.set_icon(self.trayImage, 0)
self.pauseStatus = 0
self.blink_thread.set_mode(2)
else:
self.set_icon(self.trayImage, 1)
print('Paused')
self.blink_thread.set_mode(1)
self.pauseStatus = 1
def on_exit(self, event):
self.blink_thread.set_mode(0)
wx.CallAfter(self.Destroy)
self.frame.Close()
class MonitorLang (threading.Thread):
def __init__(self, mode=2):
reader = open(keyboard_file, 'r')
self.debounce_timer = 0
self.debounce_counter = 1
self.loop_run_timer = 0
self.vendor_id = 0
self.product_id = 0
self.usage_page = 0
self.usage = 0
try:
for i in reader.readlines():
if 'vendor_id' in i:
self.vendor_id = int(i.split('=')[-1], 16)
if 'product_id' in i:
self.product_id = int(i.split('=')[-1], 16)
if 'usage_page' in i:
self.usage_page = int(i.split('=')[-1], 16)
if 'usage' in i:
self.usage = int(i.split('=')[-1], 16)
finally:
reader.close()
self._speed_cache = 0
self.mode = mode
self.lock = threading.RLock()
super(MonitorLang, self).__init__()
def getCurrentSystemLayer(self):
user32 = ctypes.WinDLL('user32', use_last_error=True)
handle = user32.GetForegroundWindow()
threadid = user32.GetWindowThreadProcessId(handle, 0)
layout_id = user32.GetKeyboardLayout(threadid)
language_id = layout_id & (2 ** 16 - 1)
return language_id
def get_raw_hid_interface(self):
device_interfaces = hid.enumerate(self.vendor_id, self.product_id)
raw_hid_interfaces = [i for i in device_interfaces if i['usage_page'] == self.usage_page and i['usage'] == self.usage]
if len(raw_hid_interfaces) == 0:
return None
interface = hid.Device(path=raw_hid_interfaces[0]['path'])
return interface
def send_raw_packet(self, data):
interface = self.get_raw_hid_interface()
if interface is None:
print("No device found")
request_data = [0x00] * 33 # First byte is Report ID
request_data[1:len(data) + 1] = data
request_packet = bytes(request_data)
if interface:
try:
interface.write(request_packet)
finally:
interface.close()
def set_mode(self, mode): # you can use a proper setter if you want
with self.lock:
self.mode = mode
def run(self):
self.layerCached = -1
self.layerStored = None
while True:
with self.lock:
if self.mode == 0:
print("Mode is 0, exiting...")
break
if self.mode == 2:
activeLayers = self.getCurrentSystemLayer()
if self.layerCached != activeLayers:
interface = self.get_raw_hid_interface()
if activeLayers == 1033:
self.send_raw_packet([1])
if activeLayers == 1049:
self.send_raw_packet([0])
self.layerCached = activeLayers
try:
response_packet = interface.read(32, timeout=400)
if response_packet:
if response_packet[0] == 0:
self.send_raw_packet([1])
if response_packet[0] == 1:
self.send_raw_packet([0])
finally:
interface.close()
time.sleep(0.2)
class App(wx.App):
def OnInit(self):
frame=wx.Frame(None)
self.SetTopWindow(frame)
TaskBarIcon(frame)
return True
def main():
app = App(False)
app.MainLoop()
if __name__ == '__main__':
main()
@Onefabis
Copy link
Author

Onefabis commented Sep 16, 2021

  1. Откройте src.c в библиотеке lang shift и добавьте новую функцию сразу после 'lang_activate' функции

########################################

void lang_toggle(int externalLang) {
  if (lang_current == lang_should_be && timer_read() - lang_timer >= 200) {
      if (externalLang == 0){
          layer_off(2);
      } else if (externalLang == 1){
          layer_on(2);
      }
      lang_current = externalLang;
      lang_should_be = externalLang;
      uint8_t response[RAW_EPSIZE];
      memset(response, 0, RAW_EPSIZE);
      response[0] = lang_current;
      raw_hid_send(response, RAW_EPSIZE);
    }
}

########################################

  1. Также включите параметр RAW_ENABLE в rules.mk в вашей клавиатуре

  2. Затем добавьте следующий код в keymap.c файл

########################################

#ifdef RAW_ENABLE
void raw_hid_receive(uint8_t *data, uint8_t length) {
	if(data[0] == 0) {
		lang_toggle(1); # здесь можно переключить на русскую раскладку
	} else if(data[0] == 1) {
		lang_toggle(0); # здесь можно переключить на английскую раскладку
	};
};
#endif

########################################

  1. Создайте файл 'keyboard.txt' рядом с 'currentLayer.exe', содержимое должно быть таким:
vendor_id = 0x0000
product_id = 0x0000
usage_page = 0xFF60
usage = 0x61

значения для vendor_id и product_id из qmk config.h вашей клавиатуры

#####################################################################

######## ЕСЛИ РЕШИЛИ СКОМПИЛИРОВАТЬ САМИ EXE ##############

#####################################################################

  1. Сохраните код выше как 'currentLayer.py'
  2. Установите следующие библиотеки:
    pip install hid
    pip install wxPython
  3. Скачайте 'hidapi.dll' из 'hidapi-win.zip' архива из https://github.com/libusb/hidapi/releases и положите в папку 'C:\Windows\System32'
  4. Установите pip install git+https://github.com/pyinstaller/pyinstaller, удалите старый pyinstaller, если такой уже установлен
  5. Скачайте icon.gif прикрепленные в комментариях выше и поместите рядом с 'currentLayer.py' и не забудьте заменить расширение на .ico
  6. В командной строке замените рабочий путь (cd) на тот в котором размещается python файл и запустите следующий код:
    pyinstaller -F --add-data "hidapi.dll;." --icon=icon.ico --onefile --windowed currentLayer.py
  7. Возьмите 'currentLayer.exe' в dist подпапке
  8. Не забудьте всегда держать 'keyboard.txt' рядом с .exe с текстом, указанным в инструкции выше в 4. пункте

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment