diff --git a/src/ClipCMD.sln b/src/ClipCMD.sln index 4c5ea3a..fc2470a 100644 --- a/src/ClipCMD.sln +++ b/src/ClipCMD.sln @@ -1,9 +1,11 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.1.32421.90 +VisualStudioVersion = 17.9.34728.123 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClipCMD", "ClipCMD\ClipCMD.csproj", "{D5BA1B2C-E995-45C8-A952-8F5A809F2566}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClipCmd", "ClipCmd\ClipCmd\ClipCmd.csproj", "{27564C22-06E6-4BE7-ADF9-75F8AE48B58B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClipCmd.Desktop", "ClipCmd\ClipCmd.Desktop\ClipCmd.Desktop.csproj", "{E676843F-19A2-456C-AD4F-520438460739}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -11,15 +13,19 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D5BA1B2C-E995-45C8-A952-8F5A809F2566}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D5BA1B2C-E995-45C8-A952-8F5A809F2566}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D5BA1B2C-E995-45C8-A952-8F5A809F2566}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D5BA1B2C-E995-45C8-A952-8F5A809F2566}.Release|Any CPU.Build.0 = Release|Any CPU + {27564C22-06E6-4BE7-ADF9-75F8AE48B58B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27564C22-06E6-4BE7-ADF9-75F8AE48B58B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27564C22-06E6-4BE7-ADF9-75F8AE48B58B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {27564C22-06E6-4BE7-ADF9-75F8AE48B58B}.Release|Any CPU.Build.0 = Release|Any CPU + {E676843F-19A2-456C-AD4F-520438460739}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E676843F-19A2-456C-AD4F-520438460739}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E676843F-19A2-456C-AD4F-520438460739}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E676843F-19A2-456C-AD4F-520438460739}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {E8688A15-37F6-4D71-A342-449BA8244CFA} + SolutionGuid = {09386B0D-E2E0-426B-AB6B-D6F9849CAC6F} EndGlobalSection EndGlobal diff --git a/src/ClipCMD/.gitignore b/src/ClipCMD/.gitignore new file mode 100644 index 0000000..8afdcb6 --- /dev/null +++ b/src/ClipCMD/.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/src/ClipCMD/App.xaml b/src/ClipCMD/App.xaml deleted file mode 100644 index 58b410e..0000000 --- a/src/ClipCMD/App.xaml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - diff --git a/src/ClipCMD/App.xaml.cs b/src/ClipCMD/App.xaml.cs deleted file mode 100644 index 9ec893d..0000000 --- a/src/ClipCMD/App.xaml.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Windows; - -namespace ClipCMD; - -/// -/// Interaction logic for App.xaml -/// -public partial class App : Application -{ - public const string AppName = "ClipCMD"; - - public const string AppGuid = "68d780d4-b946-4154-8815-e4632e5ec5d8"; - - private Mutex? mutex; - - protected override void OnStartup(StartupEventArgs e) - { - mutex = new Mutex(true, AppGuid, out bool createdNew); - - if (!createdNew) - { - //App is already running! Exiting the application - _ = MessageBox.Show($"Another instance of {AppName} already running!", $"{AppName} is already running!", MessageBoxButton.OK, MessageBoxImage.Exclamation); - Current.Shutdown(); - } - - AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - - Exit += CloseHandler; - } - - protected void CloseHandler(object sender, EventArgs e) - { - mutex?.ReleaseMutex(); - } - - private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) - { - File.WriteAllText("error.log", e.ExceptionObject.ToString()); - } -} \ No newline at end of file diff --git a/src/ClipCMD/AssemblyInfo.cs b/src/ClipCMD/AssemblyInfo.cs deleted file mode 100644 index 8b5504e..0000000 --- a/src/ClipCMD/AssemblyInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Windows; - -[assembly: ThemeInfo( - ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located - //(used if a resource is not found in the page, - // or application resource dictionaries) - ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located - //(used if a resource is not found in the page, - // app, or any theme specific resource dictionaries) -)] diff --git a/src/ClipCMD/ClipCMD.csproj b/src/ClipCMD/ClipCMD.csproj deleted file mode 100644 index eb772a1..0000000 --- a/src/ClipCMD/ClipCMD.csproj +++ /dev/null @@ -1,33 +0,0 @@ - - - - WinExe - net8.0-windows7.0 - enable - true - true - 0.0.0.3 - 0.0.0.3 - 0.0.0.3 - logo.ico - - - - - Always - - - - - - - - - - - - - Always - - - \ No newline at end of file diff --git a/src/ClipCMD/ClipCmd.Desktop/ClipCmd.Desktop.csproj b/src/ClipCMD/ClipCmd.Desktop/ClipCmd.Desktop.csproj new file mode 100644 index 0000000..cc33b40 --- /dev/null +++ b/src/ClipCMD/ClipCmd.Desktop/ClipCmd.Desktop.csproj @@ -0,0 +1,27 @@ + + + WinExe + + net8.0 + enable + true + + + + app.manifest + + + + + + + + + + + + + + + diff --git a/src/ClipCMD/ClipCmd.Desktop/Program.cs b/src/ClipCMD/ClipCmd.Desktop/Program.cs new file mode 100644 index 0000000..9980c7f --- /dev/null +++ b/src/ClipCMD/ClipCmd.Desktop/Program.cs @@ -0,0 +1,29 @@ +using Avalonia; +using Avalonia.ReactiveUI; + +using System; + +namespace ClipCmd.Desktop; + +internal static class Program +{ + // 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) + { + _ = BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + } + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + { + return AppBuilder.Configure() + .UsePlatformDetect() + .WithInterFont() + .LogToTrace() + .UseReactiveUI(); + } +} \ No newline at end of file diff --git a/src/ClipCMD/ClipCmd.Desktop/app.manifest b/src/ClipCMD/ClipCmd.Desktop/app.manifest new file mode 100644 index 0000000..00c4e51 --- /dev/null +++ b/src/ClipCMD/ClipCmd.Desktop/app.manifest @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/src/ClipCMD/ClipCmd.sln b/src/ClipCMD/ClipCmd.sln new file mode 100644 index 0000000..84b1e91 --- /dev/null +++ b/src/ClipCMD/ClipCmd.sln @@ -0,0 +1,54 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32811.315 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClipCmd", "ClipCmd\ClipCmd.csproj", "{EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClipCmd.Desktop", "ClipCmd.Desktop\ClipCmd.Desktop.csproj", "{ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClipCmd.Browser", "ClipCmd.Browser\ClipCmd.Browser.csproj", "{1C1A049E-235C-4CD0-B6FA-D53AC418F4DA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClipCmd.iOS", "ClipCmd.iOS\ClipCmd.iOS.csproj", "{EBD9022F-BC83-4846-9A11-6F7F3772DC64}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClipCmd.Android", "ClipCmd.Android\ClipCmd.Android.csproj", "{7AD1DAC8-7FBE-49D5-8614-7321233DB82E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3DA99C4E-89E3-4049-9C22-0A7EC60D83D8}" + ProjectSection(SolutionItems) = preProject + Directory.Build.props = Directory.Build.props + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Release|Any CPU.Build.0 = Release|Any CPU + {ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Release|Any CPU.Build.0 = Release|Any CPU + {1C1A049E-235C-4CD0-B6FA-D53AC418F4DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C1A049E-235C-4CD0-B6FA-D53AC418F4DA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C1A049E-235C-4CD0-B6FA-D53AC418F4DA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C1A049E-235C-4CD0-B6FA-D53AC418F4DA}.Release|Any CPU.Build.0 = Release|Any CPU + {EBD9022F-BC83-4846-9A11-6F7F3772DC64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EBD9022F-BC83-4846-9A11-6F7F3772DC64}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EBD9022F-BC83-4846-9A11-6F7F3772DC64}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EBD9022F-BC83-4846-9A11-6F7F3772DC64}.Release|Any CPU.Build.0 = Release|Any CPU + {7AD1DAC8-7FBE-49D5-8614-7321233DB82E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7AD1DAC8-7FBE-49D5-8614-7321233DB82E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7AD1DAC8-7FBE-49D5-8614-7321233DB82E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7AD1DAC8-7FBE-49D5-8614-7321233DB82E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {83CB65B8-011F-4ED7-BCD3-A6CFA935EF7E} + EndGlobalSection +EndGlobal diff --git a/src/ClipCMD/ClipCmd/App.axaml b/src/ClipCMD/ClipCmd/App.axaml new file mode 100644 index 0000000..a15550a --- /dev/null +++ b/src/ClipCMD/ClipCmd/App.axaml @@ -0,0 +1,21 @@ + + + + + + + + + 12 + Microsoft YaHei + + + + + + + \ No newline at end of file diff --git a/src/ClipCMD/ClipCmd/App.axaml.cs b/src/ClipCMD/ClipCmd/App.axaml.cs new file mode 100644 index 0000000..b35fbd5 --- /dev/null +++ b/src/ClipCMD/ClipCmd/App.axaml.cs @@ -0,0 +1,58 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; + +using ClipCmd.Models; +using ClipCmd.Utilities; +using ClipCmd.ViewModels; +using ClipCmd.Views; + +using Projektanker.Icons.Avalonia; +using Projektanker.Icons.Avalonia.FontAwesome; +using Projektanker.Icons.Avalonia.MaterialDesign; + +using System; + +namespace ClipCmd; + +public partial class App : Application +{ + public override void Initialize() + { + _ = IconProvider.Current + .Register() + .Register(); + + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow(); + ClipCmdCommandHandler clipCmdCommandHandler = new ClipCmdCommandHandler(desktop.MainWindow); + desktop.MainWindow.DataContext = new MainViewModel(clipCmdCommandHandler); + + desktop.MainWindow.Closing += (sender, e) => + { + Settings.SaveSettings(); + }; + + clipCmdCommandHandler.Start(); + } + else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform) + { + /* + singleViewPlatform.MainView = new MainView() + { + DataContext = new MainViewModel() + } + */ + + throw new NotImplementedException(); + } + + base.OnFrameworkInitializationCompleted(); + } +} \ No newline at end of file diff --git a/src/ClipCMD/ClipCmd/Assets/avalonia-logo.ico b/src/ClipCMD/ClipCmd/Assets/avalonia-logo.ico new file mode 100644 index 0000000..da8d49f Binary files /dev/null and b/src/ClipCMD/ClipCmd/Assets/avalonia-logo.ico differ diff --git a/src/ClipCMD/ClipCmd/ClipCmd.csproj b/src/ClipCMD/ClipCmd/ClipCmd.csproj new file mode 100644 index 0000000..f800838 --- /dev/null +++ b/src/ClipCMD/ClipCmd/ClipCmd.csproj @@ -0,0 +1,33 @@ + + + net8.0 + enable + latest + true + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ClipCMD/ClipCmd/GlobalSuppressions.cs b/src/ClipCMD/ClipCmd/GlobalSuppressions.cs new file mode 100644 index 0000000..924b2cd --- /dev/null +++ b/src/ClipCMD/ClipCmd/GlobalSuppressions.cs @@ -0,0 +1,4 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. \ No newline at end of file diff --git a/src/ClipCMD/ClipCmd/Models/ClipCmdCommand.cs b/src/ClipCMD/ClipCmd/Models/ClipCmdCommand.cs new file mode 100644 index 0000000..ea58fbe --- /dev/null +++ b/src/ClipCMD/ClipCmd/Models/ClipCmdCommand.cs @@ -0,0 +1,6 @@ +namespace ClipCmd.Models; + +internal class ClipCmdCommand +{ + public string Script { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/src/ClipCMD/ClipCmd/Models/ClipCmdMode.cs b/src/ClipCMD/ClipCmd/Models/ClipCmdMode.cs new file mode 100644 index 0000000..93673da --- /dev/null +++ b/src/ClipCMD/ClipCmd/Models/ClipCmdMode.cs @@ -0,0 +1,7 @@ +namespace ClipCmd.Models; + +public enum ClipCmdMode +{ + Clipboard, + AutoType +} \ No newline at end of file diff --git a/src/ClipCMD/ClipCmd/Models/Settings.cs b/src/ClipCMD/ClipCmd/Models/Settings.cs new file mode 100644 index 0000000..d351d61 --- /dev/null +++ b/src/ClipCMD/ClipCmd/Models/Settings.cs @@ -0,0 +1,53 @@ +using ClipCmd.Utilities; + +using System; +using System.Diagnostics; +using System.IO; +using System.Text.Json; + +namespace ClipCmd.Models; + +public class Settings +{ + public static Settings Current { get; } = LoadSettings(); + + public bool AutoPaste { get; set; } = true; + + public int AutoTypeDelay { get; set; } + + public ClipCmdMode Mode { get; set; } + + public string Prefix { get; set; } = "#"; + + public string Suffix { get; set; } = ""; + + public static void SaveSettings() + { + if (!Directory.Exists(Configuration.ApplicationDataPath)) + { + _ = Directory.CreateDirectory(Configuration.ApplicationDataPath); + } + + File.WriteAllText(Configuration.SettingsFilePath, JsonSerializer.Serialize(Current)); + } + + private static Settings LoadSettings() + { + if (File.Exists(Configuration.SettingsFilePath)) + { + try + { + return JsonSerializer.Deserialize(File.ReadAllText(Configuration.SettingsFilePath)) ?? new Settings(); + } + catch (Exception ex) + { + Debug.WriteLine(ex.Message); + return new Settings(); + } + } + else + { + return new Settings(); + } + } +} \ No newline at end of file diff --git a/src/ClipCMD/ClipCmd/Utilities/ClipCmdCommandHandler.cs b/src/ClipCMD/ClipCmd/Utilities/ClipCmdCommandHandler.cs new file mode 100644 index 0000000..db95667 --- /dev/null +++ b/src/ClipCMD/ClipCmd/Utilities/ClipCmdCommandHandler.cs @@ -0,0 +1,100 @@ +using Avalonia.Controls; +using Avalonia.Input.Platform; + +using ClipCmd.Models; + +using SharpHook; +using SharpHook.Native; + +using System.Collections.Concurrent; +using System.Management.Automation; +using System.Text; +using System.Threading.Tasks; + +namespace ClipCmd.Utilities; + +public class ClipCmdCommandHandler(Window window) +{ + private readonly IClipboard clipboard = window.Clipboard!; + private readonly EventSimulator simulator = new EventSimulator(); + private readonly ConcurrentDictionary commands = new(); + private string lastText = string.Empty; + + public void Start() + { + _ = new TaskFactory().StartNew(async () => + { + while (true) + { + await Run(); + await Task.Delay(100); + } + }, TaskCreationOptions.LongRunning); + } + + private async Task Run() + { + string? text = await clipboard.GetTextAsync(); + + if (text is null || text == lastText) + { + return; + } + + lastText = text; + + if (!text.StartsWith(Settings.Current.Prefix) || !text.EndsWith(Settings.Current.Suffix)) + { + return; + } + + string commandName = text[Settings.Current.Prefix.Length..^Settings.Current.Suffix.Length]; + + StringBuilder outText = new StringBuilder(); + + if (commands.TryGetValue(commandName, out ClipCmdCommand? command)) + { + PowerShell powerShell = PowerShell.Create(); + _ = powerShell.AddScript(command.Script); + + foreach (PSObject commandResult in await powerShell.InvokeAsync()) + { + _ = outText.AppendLine(commandResult?.ToString() ?? string.Empty); + } + } + else if (string.IsNullOrWhiteSpace(commandName) || string.IsNullOrEmpty(Settings.Current.Suffix + Settings.Current.Prefix)) + { + return; + } + else + { + _ = outText.AppendLine("Command not found!"); + } + + await clipboard.ClearAsync(); + + if (Settings.Current.Mode == ClipCmdMode.Clipboard) + { + await clipboard.SetTextAsync(outText.ToString().TrimEnd()); + + if (Settings.Current.AutoPaste) + { + _ = simulator.SimulateKeyPress(KeyCode.VcLeftControl); + _ = simulator.SimulateKeyPress(KeyCode.VcV); + + _ = simulator.SimulateKeyRelease(KeyCode.VcLeftControl); + _ = simulator.SimulateKeyRelease(KeyCode.VcV); + } + } + else + { + foreach (char c in outText.ToString().TrimEnd()) + { + _ = simulator.SimulateTextEntry(c.ToString()); + await Task.Delay(Settings.Current.AutoTypeDelay); + } + } + + lastText = string.Empty; + } +} \ No newline at end of file diff --git a/src/ClipCMD/ClipCmd/Utilities/Configuration.cs b/src/ClipCMD/ClipCmd/Utilities/Configuration.cs new file mode 100644 index 0000000..d1f7c37 --- /dev/null +++ b/src/ClipCMD/ClipCmd/Utilities/Configuration.cs @@ -0,0 +1,24 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace ClipCmd.Utilities; + +internal static class Configuration +{ + public static string ApplicationDataPath + { + get + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "StoneRed", "ClipCMD"); + } + + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StoneRed", "ClipCMD"); + } + } + + public static string SettingsFilePath => Path.Combine(ApplicationDataPath, "settings.json"); + public static string LogFilePath => Path.Combine(ApplicationDataPath, $"ClipCMD.log"); +} \ No newline at end of file diff --git a/src/ClipCMD/ClipCmd/ViewLocator.cs b/src/ClipCMD/ClipCmd/ViewLocator.cs new file mode 100644 index 0000000..434e2dd --- /dev/null +++ b/src/ClipCMD/ClipCmd/ViewLocator.cs @@ -0,0 +1,31 @@ +using Avalonia.Controls; +using Avalonia.Controls.Templates; + +using ClipCmd.ViewModels; + +using System; + +namespace ClipCmd; +public class ViewLocator : IDataTemplate +{ + public Control? Build(object? data) + { + if (data is null) + return null; + + var name = data.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal); + 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/src/ClipCMD/ClipCmd/ViewModels/MainViewModel.cs b/src/ClipCMD/ClipCmd/ViewModels/MainViewModel.cs new file mode 100644 index 0000000..b3b010f --- /dev/null +++ b/src/ClipCMD/ClipCmd/ViewModels/MainViewModel.cs @@ -0,0 +1,81 @@ +using ClipCmd.Models; +using ClipCmd.Utilities; + +using ReactiveUI; + +namespace ClipCmd.ViewModels; + +public class MainViewModel : ViewModelBase +{ + public bool AutoTypeModeEnabled + { + get => Settings.Current.Mode == ClipCmdMode.AutoType; + set + { + Settings.Current.Mode = value ? ClipCmdMode.AutoType : ClipCmdMode.Clipboard; + + this.RaisePropertyChanged(); + this.RaisePropertyChanged(nameof(ClipboardModeEnabled)); + } + } + + public bool ClipboardModeEnabled + { + get => Settings.Current.Mode == ClipCmdMode.Clipboard; + set + { + Settings.Current.Mode = value ? ClipCmdMode.Clipboard : ClipCmdMode.AutoType; + + this.RaisePropertyChanged(); + this.RaisePropertyChanged(nameof(AutoTypeModeEnabled)); + } + } + + public int AutoTypeDelay + { + get => Settings.Current.AutoTypeDelay; + set + { + Settings.Current.AutoTypeDelay = value; + this.RaisePropertyChanged(); + } + } + + public bool AutoPaste + { + get => Settings.Current.AutoPaste; + set + { + Settings.Current.AutoPaste = value; + this.RaisePropertyChanged(); + } + } + + public string Prefix + { + get => Settings.Current.Prefix; + set + { + Settings.Current.Prefix = value; + this.RaisePropertyChanged(); + } + } + + public string Suffix + { + get => Settings.Current.Suffix; + set + { + Settings.Current.Suffix = value; + this.RaisePropertyChanged(); + } + } + + public MainViewModel(ClipCmdCommandHandler clipCmdCommandHandler) + { + } + + public MainViewModel() + { + } +} \ No newline at end of file diff --git a/src/ClipCMD/ClipCmd/ViewModels/ViewModelBase.cs b/src/ClipCMD/ClipCmd/ViewModels/ViewModelBase.cs new file mode 100644 index 0000000..1ee54b7 --- /dev/null +++ b/src/ClipCMD/ClipCmd/ViewModels/ViewModelBase.cs @@ -0,0 +1,7 @@ +using ReactiveUI; + +namespace ClipCmd.ViewModels; + +public class ViewModelBase : ReactiveObject +{ +} \ No newline at end of file diff --git a/src/ClipCMD/ClipCmd/Views/MainView.axaml b/src/ClipCMD/ClipCmd/Views/MainView.axaml new file mode 100644 index 0000000..a6fa839 --- /dev/null +++ b/src/ClipCMD/ClipCmd/Views/MainView.axaml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ClipCMD/ClipCmd/Views/MainView.axaml.cs b/src/ClipCMD/ClipCmd/Views/MainView.axaml.cs new file mode 100644 index 0000000..a525946 --- /dev/null +++ b/src/ClipCMD/ClipCmd/Views/MainView.axaml.cs @@ -0,0 +1,47 @@ +using Avalonia.Controls; + +using AvaloniaEdit; +using AvaloniaEdit.TextMate; + +using System.Diagnostics; +using System.Management.Automation.Language; + +using TextMateSharp.Grammars; + +namespace ClipCmd.Views; + +public partial class MainView : UserControl +{ + public MainView() + { + InitializeComponent(); + } + + private void UserControl_Loaded(object? sender, Avalonia.Interactivity.RoutedEventArgs e) + { + return; + //First of all you need to have a reference for your TextEditor for it to be used inside AvaloniaEdit.TextMate project. + TextEditor? textEditor = this.FindControl("TextEditor"); + + textEditor.TextChanged += (s, e) => + { + _ = Parser.ParseInput(textEditor.Text, out _, out ParseError[] parseErrors); + + if (parseErrors.Length > 0) + { + foreach (ParseError parseError in parseErrors) + { + Debug.WriteLine(parseError.Message + " at line " + parseError.Extent.StartLineNumber + " and column " + parseError.Extent.StartColumnNumber); + } + } + }; + RegistryOptions registryOptions = new RegistryOptions(ThemeName.DarkPlus); + + //Initial setup of TextMate. + TextMate.Installation _textMateInstallation = textEditor.InstallTextMate(registryOptions); + + //Here we are getting the language by the extension and right after that we are initializing grammar with this language. + //And that's all 😀, you are ready to use AvaloniaEdit with syntax highlighting! + _textMateInstallation.SetGrammar(registryOptions.GetScopeByLanguageId(registryOptions.GetLanguageByExtension(".ps1").Id)); + } +} \ No newline at end of file diff --git a/src/ClipCMD/ClipCmd/Views/MainWindow.axaml b/src/ClipCMD/ClipCmd/Views/MainWindow.axaml new file mode 100644 index 0000000..4993950 --- /dev/null +++ b/src/ClipCMD/ClipCmd/Views/MainWindow.axaml @@ -0,0 +1,12 @@ + + + \ No newline at end of file diff --git a/src/ClipCMD/ClipCmd/Views/MainWindow.axaml.cs b/src/ClipCMD/ClipCmd/Views/MainWindow.axaml.cs new file mode 100644 index 0000000..b8ead51 --- /dev/null +++ b/src/ClipCMD/ClipCmd/Views/MainWindow.axaml.cs @@ -0,0 +1,10 @@ +using Avalonia.Controls; + +namespace ClipCmd.Views; +public partial class MainWindow : Window +{ + public MainWindow() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/src/ClipCMD/ClipboardManager.cs b/src/ClipCMD/ClipboardManager.cs deleted file mode 100644 index db374d5..0000000 --- a/src/ClipCMD/ClipboardManager.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Windows; -using System.Windows.Interop; - -namespace ClipCMD; - -public class ClipboardManager -{ - public event EventHandler? ClipboardChanged; - - public ClipboardManager(Window windowSource) - { - if (PresentationSource.FromVisual(windowSource) is not HwndSource source) - { - throw new ArgumentException( - "Window source MUST be initialized first, such as in the Window's OnSourceInitialized handler." - , nameof(windowSource)); - } - - source.AddHook(WndProc); - - // get window handle for interop - IntPtr windowHandle = new WindowInteropHelper(windowSource).Handle; - - // register for clipboard events - NativeMethods.AddClipboardFormatListener(windowHandle); - } - - private void OnClipboardChanged() - { - ClipboardChanged?.Invoke(this, EventArgs.Empty); - } - - private static readonly IntPtr WndProcSuccess = IntPtr.Zero; - - private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) - { - if (msg == NativeMethods.WM_CLIPBOARDUPDATE) - { - OnClipboardChanged(); - handled = true; - } - - return WndProcSuccess; - } -} \ No newline at end of file diff --git a/src/ClipCMD/Directory.Build.props b/src/ClipCMD/Directory.Build.props new file mode 100644 index 0000000..3a82054 --- /dev/null +++ b/src/ClipCMD/Directory.Build.props @@ -0,0 +1,6 @@ + + + enable + 11.0.10 + + diff --git a/src/ClipCMD/EnumBooleanConverter.cs b/src/ClipCMD/EnumBooleanConverter.cs deleted file mode 100644 index a4759b0..0000000 --- a/src/ClipCMD/EnumBooleanConverter.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Windows; -using System.Windows.Data; - -namespace ClipCMD; - -public class EnumBooleanConverter : IValueConverter -{ - public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) - { - if (parameter is not string parameterString) - { - return DependencyProperty.UnsetValue; - } - - if (!Enum.IsDefined(value.GetType(), value)) - { - return DependencyProperty.UnsetValue; - } - - object parameterValue = Enum.Parse(value.GetType(), parameterString); - - return parameterValue.Equals(value); - } - - public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) - { - if (parameter is not string parameterString) - { - return DependencyProperty.UnsetValue; - } - - return Enum.Parse(targetType, parameterString); - } -} \ No newline at end of file diff --git a/src/ClipCMD/MainWindow.xaml b/src/ClipCMD/MainWindow.xaml deleted file mode 100644 index 5f7ade2..0000000 --- a/src/ClipCMD/MainWindow.xaml +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -