Skip to content

Commit ba015a6

Browse files
committed
implement browser detection for Windows OS
It's a bit ugly, because we have to go to the Windows registry for that and the WinAPI stuff is more C than C++.
1 parent d4bd269 commit ba015a6

File tree

4 files changed

+183
-8
lines changed

4 files changed

+183
-8
lines changed

code/browser_detection.cpp

Lines changed: 154 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,160 @@
1919
*/
2020

2121
#include "browser_detection.hpp"
22+
#ifdef _WIN32
23+
#include <algorithm>
24+
#include <windows.h>
25+
#endif
26+
27+
#ifdef _WIN32
28+
std::vector<std::string> listSubKeyNames()
29+
{
30+
std::vector<std::string> result;
31+
HKEY startMenuInternet;
32+
LONG res = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Clients\\StartMenuInternet", 0, KEY_READ, &startMenuInternet);
33+
if (res != ERROR_SUCCESS)
34+
{
35+
// Failed to open key, maybe due to permissions.
36+
return {};
37+
}
38+
for (DWORD idx = 0; ; ++idx)
39+
{
40+
constexpr DWORD buffer_size = 256;
41+
char buffer[buffer_size];
42+
DWORD name_length = buffer_size;
43+
res = RegEnumKeyExA(startMenuInternet, idx, &buffer[0], &name_length, nullptr, nullptr, nullptr, nullptr);
44+
if (res == ERROR_NO_MORE_ITEMS)
45+
{
46+
// reached last sub key
47+
RegCloseKey(startMenuInternet);
48+
break;
49+
}
50+
if (res != ERROR_SUCCESS)
51+
{
52+
// Failed to query key, maybe due to permissions?
53+
RegCloseKey(startMenuInternet);
54+
return {};
55+
}
56+
const DWORD nul_pos = name_length >= buffer_size ? buffer_size - 1 : name_length;
57+
buffer[nul_pos] = '\0';
58+
result.emplace_back(std::string(buffer));
59+
}
60+
return result;
61+
}
62+
63+
std::vector<std::filesystem::path> getBrowserPaths()
64+
{
65+
std::vector<std::filesystem::path> result;
66+
67+
const auto subKeyNames = listSubKeyNames();
68+
for (const auto& subKeyName: subKeyNames)
69+
{
70+
const auto keyName = "SOFTWARE\\Clients\\StartMenuInternet\\" + subKeyName + "\\shell\\open\\command";
71+
HKEY commandKey;
72+
LONG res = RegOpenKeyExA(HKEY_LOCAL_MACHINE, keyName.c_str(), 0, KEY_READ, &commandKey);
73+
if (res != ERROR_SUCCESS)
74+
{
75+
// Failed to open command key, maybe due to permissions.
76+
continue;
77+
}
78+
constexpr DWORD value_buffer_size = 1024;
79+
unsigned char value_buffer[value_buffer_size];
80+
DWORD valueType = 0;
81+
DWORD valueSize = value_buffer_size - 1;
82+
res = RegQueryValueExA(commandKey, "", nullptr, &valueType, value_buffer, &valueSize);
83+
if (res != ERROR_SUCCESS)
84+
{
85+
// Failed to query key, maybe due to permissions?
86+
RegCloseKey(commandKey);
87+
continue;
88+
}
89+
if (valueType != REG_SZ)
90+
{
91+
// Wrong value type, only strings allowed.
92+
RegCloseKey(commandKey);
93+
continue;
94+
}
95+
if ((valueSize == 0) || (valueSize >= value_buffer_size))
96+
{
97+
// Invalid size.
98+
RegCloseKey(commandKey);
99+
continue;
100+
}
101+
// Value may or may not be NUL-terminated, so ensure the NUL byte is there.
102+
if (value_buffer[valueSize - 1] != 0)
103+
{
104+
value_buffer[valueSize] = 0;
105+
}
106+
auto potential_path = std::string(reinterpret_cast<char*>(value_buffer));
107+
RegCloseKey(commandKey);
108+
const auto size = potential_path.size();
109+
if ((potential_path[0] == '"') && (potential_path[size-1] == '"'))
110+
{
111+
potential_path.erase(size - 1, 1).erase(0, 1);
112+
}
113+
std::error_code error;
114+
std::filesystem::path path(potential_path);
115+
if (std::filesystem::is_regular_file(path, error) && !error)
116+
{
117+
result.push_back(path);
118+
}
119+
} // for
120+
121+
return result;
122+
}
123+
124+
std::optional<Browser> getBrowserType(const std::filesystem::path& path)
125+
{
126+
const auto basename = path.filename().string();
127+
if ((basename == "firefox.exe") || (basename == "librewolf.exe"))
128+
{
129+
return Browser::Firefox;
130+
}
131+
if (basename == "chrome.exe")
132+
{
133+
return Browser::Chromium;
134+
}
135+
if (basename == "seamonkey.exe")
136+
{
137+
return Browser::SeaMonkey;
138+
}
139+
if (basename == "msedge.exe")
140+
{
141+
return Browser::Edge;
142+
}
143+
144+
// Unknown browser type.
145+
return std::nullopt;
146+
}
147+
#endif // _WIN32
22148

