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