From 7fb1286015f07dc704c5d235a04716cf56a51271 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Wed, 21 Mar 2018 20:52:45 +0000 Subject: [PATCH] Initial import of media state tracking shell --- Makefile.am | 12 +++ acinclude.m4 | 61 ++++++++++++ autogen.sh | 20 ++++ configure.ac | 51 ++++++++++ headset.c | 262 +++++++++++++++++++++++++++++++++++++++++++++++++ headset.h | 30 ++++++ jabra.c | 32 ++++++ po/LINGUAS | 1 + po/Makefile.am | 41 ++++++++ 9 files changed, 510 insertions(+) create mode 100644 Makefile.am create mode 100644 acinclude.m4 create mode 100755 autogen.sh create mode 100644 configure.ac create mode 100644 headset.c create mode 100644 headset.h create mode 100644 jabra.c create mode 100644 po/LINGUAS create mode 100644 po/Makefile.am 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 -- 2.50.1