6 Commits
1.4.0 ... 1.6.0

Author SHA1 Message Date
Jed Laundry
911d06154c restore SendKeys as an option 2025-06-14 13:40:33 +12:00
Jed Laundry
16cd8c89f8 rebuild the project from VS 2022 template using .NET 4.7.2 2025-06-14 12:52:52 +12:00
Jed Laundry
429f683619 prepare for v1.5 packaging release 2025-06-07 09:05:28 +12:00
Jed Laundry
0875422116 Update AssemblyInfo.cs 2025-04-06 10:37:16 +12:00
Jed Laundry
a63b268fb8 wip: refactor SendKeys to user32 SendInput 2025-04-06 10:18:08 +12:00
Jed Laundry
c5e9ab752a Fix F8 hotkey not respecting Enter option
The Typer._typeEnter property was not being updated, because the LowLevelKeyboardListener was creating it's own instance, without reading settings
2025-04-06 10:05:07 +12:00
16 changed files with 673 additions and 139 deletions

View File

@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30011.22
# Visual Studio Version 17
VisualStudioVersion = 17.14.36202.13 d17.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TypeClipboard", "TypeClipboard\TypeClipboard.csproj", "{AFF27396-3E91-471E-B46B-2D2DA30FE6DB}"
EndProject

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
@@ -6,7 +6,7 @@
</sectionGroup>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
<userSettings>
<TypeClipboard.Properties.Settings>
@@ -18,4 +18,4 @@
</setting>
</TypeClipboard.Properties.Settings>
</userSettings>
</configuration>
</configuration>

View File

@@ -59,21 +59,4 @@ namespace TypeClipboard
}
}
}
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 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);
// See http://msdn.microsoft.com/en-us/library/ms633541%28v=vs.85%29.aspx
// See http://msdn.microsoft.com/en-us/library/ms649033%28VS.85%29.aspx
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
}
}

View File

