6 Commits
Author SHA1 Message Date
Stone_Red 6a8414c0a9 Update README.md 2023-03-19 01:07:09 +01:00
Stone_Red ae580599cb Store data in AppData 2023-03-19 00:42:48 +01:00
Stone_Red cee54c9364 Add support for script parameters 2023-03-18 23:50:02 +01:00
Stone_Red e23ae0a47a Add logo and improve error handling 2023-03-17 15:02:14 +01:00
Stone_Red 71451f22b2 Change syntax and add powershell support 2022-12-22 16:51:09 +01:00
Stone_Red 48af2275a7 Update issue templates 2022-06-08 15:08:30 +02:00
8 changed files with 311 additions and 102 deletions
+32
View File
@@ -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.
+20
View File
@@ -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.
+18 -7
View File
@@ -1,15 +1,26 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Threading;
using System.Windows;
namespace ClipCMD;
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
protected override void OnStartup(StartupEventArgs e)
{
const string appName = "ClipCMD";
_ = new Mutex(true, appName, 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();
}
base.OnStartup(e);
}
}
+29 -13
View File
@@ -1,17 +1,33 @@
<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>
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
<AssemblyVersion>0.0.0.2</AssemblyVersion>
<FileVersion>0.0.0.2</FileVersion>
<Version>0.0.0.2</Version>
<ApplicationIcon>logo.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="InputSimulator" Version="1.0.4" />
</ItemGroup>
<ItemGroup>
<Content Include="logo.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
<ItemGroup>
<PackageReference Include="InputSimulator" Version="1.0.4" />
<PackageReference Include="Microsoft.PowerShell.SDK" Version="7.0.0" />
<PackageReference Include="System.Management.Automation" Version="7.0.0" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<None Update="logo.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
+12 -6
View File
@@ -5,15 +5,16 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ClipCMD"
mc:Ignorable="d"
Title="ClipCMD" Height="450" Width="800">
Title="ClipCMD" Height="450" Width="800"
Closing="OnClose" StateChanged="OnStateChanged" Icon="logo.ico">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Label Content="Prefix:" Width="45" />
<TextBox x:Name="prefixTextBox" KeyUp="FixTextBox_TextChanged" Text="01" />
<TextBox x:Name="prefixTextBox" KeyUp="FixTextBox_TextChanged" Text="_" />
</StackPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Label Content="Suffix:" Width="45" />
<TextBox x:Name="suffixTextBox" KeyUp="FixTextBox_TextChanged" Text="01" />
<TextBox x:Name="suffixTextBox" KeyUp="FixTextBox_TextChanged" Text="_" />
</StackPanel>
<Grid DockPanel.Dock="Top" VerticalAlignment="Stretch">
@@ -23,14 +24,19 @@
</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" />
<Label DockPanel.Dock="Top" Content="Commands:" />
<TextBox DockPanel.Dock="Top" x:Name="commandsTextBox" HorizontalScrollBarVisibility="Visible" TextChanged="StaticCommandsTextBox_TextChanged" AcceptsReturn="True" VerticalAlignment="Stretch" />
</DockPanel>
<DockPanel Grid.Column="1">
<DockPanel x:Name="logPanel" Grid.Column="1">
<Label DockPanel.Dock="Top" Content="History:" />
<ListBox DockPanel.Dock="Top" x:Name="logListBox" VerticalAlignment="Stretch" />
</DockPanel>
<DockPanel Visibility="Collapsed" x:Name="errorPanel" Grid.Column="1">
<Label DockPanel.Dock="Top" Content="Errors:" />
<ListBox DockPanel.Dock="Top" x:Name="errorListBox" Foreground="Red" VerticalAlignment="Stretch" />
</DockPanel>
</Grid>
</DockPanel>
</Window>
+170 -66
View File
@@ -1,11 +1,15 @@
using System;
using Microsoft.VisualBasic.FileIO;
using System;
using System.Collections.Generic;
using System.Data;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Language;
using System.Text;
using System.Windows;
using System.Windows.Media;
using WindowsInput;
using WindowsInput.Native;
@@ -17,33 +21,57 @@ namespace ClipCMD;
/// </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 },
};
private readonly Dictionary<string, string> commands = new Dictionary<string, string>();
private readonly System.Windows.Forms.NotifyIcon notifyIcon;
private WindowState storedWindowState = WindowState.Normal;
private readonly string cmdPath;
private readonly string fixPath;
public MainWindow()
{
notifyIcon = new System.Windows.Forms.NotifyIcon
{
BalloonTipText = "ClipCMD has been minimized. Click the tray icon to show.",
BalloonTipTitle = "ClipCMD",
Text = "ClipCMD",
Icon = new System.Drawing.Icon("logo.ico"),
Visible = true
};
notifyIcon.Click += NotifyIcon_Click;
InitializeComponent();
string applicationDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
string folderPath = Path.Combine(applicationDataPath, "ClipCMD");
if (!Directory.Exists(folderPath))
{
_ = Directory.CreateDirectory(folderPath);
}
cmdPath = Path.Combine(folderPath, "cmd.txt");
fixPath = Path.Combine(folderPath, "fix.txt");
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
// Initialize the clipboard now that we have a window soruce to use
// Initialize the clipboard now that we have a window source to use
ClipboardManager windowClipboardManager = new ClipboardManager(this);
windowClipboardManager.ClipboardChanged += ClipboardManager_ClipboardChanged;
if (File.Exists("cmd.txt"))
if (File.Exists(cmdPath))
{
staticCommandsTextBox.Text = File.ReadAllText("cmd.txt");
commandsTextBox.Text = File.ReadAllText(cmdPath);
}
if (File.Exists("fix.txt"))
if (File.Exists(fixPath))
{
string[] lines = File.ReadAllLines("fix.txt");
string[] lines = File.ReadAllLines(fixPath);
if (lines.Length == 2)
{
prefixTextBox.Text = lines[0];
@@ -51,79 +79,105 @@ public partial class MainWindow : Window
}
else
{
File.Delete("fix.txt");
File.Delete(fixPath);
}
}
}
private void OnClose(object? sender, CancelEventArgs args)
{
notifyIcon.Dispose();
}
private void OnStateChanged(object? sender, EventArgs args)
{
if (WindowState == WindowState.Minimized)
{
Hide();
notifyIcon?.ShowBalloonTip(2000);
}
else
{
storedWindowState = WindowState;
}
}
private void NotifyIcon_Click(object? sender, EventArgs e)
{
Show();
WindowState = storedWindowState;
}
private IDataObject? oldData;
private void ClipboardManager_ClipboardChanged(object? sender, EventArgs e)
{
if (!Clipboard.ContainsText() || oldData is not null && Clipboard.IsCurrent(oldData))
if (!Clipboard.ContainsText() || (oldData is not null && Clipboard.IsCurrent(oldData)))
{
return;
}
try
{
bool result = SafeRepeat.Start(() => Clipboard.GetText().Trim(), 100, out string? text);
bool result = SafeRepeat.Start(() => Clipboard.GetText().Trim(), 100, out string? clipboardText);
if (!result || text is null)
if (!result || clipboardText is null)
{
return;
}
if (!text.StartsWith(prefixTextBox.Text) || !text.EndsWith(suffixTextBox.Text))
if (!clipboardText.StartsWith(prefixTextBox.Text) || !clipboardText.EndsWith(suffixTextBox.Text))
{
oldData = Clipboard.GetDataObject();
return;
}
text = text[prefixTextBox.Text.Length..^suffixTextBox.Text.Length].Trim();
clipboardText = clipboardText[prefixTextBox.Text.Length..^suffixTextBox.Text.Length].Trim().Replace("\"\"", "\0");
Func<string, string>? func = stuffToReplace.FirstOrDefault(v => text.StartsWith(v.Key)).Value;
string logText = text;
string? outText;
if (func is null)
TextFieldParser parser = new TextFieldParser(new StringReader(clipboardText))
{
string[]? parts = staticCommandsTextBox.Text.Split('\n').FirstOrDefault(l =>
{
if (string.IsNullOrWhiteSpace(l))
{
return false;
}
HasFieldsEnclosedInQuotes = true
};
string? startText = l.Trim().Split('>').FirstOrDefault()?.Trim();
return startText is not null && text == startText;
})?.Split('>');
parser.SetDelimiters(" ");
if (parts?.Length == 2)
{
outText = parts[1];
}
else
{
oldData = Clipboard.GetDataObject();
return;
}
}
else
string[] sections = parser.ReadFields() ?? Array.Empty<string>();
sections = sections.Select(s => s.Replace('\0', '\"')).ToArray();
if (sections.Length == 0)
{
outText = func(text);
return;
}
outText = outText.Trim();
logListBox.Items.Insert(0, $"{logText} > {outText}");
string command = sections[0].Trim();
Clipboard.SetDataObject(outText);
if (!commands.TryGetValue(command, out string? script))
{
oldData = Clipboard.GetDataObject();
return;
}
PowerShell ps = PowerShell
.Create()
.AddScript(script)
.AddParameters(sections.Skip(1).ToList());
StringBuilder outText = new StringBuilder();
foreach (PSObject commandResult in ps.Invoke())
{
_ = outText.AppendLine(commandResult?.ToString() ?? string.Empty);
}
logListBox.Items.Insert(0, $"{command} > {outText.ToString().TrimEnd()}");
Clipboard.SetText(outText.ToString().TrimEnd());
oldData = Clipboard.GetDataObject();
InputSimulator inputSimulator = new InputSimulator();
inputSimulator.Keyboard.KeyPress(new[] { VirtualKeyCode.CONTROL, VirtualKeyCode.VK_V });
_ = inputSimulator.Keyboard.ModifiedKeyStroke(new[] { VirtualKeyCode.CONTROL }, new[] { VirtualKeyCode.VK_V });
}
catch (Exception ex)
{
@@ -134,25 +188,75 @@ public partial class MainWindow : Window
private void StaticCommandsTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
bool isError = false;
foreach (string line in staticCommandsTextBox.Text.Split('\n'))
string[] lines = commandsTextBox.Text.Split('\n').Append("[#END#]").ToArray();
string commandNames = string.Empty;
StringBuilder script = new StringBuilder();
commandsTextBox.Foreground = System.Windows.Media.Brushes.Black;
errorPanel.Visibility = Visibility.Collapsed;
logPanel.Visibility = Visibility.Visible;
errorListBox.Items.Clear();
commands.Clear();
foreach (string l in lines)
{
if (!string.IsNullOrWhiteSpace(line) && line.Count(c => c == '>') != 1)
string line = l.Trim('\n', '\r');
if (line.StartsWith('[') && line.EndsWith(']'))
{
isError = true;
if (!string.IsNullOrEmpty(commandNames))
{
_ = Parser.ParseInput(script.ToString(), out _, out ParseError[] errors);
if (errors.Length > 0)
{
foreach (ParseError error in errors)
{
AddError(commandNames, error.ToString());
}
}
foreach (string commandName in commandNames.Split(','))
{
if (string.IsNullOrWhiteSpace(commandName))
{
AddError(commandNames, "Command \"{commandName}\" is empty!");
}
else if (!commands.TryAdd(commandName.Trim(), script.ToString()))
{
AddError(commandNames, "Command \"{commandName}\" already exists!");
}
}
}
commandNames = line.Trim()[1..^1].Trim();
script = new StringBuilder();
}
else if (!string.IsNullOrEmpty(commandNames))
{
if (!string.IsNullOrWhiteSpace(line))
{
_ = script.AppendLine(line);
}
}
else
{
AddError(commandNames, "Input has to start with [<Command Name>]!");
return;
}
}
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);
}
_ = commands.TryAdd("list", $"\"{string.Join(", ", commands.Keys)}\"");
File.WriteAllText(cmdPath, commandsTextBox.Text);
}
private void AddError(string commandNames, string error)
{
errorPanel.Visibility = Visibility.Visible;
logPanel.Visibility = Visibility.Collapsed;
commandsTextBox.Foreground = System.Windows.Media.Brushes.Red;
_ = errorListBox.Items.Add($"[{commandNames}]\n{error}");
}
private void FixTextBox_TextChanged(object sender, System.Windows.Input.KeyEventArgs e)
@@ -162,6 +266,6 @@ public partial class MainWindow : Window
return;
}
File.WriteAllLines("fix.txt", new[] { prefixTextBox.Text, suffixTextBox.Text });
File.WriteAllLines(fixPath, new[] { prefixTextBox.Text, suffixTextBox.Text });
}
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

