Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23a277bf00 | ||
|
|
6b1b5dcba9 | ||
|
|
a1e40bb87a | ||
|
|
64e702a0da | ||
|
|
f4f1be87dd | ||
|
|
611702eddc | ||
|
|
0be21101f0 | ||
|
|
816c55e4b5 | ||
|
|
2fb46a6e33 | ||
|
|
643ea1af3d | ||
|
|
ca665f09a8 | ||
|
|
b29fbc4ea2 | ||
|
|
3a712091d6 | ||
|
|
fcaaa2922c | ||
|
|
f7d075168f | ||
|
|
08a65eb017 | ||
|
|
93dbf73b23 | ||
|
|
9322e10913 | ||
|
|
8890a203d3 | ||
|
|
7b32b3397f | ||
|
|
7ce3ce5b3b | ||
|
|
2d749381e8 | ||
|
|
75b795abe5 | ||
|
|
d2aa8fda8d | ||
|
|
f6d8c5180b | ||
|
|
87c9e03802 | ||
|
|
c1288a1b6c | ||
|
|
dee84710a5 | ||
|
|
d4a9424fb2 | ||
|
|
f2f28e863a | ||
|
|
40292a1424 | ||
|
|
2139938496 | ||
|
|
6439c9c4bb | ||
|
|
21e50c4f0e | ||
|
|
923b00cfc8 | ||
|
|
64c693e81b | ||
|
|
1afa6cef02 | ||
|
|
bca0c378c1 | ||
|
|
6048a8330c | ||
|
|
4f75699850 | ||
|
|
6a8414c0a9 | ||
|
|
ae580599cb | ||
|
|
cee54c9364 | ||
|
|
e23ae0a47a | ||
|
|
71451f22b2 | ||
|
|
48af2275a7 |
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: Stone-Red-Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Desktop (please complete the following information):**
|
||||||
|
- OS: [e.g. iOS]
|
||||||
|
- Browser [e.g. chrome, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: enhancement
|
||||||
|
assignees: Stone-Red-Code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio Version 17
|
|
||||||
VisualStudioVersion = 17.1.32421.90
|
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClipCMD", "ClipCMD\ClipCMD.csproj", "{D5BA1B2C-E995-45C8-A952-8F5A809F2566}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
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
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
||||||
SolutionGuid = {E8688A15-37F6-4D71-A342-449BA8244CFA}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<Application x:Class="ClipCMD.App"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:local="clr-namespace:ClipCMD"
|
|
||||||
StartupUri="MainWindow.xaml">
|
|
||||||
<Application.Resources>
|
|
||||||
|
|
||||||
</Application.Resources>
|
|
||||||
</Application>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Configuration;
|
|
||||||
using System.Data;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows;
|
|
||||||
|
|
||||||
namespace ClipCMD;
|
|
||||||
/// <summary>
|
|
||||||
/// Interaction logic for App.xaml
|
|
||||||
/// </summary>
|
|
||||||
public partial class App : Application
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
)]
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>WinExe</OutputType>
|
|
||||||
<TargetFramework>net6.0-windows</TargetFramework>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<UseWPF>true</UseWPF>
|
|
||||||
<AssemblyVersion>0.0.0.2</AssemblyVersion>
|
|
||||||
<FileVersion>0.0.0.2</FileVersion>
|
|
||||||
<Version>0.0.0.2</Version>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="InputSimulator" Version="1.0.4" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
<Window x:Class="ClipCMD.MainWindow"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:local="clr-namespace:ClipCMD"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
Title="ClipCMD" Height="450" Width="800">
|
|
||||||
<DockPanel>
|
|
||||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
|
|
||||||
<Label Content="Prefix:" Width="45" />
|
|
||||||
<TextBox x:Name="prefixTextBox" KeyUp="FixTextBox_TextChanged" Text="01" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
|
|
||||||
<Label Content="Suffix:" Width="45" />
|
|
||||||
<TextBox x:Name="suffixTextBox" KeyUp="FixTextBox_TextChanged" Text="01" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<Grid DockPanel.Dock="Top" VerticalAlignment="Stretch">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition />
|
|
||||||
<ColumnDefinition />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
|
|
||||||
<DockPanel Grid.Column="0">
|
|
||||||
<Label DockPanel.Dock="Top" Content="Static Commands:" />
|
|
||||||
<TextBox DockPanel.Dock="Top" x:Name="staticCommandsTextBox" TextChanged="StaticCommandsTextBox_TextChanged" TextWrapping="Wrap" AcceptsReturn="True" VerticalAlignment="Stretch" />
|
|
||||||
</DockPanel>
|
|
||||||
|
|
||||||
<DockPanel Grid.Column="1">
|
|
||||||
<Label DockPanel.Dock="Top" Content="History:" />
|
|
||||||
<ListBox DockPanel.Dock="Top" x:Name="logListBox" VerticalAlignment="Stretch" />
|
|
||||||
</DockPanel>
|
|
||||||
</Grid>
|
|
||||||
</DockPanel>
|
|
||||||
</Window>
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Data;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Media;
|
|
||||||
|
|
||||||
using WindowsInput;
|
|
||||||
using WindowsInput.Native;
|
|
||||||
|
|
||||||
namespace ClipCMD;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Interaction logic for MainWindow.xaml
|
|
||||||
/// </summary>
|
|
||||||
public partial class MainWindow : Window
|
|
||||||
{
|
|
||||||
private readonly Dictionary<string, Func<string, string>> stuffToReplace = new()
|
|
||||||
{
|
|
||||||
{ "time", (_) => DateTime.Now.ToShortTimeString() },
|
|
||||||
{ "date", (_) => DateTime.Now.ToShortDateString() },
|
|
||||||
{ "calc", (math) => new DataTable().Compute(math.Trim()[4..], null)?.ToString() ?? string.Empty },
|
|
||||||
};
|
|
||||||
|
|
||||||
public MainWindow()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnSourceInitialized(EventArgs e)
|
|
||||||
{
|
|
||||||
base.OnSourceInitialized(e);
|
|
||||||
|
|
||||||
// Initialize the clipboard now that we have a window soruce to use
|
|
||||||
ClipboardManager windowClipboardManager = new ClipboardManager(this);
|
|
||||||
windowClipboardManager.ClipboardChanged += ClipboardManager_ClipboardChanged;
|
|
||||||
|
|
||||||
if (File.Exists("cmd.txt"))
|
|
||||||
{
|
|
||||||
staticCommandsTextBox.Text = File.ReadAllText("cmd.txt");
|
|
||||||
}
|
|
||||||
if (File.Exists("fix.txt"))
|
|
||||||
{
|
|
||||||
string[] lines = File.ReadAllLines("fix.txt");
|
|
||||||
if (lines.Length == 2)
|
|
||||||
{
|
|
||||||
prefixTextBox.Text = lines[0];
|
|
||||||
suffixTextBox.Text = lines[1];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
File.Delete("fix.txt");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IDataObject? oldData;
|
|
||||||
|
|
||||||
private void ClipboardManager_ClipboardChanged(object? sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (!Clipboard.ContainsText() || oldData is not null && Clipboard.IsCurrent(oldData))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
bool result = SafeRepeat.Start(() => Clipboard.GetText().Trim(), 100, out string? text);
|
|
||||||
|
|
||||||
if (!result || text is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!text.StartsWith(prefixTextBox.Text) || !text.EndsWith(suffixTextBox.Text))
|
|
||||||
{
|
|
||||||
oldData = Clipboard.GetDataObject();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
text = text[prefixTextBox.Text.Length..^suffixTextBox.Text.Length].Trim();
|
|
||||||
|
|
||||||
Func<string, string>? func = stuffToReplace.FirstOrDefault(v => text.StartsWith(v.Key)).Value;
|
|
||||||
|
|
||||||
string logText = text;
|
|
||||||
string? outText;
|
|
||||||
|
|
||||||
if (func is null)
|
|
||||||
{
|
|
||||||
string[]? parts = staticCommandsTextBox.Text.Split('\n').FirstOrDefault(l =>
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(l))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? startText = l.Trim().Split('>').FirstOrDefault()?.Trim();
|
|
||||||
return startText is not null && text == startText;
|
|
||||||
})?.Split('>');
|
|
||||||
|
|
||||||
if (parts?.Length == 2)
|
|
||||||
{
|
|
||||||
outText = parts[1];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
oldData = Clipboard.GetDataObject();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
outText = func(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
outText = outText.Trim();
|
|
||||||
logListBox.Items.Insert(0, $"{logText} > {outText}");
|
|
||||||
|
|
||||||
Clipboard.SetDataObject(outText);
|
|
||||||
|
|
||||||
oldData = Clipboard.GetDataObject();
|
|
||||||
|
|
||||||
InputSimulator inputSimulator = new InputSimulator();
|
|
||||||
inputSimulator.Keyboard.KeyPress(new[] { VirtualKeyCode.CONTROL, VirtualKeyCode.VK_V });
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logListBox.Items.Insert(0, $"Error: {ex.Message}");
|
|
||||||
Debug.WriteLine(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StaticCommandsTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
|
|
||||||
{
|
|
||||||
bool isError = false;
|
|
||||||
foreach (string line in staticCommandsTextBox.Text.Split('\n'))
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(line) && line.Count(c => c == '>') != 1)
|
|
||||||
{
|
|
||||||
isError = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isError)
|
|
||||||
{
|
|
||||||
staticCommandsTextBox.Foreground = Brushes.Red;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
staticCommandsTextBox.Foreground = Brushes.Black;
|
|
||||||
stuffToReplace["list"] = (_) => string.Join(", ", staticCommandsTextBox.Text.Split('\n').Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x.Split('>').FirstOrDefault()?.Trim()));
|
|
||||||
File.WriteAllText("cmd.txt", staticCommandsTextBox.Text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FixTextBox_TextChanged(object sender, System.Windows.Input.KeyEventArgs e)
|
|
||||||
{
|
|
||||||
if (prefixTextBox is null || suffixTextBox is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
File.WriteAllLines("fix.txt", new[] { prefixTextBox.Text, suffixTextBox.Text });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace ClipCMD;
|
|
||||||
|
|
||||||
internal static class NativeMethods
|
|
||||||
{
|
|
||||||
// See http://msdn.microsoft.com/en-us/library/ms649021%28v=vs.85%29.aspx
|
|
||||||
public const int WM_CLIPBOARDUPDATE = 0x031D;
|
|
||||||
|
|
||||||
public static readonly IntPtr HWND_MESSAGE = new IntPtr(-3);
|
|
||||||
|
|
||||||
// See http://msdn.microsoft.com/en-us/library/ms632599%28VS.85%29.aspx#message_only
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
|
||||||
[return: MarshalAs(UnmanagedType.Bool)]
|
|
||||||
public static extern bool AddClipboardFormatListener(IntPtr hwnd);
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace ClipCMD;
|
|
||||||
|
|
||||||
internal static class SafeRepeat
|
|
||||||
{
|
|
||||||
public static bool Start(Action action, int repeatCount)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < repeatCount; i++)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
action.Invoke();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.WriteLine(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool Start<T>(Func<T> action, int repeatCount, out T? result)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < repeatCount; i++)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
result = action.Invoke();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.WriteLine(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +1,50 @@
|
|||||||
# ClipCMD
|

|
||||||
|
|
||||||
> A simple utility program to simplify your workflow
|
> A simple utility program to simplify your workflow by executing scripts through the clipboard.
|
||||||
|
|
||||||
## What is it?
|
## What is it?
|
||||||
|
|
||||||
ClipCMD is a windows app that allows you to create macros and run them by copying them to the clipboard.
|
ClipCMD is an app that allows you to create commands and run them by copying them to the clipboard.\
|
||||||
You write a macro anywhere, copy it to your clipboard and it will be automatically replaced by the specified macro text.
|
You can write a command anywhere, copy it to your clipboard, and it will be automatically replaced by the output of the specified command script.\
|
||||||
|
ClipCMD uses [PowerShell](https://learn.microsoft.com/en-us/powershell/scripting) as the scripting language.
|
||||||
### Example
|
|
||||||
|
|
||||||
> Prefix: `_`\
|
|
||||||
> Suffix: `_`\
|
|
||||||
> Macro name: `email`\
|
|
||||||
> Macro text: `[email protected]`
|
|
||||||
|
|
||||||
> Input: `_email_`\
|
|
||||||
> Output: `[email protected]`
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
1. Download one of the [releases](https://github.com/Stone-Red-Code/ClipCMD/releases)
|
1. Download & install the latest release
|
||||||
1. Start `ClipCMD.exe`
|
- Microsoft Store: [Store Page](https://www.microsoft.com/store/productId/9NZ7PCLGPBW5)
|
||||||
1. (Optional) Set the prefix and suffix for your commands
|
- GitHub: [Releases](https://github.com/Stone-Red-Code/ClipCMD/releases)
|
||||||
1. Specify some commands in the built in text field
|
1. (Optional) Put a shortcut to the `ClipCMD.Desktop.exe` file in the installation directory into the `shell:startup` directory.
|
||||||
- Format: `[macroName]>[macroText]`
|
1. Start the app and add a new command.
|
||||||
- Each line is a new macro
|
1. Press the script button to edit the command script.
|
||||||
1. There are some built in macros like `time` and `date` which will output the current local time and date
|
1. Open an editor and type `<prefix><commandName><suffix>`.
|
||||||
1. Write `[suffix][macroName][preffix]` somewhere, and then select it and press ``CTRL+C``
|
1. Copy the command to the clipboard.
|
||||||
|
1. Whatever the script outputs should now be pasted into the editor.
|
||||||
|
|
||||||
|
## Follow the development
|
||||||
|
|
||||||
|
[](https://x.com/search?q=from%3A%40StoneRedCode%20%23ClipCMD)
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
> Prefix: `#`\
|
||||||
|
> Suffix: `+`\
|
||||||
|
> Command name: `email`\
|
||||||
|
> Command script: `Write-Output [email protected]`\
|
||||||
|
> Command script (alternative): `"[email protected]"`
|
||||||
|
|
||||||
|
> Input: `#email+`\
|
||||||
|
> Output: `[email protected]`
|
||||||
|
|
||||||
## Preview
|
## Preview
|
||||||
<img width="589" alt="Screenshot 2022-05-03 221232" src="https://user-images.githubusercontent.com/56473591/166560142-9e89c57f-1af0-4340-9317-dacf0064d6c8.png">
|
|
||||||
|
https://github.com/user-attachments/assets/a0665d6d-e6ce-4d78-88d0-e9c35214219d
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## Third party licences
|
||||||
|
|
||||||
|
- [Avalonia](https://github.com/AvaloniaUI/Avalonia) - [MIT](https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md)
|
||||||
|
- [Icons.Avalonia](https://github.com/Projektanker/Icons.Avalonia) - [MIT](https://github.com/Projektanker/Icons.Avalonia/blob/main/LICENSE)
|
||||||
|
- [Semi.Avalonia](https://github.com/irihitech/Semi.Avalonia) - [MIT](https://github.com/irihitech/Semi.Avalonia/blob/main/LICENSE)
|
||||||
|
- [SharpHook](https://github.com/TolikPylypchuk/SharpHook) - [MIT](https://github.com/TolikPylypchuk/SharpHook/blob/main/LICENSE)
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
; Script generated by the Inno Setup Script Wizard.
|
||||||
|
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||||
|
|
||||||
|
#expr Exec('cmd.exe /C', 'dotnet build -o "' + SourcePath + '\publish" -c Release ' + SourcePath + '..\src\ClipCMD.Desktop\')
|
||||||
|
|
||||||
|
#define MyAppName "ClipCMD"
|
||||||
|
#define MyAppVersion GetVersionNumbersString("/publish/ClipCmd.Desktop.exe")
|
||||||
|
#define MyAppPublisher "Stone_Red"
|
||||||
|
#define MyAppExeName "ClipCmd.Desktop.exe"
|
||||||
|
|
||||||
|
[Setup]
|
||||||
|
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
|
||||||
|
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||||
|
AppId=68d780d4-b946-4154-8815-e4632e5ec5d8
|
||||||
|
AppName={#MyAppName}
|
||||||
|
AppVersion={#MyAppVersion}
|
||||||
|
;AppVerName={#MyAppName} {#MyAppVersion}
|
||||||
|
AppPublisher={#MyAppPublisher}
|
||||||
|
DefaultDirName={autopf}\{#MyAppName}
|
||||||
|
DisableProgramGroupPage=yes
|
||||||
|
; The [Icons] "quicklaunchicon" entry uses {userappdata} but its [Tasks] entry has a proper IsAdminInstallMode Check.
|
||||||
|
UsedUserAreasWarning=no
|
||||||
|
; Remove the following line to run in administrative install mode (install for all users.)
|
||||||
|
PrivilegesRequired=lowest
|
||||||
|
PrivilegesRequiredOverridesAllowed=dialog
|
||||||
|
OutputBaseFilename={#MyAppName}-Installer
|
||||||
|
OutputDir=.
|
||||||
|
Compression=lzma
|
||||||
|
SolidCompression=yes
|
||||||
|
WizardStyle=modern
|
||||||
|
|
||||||
|
[Languages]
|
||||||
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
Name: "german"; MessagesFile: "compiler:Languages\German.isl"
|
||||||
|
|
||||||
|
[Tasks]
|
||||||
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||||
|
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 6.1; Check: not IsAdminInstallMode
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
Source: "publish\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs
|
||||||
|
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
||||||
|
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
||||||
|
Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon
|
||||||
|
|
||||||
|
[Run]
|
||||||
|
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
||||||
|
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
|
||||||
|
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("{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
|
||||||
|
Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "MsixPackaging", "MsixPackaging\MsixPackaging.wapproj", "{CBF43E7F-27D3-4106-96E6-3750D1734990}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Debug|ARM = Debug|ARM
|
||||||
|
Debug|ARM64 = Debug|ARM64
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
Release|ARM = Release|ARM
|
||||||
|
Release|ARM64 = Release|ARM64
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
|
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}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||||
|
{EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||||
|
{EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||||
|
{EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||||
|
{EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Debug|x86.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
|
||||||
|
{EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||||
|
{EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Release|ARM.Build.0 = Release|Any CPU
|
||||||
|
{EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||||
|
{EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Release|ARM64.Build.0 = Release|Any CPU
|
||||||
|
{EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Release|x86.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}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||||
|
{ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||||
|
{ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||||
|
{ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||||
|
{ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Debug|x86.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
|
||||||
|
{ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||||
|
{ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Release|ARM.Build.0 = Release|Any CPU
|
||||||
|
{ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||||
|
{ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Release|ARM64.Build.0 = Release|Any CPU
|
||||||
|
{ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Debug|ARM.ActiveCfg = Debug|ARM
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Debug|ARM.Build.0 = Debug|ARM
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Debug|ARM.Deploy.0 = Debug|ARM
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Debug|ARM64.Deploy.0 = Debug|ARM64
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Debug|x64.Deploy.0 = Debug|x64
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Debug|x86.Deploy.0 = Debug|x86
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Release|ARM.ActiveCfg = Release|ARM
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Release|ARM.Build.0 = Release|ARM
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Release|ARM.Deploy.0 = Release|ARM
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Release|ARM64.Deploy.0 = Release|ARM64
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Release|x64.Build.0 = Release|x64
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Release|x64.Deploy.0 = Release|x64
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Release|x86.Build.0 = Release|x86
|
||||||
|
{CBF43E7F-27D3-4106-96E6-3750D1734990}.Release|x86.Deploy.0 = Release|x86
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {83CB65B8-011F-4ED7-BCD3-A6CFA935EF7E}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<Application xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:dialogHostAvalonia="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||||
|
xmlns:local="using:ClipCmd"
|
||||||
|
x:Class="ClipCmd.App"
|
||||||
|
RequestedThemeVariant="Default">
|
||||||
|
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||||
|
|
||||||
|
<Application.DataTemplates>
|
||||||
|
<local:ViewLocator />
|
||||||
|
</Application.DataTemplates>
|
||||||
|
|
||||||
|
<Application.Resources>
|
||||||
|
<x:Double x:Key="ControlContentThemeFontSize">12</x:Double>
|
||||||
|
<FontFamily x:Key="ContentControlThemeFontFamily">Microsoft YaHei</FontFamily>
|
||||||
|
</Application.Resources>
|
||||||
|
|
||||||
|
<Application.Styles>
|
||||||
|
<FluentTheme />
|
||||||
|
<dialogHostAvalonia:DialogHostStyles />
|
||||||
|
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
|
||||||
|
</Application.Styles>
|
||||||
|
|
||||||
|
<TrayIcon.Icons>
|
||||||
|
<TrayIcons>
|
||||||
|
<TrayIcon Icon="/Assets/logo.ico" ToolTipText="ClipCMD" Clicked="TrayIcon_Clicked">
|
||||||
|
<TrayIcon.Menu>
|
||||||
|
<NativeMenu>
|
||||||
|
<NativeMenuItem Header="Quit" Click="NativeMenuItem_Click_Quit" />
|
||||||
|
</NativeMenu>
|
||||||
|
</TrayIcon.Menu>
|
||||||
|
</TrayIcon>
|
||||||
|
</TrayIcons>
|
||||||
|
</TrayIcon.Icons>
|
||||||
|
</Application>
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
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;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ClipCmd;
|
||||||
|
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
_ = IconProvider.Current
|
||||||
|
.Register<FontAwesomeIconProvider>()
|
||||||
|
.Register<MaterialDesignIconProvider>();
|
||||||
|
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnFrameworkInitializationCompleted()
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(Configuration.ApplicationDataPath))
|
||||||
|
{
|
||||||
|
_ = Directory.CreateDirectory(Configuration.ApplicationDataPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
|
{
|
||||||
|
desktop.MainWindow = new MainWindow();
|
||||||
|
ClipCmdCommandHandler clipCmdCommandHandler = new ClipCmdCommandHandler(desktop.MainWindow);
|
||||||
|
ImportExportService importExportService = new ImportExportService(desktop.MainWindow, clipCmdCommandHandler);
|
||||||
|
desktop.MainWindow.DataContext = new MainViewModel(clipCmdCommandHandler, importExportService);
|
||||||
|
|
||||||
|
desktop.MainWindow.Closing += (sender, e) =>
|
||||||
|
{
|
||||||
|
e.Cancel = true;
|
||||||
|
|
||||||
|
desktop.MainWindow.Hide();
|
||||||
|
|
||||||
|
Settings.SaveSettings();
|
||||||
|
clipCmdCommandHandler.SaveCommands();
|
||||||
|
};
|
||||||
|
|
||||||
|
clipCmdCommandHandler.Start();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new PlatformNotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnFrameworkInitializationCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TrayIcon_Clicked(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop && desktop.MainWindow is not null)
|
||||||
|
{
|
||||||
|
desktop.MainWindow.WindowState = Avalonia.Controls.WindowState.Normal;
|
||||||
|
desktop.MainWindow.Show();
|
||||||
|
desktop.MainWindow.Activate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NativeMenuItem_Click_Quit(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
|
{
|
||||||
|
Settings.SaveSettings();
|
||||||
|
desktop.Shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 109 KiB |
@@ -0,0 +1,46 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||||
|
<Version>1.0.0.0</Version>
|
||||||
|
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<AvaloniaResource Include="Assets\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Assets\logo.ico" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Avalonia" Version="11.2.1" />
|
||||||
|
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.1.0" />
|
||||||
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.1" />
|
||||||
|
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.1" />
|
||||||
|
<PackageReference Include="Avalonia.ReactiveUI" Version="11.2.1" />
|
||||||
|
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||||
|
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.2.1" />
|
||||||
|
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.1.0" />
|
||||||
|
<PackageReference Include="DialogHost.Avalonia" Version="0.8.1" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.11.0" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.Scripting.Common" Version="4.11.0" />
|
||||||
|
<PackageReference Include="Microsoft.PowerShell.SDK" Version="7.4.6" />
|
||||||
|
<PackageReference Include="Projektanker.Icons.Avalonia" Version="9.4.3" />
|
||||||
|
<PackageReference Include="Projektanker.Icons.Avalonia.FontAwesome" Version="9.4.3" />
|
||||||
|
<PackageReference Include="Projektanker.Icons.Avalonia.MaterialDesign" Version="9.4.3" />
|
||||||
|
<PackageReference Include="Semi.Avalonia" Version="11.2.1" />
|
||||||
|
<PackageReference Include="SharpHook" Version="5.3.8" />
|
||||||
|
<PackageReference Include="Speckle.Material.Icons.Avalonia" Version="1.2.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Update="Views\InputDialogView.axaml.cs">
|
||||||
|
<DependentUpon>InputDialogView.axaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>")]
|
||||||
|
// 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.
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace ClipCmd.Models;
|
||||||
|
|
||||||
|
public class ClipCmdCommand(string script)
|
||||||
|
{
|
||||||
|
public string Script { get; set; } = script;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace ClipCmd.Models;
|
||||||
|
|
||||||
|
public enum ClipCmdMode
|
||||||
|
{
|
||||||
|
Clipboard,
|
||||||
|
AutoType
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
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; } = 100;
|
||||||
|
|
||||||
|
public ClipCmdMode Mode { get; set; }
|
||||||
|
|
||||||
|
public string Prefix { get; set; } = "#";
|
||||||
|
|
||||||
|
public string Suffix { get; set; } = "";
|
||||||
|
|
||||||
|
public string CommandArgsSeperator { 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<Settings>(File.ReadAllText(Configuration.SettingsFilePath)) ?? new Settings();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.WriteLine(ex.Message);
|
||||||
|
return new Settings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new Settings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
using Avalonia.Collections;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input.Platform;
|
||||||
|
|
||||||
|
using ClipCmd.Models;
|
||||||
|
|
||||||
|
using SharpHook;
|
||||||
|
using SharpHook.Native;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Management.Automation;
|
||||||
|
using System.Text.Json;
|
||||||
|
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 string lastText = string.Empty;
|
||||||
|
public AvaloniaDictionary<string, ClipCmdCommand> Commands { get; } = [];
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
_ = new TaskFactory().StartNew(async () =>
|
||||||
|
{
|
||||||
|
LoadCommands();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
await Run();
|
||||||
|
await Task.Delay(100);
|
||||||
|
}
|
||||||
|
}, TaskCreationOptions.LongRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearCommands()
|
||||||
|
{
|
||||||
|
Commands.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveCommands()
|
||||||
|
{
|
||||||
|
string json = JsonSerializer.Serialize(Commands);
|
||||||
|
File.WriteAllText(Configuration.CommandsPath, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadCommands()
|
||||||
|
{
|
||||||
|
if (!File.Exists(Configuration.CommandsPath))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string json = File.ReadAllText(Configuration.CommandsPath);
|
||||||
|
Commands.Clear();
|
||||||
|
|
||||||
|
foreach (KeyValuePair<string, ClipCmdCommand> command in JsonSerializer.Deserialize<ConcurrentDictionary<string, ClipCmdCommand>>(json) ?? new())
|
||||||
|
{
|
||||||
|
Commands[command.Key] = command.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 input = text[Settings.Current.Prefix.Length..^Settings.Current.Suffix.Length].Replace(Settings.Current.CommandArgsSeperator + Settings.Current.CommandArgsSeperator, "\0");
|
||||||
|
string[] parts = input.Split(Settings.Current.CommandArgsSeperator);
|
||||||
|
string commandName = parts[0].Replace("\0", Settings.Current.CommandArgsSeperator);
|
||||||
|
string[] parameters = parts.Skip(1).Select(p => p.Replace("\0", Settings.Current.CommandArgsSeperator)).ToArray();
|
||||||
|
|
||||||
|
if (Commands.TryGetValue(commandName, out ClipCmdCommand? command))
|
||||||
|
{
|
||||||
|
PowerShell powerShell = PowerShell.Create();
|
||||||
|
_ = powerShell.AddScript(command.Script);
|
||||||
|
_ = powerShell.AddParameters(parameters);
|
||||||
|
|
||||||
|
PSDataCollection<PSObject> commandResults = await powerShell.InvokeAsync();
|
||||||
|
|
||||||
|
foreach (PSObject commandResult in commandResults)
|
||||||
|
{
|
||||||
|
// check if commandResult is string
|
||||||
|
|
||||||
|
if (commandResult.TypeNames.Contains("System.String"))
|
||||||
|
{
|
||||||
|
await Output(commandResult.ToString());
|
||||||
|
}
|
||||||
|
else if (commandResult.BaseObject is Hashtable hashtable)
|
||||||
|
{
|
||||||
|
await ProcessActions(hashtable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (string.IsNullOrWhiteSpace(commandName) || string.IsNullOrEmpty(Settings.Current.Suffix + Settings.Current.Prefix))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (commandName == "list")
|
||||||
|
{
|
||||||
|
await Output($"Commands: {string.Join(", ", Commands.Keys)}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Output("Command not found!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Settings.Current.Mode != ClipCmdMode.Clipboard || Settings.Current.AutoPaste)
|
||||||
|
{
|
||||||
|
await clipboard.ClearAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
lastText = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ProcessActions(Hashtable hashtable)
|
||||||
|
{
|
||||||
|
if (!hashtable.ContainsKey("Action") || hashtable["Action"] is not string action)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action == "Paste")
|
||||||
|
{
|
||||||
|
if (!hashtable.ContainsKey("Text") || hashtable["Text"] is not string text)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool autoPaste = Settings.Current.AutoPaste;
|
||||||
|
|
||||||
|
if (hashtable.ContainsKey("AutoPaste") && hashtable["AutoPaste"] is bool autoPasteOverwrite)
|
||||||
|
{
|
||||||
|
autoPaste = autoPasteOverwrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Paste(text, autoPaste);
|
||||||
|
}
|
||||||
|
else if (action == "Type")
|
||||||
|
{
|
||||||
|
if (!hashtable.ContainsKey("Text") || hashtable["Text"] is not string text)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int delay = Settings.Current.AutoTypeDelay;
|
||||||
|
|
||||||
|
if (hashtable.ContainsKey("Delay") && hashtable["Delay"] is int delayOverwrite)
|
||||||
|
{
|
||||||
|
delay = delayOverwrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Type(text, delay);
|
||||||
|
}
|
||||||
|
else if (action == "KeyPress")
|
||||||
|
{
|
||||||
|
if (!hashtable.ContainsKey("Key") || !Enum.TryParse(hashtable["Key"]?.ToString(), out KeyCode key))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = simulator.SimulateKeyPress(key);
|
||||||
|
}
|
||||||
|
else if (action == "KeyRelease")
|
||||||
|
{
|
||||||
|
if (!hashtable.ContainsKey("Key") || !Enum.TryParse(hashtable["Key"]?.ToString(), out KeyCode key))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = simulator.SimulateKeyRelease(key);
|
||||||
|
}
|
||||||
|
else if (action == "KeyStroke")
|
||||||
|
{
|
||||||
|
if (!hashtable.ContainsKey("Key") || !Enum.TryParse(hashtable["Key"]?.ToString(), out KeyCode key))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = simulator.SimulateKeyPress(key);
|
||||||
|
_ = simulator.SimulateKeyRelease(key);
|
||||||
|
}
|
||||||
|
else if (action == "Delay")
|
||||||
|
{
|
||||||
|
if (!hashtable.ContainsKey("Delay") || hashtable["Delay"] is not int delay)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Output(string text)
|
||||||
|
{
|
||||||
|
if (Settings.Current.Mode == ClipCmdMode.Clipboard)
|
||||||
|
{
|
||||||
|
await Paste(text, Settings.Current.AutoPaste);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Type(text, Settings.Current.AutoTypeDelay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Paste(string text, bool autoPaste)
|
||||||
|
{
|
||||||
|
await clipboard.SetTextAsync(text);
|
||||||
|
|
||||||
|
if (autoPaste)
|
||||||
|
{
|
||||||
|
await Task.Delay(10);
|
||||||
|
_ = simulator.SimulateKeyPress(KeyCode.VcLeftControl);
|
||||||
|
_ = simulator.SimulateKeyPress(KeyCode.VcV);
|
||||||
|
|
||||||
|
_ = simulator.SimulateKeyRelease(KeyCode.VcLeftControl);
|
||||||
|
_ = simulator.SimulateKeyRelease(KeyCode.VcV);
|
||||||
|
await Task.Delay(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Type(string text, int delay)
|
||||||
|
{
|
||||||
|
_ = simulator.SimulateKeyRelease(KeyCode.VcLeftControl);
|
||||||
|
_ = simulator.SimulateKeyRelease(KeyCode.VcRightControl);
|
||||||
|
_ = simulator.SimulateKeyRelease(KeyCode.VcLeftAlt);
|
||||||
|
_ = simulator.SimulateKeyRelease(KeyCode.VcRightAlt);
|
||||||
|
|
||||||
|
foreach (char c in text)
|
||||||
|
{
|
||||||
|
await Task.Delay(delay);
|
||||||
|
if (c == '\n')
|
||||||
|
{
|
||||||
|
_ = simulator.SimulateKeyPress(KeyCode.VcEnter);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = simulator.SimulateTextEntry(c.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
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");
|
||||||
|
|
||||||
|
public static string CommandsPath => Path.Combine(ApplicationDataPath, "commands.json");
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Platform.Storage;
|
||||||
|
|
||||||
|
using ClipCmd.Models;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace ClipCmd.Utilities;
|
||||||
|
|
||||||
|
public class ImportExportService(Window window, ClipCmdCommandHandler clipCmdCommandHandler)
|
||||||
|
{
|
||||||
|
private readonly TopLevel topLevel = window;
|
||||||
|
|
||||||
|
public async Task Import()
|
||||||
|
{
|
||||||
|
// Start async operation to open the dialog.
|
||||||
|
IReadOnlyList<IStorageFile> files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||||
|
{
|
||||||
|
Title = "Import ClipCMD commands",
|
||||||
|
AllowMultiple = false,
|
||||||
|
FileTypeFilter = [new FilePickerFileType("ccmd")
|
||||||
|
{
|
||||||
|
Patterns = ["*.ccmd"]
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (files.Count < 1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await using Stream stream = await files[0].OpenReadAsync();
|
||||||
|
using StreamReader streamReader = new StreamReader(stream);
|
||||||
|
|
||||||
|
string json = await streamReader.ReadToEndAsync();
|
||||||
|
|
||||||
|
clipCmdCommandHandler.ClearCommands();
|
||||||
|
|
||||||
|
Dictionary<string, ClipCmdCommand>? commands = JsonSerializer.Deserialize<Dictionary<string, ClipCmdCommand>>(json);
|
||||||
|
|
||||||
|
if (commands is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (KeyValuePair<string, ClipCmdCommand> command in commands)
|
||||||
|
{
|
||||||
|
clipCmdCommandHandler.Commands.Add(command.Key, command.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Export()
|
||||||
|
{
|
||||||
|
// Start async operation to open the dialog.
|
||||||
|
IStorageFile? file = await topLevel.StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
|
||||||
|
{
|
||||||
|
Title = "Export ClipCMD commands",
|
||||||
|
SuggestedFileName = "export.ccmd",
|
||||||
|
FileTypeChoices = [new FilePickerFileType("ccmd")
|
||||||
|
{
|
||||||
|
Patterns = ["*.ccmd"]
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (file is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string json = JsonSerializer.Serialize(clipCmdCommandHandler.Commands);
|
||||||
|
|
||||||
|
await using Stream stream = await file.OpenWriteAsync();
|
||||||
|
using StreamWriter streamWriter = new StreamWriter(stream);
|
||||||
|
await streamWriter.WriteAsync(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
|
||||||
|
using ClipCmd.Models;
|
||||||
|
using ClipCmd.Utilities;
|
||||||
|
using ClipCmd.Views;
|
||||||
|
|
||||||
|
using DialogHostAvalonia;
|
||||||
|
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace ClipCmd.ViewModels;
|
||||||
|
|
||||||
|
public class ClipCmdCommandItemViewModel(string name, ClipCmdCommandHandler clipCmdCommandHandler)
|
||||||
|
{
|
||||||
|
public string Name { get; } = name;
|
||||||
|
|
||||||
|
public ICommand RemoveCommand => ReactiveCommand.Create(Remove);
|
||||||
|
|
||||||
|
public ICommand RenameCommand => ReactiveCommand.Create(Rename);
|
||||||
|
|
||||||
|
public ICommand EditCommand => ReactiveCommand.Create(Edit);
|
||||||
|
|
||||||
|
private void Remove()
|
||||||
|
{
|
||||||
|
_ = clipCmdCommandHandler.Commands.Remove(Name);
|
||||||
|
clipCmdCommandHandler.SaveCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Rename()
|
||||||
|
{
|
||||||
|
string? newName = await DialogHost.Show(new InputDialogViewModel("Edit command name:", Name)) as string;
|
||||||
|
newName = newName?.Trim();
|
||||||
|
|
||||||
|
if (!clipCmdCommandHandler.Commands.Remove(Name, out ClipCmdCommand? clipCmdCommand))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(newName))
|
||||||
|
{
|
||||||
|
clipCmdCommandHandler.Commands.Add(Name, clipCmdCommand);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int counter = 0;
|
||||||
|
string originalName = newName;
|
||||||
|
|
||||||
|
while (clipCmdCommandHandler.Commands.ContainsKey(newName))
|
||||||
|
{
|
||||||
|
newName = $"{originalName} ({++counter})";
|
||||||
|
}
|
||||||
|
|
||||||
|
clipCmdCommandHandler.Commands.Add(newName, clipCmdCommand);
|
||||||
|
clipCmdCommandHandler.SaveCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Edit()
|
||||||
|
{
|
||||||
|
Avalonia.Controls.Window? mainWindow = Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop ? desktop.MainWindow : null;
|
||||||
|
|
||||||
|
EditorWindow editorWindow = new EditorWindow
|
||||||
|
{
|
||||||
|
DataContext = new EditorViewModel(Name, clipCmdCommandHandler)
|
||||||
|
};
|
||||||
|
|
||||||
|
await editorWindow.ShowDialog(mainWindow!);
|
||||||
|
clipCmdCommandHandler.SaveCommands();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using Avalonia.Media;
|
||||||
|
|
||||||
|
namespace ClipCmd.ViewModels;
|
||||||
|
|
||||||
|
internal class EditorMessageViewModel(string message, IBrush color)
|
||||||
|
{
|
||||||
|
public string Message { get; } = message;
|
||||||
|
|
||||||
|
public IBrush Color { get; } = color;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using ClipCmd.Utilities;
|
||||||
|
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
namespace ClipCmd.ViewModels;
|
||||||
|
|
||||||
|
internal class EditorViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly ClipCmdCommandHandler clipCmdCommandHandler;
|
||||||
|
|
||||||
|
public new string Title => $"{base.Title} - Editing {Name}";
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public string Script
|
||||||
|
{
|
||||||
|
get => clipCmdCommandHandler.Commands[Name].Script;
|
||||||
|
set => clipCmdCommandHandler.Commands[Name].Script = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<EditorMessageViewModel> Messages { get; } = [];
|
||||||
|
|
||||||
|
public EditorViewModel(string name, ClipCmdCommandHandler clipCmdCommandHandler)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
this.clipCmdCommandHandler = clipCmdCommandHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only used for the designer
|
||||||
|
public EditorViewModel()
|
||||||
|
{
|
||||||
|
Name = "Test";
|
||||||
|
clipCmdCommandHandler = new ClipCmdCommandHandler(new());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using DialogHostAvalonia;
|
||||||
|
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace ClipCmd.ViewModels;
|
||||||
|
|
||||||
|
internal class InputDialogViewModel(string title, string defaultValue = "") : ViewModelBase
|
||||||
|
{
|
||||||
|
public new string Title { get; set; } = title;
|
||||||
|
|
||||||
|
public string Value { get; set; } = defaultValue;
|
||||||
|
|
||||||
|
public ICommand OkCommand => ReactiveCommand.Create(() => DialogHost.GetDialogSession(null)!.Close(Value));
|
||||||
|
|
||||||
|
public ICommand CancelCommand => ReactiveCommand.Create(() => DialogHost.GetDialogSession(null)!.Close(null));
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
using ClipCmd.Models;
|
||||||
|
using ClipCmd.Utilities;
|
||||||
|
|
||||||
|
using DialogHostAvalonia;
|
||||||
|
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace ClipCmd.ViewModels;
|
||||||
|
|
||||||
|
public class MainViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly ClipCmdCommandHandler clipCmdCommandHandler;
|
||||||
|
private readonly ImportExportService importExportService;
|
||||||
|
|
||||||
|
public List<ClipCmdCommandItemViewModel> Commands => [.. clipCmdCommandHandler.Commands.Keys.Select(x => new ClipCmdCommandItemViewModel(x, clipCmdCommandHandler))];
|
||||||
|
|
||||||
|
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 ?? 1;
|
||||||
|
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 string CommandArgsSeparator
|
||||||
|
{
|
||||||
|
get => Settings.Current.CommandArgsSeperator;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
value = value.Replace(Settings.Current.CommandArgsSeperator, string.Empty);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
value = Settings.Current.CommandArgsSeperator;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.Length > 1)
|
||||||
|
{
|
||||||
|
value = value[..1];
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings.Current.CommandArgsSeperator = value;
|
||||||
|
this.RaisePropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand AddCommand => ReactiveCommand.Create(Add);
|
||||||
|
|
||||||
|
public ICommand ExportCommand => ReactiveCommand.Create(importExportService.Export);
|
||||||
|
public ICommand ImportCommand => ReactiveCommand.Create(importExportService.Import);
|
||||||
|
|
||||||
|
public MainViewModel(ClipCmdCommandHandler clipCmdCommandHandler, ImportExportService importExportService)
|
||||||
|
{
|
||||||
|
this.clipCmdCommandHandler = clipCmdCommandHandler;
|
||||||
|
this.importExportService = importExportService;
|
||||||
|
clipCmdCommandHandler.Commands.CollectionChanged += (sender, e) =>
|
||||||
|
{
|
||||||
|
this.RaisePropertyChanged(nameof(Commands));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only used for the designer
|
||||||
|
public MainViewModel()
|
||||||
|
{
|
||||||
|
clipCmdCommandHandler = new ClipCmdCommandHandler(new());
|
||||||
|
importExportService = new(new(), clipCmdCommandHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Add()
|
||||||
|
{
|
||||||
|
string? name = await DialogHost.Show(new InputDialogViewModel("Enter command name:")) as string;
|
||||||
|
name = name?.Trim();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int counter = 0;
|
||||||
|
string originalName = name;
|
||||||
|
|
||||||
|
while (clipCmdCommandHandler.Commands.ContainsKey(name))
|
||||||
|
{
|
||||||
|
name = $"{originalName} ({++counter})";
|
||||||
|
}
|
||||||
|
|
||||||
|
clipCmdCommandHandler.Commands.Add(name, new ClipCmdCommand(@$"Write-Output ""{name} command"""));
|
||||||
|
clipCmdCommandHandler.SaveCommands();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace ClipCmd.ViewModels;
|
||||||
|
|
||||||
|
public class ViewModelBase : ReactiveObject
|
||||||
|
{
|
||||||
|
public string Title
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
string title = "ClipCMD";
|
||||||
|
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||||
|
FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(assembly.Location);
|
||||||
|
string? version = fileVersionInfo.ProductVersion;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
return $"{title} - Dev {version}";
|
||||||
|
#else
|
||||||
|
return $"{title} - {version}";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:vm="clr-namespace:ClipCmd.ViewModels"
|
||||||
|
xmlns:AvaloniaEdit="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="ClipCmd.Views.EditorWindow"
|
||||||
|
x:DataType="vm:EditorViewModel"
|
||||||
|
Title="{Binding Title}"
|
||||||
|
Icon="/Assets/logo.ico"
|
||||||
|
Width="800" Height="450"
|
||||||
|
WindowStartupLocation="CenterScreen"
|
||||||
|
Loaded="Window_Loaded">
|
||||||
|
<Grid RowDefinitions="3* Auto 1*">
|
||||||
|
<AvaloniaEdit:TextEditor Name="Editor" ShowLineNumbers="True" FontFamily="Cascadia Code,Consolas,Menlo,Monospace" />
|
||||||
|
<Separator Grid.Row="1" Margin="0" />
|
||||||
|
<ScrollViewer Grid.Row="2">
|
||||||
|
<ItemsControl ItemsSource="{Binding Messages}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Label Content="{Binding Message}" Foreground="{Binding Color}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using Avalonia.Styling;
|
||||||
|
|
||||||
|
using AvaloniaEdit;
|
||||||
|
using AvaloniaEdit.TextMate;
|
||||||
|
|
||||||
|
using ClipCmd.ViewModels;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Management.Automation.Language;
|
||||||
|
|
||||||
|
using TextMateSharp.Grammars;
|
||||||
|
|
||||||
|
namespace ClipCmd.Views;
|
||||||
|
|
||||||
|
public partial class EditorWindow : Window
|
||||||
|
{
|
||||||
|
public EditorWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CheckScript(EditorViewModel editorViewModel)
|
||||||
|
{
|
||||||
|
_ = Parser.ParseInput(editorViewModel.Script, out _, out ParseError[] parseErrors);
|
||||||
|
|
||||||
|
editorViewModel.Messages.Clear();
|
||||||
|
|
||||||
|
if (parseErrors.Length > 0)
|
||||||
|
{
|
||||||
|
foreach (ParseError parseError in parseErrors)
|
||||||
|
{
|
||||||
|
editorViewModel.Messages.Add(new($"Error: {parseError.Message} at line {parseError.Extent.StartLineNumber} and column {parseError.Extent.StartColumnNumber}", Brushes.Red));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
editorViewModel.Messages.Add(new("No errors", Brushes.Green));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Window_Loaded(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
EditorViewModel editorViewModel = DataContext as EditorViewModel ?? throw new InvalidOperationException("DataContext is not EditorViewModel");
|
||||||
|
|
||||||
|
TextEditor textEditor = this.FindControl<TextEditor>("Editor")!;
|
||||||
|
|
||||||
|
textEditor.Text = editorViewModel.Script;
|
||||||
|
|
||||||
|
textEditor.TextChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
editorViewModel.Script = textEditor.Text;
|
||||||
|
CheckScript(editorViewModel);
|
||||||
|
};
|
||||||
|
|
||||||
|
ThemeName editorTheme = ThemeName.DarkPlus;
|
||||||
|
|
||||||
|
if (Avalonia.Application.Current?.ActualThemeVariant == ThemeVariant.Light)
|
||||||
|
{
|
||||||
|
editorTheme = ThemeName.LightPlus;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegistryOptions registryOptions = new RegistryOptions(editorTheme);
|
||||||
|
TextMate.Installation _textMateInstallation = textEditor.InstallTextMate(registryOptions);
|
||||||
|
|
||||||
|
_textMateInstallation.SetGrammar(registryOptions.GetScopeByLanguageId(registryOptions.GetLanguageByExtension(".ps1").Id));
|
||||||
|
|
||||||
|
CheckScript(editorViewModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:vm="clr-namespace:ClipCmd.ViewModels"
|
||||||
|
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="ClipCmd.Views.InputDialogView"
|
||||||
|
x:DataType="vm:InputDialogViewModel"
|
||||||
|
MinWidth="300"
|
||||||
|
MinHeight="100"
|
||||||
|
Background="{DynamicResource SemiColorBackground0}">
|
||||||
|
|
||||||
|
<UserControl.Styles>
|
||||||
|
<StyleInclude Source="avares://Semi.Avalonia/Themes/Index.axaml" />
|
||||||
|
</UserControl.Styles>
|
||||||
|
|
||||||
|
<StackPanel Spacing="10">
|
||||||
|
<Label Content="{Binding Title}" />
|
||||||
|
<TextBox VerticalContentAlignment="Center" Text="{Binding Value}" />
|
||||||
|
<StackPanel Spacing="10" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||||
|
<Button Content="Ok" Width="80" HorizontalContentAlignment="Center" Command="{Binding OkCommand}" />
|
||||||
|
<Button Content="Cancel" Width="80" HorizontalContentAlignment="Center" Command="{Binding CancelCommand}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace ClipCmd.Views;
|
||||||
|
|
||||||
|
public partial class InputDialogView : UserControl
|
||||||
|
{
|
||||||
|
public InputDialogView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:vm="clr-namespace:ClipCmd.ViewModels"
|
||||||
|
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||||
|
x:Class="ClipCmd.Views.MainView"
|
||||||
|
x:DataType="vm:MainViewModel"
|
||||||
|
Background="{DynamicResource SemiColorBackground0}">
|
||||||
|
<Design.DataContext>
|
||||||
|
<!-- This only sets the DataContext for the previewer in an IDE,
|
||||||
|
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
||||||
|
<vm:MainViewModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
|
||||||
|
<UserControl.Styles>
|
||||||
|
<StyleInclude Source="avares://Semi.Avalonia/Themes/Index.axaml" />
|
||||||
|
</UserControl.Styles>
|
||||||
|
|
||||||
|
<dialogHost:DialogHost CloseOnClickAway="False" DisableOpeningAnimation="True" Background="{DynamicResource SemiColorBackground0}">
|
||||||
|
<dialogHost:DialogHost.DialogContent>
|
||||||
|
<!-- put your dialog content here-->
|
||||||
|
</dialogHost:DialogHost.DialogContent>
|
||||||
|
|
||||||
|
<Grid RowDefinitions="Auto Auto Auto *" ColumnDefinitions="Auto * Auto" Margin="10">
|
||||||
|
|
||||||
|
<Grid RowDefinitions="Auto * Auto * Auto" VerticalAlignment="Stretch">
|
||||||
|
<TextBox Grid.Row="0" MaxLength="5" InnerLeftContent="Prefix:" Watermark="none" Text="{Binding Prefix}" />
|
||||||
|
<TextBox Grid.Row="2" MaxLength="5" InnerLeftContent="Suffix:" Watermark="none" Text="{Binding Suffix}" />
|
||||||
|
<TextBox Grid.Row="4" MaxLength="2" InnerLeftContent="Args separator:" Text="{Binding CommandArgsSeparator}" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Label Grid.Column="1" Content="ClipCMD" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="30" />
|
||||||
|
|
||||||
|
<Grid RowDefinitions="Auto Auto Auto Auto Auto" ColumnDefinitions="100 20 100" Grid.Column="2">
|
||||||
|
<Label Content="Mode" />
|
||||||
|
<RadioButton Grid.Row="1" VerticalAlignment="Center" Content="Clipboard" IsChecked="{Binding ClipboardModeEnabled}" />
|
||||||
|
<CheckBox Grid.Row="1" Grid.Column="2" VerticalAlignment="Center" Content="Auto Paste" IsEnabled="{Binding ClipboardModeEnabled}" IsChecked="{Binding AutoPaste}" />
|
||||||
|
<Separator Grid.Row="2" Grid.ColumnSpan="3" VerticalAlignment="Center" />
|
||||||
|
<RadioButton Grid.Row="3" VerticalAlignment="Center" Content="Auto Type" IsChecked="{Binding AutoTypeModeEnabled}" />
|
||||||
|
<NumericUpDown Grid.Row="3" Grid.Column="2" Increment="1" Minimum="1" Maximum="1000" VerticalAlignment="Center" IsEnabled="{Binding AutoTypeModeEnabled}" Value="{Binding AutoTypeDelay, FallbackValue=1, TargetNullValue=1}" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Separator Grid.Row="1" Grid.ColumnSpan="3" VerticalAlignment="Center" Margin="0 10" />
|
||||||
|
|
||||||
|
<Button Grid.Column="0" Grid.Row="2" Command="{Binding AddCommand}">
|
||||||
|
<StackPanel Spacing="4" Orientation="Horizontal">
|
||||||
|
<i:Icon Value="fa-plus" />
|
||||||
|
<TextBlock>Add Command</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Grid ColumnDefinitions="* 20 *" Grid.Column="2" Grid.Row="2">
|
||||||
|
<Button Command="{Binding ImportCommand}">
|
||||||
|
<StackPanel Spacing="4" Orientation="Horizontal">
|
||||||
|
<i:Icon Value="fa-file-upload" />
|
||||||
|
<TextBlock>Import</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button Grid.Column="2" Command="{Binding ExportCommand}">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<i:Icon Value="fa-file-download" />
|
||||||
|
<TextBlock>Export</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<ScrollViewer Grid.Column="0" Grid.Row="3" Grid.ColumnSpan="3" VerticalScrollBarVisibility="Auto" Margin="0 20 0 0" Theme="{DynamicResource StaticScrollViewer}">
|
||||||
|
<ItemsControl ItemsSource="{Binding Commands}">
|
||||||
|
<!--Use a StackPanel to display all the modules-->
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel Spacing="10" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
|
||||||
|
<!--This defines the layout for each item (i.e. each module)-->
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Border CornerRadius="3" Background="{DynamicResource SemiColorBackground1}" Padding="10 10">
|
||||||
|
<Grid ColumnDefinitions="* 45 10 45 10 45">
|
||||||
|
<Label Content="{Binding Name}" VerticalAlignment="Center" />
|
||||||
|
<Button Grid.Column="1" Command="{Binding RenameCommand}" HorizontalAlignment="Stretch">
|
||||||
|
<i:Icon Value="fa-edit" />
|
||||||
|
</Button>
|
||||||
|
<Button Grid.Column="3" Command="{Binding EditCommand}" HorizontalAlignment="Stretch">
|
||||||
|
<i:Icon Value="fa-code" />
|
||||||
|
</Button>
|
||||||
|
<Button Grid.Column="5" Command="{Binding RemoveCommand}" HorizontalAlignment="Stretch">
|
||||||
|
<i:Icon Value="fa-trash" />
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
</dialogHost:DialogHost>
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace ClipCmd.Views;
|
||||||
|
|
||||||
|
public partial class MainView : UserControl
|
||||||
|
{
|
||||||
|
public MainView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="using:ClipCmd.ViewModels"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||||
|
xmlns:views="clr-namespace:ClipCmd.Views"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="ClipCmd.Views.MainWindow"
|
||||||
|
x:DataType="vm:ViewModelBase"
|
||||||
|
Icon="/Assets/logo.ico"
|
||||||
|
Title="{Binding Title}" Width="800" Height="450">
|
||||||
|
|
||||||
|
<views:MainView />
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace ClipCmd.Views;
|
||||||
|
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<!--If you are willing to use Windows/MacOS native APIs you will need to create 3 projects.
|
||||||
|
One for Windows with net8.0-windows TFM, one for MacOS with net8.0-macos and one with net8.0 TFM for Linux.-->
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
|
<AssemblyName></AssemblyName>
|
||||||
|
<Version>1.0.0.0</Version>
|
||||||
|
<ApplicationIcon>logo.ico</ApplicationIcon>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="logo.ico" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Avalonia.Desktop" Version="11.2.1" />
|
||||||
|
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||||
|
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.2.1" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.11.0" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.Scripting.Common" Version="4.11.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\ClipCmd\ClipCmd.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -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<App>()
|
||||||
|
.UsePlatformDetect()
|
||||||
|
.WithInterFont()
|
||||||
|
.LogToTrace()
|
||||||
|
.UseReactiveUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<!-- This manifest is used on Windows only.
|
||||||
|
Don't remove it as it might cause problems with window transparency and embedded controls.
|
||||||
|
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="ClipCmd.Desktop"/>
|
||||||
|
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<!-- A list of the Windows versions that this application has been tested on
|
||||||
|
and is designed to work with. Uncomment the appropriate elements
|
||||||
|
and Windows will automatically select the most compatible environment. -->
|
||||||
|
|
||||||
|
<!-- Windows 10 -->
|
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
</assembly>
|
||||||
|
After Width: | Height: | Size: 109 KiB |
@@ -0,0 +1,6 @@
|
|||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<AvaloniaVersion>11.0.10</AvaloniaVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 607 B |
|
After Width: | Height: | Size: 976 B |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 607 B |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 497 B |
|
After Width: | Height: | Size: 779 B |
|
After Width: | Height: | Size: 976 B |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 9.5 KiB |