306 lines
11 KiB
C#
306 lines
11 KiB
C#
using Click_to_Call_Tray;
|
|
using Click_to_Call_Tray.Properties;
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using System.Windows.Forms;
|
|
|
|
namespace Click_to_Call_Tray
|
|
{
|
|
internal static class Program
|
|
{
|
|
// Global keyboard hook state
|
|
private static LowLevelKeyboardProc s_hookProc = HookCallback;
|
|
private static IntPtr s_hookID = IntPtr.Zero;
|
|
|
|
// Stores the Virtual Key Code for the current hotkey (e.g., 0x7A for F11)
|
|
internal static int CurrentHotkeyVkCode { get; private set; }
|
|
|
|
// Converts a key name (e.g., "F11") to its VK code (e.g., 0x7A)
|
|
internal static int GetVkCodeFromKeyName(string keyName)
|
|
{
|
|
if (keyName.StartsWith("F") && int.TryParse(keyName.Substring(1), out int fNumber))
|
|
{
|
|
if (fNumber >= 1 && fNumber <= 12)
|
|
{
|
|
// VK_F1 is 0x70. VK_F2 is 0x71, etc.
|
|
return 0x70 + (fNumber - 1);
|
|
}
|
|
}
|
|
// Fallback to F11 (0x7A) if parsing fails
|
|
return 0x7A;
|
|
}
|
|
|
|
// Sets the new hotkey and updates the hook
|
|
internal static void SetNewHotkey(string newKeyName)
|
|
{
|
|
UnhookWindowsHookEx(s_hookID);
|
|
IniConfig.WriteHotkey(newKeyName);
|
|
CurrentHotkeyVkCode = GetVkCodeFromKeyName(newKeyName);
|
|
s_hookID = SetHook(s_hookProc);
|
|
}
|
|
|
|
// P/Invoke for WinAPI
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
internal static extern bool FreeConsole();
|
|
|
|
[STAThread]
|
|
static void Main()
|
|
{
|
|
// 1. Load configuration and set initial hotkey
|
|
string initialHotkeyName = IniConfig.ReadHotkey();
|
|
CurrentHotkeyVkCode = GetVkCodeFromKeyName(initialHotkeyName);
|
|
s_hookID = SetHook(s_hookProc);
|
|
|
|
// 2. Fetch version and check for updates
|
|
var currentTag = UpdateCheck.GetCurrentVersion();
|
|
UpdateCheck.CheckUpdate();
|
|
|
|
// 3. Initialize Application and start the Tray Icon
|
|
ApplicationConfiguration.Initialize();
|
|
Application.Run(new CallTrayContext(currentTag));
|
|
|
|
// 4. Clean up upon exit
|
|
UnhookWindowsHookEx(s_hookID);
|
|
FreeConsole();
|
|
}
|
|
|
|
private static IntPtr SetHook(LowLevelKeyboardProc proc)
|
|
{
|
|
using (Process curProcess = Process.GetCurrentProcess())
|
|
using (ProcessModule curModule = curProcess.MainModule)
|
|
{
|
|
return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
|
|
GetModuleHandle(curModule.ModuleName), 0);
|
|
}
|
|
}
|
|
|
|
private delegate IntPtr LowLevelKeyboardProc(
|
|
int nCode, IntPtr wParam, IntPtr lParam);
|
|
|
|
private static IntPtr HookCallback(
|
|
int nCode, IntPtr wParam, IntPtr lParam)
|
|
{
|
|
const int WM_KEYDOWN = 0x0100;
|
|
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
|
|
{
|
|
int vkCode = Marshal.ReadInt32(lParam);
|
|
// Check if the pressed key matches the current configured hotkey VK code
|
|
if (vkCode == CurrentHotkeyVkCode)
|
|
{
|
|
// Copy selected text (Ctrl+C)
|
|
SendKeys.SendWait("^c");
|
|
Thread.Sleep(100);
|
|
ProcessClipboard();
|
|
return (IntPtr)1; // Consume the keypress
|
|
}
|
|
}
|
|
return CallNextHookEx(s_hookID, nCode, wParam, lParam);
|
|
}
|
|
|
|
private static void ProcessClipboard()
|
|
{
|
|
// The clipboard must be accessed on an STA thread.
|
|
Thread staThread = new Thread(() =>
|
|
{
|
|
try
|
|
{
|
|
string clipboardText = Clipboard.GetText();
|
|
|
|
if (!string.IsNullOrEmpty(clipboardText))
|
|
{
|
|
string cleanedNumber = CleanNumber(clipboardText);
|
|
|
|
if (IsValidNumber(cleanedNumber))
|
|
{
|
|
var url = $"tel:{cleanedNumber}";
|
|
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
|
|
Console.WriteLine($"Initiated call to: {cleanedNumber}");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine($"Invalid number format: {cleanedNumber}");
|
|
}
|
|
}
|
|
}
|
|
catch (ExternalException e)
|
|
{
|
|
Console.WriteLine($"Error accessing clipboard: {e.Message}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"An unexpected error occurred: {ex.Message}");
|
|
}
|
|
});
|
|
|
|
staThread.SetApartmentState(ApartmentState.STA);
|
|
staThread.Start();
|
|
staThread.Join(500);
|
|
}
|
|
|
|
private static string CleanNumber(string input)
|
|
{
|
|
// Remove common separators and trim leading zero (if not part of an international code)
|
|
return input
|
|
.Replace(" ", "")
|
|
.Replace(".", "")
|
|
.Replace("/", "")
|
|
.Replace("-", "")
|
|
.Replace("(0)", "")
|
|
.TrimStart('0');
|
|
}
|
|
|
|
private static bool IsValidNumber(string number)
|
|
{
|
|
// Simple validation: accepts international format, digits, and optional extensions/separators
|
|
return Regex.IsMatch(number, @"^(\+?[1-9][0-9]{0,2})?(\s?\(\d+\)\s?)?\d+(-\d+)?$");
|
|
}
|
|
|
|
#region WinAPI
|
|
private const int WH_KEYBOARD_LL = 13;
|
|
|
|
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
|
private static extern IntPtr SetWindowsHookEx(int idHook,
|
|
LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
|
|
|
|
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
|
|
|
|
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
|
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
|
|
IntPtr wParam, IntPtr lParam);
|
|
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
|
private static extern IntPtr GetModuleHandle(string lpModuleName);
|
|
#endregion
|
|
}
|
|
|
|
// =========================================================================================
|
|
// TRAY ICON CONTEXT
|
|
// =========================================================================================
|
|
|
|
public class CallTrayContext : ApplicationContext
|
|
{
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
static extern bool AllocConsole();
|
|
|
|
private NotifyIcon _trayIcon;
|
|
|
|
public CallTrayContext(string currentTag)
|
|
{
|
|
ContextMenuStrip contextMenu = new ContextMenuStrip();
|
|
|
|
ToolStripMenuItem creditMenuItem = new ToolStripMenuItem(String.Format("krjan02 ©{0}", DateTime.Now.Year));
|
|
creditMenuItem.Enabled = false;
|
|
contextMenu.Items.Add(creditMenuItem);
|
|
|
|
// 1. Debug Console
|
|
ToolStripMenuItem logMenuItem = new ToolStripMenuItem("Debug Console");
|
|
logMenuItem.Click += DisplayConsole;
|
|
contextMenu.Items.Add(logMenuItem);
|
|
|
|
// 2. Hotkey Settings Submenu
|
|
ToolStripMenuItem hotkeyMenu = new ToolStripMenuItem("Set Hotkey");
|
|
AddHotkeyMenuItems(hotkeyMenu);
|
|
contextMenu.Items.Add(hotkeyMenu);
|
|
|
|
// 3. Update Check
|
|
ToolStripMenuItem updateMenuItem = new ToolStripMenuItem("Check for Updates");
|
|
updateMenuItem.Click += CheckForUpdates;
|
|
contextMenu.Items.Add(updateMenuItem);
|
|
|
|
contextMenu.Items.Add(new ToolStripSeparator());
|
|
|
|
// 4. Exit
|
|
ToolStripMenuItem exitMenuItem = new ToolStripMenuItem("Exit");
|
|
exitMenuItem.Click += Exit;
|
|
contextMenu.Items.Add(exitMenuItem);
|
|
|
|
// Initialize Tray Icon
|
|
_trayIcon = new NotifyIcon()
|
|
{
|
|
Icon = Click_to_Call_Tray.Properties.Resources.TrayIcon,
|
|
ContextMenuStrip = contextMenu,
|
|
Visible = true,
|
|
Text = $"Click-to-Call-Tray {currentTag}"
|
|
};
|
|
}
|
|
|
|
private void AddHotkeyMenuItems(ToolStripMenuItem parentMenu)
|
|
{
|
|
// Create F1 through F12 options
|
|
for (int i = 1; i <= 12; i++)
|
|
{
|
|
string keyName = $"F{i}";
|
|
ToolStripMenuItem keyItem = new ToolStripMenuItem(keyName);
|
|
keyItem.Tag = keyName;
|
|
keyItem.Click += SetHotkey_Click;
|
|
parentMenu.DropDownItems.Add(keyItem);
|
|
}
|
|
|
|
// Initially check the currently configured hotkey
|
|
CheckCurrentHotkey(parentMenu);
|
|
}
|
|
|
|
private void CheckCurrentHotkey(ToolStripMenuItem parentMenu)
|
|
{
|
|
string currentKeyName = IniConfig.ReadHotkey();
|
|
foreach (ToolStripMenuItem item in parentMenu.DropDownItems)
|
|
{
|
|
item.Checked = item.Tag.ToString() == currentKeyName;
|
|
}
|
|
}
|
|
|
|
void SetHotkey_Click(object sender, EventArgs e)
|
|
{
|
|
ToolStripMenuItem clickedItem = (ToolStripMenuItem)sender;
|
|
string newKeyName = clickedItem.Tag.ToString();
|
|
|
|
// 1. Update the configuration and reset the hook in Program
|
|
Program.SetNewHotkey(newKeyName);
|
|
|
|
// 2. Update the checkmarks in the UI
|
|
ToolStripMenuItem parentMenu = (ToolStripMenuItem)clickedItem.OwnerItem;
|
|
CheckCurrentHotkey(parentMenu);
|
|
|
|
MessageBox.Show($"Hotkey successfully changed to {newKeyName}. Please ensure you re-select the phone number text before pressing {newKeyName}.", "Hotkey Updated");
|
|
}
|
|
|
|
void DisplayConsole(object sender, EventArgs e)
|
|
{
|
|
AllocConsole();
|
|
}
|
|
|
|
void CheckForUpdates(object sender, EventArgs e)
|
|
{
|
|
if (!UpdateCheck.CheckUpdate())
|
|
{
|
|
MessageBox.Show("No update available.", "Update Status");
|
|
}
|
|
}
|
|
|
|
void Exit(object sender, EventArgs e)
|
|
{
|
|
_trayIcon.Visible = false;
|
|
_trayIcon.Dispose();
|
|
Application.Exit();
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing && _trayIcon != null)
|
|
{
|
|
_trayIcon.Dispose();
|
|
}
|
|
base.Dispose(disposing);
|
|
}
|
|
}
|
|
}
|