Missing Linux Version
QMK Toolbox is a program that allows one to easily re-program QMK-Firmware based keyboards. The program is available as a WinForms application, and a Swift-based macOs version is also available. QMK delegates all the hard work of erasing and programming the flash to other executables.
Consequently, I decided to create a new version of the program capable of running on Linux, as this is my OS of choice for devops work.
Enter Avalonia
Avalonia is a cross-platform framework based on WPF, a presentation library for Windows desktops. I did work with WPF years ago, thus the barrier of entry was low for me.
Main Window code
Below is the code for the main Window – this is just to give readers an idea of what this looks like.
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:QMK_Toolbox.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:r="clr-namespace:QMK_Toolbox.Properties"
mc:Ignorable="d"
d:DesignWidth="800"
d:DesignHeight="450"
x:Class="QMK_Toolbox.Views.MainWindow"
Initialized="OnInitialized"
Width="800"
DragDrop.AllowDrop="True"
Height="680"
FontSize="10"
MinWidth="800"
MinHeight="680"
Title="{x:Static r:Resources.ApplicationTitle}"
Background="#f0f0f0"
Icon="/Resources/qmk.ico">
<Design.DataContext>
<vm:MainWindowViewModel />
</Design.DataContext>
<Grid HorizontalAlignment="Stretch"
ShowGridLines="False"
VerticalAlignment="Stretch"
RowDefinitions="22,20,30,30,*,30,5"
ColumnDefinitions="10,*,200,10">
<Grid.Resources>
</Grid.Resources>
<Menu Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="0,3,0,3">
<MenuItem Header="{x:Static r:Resources.Menu_File}">
<MenuItem Header="{x:Static r:Resources.Menu_FileOpen}" Command="{Binding OpenFileCommand}"/>
<Separator/>
<MenuItem Header="{x:Static r:Resources.Menu_FileExit}" Command="{Binding CloseCommand}"/>
</MenuItem>
<MenuItem Header="{x:Static r:Resources.Menu_Tools}">
<MenuItem Header="Flash" Command="{Binding FlashCommand}" />
<MenuItem Header="EEPROM" Command="{Binding ClearEepromCommand}" />
<MenuItem Header="Exit DFU" Command="{Binding ExitDfuCommand}"/>
<Separator/>
<MenuItem Header="{x:Static r:Resources.Menu_ToolsFlash}" Command="{Binding IsAutoFlash }" >
<MenuItem.Icon>
<CheckBox IsChecked="{Binding IsAutoFlash}" BorderThickness="0">
</CheckBox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{x:Static r:Resources.Menu_ToolShowAll}"/>
<Separator/>
<MenuItem Header="{x:Static r:Resources.Menu_ToolsKeyTester}" IsEnabled="False"/>
</MenuItem>
<MenuItem Header="{x:Static r:Resources.Menu_Help}">
<MenuItem Header="{x:Static r:Resources.Menu_HelpCheckNew}" IsEnabled="False"/>
<Separator/>
<MenuItem Header="{x:Static r:Resources.Menu_HelpAbout}"/>
</MenuItem>
</Menu>
<Label
FontSize="10"
Grid.Row="1"
Grid.Column="1"
Padding="0,0,0,0"
VerticalAlignment="Top"
Margin="0,8,0,0"
HorizontalAlignment="Left"
Content="{x:Static r:Resources.Label_LocalFile}" />
<Label
FontSize="10"
Grid.Row="1"
Grid.Column="2"
Padding="0,0,0,0"
VerticalAlignment="Top"
HorizontalAlignment="Right"
Margin="0,8,42,0"
Content="{x:Static r:Resources.Label_MCU}" />
<Border x:Name="border"
BorderThickness="1"
BorderBrush="Black"
Grid.Row="2"
Grid.Column="1"
VerticalAlignment="Center"
HorizontalAlignment ="Stretch">
<TextBlock
x:Name="HexFile"
TextWrapping="Wrap"
Padding="2,3,0,0"
Height="19"
Background="White"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
Margin="0,0,0,0"
FontSize="10"
Text="{Binding HexFile}" />
</Border>
<StackPanel
Orientation="Horizontal"
Grid.Row="2"
Grid.Column="2"
HorizontalAlignment="Right">
<Button
Command="{Binding OpenFileCommand}"
Margin="0,0,10,0"
Padding ="2,4,0,0"
VerticalAlignment="Center"
HorizontalAlignment="Right"
Height="22"
Width="45"
FontSize="10"
Content="{x:Static r:Resources.Button_Open}" />
<ComboBox
x:Name="McuCombo"
Padding ="4,2,0,0"
Items="{Binding Mcus}"
VerticalAlignment="Center"
HorizontalAlignment="Right"
BorderBrush="DarkGray"
FontSize="10"
Width="120"
Height="22"
SelectedIndex="{Binding SelectedMcuIndex}" />
</StackPanel>
<StackPanel
Orientation="Horizontal"
Grid.Row="3"
Grid.Column="1"
Grid.ColumnSpan="2"
HorizontalAlignment="Right">
<Label
VerticalAlignment="Center"
HorizontalAlignment="Left"
Height="20"
Margin="0,2,0,0"
FontSize="11"
Content="{x:Static r:Resources.Label_AutoFlash}" />
<CheckBox
x:Name="CheckBox"
Margin="0,0,5,0"
Padding="0,0,0,0"
FontSize="8"
Height="22"
IsChecked="{Binding IsAutoFlash}"
VerticalAlignment="Center"
HorizontalAlignment="Left" >
</CheckBox>
<Button
Command="{Binding FlashCommand}"
VerticalAlignment="Center"
HorizontalAlignment="Left"
HorizontalContentAlignment="Center"
Padding ="3,4,0,0"
Height="22"
Width="55"
FontSize="10"
Margin="0,0,5,0"
Content="{x:Static r:Resources.Button_Flash}" />
<Button
Command="{Binding ClearEEPromCommand}"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Padding ="0,4,0,0"
HorizontalContentAlignment="Center"
Height="22"
Width="110"
FontSize="10"
Margin="0,0,5,0"
Content="{x:Static r:Resources.Button_Clear}" />
<Button
Command="{Binding ExitDfuCommand}"
VerticalAlignment="Center"
Padding ="0,4,0,0"
HorizontalAlignment="Left"
HorizontalContentAlignment="Center"
Height="22"
Width="70"
FontSize="10"
Content="{x:Static r:Resources.Button_ExitDFU}" />
</StackPanel>
<Border
Margin="0,0,0,0"
BorderThickness="0"
BorderBrush="DarkGray"
Grid.ColumnSpan="2"
Grid.Row="4"
Grid.Column="1">
<TextBlock
Padding="0,0,0,0"
FontFamily="Courier New"
Background="#1e1e1e"
Foreground="DarkGray"
FontSize="13"
ScrollViewer.VerticalScrollBarVisibility ="Auto"
Margin="0,5,0,6"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Text="{Binding Log}" />
</Border>
<ComboBox x:Name="HidDeviceCombo"
Background="LightGray"
Margin="0,0,0,0"
Grid.Row="5"
Grid.Column="1"
VerticalAlignment="Top"
HorizontalAlignment="Stretch"
FontSize="10"
Height="22"
BorderBrush="DarkGray"
Grid.ColumnSpan="2"
SelectedIndex="0">
<ComboBoxItem Content="{x:Static r:Resources.NoHID}"/>
</ComboBox>
</Grid>
</Window>
One can find the full code in my github repo.
Missing Features in the Linux code
While the main UI is working, including the auto-flash functionality, the following features are missing:
- HID console logging
- Keyboard tester window
- About Box
- Main program does not install tools to program/flash MCU.
- Program does not implement Show all devices (bootloaders connections/disconnections works.
Challenges
I re-wrote the USB detection code completely, Windows handles this with WMI events. Fortunately, LibDotNetUsb came to the rescue, implementing a cross-platform way of interfacing to the systems USB driver. I leaned quite a bit about USB with this project.
I re-jigged the UI to use Avalonia/MVVM Pattern
The FileOpenDialog was not working on Linux at first due to an async/await issue.
The UI is now localizable, with strings in Resources.
Conclusion
This was a fun litte project over the holidays which tought me lots about doing cross platform development in C# for desktop apps.