mirror of
https://github.com/ashaduri/gsmartcontrol.git
synced 2026-09-27 06:15:45 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
460ed687ee | ||
|
|
b8de0d933b |
@@ -11,8 +11,6 @@ Copyright:
|
||||
|
||||
#include "gui_utils.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -167,59 +165,6 @@ bool gui_show_text_entry_dialog(const std::string& title, const std::string& mes
|
||||
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
/// Storage for Windows fractional scaling percentage (0 if no fractional scaling)
|
||||
int g_windows_fractional_scaling_percent = 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
int app_get_windows_fractional_scaling_percent()
|
||||
{
|
||||
return g_windows_fractional_scaling_percent;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void app_set_windows_fractional_scaling_percent(int percent)
|
||||
{
|
||||
g_windows_fractional_scaling_percent = percent;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void app_apply_fractional_scaling_to_default_size(Gtk::Window* window, int config_size_w, int config_size_h)
|
||||
{
|
||||
if (!window) {
|
||||
return;
|
||||
}
|
||||
|
||||
int size_w = config_size_w;
|
||||
int size_h = config_size_h;
|
||||
|
||||
// Apply fractional scaling adjustment on Windows if no custom size is configured
|
||||
// This compensates for GTK3's lack of fractional scaling support
|
||||
const int full_percent = g_windows_fractional_scaling_percent;
|
||||
if (full_percent > 0 && size_w == 0 && size_h == 0) {
|
||||
// Get the default size from glade and scale it
|
||||
int glade_w = 0, glade_h = 0;
|
||||
window->get_default_size(glade_w, glade_h);
|
||||
if (glade_w > 0 && glade_h > 0) {
|
||||
const double system_scale = static_cast<double>(full_percent) / 100.0;
|
||||
const int integer_ui_scale = full_percent / 100; // GTK3 uses floor (integer) scaling
|
||||
const double correction = system_scale / static_cast<double>(integer_ui_scale);
|
||||
size_w = static_cast<int>(std::lround(glade_w * correction));
|
||||
size_h = static_cast<int>(std::lround(glade_h * correction));
|
||||
}
|
||||
}
|
||||
|
||||
if (size_w > 0 && size_h > 0) {
|
||||
window->set_default_size(size_w, size_h);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -59,23 +59,6 @@ bool gui_show_text_entry_dialog(const std::string& title, const std::string& mes
|
||||
|
||||
|
||||
|
||||
/// Get the fractional scaling percentage detected on Windows (0 if not detected or integer scale).
|
||||
/// For example, at 150% scaling, this returns 150; at 125% scaling, this returns 125; at 250% scaling, this returns 250.
|
||||
/// Returns 0 for exact integer scales (100%, 200%, etc.).
|
||||
int app_get_windows_fractional_scaling_percent();
|
||||
|
||||
|
||||
/// Set the fractional scaling percentage detected on Windows.
|
||||
/// This should only be called once during application initialization.
|
||||
void app_set_windows_fractional_scaling_percent(int percent);
|
||||
|
||||
|
||||
/// Apply fractional scaling to default window size if fractional scaling is detected.
|
||||
/// This compensates for GTK3's lack of fractional scaling support on Windows.
|
||||
/// \param window The window to apply scaling to
|
||||
/// \param config_size_w Configured width (0 if using glade default)
|
||||
/// \param config_size_h Configured height (0 if using glade default)
|
||||
void app_apply_fractional_scaling_to_default_size(Gtk::Window* window, int config_size_w, int config_size_h);
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -68,5 +68,4 @@ endif()
|
||||
|
||||
|
||||
add_subdirectory(ui)
|
||||
add_subdirectory(tests)
|
||||
|
||||
|
||||
@@ -31,9 +31,6 @@ Copyright:
|
||||
GscExecutorLogWindow::GscExecutorLogWindow(BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder> ui)
|
||||
: AppBuilderWidget<GscExecutorLogWindow, false>(gtkcobj, std::move(ui))
|
||||
{
|
||||
// Apply fractional scaling to default window size
|
||||
app_apply_fractional_scaling_to_default_size(this, 0, 0);
|
||||
|
||||
// Connect callbacks
|
||||
|
||||
Gtk::Button* window_close_button = nullptr;
|
||||
|
||||
@@ -35,7 +35,6 @@ Copyright:
|
||||
#include "gsc_info_window.h"
|
||||
#include "gsc_executor_error_dialog.h"
|
||||
#include "gsc_startup_settings.h"
|
||||
#include "gsc_init.h" // app_apply_fractional_scaling_to_default_size()
|
||||
|
||||
|
||||
|
||||
@@ -167,7 +166,9 @@ GscInfoWindow::GscInfoWindow(BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder>
|
||||
{
|
||||
const int def_size_w = rconfig::get_data<int>("gui/info_window/default_size_w");
|
||||
const int def_size_h = rconfig::get_data<int>("gui/info_window/default_size_h");
|
||||
app_apply_fractional_scaling_to_default_size(this, def_size_w, def_size_h);
|
||||
if (def_size_w > 0 && def_size_h > 0) {
|
||||
set_default_size(def_size_w, def_size_h);
|
||||
}
|
||||
}
|
||||
|
||||
// Create missing widgets
|
||||
|
||||
+4
-10
@@ -47,7 +47,6 @@
|
||||
|
||||
#include "applib/window_instance_manager.h"
|
||||
#include "applib/gsc_settings.h"
|
||||
#include "applib/gui_utils.h" // app_set_windows_fractional_scaling_percent()
|
||||
#include "gsc_main_window.h"
|
||||
#include "gsc_executor_log_window.h"
|
||||
#include "gsc_init.h"
|
||||
@@ -81,7 +80,6 @@ namespace {
|
||||
return channel;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -521,14 +519,10 @@ bool app_init_and_loop(int& argc, char**& argv)
|
||||
if (h_ppi > 0) {
|
||||
const double scale = h_ppi / 96.0;
|
||||
debug_out_info("app", "Windows system DPI: " << h_ppi << ", scale: " << scale << "\n");
|
||||
const int full_scale_percent = static_cast<int>(std::lround(scale * 100.0));
|
||||
const int integer_scale_percent = (full_scale_percent / 100) * 100; // 250 -> 200, 150 -> 100
|
||||
if (full_scale_percent != integer_scale_percent) { // fractional scaling detected
|
||||
// Store the full scale percent for use in window sizing
|
||||
app_set_windows_fractional_scaling_percent(full_scale_percent);
|
||||
// Increase the font size by the fractional amount
|
||||
const int fraction_percent = full_scale_percent - integer_scale_percent;
|
||||
debug_out_dump("app", "Fractional scaling detected (" << full_scale_percent << "%), increasing font size by " << fraction_percent << "%.\n");
|
||||
const int fraction_percent = static_cast<int>(std::round(scale * 100)) % 100;
|
||||
if (fraction_percent != 0) { // fractional scaling
|
||||
// Increase the font size by fraction, but round down the size to match the Windows behavior (?)
|
||||
debug_out_dump("app", "Fractional scaling detected, increasing font size by " << fraction_percent << "%.\n");
|
||||
Gtk::Settings::get_default()->property_gtk_font_name()
|
||||
.set_value("Segoe UI " + hz::number_to_string_nolocale(static_cast<int>(9 * (1. + fraction_percent/100.))));
|
||||
}
|
||||
|
||||
@@ -15,11 +15,6 @@ Copyright:
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace Gtk {
|
||||
class Window;
|
||||
}
|
||||
|
||||
|
||||
/// Initialize the application and run the main loop
|
||||
bool app_init_and_loop(int& argc, char**& argv);
|
||||
|
||||
@@ -33,20 +28,6 @@ void app_quit();
|
||||
std::string app_get_debug_buffer_str();
|
||||
|
||||
|
||||
/// Get the fractional scaling percentage detected on Windows (0 if not detected or integer scale).
|
||||
/// For example, at 150% scaling, this returns 150; at 125% scaling, this returns 125; at 250% scaling, this returns 250.
|
||||
/// Returns 0 for exact integer scales (100%, 200%, etc.).
|
||||
int app_get_windows_fractional_scaling_percent();
|
||||
|
||||
|
||||
/// Apply fractional scaling to default window size if fractional scaling is detected.
|
||||
/// This compensates for GTK3's lack of fractional scaling support on Windows.
|
||||
/// \param window The window to apply scaling to
|
||||
/// \param config_size_w Configured width (0 if using glade default)
|
||||
/// \param config_size_h Configured height (0 if using glade default)
|
||||
void app_apply_fractional_scaling_to_default_size(Gtk::Window* window, int config_size_w, int config_size_h);
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -60,7 +60,9 @@ GscMainWindow::GscMainWindow(BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder>
|
||||
{
|
||||
const int def_size_w = rconfig::get_data<int>("gui/main_window/default_size_w");
|
||||
const int def_size_h = rconfig::get_data<int>("gui/main_window/default_size_h");
|
||||
app_apply_fractional_scaling_to_default_size(this, def_size_w, def_size_h);
|
||||
if (def_size_w > 0 && def_size_h > 0) {
|
||||
set_default_size(def_size_w, def_size_h);
|
||||
}
|
||||
}
|
||||
|
||||
// show the window first, scan later
|
||||
|
||||
@@ -24,7 +24,6 @@ Copyright:
|
||||
|
||||
#include "applib/app_builder_widget.h"
|
||||
#include "applib/app_gtkmm_tools.h"
|
||||
#include "applib/gui_utils.h" // app_apply_fractional_scaling_to_default_size()
|
||||
|
||||
|
||||
|
||||
@@ -50,9 +49,6 @@ class GscTextWindow : public AppBuilderWidget<GscTextWindow<InstanceSwitch>, Ins
|
||||
GscTextWindow(typename Gtk::Window::BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder> ui)
|
||||
: AppBuilderWidget<GscTextWindow<InstanceSwitch>, InstanceSwitch::multi_instance>(gtkcobj, std::move(ui))
|
||||
{
|
||||
// Apply fractional scaling to default window size
|
||||
app_apply_fractional_scaling_to_default_size(this, 0, 0);
|
||||
|
||||
// Connect callbacks
|
||||
|
||||
Gtk::Button* save_as_button = nullptr;
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
###############################################################################
|
||||
# License: BSD Zero Clause License file
|
||||
# Copyright:
|
||||
# (C) 2026 Alexander Shaduri <ashaduri@gmail.com>
|
||||
###############################################################################
|
||||
|
||||
if (NOT APP_BUILD_TESTS)
|
||||
set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL true)
|
||||
else()
|
||||
set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL false)
|
||||
endif()
|
||||
|
||||
|
||||
# Use Object libraries to allow runtime test discovery
|
||||
add_library(gui_tests OBJECT)
|
||||
target_sources(gui_tests PRIVATE
|
||||
test_fractional_scaling.cpp
|
||||
)
|
||||
target_link_libraries(gui_tests PRIVATE
|
||||
Catch2
|
||||
)
|
||||
@@ -1,216 +0,0 @@
|
||||
/******************************************************************************
|
||||
License: BSD Zero Clause License
|
||||
Copyright:
|
||||
(C) 2008 - 2026 Alexander Shaduri <ashaduri@gmail.com>
|
||||
******************************************************************************/
|
||||
/// \file
|
||||
/// \author Alexander Shaduri
|
||||
/// \ingroup gui_tests
|
||||
/// \weakgroup gui_tests
|
||||
/// @{
|
||||
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
|
||||
/// Test the fractional scaling calculation logic
|
||||
/// This tests the mathematical correctness of the scaling correction ratio
|
||||
TEST_CASE("FractionalScalingCalculation", "[gui][scaling]")
|
||||
{
|
||||
// Test the calculation logic that's used in app_apply_fractional_scaling_to_default_size()
|
||||
// correction = system_scale / integer_ui_scale
|
||||
|
||||
SECTION("125% scaling (fractional)")
|
||||
{
|
||||
const int full_percent = 125;
|
||||
const double system_scale = static_cast<double>(full_percent) / 100.0; // 1.25
|
||||
const int integer_ui_scale = full_percent / 100; // 1
|
||||
const double correction = system_scale / static_cast<double>(integer_ui_scale);
|
||||
|
||||
REQUIRE(system_scale == 1.25);
|
||||
REQUIRE(integer_ui_scale == 1);
|
||||
REQUIRE(correction == 1.25);
|
||||
|
||||
// Test window scaling: 800x600 base should become 1000x750
|
||||
const int base_w = 800;
|
||||
const int base_h = 600;
|
||||
const int scaled_w = static_cast<int>(std::lround(base_w * correction));
|
||||
const int scaled_h = static_cast<int>(std::lround(base_h * correction));
|
||||
|
||||
REQUIRE(scaled_w == 1000);
|
||||
REQUIRE(scaled_h == 750);
|
||||
}
|
||||
|
||||
SECTION("150% scaling (fractional)")
|
||||
{
|
||||
const int full_percent = 150;
|
||||
const double system_scale = static_cast<double>(full_percent) / 100.0; // 1.5
|
||||
const int integer_ui_scale = full_percent / 100; // 1
|
||||
const double correction = system_scale / static_cast<double>(integer_ui_scale);
|
||||
|
||||
REQUIRE(system_scale == 1.5);
|
||||
REQUIRE(integer_ui_scale == 1);
|
||||
REQUIRE(correction == 1.5);
|
||||
|
||||
// Test window scaling: 800x600 base should become 1200x900
|
||||
const int base_w = 800;
|
||||
const int base_h = 600;
|
||||
const int scaled_w = static_cast<int>(std::lround(base_w * correction));
|
||||
const int scaled_h = static_cast<int>(std::lround(base_h * correction));
|
||||
|
||||
REQUIRE(scaled_w == 1200);
|
||||
REQUIRE(scaled_h == 900);
|
||||
}
|
||||
|
||||
SECTION("175% scaling (fractional)")
|
||||
{
|
||||
const int full_percent = 175;
|
||||
const double system_scale = static_cast<double>(full_percent) / 100.0; // 1.75
|
||||
const int integer_ui_scale = full_percent / 100; // 1
|
||||
const double correction = system_scale / static_cast<double>(integer_ui_scale);
|
||||
|
||||
REQUIRE(system_scale == 1.75);
|
||||
REQUIRE(integer_ui_scale == 1);
|
||||
REQUIRE(correction == 1.75);
|
||||
|
||||
// Test window scaling: 800x600 base should become 1400x1050
|
||||
const int base_w = 800;
|
||||
const int base_h = 600;
|
||||
const int scaled_w = static_cast<int>(std::lround(base_w * correction));
|
||||
const int scaled_h = static_cast<int>(std::lround(base_h * correction));
|
||||
|
||||
REQUIRE(scaled_w == 1400);
|
||||
REQUIRE(scaled_h == 1050);
|
||||
}
|
||||
|
||||
SECTION("250% scaling (fractional) - Critical test case")
|
||||
{
|
||||
// This is the case that was broken before the fix
|
||||
const int full_percent = 250;
|
||||
const double system_scale = static_cast<double>(full_percent) / 100.0; // 2.5
|
||||
const int integer_ui_scale = full_percent / 100; // 2
|
||||
const double correction = system_scale / static_cast<double>(integer_ui_scale);
|
||||
|
||||
REQUIRE(system_scale == 2.5);
|
||||
REQUIRE(integer_ui_scale == 2);
|
||||
REQUIRE(correction == 1.25); // NOT 1.5 (the old broken behavior)
|
||||
|
||||
// Test window scaling: GTK already applies 2x, we need to apply 1.25x more
|
||||
// For a base of 800x600, GTK makes it 1600x1200, we should scale to 2000x1500
|
||||
const int gtk_scaled_w = 1600; // After GTK's 2x integer scaling
|
||||
const int gtk_scaled_h = 1200;
|
||||
const int final_w = static_cast<int>(std::lround(gtk_scaled_w * correction));
|
||||
const int final_h = static_cast<int>(std::lround(gtk_scaled_h * correction));
|
||||
|
||||
REQUIRE(final_w == 2000); // 2.5x total = 800 * 2.5
|
||||
REQUIRE(final_h == 1500); // 2.5x total = 600 * 2.5
|
||||
}
|
||||
|
||||
SECTION("225% scaling (fractional)")
|
||||
{
|
||||
const int full_percent = 225;
|
||||
const double system_scale = static_cast<double>(full_percent) / 100.0; // 2.25
|
||||
const int integer_ui_scale = full_percent / 100; // 2
|
||||
const double correction = system_scale / static_cast<double>(integer_ui_scale);
|
||||
|
||||
REQUIRE(system_scale == 2.25);
|
||||
REQUIRE(integer_ui_scale == 2);
|
||||
REQUIRE(correction == 1.125);
|
||||
|
||||
// Test window scaling: 800x600 at 2x GTK = 1600x1200, with correction = 1800x1350
|
||||
const int gtk_scaled_w = 1600;
|
||||
const int gtk_scaled_h = 1200;
|
||||
const int final_w = static_cast<int>(std::lround(gtk_scaled_w * correction));
|
||||
const int final_h = static_cast<int>(std::lround(gtk_scaled_h * correction));
|
||||
|
||||
REQUIRE(final_w == 1800);
|
||||
REQUIRE(final_h == 1350);
|
||||
}
|
||||
|
||||
SECTION("100% scaling (integer, no fractional)")
|
||||
{
|
||||
// Should not trigger fractional scaling at all (full_percent would be 0)
|
||||
const int full_percent = 100;
|
||||
const int integer_scale_percent = (full_percent / 100) * 100;
|
||||
|
||||
REQUIRE(full_percent == integer_scale_percent); // No fractional component
|
||||
}
|
||||
|
||||
SECTION("200% scaling (integer, no fractional)")
|
||||
{
|
||||
// Should not trigger fractional scaling at all (full_percent would be 0)
|
||||
const int full_percent = 200;
|
||||
const int integer_scale_percent = (full_percent / 100) * 100;
|
||||
|
||||
REQUIRE(full_percent == integer_scale_percent); // No fractional component
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Test the DPI detection logic
|
||||
TEST_CASE("DPIDetectionLogic", "[gui][scaling][dpi]")
|
||||
{
|
||||
SECTION("Detect 125% scaling from DPI")
|
||||
{
|
||||
const int h_ppi = 120; // 120 DPI
|
||||
const double scale = static_cast<double>(h_ppi) / 96.0;
|
||||
const int full_scale_percent = static_cast<int>(std::lround(scale * 100.0));
|
||||
const int integer_scale_percent = (full_scale_percent / 100) * 100;
|
||||
|
||||
REQUIRE(full_scale_percent == 125);
|
||||
REQUIRE(integer_scale_percent == 100);
|
||||
REQUIRE(full_scale_percent != integer_scale_percent); // Fractional scaling detected
|
||||
}
|
||||
|
||||
SECTION("Detect 150% scaling from DPI")
|
||||
{
|
||||
const int h_ppi = 144; // 144 DPI
|
||||
const double scale = static_cast<double>(h_ppi) / 96.0;
|
||||
const int full_scale_percent = static_cast<int>(std::lround(scale * 100.0));
|
||||
const int integer_scale_percent = (full_scale_percent / 100) * 100;
|
||||
|
||||
REQUIRE(full_scale_percent == 150);
|
||||
REQUIRE(integer_scale_percent == 100);
|
||||
REQUIRE(full_scale_percent != integer_scale_percent); // Fractional scaling detected
|
||||
}
|
||||
|
||||
SECTION("Detect 250% scaling from DPI")
|
||||
{
|
||||
const int h_ppi = 240; // 240 DPI
|
||||
const double scale = static_cast<double>(h_ppi) / 96.0;
|
||||
const int full_scale_percent = static_cast<int>(std::lround(scale * 100.0));
|
||||
const int integer_scale_percent = (full_scale_percent / 100) * 100;
|
||||
|
||||
REQUIRE(full_scale_percent == 250);
|
||||
REQUIRE(integer_scale_percent == 200);
|
||||
REQUIRE(full_scale_percent != integer_scale_percent); // Fractional scaling detected
|
||||
}
|
||||
|
||||
SECTION("No fractional scaling at 100% (96 DPI)")
|
||||
{
|
||||
const int h_ppi = 96; // 96 DPI (standard)
|
||||
const double scale = static_cast<double>(h_ppi) / 96.0;
|
||||
const int full_scale_percent = static_cast<int>(std::lround(scale * 100.0));
|
||||
const int integer_scale_percent = (full_scale_percent / 100) * 100;
|
||||
|
||||
REQUIRE(full_scale_percent == 100);
|
||||
REQUIRE(integer_scale_percent == 100);
|
||||
REQUIRE(full_scale_percent == integer_scale_percent); // No fractional scaling
|
||||
}
|
||||
|
||||
SECTION("No fractional scaling at 200% (192 DPI)")
|
||||
{
|
||||
const int h_ppi = 192; // 192 DPI
|
||||
const double scale = static_cast<double>(h_ppi) / 96.0;
|
||||
const int full_scale_percent = static_cast<int>(std::lround(scale * 100.0));
|
||||
const int integer_scale_percent = (full_scale_percent / 100) * 100;
|
||||
|
||||
REQUIRE(full_scale_percent == 200);
|
||||
REQUIRE(integer_scale_percent == 200);
|
||||
REQUIRE(full_scale_percent == integer_scale_percent); // No fractional scaling
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// @}
|
||||
+116
-3
@@ -21,6 +21,11 @@ Copyright:
|
||||
#include "win32_tools.h" // hz::win32_utf8_to_utf16
|
||||
#else
|
||||
#include <memory>
|
||||
#include <unistd.h> // geteuid, fork, execvp, setuid, setgid
|
||||
#include <sys/types.h> // uid_t, gid_t
|
||||
#include <sys/wait.h> // waitpid
|
||||
#include <pwd.h> // getpwuid
|
||||
#include "env_tools.h" // hz::env_get_value
|
||||
#endif
|
||||
|
||||
|
||||
@@ -29,6 +34,89 @@ Copyright:
|
||||
namespace hz {
|
||||
|
||||
|
||||
#ifndef _WIN32
|
||||
|
||||
/// Launch URL as the original user when running as root.
|
||||
/// This is needed because gtk_show_uri_on_window() doesn't work when running as root
|
||||
/// (D-Bus session is not accessible).
|
||||
/// \return error message on error, empty string on success.
|
||||
inline std::string launch_url_as_original_user(const std::string& link)
|
||||
{
|
||||
// Get the original user's UID from environment variables
|
||||
// SUDO_UID is set by sudo, PKEXEC_UID is set by pkexec
|
||||
std::string uid_str;
|
||||
uid_t original_uid = 0;
|
||||
gid_t original_gid = 0;
|
||||
|
||||
if (hz::env_get_value("SUDO_UID", uid_str) || hz::env_get_value("PKEXEC_UID", uid_str)) {
|
||||
try {
|
||||
original_uid = static_cast<uid_t>(std::stoul(uid_str));
|
||||
} catch (...) {
|
||||
return "Cannot parse original user UID";
|
||||
}
|
||||
|
||||
// Get the original user's GID
|
||||
struct passwd* pw = getpwuid(original_uid);
|
||||
if (pw) {
|
||||
original_gid = pw->pw_gid;
|
||||
} else {
|
||||
return "Cannot get original user information";
|
||||
}
|
||||
} else {
|
||||
return "Cannot determine original user UID";
|
||||
}
|
||||
|
||||
// Fork and execute xdg-open as the original user
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
return "Cannot fork process";
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
// Child process
|
||||
|
||||
// Restore HOME environment variable if available
|
||||
// This helps xdg-open find the correct configuration
|
||||
std::string sudo_user;
|
||||
if (hz::env_get_value("SUDO_USER", sudo_user)) {
|
||||
struct passwd* pw = getpwnam(sudo_user.c_str());
|
||||
if (pw && pw->pw_dir) {
|
||||
setenv("HOME", pw->pw_dir, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Drop privileges to original user
|
||||
// Set GID first, then UID (order matters for security)
|
||||
if (setgid(original_gid) != 0) {
|
||||
_exit(1);
|
||||
}
|
||||
if (setuid(original_uid) != 0) {
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
// Execute xdg-open with the URL
|
||||
const char* argv[] = {"xdg-open", link.c_str(), nullptr};
|
||||
execvp("xdg-open", const_cast<char* const*>(argv));
|
||||
|
||||
// If execvp returns, it failed
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
// Parent process - wait for child
|
||||
int status = 0;
|
||||
if (waitpid(pid, &status, 0) == -1) {
|
||||
return "Cannot wait for child process";
|
||||
}
|
||||
|
||||
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
|
||||
return {}; // Success
|
||||
}
|
||||
|
||||
return "xdg-open failed to launch URL";
|
||||
}
|
||||
|
||||
#endif // _WIN32
|
||||
|
||||
|
||||
/// Open URL in browser or mailto: link in mail client.
|
||||
/// Return error message on error, empty string otherwise.
|
||||
@@ -50,16 +138,41 @@ inline std::string launch_url([[maybe_unused]] GtkWindow* window, const std::str
|
||||
#else
|
||||
|
||||
GError* error = nullptr;
|
||||
bool status = false;
|
||||
|
||||
// Check if running as root
|
||||
bool is_root = (geteuid() == 0);
|
||||
|
||||
// If running as root, try to launch as the original user first
|
||||
if (is_root) {
|
||||
std::string result = launch_url_as_original_user(link);
|
||||
if (result.empty()) {
|
||||
return {}; // Success
|
||||
}
|
||||
// If launching as original user failed, fall through to try GTK method
|
||||
}
|
||||
|
||||
// Try the standard GTK method
|
||||
#if GTK_CHECK_VERSION(3, 22, 0)
|
||||
bool status = static_cast<bool>(gtk_show_uri_on_window(window, link.c_str(), GDK_CURRENT_TIME, &error));
|
||||
status = static_cast<bool>(gtk_show_uri_on_window(window, link.c_str(), GDK_CURRENT_TIME, &error));
|
||||
#else
|
||||
GdkScreen* screen = (window ? gtk_window_get_screen(window) : nullptr);
|
||||
bool status = static_cast<bool>(gtk_show_uri(screen, link.c_str(), GDK_CURRENT_TIME, &error));
|
||||
status = static_cast<bool>(gtk_show_uri(screen, link.c_str(), GDK_CURRENT_TIME, &error));
|
||||
#endif
|
||||
std::unique_ptr<GError, decltype(&g_error_free)> uerror(error, &g_error_free);
|
||||
|
||||
if (!status) {
|
||||
return std::string("Cannot open URL: ")
|
||||
// GTK method failed. If running as root, we already tried the fallback.
|
||||
// Otherwise, try the fallback now.
|
||||
if (!is_root) {
|
||||
std::string result = launch_url_as_original_user(link);
|
||||
if (result.empty()) {
|
||||
return {}; // Success
|
||||
}
|
||||
}
|
||||
|
||||
// Both methods failed, return error
|
||||
return std::string("Cannot open URL")
|
||||
+ ((error && error->message) ? (std::string(": ") + error->message) : ".");
|
||||
}
|
||||
return {};
|
||||
|
||||
@@ -22,7 +22,6 @@ target_link_libraries(test_all PRIVATE
|
||||
libdebug
|
||||
applib_tests
|
||||
hz_tests
|
||||
gui_tests
|
||||
Catch2
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user