Skip to content

Commit

Permalink
Allow to configure (or disable) a keyboard LED to light up during seq…
Browse files Browse the repository at this point in the history
…uences.

The default behaviour is to match the compose key if it has a LED. See #270.
  • Loading branch information
samhocevar committed Nov 21, 2019
1 parent f572d1a commit f407a12
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 59 deletions.
33 changes: 22 additions & 11 deletions src/composer/KeyboardLeds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,27 +62,38 @@ private static void DisableTimer()

private static IList<ushort> m_kbd_devices = new List<ushort>();

private static readonly IDictionary<VK, KEYBOARD> m_vk_to_flag = new Dictionary<VK, KEYBOARD>()
{
{ VK.CAPITAL, KEYBOARD.CAPS_LOCK_ON },
{ VK.NUMLOCK, KEYBOARD.NUM_LOCK_ON },
{ VK.PAUSE, KEYBOARD.SCROLL_LOCK_ON },
};

private static void Refresh(object o)
{
var indicators = new KEYBOARD_INDICATOR_PARAMETERS();
int buffer_size = (int)Marshal.SizeOf(indicators);

var led_vk = Settings.LedKey.Value[0].VirtualKey;

// NOTE: I was unable to make IOCTL.KEYBOARD_QUERY_INDICATORS work
// properly, but querying state with GetKeyState() seemed more
// robust anyway. Think of the user setting Caps Lock as their
// compose key, entering compose state, then suddenly changing
// the compose key to Shift: the LED state would be inconsistent.
if (NativeMethods.GetKeyState(VK.CAPITAL) != 0
|| (Composer.IsComposing && Composer.CurrentComposeKey.VirtualKey == VK.CAPITAL))
indicators.LedFlags |= KEYBOARD.CAPS_LOCK_ON;

if (NativeMethods.GetKeyState(VK.NUMLOCK) != 0
|| (Composer.IsComposing && Composer.CurrentComposeKey.VirtualKey == VK.NUMLOCK))
indicators.LedFlags |= KEYBOARD.NUM_LOCK_ON;

if (NativeMethods.GetKeyState(VK.SCROLL) != 0
|| (Composer.IsComposing && Composer.CurrentComposeKey.VirtualKey == VK.SCROLL))
indicators.LedFlags |= KEYBOARD.SCROLL_LOCK_ON;
foreach (var kv in m_vk_to_flag)
{
var vk = kv.Key;
var flag = kv.Value;
if (NativeMethods.GetKeyState(vk) != 0)
indicators.LedFlags |= flag;
else if (Composer.IsComposing)
{
if (Composer.CurrentComposeKey.VirtualKey == vk && led_vk == VK.COMPOSE
|| led_vk == vk)
indicators.LedFlags |= flag;
}
}

lock (m_kbd_devices)
{
Expand Down
29 changes: 28 additions & 1 deletion src/i18n/Text.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 13 additions & 1 deletion src/i18n/Text.resx
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,19 @@
<comment>Name of the category (Unicode characters, Emoji, User macros, Favorites)</comment>
</data>
<data name="TimeoutToolTip" xml:space="preserve">
<value>If enabled, any compose sequence will be cancelled after a certain delay if no keyboard activity is detected.</value>
<value>If set, any compose sequence will be cancelled after a certain delay if no keyboard activity is detected.</value>
<comment></comment>
</data>
<data name="KeyboardLed" xml:space="preserve">
<value>Keyboard LED</value>
<comment></comment>
</data>
<data name="KeyboardLedToolTip" xml:space="preserve">
<value>If set, the corresponding keyboard LED will be lit when a compose sequence is in progress.</value>
<comment></comment>
</data>
<data name="KeyCompose" xml:space="preserve">
<value>Compose</value>
<comment>The short name of the compose key</comment>
</data>
</root>
1 change: 1 addition & 0 deletions src/sequences/Key.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ private static Dictionary<Key, string> KeyNames
m_key_names = new Dictionary<Key, string>
{
{ new Key(VK.DISABLED), i18n.Text.KeyDisabled },
{ new Key(VK.COMPOSE), i18n.Text.KeyCompose},
{ new Key(VK.LMENU), i18n.Text.KeyLMenu },
{ new Key(VK.RMENU), i18n.Text.KeyRMenu },
{ new Key(VK.LCONTROL), i18n.Text.KeyLControl },
Expand Down
2 changes: 1 addition & 1 deletion src/sequences/Sequence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace WinCompose

/// <summary>
/// The KeySequenceConverter class allows to convert a string or a string-like
/// object to a Key object and back.
/// object to a KeySequence object and back.
/// </summary>
public class KeySequenceConverter : TypeConverter
{
Expand Down
22 changes: 20 additions & 2 deletions src/settings/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ public static string Version

[EntryLocation("composing", "compose_key")]
public static SettingsEntry<KeySequence> ComposeKeys { get; } = new SettingsEntry<KeySequence>(new KeySequence());
[EntryLocation("composing", "led_key")]
public static SettingsEntry<KeySequence> LedKey { get; } = new SettingsEntry<KeySequence>(new KeySequence());
[EntryLocation("composing", "reset_delay")]
public static SettingsEntry<int> ResetTimeout { get; } = new SettingsEntry<int>(-1);
[EntryLocation("composing", "use_xorg_rules")]
Expand Down Expand Up @@ -134,6 +136,15 @@ public static string Version
public static IEnumerable<Key> ValidComposeKeys => m_valid_compose_keys;
public static Dictionary<string, string> ValidLanguages => m_valid_languages;

public static IList<Key> ValidLedKeys { get; } = new List<Key>()
{
new Key(VK.DISABLED),
new Key(VK.COMPOSE),
new Key(VK.CAPITAL),
new Key(VK.NUMLOCK),
new Key(VK.PAUSE),
};

public static void StartWatchConfigFile()
{
m_ini_file.OnFileChanged += LoadConfig;
Expand All @@ -147,8 +158,9 @@ public static void StopWatchConfigFile()

private static IniFile m_ini_file;

private static void ValidateComposeKeys()
private static void ValidateSettings()
{
// Check that the configured compose key(s) are legal
KeySequence compose_keys = new KeySequence();
if (ComposeKeys.Value?.Count == 0)
{
Expand All @@ -172,6 +184,12 @@ private static void ValidateComposeKeys()
compose_keys.Add(new Key(VK.DISABLED));
}
ComposeKeys.Value = compose_keys;

// Check that the keyboard LED key is legal
if (LedKey.Value.Count != 1 || !ValidLedKeys.Contains(LedKey.Value[0]))
{
LedKey.Value = new KeySequence() { new Key(VK.COMPOSE) };
}
}

public static void LoadConfig()
Expand All @@ -187,7 +205,7 @@ public static void LoadConfig()
}
}

ValidateComposeKeys();
ValidateSettings();

// HACK: if the user uses the "it-CH" locale, replace it with "it"
// because we use "it-CH" as a special value to mean Sardinian.
Expand Down
103 changes: 60 additions & 43 deletions src/ui/SettingsWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>

<Border Margin="0" ToolTipService.ToolTip="{x:Static i18n:Text.ComposeKeyToolTip}">
Expand All @@ -175,27 +176,37 @@
<StackPanel Margin="4,8" Orientation="Horizontal" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3">
<Border Margin="0" ToolTipService.ToolTip="{x:Static i18n:Text.TimeoutToolTip}">
<emoji:TextBlock Text="{Binding StringFormat='⏳ {0}', Source={x:Static i18n:Text.ResetTimeout}, Path=.}"
Margin="8,4" VerticalAlignment="Center"
ToolTipService.ToolTip="{x:Static i18n:Text.TimeoutToolTip}"/>
Margin="8,4" VerticalAlignment="Center"/>
</Border>
<Slider Width="150" Value="{Binding DelayTicks}" VerticalAlignment="Center"
Minimum="0" Maximum="12" TickFrequency="1" IsSnapToTickEnabled="True"/>
<TextBlock Margin="8,4" Text="{Binding DelayText}"/>
</StackPanel>

<CheckBox Grid.Row="3" Grid.ColumnSpan="3" Margin="8,4" VerticalAlignment="Center"
<StackPanel Margin="4,8" Orientation="Horizontal" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="3">
<Border Margin="0" ToolTipService.ToolTip="{x:Static i18n:Text.KeyboardLedToolTip}">
<emoji:TextBlock Text="{Binding StringFormat='💡 {0}', Source={x:Static i18n:Text.KeyboardLed}, Path=.}"
Margin="8,0" VerticalAlignment="Center"/>
</Border>
<ComboBox Grid.Row="0" Grid.Column="1" Margin="8,0" VerticalAlignment="Center"
ItemsSource="{x:Static wc:Settings.ValidLedKeys}"
SelectedValue="{Binding SelectedLedKey}"
DisplayMemberPath="FriendlyName"/>
</StackPanel>

<CheckBox Grid.Row="4" Grid.ColumnSpan="3" Margin="8,4" VerticalAlignment="Center"
IsChecked="{Binding Value, Source={x:Static wc:Settings.KeepOriginalKey}}"
ToolTipService.ToolTip="{x:Static i18n:Text.KeepOriginalKeyToolTip}">
<TextBlock Text="{x:Static i18n:Text.KeepOriginalKey}" TextWrapping="Wrap"/>
</CheckBox>

<CheckBox Grid.Row="4" Grid.ColumnSpan="3" Margin="8,4" VerticalAlignment="Center"
<CheckBox Grid.Row="5" Grid.ColumnSpan="3" Margin="8,4" VerticalAlignment="Center"
IsChecked="{Binding Value, Source={x:Static wc:Settings.AlwaysCompose}}"
ToolTipService.ToolTip="{x:Static i18n:Text.AlwaysComposeToolTip}">
<TextBlock Text="{x:Static i18n:Text.AlwaysCompose}" TextWrapping="Wrap"/>
</CheckBox>

<CheckBox Grid.Row="5" Grid.ColumnSpan="3" Margin="8,4" VerticalAlignment="Center"
<CheckBox Grid.Row="6" Grid.ColumnSpan="3" Margin="8,4" VerticalAlignment="Center"
IsChecked="{Binding Value, Source={x:Static wc:Settings.UnicodeInput}}"
ToolTipService.ToolTip="{x:Static i18n:Text.UnicodeInputToolTip}">
<TextBlock Text="{x:Static i18n:Text.UnicodeInput}" TextWrapping="Wrap"/>
Expand Down Expand Up @@ -238,9 +249,15 @@
</StackPanel>
</StackPanel>
</GroupBox>
</DockPanel>
</TabItem>

<!-- The Tweaks tab -->
<TabItem Header="{x:Static i18n:Text.Tweaks}">
<DockPanel Margin="8">

<!-- The Invalid Sequences group -->
<GroupBox DockPanel.Dock="Top" Margin="0,4,0,0" Padding="0,4,0,0">
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
<GroupBox.Header>
<emoji:TextBlock Text="{Binding StringFormat='❌ {0}', Source={x:Static i18n:Text.InvalidSequences}, Path=.}"/>
</GroupBox.Header>
Expand Down Expand Up @@ -276,44 +293,44 @@

</StackPanel>
</GroupBox>
</DockPanel>
</TabItem>

<!-- The Tweaks tab -->
<TabItem Header="{x:Static i18n:Text.Tweaks}">
<DockPanel>
<StackPanel Margin="8" DockPanel.Dock="Top">

<CheckBox Margin="8,4" VerticalAlignment="Center"
IsChecked="{Binding Value, Source={x:Static wc:Settings.InsertZwsp}}"
ToolTipService.ToolTip="{x:Static i18n:Text.InsertZwspToolTip}">
<TextBlock Text="{x:Static i18n:Text.InsertZwsp}" TextWrapping="Wrap"/>
</CheckBox>

<CheckBox Margin="8,4" VerticalAlignment="Center"
IsChecked="{Binding Value, Source={x:Static wc:Settings.EmulateCapsLock}}"
ToolTipService.ToolTip="{x:Static i18n:Text.EmulateCapsLockToolTip}">
<TextBlock Text="{x:Static i18n:Text.EmulateCapsLock}" TextWrapping="Wrap"/>
</CheckBox>

<CheckBox Margin="8,4" VerticalAlignment="Center"
IsChecked="{Binding Value, Source={x:Static wc:Settings.ShiftDisablesCapsLock}}"
ToolTipService.ToolTip="{x:Static i18n:Text.ShiftDisablesCapsLockToolTip}">
<TextBlock Text="{x:Static i18n:Text.ShiftDisablesCapsLock}" TextWrapping="Wrap"/>
</CheckBox>

<CheckBox Margin="8,4" VerticalAlignment="Center"
IsChecked="{Binding Value, Source={x:Static wc:Settings.CapsLockCapitalizes}}"
ToolTipService.ToolTip="{x:Static i18n:Text.CapsLockCapitalizesToolTip}">
<TextBlock Text="{x:Static i18n:Text.CapsLockCapitalizes}" TextWrapping="Wrap"/>
</CheckBox>

<CheckBox Margin="8,4" VerticalAlignment="Center"
IsChecked="{Binding Value, Source={x:Static wc:Settings.AllowInjected}}"
ToolTipService.ToolTip="{x:Static i18n:Text.AllowInjectedToolTip}">
<TextBlock Text="{x:Static i18n:Text.AllowInjected}" TextWrapping="Wrap"/>
</CheckBox>
</StackPanel>
<GroupBox DockPanel.Dock="Top" Margin="0,4,0,0" Padding="0,4,0,0">
<GroupBox.Header>
<emoji:TextBlock Text="🚀"/>
</GroupBox.Header>
<StackPanel DockPanel.Dock="Top">

<CheckBox Margin="8,4" VerticalAlignment="Center"
IsChecked="{Binding Value, Source={x:Static wc:Settings.InsertZwsp}}"
ToolTipService.ToolTip="{x:Static i18n:Text.InsertZwspToolTip}">
<TextBlock Text="{x:Static i18n:Text.InsertZwsp}" TextWrapping="Wrap"/>
</CheckBox>

<CheckBox Margin="8,4" VerticalAlignment="Center"
IsChecked="{Binding Value, Source={x:Static wc:Settings.EmulateCapsLock}}"
ToolTipService.ToolTip="{x:Static i18n:Text.EmulateCapsLockToolTip}">
<TextBlock Text="{x:Static i18n:Text.EmulateCapsLock}" TextWrapping="Wrap"/>
</CheckBox>

<CheckBox Margin="8,4" VerticalAlignment="Center"
IsChecked="{Binding Value, Source={x:Static wc:Settings.ShiftDisablesCapsLock}}"
ToolTipService.ToolTip="{x:Static i18n:Text.ShiftDisablesCapsLockToolTip}">
<TextBlock Text="{x:Static i18n:Text.ShiftDisablesCapsLock}" TextWrapping="Wrap"/>
</CheckBox>

<CheckBox Margin="8,4" VerticalAlignment="Center"
IsChecked="{Binding Value, Source={x:Static wc:Settings.CapsLockCapitalizes}}"
ToolTipService.ToolTip="{x:Static i18n:Text.CapsLockCapitalizesToolTip}">
<TextBlock Text="{x:Static i18n:Text.CapsLockCapitalizes}" TextWrapping="Wrap"/>
</CheckBox>

<CheckBox Margin="8,4" VerticalAlignment="Center"
IsChecked="{Binding Value, Source={x:Static wc:Settings.AllowInjected}}"
ToolTipService.ToolTip="{x:Static i18n:Text.AllowInjectedToolTip}">
<TextBlock Text="{x:Static i18n:Text.AllowInjected}" TextWrapping="Wrap"/>
</CheckBox>
</StackPanel>
</GroupBox>

<!-- The Advanced group -->
<GroupBox DockPanel.Dock="Top" Margin="0,4,0,0" Padding="0,4,0,0">
Expand Down
7 changes: 7 additions & 0 deletions src/ui/SettingsWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ public string SelectedLanguage
set => SetValue(ref m_selected_language, value, nameof(SelectedLanguage));
}

public Key SelectedLedKey
{
// FIXME: this settings value should be a Key, not a KeySequence
get => Settings.LedKey.Value[0];
set => Settings.LedKey.Value = new KeySequence() { value };
}

public Key ComposeKey0 { get => GetComposeKey(0); set => SetComposeKey(0, value); }
public Key ComposeKey1 { get => GetComposeKey(1); set => SetComposeKey(1, value); }

Expand Down

0 comments on commit f407a12

Please sign in to comment.