Initial release
All checks were successful
Build and Relase / build-release (push) Successful in 1m9s
Build and Relase / create-release (push) Successful in 13s

This commit is contained in:
krjan02 2025-10-14 18:13:17 +02:00
commit 19e74f9e29
13 changed files with 1297 additions and 0 deletions

70
.github/workflows/build-release.yml vendored Normal file
View File

@ -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}}

454
.gitignore vendored Normal file
View File

@ -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

48
Click-to-Call-Tray.csproj Normal file
View File

@ -0,0 +1,48 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<RootNamespace>Click_to_Call_Tray</RootNamespace>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<ApplicationIcon>Resources\TrayIcon.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Content Include="Resources\TrayIcon.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Update="Properties\Settings.Designer.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Update="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
</Project>

25
Click-to-Call-Tray.sln Normal file
View File

@ -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

55
IniConfig.cs Normal file
View File

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

305
Program.cs Normal file
View File

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

83
Properties/Resources.Designer.cs generated Normal file
View File

@ -0,0 +1,83 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Click_to_Call_Tray.Properties {
using System;
/// <summary>
/// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw.
/// </summary>
// 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() {
}
/// <summary>
/// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird.
/// </summary>
[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;
}
}
/// <summary>
/// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle
/// Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Icon ähnlich wie (Symbol).
/// </summary>
internal static System.Drawing.Icon icon2 {
get {
object obj = ResourceManager.GetObject("icon2", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
/// <summary>
/// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Icon ähnlich wie (Symbol).
/// </summary>
internal static System.Drawing.Icon TrayIcon {
get {
object obj = ResourceManager.GetObject("TrayIcon", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
}
}

124
Properties/Resources.resx Normal file
View File

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="TrayIcon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\TrayIcon.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>

26
Properties/Settings.Designer.cs generated Normal file
View File

@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
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;
}
}
}
}

View File

@ -0,0 +1,6 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
</SettingsFile>

BIN
Resources/TrayIcon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

View File

@ -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
}

67
UpdateCheck.cs Normal file
View File

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