module-default-profile.c 9.18 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>
#include <pipewire/pipewire.h>

#define STATE_NAME "default-profile"
#define SAVE_INTERVAL_MS 1000

G_DEFINE_QUARK (wp-module-default-profile-profiles, profiles);

/* Signals */
enum
{
  SIGNAL_GET_PROFILE,
  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

G_DECLARE_DERIVABLE_TYPE (WpDefaultProfile, wp_default_profile, WP,
    DEFAULT_PROFILE, WpPlugin)

struct _WpDefaultProfileClass
{
  WpPluginClass parent_class;

33
  void (*get_profile) (WpDefaultProfile *self, WpPipewireObject *device,
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
      const char **curr_profile);
};

typedef struct _WpDefaultProfilePrivate WpDefaultProfilePrivate;
struct _WpDefaultProfilePrivate
{
  WpState *state;
  WpProperties *profiles;
  GSource *timeout_source;

  WpObjectManager *devices_om;
};

G_DEFINE_TYPE_WITH_PRIVATE (WpDefaultProfile, wp_default_profile,
    WP_TYPE_PLUGIN)

static gint
51
find_device_profile (WpPipewireObject *device, const gchar *lookup_name)
52
53
54
55
56
57
58
59
60
61
62
63
64
65
{
  WpIterator *profiles = NULL;
  g_auto (GValue) item = G_VALUE_INIT;

  profiles = g_object_get_qdata (G_OBJECT (device), profiles_quark ());
  g_return_val_if_fail (profiles, -1);

  wp_iterator_reset (profiles);
  for (; wp_iterator_next (profiles, &item); g_value_unset (&item)) {
    WpSpaPod *pod = g_value_get_boxed (&item);
    gint index = 0;
    const gchar *name = NULL;

    /* Parse */
George Kiagiadakis's avatar
George Kiagiadakis committed
66
    if (!wp_spa_pod_get_object (pod, NULL,
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
        "index", "i", &index,
        "name", "s", &name,
        NULL)) {
      continue;
    }

    if (g_strcmp0 (name, lookup_name) == 0)
      return index;
  }

  return -1;
}

static gboolean
timeout_save_callback (WpDefaultProfile *self)
{
  WpDefaultProfilePrivate *priv =
      wp_default_profile_get_instance_private (self);

86
  if (!wp_state_save (priv->state, "group", priv->profiles))
87
88
89
90
91
92
93
94
95
96
    wp_warning_object (self, "could not save profiles");

  return G_SOURCE_REMOVE;
}

static void
timeout_save_profiles (WpDefaultProfile *self, guint ms)
{
  WpDefaultProfilePrivate *priv =
      wp_default_profile_get_instance_private (self);
97
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113

  g_return_if_fail (core);
  g_return_if_fail (priv->profiles);

  /* Clear the current timeout callback */
  if (priv->timeout_source)
      g_source_destroy (priv->timeout_source);
  g_clear_pointer (&priv->timeout_source, g_source_unref);

  /* Add the timeout callback */
  wp_core_timeout_add_closure (core, &priv->timeout_source, ms,
      g_cclosure_new_object (G_CALLBACK (timeout_save_callback),
      G_OBJECT (self)));
}

static void
114
115
wp_default_profile_get_profile (WpDefaultProfile *self,
    WpPipewireObject *device, const gchar **curr_profile)
116
117
118
119
120
121
122
123
124
125
{
  WpDefaultProfilePrivate *priv =
      wp_default_profile_get_instance_private (self);
  const gchar *dev_name = NULL;

  g_return_if_fail (device);
  g_return_if_fail (curr_profile);
  g_return_if_fail (priv->profiles);

  /* Get the device name */
126
  dev_name = wp_pipewire_object_get_property (device, PW_KEY_DEVICE_NAME);
127
128
129
130
131
132
133
  g_return_if_fail (dev_name);

  /* Get the profile */
  *curr_profile = wp_properties_get (priv->profiles, dev_name);
}

static void
134
update_profile (WpDefaultProfile *self, WpPipewireObject *device,
135
136
137
138
139
140
141
142
143
144
145
    const gchar *new_profile)
{
  WpDefaultProfilePrivate *priv =
      wp_default_profile_get_instance_private (self);
  const gchar *dev_name, *curr_profile = NULL;
  gint index;

  g_return_if_fail (new_profile);
  g_return_if_fail (priv->profiles);

  /* Get the device name */
146
  dev_name = wp_pipewire_object_get_property (device, PW_KEY_DEVICE_NAME);
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
  g_return_if_fail (dev_name);

  /* Check if the new profile is the same as the current one */
  curr_profile = wp_properties_get (priv->profiles, dev_name);
  if (curr_profile && g_strcmp0 (curr_profile, new_profile) == 0)
    return;

  /* Make sure the profile is valid */
  index = find_device_profile (device, new_profile);
  if (index < 0) {
    wp_info_object (self, "profile '%s' (%d) is not valid on device '%s'",
        new_profile, index, dev_name);
    return;
  }

  /* Otherwise update the profile and add timeout save callback */
  wp_properties_set (priv->profiles, dev_name, new_profile);
  timeout_save_profiles (self, SAVE_INTERVAL_MS);

  wp_info_object (self, "updated profile '%s' (%d) on device '%s'", new_profile,
      index, dev_name);
}

static void
171
on_device_profile_notified (WpPipewireObject *device, GAsyncResult *res,
172
173
174
175
176
177
178
179
180
    WpDefaultProfile *self)
{
  g_autoptr (WpIterator) profiles = NULL;
  g_autoptr (GError) error = NULL;
  g_auto (GValue) item = G_VALUE_INIT;
  const gchar *name = NULL;
  gint index = 0;

  /* Finish */
181
  profiles = wp_pipewire_object_enum_params_finish (device, res, &error);
182
  if (error) {
183
184
    wp_warning_object (self, "failed to get current profile on device: %s",
        error->message);
185
186
187
188
189
190
191
192
193
    return;
  }

  /* Ignore empty profile notifications */
  if (!wp_iterator_next (profiles, &item))
    return;

  /* Parse the profile */
  WpSpaPod *pod = g_value_get_boxed (&item);
George Kiagiadakis's avatar
George Kiagiadakis committed
194
  if (!wp_spa_pod_get_object (pod, NULL,
195
196
197
198
199
200
201
202
203
204
      "index", "i", &index,
      "name", "s", &name,
      NULL)) {
    wp_warning_object (self, "failed to parse current profile");
    return;
  }

  g_value_unset (&item);

  /* Update the profile */
205
  update_profile (self, device, name);
206
207
208
}

static void
209
on_device_param_info_notified (WpPipewireObject * device, GParamSpec * param,
210
211
212
    WpDefaultProfile *self)
{
  /* Check the profile every time the params have changed */
213
  wp_pipewire_object_enum_params (device, "Profile", NULL, NULL,
214
215
216
217
      (GAsyncReadyCallback) on_device_profile_notified, self);
}

static void
218
on_device_added (WpObjectManager *om, WpPipewireObject *proxy, gpointer d)
219
{
220
  WpDefaultProfile *self = WP_DEFAULT_PROFILE (d);
221
222
  g_autoptr (WpIterator) profiles = NULL;

223
224
225
226
227
228
  wp_debug_object (self, "device " WP_OBJECT_FORMAT " added",
      WP_OBJECT_ARGS (proxy));

  /* Enum available profiles */
  profiles = wp_pipewire_object_enum_params_sync (proxy, "EnumProfile", NULL);
  if (!profiles)
229
230
231
    return;

  /* Keep a reference of the profiles in the device object */
232
  g_object_set_qdata_full (G_OBJECT (proxy), profiles_quark (),
233
234
235
        g_steal_pointer (&profiles), (GDestroyNotify) wp_iterator_unref);

  /* Watch for param info changes */
236
  g_signal_connect_object (proxy, "notify::param-info",
237
238
239
240
      G_CALLBACK (on_device_param_info_notified), self, 0);
}

static void
241
wp_default_profile_enable (WpPlugin * plugin, WpTransition * transition)
242
{
243
  g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (plugin));
244
245
246
247
248
249
250
  WpDefaultProfile *self = WP_DEFAULT_PROFILE (plugin);
  WpDefaultProfilePrivate *priv =
      wp_default_profile_get_instance_private (self);

