/* Copyright (C) 2002 The gtkmm Development Team * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include #include #include #include #include // g_malloc #include // std::memset() namespace Glib { namespace // anonymous { // A pointer to an OptionArgCallback instance is stored in CppOptionEntry::cpparg_ // when a callback function shall parse the command option's value. class OptionArgCallback { public: explicit OptionArgCallback(const OptionGroup::SlotOptionArgString& slot) : slot_string_(new OptionGroup::SlotOptionArgString(slot)), slot_filename_(nullptr) { } explicit OptionArgCallback(const OptionGroup::SlotOptionArgFilename& slot) : slot_string_(nullptr), slot_filename_(new OptionGroup::SlotOptionArgFilename(slot)) { } bool is_filename_option() const { return slot_filename_ != nullptr; } const OptionGroup::SlotOptionArgString* get_slot_string() const { return slot_string_; } const OptionGroup::SlotOptionArgFilename* get_slot_filename() const { return slot_filename_; } ~OptionArgCallback() { delete slot_string_; delete slot_filename_; } private: // One of these slot pointers is 0 and the other one points to a slot. OptionGroup::SlotOptionArgString* slot_string_; OptionGroup::SlotOptionArgFilename* slot_filename_; // Not copyable OptionArgCallback(const OptionArgCallback&); OptionArgCallback& operator=(const OptionArgCallback&); }; using OptionGroupPostParseFuncType = gboolean (*)(GOptionContext* context, GOptionGroup* group, gpointer data, GError** error); using OptionGroupOptionArgFuncType = gboolean (*)(const gchar* option_name, const gchar* value, gpointer data, GError** error); OptionGroupPostParseFuncType OptionGroup_post_parse_callback_funcptr; OptionGroupOptionArgFuncType OptionGroup_option_arg_callback_funcptr; extern "C" { // From functions with C linkage, to protected static member functions with C++ linkage static gboolean OptionGroup_post_parse_callback(GOptionContext* context, GOptionGroup* group, gpointer data, GError** error) { return OptionGroup_post_parse_callback_funcptr(context, group, data, error); } static gboolean OptionGroup_option_arg_callback(const gchar* option_name, const gchar* value, gpointer data, GError** error) { return OptionGroup_option_arg_callback_funcptr(option_name, value, data, error); } static gboolean g_callback_pre_parse( GOptionContext* context, GOptionGroup* /* group */, gpointer data, GError** error) { OptionContext cppContext(context, false /* take_ownership */); auto option_group = static_cast(data); if (!option_group) { OptionError(OptionError::FAILED, "Glib::OptionGroup: g_callback_pre_parse(): " "No OptionGroup pointer available") .propagate(error); return false; } try { return option_group->on_pre_parse(cppContext); } catch (Glib::Error& err) { err.propagate(error); } catch (...) { Glib::exception_handlers_invoke(); } return false; } static void g_callback_error( GOptionContext* context, GOptionGroup* /* group */, gpointer data, GError** error) { // GError** error is input data containing information on an error that // has occurred before this function is called. OptionContext cppContext(context, false /* take_ownership */); auto option_group = static_cast(data); if (option_group && error && *error) { try { Error::throw_exception(g_error_copy(*error)); } catch (const Error& err) { return option_group->on_error(cppContext, err); } } } // Non-static functions with C linkage get external linkage, even if they are // defined in an anonymous namespace. //TODO: Declare 'static' when we can break ABI. const gchar* OptionGroup_Translate_glibmm_callback(const gchar* string, gpointer data) { Glib::OptionGroup::SlotTranslate* the_slot = static_cast(data); try { // The C docs says that the char* belongs to Glib. return g_strdup((*the_slot)(Glib::ustring(string)).c_str()); } catch (...) { Glib::exception_handlers_invoke(); } return nullptr; } static void OptionGroup_Translate_glibmm_callback_destroy(void* data) { delete static_cast(data); } } /* extern "C" */ } // anonymous namespace // static gboolean OptionGroup::post_parse_callback( GOptionContext* context, GOptionGroup* /* group */, gpointer data, GError** error) { OptionContext cppContext(context, false /* take_ownership */); OptionGroup* option_group = static_cast(data); if (!option_group) { OptionError(OptionError::FAILED, "Glib::OptionGroup::post_parse_callback(): " "No OptionGroup pointer available") .propagate(error); return false; } // The C args have now been given values by g_option_context_parse(). // Convert C values to C++ values: for (auto& the_pair : option_group->map_entries_) { auto& cpp_entry = the_pair.second; cpp_entry.convert_c_to_cpp(); } try { return option_group->on_post_parse(cppContext); } catch (Glib::Error& err) { err.propagate(error); } catch (...) { Glib::exception_handlers_invoke(); } return false; } // static gboolean OptionGroup::option_arg_callback( const gchar* option_name, const gchar* value, gpointer data, GError** error) { const Glib::ustring cpp_option_name(option_name); const OptionGroup* const option_group = static_cast(data); if (!option_group) { OptionError(OptionError::FAILED, "Glib::OptionGroup::option_arg_callback(): " "No OptionGroup pointer available for option " + cpp_option_name) .propagate(error); return false; } // option_name is either a single dash followed by a single letter (for a // short name) or two dashes followed by a long option name. OptionGroup::type_map_entries::const_iterator iterFind = option_group->map_entries_.end(); if (option_name[1] == '-') { // Long option name. const auto long_option_name = Glib::ustring(option_name + 2); iterFind = option_group->map_entries_.find(long_option_name); } else { // Short option name. const auto short_option_name = option_name[1]; for (iterFind = option_group->map_entries_.begin(); iterFind != option_group->map_entries_.end(); ++iterFind) { const auto& cppOptionEntry = iterFind->second; if (cppOptionEntry.entry_ && cppOptionEntry.entry_->get_short_name() == short_option_name) break; } } if (iterFind == option_group->map_entries_.end()) { OptionError(OptionError::UNKNOWN_OPTION, "Glib::OptionGroup::option_arg_callback(): " "Unknown option " + cpp_option_name) .propagate(error); return false; } const auto& cppOptionEntry = iterFind->second; if (cppOptionEntry.carg_type_ != G_OPTION_ARG_CALLBACK) { OptionError(OptionError::FAILED, "Glib::OptionGroup::option_arg_callback() " "called for non-callback option " + cpp_option_name) .propagate(error); return false; } const bool has_value = (value != nullptr); const OptionArgCallback* const option_arg = static_cast(cppOptionEntry.cpparg_); try { if (option_arg->is_filename_option()) { const auto the_slot = option_arg->get_slot_filename(); const std::string cpp_value(value ? value : ""); return (*the_slot)(cpp_option_name, cpp_value, has_value); } else { const auto the_slot = option_arg->get_slot_string(); const Glib::ustring cpp_value(value ? value : ""); return (*the_slot)(cpp_option_name, cpp_value, has_value); } } catch (Glib::Error& err) { err.propagate(error); } catch (...) { Glib::exception_handlers_invoke(); } return false; } OptionGroup::OptionGroup(const Glib::ustring& name, const Glib::ustring& description, const Glib::ustring& help_description) : gobject_(g_option_group_new(name.c_str(), description.c_str(), help_description.c_str(), this /* user_data */, nullptr /* destroy_func */)) { // g_callback_pre_parse(), post_parse_callback(), g_callback_error(), and // option_arg_callback() depend on user_data being this. The first three // functions get a GOptionGroup*, but it would not be correct to use it for // creating a new OptionGroup. They must call their virtual functions in the // original OptionGroup instance. // Connect callbacks, so that derived classes can override the virtual methods: OptionGroup_post_parse_callback_funcptr = &post_parse_callback; g_option_group_set_parse_hooks(gobj(), &g_callback_pre_parse, &OptionGroup_post_parse_callback); g_option_group_set_error_hook(gobj(), &g_callback_error); } OptionGroup::OptionGroup(GOptionGroup* castitem) : gobject_(castitem) { // Always takes ownership - never takes copy. } void OptionGroup::release_gobject() noexcept { // Free any C types that were allocated during add_entry(): for (auto& the_pair : map_entries_) { auto& cpp_entry = the_pair.second; cpp_entry.release_c_arg(); } if (gobject_) { g_option_group_unref(gobj()); gobject_ = nullptr; } } OptionGroup::~OptionGroup() { release_gobject(); } void OptionGroup::add_entry(const OptionEntry& entry) { // It does not copy the entry, so it needs to live as long as the group. // g_option_group_add_entries takes an array, with the last item in the array having a null // long_name. // Hopefully this will be properly documented eventually - see bug # // Create a temporary array, just so we can give the correct thing to g_option_group_add_entries: GOptionEntry array[2]; array[0] = *(entry.gobj()); // Copy contents. std::memset(&array[1], 0, sizeof(GOptionEntry)); g_option_group_add_entries(gobj(), array); } void OptionGroup::add_entry(const OptionEntry& entry, bool& arg) { add_entry_with_wrapper(entry, G_OPTION_ARG_NONE /* Actually a boolean on/off, depending on whether the argument name was given, without argument parameters. */, &arg); } void OptionGroup::add_entry(const OptionEntry& entry, int& arg) { add_entry_with_wrapper(entry, G_OPTION_ARG_INT, &arg); } void OptionGroup::add_entry(const OptionEntry& entry, double& arg) { add_entry_with_wrapper(entry, G_OPTION_ARG_DOUBLE, &arg); } void OptionGroup::add_entry(const OptionEntry& entry, Glib::ustring& arg) { add_entry_with_wrapper(entry, G_OPTION_ARG_STRING, &arg); } void OptionGroup::add_entry(const OptionEntry& entry, vecustrings& arg) { add_entry_with_wrapper(entry, G_OPTION_ARG_STRING_ARRAY, &arg); } void OptionGroup::add_entry_filename(const OptionEntry& entry, std::string& arg) { add_entry_with_wrapper(entry, G_OPTION_ARG_FILENAME, &arg); } void OptionGroup::add_entry_filename(const OptionEntry& entry, vecstrings& arg) { add_entry_with_wrapper(entry, G_OPTION_ARG_FILENAME_ARRAY, &arg); } // When the command argument value is to be parsed by a user-supplied function // (indicated by G_OPTION_ARG_CALLBACK), the OptionEntry::Flags::FILENAME in 'entry' is ignored. // set_c_arg_default() clears or sets it as required in a copy of 'entry'. // // The glib API is inconsistent here. The choice between UTF-8 and filename // encoding is done with G_OPTION_ARG_STRING, G_OPTION_ARG_FILENAME, // G_OPTION_ARG_STRING_ARRAY, and G_OPTION_ARG_FILENAME_ARRAY, which in glibmm // are set by OptionGroup::add_entry[_filename]. But when a callback function // is chosen, there is only G_OPTION_ARG_CALLBACK, and the encoding is chosen // with OptionEntry::Flags::FILENAME. We do this automatiically in set_c_arg_default(). // Other option flags are set by OptionEntry::set_flags(). void OptionGroup::add_entry(const OptionEntry& entry, const SlotOptionArgString& slot) { // The OptionArgCallback is deleted in release_c_arg(). add_entry_with_wrapper(entry, G_OPTION_ARG_CALLBACK, new OptionArgCallback(slot)); } void OptionGroup::add_entry_filename(const OptionEntry& entry, const SlotOptionArgFilename& slot) { // The OptionArgCallback is deleted in release_c_arg(). add_entry_with_wrapper(entry, G_OPTION_ARG_CALLBACK, new OptionArgCallback(slot)); } void OptionGroup::add_entry_with_wrapper(const OptionEntry& entry, GOptionArg arg_type, void* cpp_arg) { const auto name = entry.get_long_name(); type_map_entries::iterator iterFind = map_entries_.find(name); if (iterFind == map_entries_.end()) // If we have not added this entry already { CppOptionEntry cppEntry; // g_option_group_add_entry() does not take its own copy, so we must keep the instance alive. cppEntry.entry_ = new OptionEntry(entry); // cppEntry.entry_ is deleted in release_c_arg(), via the destructor. // Several options can refer to the same C++ variable, // typically a pair of --enable-x / --disable-x options. // Only one C variable shall be allocated for them. // See https://bugzilla.gnome.org/show_bug.cgi?id=744854. bool is_duplicate = false; void* carg = nullptr; if (arg_type != G_OPTION_ARG_CALLBACK) { for (type_map_entries::iterator iter = map_entries_.begin(); iter != map_entries_.end(); ++iter) { const auto& cpp_entry = iter->second; if (cpp_entry.cpparg_ == cpp_arg && cpp_entry.carg_type_ == arg_type && cpp_entry.carg_) { is_duplicate = true; carg = cpp_entry.carg_; break; } } } cppEntry.carg_type_ = arg_type; if (!is_duplicate) { cppEntry.allocate_c_arg(); cppEntry.set_c_arg_default(cpp_arg); carg = cppEntry.carg_; } cppEntry.cpparg_ = cpp_arg; // Give the information to the C API: cppEntry.entry_->gobj()->arg = arg_type; cppEntry.entry_->gobj()->arg_data = carg; // Remember the C++/C mapping so that we can use it later: map_entries_[name] = cppEntry; add_entry(*(cppEntry.entry_)); } else if (arg_type == G_OPTION_ARG_CALLBACK) { // Delete the OptionArgCallback instance that was allocated by add_entry() // or add_entry_filename(). auto option_arg = static_cast(cpp_arg); delete option_arg; } } bool OptionGroup::on_pre_parse(OptionContext& /* context */) { return true; } bool OptionGroup::on_post_parse(OptionContext& /* context */) { return true; } void OptionGroup::on_error(OptionContext& /* context */, const Error& /* error */) { } void OptionGroup::set_translate_func(const SlotTranslate& slot) { // Create a copy of the slot. A pointer to this will be passed through the // callback's data parameter. It will be deleted when // OptionGroup_Translate_glibmm_callback_destroy() is called. auto slot_copy = new SlotTranslate(slot); g_option_group_set_translate_func(gobj(), &OptionGroup_Translate_glibmm_callback, slot_copy, &OptionGroup_Translate_glibmm_callback_destroy); } OptionGroup::CppOptionEntry::CppOptionEntry() : carg_type_(G_OPTION_ARG_NONE), carg_(nullptr), cpparg_(nullptr), entry_(nullptr) { } void OptionGroup::CppOptionEntry::allocate_c_arg() { // Create an instance of the appropriate C type. // This will be destroyed in the OptionGroup destructor. // // We must also call set_c_arg_default() to give these C types the specified // defaults based on the C++-typed arguments. switch (carg_type_) { case G_OPTION_ARG_STRING: // The char* will be for UTF8 a string. case G_OPTION_ARG_FILENAME: // The char* will be for a string in the current locale's encoding. { char** typed_arg = new char*; // The C code will allocate a char* and put it here, for us to g_free() later. *typed_arg = nullptr; carg_ = typed_arg; break; } case G_OPTION_ARG_INT: { int* typed_arg = new int; *typed_arg = 0; carg_ = typed_arg; break; } case G_OPTION_ARG_DOUBLE: { double* typed_arg = new double; *typed_arg = 0.0; carg_ = typed_arg; break; } case G_OPTION_ARG_STRING_ARRAY: case G_OPTION_ARG_FILENAME_ARRAY: { char*** typed_arg = new char**; // The C code will allocate a char** and put it here, for us to g_strfreev() later. *typed_arg = nullptr; carg_ = typed_arg; break; } case G_OPTION_ARG_NONE: // Actually a boolean. { gboolean* typed_arg = new gboolean; *typed_arg = false; carg_ = typed_arg; break; } case G_OPTION_ARG_CALLBACK: { // The C arg pointer is a function pointer, cast to void*. // // carg_ = reinterpret_cast(&OptionGroup_option_arg_callback); // or // union { // void* dp; // GOptionArgFunc fp; // } u; // u.fp = &OptionGroup_option_arg_callback; // carg_ = u.dp; // ? See // https://bugzilla.gnome.org/show_bug.cgi?id=589197 // https://github.com/libsigcplusplus/libsigcplusplus/issues/1 // https://github.com/libsigcplusplus/libsigcplusplus/issues/8 carg_ = reinterpret_cast(&OptionGroup_option_arg_callback); OptionGroup_option_arg_callback_funcptr = &OptionGroup::option_arg_callback; break; } default: { break; } } } void OptionGroup::CppOptionEntry::set_c_arg_default(void* cpp_arg) { switch (carg_type_) { case G_OPTION_ARG_INT: { *static_cast(carg_) = *static_cast(cpp_arg); break; } case G_OPTION_ARG_DOUBLE: { *static_cast(carg_) = *static_cast(cpp_arg); break; } case G_OPTION_ARG_NONE: { *static_cast(carg_) = *static_cast(cpp_arg); break; } case G_OPTION_ARG_STRING: case G_OPTION_ARG_FILENAME: case G_OPTION_ARG_STRING_ARRAY: case G_OPTION_ARG_FILENAME_ARRAY: { // No need to set default values for string-valued options. // If *carg_ is still 0, when convert_c_to_cpp() is called, just don't // touch *cpparg_. Besides, setting default values in *carg_ can result // in memory leaks, because glib would not free the strings before // the char*'s are overwritten with pointers to newly allocated copies // of the command option arguments. break; } case G_OPTION_ARG_CALLBACK: { // No value to set here. The arg pointer is a function pointer. // Set or clear OptionEntry::Flags::FILENAME in *entry_. const OptionArgCallback* const option_arg = static_cast(cpp_arg); if (option_arg->is_filename_option()) { entry_->set_flags(entry_->get_flags() | OptionEntry::Flags::FILENAME); } else { entry_->set_flags(entry_->get_flags() & ~OptionEntry::Flags::FILENAME); } break; } default: { break; } } } void OptionGroup::CppOptionEntry::release_c_arg() { // Delete the instances that we created in allocate_c_arg(). // Notice that we delete the type that we created, but not the value to which it points. if (carg_) { switch (carg_type_) { case G_OPTION_ARG_STRING: case G_OPTION_ARG_FILENAME: { char** typed_arg = static_cast(carg_); g_free(*typed_arg); // Free the char* string at typed_arg, if allocated by the C code. delete typed_arg; // Delete the char** that we allocated in allocate_c_arg(). break; } case G_OPTION_ARG_INT: { int* typed_arg = static_cast(carg_); delete typed_arg; break; } case G_OPTION_ARG_DOUBLE: { double* typed_arg = static_cast(carg_); delete typed_arg; break; } case G_OPTION_ARG_STRING_ARRAY: case G_OPTION_ARG_FILENAME_ARRAY: { char*** typed_arg = static_cast(carg_); g_strfreev(*typed_arg); // Free the array of strings and the array at typed_arg, if allocated // by the C code. delete typed_arg; // Delete the char*** that we allocated in allocate_c_arg(). break; } case G_OPTION_ARG_NONE: // Actually a boolean. { gboolean* typed_arg = static_cast(carg_); delete typed_arg; break; } case G_OPTION_ARG_CALLBACK: { // Delete the OptionArgCallback instance that was allocated by add_entry() // or add_entry_filename(). auto option_arg = static_cast(cpparg_); delete option_arg; cpparg_ = nullptr; break; } default: { break; } } carg_ = nullptr; } if (entry_) delete entry_; } void OptionGroup::CppOptionEntry::convert_c_to_cpp() { if (!carg_) return; switch (carg_type_) { case G_OPTION_ARG_STRING: { char** typed_arg = static_cast(carg_); auto typed_cpp_arg = static_cast(cpparg_); if (typed_arg && *typed_arg && typed_cpp_arg) { *typed_cpp_arg = *typed_arg; } break; } case G_OPTION_ARG_FILENAME: { char** typed_arg = static_cast(carg_); auto typed_cpp_arg = static_cast(cpparg_); if (typed_arg && *typed_arg && typed_cpp_arg) { *typed_cpp_arg = *typed_arg; } break; } case G_OPTION_ARG_INT: { *((int*)cpparg_) = *(static_cast(carg_)); break; } case G_OPTION_ARG_DOUBLE: { *((double*)cpparg_) = *(static_cast(carg_)); break; } case G_OPTION_ARG_STRING_ARRAY: { char*** typed_arg = static_cast(carg_); auto typed_cpp_arg = static_cast(cpparg_); if (typed_arg && *typed_arg && typed_cpp_arg) { typed_cpp_arg->clear(); // The C array is null-terminated. // std::vector array_handle(*typed_arg, Glib::OWNERSHIP_NONE); // The SUN Forte compiler complains about this: // "optiongroup.cc", line 354: Error: Cannot assign Glib::ArrayHandle> to std::vector without // "std::vector::operator=(const std::vector&)";. // //(*typed_cpp_arg) = array_handle; // // And the Tru64 compiler does not even like us to instantiate the StringArrayHandle: // // cxx: Error: ../../glib/glibmm/containerhandle_shared.h, line 149: the operand // of a pointer dynamic_cast must be a pointer to a complete class type // return dynamic_cast(Glib::wrap_auto(cobj, false /* take_copy */)); // for(auto iter = array_handle.begin(); iter != // array_handle.end(); ++iter) //{ // typed_cpp_arg->emplace_back(*iter); //} // So we do this: char** char_array_next = *typed_arg; while (*char_array_next) { typed_cpp_arg->emplace_back(*char_array_next); ++char_array_next; } } break; } case G_OPTION_ARG_FILENAME_ARRAY: { char*** typed_arg = static_cast(carg_); auto typed_cpp_arg = static_cast(cpparg_); if (typed_arg && *typed_arg && typed_cpp_arg) { typed_cpp_arg->clear(); // See comments above about the SUN Forte and Tru64 compilers. char** char_array_next = *typed_arg; while (*char_array_next) { typed_cpp_arg->emplace_back(*char_array_next); ++char_array_next; } } break; } case G_OPTION_ARG_NONE: // Actually a boolean. { *(static_cast(cpparg_)) = *(static_cast(carg_)); break; } case G_OPTION_ARG_CALLBACK: { // Nothing to convert here. That's a task for the callback function //(the SlotOptionArgString or SlotOptionArgFilename). break; } default: { break; } } } GOptionGroup* OptionGroup::gobj_copy() const { return g_option_group_ref(gobject_); } } // namespace Glib