From d247d4d819da0e8d266637358e7bedd351382cce Mon Sep 17 00:00:00 2001 From: Mike Thierman Date: Sat, 17 Aug 2024 12:38:21 -0600 Subject: [PATCH] Windows SA: Total refactor (#280) * Windows SA: Total refactor * Remove designated initializers, they are C++ 20 * Remove logging for WINDOWPOS --- cmake/base_sdks.cmake | 31 + cmake/wrap_standalone.cmake | 63 +- libs/fmt/fmt/color.h | 612 ++++++++++++++++++ libs/fmt/fmt/xchar.h | 322 +++++++++ src/detail/standalone/entry.cpp | 13 +- src/detail/standalone/standalone_host.cpp | 2 + src/detail/standalone/standalone_host.h | 15 - src/detail/standalone/windows/helper.h | 368 ----------- src/detail/standalone/windows/helpers.cpp | 203 ++++++ src/detail/standalone/windows/helpers.h | 165 +++++ src/detail/standalone/windows/host_window.cpp | 310 +++++++++ src/detail/standalone/windows/host_window.h | 38 ++ .../standalone/windows/settings_window.cpp | 63 ++ .../standalone/windows/settings_window.h | 18 + .../{win32.manifest => standalone.manifest} | 18 +- src/detail/standalone/windows/winutils.cpp | 132 ---- src/detail/standalone/windows/winutils.h | 12 - src/wrapasstandalone_win32.cpp | 52 ++ 18 files changed, 1883 insertions(+), 554 deletions(-) create mode 100644 libs/fmt/fmt/color.h create mode 100644 libs/fmt/fmt/xchar.h delete mode 100644 src/detail/standalone/windows/helper.h create mode 100644 src/detail/standalone/windows/helpers.cpp create mode 100644 src/detail/standalone/windows/helpers.h create mode 100644 src/detail/standalone/windows/host_window.cpp create mode 100644 src/detail/standalone/windows/host_window.h create mode 100644 src/detail/standalone/windows/settings_window.cpp create mode 100644 src/detail/standalone/windows/settings_window.h rename src/detail/standalone/windows/{win32.manifest => standalone.manifest} (57%) delete mode 100644 src/detail/standalone/windows/winutils.cpp delete mode 100644 src/detail/standalone/windows/winutils.h create mode 100644 src/wrapasstandalone_win32.cpp diff --git a/cmake/base_sdks.cmake b/cmake/base_sdks.cmake index 98a27b9a..5372ea91 100644 --- a/cmake/base_sdks.cmake +++ b/cmake/base_sdks.cmake @@ -320,3 +320,34 @@ function(guarantee_rtmidi) add_library(base-sdk-rtmidi INTERFACE) target_link_libraries(base-sdk-rtmidi INTERFACE rtmidi) endfunction(guarantee_rtmidi) + +function(guarantee_wil) + if (TARGET base-sdk-wil) + return() + endif() + + if (NOT "${WIL_SDK_ROOT}" STREQUAL "") + # Use the provided root + elseif (${CLAP_WRAPPER_DOWNLOAD_DEPENDENCIES}) + guarantee_cpm() + CPMAddPackage( + NAME "wil" + GITHUB_REPOSITORY "microsoft/wil" + GIT_TAG "v1.0.240803.1" + EXCLUDE_FROM_ALL TRUE + DOWNLOAD_ONLY TRUE + SOURCE_DIR cpm/wil + ) + set(WIL_SDK_ROOT "${CMAKE_CURRENT_BINARY_DIR}/cpm/wil") + else() + search_for_sdk_source(SDKDIR wil RESULT WIL_SDK_ROOT) + endif() + + cmake_path(CONVERT "${WIL_SDK_ROOT}" TO_CMAKE_PATH_LIST WIL_SDK_ROOT) + if(NOT EXISTS "${WIL_SDK_ROOT}/include/wil/common.h") + message(FATAL_ERROR "There is no wil at ${WIL_SDK_ROOT}. Please set WIL_SDK_ROOT appropriately ") + endif() + + add_library(base-sdk-wil INTERFACE) + target_include_directories(base-sdk-wil INTERFACE "${WIL_SDK_ROOT}/include") +endfunction(guarantee_wil) diff --git a/cmake/wrap_standalone.cmake b/cmake/wrap_standalone.cmake index 861c1a31..5c238665 100644 --- a/cmake/wrap_standalone.cmake +++ b/cmake/wrap_standalone.cmake @@ -11,6 +11,8 @@ function(target_add_standalone_wrapper) STATICALLY_LINKED_CLAP_ENTRY HOSTED_CLAP_NAME + WIN32_ICON + MACOS_EMBEDDED_CLAP_LOCATION ) cmake_parse_arguments(SA "" "${oneValueArgs}" "" ${ARGN} ) @@ -35,6 +37,10 @@ function(target_add_standalone_wrapper) set(SA_OUTPUT_NAME ${SA_TARGET}) endif() + if (NOT DEFINED SA_WIN32_ICON) + set(SA_WIN32_ICON "") + endif() + guarantee_rtaudio() guarantee_rtmidi() @@ -95,20 +101,55 @@ function(target_add_standalone_wrapper) macos_include_clap_in_bundle(TARGET ${SA_TARGET} MACOS_EMBEDDED_CLAP_LOCATION ${SA_MACOS_EMBEDDED_CLAP_LOCATION}) - elseif(WIN32 AND (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")) + elseif(WIN32 AND (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) + if(NOT "${SA_WIN32_ICON}" STREQUAL "") + message(STATUS "Win32 icon found: ${SA_WIN32_ICON}") + file(WRITE "${CMAKE_BINARY_DIR}/standalone_win32.rc" "1 ICON \"standalone_win32.ico\"") + file(COPY_FILE ${SA_WIN32_ICON} "${CMAKE_BINARY_DIR}/standalone_win32.ico") + else() + message(STATUS "Win32 icon not found, using default") + endif() + set_target_properties(${SA_TARGET} PROPERTIES - WIN32_EXECUTABLE TRUE - ) + WIN32_EXECUTABLE TRUE + ) + + target_compile_definitions(${salib} PUBLIC + NOMINMAX + WIN32_LEAN_AND_MEAN + CLAP_WRAPPER_HAS_WIN32 + ) target_sources(${SA_TARGET} PRIVATE - ${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/standalone/windows/winutils.cpp - ${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/standalone/windows/win32.manifest - ) + "${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/wrapasstandalone_win32.cpp" + "${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/standalone/windows/host_window.cpp" + # "${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/standalone/windows/settings_window.cpp" + "${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/standalone/windows/helpers.cpp" + "${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/standalone/windows/standalone.manifest" + ) - target_compile_definitions(${salib} PUBLIC - CLAP_WRAPPER_HAS_WIN32 - WIN32_NAME="${SA_OUTPUT_NAME}" - ) + guarantee_wil() + target_link_libraries(${SA_TARGET} PRIVATE base-sdk-wil) + + if(NOT "${SA_WIN32_ICON}" STREQUAL "") + target_sources(${SA_TARGET} PRIVATE + "${CMAKE_BINARY_DIR}/standalone_win32.rc" + ) + endif() + + if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + target_link_options( + ${SA_TARGET} + PRIVATE + /entry:mainCRTStartup + ) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + target_link_options( + ${SA_TARGET} + PRIVATE + -Wl,/entry:mainCRTStartup + ) + endif() elseif(UNIX) target_sources(${SA_TARGET} PRIVATE @@ -140,7 +181,7 @@ function(target_add_standalone_wrapper) PLUGIN_INDEX=${SA_PLUGIN_INDEX} $<$:STATICALLY_LINKED_CLAP_ENTRY=1> $<$:HOSTED_CLAP_NAME="${SA_HOSTED_CLAP_NAME}"> - OUTPUT_NAME="${OUTPUT_NAME}" + OUTPUT_NAME="${SA_OUTPUT_NAME}" ) target_link_libraries(${SA_TARGET} PRIVATE diff --git a/libs/fmt/fmt/color.h b/libs/fmt/fmt/color.h new file mode 100644 index 00000000..f0e9dd94 --- /dev/null +++ b/libs/fmt/fmt/color.h @@ -0,0 +1,612 @@ +// Formatting library for C++ - color support +// +// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_COLOR_H_ +#define FMT_COLOR_H_ + +#include "format.h" + +FMT_BEGIN_NAMESPACE +FMT_BEGIN_EXPORT + +enum class color : uint32_t { + alice_blue = 0xF0F8FF, // rgb(240,248,255) + antique_white = 0xFAEBD7, // rgb(250,235,215) + aqua = 0x00FFFF, // rgb(0,255,255) + aquamarine = 0x7FFFD4, // rgb(127,255,212) + azure = 0xF0FFFF, // rgb(240,255,255) + beige = 0xF5F5DC, // rgb(245,245,220) + bisque = 0xFFE4C4, // rgb(255,228,196) + black = 0x000000, // rgb(0,0,0) + blanched_almond = 0xFFEBCD, // rgb(255,235,205) + blue = 0x0000FF, // rgb(0,0,255) + blue_violet = 0x8A2BE2, // rgb(138,43,226) + brown = 0xA52A2A, // rgb(165,42,42) + burly_wood = 0xDEB887, // rgb(222,184,135) + cadet_blue = 0x5F9EA0, // rgb(95,158,160) + chartreuse = 0x7FFF00, // rgb(127,255,0) + chocolate = 0xD2691E, // rgb(210,105,30) + coral = 0xFF7F50, // rgb(255,127,80) + cornflower_blue = 0x6495ED, // rgb(100,149,237) + cornsilk = 0xFFF8DC, // rgb(255,248,220) + crimson = 0xDC143C, // rgb(220,20,60) + cyan = 0x00FFFF, // rgb(0,255,255) + dark_blue = 0x00008B, // rgb(0,0,139) + dark_cyan = 0x008B8B, // rgb(0,139,139) + dark_golden_rod = 0xB8860B, // rgb(184,134,11) + dark_gray = 0xA9A9A9, // rgb(169,169,169) + dark_green = 0x006400, // rgb(0,100,0) + dark_khaki = 0xBDB76B, // rgb(189,183,107) + dark_magenta = 0x8B008B, // rgb(139,0,139) + dark_olive_green = 0x556B2F, // rgb(85,107,47) + dark_orange = 0xFF8C00, // rgb(255,140,0) + dark_orchid = 0x9932CC, // rgb(153,50,204) + dark_red = 0x8B0000, // rgb(139,0,0) + dark_salmon = 0xE9967A, // rgb(233,150,122) + dark_sea_green = 0x8FBC8F, // rgb(143,188,143) + dark_slate_blue = 0x483D8B, // rgb(72,61,139) + dark_slate_gray = 0x2F4F4F, // rgb(47,79,79) + dark_turquoise = 0x00CED1, // rgb(0,206,209) + dark_violet = 0x9400D3, // rgb(148,0,211) + deep_pink = 0xFF1493, // rgb(255,20,147) + deep_sky_blue = 0x00BFFF, // rgb(0,191,255) + dim_gray = 0x696969, // rgb(105,105,105) + dodger_blue = 0x1E90FF, // rgb(30,144,255) + fire_brick = 0xB22222, // rgb(178,34,34) + floral_white = 0xFFFAF0, // rgb(255,250,240) + forest_green = 0x228B22, // rgb(34,139,34) + fuchsia = 0xFF00FF, // rgb(255,0,255) + gainsboro = 0xDCDCDC, // rgb(220,220,220) + ghost_white = 0xF8F8FF, // rgb(248,248,255) + gold = 0xFFD700, // rgb(255,215,0) + golden_rod = 0xDAA520, // rgb(218,165,32) + gray = 0x808080, // rgb(128,128,128) + green = 0x008000, // rgb(0,128,0) + green_yellow = 0xADFF2F, // rgb(173,255,47) + honey_dew = 0xF0FFF0, // rgb(240,255,240) + hot_pink = 0xFF69B4, // rgb(255,105,180) + indian_red = 0xCD5C5C, // rgb(205,92,92) + indigo = 0x4B0082, // rgb(75,0,130) + ivory = 0xFFFFF0, // rgb(255,255,240) + khaki = 0xF0E68C, // rgb(240,230,140) + lavender = 0xE6E6FA, // rgb(230,230,250) + lavender_blush = 0xFFF0F5, // rgb(255,240,245) + lawn_green = 0x7CFC00, // rgb(124,252,0) + lemon_chiffon = 0xFFFACD, // rgb(255,250,205) + light_blue = 0xADD8E6, // rgb(173,216,230) + light_coral = 0xF08080, // rgb(240,128,128) + light_cyan = 0xE0FFFF, // rgb(224,255,255) + light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210) + light_gray = 0xD3D3D3, // rgb(211,211,211) + light_green = 0x90EE90, // rgb(144,238,144) + light_pink = 0xFFB6C1, // rgb(255,182,193) + light_salmon = 0xFFA07A, // rgb(255,160,122) + light_sea_green = 0x20B2AA, // rgb(32,178,170) + light_sky_blue = 0x87CEFA, // rgb(135,206,250) + light_slate_gray = 0x778899, // rgb(119,136,153) + light_steel_blue = 0xB0C4DE, // rgb(176,196,222) + light_yellow = 0xFFFFE0, // rgb(255,255,224) + lime = 0x00FF00, // rgb(0,255,0) + lime_green = 0x32CD32, // rgb(50,205,50) + linen = 0xFAF0E6, // rgb(250,240,230) + magenta = 0xFF00FF, // rgb(255,0,255) + maroon = 0x800000, // rgb(128,0,0) + medium_aquamarine = 0x66CDAA, // rgb(102,205,170) + medium_blue = 0x0000CD, // rgb(0,0,205) + medium_orchid = 0xBA55D3, // rgb(186,85,211) + medium_purple = 0x9370DB, // rgb(147,112,219) + medium_sea_green = 0x3CB371, // rgb(60,179,113) + medium_slate_blue = 0x7B68EE, // rgb(123,104,238) + medium_spring_green = 0x00FA9A, // rgb(0,250,154) + medium_turquoise = 0x48D1CC, // rgb(72,209,204) + medium_violet_red = 0xC71585, // rgb(199,21,133) + midnight_blue = 0x191970, // rgb(25,25,112) + mint_cream = 0xF5FFFA, // rgb(245,255,250) + misty_rose = 0xFFE4E1, // rgb(255,228,225) + moccasin = 0xFFE4B5, // rgb(255,228,181) + navajo_white = 0xFFDEAD, // rgb(255,222,173) + navy = 0x000080, // rgb(0,0,128) + old_lace = 0xFDF5E6, // rgb(253,245,230) + olive = 0x808000, // rgb(128,128,0) + olive_drab = 0x6B8E23, // rgb(107,142,35) + orange = 0xFFA500, // rgb(255,165,0) + orange_red = 0xFF4500, // rgb(255,69,0) + orchid = 0xDA70D6, // rgb(218,112,214) + pale_golden_rod = 0xEEE8AA, // rgb(238,232,170) + pale_green = 0x98FB98, // rgb(152,251,152) + pale_turquoise = 0xAFEEEE, // rgb(175,238,238) + pale_violet_red = 0xDB7093, // rgb(219,112,147) + papaya_whip = 0xFFEFD5, // rgb(255,239,213) + peach_puff = 0xFFDAB9, // rgb(255,218,185) + peru = 0xCD853F, // rgb(205,133,63) + pink = 0xFFC0CB, // rgb(255,192,203) + plum = 0xDDA0DD, // rgb(221,160,221) + powder_blue = 0xB0E0E6, // rgb(176,224,230) + purple = 0x800080, // rgb(128,0,128) + rebecca_purple = 0x663399, // rgb(102,51,153) + red = 0xFF0000, // rgb(255,0,0) + rosy_brown = 0xBC8F8F, // rgb(188,143,143) + royal_blue = 0x4169E1, // rgb(65,105,225) + saddle_brown = 0x8B4513, // rgb(139,69,19) + salmon = 0xFA8072, // rgb(250,128,114) + sandy_brown = 0xF4A460, // rgb(244,164,96) + sea_green = 0x2E8B57, // rgb(46,139,87) + sea_shell = 0xFFF5EE, // rgb(255,245,238) + sienna = 0xA0522D, // rgb(160,82,45) + silver = 0xC0C0C0, // rgb(192,192,192) + sky_blue = 0x87CEEB, // rgb(135,206,235) + slate_blue = 0x6A5ACD, // rgb(106,90,205) + slate_gray = 0x708090, // rgb(112,128,144) + snow = 0xFFFAFA, // rgb(255,250,250) + spring_green = 0x00FF7F, // rgb(0,255,127) + steel_blue = 0x4682B4, // rgb(70,130,180) + tan = 0xD2B48C, // rgb(210,180,140) + teal = 0x008080, // rgb(0,128,128) + thistle = 0xD8BFD8, // rgb(216,191,216) + tomato = 0xFF6347, // rgb(255,99,71) + turquoise = 0x40E0D0, // rgb(64,224,208) + violet = 0xEE82EE, // rgb(238,130,238) + wheat = 0xF5DEB3, // rgb(245,222,179) + white = 0xFFFFFF, // rgb(255,255,255) + white_smoke = 0xF5F5F5, // rgb(245,245,245) + yellow = 0xFFFF00, // rgb(255,255,0) + yellow_green = 0x9ACD32 // rgb(154,205,50) +}; // enum class color + +enum class terminal_color : uint8_t { + black = 30, + red, + green, + yellow, + blue, + magenta, + cyan, + white, + bright_black = 90, + bright_red, + bright_green, + bright_yellow, + bright_blue, + bright_magenta, + bright_cyan, + bright_white +}; + +enum class emphasis : uint8_t { + bold = 1, + faint = 1 << 1, + italic = 1 << 2, + underline = 1 << 3, + blink = 1 << 4, + reverse = 1 << 5, + conceal = 1 << 6, + strikethrough = 1 << 7, +}; + +// rgb is a struct for red, green and blue colors. +// Using the name "rgb" makes some editors show the color in a tooltip. +struct rgb { + FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {} + FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {} + FMT_CONSTEXPR rgb(uint32_t hex) + : r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {} + FMT_CONSTEXPR rgb(color hex) + : r((uint32_t(hex) >> 16) & 0xFF), + g((uint32_t(hex) >> 8) & 0xFF), + b(uint32_t(hex) & 0xFF) {} + uint8_t r; + uint8_t g; + uint8_t b; +}; + +namespace detail { + +// color is a struct of either a rgb color or a terminal color. +struct color_type { + FMT_CONSTEXPR color_type() noexcept : is_rgb(), value{} {} + FMT_CONSTEXPR color_type(color rgb_color) noexcept : is_rgb(true), value{} { + value.rgb_color = static_cast(rgb_color); + } + FMT_CONSTEXPR color_type(rgb rgb_color) noexcept : is_rgb(true), value{} { + value.rgb_color = (static_cast(rgb_color.r) << 16) | + (static_cast(rgb_color.g) << 8) | rgb_color.b; + } + FMT_CONSTEXPR color_type(terminal_color term_color) noexcept + : is_rgb(), value{} { + value.term_color = static_cast(term_color); + } + bool is_rgb; + union color_union { + uint8_t term_color; + uint32_t rgb_color; + } value; +}; +} // namespace detail + +/// A text style consisting of foreground and background colors and emphasis. +class text_style { + public: + FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept + : set_foreground_color(), set_background_color(), ems(em) {} + + FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& { + if (!set_foreground_color) { + set_foreground_color = rhs.set_foreground_color; + foreground_color = rhs.foreground_color; + } else if (rhs.set_foreground_color) { + if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb) + report_error("can't OR a terminal color"); + foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color; + } + + if (!set_background_color) { + set_background_color = rhs.set_background_color; + background_color = rhs.background_color; + } else if (rhs.set_background_color) { + if (!background_color.is_rgb || !rhs.background_color.is_rgb) + report_error("can't OR a terminal color"); + background_color.value.rgb_color |= rhs.background_color.value.rgb_color; + } + + ems = static_cast(static_cast(ems) | + static_cast(rhs.ems)); + return *this; + } + + friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs) + -> text_style { + return lhs |= rhs; + } + + FMT_CONSTEXPR auto has_foreground() const noexcept -> bool { + return set_foreground_color; + } + FMT_CONSTEXPR auto has_background() const noexcept -> bool { + return set_background_color; + } + FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool { + return static_cast(ems) != 0; + } + FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type { + FMT_ASSERT(has_foreground(), "no foreground specified for this style"); + return foreground_color; + } + FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type { + FMT_ASSERT(has_background(), "no background specified for this style"); + return background_color; + } + FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis { + FMT_ASSERT(has_emphasis(), "no emphasis specified for this style"); + return ems; + } + + private: + FMT_CONSTEXPR text_style(bool is_foreground, + detail::color_type text_color) noexcept + : set_foreground_color(), set_background_color(), ems() { + if (is_foreground) { + foreground_color = text_color; + set_foreground_color = true; + } else { + background_color = text_color; + set_background_color = true; + } + } + + friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept + -> text_style; + + friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept + -> text_style; + + detail::color_type foreground_color; + detail::color_type background_color; + bool set_foreground_color; + bool set_background_color; + emphasis ems; +}; + +/// Creates a text style from the foreground (text) color. +FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept + -> text_style { + return text_style(true, foreground); +} + +/// Creates a text style from the background color. +FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept + -> text_style { + return text_style(false, background); +} + +FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept + -> text_style { + return text_style(lhs) | rhs; +} + +namespace detail { + +template struct ansi_color_escape { + FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color, + const char* esc) noexcept { + // If we have a terminal color, we need to output another escape code + // sequence. + if (!text_color.is_rgb) { + bool is_background = esc == string_view("\x1b[48;2;"); + uint32_t value = text_color.value.term_color; + // Background ASCII codes are the same as the foreground ones but with + // 10 more. + if (is_background) value += 10u; + + size_t index = 0; + buffer[index++] = static_cast('\x1b'); + buffer[index++] = static_cast('['); + + if (value >= 100u) { + buffer[index++] = static_cast('1'); + value %= 100u; + } + buffer[index++] = static_cast('0' + value / 10u); + buffer[index++] = static_cast('0' + value % 10u); + + buffer[index++] = static_cast('m'); + buffer[index++] = static_cast('\0'); + return; + } + + for (int i = 0; i < 7; i++) { + buffer[i] = static_cast(esc[i]); + } + rgb color(text_color.value.rgb_color); + to_esc(color.r, buffer + 7, ';'); + to_esc(color.g, buffer + 11, ';'); + to_esc(color.b, buffer + 15, 'm'); + buffer[19] = static_cast(0); + } + FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept { + uint8_t em_codes[num_emphases] = {}; + if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1; + if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2; + if (has_emphasis(em, emphasis::italic)) em_codes[2] = 3; + if (has_emphasis(em, emphasis::underline)) em_codes[3] = 4; + if (has_emphasis(em, emphasis::blink)) em_codes[4] = 5; + if (has_emphasis(em, emphasis::reverse)) em_codes[5] = 7; + if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8; + if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9; + + size_t index = 0; + for (size_t i = 0; i < num_emphases; ++i) { + if (!em_codes[i]) continue; + buffer[index++] = static_cast('\x1b'); + buffer[index++] = static_cast('['); + buffer[index++] = static_cast('0' + em_codes[i]); + buffer[index++] = static_cast('m'); + } + buffer[index++] = static_cast(0); + } + FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; } + + FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; } + FMT_CONSTEXPR20 auto end() const noexcept -> const Char* { + return buffer + basic_string_view(buffer).size(); + } + + private: + static constexpr size_t num_emphases = 8; + Char buffer[7u + 3u * num_emphases + 1u]; + + static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, + char delimiter) noexcept { + out[0] = static_cast('0' + c / 100); + out[1] = static_cast('0' + c / 10 % 10); + out[2] = static_cast('0' + c % 10); + out[3] = static_cast(delimiter); + } + static FMT_CONSTEXPR auto has_emphasis(emphasis em, emphasis mask) noexcept + -> bool { + return static_cast(em) & static_cast(mask); + } +}; + +template +FMT_CONSTEXPR auto make_foreground_color(detail::color_type foreground) noexcept + -> ansi_color_escape { + return ansi_color_escape(foreground, "\x1b[38;2;"); +} + +template +FMT_CONSTEXPR auto make_background_color(detail::color_type background) noexcept + -> ansi_color_escape { + return ansi_color_escape(background, "\x1b[48;2;"); +} + +template +FMT_CONSTEXPR auto make_emphasis(emphasis em) noexcept + -> ansi_color_escape { + return ansi_color_escape(em); +} + +template inline void reset_color(buffer& buffer) { + auto reset_color = string_view("\x1b[0m"); + buffer.append(reset_color.begin(), reset_color.end()); +} + +template struct styled_arg : detail::view { + const T& value; + text_style style; + styled_arg(const T& v, text_style s) : value(v), style(s) {} +}; + +template +void vformat_to( + buffer& buf, const text_style& ts, basic_string_view format_str, + basic_format_args>> args) { + bool has_style = false; + if (ts.has_emphasis()) { + has_style = true; + auto emphasis = detail::make_emphasis(ts.get_emphasis()); + buf.append(emphasis.begin(), emphasis.end()); + } + if (ts.has_foreground()) { + has_style = true; + auto foreground = detail::make_foreground_color(ts.get_foreground()); + buf.append(foreground.begin(), foreground.end()); + } + if (ts.has_background()) { + has_style = true; + auto background = detail::make_background_color(ts.get_background()); + buf.append(background.begin(), background.end()); + } + detail::vformat_to(buf, format_str, args, {}); + if (has_style) detail::reset_color(buf); +} + +} // namespace detail + +inline void vprint(FILE* f, const text_style& ts, string_view fmt, + format_args args) { + auto buf = memory_buffer(); + detail::vformat_to(buf, ts, fmt, args); + print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size())); +} + +/** + * Formats a string and prints it to the specified file stream using ANSI + * escape sequences to specify text formatting. + * + * **Example**: + * + * fmt::print(fmt::emphasis::bold | fg(fmt::color::red), + * "Elapsed time: {0:.2f} seconds", 1.23); + */ +template +void print(FILE* f, const text_style& ts, format_string fmt, + T&&... args) { + vprint(f, ts, fmt, fmt::make_format_args(args...)); +} + +/** + * Formats a string and prints it to stdout using ANSI escape sequences to + * specify text formatting. + * + * **Example**: + * + * fmt::print(fmt::emphasis::bold | fg(fmt::color::red), + * "Elapsed time: {0:.2f} seconds", 1.23); + */ +template +void print(const text_style& ts, format_string fmt, T&&... args) { + return print(stdout, ts, fmt, std::forward(args)...); +} + +inline auto vformat(const text_style& ts, string_view fmt, format_args args) + -> std::string { + auto buf = memory_buffer(); + detail::vformat_to(buf, ts, fmt, args); + return fmt::to_string(buf); +} + +/** + * Formats arguments and returns the result as a string using ANSI escape + * sequences to specify text formatting. + * + * **Example**: + * + * ``` + * #include + * std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), + * "The answer is {}", 42); + * ``` + */ +template +inline auto format(const text_style& ts, format_string fmt, T&&... args) + -> std::string { + return fmt::vformat(ts, fmt, fmt::make_format_args(args...)); +} + +/// Formats a string with the given text_style and writes the output to `out`. +template ::value)> +auto vformat_to(OutputIt out, const text_style& ts, string_view fmt, + format_args args) -> OutputIt { + auto&& buf = detail::get_buffer(out); + detail::vformat_to(buf, ts, fmt, args); + return detail::get_iterator(buf, out); +} + +/** + * Formats arguments with the given text style, writes the result to the output + * iterator `out` and returns the iterator past the end of the output range. + * + * **Example**: + * + * std::vector out; + * fmt::format_to(std::back_inserter(out), + * fmt::emphasis::bold | fg(fmt::color::red), "{}", 42); + */ +template ::value)> +inline auto format_to(OutputIt out, const text_style& ts, + format_string fmt, T&&... args) -> OutputIt { + return vformat_to(out, ts, fmt, fmt::make_format_args(args...)); +} + +template +struct formatter, Char> : formatter { + template + auto format(const detail::styled_arg& arg, FormatContext& ctx) const + -> decltype(ctx.out()) { + const auto& ts = arg.style; + const auto& value = arg.value; + auto out = ctx.out(); + + bool has_style = false; + if (ts.has_emphasis()) { + has_style = true; + auto emphasis = detail::make_emphasis(ts.get_emphasis()); + out = std::copy(emphasis.begin(), emphasis.end(), out); + } + if (ts.has_foreground()) { + has_style = true; + auto foreground = + detail::make_foreground_color(ts.get_foreground()); + out = std::copy(foreground.begin(), foreground.end(), out); + } + if (ts.has_background()) { + has_style = true; + auto background = + detail::make_background_color(ts.get_background()); + out = std::copy(background.begin(), background.end(), out); + } + out = formatter::format(value, ctx); + if (has_style) { + auto reset_color = string_view("\x1b[0m"); + out = std::copy(reset_color.begin(), reset_color.end(), out); + } + return out; + } +}; + +/** + * Returns an argument that will be formatted using ANSI escape sequences, + * to be used in a formatting function. + * + * **Example**: + * + * fmt::print("Elapsed time: {0:.2f} seconds", + * fmt::styled(1.23, fmt::fg(fmt::color::green) | + * fmt::bg(fmt::color::blue))); + */ +template +FMT_CONSTEXPR auto styled(const T& value, text_style ts) + -> detail::styled_arg> { + return detail::styled_arg>{value, ts}; +} + +FMT_END_EXPORT +FMT_END_NAMESPACE + +#endif // FMT_COLOR_H_ diff --git a/libs/fmt/fmt/xchar.h b/libs/fmt/fmt/xchar.h new file mode 100644 index 00000000..b1f39ed2 --- /dev/null +++ b/libs/fmt/fmt/xchar.h @@ -0,0 +1,322 @@ +// Formatting library for C++ - optional wchar_t and exotic character support +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_XCHAR_H_ +#define FMT_XCHAR_H_ + +#include "color.h" +#include "format.h" +#include "ranges.h" + +#ifndef FMT_MODULE +# include +# if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) +# include +# endif +#endif + +FMT_BEGIN_NAMESPACE +namespace detail { + +template +using is_exotic_char = bool_constant::value>; + +template struct format_string_char {}; + +template +struct format_string_char< + S, void_t())))>> { + using type = char_t; +}; + +template +struct format_string_char::value>> { + using type = typename S::char_type; +}; + +template +using format_string_char_t = typename format_string_char::type; + +inline auto write_loc(basic_appender out, loc_value value, + const format_specs& specs, locale_ref loc) -> bool { +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR + auto& numpunct = + std::use_facet>(loc.get()); + auto separator = std::wstring(); + auto grouping = numpunct.grouping(); + if (!grouping.empty()) separator = std::wstring(1, numpunct.thousands_sep()); + return value.visit(loc_writer{out, specs, separator, grouping, {}}); +#endif + return false; +} +} // namespace detail + +FMT_BEGIN_EXPORT + +using wstring_view = basic_string_view; +using wformat_parse_context = basic_format_parse_context; +using wformat_context = buffered_context; +using wformat_args = basic_format_args; +using wmemory_buffer = basic_memory_buffer; + +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 +// Workaround broken conversion on older gcc. +template using wformat_string = wstring_view; +inline auto runtime(wstring_view s) -> wstring_view { return s; } +#else +template +using wformat_string = basic_format_string...>; +inline auto runtime(wstring_view s) -> runtime_format_string { + return {{s}}; +} +#endif + +template <> struct is_char : std::true_type {}; +template <> struct is_char : std::true_type {}; +template <> struct is_char : std::true_type {}; + +#ifdef __cpp_char8_t +template <> +struct is_char : bool_constant {}; +#endif + +template +constexpr auto make_wformat_args(T&... args) + -> decltype(fmt::make_format_args(args...)) { + return fmt::make_format_args(args...); +} + +inline namespace literals { +#if FMT_USE_USER_DEFINED_LITERALS && !FMT_USE_NONTYPE_TEMPLATE_ARGS +constexpr auto operator""_a(const wchar_t* s, size_t) + -> detail::udl_arg { + return {s}; +} +#endif +} // namespace literals + +template +auto join(It begin, Sentinel end, wstring_view sep) + -> join_view { + return {begin, end, sep}; +} + +template +auto join(Range&& range, wstring_view sep) + -> join_view, detail::sentinel_t, + wchar_t> { + return join(std::begin(range), std::end(range), sep); +} + +template +auto join(std::initializer_list list, wstring_view sep) + -> join_view { + return join(std::begin(list), std::end(list), sep); +} + +template +auto join(const std::tuple& tuple, basic_string_view sep) + -> tuple_join_view { + return {tuple, sep}; +} + +template ::value)> +auto vformat(basic_string_view format_str, + typename detail::vformat_args::type args) + -> std::basic_string { + auto buf = basic_memory_buffer(); + detail::vformat_to(buf, format_str, args); + return to_string(buf); +} + +template +auto format(wformat_string fmt, T&&... args) -> std::wstring { + return vformat(fmt::wstring_view(fmt), fmt::make_wformat_args(args...)); +} + +template +auto format_to(OutputIt out, wformat_string fmt, T&&... args) + -> OutputIt { + return vformat_to(out, fmt::wstring_view(fmt), + fmt::make_wformat_args(args...)); +} + +// Pass char_t as a default template parameter instead of using +// std::basic_string> to reduce the symbol size. +template , + FMT_ENABLE_IF(!std::is_same::value && + !std::is_same::value)> +auto format(const S& format_str, T&&... args) -> std::basic_string { + return vformat(detail::to_string_view(format_str), + fmt::make_format_args>(args...)); +} + +template , + FMT_ENABLE_IF(detail::is_locale::value&& + detail::is_exotic_char::value)> +inline auto vformat(const Locale& loc, const S& format_str, + typename detail::vformat_args::type args) + -> std::basic_string { + return detail::vformat(loc, detail::to_string_view(format_str), args); +} + +template , + FMT_ENABLE_IF(detail::is_locale::value&& + detail::is_exotic_char::value)> +inline auto format(const Locale& loc, const S& format_str, T&&... args) + -> std::basic_string { + return detail::vformat( + loc, detail::to_string_view(format_str), + fmt::make_format_args>(args...)); +} + +template , + FMT_ENABLE_IF(detail::is_output_iterator::value&& + detail::is_exotic_char::value)> +auto vformat_to(OutputIt out, const S& format_str, + typename detail::vformat_args::type args) -> OutputIt { + auto&& buf = detail::get_buffer(out); + detail::vformat_to(buf, detail::to_string_view(format_str), args); + return detail::get_iterator(buf, out); +} + +template , + FMT_ENABLE_IF(detail::is_output_iterator::value && + !std::is_same::value && + !std::is_same::value)> +inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt { + return vformat_to(out, detail::to_string_view(fmt), + fmt::make_format_args>(args...)); +} + +template , + FMT_ENABLE_IF(detail::is_output_iterator::value&& + detail::is_locale::value&& + detail::is_exotic_char::value)> +inline auto vformat_to(OutputIt out, const Locale& loc, const S& format_str, + typename detail::vformat_args::type args) + -> OutputIt { + auto&& buf = detail::get_buffer(out); + vformat_to(buf, detail::to_string_view(format_str), args, + detail::locale_ref(loc)); + return detail::get_iterator(buf, out); +} + +template , + bool enable = detail::is_output_iterator::value && + detail::is_locale::value && + detail::is_exotic_char::value> +inline auto format_to(OutputIt out, const Locale& loc, const S& format_str, + T&&... args) -> + typename std::enable_if::type { + return vformat_to(out, loc, detail::to_string_view(format_str), + fmt::make_format_args>(args...)); +} + +template ::value&& + detail::is_exotic_char::value)> +inline auto vformat_to_n(OutputIt out, size_t n, + basic_string_view format_str, + typename detail::vformat_args::type args) + -> format_to_n_result { + using traits = detail::fixed_buffer_traits; + auto buf = detail::iterator_buffer(out, n); + detail::vformat_to(buf, format_str, args); + return {buf.out(), buf.count()}; +} + +template , + FMT_ENABLE_IF(detail::is_output_iterator::value&& + detail::is_exotic_char::value)> +inline auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args) + -> format_to_n_result { + return vformat_to_n(out, n, fmt::basic_string_view(fmt), + fmt::make_format_args>(args...)); +} + +template , + FMT_ENABLE_IF(detail::is_exotic_char::value)> +inline auto formatted_size(const S& fmt, T&&... args) -> size_t { + auto buf = detail::counting_buffer(); + detail::vformat_to(buf, detail::to_string_view(fmt), + fmt::make_format_args>(args...)); + return buf.count(); +} + +inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) { + auto buf = wmemory_buffer(); + detail::vformat_to(buf, fmt, args); + buf.push_back(L'\0'); + if (std::fputws(buf.data(), f) == -1) + FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); +} + +inline void vprint(wstring_view fmt, wformat_args args) { + vprint(stdout, fmt, args); +} + +template +void print(std::FILE* f, wformat_string fmt, T&&... args) { + return vprint(f, wstring_view(fmt), fmt::make_wformat_args(args...)); +} + +template void print(wformat_string fmt, T&&... args) { + return vprint(wstring_view(fmt), fmt::make_wformat_args(args...)); +} + +template +void println(std::FILE* f, wformat_string fmt, T&&... args) { + return print(f, L"{}\n", fmt::format(fmt, std::forward(args)...)); +} + +template void println(wformat_string fmt, T&&... args) { + return print(L"{}\n", fmt::format(fmt, std::forward(args)...)); +} + +inline auto vformat(const text_style& ts, wstring_view fmt, wformat_args args) + -> std::wstring { + auto buf = wmemory_buffer(); + detail::vformat_to(buf, ts, fmt, args); + return fmt::to_string(buf); +} + +template +inline auto format(const text_style& ts, wformat_string fmt, T&&... args) + -> std::wstring { + return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...)); +} + +template +FMT_DEPRECATED void print(std::FILE* f, const text_style& ts, + wformat_string fmt, const T&... args) { + vprint(f, ts, fmt, fmt::make_wformat_args(args...)); +} + +template +FMT_DEPRECATED void print(const text_style& ts, wformat_string fmt, + const T&... args) { + return print(stdout, ts, fmt, args...); +} + +/// Converts `value` to `std::wstring` using the default format for type `T`. +template inline auto to_wstring(const T& value) -> std::wstring { + return format(FMT_STRING(L"{}"), value); +} +FMT_END_EXPORT +FMT_END_NAMESPACE + +#endif // FMT_XCHAR_H_ diff --git a/src/detail/standalone/entry.cpp b/src/detail/standalone/entry.cpp index 502e8bef..26adac52 100644 --- a/src/detail/standalone/entry.cpp +++ b/src/detail/standalone/entry.cpp @@ -59,6 +59,17 @@ std::shared_ptr mainCreatePlugin(const clap_plugin_entry *ee, cons if (pt.has_value()) { auto loadPath = *pt / plugin->_plugin->desc->id; + + try + { + LOG << "Trying to save default clap wrapper settings" << std::endl; + standaloneHost->saveStandaloneAndPluginSettings(loadPath, "defaults.clapwrapper"); + } + catch (const fs::filesystem_error &e) + { + // Oh well - whatcha gonna do? + } + try { if (fs::exists(loadPath / "settings.clapwrapper")) @@ -146,4 +157,4 @@ int mainFinish() } return 0; } -} // namespace freeaudio::clap_wrapper::standalone \ No newline at end of file +} // namespace freeaudio::clap_wrapper::standalone diff --git a/src/detail/standalone/standalone_host.cpp b/src/detail/standalone/standalone_host.cpp index ecfaac95..bd2ec778 100644 --- a/src/detail/standalone/standalone_host.cpp +++ b/src/detail/standalone/standalone_host.cpp @@ -121,6 +121,8 @@ void StandaloneHost::clapProcess(void *pOutput, const void *pInput, uint32_t fra process.in_events = &inputEvents; process.out_events = &outputEvents; process.frames_count = frameCount; + process.audio_inputs_count = numAudioInputs; + process.audio_outputs_count = numAudioOutputs; assert(frameCount < utilityBufferSize); if (frameCount >= utilityBufferSize) diff --git a/src/detail/standalone/standalone_host.h b/src/detail/standalone/standalone_host.h index 231163bb..2e104d6f 100644 --- a/src/detail/standalone/standalone_host.h +++ b/src/detail/standalone/standalone_host.h @@ -38,15 +38,6 @@ struct GtkGui; #endif #endif -#if WIN -#if CLAP_WRAPPER_HAS_WIN32 -namespace windows -{ -struct Win32Gui; -} -#endif -#endif - std::optional getStandaloneSettingsPath(); struct StandaloneHost : Clap::IHost @@ -185,12 +176,6 @@ struct StandaloneHost : Clap::IHost #if CLAP_WRAPPER_HAS_GTK3 freeaudio::clap_wrapper::standalone::linux_standalone::GtkGui *gtkGui{nullptr}; #endif -#endif - -#if WIN -#if CLAP_WRAPPER_HAS_WIN32 - freeaudio::clap_wrapper::standalone::windows::Win32Gui *win32Gui{nullptr}; -#endif #endif bool register_timer(uint32_t period_ms, clap_id *timer_id) override; diff --git a/src/detail/standalone/windows/helper.h b/src/detail/standalone/windows/helper.h deleted file mode 100644 index 4431be63..00000000 --- a/src/detail/standalone/windows/helper.h +++ /dev/null @@ -1,368 +0,0 @@ -#if CLAP_WRAPPER_HAS_WIN32 - -#ifdef UNICODE -#undef UNICODE -#endif - -#include -#include - -#include -#include - -#pragma comment(lib, "dwmapi") - -#define IDM_SETTINGS 1001 -#define IDM_SAVE_STATE 1002 -#define IDM_LOAD_STATE 1003 -#define IDM_RESET_STATE 1004 - -namespace freeaudio::clap_wrapper::standalone::windows -{ -struct Window -{ - Window(); - ~Window(); - - static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); - virtual int OnClose(HWND, UINT, WPARAM, LPARAM); - virtual int OnDestroy(HWND, UINT, WPARAM, LPARAM); - virtual int OnDpiChanged(HWND, UINT, WPARAM, LPARAM); - virtual int OnKeyDown(HWND, UINT, WPARAM, LPARAM); - virtual int OnWindowPosChanged(HWND, UINT, WPARAM, LPARAM); - - bool fullscreen(); - - HWND m_hwnd; - bool isConsoleAttached{false}; -}; - -struct Console -{ - Console(); - ~Console(); - FILE* f; -}; - -template -T* InstanceFromWndProc(HWND hwnd, UINT umsg, LPARAM lparam) -{ - T* pInstance; - - if (umsg == WM_NCCREATE) - { - LPCREATESTRUCT pCreateStruct{reinterpret_cast(lparam)}; - pInstance = reinterpret_cast(pCreateStruct->lpCreateParams); - ::SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast(pInstance)); - pInstance->*m_hwnd = hwnd; - } - - else - pInstance = reinterpret_cast(::GetWindowLongPtr(hwnd, GWLP_USERDATA)); - - return pInstance; -} - -std::string narrow(std::wstring in) -{ - if (!in.empty()) - { - auto inSize{static_cast(in.size())}; - - auto outSize{::WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS | WC_ERR_INVALID_CHARS, in.data(), - inSize, nullptr, 0, nullptr, nullptr)}; - - if (outSize > 0) - { - std::string out; - out.resize(static_cast(outSize)); - - if (::WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS | WC_ERR_INVALID_CHARS, in.data(), inSize, - out.data(), outSize, nullptr, nullptr) > 0) - return out; - } - } - - return {}; -} - -std::wstring widen(std::string in) -{ - if (!in.empty()) - { - auto inSize{static_cast(in.size())}; - - auto outSize{::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, in.data(), inSize, nullptr, 0)}; - - if (outSize > 0) - { - std::wstring out; - out.resize(static_cast(outSize)); - - if (::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, in.data(), inSize, out.data(), outSize) > - 0) - return out; - } - } - - return {}; -} - -std::string randomize(std::string in) -{ - std::random_device rd; - std::mt19937 mt(rd()); - std::uniform_real_distribution dist(1.0, 10.0); - auto randomDouble{dist(mt)}; - auto randomNumber{std::to_string(randomDouble)}; - randomNumber.erase(remove(randomNumber.begin(), randomNumber.end(), '.'), randomNumber.end()); - - return (in + randomNumber); -} - -Window::Window() -{ - std::string clapName{WIN32_NAME}; - std::string randomName{randomize(clapName)}; - - WNDCLASSEX wcex{sizeof(WNDCLASSEX)}; - wcex.lpszClassName = randomName.c_str(); - wcex.lpszMenuName = randomName.c_str(); - wcex.lpfnWndProc = Window::WndProc; - wcex.style = 0; - wcex.cbClsExtra = 0; - wcex.cbWndExtra = 0; - wcex.hInstance = ::GetModuleHandle(nullptr); - wcex.hbrBackground = reinterpret_cast(::GetStockObject(BLACK_BRUSH)); - wcex.hCursor = reinterpret_cast(::LoadImage(nullptr, reinterpret_cast(IDC_ARROW), - IMAGE_CURSOR, 0, 0, LR_SHARED | LR_DEFAULTSIZE)); - wcex.hIcon = - reinterpret_cast(::LoadImage(nullptr, reinterpret_cast(IDI_APPLICATION), IMAGE_ICON, - 0, 0, LR_DEFAULTCOLOR | LR_DEFAULTSIZE | LR_SHARED)); - wcex.hIconSm = - reinterpret_cast(::LoadImage(nullptr, reinterpret_cast(IDI_APPLICATION), IMAGE_ICON, - 0, 0, LR_DEFAULTCOLOR | LR_DEFAULTSIZE | LR_SHARED)); - - ::RegisterClassEx(&wcex); - - ::CreateWindowEx(0, randomName.c_str(), clapName.c_str(), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, - CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, - ::GetModuleHandle(nullptr), this); - - auto hMenu{::GetSystemMenu(m_hwnd, FALSE)}; - - MENUITEMINFO seperator{sizeof(MENUITEMINFO)}; - seperator.fMask = MIIM_FTYPE; - seperator.fType = MFT_SEPARATOR; - - MENUITEMINFO audioIn{sizeof(MENUITEMINFO)}; - audioIn.fMask = MIIM_STRING | MIIM_ID; - audioIn.wID = IDM_SETTINGS; - audioIn.dwTypeData = const_cast("Settings"); - - MENUITEMINFO saveState{sizeof(MENUITEMINFO)}; - saveState.fMask = MIIM_STRING | MIIM_ID; - saveState.wID = IDM_SAVE_STATE; - saveState.dwTypeData = const_cast("Save state..."); - - MENUITEMINFO loadState{sizeof(MENUITEMINFO)}; - loadState.fMask = MIIM_STRING | MIIM_ID; - loadState.wID = IDM_LOAD_STATE; - loadState.dwTypeData = const_cast("Load state..."); - - MENUITEMINFO resetState{sizeof(MENUITEMINFO)}; - resetState.fMask = MIIM_STRING | MIIM_ID; - resetState.wID = IDM_RESET_STATE; - resetState.dwTypeData = const_cast("Reset state..."); - - if (hMenu != INVALID_HANDLE_VALUE) - { - ::InsertMenuItem(hMenu, 1, TRUE, &seperator); - ::InsertMenuItem(hMenu, 2, TRUE, &audioIn); - ::InsertMenuItem(hMenu, 3, TRUE, &seperator); - ::InsertMenuItem(hMenu, 4, TRUE, &saveState); - ::InsertMenuItem(hMenu, 5, TRUE, &loadState); - ::InsertMenuItem(hMenu, 6, TRUE, &resetState); - ::InsertMenuItem(hMenu, 7, TRUE, &seperator); - } -} - -Window::~Window() -{ -} - -LRESULT CALLBACK Window::WndProc(HWND h, UINT m, WPARAM w, LPARAM l) -{ - Window* pWindow = InstanceFromWndProc(h, m, l); - - if (pWindow) - { - switch (m) - { - case WM_CLOSE: - return pWindow->OnClose(h, m, w, l); - case WM_DESTROY: - return pWindow->OnDestroy(h, m, w, l); - case WM_DPICHANGED: - return pWindow->OnDpiChanged(h, m, w, l); - case WM_KEYDOWN: - return pWindow->OnKeyDown(h, m, w, l); - case WM_WINDOWPOSCHANGED: - return pWindow->OnWindowPosChanged(h, m, w, l); - } - } - - return ::DefWindowProc(h, m, w, l); -} - -int Window::OnClose(HWND h, UINT m, WPARAM w, LPARAM l) -{ - ::DestroyWindow(h); - - return 0; -} - -int Window::OnDestroy(HWND h, UINT m, WPARAM w, LPARAM l) -{ - auto plugin{freeaudio::clap_wrapper::standalone::getMainPlugin()}; - - if (plugin && plugin->_ext._gui) - { - plugin->_ext._gui->hide(plugin->_plugin); - plugin->_ext._gui->destroy(plugin->_plugin); - } - - ::PostQuitMessage(0); - - return 0; -} - -int Window::OnDpiChanged(HWND h, UINT m, WPARAM w, LPARAM l) -{ - auto plugin{freeaudio::clap_wrapper::standalone::getMainPlugin()}; - auto ui{plugin->_ext._gui}; - auto p{plugin->_plugin}; - - auto dpi{::GetDpiForWindow(h)}; - auto scaleFactor{static_cast(dpi) / static_cast(USER_DEFAULT_SCREEN_DPI)}; - - ui->set_scale(p, scaleFactor); - - auto bounds{(RECT*)l}; - ::SetWindowPos(h, nullptr, bounds->left, bounds->top, (bounds->right - bounds->left), - (bounds->bottom - bounds->top), SWP_NOZORDER | SWP_NOACTIVATE); - - return 0; -} - -int Window::OnKeyDown(HWND h, UINT m, WPARAM w, LPARAM l) -{ - auto plugin{freeaudio::clap_wrapper::standalone::getMainPlugin()}; - auto ui{plugin->_ext._gui}; - auto p{plugin->_plugin}; - - switch (w) - { - case VK_F11: - { - if (ui->can_resize(p)) fullscreen(); - - break; - } - - default: - return 0; - } - - return 0; -} - -int Window::OnWindowPosChanged(HWND h, UINT m, WPARAM w, LPARAM l) -{ - auto plugin{freeaudio::clap_wrapper::standalone::getMainPlugin()}; - auto ui{plugin->_ext._gui}; - auto p{plugin->_plugin}; - - auto dpi{::GetDpiForWindow(h)}; - auto scaleFactor{static_cast(dpi) / static_cast(USER_DEFAULT_SCREEN_DPI)}; - - if (ui->can_resize(p)) - { - RECT r{0, 0, 0, 0}; - ::GetClientRect(h, &r); - uint32_t w = (r.right - r.left); - uint32_t h = (r.bottom - r.top); - ui->adjust_size(p, &w, &h); - ui->set_size(p, w, h); - } - -#ifdef _DEBUG - if (isConsoleAttached) - { - RECT wr{0, 0, 0, 0}; - ::GetWindowRect(h, &wr); - ::SetWindowPos(::GetConsoleWindow(), nullptr, wr.left, wr.bottom, (wr.right - wr.left), 200, - SWP_NOZORDER | SWP_ASYNCWINDOWPOS); - } -#endif - - return 0; -} - -bool Window::fullscreen() -{ - auto plugin{freeaudio::clap_wrapper::standalone::getMainPlugin()}; - auto ui{plugin->_ext._gui}; - auto p{plugin->_plugin}; - - static RECT pos; - - auto style{::GetWindowLongPtr(m_hwnd, GWL_STYLE)}; - - if (style & WS_OVERLAPPEDWINDOW) - { - MONITORINFO mi = {sizeof(mi)}; - ::GetWindowRect(m_hwnd, &pos); - if (::GetMonitorInfo(::MonitorFromWindow(m_hwnd, MONITOR_DEFAULTTONEAREST), &mi)) - { - ::SetWindowLongPtr(m_hwnd, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW); - ::SetWindowPos(m_hwnd, HWND_TOP, mi.rcMonitor.left, mi.rcMonitor.top, - mi.rcMonitor.right - mi.rcMonitor.left, mi.rcMonitor.bottom - mi.rcMonitor.top, - SWP_FRAMECHANGED); - } - - return true; - } - - else - { - ::SetWindowLongPtr(m_hwnd, GWL_STYLE, style | WS_OVERLAPPEDWINDOW); - ::SetWindowPos(m_hwnd, nullptr, pos.left, pos.top, (pos.right - pos.left), (pos.bottom - pos.top), - SWP_FRAMECHANGED); - - return false; - } -} - -Console::Console() -{ - ::AllocConsole(); - ::EnableMenuItem(::GetSystemMenu(::GetConsoleWindow(), FALSE), SC_CLOSE, - MF_BYCOMMAND | MF_DISABLED | MF_GRAYED); - ::freopen_s(&f, "CONOUT$", "w", stdout); - ::freopen_s(&f, "CONOUT$", "w", stderr); - ::freopen_s(&f, "CONIN$", "r", stdin); - std::cout.clear(); - std::clog.clear(); - std::cerr.clear(); - std::cin.clear(); -} - -Console::~Console() -{ - ::fclose(f); - ::FreeConsole(); -} -} // namespace freeaudio::clap_wrapper::standalone::windows - -#endif diff --git a/src/detail/standalone/windows/helpers.cpp b/src/detail/standalone/windows/helpers.cpp new file mode 100644 index 00000000..ad29c60b --- /dev/null +++ b/src/detail/standalone/windows/helpers.cpp @@ -0,0 +1,203 @@ +#include "helpers.h" + +namespace freeaudio::clap_wrapper::standalone::windows::helpers +{ +Size getClientSize(::HWND window) +{ + ::RECT clientRect{}; + ::GetClientRect(window, &clientRect); + + return Size{static_cast(clientRect.right - clientRect.left), + static_cast(clientRect.bottom - clientRect.top)}; +} + +::HMODULE getInstance() +{ + ::HMODULE hInstance; + ::GetModuleHandleExW(0, nullptr, &hInstance); + + return hInstance; +} + +bool activateWindow(::HWND window) +{ + return ::ShowWindow(window, SW_NORMAL); +} + +bool showWindow(::HWND window) +{ + return ::ShowWindow(window, SW_SHOW); +} + +bool hideWindow(::HWND window) +{ + return ::ShowWindow(window, SW_HIDE); +} + +void centerWindow(::HWND window, int width, int height) +{ + ::MONITORINFO mi{sizeof(::MONITORINFO)}; + ::GetMonitorInfoW(::MonitorFromWindow(window, MONITOR_DEFAULTTONEAREST), &mi); + + auto monitorWidth{checkSafeSize(mi.rcWork.right - mi.rcWork.left)}; + auto monitorHeight{checkSafeSize(mi.rcWork.bottom - mi.rcWork.top)}; + + if (monitorWidth > width && monitorHeight > height) + { + auto x{(monitorWidth - width) / 2}; + auto y{(monitorHeight - height) / 2}; + ::SetWindowPos(window, nullptr, x, y, width, height, 0); + } +} + +bool closeWindow(::HWND window) +{ + return ::CloseWindow(window); +} + +bool checkWindowVisibility(::HWND window) +{ + return ::IsWindowVisible(window); +} + +unsigned int getCurrentDpi(::HWND window) +{ + return ::GetDpiForWindow(window); +} + +double getScale(::HWND window) +{ + return static_cast(::GetDpiForWindow(window)) / static_cast(USER_DEFAULT_SCREEN_DPI); +} + +void abort(unsigned int exitCode) +{ + ::ExitProcess(exitCode); +} + +void quit(unsigned int exitCode) +{ + ::PostQuitMessage(exitCode); +} + +int messageLoop() +{ + ::MSG msg{}; + int r{}; + + while ((r = ::GetMessageW(&msg, nullptr, 0, 0)) != 0) + { + if (r == -1) + { + return EXIT_FAILURE; + } + + else + { + ::TranslateMessage(&msg); + ::DispatchMessageW(&msg); + } + } + + return static_cast(msg.wParam); +} + +std::wstring toUTF16(std::string_view utf8) +{ + std::wstring utf16; + + if (utf8.length() > 0) + { + int safeSize{checkSafeSize(utf8.length())}; + + auto length{::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8.data(), safeSize, nullptr, 0)}; + + utf16.resize(length); + + if (::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8.data(), safeSize, utf16.data(), + length) == 0) + { + throw std::exception("UTF8 to UTF16 conversion failed"); + } + } + + return utf16; +} + +std::string toUTF8(std::wstring_view utf16) +{ + std::string utf8; + + if (utf16.length() > 0) + { + int safeSize{checkSafeSize(utf16.length())}; + + auto length{::WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS | WC_ERR_INVALID_CHARS, utf16.data(), + safeSize, nullptr, 0, nullptr, nullptr)}; + + utf8.resize(length); + + if (::WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS | WC_ERR_INVALID_CHARS, utf16.data(), + safeSize, utf8.data(), length, nullptr, nullptr) == 0) + { + throw std::exception("UTF16 to UTF8 conversion failed"); + } + } + + return utf8; +} + +void log(const std::string& message) +{ + ::OutputDebugStringW(toUTF16(message).c_str()); + ::OutputDebugStringW(L"\n"); +} + +void log(const std::wstring& message) +{ + ::OutputDebugStringW(message.c_str()); + ::OutputDebugStringW(L"\n"); +} + +void messageBox(const std::string& message) +{ + ::MessageBoxW(nullptr, toUTF16(message).c_str(), nullptr, MB_OK | MB_ICONASTERISK); +} + +void messageBox(const std::wstring& message) +{ + ::MessageBoxW(nullptr, message.c_str(), nullptr, MB_OK | MB_ICONASTERISK); +} + +void errorBox(const std::string& message) +{ + ::MessageBoxW(nullptr, toUTF16(message).c_str(), nullptr, MB_OK | MB_ICONHAND); +} + +void errorBox(const std::wstring& message) +{ + ::MessageBoxW(nullptr, message.c_str(), nullptr, MB_OK | MB_ICONHAND); +} + +::HBRUSH loadBrushFromSystem(int name) +{ + return static_cast<::HBRUSH>(::GetStockObject(name)); +} + +::HCURSOR loadCursorFromSystem(LPSTR name) +{ + return static_cast<::HCURSOR>( + ::LoadImageA(nullptr, name, IMAGE_CURSOR, 0, 0, LR_SHARED | LR_DEFAULTSIZE)); +} + +::HICON loadIconFromSystem(LPSTR name) +{ + return static_cast<::HICON>(::LoadImageA(nullptr, name, IMAGE_ICON, 0, 0, LR_SHARED | LR_DEFAULTSIZE)); +} + +::HICON loadIconFromResource() +{ + return static_cast<::HICON>( + ::LoadImageW(getInstance(), MAKEINTRESOURCEW(1), IMAGE_ICON, 0, 0, LR_SHARED | LR_DEFAULTSIZE)); +} +} // namespace freeaudio::clap_wrapper::standalone::windows::helpers diff --git a/src/detail/standalone/windows/helpers.h b/src/detail/standalone/windows/helpers.h new file mode 100644 index 00000000..55484b6b --- /dev/null +++ b/src/detail/standalone/windows/helpers.h @@ -0,0 +1,165 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +#include "detail/standalone/entry.h" + +#define FMT_HEADER_ONLY +#include +#include + +namespace freeaudio::clap_wrapper::standalone::windows::helpers +{ +struct Size +{ + uint32_t width; + uint32_t height; +}; + +Size getClientSize(::HWND window); + +::HMODULE getInstance(); + +bool activateWindow(::HWND window); +bool showWindow(::HWND window); +bool hideWindow(::HWND window); +void centerWindow(::HWND window, int width, int height); +bool closeWindow(::HWND window); +bool checkWindowVisibility(::HWND window); +unsigned int getCurrentDpi(::HWND window); +double getScale(::HWND window); + +void abort(unsigned int exitCode = EXIT_FAILURE); +void quit(unsigned int exitCode = EXIT_SUCCESS); + +int messageLoop(); + +std::wstring toUTF16(std::string_view utf8); +std::string toUTF8(std::wstring_view utf16); + +void log(const std::string& message); +void log(const std::wstring& message); + +void messageBox(const std::string& message); +void messageBox(const std::wstring& message); + +void errorBox(const std::string& message); +void errorBox(const std::wstring& message); + +::HBRUSH loadBrushFromSystem(int name = BLACK_BRUSH); +::HCURSOR loadCursorFromSystem(LPSTR name = IDC_ARROW); +::HICON loadIconFromSystem(LPSTR name = IDI_APPLICATION); +::HICON loadIconFromResource(); + +template +auto createWindow(std::string_view name, T* self) -> ::HWND +{ + auto className{toUTF16(name)}; + auto hInstance{getInstance()}; + auto iconFromResource{loadIconFromResource()}; + + ::WNDCLASSEXW windowClass{sizeof(::WNDCLASSEXW), + 0, + self->wndProc, + 0, + sizeof(intptr_t), + hInstance, + iconFromResource ? iconFromResource : loadIconFromSystem(), + loadCursorFromSystem(), + loadBrushFromSystem(), + nullptr, + className.c_str(), + iconFromResource ? iconFromResource : loadIconFromSystem()}; + + if (::GetClassInfoExW(hInstance, className.c_str(), &windowClass) == 0) + { + ::RegisterClassExW(&windowClass); + } + + return ::CreateWindowExW(0, className.c_str(), className.c_str(), + WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, hInstance, self); +} + +template +T* instanceFromWndProc(HWND hWnd, UINT uMsg, LPARAM lParam) +{ + T* self{nullptr}; + + if (uMsg == WM_NCCREATE) + { + auto lpCreateStruct{reinterpret_cast<::LPCREATESTRUCTW>(lParam)}; + self = static_cast(lpCreateStruct->lpCreateParams); + ::SetWindowLongPtrW(hWnd, 0, reinterpret_cast(self)); + self->m_hWnd.reset(hWnd); + } + + else + { + self = reinterpret_cast(::GetWindowLongPtrW(hWnd, 0)); + } + + return self; +} + +template +auto checkSafeSize(T value) -> R +{ + constexpr R max{std::numeric_limits::max()}; + + if (value > static_cast(max)) + { + throw std::overflow_error("Unsafe size"); + } + + return static_cast(value); +} + +template +auto log(const fmt::format_string fmt, Args&&... args) -> void +{ + ::OutputDebugStringW(toUTF16(fmt::vformat(fmt.get(), fmt::make_format_args(args...))).c_str()); + ::OutputDebugStringW(L"\n"); +} + +template +auto log(const fmt::wformat_string fmt, Args&&... args) -> void +{ + ::OutputDebugStringW(fmt::vformat(fmt.get(), fmt::make_wformat_args(args...)).c_str()); + ::OutputDebugStringW(L"\n"); +} + +template +auto messageBox(const fmt::format_string fmt, Args&&... args) -> void +{ + ::MessageBoxW(nullptr, toUTF16(fmt::vformat(fmt.get(), fmt::make_format_args(args...))).c_str(), + nullptr, MB_OK | MB_ICONASTERISK); +} + +template +auto messageBox(const fmt::wformat_string fmt, Args&&... args) -> void +{ + ::MessageBoxW(nullptr, fmt::vformat(fmt.get(), fmt::make_wformat_args(args...)).c_str(), nullptr, + MB_OK | MB_ICONASTERISK); +} + +template +auto errorBox(const fmt::format_string fmt, Args&&... args) -> void +{ + ::MessageBoxW(nullptr, toUTF16(fmt::vformat(fmt.get(), fmt::make_format_args(args...))).c_str(), + nullptr, MB_OK | MB_ICONHAND); +} + +template +auto errorBox(const fmt::wformat_string fmt, Args&&... args) -> void +{ + ::MessageBoxW(nullptr, fmt::vformat(fmt.get(), fmt::make_wformat_args(args...)).c_str(), nullptr, + MB_OK | MB_ICONHAND); +} +} // namespace freeaudio::clap_wrapper::standalone::windows::helpers diff --git a/src/detail/standalone/windows/host_window.cpp b/src/detail/standalone/windows/host_window.cpp new file mode 100644 index 00000000..0665d73c --- /dev/null +++ b/src/detail/standalone/windows/host_window.cpp @@ -0,0 +1,310 @@ +#include +#include "detail/standalone/entry.h" +#include "host_window.h" +#include "helpers.h" + +#define IDM_SETTINGS 1001 +#define IDM_SAVE_STATE 1002 +#define IDM_LOAD_STATE 1003 +#define IDM_RESET_STATE 1004 + +namespace freeaudio::clap_wrapper::standalone::windows +{ +HostWindow::HostWindow(std::shared_ptr clapPlugin) + : m_clapPlugin{clapPlugin} + , m_plugin{m_clapPlugin->_plugin} + , m_pluginGui{m_clapPlugin->_ext._gui} + , m_pluginState{m_clapPlugin->_ext._state} +{ + freeaudio::clap_wrapper::standalone::windows::helpers::createWindow(OUTPUT_NAME, this); + + setupMenu(); + + setupStandaloneHost(); + + setupPlugin(); + + helpers::activateWindow(m_hWnd.get()); +} + +void HostWindow::setupMenu() +{ + auto hMenu{::GetSystemMenu(m_hWnd.get(), FALSE)}; + + ::MENUITEMINFOW seperator{sizeof(::MENUITEMINFOW)}; + seperator.fMask = MIIM_FTYPE; + seperator.fType = MFT_SEPARATOR; + + ::MENUITEMINFOW settings{sizeof(::MENUITEMINFOW)}; + settings.fMask = MIIM_STATE | MIIM_STRING | MIIM_ID; + settings.wID = IDM_SETTINGS; + settings.dwTypeData = const_cast(L"Audio/MIDI Settings"); + settings.fState = MFS_DISABLED; + + ::MENUITEMINFOW saveState{sizeof(::MENUITEMINFOW)}; + saveState.fMask = MIIM_STRING | MIIM_ID; + saveState.wID = IDM_SAVE_STATE; + saveState.dwTypeData = const_cast(L"Save plugin state..."); + + ::MENUITEMINFOW loadState{sizeof(::MENUITEMINFOW)}; + loadState.fMask = MIIM_STRING | MIIM_ID; + loadState.wID = IDM_LOAD_STATE; + loadState.dwTypeData = const_cast(L"Load plugin state..."); + + ::MENUITEMINFOW resetState{sizeof(::MENUITEMINFOW)}; + resetState.fMask = MIIM_STRING | MIIM_ID; + resetState.wID = IDM_RESET_STATE; + resetState.dwTypeData = const_cast(L"Reset to default plugin state"); + + if (hMenu != INVALID_HANDLE_VALUE) + { + ::InsertMenuItemW(hMenu, 1, TRUE, &seperator); + ::InsertMenuItemW(hMenu, 2, TRUE, &settings); + ::InsertMenuItemW(hMenu, 3, TRUE, &seperator); + ::InsertMenuItemW(hMenu, 4, TRUE, &saveState); + ::InsertMenuItemW(hMenu, 5, TRUE, &loadState); + ::InsertMenuItemW(hMenu, 6, TRUE, &seperator); + ::InsertMenuItemW(hMenu, 7, TRUE, &resetState); + ::InsertMenuItemW(hMenu, 8, TRUE, &seperator); + } +} + +void HostWindow::setupStandaloneHost() +{ + freeaudio::clap_wrapper::standalone::getStandaloneHost()->onRequestResize = + [this](uint32_t width, uint32_t height) { return setWindowSize(width, height); }; +} + +void HostWindow::setupPlugin() +{ + m_pluginGui->create(m_plugin, CLAP_WINDOW_API_WIN32, false); + + m_pluginGui->set_scale(m_plugin, helpers::getScale(m_hWnd.get())); + + uint32_t width{0}; + uint32_t height{0}; + + if (m_pluginGui->can_resize(m_plugin)) + { + // We can restore size here + // setWindowSize(previousWidth, previousHeight); + } + else + { + // We can't resize, so disable WS_THICKFRAME and WS_MAXIMIZEBOX + ::SetWindowLongPtrW(m_hWnd.get(), GWL_STYLE, + ::GetWindowLongPtrW(m_hWnd.get(), GWL_STYLE) & ~WS_OVERLAPPEDWINDOW | + WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX); + } + + m_pluginGui->get_size(m_plugin, &width, &height); + + setWindowSize(width, height); + + clap_window clapWindow{CLAP_WINDOW_API_WIN32, static_cast(m_hWnd.get())}; + + m_pluginGui->set_parent(m_plugin, &clapWindow); +} + +bool HostWindow::setWindowSize(uint32_t width, uint32_t height) +{ + ::RECT rect{0, 0, static_cast(width), static_cast(height)}; + + ::AdjustWindowRectExForDpi(&rect, static_cast<::DWORD>(::GetWindowLongPtrW(m_hWnd.get(), GWL_STYLE)), + ::GetMenu(m_hWnd.get()) != nullptr, + static_cast<::DWORD>(::GetWindowLongPtrW(m_hWnd.get(), GWL_EXSTYLE)), + helpers::getCurrentDpi(m_hWnd.get())); + + ::SetWindowPos(m_hWnd.get(), nullptr, 0, 0, (rect.right - rect.left), (rect.bottom - rect.top), + SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE); + + return true; +} + +LRESULT CALLBACK HostWindow::wndProc(::HWND hWnd, ::UINT uMsg, ::WPARAM wParam, ::LPARAM lParam) +{ + auto self{helpers::instanceFromWndProc(hWnd, uMsg, lParam)}; + + if (self) + { + switch (uMsg) + { + case WM_DPICHANGED: + return self->onDpiChanged(); + case WM_WINDOWPOSCHANGED: + return self->onWindowPosChanged(lParam); + case WM_SYSCOMMAND: + return self->onSysCommand(hWnd, uMsg, wParam, lParam); + case WM_DESTROY: + return self->onDestroy(); + } + } + + return ::DefWindowProcW(hWnd, uMsg, wParam, lParam); +} + +int HostWindow::onDpiChanged() +{ + m_pluginGui->set_scale(m_plugin, helpers::getScale(m_hWnd.get())); + + return 0; +} + +int HostWindow::onWindowPosChanged(::LPARAM lParam) +{ + auto windowPos{reinterpret_cast<::LPWINDOWPOS>(lParam)}; + + if (windowPos->flags & SWP_SHOWWINDOW) + { + m_pluginGui->show(m_plugin); + } + + if (windowPos->flags & SWP_HIDEWINDOW) + { + m_pluginGui->hide(m_plugin); + } + + if (m_pluginGui->can_resize(m_plugin)) + { + auto size{helpers::getClientSize(m_hWnd.get())}; + + m_pluginGui->adjust_size(m_plugin, &size.width, &size.height); + m_pluginGui->set_size(m_plugin, size.width, size.height); + } + + return 0; +} + +int HostWindow::onSysCommand(::HWND hWnd, ::UINT uMsg, ::WPARAM wParam, ::LPARAM lParam) +{ + switch (wParam) + { + case IDM_SETTINGS: + { + // Disabled until settings window is finished + // helpers::checkWindowVisibility(m_settingsWindow.m_hWnd.get()) + // ? helpers::hideWindow(m_settingsWindow.m_hWnd.get()) + // : helpers::showWindow(m_settingsWindow.m_hWnd.get()); + + return 0; + } + + case IDM_SAVE_STATE: + { + auto coUninitialize{wil::CoInitializeEx(COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)}; + + auto fileSaveDialog{wil::CoCreateInstance(CLSID_FileSaveDialog)}; + + fileSaveDialog->SetDefaultExtension(m_fileTypes.at(0).pszName); + fileSaveDialog->SetFileTypes(static_cast(m_fileTypes.size()), m_fileTypes.data()); + + fileSaveDialog->Show(m_hWnd.get()); + + wil::com_ptr shellItem; + auto hr{fileSaveDialog->GetResult(&shellItem)}; + + if (SUCCEEDED(hr)) + { + wil::unique_cotaskmem_string result; + shellItem->GetDisplayName(SIGDN_FILESYSPATH, &result); + + auto saveFile{std::filesystem::path(result.get())}; + + helpers::log("{}", saveFile.string()); + + try + { + freeaudio::clap_wrapper::standalone::getStandaloneHost()->saveStandaloneAndPluginSettings( + saveFile.parent_path(), saveFile.filename()); + } + catch (const fs::filesystem_error& e) + { + helpers::errorBox("Unable to save state: {}", e.what()); + } + } + + return 0; + } + + case IDM_LOAD_STATE: + { + auto coUninitialize{wil::CoInitializeEx(COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)}; + + auto fileOpenDialog{wil::CoCreateInstance(CLSID_FileOpenDialog)}; + + fileOpenDialog->SetDefaultExtension(m_fileTypes.at(0).pszName); + fileOpenDialog->SetFileTypes(static_cast(m_fileTypes.size()), m_fileTypes.data()); + + fileOpenDialog->Show(m_hWnd.get()); + + wil::com_ptr shellItem; + auto hr{fileOpenDialog->GetResult(&shellItem)}; + + if (SUCCEEDED(hr)) + { + wil::unique_cotaskmem_string result; + shellItem->GetDisplayName(SIGDN_FILESYSPATH, &result); + + auto saveFile{std::filesystem::path(result.get())}; + + helpers::log("{}", saveFile.string()); + + try + { + if (fs::exists(saveFile)) + { + freeaudio::clap_wrapper::standalone::getStandaloneHost()->tryLoadStandaloneAndPluginSettings( + saveFile.parent_path(), saveFile.filename()); + } + } + catch (const fs::filesystem_error& e) + { + helpers::errorBox("Unable to load state: {}", e.what()); + } + } + + return 0; + } + + case IDM_RESET_STATE: + { + auto pt{freeaudio::clap_wrapper::standalone::getStandaloneSettingsPath()}; + + if (pt.has_value()) + { + auto loadPath{*pt / m_plugin->desc->id}; + + try + { + if (fs::exists(loadPath / "defaults.clapwrapper")) + { + freeaudio::clap_wrapper::standalone::getStandaloneHost()->tryLoadStandaloneAndPluginSettings( + loadPath, "defaults.clapwrapper"); + } + } + catch (const fs::filesystem_error& /* e */) + { + // + } + } + + return 0; + } + } + + ::DefWindowProcW(hWnd, uMsg, wParam, lParam); + + return 0; +} + +int HostWindow::onDestroy() +{ + m_pluginGui->destroy(m_plugin); + + freeaudio::clap_wrapper::standalone::mainFinish(); + + helpers::quit(); + + return 0; +} +} // namespace freeaudio::clap_wrapper::standalone::windows diff --git a/src/detail/standalone/windows/host_window.h b/src/detail/standalone/windows/host_window.h new file mode 100644 index 00000000..857744d8 --- /dev/null +++ b/src/detail/standalone/windows/host_window.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include + +#include "detail/standalone/standalone_host.h" +// #include "settings_window.h" + +namespace freeaudio::clap_wrapper::standalone::windows +{ +struct HostWindow +{ + HostWindow(std::shared_ptr plugin); + + void setupMenu(); + void setupStandaloneHost(); + void setupPlugin(); + + bool setWindowSize(uint32_t width, uint32_t height); + + static LRESULT CALLBACK wndProc(::HWND hWnd, ::UINT uMsg, ::WPARAM wParam, ::LPARAM lParam); + int onDpiChanged(); + int onWindowPosChanged(::LPARAM lParam); + int onSysCommand(::HWND hWnd, ::UINT uMsg, ::WPARAM wParam, ::LPARAM lParam); + int onDestroy(); + + std::shared_ptr m_clapPlugin; + const clap_plugin_t* m_plugin; + const clap_plugin_gui_t* m_pluginGui; + const clap_plugin_state_t* m_pluginState; + freeaudio::clap_wrapper::standalone::StandaloneHost* m_standaloneHost; + std::vector m_fileTypes{{L"clapwrapper", L"*.clapwrapper"}}; + + // SettingsWindow m_settingsWindow; + wil::unique_hwnd m_hWnd; +}; +} // namespace freeaudio::clap_wrapper::standalone::windows diff --git a/src/detail/standalone/windows/settings_window.cpp b/src/detail/standalone/windows/settings_window.cpp new file mode 100644 index 00000000..5961de9d --- /dev/null +++ b/src/detail/standalone/windows/settings_window.cpp @@ -0,0 +1,63 @@ +#include + +#include "detail/standalone/standalone_details.h" +#include "settings_window.h" +#include "helpers.h" + +#define ID_COMBOBOX1 101 +#define ID_COMBOBOX2 102 +#define ID_COMBOBOX3 103 +#define ID_BUTTON1 104 +#define ID_BUTTON2 105 + +namespace freeaudio::clap_wrapper::standalone::windows +{ +SettingsWindow::SettingsWindow() +{ + freeaudio::clap_wrapper::standalone::windows::helpers::createWindow("Audio/MIDI Settings", this); + + helpers::centerWindow(m_hWnd.get(), 400, 360); +} + +LRESULT CALLBACK SettingsWindow::wndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + auto self{helpers::instanceFromWndProc(hWnd, uMsg, lParam)}; + + if (self) + { + switch (uMsg) + { + case WM_CREATE: + return self->onCreate(); + case WM_CLOSE: + return self->onClose(); + } + } + + return ::DefWindowProcW(hWnd, uMsg, wParam, lParam); +} + +int SettingsWindow::onCreate() +{ + CreateWindow("COMBOBOX", NULL, WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST, 10, 10, 150, 100, + m_hWnd.get(), (HMENU)ID_COMBOBOX1, NULL, NULL); + CreateWindow("COMBOBOX", NULL, WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST, 10, 50, 150, 100, + m_hWnd.get(), (HMENU)ID_COMBOBOX2, NULL, NULL); + CreateWindow("COMBOBOX", NULL, WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST, 10, 90, 150, 100, + m_hWnd.get(), (HMENU)ID_COMBOBOX3, NULL, NULL); + + CreateWindow("BUTTON", "Button 1", WS_CHILD | WS_VISIBLE, 10, 130, 80, 30, m_hWnd.get(), + (HMENU)ID_BUTTON1, NULL, NULL); + CreateWindow("BUTTON", "Button 2", WS_CHILD | WS_VISIBLE, 100, 130, 80, 30, m_hWnd.get(), + (HMENU)ID_BUTTON2, NULL, NULL); + + return 0; +} + +int SettingsWindow::onClose() +{ + helpers::hideWindow(m_hWnd.get()); + + return 0; +} +} // namespace freeaudio::clap_wrapper::standalone::windows diff --git a/src/detail/standalone/windows/settings_window.h b/src/detail/standalone/windows/settings_window.h new file mode 100644 index 00000000..89508059 --- /dev/null +++ b/src/detail/standalone/windows/settings_window.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +namespace freeaudio::clap_wrapper::standalone::windows +{ +struct SettingsWindow +{ + SettingsWindow(); + + static LRESULT CALLBACK wndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + int onCreate(); + int onClose(); + + wil::unique_hwnd m_hWnd; +}; +} // namespace freeaudio::clap_wrapper::standalone::windows diff --git a/src/detail/standalone/windows/win32.manifest b/src/detail/standalone/windows/standalone.manifest similarity index 57% rename from src/detail/standalone/windows/win32.manifest rename to src/detail/standalone/windows/standalone.manifest index 376ec498..3e668fc6 100644 --- a/src/detail/standalone/windows/win32.manifest +++ b/src/detail/standalone/windows/standalone.manifest @@ -9,24 +9,12 @@ - - - - - - + + true/pm + PerMonitorV2 - true - - - UTF-8 - - - SegmentHeap diff --git a/src/detail/standalone/windows/winutils.cpp b/src/detail/standalone/windows/winutils.cpp deleted file mode 100644 index 47b1d5b4..00000000 --- a/src/detail/standalone/windows/winutils.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#if CLAP_WRAPPER_HAS_WIN32 - -#ifdef UNICODE -#undef UNICODE -#endif - -#include - -#include "../entry.h" -#include "../standalone_details.h" -#include "../../clap/fsutil.h" - -#include "winutils.h" -#include "helper.h" - -int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pCmdLine, int nCmdShow) -{ -#ifdef _DEBUG - freeaudio::clap_wrapper::standalone::windows::Console console; -#endif - - const clap_plugin_entry* entry{nullptr}; - -#ifdef STATICALLY_LINKED_CLAP_ENTRY - extern const clap_plugin_entry clap_entry; - entry = &clap_entry; -#else - std::string clapName{HOSTED_CLAP_NAME}; - - auto searchPaths{Clap::getValidCLAPSearchPaths()}; - - auto lib{Clap::Library()}; - - for (const auto& searchPath : searchPaths) - { - auto clapPath = searchPath / (clapName + ".clap"); - - if (fs::exists(clapPath) && !entry) - { - lib.load(clapPath); - entry = lib._pluginEntry; - } - } -#endif - - if (!entry) return 0; - - std::string pid{PLUGIN_ID}; - int pindex{PLUGIN_INDEX}; - - auto plugin{freeaudio::clap_wrapper::standalone::mainCreatePlugin(entry, pid, pindex, 1, __argv)}; - - freeaudio::clap_wrapper::standalone::mainStartAudio(); - - freeaudio::clap_wrapper::standalone::windows::Win32Gui win32Gui{}; - - auto sah{freeaudio::clap_wrapper::standalone::getStandaloneHost()}; - - sah->win32Gui = &win32Gui; - - win32Gui.plugin = plugin; - - if (plugin->_ext._gui) - { - auto ui{plugin->_ext._gui}; - auto p{plugin->_plugin}; - - ui->create(p, CLAP_WINDOW_API_WIN32, false); - - freeaudio::clap_wrapper::standalone::windows::Window window; - - auto dpi{::GetDpiForWindow(window.m_hwnd)}; - auto scaleFactor{static_cast(dpi) / static_cast(USER_DEFAULT_SCREEN_DPI)}; - ui->set_scale(p, scaleFactor); - - if (ui->can_resize(p)) - { - // We can check here if we had a previous size but we aren't saving state yet - } - - uint32_t w{0}; - uint32_t h{0}; - ui->get_size(p, &w, &h); - - RECT r{0, 0, 0, 0}; - r.right = w; - r.bottom = h; - ::AdjustWindowRectExForDpi(&r, WS_OVERLAPPEDWINDOW, 0, 0, dpi); - ::SetWindowPos(window.m_hwnd, nullptr, 0, 0, (r.right - r.left), (r.bottom - r.top), SWP_NOMOVE); - - if (!ui->can_resize(p)) - { - ::SetWindowLongPtr(window.m_hwnd, GWL_STYLE, - ::GetWindowLongPtr(window.m_hwnd, GWL_STYLE) & ~WS_OVERLAPPEDWINDOW | - WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX); - } - - clap_window win; - win.api = CLAP_WINDOW_API_WIN32; - win.win32 = (void*)window.m_hwnd; - ui->set_parent(p, &win); - - ui->show(p); - - ::ShowWindow(window.m_hwnd, SW_SHOWDEFAULT); - } - - MSG msg{}; - int r{0}; - - while ((r = ::GetMessage(&msg, nullptr, 0, 0)) != 0) - { - if (r == -1) - return 0; - - else - { - ::TranslateMessage(&msg); - ::DispatchMessage(&msg); - } - } - - win32Gui.plugin = nullptr; - - plugin = nullptr; - - freeaudio::clap_wrapper::standalone::mainFinish(); - - return 0; -} - -#endif diff --git a/src/detail/standalone/windows/winutils.h b/src/detail/standalone/windows/winutils.h deleted file mode 100644 index 7aa811c8..00000000 --- a/src/detail/standalone/windows/winutils.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include -#include "detail/standalone/standalone_host.h" - -namespace freeaudio::clap_wrapper::standalone::windows -{ -struct Win32Gui -{ - std::shared_ptr plugin; -}; -} // namespace freeaudio::clap_wrapper::standalone::windows diff --git a/src/wrapasstandalone_win32.cpp b/src/wrapasstandalone_win32.cpp new file mode 100644 index 00000000..f938c6d3 --- /dev/null +++ b/src/wrapasstandalone_win32.cpp @@ -0,0 +1,52 @@ +#include "detail/standalone/standalone_details.h" +#include "detail/standalone/entry.h" +#include "detail/standalone/windows/host_window.h" +#include "detail/standalone/windows/helpers.h" + +int main(int argc, char** argv) +{ + const clap_plugin_entry* entry{nullptr}; +#ifdef STATICALLY_LINKED_CLAP_ENTRY + extern const clap_plugin_entry clap_entry; + entry = &clap_entry; +#else + std::string clapName{HOSTED_CLAP_NAME}; + freeaudio::clap_wrapper::standalone::windows::helpers::log("Loading {}", clapName); + + auto lib{Clap::Library()}; + + for (const auto& searchPaths : Clap::getValidCLAPSearchPaths()) + { + auto clapPath{searchPaths / (clapName + ".clap")}; + + if (fs::exists(clapPath) && !entry) + { + lib.load(clapPath); + entry = lib._pluginEntry; + } + } +#endif + + if (!entry) + { + freeaudio::clap_wrapper::standalone::windows::helpers::errorBox("No entry as configured"); + freeaudio::clap_wrapper::standalone::windows::helpers::abort(3); + } + + auto clapPlugin{ + freeaudio::clap_wrapper::standalone::mainCreatePlugin(entry, PLUGIN_ID, PLUGIN_INDEX, argc, argv)}; + freeaudio::clap_wrapper::standalone::mainStartAudio(); + + if (clapPlugin->_ext._gui->is_api_supported(clapPlugin->_plugin, CLAP_WINDOW_API_WIN32, false)) + { + freeaudio::clap_wrapper::standalone::windows::HostWindow hostWindow{clapPlugin}; + + return freeaudio::clap_wrapper::standalone::windows::helpers::messageLoop(); + } + else + { + freeaudio::clap_wrapper::standalone::windows::helpers::errorBox( + "CLAP_WINDOW_API_WIN32 is not supported"); + freeaudio::clap_wrapper::standalone::windows::helpers::abort(); + } +}