diff --git a/linux/.idea/.idea.QMKToolbox/.idea/.gitignore b/linux/.idea/.idea.QMKToolbox/.idea/.gitignore
new file mode 100644
index 0000000000..46abe1c027
--- /dev/null
+++ b/linux/.idea/.idea.QMKToolbox/.idea/.gitignore
@@ -0,0 +1,13 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Rider ignored files
+/modules.xml
+/.idea.QMKToolbox.iml
+/contentModel.xml
+/projectSettingsUpdater.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/linux/.idea/.idea.QMKToolbox/.idea/.name b/linux/.idea/.idea.QMKToolbox/.idea/.name
new file mode 100644
index 0000000000..1feb46ddfe
--- /dev/null
+++ b/linux/.idea/.idea.QMKToolbox/.idea/.name
@@ -0,0 +1 @@
+QMKToolbox
\ No newline at end of file
diff --git a/linux/.idea/.idea.QMKToolbox/.idea/avalonia.xml b/linux/.idea/.idea.QMKToolbox/.idea/avalonia.xml
new file mode 100644
index 0000000000..57e103bae9
--- /dev/null
+++ b/linux/.idea/.idea.QMKToolbox/.idea/avalonia.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/linux/.idea/.idea.QMKToolbox/.idea/encodings.xml b/linux/.idea/.idea.QMKToolbox/.idea/encodings.xml
new file mode 100644
index 0000000000..df87cf951f
--- /dev/null
+++ b/linux/.idea/.idea.QMKToolbox/.idea/encodings.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/linux/.idea/.idea.QMKToolbox/.idea/indexLayout.xml b/linux/.idea/.idea.QMKToolbox/.idea/indexLayout.xml
new file mode 100644
index 0000000000..7b08163ceb
--- /dev/null
+++ b/linux/.idea/.idea.QMKToolbox/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/linux/.idea/.idea.QMKToolbox/.idea/vcs.xml b/linux/.idea/.idea.QMKToolbox/.idea/vcs.xml
new file mode 100644
index 0000000000..6c0b863585
--- /dev/null
+++ b/linux/.idea/.idea.QMKToolbox/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/linux/QMKToolbox.sln b/linux/QMKToolbox.sln
new file mode 100644
index 0000000000..73f8a71977
--- /dev/null
+++ b/linux/QMKToolbox.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QMKToolbox", "./QMKToolbox/QMKToolbox.csproj", "{E55DDF86-FC1E-4BD1-BF3A-19409E69A520}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {E55DDF86-FC1E-4BD1-BF3A-19409E69A520}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E55DDF86-FC1E-4BD1-BF3A-19409E69A520}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E55DDF86-FC1E-4BD1-BF3A-19409E69A520}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E55DDF86-FC1E-4BD1-BF3A-19409E69A520}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/linux/QMKToolbox.sln.DotSettings b/linux/QMKToolbox.sln.DotSettings
new file mode 100644
index 0000000000..c3ce823e0b
--- /dev/null
+++ b/linux/QMKToolbox.sln.DotSettings
@@ -0,0 +1,2 @@
+
+ True
\ No newline at end of file
diff --git a/linux/QMKToolbox/.gitignore b/linux/QMKToolbox/.gitignore
new file mode 100644
index 0000000000..8afdcb6356
--- /dev/null
+++ b/linux/QMKToolbox/.gitignore
@@ -0,0 +1,454 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# Tye
+.tye/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+##
+## Visual studio for Mac
+##
+
+
+# globs
+Makefile.in
+*.userprefs
+*.usertasks
+config.make
+config.status
+aclocal.m4
+install-sh
+autom4te.cache/
+*.tar.gz
+tarballs/
+test-results/
+
+# Mac bundle stuff
+*.dmg
+*.app
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+##
+## Visual Studio Code
+##
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
diff --git a/linux/QMKToolbox/App.axaml b/linux/QMKToolbox/App.axaml
new file mode 100644
index 0000000000..244e129815
--- /dev/null
+++ b/linux/QMKToolbox/App.axaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/linux/QMKToolbox/App.axaml.cs b/linux/QMKToolbox/App.axaml.cs
new file mode 100644
index 0000000000..d0b83e01a5
--- /dev/null
+++ b/linux/QMKToolbox/App.axaml.cs
@@ -0,0 +1,31 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+using QMK_Toolbox.ViewModels;
+using QMK_Toolbox.Views;
+
+namespace QMK_Toolbox;
+
+public class App : Application
+{
+ // ReSharper disable once MemberCanBePrivate.Global
+ public MainWindowViewModel MainWindowViewModel { get; set; }
+
+ public override void Initialize()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public override void OnFrameworkInitializationCompleted()
+ {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ desktop.MainWindow = new MainWindow();
+ MainWindowViewModel = new MainWindowViewModel(Program.Arg, (IWindow)desktop.MainWindow);
+ desktop.MainWindow.DataContext = MainWindowViewModel;
+ }
+
+ base.OnFrameworkInitializationCompleted();
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/App.config b/linux/QMKToolbox/App.config
new file mode 100644
index 0000000000..8cdb44bfb3
--- /dev/null
+++ b/linux/QMKToolbox/App.config
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ atmega32u4
+
+
+ False
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+ True
+
+
+ False
+
+
+ False
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/linux/QMKToolbox/Directory.Build.props b/linux/QMKToolbox/Directory.Build.props
new file mode 100644
index 0000000000..6dc4195c32
--- /dev/null
+++ b/linux/QMKToolbox/Directory.Build.props
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/linux/QMKToolbox/Directory.Build.targets b/linux/QMKToolbox/Directory.Build.targets
new file mode 100644
index 0000000000..6dc4195c32
--- /dev/null
+++ b/linux/QMKToolbox/Directory.Build.targets
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/linux/QMKToolbox/Helpers/EmbeddedResourceHelper.cs b/linux/QMKToolbox/Helpers/EmbeddedResourceHelper.cs
new file mode 100644
index 0000000000..5c14c4b49e
--- /dev/null
+++ b/linux/QMKToolbox/Helpers/EmbeddedResourceHelper.cs
@@ -0,0 +1,62 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+
+namespace QMK_Toolbox.Helpers
+{
+ internal static class EmbeddedResourceHelper
+ {
+ public static readonly string[] Resources =
+ {
+ "avrdude.conf",
+ "reset.eep",
+ "reset_left.eep",
+ "reset_right.eep",
+ "avrdude",
+ "bootloadHID",
+ "dfu-programmer",
+ "dfu-util",
+ "hid_bootloader_cli",
+ "mdloader",
+ "teensy_loader_cli",
+ "wb32-dfu-updater_cli",
+ "libftdi1.so",
+ "libusb.so",
+ "libhidapi-libusb.so",
+ "libusb-1.0.dll",
+ "wb32-dfu-updater_cli",
+ };
+
+ internal static void ExtractResource(string file)
+ {
+ var destPath = Path.Combine("/tmp", file);
+
+ if (!File.Exists(destPath))
+ {
+ using var stream =
+ Assembly.GetExecutingAssembly().GetManifestResourceStream($"QMK_Toolbox.Resources.{file}");
+ using var filestream = new FileStream(destPath, FileMode.Create);
+ stream?.CopyTo(filestream);
+ LinuxPermissions.MakeExecutable(destPath);
+ }
+ }
+
+ internal static void ExtractResources(params string[] files) => ExtractResources(files as IEnumerable);
+
+ private static void ExtractResources(IEnumerable files)
+ {
+ foreach (var s in files)
+ {
+ ExtractResource(s);
+ }
+ }
+
+ internal static string GetResourceContent(string file)
+ {
+ using var stream =
+ Assembly.GetExecutingAssembly().GetManifestResourceStream($"QMK_Toolbox.Resources.{file}");
+ using StreamReader reader = new StreamReader(stream);
+ return reader.ReadToEnd();
+ }
+ }
+}
diff --git a/linux/QMKToolbox/Helpers/LinuxPermissions.cs b/linux/QMKToolbox/Helpers/LinuxPermissions.cs
new file mode 100644
index 0000000000..73497dfd46
--- /dev/null
+++ b/linux/QMKToolbox/Helpers/LinuxPermissions.cs
@@ -0,0 +1,31 @@
+using System.Runtime.InteropServices;
+
+namespace QMK_Toolbox.Helpers;
+
+public static class LinuxPermissions
+{
+ [DllImport("libc", SetLastError = true)]
+ private static extern int chmod(string pathname, int mode);
+
+ // user permissions
+ const int S_IRUSR = 0x100;
+ const int S_IWUSR = 0x80;
+ const int S_IXUSR = 0x40;
+
+ // group permission
+ const int S_IRGRP = 0x20;
+ const int S_IWGRP = 0x10;
+ const int S_IXGRP = 0x8;
+
+ // other permissions
+ const int S_IROTH = 0x4;
+ const int S_IWOTH = 0x2;
+
+ public static void MakeExecutable(string path)
+ {
+ const int _0755 =
+ S_IRUSR | S_IXUSR | S_IWUSR
+ | S_IRGRP | S_IXGRP | S_IROTH | S_IWOTH;
+ chmod(path, _0755);
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/MessageType.cs b/linux/QMKToolbox/MessageType.cs
new file mode 100644
index 0000000000..61e951d201
--- /dev/null
+++ b/linux/QMKToolbox/MessageType.cs
@@ -0,0 +1,14 @@
+namespace QMK_Toolbox;
+
+public enum MessageType
+{
+ Bootloader,
+ Command,
+ CommandError,
+ CommandOutput,
+ Error,
+ Hid,
+ HidOutput,
+ Info,
+ Usb
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Program.cs b/linux/QMKToolbox/Program.cs
new file mode 100644
index 0000000000..c9ad7a6015
--- /dev/null
+++ b/linux/QMKToolbox/Program.cs
@@ -0,0 +1,32 @@
+using System;
+using Avalonia;
+using Avalonia.Dialogs;
+using Avalonia.ReactiveUI;
+
+namespace QMK_Toolbox;
+
+internal class Program
+{
+ public static string Arg;
+ // Initialization code. Don't use any Avalonia, third-party APIs or any
+ // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
+ // yet and stuff might break.
+ [STAThread]
+ public static void Main(string[] args)
+ {
+ Arg =args.Length == 0 ? string.Empty : args[0];
+
+ BuildAvaloniaApp()
+ .StartWithClassicDesktopLifetime(args);
+ }
+
+ // Avalonia configuration, don't remove; also used by visual designer.
+ private static AppBuilder BuildAvaloniaApp()
+ {
+ return AppBuilder.Configure()
+ .UsePlatformDetect()
+ .UseManagedSystemDialogs()
+ .LogToTrace()
+ .UseReactiveUI();
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Properties/AssemblyInfo.cs b/linux/QMKToolbox/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..5e7fa3c3c6
--- /dev/null
+++ b/linux/QMKToolbox/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("QMK Toolbox")]
+[assembly: AssemblyDescription("A flashing/debug utility for devices running QMK Firmware")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("QMK")]
+[assembly: AssemblyProduct("QMK Toolbox")]
+[assembly: AssemblyCopyright("Copyright © 2022 QMK")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("1f8be719-cb0d-4a67-8422-b0c3a1b823f7")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("0.2.2")]
+[assembly: AssemblyFileVersion("0.2.2")]
diff --git a/linux/QMKToolbox/Properties/Resources.Designer.cs b/linux/QMKToolbox/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000..3663621fc8
--- /dev/null
+++ b/linux/QMKToolbox/Properties/Resources.Designer.cs
@@ -0,0 +1,180 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace QMK_Toolbox.Properties {
+ using System;
+
+
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
+ [System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ public class Resources {
+
+ private static System.Resources.ResourceManager resourceMan;
+
+ private static System.Globalization.CultureInfo resourceCulture;
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
+ public static System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.Equals(null, resourceMan)) {
+ System.Resources.ResourceManager temp = new System.Resources.ResourceManager("QMK_Toolbox.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
+ public static System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ public static string ApplicationTitle {
+ get {
+ return ResourceManager.GetString("ApplicationTitle", resourceCulture);
+ }
+ }
+
+ public static string Menu_File {
+ get {
+ return ResourceManager.GetString("Menu_File", resourceCulture);
+ }
+ }
+
+ public static string Menu_FileOpen {
+ get {
+ return ResourceManager.GetString("Menu_FileOpen", resourceCulture);
+ }
+ }
+
+ public static string Menu_FileExit {
+ get {
+ return ResourceManager.GetString("Menu_FileExit", resourceCulture);
+ }
+ }
+
+ public static string Menu_Tools {
+ get {
+ return ResourceManager.GetString("Menu_Tools", resourceCulture);
+ }
+ }
+
+ public static string Menu_ToolsFlash {
+ get {
+ return ResourceManager.GetString("Menu_ToolsFlash", resourceCulture);
+ }
+ }
+
+ public static string Menu_ToolEeprom {
+ get {
+ return ResourceManager.GetString("Menu_ToolEeprom", resourceCulture);
+ }
+ }
+
+ public static string Menu_ToolExitDFU {
+ get {
+ return ResourceManager.GetString("Menu_ToolExitDFU", resourceCulture);
+ }
+ }
+
+ public static string Menu_ToolAutoFlash {
+ get {
+ return ResourceManager.GetString("Menu_ToolAutoFlash", resourceCulture);
+ }
+ }
+
+ public static string Menu_ToolShowAll {
+ get {
+ return ResourceManager.GetString("Menu_ToolShowAll", resourceCulture);
+ }
+ }
+
+ public static string Menu_Help {
+ get {
+ return ResourceManager.GetString("Menu_Help", resourceCulture);
+ }
+ }
+
+ public static string Menu_HelpCheckNew {
+ get {
+ return ResourceManager.GetString("Menu_HelpCheckNew", resourceCulture);
+ }
+ }
+
+ public static string Menu_HelpAbout {
+ get {
+ return ResourceManager.GetString("Menu_HelpAbout", resourceCulture);
+ }
+ }
+
+ public static string Label_LocalFile {
+ get {
+ return ResourceManager.GetString("Label_LocalFile", resourceCulture);
+ }
+ }
+
+ public static string Label_MCU {
+ get {
+ return ResourceManager.GetString("Label_MCU", resourceCulture);
+ }
+ }
+
+ public static string Button_Open {
+ get {
+ return ResourceManager.GetString("Button_Open", resourceCulture);
+ }
+ }
+
+ public static string Label_AutoFlash {
+ get {
+ return ResourceManager.GetString("Label_AutoFlash", resourceCulture);
+ }
+ }
+
+ public static string Button_Flash {
+ get {
+ return ResourceManager.GetString("Button_Flash", resourceCulture);
+ }
+ }
+
+ public static string Button_Clear {
+ get {
+ return ResourceManager.GetString("Button_Clear", resourceCulture);
+ }
+ }
+
+ public static string Button_ExitDFU {
+ get {
+ return ResourceManager.GetString("Button_ExitDFU", resourceCulture);
+ }
+ }
+
+ public static string NoHID {
+ get {
+ return ResourceManager.GetString("NoHID", resourceCulture);
+ }
+ }
+
+ public static string Menu_ToolsKeyTester {
+ get {
+ return ResourceManager.GetString("Menu_ToolsKeyTester", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/linux/QMKToolbox/Properties/Resources.resx b/linux/QMKToolbox/Properties/Resources.resx
new file mode 100644
index 0000000000..cc43a0f234
--- /dev/null
+++ b/linux/QMKToolbox/Properties/Resources.resx
@@ -0,0 +1,183 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ QMK Toolbox
+
+
+ _File
+
+
+ _Open
+
+
+ _Exit
+
+
+ _Tools
+
+
+ Flash
+
+
+ EEPROM
+
+
+ Exit DFU
+
+
+ Auto-Flash
+
+
+ Show All Devices
+
+
+ Help
+
+
+ Check for Updates...
+
+
+ About...
+
+
+ Local File
+
+
+ MCU (AVR only)
+
+
+ _Open...
+
+
+ _Auto Flash
+
+
+ _Flash
+
+
+ _Clear EEPROM
+
+
+ _Exit DFU
+
+
+ No HID console devices connected
+
+
+ Key Tester
+
+
\ No newline at end of file
diff --git a/linux/QMKToolbox/Properties/Settings.Designer.cs b/linux/QMKToolbox/Properties/Settings.Designer.cs
new file mode 100644
index 0000000000..ed0c406904
--- /dev/null
+++ b/linux/QMKToolbox/Properties/Settings.Designer.cs
@@ -0,0 +1,145 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace QMK_Toolbox.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default {
+ get {
+ return defaultInstance;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string hexFileSetting {
+ get {
+ return ((string)(this["hexFileSetting"]));
+ }
+ set {
+ this["hexFileSetting"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("atmega32u4")]
+ public string targetSetting {
+ get {
+ return ((string)(this["targetSetting"]));
+ }
+ set {
+ this["targetSetting"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool autoSetting {
+ get {
+ return ((bool)(this["autoSetting"]));
+ }
+ set {
+ this["autoSetting"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string hexFile {
+ get {
+ return ((string)(this["hexFile"]));
+ }
+ set {
+ this["hexFile"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("1")]
+ public float outputZoom {
+ get {
+ return ((float)(this["outputZoom"]));
+ }
+ set {
+ this["outputZoom"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string keyboard {
+ get {
+ return ((string)(this["keyboard"]));
+ }
+ set {
+ this["keyboard"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string keymap {
+ get {
+ return ((string)(this["keymap"]));
+ }
+ set {
+ this["keymap"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("True")]
+ public bool firstStart {
+ get {
+ return ((bool)(this["firstStart"]));
+ }
+ set {
+ this["firstStart"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool driversInstalled {
+ get {
+ return ((bool)(this["driversInstalled"]));
+ }
+ set {
+ this["driversInstalled"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool showAllDevices {
+ get {
+ return ((bool)(this["showAllDevices"]));
+ }
+ set {
+ this["showAllDevices"] = value;
+ }
+ }
+ }
+}
diff --git a/linux/QMKToolbox/Properties/Settings.settings b/linux/QMKToolbox/Properties/Settings.settings
new file mode 100644
index 0000000000..dc251e3072
--- /dev/null
+++ b/linux/QMKToolbox/Properties/Settings.settings
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+ atmega32u4
+
+
+ False
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+ True
+
+
+ False
+
+
+ False
+
+
+
\ No newline at end of file
diff --git a/linux/QMKToolbox/QMKToolbox.csproj b/linux/QMKToolbox/QMKToolbox.csproj
new file mode 100644
index 0000000000..da77ec5816
--- /dev/null
+++ b/linux/QMKToolbox/QMKToolbox.csproj
@@ -0,0 +1,70 @@
+
+
+ WinExe
+ net6.0
+ QMK_Toolbox
+ false
+ false
+ disable
+ true
+ true
+ app.manifest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PublicResXFileCodeGenerator
+
+
+
+
+
+ Resources/avrdude.conf
+
+
+ Resources/mcu-list.txt
+
+
+ Resources/reset.eep
+
+
+ Resources/reset_left.eep
+
+
+ Resources/reset_right.eep
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/linux/QMKToolbox/QMKToolbox.sln.DotSettings b/linux/QMKToolbox/QMKToolbox.sln.DotSettings
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/linux/QMKToolbox/Resources/avrdude b/linux/QMKToolbox/Resources/avrdude
new file mode 100755
index 0000000000..53f2de3eb5
Binary files /dev/null and b/linux/QMKToolbox/Resources/avrdude differ
diff --git a/linux/QMKToolbox/Resources/bootloadHID b/linux/QMKToolbox/Resources/bootloadHID
new file mode 100755
index 0000000000..34d083d3a2
Binary files /dev/null and b/linux/QMKToolbox/Resources/bootloadHID differ
diff --git a/linux/QMKToolbox/Resources/dfu-programmer b/linux/QMKToolbox/Resources/dfu-programmer
new file mode 100755
index 0000000000..6fc8d01b10
Binary files /dev/null and b/linux/QMKToolbox/Resources/dfu-programmer differ
diff --git a/linux/QMKToolbox/Resources/dfu-util b/linux/QMKToolbox/Resources/dfu-util
new file mode 100755
index 0000000000..b1647d9abc
Binary files /dev/null and b/linux/QMKToolbox/Resources/dfu-util differ
diff --git a/linux/QMKToolbox/Resources/hid_bootloader_cli b/linux/QMKToolbox/Resources/hid_bootloader_cli
new file mode 100755
index 0000000000..715f318963
Binary files /dev/null and b/linux/QMKToolbox/Resources/hid_bootloader_cli differ
diff --git a/linux/QMKToolbox/Resources/libftdi1.so b/linux/QMKToolbox/Resources/libftdi1.so
new file mode 100755
index 0000000000..ecb3c1388c
Binary files /dev/null and b/linux/QMKToolbox/Resources/libftdi1.so differ
diff --git a/linux/QMKToolbox/Resources/libhidapi-libusb.so b/linux/QMKToolbox/Resources/libhidapi-libusb.so
new file mode 100755
index 0000000000..5e6db12de2
Binary files /dev/null and b/linux/QMKToolbox/Resources/libhidapi-libusb.so differ
diff --git a/linux/QMKToolbox/Resources/libusb.so b/linux/QMKToolbox/Resources/libusb.so
new file mode 100755
index 0000000000..3b03874036
Binary files /dev/null and b/linux/QMKToolbox/Resources/libusb.so differ
diff --git a/linux/QMKToolbox/Resources/mdloader b/linux/QMKToolbox/Resources/mdloader
new file mode 100755
index 0000000000..a9645d3e73
Binary files /dev/null and b/linux/QMKToolbox/Resources/mdloader differ
diff --git a/linux/QMKToolbox/Resources/qmk.ico b/linux/QMKToolbox/Resources/qmk.ico
new file mode 100644
index 0000000000..071c7b6f8a
Binary files /dev/null and b/linux/QMKToolbox/Resources/qmk.ico differ
diff --git a/linux/QMKToolbox/Resources/teensy-loader b/linux/QMKToolbox/Resources/teensy-loader
new file mode 100755
index 0000000000..86773d4728
Binary files /dev/null and b/linux/QMKToolbox/Resources/teensy-loader differ
diff --git a/linux/QMKToolbox/Resources/wb32-dfu-updater_cli b/linux/QMKToolbox/Resources/wb32-dfu-updater_cli
new file mode 100755
index 0000000000..4dc708c0a0
Binary files /dev/null and b/linux/QMKToolbox/Resources/wb32-dfu-updater_cli differ
diff --git a/linux/QMKToolbox/Roots.xml b/linux/QMKToolbox/Roots.xml
new file mode 100644
index 0000000000..d97ed56578
--- /dev/null
+++ b/linux/QMKToolbox/Roots.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/linux/QMKToolbox/Usb/Bootloader/Apm32DfuDevice.cs b/linux/QMKToolbox/Usb/Bootloader/Apm32DfuDevice.cs
new file mode 100644
index 0000000000..6e33b5097d
--- /dev/null
+++ b/linux/QMKToolbox/Usb/Bootloader/Apm32DfuDevice.cs
@@ -0,0 +1,30 @@
+// ReSharper disable StringLiteralTypo
+
+using System.IO;
+using System.Threading.Tasks;
+
+namespace QMK_Toolbox.Usb.Bootloader;
+
+internal class Apm32DfuDevice : BootloaderDevice
+{
+ public Apm32DfuDevice(KnownHidDevice d) : base(d)
+ {
+ Type = BootloaderType.Apm32Dfu;
+ Name = "APM32 DFU";
+ IsResettable = true;
+ }
+
+ public override void Flash(string mcu, string file)
+ {
+ if (Path.GetExtension(file)?.ToLower() == ".bin")
+ RunProcessAsync("dfu-util", $"-a 0 -d 314B:0106 -s 0x08000000:leave -D \"{file}\"").Wait();
+ else
+ PrintMessage("Only firmware files in .bin format can be flashed with dfu-util!",
+ MessageType.Error);
+ }
+
+ public override void Reset(string mcu)
+ {
+ RunProcessAsync("dfu-util", "-a 0 -d 314B:0106 -s 0x08000000:leave").Wait();
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Usb/Bootloader/AtmelDfuDevice.cs b/linux/QMKToolbox/Usb/Bootloader/AtmelDfuDevice.cs
new file mode 100644
index 0000000000..1199853b54
--- /dev/null
+++ b/linux/QMKToolbox/Usb/Bootloader/AtmelDfuDevice.cs
@@ -0,0 +1,52 @@
+
+// ReSharper disable StringLiteral
+
+using System.Threading.Tasks;
+
+namespace QMK_Toolbox.Usb.Bootloader;
+
+internal class AtmelDfuDevice : BootloaderDevice
+{
+ public AtmelDfuDevice(KnownHidDevice d) : base(d)
+ {
+ if (d.RevisionBcd == 0x0936)
+ {
+ Type = BootloaderType.QmkDfu;
+ Name = "QMK DFU";
+ }
+ else
+ {
+ Type = BootloaderType.AtmelDfu;
+ Name = "Atmel DFU";
+ }
+
+ IsEepromFlashable = true;
+ IsResettable = true;
+ }
+
+ public override void Flash(string mcu, string file)
+ {
+ RunProcessAsync("dfu-programmer", $"{mcu} erase --force").Wait();
+ Task.Delay(5).Wait();
+ RunProcessAsync("dfu-programmer", $"{mcu} flash --force \"{file}\"").Wait();
+ Task.Delay(5).Wait();
+ RunProcessAsync("dfu-programmer", $"{mcu} reset").Wait();
+ }
+
+ public override void FlashEeprom(string mcu, string file)
+ {
+ if (Type == BootloaderType.AtmelDfu)
+ RunProcessAsync("dfu-programmer", $"{mcu} erase --force").Wait();
+
+ RunProcessAsync("dfu-programmer",
+ $"{mcu} flash --force --suppress-validation --eeprom \"{file}\"").Wait();
+
+ if (Type == BootloaderType.AtmelDfu)
+ PrintMessage("Please reflash device with firmware now", MessageType.Bootloader);
+ }
+
+ public override void Reset(string mcu)
+ {
+ RunProcessAsync("dfu-programmer", $"{mcu} reset").Wait();
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Usb/Bootloader/AtmelSamBaDevice.cs b/linux/QMKToolbox/Usb/Bootloader/AtmelSamBaDevice.cs
new file mode 100644
index 0000000000..2245e3f35e
--- /dev/null
+++ b/linux/QMKToolbox/Usb/Bootloader/AtmelSamBaDevice.cs
@@ -0,0 +1,45 @@
+using System.Threading.Tasks;
+
+// ReSharper disable StringLiteralTypo
+namespace QMK_Toolbox.Usb.Bootloader;
+
+internal class AtmelSamBaDevice : BootloaderDevice
+{
+ public AtmelSamBaDevice(KnownHidDevice d) : base(d)
+ {
+ Type = BootloaderType.AtmelSamBa;
+ Name = "Atmel SAM-BA";
+ IsResettable = true;
+ // TODO: Fix this
+ ComPort = "/dev/ttyS0"; // hard coded for now
+ }
+
+ private string ComPort { get; }
+
+ public override void Flash(string mcu, string file)
+ {
+ if (ComPort == null)
+ {
+ PrintMessage("COM port not found!", MessageType.Error);
+ return;
+ }
+
+ RunProcessAsync("mdloader", $"-p {ComPort} -D \"{file}\" --restart").Wait();
+ }
+
+ public override void Reset(string mcu)
+ {
+ if (ComPort == null)
+ {
+ PrintMessage("COM port not found!", MessageType.Error);
+ return;
+ }
+
+ RunProcessAsync("mdloader", $"-p {ComPort} --restart").Wait();
+ }
+
+ public override string ToString()
+ {
+ return $"{base.ToString()} [{ComPort}]";
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Usb/Bootloader/AvrIspDevice.cs b/linux/QMKToolbox/Usb/Bootloader/AvrIspDevice.cs
new file mode 100644
index 0000000000..d9390021c4
--- /dev/null
+++ b/linux/QMKToolbox/Usb/Bootloader/AvrIspDevice.cs
@@ -0,0 +1,25 @@
+// ReSharper disable StringLiteralTypo
+namespace QMK_Toolbox.Usb.Bootloader;
+
+internal class AvrIspDevice : BootloaderDevice
+{
+ public AvrIspDevice(KnownHidDevice d) : base(d)
+ {
+ Type = BootloaderType.AvrIsp;
+ Name = "AVR ISP";
+ // TODO: Fix this
+ ComPort = "/dev/ttyS0"; // hard coded for now
+ }
+
+ private string ComPort { get; }
+
+ public override void Flash(string mcu, string file)
+ {
+ if (ComPort == null)
+ {
+ PrintMessage("COM port not found!", MessageType.Error);
+ return;
+ }
+ RunProcessAsync("avrdude", $"-p {mcu} -c avrisp -U flash:w:\"{file}\":i -P {ComPort}").Wait();
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Usb/Bootloader/BootloadHidDevice.cs b/linux/QMKToolbox/Usb/Bootloader/BootloadHidDevice.cs
new file mode 100644
index 0000000000..f3cb0c2457
--- /dev/null
+++ b/linux/QMKToolbox/Usb/Bootloader/BootloadHidDevice.cs
@@ -0,0 +1,24 @@
+// ReSharper disable StringLiteralTypo
+using System.Threading.Tasks;
+
+namespace QMK_Toolbox.Usb.Bootloader;
+
+internal class BootloadHidDevice : BootloaderDevice
+{
+ public BootloadHidDevice(KnownHidDevice d) : base(d)
+ {
+ Type = BootloaderType.AtmelDfu;
+ Name = "BootloadHID";
+ IsResettable = true;
+ }
+
+ public override void Flash(string mcu, string file)
+ {
+ RunProcessAsync("bootloadHID", $"-r \"{file}\"").Wait();
+ }
+
+ public override void Reset(string mcu)
+ {
+ RunProcessAsync("bootloadHID", "-r").Wait();
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Usb/Bootloader/BootloaderDevice.cs b/linux/QMKToolbox/Usb/Bootloader/BootloaderDevice.cs
new file mode 100644
index 0000000000..923de8cbdd
--- /dev/null
+++ b/linux/QMKToolbox/Usb/Bootloader/BootloaderDevice.cs
@@ -0,0 +1,119 @@
+using System;
+using Avalonia.Threading;
+// ReSharper disable StringLiteralTypo
+using System.Diagnostics;
+using System.Threading.Tasks;
+
+namespace QMK_Toolbox.Usb.Bootloader;
+
+internal abstract class BootloaderDevice
+{
+ public delegate void FlashOutputReceivedDelegate(BootloaderDevice device, string data, MessageType type);
+
+ public FlashOutputReceivedDelegate OutputReceived;
+
+ public BootloaderDevice(KnownHidDevice d)
+ {
+ KnownHidDevice = d;
+ }
+
+ private KnownHidDevice KnownHidDevice { get; }
+
+ public ushort VendorId => KnownHidDevice.VendorId;
+
+ public ushort ProductId => KnownHidDevice.ProductId;
+
+ public ushort RevisionBcd => KnownHidDevice.RevisionBcd;
+
+ public string ManufacturerString => KnownHidDevice.VendorName;
+
+ public string ProductString => KnownHidDevice.ProductName;
+
+ public bool IsEepromFlashable { get; protected set; }
+
+ public bool IsResettable { get; protected set; }
+
+ public BootloaderType Type { get; protected set; }
+
+ public string Name { get; protected set; }
+
+ public override string ToString()
+ {
+ return KnownHidDevice.ToString();
+ }
+
+ public virtual void Flash(string mcu, string file)
+ {
+ throw new NotImplementedException();
+ }
+
+ public virtual void FlashEeprom(string mcu, string file)
+ {
+ throw new NotImplementedException();
+ }
+
+ public virtual void Reset(string mcu)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected async Task RunProcessAsync(string command, string args)
+ {
+ using var process = new Process
+ {
+ StartInfo =
+ {
+ FileName = command,
+ Arguments = args,
+ WorkingDirectory ="/tmp",
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true
+ },
+ EnableRaisingEvents = true
+ };
+ return await RunProcessAsync(process).ConfigureAwait(false);
+ }
+
+ private Task RunProcessAsync(Process process)
+ {
+ var tcs = new TaskCompletionSource();
+
+ process.Exited += (sender, e) =>
+ {
+ process.WaitForExit();
+ tcs.SetResult(process.ExitCode);
+ };
+
+ process.OutputDataReceived += ProcessOutput;
+ process.ErrorDataReceived += ProcessErrorOutput;
+
+ var started = process.Start();
+ if (!started) PrintMessage($"Could not start process: {process}", MessageType.Error);
+
+ process.BeginOutputReadLine();
+ process.BeginErrorReadLine();
+
+ return tcs.Task;
+ }
+
+ private void ProcessOutput(object sender, DataReceivedEventArgs e)
+ {
+ if (e.Data != null) PrintMessage(e.Data, MessageType.CommandOutput);
+ }
+
+ private void ProcessErrorOutput(object sender, DataReceivedEventArgs e)
+ {
+ if (e.Data != null) PrintMessage(e.Data, MessageType.CommandError);
+ }
+
+ protected void PrintMessage(string message, MessageType type)
+ {
+ Task.Delay(1).ContinueWith( t => Dispatcher.UIThread.InvokeAsync(
+ () =>
+ {
+ OutputReceived?.Invoke(this, message, type);
+ }));
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Usb/Bootloader/BootloaderType.cs b/linux/QMKToolbox/Usb/Bootloader/BootloaderType.cs
new file mode 100644
index 0000000000..d3fb41fd8a
--- /dev/null
+++ b/linux/QMKToolbox/Usb/Bootloader/BootloaderType.cs
@@ -0,0 +1,25 @@
+// ReSharper disable StringLiteralTypo
+namespace QMK_Toolbox.Usb.Bootloader;
+
+internal enum BootloaderType
+{
+ Apm32Dfu,
+ AtmelDfu,
+ AtmelSamBa,
+ AvrIsp,
+ BootloadHid,
+ Caterina,
+ Gd32VDfu,
+ HalfKay,
+ KiibohdDfu,
+ LufaHid,
+ LufaMs,
+ QmkDfu,
+ QmkHid,
+ Stm32Dfu,
+ Stm32Duino,
+ UsbAsp,
+ UsbTinyIsp,
+ Wb32Dfu,
+ None
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Usb/Bootloader/CaterinaDevice.cs b/linux/QMKToolbox/Usb/Bootloader/CaterinaDevice.cs
new file mode 100644
index 0000000000..eb2778c161
--- /dev/null
+++ b/linux/QMKToolbox/Usb/Bootloader/CaterinaDevice.cs
@@ -0,0 +1,45 @@
+// ReSharper disable StringLiteralTypo
+namespace QMK_Toolbox.Usb.Bootloader;
+
+internal class CaterinaDevice : BootloaderDevice
+{
+ public CaterinaDevice(KnownHidDevice d) : base(d)
+ {
+ Type = BootloaderType.Caterina;
+ Name = "Caterina";
+
+ IsEepromFlashable = true;
+
+ // TODO: Fix this
+ ComPort = "/dev/ttyS0"; // hard coded for now
+ }
+
+ public string ComPort { get; }
+
+ public override void Flash(string mcu, string file)
+ {
+ if (ComPort == null)
+ {
+ PrintMessage("COM port not found!", MessageType.Error);
+ return;
+ }
+
+ RunProcessAsync("avrdude", $"-p {mcu} -c avr109 -U flash:w:\"{file}\":i -P {ComPort}").Wait();
+ }
+
+ public override void FlashEeprom(string mcu, string file)
+ {
+ if (ComPort == null)
+ {
+ PrintMessage("COM port not found!", MessageType.Error);
+ return;
+ }
+
+ RunProcessAsync("avrdude", $"-p {mcu} -c avr109 -U eeprom:w:\"{file}\":i -P {ComPort}").Wait();
+ }
+
+ public override string ToString()
+ {
+ return $"{base.ToString()} [{ComPort}]";
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Usb/Bootloader/Gd32VDfuDevice.cs b/linux/QMKToolbox/Usb/Bootloader/Gd32VDfuDevice.cs
new file mode 100644
index 0000000000..2de7d07718
--- /dev/null
+++ b/linux/QMKToolbox/Usb/Bootloader/Gd32VDfuDevice.cs
@@ -0,0 +1,29 @@
+// ReSharper disable StringLiteralTypo
+using System.IO;
+using System.Threading.Tasks;
+
+namespace QMK_Toolbox.Usb.Bootloader;
+
+internal class Gd32VDfuDevice : BootloaderDevice
+{
+ public Gd32VDfuDevice(KnownHidDevice d) : base(d)
+ {
+ Type = BootloaderType.Gd32VDfu;
+ Name = "GD32V DFU";
+ IsResettable = true;
+ }
+
+ public override void Flash(string mcu, string file)
+ {
+ if (Path.GetExtension(file)?.ToLower() == ".bin")
+ RunProcessAsync("dfu-util", $"-a 0 -d 28E9:0189 -s 0x08000000:leave -D \"{file}\"").Wait();
+ else
+ PrintMessage("Only firmware files in .bin format can be flashed with dfu-util!",
+ MessageType.Error);
+ }
+
+ public override void Reset(string mcu)
+ {
+ RunProcessAsync("dfu-util", "-a 0 -d 28E9:0189 -s 0x08000000:leave").Wait();
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Usb/Bootloader/HalfKayDevice.cs b/linux/QMKToolbox/Usb/Bootloader/HalfKayDevice.cs
new file mode 100644
index 0000000000..b84055afb9
--- /dev/null
+++ b/linux/QMKToolbox/Usb/Bootloader/HalfKayDevice.cs
@@ -0,0 +1,25 @@
+// ReSharper disable StringLiteralTypo
+using System.Threading.Tasks;
+
+namespace QMK_Toolbox.Usb.Bootloader;
+
+internal class HalfKayDevice : BootloaderDevice
+{
+ public HalfKayDevice(KnownHidDevice d) : base(d)
+ {
+ Type = BootloaderType.HalfKay;
+ Name = "HalfKay";
+
+ IsResettable = true;
+ }
+
+ public override void Flash(string mcu, string file)
+ {
+ RunProcessAsync("teensy_loader_cli", $"-mmcu={mcu} \"{file}\" -v").Wait();
+ }
+
+ public override void Reset(string mcu)
+ {
+ RunProcessAsync("teensy_loader_cli", $"-mmcu={mcu} -bv").Wait();
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Usb/Bootloader/KiibohdDfuDevice.cs b/linux/QMKToolbox/Usb/Bootloader/KiibohdDfuDevice.cs
new file mode 100644
index 0000000000..03a02ddbdf
--- /dev/null
+++ b/linux/QMKToolbox/Usb/Bootloader/KiibohdDfuDevice.cs
@@ -0,0 +1,29 @@
+// ReSharper disable StringLiteralTypo
+using System.IO;
+using System.Threading.Tasks;
+
+namespace QMK_Toolbox.Usb.Bootloader;
+
+internal class KiibohdDfuDevice : BootloaderDevice
+{
+ public KiibohdDfuDevice(KnownHidDevice d) : base(d)
+ {
+ Type = BootloaderType.KiibohdDfu;
+ Name = "Kiibohd DFU";
+ IsResettable = true;
+ }
+
+ public override void Flash(string mcu, string file)
+ {
+ if (Path.GetExtension(file)?.ToLower() == ".bin")
+ RunProcessAsync("dfu-util", $"-a 0 -d 1C11:B007 -D \"{file}\"").Wait();
+ else
+ PrintMessage("Only firmware files in .bin format can be flashed with dfu-util!",
+ MessageType.Error);
+ }
+
+ public override void Reset(string mcu)
+ {
+ RunProcessAsync("dfu-util", "-a 0 -d 1C11:B007 -e").Wait();
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Usb/Bootloader/LufaHidDevice.cs b/linux/QMKToolbox/Usb/Bootloader/LufaHidDevice.cs
new file mode 100644
index 0000000000..955fb99f46
--- /dev/null
+++ b/linux/QMKToolbox/Usb/Bootloader/LufaHidDevice.cs
@@ -0,0 +1,31 @@
+// ReSharper disable StringLiteralTypo
+
+namespace QMK_Toolbox.Usb.Bootloader;
+
+internal class LufaHidDevice : BootloaderDevice
+{
+ public LufaHidDevice(KnownHidDevice d) : base(d)
+ {
+ if (d.RevisionBcd == 0x0936)
+ {
+ Type = BootloaderType.QmkHid;
+ Name = "QMK HID";
+ }
+ else
+ {
+ Type = BootloaderType.LufaHid;
+ Name = "LUFA HID";
+ }
+
+ //IsResettable = true;
+ }
+
+ public override void Flash(string mcu, string file)
+ {
+ RunProcessAsync("hid_bootloader_cli", $"-mmcu={mcu} \"{file}\" -v").Wait();
+ }
+
+ // hid_bootloader_cli 210130 lacks -b flag
+ // Next LUFA release should have it thanks to abcminiuser/lufa#173
+ //public async override Task Reset(string mcu) => await RunProcessAsync("hid_bootloader_cli", $"-mmcu={mcu} -bv");
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Usb/Bootloader/LufaMsDevice.cs b/linux/QMKToolbox/Usb/Bootloader/LufaMsDevice.cs
new file mode 100644
index 0000000000..eb3938cc76
--- /dev/null
+++ b/linux/QMKToolbox/Usb/Bootloader/LufaMsDevice.cs
@@ -0,0 +1,71 @@
+// ReSharper disable StringLiteralTypo
+using System.IO;
+using System.Threading.Tasks;
+
+namespace QMK_Toolbox.Usb.Bootloader;
+
+internal class LufaMsDevice : BootloaderDevice
+{
+ private string _mountPoint;
+
+ public LufaMsDevice(KnownHidDevice d) : base(d)
+ {
+ Type = BootloaderType.LufaMs;
+ Name = "LUFA MS";
+
+ MountPoint = FindMountPoint();
+ }
+
+ private string MountPoint { get; }
+
+ public override void Flash(string mcu, string file)
+ {
+ Task.Run(() =>
+ {
+ if (MountPoint == null)
+ {
+ PrintMessage("Mount point not found!", MessageType.Error);
+ return;
+ }
+
+ if (Path.GetExtension(file)?.ToLower() == ".bin")
+ {
+ var destFile = $"{MountPoint}/FLASH.BIN";
+
+ try
+ {
+ PrintMessage($"Deleting {destFile}...", MessageType.Command);
+ File.Delete(destFile);
+ PrintMessage($"Copying {file} to {destFile}...", MessageType.Command);
+ File.Copy(file, destFile);
+
+ PrintMessage("Done, please eject drive now.", MessageType.Bootloader);
+ }
+ catch (IOException e)
+ {
+ PrintMessage($"IO ERROR: {e.Message}", MessageType.Error);
+ }
+ }
+ else
+ {
+ PrintMessage("Only firmware files in .bin format can be flashed with this bootloader!",
+ MessageType.Error);
+ }
+ }).Wait();
+ }
+
+ public override string ToString()
+ {
+ return $"{base.ToString()} [{MountPoint}]";
+ }
+
+ private string FindMountPoint()
+ {
+ // TODO find out if this works, rework to match to usb device that was mounted.
+ // this mounts the first removable drive.
+ OutputReceived += (_, data, _) => { _mountPoint = data; };
+ RunProcessAsync("bash", "-c \"lsblk | grep media | head -n 1 | awk '{print $7}'\"").Wait();
+
+ return _mountPoint.Contains("media") ? _mountPoint : null;
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Usb/Bootloader/Stm32DfuDevice.cs b/linux/QMKToolbox/Usb/Bootloader/Stm32DfuDevice.cs
new file mode 100644
index 0000000000..0956e95c18
--- /dev/null
+++ b/linux/QMKToolbox/Usb/Bootloader/Stm32DfuDevice.cs
@@ -0,0 +1,28 @@
+// ReSharper disable StringLiteralTypo
+using System.IO;
+using System.Threading.Tasks;
+
+namespace QMK_Toolbox.Usb.Bootloader;
+
+internal class Stm32DfuDevice : BootloaderDevice
+{
+ public Stm32DfuDevice(KnownHidDevice d) : base(d)
+ {
+ Type = BootloaderType.Stm32Dfu;
+ Name = "STM32 DFU";
+ IsResettable = true;
+ }
+
+ public override void Flash(string mcu, string file)
+ {
+ if (Path.GetExtension(file)?.ToLower() == ".bin")
+ RunProcessAsync("dfu-util", $"-a 0 -d 0483:DF11 -s 0x08000000:leave -D \"{file}\"").Wait();
+ else
+ PrintMessage("Only firmware files in .bin format can be flashed with dfu-util!", MessageType.Error);
+ }
+
+ public override void Reset(string mcu)
+ {
+ RunProcessAsync("dfu-util", "-a 0 -d 0483:DF11 -s 0x08000000:leave").Wait();
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Usb/Bootloader/Stm32DuinoDevice.cs b/linux/QMKToolbox/Usb/Bootloader/Stm32DuinoDevice.cs
new file mode 100644
index 0000000000..bf5f84513e
--- /dev/null
+++ b/linux/QMKToolbox/Usb/Bootloader/Stm32DuinoDevice.cs
@@ -0,0 +1,22 @@
+// ReSharper disable StringLiteralTypo
+using System.IO;
+using System.Threading.Tasks;
+
+namespace QMK_Toolbox.Usb.Bootloader;
+
+internal class Stm32DuinoDevice : BootloaderDevice
+{
+ public Stm32DuinoDevice(KnownHidDevice d) : base(d)
+ {
+ Type = BootloaderType.Stm32Duino;
+ Name = "STM32Duino";
+ }
+
+ public override void Flash(string mcu, string file)
+ {
+ if (Path.GetExtension(file)?.ToLower() == ".bin")
+ RunProcessAsync("dfu-util", $"-a 2 -d 1EAF:0003 -R -D \"{file}\"").Wait();
+ else
+ PrintMessage("Only firmware files in .bin format can be flashed with dfu-util!", MessageType.Error);
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Usb/Bootloader/UsbAspDevice.cs b/linux/QMKToolbox/Usb/Bootloader/UsbAspDevice.cs
new file mode 100644
index 0000000000..6a54c8aa44
--- /dev/null
+++ b/linux/QMKToolbox/Usb/Bootloader/UsbAspDevice.cs
@@ -0,0 +1,24 @@
+// ReSharper disable StringLiteralTypo
+using System.Threading.Tasks;
+
+namespace QMK_Toolbox.Usb.Bootloader;
+
+internal class UsbAspDevice : BootloaderDevice
+{
+ public UsbAspDevice(KnownHidDevice d) : base(d)
+ {
+ Type = BootloaderType.UsbAsp;
+ Name = "USBasp";
+ IsEepromFlashable = true;
+ }
+
+ public override void Flash(string mcu, string file)
+ {
+ RunProcessAsync("avrdude", $"-p {mcu} -c usbasp -U flash:w:\"{file}\":i").Wait();
+ }
+
+ public override void FlashEeprom(string mcu, string file)
+ {
+ RunProcessAsync("avrdude", $"-p {mcu} -c usbasp -U eeprom:w:\"{file}\":i").Wait();
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Usb/Bootloader/UsbTinyIspDevice.cs b/linux/QMKToolbox/Usb/Bootloader/UsbTinyIspDevice.cs
new file mode 100644
index 0000000000..1a0be52064
--- /dev/null
+++ b/linux/QMKToolbox/Usb/Bootloader/UsbTinyIspDevice.cs
@@ -0,0 +1,24 @@
+// ReSharper disable StringLiteralTypo
+using System.Threading.Tasks;
+
+namespace QMK_Toolbox.Usb.Bootloader;
+
+internal class UsbTinyIspDevice : BootloaderDevice
+{
+ public UsbTinyIspDevice(KnownHidDevice d) : base(d)
+ {
+ Type = BootloaderType.UsbTinyIsp;
+ Name = "USBtinyISP";
+ IsEepromFlashable = true;
+ }
+
+ public override void Flash(string mcu, string file)
+ {
+ RunProcessAsync("avrdude", $"-p {mcu} -c usbtiny -U flash:w:\"{file}\":i").Wait();
+ }
+
+ public override void FlashEeprom(string mcu, string file)
+ {
+ RunProcessAsync("avrdude", $"-p {mcu} -c usbtiny -U eeprom:w:\"{file}\":i").Wait();
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Usb/Bootloader/Wb32DfuDevice.cs b/linux/QMKToolbox/Usb/Bootloader/Wb32DfuDevice.cs
new file mode 100644
index 0000000000..301978c334
--- /dev/null
+++ b/linux/QMKToolbox/Usb/Bootloader/Wb32DfuDevice.cs
@@ -0,0 +1,32 @@
+// ReSharper disable StringLiteralTypo
+using System.IO;
+using System.Threading.Tasks;
+
+namespace QMK_Toolbox.Usb.Bootloader;
+
+internal class Wb32DfuDevice : BootloaderDevice
+{
+ public Wb32DfuDevice(KnownHidDevice d) : base(d)
+ {
+ Type = BootloaderType.Wb32Dfu;
+ Name = "WB32 DFU";
+ IsResettable = true;
+ }
+
+ public override void Flash(string mcu, string file)
+ {
+ if (Path.GetExtension(file)?.ToLower() == ".bin")
+ RunProcessAsync
+ ("/tmp/wb32-dfu-updater_cli", $"--toolbox-mode --dfuse-address 0x08000000 --download \"{file}\"").Wait();
+ else if (Path.GetExtension(file)?.ToLower() == ".hex")
+ RunProcessAsync("wb32-dfu-updater_cli", $"--toolbox-mode --download \"{file}\"").Wait();
+ else
+ PrintMessage("Only firmware files in .bin or .hex format can be flashed with wb32-dfu-updater_cli!",
+ MessageType.Error);
+ }
+
+ public override void Reset(string mcu)
+ {
+ RunProcessAsync("wb32-dfu-updater_cli", "--reset").Wait();
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Usb/KnownHidDevice.cs b/linux/QMKToolbox/Usb/KnownHidDevice.cs
new file mode 100644
index 0000000000..f0b730c5f4
--- /dev/null
+++ b/linux/QMKToolbox/Usb/KnownHidDevice.cs
@@ -0,0 +1,14 @@
+using QMK_Toolbox.Usb.Bootloader;
+
+namespace QMK_Toolbox.Usb;
+
+internal class KnownHidDevice
+{
+ public ushort VendorId { get; init; }
+ public ushort ProductId { get; init; }
+
+ public ushort RevisionBcd { get; set; } = 0x0100;
+ public string VendorName { get; init; }
+ public string ProductName { get; init; }
+ public BootloaderType BootloaderType { get; init; }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Usb/UsbDeviceNameRecord.cs b/linux/QMKToolbox/Usb/UsbDeviceNameRecord.cs
new file mode 100644
index 0000000000..aa74de0155
--- /dev/null
+++ b/linux/QMKToolbox/Usb/UsbDeviceNameRecord.cs
@@ -0,0 +1,9 @@
+namespace QMK_Toolbox.Usb;
+
+public class UsbDeviceNameRecord
+{
+ public ushort ProductId{ get; set; }
+ public ushort VendorId{ get; set; }
+ public ushort DeviceRevisionBcd{ get; set; }
+ public string ManufacturerName { get; set; }
+}
diff --git a/linux/QMKToolbox/Usb/UsbDeviceRecordComparer.cs b/linux/QMKToolbox/Usb/UsbDeviceRecordComparer.cs
new file mode 100644
index 0000000000..4e39e0d489
--- /dev/null
+++ b/linux/QMKToolbox/Usb/UsbDeviceRecordComparer.cs
@@ -0,0 +1,21 @@
+using System.Collections.Generic;
+
+namespace QMK_Toolbox.Usb;
+
+internal class UsbDeviceRecordComparer : IEqualityComparer
+{
+ public bool Equals(UsbDeviceNameRecord x, UsbDeviceNameRecord y)
+ {
+ if (x.VendorId == y.VendorId && x.ProductId == y.ProductId)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ public int GetHashCode(UsbDeviceNameRecord obj)
+ {
+ return obj.VendorId.GetHashCode() + obj.ProductId.GetHashCode();
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Usb/UsbDeviceRecordHelper.cs b/linux/QMKToolbox/Usb/UsbDeviceRecordHelper.cs
new file mode 100644
index 0000000000..6fdab1b41c
--- /dev/null
+++ b/linux/QMKToolbox/Usb/UsbDeviceRecordHelper.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace QMK_Toolbox.Usb;
+
+public class UsbDeviceRecordHelper
+{
+ private StringBuilder StringBuilder { get; set; }
+
+ public List GetUsbDeviceRecordsWithNames(ushort vendorId=0, ushort productId=0)
+ {
+ var usbDeviceRecordList = new List();
+ if (vendorId == 0 && productId == 0)
+ RunProcessAsync("lsusb","").Wait();
+ else
+ RunProcessAsync("lsusb",$"-d {vendorId:x4}:{productId:x4}").Wait();
+
+ var outputLines= StringBuilder.ToString().Split('\n');
+ foreach (var line in outputLines)
+ {
+ var deviceRecord = new UsbDeviceNameRecord();
+ if (string.IsNullOrEmpty(line))
+ continue;
+ var split = line.Split(" ");
+ var productVendorSplit = split[5].Split(":");
+ deviceRecord.VendorId=Convert.ToUInt16(productVendorSplit[0], 16);
+ deviceRecord.ProductId= Convert.ToUInt16(split[1], 16);
+
+ var sb= new StringBuilder();
+ for (var i = 6; i < split.Length; i++)
+ {
+ sb.Append(split[i]);
+ sb.Append(" ");
+ }
+ deviceRecord.ManufacturerName = sb.ToString();
+ usbDeviceRecordList.Add(deviceRecord);
+ }
+
+ return usbDeviceRecordList;
+ }
+
+
+ private async Task RunProcessAsync(string command, string args)
+ {
+ StringBuilder = new StringBuilder();
+ using var process = new Process
+ {
+ StartInfo =
+ {
+ FileName = command,
+ Arguments = args,
+ WorkingDirectory = Path.GetDirectoryName(command) ?? "",
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true
+ },
+ EnableRaisingEvents = true
+ };
+ return await RunProcessAsync(process).ConfigureAwait(false);
+ }
+
+ private Task RunProcessAsync(Process process)
+ {
+ var tcs = new TaskCompletionSource();
+
+ process.Exited += (_, _) =>
+ {
+ process.WaitForExit();
+ tcs.SetResult(process.ExitCode);
+ };
+
+ process.OutputDataReceived += ProcessOutput;
+ process.ErrorDataReceived += ProcessErrorOutput;
+
+ var started = process.Start();
+ if (!started) Debug.WriteLine($"Could not start process: {process}");
+
+ process.BeginOutputReadLine();
+ process.BeginErrorReadLine();
+
+ return tcs.Task;
+ }
+
+ private void ProcessOutput(object sender, DataReceivedEventArgs e)
+ {
+ if (e.Data != null)
+ StringBuilder.AppendLine(e.Data);
+ }
+
+ private static void ProcessErrorOutput(object _, DataReceivedEventArgs e)
+ {
+ if (e.Data != null)
+ Debug.WriteLine(e.Data);
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Usb/UsbListener.cs b/linux/QMKToolbox/Usb/UsbListener.cs
new file mode 100644
index 0000000000..2984eb2eb9
--- /dev/null
+++ b/linux/QMKToolbox/Usb/UsbListener.cs
@@ -0,0 +1,424 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+using LibUsbDotNet.LibUsb;
+using LibUsbDotNet.Main;
+using QMK_Toolbox.Usb.Bootloader;
+
+// ReSharper disable StringLiteralTypo
+
+namespace QMK_Toolbox.Usb;
+
+internal class UsbListener
+{
+ public delegate void BootloaderDeviceEventDelegate(BootloaderDevice device);
+
+ public delegate void FlashOutputReceivedDelegate(BootloaderDevice device, string data, MessageType type);
+
+ private readonly List _attachedDevices = new();
+ private readonly List _knownDevices = new();
+ private Task _backgroundTask;
+
+ public BootloaderDeviceEventDelegate BootloaderDeviceConnected;
+ public BootloaderDeviceEventDelegate BootloaderDeviceDisconnected;
+
+ public FlashOutputReceivedDelegate OutputReceived;
+
+ public UsbListener()
+ {
+ PopulateKnownDevices();
+ }
+
+ public void Start()
+ {
+ _backgroundTask = new Task(CheckKnownDevicesConnectionState, TaskCreationOptions.LongRunning);
+ _backgroundTask.Start();
+ }
+
+
+ private void FlashOutputReceived(BootloaderDevice device, string data, MessageType type)
+ {
+ OutputReceived?.Invoke(device, data, type);
+ }
+
+ private static void GetNewUsbDevices(List listNew)
+ {
+ using UsbContext context = new UsbContext();
+ var allDevices = context.List();
+
+ foreach (var dev in allDevices)
+ {
+ var deviceRevisionBcd = dev.Info.Device;
+ if (listNew.Any(x => x.ProductId == dev.ProductId && x.VendorId == dev.VendorId))
+ continue;
+ {
+ var manufacturer = "n/a";
+
+ listNew.Add(new UsbDeviceNameRecord()
+ {
+ VendorId = dev.VendorId, ProductId = dev.ProductId,
+ DeviceRevisionBcd = deviceRevisionBcd, ManufacturerName = manufacturer
+ });
+ }
+ }
+ }
+
+ private void CheckKnownDevicesConnectionState()
+ {
+ List listOld = new();
+ List listNew = new();
+ var helper = new UsbDeviceRecordHelper();
+
+ GetNewUsbDevices(listNew);
+
+ while (true)
+ {
+ listOld = new ();
+ listOld.AddRange(listNew);
+ listNew = new();
+
+ // we are polling 2x per sec for a list of usb devices on this background
+ // thread.
+ Thread.Sleep(500);
+
+ GetNewUsbDevices(listNew);
+
+ var added = listNew.Except(listOld, new UsbDeviceRecordComparer()).ToList();
+ var removed = listOld.Except(listNew, new UsbDeviceRecordComparer()).ToList();
+ if (added.Count > 0)
+ {
+ var item = added.FirstOrDefault();
+ Debug.Assert(item!=null);
+
+ var device =_knownDevices.FirstOrDefault(x=>x.VendorId == item.VendorId &&
+ x.ProductId == item.ProductId);
+ if (device == null)
+ {
+ Console.WriteLine(@"Could not find device with VendorId: {0}, ProductId: {1}",
+ item.VendorId, item.ProductId);
+ continue;
+ }
+
+ // we take the device bcd from the actual device, not from our table
+ // the BootloaderDevice subclasses will handle it.
+ device.RevisionBcd = item.DeviceRevisionBcd;
+ var bootloaderDevice = CreateDevice(device);
+ _attachedDevices.Add(device);
+ BootloaderDeviceConnected?.Invoke(bootloaderDevice);
+ bootloaderDevice.OutputReceived = FlashOutputReceived;
+ }
+
+ if (removed.Count > 0)
+ {
+ var item = removed.FirstOrDefault();
+ Debug.Assert(item != null);
+ var device =_knownDevices.FirstOrDefault(x=>x.VendorId == item.VendorId &&
+ x.ProductId == item.ProductId);
+ if (device == null)
+ continue;
+ var bootloaderDevice = CreateDevice(device);
+ if (bootloaderDevice != null)
+ {
+ BootloaderDeviceDisconnected?.Invoke(bootloaderDevice);
+ bootloaderDevice.OutputReceived = null;
+ }
+ }
+
+ added.Clear();
+ removed.Clear();
+ }
+ }
+
+
+ private static BootloaderDevice CreateDevice(KnownHidDevice device)
+ {
+ switch (device.BootloaderType)
+ {
+ case BootloaderType.Apm32Dfu:
+ return new Apm32DfuDevice(device);
+ case BootloaderType.AtmelDfu:
+ case BootloaderType.QmkDfu:
+ return new AtmelDfuDevice(device);
+ case BootloaderType.AtmelSamBa:
+ return new AtmelSamBaDevice(device);
+ case BootloaderType.AvrIsp:
+ return new AvrIspDevice(device);
+ case BootloaderType.BootloadHid:
+ return new BootloadHidDevice(device);
+ case BootloaderType.Caterina:
+ return new CaterinaDevice(device);
+ case BootloaderType.Gd32VDfu:
+ return new Gd32VDfuDevice(device);
+ case BootloaderType.HalfKay:
+ return new HalfKayDevice(device);
+ case BootloaderType.KiibohdDfu:
+ return new KiibohdDfuDevice(device);
+ case BootloaderType.LufaHid:
+ case BootloaderType.QmkHid:
+ return new LufaHidDevice(device);
+ case BootloaderType.LufaMs:
+ return new LufaMsDevice(device);
+ case BootloaderType.Stm32Dfu:
+ return new Stm32DfuDevice(device);
+ case BootloaderType.Stm32Duino:
+ return new Stm32DuinoDevice(device);
+ case BootloaderType.UsbAsp:
+ return new UsbAspDevice(device);
+ case BootloaderType.UsbTinyIsp:
+ return new UsbTinyIspDevice(device);
+ case BootloaderType.Wb32Dfu:
+ return new Wb32DfuDevice(device);
+ }
+ return null;
+ }
+
+ private void PopulateKnownDevices()
+ {
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x03eb, ProductId = 0x2045,
+ VendorName = "Amtel", ProductName = "Bootloader", BootloaderType = BootloaderType.LufaMs
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x03eb, ProductId = 0x2067, RevisionBcd = 0x0936,
+ VendorName = "Amtel", ProductName = "Bootloader QmkHid", BootloaderType = BootloaderType.QmkHid
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x03eb, ProductId = 0x2067,
+ VendorName = "Amtel", ProductName = "Bootloader QmkHid", BootloaderType = BootloaderType.LufaHid
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x03eb, ProductId = 0x2fef,
+ VendorName = "Amtel", ProductName = "ATMega16U2", BootloaderType = BootloaderType.AtmelDfu
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x03eb, ProductId = 0x2ff0,
+ VendorName = "Amtel", ProductName = "ATmega32U2", BootloaderType = BootloaderType.AtmelDfu
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x03eb, ProductId = 0x2ff3,
+ VendorName = "Amtel", ProductName = "ATmega16U4", BootloaderType = BootloaderType.AtmelDfu
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x03eb, ProductId = 0x2ff4,
+ VendorName = "Amtel", ProductName = "ATmega32U4", BootloaderType = BootloaderType.AtmelDfu
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x03eb, ProductId = 0x2ff9,
+ VendorName = "Amtel", ProductName = "AT90USB64", BootloaderType = BootloaderType.AtmelDfu
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x03eb, ProductId = 0x2ffa,
+ VendorName = "Amtel", ProductName = "AT90USB64r", BootloaderType = BootloaderType.AtmelDfu
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x03eb, ProductId = 0x2ffb,
+ VendorName = "Amtel", ProductName = "AT90USB128", BootloaderType = BootloaderType.AtmelDfu
+ });
+ //
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x03eb, ProductId = 0x2067, RevisionBcd = 0x0936,
+ VendorName = "Amtel", ProductName = "Bootloader QmkHid", BootloaderType = BootloaderType.LufaHid
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x03eb, ProductId = 0x2fef, RevisionBcd = 0x0936,
+ VendorName = "Amtel", ProductName = "ATMega16U2", BootloaderType = BootloaderType.QmkDfu
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x03eb, ProductId = 0x2ff0, RevisionBcd = 0x0936,
+ VendorName = "Amtel", ProductName = "ATmega32U2", BootloaderType = BootloaderType.QmkDfu
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x03eb, ProductId = 0x2ff3, RevisionBcd = 0x0936,
+ VendorName = "Amtel", ProductName = "ATmega16U4", BootloaderType = BootloaderType.QmkDfu
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x03eb, ProductId = 0x2ff4, RevisionBcd = 0x0936,
+ VendorName = "Amtel", ProductName = "ATmega32U4", BootloaderType = BootloaderType.QmkDfu
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x03eb, ProductId = 0x2ff9, RevisionBcd = 0x0936,
+ VendorName = "Amtel", ProductName = "AT90USB64", BootloaderType = BootloaderType.QmkDfu
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x03eb, ProductId = 0x2ffa, RevisionBcd = 0x0936,
+ VendorName = "Amtel", ProductName = "AT90USB64r", BootloaderType = BootloaderType.QmkDfu
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x03eb, ProductId = 0x2ffb, RevisionBcd = 0x0936,
+ VendorName = "Amtel", ProductName = "AT90USB128", BootloaderType = BootloaderType.QmkDfu
+ });
+
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x0438, ProductId = 0x0000,
+ VendorName = "STMicroelectronics", ProductName = "STM Device in DFU Mode",
+ BootloaderType = BootloaderType.Stm32Dfu
+ });
+
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x2045, ProductId = 0x0000,
+ VendorName = "Generic", ProductName = "Keyboardio Atreus 2 Bootloader",
+ BootloaderType = BootloaderType.Caterina
+ });
+
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x1209, ProductId = 0x0478,
+ VendorName = "Van Ooijen Technische Informatica", ProductName = "Teensy Halfkay Bootloader",
+ BootloaderType = BootloaderType.HalfKay
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x1209, ProductId = 0x483,
+ VendorName = "Van Ooijen Technische Informatica", ProductName = "Teensyduino Serial",
+ BootloaderType = BootloaderType.AvrIsp
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x1209, ProductId = 0x05dc,
+ VendorName = "Van Ooijen Technische Informatica", ProductName = "Teensy Halfkay Bootloader",
+ BootloaderType = BootloaderType.UsbAsp
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x1209, ProductId = 0x5df,
+ VendorName = "Van Ooijen Technische Informatica", ProductName = "SharedID for USB",
+ BootloaderType = BootloaderType.BootloadHid
+ });
+
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 1781, ProductId = 0x0,
+ VendorName = "Mechanique", ProductName = "USB Tiny",
+ BootloaderType = BootloaderType.UsbTinyIsp
+ });
+
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x1b4f, ProductId = 0x9203,
+ VendorName = "Spark Fun Electronics", ProductName = "Pro Micro 3V3/8MHz",
+ BootloaderType = BootloaderType.Caterina
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x1b4f, ProductId = 0x9205,
+ VendorName = "Spark Fun Electronics", ProductName = "Pro Micro 5V/16MHz",
+ BootloaderType = BootloaderType.Caterina
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x1b4f, ProductId = 0x9207,
+ VendorName = "Spark Fun Electronics", ProductName = "LilyPad 3V3/8MHz (and some Pro Micro clones)",
+ BootloaderType = BootloaderType.Caterina
+ });
+
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x1c11, ProductId = 0xb007,
+ VendorName = "Input Club Inc.", ProductName = "Kiibohd DFU",
+ BootloaderType = BootloaderType.KiibohdDfu
+ });
+
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x1eaf, ProductId = 0x0003,
+ VendorName = "Leaf Labs", ProductName = "A-Star 32U4",
+ BootloaderType = BootloaderType.Stm32Duino
+ });
+
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x1ffb, ProductId = 0x0003,
+ VendorName = "Pololu Corporation", ProductName = "STM32Duino",
+ BootloaderType = BootloaderType.Caterina
+ });
+
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x2341, ProductId = 0x0036,
+ VendorName = "Arduino SA", ProductName = "Arduino Leonardo (bootloader)",
+ BootloaderType = BootloaderType.Caterina
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x2341, ProductId = 0x0037,
+ VendorName = "Arduino SA", ProductName = "Arduino Micro (bootloader)",
+ BootloaderType = BootloaderType.Caterina
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x2a03, ProductId = 0x0036,
+ VendorName = "dog hunter AG", ProductName = "Arduino Leonardo (bootloader)",
+ BootloaderType = BootloaderType.Caterina
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x2a03, ProductId = 0x0037,
+ VendorName = "dog hunter AG", ProductName = "Arduino Micro (bootloader)",
+ BootloaderType = BootloaderType.Caterina
+ });
+
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x2398, ProductId = 0x000c,
+ VendorName = "AdaFruit", ProductName = "Feather 32U4",
+ BootloaderType = BootloaderType.Caterina
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x2398, ProductId = 0x000d,
+ VendorName = "AdaFruit", ProductName = "ItsyBitsy 32U4 3V3/8MHz",
+ BootloaderType = BootloaderType.Caterina
+ });
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x2398, ProductId = 0x000e,
+ VendorName = "AdaFruit", ProductName = "ItsyBitsy 32U4 5V/16MHz",
+ BootloaderType = BootloaderType.Caterina
+ });
+
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x28e9, ProductId = 0x0189,
+ VendorName = "GDMicroelectronics", ProductName = "GD32 DFU Bootloader (Longan Nano)",
+ BootloaderType = BootloaderType.Gd32VDfu
+ });
+
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x314b, ProductId = 0x0106,
+ VendorName = "Geehy Semiconductor Co. Ltd.", ProductName = "APM32 DFU Bootloader",
+ BootloaderType = BootloaderType.Apm32Dfu
+ });
+
+ _knownDevices.Add(new KnownHidDevice
+ {
+ VendorId = 0x3420, ProductId = 0x0003,
+ VendorName = "WestBerryTech", ProductName = "WB32 DFU Bootloader",
+ BootloaderType = BootloaderType.Wb32Dfu
+ });
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/ViewLocator.cs b/linux/QMKToolbox/ViewLocator.cs
new file mode 100644
index 0000000000..fcfdd4ecfe
--- /dev/null
+++ b/linux/QMKToolbox/ViewLocator.cs
@@ -0,0 +1,24 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Controls.Templates;
+using QMK_Toolbox.ViewModels;
+
+namespace QMK_Toolbox;
+
+public class ViewLocator : IDataTemplate
+{
+ public IControl Build(object data)
+ {
+ var name = data.GetType().FullName!.Replace("ViewModel", "View");
+ var type = Type.GetType(name);
+
+ if (type != null) return (Control)Activator.CreateInstance(type)!;
+
+ return new TextBlock { Text = "Not Found: " + name };
+ }
+
+ public bool Match(object data)
+ {
+ return data is ViewModelBase;
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/ViewModels/MainWindowViewModel.cs b/linux/QMKToolbox/ViewModels/MainWindowViewModel.cs
new file mode 100644
index 0000000000..bbd5e1661c
--- /dev/null
+++ b/linux/QMKToolbox/ViewModels/MainWindowViewModel.cs
@@ -0,0 +1,324 @@
+
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Metadata;
+using Avalonia.Threading;
+using QMK_Toolbox.Helpers;
+using QMK_Toolbox.Properties;
+using QMK_Toolbox.Usb;
+using QMK_Toolbox.Usb.Bootloader;
+using QMK_Toolbox.Views;
+using ReactiveUI;
+
+namespace QMK_Toolbox.ViewModels;
+
+public class MainWindowViewModel : ViewModelBase
+{
+ internal readonly string Prompt = "Click Open or drag to window to select file";
+ private string _hexFile;
+ private List _mcus;
+ private int _selectedMcu=10;
+ private bool _isAutoFlash;
+ private readonly StringBuilder _log = new();
+ private readonly string _arg;
+ private readonly IWindow _mainWindow;
+ private readonly UsbListener _usbListener;
+ private BootloaderDevice _currentBootloader;
+ private bool _canOpenFile = true;
+ private bool _canFlash;
+ private bool _canReset;
+ private bool _canClearEeprom;
+ private bool _canAutoFlash = true;
+
+ public MainWindowViewModel(string arg, IWindow mainWindow)
+ {
+ _arg = arg;
+ _mainWindow = mainWindow;
+ _usbListener = new UsbListener();
+ _usbListener.Start();
+ if (string.IsNullOrEmpty(Settings.Default.hexFile))
+ HexFile = Prompt;
+ else
+ HexFile = Settings.Default.hexFile;
+ }
+
+ // ReSharper disable once InconsistentNaming
+ private void EnableUI()
+ {
+ Task.Delay(1).ContinueWith( t => Dispatcher.UIThread.InvokeAsync(
+ () =>
+ {
+ CanOpenFile = true;
+ CanFlash = true;
+ CanReset = true;
+ CanClearEeprom = true;
+ CanAutoFlash = true;
+ }));
+ }
+
+ // ReSharper disable once InconsistentNaming
+ private void DisableUI()
+ {
+ Task.Delay(1).ContinueWith( t => Dispatcher.UIThread.InvokeAsync(
+ () =>
+ {
+ CanOpenFile = true;
+ CanFlash = false;
+ CanReset = false;
+ CanClearEeprom = false;
+ CanAutoFlash = true;
+ }));
+ }
+
+ public bool CanAutoFlash
+ {
+ get => _canAutoFlash;
+ set => this.RaiseAndSetIfChanged(ref _canAutoFlash, value);
+ }
+
+ public bool CanOpenFile
+ {
+ get => _canOpenFile;
+ set => this.RaiseAndSetIfChanged(ref _canOpenFile, value);
+ }
+
+ public bool CanFlash
+ {
+ get => _canFlash;
+ set => this.RaiseAndSetIfChanged(ref _canFlash, value);
+ }
+
+ public bool CanReset
+ {
+ get => _canReset;
+ set => this.RaiseAndSetIfChanged(ref _canReset, value);
+ }
+
+ public bool CanClearEeprom
+ {
+ get => _canClearEeprom;
+ set => this.RaiseAndSetIfChanged(ref _canClearEeprom, value);
+ }
+
+ public List Mcus
+ {
+ get => _mcus;
+ set => this.RaiseAndSetIfChanged(ref _mcus, value);
+ }
+
+ public string Log => _log.ToString();
+
+ private void AddToLog(string text, bool notify=true)
+ {
+ _log.AppendLine(text);
+ if (notify)
+ this.RaisePropertyChanged(nameof(Log));
+ }
+
+ public bool IsAutoFlash
+ {
+ get => _isAutoFlash;
+ set => this.RaiseAndSetIfChanged(ref _isAutoFlash, value);
+ }
+
+ public int SelectedMcuIndex
+ {
+ get => _selectedMcu;
+ set => this.RaiseAndSetIfChanged(ref _selectedMcu, value);
+ }
+
+ private AssemblyFileVersionAttribute VersionAttribute =>
+ typeof(MainWindowViewModel).Assembly
+ .GetCustomAttributes(typeof(AssemblyFileVersionAttribute), false).FirstOrDefault()
+ as AssemblyFileVersionAttribute;
+
+ public void InitUi()
+ {
+ Mcus = PopulateMcuListFromResourceFile();
+ UpdateHexFileFromCommandLineArguments();
+ RestoreLastSavedHexFile();
+ ShowInitialLogMessages();
+ SubscribeToEvents();
+ /*
+ The extraction of embedded resources is nice, but more work is needed on Linux for this
+ The installers for the flash programmers do an allow-listing for them to be able to access the
+ USB device in write mode. Also, all the dependencies of all the installers need to be
+ fully taken into consideration. For now, we will assume that the flash drivers have been
+ installed and are available. See discussion here: https://github.com/qmk/qmk_toolbox/issues/59
+ */
+ // EmbeddedResourceHelper.ExtractResources(EmbeddedResourceHelper.Resources);
+ }
+
+
+ private void SubscribeToEvents()
+ {
+ _usbListener.OutputReceived += (_, data, _) => { AddToLog(data); };
+ _usbListener.BootloaderDeviceConnected += (device) =>
+ {
+ EnableUI();
+ AddToLog($"Bootloader device connected: {device.ManufacturerString} - " +
+ $"{device.ProductString}({device.VendorId:x4}:{device.ProductId:x4})");
+ _currentBootloader = device;
+ _currentBootloader.OutputReceived += (_, data, _) => { AddToLog(data); };
+ if (_isAutoFlash)
+ FlashCommand();
+ };
+ _usbListener.BootloaderDeviceDisconnected += (device) =>
+ {
+ DisableUI();
+ AddToLog($"Bootloader device disconnected: {device.ManufacturerString} - +" +
+ $"{device.ProductString}({device.VendorId:x4}:{device.ProductId:x4})");
+ _currentBootloader = null;
+ };
+ }
+
+ private void RestoreLastSavedHexFile()
+ {
+ if (Settings.Default.hexFile != null && string.IsNullOrEmpty(_arg))
+ {
+ HexFile = Settings.Default.hexFile;
+ }
+ }
+
+ private void UpdateHexFileFromCommandLineArguments()
+ {
+ if (string.IsNullOrEmpty(_arg))
+ return;
+ var extension = Path.GetExtension(_arg)?.ToLower();
+ if (extension is ".hex" or ".bin")
+ {
+ HexFile = _arg;
+ }
+ else
+ {
+ _mainWindow
+ .ShowMessage("QMK Toolbox doesn't support this kind of file, only .hex and .bin are supported.");
+ }
+ }
+
+ private void ShowInitialLogMessages()
+ {
+ AddToLog($"* QMK Toolbox {VersionAttribute.Version} (https://qmk.fm/toolbox)", false);
+ AddToLog("* Supported bootloaders:", false);
+ AddToLog(
+ "* - ARM DFU (APM32, Kiibohd, STM32, STM32duino) and RISC-V DFU (GD32V) via dfu-util (http://dfu-util.sourceforge.net/)",
+ false);
+ AddToLog("* - Atmel/LUFA/QMK DFU via dfu-programmer (http://dfu-programmer.github.io/)", false);
+ AddToLog("* - Atmel SAM-BA (Massdrop) via Massdrop Loader (https://github.com/massdrop/mdloader)", false);
+ AddToLog(
+ "* - BootloadHID (Atmel, PS2AVRGB) via bootloadHID (https://www.obdev.at/products/vusb/bootloadhid.html)",
+ false);
+ AddToLog("* - Caterina (Arduino, Pro Micro) via avrdude (http://nongnu.org/avrdude/)", false);
+ AddToLog("* - HalfKay (Teensy, Ergodox EZ) via Teensy Loader (https://pjrc.com/teensy/loader_cli.html)", false);
+ AddToLog("* - LUFA/QMK HID via hid_bootloader_cli (https://github.com/abcminiuser/lufa)", false);
+ AddToLog("* - WB32 DFU via wb32-dfu-updater_cli (https://github.com/WestberryTech/wb32-dfu-updater)", false);
+ AddToLog("* - LUFA Mass Storage", false);
+ AddToLog("* Supported ISP flashers:", false);
+ AddToLog("* - AVRISP (Arduino ISP)", false);
+ AddToLog("* - USBasp (AVR ISP)", false);
+ AddToLog("* - USBTiny (AVR Pocket)", true);
+ }
+
+ public string HexFile
+ {
+ get => _hexFile;
+ set
+ {
+ this.RaiseAndSetIfChanged(ref _hexFile, value);
+ _hexFile = value;
+ Settings.Default.hexFile = value;
+ Settings.Default.Save();
+ }
+ }
+
+ public void CloseCommand()
+ {
+ Task.Delay(1).ContinueWith(_ => Dispatcher.UIThread.InvokeAsync(
+ () => { _mainWindow.OnClose(); }));
+ }
+
+ public void OpenFileCommand()
+ {
+ Task.Delay(30).ContinueWith(_ => Dispatcher.UIThread.InvokeAsync(
+ () =>
+ { _mainWindow.OnFileOpen(); }));
+ }
+
+ public void AutoFlashMenuCommand()
+ {
+ CanAutoFlash = !CanAutoFlash;
+ }
+
+ [DependsOn(nameof(CanAutoFlash))]
+ public bool CanAutoFlashMenuCommand()
+ {
+ return _canAutoFlash;
+ }
+
+ [DependsOn(nameof(CanOpenFile))]
+ public bool CanOpenFileCommand(object _)
+ {
+ return _canOpenFile;
+ }
+
+ public void FlashCommand()
+ {
+ DisableUI();
+ _currentBootloader.Flash(Mcus[SelectedMcuIndex], HexFile);
+ EnableUI();
+ }
+
+ [DependsOn(nameof(CanFlash))]
+ public bool CanFlashCommand(object _)
+ {
+ return _canFlash;
+ }
+
+ // ReSharper disable once InconsistentNaming
+ public void ClearEEPromCommand()
+ {
+ DisableUI();
+ _currentBootloader.FlashEeprom(Mcus[SelectedMcuIndex], HexFile);
+ EnableUI();
+ }
+
+ // ReSharper disable once InconsistentNaming
+ [DependsOn(nameof(CanClearEeprom))]
+ public bool CanClearEEPromCommand(object _)
+ {
+ return _canClearEeprom;
+ }
+
+ public void ExitDfuCommand()
+ {
+ DisableUI();
+ _currentBootloader.Reset(Mcus[SelectedMcuIndex]);
+ EnableUI();
+ }
+
+ [DependsOn(nameof(CanReset))]
+ public bool CanExitDfuCommand(object _)
+ {
+ return _canReset;
+ }
+
+ private List PopulateMcuListFromResourceFile()
+ {
+ List mcuList = new();
+ var microcontrollers = EmbeddedResourceHelper.GetResourceContent("mcu-list.txt").Split('\n');
+
+ foreach (var microcontroller in microcontrollers)
+ {
+ if (microcontroller.Length <= 0)
+ continue;
+ var parts = microcontroller.Split(':');
+ mcuList.Add(parts[0]);
+ }
+
+ return mcuList;
+ }
+}
diff --git a/linux/QMKToolbox/ViewModels/ViewModelBase.cs b/linux/QMKToolbox/ViewModels/ViewModelBase.cs
new file mode 100644
index 0000000000..93bfd269a0
--- /dev/null
+++ b/linux/QMKToolbox/ViewModels/ViewModelBase.cs
@@ -0,0 +1,7 @@
+using ReactiveUI;
+
+namespace QMK_Toolbox.ViewModels;
+
+public class ViewModelBase : ReactiveObject
+{
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Views/IWindow.cs b/linux/QMKToolbox/Views/IWindow.cs
new file mode 100644
index 0000000000..d0944d14bd
--- /dev/null
+++ b/linux/QMKToolbox/Views/IWindow.cs
@@ -0,0 +1,10 @@
+0using System.Threading.Tasks;
+
+namespace QMK_Toolbox.Views;
+
+public interface IWindow
+{
+ public void ShowMessage(string message);
+ public void OnClose();
+ public Task OnFileOpen();
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/Views/MainWindow.axaml b/linux/QMKToolbox/Views/MainWindow.axaml
new file mode 100644
index 0000000000..61bd8340ca
--- /dev/null
+++ b/linux/QMKToolbox/Views/MainWindow.axaml
@@ -0,0 +1,268 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/linux/QMKToolbox/Views/MainWindow.axaml.cs b/linux/QMKToolbox/Views/MainWindow.axaml.cs
new file mode 100644
index 0000000000..b2ea1cb29f
--- /dev/null
+++ b/linux/QMKToolbox/Views/MainWindow.axaml.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Avalonia.Controls.Platform;
+using Avalonia.Input;
+using Avalonia.Threading;
+using MessageBox.Avalonia;
+
+namespace QMK_Toolbox.Views;
+
+public partial class MainWindow : Window, IWindow
+{
+ private TextBlock _hexTextBlock;
+ private Border _border;
+ public MainWindow()
+ {
+ InitializeComponent();
+
+
+ AddHandler(DragDrop.DropEvent, Drop);
+ AddHandler(DragDrop.DragOverEvent, DragOver);
+ }
+
+ private void OnInitialized(object sender, EventArgs e)
+ {
+ Task.Delay(50).ContinueWith( t => Dispatcher.UIThread.InvokeAsync(
+ () =>
+ {
+ var vm = ((App)App.Current).MainWindowViewModel;
+ vm.InitUi();
+ _hexTextBlock = this.Find("HexFile");
+ _border = this.Find("border");
+ _border.PointerPressed += DoDrag;
+ } ) );
+ }
+
+ public void ShowMessage(string message)
+ {
+ var messageBoxStandardWindow = MessageBoxManager
+ .GetMessageBoxStandardWindow("QMK Toolbox - File Type Error",
+ message);
+ messageBoxStandardWindow.Show();
+ }
+
+ public void OnClose()
+ {
+ Close();
+ }
+
+ public async Task OnFileOpen()
+ {
+ var vm = ((App)App.Current).MainWindowViewModel;
+ var hexFile = await GetPath();
+ vm.HexFile = string.IsNullOrEmpty(hexFile) ? vm.Prompt : hexFile;
+ }
+
+ private async Task GetPath()
+ {
+ var dialog = new OpenFileDialog();
+ if (dialog.Filters != null)
+ {
+ dialog.Filters.Add(new FileDialogFilter() { Name = "Hex", Extensions = { "hex" } });
+ dialog.Filters.Add(new FileDialogFilter() { Name = "Bin", Extensions = { "bin" } });
+ }
+ dialog.AllowMultiple = false;
+ var result = await dialog.ShowAsync(this);
+ return result?[0];
+ }
+
+ private async void DoDrag(object sender, Avalonia.Input.PointerPressedEventArgs e)
+ {
+ DataObject dragData = new DataObject();
+ var result = await DragDrop.DoDragDrop(e, dragData, DragDropEffects.Copy);
+ }
+
+ private void DragOver(object sender, DragEventArgs e)
+ {
+ Debug.WriteLine("DragOver");
+ // Only allow Copy or Link as Drop Operations.
+ e.DragEffects = e.DragEffects & (DragDropEffects.Copy | DragDropEffects.Link);
+
+ // Only allow if the dragged data contains text or filenames.
+ if (!e.Data.Contains(DataFormats.Text) && !e.Data.Contains(DataFormats.FileNames))
+ e.DragEffects = DragDropEffects.None;
+ }
+ private void Drop(object sender, DragEventArgs e)
+ {
+ var vm = ((App)App.Current).MainWindowViewModel;
+ Debug.WriteLine("Drop");
+ if (e.Data.Contains(DataFormats.Text))
+ vm.HexFile = e.Data.GetText();
+ else if (e.Data.Contains(DataFormats.FileNames))
+ vm.HexFile = e.Data.GetFileNames()?.FirstOrDefault();
+ }
+}
\ No newline at end of file
diff --git a/linux/QMKToolbox/app.manifest b/linux/QMKToolbox/app.manifest
new file mode 100644
index 0000000000..4ad349fa56
--- /dev/null
+++ b/linux/QMKToolbox/app.manifest
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/linux/QMKToolbox/d.diff b/linux/QMKToolbox/d.diff
new file mode 100644
index 0000000000..bfb3a33585
--- /dev/null
+++ b/linux/QMKToolbox/d.diff
@@ -0,0 +1,212 @@
+diff --git a/linux/QMKToolbox/App.axaml b/linux/QMKToolbox/App.axaml
+index a383e91..af1a745 100644
+--- a/linux/QMKToolbox/App.axaml
++++ b/linux/QMKToolbox/App.axaml
+@@ -1,12 +1,15 @@
+
++
++
+
+
+
+
+
+-
++
+
+
+\ No newline at end of file
+diff --git a/linux/QMKToolbox/Program.cs b/linux/QMKToolbox/Program.cs
+index b4da340..ec5451a 100644
+--- a/linux/QMKToolbox/Program.cs
++++ b/linux/QMKToolbox/Program.cs
+@@ -1,5 +1,6 @@
+ using System;
+ using Avalonia;
++using Avalonia.Dialogs;
+ using Avalonia.ReactiveUI;
+
+ namespace QMK_Toolbox;
+@@ -25,6 +26,7 @@ internal class Program
+ return AppBuilder.Configure()
+ .UsePlatformDetect()
+ .LogToTrace()
++ // .UseManagedSystemDialogs()
+ .UseReactiveUI();
+ }
+ }
+\ No newline at end of file
+diff --git a/linux/QMKToolbox/Properties/Resources.Designer.cs b/linux/QMKToolbox/Properties/Resources.Designer.cs
+index e9a874b..f3c3587 100644
+--- a/linux/QMKToolbox/Properties/Resources.Designer.cs
++++ b/linux/QMKToolbox/Properties/Resources.Designer.cs
+@@ -1,7 +1,6 @@
+ //------------------------------------------------------------------------------
+ //
+ // This code was generated by a tool.
+-// Runtime Version:4.0.30319.42000
+ //
+ // Changes to this file may cause incorrect behavior and will be lost if
+ // the code is regenerated.
+@@ -12,46 +11,32 @@ namespace QMK_Toolbox.Properties {
+ using System;
+
+
+- ///
+- /// A strongly-typed resource class, for looking up localized strings, etc.
+- ///
+- // This class was auto-generated by the StronglyTypedResourceBuilder
+- // class via a tool like ResGen or Visual Studio.
+- // To add or remove a member, edit your .ResX file then rerun ResGen
+- // with the /str option, or rebuild your VS project.
+- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
+- [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+- [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+- internal class Resources {
++ [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
++ [System.Diagnostics.DebuggerNonUserCodeAttribute()]
++ [System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
++ public class Resources {
+
+- private static global::System.Resources.ResourceManager resourceMan;
++ private static System.Resources.ResourceManager resourceMan;
+
+- private static global::System.Globalization.CultureInfo resourceCulture;
++ private static System.Globalization.CultureInfo resourceCulture;
+
+- [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
++ [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+- ///
+- /// Returns the cached ResourceManager instance used by this class.
+- ///
+- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+- internal static global::System.Resources.ResourceManager ResourceManager {
++ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
++ public static System.Resources.ResourceManager ResourceManager {
+ get {
+- if (object.ReferenceEquals(resourceMan, null)) {
+- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("QMK_Toolbox.Properties.Resources", typeof(Resources).Assembly);
++ if (object.Equals(null, resourceMan)) {
++ System.Resources.ResourceManager temp = new System.Resources.ResourceManager("QMK_Toolbox.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+- ///
+- /// Overrides the current thread's CurrentUICulture property for all
+- /// resource lookups using this strongly typed resource class.
+- ///
+- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+- internal static global::System.Globalization.CultureInfo Culture {
++ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
++ public static System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+@@ -59,5 +44,17 @@ namespace QMK_Toolbox.Properties {
+ resourceCulture = value;
+ }
+ }
++
++ public static string ApplicationTitle {
++ get {
++ return ResourceManager.GetString("ApplicationTitle", resourceCulture);
++ }
++ }
++
++ public static string FileMenu {
++ get {
++ return ResourceManager.GetString("FileMenu", resourceCulture);
++ }
++ }
+ }
+ }
+diff --git a/linux/QMKToolbox/Properties/Resources.resx b/linux/QMKToolbox/Properties/Resources.resx
+index af7dbeb..44be546 100644
+--- a/linux/QMKToolbox/Properties/Resources.resx
++++ b/linux/QMKToolbox/Properties/Resources.resx
+@@ -114,4 +114,10 @@
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
++
++ QMK Toolbox
++
++
++ _File
++
+
+\ No newline at end of file
+diff --git a/linux/QMKToolbox/Usb/UsbDeviceRecordHelper.cs b/linux/QMKToolbox/Usb/UsbDeviceRecordHelper.cs
+index 6fdab1b..a0f0d09 100644
+--- a/linux/QMKToolbox/Usb/UsbDeviceRecordHelper.cs
++++ b/linux/QMKToolbox/Usb/UsbDeviceRecordHelper.cs
+@@ -1,5 +1,4 @@
+-using System;
+-using System.Collections.Generic;
++using System;using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.IO;
+ using System.Text;
+diff --git a/linux/QMKToolbox/Views/MainWindow.axaml b/linux/QMKToolbox/Views/MainWindow.axaml
+index bbc9822..48a1000 100644
+--- a/linux/QMKToolbox/Views/MainWindow.axaml
++++ b/linux/QMKToolbox/Views/MainWindow.axaml
+@@ -3,6 +3,8 @@
+ 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"
+@@ -12,7 +14,7 @@
+ FontSize="10"
+ MinWidth="800"
+ MinHeight="680"
+- Title="QMK Toolbox"
++ Title="{x:Static r:Resources.ApplicationTitle}"
+ Background="#f0f0f0"
+ Icon="/Resources/qmk.ico">
+
+@@ -30,7 +32,7 @@
+ Grid.Column="0"
+ Grid.ColumnSpan="2"
+ Margin="0,3,0,3">
+-