From f15b59b9319d4e5a43a3e3515cb0f0449c7224c7 Mon Sep 17 00:00:00 2001 From: Lars-Dominik Braun Date: Sun, 19 Jan 2020 10:11:37 +0100 Subject: Add Windows driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generate C header file based on layout description and create a source bundle that must be compiled on a Windows system and then moved back to the source tree. This sucks, but cross-compiling on Linux is a pain, since Windows’ development headers assume a case-insensitive filesystem. Also I’m using MSKLC because the latest driver development kit cannot compile these drivers correctly. Dear @microsoft, please fix your shit: https://github.com/microsoft/Windows-driver-samples/issues/433 A remaining concern right now is licensing. keyboard.{c,h,def,rc} have been copied from a project generated by MSKLC and are probably non-free, although pretty much identical files like https://github.com/microsoft/Windows-driver-samples/blob/master/input/layout/kbdus/kbdus.c are covered by MS-PL. Also binds backspace key to \b and adjusts xmodmap/svg rendering accordingly. See #7. --- README.rst | 22 +++ doc/index.html | 11 +- gen.sh | 25 +++ lulua/data/layouts/ar-lulua.yaml | 11 ++ lulua/data/winkbd/README.txt | 11 ++ lulua/data/winkbd/install.bat | 5 + lulua/data/winkbd/keyboard.c | 350 +++++++++++++++++++++++++++++++++++++++ lulua/data/winkbd/keyboard.def | 4 + lulua/data/winkbd/keyboard.h | 36 ++++ lulua/data/winkbd/keyboard.rc | 29 ++++ lulua/data/winkbd/lulua.reg | Bin 0 -> 916 bytes lulua/data/winkbd/make.bat | 22 +++ lulua/keyboard.py | 72 ++++++++ lulua/render.py | 257 +++++++++++++++++++++++++++- lulua/util.py | 1 + makezip.sh | 7 + 16 files changed, 857 insertions(+), 6 deletions(-) create mode 100644 lulua/data/winkbd/README.txt create mode 100755 lulua/data/winkbd/install.bat create mode 100644 lulua/data/winkbd/keyboard.c create mode 100644 lulua/data/winkbd/keyboard.def create mode 100644 lulua/data/winkbd/keyboard.h create mode 100644 lulua/data/winkbd/keyboard.rc create mode 100644 lulua/data/winkbd/lulua.reg create mode 100755 lulua/data/winkbd/make.bat create mode 100755 makezip.sh diff --git a/README.rst b/README.rst index 123246a..25937c2 100644 --- a/README.rst +++ b/README.rst @@ -51,6 +51,28 @@ to analyze them and create pretty pictures as well as statistics in ``doc/``. .. _me: lars+lulua@6xq.net +Building Windows drivers +------------------------ + +There is no easy way to build Windows keyboard drivers, but the following +instructions have worked in the past: + +.. code:: bash + + ninja doc/_temp/winkbd/customization.h + +Share the folder `doc/_temp/winkbd` with a Windows system, install `Microsoft +Keyboard Layout Creator 1.4`_ (MSKLC; the Windows Driver Kit (WDK) would work +too, but is much larger), adjust ``make.bat`` pointing to your MSKLC +installation and run it. This should generate two directories, ``System32`` and +``SysWOW64``, which must be copied back to `doc/_temp/winkbd`. Then run: + +.. code:: bash + + ninja doc/_build/ar-lulua-w64.zip + +.. _Microsoft Keyboard Layout Creator 1.4: https://www.microsoft.com/en-us/download/details.aspx?id=22339 + Acknowledgements ---------------- diff --git a/doc/index.html b/doc/index.html index 22f72d7..7e70ce5 100644 --- a/doc/index.html +++ b/doc/index.html @@ -77,13 +77,15 @@

Usage

-
Linux
-
Run: xmodmap ar-lulua.xmodmap
+
Windows
+
Download driver and follow instructions in INSTALL.txt
Android
Install AnySoftKeyboard and Arabic for AnySoftKeyboard +
Linux
+
Run: xmodmap ar-lulua.xmodmap
@@ -180,6 +182,11 @@

Related work

+

This non-comprehensive section… Unicode’s + CLDR + lists + various + Arabic keyboard layouts too.