  /* Create the devices object manager */
  priv->devices_om = wp_object_manager_new ();
  wp_object_manager_add_interest (priv->devices_om, WP_TYPE_DEVICE, NULL);
251
  wp_object_manager_request_object_features (priv->devices_om,
252
      WP_TYPE_DEVICE, WP_PIPEWIRE_OBJECT_FEATURES_ALL);
253
254
255
  g_signal_connect_object (priv->devices_om, "object-added",
      G_CALLBACK (on_device_added), self, 0);
  wp_core_install_object_manager (core, priv->devices_om);
256
257

  wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0);
258
259
260
}

static void
261
wp_default_profile_disable (WpPlugin * plugin)
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
{
  WpDefaultProfile *self = WP_DEFAULT_PROFILE (plugin);
  WpDefaultProfilePrivate *priv =
      wp_default_profile_get_instance_private (self);

  g_clear_object (&priv->devices_om);
}

static void
wp_default_profile_finalize (GObject * object)
{
  WpDefaultProfile *self = WP_DEFAULT_PROFILE (object);
  WpDefaultProfilePrivate *priv =
      wp_default_profile_get_instance_private (self);

  /* Clear the current timeout callback */
  if (priv->timeout_source)
      g_source_destroy (priv->timeout_source);
  g_clear_pointer (&priv->timeout_source, g_source_unref);

  g_clear_pointer (&priv->profiles, wp_properties_unref);
  g_clear_object (&priv->state);
}

static void
wp_default_profile_init (WpDefaultProfile * self)
{
  WpDefaultProfilePrivate *priv =
      wp_default_profile_get_instance_private (self);

  priv->state = wp_state_new (STATE_NAME);

  /* Load the saved profiles */
295
  priv->profiles = wp_state_load (priv->state, "group");
296
297
298
299
300
301
302
303
304
305
306
307
308
  if (!priv->profiles) {
    wp_warning_object (self, "could not load profiles");
    return;
  }
}

static void
wp_default_profile_class_init (WpDefaultProfileClass * klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpPluginClass *plugin_class = (WpPluginClass *) klass;

  object_class->finalize = wp_default_profile_finalize;
309
310
  plugin_class->enable = wp_default_profile_enable;
  plugin_class->disable = wp_default_profile_disable;
311
312
313
314
315
316
317
318
319
320
321

  klass->get_profile = wp_default_profile_get_profile;

  /* Signals */
  signals[SIGNAL_GET_PROFILE] = g_signal_new ("get-profile",
      G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
      G_STRUCT_OFFSET (WpDefaultProfileClass, get_profile), NULL, NULL,
      NULL, G_TYPE_NONE, 2, WP_TYPE_DEVICE, G_TYPE_POINTER);
}

322
323
WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
324
325
326
{
  wp_plugin_register (g_object_new (wp_default_profile_get_type (),
      "name", STATE_NAME,
327
      "core", core,
328
      NULL));
329
  return TRUE;
330
}