From: David Woodhouse Date: Wed, 21 Mar 2018 20:52:45 +0000 (+0000) Subject: Initial import of media state tracking shell X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=7fb1286015f07dc704c5d235a04716cf56a51271;p=users%2Fdwmw2%2Fpidgin-headset.git Initial import of media state tracking shell --- 7fb1286015f07dc704c5d235a04716cf56a51271 diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..85e5eb4 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,12 @@ +# @PIDGIN_PLUGIN_RULE@ + +pidgin_plugin_LTLIBRARIES = headset.la + +headset_la_CFLAGS = $(PIDGIN_CFLAGS) + +headset_la_SOURCES = headset.c jabra.c headset.h + +headset_la_LDFLAGS = -module -avoid-version -no-undefined + +headset_la_LIBADD = $(PIDGIN_LIBS) + diff --git a/acinclude.m4 b/acinclude.m4 new file mode 100644 index 0000000..1c74267 --- /dev/null +++ b/acinclude.m4 @@ -0,0 +1,61 @@ +dnl as-compiler-flag.m4 0.1.0 + +dnl autostars m4 macro for detection of compiler flags + +dnl David Schleef + +dnl $Id: as-compiler-flag.m4,v 1.1 2005/12/15 23:35:19 ds Exp $ + +dnl AS_COMPILER_FLAG(CFLAGS, ACTION-IF-ACCEPTED, [ACTION-IF-NOT-ACCEPTED]) +dnl Tries to compile with the given CFLAGS. +dnl Runs ACTION-IF-ACCEPTED if the compiler can compile with the flags, +dnl and ACTION-IF-NOT-ACCEPTED otherwise. + +AC_DEFUN([AS_COMPILER_FLAG], +[ + AC_MSG_CHECKING([to see if compiler understands $1]) + + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $1" + + AC_TRY_COMPILE([ ], [], [flag_ok=yes], [flag_ok=no]) + CFLAGS="$save_CFLAGS" + + if test "X$flag_ok" = Xyes ; then + m4_ifvaln([$2],[$2]) + true + else + m4_ifvaln([$3],[$3]) + true + fi + AC_MSG_RESULT([$flag_ok]) +]) + +dnl AS_COMPILER_FLAGS(VAR, FLAGS) +dnl Tries to compile with the given CFLAGS. + +AC_DEFUN([AS_COMPILER_FLAGS], +[ + list=$2 + flags_supported="" + flags_unsupported="" + AC_MSG_CHECKING([for supported compiler flags]) + for each in $list + do + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $each" + AC_TRY_COMPILE([ ], [], [flag_ok=yes], [flag_ok=no]) + CFLAGS="$save_CFLAGS" + + if test "X$flag_ok" = Xyes ; then + flags_supported="$flags_supported $each" + else + flags_unsupported="$flags_unsupported $each" + fi + done + AC_MSG_RESULT([$flags_supported]) + if test "X$flags_unsupported" != X ; then + AC_MSG_WARN([unsupported compiler flags: $flags_unsupported]) + fi + $1="$$1 $flags_supported" +]) diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..fba4adc --- /dev/null +++ b/autogen.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# Run this to generate all the initial makefiles, etc. + +test -n "$srcdir" || srcdir=`dirname "$0"` +test -n "$srcdir" || srcdir=. + +OLDDIR=`pwd` +cd $srcdir + +AUTORECONF=`which autoreconf` +if test -z $AUTORECONF; then + echo "*** No autoreconf found, please install it ***" + exit 1 +fi + +aclocal --install -I m4 || exit 1 +autoreconf --force --install --verbose || exit 1 + +cd $OLDDIR +test -n "$NOCONFIGURE" || "$srcdir/configure" "$@" diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..7716c59 --- /dev/null +++ b/configure.ac @@ -0,0 +1,51 @@ +AC_INIT(pidgin-headset, 0.01) + +PKG_PROG_PKG_CONFIG +AC_LANG_C +AC_CANONICAL_HOST +AC_DISABLE_STATIC +AM_MAINTAINER_MODE([enable]) +AM_INIT_AUTOMAKE([foreign tar-ustar subdir-objects]) +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) +LT_INIT + +AC_PREREQ([2.62], [], [AC_SUBST([localedir], ['$(datadir)/locale'])]) + +AS_COMPILER_FLAGS(WFLAGS, + "-Wall + -Wextra + -Wno-missing-field-initializers + -Wno-sign-compare + -Wno-unused-parameter + -Werror=pointer-to-int-cast + -Wdeclaration-after-statement + -Werror-implicit-function-declaration + -Wformat-nonliteral + -Wformat-security + -Winit-self + -Wmissing-declarations + -Wmissing-include-dirs + -Wnested-externs + -Wpointer-arith + -Wwrite-strings + -Wno-declaration-after-statement") +AC_SUBST(WFLAGS, [$WFLAGS]) + +pidgin_plugindir= +PKG_CHECK_MODULES(PIDGIN, [pidgin]) +pidgin_plugindir="$($PKG_CONFIG --variable=plugindir pidgin)" +AC_SUBST(pidgin_plugindir, ${pidgin_plugindir}) + +AC_SUBST([CONFIG_STATUS_DEPENDENCIES], + ['$(top_srcdir)/po/LINGUAS']) + +RAWLINGUAS=`sed -e "/^#/d" -e "s/#.*//" "${srcdir}/po/LINGUAS"` +# Remove newlines +LINGUAS=`echo $RAWLINGUAS` +AC_SUBST(LINGUAS) + +AC_CONFIG_FILES([ + Makefile + po/Makefile + ]) +AC_OUTPUT diff --git a/headset.c b/headset.c new file mode 100644 index 0000000..7ef8139 --- /dev/null +++ b/headset.c @@ -0,0 +1,262 @@ +/* + * Pidgin plugin for USB headset management + * + * Copyright © 2018 David Woodhouse. + * + * Author: David Woodhouse + * + * 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 +#include +#include + +#define PURPLE_PLUGINS +#include +#include +#include +#include + +#include +#include + +#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, /**", /**< 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) diff --git a/headset.h b/headset.h new file mode 100644 index 0000000..8e98030 --- /dev/null +++ b/headset.h @@ -0,0 +1,30 @@ +/* + * Pidgin plugin for USB headset management + * + * Copyright © 2018 David Woodhouse. + * + * Author: David Woodhouse + * + * 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. + */ + +#ifndef __HEADSET_H__ +#define __HEADSET_H__ + +/* headset.c */ +void headset_muted(gpointer user_data, gboolean mute); +void headset_connected(gpointer user_data, gboolean connected); + + +/* jabra.c */ +void mute_headset(gpointer user_data, gboolean muted); +void connect_headset(gpointer user_data, gboolean connected); + +#endif /* __HEADSET_H__ */ diff --git a/jabra.c b/jabra.c new file mode 100644 index 0000000..c9d54ea --- /dev/null +++ b/jabra.c @@ -0,0 +1,32 @@ +/* + * Pidgin plugin for USB headset management + * + * Copyright © 2018 David Woodhouse. + * + * Author: David Woodhouse + * + * 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. + */ + +#define PURPLE_PLUGINS +#include + +#include "headset.h" + +void mute_headset(gpointer user_data, gboolean muted) +{ + purple_debug(PURPLE_DEBUG_INFO, "headset", "mute %d\n", muted); +} + +void connect_headset(gpointer user_data, gboolean muted) +{ + purple_debug(PURPLE_DEBUG_INFO, "headset", "connect %d\n", muted); +} + diff --git a/po/LINGUAS b/po/LINGUAS new file mode 100644 index 0000000..daa2955 --- /dev/null +++ b/po/LINGUAS @@ -0,0 +1 @@ +en_GB diff --git a/po/Makefile.am b/po/Makefile.am new file mode 100644 index 0000000..90b5b4a --- /dev/null +++ b/po/Makefile.am @@ -0,0 +1,41 @@ + +LINGUAS = @LINGUAS@ +MOFILES = $(LINGUAS:%=%.mo) +POFILES = $(LINGUAS:%=%.po) + +noinst_DATA = $(MOFILES) + +SUFFIXES = .mo + +.po.mo: + rm -f && $(MSGFMT) -o $@ $< + +clean-local: + rm -f $(MOFILES) + +install-data-hook: all + linguas="$(LINGUAS)"; \ + for l in $$linguas; do \ + dir="$(DESTDIR)$(localedir)/$$l/LC_MESSAGES"; \ + $(mkdir_p) $$dir; \ + echo Installing $$l.mo to $$dir/$(PACKAGE).mo ; \ + $(INSTALL_DATA) $$l.mo $$dir/$(PACKAGE).mo; \ + done + +uninstall-hook: + linguas="$(LINGUAS)"; \ + for l in $$linguas; do \ + file="$(DESTDIR)$(localedir)/$$l/LC_MESSAGES/$(PACKAGE).mo"; \ + if [ -r "$$file" ]; then \ + echo "Removing $$file"; rm -f "$$file"; \ + fi ; \ + done + +# $(PACKAGE).pot is built by a rule in the parent directory Makefile +# This rule isn't needed but is here for convenience if manually invoked +.PHONY: $(PACKAGE).pot +$(PACKAGE).pot: + $(MAKE) -C .. po/$@ + +EXTRA_DIST = $(POFILES) LINGUAS +DISTCLEANFILES=$(PACKAGE).pot