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); } } }