diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c69211..382eea3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,3 +75,11 @@ if(IZO_BUILD_EXAMPLE) add_executable(izo_example examples/dialogs.cpp) target_link_libraries(izo_example PRIVATE iZo::izo) endif() + +option(IZO_BUILD_TESTS "Build the iZo tests" OFF) +if(IZO_BUILD_TESTS) + enable_testing() + add_executable(izo_smoke_test tests/smoke.cpp) + target_link_libraries(izo_smoke_test PRIVATE iZo::izo) + add_test(NAME izo_smoke COMMAND izo_smoke_test) +endif() diff --git a/README.md b/README.md index 2d08d68..5ff28a8 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,30 @@ # iZo -iZo is a small C++17 static library for native file-system dialogs on Windows -and Linux. +iZo is a small C++17 library that exposes common operating-system services through +one consistent API on Windows and Linux. ## Features -- Open file, including multi-select and file filters -- Save file -- Pick folder -- Open a path with its default application -- Reveal a path in Explorer or the desktop file manager -- Explicit selected, cancelled, and error result states +- Native open, save, folder, and message dialogs +- UTF-8 text clipboard access +- Standard application, user, temporary, and executable paths +- Non-blocking process launch +- Move-only dynamic library handles with automatic cleanup +- Environment variables and basic system information +- Monotonic timing and sleeping +- Recursive or non-recursive directory watching +- Crash callbacks, debugger detection, debug breaks, and debug output +- Opening paths and revealing files in the platform file manager -Windows uses the native Explorer `IFileDialog` API. Linux discovers `zenity` or -`kdialog` at runtime; at least one must be installed for dialogs. Path opening -uses `xdg-open`. Revealing uses the FreeDesktop file-manager D-Bus interface -when available and otherwise opens the parent folder. +Linux dialogs discover `zenity` or `kdialog` at runtime. Linux clipboard access +discovers `wl-clipboard` or `xclip`. No GUI toolkit is linked into applications. ## Build ```sh -cmake -S . -B build -DIZO_BUILD_EXAMPLE=ON +cmake -S . -B build -DIZO_BUILD_EXAMPLE=ON -DIZO_BUILD_TESTS=ON cmake --build build +ctest --test-dir build ``` To consume an installed copy: @@ -33,17 +36,45 @@ target_link_libraries(your_target PRIVATE iZo::izo) ## Usage +Include individual feature headers or the complete API: + ```cpp -#include +#include izo::dialog_options options; options.title = "Choose an image"; options.filters = {{"Images", {"*.png", "*.jpg"}}}; -auto result = izo::open_file(options); -if (result) { - auto selected_path = result.paths.front(); -} else if (result.status == izo::dialog_status::error) { - // result.error_message contains the platform failure. +if (auto selection = izo::open_file(options)) { + izo::reveal_in_file_manager(selection.paths.front()); +} + +auto config = izo::get_known_path(izo::known_path::config); + +izo::process_options process; +process.executable = "tool"; +process.arguments = {"--input", "asset.png"}; +if (auto launched = izo::launch_process(process)) { + // launched.process_id identifies the new process. } ``` + +Resource-owning APIs are move-only and clean themselves up: + +```cpp +std::string error; +auto library = izo::load_dynamic_library("plugin.dll", &error); +if (library) { + void* entry = library.symbol("initialize_plugin", &error); +} + +auto watcher = izo::watch_directory( + {{"assets"}, true}, + [](const izo::file_event& event) { + // Handle added, removed, or modified files. + }, + &error); +``` + +Dialog calls block until dismissed. Directory callbacks run on the watcher's worker +thread and must not destroy their own watcher. diff --git a/tests/smoke.cpp b/tests/smoke.cpp new file mode 100644 index 0000000..96fc034 --- /dev/null +++ b/tests/smoke.cpp @@ -0,0 +1,52 @@ +#include + +#include +#include +#include + +int main() { + std::string error; + + assert(izo::set_environment_variable("IZO_SMOKE_TEST", "utf8-value", &error)); + const auto variable = izo::get_environment_variable("IZO_SMOKE_TEST"); + assert(variable && *variable == "utf8-value"); + assert(izo::unset_environment_variable("IZO_SMOKE_TEST", &error)); + + assert(!izo::get_known_path(izo::known_path::temporary, &error).empty()); + assert(!izo::get_known_path(izo::known_path::executable_directory, &error).empty()); + + const auto system = izo::get_system_info(); + assert(system.logical_cpu_count > 0); + assert(system.total_memory_bytes > 0); + assert(!system.os_name.empty()); + + const auto before = izo::monotonic_now(); + izo::sleep_for(std::chrono::milliseconds(2)); + assert(izo::monotonic_now() > before); + +#ifdef _WIN32 + auto library = izo::load_dynamic_library("kernel32.dll", &error); + assert(library && library.symbol("GetCurrentProcessId", &error)); +#else + auto library = izo::load_dynamic_library("libc.so.6", &error); + assert(library && library.symbol("getpid", &error)); +#endif + + const auto directory = std::filesystem::temp_directory_path() / "izo-smoke-watch"; + std::filesystem::remove_all(directory); + std::filesystem::create_directory(directory); + std::atomic observed{false}; + auto watcher = izo::watch_directory( + {directory, false, std::chrono::milliseconds(10)}, + [&](const izo::file_event& event) { + if (event.change == izo::file_change::added && event.path.filename() == "created.txt") + observed = true; + }, &error); + assert(watcher); + izo::sleep_for(std::chrono::milliseconds(30)); + { std::ofstream file(directory / "created.txt"); file << "test"; } + for (int i = 0; i < 100 && !observed; ++i) izo::sleep_for(std::chrono::milliseconds(10)); + assert(observed); + watcher.stop(); + std::filesystem::remove_all(directory); +}