diff --git a/gen.sh b/gen.sh index 17d4214..3d83839 100755 --- a/gen.sh +++ b/gen.sh @@ -101,12 +101,21 @@ rule html rule cp command = cp \$in \$out +rule cpR + command = cp -R \$in \$out + rule gz command = gzip -c \$in > \$out rule configure-make command = cd \$in && autoreconf --install && ./configure && make +rule zipR + command = ./makezip.sh \$in \$out + +rule render-winkbd + command = lulua-render -l ar-lulua winkbd \$out + ### build targets ### build \$docdir/_build: mkdir build \$docdir/_build/fonts: mkdir @@ -124,6 +133,22 @@ build \$docdir/_build/fonts/IBMPlexArabic-Thin.woff2: cp \$fontdir/IBMPlexArabic # build osmconvert build \$osmconvert: configure-make 3rdparty/osmctools + +# windows drivers +build \$docdir/_temp/winkbd: cpR lulua/data/winkbd +build \$docdir/_temp/winkbd/customization.h: render-winkbd || \$docdir/_temp/winkbd +build \$docdir/_temp/ar-lulua-w64: mkdir +EOF + +w64zipfile="System32/kbdarlulua.dll SysWOW64/kbdarlulua.dll README.txt lulua.reg install.bat" +deps="" +for f in $w64zipfile; do + echo "build \$docdir/_temp/ar-lulua-w64/$f: cp \$docdir/_temp/winkbd/$f || \$docdir/_temp/ar-lulua-w64" + deps+=" \$docdir/_temp/ar-lulua-w64/$f" +done +cat < +#include "kbd.h" +#include "keyboard.h" + +#if defined(_M_IA64) +#pragma section(".data") +#define ALLOC_SECTION_LDATA __declspec(allocate(".data")) +#else +#pragma data_seg(".data") +#define ALLOC_SECTION_LDATA +#endif + +/***************************************************************************\ +* ausVK[] - Virtual Scan Code to Virtual Key conversion table +\***************************************************************************/ + +static ALLOC_SECTION_LDATA USHORT ausVK[] = { + T00, T01, T02, T03, T04, T05, T06, T07, + T08, T09, T0A, T0B, T0C, T0D, T0E, T0F, + T10, T11, T12, T13, T14, T15, T16, T17, + T18, T19, T1A, T1B, T1C, T1D, T1E, T1F, + T20, T21, T22, T23, T24, T25, T26, T27, + T28, T29, T2A, T2B, T2C, T2D, T2E, T2F, + T30, T31, T32, T33, T34, T35, + + /* + * Right-hand Shift key must have KBDEXT bit set. + */ + T36 | KBDEXT, + + T37 | KBDMULTIVK, // numpad_* + Shift/Alt -> SnapShot + + T38, T39, T3A, T3B, T3C, T3D, T3E, + T3F, T40, T41, T42, T43, T44, + + /* + * NumLock Key: + * KBDEXT - VK_NUMLOCK is an Extended key + * KBDMULTIVK - VK_NUMLOCK or VK_PAUSE (without or with CTRL) + */ + T45 | KBDEXT | KBDMULTIVK, + + T46 | KBDMULTIVK, + + /* + * Number Pad keys: + * KBDNUMPAD - digits 0-9 and decimal point. + * KBDSPECIAL - require special processing by Windows + */ + T47 | KBDNUMPAD | KBDSPECIAL, // Numpad 7 (Home) + T48 | KBDNUMPAD | KBDSPECIAL, // Numpad 8 (Up), + T49 | KBDNUMPAD | KBDSPECIAL, // Numpad 9 (PgUp), + T4A, + T4B | KBDNUMPAD | KBDSPECIAL, // Numpad 4 (Left), + T4C | KBDNUMPAD | KBDSPECIAL, // Numpad 5 (Clear), + T4D | KBDNUMPAD | KBDSPECIAL, // Numpad 6 (Right), + T4E, + T4F | KBDNUMPAD | KBDSPECIAL, // Numpad 1 (End), + T50 | KBDNUMPAD | KBDSPECIAL, // Numpad 2 (Down), + T51 | KBDNUMPAD | KBDSPECIAL, // Numpad 3 (PgDn), + T52 | KBDNUMPAD | KBDSPECIAL, // Numpad 0 (Ins), + T53 | KBDNUMPAD | KBDSPECIAL, // Numpad . (Del), + + T54, T55, T56, T57, T58, T59, T5A, T5B, + T5C, T5D, T5E, T5F, T60, T61, T62, T63, + T64, T65, T66, T67, T68, T69, T6A, T6B, + T6C, T6D, T6E, T6F, T70, T71, T72, T73, + T74, T75, T76, T77, T78, T79, T7A, T7B, + T7C, T7D, T7E + +}; + +static ALLOC_SECTION_LDATA VSC_VK aE0VscToVk[] = { + { 0x10, X10 | KBDEXT }, // Speedracer: Previous Track + { 0x19, X19 | KBDEXT }, // Speedracer: Next Track + { 0x1D, X1D | KBDEXT }, // RControl + { 0x20, X20 | KBDEXT }, // Speedracer: Volume Mute + { 0x21, X21 | KBDEXT }, // Speedracer: Launch App 2 + { 0x22, X22 | KBDEXT }, // Speedracer: Media Play/Pause + { 0x24, X24 | KBDEXT }, // Speedracer: Media Stop + { 0x2E, X2E | KBDEXT }, // Speedracer: Volume Down + { 0x30, X30 | KBDEXT }, // Speedracer: Volume Up + { 0x32, X32 | KBDEXT }, // Speedracer: Browser Home + { 0x35, X35 | KBDEXT }, // Numpad Divide + { 0x37, X37 | KBDEXT }, // Snapshot + { 0x38, X38 | KBDEXT }, // RMenu + { 0x47, X47 | KBDEXT }, // Home + { 0x48, X48 | KBDEXT }, // Up + { 0x49, X49 | KBDEXT }, // Prior + { 0x4B, X4B | KBDEXT }, // Left + { 0x4D, X4D | KBDEXT }, // Right + { 0x4F, X4F | KBDEXT }, // End + { 0x50, X50 | KBDEXT }, // Down + { 0x51, X51 | KBDEXT }, // Next + { 0x52, X52 | KBDEXT }, // Insert + { 0x53, X53 | KBDEXT }, // Delete + { 0x5B, X5B | KBDEXT }, // Left Win + { 0x5C, X5C | KBDEXT }, // Right Win + { 0x5D, X5D | KBDEXT }, // Application + { 0x5F, X5F | KBDEXT }, // Speedracer: Sleep + { 0x65, X65 | KBDEXT }, // Speedracer: Browser Search + { 0x66, X66 | KBDEXT }, // Speedracer: Browser Favorites + { 0x67, X67 | KBDEXT }, // Speedracer: Browser Refresh + { 0x68, X68 | KBDEXT }, // Speedracer: Browser Stop + { 0x69, X69 | KBDEXT }, // Speedracer: Browser Forward + { 0x6A, X6A | KBDEXT }, // Speedracer: Browser Back + { 0x6B, X6B | KBDEXT }, // Speedracer: Launch App 1 + { 0x6C, X6C | KBDEXT }, // Speedracer: Launch Mail + { 0x6D, X6D | KBDEXT }, // Speedracer: Launch Media Selector + { 0x1C, X1C | KBDEXT }, // Numpad Enter + { 0x46, X46 | KBDEXT }, // Break (Ctrl + Pause) + { 0, 0 } +}; + +static ALLOC_SECTION_LDATA VSC_VK aE1VscToVk[] = { + { 0x1D, Y1D }, // Pause + { 0 , 0 } +}; + +/***************************************************************************\ +* aVkToBits[] - map Virtual Keys to Modifier Bits +* +* See kbd.h for a full description. +* +* The keyboard has only three shifter keys: +* SHIFT (L & R) affects alphabnumeric keys, +* CTRL (L & R) is used to generate control characters +* ALT (L & R) used for generating characters by number with numpad +\***************************************************************************/ +static ALLOC_SECTION_LDATA VK_TO_BIT aVkToBits[] = { + MODIFIER_BITS + {0, 0} +}; + +/***************************************************************************\ +* aModification[] - map character modifier bits to modification number +* +* See kbd.h for a full description. +* +\***************************************************************************/ + +static ALLOC_SECTION_LDATA MODIFIERS CharModifiers = { + &aVkToBits[0], + CHAR_MODIFIERS_MASK, + CHAR_MODIFIERS +}; + +/***************************************************************************\ +* +* aVkToWch2[] - Virtual Key to WCHAR translation for 2 shift states +* aVkToWch3[] - Virtual Key to WCHAR translation for 3 shift states +* aVkToWch4[] - Virtual Key to WCHAR translation for 4 shift states +* aVkToWch5[] - Virtual Key to WCHAR translation for 5 shift states +* aVkToWch6[] - Virtual Key to WCHAR translation for 6 shift states +* +* Table attributes: Unordered Scan, null-terminated +* +* Search this table for an entry with a matching Virtual Key to find the +* corresponding unshifted and shifted WCHAR characters. +* +* Special values for VirtualKey (column 1) +* 0xff - dead chars for the previous entry +* 0 - terminate the list +* +* Special values for Attributes (column 2) +* CAPLOK bit - CAPS-LOCK affect this key like SHIFT +* +* Special values for wch[*] (column 3 & 4) +* WCH_NONE - No character +* WCH_DEAD - Dead Key (diaresis) or invalid (US keyboard has none) +* WCH_LGTR - Ligature (generates multiple characters) +* +\***************************************************************************/ + +static ALLOC_SECTION_LDATA VK_TO_WCHARS6 aVkToWch6[] = { + VK_TO_WCH6 + {0,0,0,0,0,0,0,0} + }; + +// Put this last so that VkKeyScan interprets number characters +// as coming from the main section of the kbd (aVkToWch2 and +// aVkToWch5) before considering the numpad (aVkToWch1). + +static ALLOC_SECTION_LDATA VK_TO_WCHARS1 aVkToWch1[] = { + { VK_NUMPAD0 , 0 , '0' }, + { VK_NUMPAD1 , 0 , '1' }, + { VK_NUMPAD2 , 0 , '2' }, + { VK_NUMPAD3 , 0 , '3' }, + { VK_NUMPAD4 , 0 , '4' }, + { VK_NUMPAD5 , 0 , '5' }, + { VK_NUMPAD6 , 0 , '6' }, + { VK_NUMPAD7 , 0 , '7' }, + { VK_NUMPAD8 , 0 , '8' }, + { VK_NUMPAD9 , 0 , '9' }, + { 0 , 0 , '\0' } +}; + +static ALLOC_SECTION_LDATA VK_TO_WCHAR_TABLE aVkToWcharTable[] = { + { (PVK_TO_WCHARS1)aVkToWch6, 6, sizeof(aVkToWch6[0]) }, + { (PVK_TO_WCHARS1)aVkToWch1, 1, sizeof(aVkToWch1[0]) }, + { NULL, 0, 0 }, +}; + +/***************************************************************************\ +* aKeyNames[], aKeyNamesExt[] - Virtual Scancode to Key Name tables +* +* Table attributes: Ordered Scan (by scancode), null-terminated +* +* Only the names of Extended, NumPad, Dead and Non-Printable keys are here. +* (Keys producing printable characters are named by that character) +\***************************************************************************/ + +static ALLOC_SECTION_LDATA VSC_LPWSTR aKeyNames[] = { + 0x01, L"Esc", + 0x0e, L"Backspace", + 0x0f, L"Tab", + 0x1c, L"Enter", + 0x1d, L"Ctrl", + 0x2a, L"Shift", + 0x36, L"Right Shift", + 0x37, L"Num *", + 0x38, L"Alt", + 0x39, L"Space", + 0x3a, L"Caps Lock", + 0x3b, L"F1", + 0x3c, L"F2", + 0x3d, L"F3", + 0x3e, L"F4", + 0x3f, L"F5", + 0x40, L"F6", + 0x41, L"F7", + 0x42, L"F8", + 0x43, L"F9", + 0x44, L"F10", + 0x45, L"Pause", + 0x46, L"Scroll Lock", + 0x47, L"Num 7", + 0x48, L"Num 8", + 0x49, L"Num 9", + 0x4a, L"Num -", + 0x4b, L"Num 4", + 0x4c, L"Num 5", + 0x4d, L"Num 6", + 0x4e, L"Num +", + 0x4f, L"Num 1", + 0x50, L"Num 2", + 0x51, L"Num 3", + 0x52, L"Num 0", + 0x53, L"Num Del", + 0x54, L"Sys Req", + 0x57, L"F11", + 0x58, L"F12", + 0x7c, L"F13", + 0x7d, L"F14", + 0x7e, L"F15", + 0x7f, L"F16", + 0x80, L"F17", + 0x81, L"F18", + 0x82, L"F19", + 0x83, L"F20", + 0x84, L"F21", + 0x85, L"F22", + 0x86, L"F23", + 0x87, L"F24", + 0 , NULL +}; + +static ALLOC_SECTION_LDATA VSC_LPWSTR aKeyNamesExt[] = { + 0x1c, L"Num Enter", + 0x1d, L"Right Ctrl", + 0x35, L"Num /", + 0x37, L"Prnt Scrn", + 0x38, L"Right Alt", + 0x45, L"Num Lock", + 0x46, L"Break", + 0x47, L"Home", + 0x48, L"Up", + 0x49, L"Page Up", + 0x4b, L"Left", + 0x4d, L"Right", + 0x4f, L"End", + 0x50, L"Down", + 0x51, L"Page Down", + 0x52, L"Insert", + 0x53, L"Delete", + 0x54, L"<00>", + 0x56, L"Help", + 0x5b, L"Left Windows", + 0x5c, L"Right Windows", + 0x5d, L"Application", + 0 , NULL +}; + +static ALLOC_SECTION_LDATA KBDTABLES KbdTables = { + /* + * Modifier keys + */ + &CharModifiers, + + /* + * Characters tables + */ + aVkToWcharTable, + + /* + * Diacritics + */ + NULL, + + /* + * Names of Keys + */ + aKeyNames, + aKeyNamesExt, + NULL, + + /* + * Scan codes to Virtual Keys + */ + ausVK, + sizeof(ausVK) / sizeof(ausVK[0]), + aE0VscToVk, + aE1VscToVk, + + /* + * Locale-specific special processing + */ + MAKELONG(0, KBD_VERSION), + + /* + * Ligatures + */ + 0, + 0, + NULL +}; + +PKBDTABLES KbdLayerDescriptor(VOID) +{ + return &KbdTables; +} diff --git a/lulua/data/winkbd/keyboard.def b/lulua/data/winkbd/keyboard.def new file mode 100644 index 0000000..11b56eb --- /dev/null +++ b/lulua/data/winkbd/keyboard.def @@ -0,0 +1,4 @@ +LIBRARY Keyboard + + EXPORTS + KbdLayerDescriptor @1 diff --git a/lulua/data/winkbd/keyboard.h b/lulua/data/winkbd/keyboard.h new file mode 100644 index 0000000..0ef87d4 --- /dev/null +++ b/lulua/data/winkbd/keyboard.h @@ -0,0 +1,36 @@ +/****************************** Module Header ******************************\ +* keyboard layout header +* +* Copyright (c) 1985-2001, Microsoft Corporation +* +* Various defines for use by keyboard input code. +* +* History: +* +* created by KBDTOOL v3.40 Sat Jan 04 14:19:07 2020 +* +\***************************************************************************/ + +/* + * kbd type should be controlled by cl command-line argument + */ +#define KBD_TYPE 4 + +/* +* Include the basis of all keyboard table values +*/ +#include "kbd.h" +/***************************************************************************\ +* The table below defines the virtual keys for various keyboard types where +* the keyboard differ from the US keyboard. +* +* _EQ() : all keyboard types have the same virtual key for this scancode +* _NE() : different virtual keys for this scancode, depending on kbd type +* +* +------+ +----------+----------+----------+----------+----------+----------+ +* | Scan | | kbd | kbd | kbd | kbd | kbd | kbd | +* | code | | type 1 | type 2 | type 3 | type 4 | type 5 | type 6 | +\****+-------+_+----------+----------+----------+----------+----------+----------+*/ + +#include "customization.h" + diff --git a/lulua/data/winkbd/keyboard.rc b/lulua/data/winkbd/keyboard.rc new file mode 100644 index 0000000..c4576d7 --- /dev/null +++ b/lulua/data/winkbd/keyboard.rc @@ -0,0 +1,29 @@ +#include "winver.h" +1 VERSIONINFO + FILEVERSION 1,0,0,02 + PRODUCTVERSION 1,0,0,02 + FILEFLAGSMASK 0x3fL + FILEFLAGS 0x0L +FILEOS 0x40004L + FILETYPE VFT_DLL + FILESUBTYPE VFT2_DRV_KEYBOARD +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "000004B0" + BEGIN + VALUE "CompanyName", "6xq.net\0" + VALUE "FileDescription", "Ergonomic Arabic Keyboard Layout lulua\0" + VALUE "FileVersion", "0.2\0" + VALUE "InternalName", "kbdarlulua\0" + VALUE "ProductName","lulua\0" + VALUE "LegalCopyright", "see https://opensource.org/licenses/MIT\0" + VALUE "OriginalFilename","kbdarlulua\0" + VALUE "ProductVersion", "0.2\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0000, 0x04B0 + END +END diff --git a/lulua/data/winkbd/lulua.reg b/lulua/data/winkbd/lulua.reg new file mode 100644 index 0000000..326941c Binary files /dev/null and b/lulua/data/winkbd/lulua.reg differ diff --git a/lulua/data/winkbd/make.bat b/lulua/data/winkbd/make.bat new file mode 100755 index 0000000..4c66cc0 --- /dev/null +++ b/lulua/data/winkbd/make.bat @@ -0,0 +1,22 @@ +REM taken from https://levicki.net/articles/2006/09/29/HOWTO_Build_keyboard_layouts_for_Windows_x64.php + +SET OUTNAME=kbdarlulua +SET MSKLC="C:\Program Files (x86)\Microsoft Keyboard Layout Creator 1.4" + +mkdir System32 + +%MSKLC%\bin\i386\rc.exe -r -i%MSKLC%\inc -DSTD_CALL -DCONDITION_HANDLING=1 -DNT_UP=1 -DNT_INST=0 -DWIN32=100 -D_NT1X_=100 -DWINNT=1 -D_WIN32_WINNT=0x0502 /DWINVER=0x0502 -D_WIN32_IE=0x0600 -DWIN32_LEAN_AND_MEAN=1 -DDEVL=1 -DFPO=1 -DNDEBUG -l 409 -Fokeyboard.res keyboard.rc || exit /b + +REM build 64 bit +%MSKLC%\bin\i386\amd64\cl.exe -nologo -I%MSKLC%\inc -DNOGDICAPMASKS -DNOWINMESSAGES -DNOWINSTYLES -DNOSYSMETRICS -DNOMENUS -DNOICONS -DNOSYSCOMMANDS -DNORASTEROPS -DNOSHOWWINDOW -DOEMRESOURCE -DNOATOM -DNOCLIPBOARD -DNOCOLOR -DNOCTLMGR -DNODRAWTEXT -DNOGDI -DNOKERNEL -DNONLS -DNOMB -DNOMEMMGR -DNOMETAFILE -DNOMINMAX -DNOMSG -DNOOPENFILE -DNOSCROLL -DNOSERVICE -DNOSOUND -DNOTEXTMETRIC -DNOWINOFFSETS -DNOWH -DNOCOMM -DNOKANJI -DNOHELP -DNOPROFILER -DNODEFERWINDOWPOS -DNOMCX -DWIN32_LEAN_AND_MEAN -DRoster -DSTD_CALL -D_WIN32_WINNT=0x0502 /c /Zp8 /Gy /W3 /WX /Gz /Gm- /EHs-c- /GR- /GF -Z7 /Zl /Oxs -Fokeyboard64.obj keyboard.c || exit /b + +REM XXX: why use the 32 bit linker here? the one in amd64\ does not work +%MSKLC%\bin\i386\link.exe -nologo -base:0x5FFE0000 -merge:.edata=.data -merge:.rdata=.data -merge:.text=.data -merge:.bss=.data -section:.data,re -MERGE:_PAGE=PAGE -MERGE:_TEXT=.text -MACHINE:AMD64 -SECTION:INIT,d -OPT:REF -OPT:ICF -IGNORE:4039,4078 -noentry -dll -subsystem:native,5.2 -merge:.rdata=.text -PDBPATH:NONE -STACK:0x40000,0x1000 /opt:nowin98 -debugtype:cv,fixup -debug -osversion:5.2 -version:5.2 /release -def:keyboard.def -out:system32\%OUTNAME%.dll keyboard.res keyboard64.obj || exit /b + +REM and now 32 bit +mkdir SysWOW64 + +%MSKLC%\bin\i386\cl.exe -nologo -I%MSKLC%\inc -DBUILD_WOW6432 -DNOGDICAPMASKS -DNOWINMESSAGES -DNOWINSTYLES -DNOSYSMETRICS -DNOMENUS -DNOICONS -DNOSYSCOMMANDS -DNORASTEROPS -DNOSHOWWINDOW -DOEMRESOURCE -DNOATOM -DNOCLIPBOARD -DNOCOLOR -DNOCTLMGR -DNODRAWTEXT -DNOGDI -DNOKERNEL -DNONLS -DNOMB -DNOMEMMGR -DNOMETAFILE -DNOMINMAX -DNOMSG -DNOOPENFILE -DNOSCROLL -DNOSERVICE -DNOSOUND -DNOTEXTMETRIC -DNOWINOFFSETS -DNOWH -DNOCOMM -DNOKANJI -DNOHELP -DNOPROFILER -DNODEFERWINDOWPOS -DNOMCX -DWIN32_LEAN_AND_MEAN -DRoster -DSTD_CALL -D_WIN32_WINNT=0x0502 /c /Zp8 /Gy /W3 /WX /Gz /Gm- /EHs-c- /GR- /GF -Z7 /Zl /Oxs -Fokeyboard32.obj keyboard.c || exit /b + +%MSKLC%\bin\i386\link.exe -nologo -base:0x5FFF0000 -merge:.edata=.data -merge:.rdata=.data -merge:.text=.data -merge:.bss=.data -section:.data,re -MERGE:_PAGE=PAGE -MERGE:_TEXT=.text -MACHINE:IX86 -SECTION:INIT,d -OPT:REF -OPT:ICF -IGNORE:4039,4078 -noentry -dll -subsystem:native,5.2 -merge:.rdata=.text -PDBPATH:NONE -STACK:0x40000,0x1000 /opt:nowin98 -debugtype:cv,fixup -debug -osversion:5.2 -version:5.2 /release -def:keyboard.def -out:SysWOW64\%OUTNAME%.dll keyboard.res keyboard32.obj || exit /b + diff --git a/lulua/keyboard.py b/lulua/keyboard.py index bd1d449..0a7e2e6 100644 --- a/lulua/keyboard.py +++ b/lulua/keyboard.py @@ -157,6 +157,74 @@ _buttonToKeyman = { 'Fr_ctrl': 'RCTRL', } +# button to symbolic windows scancode usable in keyboard.c +# see windows header kbd.h (#define TXX _EQ(YY)) +_buttonToWinScancode = { + 'Bl1': 'T29', + 'Bl2': 'T02', + 'Bl3': 'T03', + 'Bl4': 'T04', + 'Bl5': 'T05', + 'Bl6': 'T06', + 'Bl7': 'T07', + 'Br6': 'T08', + 'Br5': 'T09', + 'Br4': 'T0A', + 'Br3': 'T0B', + 'Br2': 'T0C', + 'Br1': 'T0D', + 'Br_bs': 'T0E', + 'Cl_tab': 'T0F', + 'Cl1': 'T10', + 'Cl2': 'T11', + 'Cl3': 'T12', + 'Cl4': 'T13', + 'Cl5': 'T14', + 'Cr7': 'T15', + 'Cr6': 'T16', + 'Cr5': 'T17', + 'Cr4': 'T18', + 'Cr3': 'T19', + 'Cr2': 'T1A', + 'Cr1': 'T1B', + 'CD_ret': 'T1C', + 'Dl_caps': 'T3A', + 'Dl1': 'T1E', + 'Dl2': 'T1F', + 'Dl3': 'T20', + 'Dl4': 'T21', + 'Dl5': 'T22', + 'Dr7': 'T23', + 'Dr6': 'T24', + 'Dr5': 'T25', + 'Dr4': 'T26', + 'Dr3': 'T27', + 'Dr2': 'T28', + 'Dr1': 'T2B', + 'El_shift': 'T2A', + 'El1': 'T56', + 'El2': 'T2C', + 'El3': 'T2D', + 'El4': 'T2E', + 'El5': 'T2F', + 'El6': 'T30', + 'Er5': 'T31', + 'Er4': 'T32', + 'Er3': 'T33', + 'Er2': 'T34', + 'Er1': 'T35', + 'Er_shift': 'T36', + 'Fl_ctrl': 'T1D', + 'Fl_win': 'X5B', + 'Fl_alt': 'T38', + 'Fl_space': 'T39', + 'Fr_space': 'T39', + 'Fr_altgr': 'X38', + 'Fr_win': 'X5C', + 'Fr_menu': 'X5D', + 'Fr_ctrl': 'X1D', + } + class Button: __slots__ = ('width', 'isMarked', 'i') _idToName : Dict[int, Text] = {} @@ -199,6 +267,10 @@ class Button: def keymanCode (self): return _buttonToKeyman[self.name] + @property + def windowsScancode (self): + return _buttonToWinScancode[self.name] + @classmethod def deserialize (self, data: Dict): kindMap = {'standard': Button, 'letter': LetterButton, 'multi': MultiRowButton} diff --git a/lulua/render.py b/lulua/render.py index be3bf68..3bde0b7 100644 --- a/lulua/render.py +++ b/lulua/render.py @@ -290,10 +290,7 @@ def renderXmodmap (args): if not layout.isModifier (frozenset ([btn])): text = l.layout.get (btn) if not text: - if btn.name == 'Br_bs' and i == 0: - text = 'BackSpace' - else: - text = 'NoSymbol' + text = 'NoSymbol' else: # some keys cannot be represented by unicode # characters and must be mapped @@ -301,6 +298,7 @@ def renderXmodmap (args): '\t': 'Tab', '\n': 'Return', ' ': 'space', + '\b': 'BackSpace', } text = specialMap.get (text, f'U{ord (text):04X}') keycodeMap[btn].append (text) @@ -426,6 +424,255 @@ def renderAsk (args): tree = ET.ElementTree (kbdelem) tree.write (args.output, encoding='utf-8', xml_declaration=True) +def renderWinKbd (args): + keyboard = defaultKeyboards[args.keyboard] + layout = defaultLayouts[args.layout].specialize (keyboard) + + if args.layout != 'ar-lulua': + logging.error (f'due to the delicate relationship between virtual keys and text output this command will probably not produce working files for your layout. Please have a look at renderWinKbd() in {__file__} and fix the code.') + return + + resPath = pkg_resources.resource_filename (__package__, 'data/winkbd') + + with open (args.output, 'w') as fd: + lines = [] + lines.append (f'/* This header was auto-generated by {__package__}. It is included by {resPath}/keyboard.h. Do not modify. */') + lines.append ('') + + # copied from kbdneo2.c as well + # XXX: modifier keys are fixed for now + # maps virtual keys (first value) to shift bitfield value (second value) + lines.append ('#define MODIFIER_BITS\\') + lines.append (""" { VK_SHIFT , KBDSHIFT }, \\ + { VK_CONTROL , KBDCTRL }, \\ + { VK_MENU , KBDALT }, \\ + { VK_OEM_8 , KBDKANA }, \\ + { VK_OEM_102 , 16 },""") + + # copied from kbdneo2.c + # maps a shift bitfield value (array index) to a layer number in + # virtual key translation (VK_TO_WCHARS, array value) + lines.append ('#define CHAR_MODIFIERS_MASK 24') + lines.append ('#define CHAR_MODIFIERS\\') + lines.append ('\t{ 0, 1, 6, 7, SHFT_INVALID, SHFT_INVALID, SHFT_INVALID, SHFT_INVALID, 3, 8, SHFT_INVALID, SHFT_INVALID, SHFT_INVALID, SHFT_INVALID, SHFT_INVALID, SHFT_INVALID, 2, 4, SHFT_INVALID, SHFT_INVALID, SHFT_INVALID, SHFT_INVALID, SHFT_INVALID, SHFT_INVALID, 5, }') + + # this is the standard layout from windows’ kbd.h (for KBD_TYPE == 4) + scancodeToVk = { + 'T01': 'ESCAPE', + 'T02': '1', + 'T03': '2', + 'T04': '3', + 'T05': '4', + 'T06': '5', + 'T07': '6', + 'T08': '7', + 'T09': '8', + 'T0A': '9', + 'T0B': '0', + 'T0C': 'OEM_MINUS', + 'T0D': 'OEM_PLUS', + 'T0E': 'BACK', + 'T0F': 'TAB', + 'T10': 'Q', + 'T11': 'W', + 'T12': 'E', + 'T13': 'R', + 'T14': 'T', + 'T15': 'Y', + 'T16': 'U', + 'T17': 'I', + 'T18': 'O', + 'T19': 'P', + 'T1A': 'OEM_4', + 'T1B': 'OEM_6', + 'T1C': 'RETURN', + 'T1D': 'LCONTROL', + 'T1E': 'A', + 'T1F': 'S', + 'T20': 'D', + 'T21': 'F', + 'T22': 'G', + 'T23': 'H', + 'T24': 'J', + 'T25': 'K', + 'T26': 'L', + 'T27': 'OEM_1', + 'T28': 'OEM_7', + 'T29': 'OEM_3', + 'T2A': 'LSHIFT', + 'T2B': 'OEM_5', + 'T2C': 'Z', + 'T2D': 'X', + 'T2E': 'C', + 'T2F': 'V', + 'T30': 'B', + 'T31': 'N', + 'T32': 'M', + 'T33': 'OEM_COMMA', + 'T34': 'OEM_PERIOD', + 'T35': 'OEM_2', + 'T36': 'RSHIFT', + 'T37': 'MULTIPLY', + 'T38': 'LMENU', + 'T39': 'SPACE', + 'T3A': 'CAPITAL', + 'T3B': 'F1', + 'T3C': 'F2', + 'T3D': 'F3', + 'T3E': 'F4', + 'T3F': 'F5', + 'T40': 'F6', + 'T41': 'F7', + 'T42': 'F8', + 'T43': 'F9', + 'T44': 'F10', + 'T45': 'NUMLOCK', + 'T46': 'SCROLL', + 'T47': 'HOME', + 'T48': 'UP', + 'T49': 'PRIOR', + 'T4A': 'SUBTRACT', + 'T4B': 'LEFT', + 'T4C': 'CLEAR', + 'T4D': 'RIGHT', + 'T4E': 'ADD', + 'T4F': 'END', + 'T50': 'DOWN', + 'T51': 'NEXT', + 'T52': 'INSERT', + 'T53': 'DELETE', + 'T54': 'SNAPSHOT', + 'T56': 'OEM_102', + 'T57': 'F11', + 'T58': 'F12', + 'T59': 'CLEAR', + 'T5A': 'OEM_WSCTRL', + 'T5B': 'OEM_FINISH', + 'T5C': 'OEM_JUMP', + 'T5D': 'EREOF', + 'T5E': 'OEM_BACKTAB', + 'T5F': 'OEM_AUTO', + 'T62': 'ZOOM', + 'T63': 'HELP', + 'T64': 'F13', + 'T65': 'F14', + 'T66': 'F15', + 'T67': 'F16', + 'T68': 'F17', + 'T69': 'F18', + 'T6A': 'F19', + 'T6B': 'F20', + 'T6C': 'F21', + 'T6D': 'F22', + 'T6E': 'F23', + 'T6F': 'OEM_PA3', + 'T71': 'OEM_RESET', + 'T73': 'ABNT_C1', + 'T76': 'F24', + 'T7B': 'OEM_PA1', + 'T7C': 'TAB', + 'T7E': 'ABNT_C2', + 'T7F': 'OEM_PA2', + + 'X10': 'MEDIA_PREV_TRACK', + 'X19': 'MEDIA_NEXT_TRACK', + 'X1C': 'RETURN', + 'X1D': 'RCONTROL', + 'X20': 'VOLUME_MUTE', + 'X21': 'LAUNCH_APP2', + 'X22': 'MEDIA_PLAY_PAUSE', + 'X24': 'MEDIA_STOP', + 'X2E': 'VOLUME_DOWN', + 'X30': 'VOLUME_UP', + 'X32': 'BROWSER_HOME', + 'X35': 'DIVIDE', + 'X37': 'SNAPSHOT', + 'X38': 'RMENU', + 'X46': 'CANCEL', + 'X47': 'HOME', + 'X48': 'UP', + 'X49': 'PRIOR', + 'X4B': 'LEFT', + 'X4D': 'RIGHT', + 'X4F': 'END', + 'X50': 'DOWN', + 'X51': 'NEXT', + 'X52': 'INSERT', + 'X53': 'DELETE', + 'X5B': 'LWIN', + 'X5C': 'RWIN', + 'X5D': 'APPS', + 'X5E': 'POWER', + 'X5F': 'SLEEP', + 'X65': 'BROWSER_SEARCH', + 'X66': 'BROWSER_FAVORITES', + 'X67': 'BROWSER_REFRESH', + 'X68': 'BROWSER_STOP', + 'X69': 'BROWSER_FORWARD', + 'X6A': 'BROWSER_BACK', + 'X6B': 'LAUNCH_APP1', + 'X6C': 'LAUNCH_MAIL', + 'X6D': 'LAUNCH_MEDIA_SELECT', + } + # modifications copied from kbdneo2.c + # maps modifier keys to oem values used above. + scancodeToVk.update ({ + # mod 3 + 'T2B': 'OEM_102', + 'T3A': 'OEM_102', + # mod 4 + 'X38': 'OEM_8', + 'T56': 'OEM_8', + }) + for k, v in scancodeToVk.items (): + lines.append (f'#undef {k}') + if len (v) == 1: + # character value if not symbolic + v = f"'{v}'" + lines.append (f'#define {k} _EQ({v})') + lines.append ('') + + lines.append ('#define VK_TO_WCH6 \\') + for btn in unique (keyboard.keys (), key=attrgetter ('windowsScancode')): + def toUnicode (s): + if s is None: + return 'WCH_NONE' + elif len (s) != 1: + logging.error (f'only single-character strings are supported, ignoring {s}') + return 'WCH_NONE' + elif s == '\n': + # convert to windows-convention + s = '\r' + return f'0x{ord (s):x} /*{repr (s)}*/' + + text = list (layout.getButtonText (btn)) + assert len (text) < 7, "supporting six layers only right now" + + # skip unused keys + if sum (map (lambda x: 1 if x is not None else 0, text)) == 0: + continue + + # fixed-length array, need padding + mappedText = [toUnicode (s) for s in text] + while len (mappedText) < 6: + mappedText.append ('WCH_NONE') + + vk = scancodeToVk[btn.windowsScancode] + if len (vk) == 1: + # character value + vk = f"'{vk}'" + else: + # symbolic value + vk = f'VK_{vk}' + mappingStr = ', '.join (mappedText) + lines.append (f'\t{{ {vk}, 0, {mappingStr} }}, \\') + # NUL-termination entry is already present in keyboard.c + lines.append ('\n') + + fd.write ('\n'.join (lines)) + logging.info ('refer to README.rst on how to build a windows driver. ' + f'Template files are located in {resPath}') + def yamlload (s): try: with open (s) as fd: @@ -458,6 +705,8 @@ def render (): sp.set_defaults (func=renderKeyman) sp = subparsers.add_parser('ask') sp.set_defaults (func=renderAsk) + sp = subparsers.add_parser('winkbd') + sp.set_defaults (func=renderWinKbd) parser.add_argument('output', metavar='FILE', help='Output file') logging.basicConfig (level=logging.INFO) diff --git a/lulua/util.py b/lulua/util.py index f4ad76c..ce6e887 100644 --- a/lulua/util.py +++ b/lulua/util.py @@ -80,6 +80,7 @@ def displayText (text): '\t': '⭾', '\n': '↳', ' ': '\u2423', + '\b': '⌦', '\u200e': '[LRM]', # left to right mark '\u061c': '[ALM]', # arabic letter mark '\u202c': '[PDF]', # pop directional formatting diff --git a/makezip.sh b/makezip.sh new file mode 100755 index 0000000..e8b4f92 --- /dev/null +++ b/makezip.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +tmpfile=$(mktemp -u) +in=$1 +out=$2 +pushd $(dirname $in) && zip -r $tmpfile $(basename $in) && popd && cp $tmpfile $out && rm $tmpfile + -- cgit v1.2.3