C++ Windows:检测右alt是否生成Ctrl+;当前布局中的Alt(AltGr)

C++ Windows:检测右alt是否生成Ctrl+;当前布局中的Alt(AltGr),c++,windows,keyboard,C++,Windows,Keyboard,Windows中的某些键盘布局(例如US-QWERTY)将右Alt键视为常规Alt键,而其他键盘布局(例如US International)将其视为AltGr键,并在按下时生成Ctrl和Alt键。Microsoft键盘布局创建者提供了“按Ctrl+Alt处理的右Alt(也称为AltGr)”选项,以确定给定布局使用的模式 Windows上是否有一种方法可以通过编程确定当前活动键盘布局处理右Alt的方式 Windows 10中的屏幕键盘似乎可以区分这两个键(根据布局将键标记为“Alt”或“AltGr

Windows中的某些键盘布局(例如US-QWERTY)将右Alt键视为常规Alt键,而其他键盘布局(例如US International)将其视为AltGr键,并在按下时生成Ctrl和Alt键。Microsoft键盘布局创建者提供了“按Ctrl+Alt处理的右Alt(也称为AltGr)”选项,以确定给定布局使用的模式

Windows上是否有一种方法可以通过编程确定当前活动键盘布局处理右Alt的方式

Windows 10中的屏幕键盘似乎可以区分这两个键(根据布局将键标记为“Alt”或“AltGr”),但我不确定它是否决定了通过公共API,通过操作系统中更深的挂钩,或者只需了解Windows附带的布局。

具有以下返回值:

类型:
SHORT

如果函数成功,返回值的低位字节 包含虚拟密钥代码,高阶字节包含 移位状态,可以是以下标志位的组合

Return value  Description
 1              Either SHIFT key is pressed.
 2              Either CTRL key is pressed.
 4              Either ALT key is pressed.
 8              The Hankaku key is pressed
16              Reserved (defined by the keyboard layout driver).
32              Reserved (defined by the keyboard layout driver).
如果函数未找到转换为传递字符的键 代码,低阶和高阶字节都包含
–1

…用于将右侧ALT键用作shift键的键盘布局 (例如,法语键盘布局),shift状态为 由值
6
表示,因为右侧的ALT键是 内部转换为CTRL+ALT

该函数将字符转换为相应的虚拟键代码和移位状态。该函数使用输入语言和由输入区域设置标识符标识的物理键盘布局翻译字符

不幸的是,我不知道反向函数(使用输入语言和物理键盘布局将虚拟键代码和移位状态转换为相应的字符)。
以下PowerShell脚本中定义的函数“Get Keyboard RightAlt”
58633725_RightAltFinder.ps1
模拟特定换档状态下的反向函数(对应于RightAlt)对于任何应用蛮力方法安装的输入区域设置标识符:检查
VkKeyScanExW
中每个可能字符的输出(Unicode范围从
U+0020
U+FFFD


另一个问题是确定当前活动的键盘布局。以下PowerShell脚本声明了
获取KeyboardLayoutForPid
函数,该函数可可靠地获取任何进程的当前Windows键盘布局(如中所述):

if($null-eq('Win32Functions.KeyboardLayout'-as[type])){
添加类型-MemberDefinition@'
[DllImport(“user32.dll”)]
公共静态外部IntPtr GetKeyboardLayout(uint线程);
'@-Name KeyboardLayout-命名空间Win32Functions
}
函数Get KeyboardLayoutForPid{
[cmdletbinding()]
Param(
[参数(必需=$False,ValueFromPipeline=$False)]
[int]$Id=$PID,
#以前用于调试
[参数(必需=$False,DontShow=$True)]
[开关]$Raw
)
$InstalledInputLanguages=[System.Windows.Forms.InputLanguages]::InstalledInputLanguages
$CurrentInputLanguage=[System.Windows.Forms.InputLanguage]::DefaultInputLanguage#CurrentInputLanguage
$CurrentInputLanguageHKL=$CurrentInputLanguage.Handle#只是一个假设
###详细写入($CurrentInputLanguage:{0},0x{1:X8}({2}),{3}'-f$CurrentInputLanguage.Culture,($CurrentInputLanguageHKL-band 0xffffffff),$CurrentInputLanguageHKL,$CurrentInputLanguage.LayoutName)
函数GetRealLayoutName([IntPtr]$HKL){
$regBase='注册表::'+
“HKLM\SYSTEM\CurrentControlSet\Control\Keyboard布局”
$LayoutHex='{0:x8}'-f($hkl-band 0xFFFFFF)
如果($hkl-频带0xFFFFFFFF)-lt 0){
$auxKeyb=获取子项-路径$regBase |
其中对象{
$\属性-包含“布局Id”-和
(Get-ItemPropertyValue-Path“注册表::$($.Name)”`
-名称“布局Id”`
-错误动作持续不断
)-eq$LayoutHex.子字符串(2,2).PadLeft(4,'0')
}|选择对象-ExpandProperty PSChildName
}否则{
$auxKeyb=$LayoutHex.Substring(0,4).PadLeft(8,'0')
}
$KbdLayoutName=获取项目属性值-路径(
连接路径-Path$regBase-ChildPath$auxKeyb
)-ErrorAction SilentlyContinue-名称“布局文本”
$KbdLayoutName
#另一个选项:从“布局显示名称”中获取本地化字符串
}#函数GetRealLayoutName
函数getKBDLayOutorpId{
Param(
[参数(必需=$True,ValueFromPipeline=$False)]
[int]$Id,
[参数(必需=$False,DontShow=$True)]
[字符串]$Parent=''
) 
$processs=获取进程-Id$Id
$Weirds=@('powershell_ise','explorer')#尚未实现
$allLayouts=foreach($processs中的进程){
$LayoutsExtra=[ordered]@{}
$auxKLIDs=@(对于($i=0;$i-lt$Proces.Threads.Count;$i++){
$thread=$Proces.Threads[$i]
##返回值是线程的输入区域设置标识符:
$LayoutInt=[Win32Functions.KeyboardLayout]::GetKeyboardLayout($thread.Id)
$LayoutsExtra[$LayoutInt]=$thread.Id
如果($Raw.IsPresent){
$auxIndicator=if($LayoutInt-notin($CurrentInputLanguageHKL[System.IntPtr]::Zero)){'#'}否则{''}
写详细的('thread{0,6}{1,8}{2,1}{3,8}{4,8}'-f$i,
(({0:x8}'-f($LayoutInt-band 0xffffffff))-替换“00000000”),
$auxIndicator,
({0:x}'-f$thread.Id),
$thread.Id)
}
}
Function Get-KeyboardRightAlt {
[cmdletbinding()]
Param (
    [parameter(Mandatory=$False)] [int32]$HKL=0,
    [parameter(Mandatory=$False)][switch]$All    # returns all `RightAlt` codes
)
begin {
    $InstalledInputLanguages = [System.Windows.Forms.InputLanguage]::InstalledInputLanguages
    if ( $HKL -eq 0 ) {
        $DefaultInputLanguage = [System.Windows.Forms.InputLanguage]::DefaultInputLanguage
        $HKL = $DefaultInputLanguage.Handle
        Write-Warning ( "input language {0:x8}: system default" -f $HKL )
    } else {
        if ( $HKL -notin $InstalledInputLanguages.Handle ) {
            Write-Warning ( "input language {0:x8}: not installed!" -f $HKL )
        }
    }
    $resultFound = $False
    $result = [PSCustomObject]@{
        VkKey         = '{0:x4}' -f 0
        Unicode       = '{0:x4}' -f 0
        Char          = ''
        RightAltKey   = ''
    }
}
Process {
    Write-Verbose ( "input language {0:x8}: processed" -f $HKL )
    for ( $i   = 0x0020;  # Space (1st "printable" character)
          $i -le 0xFFFD;  # Replacement Character
          $i++ ) {
        if ( $i -ge 0xD800 -and # <Non Private Use High Surrogate, First>
                  #   DB7F      # <Non Private Use High Surrogate, Last>
                  #   DB80      # <Private Use High Surrogate, First>
                  #   DBFF      # <Private Use High Surrogate, Last>
                  #   DC00      # <Low Surrogate, First>
                  #   DFFF      # <Low Surrogate, Last>
                  #   E000      # <Private Use, First>
             $i -le 0xF8FF      # <Private Use, Last>
            ) { continue }      # skip some codepoints
        $VkKey = [Win32Functions.KeyboardScan]::VkKeyScanEx([char]$i, $HKL)
        if ( ( $VkKey -ne -1 ) -and ( ($VkKey -band 0x0600) -eq 0x0600) ) {
            $resultFound = $True
            if ( $All.IsPresent ) {
                $result.VkKey       = '{0:x4}' -f $VkKey
                $result.Unicode     = '{0:x4}' -f $i
                $result.Char        = [char]$i
                $result.RightAltKey = [Win32Functions.KeyboardScan]::
                            VKCodeToUnicodeHKL($VkKey % 0x100, $HKL)
                $result
            } else {
                break
            }
        }
    }
    if ( -not ($All.IsPresent) ) { $resultFound }
}
} # Function Get-KeyboardRightAlt
  # abbreviation HKL (Handle Keyboard Layout) = an input locale identifier

if ( $null -eq  ( 'Win32Functions.KeyboardScan' -as [type]) ) {
    Add-Type -Name KeyboardScan -Namespace Win32Functions -MemberDefinition @'

    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    public static extern short VkKeyScanEx(char ch, IntPtr dwhkl);

    [DllImport("user32.dll")]
    public static extern uint MapVirtualKeyEx(uint uCode, uint uMapType, IntPtr dwhkl);

    [DllImport("user32.dll")]
    static extern int GetKeyNameText(int lParam, 
        [Out] System.Text.StringBuilder lpString,
        int nSize);

    [DllImport("user32.dll")]
    static extern int ToUnicodeEx( 
        uint wVirtKey, uint wScanCode, byte [] lpKeyState, 
        [Out, MarshalAs(UnmanagedType.LPWStr)] System.Text.StringBuilder pwszBuff, 
        int cchBuff, uint wFlags, IntPtr dwhkl);

    [DllImport("user32.dll")]
    public static extern bool GetKeyboardState(byte [] lpKeyState);

    [DllImport("user32.dll")]
    static extern uint MapVirtualKey(uint uCode, uint uMapType);

    [DllImport("user32.dll")]
    public static extern IntPtr GetKeyboardLayout(uint idThread);

    public static string VKCodeToUnicodeHKL(uint VKCode, IntPtr HKL)
    {
        System.Text.StringBuilder sbString = new System.Text.StringBuilder();
        byte[] bKeyState  = new byte[255];
        bool bKeyStateStatus = GetKeyboardState(bKeyState);
        if (!bKeyStateStatus) 
            return "";
        uint lScanCode = MapVirtualKey(VKCode,0);
        uint xflags = 4; // If bit 2 is set, keyboard state is not changed (Windows 10, version 1607 and newer)

        ToUnicodeEx(VKCode, lScanCode, bKeyState, sbString, (int)5, xflags, HKL);
        return sbString.ToString();
    }

    // ↓ for the present: unsuccessful
    public static string VKCodeToUnicodeHKLRightAlt(uint VKCode, IntPtr HKL)
    {
        System.Text.StringBuilder sbString = new System.Text.StringBuilder();
        byte[] bKeyState  = new byte[255];
        // bool bKeyStateStatus = GetKeyboardState(bKeyState);
        // if (!bKeyStateStatus) 
        //    return "";
        bKeyState[118] = 128; // LeftCtrl
        bKeyState[120] = 128; // LeftAlt
        bKeyState[121] = 128; // RightAlt
        uint lScanCode = MapVirtualKey(VKCode,0);
        uint xflags = 4; // If bit 2 is set, keyboard state is not changed (Windows 10, version 1607 and newer)

        ToUnicodeEx(VKCode, lScanCode, bKeyState, sbString, (int)5, xflags, HKL);
        return sbString.ToString();
    // ↑ for the present: unsuccessful
    }
'@
}

if ( -not ('System.Windows.Forms.InputLanguage' -as [type]) ) {
    Add-Type -AssemblyName System.Windows.Forms
}

# EOF 58633725_RightAltFinder.ps1
. D:\PShell\SO\58633725_RightAltFinder.ps1 # activate the function

Get-KeyboardRightAlt -HKL 0x04090405       # US keyboard
False
Get-KeyboardRightAlt -HKL 0xf0330419       # Russian - Mnemonic
True
Get-KeyboardRightAlt -HKL 0xf0330419 -all  # Russian - Mnemonic
VkKey Unicode Char RightAltKey
----- ------- ---- -----------
06c0  00a8       ¨ ъ          
06de  2019       ’ ь          
0638  20bd       ₽ 8
if ( $null -eq ('Win32Functions.KeyboardLayout' -as [type]) ) {
    Add-Type -MemberDefinition @'
        [DllImport("user32.dll")] 
        public static extern IntPtr GetKeyboardLayout(uint thread);
'@ -Name KeyboardLayout -Namespace Win32Functions
}

Function Get-KeyboardLayoutForPid {
    [cmdletbinding()]
    Param (
        [parameter(Mandatory=$False, ValueFromPipeline=$False)]
        [int]$Id = $PID,
        # used formerly for debugging
        [parameter(Mandatory=$False, DontShow=$True)]
        [switch]$Raw
    )

    $InstalledInputLanguages = [System.Windows.Forms.InputLanguage]::InstalledInputLanguages
    $CurrentInputLanguage    = [System.Windows.Forms.InputLanguage]::DefaultInputLanguage # CurrentInputLanguage
    $CurrentInputLanguageHKL = $CurrentInputLanguage.Handle # just an assumption
    ### Write-Verbose ('CurrentInputLanguage: {0}, 0x{1:X8} ({2}), {3}' -f $CurrentInputLanguage.Culture, ($CurrentInputLanguageHKL -band 0xffffffff), $CurrentInputLanguageHKL, $CurrentInputLanguage.LayoutName)

    Function GetRealLayoutName ( [IntPtr]$HKL ) {
    $regBase = 'Registry::' + 
                'HKLM\SYSTEM\CurrentControlSet\Control\Keyboard Layouts'
    $LayoutHex = '{0:x8}' -f ($hkl -band 0xFFFFFFFF)
    if ( ($hkl -band 0xFFFFFFFF) -lt 0 ) {
        $auxKeyb = Get-ChildItem -Path $regBase | 
            Where-Object { 
            $_.Property -contains 'Layout Id' -and 
                (Get-ItemPropertyValue -Path "Registry::$($_.Name)" `
                    -Name 'Layout Id' `
                    -ErrorAction SilentlyContinue
                ) -eq $LayoutHex.Substring(2,2).PadLeft(4,'0')
        } | Select-Object -ExpandProperty PSChildName
    } else {
        $auxKeyb = $LayoutHex.Substring(0,4).PadLeft(8,'0')
    }
    $KbdLayoutName  = Get-ItemPropertyValue -Path (
        Join-Path -Path $regBase -ChildPath $auxKeyb
    ) -ErrorAction SilentlyContinue -Name 'Layout Text'
    $KbdLayoutName
    # Another option: grab localized string from 'Layout Display Name'
    } # Function GetRealLayoutName

    Function  GetKbdLayoutForPid {
    Param (
        [parameter(Mandatory=$True, ValueFromPipeline=$False)]
        [int]$Id,
        [parameter(Mandatory=$False, DontShow=$True)]
        [string]$Parent = ''
    ) 
    $Processes = Get-Process -Id $Id
    $Weirds  = @('powershell_ise','explorer') # not implemented yet

    $allLayouts = foreach ( $Proces in $Processes ) {
        $LayoutsExtra = [ordered]@{}
        $auxKLIDs = @( for ( $i=0; $i -lt $Proces.Threads.Count; $i++ ) {
            $thread = $Proces.Threads[$i]
            ## The return value is the input locale identifier for the thread:
            $LayoutInt = [Win32Functions.KeyboardLayout]::GetKeyboardLayout( $thread.Id )
            $LayoutsExtra[$LayoutInt] = $thread.Id
            if ($Raw.IsPresent) {
                $auxIndicator = if ($LayoutInt -notin ($CurrentInputLanguageHKL, [System.IntPtr]::Zero)) { '#' } else { '' }
                Write-Verbose ('thread {0,6} {1,8} {2,1} {3,8} {4,8}' -f $i, 
                (('{0:x8}' -f ($LayoutInt -band 0xffffffff)) -replace '00000000'), 
                $auxIndicator,
                ('{0:x}' -f $thread.Id), 
                $thread.Id)
            }
        })
        Write-Verbose ('{0,6} ({1,6}) {2}: {3}' -f $Proces.Id, $Parent, 
            $Proces.ProcessName, (($LayoutsExtra.Keys | 
                Select-Object -Property @{ N='Handl';E={('{0:x8}' -f ($_ -band 0xffffffff))}} | 
                Select-Object -ExpandProperty Handl) -join ', '))
        foreach ( $auxHandle in $LayoutsExtra.Keys ) {
            $InstalledInputLanguages | Where-Object { $_.Handle -eq $auxHandle }
        }
        $ConHost = Get-WmiObject Win32_Process -Filter "Name = 'conhost.exe'"
        $isConsoleApp = $ConHost | Where-Object { $_.ParentProcessId -eq $Proces.Id }
        if ( $null -ne $isConsoleApp ) {
            GetKbdLayoutForPid -Id ($isConsoleApp.ProcessId) -Parent ($Proces.Id -as [string])
        }
    }
    if ( $null -eq $allLayouts ) {
        # Write-Verbose ('{0,6} ({1,6}) {2}: {3}' -f $Proces.Id, $Parent, $Proces.ProcessName, '')
    } else {
        $allLayouts
    }
    } # GetKbdLayoutForPid

    $allLayoutsRaw = GetKbdLayoutForPid -Id $Id
    if ( $null -ne $allLayoutsRaw ) {
        if ( ([bool]$PSBoundParameters['Raw']) ) {
            $allLayoutsRaw
        } else {
            $retLayouts = $allLayoutsRaw | 
                Sort-Object -Property Handle -Unique | 
                Where-Object { $_.Handle -ne $CurrentInputLanguageHKL }
            if ( $null -eq $retLayouts ) { $retLayouts = $CurrentInputLanguage }
            $RealLayoutName = $retLayouts.Handle | 
                ForEach-Object { GetRealLayoutName -HKL $_ }
            $ProcessAux = Get-Process -Id $Id
            $retLayouts | Add-Member -MemberType NoteProperty -Name 'ProcessId' -Value "$Id"
            $retLayouts | Add-Member -MemberType NoteProperty -Name 'ProcessName' -Value ($ProcessAux | Select-Object -ExpandProperty ProcessName )
            # $retLayouts | Add-Member -MemberType NoteProperty -Name 'WindowTitle' -Value ($ProcessAux | Select-Object -ExpandProperty MainWindowTitle )
            $retLayouts | Add-Member -MemberType NoteProperty -Name 'RealLayoutName' -Value ($RealLayoutName -join ';')
            $retLayouts
        }
    }
<#

.Synopsis
Get the current Windows Keyboard Layout for a process.

.Description
Gets the current Windows Keyboard Layout for a process. Identify the process
using -Id parameter.

.Parameter Id
A process Id, e.g.
- Id property of System.Diagnostics.Process instance (Get-Process), or
- ProcessId property (an instance of the Win32_Process WMI class), or 
- PID property from "TaskList.exe /FO CSV", …

.Parameter Raw
Parameter -Raw is used merely for debugging. 

.Example
Get-KeyboardLayoutForPid

This example shows output for the current process (-Id $PID). 
Note that properties RealLayoutName and LayoutName could differ (the latter is wrong; a bug in [System.Windows.Forms.InputLanguage] implementation?)

ProcessId      : 2528
ProcessName    : powershell
RealLayoutName : United States-International
Culture        : cs-CZ
Handle         : -268368891
LayoutName     : US

.Example
. D:\PShell\tests\Get-KeyboardLayoutForPid.ps1  # activate the function

Get-Process -Name * | 
    ForEach-Object { Get-KeyboardLayoutForPid -Id $_.Id -Verbose }

This example shows output for each currently running process, unfortunately
even (likely unusable) info about utility/service processes. 

The output itself can be empty for most processes, but the verbose stream
shows (hopefully worthwhile) info where current keboard layout is held.

Note different placement of the current keboard layout ID:
- console application      (cmd, powershell, ubuntu): conhost
- combined GUI/console app (powershell_ise)         : the app itself
- classic  GUI apps        (notepad, notepad++, …)  : the app itself
- advanced GUI apps        (iexplore)               : Id ≘ tab
- "modern" GUI apps        (MicrosoftEdge*)         : Id ≟ tab (unclear)
- combined GUI/service app (explorer)               : indiscernible
- etc…                     (this list is incomplete).

For instance, iexplore.exe creates a separate process for each open window 
or tab, so their identifying and assigning input languages is an easy task.

On the other side, explorer.exe creates the only process, regardless of 
open visible window(s), so they are indistinguishable by techniques used here…

.Example
gps -Name explorer | % { Get-KeyboardLayoutForPid -Id $_.Id } | ft -au

This example shows where the function could fail in a language multifarious environment:

ProcessId ProcessName RealLayoutName Culture     Handle LayoutName 
--------- ----------- -------------- -------     ------ ---------- 
5344      explorer    Greek (220);US el-GR   -266992632 Greek (220)
5344      explorer    Greek (220);US cs-CZ     67699717 US         

- scenario: 
  open three different file explorer windows and set their input languages
  as follows (their order does not matter):
    - 1st window: let default input language   (e.g. Czech, in my case),
    - 2nd window: set different input language (e.g. US English),
    - 3rd window: set different input language (e.g. Greek).
- output:
  an array (and note that default input language window isn't listed).

.Inputs
No object can be piped to the function. Use -Id pameter instead,
named or positional.

.Outputs
[System.Windows.Forms.InputLanguage] extended as follows:
                                     note the <…> placeholder

Get-KeyboardLayoutForPid | Get-Member -MemberType Properties

   TypeName: System.Windows.Forms.InputLanguage

Name           MemberType   Definition
----           ----------   ----------
ProcessId      NoteProperty string ProcessId=<…>
ProcessName    NoteProperty System.String ProcessName=powershell
RealLayoutName NoteProperty string RealLayoutName=<…>
Culture        Property     cultureinfo Culture {get;}
Handle         Property     System.IntPtr Handle {get;}
LayoutName     Property     string LayoutName {get;}

.Notes
To add the `Get-KeyboardLayoutForPid` function to the current scope,
run the script using `.` dot sourcing operator, e.g. as 
. D:\PShell\tests\Get-KeyboardLayoutForPid.ps1

Auhor:     https://stackoverflow.com/users/3439404/josefz
Created:   2019-11-24
Revisions:  

.Link

.Component
P/Invoke

<##>

} # Function Get-KeyboardLayoutForPid

if ( -not ('System.Windows.Forms.InputLanguage' -as [type]) ) {
    Add-Type -AssemblyName System.Windows.Forms
}

# EOF Get-KeyboardLayoutForPid.ps1