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.Threading;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
namespace ClipCMD; namespace ClipCMD;
/// <summary> /// <summary>
/// Interaction logic for App.xaml /// Interaction logic for App.xaml
/// </summary> /// </summary>
public partial class App : Application 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"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework> <TargetFramework>net7.0-windows</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<AssemblyVersion>0.0.0.2</AssemblyVersion> <UseWindowsForms>true</UseWindowsForms>
<FileVersion>0.0.0.2</FileVersion> <AssemblyVersion>0.0.0.2</AssemblyVersion>
<Version>0.0.0.2</Version> <FileVersion>0.0.0.2</FileVersion>
</PropertyGroup> <Version>0.0.0.2</Version>
<ApplicationIcon>logo.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="InputSimulator" Version="1.0.4" /> <Content Include="logo.ico">
</ItemGroup> <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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ClipCMD" xmlns:local="clr-namespace:ClipCMD"
mc:Ignorable="d" mc:Ignorable="d"
Title="ClipCMD" Height="450" Width="800"> Title="ClipCMD" Height="450" Width="800"
Closing="OnClose" StateChanged="OnStateChanged" Icon="logo.ico">
<DockPanel> <DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal"> <StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Label Content="Prefix:" Width="45" /> <Label Content="Prefix:" Width="45" />
<TextBox x:Name="prefixTextBox" KeyUp="FixTextBox_TextChanged" Text="01" /> <TextBox x:Name="prefixTextBox" KeyUp="FixTextBox_TextChanged" Text="_" />
</StackPanel> </StackPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal"> <StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Label Content="Suffix:" Width="45" /> <Label Content="Suffix:" Width="45" />
<TextBox x:Name="suffixTextBox" KeyUp="FixTextBox_TextChanged" Text="01" /> <TextBox x:Name="suffixTextBox" KeyUp="FixTextBox_TextChanged" Text="_" />
</StackPanel> </StackPanel>
<Grid DockPanel.Dock="Top" VerticalAlignment="Stretch"> <Grid DockPanel.Dock="Top" VerticalAlignment="Stretch">
@@ -23,14 +24,19 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<DockPanel Grid.Column="0"> <DockPanel Grid.Column="0">
<Label DockPanel.Dock="Top" Content="Static Commands:" /> <Label DockPanel.Dock="Top" Content="Commands:" />
<TextBox DockPanel.Dock="Top" x:Name="staticCommandsTextBox" TextChanged="StaticCommandsTextBox_TextChanged" TextWrapping="Wrap" AcceptsReturn="True" VerticalAlignment="Stretch" /> <TextBox DockPanel.Dock="Top" x:Name="commandsTextBox" HorizontalScrollBarVisibility="Visible" TextChanged="StaticCommandsTextBox_TextChanged" AcceptsReturn="True" VerticalAlignment="Stretch" />
</DockPanel> </DockPanel>
<DockPanel Grid.Column="1"> <DockPanel x:Name="logPanel" Grid.Column="1">
<Label DockPanel.Dock="Top" Content="History:" /> <Label DockPanel.Dock="Top" Content="History:" />
<ListBox DockPanel.Dock="Top" x:Name="logListBox" VerticalAlignment="Stretch" /> <ListBox DockPanel.Dock="Top" x:Name="logListBox" VerticalAlignment="Stretch" />
</DockPanel> </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> </Grid>
</DockPanel> </DockPanel>
</Window> </Window>
+170 -66
View File
@@ -1,11 +1,15 @@
using System; using Microsoft.VisualBasic.FileIO;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Language;
using System.Text;
using System.Windows; using System.Windows;
using System.Windows.Media;
using WindowsInput; using WindowsInput;
using WindowsInput.Native; using WindowsInput.Native;
@@ -17,33 +21,57 @@ namespace ClipCMD;
/// </summary> /// </summary>
public partial class MainWindow : Window public partial class MainWindow : Window
{ {
private readonly Dictionary<string, Func<string, string>> stuffToReplace = new() private readonly Dictionary<string, string> commands = new Dictionary<string, string>();
{
{ "time", (_) => DateTime.Now.ToShortTimeString() }, private readonly System.Windows.Forms.NotifyIcon notifyIcon;
{ "date", (_) => DateTime.Now.ToShortDateString() }, private WindowState storedWindowState = WindowState.Normal;
{ "calc", (math) => new DataTable().Compute(math.Trim()[4..], null)?.ToString() ?? string.Empty },
}; private readonly string cmdPath;
private readonly string fixPath;
public MainWindow() 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(); 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) protected override void OnSourceInitialized(EventArgs e)
{ {
base.OnSourceInitialized(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); ClipboardManager windowClipboardManager = new ClipboardManager(this);
windowClipboardManager.ClipboardChanged += ClipboardManager_ClipboardChanged; 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) if (lines.Length == 2)
{ {
prefixTextBox.Text = lines[0]; prefixTextBox.Text = lines[0];
@@ -51,79 +79,105 @@ public partial class MainWindow : Window
} }
else 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 IDataObject? oldData;
private void ClipboardManager_ClipboardChanged(object? sender, EventArgs e) 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; return;
} }
try 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; return;
} }
if (!text.StartsWith(prefixTextBox.Text) || !text.EndsWith(suffixTextBox.Text)) if (!clipboardText.StartsWith(prefixTextBox.Text) || !clipboardText.EndsWith(suffixTextBox.Text))
{ {
oldData = Clipboard.GetDataObject(); oldData = Clipboard.GetDataObject();
return; 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; TextFieldParser parser = new TextFieldParser(new StringReader(clipboardText))
string logText = text;
string? outText;
if (func is null)
{ {
string[]? parts = staticCommandsTextBox.Text.Split('\n').FirstOrDefault(l => HasFieldsEnclosedInQuotes = true
{ };
if (string.IsNullOrWhiteSpace(l))
{
return false;
}
string? startText = l.Trim().Split('>').FirstOrDefault()?.Trim(); parser.SetDelimiters(" ");
return startText is not null && text == startText;
})?.Split('>');
if (parts?.Length == 2) string[] sections = parser.ReadFields() ?? Array.Empty<string>();
{
outText = parts[1]; sections = sections.Select(s => s.Replace('\0', '\"')).ToArray();
}
else if (sections.Length == 0)
{
oldData = Clipboard.GetDataObject();
return;
}
}
else
{ {
outText = func(text); return;
} }
outText = outText.Trim(); string command = sections[0].Trim();
logListBox.Items.Insert(0, $"{logText} > {outText}");
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(); oldData = Clipboard.GetDataObject();
InputSimulator inputSimulator = new InputSimulator(); 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) catch (Exception ex)
{ {
@@ -134,25 +188,75 @@ public partial class MainWindow : Window
private void StaticCommandsTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e) private void StaticCommandsTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{ {
bool isError = false; string[] lines = commandsTextBox.Text.Split('\n').Append("[#END#]").ToArray();
foreach (string line in staticCommandsTextBox.Text.Split('\n')) 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) _ = commands.TryAdd("list", $"\"{string.Join(", ", commands.Keys)}\"");
{
staticCommandsTextBox.Foreground = Brushes.Red; File.WriteAllText(cmdPath, commandsTextBox.Text);
} }
else
{ private void AddError(string commandNames, string error)
staticCommandsTextBox.Foreground = Brushes.Black; {
stuffToReplace["list"] = (_) => string.Join(", ", staticCommandsTextBox.Text.Split('\n').Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x.Split('>').FirstOrDefault()?.Trim())); errorPanel.Visibility = Visibility.Visible;
File.WriteAllText("cmd.txt", staticCommandsTextBox.Text); 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) private void FixTextBox_TextChanged(object sender, System.Windows.Input.KeyEventArgs e)
@@ -162,6 +266,6 @@ public partial class MainWindow : Window
return; 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? ## 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 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. 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 ### Example
> Prefix: `_`\ > Prefix: `_`\
> Suffix: `_`\ > Suffix: `_`\
> Macro name: `email`\ > Macro name: `email`\
> Macro text: `[email protected]` > Macro script: `Write-Output [email protected]`\
> Macro script (alternative): `"[email protected]"`
> Input: `_email_`\ > Input: `_email_`\
> Output: `[email protected]` > Output: `[email protected]`
@@ -20,13 +22,31 @@ You write a macro anywhere, copy it to your clipboard and it will be automatical
## Usage ## Usage
1. Download one of the [releases](https://github.com/Stone-Red-Code/ClipCMD/releases) 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. (Optional) Set the prefix and suffix for your commands
1. Specify some commands in the built in text field 1. Specify some commands in the built-in text field
- Format: `[macroName]>[macroText]` - Format:
- 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 [<command name(s) (separated by comma)>]
1. Write `[suffix][macroName][preffix]` somewhere, and then select it and press ``CTRL+C`` <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 ## 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">