@@ -1,4 +1,7 @@
namespace TypeClipboard
using System.Drawing;
using System.Windows.Forms;
namespace TypeClipboard
{
partial class Form1
{
@@ -23,8 +26,8 @@
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
@@ -37,6 +40,8 @@
this.button3 = new System.Windows.Forms.Button();
this.chkEnter = new System.Windows.Forms.CheckBox();
this.toolTip1 = new System.Windows.Forms.ToolTip(this.components);
this.comboBox1 = new System.Windows.Forms.ComboBox();
this.linkLabel1 = new System.Windows.Forms.LinkLabel();
this.SuspendLayout();
//
// textBox1
@@ -61,7 +66,7 @@
// chkHotkey
//
this.chkHotkey.AutoSize = true;
this.chkHotkey.Location = new System.Drawing.Point(172, 42);
this.chkHotkey.Location = new System.Drawing.Point(102, 98);
this.chkHotkey.Name = "chkHotkey";
this.chkHotkey.Size = new System.Drawing.Size(73, 17);
this.chkHotkey.TabIndex = 2;
@@ -102,7 +107,7 @@
// chkEnter
//
this.chkEnter.AutoSize = true;
this.chkEnter.Location = new System.Drawing.Point(244, 42);
this.chkEnter.Location = new System.Drawing.Point(177, 98);
this.chkEnter.Name = "chkEnter";
this.chkEnter.Size = new System.Drawing.Size(78, 17);
this.chkEnter.TabIndex = 7;
@@ -117,10 +122,39 @@
//
this.toolTip1.ShowAlways = true;
//
// comboBox1
//
this.comboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBox1.FormattingEnabled = true;
this.comboBox1.Items.AddRange(new object[] {
"SendInput",
"SendKeys"});
this.comboBox1.Location = new System.Drawing.Point(12, 95);
this.comboBox1.Margin = new System.Windows.Forms.Padding(5);
this.comboBox1.Name = "comboBox1";
this.comboBox1.Size = new System.Drawing.Size(82, 21);
this.comboBox1.TabIndex = 9;
this.comboBox1.SelectedIndexChanged += new System.EventHandler(this.comboBox1_SelectedIndexChanged);
//
// linkLabel1
//
this.linkLabel1.AutoSize = true;
this.linkLabel1.Location = new System.Drawing.Point(293, 98);
this.linkLabel1.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
this.linkLabel1.Name = "linkLabel1";
this.linkLabel1.Size = new System.Drawing.Size(29, 13);
this.linkLabel1.TabIndex = 10;
this.linkLabel1.TabStop = true;
this.linkLabel1.Text = "Help";
this.linkLabel1.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabel1_LinkClicked);
//
// Form1
//
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
this.ClientSize = new System.Drawing.Size(334, 99);
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(332, 124);
this.Controls.Add(this.linkLabel1);
this.Controls.Add(this.comboBox1);
this.Controls.Add(this.chkEnter);
this.Controls.Add(this.button3);
this.Controls.Add(this.textBox2);
@@ -146,14 +180,15 @@
#endregion
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.CheckBox chkHotkey;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.TextBox textBox2;
private System.Windows.Forms.Button button3;
private System.Windows.Forms.CheckBox chkEnter;
private System.Windows.Forms.ToolTip toolTip1;
private TextBox textBox1;
private Button button1;
private CheckBox chkHotkey;
private Button button2;
private TextBox textBox2;
private Button button3;
private CheckBox chkEnter;
private ToolTip toolTip1;
private ComboBox comboBox1;
private LinkLabel linkLabel1;
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Windows.Forms;
@@ -89,16 +90,18 @@ namespace TypeClipboard
private void Form1_Load(object sender, EventArgs e)
{
_listener = new LowLevelKeyboardListener();
_tc = new Typer();
_listener = new LowLevelKeyboardListener(_tc);
// Changing the chkHotkey.Checked property also hooks the listener
chkHotkey.Checked = Properties.Settings.Default.enableHotkey;
_tc = new Typer();
// Changing the chkEnter.Checked property also changes _tc.TypeEnter property
chkEnter.Checked = Properties.Settings.Default.enableEnter;
ClipboardNotification.ClipboardUpdate += delegate (object cb_sender, EventArgs cb_e) {
comboBox1.SelectedItem = Properties.Settings.Default.typeMethod;
ClipboardNotification.ClipboardUpdate += delegate (object cb_sender, EventArgs cb_e)
{
UpdateTextbox();
};
UpdateTextbox();
@@ -142,5 +145,24 @@ namespace TypeClipboard
_tc.TypeEnter = chkEnter.Checked;
Properties.Settings.Default.Save();
}
private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
try
{
Process.Start(new ProcessStartInfo { FileName = @"https://github.com/jlaundry/TypeClipboard/wiki/Help", UseShellExecute = true });
}
catch (Exception ex)
{
MessageBox.Show("Unable to open default browser. Go to https://github.com/jlaundry/TypeClipboard/wiki/Help");
}
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
Properties.Settings.Default.typeMethod = (string)comboBox1.SelectedItem;
_tc.TypeMethod = (string)comboBox1.SelectedItem;
Properties.Settings.Default.Save();
}
}
}

View File

@@ -120,10 +120,4 @@
<metadata name="toolTip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="toolTip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="toolTip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>

View File

@@ -51,11 +51,12 @@ namespace TypeClipboard
private LowLevelKeyboardProc _proc;
private IntPtr _hookID = IntPtr.Zero;
private Typer _tc = new Typer();
public Typer _tc;
public LowLevelKeyboardListener()
public LowLevelKeyboardListener(Typer tc)
{
_proc = HookCallback;
_tc = tc;
}
public void HookKeyboard()

View File