+30 -10
View File
@@ -4,15 +4,17 @@
## What is it?
ClipCMD is a windows app that allows you to create macros 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.
ClipCMD is a Windows app that allows you to create macros 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 output of the specified macro script.\
ClipCMD uses [PowerShell](https://learn.microsoft.com/en-us/powershell/scripting) as scripting language.
### Example
> Prefix: `_`\
> Suffix: `_`\
> Macro name: `email`\
> Macro text: `[email protected]`
> Macro script: `Write-Output [email protected]`\
> Macro script (alternative): `"[email protected]"`
> Input: `_email_`\
> Output: `[email protected]`
@@ -20,13 +22,31 @@ You write a macro anywhere, copy it to your clipboard and it will be automatical
## Usage
1. Download one of the [releases](https://github.com/Stone-Red-Code/ClipCMD/releases)
1. Start `ClipCMD.exe`
1. Start `setup.exe`
1. (Optional) Set the prefix and suffix for your commands
1. Specify some commands in the built in text field
- Format: `[macroName]>[macroText]`
- Each line is a new macro
1. There are some built in macros like `time` and `date` which will output the current local time and date
1. Write `[suffix][macroName][preffix]` somewhere, and then select it and press ``CTRL+C``
1. Specify some commands in the built-in text field
- Format:
```
[<command name(s) (separated by comma)>]
<scriptText>
<scriptText>
<scriptText>
...
[<command name(s) (separated by comma)>]
<scriptText>
<scriptText>
<scriptText>
...
```
- Strings between square brackets define macros.
- You can define macro aliases by separating them by a comma `[quit, exit, q]`
- Lines between a macro definition and the next macro (or the end of the file) is PowerShell code.
- Everything you write to the output stream replaces the input macro.
- You can pass parameters to the scripts by separating the parameters by space after the macro: `_combine hi nope_`
- You can access the parameters by accessing the `$args` array.
1. There is one prebuilt macro named `list`. This lists all of your macros.
1. Write `[suffix][macroName][preffix]` somewhere, and then select it and press `CTRL+C`
## Preview
<img width="589" alt="Screenshot 2022-05-03 221232" src="https://user-images.githubusercontent.com/56473591/166560142-9e89c57f-1af0-4340-9317-dacf0064d6c8.png">
<img width="591" alt="image" src="https://user-images.githubusercontent.com/56473591/226146460-d9d2e8fc-3754-44c7-bc43-2e32f1d07847.png">