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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +