From 6fe42963fb488525027b96f7f5710ecf73679eb0 Mon Sep 17 00:00:00 2001 From: GigabiteStudios Date: Thu, 18 Jun 2026 19:23:33 -0500 Subject: [PATCH] feat(interaction): add message boxes and text clipboard --- CMakeLists.txt | 3 +- src/interaction_windows.cpp | 120 ++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 src/interaction_windows.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index cdaab8e..e8fd40d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ target_sources(izo src/dialogs.cpp src/directory_watcher.cpp src/time.cpp + $<$:src/interaction_windows.cpp> $<$:src/dialogs_windows.cpp> $<$:src/dialogs_linux.cpp> ) @@ -27,7 +28,7 @@ set_target_properties(izo PROPERTIES if(WIN32) target_compile_definitions(izo PRIVATE UNICODE _UNICODE WIN32_LEAN_AND_MEAN NOMINMAX) - target_link_libraries(izo PRIVATE ole32 shell32 uuid) + target_link_libraries(izo PRIVATE ole32 shell32 uuid user32) elseif(UNIX AND NOT APPLE) # No link-time desktop dependency. The Linux backend discovers zenity or # kdialog at runtime so applications do not inherit GTK or Qt dependencies. diff --git a/src/interaction_windows.cpp b/src/interaction_windows.cpp new file mode 100644 index 0000000..830959e --- /dev/null +++ b/src/interaction_windows.cpp @@ -0,0 +1,120 @@ +#include +#include + +#include + +#include + +namespace izo { +namespace { + +std::wstring widen(std::string_view value) { + if (value.empty()) return {}; + const int count = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, value.data(), + static_cast(value.size()), nullptr, 0); + if (count <= 0) return {}; + std::wstring result(static_cast(count), L'\0'); + MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, value.data(), + static_cast(value.size()), result.data(), count); + return result; +} + +std::string narrow(const wchar_t* value) { + if (!value || !*value) return {}; + const int length = static_cast(wcslen(value)); + const int count = WideCharToMultiByte(CP_UTF8, 0, value, length, nullptr, 0, nullptr, nullptr); + std::string result(static_cast(count), '\0'); + WideCharToMultiByte(CP_UTF8, 0, value, length, result.data(), count, nullptr, nullptr); + return result; +} + +std::string windows_error(const char* operation) { + return std::string(operation) + " failed with error " + std::to_string(GetLastError()); +} + +} // namespace + +message_response show_message_box(const message_box_options& options, std::string* error_message) { + UINT flags = MB_TASKMODAL; + switch (options.icon) { + case message_icon::info: flags |= MB_ICONINFORMATION; break; + case message_icon::warning: flags |= MB_ICONWARNING; break; + case message_icon::error: flags |= MB_ICONERROR; break; + case message_icon::question: flags |= MB_ICONQUESTION; break; + } + switch (options.buttons) { + case message_buttons::ok: flags |= MB_OK; break; + case message_buttons::ok_cancel: flags |= MB_OKCANCEL; break; + case message_buttons::yes_no: flags |= MB_YESNO; break; + case message_buttons::yes_no_cancel: flags |= MB_YESNOCANCEL; break; + } + const auto title = widen(options.title); + const auto message = widen(options.message); + switch (MessageBoxW(nullptr, message.c_str(), title.c_str(), flags)) { + case IDOK: return message_response::ok; + case IDCANCEL: return message_response::cancel; + case IDYES: return message_response::yes; + case IDNO: return message_response::no; + default: + if (error_message) *error_message = windows_error("MessageBoxW"); + return message_response::error; + } +} + +bool set_clipboard_text(std::string_view text, std::string* error_message) { + const auto value = widen(text); + if (!OpenClipboard(nullptr)) { + if (error_message) *error_message = windows_error("OpenClipboard"); + return false; + } + EmptyClipboard(); + const SIZE_T bytes = (value.size() + 1) * sizeof(wchar_t); + HGLOBAL memory = GlobalAlloc(GMEM_MOVEABLE, bytes); + if (!memory) { + if (error_message) *error_message = windows_error("GlobalAlloc"); + CloseClipboard(); + return false; + } + void* destination = GlobalLock(memory); + if (!destination) { + if (error_message) *error_message = windows_error("GlobalLock"); + GlobalFree(memory); + CloseClipboard(); + return false; + } + memcpy(destination, value.c_str(), bytes); + GlobalUnlock(memory); + if (!SetClipboardData(CF_UNICODETEXT, memory)) { + if (error_message) *error_message = windows_error("SetClipboardData"); + GlobalFree(memory); + CloseClipboard(); + return false; + } + CloseClipboard(); + return true; +} + +std::optional get_clipboard_text(std::string* error_message) { + if (!OpenClipboard(nullptr)) { + if (error_message) *error_message = windows_error("OpenClipboard"); + return std::nullopt; + } + HANDLE memory = GetClipboardData(CF_UNICODETEXT); + if (!memory) { + if (error_message) *error_message = "Clipboard does not contain text"; + CloseClipboard(); + return std::nullopt; + } + const auto* value = static_cast(GlobalLock(memory)); + if (!value) { + if (error_message) *error_message = windows_error("GlobalLock"); + CloseClipboard(); + return std::nullopt; + } + auto result = narrow(value); + GlobalUnlock(memory); + CloseClipboard(); + return result; +} + +} // namespace izo