This commit is contained in:
Jonathan Martin
2026-03-13 13:22:04 -07:00
parent 1e2a37d56f
commit b580884cb3
25 changed files with 605 additions and 315 deletions
+1
View File
@@ -0,0 +1 @@
blank_issues_enabled: false
+15 -1
View File
@@ -1,3 +1,17 @@
2.21.5 (09/03/2026)
All:
* Added favourite locations to preferences import/export. #1691
* Improved additional Slovak translations from GitHub user kubalav. #1684
* Improved Spanish translations. #1685
* Fixed DPI scaling issues in login/signup UI. #1628
* Fixed a failed assertion at app start. #1582
* Fixed incorrect window ordering when returning to login screen. #1673
* Fixed incorrect Update button size. #1659
* Fixed login not working with windscribe-cli. #1693
Windows:
* Improved app (helper) to support running on Windows Server 2019/2022 without requiring the Wireless LAN Service to be installed. #1681
2.21.4 (02/03/2026)
All:
* Added sound preview for sound notifications. #1582
@@ -6,7 +20,7 @@ All:
* Fixed more incorrect window ordering issues. #1673
* Fixed IP utilities sometimes not available after changing from custom config to regular location. #1677
Windows:
* Improved adapter network identfication state detection on Windows. #1680
* Improved adapter network identification state detection on Windows. #1680
* Fixed IKEv2 may get stuck after waking from sleep. #1676
macOS:
* Fixed app may crash during launch. #1679
+1 -1
View File
@@ -22,7 +22,7 @@ find_package(OpenSSL REQUIRED)
find_package(Boost REQUIRED COMPONENTS serialization)
find_package(spdlog CONFIG REQUIRED)
include("${CMAKE_SOURCE_DIR}/cmake/fetch_wsnet.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/../../cmake/fetch_wsnet.cmake")
# build_all.py sets this option when invoked with the '--sign' flag. Disabled by default
option(DEFINE_USE_SIGNATURE_CHECK_MACRO "Add define USE_SIGNATURE_CHECK to project" OFF)
@@ -0,0 +1,106 @@
#pragma once
#include <string>
#include <system_error>
#include <utility>
#include <windows.h>
/*
This class ensures a DLL is loaded from the system32, or its localized equivalent, folder
to mitigate DLL hijacking attacks.
NOTES:
- You do not need to use this class for 'known' DLLs listed in HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs.
The OS loader ensures all DLLs in that list, and any DLLs loaded by them, are only loaded from system32.
- One should use this class to replace any statically linked DLLs listed in cmake's target_link_libraries declaration.
TL;DR your cmake file should not have a target_link_libraries declaration.
*/
namespace wsl {
class SystemLibLoader {
public:
SystemLibLoader(const SystemLibLoader&) = delete;
SystemLibLoader& operator=(const SystemLibLoader&) = delete;
SystemLibLoader(SystemLibLoader &&other) noexcept : handle_(other.handle_)
{
other.handle_ = nullptr;
}
SystemLibLoader& operator=(SystemLibLoader &&other) noexcept
{
if (this != &other) {
std::swap(handle_, other.handle_);
}
return *this;
}
explicit SystemLibLoader(const char *libName)
{
if (!libName) {
throw std::invalid_argument("Null parameter passed to SystemLibLoader");
}
handle_ = ::LoadLibraryExA(libName, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32 | LOAD_LIBRARY_REQUIRE_SIGNED_TARGET);
if (!handle_) {
throw std::system_error(::GetLastError(), std::system_category(), std::string("SystemLibLoader failed to load DLL ") + libName);
}
}
~SystemLibLoader()
{
if (handle_) {
::FreeLibrary(handle_);
}
}
static bool isAvailable(const char *libName) noexcept
{
if (!libName) {
return false;
}
HMODULE handle = ::LoadLibraryExA(libName, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32 | LOAD_LIBRARY_REQUIRE_SIGNED_TARGET);
if (handle) {
::FreeLibrary(handle);
return true;
}
return false;
}
template<typename T>
T *getFunction(const char *name) const
{
return reinterpret_cast<T *>(getProcAddress(name));
}
private:
HMODULE handle_ = nullptr;
FARPROC getProcAddress(const char *name) const
{
if (!name) {
throw std::invalid_argument("Null parameter passed to getProcAddress");
}
if (!handle_) {
throw std::logic_error("The DLL has not been loaded");
}
auto symbol = ::GetProcAddress(handle_, name);
if (symbol == nullptr) {
throw std::system_error(::GetLastError(), std::system_category(), std::string("SystemLibLoader failed to load function ") + name);
}
return symbol;
}
};
}
@@ -2,7 +2,7 @@
#define WINDSCRIBE_MAJOR_VERSION 2
#define WINDSCRIBE_MINOR_VERSION 21
#define WINDSCRIBE_BUILD_VERSION 4
#define WINDSCRIBE_BUILD_VERSION 5
// only one of these should be enabled; neither -> stable
//#define WINDSCRIBE_IS_BETA
@@ -202,6 +202,16 @@ void LocationsModelManager::saveFavoriteLocations()
locationsModel_->saveFavoriteLocations();
}
QJsonArray LocationsModelManager::favoriteLocationsToJson() const
{
return locationsModel_->favoriteLocationsToJson();
}
void LocationsModelManager::setFavoriteLocationsFromJson(const QJsonArray &arr)
{
locationsModel_->setFavoriteLocationsFromJson(arr);
}
void LocationsModelManager::onChangeConnectionSpeedTimer()
{
for (QHash<LocationID, PingTime>::const_iterator it = connectionSpeeds_.constBegin(); it != connectionSpeeds_.constEnd(); ++it)
@@ -43,6 +43,9 @@ public:
void saveFavoriteLocations();
QJsonArray favoriteLocationsToJson() const;
void setFavoriteLocationsFromJson(const QJsonArray &arr);
QJsonObject renamedLocations() const;
void setRenamedLocations(const QJsonObject &obj);
void resetRenamedLocations();
@@ -3,6 +3,8 @@
#include <QDataStream>
#include <QDataStream>
#include <QIODevice>
#include <QJsonArray>
#include <QJsonObject>
#include <QSettings>
#include "utils/simplecrypt.h"
@@ -131,4 +133,40 @@ void FavoriteLocationsStorage::writeToSettings()
isFavoriteLocationsSetModified_ = false;
}
QJsonArray FavoriteLocationsStorage::toJson() const
{
QJsonArray arr;
for (auto it = favoriteLocations_.constBegin(); it != favoriteLocations_.constEnd(); ++it) {
QJsonObject entry;
entry["type"] = it.key().type();
entry["id"] = it.key().id();
entry["city"] = it.key().city();
if (!it.value().pinnedHostname.isEmpty())
entry["pinnedHostname"] = it.value().pinnedHostname;
if (!it.value().pinnedIp.isEmpty())
entry["pinnedIp"] = it.value().pinnedIp;
arr.append(entry);
}
return arr;
}
void FavoriteLocationsStorage::fromJson(const QJsonArray &arr)
{
favoriteLocations_.clear();
for (const QJsonValue &val : arr) {
if (!val.isObject())
continue;
QJsonObject entry = val.toObject();
if (!entry.contains("type") || !entry.contains("id") || !entry.contains("city"))
continue;
LocationID loc(entry["type"].toInt(), entry["id"].toInt(), entry["city"].toString());
FavoriteData data;
data.pinnedHostname = entry["pinnedHostname"].toString();
data.pinnedIp = entry["pinnedIp"].toString();
favoriteLocations_.insert(loc, data);
}
isFavoriteLocationsSetModified_ = true;
writeToSettings();
}
} //namespace gui_locations
@@ -1,5 +1,6 @@
#pragma once
#include <QJsonArray>
#include <QObject>
#include <QSet>
#include <QHash>
@@ -21,6 +22,9 @@ public:
void readFromSettings();
void writeToSettings();
QJsonArray toJson() const;
void fromJson(const QJsonArray &arr);
private:
struct FavoriteData {
QString pinnedHostname;
@@ -507,6 +507,18 @@ void LocationsModel::saveFavoriteLocations()
favoriteLocationsStorage_.writeToSettings();
}
QJsonArray LocationsModel::favoriteLocationsToJson() const
{
return favoriteLocationsStorage_.toJson();
}
void LocationsModel::setFavoriteLocationsFromJson(const QJsonArray &arr)
{
beginResetModel();
favoriteLocationsStorage_.fromJson(arr);
endResetModel();
}
QVariant LocationsModel::dataForLocation(int row, int role) const
{
if (role == Qt::DisplayRole)
@@ -66,6 +66,9 @@ public:
// the client of the class must explicitly save locations if required
void saveFavoriteLocations();
QJsonArray favoriteLocationsToJson() const;
void setFavoriteLocationsFromJson(const QJsonArray &arr);
QJsonObject renamedLocations() const;
void setRenamedLocations(const QJsonObject &obj);
void resetRenamedLocations();
@@ -64,6 +64,7 @@ void CustomMenuLineEdit::updateScaling()
menu_->clearItems();
menu_->clear();
menu_->initContextMenu();
updatePositions();
}
void CustomMenuLineEdit::appendText(const QString &str)
@@ -230,6 +231,7 @@ void CustomMenuLineEdit::updatePositions()
// Handle icon2 (rightmost)
if (isCustomIcon2_ && icon2_) {
icon2_->updateSize();
xPos -= iconSize + iconSpacing;
icon2_->setGeometry(xPos, height()/2 - iconSize/2, iconSize, iconSize);
icon2_->show();
@@ -240,6 +242,7 @@ void CustomMenuLineEdit::updatePositions()
// Handle icon1 (second from right, or rightmost if icon2 is not set)
if (isCustomIcon1_ && icon1_) {
icon1_->updateSize();
xPos -= iconSize + iconSpacing;
icon1_->setGeometry(xPos, height()/2 - iconSize/2, iconSize, iconSize);
icon1_->show();
@@ -252,6 +255,7 @@ void CustomMenuLineEdit::updatePositions()
if ((echoMode_ == QLineEdit::Password && showRevealToggle_) && !isCustomIcon1_ && !isCustomIcon2_) {
xPos -= iconSize + iconSpacing;
if (icon_) {
icon_->updateSize();
icon_->setGeometry(xPos, height()/2 - iconSize/2, iconSize, iconSize);
icon_->show();
rightMargin = width() - xPos + 4*G_SCALE;
@@ -134,7 +134,7 @@ SignupWindowItem::SignupWindowItem(QGraphicsObject *parent, PreferencesHelper *p
continueButton_ = new CommonGraphics::BubbleButton(this, CommonGraphics::BubbleButton::kWelcome, 121, 38, 19);
continueButton_->setFont(FontDescr(15, QFont::Medium));
continueButton_->setWidth((WINDOW_WIDTH- 36)*G_SCALE);
continueButton_->setWidth(WINDOW_WIDTH - 36);
connect(continueButton_, &CommonGraphics::BubbleButton::clicked, this, &SignupWindowItem::onContinueClick);
continueButton_->setClickable(true);
continueButton_->show();
@@ -195,7 +195,21 @@ void SignupWindowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *
void SignupWindowItem::updateScaling()
{
// These items store pixel-valued widths that must be recomputed for the new scale
// before ScalableGraphicsObject::updateScaling() propagates to children and
// triggers their recalcBoundingRect() / recalcWidth() calls.
const int hintMaxWidth = (WINDOW_WIDTH - 36) * G_SCALE;
passwordHint_->setMaxWidth(hintMaxWidth);
hashHint_->setMaxWidth(hintMaxWidth);
emailHint_->setMaxWidth(hintMaxWidth);
errorHint_->setMaxWidth(hintMaxWidth);
const int btnMaxWidth = (WINDOW_WIDTH - 30) * G_SCALE;
btnVoucherCode_->setMaxWidth(btnMaxWidth);
btnReferred_->setMaxWidth(btnMaxWidth);
ScalableGraphicsObject::updateScaling();
updatePositions();
}
void SignupWindowItem::setClickable(bool enabled)
@@ -130,7 +130,7 @@ void UsernamePasswordEntry::setWidth(int width)
void UsernamePasswordEntry::updateScaling()
{
ClickableGraphicsObject::updateScaling();
int yPos = showDescription_ ? 20 : 0;
int yPos = showDescription_ ? 20 : 0;
userEntryProxy_->setPos(WINDOW_MARGIN*G_SCALE, yPos*G_SCALE);
updateFontSize();
userEntryLine_->setFixedHeight(height_*G_SCALE - userEntryProxy_->pos().y()); // keep text box within drawing region and above line
+14 -2
View File
@@ -1254,8 +1254,14 @@ void MainWindow::onPreferencesExportSettingsClick()
return;
}
QJsonDocument doc(backend_->getPreferences()->toJson());
QJsonObject json = backend_->getPreferences()->toJson();
// Add favourite locations
json["favouriteLocations"] = backend_->locationsModelManager()->favoriteLocationsToJson();
QJsonDocument doc(json);
file.write(doc.toJson());
qCDebug(LOG_BASIC) << "Exported preferences to the file.";
}
@@ -1296,7 +1302,13 @@ void MainWindow::onPreferencesImportSettingsClick()
return;
}
backend_->getPreferences()->updateFromJson(jsonDoc.object());
const QJsonObject jsonObj = jsonDoc.object();
backend_->getPreferences()->updateFromJson(jsonObj);
// Read favourites
if (jsonObj.contains("favouriteLocations") && jsonObj["favouriteLocations"].isArray()) {
backend_->locationsModelManager()->setFavoriteLocationsFromJson(jsonObj["favouriteLocations"].toArray());
}
// Preferences itself does not have any information on the current portmap. Check that connection setting ports are valid, otherwise use default port
types::ConnectionSettings cs = backend_->getPreferences()->connectionSettings();
@@ -1191,6 +1191,7 @@ void MainWindowController::gotoWelcomeWindow()
connect(anim, &QPropertyAnimation::finished, [this]() {
generalMessageWindow_->setVisible(false);
connectWindow_->hide();
welcomeWindow_->setClickable(true);
welcomeWindow_->setFocus();
isAtomicAnimationActive_ = false;
@@ -1395,6 +1396,8 @@ void MainWindowController::gotoLoginWindow()
connect(anim, &QPropertyAnimation::finished, [this]() {
generalMessageWindow_->setVisible(false);
welcomeWindow_->setVisible(false);
connectWindow_->hide();
loginWindow_->setClickable(true);
loginWindow_->setUsernameFocus();
isAtomicAnimationActive_ = false;
@@ -1595,13 +1598,39 @@ void MainWindowController::gotoEmergencyWindow()
void MainWindowController::gotoLoggingInWindow()
{
WS_ASSERT(curWindow_ == WINDOW_ID_LOGIN
|| curWindow_ == WINDOW_ID_WELCOME
|| curWindow_ == WINDOW_ID_SIGNUP
|| curWindow_ == WINDOW_ID_INITIALIZATION
|| curWindow_ == WINDOW_ID_GENERAL_MESSAGE
|| curWindow_ == WINDOW_ID_EXTERNAL_CONFIG
|| curWindow_ == WINDOW_ID_TWO_FACTOR_AUTH);
if (curWindow_ == WINDOW_ID_LOGIN) {
if (curWindow_ == WINDOW_ID_WELCOME) {
curWindow_ = WINDOW_ID_LOGGING_IN;
welcomeWindow_->setClickable(false);
TooltipController::instance().hideAllTooltips();
welcomeWindow_->stackBefore(loggingInWindow_);
loggingInWindow_->setOpacity(0.0);
loggingInWindow_->setVisible(true);
loggingInWindow_->startAnimation();
QPropertyAnimation *anim = new QPropertyAnimation(this);
anim->setTargetObject(loggingInWindow_);
anim->setPropertyName("opacity");
anim->setStartValue(0.0);
anim->setEndValue(1.0);
anim->setDuration(SCREEN_SWITCH_OPACITY_ANIMATION_DURATION);
connect(anim, &QPropertyAnimation::finished, [this]() {
welcomeWindow_->setVisible(false);
isAtomicAnimationActive_ = false;
handleNextWindowChange();
updateMainAndViewGeometry(true);
});
isAtomicAnimationActive_ = true;
anim->start(QPropertyAnimation::DeleteWhenStopped);
} else if (curWindow_ == WINDOW_ID_LOGIN) {
curWindow_ = WINDOW_ID_LOGGING_IN;
loginWindow_->setClickable(false);
TooltipController::instance().hideAllTooltips();
@@ -214,9 +214,7 @@ void UpdateAppItem::onUpdateClick()
void UpdateAppItem::onLanguageChanged()
{
updateButton_->setText(tr("Update"));
int width = preferences_->appSkin() == APP_SKIN_VAN_GOGH ? WIDTH_VAN_GOGH*G_SCALE : WIDTH*G_SCALE;
int updatePosX = width - updateButton_->boundingRect().width();
updateButton_->setPos(updatePosX, 1*G_SCALE);
updatePositions();
update();
}
@@ -242,7 +240,7 @@ void UpdateAppItem::updatePositions()
{
int width = preferences_->appSkin() == APP_SKIN_VAN_GOGH ? WIDTH_VAN_GOGH : WIDTH;
QFontMetrics fm(FontManager::instance().getFont(12, QFont::Normal));
QFontMetrics fm(FontManager::instance().getFont(12, QFont::Medium));
updateButton_->setWidth(fm.horizontalAdvance(tr("Update")) + 16*G_SCALE);
int updatePosX = width*G_SCALE - updateButton_->boundingRect().width();
@@ -88,6 +88,10 @@ void MixedComboBoxItem::updatePositions()
void MixedComboBoxItem::updateSecondaryItemVisibility(bool signal)
{
if (!hasItems()) {
return;
}
QVariant currentValue = currentItem();
if (currentValue == customValue_) {
@@ -152,16 +156,16 @@ void MixedComboBoxItem::updatePreviewButtonVisibility()
previewButton_->show();
QVariant currentValue = currentItem();
bool isActive;
if (currentValue == bundledValue_) {
isActive = true;
} else if (currentValue == customValue_) {
const QString path = selectFileItem_->path();
isActive = !path.isEmpty() && QFile::exists(path);
} else {
isActive = false;
QVariant currentValue;
bool isActive = false;
if (hasItems()) {
currentValue = currentItem();
if (currentValue == bundledValue_) {
isActive = true;
} else if (currentValue == customValue_) {
const QString path = selectFileItem_->path();
isActive = !path.isEmpty() && QFile::exists(path);
}
}
if (!isActive && isPlaying_) {
File diff suppressed because it is too large Load Diff
@@ -372,11 +372,11 @@
</message>
<message>
<source>Standard</source>
<translation>Štandard</translation>
<translation>Štandardné</translation>
</message>
<message>
<source>Hashed</source>
<translation>Hashed</translation>
<translation>Hašované</translation>
</message>
<message>
<source>Username</source>
@@ -396,15 +396,15 @@
</message>
<message>
<source>Hash</source>
<translation>Hash</translation>
<translation>Haš</translation>
</message>
<message>
<source>Account Hash or upload file</source>
<translation>Hash účtu alebo nahra súbor</translation>
<translation>Haš účtu alebo nahrať súbor</translation>
</message>
<message>
<source>Select file for hash</source>
<translation>Vyberte súbor pre hash</translation>
<translation>Vyberte súbor pre haš</translation>
</message>
<message>
<source>All Files (*.*)</source>
@@ -1476,19 +1476,19 @@ Ak problém pretrváva aj po reštarte, pošlite nám denník ladenia, otvorte
</message>
<message>
<source>Clear Wi-Fi History</source>
<translation>Jasná história Wi-Fi</translation>
<translation>Vymazať históriu Wi-Fi</translation>
</message>
<message>
<source>Are you sure?</source>
<translation>Si si istý?</translation>
<translation>Ste si istý?</translation>
</message>
<message>
<source>Are you sure you want to clear all your Wi-Fi history? This will also clear all Wi-Fi passwords except for the one you&apos;re currently connected to. This may also temporarily disable your Wi-Fi.</source>
<translation>Si si istý, že chceš vymazať všetku svoju Wi-Fi históriu? Tým sa tiež vymažú všetky Wi-Fi heslá okrem toho, na ktoré ste momentálne pripojení. To môže tiež dočasne deaktivovať vašu Wi-Fi.</translation>
<translation>Naozaj chcete vymazať celú históriu Wi-Fi? Tým sa tiež vymažú všetky heslá Wi-Fi okrem tej momentálne pripojenej. To môže tiež dočasne vypnúť Wi-Fi.</translation>
</message>
<message>
<source>Remove Wi-Fi SSID and MAC information from your operating system to prevent location history tracking.</source>
<translation>Odstráňte Wi-Fi SSID a MAC údaje z operačného systému, aby ste zabránili sledovaniu histórie polohy.</translation>
<translation>Odstrániť informácie o Wi-Fi SSID a MAC z operačného systému, aby sa zabránilo sledovaniu histórie polohy.</translation>
</message>
<message>
<source>Connect to the VPN with WireGuard even in a hostile environment.</source>
@@ -2026,11 +2026,11 @@ Najprv sa pripojte k sieti</translation>
</message>
<message>
<source>Discard</source>
<translation>Vyhodiť</translation>
<translation>Zahodiť</translation>
</message>
<message>
<source>You have unsaved changes in edit fields. Do you want to save them?</source>
<translation>Máte neuložené zmeny v editačných poliach. Chceš ich zachrán?</translation>
<translation>Máte neuložené zmeny v editačných poliach. Chcete ich ulož?</translation>
</message>
<message>
<source>Save</source>
@@ -2509,7 +2509,7 @@ Ak preinštalovanie nepomôže, obráťte sa na podporu Windscribe a požiadajte
</message>
<message>
<source>Release</source>
<translation>Vydanie</translation>
<translation>Stabilná verzia</translation>
</message>
<message>
<source>Beta</source>
@@ -2741,7 +2741,7 @@ Ak preinštalovanie nepomôže, obráťte sa na podporu Windscribe a požiadajte
</message>
<message>
<source>Show app</source>
<translation>Aplikácia Show</translation>
<translation>Zobraziť aplikáciu</translation>
</message>
</context>
<context>
+1 -3
View File
@@ -10,7 +10,7 @@ set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
project(helper)
include("${CMAKE_SOURCE_DIR}/cmake/fetch_wsnet.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/../../cmake/fetch_wsnet.cmake")
if (WIN32)
add_subdirectory(windows)
@@ -19,5 +19,3 @@ elseif(APPLE)
elseif(UNIX)
add_subdirectory(linux)
endif()
-1
View File
@@ -93,7 +93,6 @@ target_link_libraries(WindscribeService
Newdev
Shlwapi
wevtapi
wlanapi
wsnet::wsnet
Boost::serialization
spdlog::spdlog
@@ -1,11 +1,14 @@
#include "clear_wifi_history.h"
#include <spdlog/spdlog.h>
#include <windows.h>
#include <winevt.h>
#include <wlanapi.h>
#include <spdlog/spdlog.h>
#include <winreg/WinReg.hpp>
#include <vector>
#include <set>
#include "utils/systemlibloader.h"
#include "utils/wsscopeguard.h"
bool ClearWiFiHistory::clear()
{
@@ -49,62 +52,80 @@ bool ClearWiFiHistory::clear()
std::set<std::wstring> ClearWiFiHistory::getCurrentConnectedProfiles()
{
std::set<std::wstring> connectedProfiles;
HANDLE hClient = NULL;
DWORD dwClientVersion = 2;
DWORD dwCurVersion = 0;
DWORD dwResult = 0;
// Initialize WLAN handle
dwResult = WlanOpenHandle(dwClientVersion, NULL, &dwCurVersion, &hClient);
if (dwResult != ERROR_SUCCESS) {
spdlog::warn("WlanOpenHandle failed with error: {}", dwResult);
if (!wsl::SystemLibLoader::isAvailable("wlanapi.dll")) {
return connectedProfiles;
}
// Enumerate WLAN interfaces
PWLAN_INTERFACE_INFO_LIST pIfList = NULL;
dwResult = WlanEnumInterfaces(hClient, NULL, &pIfList);
if (dwResult != ERROR_SUCCESS) {
spdlog::warn("WlanEnumInterfaces failed with error: {}", dwResult);
WlanCloseHandle(hClient, NULL);
return connectedProfiles;
}
try {
// Load the DLL dynamically as Windows server OSes may not have the Wireless LAN Service installed. The DLL will only
// exist on the system if said service is installed, and we don't want to block Windscribe install on the server OS
// by statically linking to the DLL.
wsl::SystemLibLoader wlanLib("wlanapi.dll");
const auto wlanOpenHandle = wlanLib.getFunction<DWORD WINAPI(DWORD, PVOID, PDWORD, PHANDLE)>("WlanOpenHandle");
const auto wlanCloseHandle = wlanLib.getFunction<DWORD WINAPI(HANDLE, PVOID)>("WlanCloseHandle");
const auto wlanEnumInterfaces = wlanLib.getFunction<DWORD WINAPI(HANDLE, PVOID, PWLAN_INTERFACE_INFO_LIST*)>("WlanEnumInterfaces");
const auto wlanQueryInterface = wlanLib.getFunction<DWORD WINAPI(HANDLE, const GUID*, WLAN_INTF_OPCODE, PVOID, PDWORD, PVOID*, PWLAN_OPCODE_VALUE_TYPE)>("WlanQueryInterface");
const auto wlanFreeMemory = wlanLib.getFunction<VOID WINAPI(PVOID)>("WlanFreeMemory");
if (pIfList != NULL) {
// Iterate through all interfaces
for (DWORD i = 0; i < pIfList->dwNumberOfItems; i++) {
PWLAN_INTERFACE_INFO pIfInfo = &pIfList->InterfaceInfo[i];
HANDLE hClient = NULL;
DWORD dwClientVersion = 2;
DWORD dwCurVersion = 0;
// Check if interface is connected
if (pIfInfo->isState == wlan_interface_state_connected) {
PWLAN_CONNECTION_ATTRIBUTES pConnectInfo = NULL;
DWORD connectInfoSize = sizeof(WLAN_CONNECTION_ATTRIBUTES);
// Initialize WLAN handle
DWORD dwResult = wlanOpenHandle(dwClientVersion, NULL, &dwCurVersion, &hClient);
if (dwResult != ERROR_SUCCESS) {
spdlog::warn("WlanOpenHandle failed with error: {}", dwResult);
return connectedProfiles;
}
// Get connection attributes
dwResult = WlanQueryInterface(
hClient,
&pIfInfo->InterfaceGuid,
wlan_intf_opcode_current_connection,
NULL,
&connectInfoSize,
(PVOID*)&pConnectInfo,
NULL
);
auto exitGuard = wsl::wsScopeGuard([&] {
wlanCloseHandle(hClient, NULL);
});
if (dwResult == ERROR_SUCCESS) {
// Get profile name (this is what we need to preserve)
std::wstring profileName(pConnectInfo->strProfileName);
connectedProfiles.insert(profileName);
// Enumerate WLAN interfaces
PWLAN_INTERFACE_INFO_LIST pIfList = NULL;
dwResult = wlanEnumInterfaces(hClient, NULL, &pIfList);
if (dwResult != ERROR_SUCCESS) {
spdlog::warn("WlanEnumInterfaces failed with error: {}", dwResult);
return connectedProfiles;
}
WlanFreeMemory(pConnectInfo);
if (pIfList != NULL) {
// Iterate through all interfaces
for (DWORD i = 0; i < pIfList->dwNumberOfItems; i++) {
PWLAN_INTERFACE_INFO pIfInfo = &pIfList->InterfaceInfo[i];
// Check if interface is connected
if (pIfInfo->isState == wlan_interface_state_connected) {
PWLAN_CONNECTION_ATTRIBUTES pConnectInfo = NULL;
DWORD connectInfoSize = sizeof(WLAN_CONNECTION_ATTRIBUTES);
// Get connection attributes
dwResult = wlanQueryInterface(
hClient,
&pIfInfo->InterfaceGuid,
wlan_intf_opcode_current_connection,
NULL,
&connectInfoSize,
(PVOID*)&pConnectInfo,
NULL
);
if (dwResult == ERROR_SUCCESS) {
// Get profile name (this is what we need to preserve)
std::wstring profileName(pConnectInfo->strProfileName);
connectedProfiles.insert(profileName);
wlanFreeMemory(pConnectInfo);
}
}
}
wlanFreeMemory(pIfList);
}
WlanFreeMemory(pIfList);
} catch (const std::exception& e) {
spdlog::warn("getCurrentConnectedProfiles failed: {}", e.what());
}
WlanCloseHandle(hClient, NULL);
return connectedProfiles;
}
@@ -186,85 +207,105 @@ bool ClearWiFiHistory::clearNlaCache()
bool ClearWiFiHistory::clearWlanProfileFiles(const std::set<std::wstring>& connectedProfiles)
{
HANDLE hClient = NULL;
DWORD dwMaxClient = 2;
DWORD dwClientVersion = 0;
DWORD dwResult = 0;
// Initialize WLAN handle
dwResult = WlanOpenHandle(dwMaxClient, NULL, &dwClientVersion, &hClient);
if (dwResult != ERROR_SUCCESS) {
spdlog::warn("WlanOpenHandle failed with error: {}", dwResult);
return false;
if (!wsl::SystemLibLoader::isAvailable("wlanapi.dll")) {
// Returning true since the OS has no Wi-Fi support without this DLL.
return true;
}
bool overallSuccess = true;
int totalDeleted = 0;
int totalPreserved = 0;
// Enumerate WLAN interfaces
PWLAN_INTERFACE_INFO_LIST pIfList = NULL;
dwResult = WlanEnumInterfaces(hClient, NULL, &pIfList);
if (dwResult != ERROR_SUCCESS) {
spdlog::warn("WlanEnumInterfaces failed with error: {}", dwResult);
WlanCloseHandle(hClient, NULL);
return false;
}
try {
wsl::SystemLibLoader wlanLib("wlanapi.dll");
const auto wlanOpenHandle = wlanLib.getFunction<DWORD WINAPI(DWORD, PVOID, PDWORD, PHANDLE)>("WlanOpenHandle");
const auto wlanCloseHandle = wlanLib.getFunction<DWORD WINAPI(HANDLE, PVOID)>("WlanCloseHandle");
const auto wlanEnumInterfaces = wlanLib.getFunction<DWORD WINAPI(HANDLE, PVOID, PWLAN_INTERFACE_INFO_LIST*)>("WlanEnumInterfaces");
const auto wlanGetProfileList = wlanLib.getFunction<DWORD WINAPI(HANDLE, const GUID*, PVOID, PWLAN_PROFILE_INFO_LIST*)>("WlanGetProfileList");
const auto wlanDeleteProfile = wlanLib.getFunction<DWORD WINAPI(HANDLE, const GUID*, LPCWSTR, PVOID)>("WlanDeleteProfile");
const auto wlanFreeMemory = wlanLib.getFunction<VOID WINAPI(PVOID)>("WlanFreeMemory");
if (pIfList != NULL) {
// Iterate through all interfaces
for (DWORD i = 0; i < pIfList->dwNumberOfItems; i++) {
PWLAN_INTERFACE_INFO pIfInfo = &pIfList->InterfaceInfo[i];
HANDLE hClient = NULL;
DWORD dwMaxClient = 2;
DWORD dwClientVersion = 0;
// Get list of profiles for this interface
PWLAN_PROFILE_INFO_LIST pProfileList = NULL;
dwResult = WlanGetProfileList(hClient, &pIfInfo->InterfaceGuid, NULL, &pProfileList);
// Initialize WLAN handle
DWORD dwResult = wlanOpenHandle(dwMaxClient, NULL, &dwClientVersion, &hClient);
if (dwResult != ERROR_SUCCESS) {
spdlog::warn("WlanOpenHandle failed with error: {}", dwResult);
return false;
}
if (dwResult != ERROR_SUCCESS) {
spdlog::warn("WlanGetProfileList failed for interface {} with error: {}", i, dwResult);
overallSuccess = false;
continue;
}
if (pProfileList == nullptr) {
spdlog::error("pProfileList = nullptr in ClearWiFiHistory::clearWlanProfileFiles");
overallSuccess = false;
continue;
}
auto exitGuard = wsl::wsScopeGuard([&] {
wlanCloseHandle(hClient, NULL);
});
// Iterate through profiles
for (DWORD j = 0; j < pProfileList->dwNumberOfItems; j++) {
PWLAN_PROFILE_INFO pProfile = &pProfileList->ProfileInfo[j];
std::wstring profileName(pProfile->strProfileName);
int totalDeleted = 0;
int totalPreserved = 0;
// Check if this profile is currently connected
if (connectedProfiles.find(profileName) != connectedProfiles.end()) {
spdlog::debug(L"Preserving connected WiFi profile: {}", profileName);
totalPreserved++;
// Enumerate WLAN interfaces
PWLAN_INTERFACE_INFO_LIST pIfList = NULL;
dwResult = wlanEnumInterfaces(hClient, NULL, &pIfList);
if (dwResult != ERROR_SUCCESS) {
spdlog::warn("WlanEnumInterfaces failed with error: {}", dwResult);
return false;
}
if (pIfList != NULL) {
// Iterate through all interfaces
for (DWORD i = 0; i < pIfList->dwNumberOfItems; i++) {
PWLAN_INTERFACE_INFO pIfInfo = &pIfList->InterfaceInfo[i];
// Get list of profiles for this interface
PWLAN_PROFILE_INFO_LIST pProfileList = NULL;
dwResult = wlanGetProfileList(hClient, &pIfInfo->InterfaceGuid, NULL, &pProfileList);
if (dwResult != ERROR_SUCCESS) {
spdlog::warn("WlanGetProfileList failed for interface {} with error: {}", i, dwResult);
overallSuccess = false;
continue;
}
if (pProfileList == nullptr) {
spdlog::error("pProfileList = nullptr in ClearWiFiHistory::clearWlanProfileFiles");
overallSuccess = false;
continue;
}
// Delete the profile
dwResult = WlanDeleteProfile(hClient, &pIfInfo->InterfaceGuid, profileName.c_str(), NULL);
// Iterate through profiles
for (DWORD j = 0; j < pProfileList->dwNumberOfItems; j++) {
PWLAN_PROFILE_INFO pProfile = &pProfileList->ProfileInfo[j];
std::wstring profileName(pProfile->strProfileName);
if (dwResult == ERROR_SUCCESS) {
spdlog::debug(L"Deleted WiFi profile: {}", profileName);
totalDeleted++;
} else {
spdlog::warn(L"Failed to delete WiFi profile '{}': error {}", profileName, dwResult);
overallSuccess = false;
// Check if this profile is currently connected
if (connectedProfiles.find(profileName) != connectedProfiles.end()) {
spdlog::debug(L"Preserving connected WiFi profile: {}", profileName);
totalPreserved++;
continue;
}
// Delete the profile
dwResult = wlanDeleteProfile(hClient, &pIfInfo->InterfaceGuid, profileName.c_str(), NULL);
if (dwResult == ERROR_SUCCESS) {
spdlog::debug(L"Deleted WiFi profile: {}", profileName);
totalDeleted++;
} else {
spdlog::warn(L"Failed to delete WiFi profile '{}': error {}", profileName, dwResult);
overallSuccess = false;
}
}
if (pProfileList != NULL) {
wlanFreeMemory(pProfileList);
}
}
if (pProfileList != NULL) {
WlanFreeMemory(pProfileList);
}
wlanFreeMemory(pIfList);
}
WlanFreeMemory(pIfList);
spdlog::debug("WiFi profile cleanup: {} profiles deleted, {} preserved", totalDeleted, totalPreserved);
} catch (const std::exception& e) {
spdlog::warn("clearWlanProfileFiles failed: {}", e.what());
return false;
}
WlanCloseHandle(hClient, NULL);
spdlog::debug("WiFi profile cleanup: {} profiles deleted, {} preserved", totalDeleted, totalPreserved);
return overallSuccess;
}
@@ -1,4 +1,5 @@
#pragma once
#include <string>
#include <set>
@@ -40,4 +41,3 @@ private:
// Returns: true if operation succeeded, false otherwise
static bool clearRegistry(const std::wstring &subKey);
};
@@ -15,10 +15,10 @@
#include <sstream>
#include <spdlog/spdlog.h>
#include "../../../client/client-common/types/global_consts.h"
#include "../../../client/client-common/utils/servicecontrolmanager.h"
#include "../../../client/client-common/utils/win32handle.h"
#include "../../../client/client-common/utils/wsscopeguard.h"
#include "types/global_consts.h"
#include "utils/servicecontrolmanager.h"
#include "utils/win32handle.h"
#include "utils/wsscopeguard.h"
#include "../../common/helper_commands.h"
#include "../executecmd.h"
#include "../utils.h"