commit 19e74f9e295c983ac3a8a4cdedeff3bb8192aead Author: krjan02 Date: Tue Oct 14 18:13:17 2025 +0200 Initial release diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml new file mode 100644 index 0000000..e381ebf --- /dev/null +++ b/.github/workflows/build-release.yml @@ -0,0 +1,70 @@ +name: Build and Relase + +on: + push: + tags: + - '[0-9]+.[0-9]+.[0-9]+' + +jobs: + build-release: + runs-on: windows-2022 + + steps: + + #Check out the code from the repository + - name: Checkout repository + uses: actions/checkout@v3 + + #Verify installed .NET Framework version (Optional Debug Step) + - name: Check .NET Framework version + run: | + reg query "HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" /v Release + + - name: Run Replace-Version-Strings.ps1 + run: | + .\Scripts\Replace-Version-Strings.ps1 -version "${{github.ref_name}}" + shell: pwsh + + #Restore NuGet packages + - name: Restore NuGet packages + run: nuget restore Click-to-Call-Tray.sln + + #Build the project + - name: Build the solution + run: msbuild Click-to-Call-Tray.sln /p:Configuration=Release /p:Version=${{github.ref_name}} /t:Rebuild + + #Upload the build artifact + - name: Upload build artifact + uses: actions/upload-artifact@v3 + with: + name: build-artifact + path: bin/Release/net8.0-windows/** + + create-release: + needs: build + runs-on: ubuntu-latest + + steps: + + #Checkout repository to access release-related metadata + - name: Checkout repository + uses: actions/checkout@v3 + + #Download the build artifact + - name: Download build artifact + uses: actions/download-artifact@v3 + with: + name: build-artifact + path: ${{ github.workspace }}/artifact + + - name: Archive Release + run: (cd ${{ github.workspace }}/artifact && zip -r ${{ github.workspace }}/artifact/Click-to-Call-Tray_${{github.ref_name}}.zip .) + + - name: Release + uses: akkuman/gitea-release-action@v1 + with: + name: "Click-to-Call ${{github.ref_name}}" + files: |- + ${{ github.workspace }}/artifact/Click-to-Call-Tray_${{github.ref_name}}.zip + token: '${{secrets.KRJAN02_RELEASE_TOKEN}}' + tag_name: ${{github.ref_name}} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6a72562 --- /dev/null +++ b/.gitignore @@ -0,0 +1,454 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# JetBrains Rider +.idea/ +*.sln.iml + +## +## Visual Studio Code +## +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json \ No newline at end of file diff --git a/Click-to-Call-Tray.csproj b/Click-to-Call-Tray.csproj new file mode 100644 index 0000000..6cc349d --- /dev/null +++ b/Click-to-Call-Tray.csproj @@ -0,0 +1,48 @@ + + + + WinExe + net8.0-windows + Click_to_Call_Tray + enable + true + enable + Resources\TrayIcon.ico + + + + + + + + + + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + \ No newline at end of file diff --git a/Click-to-Call-Tray.sln b/Click-to-Call-Tray.sln new file mode 100644 index 0000000..394b9cc --- /dev/null +++ b/Click-to-Call-Tray.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36401.2 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Click-to-Call-Tray", "Click-to-Call-Tray.csproj", "{FDD892DF-B784-42F6-AA74-6401D2627451}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FDD892DF-B784-42F6-AA74-6401D2627451}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FDD892DF-B784-42F6-AA74-6401D2627451}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FDD892DF-B784-42F6-AA74-6401D2627451}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FDD892DF-B784-42F6-AA74-6401D2627451}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {90EEFD6A-FBA5-4805-A580-5265F9FEA77D} + EndGlobalSection +EndGlobal diff --git a/IniConfig.cs b/IniConfig.cs new file mode 100644 index 0000000..2ea0161 --- /dev/null +++ b/IniConfig.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Click_to_Call_Tray +{ + internal static class IniConfig + { + // Hotkey settings + private const string IniSection = "Settings"; + private const string IniKey = "Hotkey"; + private const string DefaultHotkey = "F11"; // Default key name + + // Full path to the INI file, placed next to the executable + private static readonly string IniFilePath = Path.Combine( + Path.GetDirectoryName(Application.ExecutablePath), "Click-to-Call-Tray.ini"); + + // P/Invoke for INI file reading + [DllImport("kernel32", CharSet = CharSet.Unicode)] + private static extern int GetPrivateProfileString( + string lpAppName, + string lpKeyName, + string lpDefault, + StringBuilder lpReturnedString, + int nSize, + string lpFileName); + + // P/Invoke for INI file writing + [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool WritePrivateProfileString( + string lpAppName, + string lpKeyName, + string lpString, + string lpFileName); + + // Reads the current hotkey from the INI file + public static string ReadHotkey() + { + var temp = new StringBuilder(255); + GetPrivateProfileString(IniSection, IniKey, DefaultHotkey, temp, temp.Capacity, IniFilePath); + return temp.ToString().Trim().ToUpper(); + } + + // Writes the new hotkey to the INI file + public static void WriteHotkey(string hotkeyName) + { + WritePrivateProfileString(IniSection, IniKey, hotkeyName, IniFilePath); + } + } + +} diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..2e95ec8 --- /dev/null +++ b/Program.cs @@ -0,0 +1,305 @@ +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); + } + } +} diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs new file mode 100644 index 0000000..4e9f42d --- /dev/null +++ b/Properties/Resources.Designer.cs @@ -0,0 +1,83 @@ +//------------------------------------------------------------------------------ +// +// Dieser Code wurde von einem Tool generiert. +// Laufzeitversion:4.0.30319.42000 +// +// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn +// der Code erneut generiert wird. +// +//------------------------------------------------------------------------------ + +namespace Click_to_Call_Tray.Properties { + using System; + + + /// + /// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. + /// + // Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert + // -Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert. + // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen + // mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + 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() { + } + + /// + /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Click_to_Call_Tray.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle + /// Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Icon ähnlich wie (Symbol). + /// + internal static System.Drawing.Icon icon2 { + get { + object obj = ResourceManager.GetObject("icon2", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + + /// + /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Icon ähnlich wie (Symbol). + /// + internal static System.Drawing.Icon TrayIcon { + get { + object obj = ResourceManager.GetObject("TrayIcon", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + } +} diff --git a/Properties/Resources.resx b/Properties/Resources.resx new file mode 100644 index 0000000..a03fc62 --- /dev/null +++ b/Properties/Resources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\TrayIcon.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/Properties/Settings.Designer.cs b/Properties/Settings.Designer.cs new file mode 100644 index 0000000..ae7ceaf --- /dev/null +++ b/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// Dieser Code wurde von einem Tool generiert. +// Laufzeitversion:4.0.30319.42000 +// +// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn +// der Code erneut generiert wird. +// +//------------------------------------------------------------------------------ + +namespace Click_to_Call_Tray.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [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()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/Properties/Settings.settings b/Properties/Settings.settings new file mode 100644 index 0000000..049245f --- /dev/null +++ b/Properties/Settings.settings @@ -0,0 +1,6 @@ + + + + + + diff --git a/Resources/TrayIcon.ico b/Resources/TrayIcon.ico new file mode 100644 index 0000000..4abcf39 Binary files /dev/null and b/Resources/TrayIcon.ico differ diff --git a/Scripts/Replace-Version-Strings.ps1 b/Scripts/Replace-Version-Strings.ps1 new file mode 100644 index 0000000..4a55249 --- /dev/null +++ b/Scripts/Replace-Version-Strings.ps1 @@ -0,0 +1,34 @@ +param ( + [string]$version +) + +# Function to replace version in a file +function Replace-Version { + param ( + [string]$filePath, + [string]$regexPattern, + [string]$replacement + ) + + if (Test-Path $filePath) { + (Get-Content $filePath) -replace $regexPattern, $replacement | Set-Content $filePath + Write-Host "Updated $filePath" + } else { + Write-Host "File not found: $filePath" + } +} + +# Update AssemblyInfo.cs files +$assemblyFiles = @( + "Properties\AssemblyInfo.cs" +) + +$assemblyVersionPattern = '(\s*)\[assembly: AssemblyVersion\("(\d+\.\d+\.\d+\.\d+)"\)\]' +$assemblyFileVersionPattern = '(\s*)\[assembly: AssemblyFileVersion\("(\d+\.\d+\.\d+\.\d+)"\)\]' +$assemblyVersionReplacement = '$1[assembly: AssemblyVersion("' + $version + '")]' +$assemblyFileVersionReplacement = '$1[assembly: AssemblyFileVersion("' + $version + '")]' + +foreach ($assemblyFile in $assemblyFiles) { + Replace-Version -filePath $assemblyFile -regexPattern $assemblyVersionPattern -replacement $assemblyVersionReplacement + Replace-Version -filePath $assemblyFile -regexPattern $assemblyFileVersionPattern -replacement $assemblyFileVersionReplacement +} diff --git a/UpdateCheck.cs b/UpdateCheck.cs new file mode 100644 index 0000000..3166fa8 --- /dev/null +++ b/UpdateCheck.cs @@ -0,0 +1,67 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Xml.Linq; + +namespace Click_to_Call_Tray +{ + public class UpdateCheck + { + private static string GetLatestReleaseTag() + { + try + { + using (var client = new HttpClient()) + { + var url = $"https://git.jan.sx/api/v1/repos/krjan02/Click-to-Call-Tray/releases/latest"; + var response = client.GetStringAsync(url).Result; + var json = JObject.Parse(response); + return json["tag_name"].ToString(); + } + } catch(Exception p) + { + return "0.0"; + } + } + + public static string GetCurrentVersion() + { + System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly(); + System.Diagnostics.FileVersionInfo fvi = System.Diagnostics.FileVersionInfo.GetVersionInfo(assembly.Location); + return fvi.FileVersion; + } + + private static bool IsVersionLower(string currentVersion, string releaseVersion) + { + Version current = Version.Parse(currentVersion); + Version release = Version.Parse(releaseVersion); + + return current.CompareTo(release) < 0; + } + + public static bool CheckUpdate() + { + var CurrentVersion = GetCurrentVersion(); + var LatestVersion = GetLatestReleaseTag(); + + if (IsVersionLower(CurrentVersion, LatestVersion)) + { + DialogResult dialogResult = MessageBox.Show("" + + String.Format("Version {0} is available. You are running {1}" + + "\nDo you want to Update now?", LatestVersion, CurrentVersion), + "Click-to-Call-Tray Updater", MessageBoxButtons.YesNo); + if (dialogResult == DialogResult.Yes) + { + System.Diagnostics.Process.Start("https://git.jan.sx/krjan02/Click-to-Call-Tray/releases/latest"); + } + return true; + } + return false; + } + } +}