pkgsrc-Users archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

playing with NetBSD, here is a pkgsrc that insert modules into running pulseaudio using devpubd



the file is in shar file, one can drop into /usr/pkgsrc/audio, and do a make && make install than
one can plug a usb audio, and if your pulseaudio was compiled with dbus,
it will accept sinks (audio) on the fly...

####################################################
# This is a shell archive. Save it in a file, remove anything before
# this line, and then unpack it by entering "sh file". Note, it may
# create directories; files and directories will be owned by you and
# have default permissions.
#
# This archive contains:
#
# pa-hotplug
# pa-hotplug/README
# pa-hotplug/DEINSTALL
# pa-hotplug/DESCR
# pa-hotplug/INSTALL
# pa-hotplug/files
# pa-hotplug/files/pa-hotplug.c
# pa-hotplug/files/pa-hotplug.desktop
# pa-hotplug/files/pa-hotplug-dbus.conf
# pa-hotplug/files/Makefile
# pa-hotplug/files/devpubd-hook.sh
# pa-hotplug/Makefile
# pa-hotplug/PLIST
# pa-hotplug/distinfo
# pa-hotplug/.config
# pa-hotplug/.config/pulse
#
echo c - pa-hotplug
mkdir -p pa-hotplug > /dev/null 2>&1
echo x - pa-hotplug/README
sed 's/^X//' >pa-hotplug/README << 'END-of-pa-hotplug/README'
Xpa-hotplug - Bridge devpubd audio events to PulseAudio via D-Bus
X
XINSTALL
X-------
X
XCopy this directory into your pkgsrc tree and build:
X
X cp -R pa-hotplug /usr/pkgsrc/audio/pa-hotplug
X cd /usr/pkgsrc/audio/pa-hotplug
X make install
X
XOr build from any location:
X
X cd pa-hotplug
X make PKGSRCDIR=/usr/pkgsrc install
X
XREQUIREMENTS
X------------
X
X- NetBSD 10+
X- dbus (sysutils/dbus)
X- pulseaudio (audio/pulseaudio)
X- User must be in the "pulse" group to receive D-Bus signals
X
XHOW IT WORKS
X------------
X
X1. devpubd hook (devpubd-hook.sh) runs as root when audio devices
X attach/detach, sends D-Bus signal on the system bus.
X2. D-Bus policy (pa-hotplug-dbus.conf) allows root to send signals
X and users in pulse group to receive them.
X3. User daemon (pa-hotplug) listens for D-Bus signals via dbus-monitor,
X loads/unloads module-oss in PulseAudio via pactl.
X4. Daemon tracks parent session PID; exits when session ends.
X
XLICENSE
X-------
X
XISC
END-of-pa-hotplug/README
echo x - pa-hotplug/DEINSTALL
sed 's/^X//' >pa-hotplug/DEINSTALL << 'END-of-pa-hotplug/DEINSTALL'
X#!/bin/sh
X#
X# $NetBSD$
X#
X
Xcase ${STAGE} in
XPOST-DEINSTALL)
X dbus-send --system --type=method_call \
X --dest=org.freedesktop.DBus \
X /org/freedesktop/DBus \
X org.freedesktop.DBus.ReloadConfig \
X 2>/dev/null || true
X ;;
Xesac
END-of-pa-hotplug/DEINSTALL
echo x - pa-hotplug/DESCR
sed 's/^X//' >pa-hotplug/DESCR << 'END-of-pa-hotplug/DESCR'
XBridge between NetBSD devpubd device hotplug events and PulseAudio.
X
XWhen a USB audio device is plugged in, devpubd calls a hook script that
Xsends a D-Bus signal on the system bus. A user-level daemon listens for
Xthese signals and loads/unloads the appropriate PulseAudio OSS module.
END-of-pa-hotplug/DESCR
echo x - pa-hotplug/INSTALL
sed 's/^X//' >pa-hotplug/INSTALL << 'END-of-pa-hotplug/INSTALL'
X#!/bin/sh
X#
X# $NetBSD$
X#
X
Xcase ${STAGE} in
XPOST-INSTALL)
X dbus-send --system --type=method_call \
X --dest=org.freedesktop.DBus \
X /org/freedesktop/DBus \
X org.freedesktop.DBus.ReloadConfig \
X 2>/dev/null || true
X ;;
Xesac
END-of-pa-hotplug/INSTALL
echo c - pa-hotplug/files
mkdir -p pa-hotplug/files > /dev/null 2>&1
echo x - pa-hotplug/files/pa-hotplug.c
sed 's/^X//' >pa-hotplug/files/pa-hotplug.c << 'END-of-pa-hotplug/files/pa-hotplug.c'
X/*
X * pa-hotplug - bridge devpubd audio events to PulseAudio via D-Bus
X *
X * Listens on the system bus for org.netbsd.DeviceHotplug signals
X * and loads/unloads PulseAudio OSS modules accordingly.
X *
X * Exits when the parent session (mate-session/gdm/xdm) goes away.
X */
X
X#include <sys/types.h>
X#include <sys/stat.h>
X
X#include <ctype.h>
X#include <dirent.h>
X#include <err.h>
X#include <errno.h>
X#include <limits.h>
X#include <signal.h>
X#include <stdio.h>
X#include <stdlib.h>
X#include <string.h>
X#include <syslog.h>
X#include <unistd.h>
X#include <util.h>
X
X#include <dbus/dbus.h>
X
X#define MAX_MODULES 16
X#define POLL_TIMEOUT 5000 /* ms */
X
X#define HOTPLUG_IFACE "org.netbsd.DeviceHotplug"
X#define HOTPLUG_PATH "/org/netbsd/DeviceHotplug"
X
X#define MATCH_RULE "type='signal',interface='" HOTPLUG_IFACE "'"
X
Xstruct module {
X char device[64];
X int index;
X};
X
Xstatic struct module modules[MAX_MODULES];
Xstatic int nmodules;
Xstatic pid_t session_pid;
Xstatic volatile sig_atomic_t quit;
Xstatic char pulse_server[PATH_MAX];
X
Xstatic void sighandler(int);
Xstatic void cleanup(void);
Xstatic void find_pulse_socket(void);
Xstatic int valid_device(const char *);
Xstatic int load_module(const char *);
Xstatic void unload_module(int);
Xstatic void handle_add(const char *);
Xstatic void handle_remove(const char *);
Xstatic int session_alive(void);
X
Xstatic DBusHandlerResult
Xsignal_filter(DBusConnection *conn, DBusMessage *msg, void *data)
X{
X const char *dev;
X const char *iface, *member, *sender;
X
X (void)conn;
X (void)data;
X
X iface = dbus_message_get_interface(msg);
X member = dbus_message_get_member(msg);
X sender = dbus_message_get_sender(msg);
X syslog(LOG_DEBUG, "dbus signal: iface=%s member=%s sender=%s",
X iface ? iface : "(null)",
X member ? member : "(null)",
X sender ? sender : "(null)");
X
X if (dbus_message_is_signal(msg, HOTPLUG_IFACE, "DeviceAdded")) {
X if (dbus_message_get_args(msg, NULL,
X DBUS_TYPE_STRING, &dev, DBUS_TYPE_INVALID)) {
X syslog(LOG_DEBUG, "DeviceAdded: dev=%s", dev);
X handle_add(dev);
X } else {
X syslog(LOG_WARNING, "DeviceAdded: failed to get args");
X }
X return (DBUS_HANDLER_RESULT_HANDLED);
X }
X
X if (dbus_message_is_signal(msg, HOTPLUG_IFACE, "DeviceRemoved")) {
X if (dbus_message_get_args(msg, NULL,
X DBUS_TYPE_STRING, &dev, DBUS_TYPE_INVALID)) {
X syslog(LOG_DEBUG, "DeviceRemoved: dev=%s", dev);
X handle_remove(dev);
X } else {
X syslog(LOG_WARNING, "DeviceRemoved: failed to get args");
X }
X return (DBUS_HANDLER_RESULT_HANDLED);
X }
X
X return (DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
X}
X
Xstatic void
Xsighandler(int sig)
X{
X (void)sig;
X quit = 1;
X}
X
Xstatic void
Xcleanup(void)
X{
X pidfile_clean();
X}
X
X/*
X * Find the PulseAudio native socket.
X * Checks /run/user/UID/pulse/native first, then scans /tmp/pulse-* dirs.
X * This avoids relying on PULSE_SERVER environment after daemon(3).
X */
Xstatic void
Xfind_pulse_socket(void)
X{
X struct stat sb;
X char path[PATH_MAX];
X DIR *dp;
X struct dirent *de;
X
X /* try XDG_RUNTIME_DIR/pulse/native */
X (void)snprintf(path, sizeof(path),
X "/run/user/%d/pulse/native", (int)getuid());
X if (stat(path, &sb) == 0 && S_ISSOCK(sb.st_mode)) {
X (void)snprintf(pulse_server, sizeof(pulse_server),
X "unix:%s", path);
X syslog(LOG_DEBUG, "pulse socket: %s", pulse_server);
X return;
X }
X
X /* scan /tmp for pulse-* directories containing native socket */
X dp = opendir("/tmp");
X if (dp != NULL) {
X while ((de = readdir(dp)) != NULL) {
X if (strncmp(de->d_name, "pulse-", 6) != 0)
X continue;
X (void)snprintf(path, sizeof(path),
X "/tmp/%s/native", de->d_name);
X if (stat(path, &sb) == 0 && S_ISSOCK(sb.st_mode)) {
X (void)snprintf(pulse_server,
X sizeof(pulse_server), "unix:%s", path);
X syslog(LOG_DEBUG, "pulse socket: %s",
X pulse_server);
X (void)closedir(dp);
X return;
X }
X }
X (void)closedir(dp);
X }
X
X /* fallback: let pactl figure it out */
X pulse_server[0] = '\0';
X syslog(LOG_WARNING, "pulse socket not found, pactl may fail");
X}
X
X/*
X * Validate device path: must be /dev/audio followed by digits only.
X */
Xstatic int
Xvalid_device(const char *dev)
X{
X const char *p;
X
X if (strncmp(dev, "/dev/audio", 10) != 0)
X return (0);
X for (p = dev + 10; *p != '\0'; p++) {
X if (!isdigit((unsigned char)*p))
X return (0);
X }
X return (1);
X}
X
Xstatic int
Xload_module(const char *dev)
X{
X FILE *fp;
X char cmd[256];
X char buf[64];
X long idx;
X int i;
X
X if (pulse_server[0] != '\0')
X (void)snprintf(cmd, sizeof(cmd),
X "pactl --server=%s load-module module-oss device=%s",
X pulse_server, dev);
X else
X (void)snprintf(cmd, sizeof(cmd),
X "pactl load-module module-oss device=%s", dev);
X syslog(LOG_DEBUG, "running: %s", cmd);
X fp = popen(cmd, "r");
X if (fp == NULL) {
X syslog(LOG_ERR, "popen failed: %s", strerror(errno));
X return (-1);
X }
X if (fgets(buf, sizeof(buf), fp) == NULL) {
X syslog(LOG_ERR, "pactl returned no output for %s", dev);
X (void)pclose(fp);
X return (-1);
X }
X i = pclose(fp);
X syslog(LOG_DEBUG, "pactl output: '%s' (exit %d)", buf, i);
X
X idx = strtol(buf, NULL, 10);
X return (idx > 0 ? (int)idx : -1);
X}
X
Xstatic void
Xunload_module(int idx)
X{
X char cmd[256];
X
X if (pulse_server[0] != '\0')
X (void)snprintf(cmd, sizeof(cmd),
X "pactl --server=%s unload-module %d",
X pulse_server, idx);
X else
X (void)snprintf(cmd, sizeof(cmd),
X "pactl unload-module %d", idx);
X syslog(LOG_DEBUG, "running: %s", cmd);
X (void)system(cmd);
X}
X
Xstatic void
Xhandle_add(const char *dev)
X{
X int i, idx;
X
X if (!valid_device(dev)) {
X syslog(LOG_DEBUG, "ignoring non-audio device: %s", dev);
X return;
X }
X
X for (i = 0; i < nmodules; i++) {
X if (strcmp(modules[i].device, dev) == 0) {
X syslog(LOG_DEBUG, "device %s already tracked", dev);
X return;
X }
X }
X if (nmodules >= MAX_MODULES) {
X syslog(LOG_WARNING, "too many modules, ignoring %s", dev);
X return;
X }
X
X syslog(LOG_DEBUG, "loading module-oss for %s", dev);
X idx = load_module(dev);
X if (idx < 0) {
X syslog(LOG_ERR, "failed to load module-oss for %s", dev);
X return;
X }
X
X strlcpy(modules[nmodules].device, dev, sizeof(modules[0].device));
X modules[nmodules].index = idx;
X nmodules++;
X syslog(LOG_INFO, "loaded module-oss for %s (index %d)", dev, idx);
X}
X
Xstatic void
Xhandle_remove(const char *dev)
X{
X int i;
X
X for (i = 0; i < nmodules; i++) {
X if (strcmp(modules[i].device, dev) == 0) {
X syslog(LOG_INFO, "unloading module %d for %s",
X modules[i].index, dev);
X unload_module(modules[i].index);
X nmodules--;
X if (i < nmodules)
X modules[i] = modules[nmodules];
X return;
X }
X }
X}
X
Xstatic int
Xsession_alive(void)
X{
X if (session_pid <= 1)
X return (1);
X return (kill(session_pid, 0) == 0);
X}
X
Xint
Xmain(int argc, char *argv[])
X{
X int i;
X pid_t otherpid;
X const char *rundir;
X char pidbuf[PATH_MAX];
X DBusError dberr;
X DBusConnection *conn;
X
X (void)argc;
X (void)argv;
X
X /* save parent PID before daemonizing */
X session_pid = getppid();
X
X rundir = getenv("XDG_RUNTIME_DIR");
X if (rundir != NULL)
X (void)snprintf(pidbuf, sizeof(pidbuf),
X "%s/pa-hotplug.pid", rundir);
X else
X (void)snprintf(pidbuf, sizeof(pidbuf),
X "/tmp/pa-hotplug-%d.pid", (int)getuid());
X
X otherpid = pidfile_lock(pidbuf);
X if (otherpid > 0)
X errx(1, "already running (pid %d)", (int)otherpid);
X if (otherpid == -1)
X err(1, "pidfile_lock");
X
X if (daemon(0, 0) == -1) {
X pidfile_clean();
X err(1, "daemon");
X }
X
X /* re-lock after daemon() to record new PID */
X (void)pidfile_lock(pidbuf);
X openlog("pa-hotplug", LOG_PID, LOG_DAEMON);
X syslog(LOG_INFO, "started, tracking session pid %d",
X (int)session_pid);
X syslog(LOG_DEBUG, "uid=%d euid=%d", (int)getuid(), (int)geteuid());
X
X find_pulse_socket();
X
X signal(SIGTERM, sighandler);
X signal(SIGINT, sighandler);
X signal(SIGHUP, sighandler);
X
X /* connect to the system bus */
X dbus_error_init(&dberr);
X conn = dbus_bus_get(DBUS_BUS_SYSTEM, &dberr);
X if (conn == NULL) {
X syslog(LOG_ERR, "dbus_bus_get: %s", dberr.message);
X dbus_error_free(&dberr);
X cleanup();
X return (1);
X }
X
X /* subscribe to hotplug signals */
X dbus_bus_add_match(conn, MATCH_RULE, &dberr);
X if (dbus_error_is_set(&dberr)) {
X syslog(LOG_ERR, "dbus_bus_add_match: %s", dberr.message);
X dbus_error_free(&dberr);
X dbus_connection_unref(conn);
X cleanup();
X return (1);
X }
X
X if (!dbus_connection_add_filter(conn, signal_filter, NULL, NULL)) {
X syslog(LOG_ERR, "dbus_connection_add_filter failed");
X dbus_connection_unref(conn);
X cleanup();
X return (1);
X }
X
X /* main loop */
X while (!quit) {
X /* dispatch pending messages, with timeout for session check */
X if (!dbus_connection_read_write(conn, POLL_TIMEOUT))
X break; /* disconnected */
X
X while (dbus_connection_dispatch(conn) ==
X DBUS_DISPATCH_DATA_REMAINS)
X ;
X
X if (!session_alive()) {
X syslog(LOG_INFO, "session pid %d gone, exiting",
X (int)session_pid);
X break;
X }
X }
X
X /* unload all modules on exit */
X for (i = 0; i < nmodules; i++) {
X syslog(LOG_INFO, "unloading module %d for %s",
X modules[i].index, modules[i].device);
X unload_module(modules[i].index);
X }
X
X dbus_connection_remove_filter(conn, signal_filter, NULL);
X dbus_connection_unref(conn);
X cleanup();
X closelog();
X return (0);
X}
END-of-pa-hotplug/files/pa-hotplug.c
echo x - pa-hotplug/files/pa-hotplug.desktop
sed 's/^X//' >pa-hotplug/files/pa-hotplug.desktop << 'END-of-pa-hotplug/files/pa-hotplug.desktop'
X[Desktop Entry]
XType=Application
XName=PA Hotplug
XComment=Bridge audio device hotplug events to PulseAudio
XExec=pa-hotplug
XTerminal=false
XNoDisplay=true
XX-MATE-Autostart-enabled=true
XOnlyShowIn=MATE;
END-of-pa-hotplug/files/pa-hotplug.desktop
echo x - pa-hotplug/files/pa-hotplug-dbus.conf
sed 's/^X//' >pa-hotplug/files/pa-hotplug-dbus.conf << 'END-of-pa-hotplug/files/pa-hotplug-dbus.conf'
X<!DOCTYPE busconfig PUBLIC
X "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
X "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
X<busconfig>
X <!-- Allow root to own and send signals on org.netbsd.DeviceHotplug -->
X <policy user="root">
X <allow send_type="signal" send_interface="org.netbsd.DeviceHotplug"/>
X </policy>
X
X <!-- Allow users in the pulse group to receive signals -->
X <policy group="pulse">
X <allow receive_sender="org.netbsd.DeviceHotplug"/>
X <allow receive_interface="org.netbsd.DeviceHotplug"/>
X <allow eavesdrop="true"/>
X </policy>
X</busconfig>
END-of-pa-hotplug/files/pa-hotplug-dbus.conf
echo x - pa-hotplug/files/Makefile
sed 's/^X//' >pa-hotplug/files/Makefile << 'END-of-pa-hotplug/files/Makefile'
XPROG= pa-hotplug
XSRCS= pa-hotplug.c
X
XBINDIR= ${PREFIX}/bin
XMKMAN= no
X
XPKG_CONFIG?= pkg-config
XDBUS_CFLAGS!= ${PKG_CONFIG} --cflags dbus-1
XDBUS_LIBS!= ${PKG_CONFIG} --libs dbus-1
X
XCPPFLAGS+= ${DBUS_CFLAGS}
XLDADD+= -lutil ${DBUS_LIBS}
X
X.include <bsd.prog.mk>
END-of-pa-hotplug/files/Makefile
echo x - pa-hotplug/files/devpubd-hook.sh
sed 's/^X//' >pa-hotplug/files/devpubd-hook.sh << 'END-of-pa-hotplug/files/devpubd-hook.sh'
X#!/bin/sh
X#
X# devpubd hook for audio device hotplug.
X# Sends D-Bus signals on the system bus when audio devices
X# are attached or detached.
X#
X# Called by devpubd as: device-attach <device> / device-detach <device>
X#
X
Xevent="$1"
Xdevice="$2"
X
X# Only handle audio devices
Xcase "$device" in
Xaudio*) ;;
X*) exit 0 ;;
Xesac
X
Xdevpath="/dev/${device}"
X
Xcase "$event" in
Xdevice-attach)
X /usr/pkg/bin/dbus-send --system --type=signal \
X /org/netbsd/DeviceHotplug \
X org.netbsd.DeviceHotplug.DeviceAdded \
X "string:${devpath}"
X ;;
Xdevice-detach)
X /usr/pkg/bin/dbus-send --system --type=signal \
X /org/netbsd/DeviceHotplug \
X org.netbsd.DeviceHotplug.DeviceRemoved \
X "string:${devpath}"
X ;;
Xesac
X
Xexit 0
END-of-pa-hotplug/files/devpubd-hook.sh
echo x - pa-hotplug/Makefile
sed 's/^X//' >pa-hotplug/Makefile << 'END-of-pa-hotplug/Makefile'
X# $NetBSD$
X
XDISTNAME= pa-hotplug-0.1
XCATEGORIES= audio
XMASTER_SITES= # empty
XDISTFILES= # empty
X
XMAINTAINER= pkgsrc-users%NetBSD.org@localhost
XHOMEPAGE= # empty
XCOMMENT= Bridge devpubd audio events to PulseAudio via D-Bus
XLICENSE= isc
X
XDEPENDS+= pulseaudio-[0-9]*:../../audio/pulseaudio
X
XUSE_DBUS-ARCH-DEPS_H= yes
XBUILDLINK_INCDIRS.dbus+= include/dbus-1.0
X
XUSE_TOOLS+= pkg-config
X
X
XINSTALLATION_DIRS+= bin
XINSTALLATION_DIRS+= share/examples/pa-hotplug
X
XEGDIR= ${PREFIX}/share/examples/pa-hotplug
X
XCONF_FILES_PERMS+= ${EGDIR}/devpubd-hook.sh \
X /libexec/devpubd-hooks/01-audio \
X root wheel 0555
XCONF_FILES_PERMS+= ${EGDIR}/pa-hotplug-dbus.conf \
X ${PREFIX}/etc/dbus-1/system.d/pa-hotplug.conf \
X root wheel 0644
XCONF_FILES_PERMS+= ${EGDIR}/pa-hotplug.desktop \
X ${PREFIX}/etc/xdg/autostart/pa-hotplug.desktop \
X root wheel 0644
X
XINSTALL_SCRIPT= INSTALL
XDEINSTALL_SCRIPT= DEINSTALL
X
Xdo-configure:
X @${TRUE}
X
Xdo-extract:
X ${MKDIR} ${WRKSRC}
X ${CP} ${FILESDIR}/pa-hotplug.c ${WRKSRC}/
X ${CP} ${FILESDIR}/Makefile ${WRKSRC}/
X
Xpost-install:
X ${INSTALL_DATA_DIR} ${DESTDIR}${EGDIR}
X ${INSTALL_DATA} ${FILESDIR}/devpubd-hook.sh \
X ${DESTDIR}${EGDIR}/devpubd-hook.sh
X ${INSTALL_DATA} ${FILESDIR}/pa-hotplug-dbus.conf \
X ${DESTDIR}${EGDIR}/pa-hotplug-dbus.conf
X ${INSTALL_DATA} ${FILESDIR}/pa-hotplug.desktop \
X ${DESTDIR}${EGDIR}/pa-hotplug.desktop
X
X.include "../../sysutils/dbus/buildlink3.mk"
X.include "../../mk/bsd.pkg.mk"
END-of-pa-hotplug/Makefile
echo x - pa-hotplug/PLIST
sed 's/^X//' >pa-hotplug/PLIST << 'END-of-pa-hotplug/PLIST'
X@comment $NetBSD$
Xbin/pa-hotplug
Xshare/examples/pa-hotplug/devpubd-hook.sh
Xshare/examples/pa-hotplug/pa-hotplug-dbus.conf
Xshare/examples/pa-hotplug/pa-hotplug.desktop
END-of-pa-hotplug/PLIST
echo x - pa-hotplug/distinfo
sed 's/^X//' >pa-hotplug/distinfo << 'END-of-pa-hotplug/distinfo'
X$NetBSD$
END-of-pa-hotplug/distinfo
echo c - pa-hotplug/.config
mkdir -p pa-hotplug/.config > /dev/null 2>&1
echo c - pa-hotplug/.config/pulse
mkdir -p pa-hotplug/.config/pulse > /dev/null 2>&1
exit




Home | Main Index | Thread Index | Old Index