diff --git a/PasteIntoFile/Dialog.cs b/PasteIntoFile/Dialog.cs
index 2acb18f..8cea0ed 100644
--- a/PasteIntoFile/Dialog.cs
+++ b/PasteIntoFile/Dialog.cs
@@ -8,7 +8,6 @@
using System.Windows.Forms;
using PasteIntoFile.Properties;
using WK.Libraries.BetterFolderBrowserNS;
-using WK.Libraries.SharpClipboardNS;
namespace PasteIntoFile {
public sealed partial class Dialog : MasterForm {
@@ -16,20 +15,14 @@ public sealed partial class Dialog : MasterForm {
private int saveCount = 0;
private bool _formLoaded = false;
- private SharpClipboard _clipMonitor;
+ private SystemEventMonitor eventMonitor = new SystemEventMonitor();
private bool _disableUiEvents = false;
private bool _topMostPreviousState = false;
private const string DYNAMIC_EXTENSION = "*"; // special value to determine extension dynamically
- public SharpClipboard clipMonitor {
- get {
- if (_clipMonitor == null) _clipMonitor = new SharpClipboard();
- return _clipMonitor;
- }
- }
protected override void OnFormClosed(FormClosedEventArgs e) {
// leave the clipboard monitoring chain in a clean way, otherwise the chain will break when the program exits
- clipMonitor?.StopMonitoring();
+ eventMonitor?.StopClipboardMonitoring();
base.OnFormClosed(e);
}
@@ -118,8 +111,8 @@ public Dialog(
BringToFrontForced();
// register clipboard monitor
- clipMonitor.ClipboardChanged += ClipboardChanged;
- FormClosing += (s, e) => clipMonitor.ClipboardChanged -= ClipboardChanged;
+ eventMonitor.StartClipboardMonitoring();
+ eventMonitor.ClipboardChanged += ClipboardChanged;
} else {
@@ -286,7 +279,7 @@ private bool readClipboard() {
- private void ClipboardChanged(Object sender, SharpClipboard.ClipboardChangedEventArgs e) {
+ private void ClipboardChanged(Object sender, EventArgs e) {
// Only process update if live update enabled, or in batch mode
if (!chkEnableLiveClipboardUpdate.Checked && !chkContinuousMode.Checked) return;
@@ -465,9 +458,10 @@ string save(bool overwriteIfExists = false, bool? clearClipboardOverwrite = fals
}
if (clearClipboardOverwrite ?? Settings.Default.clrClipboard) {
- clipMonitor.MonitorClipboard = false; // to prevent callback during batch mode
- Clipboard.Clear();
- clipMonitor.MonitorClipboard = true;
+ // Prevent callback during batch mode
+ eventMonitor.CallWithoutClipboardMonitoring(() => {
+ Clipboard.Clear();
+ });
}
rememberExtension(content, comExt.Text);
diff --git a/PasteIntoFile/KeyboardHook.cs b/PasteIntoFile/KeyboardHook.cs
deleted file mode 100644
index 7f5e339..0000000
--- a/PasteIntoFile/KeyboardHook.cs
+++ /dev/null
@@ -1,133 +0,0 @@
-using System;
-using System.Runtime.InteropServices;
-using System.Windows.Forms;
-
-namespace PasteIntoFile {
- ///
- /// Register global Keyboard Hotkey
- /// Thankfully taken from https://stackoverflow.com/a/27309185/13324744
- ///
- public sealed class KeyboardHook : IDisposable {
- // Registers a hot key with Windows.
- [DllImport("user32.dll", SetLastError = true)]
- private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
- // Unregisters the hot key with Windows.
- [DllImport("user32.dll")]
- private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
-
- ///
- /// Represents the window that is used internally to get the messages.
- ///
- private sealed class Window : NativeWindow, IDisposable {
- private static int WM_HOTKEY = 0x0312;
-
- public Window() {
- // create the handle for the window.
- CreateHandle(new CreateParams());
- }
-
- ///
- /// Overridden to get the notifications.
- ///
- ///
- protected override void WndProc(ref Message m) {
- base.WndProc(ref m);
-
- // check if we got a hot key pressed.
- if (m.Msg == WM_HOTKEY) {
- // get the keys.
- Keys key = (Keys)(((int)m.LParam >> 16) & 0xFFFF);
- ModifierKeys modifier = (ModifierKeys)((int)m.LParam & 0xFFFF);
-
- // invoke the event to notify the parent.
- if (KeyPressed != null)
- KeyPressed(this, new KeyPressedEventArgs(modifier, key));
- }
- }
-
- public event EventHandler KeyPressed;
-
- #region IDisposable Members
-
- public void Dispose() {
- DestroyHandle();
- }
-
- #endregion
- }
-
- private Window _window = new Window();
- private int _currentId;
-
- public KeyboardHook() {
- // register the event of the inner native window.
- _window.KeyPressed += delegate (object sender, KeyPressedEventArgs args) {
- if (KeyPressed != null)
- KeyPressed(this, args);
- };
- }
-
- ///
- /// Registers a hot key in the system.
- ///
- /// The modifiers that are associated with the hot key.
- /// The key itself that is associated with the hot key.
- public void RegisterHotKey(ModifierKeys modifier, Keys key) {
- // increment the counter.
- _currentId += 1;
-
- // register the hot key.
- if (!RegisterHotKey(_window.Handle, _currentId, (uint)modifier, (uint)key)) {
- var error = new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
- throw new InvalidOperationException($"Registration of HotKey {modifier.ToString().Replace(", ", "+").ToUpper()}+{key} failed with error {error.NativeErrorCode}: {error.Message}");
- }
- }
-
- ///
- /// A hot key has been pressed.
- ///
- public event EventHandler KeyPressed;
-
- #region IDisposable Members
-
- public void Dispose() {
- // unregister all the registered hot keys.
- for (int i = _currentId; i > 0; i--) {
- UnregisterHotKey(_window.Handle, i);
- }
-
- // dispose the inner native window.
- _window.Dispose();
- }
-
- #endregion
- }
-
- ///
- /// Event Args for the event that is fired after the hot key has been pressed.
- ///
- public class KeyPressedEventArgs : EventArgs {
- private ModifierKeys _modifier;
- private Keys _key;
-
- internal KeyPressedEventArgs(ModifierKeys modifier, Keys key) {
- _modifier = modifier;
- _key = key;
- }
-
- public ModifierKeys Modifier => _modifier;
-
- public Keys Key => _key;
- }
-
- ///
- /// The enumeration of possible modifiers.
- ///
- [Flags]
- public enum ModifierKeys : uint {
- Alt = 1,
- Control = 2,
- Shift = 4,
- Win = 8
- }
-}
diff --git a/PasteIntoFile/Main.cs b/PasteIntoFile/Main.cs
index cb5a174..09f7a79 100644
--- a/PasteIntoFile/Main.cs
+++ b/PasteIntoFile/Main.cs
@@ -14,7 +14,6 @@
using CommandLine.Text;
using Microsoft.Toolkit.Uwp.Notifications;
using PasteIntoFile.Properties;
-using WK.Libraries.SharpClipboardNS;
#if PORTABLE
using Bluegrams.Application;
#endif
@@ -273,56 +272,55 @@ static int RunTray(ArgsTray args = null) {
}
+ var monitor = new SystemEventMonitor();
+
// Register hotkeys
- KeyboardHook paste = new KeyboardHook();
- paste.KeyPressed += (s, e) => {
- var arg = new ArgsPaste();
- arg.Directory = ExplorerUtil.GetActiveExplorerPath();
- RunPaste(arg);
- };
- paste.RegisterHotKey(ModifierKeys.Win | ModifierKeys.Alt, Keys.V);
- paste.RegisterHotKey(ModifierKeys.Win | ModifierKeys.Alt | ModifierKeys.Shift, Keys.V);
- paste.RegisterHotKey(ModifierKeys.Win | ModifierKeys.Alt | ModifierKeys.Control, Keys.V);
- paste.RegisterHotKey(ModifierKeys.Win | ModifierKeys.Alt | ModifierKeys.Shift | ModifierKeys.Control, Keys.V);
-
- KeyboardHook copy = new KeyboardHook();
- copy.KeyPressed += (s, e) => {
- var files = ExplorerUtil.GetActiveExplorerSelectedFiles();
- if (files.Count == 1) {
- var arg = new ArgsCopy();
- arg.FilePath = files.Item(0).Path;
- RunCopy(arg);
- } else {
- MessageBox.Show(Resources.str_copy_failed_not_single_file, Resources.app_title, MessageBoxButtons.OK, MessageBoxIcon.Error);
+ monitor.KeyPressed += (s, e) => {
+ if (e.Key == Keys.V) {
+ // Paste hotkey
+ var arg = new ArgsPaste();
+ arg.Directory = ExplorerUtil.GetActiveExplorerPath();
+ RunPaste(arg);
+ } else if (e.Key == Keys.C) {
+ // Copy hotkey
+ var files = ExplorerUtil.GetActiveExplorerSelectedFiles();
+ if (files.Count == 1) {
+ var arg = new ArgsCopy();
+ arg.FilePath = files.Item(0).Path;
+ RunCopy(arg);
+ } else {
+ MessageBox.Show(Resources.str_copy_failed_not_single_file, Resources.app_title, MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
}
};
- copy.RegisterHotKey(ModifierKeys.Win | ModifierKeys.Alt, Keys.C);
+ // Paste hotkeys (with different modifier combinations)
+ monitor.RegisterHotKey(ModifierKeys.Win | ModifierKeys.Alt, Keys.V);
+ monitor.RegisterHotKey(ModifierKeys.Win | ModifierKeys.Alt | ModifierKeys.Shift, Keys.V);
+ monitor.RegisterHotKey(ModifierKeys.Win | ModifierKeys.Alt | ModifierKeys.Control, Keys.V);
+ monitor.RegisterHotKey(ModifierKeys.Win | ModifierKeys.Alt | ModifierKeys.Shift | ModifierKeys.Control, Keys.V);
+ // Copy hotkey
+ monitor.RegisterHotKey(ModifierKeys.Win | ModifierKeys.Alt, Keys.C);
+
// Register clipboard observer for patching
- SharpClipboard clipMonitor = null;
if (Settings.Default.trayPatchingEnabled) {
bool skipFirst = true;
- void PatchClipboard(object s, SharpClipboard.ClipboardChangedEventArgs e) {
+ monitor.ClipboardChanged += (s, e) => {
if (skipFirst) { skipFirst = false; return; }
Settings.Default.Reload(); // load modifications made from other instance
if (!Settings.Default.trayPatchingEnabled) return; // allow to temporarily disable
if (Settings.Default.continuousMode) return; // don't interfere with batch mode
+
if (PatchedClipboardContents() is IDataObject data) {
// TODO: This is experimental (might impact performance, might break proprietary formats used internally by other programs, not 100% stable)
- // Temporarily pausing monitoring seams unstable with the SharpClipboard library, so close and re-create the monitor instead
-
- // Stop monitoring and leave clipboard chain cleanly
- clipMonitor.MonitorClipboard = false;
- clipMonitor.StopMonitoring();
- // Re-write clipboard contents
- Clipboard.SetDataObject(data, false);
- // Create a new monitor to handle future updates
- clipMonitor = new SharpClipboard();
- clipMonitor.ClipboardChanged += PatchClipboard;
+ monitor.CallWithoutClipboardMonitoring(() => {
+ // Re-write clipboard contents with patched version
+ Clipboard.SetDataObject(data, false);
+ });
}
- }
- clipMonitor = new SharpClipboard();
- clipMonitor.ClipboardChanged += PatchClipboard;
+ };
+
+ monitor.StartClipboardMonitoring();
}
// Tray icon
@@ -345,7 +343,7 @@ void PatchClipboard(object s, SharpClipboard.ClipboardChangedEventArgs e) {
Application.Run();
// leave the clipboard monitoring chain in a clean way, otherwise the chain will break when the program exits
- clipMonitor?.StopMonitoring();
+ monitor.StopClipboardMonitoring();
icon.Visible = false;
diff --git a/PasteIntoFile/PasteIntoFile.csproj b/PasteIntoFile/PasteIntoFile.csproj
index 5e306eb..ec7043b 100644
--- a/PasteIntoFile/PasteIntoFile.csproj
+++ b/PasteIntoFile/PasteIntoFile.csproj
@@ -52,7 +52,6 @@
-
@@ -76,7 +75,7 @@
-
+
diff --git a/PasteIntoFile/SystemEventMonitor.cs b/PasteIntoFile/SystemEventMonitor.cs
new file mode 100644
index 0000000..29d5fd6
--- /dev/null
+++ b/PasteIntoFile/SystemEventMonitor.cs
@@ -0,0 +1,229 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Windows.Forms;
+
+namespace PasteIntoFile {
+ ///
+ /// Monitor and react to global events like hotkeys and clipboard updates
+ ///
+ public sealed class SystemEventMonitor : IDisposable {
+ // Registers a hot key with Windows.
+ [DllImport("user32.dll", SetLastError = true)]
+ private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
+ // Unregisters the hot key with Windows.
+ [DllImport("user32.dll")]
+ private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
+
+ // Clipboard listener APIs
+ [DllImport("user32.dll", SetLastError = true)]
+ private static extern bool AddClipboardFormatListener(IntPtr hwnd);
+ [DllImport("user32.dll", SetLastError = true)]
+ private static extern bool RemoveClipboardFormatListener(IntPtr hwnd);
+
+ ///
+ /// Represents the window that is used internally to get the messages.
+ ///
+ private sealed class Window : NativeWindow, IDisposable {
+ private static int WM_HOTKEY = 0x0312;
+ private static int WM_CLIPBOARDUPDATE = 0x031D;
+
+ public Window() {
+ // create the handle for the window.
+ CreateHandle(new CreateParams());
+ }
+
+ ///
+ /// Overridden to get the notifications.
+ ///
+ ///
+ protected override void WndProc(ref Message m) {
+ base.WndProc(ref m);
+
+ // check if we got a hot key pressed.
+ if (m.Msg == WM_HOTKEY) {
+ // get the keys.
+ Keys key = (Keys)(((int)m.LParam >> 16) & 0xFFFF);
+ ModifierKeys modifier = (ModifierKeys)((int)m.LParam & 0xFFFF);
+
+ // invoke the event to notify the parent.
+ KeyPressed?.Invoke(this, new KeyPressedEventArgs(modifier, key));
+ }
+
+ // check if we got a clipboard update
+ if (m.Msg == WM_CLIPBOARDUPDATE) {
+ ClipboardChanged?.Invoke(this, EventArgs.Empty);
+ }
+ }
+
+ public event EventHandler KeyPressed;
+ public event EventHandler ClipboardChanged;
+
+ #region IDisposable Members
+
+ public void Dispose() {
+ DestroyHandle();
+ }
+
+ #endregion
+ }
+
+ private Window _window;
+ private int _currentId;
+
+ // clipboard monitoring state
+ private volatile bool _clipboardMonitoring;
+
+ public SystemEventMonitor() {
+ _window = new Window();
+
+ // register the event of the inner native window.
+ _window.KeyPressed += delegate (object sender, KeyPressedEventArgs args) {
+ KeyPressed?.Invoke(this, args);
+ };
+
+ // Map window clipboard events to the monitor, with safe access handling
+ _window.ClipboardChanged += (s, e) => {
+ if (!_clipboardMonitoring) return;
+
+ // Ensure clipboard is readable before firing event
+ for (var i = 0; i < 10; i++) {
+ try {
+ Clipboard.GetDataObject();
+ // Clipboard access works, fire event
+ ClipboardChanged?.Invoke(this, EventArgs.Empty);
+ break;
+ } catch {
+ Thread.Sleep(100);
+ }
+ }
+
+ };
+ }
+
+ ///
+ /// Registers a hot key in the system.
+ ///
+ /// The modifiers that are associated with the hot key.
+ /// The key itself that is associated with the hot key.
+ public void RegisterHotKey(ModifierKeys modifier, Keys key) {
+ // increment the counter.
+ _currentId += 1;
+
+ // register the hot key.
+ if (!RegisterHotKey(_window.Handle, _currentId, (uint)modifier, (uint)key)) {
+ var error = new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
+ throw new InvalidOperationException($"Registration of HotKey {modifier.ToString().Replace(", ", "+").ToUpper()}+{key} failed with error {error.NativeErrorCode}: {error.Message}");
+ }
+ }
+
+ ///
+ /// A hot key has been pressed.
+ ///
+ public event EventHandler KeyPressed;
+
+ ///
+ /// Fired when the clipboard was changed and the new content is accessible
+ ///
+ public event EventHandler ClipboardChanged;
+
+ public bool IsClipboardMonitoring => _clipboardMonitoring;
+
+ ///
+ /// Starts monitoring the clipboard for changes.
+ /// When the clipboard content changes and is accessible, the ClipboardChanged event will be fired.
+ ///
+ ///
+ public void StartClipboardMonitoring() {
+ if (_clipboardMonitoring) return;
+ if (_window == null) throw new InvalidOperationException("Internal window not created.");
+
+ if (!AddClipboardFormatListener(_window.Handle)) {
+ var err = new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
+ throw new InvalidOperationException($"AddClipboardFormatListener failed: {err.NativeErrorCode} {err.Message}");
+ }
+
+ _clipboardMonitoring = true;
+ }
+
+ ///
+ /// Stops monitoring the clipboard for changes.
+ ///
+ public void StopClipboardMonitoring() {
+ if (!_clipboardMonitoring) return;
+ try {
+ RemoveClipboardFormatListener(_window.Handle);
+ } catch {
+ // ignored
+ }
+ _clipboardMonitoring = false;
+ }
+
+ ///
+ /// Calls a callback while temporarily stopping clipboard monitoring.
+ /// The callback may alter the clipboard without triggering the ClipboardChanged event.
+ ///
+ ///
+ public void CallWithoutClipboardMonitoring(Action callback) {
+ var wasMonitoring = _clipboardMonitoring;
+ try {
+ StopClipboardMonitoring();
+ } catch {
+ // ignored
+ }
+ try {
+ callback?.Invoke();
+ } finally {
+ if (wasMonitoring) {
+ StartClipboardMonitoring();
+ }
+ }
+ }
+
+ #region IDisposable Members
+
+ public void Dispose() {
+ // stop clipboard monitoring
+ StopClipboardMonitoring();
+
+ // unregister all the registered hot keys.
+ for (var i = _currentId; i > 0; i--) {
+ UnregisterHotKey(_window.Handle, i);
+ }
+
+ // dispose the inner native window.
+ _window.Dispose();
+ _window = null;
+ }
+
+ #endregion
+ }
+
+ ///
+ /// Event Args for the event that is fired after the hot key has been pressed.
+ ///
+ public class KeyPressedEventArgs : EventArgs {
+ private ModifierKeys _modifier;
+ private Keys _key;
+
+ internal KeyPressedEventArgs(ModifierKeys modifier, Keys key) {
+ _modifier = modifier;
+ _key = key;
+ }
+
+ public ModifierKeys Modifier => _modifier;
+
+ public Keys Key => _key;
+ }
+
+ ///
+ /// The enumeration of possible modifiers.
+ ///
+ [Flags]
+ public enum ModifierKeys : uint {
+ Alt = 1,
+ Control = 2,
+ Shift = 4,
+ Win = 8
+ }
+}