/* Copyright (C) 2007 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 // std::memset() #include #include #include using Flags = Gio::Application::Flags; namespace // anonymous { static void Application_signal_open_callback( GApplication* self, GFile** files, gint n_files, const gchar* hint, void* data) { using SlotType = sigc::slot; Gio::Application::type_vec_files vec_files(n_files); for (int i = 0; i < n_files; ++i) { vec_files[i] = Glib::wrap(files[i], true); } const auto hint_str = (hint ? hint : Glib::ustring()); // Do not try to call a signal on a disassociated wrapper. if (Glib::ObjectBase::_get_current_wrapper((GObject*)self)) { try { if (const auto slot = Glib::SignalProxyNormal::data_to_slot(data)) { (*static_cast(slot))(vec_files, hint_str); return; } } catch (...) { Glib::exception_handlers_invoke(); } } return; } static void Application_signal_open_notify_callback( GApplication* self, GFile** files, gint n_files, const gchar* hint, void* data) { using namespace Gio; using SlotType = sigc::slot; Application::type_vec_files vec_files(n_files); for (int i = 0; i < n_files; i++) { vec_files[i] = Glib::wrap(files[i], true); } const auto hint_str = (hint ? hint : Glib::ustring()); // Do not try to call a signal on a disassociated wrapper. if (Glib::ObjectBase::_get_current_wrapper((GObject*)self)) { try { if (const auto slot = Glib::SignalProxyNormal::data_to_slot(data)) { (*static_cast(slot))(vec_files, hint_str); return; } } catch (...) { Glib::exception_handlers_invoke(); } } return; } static const Glib::SignalProxyInfo Application_signal_open_info = { "open", (GCallback)&Application_signal_open_callback, (GCallback)&Application_signal_open_notify_callback }; // The add_main_option_entry*() methods that take a slot parameter are handled // similarly to the corresponding Glib::OptionGroup::add_entry*() methods. // There is an important difference: In add_main_option_entry*() we can't pass // an Application pointer to the used GOptionGroup. // g_application_add_main_option_entries() creates a GOptionGroup with user_data == nullptr. // Therefore Application_option_arg_callback() is called with data == nullptr. // Application_option_arg_callback() does not know which Application instance // the command-line option belongs to. All Application instances (usually only one) // share a map, mapping the long command option name to an OptionArgCallbackData. class OptionArgCallbackData { public: explicit OptionArgCallbackData(const Gio::Application* application, const Glib::ustring& long_name, gchar short_name, const Glib::ustring& description, const Glib::ustring& arg_description, const Glib::OptionGroup::SlotOptionArgString& slot) : application_(application), long_name_(g_strdup(long_name.c_str())), // must not be nullptr short_name_(short_name), description_(g_strdup(Glib::c_str_or_nullptr(description))), arg_description_(g_strdup(Glib::c_str_or_nullptr(arg_description))), slot_string_(new Glib::OptionGroup::SlotOptionArgString(slot)), slot_filename_(nullptr) { } explicit OptionArgCallbackData(const Gio::Application* application, const Glib::ustring& long_name, gchar short_name, const Glib::ustring& description, const Glib::ustring& arg_description, const Glib::OptionGroup::SlotOptionArgFilename& slot) : application_(application), long_name_(g_strdup(long_name.c_str())), // must not be nullptr short_name_(short_name), description_(g_strdup(Glib::c_str_or_nullptr(description))), arg_description_(g_strdup(Glib::c_str_or_nullptr(arg_description))), slot_string_(nullptr), slot_filename_(new Glib::OptionGroup::SlotOptionArgFilename(slot)) { } const Gio::Application* get_application() const { return application_; } const gchar* get_long_name() const { return long_name_; } gchar get_short_name() const { return short_name_; } const gchar* get_description() const { return description_; } const gchar* get_arg_description() const { return arg_description_; } bool is_filename_option() const { return slot_filename_ != nullptr; } const Glib::OptionGroup::SlotOptionArgString* get_slot_string() const { return slot_string_; } const Glib::OptionGroup::SlotOptionArgFilename* get_slot_filename() const { return slot_filename_; } ~OptionArgCallbackData() { g_free(long_name_); g_free(description_); g_free(arg_description_); delete slot_string_; delete slot_filename_; // Don't delete application_. It's not owned by this class. } private: const Gio::Application* application_; gchar* long_name_; gchar short_name_; gchar* description_; gchar* arg_description_; // One of these slot pointers is nullptr and the other one points to a slot. Glib::OptionGroup::SlotOptionArgString* slot_string_; Glib::OptionGroup::SlotOptionArgFilename* slot_filename_; // Not copyable OptionArgCallbackData(const OptionArgCallbackData&) = delete; OptionArgCallbackData& operator=(const OptionArgCallbackData&) = delete; }; // end class OptionArgCallbackData using OptionArgCallbackDataMap = std::map; OptionArgCallbackDataMap option_arg_callback_data; // Gio::Application instances may be used in different threads. // Accesses to option_arg_callback_data must be thread-safe. std::mutex option_arg_callback_data_mutex; gboolean Application_option_arg_callback( const gchar* option_name, const gchar* value, gpointer /* data */, GError** error) { const Glib::ustring cpp_option_name(option_name); // 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. std::unique_lock lock(option_arg_callback_data_mutex); OptionArgCallbackDataMap::const_iterator iterFind = option_arg_callback_data.end(); if (option_name[1] == '-') { // Long option name. const auto long_option_name = Glib::ustring(option_name + 2); iterFind = option_arg_callback_data.find(long_option_name); } else { // Short option name. const auto short_option_name = option_name[1]; for (iterFind = option_arg_callback_data.begin(); iterFind != option_arg_callback_data.end(); ++iterFind) { if (iterFind->second->get_short_name() == short_option_name) break; } } if (iterFind == option_arg_callback_data.end()) { Glib::OptionError(Glib::OptionError::UNKNOWN_OPTION, "Application_option_arg_callback(): " "Unknown option " + cpp_option_name) .propagate(error); return false; } const bool has_value = (value != nullptr); const OptionArgCallbackData* const option_arg = iterFind->second; try { if (option_arg->is_filename_option()) { const auto the_slot = option_arg->get_slot_filename(); lock.unlock(); 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(); lock.unlock(); 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; } } // anonymous namespace namespace Gio { const Glib::Class& Application::custom_class_init() { Glib::init(); Gio::init(); return application_class_.init(); } Application::Application(const Glib::ustring& application_id, Flags flags) : // Mark this class as non-derived to allow C++ vfuncs to be skipped. // GApplication complains about "" but allows nullptr, so we avoid passing "". Glib::ObjectBase(nullptr), Glib::Object(Glib::ConstructParams(custom_class_init(), "application_id", Glib::c_str_or_nullptr(application_id), "flags", static_cast(flags), nullptr)) { } Application::~Application() noexcept { // Delete all OptionArgCallbackData instances that belong to this application. std::lock_guard lock(option_arg_callback_data_mutex); OptionArgCallbackDataMap::iterator iter = option_arg_callback_data.begin(); while (iter != option_arg_callback_data.end()) { OptionArgCallbackDataMap::iterator saved_iter = iter; ++iter; if (saved_iter->second->get_application() == this) { delete saved_iter->second; option_arg_callback_data.erase(saved_iter); } } } // static void Application::unset_default() { g_application_set_default(nullptr); } void Application_Class::open_callback(GApplication* self, GFile** files, gint n_files, const gchar* hint) { const auto obj_base = static_cast(Glib::ObjectBase::_get_current_wrapper((GObject*)self)); // Non-gtkmmproc-generated custom classes implicitly call the default // Glib::ObjectBase constructor, which sets is_derived_. But gtkmmproc- // generated classes can use this optimisation, which avoids the unnecessary // parameter conversions if there is no possibility of the virtual function // being overridden: if (obj_base && obj_base->is_derived_()) { const auto obj = dynamic_cast(obj_base); if (obj) // This can be nullptr during destruction. { try // Trap C++ exceptions which would normally be lost because this is a C callback. { // Call the virtual member method, which derived classes might override. Application::type_vec_files vec_files(n_files); for (int i = 0; i < n_files; i++) { vec_files[i] = Glib::wrap(files[i], true); } const auto hint_str = (hint ? hint : Glib::ustring()); obj->on_open(vec_files, hint_str); return; } catch (...) { Glib::exception_handlers_invoke(); } } } const auto base = static_cast(g_type_class_peek_parent(G_OBJECT_GET_CLASS( self)) // Get the parent class of the object class (The original underlying C class). ); // Call the original underlying C function: if (base && base->open) (*base->open)(self, files, n_files, hint); } Glib::SignalProxy Application::signal_open() { return Glib::SignalProxy( this, &Application_signal_open_info); } void Gio::Application::on_open(const Application::type_vec_files& files, const Glib::ustring& hint) { const auto base = static_cast(g_type_class_peek_parent(G_OBJECT_GET_CLASS( gobject_)) // Get the parent class of the object class (The original underlying C class). ); if (base && base->open) { (*base->open)(gobj(), Glib::ArrayHandler::vector_to_array(files).data(), files.size(), hint.c_str()); } } void Application::open(const type_vec_files& files, const Glib::ustring& hint) { g_application_open(gobj(), Glib::ArrayHandler::vector_to_array(files).data(), files.size(), hint.c_str()); } void Application::open(const Glib::RefPtr& file, const Glib::ustring& hint) { type_vec_files files(1); files[0] = file; open(files, hint); } void Application::add_main_option_entry(OptionType arg_type, const Glib::ustring& long_name, gchar short_name, const Glib::ustring& description, const Glib::ustring& arg_description, Glib::OptionEntry::Flags flags) { // g_application_add_main_option() saves copies of the strings. // No need to keep copies in Gio::Application. g_application_add_main_option(gobj(), long_name.c_str(), short_name, static_cast(flags), static_cast(arg_type), description.c_str(), Glib::c_str_or_nullptr(arg_description)); } void Application::add_main_option_entry(const Glib::OptionGroup::SlotOptionArgString& slot, const Glib::ustring& long_name, gchar short_name, const Glib::ustring& description, const Glib::ustring& arg_description, Glib::OptionEntry::Flags flags) { OptionArgCallbackData* callback_data = nullptr; { std::lock_guard lock(option_arg_callback_data_mutex); OptionArgCallbackDataMap::iterator iterFind = option_arg_callback_data.find(long_name); if (iterFind != option_arg_callback_data.end()) return; // Ignore duplicates callback_data = new OptionArgCallbackData( this, long_name, short_name, description, arg_description, slot); option_arg_callback_data[long_name] = callback_data; } // option_arg_callback_data_mutex.unlock() add_main_option_entry_private(callback_data->get_long_name(), short_name, callback_data->get_description(), callback_data->get_arg_description(), flags & ~Glib::OptionEntry::Flags::FILENAME); } void Application::add_main_option_entry_filename(const Glib::OptionGroup::SlotOptionArgFilename& slot, const Glib::ustring& long_name, gchar short_name, const Glib::ustring& description, const Glib::ustring& arg_description, Glib::OptionEntry::Flags flags) { OptionArgCallbackData* callback_data = nullptr; { std::lock_guard lock(option_arg_callback_data_mutex); OptionArgCallbackDataMap::iterator iterFind = option_arg_callback_data.find(long_name); if (iterFind != option_arg_callback_data.end()) return; // Ignore duplicates callback_data = new OptionArgCallbackData( this, long_name, short_name, description, arg_description, slot); option_arg_callback_data[long_name] = callback_data; } // option_arg_callback_data_mutex.unlock() add_main_option_entry_private(callback_data->get_long_name(), short_name, callback_data->get_description(), callback_data->get_arg_description(), flags | Glib::OptionEntry::Flags::FILENAME); } void Application::add_main_option_entry_private(const gchar* long_name, gchar short_name, const gchar* description, const gchar* arg_description, Glib::OptionEntry::Flags flags) { // g_application_add_main_option() can't be used for options with // a callback slot, because GOptionEntry.arg_data must be non-null. // Create a temporary array, just so we can give the correct thing to // g_application_add_main_option_entries(): GOptionEntry array[2]; std::memset(array, 0, 2 * sizeof(GOptionEntry)); // null-termination // g_application_add_main_option_entries() does not take its own copy // of the strings. We must keep them alive, and keep pointers to them, // so we can delete them when the Application instance is deleted. // This is handled in OptionArgCallbackData. // Fill in array[0]. array[0].arg = G_OPTION_ARG_CALLBACK; array[0].long_name = long_name; array[0].short_name = short_name; array[0].description = description; array[0].arg_description = arg_description; array[0].flags = static_cast(flags); // GoptionEntry.arg_data is a function pointer, cast to void*. // See Glib::OptionGroup::CppOptionEntry::allocate_c_arg() for a discussion // of this hack. union { void* dp; GOptionArgFunc fp; } u; u.fp = &Application_option_arg_callback; array[0].arg_data = u.dp; g_application_add_main_option_entries(gobj(), array); } void Application::unset_resource_base_path() { g_application_set_resource_base_path(gobj(), nullptr /* see the C docs. */); } } // namespace Gio