diff --git a/BrowseRouter/Interop/Win32/BasicProcessInfo.cs b/BrowseRouter/Interop/Win32/BasicProcessInfo.cs new file mode 100644 index 0000000..0cf5739 --- /dev/null +++ b/BrowseRouter/Interop/Win32/BasicProcessInfo.cs @@ -0,0 +1,48 @@ +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace BrowseRouter.Interop.Win32 +{ + /// + /// A utility class to determine a process parent. Originally copied from https://stackoverflow.com/a/3346055 + /// + [StructLayout(LayoutKind.Sequential)] + public struct BasicProcessInfo + { + // These members must match PROCESS_BASIC_INFORMATION + internal IntPtr Reserved1; + internal IntPtr PebBaseAddress; + internal IntPtr Reserved2_0; + internal IntPtr Reserved2_1; + internal IntPtr UniqueProcessId; + internal IntPtr InheritedFromUniqueProcessId; + + [DllImport("ntdll.dll")] + private static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref BasicProcessInfo processInformation, int processInformationLength, out int returnLength); + + + /// + /// Gets the parent process of a specified process. + /// + /// The process handle. + /// An instance of the Process class. + public static Process? GetParentProcess(IntPtr handle) + { + BasicProcessInfo pbi = new(); + int status = NtQueryInformationProcess(handle, 0, ref pbi, Marshal.SizeOf(pbi), out _); + if (status != 0) + throw new Win32Exception(status); + + try + { + return Process.GetProcessById(pbi.InheritedFromUniqueProcessId.ToInt32()); + } + catch (ArgumentException) + { + // not found + return null; + } + } + } +} diff --git a/BrowseRouter/ProcessService.cs b/BrowseRouter/ProcessService.cs new file mode 100644 index 0000000..7cd28b5 --- /dev/null +++ b/BrowseRouter/ProcessService.cs @@ -0,0 +1,55 @@ +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; +using BrowseRouter.Interop.Win32; + +namespace BrowseRouter +{ + + public interface IProcessService + { + /// + /// Try to get the name of the parent process of the current process. + /// + /// The name of the parent process main window title (may be empty) and the specific process name. + /// True if the name was succesfully found, False otherwise. + public bool TryGetParentProcessTitle(out string parentProcessTitle); + } + + public class ProcessService : IProcessService + { + public bool TryGetParentProcessTitle(out string parentProcessTitle) + { + Process? parentProcess = GetParentProcess(); + if (parentProcess is null || (parentProcess.MainWindowTitle == string.Empty && parentProcess.ProcessName == string.Empty)) + { + parentProcessTitle = string.Empty; + return false; + } + + parentProcessTitle = parentProcess.MainWindowTitle + " -> " + parentProcess.ProcessName; + return true; + } + + /// + /// Gets the parent process of the current process. + /// + /// An instance of the Process class. + public static Process? GetParentProcess() + { + return BasicProcessInfo.GetParentProcess(Process.GetCurrentProcess().Handle); + } + + /// + /// Gets the parent process of specified process. + /// + /// The process id. + /// An instance of the Process class. + public static Process? GetParentProcess(int id) + { + Process process = Process.GetProcessById(id); + return BasicProcessInfo.GetParentProcess(process.Handle); + } + + } +} diff --git a/BrowseRouter/Program.cs b/BrowseRouter/Program.cs index bb18f14..c9cc8a5 100644 --- a/BrowseRouter/Program.cs +++ b/BrowseRouter/Program.cs @@ -64,9 +64,11 @@ private static async Task RunOption(string arg) private static async Task LaunchUrlAsyc(string url) { // Get the window title for whichever application is opening the URL. - string windowTitle = User32.GetActiveWindowTitle(); + ProcessService processService = new(); + if (!processService.TryGetParentProcessTitle(out string windowTitle)) + windowTitle = User32.GetActiveWindowTitle(); //if it didn't work we get the current foreground window name instead - var configService = new ConfigService(); + ConfigService configService = new(); Log.Preference = configService.GetLogPreference(); NotifyPreference notifyPref = configService.GetNotifyPreference(); diff --git a/README.md b/README.md index d1a39ca..2edb933 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,8 @@ Config is a poor man's INI file: [notify] # Show a desktop notification when opening a link. Defaults to true enabled = true +# Should the windows notification sound be silenced when displaying it. Defaults to true +#silent = false [log] # Write log entries to a file. Defaults to false @@ -98,13 +100,27 @@ enabled = true #file = "C:\Users\\Desktop\BrowseRouter.log" # Default browser is first in list -# Use `{url}` to specify UWP app browser details +# Use `{url}` to specify UWP app browser details (not currently working, see following issue: https://github.com/nref/BrowseRouter/issues/10) +# Environment variables (like %ProgramFiles% for example) can be used [browsers] -ff = C:\Program Files\Mozilla Firefox\firefox.exe +ff = %ProgramFiles%\Mozilla Firefox\firefox.exe # Open in a new window #chrome = "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --new-window chrome = C:\Program Files (x86)\Google\Chrome\Application\chrome.exe -edge = C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe +edge = %ProgramFiles(x86)%\Microsoft\Edge\Application\msedge.exe +opera = %UserProfile%\AppData\Local\Programs\Opera\opera.exe + +# Source preferences. +# - Only * is treated as a special character (wildcard). +# - Will take precedence over any URL preferences. +# - Matches on window title and specific process of the application used to open the link, like so "WindowTitle -> ProcessName". +[sources] +* - Notepad -> notepad = ff +Slack | Test* = chrome +# Source with no window (background processes) + -> AutoHotkey64 = ff +# Default case. Added automatically +# * = whatever # Url preferences. # - Only * is treated as a special character (wildcard). @@ -117,37 +133,32 @@ edge = C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe *.youtube.com = chrome *.visualstudio.com = edge *.mozilla.org = ff - -# Source preferences. -# Only * is treated as a special character (wildcard). -# Matches on window title of application used to open link. -# Applied regardless of any url preference match. -[sources] -* - Notepad = ff -Slack | Test = chrome -# Default case. Added automatically -# * = whatever ``` ### Browsers -- Browsers must either be in your path or be fully-qualified paths to the executable e.g. `C:\Program Files (x86)\Google\Chrome\Application\chrome.exe`. +- Browsers must either be : + - fully-qualified paths to the executable e.g. `C:\Program Files (x86)\Google\Chrome\Application\chrome.exe`. + - full path to the executable with environment variable e.g. `%ProgramFiles(x86)%\Google\Chrome\Application\chrome.exe`. + - in your PATH environment variable (you will just have to set the name of the .exe then) e.g. `chrome.exe` with `%ProgramFiles(x86)%\Google\Chrome\Application` added to the PATH variable. - Arguments are optional. However, if you provide arguments the path _must_ be enclosed in quotes. For example, `"chrome.exe" --new-window` - If there are no arguments, then the paths do not need to be quoted. For example, `chrome.exe` will work. ### Sources -- You can optionally specify a "source preference" which matches the window title of the application used to open the link. +- You can specify a "source preference" which matches the window title and the process name of the application used to open the link. - For example, with this in the previous example `config.ini`: ```ini [sources] - *Microsoft Teams* = ff + * - Notepad -> notepad = ff ``` - Then clicking a link in Microsoft Teams will open the link in Firefox, regardless of the URL. + Then clicking a link in Notepad (end of the windows title ending with " - Notepad" with the process named "notepad") will open the link in Firefox, regardless of the URL. + +- Wildcards and full regular expressions may be used to match source window titles and process name the same way urls are. [See Urls section](#Urls). -- In the case of a conflict between a source preference and a URL preference, the source preference wins. +- Sources preferences takes precedence over all URLs preferences, so in the case of a conflict between a source preference and a URL preference, the source preference wins. ### Urls @@ -171,10 +182,6 @@ There are two ways to specify an Url. You can use simple wildcards or full regul - The domain _and_ path are used in the Url comparison. - The regular expression syntax is based on the Microsoft .NET implementation. -### Sources - -Wildcards and full regular expressions may also be used to match source window titles. - ## Logs Logs are stored by default in `%localappdata%/BrowseRouter/`. For example, if you user name is `joe`, then the logs will be in `C:\Users\joe\AppData\Local\BrowseRouter\`.