23149
std::optional<BrowserInformation> detect_browser()
24150
{
25151
#ifdef _WIN32
26-
// TODO: Not implemented yet.
27-
return std::optional<BrowserInformation>();
152+
const auto paths = getBrowserPaths();
153+
std::vector <BrowserInformation> browsers;
154+
for (const auto& path: paths)
155+
{
156+
const auto type = getBrowserType(path);
157+
if (!type.has_value())
158+
{
159+
continue;
160+
}
161+
browsers.push_back({path, type.value()});
162+
} // for
163+
164+
if (browsers.empty())
165+
{
166+
return std::nullopt;
167+
}
168+
169+
// Sort to make sure we have stuff like Edge only as last resort.
170+
std::stable_sort(browsers.begin(), browsers.end(),
171+
[](const BrowserInformation& left, const BrowserInformation& right) {
172+
return left.type < right.type;
173+
});
174+
175+
return browsers.at(0);
28176
#else
29177
// On Linux systems the paths are usually similar, depending on how the
30178
// browser was packaged. So it's easiest to just test some known paths.
@@ -58,6 +206,10 @@ std::vector<std::string> additional_parameters(const Browser type)
58206
return { "--private-window" };
59207
case Browser::Chromium:
60208
return { "--incognito" };
209+
case Browser::SeaMonkey:
210+
return { "-private" };
211+
case Browser::Edge:
212+
return { "--inprivate" };
61213
default:
62214
return {};
63215
}

code/browser_detection.hpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,13 @@ enum class Browser
3333
Firefox,
3434

3535
/// Chromium browser
36-
Chromium
36+
Chromium,
37+
38+
/// SeaMonkey browser
39+
SeaMonkey,
40+
41+
/// Microsoft Edge
42+
Edge
3743
};
3844

3945
struct BrowserInformation

code/html_generation.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ void openFirstIndexFile(const FolderMap& fm, const std::string& html_dir)
6565
return;
6666
}
6767
// TODO: Open file via Boost Process.
68+
std::cout << "Run\n\n\t" << browser.value().path.string();
69+
const auto params = additional_parameters(browser.value().type);
70+
for (const auto& param: params)
71+
{
72+
std::cout << " " << param;
73+
}
74+
std::cout << " " << fullFileName << "\n\n"
75+
<< "to show the generated HTML output.\n";
6876
}
6977

7078
int generateHtmlFiles(const MessageDatabase& mdb, const FolderMap& fm, const HTMLOptions htmlOptions)

tests/components/browser_detection.cpp

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,8 @@ TEST_CASE("browser detection")
2626
SECTION("detect_browser")
2727
{
2828
const auto info = detect_browser();
29-
#if _WIN32
30-
// TODO: Change this as soon as detect_browser() works on Windows, too.
31-
REQUIRE_FALSE( info.has_value() );
32-
#else
3329
REQUIRE( info.has_value() );
3430
REQUIRE_FALSE( info.value().path.empty() );
35-
#endif
3631
}
3732

3833
SECTION("additional parameters")
@@ -50,5 +45,19 @@ TEST_CASE("browser detection")
5045
REQUIRE( params.size() == 1 );
5146
REQUIRE( params[0] == "--private-window" );
5247
}
48+
49+
SECTION("SeaMonkey")
50+
{
51+
const auto params = additional_parameters(Browser::SeaMonkey);
52+
REQUIRE( params.size() == 1 );
53+
REQUIRE( params[0] == "-private" );
54+
}
55+
56+
SECTION("Edge")
57+
{
58+
const auto params = additional_parameters(Browser::Edge);
59+
REQUIRE( params.size() == 1 );
60+
REQUIRE( params[0] == "--inprivate" );
61+
}
5362
}
5463
}

0 commit comments

Comments
 (0)