1 /* dspy-name.c
2  *
3  * Copyright 2019 Christian Hergert <chergert@redhat.com>
4  *
5  * This file is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU Lesser General Public License as
7  * published by the Free Software Foundation; either version 3 of the
8  * License, or (at your option) any later version.
9  *
10  * This file is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  * SPDX-License-Identifier: LGPL-3.0-or-later
19  */
20 
21 #define G_LOG_DOMAIN "dspy-name"
22 
23 #include "config.h"
24 
25 #include "dspy-introspection-model.h"
26 #include "dspy-name.h"
27 #include "dspy-private.h"
28 
29 struct _DspyName
30 {
31   GObject         parent_instance;
32   DspyConnection *connection;
33   gchar          *name;
34   gchar          *owner;
35   gchar          *search_text;
36   GPid            pid;
37   guint           activatable : 1;
38 };
39 
40 enum {
41   PROP_0,
42   PROP_ACTIVATABLE,
43   PROP_CONNECTION,
44   PROP_NAME,
45   PROP_OWNER,
46   PROP_PID,
47   N_PROPS
48 };
49 
G_DEFINE_FINAL_TYPE(DspyName,dspy_name,G_TYPE_OBJECT)50 G_DEFINE_FINAL_TYPE (DspyName, dspy_name, G_TYPE_OBJECT)
51 
52 static GParamSpec *properties [N_PROPS];
53 
54 static void
55 dspy_name_finalize (GObject *object)
56 {
57   DspyName *self = (DspyName *)object;
58 
59   g_clear_object (&self->connection);
60   g_clear_pointer (&self->name, g_free);
61   g_clear_pointer (&self->owner, g_free);
62   g_clear_pointer (&self->search_text, g_free);
63 
64   G_OBJECT_CLASS (dspy_name_parent_class)->finalize (object);
65 }
66 
67 static void
dspy_name_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)68 dspy_name_get_property (GObject    *object,
69                         guint       prop_id,
70                         GValue     *value,
71                         GParamSpec *pspec)
72 {
73   DspyName *self = DSPY_NAME (object);
74 
75   switch (prop_id)
76     {
77     case PROP_ACTIVATABLE:
78       g_value_set_boolean (value, dspy_name_get_activatable (self));
79       break;
80 
81     case PROP_CONNECTION:
82       g_value_set_object (value, dspy_name_get_connection (self));
83       break;
84 
85     case PROP_NAME:
86       g_value_set_string (value, dspy_name_get_name (self));
87       break;
88 
89     case PROP_OWNER:
90       g_value_set_string (value, dspy_name_get_owner (self));
91       break;
92 
93     case PROP_PID:
94       g_value_set_int (value, dspy_name_get_pid (self));
95       break;
96 
97     default:
98       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
99     }
100 }
101 
102 static void
dspy_name_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)103 dspy_name_set_property (GObject      *object,
104                         guint         prop_id,
105                         const GValue *value,
106                         GParamSpec   *pspec)
107 {
108   DspyName *self = DSPY_NAME (object);
109 
110   switch (prop_id)
111     {
112     case PROP_ACTIVATABLE:
113       self->activatable = g_value_get_boolean (value);
114       break;
115 
116     case PROP_CONNECTION:
117       self->connection = g_value_dup_object (value);
118       break;
119 
120     case PROP_NAME:
121       self->name = g_value_dup_string (value);
122       break;
123 
124     default:
125       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
126     }
127 }
128 
129 static void
dspy_name_class_init(DspyNameClass * klass)130 dspy_name_class_init (DspyNameClass *klass)
131 {
132   GObjectClass *object_class = G_OBJECT_CLASS (klass);
133 
134   object_class->finalize = dspy_name_finalize;
135   object_class->get_property = dspy_name_get_property;
136   object_class->set_property = dspy_name_set_property;
137 
138   properties [PROP_ACTIVATABLE] =
139     g_param_spec_boolean ("activatable",
140                           "Activatable",
141                           "Activatable",
142                           FALSE,
143                           (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
144 
145   properties [PROP_CONNECTION] =
146     g_param_spec_object ("connection",
147                          "Connection",
148                          "The connection where the name can be found",
149                          DSPY_TYPE_CONNECTION,
150                          (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
151 
152   properties [PROP_NAME] =
153     g_param_spec_string ("name",
154                          "Name",
155                          "The peer name",
156                          NULL,
157                          (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
158 
159   properties [PROP_OWNER] =
160     g_param_spec_string ("owner",
161                          "Owner",
162                          "The owner of the D-Bus name",
163                          NULL,
164                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
165 
166   properties [PROP_PID] =
167     g_param_spec_int ("pid",
168                       "Pid",
169                       "The pid of the peer",
170                       -1, G_MAXINT, -1,
171                       (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
172 
173   g_object_class_install_properties (object_class, N_PROPS, properties);
174 }
175 
176 static void
dspy_name_init(DspyName * self)177 dspy_name_init (DspyName *self)
178 {
179   self->pid = -1;
180 }
181 
182 DspyName *
dspy_name_new(DspyConnection * connection,const gchar * name,gboolean activatable)183 dspy_name_new (DspyConnection *connection,
184                const gchar    *name,
185                gboolean        activatable)
186 {
187   return g_object_new (DSPY_TYPE_NAME,
188                        "activatable", activatable,
189                        "connection", connection,
190                        "name", name,
191                        NULL);
192 }
193 
194 gboolean
dspy_name_get_activatable(DspyName * self)195 dspy_name_get_activatable (DspyName *self)
196 {
197   g_return_val_if_fail (DSPY_IS_NAME (self), FALSE);
198 
199   return self->activatable;
200 }
201 
202 void
_dspy_name_set_activatable(DspyName * self,gboolean activatable)203 _dspy_name_set_activatable (DspyName *self,
204                             gboolean  activatable)
205 {
206   g_return_if_fail (DSPY_IS_NAME (self));
207 
208   activatable = !!activatable;
209 
210   if (self->activatable != activatable)
211     {
212       self->activatable = activatable;
213       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ACTIVATABLE]);
214     }
215 }
216 
217 const gchar *
dspy_name_get_name(DspyName * self)218 dspy_name_get_name (DspyName *self)
219 {
220   g_return_val_if_fail (DSPY_IS_NAME (self), NULL);
221 
222   return self->name;
223 }
224 
225 gint
dspy_name_compare(gconstpointer a,gconstpointer b)226 dspy_name_compare (gconstpointer a,
227                    gconstpointer b)
228 {
229   DspyName *item1 = DSPY_NAME ((gpointer)a);
230   DspyName *item2 = DSPY_NAME ((gpointer)b);
231   const gchar *name1 = dspy_name_get_name (item1);
232   const gchar *name2 = dspy_name_get_name (item2);
233 
234   if (name1[0] != name2[0])
235     {
236       if (name1[0] == ':')
237         return 1;
238       if (name2[0] == ':')
239         return -1;
240     }
241 
242   /* Sort numbers like :1.300 better */
243   if (g_str_has_prefix (name1, ":1.") &&
244       g_str_has_prefix (name2, ":1."))
245     {
246       gint i1 = g_ascii_strtoll (name1 + 3, NULL, 10);
247       gint i2 = g_ascii_strtoll (name2 + 3, NULL, 10);
248 
249       return i1 - i2;
250     }
251 
252   return g_strcmp0 (name1, name2);
253 }
254 
255 GPid
dspy_name_get_pid(DspyName * self)256 dspy_name_get_pid (DspyName *self)
257 {
258   g_return_val_if_fail (DSPY_IS_NAME (self), 0);
259 
260   return self->pid;
261 }
262 
263 const gchar *
dspy_name_get_owner(DspyName * self)264 dspy_name_get_owner (DspyName *self)
265 {
266   g_return_val_if_fail (DSPY_IS_NAME (self), NULL);
267 
268   return self->owner ? self->owner : self->name;
269 }
270 
271 void
_dspy_name_set_owner(DspyName * self,const gchar * owner)272 _dspy_name_set_owner (DspyName    *self,
273                       const gchar *owner)
274 {
275   g_return_if_fail (DSPY_IS_NAME (self));
276 
277   if (g_strcmp0 (owner, self->owner) != 0)
278     {
279       g_free (self->owner);
280       self->owner = g_strdup (owner);
281       g_clear_pointer (&self->search_text, g_free);
282       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_OWNER]);
283     }
284 }
285 
286 void
_dspy_name_clear_pid(DspyName * self)287 _dspy_name_clear_pid (DspyName *self)
288 {
289   g_return_if_fail (DSPY_IS_NAME (self));
290 
291   if (self->pid != -1)
292     {
293       self->pid = -1;
294       g_clear_pointer (&self->search_text, g_free);
295       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PID]);
296     }
297 }
298 
299 static void
dspy_name_get_pid_cb(GObject * object,GAsyncResult * result,gpointer user_data)300 dspy_name_get_pid_cb (GObject      *object,
301                       GAsyncResult *result,
302                       gpointer      user_data)
303 {
304   GDBusConnection *connection = (GDBusConnection *)object;
305   g_autoptr(DspyName) self = user_data;
306   g_autoptr(GError) error = NULL;
307   g_autoptr(GVariant) reply = NULL;
308   guint pid;
309 
310   g_assert (G_IS_DBUS_CONNECTION (connection));
311   g_assert (G_IS_ASYNC_RESULT (result));
312   g_assert (DSPY_IS_NAME (self));
313 
314   if (!(reply = g_dbus_connection_call_finish (connection, result, &error)))
315     return;
316 
317   g_variant_get (reply, "(u)", &pid);
318 
319   if (self->pid != pid)
320     {
321       self->pid = pid;
322       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PID]);
323     }
324 }
325 
326 void
_dspy_name_refresh_pid(DspyName * self,GDBusConnection * connection)327 _dspy_name_refresh_pid (DspyName        *self,
328                         GDBusConnection *connection)
329 {
330   g_return_if_fail (DSPY_IS_NAME (self));
331   g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
332 
333   g_dbus_connection_call (connection,
334                           "org.freedesktop.DBus",
335                           "/org/freedesktop/DBus",
336                           "org.freedesktop.DBus",
337                           "GetConnectionUnixProcessID",
338                           g_variant_new ("(s)", self->name),
339                           G_VARIANT_TYPE ("(u)"),
340                           G_DBUS_CALL_FLAGS_NONE,
341                           -1,
342                           NULL,
343                           dspy_name_get_pid_cb,
344                           g_object_ref (self));
345 }
346 
347 static void
dspy_name_get_owner_cb(GObject * object,GAsyncResult * result,gpointer user_data)348 dspy_name_get_owner_cb (GObject      *object,
349                         GAsyncResult *result,
350                         gpointer      user_data)
351 {
352   GDBusConnection *connection = (GDBusConnection *)object;
353   g_autoptr(DspyName) self = user_data;
354   g_autoptr(GError) error = NULL;
355   g_autoptr(GVariant) reply = NULL;
356   const gchar *owner = NULL;
357 
358   g_assert (G_IS_DBUS_CONNECTION (connection));
359   g_assert (G_IS_ASYNC_RESULT (result));
360   g_assert (DSPY_IS_NAME (self));
361 
362   if (!(reply = g_dbus_connection_call_finish (connection, result, &error)))
363     return;
364 
365   g_variant_get (reply, "(&s)", &owner);
366   _dspy_name_set_owner (self, owner);
367 }
368 
369 void
_dspy_name_refresh_owner(DspyName * self,GDBusConnection * connection)370 _dspy_name_refresh_owner (DspyName        *self,
371                           GDBusConnection *connection)
372 {
373   g_return_if_fail (DSPY_IS_NAME (self));
374   g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
375 
376   g_clear_pointer (&self->owner, g_free);
377 
378   /* If the name is already a :0.123 style name, that's the owner */
379   if (self->name[0] == ':')
380     return;
381 
382   g_dbus_connection_call (connection,
383                           "org.freedesktop.DBus",
384                           "/org/freedesktop/DBus",
385                           "org.freedesktop.DBus",
386                           "GetNameOwner",
387                           g_variant_new ("(s)", self->name),
388                           G_VARIANT_TYPE ("(s)"),
389                           G_DBUS_CALL_FLAGS_NONE,
390                           -1,
391                           NULL,
392                           dspy_name_get_owner_cb,
393                           g_object_ref (self));
394 }
395 
396 /**
397  * dspy_name_get_connection:
398  *
399  * Gets the connection that is to be used.
400  *
401  * Returns: (transer none): a #DspyConnection or %NULL
402  */
403 DspyConnection *
dspy_name_get_connection(DspyName * self)404 dspy_name_get_connection (DspyName *self)
405 {
406   g_return_val_if_fail (DSPY_IS_NAME (self), NULL);
407 
408   return self->connection;
409 }
410 
411 static void
dspy_name_introspection_cb(GObject * object,GAsyncResult * result,gpointer user_data)412 dspy_name_introspection_cb (GObject      *object,
413                             GAsyncResult *result,
414                             gpointer      user_data)
415 {
416   GAsyncInitable *initable = (GAsyncInitable *)object;
417   g_autoptr(GError) error = NULL;
418   g_autoptr(GTask) task = user_data;
419 
420   g_assert (G_IS_ASYNC_INITABLE (initable));
421   g_assert (G_IS_ASYNC_RESULT (result));
422   g_assert (G_IS_TASK (task));
423 
424   if (!g_async_initable_init_finish (initable, result, &error))
425     g_task_return_error (task, g_steal_pointer (&error));
426   else
427     g_task_return_pointer (task, g_object_ref (initable), g_object_unref);
428 }
429 
430 void
dspy_name_introspect_async(DspyName * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)431 dspy_name_introspect_async (DspyName            *self,
432                             GCancellable        *cancellable,
433                             GAsyncReadyCallback  callback,
434                             gpointer             user_data)
435 {
436   g_autoptr(GTask) task = NULL;
437   g_autoptr(DspyIntrospectionModel) model = NULL;
438 
439   g_return_if_fail (DSPY_IS_NAME (self));
440   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
441 
442   task = g_task_new (self, cancellable, callback, user_data);
443   g_task_set_source_tag (task, dspy_name_introspect_async);
444 
445   model = _dspy_introspection_model_new (self);
446 
447   g_async_initable_init_async (G_ASYNC_INITABLE (model),
448                                G_PRIORITY_DEFAULT,
449                                cancellable,
450                                dspy_name_introspection_cb,
451                                g_steal_pointer (&task));
452 
453 }
454 
455 /**
456  * dspy_name_introspect_finish:
457  *
458  * Completes a request to dspy_name_introspect_async().
459  *
460  * Returns: (transfer full): a #GtkTreeModel if successful; otherwise
461  *   %NULL and @error is set.
462  */
463 GtkTreeModel *
dspy_name_introspect_finish(DspyName * self,GAsyncResult * result,GError ** error)464 dspy_name_introspect_finish (DspyName      *self,
465                              GAsyncResult  *result,
466                              GError       **error)
467 {
468   g_return_val_if_fail (DSPY_IS_NAME (self), NULL);
469   g_return_val_if_fail (G_IS_TASK (result), NULL);
470 
471   return g_task_propagate_pointer (G_TASK (result), error);
472 }
473 
474 const gchar *
dspy_name_get_search_text(DspyName * self)475 dspy_name_get_search_text (DspyName *self)
476 {
477   g_return_val_if_fail (DSPY_IS_NAME (self), FALSE);
478 
479   if (self->search_text == NULL)
480     {
481       const gchar *owner = dspy_name_get_owner (self);
482       self->search_text = g_strdup_printf ("%s %s %d", self->name, owner, self->pid);
483     }
484 
485   return self->search_text;
486 }
487