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\`.