// -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*- /* 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, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include // std::memset() #include #include #include namespace // anonymous { //TODO: At the next ABI break, implement the pimpl idiom. Then we need not use // a GQuark for ExtraApplicationData, which should be renamed to // struct Gio::Application::Impl. // These are new data members that can't be added to Gio::Application now, // because it would break ABI. struct ExtraApplicationData { std::vector option_entry_strings; ~ExtraApplicationData() { for (std::vector::iterator iter = option_entry_strings.begin(); iter != option_entry_strings.end(); ++iter) { g_free(*iter); *iter = 0; } } }; GQuark quark_extra_application_data = g_quark_from_static_string("glibmm__Gio::Application::quark_extra_application_data"); void Application_delete_extra_application_data(gpointer data) { ExtraApplicationData* extra_application_data = static_cast(data); delete extra_application_data; } static void Application_signal_open_callback(GApplication* self, GFile** files, gint n_files, const gchar* hint, void* data) { typedef sigc::slot< void, const Gio::Application::type_vec_files&, const Glib::ustring& > SlotType; 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(sigc::slot_base *const 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; typedef sigc::slot< void, const Application::type_vec_files&, const Glib::ustring& > SlotType; 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(sigc::slot_base *const 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 == NULL. // Therefore Application_option_arg_callback() is called with data == NULL. // 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, gchar short_name, const Glib::OptionGroup::SlotOptionArgString& slot) : application_(application), short_name_(short_name), slot_string_(new Glib::OptionGroup::SlotOptionArgString(slot)), slot_filename_(0) { } explicit OptionArgCallbackData(const Gio::Application* application, gchar short_name, const Glib::OptionGroup::SlotOptionArgFilename& slot) : application_(application), short_name_(short_name), slot_string_(0), slot_filename_(new Glib::OptionGroup::SlotOptionArgFilename(slot)) { } const Gio::Application* get_application() const { return application_; } gchar get_short_name() const { return short_name_; } bool is_filename_option() const { return slot_filename_ != 0; } const Glib::OptionGroup::SlotOptionArgString* get_slot_string() const { return slot_string_; } const Glib::OptionGroup::SlotOptionArgFilename* get_slot_filename() const { return slot_filename_; } ~OptionArgCallbackData() { delete slot_string_; delete slot_filename_; // Don't delete application_. It's not owned by this class. } private: const Gio::Application* application_; gchar short_name_; // One of these slot pointers is 0 and the other one points to a slot. Glib::OptionGroup::SlotOptionArgString* slot_string_; Glib::OptionGroup::SlotOptionArgFilename* slot_filename_; // Not copyable OptionArgCallbackData(const OptionArgCallbackData&); OptionArgCallbackData& operator=(const OptionArgCallbackData&); }; typedef std::map OptionArgCallbackDataMap; OptionArgCallbackDataMap option_arg_callback_data; // Gio::Application instances may be used in different threads. // Accesses to option_arg_callback_data must be thread-safe. Glib::Threads::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. Glib::Threads::Mutex::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 != 0); const OptionArgCallbackData* const option_arg = iterFind->second; try { if (option_arg->is_filename_option()) { const auto the_slot = option_arg->get_slot_filename(); lock.release(); 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.release(); 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, ApplicationFlags flags) : // Mark this class as non-derived to allow C++ vfuncs to be skipped. Glib::ObjectBase(0), Glib::Object(Glib::ConstructParams(custom_class_init(), "application_id", (application_id.empty() ? 0 : application_id.c_str()), "flags", ((GApplicationFlags)(flags)), static_cast(0))) { } Application::~Application() { // Delete all OptionArgCallbackData instances that belong to this application. Glib::Threads::Mutex::Lock 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(0); } 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 NULL 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::SignalProxy2< void, const Application::type_vec_files&, const Glib::ustring& > Application::signal_open() { return Glib::SignalProxy2< void, const Application::type_vec_files&, const Glib::ustring& >(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, int flags) { add_main_option_entry_private((GOptionArg)arg_type, long_name, short_name, description, arg_description, flags); } 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, int flags) { Glib::Threads::Mutex::Lock 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 auto callback_data = new OptionArgCallbackData(this, short_name, slot); option_arg_callback_data[long_name] = callback_data; lock.release(); add_main_option_entry_private(G_OPTION_ARG_CALLBACK, long_name, short_name, description, arg_description, flags & ~Glib::OptionEntry::FLAG_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, int flags) { Glib::Threads::Mutex::Lock 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 auto callback_data = new OptionArgCallbackData(this, short_name, slot); option_arg_callback_data[long_name] = callback_data; lock.release(); add_main_option_entry_private(G_OPTION_ARG_CALLBACK, long_name, short_name, description, arg_description, flags | Glib::OptionEntry::FLAG_FILENAME); } void Application::add_main_option_entry_private(GOptionArg arg, const Glib::ustring& long_name, gchar short_name, const Glib::ustring& description, const Glib::ustring& arg_description, int flags) { // 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. // GOptionEntry.long_name must be set, even if it's an empty string. gchar* lname = g_strdup(long_name.c_str()); gchar* desc = description.empty() ? 0 : g_strdup(description.c_str()); gchar* arg_desc = arg_description.empty() ? 0 : g_strdup(arg_description.c_str()); ExtraApplicationData* extra_application_data = static_cast(g_object_get_qdata(gobject_, quark_extra_application_data)); if (!extra_application_data) { extra_application_data = new ExtraApplicationData(); g_object_set_qdata_full(gobject_, quark_extra_application_data, extra_application_data, Application_delete_extra_application_data); } extra_application_data->option_entry_strings.push_back(lname); if (desc) extra_application_data->option_entry_strings.push_back(desc); if (arg_desc) extra_application_data->option_entry_strings.push_back(arg_desc); // Fill in array[0]. array[0].arg = arg; array[0].long_name = lname; array[0].short_name = short_name; array[0].description = desc; array[0].arg_description = arg_desc; array[0].flags = flags; if (arg == G_OPTION_ARG_CALLBACK) { // 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; } else // We ensure that this is null to ensure that it is not used, // telling GApplication to put the parsed value in the options VariantDict instead. array[0].arg_data = 0; g_application_add_main_option_entries(gobj(), array); } void Application::unset_resource_base_path() { g_application_set_resource_base_path(gobj(), 0 /* see the C docs. */); } } // namespace Gio