diff --git a/.gitmodules b/.gitmodules index 76e1b63..35cbbdf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "ext/CLI11"] path = ext/CLI11 url = https://github.com/CLIUtils/CLI11 +[submodule "ext/winui3_cmake"] + path = ext/winui3_cmake + url = https://github.com/res2k/winui3_cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index c93d36d..c2348fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ set(VERSION_MINOR ${GITVERSIONDETECT_VERSION_MINOR}) set(VERSION_REVISION ${GITVERSIONDETECT_VERSION_PATCH}) set(VERSION_BUILD ${GITVERSIONDETECT_VERSION_COMMIT_NUM}) set(VERSION_FULL ${GITVERSIONDETECT_VERSION}) +set(VERSION_COMMIT ${GITVERSIONDETECT_VERSION_COMMIT_SHA}) set(COPYRIGHT_STR "Copyright (c) 2022-2025 Frank Richter") set(CMAKE_CXX_STANDARD 23) @@ -62,11 +63,29 @@ endif() set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON) add_subdirectory(ext) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/ext/winui3_cmake/cmake") + +# We need to use the same CppWinRT version for common and HDRTrayConfig, +# so generate headers from the nuget packages +include(nuget) +nuget_install() + +set(WINSDK_VERSION 10.0.17763.0) + +include(find_platform_winmds) +include(run_cppwinrt) +find_platform_winmds(PATHS_VAR PLATFORM_WINMD_PATHS WINSDK_VERSION ${WINSDK_VERSION}) +run_cppwinrt(TARGET platform_cppwinrt + OUT "${CMAKE_CURRENT_BINARY_DIR}/generated" + IN ${PLATFORM_WINMD_PATHS}) +include_directories("${CMAKE_CURRENT_BINARY_DIR}/generated") add_subdirectory(common) +add_dependencies(common platform_cppwinrt) -add_subdirectory(HDRTray) add_subdirectory(HDRCmd) +add_subdirectory(HDRTray) +add_subdirectory(HDRTrayConfig) if(MARKO_AVAILABLE) set(MD2HTML "${CMAKE_CURRENT_SOURCE_DIR}/build/md2html.py") @@ -85,9 +104,13 @@ if(MARKO_AVAILABLE) add_custom_target(ConvertMD ALL DEPENDS ${GENERATED_HTML_FILES}) endif() -install(TARGETS HDRTray HDRCmd +install(TARGETS HDRTray HDRCmd HDRTrayConfig RUNTIME DESTINATION ".") +install(FILES + "$/Microsoft.WindowsAppRuntime.Bootstrap.dll" + "$/HDRTrayConfig.pri" + DESTINATION ".") if(MARKO_AVAILABLE) foreach(md_file LICENSE README) install(FILES "${MD_OUTPUT_DIR}/${md_file}.html" diff --git a/HDRCmd/CMakeLists.txt b/HDRCmd/CMakeLists.txt index 836c6b6..2cc1b57 100644 --- a/HDRCmd/CMakeLists.txt +++ b/HDRCmd/CMakeLists.txt @@ -1,4 +1,3 @@ -configure_file(version.h.in "${CMAKE_CURRENT_BINARY_DIR}/generated/version.h") configure_file(version.rc.in "${CMAKE_CURRENT_BINARY_DIR}/generated/version.rc") add_executable(HDRCmd) @@ -6,11 +5,21 @@ target_sources(HDRCmd PRIVATE "HDRCmd.cpp" "HDRCmd.manifest" "HDRCmd.rc" + "subcommand/display_status.hpp" + "subcommand/display_status.cpp" + "subcommand/resolve_display.hpp" + "subcommand/resolve_display.cpp" "subcommand/Base.hpp" "subcommand/Disable.hpp" "subcommand/Disable.cpp" + "subcommand/DisplayUsing.hpp" + "subcommand/DisplayUsing.cpp" "subcommand/Enable.hpp" "subcommand/Enable.cpp" + "subcommand/LoginStartup.hpp" + "subcommand/LoginStartup.cpp" + "subcommand/Select.hpp" + "subcommand/Select.cpp" "subcommand/Status.hpp" "subcommand/Status.cpp" ) diff --git a/HDRCmd/HDRCmd.cpp b/HDRCmd/HDRCmd.cpp index bdb7c2f..f24986e 100644 --- a/HDRCmd/HDRCmd.cpp +++ b/HDRCmd/HDRCmd.cpp @@ -20,12 +20,16 @@ #include "subcommand/Disable.hpp" #include "subcommand/Enable.hpp" +#include "subcommand/LoginStartup.hpp" +#include "subcommand/Select.hpp" #include "subcommand/Status.hpp" #include "version.h" #include "WinVerCheck.hpp" #include +#include + static std::string failure_message(const CLI::App *app, const CLI::Error &e) { return std::format("Invalid command line arguments: {}\n\n{}", e.what(), app->help()); } @@ -34,12 +38,16 @@ int wmain(int argc, const wchar_t* const argv[]) { CLI::App app{"HDRCmd " VERSION_FULL " - turn \"Use HDR\" on or off from command line"}; - // if Windows 10 < version 1803 refuse to start - if (!IsWindows10_1803OrGreater()) { - std::cerr << "Sorry, HDRCmd only works on Windows 10, version 1803 and above" << std::endl; + // if Windows 10 < version 1809 refuse to start + if (!IsWindows10_1809OrGreater()) { + std::cerr << "Sorry, HDRCmd only works on Windows 10, version 1809 and above" << std::endl; return -2; } + // Init WinRT. For monitor stable ID. + // FIXME: Obtain lazily? + winrt::init_apartment(); + app.allow_windows_style_options(); app.ignore_case(); app.require_subcommand(1); @@ -48,6 +56,8 @@ int wmain(int argc, const wchar_t* const argv[]) subcommand::Status::add(app); subcommand::Enable::add(app); subcommand::Disable::add(app); + subcommand::Select::add(app); + subcommand::LoginStartup::add(app); CLI11_PARSE(app, argc, argv); const auto* subcmd = app.get_subcommands()[0]; diff --git a/HDRCmd/subcommand/Disable.cpp b/HDRCmd/subcommand/Disable.cpp index fbe20ac..e5655ee 100644 --- a/HDRCmd/subcommand/Disable.cpp +++ b/HDRCmd/subcommand/Disable.cpp @@ -22,11 +22,14 @@ namespace subcommand { -Disable::Disable(CLI::App* parent) : Base("Turn HDR off", "off", parent) { } +Disable::Disable(CLI::App* parent) : DisplayUsing("Turn HDR off", "off", parent) +{ + add_displays_option(this, default_displays_option_name, "Displays to consider"); +} int Disable::run() const { - auto result = hdr::SetWindowsHDRStatus(false); + auto result = hdr::SetWindowsHDRStatus(GetSelectedDisplays(), false); if (!result) return -1; return *result == hdr::Status::Off ? 0 : 1; diff --git a/HDRCmd/subcommand/Disable.hpp b/HDRCmd/subcommand/Disable.hpp index d5d88c0..51b3edd 100644 --- a/HDRCmd/subcommand/Disable.hpp +++ b/HDRCmd/subcommand/Disable.hpp @@ -19,10 +19,10 @@ #ifndef SUBCOMMAND_DISABLE_HPP_ #define SUBCOMMAND_DISABLE_HPP_ -#include "Base.hpp" +#include "DisplayUsing.hpp" namespace subcommand { -class Disable : public Base +class Disable : public DisplayUsing { protected: Disable(CLI::App* parent); diff --git a/HDRCmd/subcommand/DisplayUsing.cpp b/HDRCmd/subcommand/DisplayUsing.cpp new file mode 100644 index 0000000..2f511ed --- /dev/null +++ b/HDRCmd/subcommand/DisplayUsing.cpp @@ -0,0 +1,52 @@ +/* + HDRCmd - enable/disable "Use HDR" from command line + Copyright (C) 2024 Frank Richter + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "DisplayUsing.hpp" + +#include "resolve_display.hpp" + +namespace subcommand { + +const char* DisplayUsing::default_displays_option_name = "-d,--displays"; + +CLI::Option* DisplayUsing::add_displays_option(CLI::App* app, std::string option_name, std::string option_description) +{ + return app->add_option(option_name, display_ids, option_description)->type_name("# or NAME"); +} + +hdr::DisplayInfo_vec DisplayUsing::GetSelectedDisplays() const +{ + if (display_ids.empty()) + return hdr::GetEnabledDisplays(); + + auto all_displays = hdr::GetDisplays(); + hdr::DisplayInfo_vec selected_displays; + for(const auto& disp_id : display_ids) { + if (const auto* disp = resolve_display(all_displays, disp_id)) { + if (std::find_if(selected_displays.begin(), selected_displays.end(), + [disp](const hdr::DisplayInfo& already_selected) { + return already_selected.GetID() == disp->GetID(); + }) != selected_displays.end()) + continue; + selected_displays.push_back(*disp); + } + } + return selected_displays; +} + +} diff --git a/HDRCmd/subcommand/DisplayUsing.hpp b/HDRCmd/subcommand/DisplayUsing.hpp new file mode 100644 index 0000000..1888346 --- /dev/null +++ b/HDRCmd/subcommand/DisplayUsing.hpp @@ -0,0 +1,50 @@ +/* + HDRCmd - enable/disable "Use HDR" from command line + Copyright (C) 2024 Frank Richter + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef SUBCOMMAND_DISPLAYUSING_HPP_ +#define SUBCOMMAND_DISPLAYUSING_HPP_ + +#include "Base.hpp" + +#include "HDR.h" + +namespace subcommand { + +class DisplayUsing : public Base +{ +protected: + /// Default name for "optional" display specification + static const char* default_displays_option_name; + /// Display IDs given on command line + std::vector display_ids; + + /// Add an option for explicit display selection. Fills display_ids + CLI::Option* add_displays_option(CLI::App* app, std::string option_name, std::string option_description); + + /// Return selected displays, from command line, if explicitly given, or configuration by default + hdr::DisplayInfo_vec GetSelectedDisplays() const; + + template + DisplayUsing(Arg&&... arg) : Base(std::forward(arg)...) + { + } +}; + +} // namespace subcommand + +#endif // SUBCOMMAND_DISPLAYUSING_HPP_ diff --git a/HDRCmd/subcommand/Enable.cpp b/HDRCmd/subcommand/Enable.cpp index f62a6b4..9041b61 100644 --- a/HDRCmd/subcommand/Enable.cpp +++ b/HDRCmd/subcommand/Enable.cpp @@ -22,11 +22,14 @@ namespace subcommand { -Enable::Enable(CLI::App* parent) : Base("Turn HDR on", "on", parent) { } +Enable::Enable(CLI::App* parent) : DisplayUsing("Turn HDR on", "on", parent) +{ + add_displays_option(this, default_displays_option_name, "Displays to consider"); +} int Enable::run() const { - auto result = hdr::SetWindowsHDRStatus(true); + auto result = hdr::SetWindowsHDRStatus(GetSelectedDisplays(), true); if (!result) return -1; return *result == hdr::Status::On ? 0 : 1; diff --git a/HDRCmd/subcommand/Enable.hpp b/HDRCmd/subcommand/Enable.hpp index bdb0545..d233994 100644 --- a/HDRCmd/subcommand/Enable.hpp +++ b/HDRCmd/subcommand/Enable.hpp @@ -19,10 +19,10 @@ #ifndef SUBCOMMAND_ENABLE_HPP_ #define SUBCOMMAND_ENABLE_HPP_ -#include "Base.hpp" +#include "DisplayUsing.hpp" namespace subcommand { -class Enable : public Base +class Enable : public DisplayUsing { protected: Enable(CLI::App* parent); diff --git a/HDRCmd/subcommand/LoginStartup.cpp b/HDRCmd/subcommand/LoginStartup.cpp new file mode 100644 index 0000000..e059760 --- /dev/null +++ b/HDRCmd/subcommand/LoginStartup.cpp @@ -0,0 +1,78 @@ +/* + HDRCmd - enable/disable "Use HDR" from command line + Copyright (C) 2024 Frank Richter + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "LoginStartup.hpp" + +#include "LoginStartupConfig.hpp" + +#include +#include +#include + +namespace subcommand { + +LoginStartup::LoginStartup(CLI::App* parent) : Base("Change 'Start when logging in'", "startup", parent) +{ + add_subcommand("status", "Print current status of option"); + add_subcommand("on", "Turn 'Start when logging in' on"); + add_subcommand("off", "Turn 'Start when logging in' off"); +} + +int LoginStartup::print_status(bool active) +{ + auto status_result = LoginStartupConfig::instance().IsEnabled(); + if (!status_result) { + std::println("Could not get login startup state, error {}", status_result.error()); + return status_result.error(); + } + + auto status_str = *status_result ? "ON" : "OFF"; + if (active) + std::println("'Start when logging in' is now {}", status_str); + else + std::println("'Start when logging in' is {}", status_str); + return 0; +} + +int LoginStartup::run() const +{ + bool has_status = got_subcommand("status"); + bool has_on = got_subcommand("on"); + bool has_off = got_subcommand("off"); + + if (has_on || has_off) { + auto enable_result = LoginStartupConfig::instance().SetEnabled(has_on); + if (!enable_result) { + std::println("Could not change login startup state, error {}", enable_result.error()); + return enable_result.error(); + } + } + + if (!has_status && !has_on && !has_off) { + // No explicit command given: also include help text + std::cout << help() << std::endl; + } + return print_status(has_on || has_off); +} + +CLI::App* LoginStartup::add(CLI::App& app) +{ + return app.add_subcommand(std::shared_ptr(new LoginStartup(&app))); +} + +} // namespace subcommand diff --git a/HDRCmd/subcommand/LoginStartup.hpp b/HDRCmd/subcommand/LoginStartup.hpp new file mode 100644 index 0000000..5b63dc5 --- /dev/null +++ b/HDRCmd/subcommand/LoginStartup.hpp @@ -0,0 +1,41 @@ +/* + HDRCmd - enable/disable "Use HDR" from command line + Copyright (C) 2024 Frank Richter + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef SUBCOMMAND_LOGINSTARTUP_HPP_ +#define SUBCOMMAND_LOGINSTARTUP_HPP_ + +#include "Base.hpp" + +namespace subcommand { + +class LoginStartup : public Base +{ + static int print_status(bool active); + +protected: + LoginStartup(CLI::App* parent); + +public: + int run() const override; + + static CLI::App* add(CLI::App& app); +}; + +} // namespace subcommand + +#endif // SUBCOMMAND_LOGINSTARTUP_HPP_ diff --git a/HDRCmd/subcommand/Select.cpp b/HDRCmd/subcommand/Select.cpp new file mode 100644 index 0000000..22eb293 --- /dev/null +++ b/HDRCmd/subcommand/Select.cpp @@ -0,0 +1,84 @@ +/* + HDRCmd - enable/disable "Use HDR" from command line + Copyright (C) 2024 Frank Richter + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "Select.hpp" + +#include "DisplayConfig.hpp" +#include "display_name.hpp" +#include "display_status.hpp" +#include "resolve_display.hpp" + +namespace subcommand { + +Select::Select(CLI::App* parent) : DisplayUsing("Select displays to include in HDR toggling", "select", parent) +{ + add_subcommand("list", "Print list of displays"); + auto on_cmd = add_subcommand("on", "Include a display in HDR toggling"); + add_displays_option(on_cmd, "DISPLAY", "displays to include in HDR toggling")->required(); + auto off_cmd = add_subcommand("off", "Exclude a display from HDR toggling"); + add_displays_option(off_cmd, "DISPLAY", "displays to exclude from HDR toggling")->required(); +} + +int Select::toggle_displays(bool flag) const +{ + std::optional result; + auto all_displays = hdr::GetDisplays(); + for(const auto& disp_id : display_ids) { + if (const auto* disp = resolve_display(all_displays, disp_id)) { + auto stable_id = disp->GetStableID(); + if (!stable_id) // FIXME print an error? + continue; + auto enable_res = DisplayConfig::instance().SetEnabledFlag(*stable_id, flag); + if (!enable_res) { + if (!result) + *result = int(enable_res.error()); + std::println(std::cerr, "Could not store selection for display '{}': error {}", display_name(*disp), enable_res.error()); + } + } + } + return result.value_or(0); +} + +int Select::run() const +{ + bool has_list = got_subcommand("list"); + bool has_on = got_subcommand("on"); + bool has_off = got_subcommand("off"); + + if (!has_on && !has_off && !has_list) + { + // No explicit command given: also include help text + std::cout << help() << std::endl; + } + + int result = 0; + if (has_on || has_off) { + result = toggle_displays(has_on); + } + + // Always print list at the end + display_status::print_status_long(); + return result; +} + +CLI::App* Select::add(CLI::App& app) +{ + return app.add_subcommand(std::shared_ptr