お疲れ様です。金曜日です。金曜日といえばビールですね。
年度末ですが気にせずビールを飲みましょう。
さて、Unicodeで定義されているビールのEmojiには2つの種類があるのをご存知でしょうか。
🍺 BEER MUG
🍻 CLINKING BEER MUGS
「ビール」と「ビールで乾杯」です。
この2つ、何かに似ていると思いませんか?
そう、モールス符号のトン(・)とツー(-)ですね。
我々は2種の符号と少しの空白のみで会話ができる優れた知能を持った生命体です。アルファベットや数字などは捨て去り、すべてをビールで満たしましょう。
コード/使い方
Add-Type -Language VisualBasic -ReferencedAssemblies System.Windows.Forms @"
Option Infer On
Imports System
Imports System.Diagnostics
Imports System.Collections.Generic
Imports System.Linq
Imports System.Runtime.InteropServices
Imports Microsoft.VisualBasic
Imports System.Threading
Imports System.Windows.Forms
Public Class BeerTyper
Const DIT = "🍺"
Const DAH = "🍻"
Const SEP = " "
Shared morseDictSource As New List(Of Array) From { _
({48, "0", DAH & DAH & DAH & DAH & DAH}), _
({49, "1", DIT & DAH & DAH & DAH & DAH}), _
({50, "2", DIT & DIT & DAH & DAH & DAH}), _
({51, "3", DIT & DIT & DIT & DAH & DAH}), _
({52, "4", DIT & DIT & DIT & DIT & DAH}), _
({53, "5", DIT & DIT & DIT & DIT & DIT}), _
({54, "6", DAH & DIT & DIT & DIT & DIT}), _
({55, "7", DAH & DAH & DIT & DIT & DIT}), _
({56, "8", DAH & DAH & DAH & DIT & DIT}), _
({57, "9", DAH & DAH & DAH & DAH & DIT}), _
({65, "a", DIT & DAH}), _
({66, "b", DAH & DIT & DIT & DIT}), _
({67, "c", DAH & DIT & DAH & DIT}), _
({68, "d", DAH & DIT & DIT}), _
({69, "e", DIT}), _
({70, "f", DIT & DIT & DAH & DIT}), _
({71, "g", DAH & DAH & DIT}), _
({72, "h", DIT & DIT & DIT & DIT}), _
({73, "i", DIT & DIT}), _
({74, "j", DIT & DAH & DAH & DAH}), _
({75, "k", DAH & DIT & DAH}), _
({76, "l", DIT & DAH & DIT & DIT}), _
({77, "m", DAH & DAH}), _
({78, "n", DAH & DIT}), _
({79, "o", DAH & DAH & DAH}), _
({80, "p", DIT & DAH & DAH & DIT}), _
({81, "q", DAH & DAH & DIT & DAH}), _
({82, "r", DIT & DAH & DIT}), _
({83, "s", DIT & DIT & DIT}), _
({84, "t", DAH}), _
({85, "u", DIT & DIT & DAH}), _
({86, "v", DIT & DIT & DIT & DAH}), _
({87, "w", DIT & DAH & DAH}), _
({88, "x", DAH & DIT & DIT & DAH}), _
({89, "y", DAH & DIT & DAH & DAH}), _
({90, "z", DAH & DAH & DIT & DIT}), _
({00, " ", String.Empty}) _
}
Const WH_KEYBOARD_LL = &H0D
Const WM_KEYDOWN = &H0100
Const WM_KEYUP = &H0101
Const INPUT_KEYBOARD = &H01
Const KEYEVENTF_EXTENDEDKEY = &H01
Const KEYEVENTF_KEYUP = &H02
Const KEYEVENTF_UNICODE = &H04
Const CALL_NEXT_HOOK = 666
<DllImport("User32.dll")> _
Public Overloads Shared Function SetWindowsHookEx( _
ByVal idHook As Integer, _
ByVal hookProc As CallBack, _
ByVal hInstance As IntPtr, _
ByVal wParam As Integer) As Integer
End Function
<DllImport("kernel32.dll")> _
Public Overloads Shared Function GetModuleHandle( _
ByVal lpModuleName As String) As IntPtr
End Function
<DllImport("User32.dll")> _
Public Overloads Shared Function CallNextHookEx( _
ByVal idHook As Integer, _
ByVal nCode As Integer, _
ByVal wParam As IntPtr, _
ByVal lParam As IntPtr) As Integer
End Function
<DllImport("User32.dll")> _
Public Overloads Shared Function UnhookWindowsHookEx( _
ByVal idHook As Integer) As Boolean
End Function
<DllImport("User32.dll")> _
Public Overloads Shared Function SendInput( _
ByVal nInputs As UInteger, _
ByVal pInputs As INPUT(), _
ByVal cbsize As Integer) As Integer
End Function
<StructLayout(LayoutKind.Sequential)> _
Public Structure KBDLLHOOKSTRUCT
Public vkCode As UInteger
Public scanCode As UInteger
Public dwFlags As UInteger
Public time As UInteger
Public dwExtraInfo As IntPtr
End Structure
<StructLayout(LayoutKind.Sequential)> _
Public Structure INPUT
Public type As Integer
Public iu As InputUnion
End Structure
<StructLayout(LayoutKind.Explicit)> _
Public Structure InputUnion
<FieldOffset(0)> Public mi As MOUSEINPUT
<FieldOffset(0)> Public ki As KEYBDINPUT
<FieldOffset(0)> Public hi As HARDWAREINPUT
End Structure
<StructLayout(LayoutKind.Sequential)> _
Public Structure MOUSEINPUT
Public dx As Integer
Public dy As Integer
Public mouseData As UInteger
Public dwFlags As UInteger
Public time As UInteger
Public dwExtraInfo As IntPtr
End Structure
<StructLayout(LayoutKind.Sequential)> _
Public Structure KEYBDINPUT
Public vkCode As UShort
Public scanCode As UShort
Public dwFlags As UInteger
Public time As UInteger
Public dwExtraInfo As IntPtr
End Structure
<StructLayout(LayoutKind.Sequential)> _
Public Structure HARDWAREINPUT
Public uMsg As UInteger
Public wParamL As UShort
Public wParamH As UShort
End Structure
Public Delegate Function CallBack( _
ByVal nCode As Integer, _
ByVal wParam As IntPtr, _
ByVal lParam As IntPtr) As IntPtr
Shared hHook = IntPtr.Zero
Shared isFirstWrite = True
Shared MorseCode As New Dictionary(Of Integer, String)
Shared EquivalentKey As New Dictionary(Of String, String)
Public Sub New()
MorseCode = morseDictSource.ToDictionary(Function(x) CInt(x(0)), Function(x) CStr(x(2)))
EquivalentKey = morseDictSource.ToDictionary(Function(x) CStr(x(2)), Function(x) CStr(x(1)))
Dim hookProc As CallBack = AddressOf KeybordHook
Dim hModule = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName)
hHook = SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, hModule, 0)
End Sub
Private Function KeybordHook( _
ByVal nCode As Integer, _
ByVal wParam As IntPtr, _
ByVal lParam As IntPtr) As Integer
Dim llhStruct = CType(Marshal.PtrToStructure(lParam, GetType(KBDLLHOOKSTRUCT)), KBDLLHOOKSTRUCT)
If llhStruct.dwExtraInfo = CALL_NEXT_HOOK _
OrElse nCode < 0 Then _
Return CallNextHookEx(hHook, nCode, wParam, lParam)
Dim dicVal = String.Empty
If MorseCode.TryGetValue(llhStruct.vkCode, dicVal) Then
If wParam = WM_KEYDOWN Then EncodeMorse(dicVal)
Return 1
End If
If llhStruct.vkCode = 112
Select wParam
Case WM_KEYDOWN: SnedCtrlC()
Case WM_KEYUP: DecodeMorse()
End Select
Return 1
End If
Return CallNextHookEx(hHook, nCode, wParam, lParam)
End Function
Private Function EncodeMorse(Byval str As String) As Integer
Dim inputs = CreateInputStructure(str & SEP)
Return SendInput(inputs.Length, inputs, Marshal.SizeOf(GetType(INPUT)))
End Function
Private Function SnedCtrlC() As Integer
If isFirstWrite Then
Console.writeline(vbCrLf)
isFirstWrite = False
End If
ClipBoard.Clear()
Dim inputs = CreateInputStructure(New List(Of Integer)({17, 67}))
Return SendInput(inputs.Length, inputs, Marshal.SizeOf(GetType(INPUT)))
End Function
Private Function DecodeMorse() As Boolean
Dim tgt = String.Empty
For i = 1 to 10
tgt = ClipBoard.GetText()
If Not String.IsNullOrEmpty(tgt) Then Exit For
Thread.Sleep(100)
Next
If String.IsNullOrEmpty(tgt) Then Return False
Dim dicVal = String.Empty
Dim decodedTgt = String.Join(String.Empty, New List(Of String)(tgt.Split(SEP)) _
.Select(Function(x) If(EquivalentKey.TryGetValue(x, dicVal), dicVal, SEP+x)))
Console.Writeline(decodedTgt)
Return True
End Function
Private Overloads Function CreateInputStructure(Byval tgt As String) As INPUT()
Dim iStruct As New INPUT
iStruct.type = INPUT_KEYBOARD
iStruct.iu.ki.dwFlags = KEYEVENTF_UNICODE
iStruct.iu.ki.dwExtraInfo = CALL_NEXT_HOOK
Dim keyDowns As New List(Of INPUT)
For Each c In tgt
iStruct.iu.ki.scanCode = AscW(c)
keyDowns.Add(iStruct)
Next
Dim keyUps = keyDowns.Select(Function(x)
x.iu.ki.dwFlags = KEYEVENTF_UNICODE Or KEYEVENTF_KEYUP
Return x
End Function)
Return keyDowns.Zip(keyUps, Function(x,y) {x, y}) _
.SelectMany(Function(x) x) _
.ToArray()
End Function
Private Overloads Function CreateInputStructure(Byval tgt As List(Of Integer)) As INPUT()
Dim iStruct As New INPUT
iStruct.type = INPUT_KEYBOARD
iStruct.iu.ki.dwFlags = KEYEVENTF_EXTENDEDKEY
iStruct.iu.ki.dwExtraInfo = CALL_NEXT_HOOK
Dim keyDowns As New List(Of INPUT)
For Each elm In tgt
iStruct.iu.ki.vkCode = elm
keyDowns.Add(iStruct)
Next
Dim keyUps = keyDowns.Select(Function(x)
x.iu.ki.dwFlags = KEYEVENTF_EXTENDEDKEY Or KEYEVENTF_KEYUP
Return x
End Function)
Return keyDowns.Concat(keyUps) _
.ToArray()
End Function
Public Sub Dispose()
hHook = UnhookWindowsHookEx(hHook)
End Sub
End Class
"@
New-Object BeerTyper
exit
PowerShellを立ち上げ、上記コードを張り付けてください。
32/64bit両対応ですがPowerShellのバージョンが古いと動かないかもしれません。
全てのアルファベットおよび数字の入力がビールになるほか、念のためF1キーでデコードする機能も入っています。
🍺🍻🍺 🍺 🍺🍺🍺 🍺🍺🍻 🍺🍻🍺🍺 🍻
🍻🍺🍺🍺 🍺 🍺 🍺🍻🍺 🍺🍺🍻🍺 🍺🍻 🍻🍺🍻🍺 🍺🍺 🍺🍻🍺🍺 🍺🍺 🍻 🍺🍻 🍻 🍺 🍻🍺🍻🍻 🍻🍻🍻 🍺🍺🍻 🍺🍻🍺 🍻🍺🍻🍺 🍻🍻🍻 🍻🍻 🍻🍻 🍺🍺🍻 🍻🍺 🍺🍺 🍻🍺🍻🍺 🍺🍻 🍻 🍺🍺 🍻🍻🍻 🍻🍺
🍺🍺 🍻🍺 🍺🍺🍺🍻 🍺🍺 🍻 🍺🍻 🍻 🍺🍺 🍻🍻🍻 🍻🍺
🍺🍻🍻 🍺🍺🍺🍺 🍺 🍻🍺 🍻🍺🍻🍻 🍻🍻🍻 🍺🍺🍻 🍻🍺 🍺 🍺 🍻🍺🍺 🍺🍺🍺🍺 🍺 🍺🍻🍺🍺 🍺🍻🍻🍺
🍺🍻 🍺🍺🍺 🍻🍺🍻🍻 🍻🍻🍻 🍺🍺🍻 🍻🍺🍻🍺 🍺🍻 🍻🍺 🍺🍺🍺 🍺 🍺 🍺🍺 🍻 🍺🍺 🍺🍺🍺 🍻🍻🍺 🍺🍻🍺 🍺 🍺🍻 🍻
🍺🍻🍻🍺 🍺 🍺🍻🍺 🍺🍺🍻🍺 🍺 🍻🍺🍻🍺 🍻 🍺🍺🍻🍺 🍻🍻🍻 🍺🍻🍺 🍺🍺🍻🍺 🍺🍻🍺 🍺🍺 🍻🍺🍺 🍺🍻 🍻🍺🍻🍻 🍻🍺 🍺🍺 🍻🍻🍺 🍺🍺🍺🍺 🍻
🍺🍺 🍺🍺🍺🍺 🍻🍻🍻 🍺🍻🍻🍺 🍺 🍻🍺🍻🍻 🍻🍻🍻 🍺🍺🍻 🍺 🍻🍺 🍺🍻🍻🍻 🍻🍻🍻 🍻🍺🍻🍻 🍻🍺🍺🍺 🍺 🍺 🍺🍻🍺 🍻🍺🍻🍺 🍻🍻🍻 🍻🍻 🍻🍻 🍺🍺🍻 🍻🍺 🍺🍺 🍻🍺🍻🍺 🍺🍻 🍻 🍺🍺 🍻🍻🍻 🍻🍺
🍺🍺🍻🍺 🍻🍻🍻 🍺🍻🍺 🍻🍺🍻🍻 🍻🍻🍻 🍺🍺🍻 🍺🍻🍺 🍺🍺 🍻🍺 🍺🍺🍻🍺 🍻🍻🍻 🍺🍻🍺 🍻🍻 🍺🍻 🍻 🍺🍺 🍻🍻🍻 🍻🍺 🍺🍻 🍺🍻🍺🍺 🍺🍻🍺🍺 🍺 🍻🍺 🍻🍻🍺 🍺🍻🍺🍺 🍺🍺 🍺🍺🍺 🍺🍺🍺🍺 🍺🍺 🍻🍺 🍻 🍺🍺🍺🍺 🍺🍺 🍺🍺🍺 🍻🍺🍻🍺 🍺🍺🍺🍺 🍺🍻 🍺🍻🍻🍺 🍻 🍺 🍺🍻🍺 🍺🍺 🍺🍺🍺 🍻 🍺🍻🍺 🍺🍻 🍻🍺 🍺🍺🍺 🍺🍻🍺🍺 🍺🍻 🍻 🍺 🍻🍺🍺 🍻🍺🍺🍺 🍻🍺🍻🍻 🍻🍻🍺 🍻🍻🍻 🍻🍻🍻 🍻🍻🍺 🍺🍻🍺🍺 🍺
真面目な解説
を書こうと思ったんですが.NET FrameworkでWH_KEYBOARD_LLを使う方法は既に記事がたくさんあるのでポイントだけ箇条書きで。
- PowerShellはクラスをVB.NETとかC#で書ける
- ただしImports(Using)は全部書かないといけないです。
- System.Windows.Formsみたいな読めないやつは外から入れてやる必要があります。
- INPUT構造体はMSDNの例で作ると64bitで詰む
- アラインメントの問題です(浅学ゆえこの程度で勘弁してください)。
- フックは処理スピードが命
- WH_KEYBOARD_LLは300msが上限らしいですがとにかく呼ばれる回数が多いので50msを目標にしました。
- フックプロシージャ自身がキーイベントを呼んでしまうので対策としてdwExtraInfoを使っています。
- 構造体はクラスと違って値型なのでガンガンコピーします。
- LINQはいいぞ(LINQ自体が速いわけではないですが)。