1/* Copyright (C) 2007 The gtkmm Development Team
2 *
3 * This library is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU Lesser General Public
5 * License as published by the Free Software Foundation; either
6 * version 2.1 of the License, or (at your option) any later version.
7 *
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include <gio/gio.h>
18#include <giomm/file.h>
19#include <giomm/actiongroup.h>
20#include <giomm/init.h>
21#include <cstring> // std::memset()
22#include <map>
23#include <vector>
24
25namespace // anonymous
26{
27// TODO: At the next ABI break, implement the pimpl idiom. Then we need not use
28// a GQuark for ExtraApplicationData, which should be renamed to
29// struct Gio::Application::Impl.
30// These are new data members that can't be added to Gio::Application now,
31// because it would break ABI.
32struct ExtraApplicationData
33{
34  std::vector<gchar*> option_entry_strings;
35
36  ~ExtraApplicationData()
37  {
38    for (auto str : option_entry_strings)
39    {
40      g_free(str);
41    }
42  }
43};
44
45GQuark quark_extra_application_data =
46  g_quark_from_static_string("glibmm__Gio::Application::quark_extra_application_data");
47
48void
49Application_delete_extra_application_data(gpointer data)
50{
51  ExtraApplicationData* extra_application_data = static_cast<ExtraApplicationData*>(data);
52  delete extra_application_data;
53}
54
55static void
56Application_signal_open_callback(
57  GApplication* self, GFile** files, gint n_files, const gchar* hint, void* data)
58{
59  using SlotType = sigc::slot<void, const Gio::Application::type_vec_files&, const Glib::ustring&>;
60
61  Gio::Application::type_vec_files vec_files(n_files);
62  for (int i = 0; i < n_files; ++i)
63  {
64    vec_files[i] = Glib::wrap(files[i], true);
65  }
66
67  const auto hint_str = (hint ? hint : Glib::ustring());
68
69  // Do not try to call a signal on a disassociated wrapper.
70  if (Glib::ObjectBase::_get_current_wrapper((GObject*)self))
71  {
72    try
73    {
74      if (const auto slot = Glib::SignalProxyNormal::data_to_slot(data))
75      {
76        (*static_cast<SlotType*>(slot))(vec_files, hint_str);
77        return;
78      }
79    }
80    catch (...)
81    {
82      Glib::exception_handlers_invoke();
83    }
84  }
85
86  return;
87}
88
89static void
90Application_signal_open_notify_callback(
91  GApplication* self, GFile** files, gint n_files, const gchar* hint, void* data)
92{
93  using namespace Gio;
94  using SlotType = sigc::slot<void, const Application::type_vec_files&, const Glib::ustring&>;
95
96  Application::type_vec_files vec_files(n_files);
97  for (int i = 0; i < n_files; i++)
98  {
99    vec_files[i] = Glib::wrap(files[i], true);
100  }
101
102  const auto hint_str = (hint ? hint : Glib::ustring());
103
104  // Do not try to call a signal on a disassociated wrapper.
105  if (Glib::ObjectBase::_get_current_wrapper((GObject*)self))
106  {
107    try
108    {
109      if (const auto slot = Glib::SignalProxyNormal::data_to_slot(data))
110      {
111        (*static_cast<SlotType*>(slot))(vec_files, hint_str);
112        return;
113      }
114    }
115    catch (...)
116    {
117      Glib::exception_handlers_invoke();
118    }
119  }
120
121  return;
122}
123
124static const Glib::SignalProxyInfo Application_signal_open_info = { "open",
125  (GCallback)&Application_signal_open_callback,
126  (GCallback)&Application_signal_open_notify_callback };
127
128// The add_main_option_entry*() methods that take a slot parameter are handled
129// similarly to the corresponding Glib::OptionGroup::add_entry*() methods.
130// There is an important difference: In add_main_option_entry*() we can't pass
131// an Application pointer to the used GOptionGroup.
132// g_application_add_main_option_entries() creates a GOptionGroup with user_data == nullptr.
133// Therefore Application_option_arg_callback() is called with data == nullptr.
134// Application_option_arg_callback() does not know which Application instance
135// the command-line option belongs to. All Application instances (usually only one)
136// share a map, mapping the long command option name to an OptionArgCallbackData.
137class OptionArgCallbackData
138{
139public:
140  explicit OptionArgCallbackData(const Gio::Application* application, gchar short_name,
141    const Glib::OptionGroup::SlotOptionArgString& slot)
142  : application_(application),
143    short_name_(short_name),
144    slot_string_(new Glib::OptionGroup::SlotOptionArgString(slot)),
145    slot_filename_(nullptr)
146  {
147  }
148
149  explicit OptionArgCallbackData(const Gio::Application* application, gchar short_name,
150    const Glib::OptionGroup::SlotOptionArgFilename& slot)
151  : application_(application),
152    short_name_(short_name),
153    slot_string_(nullptr),
154    slot_filename_(new Glib::OptionGroup::SlotOptionArgFilename(slot))
155  {
156  }
157
158  const Gio::Application* get_application() const { return application_; }
159  gchar get_short_name() const { return short_name_; }
160  bool is_filename_option() const { return slot_filename_ != nullptr; }
161
162  const Glib::OptionGroup::SlotOptionArgString* get_slot_string() const { return slot_string_; }
163
164  const Glib::OptionGroup::SlotOptionArgFilename* get_slot_filename() const
165  {
166    return slot_filename_;
167  }
168
169  ~OptionArgCallbackData()
170  {
171    delete slot_string_;
172    delete slot_filename_;
173    // Don't delete application_. It's not owned by this class.
174  }
175
176private:
177  const Gio::Application* application_;
178  gchar short_name_;
179  // One of these slot pointers is nullptr and the other one points to a slot.
180  Glib::OptionGroup::SlotOptionArgString* slot_string_;
181  Glib::OptionGroup::SlotOptionArgFilename* slot_filename_;
182
183  // Not copyable
184  OptionArgCallbackData(const OptionArgCallbackData&);
185  OptionArgCallbackData& operator=(const OptionArgCallbackData&);
186};
187
188using OptionArgCallbackDataMap = std::map<Glib::ustring, OptionArgCallbackData*>;
189OptionArgCallbackDataMap option_arg_callback_data;
190
191// Gio::Application instances may be used in different threads.
192// Accesses to option_arg_callback_data must be thread-safe.
193std::mutex option_arg_callback_data_mutex;
194
195gboolean
196Application_option_arg_callback(
197  const gchar* option_name, const gchar* value, gpointer /* data */, GError** error)
198{
199  const Glib::ustring cpp_option_name(option_name);
200
201  // option_name is either a single dash followed by a single letter (for a
202  // short name) or two dashes followed by a long option name.
203  std::unique_lock<std::mutex> lock(option_arg_callback_data_mutex);
204  OptionArgCallbackDataMap::const_iterator iterFind = option_arg_callback_data.end();
205  if (option_name[1] == '-')
206  {
207    // Long option name.
208    const auto long_option_name = Glib::ustring(option_name + 2);
209    iterFind = option_arg_callback_data.find(long_option_name);
210  }
211  else
212  {
213    // Short option name.
214    const auto short_option_name = option_name[1];
215    for (iterFind = option_arg_callback_data.begin(); iterFind != option_arg_callback_data.end();
216         ++iterFind)
217    {
218      if (iterFind->second->get_short_name() == short_option_name)
219        break;
220    }
221  }
222
223  if (iterFind == option_arg_callback_data.end())
224  {
225    Glib::OptionError(Glib::OptionError::UNKNOWN_OPTION, "Application_option_arg_callback(): "
226                                                         "Unknown option " +
227                                                           cpp_option_name)
228      .propagate(error);
229    return false;
230  }
231
232  const bool has_value = (value != nullptr);
233  const OptionArgCallbackData* const option_arg = iterFind->second;
234  try
235  {
236    if (option_arg->is_filename_option())
237    {
238      const auto the_slot = option_arg->get_slot_filename();
239      lock.unlock();
240      const std::string cpp_value(value ? value : "");
241      return (*the_slot)(cpp_option_name, cpp_value, has_value);
242    }
243    else
244    {
245      const auto the_slot = option_arg->get_slot_string();
246      lock.unlock();
247      const Glib::ustring cpp_value(value ? value : "");
248      return (*the_slot)(cpp_option_name, cpp_value, has_value);
249    }
250  }
251  catch (Glib::Error& err)
252  {
253    err.propagate(error);
254  }
255  catch (...)
256  {
257    Glib::exception_handlers_invoke();
258  }
259  return false;
260}
261
262} // anonymous namespace
263
264namespace Gio
265{
266
267const Glib::Class&
268Application::custom_class_init()
269{
270  Glib::init();
271  Gio::init();
272  return application_class_.init();
273}
274
275Application::Application(const Glib::ustring& application_id, ApplicationFlags flags)
276: // Mark this class as non-derived to allow C++ vfuncs to be skipped.
277  Glib::ObjectBase(nullptr),
278  Glib::Object(Glib::ConstructParams(custom_class_init(), "application_id",
279    Glib::c_str_or_nullptr(application_id), "flags", ((GApplicationFlags)(flags)), nullptr))
280{
281}
282
283Application::~Application() noexcept
284{
285  // Delete all OptionArgCallbackData instances that belong to this application.
286  std::lock_guard<std::mutex> lock(option_arg_callback_data_mutex);
287  OptionArgCallbackDataMap::iterator iter = option_arg_callback_data.begin();
288  while (iter != option_arg_callback_data.end())
289  {
290    OptionArgCallbackDataMap::iterator saved_iter = iter;
291    ++iter;
292    if (saved_iter->second->get_application() == this)
293    {
294      delete saved_iter->second;
295      option_arg_callback_data.erase(saved_iter);
296    }
297  }
298}
299
300// static
301void
302Application::unset_default()
303{
304  g_application_set_default(nullptr);
305}
306
307void
308Application_Class::open_callback(GApplication* self, GFile** files, gint n_files, const gchar* hint)
309{
310  const auto obj_base =
311    static_cast<Glib::ObjectBase*>(Glib::ObjectBase::_get_current_wrapper((GObject*)self));
312
313  // Non-gtkmmproc-generated custom classes implicitly call the default
314  // Glib::ObjectBase constructor, which sets is_derived_. But gtkmmproc-
315  // generated classes can use this optimisation, which avoids the unnecessary
316  // parameter conversions if there is no possibility of the virtual function
317  // being overridden:
318
319  if (obj_base && obj_base->is_derived_())
320  {
321    const auto obj = dynamic_cast<CppObjectType* const>(obj_base);
322    if (obj) // This can be nullptr during destruction.
323    {
324      try // Trap C++ exceptions which would normally be lost because this is a C callback.
325      {
326        // Call the virtual member method, which derived classes might override.
327        Application::type_vec_files vec_files(n_files);
328        for (int i = 0; i < n_files; i++)
329        {
330          vec_files[i] = Glib::wrap(files[i], true);
331        }
332
333        const auto hint_str = (hint ? hint : Glib::ustring());
334
335        obj->on_open(vec_files, hint_str);
336        return;
337      }
338      catch (...)
339      {
340        Glib::exception_handlers_invoke();
341      }
342    }
343  }
344
345  const auto base = static_cast<BaseClassType*>(g_type_class_peek_parent(G_OBJECT_GET_CLASS(
346    self)) // Get the parent class of the object class (The original underlying C class).
347    );
348
349  // Call the original underlying C function:
350  if (base && base->open)
351    (*base->open)(self, files, n_files, hint);
352}
353
354Glib::SignalProxy<void, const Application::type_vec_files&, const Glib::ustring&>
355Application::signal_open()
356{
357  return Glib::SignalProxy<void, const Application::type_vec_files&, const Glib::ustring&>(
358    this, &Application_signal_open_info);
359}
360
361void
362Gio::Application::on_open(const Application::type_vec_files& files, const Glib::ustring& hint)
363{
364  const auto base = static_cast<BaseClassType*>(g_type_class_peek_parent(G_OBJECT_GET_CLASS(
365    gobject_)) // Get the parent class of the object class (The original underlying C class).
366    );
367
368  if (base && base->open)
369  {
370    (*base->open)(gobj(),
371      Glib::ArrayHandler<type_vec_files::value_type>::vector_to_array(files).data(), files.size(),
372      hint.c_str());
373  }
374}
375
376void
377Application::open(const type_vec_files& files, const Glib::ustring& hint)
378{
379  g_application_open(gobj(),
380    Glib::ArrayHandler<type_vec_files::value_type>::vector_to_array(files).data(), files.size(),
381    hint.c_str());
382}
383
384void
385Application::open(const Glib::RefPtr<Gio::File>& file, const Glib::ustring& hint)
386{
387  type_vec_files files(1);
388  files[0] = file;
389  open(files, hint);
390}
391
392void
393Application::add_main_option_entry(OptionType arg_type, const Glib::ustring& long_name,
394  gchar short_name, const Glib::ustring& description, const Glib::ustring& arg_description,
395  int flags)
396{
397  add_main_option_entry_private(
398    (GOptionArg)arg_type, long_name, short_name, description, arg_description, flags);
399}
400
401void
402Application::add_main_option_entry(const Glib::OptionGroup::SlotOptionArgString& slot,
403  const Glib::ustring& long_name, gchar short_name, const Glib::ustring& description,
404  const Glib::ustring& arg_description, int flags)
405{
406  {
407    std::lock_guard<std::mutex> lock(option_arg_callback_data_mutex);
408    OptionArgCallbackDataMap::iterator iterFind = option_arg_callback_data.find(long_name);
409    if (iterFind != option_arg_callback_data.end())
410      return; // Ignore duplicates
411
412    auto callback_data = new OptionArgCallbackData(this, short_name, slot);
413    option_arg_callback_data[long_name] = callback_data;
414  } // option_arg_callback_data_mutex.unlock()
415
416  add_main_option_entry_private(G_OPTION_ARG_CALLBACK, long_name, short_name, description,
417    arg_description, flags & ~Glib::OptionEntry::FLAG_FILENAME);
418}
419
420void
421Application::add_main_option_entry_filename(const Glib::OptionGroup::SlotOptionArgFilename& slot,
422  const Glib::ustring& long_name, gchar short_name, const Glib::ustring& description,
423  const Glib::ustring& arg_description, int flags)
424{
425  {
426    std::lock_guard<std::mutex> lock(option_arg_callback_data_mutex);
427    OptionArgCallbackDataMap::iterator iterFind = option_arg_callback_data.find(long_name);
428    if (iterFind != option_arg_callback_data.end())
429      return; // Ignore duplicates
430
431    auto callback_data = new OptionArgCallbackData(this, short_name, slot);
432    option_arg_callback_data[long_name] = callback_data;
433  } // option_arg_callback_data_mutex.unlock()
434
435  add_main_option_entry_private(G_OPTION_ARG_CALLBACK, long_name, short_name, description,
436    arg_description, flags | Glib::OptionEntry::FLAG_FILENAME);
437}
438
439void
440Application::add_main_option_entry_private(GOptionArg arg, const Glib::ustring& long_name,
441  gchar short_name, const Glib::ustring& description, const Glib::ustring& arg_description,
442  int flags)
443{
444  // Create a temporary array, just so we can give the correct thing to
445  // g_application_add_main_option_entries():
446  GOptionEntry array[2];
447  std::memset(array, 0, 2 * sizeof(GOptionEntry)); // null-termination
448
449  // g_application_add_main_option_entries() does not take its own copy
450  // of the strings. We must keep them alive, and keep pointers to them,
451  // so we can delete them when the Application instance is deleted.
452
453  // GOptionEntry.long_name must be set, even if it's an empty string.
454  gchar* lname = g_strdup(long_name.c_str());
455  gchar* desc = description.empty() ? nullptr : g_strdup(description.c_str());
456  gchar* arg_desc = arg_description.empty() ? nullptr : g_strdup(arg_description.c_str());
457
458  ExtraApplicationData* extra_application_data =
459    static_cast<ExtraApplicationData*>(g_object_get_qdata(gobject_, quark_extra_application_data));
460  if (!extra_application_data)
461  {
462    extra_application_data = new ExtraApplicationData();
463    g_object_set_qdata_full(gobject_, quark_extra_application_data, extra_application_data,
464      Application_delete_extra_application_data);
465  }
466
467  extra_application_data->option_entry_strings.emplace_back(lname);
468  if (desc)
469    extra_application_data->option_entry_strings.emplace_back(desc);
470  if (arg_desc)
471    extra_application_data->option_entry_strings.emplace_back(arg_desc);
472
473  // Fill in array[0].
474  array[0].arg = arg;
475  array[0].long_name = lname;
476  array[0].short_name = short_name;
477  array[0].description = desc;
478  array[0].arg_description = arg_desc;
479  array[0].flags = flags;
480
481  if (arg == G_OPTION_ARG_CALLBACK)
482  {
483    // GoptionEntry.arg_data is a function pointer, cast to void*.
484    // See Glib::OptionGroup::CppOptionEntry::allocate_c_arg() for a discussion
485    // of this hack.
486    union {
487      void* dp;
488      GOptionArgFunc fp;
489    } u;
490    u.fp = &Application_option_arg_callback;
491    array[0].arg_data = u.dp;
492  }
493  else
494    // We ensure that this is null to ensure that it is not used,
495    // telling GApplication to put the parsed value in the options VariantDict instead.
496    array[0].arg_data = nullptr;
497
498  g_application_add_main_option_entries(gobj(), array);
499}
500
501void
502Application::unset_resource_base_path()
503{
504  g_application_set_resource_base_path(gobj(), nullptr /* see the C docs. */);
505}
506
507Glib::PropertyProxy< std::string > Application::property_resource_base_path_string()
508{
509  return Glib::PropertyProxy< std::string >(this, "resource-base-path");
510}
511
512Glib::PropertyProxy_ReadOnly< std::string > Application::property_resource_base_path_string() const
513{
514  return Glib::PropertyProxy_ReadOnly< std::string >(this, "resource-base-path");
515}
516
517} // namespace Gio
518