--- /dev/null
+/*
+ * Pidgin plugin for USB headset management
+ *
+ * Copyright © 2018 David Woodhouse.
+ *
+ * Author: David Woodhouse <dwmw2@infradead.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program 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.
+ */
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <glib/gi18n.h>
+
+#define PURPLE_PLUGINS
+#include <debug.h>
+#include <version.h>
+#include <mediamanager.h>
+#include <request.h>
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include "headset.h"
+
+/** Plugin id : type-author-name (to guarantee uniqueness) */
+#define SIMPLE_PLUGIN_ID "media-dwmw2-headset"
+static void
+headset_show_about_plugin(PurplePluginAction *action)
+{
+ purple_notify_formatted(action->context,
+ NULL, _("Headset media plugin"), PACKAGE_STRING, _("Beep Beep"),
+ NULL, NULL);
+}
+
+static GList*
+headset_plugin_actions(PurplePlugin *plugin,
+ gpointer context)
+{
+ PurplePluginAction *act;
+ GList *acts = NULL;
+
+ act = purple_plugin_action_new(_("About headset plugin..."),
+ headset_show_about_plugin);
+ acts = g_list_append(acts, act);
+
+ return acts;
+}
+
+typedef gboolean (*media_cb)(PurpleMedia *media, const gchar *sess_id, gpointer user_data);
+static gboolean foreach_media(media_cb fn, gpointer user_data)
+{
+ PurpleMediaManager *mgr = purple_media_manager_get();
+ GList *medias = purple_media_manager_get_media(mgr);
+ gboolean ret = FALSE;
+
+ for (; medias; medias = medias->next) {
+ PurpleMedia *media = medias->data;
+
+ GList *sess_ids = purple_media_get_session_ids(media);
+ while (sess_ids) {
+ const gchar *sess_id = sess_ids->data;
+ sess_ids = sess_ids->next;
+ if (purple_media_get_session_type(media, sess_id) == PURPLE_MEDIA_AUDIO) {
+ ret = (*fn)(media, sess_id, user_data);
+ if (ret)
+ return ret;
+ }
+ }
+ }
+ return ret;
+}
+
+gboolean disconnect_stream(PurpleMedia *media, const gchar *sess_id, gpointer user_data)
+{
+ purple_debug(PURPLE_DEBUG_INFO, "headset", "disconnecting %s\n", sess_id);
+ purple_media_end(media, sess_id, NULL);
+ return FALSE;
+}
+
+void headset_connected(gpointer user_data, gboolean connected)
+{
+ if (!connected)
+ foreach_media(disconnect_stream, user_data);
+ /* Once we can handle ringing, we can do ACCEPT here too */
+}
+
+gboolean mute_stream(PurpleMedia *media, const gchar *sess_id, gpointer user_data)
+{
+ purple_debug(PURPLE_DEBUG_INFO, "headset", "muting %s\n", sess_id);
+ purple_media_stream_info(media, PURPLE_MEDIA_INFO_MUTE, sess_id, NULL, TRUE);
+ return FALSE;
+}
+
+gboolean unmute_stream(PurpleMedia *media, const gchar *sess_id, gpointer user_data)
+{
+ purple_debug(PURPLE_DEBUG_INFO, "headset", "unmuting %s\n", sess_id);
+ purple_media_stream_info(media, PURPLE_MEDIA_INFO_UNMUTE, sess_id, NULL, TRUE);
+ return FALSE;
+}
+
+void headset_muted(gpointer user_data, gboolean muted)
+{
+ if (muted)
+ foreach_media(mute_stream, user_data);
+ else
+ foreach_media(unmute_stream, user_data);
+}
+
+static void headset_media_stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type, gchar *id,
+ const gchar *participant, gboolean local, gpointer user_data)
+{
+ if (id && purple_media_get_session_type(media, id) != PURPLE_MEDIA_AUDIO)
+ return;
+
+ gpointer klass = g_type_class_ref(purple_media_info_type_get_type());
+ GEnumValue *val = g_enum_get_value(klass, type);
+ purple_debug(PURPLE_DEBUG_INFO, "headset", "media stream-info: %s %s %s %d\n",
+ val->value_name, id, participant, local);
+ g_type_class_unref(klass);
+
+ switch (type) {
+ case PURPLE_MEDIA_INFO_MUTE:
+ mute_headset(user_data, TRUE);
+ break;
+
+ case PURPLE_MEDIA_INFO_UNMUTE:
+ mute_headset(user_data, FALSE);
+ break;
+ }
+}
+
+static gboolean media_exists_fn(PurpleMedia *media, const gchar *sess_id, gpointer user_data)
+{
+ return TRUE;
+}
+
+
+static gboolean terminate_media(gpointer user_data)
+{
+ headset_connected(user_data, FALSE);
+ return FALSE;
+}
+
+static void headset_media_state_changed_cb(PurpleMedia *media, PurpleMediaState state, const gchar *id,
+ const gchar *participant, gpointer user_data)
+{
+ if (id && purple_media_get_session_type(media, id) != PURPLE_MEDIA_AUDIO)
+ return;
+
+ gpointer klass = g_type_class_ref(purple_media_state_changed_get_type());
+ GEnumValue *val = g_enum_get_value(klass, state);
+ purple_debug(PURPLE_DEBUG_INFO, "headset", "media state-changed: %s %s %s\n",
+ val->value_name, id, participant);
+ g_type_class_unref(klass);
+
+ switch (state) {
+ case PURPLE_MEDIA_STATE_NEW:
+ connect_headset(user_data, TRUE);
+ break;
+
+ case PURPLE_MEDIA_STATE_END:
+ if (!foreach_media(media_exists_fn, NULL))
+ connect_headset(user_data, FALSE);
+ break;
+ }
+}
+
+
+static gboolean
+headset_init_media_cb(PurpleMediaManager *mgr, PurpleMedia *media, PurpleAccount *account,
+ const gchar *remote_user, gpointer user_data)
+{
+ purple_debug(PURPLE_DEBUG_INFO, "headset", "media created for %s\n", remote_user);
+ g_signal_connect(media, "state-changed", G_CALLBACK(headset_media_state_changed_cb), user_data);
+ g_signal_connect(media, "stream-info", G_CALLBACK(headset_media_stream_info_cb), user_data);
+
+ return TRUE;
+}
+
+static gboolean
+headset_plugin_load(PurplePlugin *plugin)
+{
+ PurpleMediaManager *mgr = purple_media_manager_get();
+ g_signal_connect(mgr, "init-media", G_CALLBACK(headset_init_media_cb), NULL);
+
+ purple_debug(PURPLE_DEBUG_INFO, "headset", "headset plugin loaded.\n");
+ return TRUE;
+}
+
+static gboolean
+headset_plugin_unload(PurplePlugin *plugin)
+{
+ PurpleMediaManager *mgr = purple_media_manager_get();
+
+ g_signal_handlers_disconnect_matched(mgr, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, G_CALLBACK(headset_init_media_cb), NULL);
+
+ GList *medias = purple_media_manager_get_media(mgr);
+ gboolean ret = FALSE;
+
+ for (; medias; medias = medias->next) {
+ PurpleMedia *media = medias->data;
+
+ g_signal_handlers_disconnect_matched(media, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, G_CALLBACK(headset_media_state_changed_cb), NULL);
+ g_signal_handlers_disconnect_matched(media, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, G_CALLBACK(headset_media_stream_info_cb), NULL);
+
+ }
+ return ret;
+
+ purple_debug(PURPLE_DEBUG_INFO, "headset", "headset plugin unloaded.\n");
+ return TRUE;
+}
+
+static void
+headset_plugin_destroy(PurplePlugin *plugin)
+{
+}
+
+static PurplePluginInfo info =
+{
+ PURPLE_PLUGIN_MAGIC,
+ PURPLE_MAJOR_VERSION, /**<major version */
+ PURPLE_MINOR_VERSION, /**<minor version */
+ PURPLE_PLUGIN_STANDARD, /**< type */
+ NULL, /**< ui_requirement */
+ 0, /**< flags */
+ NULL, /**< dependencies */
+ PURPLE_PRIORITY_DEFAULT, /**< priority */
+ "headset", /**< id */
+ "Headset", /**< name */
+ PACKAGE_VERSION, /**< version */
+ "Headset control.", /**< summary */
+ "Controls headsets ", /**< description */
+ "David Woodhouse <dwmw2@infradead.org>", /**< author */
+ "www.infradead.org", /**< homepage */
+ headset_plugin_load, /**< load */
+ headset_plugin_unload, /**< unload */
+ headset_plugin_destroy, /**< destroy */
+ NULL, /**< ui_info */
+ NULL, /**< extra_info */
+ NULL, /**< prefs_info */
+ headset_plugin_actions, /**< actions */
+
+ /* padding */
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+static void
+init_plugin(PurplePlugin *plugin)
+{
+}
+PURPLE_INIT_PLUGIN(headset_plugin, init_plugin, info)