/** * @file wfassoc++.h * @brief Windows File Association C++ API header * * This header provides C++ API for managing Windows file associations, * based on its C-compatible API. * The API is designed to work with at least C++17. */ #pragma once #ifndef WFASSOCPP_H_ #define WFASSOCPP_H_ #include "wfassoc.h" #include #include #include #include /** * @brief Windows File Association C++ API namespace * * Provides C++ RAII wrappers around the C-compatible wfassoc API. */ namespace wfassocpp { /** @brief Type representing a null-terminated UTF-8 C-style string */ using CString = wfassoc::WFCString; /** * @brief Type representing a handle/token for managed objects * * This library uses an object pool to manage any objects created during calling. * This type is exposed as an opaque handle for visiting your created object. */ using Token = wfassoc::WFToken; /** * @brief Type representing an icon handle (opaque pointer) * * This type is equivalent to the Win32 HICON type. */ using HICON = wfassoc::WFHICON; /** @brief Invalid icon handle value */ using INVALID_HICON = wfassoc::WF_INVALID_HICON; /** @brief Invalid index value used for error conditions */ using INVALID_INDEX = wfassoc::WF_INVALID_INDEX; /** * @brief Registration scope for file associations * * Determines whether a program is registered for the current user or system-wide. */ using Scope = wfassoc::WFScope; /** * @brief View mode for querying file association status * * Determines how the association status is viewed/queried. */ using View = wfassoc::WFView; /** * @private * @brief Check the result of a C API call and throw on failure * * @param[in] result Boolean result from a C API call * @throws std::runtime_error if result is false, with the error message from WFGetLastError() */ inline void _Check(bool result) { if (!result) { throw std::runtime_error(wfassoc::WFGetLastError()); } } /** * @private * @brief Get the invalid token value * * In theory, invalid token value should be a constant value. * However, due to the library used in Rust side, this value can only be fetched at runtime. * This function caches the value on first call for subsequent use. * * @return An invalid token value */ inline Token _INVALID_TOKEN() { static Token v = wfassoc::WFInvalidToken(); return v; } /** * @brief Schema object for defining a program's file association configuration * * A Schema is a sketchpad of a complete program. * Create a Schema object first, then convert it to a Program object for following operations. * * This class is move-only (non-copyable). */ class Schema { public: /** * @brief Construct a new Schema object * @throws std::runtime_error if schema creation fails */ Schema() { _Check(wfassoc::WFSchemaCreate(&_token)); } /** * @brief Destroy the Schema object * * Releases resources associated with the Schema object. */ ~Schema() { if (_token != _INVALID_TOKEN()) { wfassoc::WFSchemaDestroy(_token); } } /** @brief Non-copyable */ Schema(const Schema&) = delete; /** @brief Non-copyable */ Schema& operator=(const Schema&) = delete; /** @brief Move constructor */ Schema(Schema&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); } /** @brief Move assignment operator */ Schema& operator=(Schema&& other) noexcept { if (this != &other) { if (_token != _INVALID_TOKEN()) { wfassoc::WFSchemaDestroy(_token); } _token = other._token; other._token = _INVALID_TOKEN(); } return *this; } /** * @brief Set the program identifier for this Schema * * @param[in] value Null-terminated UTF-8 string containing the identifier. * This identifier should not be empty, must start with alphabet character, * and follow with alphabet characters, digits, underline, or hyphens. * @throws std::runtime_error if the operation fails */ void SetIdentifier(const char* value) { _Check(wfassoc::WFSchemaSetIdentifier(_token, value)); } /** * @brief Set the program path for this Schema * * @param[in] value Null-terminated UTF-8 string containing the program path. * This path should be the fully qualified path to the application. * @throws std::runtime_error if the operation fails */ void SetPath(const char* value) { _Check(wfassoc::WFSchemaSetPath(_token, value)); } /** * @brief Set the program CLSID for this Schema * * @param[in] value Null-terminated UTF-8 string containing the CLSID. * This CLSID string should be in the format of @c {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}. * Please note that curly braces are required. * @throws std::runtime_error if the operation fails */ void SetClsid(const char* value) { _Check(wfassoc::WFSchemaSetClsid(_token, value)); } /** * @brief Set the program name for this Schema (optional) * * @param[in] value Null-terminated UTF-8 string containing the name, or nullptr to clear * @throws std::runtime_error if the operation fails */ void SetName(const char* value) { _Check(wfassoc::WFSchemaSetName(_token, value)); } /** * @brief Set the program icon for this Schema (optional) * * @param[in] value Null-terminated UTF-8 string containing the icon path, or nullptr to clear * @throws std::runtime_error if the operation fails */ void SetIcon(const char* value) { _Check(wfassoc::WFSchemaSetIcon(_token, value)); } /** * @brief Set the program behavior for this Schema (optional) * * @param[in] value Null-terminated UTF-8 string containing the behavior command, or nullptr to clear * @throws std::runtime_error if the operation fails */ void SetBehavior(const char* value) { _Check(wfassoc::WFSchemaSetBehavior(_token, value)); } /** * @brief Add a string resource entry to this Schema * * @param[in] name Null-terminated UTF-8 string containing the name of this entry * @param[in] value Null-terminated UTF-8 string containing the value of this entry. * It can be a plain string or a reference string to resource. * @throws std::runtime_error if the operation fails */ void AddStr(const char* name, const char* value) { _Check(wfassoc::WFSchemaAddStr(_token, name, value)); } /** * @brief Add an icon registry entry to this Schema * * @param[in] name Null-terminated UTF-8 string containing the name of this entry * @param[in] value Null-terminated UTF-8 string containing the value of this entry. * It can be a path to icon or a reference string to resource. * @throws std::runtime_error if the operation fails */ void AddIcon(const char* name, const char* value) { _Check(wfassoc::WFSchemaAddIcon(_token, name, value)); } /** * @brief Add a behavior registry entry to this Schema * * @param[in] name Null-terminated UTF-8 string containing the name of this entry * @param[in] value Null-terminated UTF-8 string containing the value of this entry. * It should be a valid command line string which uses @c %1, @c %2, etc. to represent parameters. * @throws std::runtime_error if the operation fails */ void AddBehavior(const char* name, const char* value) { _Check(wfassoc::WFSchemaAddBehavior(_token, name, value)); } /** * @brief Add a file extension to this Schema * * @param[in] ext Null-terminated UTF-8 string containing the file extension name (without leading dot) * @param[in] ext_name Null-terminated UTF-8 string containing the name pointing to associated name for this extension. * This name should be registered by calling AddStr(). * @param[in] ext_icon Null-terminated UTF-8 string containing the name pointing to associated icon for this extension. * This name should be registered by calling AddIcon(). * @param[in] ext_behavior Null-terminated UTF-8 string containing the name pointing to associated behavior for this extension. * This name should be registered by calling AddBehavior(). * @throws std::runtime_error if the operation fails */ void AddExt(const char* ext, const char* ext_name, const char* ext_icon, const char* ext_behavior) { _Check(wfassoc::WFSchemaAddExt(_token, ext, ext_name, ext_icon, ext_behavior)); } private: friend class Program; /** * @private * @brief Release ownership of the token * * @return The released token */ Token Release() noexcept { Token t = _token; _token = _INVALID_TOKEN(); return t; } Token _token; }; /** * @brief Icon resource object for managing icon handles * * This class wraps an icon resource token and provides access to the icon handle. * The icon handle will be freed when this object is destroyed. * * This class is move-only (non-copyable). */ class IconRc { public: /** * @brief Construct an IconRc from an existing token * * @param[in] token The icon resource token to wrap */ explicit IconRc(Token token) : _token(token) {} /** * @brief Destroy the IconRc object * * Releases resources associated with the icon resource. */ ~IconRc() { if (_token != _INVALID_TOKEN()) { wfassoc::WFIconRcDestroy(_token); } } /** @brief Non-copyable */ IconRc(const IconRc&) = delete; /** @brief Non-copyable */ IconRc& operator=(const IconRc&) = delete; /** @brief Move constructor */ IconRc(IconRc&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); } /** @brief Move assignment operator */ IconRc& operator=(IconRc&& other) noexcept { if (this != &other) { if (_token != _INVALID_TOKEN()) { wfassoc::WFIconRcDestroy(_token); } _token = other._token; other._token = _INVALID_TOKEN(); } return *this; } /** * @brief Get the icon handle from this icon resource * * @return The icon handle. This handle will be freed when this object is destroyed. * Please make a copy immediately if you need to use it longer. * @throws std::runtime_error if the operation fails */ HICON GetIcon() { HICON icon = nullptr; _Check(wfassoc::WFIconRcGetIcon(_token, &icon)); return icon; } private: Token _token; }; /** * @brief Extension status object for querying file extension registration status * * This class wraps an extension status token and provides access to extension details. * The extension status object will be freed when this object is destroyed. * * This class is move-only (non-copyable). */ class ExtStatus { public: /** * @brief Construct an ExtStatus from an existing token * * @param[in] token The extension status token to wrap */ explicit ExtStatus(Token token) : _token(token) {} /** * @brief Destroy the ExtStatus object * * Releases resources associated with the extension status. */ ~ExtStatus() { if (_token != _INVALID_TOKEN()) { wfassoc::WFExtStatusDestroy(_token); } } /** @brief Non-copyable */ ExtStatus(const ExtStatus&) = delete; /** @brief Non-copyable */ ExtStatus& operator=(const ExtStatus&) = delete; /** @brief Move constructor */ ExtStatus(ExtStatus&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); } /** @brief Move assignment operator */ ExtStatus& operator=(ExtStatus&& other) noexcept { if (this != &other) { if (_token != _INVALID_TOKEN()) { wfassoc::WFExtStatusDestroy(_token); } _token = other._token; other._token = _INVALID_TOKEN(); } return *this; } /** * @brief Get the display name from this extension status * * The display name will be user specified first, then fallback to its ProgId verbatim. * The library will try to use localized name first, then use raw ProgId name if localized name is not available. * * @return The display name string. There is no possibility that this value is empty. * @throws std::runtime_error if the operation fails */ std::string GetName() { const char* name = nullptr; _Check(wfassoc::WFExtStatusGetName(_token, &name)); return std::string(name); } /** * @brief Get the icon from this extension status * * The icon will be user specified first, then fallback to the system default file icon. * * @return The icon handle. This handle will be freed when this object is destroyed. * Please make a copy immediately if you need to use it longer. * @throws std::runtime_error if the operation fails */ HICON GetIcon() { HICON icon = nullptr; _Check(wfassoc::WFExtStatusGetIcon(_token, &icon)); return icon; } private: Token _token; }; /** * @brief Self extension status object for accessing program-provided extension details * * This class wraps a self extension status token and provides access to extension details * as defined by the program itself. * * This class is move-only (non-copyable). */ class SelfExtStatus { public: /** * @brief Construct a SelfExtStatus from an existing token * * @param[in] token The self extension status token to wrap */ explicit SelfExtStatus(Token token) : _token(token) {} /** * @brief Destroy the SelfExtStatus object * * Releases resources associated with the self extension status. */ ~SelfExtStatus() { if (_token != _INVALID_TOKEN()) { wfassoc::WFSelfExtStatusDestroy(_token); } } /** @brief Non-copyable */ SelfExtStatus(const SelfExtStatus&) = delete; /** @brief Non-copyable */ SelfExtStatus& operator=(const SelfExtStatus&) = delete; /** @brief Move constructor */ SelfExtStatus(SelfExtStatus&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); } /** @brief Move assignment operator */ SelfExtStatus& operator=(SelfExtStatus&& other) noexcept { if (this != &other) { if (_token != _INVALID_TOKEN()) { wfassoc::WFSelfExtStatusDestroy(_token); } _token = other._token; other._token = _INVALID_TOKEN(); } return *this; } /** * @brief Get the display name from this self extension status * * The display name will be user specified first, then fallback to its ProgId verbatim. * * @return The display name string. There is no possibility that this value is empty. * @throws std::runtime_error if the operation fails */ std::string GetName() { const char* name = nullptr; _Check(wfassoc::WFSelfExtStatusGetName(_token, &name)); return std::string(name); } /** * @brief Get the icon from this self extension status * * The icon will be user specified first, then fallback to the system default file icon. * * @return The icon handle. This handle will be freed when this object is destroyed. * Please make a copy immediately if you need to use it longer. * @throws std::runtime_error if the operation fails */ HICON GetIcon() { HICON icon = nullptr; _Check(wfassoc::WFSelfExtStatusGetIcon(_token, &icon)); return icon; } /** * @brief Get the extension string (without leading dot) from this self extension status * * @return The file extension name (without leading dot). There is no possibility that this value is empty. * @throws std::runtime_error if the operation fails */ std::string GetExt() { const char* inner = nullptr; _Check(wfassoc::WFSelfExtStatusGetExt(_token, &inner)); return std::string(inner); } /** * @brief Get the dotted extension string (with leading dot) from this self extension status * * @return The file extension string (with leading dot). There is no possibility that this value is empty. * @throws std::runtime_error if the operation fails */ std::string GetDottedExt() { const char* inner = nullptr; _Check(wfassoc::WFSelfExtStatusGetDottedExt(_token, &inner)); return std::string(inner); } private: Token _token; }; /** * @brief Program object for managing file association registration * * A Program is created from a Schema and provides methods for registering, * unregistering, and querying file associations. * * This class is move-only (non-copyable). */ class Program { public: /** * @brief Construct a Program from a Schema * * This constructor consumes the Schema object. * The Schema object cannot be used after this call. * * @param[in] schema The Schema to create the Program from (will be consumed) * @throws std::runtime_error if program creation fails */ explicit Program(Schema&& schema) { _Check(wfassoc::WFProgramCreate(schema.Release(), &_token)); } /** * @brief Destroy the Program object * * Releases resources associated with the Program. */ ~Program() { if (_token != _INVALID_TOKEN()) { wfassoc::WFProgramDestroy(_token); } } /** @brief Non-copyable */ Program(const Program&) = delete; /** @brief Non-copyable */ Program& operator=(const Program&) = delete; /** @brief Move constructor */ Program(Program&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); } /** @brief Move assignment operator */ Program& operator=(Program&& other) noexcept { if (this != &other) { if (_token != _INVALID_TOKEN()) { wfassoc::WFProgramDestroy(_token); } _token = other._token; other._token = _INVALID_TOKEN(); } return *this; } /** * @brief Resolve the provided program name of this Program * * The name will be user specified first, * then fallback to program manifest file specified name, * and finally fallback to the file name of executable. * * @return The resolved program name. There is no possibility that this value is empty. * @throws std::runtime_error if the operation fails */ std::string ResolveName() { const char* name = nullptr; _Check(wfassoc::WFProgramResolveName(_token, &name)); return std::string(name); } /** * @brief Resolve the Program icon resource * * The icon will be user specified first, * then fallback to the first icon of program, * and finally fallback to the system default executable icon. * * @return An IconRc object containing the resolved icon * @throws std::runtime_error if the operation fails */ IconRc ResolveIcon() { Token token = _INVALID_TOKEN(); _Check(wfassoc::WFProgramResolveIcon(_token, &token)); return IconRc(token); } /** * @brief Get the number of file extensions in the Program * * @return The number of extensions * @throws std::runtime_error if the operation fails */ size_t ExtsLen() { size_t len = 0; _Check(wfassoc::WFProgramExtsLen(_token, &len)); return len; } /** * @brief Find a file extension by its body (extension string) * * @param[in] body Null-terminated UTF-8 string containing the file extension name (without leading dot) to find * @return The index of the extension, or INVALID_INDEX if not found * @throws std::runtime_error if the operation fails */ size_t FindExt(const char* body) { size_t index = INVALID_INDEX; _Check(wfassoc::WFProgramFindExt(_token, body, &index)); return index; } /** * @brief Resolve this program provided extension's details by index * * @param[in] index Index of the extension to resolve * @return A SelfExtStatus object containing the extension details * @throws std::runtime_error if the operation fails */ SelfExtStatus ResolveExt(size_t index) { Token token = _INVALID_TOKEN(); _Check(wfassoc::WFProgramResolveExt(_token, index, &token)); return SelfExtStatus(token); } /** * @brief Register the Program in the specified scope * * @param[in] scope Registration scope * @throws std::runtime_error if the operation fails */ void Register(Scope scope) { _Check(wfassoc::WFProgramRegister(_token, scope)); } /** * @brief Unregister the Program from the specified scope * * @param[in] scope Registration scope * @throws std::runtime_error if the operation fails */ void Unregister(Scope scope) { _Check(wfassoc::WFProgramUnregister(_token, scope)); } /** * @brief Check if the Program is registered in the specified scope * * @param[in] scope Registration scope for checking * @return true if the Program is registered in the specified scope, false otherwise * @throws std::runtime_error if the operation fails */ bool IsRegistered(Scope scope) { bool result = false; _Check(wfassoc::WFProgramIsRegistered(_token, scope, &result)); return result; } /** * @brief Link a file extension in the specified scope * * @param[in] scope Registration scope * @param[in] index Index of the extension to link * @throws std::runtime_error if the operation fails */ void LinkExt(Scope scope, size_t index) { _Check(wfassoc::WFProgramLinkExt(_token, scope, index)); } /** * @brief Unlink a file extension in the specified scope * * @param[in] scope Registration scope * @param[in] index Index of the extension to unlink * @throws std::runtime_error if the operation fails */ void UnlinkExt(Scope scope, size_t index) { _Check(wfassoc::WFProgramUnlinkExt(_token, scope, index)); } /** * @brief Query the status of a file extension * * @param[in] view View viewpoint * @param[in] index Index of the extension to query * @return An optional ExtStatus object. Returns std::nullopt if the extension is not found * (usually means that this extension is not registered in the specified scope). * @throws std::runtime_error if the operation fails */ std::optional QueryExt(View view, size_t index) { Token token = _INVALID_TOKEN(); _Check(wfassoc::WFProgramQueryExt(_token, view, index, &token)); if (token == _INVALID_TOKEN()) { return std::nullopt; } return ExtStatus(token); } private: Token _token; }; } // namespace wfassocpp #endif // WFASSOCPP_H_