pax_global_header00006660000000000000000000000064146004513240014511gustar00rootroot0000000000000052 comment=748f562d616590c678d5b06722f3fe5ae9707465 neutrinolabs-pipewire-module-xrdp-f8ce913/000077500000000000000000000000001460045132400207225ustar00rootroot00000000000000neutrinolabs-pipewire-module-xrdp-f8ce913/.gitignore000066400000000000000000000011751460045132400227160ustar00rootroot00000000000000*~ *.o config_ac.h config_ac.h.in build-aux/* *.swp src/cli_0358_mod instfiles/pipewire-xrdp.desktop # http://www.gnu.org/software/automake Makefile Makefile.in /ar-lib /mdate-sh /py-compile /test-driver /ylwrap # http://www.gnu.org/software/autoconf autom4te.cache /autoscan.log /autoscan-*.log /aclocal.m4 /compile /config.guess /config.log /config.status /config.sub /configure /configure.scan /depcomp .deps/ /install-sh /missing /stamp-h1 # https://www.gnu.org/software/libtool/ /ltmain.sh *.la *.lo .libs/ # http://www.gnu.org/software/m4/ libtool m4/libtool.m4 m4/ltoptions.m4 m4/ltsugar.m4 m4/ltversion.m4 m4/lt~obsolete.m4 neutrinolabs-pipewire-module-xrdp-f8ce913/LICENSE000066400000000000000000000020531460045132400217270ustar00rootroot00000000000000MIT License Copyright (c) 2023 matt335672 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. neutrinolabs-pipewire-module-xrdp-f8ce913/Makefile.am000066400000000000000000000001101460045132400227460ustar00rootroot00000000000000ACLOCAL_AMFLAGS = -I m4 SUBDIRS = src instfiles EXTRA_DIST = bootstrap neutrinolabs-pipewire-module-xrdp-f8ce913/README.md000066400000000000000000000023671460045132400222110ustar00rootroot00000000000000# pipewire-module-xrdp Thanks to @Hiero32 who contributed this module. This module allows xrdp to generate sound on a pipewire-based system. Pipewire versions 0.3.58 and later are supported. # Files ## Sources - [src/module-xrdp.c](src/module-xrdp.c) The module source. - [src/pw-cli_0358_mod.c](src/pw-cli_0358_mod.c) pw-cli for older systems running pipewire 0.3.58 ## Other - [instfiles/load_pw_modules.sh](instfiles/load_pw_modules.sh) Shell script invoked when the desktop starts to load the pipewire module on xrdp connections. - [instfiles/pipewire-xrdp.desktop.in](instfiles/pipewire-xrdp.desktop.in) Templated file which uses the desktop XDG autostart mechanism to run load_pw_modules.sh when a desktop is started. # Installing a build environment and dependencies ## Debian and derivatives ```sh # Install build environment sudo apt install git pkg-config autotools-dev libtool make gcc # Install dependencies sudo apt install libpipewire-0.3-dev libspa-0.2-dev ``` ## Fedora ```sh # Install build environment sudo dnf install git gcc make autoconf libtool automake pkgconfig # Install dependencies sudo dnf install pipewire-devel ``` # Building and installing ``` ./bootstrap ./configure make # Install sudo make install ``` neutrinolabs-pipewire-module-xrdp-f8ce913/bootstrap000077500000000000000000000000321460045132400226600ustar00rootroot00000000000000#!/bin/sh autoreconf -ivf neutrinolabs-pipewire-module-xrdp-f8ce913/configure.ac000066400000000000000000000052621460045132400232150ustar00rootroot00000000000000# -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ([2.65]) AC_INIT([pipewire-module-xrdp], [0.2], [xrdp-devel@googlegroups.com]) # specify config_ac.h for familiarity AC_CONFIG_HEADERS([config_ac.h]) AC_CONFIG_SRCDIR([src]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIRS([m4]) AM_PROG_AR AM_INIT_AUTOMAKE([-Wall foreign silent-rules]) AM_SILENT_RULES([yes]) PKG_PROG_PKG_CONFIG if test "x$PKG_CONFIG" = "x"; then AC_MSG_ERROR([pkg-config not found]) fi m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) # Default system-wide autostart directory from XDG autostart specification m4_define([XDG_AUTOSTART_DIR], /etc/xdg/autostart) # Build shared libraries LT_INIT([shared disable-static]) # Checks for programs. AC_PROG_AWK AC_PROG_CC AC_PROG_CPP AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_MAKE_SET # Checks for libraries. PKG_CHECK_MODULES([PW], [libpipewire-0.3 >= 0.3.58], [], [AC_MSG_ERROR([please install libpipewire-0.3-dev or pipewire-devel])]) PKG_CHECK_MODULES([SPA], [libspa-0.2], [], [AC_MSG_ERROR([please install libspa-0.2-dev or pipewire-devel])]) PW_MODDIR=`$PKG_CONFIG --variable=moduledir libpipewire-0.3` # use the prefix as same as pipewire AC_PREFIX_PROGRAM(pw) AC_ARG_WITH( [module-dir], [AS_HELP_STRING([--with-module-dir], [Directory where to install the modules to (defaults to `$PKG_CONFIG --variable=moduledir libpipewire-0.3`)])], [modlibexecdir=$withval], [modlibexecdir="${PW_MODDIR}"]) AC_SUBST(modlibexecdir) AC_ARG_WITH( [xdgautostart-dir], [AS_HELP_STRING([--with-xdgautostart-dir], [Directory to install the desktop file (defaults to XDG_AUTOSTART_DIR)])], [xdgautostartdir=$withval], [xdgautostartdir=XDG_AUTOSTART_DIR]) AC_SUBST(xdgautostartdir) AC_ARG_ENABLE( [alt-pw-cli], [AS_HELP_STRING([--enable-alt-pw-cli], [Enable the alternate ps-cli command (yes/no/auto)])], [alt_pw_cli=$enableval], [alt_pw_cli=auto]) # The alternative pw-cli is not needed for pipewire 0.3.59 and # later. # See https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/2709 if test x$alt_pw_cli = xauto; then if $PKG_CONFIG --atleast-version 0.3.59 libpipewire-0.3; then AC_MSG_NOTICE([The alternative pw-cli will not be used]) alt_pw_cli=no else AC_MSG_NOTICE([The alternative pw-cli will be used]) alt_pw_cli=yes fi fi AM_CONDITIONAL(USE_ALT_PW_CLI, [test "x$alt_pw_cli" = xyes]) # configure compiler options and CFLAGS AX_CFLAGS_WARN_ALL AX_APPEND_COMPILE_FLAGS([-Wwrite-strings]) AC_CONFIG_FILES([Makefile src/Makefile instfiles/Makefile]) AC_OUTPUT neutrinolabs-pipewire-module-xrdp-f8ce913/instfiles/000077500000000000000000000000001460045132400227225ustar00rootroot00000000000000neutrinolabs-pipewire-module-xrdp-f8ce913/instfiles/Makefile.am000066400000000000000000000010241460045132400247530ustar00rootroot00000000000000EXTRA_DIST = \ load_pw_modules.sh \ pipewire-xrdp.desktop.in # # substitute directories in service file # CLEANFILES= \ pipewire-xrdp.desktop SUBST_VARS = sed \ -e 's|@pkglibexecdir[@]|$(pkglibexecdir)|g' subst_verbose = $(subst_verbose_@AM_V@) subst_verbose_ = $(subst_verbose_@AM_DEFAULT_V@) subst_verbose_0 = @echo " SUBST $@"; SUFFIXES = .in .in: $(subst_verbose)$(SUBST_VARS) $< > $@ # # files for all platforms # xdgautostart_DATA = \ pipewire-xrdp.desktop pkglibexec_SCRIPTS = \ load_pw_modules.sh neutrinolabs-pipewire-module-xrdp-f8ce913/instfiles/load_pw_modules.sh000077500000000000000000000055741460045132400264510ustar00rootroot00000000000000#!/bin/sh if ! command -v pw-cli >/dev/null; then echo "pw-cli not found, exiting" exit 0 fi if ! pw-cli quit 2>/dev/null; then echo "pipewire server is not running, exiting" exit 0 fi status=0 if [ -n "$XRDP_SESSION" -a -n "$XRDP_SOCKET_PATH" ]; then # Destroy xrdp sink and source if they already exist OBJECT_IDS=$(pw-cli ls Node | sed -e "s/^[^a-z]//" | grep -w "^id" | sed -e "s/^[^0-9]*//" -e "s/[^0-9]/-/" | cut -d- -f1) for OBJECT_ID in $OBJECT_IDS; do NODE_NAME=$(pw-cli info $OBJECT_ID | grep -w "node\.name" | cut -d\" -f2) if [ "$NODE_NAME" = "xrdp-sink" -o "$NODE_NAME" = "xrdp-source" ]; then pw-cli destroy $OBJECT_ID fi done # Kill module, if it is working #PID=$(ps -u $(id -u) -o pid,ruser,cmd | grep libpipewire-module-xrdp | grep -v grep | sed -e 's/^ *//' | cut -d' ' -f1) #if [ -n "$PID" ]; then # kill -HUP $PID #fi if [ "$1" = "-d" ]; then exit; fi export PIPEWIRE_LOG_SYSTEMD=false if [ "$1" = "-l" ]; then # debug: 0:none, 1:error, 2:warnings, 3:info, 4:debug, 5:trace if [ -n "$2" ]; then export PIPEWIRE_DEBUG=$2 else export PIPEWIRE_DEBUG=3 fi export PIPEWIRE_LOG=/tmp/xrdp_pipewire_$(echo $DISPLAY | sed -e 's/^[^0-9]//' | cut -d. -f1).log else export PIPEWIRE_DEBUG=1 fi # Reload modules PWCLI=pw-cli if [ "$(pipewire --version | sed -e "s/[ a-zA-Z]//g" | tail -n 1)" = "0.3.58" ]; then PWCLI=$(dirname $0)/pw-cli_0358_mod fi QUANTUMVAL=2048 QUANTUMVAL2=$(($QUANTUMVAL * 2)) # enable both xrdp-sink ans xrdp-source $PWCLI -m -d load-module libpipewire-module-xrdp sink.node.latency=$QUANTUMVAL sink.stream.props={node.name=xrdp-sink} source.stream.props={node.name=xrdp-source} > /dev/null & # enable xrdp-sink only # $PWCLI -m -d load-module libpipewire-module-xrdp sink.node.latency=$QUANTUMVAL sink.stream.props={node.name=xrdp-sink} > /dev/null & # enable xrdp-source only # $PWCLI -m -d load-module libpipewire-module-xrdp source.stream.props={node.name=xrdp-source} > /dev/null & sleep 1 #increase the quantum(latency) value to reduce choppy audio # from PipeWire debian # https://wiki.debian.org/PipeWire#choppy_audio_on_systems_with_high_load pw-metadata -n settings 0 clock.force-quantum $QUANTUMVAL >/dev/null pw-metadata -n settings 0 default.clock.force-quantum $QUANTUMVAL2 >/dev/null pw-metadata -n settings 0 default.clock.quantum $QUANTUMVAL2 >/dev/null pw-metadata -n settings 0 default.clock.min-quantum $QUANTUMVAL2 >/dev/null # set default sample rate = 44100, because xrdp uses it. pw-metadata -n settings 0 default.clock.rate 44100 >/dev/null if command -v pactl >/dev/null; then pactl set-default-sink xrdp-sink pactl set-default-source xrdp-source fi fi exit $status neutrinolabs-pipewire-module-xrdp-f8ce913/instfiles/pipewire-xrdp.desktop.in000066400000000000000000000002771460045132400275270ustar00rootroot00000000000000[Desktop Entry] Version=1.0 Name=Pipewire xrdp modules Comment=Load Pipewire Modules for xrdp Exec=@pkglibexecdir@/load_pw_modules.sh Terminal=false Type=Application Categories= GenericName= neutrinolabs-pipewire-module-xrdp-f8ce913/m4/000077500000000000000000000000001460045132400212425ustar00rootroot00000000000000neutrinolabs-pipewire-module-xrdp-f8ce913/m4/ax_append_compile_flags.m4000066400000000000000000000033451460045132400263340ustar00rootroot00000000000000# ============================================================================ # https://www.gnu.org/software/autoconf-archive/ax_append_compile_flags.html # ============================================================================ # # SYNOPSIS # # AX_APPEND_COMPILE_FLAGS([FLAG1 FLAG2 ...], [FLAGS-VARIABLE], [EXTRA-FLAGS], [INPUT]) # # DESCRIPTION # # For every FLAG1, FLAG2 it is checked whether the compiler works with the # flag. If it does, the flag is added FLAGS-VARIABLE # # If FLAGS-VARIABLE is not specified, the current language's flags (e.g. # CFLAGS) is used. During the check the flag is always added to the # current language's flags. # # If EXTRA-FLAGS is defined, it is added to the current language's default # flags (e.g. CFLAGS) when the check is done. The check is thus made with # the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to # force the compiler to issue an error when a bad flag is given. # # INPUT gives an alternative input source to AC_COMPILE_IFELSE. # # NOTE: This macro depends on the AX_APPEND_FLAG and # AX_CHECK_COMPILE_FLAG. Please keep this macro in sync with # AX_APPEND_LINK_FLAGS. # # LICENSE # # Copyright (c) 2011 Maarten Bosmans # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 7 AC_DEFUN([AX_APPEND_COMPILE_FLAGS], [AX_REQUIRE_DEFINED([AX_CHECK_COMPILE_FLAG]) AX_REQUIRE_DEFINED([AX_APPEND_FLAG]) for flag in $1; do AX_CHECK_COMPILE_FLAG([$flag], [AX_APPEND_FLAG([$flag], [$2])], [], [$3], [$4]) done ])dnl AX_APPEND_COMPILE_FLAGS neutrinolabs-pipewire-module-xrdp-f8ce913/m4/ax_append_flag.m4000066400000000000000000000030201460045132400244270ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_append_flag.html # =========================================================================== # # SYNOPSIS # # AX_APPEND_FLAG(FLAG, [FLAGS-VARIABLE]) # # DESCRIPTION # # FLAG is appended to the FLAGS-VARIABLE shell variable, with a space # added in between. # # If FLAGS-VARIABLE is not specified, the current language's flags (e.g. # CFLAGS) is used. FLAGS-VARIABLE is not changed if it already contains # FLAG. If FLAGS-VARIABLE is unset in the shell, it is set to exactly # FLAG. # # NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. # # LICENSE # # Copyright (c) 2008 Guido U. Draheim # Copyright (c) 2011 Maarten Bosmans # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 8 AC_DEFUN([AX_APPEND_FLAG], [dnl AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_SET_IF AS_VAR_PUSHDEF([FLAGS], [m4_default($2,_AC_LANG_PREFIX[FLAGS])]) AS_VAR_SET_IF(FLAGS,[ AS_CASE([" AS_VAR_GET(FLAGS) "], [*" $1 "*], [AC_RUN_LOG([: FLAGS already contains $1])], [ AS_VAR_APPEND(FLAGS,[" $1"]) AC_RUN_LOG([: FLAGS="$FLAGS"]) ]) ], [ AS_VAR_SET(FLAGS,[$1]) AC_RUN_LOG([: FLAGS="$FLAGS"]) ]) AS_VAR_POPDEF([FLAGS])dnl ])dnl AX_APPEND_FLAG neutrinolabs-pipewire-module-xrdp-f8ce913/m4/ax_cflags_warn_all.m4000066400000000000000000000135111460045132400253130ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_cflags_warn_all.html # =========================================================================== # # SYNOPSIS # # AX_CFLAGS_WARN_ALL [(shellvar[, default[, action-if-found[, action-if-not-found]]])] # AX_CXXFLAGS_WARN_ALL [(shellvar[, default[, action-if-found[, action-if-not-found]]])] # AX_FCFLAGS_WARN_ALL [(shellvar[, default[, action-if-found[, action-if-not-found]]])] # # DESCRIPTION # # Specify compiler options that enable most reasonable warnings. For the # GNU Compiler Collection (GCC), for example, it will be "-Wall". The # result is added to shellvar, one of CFLAGS, CXXFLAGS or FCFLAGS if the # first parameter is not specified. # # Each of these macros accepts the following optional arguments: # # - $1 - shellvar # shell variable to use (CFLAGS, CXXFLAGS or FCFLAGS if not # specified, depending on macro) # # - $2 - default # value to use for flags if compiler vendor cannot be determined (by # default, "") # # - $3 - action-if-found # action to take if the compiler vendor has been successfully # determined (by default, add the appropriate compiler flags to # shellvar) # # - $4 - action-if-not-found # action to take if the compiler vendor has not been determined or # is unknown (by default, add the default flags, or "" if not # specified, to shellvar) # # These macros use AX_COMPILER_VENDOR to determine which flags should be # returned for a given compiler. Not all compilers currently have flags # defined for them; patches are welcome. If need be, compiler flags may # be made language-dependent: use a construct like the following: # # [vendor_name], [m4_if(_AC_LANG_PREFIX,[C], VAR="--relevant-c-flags",dnl # m4_if(_AC_LANG_PREFIX,[CXX], VAR="--relevant-c++-flags",dnl # m4_if(_AC_LANG_PREFIX,[FC], VAR="--relevant-fortran-flags",dnl # VAR="$2"; FOUND="no")))], # # Note: These macros also depend on AX_PREPEND_FLAG. # # LICENSE # # Copyright (c) 2008 Guido U. Draheim # Copyright (c) 2010 Rhys Ulerich # Copyright (c) 2018 John Zaitseff # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # 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 General # Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 25 AC_DEFUN([AX_FLAGS_WARN_ALL], [ AX_REQUIRE_DEFINED([AX_PREPEND_FLAG])dnl AC_REQUIRE([AX_COMPILER_VENDOR])dnl AS_VAR_PUSHDEF([FLAGS], [m4_default($1,_AC_LANG_PREFIX[]FLAGS)])dnl AS_VAR_PUSHDEF([VAR], [ac_cv_[]_AC_LANG_ABBREV[]flags_warn_all])dnl AS_VAR_PUSHDEF([FOUND], [ac_save_[]_AC_LANG_ABBREV[]flags_warn_all_found])dnl AC_CACHE_CHECK([FLAGS for most reasonable warnings], VAR, [ VAR="" FOUND="yes" dnl Cases are listed in the order found in ax_compiler_vendor.m4 AS_CASE("$ax_cv_[]_AC_LANG_ABBREV[]_compiler_vendor", [intel], [VAR="-w2"], [ibm], [VAR="-qsrcmsg -qinfo=all:noppt:noppc:noobs:nocnd"], [pathscale], [], [clang], [VAR="-Wall"], [cray], [VAR="-h msglevel 2"], [fujitsu], [], [sdcc], [], [sx], [VAR="-pvctl[,]fullmsg"], [portland], [], [gnu], [VAR="-Wall"], [sun], [VAR="-v"], [hp], [VAR="+w1"], [dec], [VAR="-verbose -w0 -warnprotos"], [borland], [], [comeau], [], [kai], [], [lcc], [], [sgi], [VAR="-fullwarn"], [microsoft], [], [metrowerks], [], [watcom], [], [tcc], [], [unknown], [ VAR="$2" FOUND="no" ], [ AC_MSG_WARN([Unknown compiler vendor returned by [AX_COMPILER_VENDOR]]) VAR="$2" FOUND="no" ] ) AS_IF([test "x$FOUND" = "xyes"], [dnl m4_default($3, [AS_IF([test "x$VAR" != "x"], [AX_PREPEND_FLAG([$VAR], [FLAGS])])]) ], [dnl m4_default($4, [m4_ifval($2, [AX_PREPEND_FLAG([$VAR], [FLAGS])], [true])]) ])dnl ])dnl AS_VAR_POPDEF([FOUND])dnl AS_VAR_POPDEF([VAR])dnl AS_VAR_POPDEF([FLAGS])dnl ])dnl AX_FLAGS_WARN_ALL AC_DEFUN([AX_CFLAGS_WARN_ALL], [dnl AC_LANG_PUSH([C]) AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4]) AC_LANG_POP([C]) ])dnl AC_DEFUN([AX_CXXFLAGS_WARN_ALL], [dnl AC_LANG_PUSH([C++]) AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4]) AC_LANG_POP([C++]) ])dnl AC_DEFUN([AX_FCFLAGS_WARN_ALL], [dnl AC_LANG_PUSH([Fortran]) AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4]) AC_LANG_POP([Fortran]) ])dnl neutrinolabs-pipewire-module-xrdp-f8ce913/m4/ax_check_compile_flag.m4000066400000000000000000000040701460045132400257530ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html # =========================================================================== # # SYNOPSIS # # AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) # # DESCRIPTION # # Check whether the given FLAG works with the current language's compiler # or gives an error. (Warnings, however, are ignored) # # ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on # success/failure. # # If EXTRA-FLAGS is defined, it is added to the current language's default # flags (e.g. CFLAGS) when the check is done. The check is thus made with # the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to # force the compiler to issue an error when a bad flag is given. # # INPUT gives an alternative input source to AC_COMPILE_IFELSE. # # NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this # macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. # # LICENSE # # Copyright (c) 2008 Guido U. Draheim # Copyright (c) 2011 Maarten Bosmans # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 6 AC_DEFUN([AX_CHECK_COMPILE_FLAG], [AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], [AS_VAR_SET(CACHEVAR,[yes])], [AS_VAR_SET(CACHEVAR,[no])]) _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) AS_VAR_IF(CACHEVAR,yes, [m4_default([$2], :)], [m4_default([$3], :)]) AS_VAR_POPDEF([CACHEVAR])dnl ])dnl AX_CHECK_COMPILE_FLAGS neutrinolabs-pipewire-module-xrdp-f8ce913/m4/ax_compiler_vendor.m4000066400000000000000000000103621460045132400253650ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_compiler_vendor.html # =========================================================================== # # SYNOPSIS # # AX_COMPILER_VENDOR # # DESCRIPTION # # Determine the vendor of the C, C++ or Fortran compiler. The vendor is # returned in the cache variable $ax_cv_c_compiler_vendor for C, # $ax_cv_cxx_compiler_vendor for C++ or $ax_cv_fc_compiler_vendor for # (modern) Fortran. The value is one of "intel", "ibm", "pathscale", # "clang" (LLVM), "cray", "fujitsu", "sdcc", "sx", "nvhpc" (NVIDIA HPC # Compiler), "portland" (PGI), "gnu" (GCC), "sun" (Oracle Developer # Studio), "hp", "dec", "borland", "comeau", "kai", "lcc", "sgi", # "microsoft", "metrowerks", "watcom", "tcc" (Tiny CC) or "unknown" (if # the compiler cannot be determined). # # To check for a Fortran compiler, you must first call AC_FC_PP_SRCEXT # with an appropriate preprocessor-enabled extension. For example: # # AC_LANG_PUSH([Fortran]) # AC_PROG_FC # AC_FC_PP_SRCEXT([F]) # AX_COMPILER_VENDOR # AC_LANG_POP([Fortran]) # # LICENSE # # Copyright (c) 2008 Steven G. Johnson # Copyright (c) 2008 Matteo Frigo # Copyright (c) 2018-19 John Zaitseff # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation, either version 3 of the License, or (at your # option) any later version. # # 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 General # Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 32 AC_DEFUN([AX_COMPILER_VENDOR], [dnl AC_CACHE_CHECK([for _AC_LANG compiler vendor], ax_cv_[]_AC_LANG_ABBREV[]_compiler_vendor, [dnl dnl If you modify this list of vendors, please add similar support dnl to ax_compiler_version.m4 if at all possible. dnl dnl Note: Do NOT check for GCC first since some other compilers dnl define __GNUC__ to remain compatible with it. Compilers that dnl are very slow to start (such as Intel) are listed first. vendors=" intel: __ICC,__ECC,__INTEL_COMPILER ibm: __xlc__,__xlC__,__IBMC__,__IBMCPP__,__ibmxl__ pathscale: __PATHCC__,__PATHSCALE__ clang: __clang__ cray: _CRAYC fujitsu: __FUJITSU sdcc: SDCC,__SDCC sx: _SX nvhpc: __NVCOMPILER portland: __PGI gnu: __GNUC__ sun: __SUNPRO_C,__SUNPRO_CC,__SUNPRO_F90,__SUNPRO_F95 hp: __HP_cc,__HP_aCC dec: __DECC,__DECCXX,__DECC_VER,__DECCXX_VER borland: __BORLANDC__,__CODEGEARC__,__TURBOC__ comeau: __COMO__ kai: __KCC lcc: __LCC__ sgi: __sgi,sgi microsoft: _MSC_VER metrowerks: __MWERKS__ watcom: __WATCOMC__ tcc: __TINYC__ unknown: UNKNOWN " for ventest in $vendors; do case $ventest in *:) vendor=$ventest continue ;; *) vencpp="defined("`echo $ventest | sed 's/,/) || defined(/g'`")" ;; esac AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [[ #if !($vencpp) thisisanerror; #endif ]])], [break]) done ax_cv_[]_AC_LANG_ABBREV[]_compiler_vendor=`echo $vendor | cut -d: -f1` ]) ])dnl neutrinolabs-pipewire-module-xrdp-f8ce913/m4/ax_prepend_flag.m4000066400000000000000000000031111460045132400246160ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_prepend_flag.html # =========================================================================== # # SYNOPSIS # # AX_PREPEND_FLAG(FLAG, [FLAGS-VARIABLE]) # # DESCRIPTION # # FLAG is added to the front of the FLAGS-VARIABLE shell variable, with a # space added in between. # # If FLAGS-VARIABLE is not specified, the current language's flags (e.g. # CFLAGS) is used. FLAGS-VARIABLE is not changed if it already contains # FLAG. If FLAGS-VARIABLE is unset in the shell, it is set to exactly # FLAG. # # NOTE: Implementation based on AX_APPEND_FLAG. # # LICENSE # # Copyright (c) 2008 Guido U. Draheim # Copyright (c) 2011 Maarten Bosmans # Copyright (c) 2018 John Zaitseff # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 2 AC_DEFUN([AX_PREPEND_FLAG], [dnl AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_SET_IF AS_VAR_PUSHDEF([FLAGS], [m4_default($2,_AC_LANG_PREFIX[FLAGS])]) AS_VAR_SET_IF(FLAGS,[ AS_CASE([" AS_VAR_GET(FLAGS) "], [*" $1 "*], [AC_RUN_LOG([: FLAGS already contains $1])], [ FLAGS="$1 $FLAGS" AC_RUN_LOG([: FLAGS="$FLAGS"]) ]) ], [ AS_VAR_SET(FLAGS,[$1]) AC_RUN_LOG([: FLAGS="$FLAGS"]) ]) AS_VAR_POPDEF([FLAGS])dnl ])dnl AX_PREPEND_FLAG neutrinolabs-pipewire-module-xrdp-f8ce913/m4/ax_require_defined.m4000066400000000000000000000023021460045132400253230ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_require_defined.html # =========================================================================== # # SYNOPSIS # # AX_REQUIRE_DEFINED(MACRO) # # DESCRIPTION # # AX_REQUIRE_DEFINED is a simple helper for making sure other macros have # been defined and thus are available for use. This avoids random issues # where a macro isn't expanded. Instead the configure script emits a # non-fatal: # # ./configure: line 1673: AX_CFLAGS_WARN_ALL: command not found # # It's like AC_REQUIRE except it doesn't expand the required macro. # # Here's an example: # # AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG]) # # LICENSE # # Copyright (c) 2014 Mike Frysinger # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 2 AC_DEFUN([AX_REQUIRE_DEFINED], [dnl m4_ifndef([$1], [m4_fatal([macro ]$1[ is not defined; is a m4 file missing?])]) ])dnl AX_REQUIRE_DEFINED neutrinolabs-pipewire-module-xrdp-f8ce913/src/000077500000000000000000000000001460045132400215115ustar00rootroot00000000000000neutrinolabs-pipewire-module-xrdp-f8ce913/src/Makefile.am000066400000000000000000000010271460045132400235450ustar00rootroot00000000000000AM_CFLAGS = AM_LDFLAGS = if USE_ALT_PW_CLI pkglibexec_PROGRAMS = pw-cli_0358_mod endif modlibexec_LTLIBRARIES = libpipewire-module-xrdp.la # _GNU_SOURCE needed for asprintf() declaration pw_cli_0358_mod_SOURCES = pw-cli_0358_mod.c pw_cli_0358_mod_CFLAGS = $(PW_CFLAGS) $(SPA_CFLAGS) -D_GNU_SOURCE pw_cli_0358_mod_LDFLAGS = $(PW_LIBS) libpipewire_module_xrdp_la_SOURCES = module-xrdp.c libpipewire_module_xrdp_la_CFLAGS = $(PW_CFLAGS) $(SPA_CFLAGS) -fPIC libpipewire_module_xrdp_la_LDFLAGS = $(PW_LIBS) $(SPA_LIBS) -avoid-version neutrinolabs-pipewire-module-xrdp-f8ce913/src/module-xrdp.c000066400000000000000000000730211460045132400241200ustar00rootroot00000000000000/** * xrdp pipewire module * * This is a modified version of src/modules/module-pipe-tunnel.c * from pipewire 0.3.64 */ /* PipeWire * * Copyright © 2021 Sanchayan Maity * Copyright © 2022 Wim Taymans * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** \page page_module_pipe_tunnel PipeWire Module: Unix Pipe Tunnel * * The pipe-tunnel module provides a source or sink that tunnels all audio to * a unix pipe. * * ## Module Options * * - `tunnel.mode`: the desired tunnel to create. (Default `playback`) * - `pipe.filename`: the filename of the pipe. * - `stream.props`: Extra properties for the local stream. * * When `tunnel.mode` is `capture`, a capture stream on the default source is * created. Samples read from the pipe will be the contents of the captured source. * * When `tunnel.mode` is `sink`, a sink node is created. Samples read from the * pipe will be the samples played on the sink. * * When `tunnel.mode` is `playback`, a playback stream on the default sink is * created. Samples written to the pipe will be played on the sink. * * When `tunnel.mode` is `source`, a source node is created. Samples written to * the pipe will be made available to streams connected to the source. * * When `pipe.filename` is not given, a default fifo in `/tmp/fifo_input` or * `/tmp/fifo_output` will be created that can be written and read respectively, * depending on the selected `tunnel.mode`. * * ## General options * * Options with well-known behavior. * * - \ref PW_KEY_REMOTE_NAME * - \ref PW_KEY_AUDIO_FORMAT * - \ref PW_KEY_AUDIO_RATE * - \ref PW_KEY_AUDIO_CHANNELS * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_NODE_LATENCY * - \ref PW_KEY_NODE_NAME * - \ref PW_KEY_NODE_DESCRIPTION * - \ref PW_KEY_NODE_GROUP * - \ref PW_KEY_NODE_VIRTUAL * - \ref PW_KEY_MEDIA_CLASS * - \ref PW_KEY_TARGET_OBJECT to specify the remote name or serial id to link to * * When not otherwise specified, the pipe will accept or produce a * 16 bits, stereo, 48KHz sample stream. * * ## Example configuration of a pipe playback stream * *\code{.unparsed} * context.modules = [ * { name = libpipewire-module-pipe-tunnel * args = { * tunnel.mode = playback * # Set the pipe name to tunnel to * pipe.filename = "/tmp/fifo_output" * #audio.format= * #audio.rate= * #audio.channels= * #audio.position= * #target.object= * stream.props = { * # extra sink properties * } * } * } * ] *\endcode */ #define NAME "xrdp" #define DEFAULT_FORMAT "S16" #define DEFAULT_RATE 44100 #define DEFAULT_CHANNELS 2 #define DEFAULT_POSITION "[ FL FR ]" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic #define MODULE_USAGE "[ remote.name= ] " \ "[ sink.node.latency= ] " \ "[ target.object= ] " \ "[ audio.format= ] " \ "[ audio.rate= ] " \ "[ audio.channels= ] " \ "[ audio.position= ] " \ "[ sink.stream.props= ] " \ "[ source.stream.props= ] " /* commands to xrdp_chansrv_audio_in_socket (xrdp/sesman/chansrv/sound.h)*/ #define PA_CMD_START_REC 1 #define PA_CMD_STOP_REC 2 #define PA_CMD_SEND_DATA 3 static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans " }, { PW_KEY_MODULE_DESCRIPTION, "Create a xrdp pipewire interface" }, { PW_KEY_MODULE_USAGE, MODULE_USAGE }, { PW_KEY_MODULE_VERSION, pw_get_headers_version() }, }; struct impl { struct pw_context *context; // common uint32_t destroy_work_id; // common #define MODE_XRDP_SINK 1 #define MODE_XRDP_SOURCE 2 #define MODE_BOTH (MODE_XRDP_SINK | MODE_XRDP_SOURCE) uint32_t mode; struct pw_properties *props_sink; struct pw_properties *props_source; struct pw_impl_module *module; // common struct spa_hook module_listener; // common struct pw_core *core; // common struct spa_hook core_proxy_listener; // common struct spa_hook core_listener; // common char *filename_sink; char *filename_source; int fd_sink; int fd_source; uint64_t failed_connect_time; struct pw_properties *stream_props_sink; struct pw_properties *stream_props_source; struct pw_stream *stream_sink; struct pw_stream *stream_source; struct spa_hook stream_listener_sink; struct spa_hook stream_listener_source; struct spa_audio_info_raw info; // common uint32_t frame_size; // only source unsigned int do_disconnect:1; // common uint32_t leftover_count; // only source uint8_t *leftover; // only source int want_src_data; // only source unsigned int unloading:1; // common struct pw_work_queue *work; // common int display_num; // for debug }; static void do_unload_module(void *obj, void *data, int res, uint32_t id) { struct impl *impl = data; pw_impl_module_destroy(impl->module); } static void unload_module(struct impl *impl) { if (!impl->unloading) { impl->unloading = true; pw_work_queue_add(impl->work, impl, 0, do_unload_module, impl); } } static void stream_destroy_sink(void *d) { struct impl *impl = d; spa_hook_remove(&impl->stream_listener_sink); impl->stream_sink = NULL; } static void stream_destroy_source(void *d) { struct impl *impl = d; spa_hook_remove(&impl->stream_listener_source); impl->stream_source = NULL; } struct header { int code; int bytes; }; static int get_display_num_from_display(const char *display_text) { int mode = 0; int disp_index = 0; char disp[16] = { 0 }; if (display_text == NULL) return 0; for (size_t index = 0; display_text[index] != 0 && index < sizeof(disp) ; index++) { if (display_text[index] == ':') mode = 1; else if (display_text[index] == '.') break; else if (mode == 1) disp[disp_index++] = display_text[index]; } disp[disp_index] = 0; return atoi(disp); } static int lsend(int fd, char *data, int bytes) { int sent = 0; while (sent < bytes) { int error = send(fd, data + sent, bytes - sent, MSG_NOSIGNAL); if (error < 1) return error; sent += error; } return sent; } static int lrecv(int fd, char *data, int bytes) { int recved = 0; while (recved < bytes) { int error = recv(fd, data + recved, bytes - recved, 0); if (error < 1) return error; recved += error; } return recved; } static int close_send_sink(struct impl *impl) { pw_log_info("close_send_sink"); if (impl->fd_sink != -1) { struct header h; h.code = 1; h.bytes = 8; if (lsend(impl->fd_sink, (char*)(&h), 8) != 8) { pw_log_debug("close_send: send failed"); close(impl->fd_sink); impl->fd_sink = -1; return 0; } else { pw_log_debug("close_send: sent header ok"); } } return 8; } static int close_send_source(struct impl *impl) { pw_log_info("close_send_source"); if (impl->fd_source != -1) { /* we don't want source data anymore */ char stop_rec[] = { 0, 0, 0, 0, 11, 0, 0, 0, PA_CMD_STOP_REC, 0, 0 }; if (lsend(impl->fd_source, stop_rec, 11) != 11) { close(impl->fd_source); impl->fd_source = -1; } impl->want_src_data = 0; pw_log_debug("###### stopped recording"); } return 8; } static void stream_state_changed_sink(void *d, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct impl *impl = d; switch (state) { case PW_STREAM_STATE_ERROR: case PW_STREAM_STATE_UNCONNECTED: //pw_impl_module_schedule_destroy(impl->module); unload_module(impl); break; case PW_STREAM_STATE_PAUSED: close_send_sink(impl); break; case PW_STREAM_STATE_STREAMING: break; default: break; } pw_log_debug("stream_state_changed:%s", pw_stream_state_as_string (state)); } static void stream_state_changed_source(void *d, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct impl *impl = d; switch (state) { case PW_STREAM_STATE_ERROR: case PW_STREAM_STATE_UNCONNECTED: //pw_impl_module_schedule_destroy(impl->module); unload_module(impl); break; case PW_STREAM_STATE_PAUSED: close_send_source(impl); break; case PW_STREAM_STATE_STREAMING: break; default: break; } pw_log_debug("stream_state_changed:%s", pw_stream_state_as_string (state)); } static void set_socket_path(struct impl *impl) { const char *socket_path; char default_socket_path[128]; const char *socket_dir; const char *socket_name; socket_dir = getenv("XRDP_SOCKET_PATH"); if (socket_dir == NULL || socket_dir[0] == '\0') { return; } impl->display_num = get_display_num_from_display(getenv("DISPLAY")); socket_name = getenv("XRDP_PULSE_SINK_SOCKET"); if (socket_name == NULL || socket_name[0] == '\0') { return; //pw_log_debug("Could not obtain xrdp_socket from environment."); //snprintf(default_socket_name, sizeof(default_socket_name)-1, // "xrdp_chansrv_audio_out_socket_%d", impl->display_num); //socket_name = default_socket_name; } snprintf(default_socket_path, sizeof(default_socket_path)-1, "%s/%s", socket_dir, socket_name); socket_path = default_socket_path; pw_log_info("set_sink_socket. socket path:%s", socket_path); impl->filename_sink = strdup(socket_path); socket_name = getenv("XRDP_PULSE_SOURCE_SOCKET"); if (socket_name == NULL || socket_name[0] == '\0') { return; //pw_log_debug("Could not obtain xrdp_socket from environment."); //snprintf(default_socket_name, sizeof(default_socket_name)-1, // "xrdp_chansrv_audio_out_socket_%d", impl->display_num); //socket_name = default_socket_name; } snprintf(default_socket_path, sizeof(default_socket_path)-1, "%s/%s", socket_dir, socket_name); socket_path = default_socket_path; pw_log_info("set_source_socket. socket path:%s", socket_path); impl->filename_source = strdup(socket_path); } static int conect_xrdp_socket(struct impl *impl, char *filename) { struct sockaddr_un s = { 0 }; struct timespec tm; if (impl->failed_connect_time != 0) { clock_gettime(CLOCK_MONOTONIC, &tm); //pw_log_debug("wait 1sec when connect error occurred. waiting %lld nS", (tm.tv_sec * 1000000000LL + tm.tv_nsec) - impl->failed_connect_time); if ((tm.tv_sec * 1000000000LL + tm.tv_nsec) - impl->failed_connect_time < 1000000000LL) { return -1; } } /* connect to xrdp unix domain socket */ int fd = socket(PF_LOCAL, SOCK_STREAM, 0); s.sun_family = AF_UNIX; strncpy(s.sun_path, filename, sizeof(s.sun_path)-1); pw_log_info("trying to connect to %s", s.sun_path); if (connect(fd, (struct sockaddr *)&s, sizeof(struct sockaddr_un)) != 0) { pw_log_debug("Connect failed"); close(fd); clock_gettime(CLOCK_MONOTONIC, &tm); impl->failed_connect_time = tm.tv_sec * 1000000000LL + tm.tv_nsec; fd = -1; } else { impl->failed_connect_time = 0; pw_log_info("Connected ok fd %d", fd); } return fd; } static void playback_stream_process(void *data) { struct impl *impl = data; struct pw_buffer *buf; ssize_t written_all = 0; uint32_t size_all = 0; if ((buf = pw_stream_dequeue_buffer(impl->stream_sink)) == NULL) { pw_log_debug("out of buffers: %m"); return; } if (impl->fd_sink == -1) { if ((impl->fd_sink = conect_xrdp_socket(impl, impl->filename_sink)) == -1) goto error; } for (uint32_t i = 0; i < buf->buffer->n_datas; i++) { uint32_t size, offs; struct spa_data *d; d = &buf->buffer->datas[i]; offs = SPA_MIN(d->chunk->offset, d->maxsize); size = SPA_MIN(d->chunk->size, d->maxsize - offs); size_all += size; } struct header h; h.code = 0; h.bytes = 8 + size_all; if (lsend(impl->fd_sink, (char*)(&h), 8) != 8) { pw_log_warn("data_send: send failed"); close(impl->fd_sink); impl->fd_sink = -1; goto error; } else { //pw_log_debug("data_send: sent header ok bytes %d", size_all); } for (uint32_t i = 0; i < buf->buffer->n_datas; i++) { uint32_t size, offs; ssize_t written; struct spa_data *d; d = &buf->buffer->datas[i]; offs = SPA_MIN(d->chunk->offset, d->maxsize); size = SPA_MIN(d->chunk->size, d->maxsize - offs); written = lsend(impl->fd_sink, SPA_MEMBER(d->data, offs, void), size); written_all += written; if (written != size) { pw_log_warn("Failed to write to xrdp sink"); close(impl->fd_sink); impl->fd_sink = -1; goto error; } } error: pw_stream_queue_buffer(impl->stream_sink, buf); if (written_all != size_all) { //pw_log_warn("data_send: send failed sent %ld bytes %d", written_all, size_all); } else { //pw_log_warn("data_send: send OK n_datas:%d sent %ld bytes %d", buf->buffer->n_datas, written_all, size_all); } } static void capture_stream_process(void *data) { struct impl *impl = data; struct pw_buffer *buf; struct spa_data *d; uint32_t req; ssize_t nread = 0; if ((buf = pw_stream_dequeue_buffer(impl->stream_source)) == NULL) { pw_log_debug("out of buffers: %m"); return; } d = &buf->buffer->datas[0]; if ((req = buf->requested * impl->frame_size) == 0) req = 4096 * impl->frame_size; req = SPA_MIN(req, d->maxsize); d->chunk->offset = 0; d->chunk->stride = impl->frame_size; d->chunk->size = SPA_MIN(req, impl->leftover_count); memcpy(d->data, impl->leftover, d->chunk->size); req -= d->chunk->size; uint32_t bytes = 0; unsigned char ubuf[10]; if (impl->fd_source == -1) { if ((impl->fd_source = conect_xrdp_socket(impl, impl->filename_source)) == -1) goto nodata; } if (!impl->want_src_data) { char start_rec[] = { 0, 0, 0, 0, 11, 0, 0, 0, PA_CMD_START_REC, 0, 0 }; if (lsend(impl->fd_source, start_rec, 11) != 11) { close(impl->fd_source); impl->fd_source = -1; goto nodata; } impl->want_src_data = 1; pw_log_debug("###### started recording"); } /* ask for more data */ char send_data[] = { 0, 0, 0, 0, 11, 0, 0, 0, PA_CMD_SEND_DATA, (unsigned char) req, (unsigned char) ((req >> 8) & 0xff) }; if (lsend(impl->fd_source, send_data, 11) != 11) { close(impl->fd_source); impl->fd_source = -1; impl->want_src_data = 0; goto nodata; } /* read length of data available */ if (lrecv(impl->fd_source, (char *) ubuf, 2) != 2) { close(impl->fd_source); impl->fd_source = -1; impl->want_src_data = 0; goto nodata; } bytes = ((ubuf[1] << 8) & 0xff00) | (ubuf[0] & 0xff); if (bytes == 0) goto nodata; /* get data */ nread = lrecv(impl->fd_source, SPA_PTROFF(d->data, d->chunk->size, void), /*req*/bytes); if (nread < 0) { close(impl->fd_source); impl->fd_source = -1; impl->want_src_data = 0; pw_log_warn("failed to read from pipe (%s): %s", impl->filename_source, strerror(errno)); } else { d->chunk->size += nread; } nodata: //pw_log_debug("nread:%ld. req:%d. %s", nread, req, req == bytes ? "":"req != bytes"); impl->leftover_count = d->chunk->size % impl->frame_size; d->chunk->size -= impl->leftover_count; memcpy(impl->leftover, SPA_PTROFF(d->data, d->chunk->size, void), impl->leftover_count); pw_stream_queue_buffer(impl->stream_source, buf); } static const struct pw_stream_events playback_stream_events = { PW_VERSION_STREAM_EVENTS, .destroy = stream_destroy_sink, .state_changed = stream_state_changed_sink, .process = playback_stream_process }; static const struct pw_stream_events capture_stream_events = { PW_VERSION_STREAM_EVENTS, .destroy = stream_destroy_source, .state_changed = stream_state_changed_source, .process = capture_stream_process }; static int create_stream(struct impl *impl) { int res; uint32_t n_params; const struct spa_pod *params[1]; uint8_t buffer[1024]; struct spa_pod_builder b; // sink if (impl->mode & MODE_XRDP_SINK) { impl->stream_sink = pw_stream_new(impl->core, "xrdp-sink", impl->stream_props_sink); impl->stream_props_sink = NULL; if (impl->stream_sink == NULL) return -errno; pw_stream_add_listener(impl->stream_sink, &impl->stream_listener_sink, &playback_stream_events, impl); } //source if (impl->mode & MODE_XRDP_SOURCE) { impl->stream_source = pw_stream_new(impl->core, "xrdp-source", impl->stream_props_source); impl->stream_props_source = NULL; if (impl->stream_source == NULL) return -errno; pw_stream_add_listener(impl->stream_source, &impl->stream_listener_source, &capture_stream_events, impl); } n_params = 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &impl->info); if (impl->mode & MODE_XRDP_SINK) { if ((res = pw_stream_connect(impl->stream_sink, PW_DIRECTION_INPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, params, n_params)) < 0) return res; } if (impl->mode & MODE_XRDP_SOURCE) { if ((res = pw_stream_connect(impl->stream_source, PW_DIRECTION_OUTPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, params, n_params)) < 0) return res; } return 0; } static void core_error(void *data, uint32_t id, int seq, int res, const char *message) { struct impl *impl = data; pw_log_error("error id:%u seq:%d res:%d (%s): %s", id, seq, res, spa_strerror(res), message); if (id == PW_ID_CORE && res == -EPIPE) //pw_impl_module_schedule_destroy(impl->module); unload_module(impl); } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .error = core_error, }; static void core_destroy(void *d) { struct impl *impl = d; spa_hook_remove(&impl->core_listener); impl->core = NULL; //pw_impl_module_schedule_destroy(impl->module); unload_module(impl); } static const struct pw_proxy_events core_proxy_events = { .destroy = core_destroy, }; static void impl_destroy(struct impl *impl) { close_send_sink(impl); close_send_source(impl); if (impl->stream_sink) pw_stream_destroy(impl->stream_sink); if (impl->core && impl->do_disconnect) pw_core_disconnect(impl->core); if (impl->filename_sink) { free(impl->filename_sink); impl->filename_sink = NULL; } if (impl->fd_sink >= 0) close(impl->fd_sink); pw_properties_free(impl->stream_props_sink); pw_properties_free(impl->props_sink); if (impl->stream_source) pw_stream_destroy(impl->stream_source); if (impl->filename_source) { free(impl->filename_source); impl->filename_source = NULL; } if (impl->fd_source >= 0) close(impl->fd_source); pw_properties_free(impl->stream_props_source); pw_properties_free(impl->props_source); free(impl->leftover); free(impl); } static void module_destroy(void *data) { struct impl *impl = data; spa_hook_remove(&impl->module_listener); impl_destroy(impl); } static const struct pw_impl_module_events module_events = { PW_VERSION_IMPL_MODULE_EVENTS, .destroy = module_destroy, }; static uint32_t channel_from_name(const char *name) { int i; for (i = 0; spa_type_audio_channel[i].name; i++) { if (spa_streq(name, spa_debug_type_short_name(spa_type_audio_channel[i].name))) return spa_type_audio_channel[i].type; } return SPA_AUDIO_CHANNEL_UNKNOWN; } static void parse_position(struct spa_audio_info_raw *info, const char *val, size_t len) { struct spa_json it[2]; char v[256]; spa_json_init(&it[0], val, len); if (spa_json_enter_array(&it[0], &it[1]) <= 0) spa_json_init(&it[1], val, len); info->channels = 0; while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && info->channels < SPA_AUDIO_MAX_CHANNELS) { info->position[info->channels++] = channel_from_name(v); } } static inline uint32_t format_from_name(const char *name, size_t len) { int i; for (i = 0; spa_type_audio_format[i].name; i++) { if (strncmp(name, spa_debug_type_short_name(spa_type_audio_format[i].name), len) == 0) return spa_type_audio_format[i].type; } return SPA_AUDIO_FORMAT_UNKNOWN; } static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { const char *str; spa_zero(*info); if ((str = pw_properties_get(props, PW_KEY_AUDIO_FORMAT)) == NULL) str = DEFAULT_FORMAT; info->format = format_from_name(str, strlen(str)); info->rate = pw_properties_get_uint32(props, PW_KEY_AUDIO_RATE, info->rate); if (info->rate == 0) info->rate = DEFAULT_RATE; info->channels = pw_properties_get_uint32(props, PW_KEY_AUDIO_CHANNELS, info->channels); info->channels = SPA_MIN(info->channels, SPA_AUDIO_MAX_CHANNELS); if ((str = pw_properties_get(props, SPA_KEY_AUDIO_POSITION)) != NULL) parse_position(info, str, strlen(str)); if (info->channels == 0) parse_position(info, DEFAULT_POSITION, strlen(DEFAULT_POSITION)); } static int calc_frame_size(const struct spa_audio_info_raw *info) { int res = info->channels; switch (info->format) { case SPA_AUDIO_FORMAT_U8: case SPA_AUDIO_FORMAT_S8: case SPA_AUDIO_FORMAT_ALAW: case SPA_AUDIO_FORMAT_ULAW: return res; case SPA_AUDIO_FORMAT_S16: case SPA_AUDIO_FORMAT_S16_OE: case SPA_AUDIO_FORMAT_U16: return res * 2; case SPA_AUDIO_FORMAT_S24: case SPA_AUDIO_FORMAT_S24_OE: case SPA_AUDIO_FORMAT_U24: return res * 3; case SPA_AUDIO_FORMAT_S24_32: case SPA_AUDIO_FORMAT_S24_32_OE: case SPA_AUDIO_FORMAT_S32: case SPA_AUDIO_FORMAT_S32_OE: case SPA_AUDIO_FORMAT_U32: case SPA_AUDIO_FORMAT_U32_OE: case SPA_AUDIO_FORMAT_F32: case SPA_AUDIO_FORMAT_F32_OE: return res * 4; case SPA_AUDIO_FORMAT_F64: case SPA_AUDIO_FORMAT_F64_OE: return res * 8; default: return 0; } } static void copy_props(struct pw_properties *stream_props, struct pw_properties *props, const char *key) { const char *str; if ((str = pw_properties_get(props, key)) != NULL) { if (pw_properties_get(stream_props, key) == NULL) pw_properties_set(stream_props, key, str); } } SPA_EXPORT int pipewire__module_init(struct pw_impl_module *module, const char *args) { struct pw_context *context = pw_impl_module_get_context(module); struct pw_properties *props = NULL; struct impl *impl; const char *str; int res; PW_LOG_TOPIC_INIT(mod_topic); impl = calloc(1, sizeof(struct impl)); if (impl == NULL) return -errno; impl->fd_sink = -1; impl->fd_source = -1; impl->filename_sink = NULL; impl->filename_source = NULL; impl->module = module; impl->context = context; impl->work = pw_context_get_work_queue(context); pw_log_debug("module %p: new %s", impl, args); if (args == NULL) args = ""; props = pw_properties_new_string(args); if (props == NULL) { res = -errno; pw_log_error( "can't create properties: %m"); goto error; } impl->props_sink = props; impl->stream_props_sink = pw_properties_new(NULL, NULL); if (impl->stream_props_sink == NULL) { res = -errno; pw_log_error( "can't create properties: %m"); goto error; } // sink if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL) pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); if (pw_properties_get(props, PW_KEY_NODE_NETWORK) == NULL) pw_properties_set(props, PW_KEY_NODE_NETWORK, "true"); if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL) pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Sink"); if ((str = pw_properties_get(props, "sink.stream.props")) != NULL) { impl->mode |= MODE_XRDP_SINK; pw_properties_update_string(impl->stream_props_sink, str, strlen(str)); } copy_props(impl->stream_props_sink, props, PW_KEY_AUDIO_FORMAT); copy_props(impl->stream_props_sink, props, PW_KEY_AUDIO_RATE); copy_props(impl->stream_props_sink, props, PW_KEY_AUDIO_CHANNELS); copy_props(impl->stream_props_sink, props, SPA_KEY_AUDIO_POSITION); copy_props(impl->stream_props_sink, props, PW_KEY_NODE_NAME); copy_props(impl->stream_props_sink, props, PW_KEY_NODE_DESCRIPTION); copy_props(impl->stream_props_sink, props, PW_KEY_NODE_GROUP); // copy_props(impl->stream_props_sink, props, PW_KEY_NODE_LATENCY); copy_props(impl->stream_props_sink, props, PW_KEY_NODE_VIRTUAL); copy_props(impl->stream_props_sink, props, PW_KEY_NODE_NETWORK); copy_props(impl->stream_props_sink, props, PW_KEY_MEDIA_CLASS); parse_audio_info(impl->stream_props_sink, &impl->info); if (impl->info.rate != 0 && pw_properties_get(props, PW_KEY_NODE_RATE) == NULL) pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", impl->info.rate); copy_props(impl->stream_props_sink, props, PW_KEY_NODE_RATE); if ((str = pw_properties_get(props, "sink.node.latency")) != NULL) pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%s/%u", str, impl->info.rate); copy_props(impl->stream_props_sink, props, PW_KEY_NODE_LATENCY); // source props = pw_properties_new_string(args); if (props == NULL) { res = -errno; pw_log_error( "can't create properties: %m"); goto error; } impl->props_source = props; impl->stream_props_source = pw_properties_new(NULL, NULL); if (impl->stream_props_source == NULL) { res = -errno; pw_log_error( "can't create properties: %m"); goto error; } if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL) pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); if (pw_properties_get(props, PW_KEY_NODE_NETWORK) == NULL) pw_properties_set(props, PW_KEY_NODE_NETWORK, "true"); if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL) pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Source"); if ((str = pw_properties_get(props, "source.stream.props")) != NULL) { impl->mode |= MODE_XRDP_SOURCE; pw_properties_update_string(impl->stream_props_source, str, strlen(str)); } copy_props(impl->stream_props_source, props, PW_KEY_AUDIO_FORMAT); copy_props(impl->stream_props_source, props, PW_KEY_AUDIO_RATE); copy_props(impl->stream_props_source, props, PW_KEY_AUDIO_CHANNELS); copy_props(impl->stream_props_source, props, SPA_KEY_AUDIO_POSITION); copy_props(impl->stream_props_source, props, PW_KEY_NODE_NAME); copy_props(impl->stream_props_source, props, PW_KEY_NODE_DESCRIPTION); copy_props(impl->stream_props_source, props, PW_KEY_NODE_GROUP); copy_props(impl->stream_props_source, props, PW_KEY_NODE_LATENCY); copy_props(impl->stream_props_source, props, PW_KEY_NODE_VIRTUAL); copy_props(impl->stream_props_source, props, PW_KEY_NODE_NETWORK); copy_props(impl->stream_props_source, props, PW_KEY_MEDIA_CLASS); parse_audio_info(impl->stream_props_source, &impl->info); impl->frame_size = calc_frame_size(&impl->info); if (impl->frame_size == 0) { pw_log_error("unsupported audio format:%d channels:%d", impl->info.format, impl->info.channels); res = -EINVAL; goto error; } if (impl->info.rate != 0 && pw_properties_get(props, PW_KEY_NODE_RATE) == NULL) pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", impl->info.rate); copy_props(impl->stream_props_source, props, PW_KEY_NODE_RATE); impl->leftover = calloc(1, impl->frame_size); if (impl->leftover == NULL) { res = -errno; pw_log_error("can't alloc leftover buffer: %m"); goto error; } if (!impl->mode) { res = -EINVAL; goto error; } impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { str = pw_properties_get(props, PW_KEY_REMOTE_NAME); impl->core = pw_context_connect(impl->context, pw_properties_new( PW_KEY_REMOTE_NAME, str, NULL), 0); impl->do_disconnect = true; } if (impl->core == NULL) { res = -errno; pw_log_error("can't connect: %m"); goto error; } pw_proxy_add_listener((struct pw_proxy*)impl->core, &impl->core_proxy_listener, &core_proxy_events, impl); pw_core_add_listener(impl->core, &impl->core_listener, &core_events, impl); set_socket_path(impl); if ((res = create_stream(impl)) < 0) goto error; pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); return 0; error: impl_destroy(impl); return res; } neutrinolabs-pipewire-module-xrdp-f8ce913/src/pw-cli_0358_mod.c000066400000000000000000001764701460045132400244050ustar00rootroot00000000000000/** * pw-cli.c for older systems * * This is src/tools/pw-cli.c from pipewire 0.3.58 modified to address * pipewire issue #2709:- * * https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/2709 * * This file is only built on older pipewire systems. It is ignored for * pipewire 0.3.59 and later. */ /* PipeWire * * Copyright © 2018 Wim Taymans * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ //#include "config.h" #include #include #include #include #include #include #include #if !defined(__FreeBSD__) && !defined(__MidnightBSD__) #include #endif #include #include #ifdef HAVE_READLINE #include #include #endif #include #if !defined(FNM_EXTMATCH) #define FNM_EXTMATCH 0 #endif #define spa_debug(fmt,...) printf(fmt"\n", ## __VA_ARGS__) #include #include #include #include #include #include #include #include #include static const char WHITESPACE[] = " \t"; static char prompt[64]; struct remote_data; struct proxy_data; typedef void (*info_func_t) (struct proxy_data *pd); struct class { const char *type; uint32_t version; const void *events; pw_destroy_t destroy; info_func_t info; const char *name_key; }; struct data { struct pw_main_loop *loop; struct pw_context *context; struct spa_list remotes; struct remote_data *current; struct pw_map vars; unsigned int interactive:1; unsigned int monitoring:1; unsigned int quit:1; }; struct global { struct remote_data *rd; uint32_t id; uint32_t permissions; uint32_t version; char *type; const struct class *class; struct pw_proxy *proxy; bool info_pending; struct pw_properties *properties; }; struct remote_data { struct spa_list link; struct data *data; char *name; uint32_t id; int prompt_pending; struct pw_core *core; struct spa_hook core_listener; struct spa_hook proxy_core_listener; struct pw_registry *registry; struct spa_hook registry_listener; struct pw_map globals; }; struct proxy_data { struct remote_data *rd; struct global *global; struct pw_proxy *proxy; void *info; const struct class *class; struct spa_hook proxy_listener; struct spa_hook object_listener; }; struct command { const char *name; const char *alias; const char *description; bool (*func) (struct data *data, const char *cmd, char *args, char **error); }; static struct spa_dict * global_props(struct global *global); static struct global * obj_global(struct remote_data *rd, uint32_t id); static int children_of(struct remote_data *rd, uint32_t parent_id, const char *child_type, uint32_t **children); static int pw_split_ip(char *str, const char *delimiter, int max_tokens, char *tokens[]) { const char *state = NULL; char *s, *t; size_t len, l2; int n = 0; s = (char *)pw_split_walk(str, delimiter, &len, &state); while (s && n + 1 < max_tokens) { t = (char*)pw_split_walk(str, delimiter, &l2, &state); s[len] = '\0'; tokens[n++] = s; s = t; len = l2; } if (s) tokens[n++] = s; return n; } static void print_properties(struct spa_dict *props, char mark, bool header) { const struct spa_dict_item *item; if (header) printf("%c\tproperties:\n", mark); if (props == NULL || props->n_items == 0) { if (header) printf("\t\tnone\n"); return; } spa_dict_for_each(item, props) { printf("%c\t\t%s = \"%s\"\n", mark, item->key, item->value); } } static void print_params(struct spa_param_info *params, uint32_t n_params, char mark, bool header) { uint32_t i; if (header) printf("%c\tparams: (%u)\n", mark, n_params); if (params == NULL || n_params == 0) { if (header) printf("\t\tnone\n"); return; } for (i = 0; i < n_params; i++) { const struct spa_type_info *type_info = spa_type_param; printf("%c\t %d (%s) %c%c\n", params[i].user > 0 ? mark : ' ', params[i].id, spa_debug_type_find_name(type_info, params[i].id), params[i].flags & SPA_PARAM_INFO_READ ? 'r' : '-', params[i].flags & SPA_PARAM_INFO_WRITE ? 'w' : '-'); params[i].user = 0; } } static bool do_not_implemented(struct data *data, const char *cmd, char *args, char **error) { *error = spa_aprintf("Command \"%s\" not yet implemented", cmd); return false; } static bool do_help(struct data *data, const char *cmd, char *args, char **error); static bool do_load_module(struct data *data, const char *cmd, char *args, char **error); static bool do_list_objects(struct data *data, const char *cmd, char *args, char **error); static bool do_connect(struct data *data, const char *cmd, char *args, char **error); static bool do_disconnect(struct data *data, const char *cmd, char *args, char **error); static bool do_list_remotes(struct data *data, const char *cmd, char *args, char **error); static bool do_switch_remote(struct data *data, const char *cmd, char *args, char **error); static bool do_info(struct data *data, const char *cmd, char *args, char **error); static bool do_create_device(struct data *data, const char *cmd, char *args, char **error); static bool do_create_node(struct data *data, const char *cmd, char *args, char **error); static bool do_destroy(struct data *data, const char *cmd, char *args, char **error); static bool do_create_link(struct data *data, const char *cmd, char *args, char **error); static bool do_export_node(struct data *data, const char *cmd, char *args, char **error); static bool do_enum_params(struct data *data, const char *cmd, char *args, char **error); static bool do_set_param(struct data *data, const char *cmd, char *args, char **error); static bool do_permissions(struct data *data, const char *cmd, char *args, char **error); static bool do_get_permissions(struct data *data, const char *cmd, char *args, char **error); static bool do_send_command(struct data *data, const char *cmd, char *args, char **error); static bool do_quit(struct data *data, const char *cmd, char *args, char **error); #define DUMP_NAMES "Core|Module|Device|Node|Port|Factory|Client|Link|Session|Endpoint|EndpointStream" static const struct command command_list[] = { { "help", "h", "Show this help", do_help }, { "load-module", "lm", "Load a module. []", do_load_module }, { "unload-module", "um", "Unload a module. ", do_not_implemented }, { "connect", "con", "Connect to a remote. []", do_connect }, { "disconnect", "dis", "Disconnect from a remote. []", do_disconnect }, { "list-remotes", "lr", "List connected remotes.", do_list_remotes }, { "switch-remote", "sr", "Switch between current remotes. []", do_switch_remote }, { "list-objects", "ls", "List objects or current remote. []", do_list_objects }, { "info", "i", "Get info about an object. |all", do_info }, { "create-device", "cd", "Create a device from a factory. []", do_create_device }, { "create-node", "cn", "Create a node from a factory. []", do_create_node }, { "destroy", "d", "Destroy a global object. ", do_destroy }, { "create-link", "cl", "Create a link between nodes. []", do_create_link }, { "export-node", "en", "Export a local node to the current remote. [remote-var]", do_export_node }, { "enum-params", "e", "Enumerate params of an object ", do_enum_params }, { "set-param", "s", "Set param of an object ", do_set_param }, { "permissions", "sp", "Set permissions for a client ", do_permissions }, { "get-permissions", "gp", "Get permissions of a client ", do_get_permissions }, { "send-command", "c", "Send a command ", do_send_command }, { "quit", "q", "Quit", do_quit }, }; static bool do_quit(struct data *data, const char *cmd, char *args, char **error) { pw_main_loop_quit(data->loop); data->quit = true; return true; } static bool do_help(struct data *data, const char *cmd, char *args, char **error) { size_t i; printf("Available commands:\n"); for (i = 0; i < SPA_N_ELEMENTS(command_list); i++) { char cmd[256]; snprintf(cmd, sizeof(cmd), "%s | %s", command_list[i].name, command_list[i].alias); printf("\t%-20.20s\t%s\n", cmd, command_list[i].description); } return true; } static bool do_load_module(struct data *data, const char *cmd, char *args, char **error) { struct pw_impl_module *module; char *a[2]; int n; uint32_t id; n = pw_split_ip(args, WHITESPACE, 2, a); if (n < 1) { *error = spa_aprintf("%s []", cmd); return false; } module = pw_context_load_module(data->context, a[0], n == 2 ? a[1] : NULL, NULL); if (module == NULL) { *error = spa_aprintf("Could not load module"); return false; } id = pw_map_insert_new(&data->vars, module); printf("%d = @module:%d\n", id, pw_global_get_id(pw_impl_module_get_global(module))); return true; } static void on_core_info(void *_data, const struct pw_core_info *info) { struct remote_data *rd = _data; free(rd->name); rd->name = info->name ? strdup(info->name) : NULL; if (rd->data->interactive) printf("remote %d is named '%s'\n", rd->id, rd->name); } static void set_prompt(struct remote_data *rd) { snprintf(prompt, sizeof(prompt), "%s>> ", rd->name); #ifdef HAVE_READLINE rl_set_prompt(prompt); #else printf("%s", prompt); fflush(stdout); #endif } static void on_core_done(void *_data, uint32_t id, int seq) { struct remote_data *rd = _data; struct data *d = rd->data; if (seq == rd->prompt_pending) { if (d->interactive) { set_prompt(rd); rd->data->monitoring = true; } else { pw_main_loop_quit(d->loop); } } } static bool global_matches(struct global *g, const char *pattern) { const char *str; if (g->properties == NULL) return false; if (strstr(g->type, pattern) != NULL) return true; if ((str = pw_properties_get(g->properties, PW_KEY_OBJECT_PATH)) != NULL && fnmatch(pattern, str, FNM_EXTMATCH) == 0) return true; if ((str = pw_properties_get(g->properties, PW_KEY_OBJECT_SERIAL)) != NULL && spa_streq(pattern, str)) return true; if (g->class != NULL && g->class->name_key != NULL && (str = pw_properties_get(g->properties, g->class->name_key)) != NULL && fnmatch(pattern, str, FNM_EXTMATCH) == 0) return true; return false; } static int print_global(void *obj, void *data) { struct global *global = obj; const char *filter = data; if (global == NULL) return 0; if (filter && !global_matches(global, filter)) return 0; printf("\tid %d, type %s/%d\n", global->id, global->type, global->version); if (global->properties) print_properties(&global->properties->dict, ' ', false); return 0; } static bool bind_global(struct remote_data *rd, struct global *global, char **error); static void registry_event_global(void *data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) { struct remote_data *rd = data; struct global *global; size_t size; char *error; bool ret; global = calloc(1, sizeof(struct global)); global->rd = rd; global->id = id; global->permissions = permissions; global->type = strdup(type); global->version = version; global->properties = props ? pw_properties_new_dict(props) : NULL; if (rd->data->monitoring) { printf("remote %d added global: ", rd->id); print_global(global, NULL); } size = pw_map_get_size(&rd->globals); while (id > size) pw_map_insert_at(&rd->globals, size++, NULL); pw_map_insert_at(&rd->globals, id, global); /* immediately bind the object always */ ret = bind_global(rd, global, &error); if (!ret) { if (rd->data->interactive) fprintf(stderr, "Error: \"%s\"\n", error); free(error); } } static int destroy_global(void *obj, void *data) { struct global *global = obj; if (global == NULL) return 0; if (global->proxy) pw_proxy_destroy(global->proxy); pw_map_insert_at(&global->rd->globals, global->id, NULL); pw_properties_free(global->properties); free(global->type); free(global); return 0; } static void registry_event_global_remove(void *data, uint32_t id) { struct remote_data *rd = data; struct global *global; global = pw_map_lookup(&rd->globals, id); if (global == NULL) { fprintf(stderr, "remote %d removed unknown global %d\n", rd->id, id); return; } if (rd->data->monitoring) { printf("remote %d removed global: ", rd->id); print_global(global, NULL); } destroy_global(global, rd); } static const struct pw_registry_events registry_events = { PW_VERSION_REGISTRY_EVENTS, .global = registry_event_global, .global_remove = registry_event_global_remove, }; static struct global *find_global(struct remote_data *rd, const char *pattern) { uint32_t id; union pw_map_item *item; if (spa_atou32(pattern, &id, 0)) return pw_map_lookup(&rd->globals, id); pw_array_for_each(item, &rd->globals.items) { struct global *g = item->data; if (pw_map_item_is_free(item) || g == NULL) continue; if (global_matches(g, pattern)) return g; } return NULL; } static void on_core_error(void *_data, uint32_t id, int seq, int res, const char *message) { struct remote_data *rd = _data; struct data *data = rd->data; pw_log_error("remote %p: error id:%u seq:%d res:%d (%s): %s", rd, id, seq, res, spa_strerror(res), message); if (id == PW_ID_CORE && res == -EPIPE) pw_main_loop_quit(data->loop); } static const struct pw_core_events remote_core_events = { PW_VERSION_CORE_EVENTS, .info = on_core_info, .done = on_core_done, .error = on_core_error, }; static void on_core_destroy(void *_data) { struct remote_data *rd = _data; struct data *data = rd->data; spa_list_remove(&rd->link); spa_hook_remove(&rd->core_listener); spa_hook_remove(&rd->proxy_core_listener); pw_map_remove(&data->vars, rd->id); pw_map_for_each(&rd->globals, destroy_global, rd); pw_map_clear(&rd->globals); if (data->current == rd) data->current = NULL; free(rd->name); } static const struct pw_proxy_events proxy_core_events = { PW_VERSION_PROXY_EVENTS, .destroy = on_core_destroy, }; static void remote_data_free(struct remote_data *rd) { spa_hook_remove(&rd->registry_listener); pw_proxy_destroy((struct pw_proxy*)rd->registry); pw_core_disconnect(rd->core); } static bool do_connect(struct data *data, const char *cmd, char *args, char **error) { char *a[1]; int n; struct pw_properties *props = NULL; struct pw_core *core; struct remote_data *rd; n = args ? pw_split_ip(args, WHITESPACE, 1, a) : 0; if (n == 1) { props = pw_properties_new(PW_KEY_REMOTE_NAME, a[0], NULL); } core = pw_context_connect(data->context, props, sizeof(struct remote_data)); if (core == NULL) { *error = spa_aprintf("failed to connect: %m"); return false; } rd = pw_proxy_get_user_data((struct pw_proxy*)core); rd->core = core; rd->data = data; pw_map_init(&rd->globals, 64, 16); rd->id = pw_map_insert_new(&data->vars, rd); spa_list_append(&data->remotes, &rd->link); if (rd->data->interactive) printf("%d = @remote:%p\n", rd->id, rd->core); data->current = rd; pw_core_add_listener(rd->core, &rd->core_listener, &remote_core_events, rd); pw_proxy_add_listener((struct pw_proxy*)rd->core, &rd->proxy_core_listener, &proxy_core_events, rd); rd->registry = pw_core_get_registry(rd->core, PW_VERSION_REGISTRY, 0); pw_registry_add_listener(rd->registry, &rd->registry_listener, ®istry_events, rd); rd->prompt_pending = pw_core_sync(rd->core, 0, 0); return true; } static bool do_disconnect(struct data *data, const char *cmd, char *args, char **error) { char *a[1]; int n; uint32_t idx; struct remote_data *rd = data->current; n = pw_split_ip(args, WHITESPACE, 1, a); if (n >= 1) { idx = atoi(a[0]); rd = pw_map_lookup(&data->vars, idx); if (rd == NULL) goto no_remote; } if (rd) remote_data_free(rd); if (data->current == NULL) { if (spa_list_is_empty(&data->remotes)) { return true; } data->current = spa_list_last(&data->remotes, struct remote_data, link); } return true; no_remote: *error = spa_aprintf("Remote %d does not exist", idx); return false; } static bool do_list_remotes(struct data *data, const char *cmd, char *args, char **error) { struct remote_data *rd; spa_list_for_each(rd, &data->remotes, link) printf("\t%d = @remote:%p '%s'\n", rd->id, rd->core, rd->name); return true; } static bool do_switch_remote(struct data *data, const char *cmd, char *args, char **error) { char *a[1]; int n, idx = 0; struct remote_data *rd; n = pw_split_ip(args, WHITESPACE, 1, a); if (n == 1) idx = atoi(a[0]); rd = pw_map_lookup(&data->vars, idx); if (rd == NULL) goto no_remote; spa_list_remove(&rd->link); spa_list_append(&data->remotes, &rd->link); data->current = rd; return true; no_remote: *error = spa_aprintf("Remote %d does not exist", idx); return false; } #define MARK_CHANGE(f) ((((info)->change_mask & (f))) ? '*' : ' ') static void info_global(struct proxy_data *pd) { struct global *global = pd->global; if (global == NULL) return; printf("\tid: %d\n", global->id); printf("\tpermissions: "PW_PERMISSION_FORMAT"\n", PW_PERMISSION_ARGS(global->permissions)); printf("\ttype: %s/%d\n", global->type, global->version); } static void info_core(struct proxy_data *pd) { struct pw_core_info *info = pd->info; info_global(pd); printf("\tcookie: %u\n", info->cookie); printf("\tuser-name: \"%s\"\n", info->user_name); printf("\thost-name: \"%s\"\n", info->host_name); printf("\tversion: \"%s\"\n", info->version); printf("\tname: \"%s\"\n", info->name); print_properties(info->props, MARK_CHANGE(PW_CORE_CHANGE_MASK_PROPS), true); info->change_mask = 0; } static void info_module(struct proxy_data *pd) { struct pw_module_info *info = pd->info; info_global(pd); printf("\tname: \"%s\"\n", info->name); printf("\tfilename: \"%s\"\n", info->filename); printf("\targs: \"%s\"\n", info->args); print_properties(info->props, MARK_CHANGE(PW_MODULE_CHANGE_MASK_PROPS), true); info->change_mask = 0; } static void info_node(struct proxy_data *pd) { struct pw_node_info *info = pd->info; info_global(pd); printf("%c\tinput ports: %u/%u\n", MARK_CHANGE(PW_NODE_CHANGE_MASK_INPUT_PORTS), info->n_input_ports, info->max_input_ports); printf("%c\toutput ports: %u/%u\n", MARK_CHANGE(PW_NODE_CHANGE_MASK_OUTPUT_PORTS), info->n_output_ports, info->max_output_ports); printf("%c\tstate: \"%s\"", MARK_CHANGE(PW_NODE_CHANGE_MASK_STATE), pw_node_state_as_string(info->state)); if (info->state == PW_NODE_STATE_ERROR && info->error) printf(" \"%s\"\n", info->error); else printf("\n"); print_properties(info->props, MARK_CHANGE(PW_NODE_CHANGE_MASK_PROPS), true); print_params(info->params, info->n_params, MARK_CHANGE(PW_NODE_CHANGE_MASK_PARAMS), true); info->change_mask = 0; } static void info_port(struct proxy_data *pd) { struct pw_port_info *info = pd->info; info_global(pd); printf("\tdirection: \"%s\"\n", pw_direction_as_string(info->direction)); print_properties(info->props, MARK_CHANGE(PW_PORT_CHANGE_MASK_PROPS), true); print_params(info->params, info->n_params, MARK_CHANGE(PW_PORT_CHANGE_MASK_PARAMS), true); info->change_mask = 0; } static void info_factory(struct proxy_data *pd) { struct pw_factory_info *info = pd->info; info_global(pd); printf("\tname: \"%s\"\n", info->name); printf("\tobject-type: %s/%d\n", info->type, info->version); print_properties(info->props, MARK_CHANGE(PW_FACTORY_CHANGE_MASK_PROPS), true); info->change_mask = 0; } static void info_client(struct proxy_data *pd) { struct pw_client_info *info = pd->info; info_global(pd); print_properties(info->props, MARK_CHANGE(PW_CLIENT_CHANGE_MASK_PROPS), true); info->change_mask = 0; } static void info_link(struct proxy_data *pd) { struct pw_link_info *info = pd->info; info_global(pd); printf("\toutput-node-id: %u\n", info->output_node_id); printf("\toutput-port-id: %u\n", info->output_port_id); printf("\tinput-node-id: %u\n", info->input_node_id); printf("\tinput-port-id: %u\n", info->input_port_id); printf("%c\tstate: \"%s\"", MARK_CHANGE(PW_LINK_CHANGE_MASK_STATE), pw_link_state_as_string(info->state)); if (info->state == PW_LINK_STATE_ERROR && info->error) printf(" \"%s\"\n", info->error); else printf("\n"); printf("%c\tformat:\n", MARK_CHANGE(PW_LINK_CHANGE_MASK_FORMAT)); if (info->format) spa_debug_pod(2, NULL, info->format); else printf("\t\tnone\n"); print_properties(info->props, MARK_CHANGE(PW_LINK_CHANGE_MASK_PROPS), true); info->change_mask = 0; } static void info_device(struct proxy_data *pd) { struct pw_device_info *info = pd->info; info_global(pd); print_properties(info->props, MARK_CHANGE(PW_DEVICE_CHANGE_MASK_PROPS), true); print_params(info->params, info->n_params, MARK_CHANGE(PW_DEVICE_CHANGE_MASK_PARAMS), true); info->change_mask = 0; } static void info_session(struct proxy_data *pd) { struct pw_session_info *info = pd->info; info_global(pd); print_properties(info->props, MARK_CHANGE(0), true); print_params(info->params, info->n_params, MARK_CHANGE(1), true); info->change_mask = 0; } static void info_endpoint(struct proxy_data *pd) { struct pw_endpoint_info *info = pd->info; const char *direction; info_global(pd); printf("\tname: %s\n", info->name); printf("\tmedia-class: %s\n", info->media_class); switch(info->direction) { case PW_DIRECTION_OUTPUT: direction = "source"; break; case PW_DIRECTION_INPUT: direction = "sink"; break; default: direction = "invalid"; break; } printf("\tdirection: %s\n", direction); printf("\tflags: 0x%x\n", info->flags); printf("%c\tstreams: %u\n", MARK_CHANGE(0), info->n_streams); printf("%c\tsession: %u\n", MARK_CHANGE(1), info->session_id); print_properties(info->props, MARK_CHANGE(2), true); print_params(info->params, info->n_params, MARK_CHANGE(3), true); info->change_mask = 0; } static void info_endpoint_stream(struct proxy_data *pd) { struct pw_endpoint_stream_info *info = pd->info; info_global(pd); printf("\tid: %u\n", info->id); printf("\tendpoint-id: %u\n", info->endpoint_id); printf("\tname: %s\n", info->name); print_properties(info->props, MARK_CHANGE(1), true); print_params(info->params, info->n_params, MARK_CHANGE(2), true); info->change_mask = 0; } static void core_event_info(void *data, const struct pw_core_info *info) { struct proxy_data *pd = data; struct remote_data *rd = pd->rd; if (pd->info) printf("remote %d core %d changed\n", rd->id, info->id); pd->info = pw_core_info_update(pd->info, info); if (pd->global == NULL) pd->global = pw_map_lookup(&rd->globals, info->id); if (pd->global && pd->global->info_pending) { info_core(pd); pd->global->info_pending = false; } } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .info = core_event_info }; static void module_event_info(void *data, const struct pw_module_info *info) { struct proxy_data *pd = data; struct remote_data *rd = pd->rd; if (pd->info) printf("remote %d module %d changed\n", rd->id, info->id); pd->info = pw_module_info_update(pd->info, info); if (pd->global == NULL) pd->global = pw_map_lookup(&rd->globals, info->id); if (pd->global && pd->global->info_pending) { info_module(pd); pd->global->info_pending = false; } } static const struct pw_module_events module_events = { PW_VERSION_MODULE_EVENTS, .info = module_event_info }; static void node_event_info(void *data, const struct pw_node_info *info) { struct proxy_data *pd = data; struct remote_data *rd = pd->rd; if (pd->info) printf("remote %d node %d changed\n", rd->id, info->id); pd->info = pw_node_info_update(pd->info, info); if (pd->global == NULL) pd->global = pw_map_lookup(&rd->globals, info->id); if (pd->global && pd->global->info_pending) { info_node(pd); pd->global->info_pending = false; } } static void event_param(void *_data, int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param) { struct proxy_data *data = _data; struct remote_data *rd = data->rd; if (rd->data->interactive) printf("remote %d object %d param %d index %d\n", rd->id, data->global->id, id, index); spa_debug_pod(2, NULL, param); } static const struct pw_node_events node_events = { PW_VERSION_NODE_EVENTS, .info = node_event_info, .param = event_param }; static void port_event_info(void *data, const struct pw_port_info *info) { struct proxy_data *pd = data; struct remote_data *rd = pd->rd; if (pd->info) printf("remote %d port %d changed\n", rd->id, info->id); pd->info = pw_port_info_update(pd->info, info); if (pd->global == NULL) pd->global = pw_map_lookup(&rd->globals, info->id); if (pd->global && pd->global->info_pending) { info_port(pd); pd->global->info_pending = false; } } static const struct pw_port_events port_events = { PW_VERSION_PORT_EVENTS, .info = port_event_info, .param = event_param }; static void factory_event_info(void *data, const struct pw_factory_info *info) { struct proxy_data *pd = data; struct remote_data *rd = pd->rd; if (pd->info) printf("remote %d factory %d changed\n", rd->id, info->id); pd->info = pw_factory_info_update(pd->info, info); if (pd->global == NULL) pd->global = pw_map_lookup(&rd->globals, info->id); if (pd->global && pd->global->info_pending) { info_factory(pd); pd->global->info_pending = false; } } static const struct pw_factory_events factory_events = { PW_VERSION_FACTORY_EVENTS, .info = factory_event_info }; static void client_event_info(void *data, const struct pw_client_info *info) { struct proxy_data *pd = data; struct remote_data *rd = pd->rd; if (pd->info) printf("remote %d client %d changed\n", rd->id, info->id); pd->info = pw_client_info_update(pd->info, info); if (pd->global == NULL) pd->global = pw_map_lookup(&rd->globals, info->id); if (pd->global && pd->global->info_pending) { info_client(pd); pd->global->info_pending = false; } } static void client_event_permissions(void *_data, uint32_t index, uint32_t n_permissions, const struct pw_permission *permissions) { struct proxy_data *data = _data; struct remote_data *rd = data->rd; uint32_t i; printf("remote %d node %d index %d\n", rd->id, data->global->id, index); for (i = 0; i < n_permissions; i++) { if (permissions[i].id == PW_ID_ANY) printf(" default:"); else printf(" %u:", permissions[i].id); printf(" "PW_PERMISSION_FORMAT"\n", PW_PERMISSION_ARGS(permissions[i].permissions)); } } static const struct pw_client_events client_events = { PW_VERSION_CLIENT_EVENTS, .info = client_event_info, .permissions = client_event_permissions }; static void link_event_info(void *data, const struct pw_link_info *info) { struct proxy_data *pd = data; struct remote_data *rd = pd->rd; if (pd->info) printf("remote %d link %d changed\n", rd->id, info->id); pd->info = pw_link_info_update(pd->info, info); if (pd->global == NULL) pd->global = pw_map_lookup(&rd->globals, info->id); if (pd->global && pd->global->info_pending) { info_link(pd); pd->global->info_pending = false; } } static const struct pw_link_events link_events = { PW_VERSION_LINK_EVENTS, .info = link_event_info }; static void device_event_info(void *data, const struct pw_device_info *info) { struct proxy_data *pd = data; struct remote_data *rd = pd->rd; if (pd->info) printf("remote %d device %d changed\n", rd->id, info->id); pd->info = pw_device_info_update(pd->info, info); if (pd->global == NULL) pd->global = pw_map_lookup(&rd->globals, info->id); if (pd->global && pd->global->info_pending) { info_device(pd); pd->global->info_pending = false; } } static const struct pw_device_events device_events = { PW_VERSION_DEVICE_EVENTS, .info = device_event_info, .param = event_param }; static void session_info_free(struct pw_session_info *info) { free(info->params); pw_properties_free ((struct pw_properties *)info->props); free(info); } static void session_event_info(void *data, const struct pw_session_info *update) { struct proxy_data *pd = data; struct remote_data *rd = pd->rd; struct pw_session_info *info = pd->info; if (!info) { info = pd->info = calloc(1, sizeof(*info)); info->id = update->id; } if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_PARAMS) { info->n_params = update->n_params; free(info->params); info->params = malloc(info->n_params * sizeof(struct spa_param_info)); memcpy(info->params, update->params, info->n_params * sizeof(struct spa_param_info)); } if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_PROPS) { pw_properties_free ((struct pw_properties *)info->props); info->props = (struct spa_dict *) pw_properties_new_dict (update->props); } if (pd->global == NULL) pd->global = pw_map_lookup(&rd->globals, info->id); if (pd->global && pd->global->info_pending) { info_session(pd); pd->global->info_pending = false; } } static const struct pw_session_events session_events = { PW_VERSION_SESSION_EVENTS, .info = session_event_info, .param = event_param }; static void endpoint_info_free(struct pw_endpoint_info *info) { free(info->name); free(info->media_class); free(info->params); pw_properties_free ((struct pw_properties *)info->props); free(info); } static void endpoint_event_info(void *data, const struct pw_endpoint_info *update) { struct proxy_data *pd = data; struct remote_data *rd = pd->rd; struct pw_endpoint_info *info = pd->info; if (!info) { info = pd->info = calloc(1, sizeof(*info)); info->id = update->id; info->name = update->name ? strdup(update->name) : NULL; info->media_class = update->media_class ? strdup(update->media_class) : NULL; info->direction = update->direction; info->flags = update->flags; } if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_STREAMS) info->n_streams = update->n_streams; if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_SESSION) info->session_id = update->session_id; if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_PARAMS) { info->n_params = update->n_params; free(info->params); info->params = malloc(info->n_params * sizeof(struct spa_param_info)); memcpy(info->params, update->params, info->n_params * sizeof(struct spa_param_info)); } if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_PROPS) { pw_properties_free ((struct pw_properties *)info->props); info->props = (struct spa_dict *) pw_properties_new_dict (update->props); } if (pd->global == NULL) pd->global = pw_map_lookup(&rd->globals, info->id); if (pd->global && pd->global->info_pending) { info_endpoint(pd); pd->global->info_pending = false; } } static const struct pw_endpoint_events endpoint_events = { PW_VERSION_ENDPOINT_EVENTS, .info = endpoint_event_info, .param = event_param }; static void endpoint_stream_info_free(struct pw_endpoint_stream_info *info) { free(info->name); free(info->params); pw_properties_free ((struct pw_properties *)info->props); free(info); } static void endpoint_stream_event_info(void *data, const struct pw_endpoint_stream_info *update) { struct proxy_data *pd = data; struct remote_data *rd = pd->rd; struct pw_endpoint_stream_info *info = pd->info; if (!info) { info = pd->info = calloc(1, sizeof(*info)); info->id = update->id; info->endpoint_id = update->endpoint_id; info->name = update->name ? strdup(update->name) : NULL; } if (update->change_mask & PW_ENDPOINT_STREAM_CHANGE_MASK_PARAMS) { info->n_params = update->n_params; free(info->params); info->params = malloc(info->n_params * sizeof(struct spa_param_info)); memcpy(info->params, update->params, info->n_params * sizeof(struct spa_param_info)); } if (update->change_mask & PW_ENDPOINT_STREAM_CHANGE_MASK_PROPS) { pw_properties_free ((struct pw_properties *)info->props); info->props = (struct spa_dict *) pw_properties_new_dict (update->props); } if (pd->global == NULL) pd->global = pw_map_lookup(&rd->globals, info->id); if (pd->global && pd->global->info_pending) { info_endpoint_stream(pd); pd->global->info_pending = false; } } static const struct pw_endpoint_stream_events endpoint_stream_events = { PW_VERSION_ENDPOINT_STREAM_EVENTS, .info = endpoint_stream_event_info, .param = event_param }; static void removed_proxy (void *data) { struct proxy_data *pd = data; pw_proxy_destroy(pd->proxy); } static void destroy_proxy (void *data) { struct proxy_data *pd = data; spa_hook_remove(&pd->proxy_listener); spa_hook_remove(&pd->object_listener); if (pd->global) pd->global->proxy = NULL; if (pd->info == NULL) return; if (pd->class->destroy) pd->class->destroy(pd->info); pd->info = NULL; } static const struct pw_proxy_events proxy_events = { PW_VERSION_PROXY_EVENTS, .removed = removed_proxy, .destroy = destroy_proxy, }; static bool do_list_objects(struct data *data, const char *cmd, char *args, char **error) { struct remote_data *rd = data->current; pw_map_for_each(&rd->globals, print_global, args); return true; } static const struct class core_class = { .type = PW_TYPE_INTERFACE_Core, .version = PW_VERSION_CORE, .events = &core_events, .destroy = (pw_destroy_t) pw_core_info_free, .info = info_core, .name_key = PW_KEY_CORE_NAME, }; static const struct class module_class = { .type = PW_TYPE_INTERFACE_Module, .version = PW_VERSION_MODULE, .events = &module_events, .destroy = (pw_destroy_t) pw_module_info_free, .info = info_module, .name_key = PW_KEY_MODULE_NAME, }; static const struct class factory_class = { .type = PW_TYPE_INTERFACE_Factory, .version = PW_VERSION_FACTORY, .events = &factory_events, .destroy = (pw_destroy_t) pw_factory_info_free, .info = info_factory, .name_key = PW_KEY_FACTORY_NAME, }; static const struct class client_class = { .type = PW_TYPE_INTERFACE_Client, .version = PW_VERSION_CLIENT, .events = &client_events, .destroy = (pw_destroy_t) pw_client_info_free, .info = info_client, .name_key = PW_KEY_APP_NAME, }; static const struct class device_class = { .type = PW_TYPE_INTERFACE_Device, .version = PW_VERSION_DEVICE, .events = &device_events, .destroy = (pw_destroy_t) pw_device_info_free, .info = info_device, .name_key = PW_KEY_DEVICE_NAME, }; static const struct class node_class = { .type = PW_TYPE_INTERFACE_Node, .version = PW_VERSION_NODE, .events = &node_events, .destroy = (pw_destroy_t) pw_node_info_free, .info = info_node, .name_key = PW_KEY_NODE_NAME, }; static const struct class port_class = { .type = PW_TYPE_INTERFACE_Port, .version = PW_VERSION_PORT, .events = &port_events, .destroy = (pw_destroy_t) pw_port_info_free, .info = info_port, .name_key = PW_KEY_PORT_NAME, }; static const struct class link_class = { .type = PW_TYPE_INTERFACE_Link, .version = PW_VERSION_LINK, .events = &link_events, .destroy = (pw_destroy_t) pw_link_info_free, .info = info_link, }; static const struct class session_class = { .type = PW_TYPE_INTERFACE_Session, .version = PW_VERSION_SESSION, .events = &session_events, .destroy = (pw_destroy_t) session_info_free, .info = info_session, }; static const struct class endpoint_class = { .type = PW_TYPE_INTERFACE_Endpoint, .version = PW_VERSION_ENDPOINT, .events = &endpoint_events, .destroy = (pw_destroy_t) endpoint_info_free, .info = info_endpoint, }; static const struct class endpoint_stream_class = { .type = PW_TYPE_INTERFACE_EndpointStream, .version = PW_VERSION_ENDPOINT_STREAM, .events = &endpoint_stream_events, .destroy = (pw_destroy_t) endpoint_stream_info_free, .info = info_endpoint_stream, }; static const struct class metadata_class = { .type = PW_TYPE_INTERFACE_Metadata, .version = PW_VERSION_METADATA, .name_key = PW_KEY_METADATA_NAME, }; static const struct class *classes[] = { &core_class, &module_class, &factory_class, &client_class, &device_class, &node_class, &port_class, &link_class, &session_class, &endpoint_class, &endpoint_stream_class, &metadata_class, }; static const struct class *find_class(const char *type, uint32_t version) { size_t i; for (i = 0; i < SPA_N_ELEMENTS(classes); i++) { if (spa_streq(classes[i]->type, type) && classes[i]->version <= version) return classes[i]; } return NULL; } static bool bind_global(struct remote_data *rd, struct global *global, char **error) { const struct class *class; struct proxy_data *pd; struct pw_proxy *proxy; class = find_class(global->type, global->version); if (class == NULL) { *error = spa_aprintf("unsupported type %s", global->type); return false; } global->class = class; proxy = pw_registry_bind(rd->registry, global->id, global->type, class->version, sizeof(struct proxy_data)); pd = pw_proxy_get_user_data(proxy); pd->rd = rd; pd->global = global; pd->proxy = proxy; pd->class = class; pw_proxy_add_object_listener(proxy, &pd->object_listener, class->events, pd); pw_proxy_add_listener(proxy, &pd->proxy_listener, &proxy_events, pd); global->proxy = proxy; rd->prompt_pending = pw_core_sync(rd->core, 0, 0); return true; } static bool do_global_info(struct global *global, char **error) { struct remote_data *rd = global->rd; struct proxy_data *pd; if (global->proxy == NULL) { if (!bind_global(rd, global, error)) return false; global->info_pending = true; } else { pd = pw_proxy_get_user_data(global->proxy); if (pd->class->info) pd->class->info(pd); } return true; } static int do_global_info_all(void *obj, void *data) { struct global *global = obj; char *error; if (global == NULL) return 0; if (!do_global_info(global, &error)) { fprintf(stderr, "info: %s\n", error); free(error); } return 0; } static bool do_info(struct data *data, const char *cmd, char *args, char **error) { struct remote_data *rd = data->current; char *a[1]; int n; struct global *global; n = pw_split_ip(args, WHITESPACE, 1, a); if (n < 1) { *error = spa_aprintf("%s |all", cmd); return false; } if (spa_streq(a[0], "all")) { pw_map_for_each(&rd->globals, do_global_info_all, NULL); } else { global = find_global(rd, a[0]); if (global == NULL) { *error = spa_aprintf("%s: unknown global '%s'", cmd, a[0]); return false; } return do_global_info(global, error); } return true; } static bool do_create_device(struct data *data, const char *cmd, char *args, char **error) { struct remote_data *rd = data->current; char *a[2]; int n; uint32_t id; struct pw_proxy *proxy; struct pw_properties *props = NULL; struct proxy_data *pd; n = pw_split_ip(args, WHITESPACE, 2, a); if (n < 1) { *error = spa_aprintf("%s []", cmd); return false; } if (n == 2) props = pw_properties_new_string(a[1]); proxy = pw_core_create_object(rd->core, a[0], PW_TYPE_INTERFACE_Device, PW_VERSION_DEVICE, props ? &props->dict : NULL, sizeof(struct proxy_data)); pw_properties_free(props); pd = pw_proxy_get_user_data(proxy); pd->rd = rd; pd->proxy = proxy; pd->class = &device_class; pw_proxy_add_object_listener(proxy, &pd->object_listener, &device_events, pd); pw_proxy_add_listener(proxy, &pd->proxy_listener, &proxy_events, pd); id = pw_map_insert_new(&data->vars, proxy); printf("%d = @proxy:%d\n", id, pw_proxy_get_id(proxy)); return true; } static bool do_create_node(struct data *data, const char *cmd, char *args, char **error) { struct remote_data *rd = data->current; char *a[2]; int n; uint32_t id; struct pw_proxy *proxy; struct pw_properties *props = NULL; struct proxy_data *pd; n = pw_split_ip(args, WHITESPACE, 2, a); if (n < 1) { *error = spa_aprintf("%s []", cmd); return false; } if (n == 2) props = pw_properties_new_string(a[1]); proxy = pw_core_create_object(rd->core, a[0], PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, props ? &props->dict : NULL, sizeof(struct proxy_data)); pw_properties_free(props); pd = pw_proxy_get_user_data(proxy); pd->rd = rd; pd->proxy = proxy; pd->class = &node_class; pw_proxy_add_object_listener(proxy, &pd->object_listener, &node_events, pd); pw_proxy_add_listener(proxy, &pd->proxy_listener, &proxy_events, pd); id = pw_map_insert_new(&data->vars, proxy); printf("%d = @proxy:%d\n", id, pw_proxy_get_id(proxy)); return true; } static bool do_destroy(struct data *data, const char *cmd, char *args, char **error) { struct remote_data *rd = data->current; char *a[1]; int n; struct global *global; n = pw_split_ip(args, WHITESPACE, 1, a); if (n < 1) { *error = spa_aprintf("%s ", cmd); return false; } global = find_global(rd, a[0]); if (global == NULL) { *error = spa_aprintf("%s: unknown global '%s'", cmd, a[0]); return false; } pw_registry_destroy(rd->registry, global->id); return true; } static struct global * obj_global_port(struct remote_data *rd, struct global *global, const char *port_direction, const char *port_id) { struct global *global_port_found = NULL; uint32_t *ports = NULL; int port_count; port_count = children_of(rd, global->id, PW_TYPE_INTERFACE_Port, &ports); if (port_count <= 0) return NULL; for (int i = 0; i < port_count; i++) { struct global *global_port = obj_global(rd, ports[i]); if (!global_port) continue; struct spa_dict *props_port = global_props(global_port); if (spa_streq(spa_dict_lookup(props_port, "port.direction"), port_direction) && spa_streq(spa_dict_lookup(props_port, "port.id"), port_id)) { global_port_found = global_port; break; } } free(ports); return global_port_found; } static void create_link_with_properties(struct data *data, struct pw_properties *props) { struct remote_data *rd = data->current; uint32_t id; struct pw_proxy *proxy; struct proxy_data *pd; proxy = (struct pw_proxy*)pw_core_create_object(rd->core, "link-factory", PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, props ? &props->dict : NULL, sizeof(struct proxy_data)); pd = pw_proxy_get_user_data(proxy); pd->rd = rd; pd->proxy = proxy; pd->class = &link_class; pw_proxy_add_object_listener(proxy, &pd->object_listener, &link_events, pd); pw_proxy_add_listener(proxy, &pd->proxy_listener, &proxy_events, pd); id = pw_map_insert_new(&data->vars, proxy); printf("%d = @proxy:%d\n", id, pw_proxy_get_id((struct pw_proxy*)proxy)); } static bool do_create_link(struct data *data, const char *cmd, char *args, char **error) { struct remote_data *rd = data->current; char *a[5]; int n; struct pw_properties *props = NULL; n = pw_split_ip(args, WHITESPACE, 5, a); if (n < 4) { *error = spa_aprintf("%s []", cmd); return false; } if (n == 5) props = pw_properties_new_string(a[4]); else props = pw_properties_new(NULL, NULL); if (!spa_streq(a[0], "-")) pw_properties_set(props, PW_KEY_LINK_OUTPUT_NODE, a[0]); if (!spa_streq(a[1], "-")) pw_properties_set(props, PW_KEY_LINK_OUTPUT_PORT, a[1]); if (!spa_streq(a[2], "-")) pw_properties_set(props, PW_KEY_LINK_INPUT_NODE, a[2]); if (!spa_streq(a[3], "-")) pw_properties_set(props, PW_KEY_LINK_INPUT_PORT, a[3]); if (spa_streq(a[1], "*") && spa_streq(a[3], "*")) { struct global *global_out, *global_in; struct proxy_data *pd_out, *pd_in; uint32_t n_output_ports, n_input_ports; global_out = find_global(rd, a[0]); if (global_out == NULL) { *error = spa_aprintf("%s: unknown global '%s'", cmd, a[0]); return false; } global_in = find_global(rd, a[2]); if (global_in == NULL) { *error = spa_aprintf("%s: unknown global '%s'", cmd, a[2]); return false; } pd_out = pw_proxy_get_user_data(global_out->proxy); pd_in = pw_proxy_get_user_data(global_in->proxy); n_output_ports = ((struct pw_node_info *)pd_out->info)->n_output_ports; n_input_ports = ((struct pw_node_info *)pd_in->info)->n_input_ports; if (n_output_ports != n_input_ports) { *error = spa_aprintf("%s: Number of ports don't match (%u != %u)", cmd, n_output_ports, n_input_ports); return false; } for (uint32_t i = 0; i < n_output_ports; i++) { char port_id[4]; struct global *global_port_out, *global_port_in; snprintf(port_id, 4, "%d", i); global_port_out = obj_global_port(rd, global_out, "out", port_id); global_port_in = obj_global_port(rd, global_in, "in", port_id); if (!global_port_out || !global_port_in) continue; pw_properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%d", global_port_out->id); pw_properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%d", global_port_in->id); create_link_with_properties(data, props); } } else create_link_with_properties(data, props); pw_properties_free(props); return true; } static bool do_export_node(struct data *data, const char *cmd, char *args, char **error) { struct remote_data *rd = data->current; struct pw_global *global; struct pw_node *node; struct pw_proxy *proxy; char *a[2]; int n, idx; uint32_t id; n = pw_split_ip(args, WHITESPACE, 2, a); if (n < 1) { *error = spa_aprintf("%s []", cmd); return false; } if (n == 2) { idx = atoi(a[1]); rd = pw_map_lookup(&data->vars, idx); if (rd == NULL) goto no_remote; } global = pw_context_find_global(data->context, atoi(a[0])); if (global == NULL) { *error = spa_aprintf("object %d does not exist", atoi(a[0])); return false; } if (!pw_global_is_type(global, PW_TYPE_INTERFACE_Node)) { *error = spa_aprintf("object %d is not a node", atoi(a[0])); return false; } node = pw_global_get_object(global); proxy = pw_core_export(rd->core, PW_TYPE_INTERFACE_Node, NULL, node, 0); id = pw_map_insert_new(&data->vars, proxy); printf("%d = @proxy:%d\n", id, pw_proxy_get_id((struct pw_proxy*)proxy)); return true; no_remote: *error = spa_aprintf("Remote %d does not exist", idx); return false; } static bool do_enum_params(struct data *data, const char *cmd, char *args, char **error) { struct remote_data *rd = data->current; char *a[2]; int n; uint32_t param_id; const struct spa_type_info *ti; struct global *global; n = pw_split_ip(args, WHITESPACE, 2, a); if (n < 2) { *error = spa_aprintf("%s ", cmd); return false; } ti = spa_debug_type_find_short(spa_type_param, a[1]); if (ti == NULL) { *error = spa_aprintf("%s: unknown param type: %s", cmd, a[1]); return false; } param_id = ti->type; global = find_global(rd, a[0]); if (global == NULL) { *error = spa_aprintf("%s: unknown global '%s'", cmd, a[0]); return false; } if (global->proxy == NULL) { if (!bind_global(rd, global, error)) return false; } if (spa_streq(global->type, PW_TYPE_INTERFACE_Node)) pw_node_enum_params((struct pw_node*)global->proxy, 0, param_id, 0, 0, NULL); else if (spa_streq(global->type, PW_TYPE_INTERFACE_Port)) pw_port_enum_params((struct pw_port*)global->proxy, 0, param_id, 0, 0, NULL); else if (spa_streq(global->type, PW_TYPE_INTERFACE_Device)) pw_device_enum_params((struct pw_device*)global->proxy, 0, param_id, 0, 0, NULL); else if (spa_streq(global->type, PW_TYPE_INTERFACE_Endpoint)) pw_endpoint_enum_params((struct pw_endpoint*)global->proxy, 0, param_id, 0, 0, NULL); else { *error = spa_aprintf("enum-params not implemented on object %d type:%s", atoi(a[0]), global->type); return false; } return true; } static bool do_set_param(struct data *data, const char *cmd, char *args, char **error) { struct remote_data *rd = data->current; char *a[3]; int res, n; uint32_t param_id; struct global *global; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); const struct spa_type_info *ti; struct spa_pod *pod; n = pw_split_ip(args, WHITESPACE, 3, a); if (n < 3) { *error = spa_aprintf("%s ", cmd); return false; } global = find_global(rd, a[0]); if (global == NULL) { *error = spa_aprintf("%s: unknown global '%s'", cmd, a[0]); return false; } if (global->proxy == NULL) { if (!bind_global(rd, global, error)) return false; } ti = spa_debug_type_find_short(spa_type_param, a[1]); if (ti == NULL) { *error = spa_aprintf("%s: unknown param type: %s", cmd, a[1]); return false; } if ((res = spa_json_to_pod(&b, 0, ti, a[2], strlen(a[2]))) < 0) { *error = spa_aprintf("%s: can't make pod: %s", cmd, spa_strerror(res)); return false; } if ((pod = spa_pod_builder_deref(&b, 0)) == NULL) { *error = spa_aprintf("%s: can't make pod", cmd); return false; } spa_debug_pod(0, NULL, pod); param_id = ti->type; if (spa_streq(global->type, PW_TYPE_INTERFACE_Node)) pw_node_set_param((struct pw_node*)global->proxy, param_id, 0, pod); else if (spa_streq(global->type, PW_TYPE_INTERFACE_Device)) pw_device_set_param((struct pw_device*)global->proxy, param_id, 0, pod); else if (spa_streq(global->type, PW_TYPE_INTERFACE_Endpoint)) pw_endpoint_set_param((struct pw_endpoint*)global->proxy, param_id, 0, pod); else { *error = spa_aprintf("set-param not implemented on object %d type:%s", atoi(a[0]), global->type); return false; } return true; } static bool do_permissions(struct data *data, const char *cmd, char *args, char **error) { struct remote_data *rd = data->current; char *a[3]; int n; uint32_t p; struct global *global; struct pw_permission permissions[1]; n = pw_split_ip(args, WHITESPACE, 3, a); if (n < 3) { *error = spa_aprintf("%s ", cmd); return false; } global = find_global(rd, a[0]); if (global == NULL) { *error = spa_aprintf("%s: unknown global '%s'", cmd, a[0]); return false; } if (!spa_streq(global->type, PW_TYPE_INTERFACE_Client)) { *error = spa_aprintf("object %d is not a client", atoi(a[0])); return false; } if (global->proxy == NULL) { if (!bind_global(rd, global, error)) return false; } p = strtol(a[2], NULL, 0); printf("setting permissions: "PW_PERMISSION_FORMAT"\n", PW_PERMISSION_ARGS(p)); permissions[0] = PW_PERMISSION_INIT(atoi(a[1]), p); pw_client_update_permissions((struct pw_client*)global->proxy, 1, permissions); return true; } static bool do_get_permissions(struct data *data, const char *cmd, char *args, char **error) { struct remote_data *rd = data->current; char *a[3]; int n; struct global *global; n = pw_split_ip(args, WHITESPACE, 1, a); if (n < 1) { *error = spa_aprintf("%s ", cmd); return false; } global = find_global(rd, a[0]); if (global == NULL) { *error = spa_aprintf("%s: unknown global '%s'", cmd, a[0]); return false; } if (!spa_streq(global->type, PW_TYPE_INTERFACE_Client)) { *error = spa_aprintf("object %d is not a client", atoi(a[0])); return false; } if (global->proxy == NULL) { if (!bind_global(rd, global, error)) return false; } pw_client_get_permissions((struct pw_client*)global->proxy, 0, UINT32_MAX); return true; } static bool do_send_command(struct data *data, const char *cmd, char *args, char **error) { struct remote_data *rd = data->current; char *a[3]; int res, n; struct global *global; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); const struct spa_type_info *ti; struct spa_pod *pod; n = pw_split_ip(args, WHITESPACE, 3, a); if (n < 3) { *error = spa_aprintf("%s ", cmd); return false; } global = find_global(rd, a[0]); if (global == NULL) { *error = spa_aprintf("%s: unknown global '%s'", cmd, a[0]); return false; } if (global->proxy == NULL) { if (!bind_global(rd, global, error)) return false; } if (spa_streq(global->type, PW_TYPE_INTERFACE_Node)) { ti = spa_debug_type_find_short(spa_type_node_command_id, a[1]); } else { *error = spa_aprintf("send-command not implemented on object %d type:%s", atoi(a[0]), global->type); return false; } if (ti == NULL) { *error = spa_aprintf("%s: unknown node command type: %s", cmd, a[1]); return false; } if ((res = spa_json_to_pod(&b, 0, ti, a[2], strlen(a[2]))) < 0) { *error = spa_aprintf("%s: can't make pod: %s", cmd, spa_strerror(res)); return false; } if ((pod = spa_pod_builder_deref(&b, 0)) == NULL) { *error = spa_aprintf("%s: can't make pod", cmd); return false; } spa_debug_pod(0, NULL, pod); pw_node_send_command((struct pw_node*)global->proxy, (struct spa_command*)pod); return true; } static struct global * obj_global(struct remote_data *rd, uint32_t id) { struct global *global; struct proxy_data *pd; if (!rd) return NULL; global = pw_map_lookup(&rd->globals, id); if (!global) return NULL; pd = pw_proxy_get_user_data(global->proxy); if (!pd || !pd->info) return NULL; return global; } static struct spa_dict * global_props(struct global *global) { struct proxy_data *pd; if (!global) return NULL; pd = pw_proxy_get_user_data(global->proxy); if (!pd || !pd->info) return NULL; if (spa_streq(global->type, PW_TYPE_INTERFACE_Core)) return ((struct pw_core_info *)pd->info)->props; if (spa_streq(global->type, PW_TYPE_INTERFACE_Module)) return ((struct pw_module_info *)pd->info)->props; if (spa_streq(global->type, PW_TYPE_INTERFACE_Device)) return ((struct pw_device_info *)pd->info)->props; if (spa_streq(global->type, PW_TYPE_INTERFACE_Node)) return ((struct pw_node_info *)pd->info)->props; if (spa_streq(global->type, PW_TYPE_INTERFACE_Port)) return ((struct pw_port_info *)pd->info)->props; if (spa_streq(global->type, PW_TYPE_INTERFACE_Factory)) return ((struct pw_factory_info *)pd->info)->props; if (spa_streq(global->type, PW_TYPE_INTERFACE_Client)) return ((struct pw_client_info *)pd->info)->props; if (spa_streq(global->type, PW_TYPE_INTERFACE_Link)) return ((struct pw_link_info *)pd->info)->props; if (spa_streq(global->type, PW_TYPE_INTERFACE_Session)) return ((struct pw_session_info *)pd->info)->props; if (spa_streq(global->type, PW_TYPE_INTERFACE_Endpoint)) return ((struct pw_endpoint_info *)pd->info)->props; if (spa_streq(global->type, PW_TYPE_INTERFACE_EndpointStream)) return ((struct pw_endpoint_stream_info *)pd->info)->props; return NULL; } static const char * global_lookup(struct global *global, const char *key) { struct spa_dict *dict; dict = global_props(global); if (!dict) return NULL; return spa_dict_lookup(dict, key); } static int children_of(struct remote_data *rd, uint32_t parent_id, const char *child_type, uint32_t **children) { const char *parent_type; union pw_map_item *item; struct global *global; struct proxy_data *pd; const char *parent_key = NULL, *child_key = NULL; const char *parent_value = NULL, *child_value = NULL; int pass, i, count; if (!rd || !children) return -1; /* get the device info */ global = obj_global(rd, parent_id); if (!global) return -1; parent_type = global->type; pd = pw_proxy_get_user_data(global->proxy); if (!pd || !pd->info) return -1; /* supported combinations */ if (spa_streq(parent_type, PW_TYPE_INTERFACE_Device) && spa_streq(child_type, PW_TYPE_INTERFACE_Node)) { parent_key = PW_KEY_OBJECT_ID; child_key = PW_KEY_DEVICE_ID; } else if (spa_streq(parent_type, PW_TYPE_INTERFACE_Node) && spa_streq(child_type, PW_TYPE_INTERFACE_Port)) { parent_key = PW_KEY_OBJECT_ID; child_key = PW_KEY_NODE_ID; } else if (spa_streq(parent_type, PW_TYPE_INTERFACE_Module) && spa_streq(child_type, PW_TYPE_INTERFACE_Factory)) { parent_key = PW_KEY_OBJECT_ID; child_key = PW_KEY_MODULE_ID; } else if (spa_streq(parent_type, PW_TYPE_INTERFACE_Factory) && spa_streq(child_type, PW_TYPE_INTERFACE_Device)) { parent_key = PW_KEY_OBJECT_ID; child_key = PW_KEY_FACTORY_ID; } else return -1; /* get the parent key value */ if (parent_key) { parent_value = global_lookup(global, parent_key); if (!parent_value) return -1; } count = 0; *children = NULL; i = 0; for (pass = 1; pass <= 2; pass++) { if (pass == 2) { count = i; if (!count) return 0; *children = malloc(sizeof(uint32_t) * count); if (!*children) return -1; } i = 0; pw_array_for_each(item, &rd->globals.items) { if (pw_map_item_is_free(item) || item->data == NULL) continue; global = item->data; if (!spa_streq(global->type, child_type)) continue; pd = pw_proxy_get_user_data(global->proxy); if (!pd || !pd->info) return -1; if (child_key) { /* get the device path */ child_value = global_lookup(global, child_key); if (!child_value) continue; } /* match? */ if (!spa_streq(parent_value, child_value)) continue; if (*children) (*children)[i] = global->id; i++; } } return count; } #define INDENT(_level) \ ({ \ int __level = (_level); \ char *_indent = alloca(__level + 1); \ memset(_indent, '\t', __level); \ _indent[__level] = '\0'; \ (const char *)_indent; \ }) static bool parse(struct data *data, char *buf, char **error) { char *a[2]; int n; size_t i; char *p, *cmd, *args; char empty_arg[] = ""; if ((p = strchr(buf, '#'))) *p = '\0'; p = pw_strip(buf, "\n\r \t"); if (*p == '\0') return true; n = pw_split_ip(p, WHITESPACE, 2, a); if (n < 1) return true; cmd = a[0]; args = n > 1 ? a[1] : empty_arg; for (i = 0; i < SPA_N_ELEMENTS(command_list); i++) { if (spa_streq(command_list[i].name, cmd) || spa_streq(command_list[i].alias, cmd)) { return command_list[i].func(data, cmd, args, error); } } *error = spa_aprintf("Command \"%s\" does not exist. Type 'help' for usage.", cmd); return false; } /* We need a global variable, readline doesn't have a closure arg */ static struct data *input_dataptr; static void input_process_line(char *line) { struct data *d = input_dataptr; char *error; if (!line) line = strdup("quit"); if (line[0] != '\0') { #ifdef HAVE_READLINE add_history(line); #endif if (!parse(d, line, &error)) { fprintf(stderr, "Error: \"%s\"\n", error); free(error); } } free(line); } static void do_input(void *data, int fd, uint32_t mask) { struct data *d = data; if (mask & SPA_IO_IN) { input_dataptr = d; #ifdef HAVE_READLINE rl_callback_read_char(); #else { char *line = NULL; size_t s = 0; if (getline(&line, &s, stdin) < 0) { free(line); line = NULL; } input_process_line(line); } #endif if (d->current == NULL) pw_main_loop_quit(d->loop); else { struct remote_data *rd = d->current; if (rd->core) rd->prompt_pending = pw_core_sync(rd->core, 0, 0); } } } #ifdef HAVE_READLINE static char * readline_match_command(const char *text, int state) { static size_t idx; static int len; if (!state) { idx = 0; len = strlen(text); } while (idx < SPA_N_ELEMENTS(command_list)) { const char *name = command_list[idx].name; const char *alias = command_list[idx].alias; idx++; if (spa_strneq(name, text, len) || spa_strneq(alias, text, len)) return strdup(name); } return NULL; } static char ** readline_command_completion(const char *text, int start, int end) { char **matches = NULL; /* Only try to complete the first word in a line */ if (start == 0) matches = rl_completion_matches(text, readline_match_command); /* Don't fall back to filename completion */ rl_attempted_completion_over = true; return matches; } static void readline_init() { rl_attempted_completion_function = readline_command_completion; rl_callback_handler_install(">> ", input_process_line); } static void readline_cleanup() { rl_callback_handler_remove(); } #endif static void do_quit_on_signal(void *data, int signal_number) { struct data *d = data; d->quit = true; pw_main_loop_quit(d->loop); } static void show_help(struct data *data, const char *name, bool error) { char empty_arg[] = ""; fprintf(error ? stderr : stdout, _("%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n\n"), name); do_help(data, "help", empty_arg, NULL); } int main(int argc, char *argv[]) { struct data data = { 0 }; struct pw_loop *l; char *opt_remote = NULL; char *error; bool daemon = false, monitor = false; struct remote_data *rd; static const struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, { "monitor", no_argument, NULL, 'm' }, { "daemon", no_argument, NULL, 'd' }, { "remote", required_argument, NULL, 'r' }, { NULL, 0, NULL, 0} }; int c, i; setlinebuf(stdout); setlocale(LC_ALL, ""); pw_init(&argc, &argv); while ((c = getopt_long(argc, argv, "hVmdr:", long_options, NULL)) != -1) { switch (c) { case 'h': show_help(&data, argv[0], false); return 0; case 'V': printf("%s\n" "Compiled with libpipewire %s\n" "Linked with libpipewire %s\n", argv[0], pw_get_headers_version(), pw_get_library_version()); return 0; case 'd': daemon = true; break; case 'm': monitor = true; break; case 'r': opt_remote = optarg; break; default: show_help(&data, argv[0], true); return -1; } } data.loop = pw_main_loop_new(NULL); if (data.loop == NULL) { fprintf(stderr, "Broken installation: %m\n"); return -1; } l = pw_main_loop_get_loop(data.loop); pw_loop_add_signal(l, SIGINT, do_quit_on_signal, &data); pw_loop_add_signal(l, SIGTERM, do_quit_on_signal, &data); spa_list_init(&data.remotes); pw_map_init(&data.vars, 64, 16); data.context = pw_context_new(l, pw_properties_new( PW_KEY_CORE_DAEMON, daemon ? "true" : NULL, NULL), 0); if (data.context == NULL) { fprintf(stderr, "Can't create context: %m\n"); return -1; } pw_context_load_module(data.context, "libpipewire-module-link-factory", NULL, NULL); if (!do_connect(&data, "connect", opt_remote, &error)) { fprintf(stderr, "Error: \"%s\"\n", error); return -1; } if (optind == argc) { data.interactive = true; printf("Welcome to PipeWire version %s. Type 'help' for usage.\n", pw_get_library_version()); #ifdef HAVE_READLINE readline_init(); #endif pw_loop_add_io(l, STDIN_FILENO, SPA_IO_IN|SPA_IO_HUP, false, do_input, &data); pw_main_loop_run(data.loop); #ifdef HAVE_READLINE readline_cleanup(); #endif } else { char buf[4096], *p, *error; p = buf; for (i = optind; i < argc; i++) { p = stpcpy(p, argv[i]); p = stpcpy(p, " "); } pw_main_loop_run(data.loop); if (!parse(&data, buf, &error)) { fprintf(stderr, "Error: \"%s\"\n", error); free(error); } data.current->prompt_pending = pw_core_sync(data.current->core, 0, 0); while (!data.quit && data.current) { pw_main_loop_run(data.loop); if (!monitor) break; } } spa_list_consume(rd, &data.remotes, link) remote_data_free(rd); pw_context_destroy(data.context); pw_main_loop_destroy(data.loop); pw_map_clear(&data.vars); pw_deinit(); return 0; } // cc -o pw-cli_0358_mod pw-cli_0358_mod.c $(pkg-config --cflags --libs libpipewire-0.3)