@@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace TypeClipboard
{
static class Program
internal static class Program
{
/// <summary>
/// The main entry point for the application.

View File

@@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Jed Laundry")]
[assembly: AssemblyProduct("Type Clipboard")]
[assembly: AssemblyCopyright("Copyright © Jed Laundry, 2022")]
[assembly: AssemblyCopyright("Copyright © Jed Laundry, 2025")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.4.0.0")]
[assembly: AssemblyFileVersion("1.4.0.0")]
[assembly: AssemblyVersion("1.6.0.0")]
[assembly: AssemblyFileVersion("1.6.0.0")]

View File

@@ -8,10 +8,10 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace TypeClipboard.Properties {
using System;
namespace TypeClipboard.Properties
{
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
@@ -19,43 +19,51 @@ namespace TypeClipboard.Properties {
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
internal class Resources
{
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
internal Resources()
{
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
internal static global::System.Resources.ResourceManager ResourceManager
{
get
{
if ((resourceMan == null))
{
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TypeClipboard.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
internal static global::System.Globalization.CultureInfo Culture
{
get
{
return resourceCulture;
}
set {
set
{
resourceCulture = value;
}
}

View File

@@ -12,7 +12,7 @@ namespace TypeClipboard.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.2.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.14.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
@@ -26,25 +26,46 @@ namespace TypeClipboard.Properties {
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool enableHotkey {
get {
public bool enableHotkey
{
get
{
return ((bool)(this["enableHotkey"]));
}
set {
set
{
this["enableHotkey"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool enableEnter {
get {
public bool enableEnter
{
get
{
return ((bool)(this["enableEnter"]));
}
set {
set
{
this["enableEnter"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("SendInput")]
public string typeMethod
{
get
{
return ((string)(this["typeMethod"]));
}
set
{
this["typeMethod"] = value;
}
}
}
}

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
@@ -12,7 +12,6 @@
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
<IsWebBootstrapper>false</IsWebBootstrapper>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
@@ -25,9 +24,12 @@
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.4.0.%2a</ApplicationVersion>
<ApplicationVersion>1.6.0.0</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
<ForceDesignerDpiUnaware>true</ForceDesignerDpiUnaware>
<!-- Otherwise System.Windows.Input has no methods in .NET 8... -->
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -38,18 +40,15 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>
</DefineConstants>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>typeclipboard.ico</ApplicationIcon>
@@ -91,7 +90,6 @@
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
</Compile>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
@@ -110,9 +108,9 @@
<Content Include="typeclipboard.ico" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.6.1">
<BootstrapperPackage Include=".NETFramework,Version=v4.7.2">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4.6.1 %28x86 and x64%29</ProductName>
<ProductName>Microsoft .NET Framework 4.7.2 %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">

View File

@@ -5,17 +5,36 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Input;
namespace TypeClipboard
{
class Typer
public class Typer
{
private const int INTERKEY_DELAY = 20;
private const int INTERKEY_DELAY = 50;
private bool _typeEnter = false;
private string _typeMethod = "SendInput";
public bool TypeEnter { get => _typeEnter; set => _typeEnter = value; }
public string TypeMethod { get => _typeMethod; set => _typeMethod = value; }
public void Type(String str, int delay = 2000)
{
Thread.Sleep(delay);
NativeMethods.BlockInput(true);
//KeyboardTyper.Reset();
KeyboardTyper.Type(str, _typeEnter, INTERKEY_DELAY);
NativeMethods.BlockInput(false);
}
public void TypeWithSendKeys(String str, int delay)
{
Thread.Sleep(delay);
foreach (Char c in str.ToCharArray())
@@ -29,7 +48,8 @@ namespace TypeClipboard
{
SendKeys.Send("{ENTER}");
break;
} else
}
else
{
return;
}
@@ -79,8 +99,486 @@ namespace TypeClipboard
if (Clipboard.ContainsText(TextDataFormat.UnicodeText))
{
String clipboard = Clipboard.GetText(TextDataFormat.UnicodeText);
this.Type(clipboard, delay);
if (_typeMethod == "SendKeys")
{
TypeWithSendKeys(clipboard, delay);
}
else
{
// SendInput is default
Type(clipboard, delay);
}
}
}
}
// https://gist.github.com/obviliontsk/90403a0fea8c24258570f3a577704864
/// <summary>
/// Native methods
/// </summary>
public static partial class NativeMethods
{
// See http://msdn.microsoft.com/en-us/library/ms649021%28v=vs.85%29.aspx
public const int WM_CLIPBOARDUPDATE = 0x031D;
public static 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);
// See http://msdn.microsoft.com/en-us/library/ms633541%28v=vs.85%29.aspx
// See http://msdn.microsoft.com/en-us/library/ms649033%28VS.85%29.aspx
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
//User32 wrappers cover API's used for Mouse input
#region User32
// Two special bitMasks we define to be able to grab
// shift and character information out of a VKey.
internal const int VKeyShiftMask = 0x0100;
internal const int VKeyCharMask = 0x00FF;
// Various Win32 constants
internal const int KeyeventfExtendedkey = 0x0001;
internal const int KeyeventfKeyup = 0x0002;
internal const int KeyeventfUnicode = 0x0004;
internal const int KeyeventfScancode = 0x0008;
internal const int MouseeventfVirtualdesk = 0x4000;
internal const int SMXvirtualscreen = 76;
internal const int SMYvirtualscreen = 77;
internal const int SMCxvirtualscreen = 78;
internal const int SMCyvirtualscreen = 79;
internal const int XButton1 = 0x0001;
internal const int XButton2 = 0x0002;
internal const int WheelDelta = 120;
internal const int InputMouse = 0;
internal const int InputKeyboard = 1;
// Various Win32 data structures
[StructLayout(LayoutKind.Sequential)]
internal struct INPUT
{
internal int type;
internal INPUTUNION union;
};
[StructLayout(LayoutKind.Explicit)]
internal struct INPUTUNION
{
[FieldOffset(0)]
internal MOUSEINPUT mouseInput;
[FieldOffset(0)]
internal KEYBDINPUT keyboardInput;
};
[StructLayout(LayoutKind.Sequential)]
internal struct MOUSEINPUT
{
internal int dx;
internal int dy;
internal int mouseData;
internal int dwFlags;
internal int time;
internal IntPtr dwExtraInfo;
};
[StructLayout(LayoutKind.Sequential)]
internal struct KEYBDINPUT
{
internal short wVk;
internal short wScan;
internal int dwFlags;
internal int time;
internal IntPtr dwExtraInfo;
};
[StructLayout(LayoutKind.Sequential)]
internal struct POINT
{
public readonly int X;
public readonly int Y;
}
[Flags]
internal enum SendMouseInputFlags
{
Move = 0x0001,
LeftDown = 0x0002,
LeftUp = 0x0004,
RightDown = 0x0008,
RightUp = 0x0010,
MiddleDown = 0x0020,
MiddleUp = 0x0040,
XDown = 0x0080,
XUp = 0x0100,
Wheel = 0x0800,
Absolute = 0x8000,
};
// Importing various Win32 APIs that we need for input
[DllImport("user32.dll")]
public static extern int GetSystemMetrics(int nIndex);
// DllImport CharSet = CharSet.Auto -> Unicode / Utf.16 for windows, so 'W'
[DllImport("user32.dll", EntryPoint = "MapVirtualKeyW")]
public static extern int MapVirtualKey(int nVirtKey, int nMapType);
[DllImport("user32.dll", SetLastError = true)]
internal static extern int SendInput(int nInputs, ref INPUT mi, int cbSize);
[DllImport("user32.dll", EntryPoint = "BlockInput")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool BlockInput([MarshalAs(UnmanagedType.Bool)] bool fBlockIt);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetCursorPos(out POINT lpPoint);
#endregion
}
/// <summary>
/// Exposes a simple interface to common keyboard operations, allowing the user to simulate keyboard input.
/// </summary>
/// <example>
/// The following code types "Hello world" with the specified casing,
/// and then types "hello, capitalized world" which will be in all caps because
/// the left shift key is being held down.
/// To send input with hotkeys use BlockInput method (app will need admin rights) to block all users inputs
/// and reset inputs state, so they won't interfere
/// <code>
/**
NativeMethods.BlockInput(true);
Keyboard.Reset();
Keyboard.Type("Hello world");
Keyboard.Press(Key.LeftShift);
Keyboard.Type("hello, capitalized world");
Keyboard.Release(Key.LeftShift);
NativeMethods.BlockInput(false);
*/
/// </code>
/// </example>
public static class KeyboardTyper
{
#region Public Members
/// <summary>
/// Presses down a key.
/// </summary>
/// <param name="key">The key to press.</param>
public static void Press(Key key)
{
SendKeyboardInput(key, true);
}
/// <summary>
/// Releases a key.
/// </summary>
/// <param name="key">The key to release.</param>
public static void Release(Key key)
{
SendKeyboardInput(key, false);
}
/// <summary>
/// Presses down a key.
/// </summary>
/// <param name="input">The key to press.</param>
public static void Press(char input)
{
SendKeyboardInput(input, true);
}
/// <summary>
/// Releases a key.
/// </summary>
/// <param name="input">The key to release.</param>
public static void Release(char input)
{
SendKeyboardInput(input, false);
}
/// <summary>
/// Resets the system keyboard to a clean state.
/// </summary>
public static void Reset()
{
return;
foreach (Key key in Enum.GetValues(typeof(Key)))
{
// TODO System.Windows.Input.Keyboard exists from WPF, this is WinForms...
//if (key != Key.None && (System.Windows.Input.Keyboard.GetKeyStates(key) & KeyStates.Down) > 0)
//{
// Release(key);
//}
}
}
/// <summary>
/// Performs a press-and-release operation for the specified key, which is effectively equivalent to typing.
/// </summary>
/// <param name="key">The key to press.</param>
/// <param name="releaseDelayMs">Delay before key release</param>
/// <exception cref="ArgumentOutOfRangeException">releaseDelayMs less than 0</exception>
public static void Type(Key key, int releaseDelayMs = 0)
{
if (releaseDelayMs < 0) throw new ArgumentOutOfRangeException(nameof(releaseDelayMs), releaseDelayMs, "Less than zero");
Press(key);
if (releaseDelayMs > 0)
Thread.Sleep(releaseDelayMs);
Release(key);
}
/// <summary>
/// Performs a press-and-release operation for the specified key specific amount of times.
/// </summary>
/// <param name="key">The key to press.</param>
/// <param name="amountToType"></param>
/// <param name="inputDelayMs">Delay after releasing key</param>
/// <param name="releaseDelayMs">Delay before key release</param>
/// <exception cref="ArgumentOutOfRangeException">releaseDelayMs less than 0 or amountToType less than 1 or inputDelayMs less than 0</exception>
public static void TypeKeyNTimes(Key key, int amountToType = 1, int inputDelayMs = 0, int releaseDelayMs = 0)
{
if (amountToType < 1) throw new ArgumentOutOfRangeException(nameof(amountToType), amountToType, "Less than one");
if (inputDelayMs < 0) throw new ArgumentOutOfRangeException(nameof(inputDelayMs), releaseDelayMs, "Less than zero");
for (int i = 0; i < amountToType; i++)
{
Type(key, releaseDelayMs);
if (inputDelayMs > 0)
Thread.Sleep(releaseDelayMs);
}
}
/// <summary>
/// Types the specified text.
/// </summary>
/// <param name="text">The text to type.</param>
/// <param name="inputDelayMs">Delay between typing each key</param>
/// <param name="releaseDelayMs">Delay before key release</param>
/// <exception cref="ArgumentOutOfRangeException">releaseDelayMs less than 0 or inputDelayMs less than 0</exception>
public static void Type(string text, bool typeEnter = false, int inputDelayMs = 0, int releaseDelayMs = 0)
{
int delay = 0;
if (text.Length > 1)
delay = inputDelayMs;
foreach (char c in text)
{
switch (c)
{
case '\n':
if (typeEnter)
{
Press(Key.Return);
if (releaseDelayMs > 0) Thread.Sleep(releaseDelayMs);
Release(Key.Return);
break;
}
else
// Stop at the first line break
return;
case '\r':
break;
default:
Type(c, releaseDelayMs);
break;
}
if (delay > 0) Thread.Sleep(delay);
}
}
/// <summary>
/// Types the specified char.
/// </summary>
/// <param name="input">The char to type.</param>
/// <param name="releaseDelayMs">Delay before key release</param>
/// <exception cref="ArgumentOutOfRangeException">releaseDelayMs less than 0 or inputDelayMs less than 0</exception>
public static void Type(char input, int releaseDelayMs = 0)
{
if (releaseDelayMs < 0) throw new ArgumentOutOfRangeException(nameof(releaseDelayMs), releaseDelayMs, "Less than zero");
Press(input);
if (releaseDelayMs > 0) Thread.Sleep(releaseDelayMs);
Release(input);
}
/// <summary>
/// Types a key while a set of modifier keys are being pressed. Modifer keys
/// are pressed in the order specified and released in reverse order.
/// </summary>
/// <param name="key">Key to type.</param>
/// <param name="modifierKeys">Set of keys to hold down with key is typed.</param>
/// <param name="releaseDelayMs">Delay before key release</param>
public static void Type(Key key, Key[] modifierKeys, int releaseDelayMs = 0)
{
foreach (Key modifierKey in modifierKeys)
{
if (modifierKey == Key.None)
continue;
Press(modifierKey);
}
Type(key, releaseDelayMs);
foreach (Key modifierKey in modifierKeys.Reverse())
{
if (modifierKey == Key.None)
continue;
Release(modifierKey);
}
}
/// <summary>
/// Types a key while a set of modifier keys are being pressed. Modifer keys
/// are pressed in the order Ctrl->Shift->Alt->Win.
/// </summary>
/// <param name="key">Key to type.</param>
/// <param name="modifierKeys">Set of ModifierKeys enum flags to hold down with key is typed. </param>
/// <param name="releaseDelayMs">Delay before key release</param>
public static void Type(Key key, ModifierKeys modifierKeys, int releaseDelayMs = 0)
{
var modifierKeysArray = ModifierKeysToArrayConverter(modifierKeys);
Type(key, modifierKeysArray, releaseDelayMs);
}
#endregion
#region Private Members
/// <summary>
/// Helper method to convert ModifierKeys enum flags to Key array
/// </summary>
/// <param name="modifierKeys">ModifierKeys enum flags</param>
private static Key[] ModifierKeysToArrayConverter(ModifierKeys modifierKeys)
{
Key[] keys = new Key[4];
if (modifierKeys.HasFlag(ModifierKeys.Control))
keys.SetValue(Key.LeftCtrl, 0);
if (modifierKeys.HasFlag(ModifierKeys.Shift))
keys.SetValue(Key.LeftShift, 1);
if (modifierKeys.HasFlag(ModifierKeys.Alt))
keys.SetValue(Key.LeftAlt, 2);
if (modifierKeys.HasFlag(ModifierKeys.Windows))
keys.SetValue(Key.LWin, 3);
return keys;
}
/// <summary>
/// Injects keyboard input into the system.
/// </summary>
/// <param name="key">Indicates the key pressed or released. Can be one of the constants defined in the Key enum.</param>
/// <param name="press">True to inject a key press, false to inject a key release.</param>
private static void SendKeyboardInput(Key key, bool press)
{
NativeMethods.INPUT ki = new NativeMethods.INPUT
{
type = NativeMethods.InputKeyboard
};
ki.union.keyboardInput.wVk = (short)KeyInterop.VirtualKeyFromKey(key);
ki.union.keyboardInput.wScan = (short)NativeMethods.MapVirtualKey(ki.union.keyboardInput.wVk, 0);
int dwFlags = 0;
if (ki.union.keyboardInput.wScan > 0)
{
dwFlags |= NativeMethods.KeyeventfScancode;
}
if (!press)
{
dwFlags |= NativeMethods.KeyeventfKeyup;
}
ki.union.keyboardInput.dwFlags = dwFlags;
if (ExtendedKeys.Contains(key))
{
ki.union.keyboardInput.dwFlags |= NativeMethods.KeyeventfExtendedkey;
}
ki.union.keyboardInput.time = 0;
ki.union.keyboardInput.dwExtraInfo = new IntPtr(0);
if (NativeMethods.SendInput(1, ref ki, Marshal.SizeOf(ki)) == 0)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
/// <summary>
/// Injects keyboard input into the system.
/// </summary>
/// <param name="input">Indicates the character pressed or released..</param>
/// <param name="press">True to inject a key press, false to inject a key release.</param>
private static void SendKeyboardInput(char input, bool press)
{
NativeMethods.INPUT ki = new NativeMethods.INPUT
{
type = NativeMethods.InputKeyboard
};
ki.union.keyboardInput.wVk = 0;
ki.union.keyboardInput.wScan = (short)input;
int dwFlags = 0;
dwFlags |= NativeMethods.KeyeventfUnicode;
if (!press)
{
dwFlags |= NativeMethods.KeyeventfKeyup;
}
ki.union.keyboardInput.dwFlags = dwFlags;
ki.union.keyboardInput.time = 0;
ki.union.keyboardInput.dwExtraInfo = new IntPtr(0);
if (NativeMethods.SendInput(1, ref ki, Marshal.SizeOf(ki)) == 0)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
// From the SDK:
// The extended-key flag indicates whether the keystroke message originated from one of
// the additional keys on the enhanced keyboard. The extended keys consist of the ALT and
// CTRL keys on the right-hand side of the keyboard; the INS, DEL, HOME, END, PAGE UP,
// PAGE DOWN, and arrow keys in the clusters to the left of the numeric keypad; the NUM LOCK
// key; the BREAK (CTRL+PAUSE) key; the PRINT SCRN key; and the divide (/) and ENTER keys in
// the numeric keypad. The extended-key flag is set if the key is an extended key.
//
// - docs appear to be incorrect. Use of Spy++ indicates that break is not an extended key.
// Also, menu key and windows keys also appear to be extended.
private static readonly Key[] ExtendedKeys = {
Key.RightAlt,
Key.RightCtrl,
Key.NumLock,
Key.Insert,
Key.Delete,
Key.Home,
Key.End,
Key.Prior,
Key.Next,
Key.Up,
Key.Down,
Key.Left,
Key.Right,
Key.Apps,
Key.RWin,
Key.LWin
};
// Note that there are no distinct values for the following keys:
// numpad divide
// numpad enter
#endregion
}
}

View File

@@ -9,7 +9,7 @@
<Identity
Name="3373JedLaundry.TypeClipboard"
Publisher="CN=CE320BA0-58BC-4F47-AF5B-94DB661147CC"
Version="1.4.0.0" />
Version="1.6.0.0" />
<Properties>
<DisplayName>TypeClipboard</DisplayName>

View File

@@ -51,50 +51,12 @@
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.props" />
<PropertyGroup>
<ProjectGuid>d041bcc6-3c9d-4bdc-8443-2bec9b290d1a</ProjectGuid>
<TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion>
<TargetPlatformVersion>10.0.26100.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<DefaultLanguage>en-US</DefaultLanguage>
<AppxPackageSigningEnabled>True</AppxPackageSigningEnabled>
<EntryPointProjectUniqueName>..\TypeClipboard\TypeClipboard.csproj</EntryPointProjectUniqueName>
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
<AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
<GenerateTestArtifacts>False</GenerateTestArtifacts>
<AppxBundlePlatforms>neutral</AppxBundlePlatforms>
<AppInstallerUri>C:\temp</AppInstallerUri>
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
<PackageCertificateThumbprint>0AF17626AE23A8AA61789F56A539111877550E76</PackageCertificateThumbprint>
<PackageCertificateKeyFile>TypeClipboardAppx_TemporaryKey.pfx</PackageCertificateKeyFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<AppxBundle>Always</AppxBundle>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<AppxBundle>Always</AppxBundle>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<AppxBundle>Always</AppxBundle>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
<AppxBundle>Always</AppxBundle>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
<AppxBundle>Always</AppxBundle>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<AppxBundle>Always</AppxBundle>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">
<AppxBundle>Always</AppxBundle>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">
<AppxBundle>Always</AppxBundle>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<AppxBundle>Always</AppxBundle>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
<AppxBundle>Always</AppxBundle>
<NoWarn>$(NoWarn);NU1702</NoWarn>
<PackageCertificateThumbprint>FEEF43DFCBAB446B040886D53A9FF02C8C201CBA</PackageCertificateThumbprint>
</PropertyGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
@@ -109,10 +71,11 @@
<Content Include="Images\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Images\StoreLogo.png" />
<Content Include="Images\Wide310x150Logo.scale-200.png" />
<None Include="TypeClipboardAppx_TemporaryKey.pfx" />
<None Include="Package.StoreAssociation.xml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TypeClipboard\TypeClipboard.csproj" />
</ItemGroup>
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" />
<ItemGroup>
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.1742" PrivateAssets="all" />
</ItemGroup>
</Project>

8
teststrings.txt Normal file
View File

@@ -0,0 +1,8 @@
~!@#$%^&*()_+
`1234567890-=
qwertyuiop[]\
QWERTYUIOP{}|
asdfghjkl;'
ASDFGHJKL:"
zxcvbnm,./
ZXCVBNM<>?