diff --git a/.gitignore b/.gitignore index 9802ce6..dd6862d 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ miracle-dhcp miracle-sinkctl miracle-wifictl miracle-wifid +miracle-uibcctl miraclectl miracled stamp-h1 @@ -35,3 +36,9 @@ test_valgrind test_wpas wpa_cli wpa_supplicant +CMakeFiles/ +cmake_install.cmake +CMakeCache.txt +libmiracle-shared.a +install_manifest.txt +/build/ diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml new file mode 100644 index 0000000..58a1fd0 --- /dev/null +++ b/.semaphore/semaphore.yml @@ -0,0 +1,35 @@ +version: v1.0 +name: Docker + +agent: + machine: + type: e1-standard-2 + os_image: ubuntu1804 + +global_job_config: + secrets: + - name: dockerhub + +blocks: + - name: Checkout + task: + jobs: + - name: Checkout + commands: + - checkout + - name: Build + task: + jobs: + - name: Autotools + commands: + - checkout + - docker build -t autotools -f autotools.Dockerfile . + - name: Cmake + commands: + - checkout + - docker build -t cmake -f cmake.Dockerfile . + - name: meson + commands: + - checkout + - docker build -t meson -f meson.Dockerfile . + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c2a4f7b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +language: c + +services: + - docker + +jobs: + include: + - stage: autotools + script: + docker build -t autotools -f autotools.Dockerfile . + - stage: cmake + script: + docker build -t cmake -f cmake.Dockerfile . + - stage: meson + script: + docker build -t meson -f meson.Dockerfile . diff --git a/CMakeLists.txt b/CMakeLists.txt index ffbe32c..8e4dd04 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,28 +1,63 @@ -set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake - ${CMAKE_MODULE_PATH}) - -set(CMAKE_C_FLAGS "-std=gnu11") -add_definitions(-D_GNU_SOURCE) - +cmake_minimum_required(VERSION 2.8) project(Miraclecast) -cmake_minimum_required(VERSION 3.0) - -add_subdirectory(src) -add_subdirectory(res) -add_subdirectory(test) - -SET(BUILD_BINDIR "${CMAKE_INSTALL_PREFIX}/bin") -OPTION(BUILD_ENABLE_DEBUG "Enable Debug" ON ) -OPTION(RELY_UDEV "Rely in udev tag to select device" OFF ) -OPTION(BUILD_TESTS "Enable TEST" ON ) SET(PACKAGE_NAME miraclecast) SET(PACKAGE_VERSION 1) SET(PACKAGE_STRING "${PACKAGE_NAME} ${PACKAGE_VERSION}") +set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake + ${CMAKE_MODULE_PATH}) + +set(CMAKE_C_FLAGS "-std=gnu11 ${CMAKE_C_FLAGS}") +add_definitions(-D_GNU_SOURCE) + +OPTION(ENABLE_SYSTEMD "Enable Systemd" ON) + find_package(PkgConfig) + +if(ENABLE_SYSTEMD) + pkg_check_modules (SYSTEMD REQUIRED libsystemd>=213) + SET(SESSION_LIBRARIES "${SYSTEMD_LIBRARIES}") +else(ENABLE_SYSTEMD) + pkg_check_modules (ELOGIND REQUIRED libelogind>=213) + include_directories ("${ELOGIND_INCLUDEDIR}") + SET(SESSION_LIBRARIES "${ELOGIND_LIBRARIES}") +endif(ENABLE_SYSTEMD) + +SET(BUILD_BINDIR "${CMAKE_INSTALL_PREFIX}/bin") +OPTION(BUILD_ENABLE_DEBUG "Enable Debug" ON ) +OPTION(RELY_UDEV "Rely in udev tag to select device" OFF ) +OPTION(BUILD_TESTS "Enable TEST" ON ) +OPTION(BUILD_ENABLE_CPPCHECK "Enable CPPCheck static analysis" OFF ) +SET(IP_BINARY "/bin/ip" CACHE STRING "Path to ip binary") + +if(BUILD_ENABLE_DEBUG) + add_definitions(-DBUILD_ENABLE_DEBUG) +endif() + +set(SYSCONFDIR "/etc" CACHE STRING "system config dir") +set(DATADIR "${CMAKE_INSTALL_PREFIX}/share" CACHE STRING "shared data dir") + pkg_check_modules (GLIB2 REQUIRED glib-2.0) pkg_check_modules (UDEV REQUIRED libudev) -CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) +CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/config.h.cmake ${CMAKE_BINARY_DIR}/config.h) + +if(BUILD_ENABLE_CPPCHECK) + find_program(CMAKE_C_CPPCHECK NAMES cppcheck) + if (CMAKE_C_CPPCHECK) + list( + APPEND CMAKE_C_CPPCHECK + "--enable=warning" + "--inline-suppr" + "--std=c11" + "-D__SIZEOF_POINTER__=8" + ) + endif() +endif() + + +add_subdirectory(src) +add_subdirectory(res) +add_subdirectory(test) diff --git a/COPYING b/COPYING index 0dbad09..e6f33de 100644 --- a/COPYING +++ b/COPYING @@ -8,7 +8,7 @@ This software was written by: This software is licensed under the terms of the LGPL. Please see each source file for the related copyright notice and license. -If a file does not contain a copright notice, the following notice shall apply: +If a file does not contain a copyright notice, the following notice shall apply: MiracleCast - Wifi-Display/Miracast Implementation diff --git a/Makefile.am b/Makefile.am index ef42db4..5a62abe 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4,3 +4,21 @@ EXTRA_DIST = README.md \ NEWS ACLOCAL_AMFLAGS = -I m4 + +.PHONY: lcov genlcov lcov-clean + +lcov: + -$(MAKE) $(AM_MAKEFLAGS) -k check + $(MAKE) $(AM_MAKEFLAGS) genlcov + +# we have to massage the lcov.info file slightly to hide the effect of libtool +# placing the objects files in the .libs/ directory separate from the *.c +genlcov: + $(LTP) --directory $(top_builddir) --capture --output-file miraclecast-lcov.info --test-name GLIB_PERF --no-checksum + LANG=C $(LTP_GENHTML) --prefix $(top_builddir) --output-directory miraclecast-lcov --title "Miraclecast Code Coverage" --legend --show-details miraclecast-lcov.info + +lcov-clean: + -$(LTP) --directory $(top_builddir) -z + -rm -rf miraclecast-lcov.info miraclecast-lcov + -find -name '*.gcda' -print | xargs -Ix rm x + diff --git a/README.md b/README.md index d7b1448..b100f41 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,46 @@ # MiracleCast - Wifi-Display/Miracast Implementation [![Join the chat at https://gitter.im/albfan/miraclecast](https://badges.gitter.im/albfan/miraclecast.svg)](https://gitter.im/albfan/miraclecast?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Semaphore CI Build Status](https://albfan.semaphoreci.com/badges/miraclecast/branches/master.svg?style=shields)](https://albfan.semaphoreci.com/projects/miraclecast) +[![Travis CI Build Status](https://travis-ci.org/albfan/miraclecast.svg?branch=master)](https://travis-ci.org/albfan/miraclecast) +[![Coverage Status](https://coveralls.io/repos/github/albfan/miraclecast/badge.svg?branch=master)](https://coveralls.io/github/albfan/miraclecast?branch=master) The MiracleCast project provides software to connect external monitors to your system via Wi-Fi. It is compatible to the Wifi-Display specification also known as Miracast. MiracleCast implements the Display-Source as well as Display-Sink side. -The Display-Source side allows you to connect external displays to your system and stream local content to the device. A lot of effort is put into making this as easy as connecting external displays via HDMI. +The Display-Source side allows you to connect external displays to your system and stream local content to the device. A lot of effort is put into making this as easy as connecting external displays via HDMI. *Note: This is not implemented yet. Please see [#4](../../issues/4).* On the other hand, the Display-Sink side allows you to create wifi-capable external displays yourself. You can use it on your embedded devices or even on full desktops to allow other systems to use your device as external display. - ## Requirements The MiracleCast projects requires the following software to be installed: - **systemd**: A system management daemon. It is used for device-management (udev), dbus management (sd-bus) and service management. - Systemd must be compiled with --enable-kdbus, even though kdbus isn't used, but only the independent, experimental sd-libraries. + Systemd >= 221 will work out of the box. For earlier versions systemd must be compiled with --enable-kdbus, even though kdbus isn't used, but only the independent, experimental sd-libraries. *required*: >=systemd-213 - **glib**: A utility library. Used by the current DHCP implementation. Will be removed once sd-dns gains DHCP-server capabilities. *required*: ~=glib2-2.38 (might work with older releases, untested..) + - **gstreamer**: MiracleCast rely on gstreamer to show cast its output. You can test if all needed is installed launching [res/test-viewer.sh](https://github.com/albfan/miraclecast/blob/master/res/test-viewer.sh) + + - **wpa_supplicant**: MiracleCast spawns wpa_supplicant with a custom config. + + - **P2P Wi-Fi device** Although widespread these days, there are some devices not compatible with [Wi-Fi Direct](http://en.wikipedia.org/wiki/Wi-Fi_Direct) (prior know as Wi-Fi P2P). Test yours with [res/test-hardware-capabilities.sh](https://github.com/albfan/miraclecast/blob/master/res/test-hardware-capabilities.sh) + - **check**: Test-suite for C programs. Used for optional tests of the MiracleCast code base. *optional*: ~=check-0.9.11 (might work with older releases, untested..) - - **gstreamer**: MiracleCast relay on gstreamer to show cast its output. You can test if all needed is installed launching [res/test-viewer.sh](https://github.com/albfan/miraclecast/blob/master/res/test-viewer.sh) - - - **P2P Wi-Fi device** Although widespread this days, there are some devices not compatible with [Wi-Fi Direct](http://en.wikipedia.org/wiki/Wi-Fi_Direct) (prior know as Wi-Fi P2P). Test yours with [res/test-hardware-capabilities.sh](https://github.com/albfan/miraclecast/blob/master/res/test-hardware-capabilities.sh) - - copy the dbus policy **res/org.freedesktop.miracle.conf** to `/etc/dbus-1/system.d/` -## Install +## Build and install -To compile MiracleCast, you can choose from [autotools](http://en.wikipedia.org/wiki/GNU_build_system) or [cmake](http://en.wikipedia.org/wiki/CMake): +To compile MiracleCast, you can choose from: -Autotools: + - [autotools](http://en.wikipedia.org/wiki/GNU_build_system) + - [cmake](http://en.wikipedia.org/wiki/CMake) + - [meson](http://mesonbuild.com/) - $ ./autogen.sh - $ mkdir build - $ cd build - $ ../configure --prefix=/usr/local #avoid --prefix for a standard install - -Cmake: - - $ mkdir build - $ cd build - $ cmake .. - -Compile - - $ make - -Test - - $ make check #only with autotools by now - -Install - - $ sudo make install +See more info on wiki [Building](https://github.com/albfan/miraclecast/wiki/Building) ## Automatic interface selection with udev @@ -69,17 +54,23 @@ You can also choose the interface with `--interface` option for miracle-wifid. ### Ubuntu -This specific linux flavour is so hard to get miraclecast dependencies that an alternative repo was created to install systemd with dbus +Check your systemd version with: + + $ systemctl --version + +If you are on 221 or above your systemd has kdbus enabled. + +If you are below 221, an alternative repo was created to install systemd with dbus https://github.com/albfan/systemd-ubuntu-with-dbus -At this time, ubuntu is on version 15.04 and systemd is stick on 219 version, use branch [systemd-219](https://github.com/albfan/miraclecast/tree/systemd-219) to compile miraclecast +See there was interface changes on systemd 219, if you are below that version, use branch [systemd-219](https://github.com/albfan/miraclecast/tree/systemd-219) to compile miraclecast > See specific instructions on that repo ### Arch linux -Use existing [AUR package](https://aur.archlinux.org/packages/miraclecast/). Remember to enable kdus to systemd-git dependency +Use existing [AUR package](https://aur.archlinux.org/packages/miraclecast-git/). Remember to enable kdbus to systemd-git dependency if you are below 221 systemd. $ export _systemd_git_kdbus=--enable-kdbus @@ -94,15 +85,12 @@ If you feel confidence enough (since systemd is the entrypoint for an OS) extrac ## Documentation -Steps to use it as sink: +### Steps to use it as sink - 1. shutdown wpa_supplicant + 1. shutdown wpa_supplicant and NetworkManager - $ sudo kill -9 $(ps -ef | grep wpa_supplican[t] | awk '{print $2}') - # now you can use `res/kill-wpa.sh` - - >Remember to save your config to use with `res/normal-wifi.sh` - >it will be easily located with `ps -ef | grep wpa_supplicant` on `-c` option. + $ systemctl stop NetworkManager.service + $ systemctl stop wpa_supplicant.service 2. launch wifi daemon @@ -121,7 +109,7 @@ Steps to use it as sink: 6. See your screen device on this machine -Steps to use it as peer: +### Steps to use it as peer 1. Repeat steps 1 and 2 from "use as sink" @@ -133,9 +121,15 @@ Steps to use it as peer: 4. Locate them using scanning - > psp-scan + > p2p-scan - 5. Apart from list, or show info with peer <mac> there's nothing useful here by now. + 5. Apart from list, or show info with peer <mac> there's nothing useful here by now. For a Q&D see [Using as peer](https://github.com/albfan/miraclecast/issues/4) + +## UIBC + +> The User Input Back Channel (UIBC) is an optional WFD feature that when implemented facilitates communication of user inputs to a User Interface, present at the WFD Sink, to the WFD Source. + +To use it just add `--uibc` on `miracle-sinkctl` startup. Single mouse events and key events are implemented. ## Autocompletion @@ -158,4 +152,4 @@ If you have any questions, do not hesitate to contact one of the maintainers. - Website: http://www.freedesktop.org/wiki/Software/miracle - Original repo: git://people.freedesktop.org/~dvdhrm/miracle - Fork repo: https://github.com/albfan/miraclecast - +- Technical spec: https://www.wi-fi.org/file/wi-fi-display-technical-specification-v11 (free registration required) diff --git a/autogen.sh b/autogen.sh index 3c5fdcb..48dc21e 100755 --- a/autogen.sh +++ b/autogen.sh @@ -1,2 +1,68 @@ #!/bin/sh -autoreconf -f --install + +set -e + +oldpwd=$(pwd) +topdir=$(dirname $0) +cd $topdir + +#intltoolize --force --automake +autoreconf --force --install --symlink + +libdir() { + echo $(cd "$1/$(gcc -print-multi-os-directory)"; pwd) +} + +args="\ +--sysconfdir=/etc \ +--localstatedir=/var \ +--libdir=$(libdir /usr/lib) \ +" + +if [ -f "$topdir/.config.args" ]; then + args="$args $(cat $topdir/.config.args)" +fi + +cd $oldpwd + +if [ "x$1" = "xc" ]; then + shift + args="$args $@" + $topdir/configure CFLAGS='-g -O0 -ftrapv' $args +elif [ "x$1" = "xg" ]; then + shift + args="$args $@" + $topdir/configure CFLAGS='-g -O0 -ftrapv' $args +elif [ "x$1" = "xa" ]; then + shift + args="$args $@" + $topdir/configure CFLAGS='-g -O0 -Wsuggest-attribute=pure -Wsuggest-attribute=const -ftrapv' $args +elif [ "x$1" = "xl" ]; then + shift + args="$args $@" + $topdir/configure CC=clang CFLAGS='-g -O0 -ftrapv' $args +elif [ "x$1" = "xs" ]; then + shift + args="$args $@" + scan-build $topdir/configure CFLAGS='-std=gnu99 -g -O0 -ftrapv' $args + scan-build make +else + cat < 219]) -PKG_CHECK_MODULES([GDHCP], [glib-2.0]) +AS_IF([test "$use_libsystemd" = "yes"], + [ + AC_DEFINE([ENABLE_SYSTEMD], [], [Use systemd]) + m4_ifdef([PKG_CHECK_MODULES], [ + PKG_CHECK_MODULES([DEPS], [libudev libsystemd > 219]) + PKG_CHECK_MODULES([GLIB], [glib-2.0]) + ]) + ], + [ + m4_ifdef([PKG_CHECK_MODULES], [ + PKG_CHECK_MODULES([DEPS], [libudev libelogind]) + PKG_CHECK_MODULES([GLIB], [glib-2.0]) + ]) + ]) AC_CHECK_HEADERS(readline/readline.h,, AC_MSG_ERROR(GNU readline not found)) @@ -54,10 +82,84 @@ AC_CHECK_HEADERS(readline/readline.h,, AC_MSG_ERROR(GNU readline not found)) # all tests. # -PKG_CHECK_MODULES([CHECK], [check], - [have_check=yes], [have_check=no]) +m4_ifdef([PKG_CHECK_MODULES], [ + PKG_CHECK_MODULES([CHECK], [check], + [have_check=yes], [have_check=no]) +]) AM_CONDITIONAL([BUILD_HAVE_CHECK], [test "x$have_check" = "xyes"]) +if test "x$have_check" = "xyes" +then + + dnl ************************************ + dnl *** Enable lcov coverage reports *** + dnl ************************************ + + AC_ARG_ENABLE(gcov, + AS_HELP_STRING([--enable-gcov], + [Enable gcov]), + [use_gcov=$enableval], [use_gcov=no]) + + if test "x$use_gcov" = "xyes"; then + dnl we need gcc: + if test "$GCC" != "yes"; then + AC_MSG_ERROR([GCC is required for --enable-gcov]) + fi + + dnl Check if ccache is being used + AC_CHECK_PROG(SHTOOL, shtool, shtool) + case `$SHTOOL path $CC` in + *ccache*[)] gcc_ccache=yes;; + *[)] gcc_ccache=no;; + esac + + if test "$gcc_ccache" = "yes" && (test -z "$CCACHE_DISABLE" || test "$CCACHE_DISABLE" != "1"); then + AC_MSG_ERROR([ccache must be disabled when --enable-gcov option is used. You can disable ccache by setting environment variable CCACHE_DISABLE=1.]) + fi + + ltp_version_list="1.6 1.7 1.8 1.10 1.11 1.12" + AC_CHECK_PROG(LTP, lcov, lcov) + AC_CHECK_PROG(LTP_GENHTML, genhtml, genhtml) + + if test "$LTP"; then + AC_CACHE_CHECK([for ltp version], glib_cv_ltp_version, [ + glib_cv_ltp_version=invalid + ltp_version=`$LTP -v 2>/dev/null | $SED -e 's/^.* //'` + for ltp_check_version in $ltp_version_list; do + if test "$ltp_version" = "$ltp_check_version"; then + glib_cv_ltp_version="$ltp_check_version (ok)" + fi + done + ]) + else + ltp_msg="To enable code coverage reporting you must have one of the following LTP versions installed: $ltp_version_list" + AC_MSG_ERROR([$ltp_msg]) + fi + + case $glib_cv_ltp_version in + ""|invalid[)] + ltp_msg="You must have one of the following versions of LTP: $ltp_version_list (found: $ltp_version)." + AC_MSG_ERROR([$ltp_msg]) + LTP="exit 0;" + ;; + esac + + if test -z "$LTP_GENHTML"; then + AC_MSG_ERROR([Could not find genhtml from the LTP package]) + fi + + AC_DEFINE(HAVE_GCOV, 1, [Whether you have gcov]) + + dnl Remove all optimization flags from CFLAGS + changequote({,}) + CFLAGS=`echo "$CFLAGS" | $SED -e 's/-O[0-9]*//g'` + changequote([,]) + + dnl Add the special gcc flags + CFLAGS="$CFLAGS -O0 -fprofile-arcs -ftest-coverage" + LDFLAGS="$LDFLAGS -lgcov" + fi +fi # # Makefile vars # After everything is configured, we create all makefiles. @@ -70,6 +172,7 @@ AC_CONFIG_FILES([Makefile src/dhcp/Makefile src/shared/Makefile src/wifi/Makefile + src/uibc/Makefile test/Makefile]) AC_OUTPUT @@ -87,12 +190,15 @@ AC_MSG_NOTICE([Build configuration: bindir: $bindir libdir: $libdir includedir: $includedir + sysconfdir: $sysconfdir + ip-binary: $IP_BINARY Miscellaneous Options: building tests: $have_check + code coverage: $use_gcov + rely udev: ${enable_rely_udev:-no} Compilation - mkdir build && cd build "${MAKE-make}" to start compilation process "${MAKE-make}" check to pass test]) else @@ -102,11 +208,13 @@ AC_MSG_NOTICE([Build configuration: bindir: $bindir libdir: $libdir includedir: $includedir + sysconfdir: $sysconfdir + ip-binary: $IP_BINARY Miscellaneous Options: building tests: $have_check + rely udev: ${enable_rely_udev:-no} Compilation - mkdir build && cd build "${MAKE-make}" to start compilation process]) fi diff --git a/meson.Dockerfile b/meson.Dockerfile new file mode 100644 index 0000000..c57581b --- /dev/null +++ b/meson.Dockerfile @@ -0,0 +1,7 @@ +FROM docker.io/albfan/miraclecast-ci + +COPY . ./ + +RUN rm -rf build-meson; \ + meson build-meson; \ + ninja -C build-meson diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..84b20c3 --- /dev/null +++ b/meson.build @@ -0,0 +1,55 @@ +project('Miraclecast', + 'c', + version: '1', + meson_version: '>=0.39', + default_options: ['buildtype=debugoptimized', 'c_std=gnu11'] +) + +add_project_arguments('-D_GNU_SOURCE', language: 'c') + +conf_data = configuration_data() +conf_data.set_quoted('BUILD_BINDIR', + join_paths(get_option('prefix'), get_option('bindir')) +) +conf_data.set_quoted('PACKAGE_STRING', + '@0@ @1@'.format(meson.project_name(), meson.project_version()) +) +configure_file(output: 'config.h', + configuration: conf_data +) + +c_compiler = meson.get_compiler('c') +readline = c_compiler.find_library('readline', required: true) +if readline.found() + add_project_arguments('-DHAVE_READLINE', language: 'c') +endif + +if get_option('build-enable-debug') + add_project_arguments('-DBUILD_ENABLE_DEBUG', language: 'c') +endif + +if get_option('rely-udev') + add_project_arguments('-DRELY_UDEV', language: 'c') +endif + +if get_option('enable-systemd') + add_project_arguments('-DENABLE_SYSTEMD', language: 'c') + libsystemd = dependency('libsystemd') +else + libsystemd = dependency('libelogind') +endif + +add_project_arguments('-DIP_BINARY='+get_option('ip-binary'), language: 'c') + +glib2 = dependency('glib-2.0') +udev = dependency('libudev') + +m = c_compiler.find_library('m', required: false) + +subdir('src') +subdir('res') + +if get_option('build-tests') + subdir('test') +endif + diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..e3a94aa --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,20 @@ +option('build-enable-debug', + type: 'boolean', + value: true, + description: 'Enable Debug') +option('rely-udev', + type: 'boolean', + value: false, + description: 'Rely in udev tag to select device') +option('build-tests', + type: 'boolean', + value: true, + description: 'Enable TEST') +option('ip-binary', + type: 'string', + value: '/bin/ip', + description: 'Path for ip binary') +option('enable-systemd', + type: 'boolean', + value: 'true', + description: 'Enable systemd') diff --git a/res/CMakeLists.txt b/res/CMakeLists.txt index 2065690..e67277e 100644 --- a/res/CMakeLists.txt +++ b/res/CMakeLists.txt @@ -1,11 +1,14 @@ +INSTALL( + PROGRAMS miracle-gst gstplayer uibc-viewer + DESTINATION bin + ) -########### install files ############### +INSTALL( + FILES org.freedesktop.miracle.conf + DESTINATION ${SYSCONFDIR}/dbus-1/system.d + ) -install(FILES miracle-gst.sh DESTINATION bin) - - - -#original Makefile.am contents follow: - -#bin_SCRIPTS = miracle-gst.sh -#EXTRA_DIST = wpa.conf +INSTALL( + FILES miracle-wifid miracle-sinkctl miracle-wifictl + DESTINATION ${DATADIR}/bash-completion/completions + ) diff --git a/res/Makefile.am b/res/Makefile.am index 51f71fb..b11d0b3 100644 --- a/res/Makefile.am +++ b/res/Makefile.am @@ -1,2 +1,9 @@ -bin_SCRIPTS = miracle-gst.sh +bin_SCRIPTS = miracle-gst gstplayer uibc-viewer miracle-omxplayer EXTRA_DIST = wpa.conf + +dbuspolicydir=$(sysconfdir)/dbus-1/system.d +dbuspolicy_DATA = org.freedesktop.miracle.conf + +bashcompletiondir=${datadir}/bash-completion/completions +bashcompletion_DATA=miracle-wifid miracle-sinkctl miracle-wifictl + diff --git a/res/gstplayer b/res/gstplayer new file mode 100755 index 0000000..f048e6a --- /dev/null +++ b/res/gstplayer @@ -0,0 +1,223 @@ +#!/usr/bin/python3 -u + +import gi +import argparse + +gi.require_version('Gst', '1.0') +gi.require_version('Gtk', '3.0') +gi.require_version('GstVideo', '1.0') +gi.require_version('GdkX11', '3.0') + +from gi.repository import GObject, Gst, Gtk, Gdk, GLib + +# Needed for window.get_xid(), xvimagesink.set_window_handle(), respectively: +from gi.repository import GdkX11, GstVideo + +GObject.threads_init() +Gst.init(None) + +class Player(object): + def __init__(self, **kwargs): + + resolution = kwargs.get("resolution") + if resolution: + split = resolution.split("x") + self.width = int(split[0]) + self.height = int(split[1]) + + scale = kwargs.get("scale") + if scale: + split = scale.split("x") + self.width = int(split[0]) + self.height = int(split[1]) + + debug = kwargs.get("debug") + if debug: + Gst.debug_set_active(True) + Gst.debug_set_threshold_from_string(debug, True) + + port = kwargs.get("port") + + uri = kwargs.get("uri") + + self.window = Gtk.Window() + self.window.set_name('gstplayer') + self.window.connect('destroy', self.quit) + + title = kwargs.get("title") + if title: + self.window.set_title(title) + + if hasattr(self,'width') and hasattr(self,'height'): + self.window.set_default_size(self.width, self.height) + + self.drawingarea = Gtk.DrawingArea() + self.window.add(self.drawingarea) + + if hasattr(self,'width') and hasattr(self,'height'): + self.drawingarea.set_size_request(self.width,self.height) + self.drawingarea.add_events(Gdk.EventMask.BUTTON_PRESS_MASK|Gdk.EventMask.BUTTON_RELEASE_MASK) + self.drawingarea.connect('button-press-event', self.on_mouse_pressed) + self.drawingarea.connect('button-release-event', self.on_mouse_pressed) + self.drawingarea.add_events(Gdk.EventMask.KEY_PRESS_MASK) + self.window.connect('key-press-event', self.on_key_pressed) + + audio = kwargs.get("audio") + + self.playbin = None + + #Create GStreamer pipeline + if uri is not None: + self.pipeline = Gst.Pipeline() + + # Create GStreamer elements + self.playbin = Gst.ElementFactory.make('playbin', "source") + + if not uri.startswith("http://") or not uri.startswith("http://") or not uri.startswith("file://"): + if not uri.startswith("/"): + import os + uri = os.path.abspath(uri) + uri = "file://"+uri + + # Set properties + self.playbin.set_property('uri', uri) + + + # Add playbin to the pipeline + self.pipeline.add(self.playbin) + else: + gstcommand = "udpsrc port="+str(port)+" caps=\"application/x-rtp, media=video\" ! rtpjitterbuffer latency=100 ! rtpmp2tdepay ! tsdemux " + + if audio: + gstcommand += "name=demuxer demuxer. " + + gstcommand += "! queue max-size-buffers=0 max-size-time=0 ! h264parse ! avdec_h264 ! videoconvert ! " + + if scale: + gstcommand += "videoscale method=1 ! video/x-raw,width="+str(self.width)+",height="+str(self.height)+" ! " + + gstcommand += "autovideosink " + + if audio: + gstcommand += "demuxer. ! queue max-size-buffers=0 max-size-time=0 ! aacparse ! avdec_aac ! audioconvert ! audioresample ! autoaudiosink " + + self.pipeline = Gst.parse_launch(gstcommand) + + + # Create bus to get events from GStreamer pipeline + self.bus = self.pipeline.get_bus() + self.bus.add_signal_watch() + self.bus.connect('message::eos', self.on_eos) + self.bus.connect('message::error', self.on_error) + + # This is needed to make the video output in our DrawingArea: + self.bus.enable_sync_message_emission() + self.bus.connect('sync-message::element', self.on_sync_message) + self.bus.connect('message', self.on_message) + + self.success = False + + def on_message(self, bus, message): + if self.playbin: + videoPad = self.playbin.emit("get-video-pad", 0) + if videoPad and not self.success: + videoPadCapabilities = videoPad.get_current_caps() + if videoPadCapabilities: + (self.success, self.videoWidth) = \ + videoPadCapabilities.get_structure(0).get_int("width") + (self.success, self.videoHeight) = \ + videoPadCapabilities.get_structure(0).get_int("height") + if self.success: + print("{0} {1}".format(self.videoWidth, self.videoHeight)) + self.drawingarea.set_size_request(self.videoWidth, self.videoHeight) + + def on_mouse_pressed(self, widget, event): + #,,,, + if event.type == Gdk.EventType.BUTTON_PRESS: + type = 0 + else: + type = 1 + + width = self.drawingarea.get_allocation().width + height = self.drawingarea.get_allocation().height + half_area_width = width / 2 + half_area_height = height / 2 + + half_def_width = self.width / 2 + half_def_height = self.height / 2 + + min_hor_pos = half_area_width - half_def_width + max_hor_pos = half_area_width + half_def_width + min_ver_pos = half_area_height - half_def_height + max_ver_pos = half_area_height + half_def_height + + pos_event_x = event.x + pos_event_y = event.y + + + if min_hor_pos <= pos_event_x <= max_hor_pos and min_ver_pos <= pos_event_y <= max_ver_pos: + uibc_x = int(pos_event_x - (half_area_width - half_def_width)) + uibc_y = int(pos_event_y - (half_area_height - half_def_height)) + print('{0},1,0,{1},{2}'.format(type, uibc_x , uibc_y)) + + def on_key_pressed(self, widget, event): + print("3,0x%04X,0x0000" % event.keyval) + + def run(self): + self.window.show_all() + # You need to get the XID after window.show_all(). You shouldn't get it + # in the on_sync_message() handler because threading issues will cause + # segfaults there. + window = self.drawingarea.get_property('window') + if hasattr(window,'get_xid'): + self.xid = self.drawingarea.get_property('window').get_xid() + + self.pipeline.set_state(Gst.State.PLAYING) + Gtk.main() + + + def quit(self, window): + self.pipeline.set_state(Gst.State.NULL) + Gtk.main_quit() + + def on_sync_message(self, bus, msg): + if msg.get_structure().get_name() == 'prepare-window-handle': + print(self.drawingarea.get_allocation()) + if hasattr(self,'xid'): + msg.src.set_window_handle(self.xid) + + def on_eos(self, bus, msg): + print('on_eos(): seeking to start of video') + self.pipeline.seek_simple( + Gst.Format.TIME, + Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, + 0 + ) + + def on_error(self, bus, msg): + print('on_error():', msg.parse_error()) + + +if __name__ == '__main__': + + parser = argparse.ArgumentParser() + parser.add_argument("uri", nargs="?", help="Uri to play") + parser.add_argument("-v", "--version", help="Show package version") + parser.add_argument("--log-level", metavar="lvl", help="Maximum level for log messages") + parser.add_argument("-p", "--port", type=int, default=7236, help="Port for rtsp") + parser.add_argument("-a", "--audio", dest="audio", action="store_true", help="Enable audio support") + parser.add_argument("-s", "--scale", metavar="WxH", help="Scale to resolution") + parser.add_argument("-d", "--debug", help="Debug") + parser.add_argument("--uibc", help="Enable UIBC") + parser.add_argument("--title", help="set player title") + parser.add_argument("--res", metavar="n,n,n", help="Supported resolutions masks (CEA, VESA, HH)") + # res + # " default CEA %08X\n" + # " default VESA %08X\n" + # " default HH %08X\n" + parser.add_argument("-r", "--resolution", help="Resolution") + parser.set_defaults(audio=True) + args = parser.parse_args() + + p = Player(**vars(args)) + p.run() diff --git a/res/kill-wpa.sh b/res/kill-wpa.sh index e210b8f..0f92662 100755 --- a/res/kill-wpa.sh +++ b/res/kill-wpa.sh @@ -1,8 +1,10 @@ #!/bin/bash -. miracle-utils.sh +DIRNAME=$(dirname $0) -kill_ubuntu_network_manager +. $DIRNAME/miracle-utils.sh + +kill_network_manager WPA_PID=$(find_wpa_supplicant_pid) if [ -n "$WPA_PID" ] diff --git a/res/meson.build b/res/meson.build new file mode 100644 index 0000000..9a0992c --- /dev/null +++ b/res/meson.build @@ -0,0 +1,13 @@ +install_data( + 'org.freedesktop.miracle.conf', + install_dir: join_paths(get_option('sysconfdir'), 'dbus-1', 'system.d') +) + +install_data('miracle-gst', 'gstplayer', 'uibc-viewer', + install_dir: get_option('bindir'), + install_mode: 'rwxr-xr-x') + +install_data( + 'miracle-wifid', 'miracle-sinkctl', 'miracle-wifictl', + install_dir: join_paths(get_option('datadir'), 'bash-completion', 'completions') +) diff --git a/res/miracle-gst.sh b/res/miracle-gst similarity index 54% rename from res/miracle-gst.sh rename to res/miracle-gst index 99749d5..9a2a4db 100755 --- a/res/miracle-gst.sh +++ b/res/miracle-gst @@ -1,12 +1,46 @@ #!/bin/bash +function help { + local scriptname="$(basename $0)" + cat >&2 <x Scale + -d Log level for gst + -p Port for stream + -a Enables audio + -h Show this help + +Examples: + + # play stream on port 7236 + $ $scriptname -p 7236 + # play stream with resolution 800x600 + $ $scriptname -s 800x600 + # play stream with audio + $ $scriptname -a + # play stream with debug level 3 + $ $scriptname -d 3 + +EOF +} + DEBUG='0' AUDIO='0' SCALE='0' -while getopts "r:d:as:" optname +while getopts "r:d:as:p:h" optname do case "$optname" in + "h") + help + exit 0 + ;; "d") DEBUG=`echo ${OPTARG} | tr -d ' '` ;; @@ -16,6 +50,9 @@ while getopts "r:d:as:" optname "a") AUDIO='1' ;; + "p") + PORT=`echo ${OPTARG} | tr -d ' '` + ;; "s") SCALE='1' WIDTH=`echo ${OPTARG} | tr -d ' ' | cut -dx -f 1` @@ -25,8 +62,9 @@ while getopts "r:d:as:" optname echo "Unknown option $OPTARG" ;; *) - # Should not occur - echo "Unknown error while processing options" + echo "Unknown parameter $OPTARG" + help + exit 1 ;; esac done @@ -37,7 +75,7 @@ then RUN+="--gst-debug=${DEBUG} " fi -RUN+="udpsrc port=1991 caps=\"application/x-rtp, media=video\" ! rtpjitterbuffer latency=100 ! rtpmp2tdepay ! tsdemux " +RUN+="udpsrc port=$PORT caps=\"application/x-rtp, media=video\" ! rtpjitterbuffer latency=100 ! rtpmp2tdepay ! tsdemux " if [ $AUDIO == '1' ] then diff --git a/res/miracle-omxplayer b/res/miracle-omxplayer new file mode 100755 index 0000000..961e2ec --- /dev/null +++ b/res/miracle-omxplayer @@ -0,0 +1,69 @@ +#!/bin/bash + +function help { + local scriptname="$(basename $0)" + cat >&2 <x Scale + -d Log level for gst + -p Port for stream + -a Enables audio + -h Show this help + +Examples: + + # play stream on port 7236 + $ $scriptname -p 7236 + +EOF +} + +DEBUG='0' +AUDIO='0' +SCALE='0' + +while getopts "r:d:as:p:h" optname + do + case "$optname" in + "h") + help + exit 0 + ;; + "d") + DEBUG=`echo ${OPTARG} | tr -d ' '` + ;; + "r") + RESOLUTION=`echo ${OPTARG} | tr -d ' '` + ;; + "a") + AUDIO='1' + ;; + "p") + PORT=`echo ${OPTARG} | tr -d ' '` + ;; + "s") + SCALE='1' + WIDTH=`echo ${OPTARG} | tr -d ' ' | cut -dx -f 1` + HEIGHT=`echo ${OPTARG} | tr -d ' ' | cut -dx -f 2` + ;; + "?") + echo "Unknown option $OPTARG" + ;; + *) + echo "Unknown parameter $OPTARG" + help + exit 1 + ;; + esac + done + +RUN="omxplayer -live -b -o hdmi rtp://@:$PORT" + +echo "running: $RUN" +exec ${RUN} diff --git a/res/miracle-sinkctl b/res/miracle-sinkctl new file mode 100755 index 0000000..a14c0bb --- /dev/null +++ b/res/miracle-sinkctl @@ -0,0 +1,85 @@ +# +# Autocompletion for miraclecast commands +# +# Maintainer: Alberto Fanjul +# + +function _miracle-sinkctl() { + local cur prev + + _get_comp_words_by_ref cur + prev=${COMP_WORDS[COMP_CWORD-1]} + + case "$prev" in + --log-level) + COMPREPLY=($(compgen -W 'fatal alert critical error warning notice info debug trace 1 2 3 4 5 6 7 8' -- "$cur")) + return 0 + ;; + esac + + COMPREPLY=($(compgen -W "$(_parse_help miracle-sinkctl) $(_parse_short_help miracle-sinkctl)" -- "$cur")) +} + +_parse_short_help () +{ + eval local cmd=$( quote "$1" ); + local line; + { + case $cmd in + -) + cat + ;; + *) + LC_ALL=C "$( dequote "$cmd" )" ${2:---help} 2>&1 + ;; + esac + } | while read -r line; do + [[ $line == *([[:blank:]])-* ]] || continue; + while [[ $line =~ ((^|[^-])-[A-Za-z0-9?][[:space:]]+)\[?[A-Z0-9]+\]? ]]; do + line=${line/"${BASH_REMATCH[0]}"/"${BASH_REMATCH[1]}"}; + done; + __parse_short_options "${line// or /, }"; + done +} + +__parse_short_options () +{ + local option option2 i IFS=' +,/|'; + option=; + local -a array; + read -a array <<< "$1"; + for i in "${array[@]}"; + do + case "$i" in + ---*) + break + ;; + --?*) + break + ;; + -?*) + option=$i; + break + ;; + *) + break + ;; + esac; + done; + [[ -n $option ]] || return; + IFS=' +'; + if [[ $option =~ (\[((no|dont)-?)\]). ]]; then + option2=${option/"${BASH_REMATCH[1]}"/}; + option2=${option2%%[<{().[]*}; + printf '%s\n' "${option2/=*/=}"; + option=${option/"${BASH_REMATCH[1]}"/"${BASH_REMATCH[2]}"}; + fi; + option=${option%%[<{().[]*}; + printf '%s\n' "${option/=*/=}" +} + +complete -F _miracle-sinkctl miracle-sinkctl + +# ex: filetype=sh diff --git a/res/miracle-utils.sh b/res/miracle-utils.sh index e77fff0..05ce20e 100755 --- a/res/miracle-utils.sh +++ b/res/miracle-utils.sh @@ -27,6 +27,13 @@ function find_wireless_network_interfaces { done } +# +# show pci slot +# +function show_pci_slot { + basename $(readlink /sys/class/net/$1/device) | cut -d: -f2 | sed 's/^0*//' +} + # # test if interface is connected # @@ -34,6 +41,26 @@ function is_interface_connected { test x$( cat /sys/class/net/$1/carrier 2>/dev/null) = x1 } +# +# find wireless pci slot +# +function find_wireless_pci_slot { + for i in $( find_wireless_network_interfaces ) + do + show_pci_slot $i + done +} + +# +# find wireless pci slot +# +function find_wireless_ifindex { + for i in $( find_wireless_network_interfaces ) + do + show_ifindex $i + done +} + # # find wireless connected interfaces # @@ -52,12 +79,23 @@ function find_wireless_connected_network_interfaces { # function find_physical_for_network_interface { PHY_INDEX=$(iw dev $1 info | grep wiphy | awk '{print $2}') - if [ -n $PHY_INDEX ] + if [ -n "$PHY_INDEX" ] then echo phy$PHY_INDEX fi } +# +# find interface index for interface +# +function show_ifindex { + IF_INDEX=$(iw dev $1 info | grep ifindex | awk '{print $2}') + if [ -n "$IF_INDEX" ] + then + echo $IF_INDEX + fi +} + # # Check interface for P2P capabilities # @@ -65,6 +103,12 @@ function search_p2p_capabilities { WI_DEVICE=$1 PHY_DEVICE=$(find_physical_for_network_interface $WI_DEVICE) + if [ -z "$PHY_DEVICE" ] + then + echo "cannot find physical device for $WI_DEVICE" + return + fi + if iw phy $PHY_DEVICE info | grep -Pzo "(?s)Supported interface modes.*Supported commands" | grep "P2P" &> /dev/null then echo $WI_DEVICE supports P2P @@ -95,31 +139,55 @@ function find_wpa_supplicant_pid { show_wpa_supplicant_process | awk '{print $2}' } +# +# checking if distro is archlinux +# +function check_archlinux_distro { + test -f "/etc/arch-release" +} # # checking if distro is ubuntu # function check_ubuntu_distro { cat /proc/version | grep -i ubuntu } +# +# checking if distro is debian +# +function check_debian_distro { + cat /proc/version | grep -i debian +} # -# ubuntu manager restarts automatically wpa_supplicant +# kills network manager # -function kill_ubuntu_network_manager { - if check_ubuntu_distro +function kill_network_manager { + echo stopping NetworkManager + if check_ubuntu_distro || check_debian_distro then - echo stopping NetworkManager + # ubuntu manager restarts automatically wpa_supplicant sudo service NetworkManager stop + elif check_archlinux_distro + then + sudo systemctl stop Network.service + else + sudo systemctl stop NetworkManager + sudo systemctl stop wpa_supplicant fi } # -# start ubuntu manager +# start network manager # -function start_ubuntu_network_manager { - if check_ubuntu_distro +function start_network_manager { + echo starting NetworkManager + if check_ubuntu_distro || check_debian_distro then - echo starting NetworkManager + sudo service NetworkManager start + elif check_archlinux_distro + then + sudo systemctl start Network.service + else sudo service NetworkManager start fi } diff --git a/res/miracle-vlc b/res/miracle-vlc new file mode 100755 index 0000000..a4e61c7 --- /dev/null +++ b/res/miracle-vlc @@ -0,0 +1,69 @@ +#!/bin/bash + +function help { + local scriptname="$(basename $0)" + cat >&2 <x Scale + -d Log level for gst + -p Port for stream + -a Enables audio + -h Show this help + +Examples: + + # play stream on port 7236 + $ $scriptname -p 7236 + +EOF +} + +DEBUG='0' +AUDIO='0' +SCALE='0' + +while getopts "r:d:as:p:h" optname + do + case "$optname" in + "h") + help + exit 0 + ;; + "d") + DEBUG=`echo ${OPTARG} | tr -d ' '` + ;; + "r") + RESOLUTION=`echo ${OPTARG} | tr -d ' '` + ;; + "a") + AUDIO='1' + ;; + "p") + PORT=`echo ${OPTARG} | tr -d ' '` + ;; + "s") + SCALE='1' + WIDTH=`echo ${OPTARG} | tr -d ' ' | cut -dx -f 1` + HEIGHT=`echo ${OPTARG} | tr -d ' ' | cut -dx -f 2` + ;; + "?") + echo "Unknown option $OPTARG" + ;; + *) + echo "Unknown parameter $OPTARG" + help + exit 1 + ;; + esac + done + +RUN="vlc rtp://@:$PORT" + +echo "running: $RUN" +exec ${RUN} diff --git a/res/miracle-wifictl b/res/miracle-wifictl new file mode 100755 index 0000000..07fc4ae --- /dev/null +++ b/res/miracle-wifictl @@ -0,0 +1,85 @@ +# +# Autocompletion for miracle-wifictl +# +# Maintainer: Alberto Fanjul +# + +function _miracle-wifictl() { + local cur prev + + _get_comp_words_by_ref cur + prev=${COMP_WORDS[COMP_CWORD-1]} + + case "$prev" in + --log-level) + COMPREPLY=($(compgen -W 'fatal alert critical error warning notice info debug trace 1 2 3 4 5 6 7 8' -- "$cur")) + return 0 + ;; + esac + + COMPREPLY=($(compgen -W "$(_parse_help miracle-wifictl) $(_parse_short_help miracle-wifictl)" -- "$cur")) +} + +_parse_short_help () +{ + eval local cmd=$( quote "$1" ); + local line; + { + case $cmd in + -) + cat + ;; + *) + LC_ALL=C "$( dequote "$cmd" )" ${2:---help} 2>&1 + ;; + esac + } | while read -r line; do + [[ $line == *([[:blank:]])-* ]] || continue; + while [[ $line =~ ((^|[^-])-[A-Za-z0-9?][[:space:]]+)\[?[A-Z0-9]+\]? ]]; do + line=${line/"${BASH_REMATCH[0]}"/"${BASH_REMATCH[1]}"}; + done; + __parse_short_options "${line// or /, }"; + done +} + +__parse_short_options () +{ + local option option2 i IFS=' +,/|'; + option=; + local -a array; + read -a array <<< "$1"; + for i in "${array[@]}"; + do + case "$i" in + ---*) + break + ;; + --?*) + break + ;; + -?*) + option=$i; + break + ;; + *) + break + ;; + esac; + done; + [[ -n $option ]] || return; + IFS=' +'; + if [[ $option =~ (\[((no|dont)-?)\]). ]]; then + option2=${option/"${BASH_REMATCH[1]}"/}; + option2=${option2%%[<{().[]*}; + printf '%s\n' "${option2/=*/=}"; + option=${option/"${BASH_REMATCH[1]}"/"${BASH_REMATCH[2]}"}; + fi; + option=${option%%[<{().[]*}; + printf '%s\n' "${option/=*/=}" +} + +complete -F _miracle-wifictl miracle-wifictl + +# ex: filetype=sh diff --git a/res/miracle-wifid b/res/miracle-wifid new file mode 100755 index 0000000..1f320bd --- /dev/null +++ b/res/miracle-wifid @@ -0,0 +1,95 @@ +# +# Autocompletion for miracle-wifid +# +# Maintainer: Alberto Fanjul +# + +function _miracle-wifid() { + local cur prev + + _get_comp_words_by_ref cur + prev=${COMP_WORDS[COMP_CWORD-1]} + + case "$prev" in + --config-methods) + COMPREPLY=($(compgen -W 'pbc pin usba ethernet label display ext_nfc_token int_nfc_token nfc_interface push_button keypad virtual_display physical_display virtual_push_button physical_push_button' -- "$cur")) + return 0 + ;; + --log-level) + COMPREPLY=($(compgen -W 'fatal alert critical error warning notice info debug trace 1 2 3 4 5 6 7 8' -- "$cur")) + return 0 + ;; + --wpa-loglevel) + COMPREPLY=($(compgen -W 'fatal alert critical error warning notice info debug trace 1 2 3 4 5 6 7 8' -- "$cur")) + return 0 + ;; + esac + + COMPREPLY=($(compgen -W "$(_parse_help miracle-wifid) $(_parse_short_help miracle-wifid)" -- "$cur")) +} + + +_parse_short_help () +{ + eval local cmd=$( quote "$1" ); + local line; + { + case $cmd in + -) + cat + ;; + *) + LC_ALL=C "$( dequote "$cmd" )" ${2:---help} 2>&1 + ;; + esac + } | while read -r line; do + [[ $line == *([[:blank:]])-* ]] || continue; + while [[ $line =~ ((^|[^-])-[A-Za-z0-9?][[:space:]]+)\[?[A-Z0-9]+\]? ]]; do + line=${line/"${BASH_REMATCH[0]}"/"${BASH_REMATCH[1]}"}; + done; + __parse_short_options "${line// or /, }"; + done +} + +__parse_short_options () +{ + local option option2 i IFS=' +,/|'; + option=; + local -a array; + read -a array <<< "$1"; + for i in "${array[@]}"; + do + case "$i" in + ---*) + break + ;; + --?*) + break + ;; + -?*) + option=$i; + break + ;; + *) + break + ;; + esac; + done; + [[ -n $option ]] || return; + IFS=' +'; + if [[ $option =~ (\[((no|dont)-?)\]). ]]; then + option2=${option/"${BASH_REMATCH[1]}"/}; + option2=${option2%%[<{().[]*}; + printf '%s\n' "${option2/=*/=}"; + option=${option/"${BASH_REMATCH[1]}"/"${BASH_REMATCH[2]}"}; + fi; + option=${option%%[<{().[]*}; + printf '%s\n' "${option/=*/=}" +} + +complete -F _miracle-wifid miracle-wifid + + +# ex: filetype=sh diff --git a/res/miraclecast-ci.Dockerfile b/res/miraclecast-ci.Dockerfile new file mode 100644 index 0000000..cb55da0 --- /dev/null +++ b/res/miraclecast-ci.Dockerfile @@ -0,0 +1,18 @@ +FROM debian:buster-slim + +RUN dpkg --add-architecture i386 + +RUN apt-get update && apt-get install -y \ + build-essential \ + systemd \ + libglib2.0-dev \ + libreadline-dev \ + libudev-dev \ + libsystemd-dev \ + libusb-dev \ + automake \ + autoconf \ + libtool \ + cmake \ + meson + diff --git a/res/miraclecast-completion b/res/miraclecast-completion deleted file mode 100755 index 707ccad..0000000 --- a/res/miraclecast-completion +++ /dev/null @@ -1,57 +0,0 @@ -function _miracle-wifid() { - local cur prev - - _get_comp_words_by_ref cur - prev=${COMP_WORDS[COMP_CWORD-1]} - - case "$prev" in - --log-level) - COMPREPLY=($(compgen -W 'fatal alert critical error warning notice info debug trace 1 2 3 4 5 6 7 8' -- "$cur")) - return 0 - ;; - --wpa-loglevel) - COMPREPLY=($(compgen -W 'fatal alert critical error warning notice info debug trace 1 2 3 4 5 6 7 8' -- "$cur")) - return 0 - ;; - esac - - COMPREPLY=($(compgen -W "$(_parse_help miracle-wifid)" -- "$cur")) -} - -complete -F _miracle-wifid miracle-wifid - -function _miracle-sinkctl() { - local cur prev - - _get_comp_words_by_ref cur - prev=${COMP_WORDS[COMP_CWORD-1]} - - case "$prev" in - --log-level) - COMPREPLY=($(compgen -W 'fatal alert critical error warning notice info debug trace 1 2 3 4 5 6 7 8' -- "$cur")) - return 0 - ;; - esac - - COMPREPLY=($(compgen -W "$(_parse_help miracle-sinkctl)" -- "$cur")) -} - -complete -F _miracle-sinkctl miracle-sinkctl - -function _miracle-wifictl() { - local cur prev - - _get_comp_words_by_ref cur - prev=${COMP_WORDS[COMP_CWORD-1]} - - case "$prev" in - --log-level) - COMPREPLY=($(compgen -W 'fatal alert critical error warning notice info debug trace 1 2 3 4 5 6 7 8' -- "$cur")) - return 0 - ;; - esac - - COMPREPLY=($(compgen -W "$(_parse_help miracle-wifictl)" -- "$cur")) -} - -complete -F _miracle-wifictl miracle-wifictl diff --git a/res/miraclecast.spec b/res/miraclecast.spec new file mode 100644 index 0000000..0ba2eab --- /dev/null +++ b/res/miraclecast.spec @@ -0,0 +1,68 @@ +%global commit 264a222d242734da369ca287aa6cfc6ca4f1f7bf +%global shortcommit %(c=%{commit}; echo ${c:0:7}) + +Name: miraclecast +Version: 1.0 +Release: 2.git%{shortcommit}%{?dist} +Summary: Connect external monitors to your system via Wi-Fi Display (miracast) +License: LGPLv2 +URL: https://github.com/albfan/miraclecast +Source0: https://github.com/albfan/miraclecast/archive/%{commit}/%{name}-%{shortcommit}.tar.gz + +BuildRequires: autoconf +BuildRequires: automake +BuildRequires: libtool + +BuildRequires: glib2-devel +BuildRequires: readline-devel +BuildRequires: systemd-devel + +# "Recommends" is stronger than "Suggests", and gets installed by default by DNF. + +# for gstplayer +Recommends: python3-gobject-base + +# for miracle-gst (/usr/bin/gst-launch-1.0) +Suggests: gstreamer + +# for miracle-omxplayer +Suggests: omxplayer + +%description +The MiracleCast project provides software to connect external monitors to your +system via Wi-Fi. It is compatible to the Wi-Fi Display specification also +known as Miracast. MiracleCast implements the Display-Source as well as +Display-Sink side. + +The Display-Source side allows you to connect external displays to your system +and stream local content to the device. A lot of effort is put into making +this as easy as connecting external displays via HDMI. + +On the other hand, the Display-Sink side allows you to create Wi-Fi capable +external displays yourself. You can use it on your embedded devices or even on +full desktops to allow other systems to use your device as external display. + +%prep +%autosetup -n %{name}-%{commit} + +%build +autoreconf -fiv +%configure +%make_build + +%install +%make_install + +%files +%license COPYING LICENSE_gdhcp LICENSE_htable LICENSE_lgpl +%doc README.md +%config(noreplace) %{_sysconfdir}/dbus-1/system.d/org.freedesktop.miracle.conf +%{_bindir}/* +%{_datadir}/bash-completion/completions/* + +%changelog +* Wed Jan 05 2022 Korenberg Mark +- Fix .spec-file, bump version + +* Wed Nov 21 2018 Graham White +- first build diff --git a/res/miraclecast.spec-README.md b/res/miraclecast.spec-README.md new file mode 100644 index 0000000..4fc60b6 --- /dev/null +++ b/res/miraclecast.spec-README.md @@ -0,0 +1,24 @@ +This file can be used with the rpmbuild or mock commands to create a binary RPM. Tested and working on Fedora 28 x86\_64. + +Example usage (for Fedora 28 systems): +1. Create a tar.gz file of the source code and add the short name of the commit to the tar file, for the most recent commit at the time of writing this would be: + * `git clone https://github.com/albfan/miraclecast.git miraclecast-c3c868e` + * `tar -zcf miraclecast-c3c868e.tar.gz miraclecast-c3c868e` + +2. Create a Source RPM + * `rpmbuild -bs miraclecast.spec --define "_sourcedir $PWD"` + +Then build the RPM with one of the following: + +* Using mock (assuming you're in the dir where the .src.rpm file is): + * `mock --arch=x86_64 -r fedora-28-x86_64 --resultdir=results miraclecast-1.0-1.gitc3c868e.fc28.src.rpm` + +OR + +* Using rpmbuild (assuming you're in the dir where the .src.rpm file is): + * `rpmbuild -ra miraclecast-1.0-1.gitc3c868e.fc28.src.rpm` + +OR + +* Using rpmbuild (assuming you're in the dir where the .spec and .tar.gz files are): + * `rpmbuild -bs miraclecast.spec --define "_sourcedir $PWD"`` diff --git a/res/normal-wifi.sh b/res/normal-wifi.sh index 230f4db..e8768c1 100755 --- a/res/normal-wifi.sh +++ b/res/normal-wifi.sh @@ -1,57 +1,9 @@ #!/bin/bash -./kill-wpa.sh +DIRNAME=$(dirname $0) -. miracle-utils.sh +. $DIRNAME/miracle-utils.sh -ETHER_NAMES=$(find_choosable_networknames) - -ETHER_COUNT=$(echo "$ETHER_NAMES" | wc -l) - -if [ 0 = $ETHER_COUNT ] -then - echo There is no net devices avaliable - exit 1 -elif [ 1 = $ETHER_COUNT ] -then - ETHERNAME="$ETHER_NAMES" -elif [ 2 -le $ETHER_COUNT ] -then - echo choose device for normal connection: - QUIT="exit" - select et_name in $ETHER_NAMES $QUIT - do - case $et_name - in - "$QUIT") - exit - ;; - "") - if [ "$REPLY" = $QUIT ] - then - exit - else - echo unknow $REPLY - fi - ;; - *) - ETHERNAME=$et_name - break - ;; - esac - done -fi - -# default path for config file -CONFIG_FILE=${1:-/run/network/wpa_supplicant_${ETHERNAME}.conf} - - -echo starting wpa_supplicant for normal connection -if check_ubuntu_distro -then - start_ubuntu_network_manager - sudo wpa_supplicant -B -u -s -O /var/run/wpa_supplicant -else - sudo wpa_supplicant -B -u -P /run/wpa_supplicant_${ETHERNAME}pid -i ${ETHERNAME} -D nl80211 -c$CONFIG_FILE -fi +./$DIRNAME/kill-wpa.sh +start_network_manager diff --git a/res/sinkctl.protocol-extension.example b/res/sinkctl.protocol-extension.example new file mode 100644 index 0000000..a0205fe --- /dev/null +++ b/res/sinkctl.protocol-extension.example @@ -0,0 +1,18 @@ +#Example of extra parameters to extend sink request + +[sinkctl] +extends.wfd_video_formats=40 00 01 10 0001bdeb 051557ff 00000fff 10 0000 001f 11 0780 0438, 02 10 0001bdeb 155557ff 00000fff 10 0000 001f 11 0780 0438 +extends.wfd_audio_codecs=LPCM 00000003 00, AAC 0000000f 00, AC3 00000007 00 +extends.wfd_display_edid=0001 00ffffffffffff0051f38f50010000000e100104a51d10ff2f0000a057499b2610484f000000010101010101010101010101010101011a36809c70381f403020350025a510000018000000fc00496e7465726e616c204c43440a000000fd003c3c9a9a0e00000000000000000000000000000000000000000000000000000030 +extends.wfd_connector_type=05 +extends.microsoft_cursor=none +extends.microsoft_rtcp_capability=none +extends.wfd_idr_request_capability=1 +extends.microsoft_latency_management_capability=none +extends.microsoft_format_change_capability=none +extends.microsoft_diagnostics_capability=none +extends.intel_friendly_name=miraclecast +extends.intel_sink_manufacturer_name=GNU Linux +extends.intel_sink_model_name=Arch linux +extends.intel_sink_device_URL=http://github.com/albfan/miraclecast +extends.wfd_uibc_capability=input_category_list=GENERIC, HIDC;generic_cap_list=Keyboard;hidc_cap_list=Keyboard/USB, Mouse/USB, MultiTouch/USB, Gesture/USB, RemoteControl/USB;port=none diff --git a/res/test-hardware-capabilities.sh b/res/test-hardware-capabilities.sh index 15a0cbf..602c7c8 100755 --- a/res/test-hardware-capabilities.sh +++ b/res/test-hardware-capabilities.sh @@ -1,18 +1,34 @@ #!/bin/bash -. miracle-utils.sh +eval SCRIPT_DEBUG="\$$(basename $0 .sh | tr - _)_DEBUG" +SCRIPT_DEBUG=${SCRIPT_DEBUG:--1} -WIFI_NAMES=$(find_wireless_network_interfaces) -WIFI_COUNT=$(echo "$WIFI_NAMES" | wc -l) +if [ "$SCRIPT_DEBUG" -ge 1 ] +then + set -x +fi +if [ "$SCRIPT_DEBUG" -ge 10 ] +then + set -v +fi + +. ./miracle-utils.sh + +WIFI_COUNT=0 +WIFI_NAMES="$(find_wireless_network_interfaces)" +if [ -n "$WIFI_NAMES" ] +then + WIFI_COUNT=$(echo "$WIFI_NAMES" | wc -l) +fi if [ 0 = $WIFI_COUNT ] then - echo There is no wireless devices avaliable + echo There is no wireless devices available exit 1 elif [ 1 = $WIFI_COUNT ] then WIFI_NAME="$WIFI_NAMES" -elif [ 2 -ge $WIFI_COUNT ] +elif [ 2 -le $WIFI_COUNT ] then echo Choose wireless device: PS3="device: " diff --git a/res/test-viewer.sh b/res/test-viewer.sh index ab1fa99..914012c 100755 --- a/res/test-viewer.sh +++ b/res/test-viewer.sh @@ -35,7 +35,7 @@ Try installing packages "gst-plugins-bad, gst-plugins-base, gst-plugins-base-lib If that fails too, try: -$ vlc rtp://@1991 +$ vlc rtp://@:7236 EOF else diff --git a/res/uibc-viewer b/res/uibc-viewer new file mode 100755 index 0000000..90c623e --- /dev/null +++ b/res/uibc-viewer @@ -0,0 +1,19 @@ +#!/bin/bash + +function kill_child() { + CHILDREN="$(ps -o pid= --ppid $$)" + echo killing $CHILDREN + kill $CHILDREN +} + +IP=$1 +shift +UIBC_PORT=$1 +shift + +echo $$ + +trap 'kill_child' SIGTERM + +gstplayer $@ | miracle-uibcctl $IP $UIBC_PORT --daemon & +wait diff --git a/res/write-udev-rule.sh b/res/write-udev-rule.sh index 6786dc9..73095da 100755 --- a/res/write-udev-rule.sh +++ b/res/write-udev-rule.sh @@ -8,7 +8,7 @@ ETHER_COUNT=$(echo "$ETHER_NAMES" | wc -l) if [ 0 = $ETHER_COUNT ] then - echo There is no net devices avaliable + echo There is no net devices available exit 1 elif [ 1 = $ETHER_COUNT ] then diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5b8363c..c049c12 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,37 +1,15 @@ -set(CMAKE_C_FLAGS "-std=gnu11") +set(CMAKE_C_FLAGS "-std=gnu11 ${CMAKE_C_FLAGS}") add_subdirectory(shared) add_subdirectory(wifi) add_subdirectory(dhcp) add_subdirectory(ctl) +add_subdirectory(uibc) set(miracled_SRCS miracled.h miracled.c) add_executable(miracled ${miracled_SRCS}) target_link_libraries(miracled miracle-shared) +target_link_libraries(miracled m) install(TARGETS miracled DESTINATION bin) INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/shared) - - -########### install files ############### - - - - -#original Makefile.am contents follow: - -#include $(top_srcdir)/common.am -#SUBDIRS = shared wifi dhcp ctl -# -#bin_PROGRAMS = miracled -# -#miracled_SOURCES = \ -# miracled.h \ -# miracled.c -#miracled_CPPFLAGS = \ -# $(AM_CPPFLAGS) \ -# $(DEPS_CFLAGS) -#miracled_LDADD = \ -# shared/libmiracle-shared.la \ -# $(DEPS_LIBS) -# diff --git a/src/Makefile.am b/src/Makefile.am index f10e22b..3a3ead6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,5 @@ include $(top_srcdir)/common.am -SUBDIRS = shared wifi dhcp ctl +SUBDIRS = shared wifi dhcp ctl uibc bin_PROGRAMS = miracled diff --git a/src/ctl/.ctl-sink.c.swo b/src/ctl/.ctl-sink.c.swo new file mode 100644 index 0000000..7736518 Binary files /dev/null and b/src/ctl/.ctl-sink.c.swo differ diff --git a/src/ctl/CMakeLists.txt b/src/ctl/CMakeLists.txt index 727a2d6..234b343 100644 --- a/src/ctl/CMakeLists.txt +++ b/src/ctl/CMakeLists.txt @@ -1,6 +1,9 @@ - -find_package(Readline) -########### next target ############### +find_package(PkgConfig) +pkg_check_modules (GLIB2 REQUIRED glib-2.0) +link_directories( ${GLIB2_LIBRARY_DIRS}) +include_directories( ${GLIB2_INCLUDE_DIRS}) +find_package(Readline REQUIRED) +pkg_check_modules (GLIB2 REQUIRED glib-2.0) set(miracle-wifictl_SRCS ctl.h ctl-cli.c @@ -20,17 +23,20 @@ if(READLINE_FOUND) endif(READLINE_FOUND) target_link_libraries(miracle-wifictl miracle-shared) -########### next target ############### +target_link_libraries(miracle-wifictl m) +target_link_libraries(miracle-wifictl ${GLIB2_LIBRARIES}) set(miracle-sinkctl_SRCS ctl.h ctl-cli.c + ctl-sink.h ctl-sink.c ctl-wifi.c sinkctl.c wfd.c) - add_executable(miracle-sinkctl ${miracle-sinkctl_SRCS}) +target_link_libraries(miracle-sinkctl ${GLIB2_LIBRARIES}) +target_link_libraries(miracle-sinkctl m) install(TARGETS miracle-sinkctl DESTINATION bin) @@ -45,42 +51,3 @@ endif(READLINE_FOUND) target_link_libraries(miracle-sinkctl miracle-shared) INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/shared) - -########### install files ############### - - - - -#original Makefile.am contents follow: - -#include $(top_srcdir)/common.am -#bin_PROGRAMS = miracle-wifictl miracle-sinkctl -# -#miracle_wifictl_SOURCES = \ -# ctl.h \ -# ctl-cli.c \ -# ctl-wifi.c \ -# wifictl.c -#miracle_wifictl_CPPFLAGS = \ -# $(AM_CPPFLAGS) \ -# $(DEPS_CFLAGS) -#miracle_wifictl_LDADD = \ -# ../shared/libmiracle-shared.la \ -# -lreadline \ -# $(DEPS_LIBS) -# -#miracle_sinkctl_SOURCES = \ -# ctl.h \ -# ctl-cli.c \ -# ctl-sink.c \ -# ctl-wifi.c \ -# sinkctl.c -#miracle_sinkctl_CPPFLAGS = \ -# $(AM_CPPFLAGS) \ -# $(DEPS_CFLAGS) -#miracle_sinkctl_LDADD = \ -# ../shared/libmiracle-shared.la \ -# -lreadline \ -# $(DEPS_LIBS) -# -# diff --git a/src/ctl/Makefile.am b/src/ctl/Makefile.am index 9f88336..ded5b45 100644 --- a/src/ctl/Makefile.am +++ b/src/ctl/Makefile.am @@ -8,7 +8,8 @@ miracle_wifictl_SOURCES = \ wifictl.c miracle_wifictl_CPPFLAGS = \ $(AM_CPPFLAGS) \ - $(DEPS_CFLAGS) + $(DEPS_CFLAGS) \ + $(GLIB_CFLAGS) miracle_wifictl_LDADD = \ ../shared/libmiracle-shared.la \ -lreadline \ @@ -17,16 +18,19 @@ miracle_wifictl_LDADD = \ miracle_sinkctl_SOURCES = \ ctl.h \ ctl-cli.c \ + ctl-sink.h \ ctl-sink.c \ ctl-wifi.c \ wfd.c \ sinkctl.c miracle_sinkctl_CPPFLAGS = \ $(AM_CPPFLAGS) \ - $(DEPS_CFLAGS) + $(DEPS_CFLAGS) \ + $(GLIB_CFLAGS) miracle_sinkctl_LDADD = \ ../shared/libmiracle-shared.la \ -lreadline \ - $(DEPS_LIBS) + $(DEPS_LIBS) \ + $(GLIB_LIBS) diff --git a/src/ctl/ctl-cli.c b/src/ctl/ctl-cli.c index 72d1eba..844d774 100644 --- a/src/ctl/ctl-cli.c +++ b/src/ctl/ctl-cli.c @@ -27,15 +27,15 @@ #include #include #include +#include #include #include #include "ctl.h" #include "shl_macro.h" #include "shl_util.h" +#include "shl_log.h" +#include -/* *sigh* readline doesn't include all their deps, so put them last */ -#include -#include /* * Helpers for interactive commands @@ -47,14 +47,14 @@ static sd_event_source *cli_sigs[_NSIG]; static sd_event_source *cli_stdin; static bool cli_rl; static const struct cli_cmd *cli_cmds; -int cli_max_sev = LOG_NOTICE; +unsigned int cli_max_sev = LOG_NOTICE; static bool is_cli(void) { return cli_rl; } -void cli_printv(const char *fmt, va_list args) +void cli_printv(const char *fmt, bool prefix_time, va_list args) { SHL_PROTECT_ERRNO; _shl_free_ char *line = NULL; @@ -73,6 +73,10 @@ void cli_printv(const char *fmt, va_list args) rl_redisplay(); } + if (prefix_time) { + cli_printf_time_prefix(); + } + vprintf(fmt, args); if (async) { @@ -83,24 +87,65 @@ void cli_printv(const char *fmt, va_list args) } } +void cli_printf_time_prefix(const char *fmt, va_list args) +{ + long long sec, usec; + time_t now; + struct tm *timeinfo; + struct timeval tv; + char buffertmp[80]; + char buffer[120]; + int millisec; + + + log__time(&sec, &usec); + + if (log_date_time) { + gettimeofday(&tv, NULL); + millisec = lrint(tv.tv_usec/1000.0); + if (millisec>=1000) { + millisec -=1000; + tv.tv_sec++; + } + + time(&now); + timeinfo = localtime(&now); + + strftime(buffertmp, 80, "%x - %X.%03d", timeinfo); + sprintf(buffer, "%s.%03d", buffertmp, millisec); + } + + if (log_date_time) + printf("[%s] ", buffer); + else if (log__have_time()) + printf("[%.4lld.%.6lld] ", sec, usec); +} + +void cli_command_printf(const char *fmt, ...) +{ + SHL_PROTECT_ERRNO; + va_list args; + + va_start(args, fmt); + cli_printv(fmt, false, args); + va_end(args); +} + void cli_printf(const char *fmt, ...) { SHL_PROTECT_ERRNO; va_list args; va_start(args, fmt); - cli_printv(fmt, args); + cli_printv(fmt, true, args); va_end(args); } -int cli_help(const struct cli_cmd *cmds) +int cli_help(const struct cli_cmd *cmds, int whitespace) { unsigned int i; - if (!is_cli()) { - cli_fn_help(); - } - cli_printf("Available commands:\n"); + cli_command_printf("Available commands:\n"); for (i = 0; cmds[i].cmd; ++i) { if (!cmds[i].desc) @@ -110,11 +155,11 @@ int cli_help(const struct cli_cmd *cmds) if (!is_cli() && cmds[i].cli_cmp == CLI_Y) continue; - cli_printf(" %s %-*s %s\n", - cmds[i].cmd, - (int)(40 - strlen(cmds[i].cmd)), - cmds[i].args ? : "", - cmds[i].desc ? : ""); + cli_command_printf(" %s %-*s %s\n", + cmds[i].cmd, + (int)(whitespace - strlen(cmds[i].cmd)), + cmds[i].args ? : "", + cmds[i].desc ? : ""); } return 0; @@ -143,21 +188,21 @@ int cli_do(const struct cli_cmd *cmds, char **args, unsigned int n) switch (cmds[i].argc_cmp) { case CLI_EQUAL: if (n != cmds[i].argc) { - cli_printf("Invalid number of arguments\n"); + cli_command_printf("Invalid number of arguments\n"); return -EINVAL; } break; case CLI_MORE: if (n < cmds[i].argc) { - cli_printf("too few arguments\n"); + cli_command_printf("too few arguments\n"); return -EINVAL; } break; case CLI_LESS: if (n > cmds[i].argc) { - cli_printf("too many arguments\n"); + cli_command_printf("too many arguments\n"); return -EINVAL; } @@ -173,7 +218,7 @@ int cli_do(const struct cli_cmd *cmds, char **args, unsigned int n) } if (!strcmp(cmd, "help")) - return cli_help(cmds); + return cli_help(cmds, 40); return -EAGAIN; } @@ -198,12 +243,15 @@ static void cli_handler_fn(char *input) else if (!r) return; - add_history(original); + if (!(strcmp(original, "quit") == 0 || strcmp(original, "exit") == 0)) { + add_history(original); + write_history(get_history_filename()); + } r = cli_do(cli_cmds, args, r); if (r != -EAGAIN) return; - cli_printf("Command not found\n"); + cli_command_printf("Command not found\n"); } static int cli_stdin_fn(sd_event_source *source, @@ -277,6 +325,312 @@ void cli_destroy(void) cli_event = NULL; } +char *yes_no_options[] = {"yes", "no", NULL}; + +char * +yes_no_generator (const char *text, int state) +{ + static int list_index, len; + const char *name; + + /* If this is a new word to complete, initialize now. This includes + saving the length of TEXT for efficiency, and initializing the index + variable to 0. */ + if (!state) + { + list_index = 0; + len = strlen (text); + } + + /* Return the next name which partially matches from the command list. */ + while ((name = yes_no_options[list_index]) != NULL) + { + list_index++; + + if (strncmp (name, text, len) == 0) + return (strdup(name)); + } + + /* If no names matched, then return NULL. */ + return ((char *)NULL); +} + +char * +links_peers_generator (const char *text, int state) +{ + static int list_index, len; + size_t peer_cnt = 0; + size_t link_cnt = 0; + struct shl_dlist *i, *j; + struct ctl_link *l; + struct ctl_peer *p; + + /* If this is a new word to complete, initialize now. This includes + saving the length of TEXT for efficiency, and initializing the index + variable to 0. */ + if (!state) + { + list_index = 0; + len = strlen (text); + } + + shl_dlist_for_each(i, &get_wifi()->links) { + l = link_from_dlist(i); + + char *name = l->label; + if (strncmp (name, text, len) == 0) + { + if (link_cnt == list_index) + { + list_index++; + return strdup(name); + } + link_cnt++; + } + + name = l->friendly_name; + if (!shl_isempty(name)) + { + if (strncmp (name, text, len) == 0) + { + if (link_cnt == list_index) + { + list_index++; + return strdup(name); + } + link_cnt++; + } + } + } + + peer_cnt = link_cnt; + + shl_dlist_for_each(i, &get_wifi()->links) { + l = link_from_dlist(i); + + shl_dlist_for_each(j, &l->peers) { + p = peer_from_dlist(j); + char *name = p->label; + if (strncmp (name, text, len) == 0) + { + if (peer_cnt == list_index) + { + list_index++; + return strdup(name); + } + peer_cnt++; + } + name = p->friendly_name; + if (!shl_isempty(name)) + { + if (strncmp (name, text, len) == 0) + { + if (peer_cnt == list_index) + { + list_index++; + return strdup(name); + } + peer_cnt++; + } + } + } + } + /* If no names matched, then return NULL. */ + return ((char *)NULL); +} + +char * +peers_generator (const char *text, int state) +{ + static int list_index, len; + size_t peer_cnt = 0; + struct shl_dlist *i, *j; + struct ctl_link *l; + struct ctl_peer *p; + + /* If this is a new word to complete, initialize now. This includes + saving the length of TEXT for efficiency, and initializing the index + variable to 0. */ + if (!state) + { + list_index = 0; + len = strlen (text); + } + + shl_dlist_for_each(i, &get_wifi()->links) { + l = link_from_dlist(i); + + shl_dlist_for_each(j, &l->peers) { + p = peer_from_dlist(j); + char *name = p->label; + if (strncmp (name, text, len) == 0) + { + if (peer_cnt == list_index) + { + list_index++; + return strdup(name); + } + peer_cnt++; + } + name = p->friendly_name; + if (!shl_isempty(name)) + { + if (strncmp (name, text, len) == 0) + { + if (peer_cnt == list_index) + { + list_index++; + return strdup(name); + } + peer_cnt++; + } + } + } + } + /* If no names matched, then return NULL. */ + return ((char *)NULL); +} + +char * +links_generator (const char *text, int state) +{ + static int list_index, len; + size_t link_cnt = 0; + struct shl_dlist *i; + struct ctl_link *l; + + + /* If this is a new word to complete, initialize now. This includes + saving the length of TEXT for efficiency, and initializing the index + variable to 0. */ + if (!state) + { + list_index = 0; + len = strlen (text); + } + + shl_dlist_for_each(i, &get_wifi()->links) { + l = link_from_dlist(i); + + + char *name = l->label; + if (strncmp (name, text, len) == 0) + { + if (link_cnt == list_index) + { + list_index++; + return strdup(name); + } + link_cnt++; + } + name = l->friendly_name; + if (!shl_isempty(name)) + { + if (strncmp (name, text, len) == 0) + { + if (link_cnt == list_index) + { + list_index++; + return strdup(name); + } + link_cnt++; + } + } + } + /* If no names matched, then return NULL. */ + return ((char *)NULL); +} + +/* Generator function for command completion. STATE lets us know whether + * to start from scratch; without any state (i.e. STATE == 0), then we + * start at the top of the list. + */ + +char * +command_generator (const char *text, int state) +{ + static int list_index, len; + const char *name; + + /* If this is a new word to complete, initialize now. This includes + saving the length of TEXT for efficiency, and initializing the index + variable to 0. */ + if (!state) + { + list_index = 0; + len = strlen (text); + } + + /* Return the next name which partially matches from the command list. */ + while ((name = cli_cmds[list_index].cmd) != NULL) + { + list_index++; + + if (strncmp (name, text, len) == 0) + return (strdup(name)); + } + + /* If no names matched, then return NULL. */ + return ((char *)NULL); +} + +int get_args(char* line) +{ + char* tmp = line; + char* last_delim = tmp; + int count = 0; + + /* Count how many elements will be extracted. */ + while (*tmp) + { + if (' ' == *tmp) + { + if (last_delim+1 < tmp) + count++; + last_delim = tmp; + } + tmp++; + } + if (' ' != *last_delim) + count++; + return count; +} + +/* + * Attempt to complete on the contents of TEXT. START and END bound the + * region of rl_line_buffer that contains the word to complete. TEXT is + * the word to complete. We can use the entire contents of rl_line_buffer + * in case we want to do some simple parsing. Return the array of matches, + * or NULL if there aren't any. + */ +char ** +completion_fn (const char *text, int start, int end) +{ + char **matches; + + rl_attempted_completion_over = 1; + if (start == 0) + matches = rl_completion_matches (text, command_generator); + else + { + matches = (char **)NULL; + struct cli_cmd cmd; + int cmd_pos = 0; + while ((cmd = cli_cmds[cmd_pos++]).cmd) + { + if (strncmp(cmd.cmd, rl_line_buffer, strlen(cmd.cmd)) == 0) + { + int nargs = get_args(rl_line_buffer); + rl_compentry_func_t* completion_fn = cmd.completion_fns[nargs-2]; + if (completion_fn) + matches = rl_completion_matches (text, completion_fn); + } + } + } + + return (matches); +} + int cli_init(sd_bus *bus, const struct cli_cmd *cmds) { static const int sigs[] = { @@ -320,27 +674,31 @@ int cli_init(sd_bus *bus, const struct cli_cmd *cmds) } } - r = sd_event_add_io(cli_event, + if (isatty(fileno(stdin))) { + r = sd_event_add_io(cli_event, &cli_stdin, fileno(stdin), EPOLLHUP | EPOLLERR | EPOLLIN, cli_stdin_fn, NULL); - if (r < 0) { - cli_vERR(r); - goto error; + if (r < 0) { + cli_vERR(r); + goto error; + } + cli_rl = true; + + rl_erase_empty_line = 1; + rl_attempted_completion_function = completion_fn; + rl_callback_handler_install(get_cli_prompt(), cli_handler_fn); + using_history(); + read_history(get_history_filename()); + rl_end_of_history(0, 0); + + printf("\r"); + rl_on_new_line(); + rl_redisplay(); } - cli_rl = true; - - rl_erase_empty_line = 1; - rl_callback_handler_install(NULL, cli_handler_fn); - - rl_set_prompt(CLI_PROMPT); - printf("\r"); - rl_on_new_line(); - rl_redisplay(); - return 0; error: diff --git a/src/ctl/ctl-sink.c b/src/ctl/ctl-sink.c index c4ba423..dd89fa4 100644 --- a/src/ctl/ctl-sink.c +++ b/src/ctl/ctl-sink.c @@ -17,49 +17,7 @@ * along with MiracleCast; If not, see . */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "ctl.h" -#include "rtsp.h" -#include "shl_macro.h" -#include "shl_util.h" -#include "wfd.h" - -struct ctl_sink { - sd_event *event; - - char *target; - char *session; - char *url; - struct sockaddr_storage addr; - size_t addr_size; - int fd; - sd_event_source *fd_source; - - struct rtsp *rtsp; - - bool connected : 1; - bool hup : 1; - - uint32_t resolutions_cea; - uint32_t resolutions_vesa; - uint32_t resolutions_hh; - - int hres; - int vres; -}; +#include "ctl-sink.h" /* * RTSP Session @@ -119,53 +77,92 @@ static void sink_handle_options(struct ctl_sink *s, } static void sink_handle_get_parameter(struct ctl_sink *s, - struct rtsp_message *m) + struct rtsp_message *m) { - _rtsp_message_unref_ struct rtsp_message *rep = NULL; - int r; + _rtsp_message_unref_ struct rtsp_message *rep = NULL; + int r; - r = rtsp_message_new_reply_for(m, &rep, RTSP_CODE_OK, NULL); - if (r < 0) - return cli_vERR(r); + r = rtsp_message_new_reply_for(m, &rep, RTSP_CODE_OK, NULL); + if (r < 0) + return cli_vERR(r); - /* wfd_content_protection */ - if (rtsp_message_read(m, "{<>}", "wfd_content_protection") >= 0) { - r = rtsp_message_append(rep, "{&}", - "wfd_content_protection: none"); - if (r < 0) - return cli_vERR(r); - } - /* wfd_video_formats */ - if (rtsp_message_read(m, "{<>}", "wfd_video_formats") >= 0) { - char wfd_video_formats[128]; - sprintf(wfd_video_formats, - "wfd_video_formats: 00 00 03 10 %08x %08x %08x 00 0000 0000 10 none none", - s->resolutions_cea, s->resolutions_vesa, s->resolutions_hh); - r = rtsp_message_append(rep, "{&}", wfd_video_formats); - if (r < 0) - return cli_vERR(r); - } - /* wfd_audio_codecs */ - if (rtsp_message_read(m, "{<>}", "wfd_audio_codecs") >= 0) { - r = rtsp_message_append(rep, "{&}", - "wfd_audio_codecs: AAC 00000007 00"); - if (r < 0) - return cli_vERR(r); - } - /* wfd_client_rtp_ports */ - if (rtsp_message_read(m, "{<>}", "wfd_client_rtp_ports") >= 0) { - r = rtsp_message_append(rep, "{&}", - "wfd_client_rtp_ports: RTP/AVP/UDP;unicast 1991 0 mode=play"); - if (r < 0) - return cli_vERR(r); - } + /* wfd_content_protection */ + check_and_response_option("wfd_content_protection", "none"); + GHashTable* protocol_extensions = s->protocol_extensions; + /* wfd_video_formats */ + gchar* wfd_video_formats = NULL; + if (protocol_extensions != NULL) { + gchar* wfd_video_formats_extension = g_hash_table_lookup(protocol_extensions, WFD_VIDEO_FORMATS); + if (wfd_video_formats_extension != NULL) { + wfd_video_formats = wfd_video_formats_extension; + } + } + bool create_wfd_video_formats = wfd_video_formats == NULL; + if (create_wfd_video_formats) { + gchar video_formats[128]; + sprintf(video_formats, "00 00 03 10 %08x %08x %08x 00 0000 0000 10 none none", + s->resolutions_cea, s->resolutions_vesa, s->resolutions_hh); + wfd_video_formats = strdup(video_formats); + } + check_and_response_option(WFD_VIDEO_FORMATS, wfd_video_formats); + if (create_wfd_video_formats) { + g_free(wfd_video_formats); + } - rtsp_message_seal(rep); - cli_debug("OUTGOING: %s\n", rtsp_message_get_raw(rep)); + /* wfd_audio_codecs */ + gchar* wfd_audio_codecs = "AAC 00000007 00"; + if (protocol_extensions != NULL) { + gchar* wfd_audio_codecs_extension = g_hash_table_lookup(protocol_extensions, WFD_AUDIO_CODECS); + if (wfd_audio_codecs_extension != NULL) { + wfd_audio_codecs = wfd_audio_codecs_extension; + } + } + check_and_response_option(WFD_AUDIO_CODECS, wfd_audio_codecs); - r = rtsp_send(s->rtsp, rep); - if (r < 0) - return cli_vERR(r); + /* wfd_client_rtp_ports */ + char wfd_client_rtp_ports[128]; + sprintf(wfd_client_rtp_ports, "RTP/AVP/UDP;unicast %d 0 mode=play", rstp_port); + check_and_response_option("wfd_client_rtp_ports", wfd_client_rtp_ports); + + if (protocol_extensions != NULL) { + GList* extension_keys = g_hash_table_get_keys(protocol_extensions); + for (int i = 0; irtsp, rep); + if (r < 0) + return cli_vERR(r); +} + +bool check_rtsp_option(struct rtsp_message *m, char *option) { + return rtsp_message_read(m, "{<>}", option) >= 0; } static int sink_setup_fn(struct rtsp *bus, struct rtsp_message *m, void *data) @@ -227,7 +224,7 @@ static int sink_set_format(struct ctl_sink *s, if (hres && vres) { s->hres = hres; s->vres = vres; - ctl_fn_sink_resolution_set(s, hres, vres); + ctl_fn_sink_resolution_set(s); return 0; } } @@ -241,6 +238,8 @@ static void sink_handle_set_parameter(struct ctl_sink *s, _rtsp_message_unref_ struct rtsp_message *rep = NULL; const char *trigger; const char *url; + char *uibc_config; + const char *uibc_setting; char *nu; unsigned int cea_res, vesa_res, hh_res; int r; @@ -273,6 +272,49 @@ static void sink_handle_set_parameter(struct ctl_sink *s, } } + /* M4 (or any other) can pass presentation URLs */ + r = rtsp_message_read(m, "{<&>}", "wfd_uibc_capability", &uibc_config); + if (r >= 0) { + if (!s->uibc_config || strcmp(s->uibc_config, uibc_config)) { + nu = strdup(uibc_config); + if (!nu) + return cli_vENOMEM(); + + free(s->uibc_config); + s->uibc_config = nu; + + if (!strcasecmp(uibc_config, "none")) { + uibc_enabled = false; + } else { + char* token = strtok(uibc_config, ";"); + + while (token) { + if (sscanf(token, "port=%d", &uibc_port)) { + log_debug("UIBC port: %d\n", uibc_port); + if (uibc_option) { + uibc_enabled = true; + } + break; + } + token = strtok(0, ";"); + } + } + } + } + + /* M4 (or any other) can pass presentation URLs */ + r = rtsp_message_read(m, "{}", "wfd_uibc_setting", &uibc_setting); + if (r >= 0) { + if (!s->uibc_setting || strcmp(s->uibc_setting, uibc_setting)) { + nu = strdup(uibc_setting); + if (!nu) + return cli_vENOMEM(); + + free(s->uibc_setting); + s->uibc_setting = nu; + cli_debug("uibc setting: %s\n", s->uibc_setting); + } + } /* M4 again */ r = rtsp_message_read(m, "{<****hhh>}", "wfd_video_formats", &cea_res, &vesa_res, &hh_res); @@ -300,9 +342,9 @@ static void sink_handle_set_parameter(struct ctl_sink *s, if (r < 0) return cli_vERR(r); - r = rtsp_message_append(rep, "", - "Transport", - "RTP/AVP/UDP;unicast;client_port=1991"); + char rtsp_setup[128]; + sprintf(rtsp_setup, "RTP/AVP/UDP;unicast;client_port=%d", rstp_port); + r = rtsp_message_append(rep, "", "Transport", rtsp_setup); if (r < 0) return cli_vERR(r); @@ -528,6 +570,8 @@ void ctl_sink_free(struct ctl_sink *s) free(s->session); free(s->url); sd_event_unref(s->event); + if (s->protocol_extensions) + g_hash_table_destroy(s->protocol_extensions); free(s); } diff --git a/src/ctl/ctl-sink.h b/src/ctl/ctl-sink.h new file mode 100644 index 0000000..22c8401 --- /dev/null +++ b/src/ctl/ctl-sink.h @@ -0,0 +1,92 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * MiracleCast is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * MiracleCast is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with MiracleCast; If not, see . + */ + +#ifndef CTL_SINK_H +#define CTL_SINK_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ctl.h" + +#include "rtsp.h" +#include "shl_macro.h" +#include "shl_util.h" +#include "wfd.h" + +#define WFD_VIDEO_FORMATS "wfd_video_formats" +#define WFD_AUDIO_CODECS "wfd_audio_codecs" +#define WFD_UIBC_CAPABILITY "wfd_uibc_capability" + +extern int rstp_port; +extern bool uibc_option; +extern bool uibc_enabled; +extern int uibc_port; + +struct ctl_sink { + sd_event *event; + + char *target; + char *session; + char *url; + char *uibc_config; + char *uibc_setting; + struct sockaddr_storage addr; + size_t addr_size; + int fd; + sd_event_source *fd_source; + + struct rtsp *rtsp; + + bool connected : 1; + bool hup : 1; + + uint32_t resolutions_cea; + uint32_t resolutions_vesa; + uint32_t resolutions_hh; + + int hres; + int vres; + + GHashTable* protocol_extensions; +}; + +bool check_rtsp_option(struct rtsp_message *m, char *option); + +#define check_and_response_option(option, response) \ + if (check_rtsp_option(m, option)) { \ + char option_response[512]; \ + sprintf(option_response, "%s: %s", option, response); \ + r = rtsp_message_append(rep, "{&}", option_response); \ + if (r < 0) {\ + return cli_vERR(r); \ + } \ + } + +#endif /* CTL_SINK_H */ diff --git a/src/ctl/ctl-wifi.c b/src/ctl/ctl-wifi.c index 6efca2c..2e06e72 100644 --- a/src/ctl/ctl-wifi.c +++ b/src/ctl/ctl-wifi.c @@ -414,6 +414,8 @@ static int ctl_link_parse_properties(struct ctl_link *l, bool p2p_scanning_set = false; char *tmp; int p2p_scanning, r; + bool managed_set = false; + int managed; if (!l || !m) return cli_EINVAL(); @@ -444,6 +446,13 @@ static int ctl_link_parse_properties(struct ctl_link *l, &friendly_name); if (r < 0) return cli_log_parser(r); + } else if (!strcmp(t, "Managed")) { + r = bus_message_read_basic_variant(m, "b", + &managed); + if (r < 0) + return cli_log_parser(r); + + managed_set = true; } else if (!strcmp(t, "P2PScanning")) { r = bus_message_read_basic_variant(m, "b", &p2p_scanning); @@ -494,6 +503,9 @@ static int ctl_link_parse_properties(struct ctl_link *l, } } + if (managed_set) + l->managed = managed; + if (p2p_scanning_set) l->p2p_scanning = p2p_scanning; @@ -620,6 +632,63 @@ int ctl_link_set_wfd_subelements(struct ctl_link *l, const char *val) return 0; } +int ctl_link_set_managed(struct ctl_link *l, bool val) +{ + _sd_bus_message_unref_ sd_bus_message *m = NULL; + _sd_bus_error_free_ sd_bus_error err = SD_BUS_ERROR_NULL; + _shl_free_ char *node = NULL; + int r; + + if (!l) + return cli_EINVAL(); + if (l->managed == val) + return 0; + + r = sd_bus_path_encode("/org/freedesktop/miracle/wifi/link", + l->label, + &node); + if (r < 0) + return cli_ERR(r); + + r = sd_bus_message_new_method_call(l->w->bus, + &m, + "org.freedesktop.miracle.wifi", + node, + "org.freedesktop.DBus.Properties", + "Set"); + if (r < 0) + return cli_log_create(r); + + r = sd_bus_message_append(m, "ss", + "org.freedesktop.miracle.wifi.Link", + "Managed"); + if (r < 0) + return cli_log_create(r); + + r = sd_bus_message_open_container(m, 'v', "b"); + if (r < 0) + return cli_log_create(r); + + r = sd_bus_message_append(m, "b", val); + if (r < 0) + return cli_log_create(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return cli_log_create(r); + + r = sd_bus_call(l->w->bus, m, 0, &err, NULL); + if (r < 0) { + cli_error("cannot change managed state on link %s to %d: %s", + l->label, val, bus_error_message(&err, r)); + return r; + } + + l->managed = val; + + return 0; +} + int ctl_link_set_p2p_scanning(struct ctl_link *l, bool val) { _sd_bus_message_unref_ sd_bus_message *m = NULL; @@ -754,7 +823,7 @@ static int ctl_wifi_parse_peer(struct ctl_wifi *w, l = ctl_wifi_find_link_by_peer(w, label); if (!l) - return cli_EINVAL(); + return 0; r = ctl_peer_new(&p, l, label); if (r < 0) diff --git a/src/ctl/ctl.h b/src/ctl/ctl.h index 40e49ec..cb8855f 100644 --- a/src/ctl/ctl.h +++ b/src/ctl/ctl.h @@ -17,6 +17,9 @@ * along with MiracleCast; If not, see . */ +#ifndef CTL_CTL_H +#define CTL_CTL_H + #include #include #include @@ -29,13 +32,22 @@ #include "shl_dlist.h" #include "shl_log.h" -#ifndef CTL_CTL_H -#define CTL_CTL_H +/* *sigh* readline doesn't include all their deps, so put them last */ +#include +#include +#include struct ctl_wifi; struct ctl_link; struct ctl_peer; +char* get_history_filename (); +struct ctl_wifi * get_wifi (); +char * links_peers_generator (const char *text, int state); +char * links_generator (const char *text, int state); +char * peers_generator (const char *text, int state); +char * yes_no_generator (const char *text, int state); + /* wifi handling */ struct ctl_peer { @@ -71,6 +83,7 @@ struct ctl_link { unsigned int ifindex; char *ifname; char *friendly_name; + bool managed; char *wfd_subelements; bool p2p_scanning; }; @@ -78,6 +91,7 @@ struct ctl_link { #define link_from_dlist(_l) shl_dlist_entry((_l), struct ctl_link, list); int ctl_link_set_friendly_name(struct ctl_link *l, const char *name); +int ctl_link_set_managed(struct ctl_link *l, bool val); int ctl_link_set_wfd_subelements(struct ctl_link *l, const char *val); int ctl_link_set_p2p_scanning(struct ctl_link *l, bool val); @@ -120,9 +134,11 @@ bool ctl_sink_is_closed(struct ctl_sink *s); /* CLI handling */ -extern int cli_max_sev; -void cli_printv(const char *fmt, va_list args); +extern unsigned int cli_max_sev; +void cli_printv(const char *fmt, bool prefix_time, va_list args); +void cli_printf_time_prefix(); void cli_printf(const char *fmt, ...); +void cli_command_printf(const char *fmt, ...); #define cli_log(_fmt, ...) \ cli_printf(_fmt "\n", ##__VA_ARGS__) @@ -186,7 +202,6 @@ void cli_printf(const char *fmt, ...); #define CLI_BLUE "\x1B[0;94m" #define CLI_BOLDGRAY "\x1B[1;30m" #define CLI_BOLDWHITE "\x1B[1;37m" -#define CLI_PROMPT CLI_BLUE "[miraclectl] # " CLI_DEFAULT struct cli_cmd { const char *cmd; @@ -204,6 +219,7 @@ struct cli_cmd { int argc; int (*fn) (char **args, unsigned int n); const char *desc; + rl_compentry_func_t *completion_fns[2]; }; extern sd_event *cli_event; @@ -213,13 +229,14 @@ extern unsigned int wfd_supported_res_cea; extern unsigned int wfd_supported_res_vesa; extern unsigned int wfd_supported_res_hh; +char* get_cli_prompt(); int cli_init(sd_bus *bus, const struct cli_cmd *cmds); void cli_destroy(void); int cli_run(void); void cli_exit(void); bool cli_running(void); -int cli_help(const struct cli_cmd *cmds); +int cli_help(const struct cli_cmd *cmds, int whitespace); int cli_do(const struct cli_cmd *cmds, char **args, unsigned int n); /* callback functions */ @@ -240,7 +257,7 @@ void ctl_fn_link_free(struct ctl_link *l); void ctl_fn_sink_connected(struct ctl_sink *s); void ctl_fn_sink_disconnected(struct ctl_sink *s); -void ctl_fn_sink_resolution_set(struct ctl_sink *s, int hres, int vres); +void ctl_fn_sink_resolution_set(struct ctl_sink *s); void cli_fn_help(void); diff --git a/src/ctl/meson.build b/src/ctl/meson.build new file mode 100644 index 0000000..cd9b77a --- /dev/null +++ b/src/ctl/meson.build @@ -0,0 +1,24 @@ +inc = include_directories('../..') +deps = [libsystemd, libmiracle_shared_dep, glib2, m] +if readline.found() + deps += readline +endif + +miracle_wifictl_srcs = ['ctl-cli.c', 'ctl-wifi.c', 'wifictl.c'] +executable('miracle-wifictl', miracle_wifictl_srcs, + install: true, + include_directories: inc, + dependencies: deps +) + +miracle_sinkctl_srcs = ['ctl-cli.c', + 'ctl-sink.c', + 'ctl-wifi.c', + 'sinkctl.c', + 'wfd.c' +] +executable('miracle-sinkctl', miracle_sinkctl_srcs, + install: true, + include_directories: inc, + dependencies: deps +) diff --git a/src/ctl/sinkctl.c b/src/ctl/sinkctl.c index 24d461e..841dbb2 100644 --- a/src/ctl/sinkctl.c +++ b/src/ctl/sinkctl.c @@ -17,6 +17,8 @@ * along with MiracleCast; If not, see . */ +#include "config.h" + #include #include #include @@ -30,15 +32,27 @@ #include #include #include + +#ifdef ENABLE_SYSTEMD #include +#endif + #include #include #include "ctl.h" +#include "ctl-sink.h" #include "wfd.h" #include "shl_macro.h" #include "shl_util.h" +#include "util.h" #include "config.h" +#include + +#define HISTORY_FILENAME ".miracle-sink.history" + +#define CLI_PROMPT "\001" CLI_BLUE "\002" "[sinkctl] # " "\001" CLI_DEFAULT "\002" + static sd_bus *bus; static struct ctl_wifi *wifi; static struct ctl_sink *sink; @@ -53,11 +67,41 @@ static struct ctl_link *running_link; static struct ctl_peer *running_peer; static struct ctl_peer *pending_peer; +void launch_player(struct ctl_sink *s); + char *gst_scale_res; int gst_audio_en = 1; -unsigned int wfd_supported_res_cea = 0x0000001f; /* up to 720x576 */ -unsigned int wfd_supported_res_vesa = 0x00000003; /* up to 800x600 */ -unsigned int wfd_supported_res_hh = 0x00000000; /* not supported */ +static const int DEFAULT_RSTP_PORT = 7236; +bool uibc_option; +bool uibc_enabled; +bool external_player; +int rstp_port; +int uibc_port; +char* player; +GHashTable* protocol_extensions; + +unsigned int wfd_supported_res_cea = 0x0001ffff; +unsigned int wfd_supported_res_vesa = 0x1fffffff; +unsigned int wfd_supported_res_hh = 0x00001fff; + +struct ctl_wifi *get_wifi() +{ + return wifi; +} + +char* get_cli_prompt() +{ + return CLI_PROMPT; +} + +/* + * get history filename + */ + +char* get_history_filename() +{ + return HISTORY_FILENAME; +} /* * cmd list @@ -72,27 +116,28 @@ static int cmd_list(char **args, unsigned int n) /* list links */ - cli_printf("%6s %-24s %-30s\n", - "LINK", "INTERFACE", "FRIENDLY-NAME"); + cli_command_printf("%6s %-24s %-30s %-10s\n", + "LINK", "INTERFACE", "FRIENDLY-NAME", "MANAGED"); shl_dlist_for_each(i, &wifi->links) { l = link_from_dlist(i); ++link_cnt; - cli_printf("%6s %-24s %-30s\n", - l->label, - shl_isempty(l->ifname) ? - "" : l->ifname, + cli_command_printf("%6s %-24s %-30s %-10s\n", + l->label, + shl_isempty(l->ifname) ? + "" : l->ifname, shl_isempty(l->friendly_name) ? - "" : l->friendly_name); + "" : l->friendly_name, + l->managed ? "yes": "no"); } - cli_printf("\n"); + cli_command_printf("\n"); /* list peers */ - cli_printf("%6s %-24s %-30s %-10s\n", - "LINK", "PEER-ID", "FRIENDLY-NAME", "CONNECTED"); + cli_command_printf("%6s %-24s %-30s %-10s\n", + "LINK", "PEER-ID", "FRIENDLY-NAME", "CONNECTED"); shl_dlist_for_each(i, &wifi->links) { l = link_from_dlist(i); @@ -101,16 +146,16 @@ static int cmd_list(char **args, unsigned int n) p = peer_from_dlist(j); ++peer_cnt; - cli_printf("%6s %-24s %-30s %-10s\n", - p->l->label, - p->label, - shl_isempty(p->friendly_name) ? + cli_command_printf("%6s %-24s %-30s %-10s\n", + p->l->label, + p->label, + shl_isempty(p->friendly_name) ? "" : p->friendly_name, p->connected ? "yes" : "no"); } } - cli_printf("\n %u peers and %u links listed.\n", peer_cnt, link_cnt); + cli_command_printf("\n %u peers and %u links listed.\n", peer_cnt, link_cnt); return 0; } @@ -129,45 +174,81 @@ static int cmd_show(char **args, unsigned int n) !(p = ctl_wifi_find_peer(wifi, args[0])) && !(l = ctl_wifi_search_link(wifi, args[0])) && !(p = ctl_wifi_search_peer(wifi, args[0]))) { - cli_error("unknown link or peer %s", args[0]); + cli_error("unknown %s", args[0]); return 0; } } if (l) { - cli_printf("Link=%s\n", l->label); + cli_command_printf("Link=%s\n", l->label); if (l->ifindex > 0) - cli_printf("InterfaceIndex=%u\n", l->ifindex); + cli_command_printf("InterfaceIndex=%u\n", l->ifindex); if (l->ifname && *l->ifname) - cli_printf("InterfaceName=%s\n", l->ifname); + cli_command_printf("InterfaceName=%s\n", l->ifname); if (l->friendly_name && *l->friendly_name) - cli_printf("FriendlyName=%s\n", l->friendly_name); - cli_printf("P2PScanning=%d\n", l->p2p_scanning); + cli_command_printf("FriendlyName=%s\n", l->friendly_name); + cli_command_printf("P2PScanning=%d\n", l->p2p_scanning); if (l->wfd_subelements && *l->wfd_subelements) - cli_printf("WfdSubelements=%s\n", l->wfd_subelements); + cli_command_printf("WfdSubelements=%s\n", l->wfd_subelements); + cli_command_printf("Managed=%d\n", l->managed); } else if (p) { - cli_printf("Peer=%s\n", p->label); + cli_command_printf("Peer=%s\n", p->label); if (p->p2p_mac && *p->p2p_mac) - cli_printf("P2PMac=%s\n", p->p2p_mac); + cli_command_printf("P2PMac=%s\n", p->p2p_mac); if (p->friendly_name && *p->friendly_name) - cli_printf("FriendlyName=%s\n", p->friendly_name); - cli_printf("Connected=%d\n", p->connected); + cli_command_printf("FriendlyName=%s\n", p->friendly_name); + cli_command_printf("Connected=%d\n", p->connected); if (p->interface && *p->interface) - cli_printf("Interface=%s\n", p->interface); + cli_command_printf("Interface=%s\n", p->interface); if (p->local_address && *p->local_address) - cli_printf("LocalAddress=%s\n", p->local_address); + cli_command_printf("LocalAddress=%s\n", p->local_address); if (p->remote_address && *p->remote_address) - cli_printf("RemoteAddress=%s\n", p->remote_address); + cli_command_printf("RemoteAddress=%s\n", p->remote_address); if (p->wfd_subelements && *p->wfd_subelements) - cli_printf("WfdSubelements=%s\n", p->wfd_subelements); + cli_command_printf("WfdSubelements=%s\n", p->wfd_subelements); } else { - cli_printf("Show what?\n"); + cli_command_printf("Show what?\n"); return 0; } return 0; } +/* + * cmd: set-friendly-name + */ + +static int cmd_set_friendly_name(char **args, unsigned int n) +{ + struct ctl_link *l = NULL; + const char *name; + + if (n < 1) { + cli_command_printf("To what?\n"); + return 0; + } + + if (n > 1) { + l = ctl_wifi_search_link(wifi, args[0]); + if (!l) { + cli_error("unknown link %s", args[0]); + return 0; + } + + name = args[1]; + } else { + name = args[0]; + } + + l = l ? : running_link; + if (!l) { + cli_error("no running link"); + return 0; + } + + return ctl_link_set_friendly_name(l, name); +} + /* * cmd: run */ @@ -198,6 +279,11 @@ static int cmd_run(char **args, unsigned int n) return 0; } + if (!l->managed) { + cli_printf("link %s not managed\n", l->label); + return 0; + } + run_on(l); return 0; @@ -228,11 +314,37 @@ static int cmd_bind(char **args, unsigned int n) if (!l) return 0; + if (!l->managed) { + cli_printf("link %s not managed\n", l->label); + return 0; + } + run_on(l); return 0; } +/* + * cmd: set-managed + */ + +static int cmd_set_managed(char **args, unsigned int n) +{ + struct ctl_link *l = NULL; + bool managed = true; + + l = ctl_wifi_search_link(wifi, args[0]); + if (!l) { + cli_error("unknown link %s", args[0]); + return 0; + } + + if (!strcmp(args[1], "no")) { + managed = false; + } + return ctl_link_set_managed(l, managed); +} + /* * cmd: quit/exit */ @@ -325,22 +437,22 @@ static int sink_timeout_fn(sd_event_source *s, uint64_t usec, void *data) } static const struct cli_cmd cli_cmds[] = { - { "list", NULL, CLI_M, CLI_LESS, 0, cmd_list, "List all objects" }, - { "show", "", CLI_M, CLI_LESS, 1, cmd_show, "Show detailed object information" }, - { "run", "", CLI_M, CLI_EQUAL, 1, cmd_run, "Run sink on given link" }, - { "bind", "", CLI_M, CLI_EQUAL, 1, cmd_bind, "Like 'run' but bind the link name to run when it is hotplugged" }, - { "quit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, "Quit program" }, - { "exit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, NULL }, - { "help", NULL, CLI_M, CLI_MORE, 0, NULL, "Print help" }, + { "list", NULL, CLI_M, CLI_LESS, 0, cmd_list, "List all objects", {NULL} }, + { "show", "", CLI_M, CLI_LESS, 1, cmd_show, "Show detailed object information", {links_peers_generator, NULL} }, + { "run", "", CLI_M, CLI_EQUAL, 1, cmd_run, "Run sink on given link", {links_generator, NULL} }, + { "bind", "", CLI_M, CLI_EQUAL, 1, cmd_bind, "Like 'run' but bind the link name to run when it is hotplugged", {links_generator, NULL} }, + { "set-friendly-name", "[link] ", CLI_M, CLI_LESS, 2, cmd_set_friendly_name, "Set friendly name of an object", {links_generator, NULL} }, + { "set-managed", " ", CLI_M, CLI_EQUAL, 2, cmd_set_managed, "Manage or unmnage a link", {links_generator, yes_no_generator} }, + { "quit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, "Quit program", {NULL} }, + { "exit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, NULL, {NULL} }, + { "help", NULL, CLI_M, CLI_MORE, 0, NULL, "Print help", {NULL} }, { }, }; -static void spawn_gst(int hres, int vres) +static void spawn_gst(struct ctl_sink *s) { - char *argv[64]; - char resolution[64]; pid_t pid; - int fd_journal, i; + int fd_journal; sigset_t mask; if (sink_pid > 0) @@ -355,43 +467,113 @@ static void spawn_gst(int hres, int vres) sigemptyset(&mask); sigprocmask(SIG_SETMASK, &mask, NULL); +#ifdef ENABLE_SYSTEMD /* redirect stdout/stderr to journal */ fd_journal = sd_journal_stream_fd("miracle-sinkctl-gst", - LOG_INFO, + LOG_DEBUG, false); if (fd_journal >= 0) { /* dup journal-fd to stdout and stderr */ dup2(fd_journal, 1); dup2(fd_journal, 2); } else { +#endif /* no journal? redirect stdout to parent's stderr */ dup2(2, 1); +#ifdef ENABLE_SYSTEMD } +#endif - i = 0; - argv[i++] = (char*) BUILD_BINDIR "/miracle-gst.sh"; - if (cli_max_sev >= 7) - argv[i++] = "-d 3"; - if (gst_audio_en) - argv[i++] = "-a"; - if (gst_scale_res) { - argv[i++] = "-s"; - argv[i++] = gst_scale_res; - } - if (hres && vres) { - sprintf(resolution, "%dx%d", hres, vres); - argv[i++] = "-r"; - argv[i++] = resolution; - } - argv[i] = NULL; - - execve(argv[0], argv, environ); + launch_player(s); _exit(1); } else { sink_pid = pid; } } +void launch_player(struct ctl_sink *s) { + char *argv[64]; + char resolution[64]; + char port[64]; + char uibc_portStr[64]; + int i = 0; + if (!external_player) { + if (uibc_enabled) { + player = "uibc-viewer"; + } else { + player = "miracle-gst"; + } + } + + argv[i++] = player; + if (uibc_enabled) { + argv[i++] = s->target; + sprintf(uibc_portStr, "%d", uibc_port); + argv[i++] = uibc_portStr; + } + if (gst_debug) { + argv[i++] = "-d"; + argv[i++] = gst_debug; + } else if (cli_max_sev >= LOG_DEBUG) { + argv[i++] = "-d"; + argv[i++] = "3"; + } + if (gst_audio_en) + argv[i++] = "-a"; + if (gst_scale_res) { + argv[i++] = "-s"; + argv[i++] = gst_scale_res; + } + argv[i++] = "-p"; + sprintf(port, "%d", rstp_port); + argv[i++] = port; + + if (s->hres && s->vres) { + sprintf(resolution, "%dx%d", s->hres, s->vres); + argv[i++] = "-r"; + argv[i++] = resolution; + } + + argv[i] = NULL; + + i = 0; + size_t size = 0; + while (argv[i]) { + size += strlen(argv[i++]) + 1; + } + + char* player_command = malloc(size); + i = 0; + strcpy(player_command, argv[i++]); + while (argv[i]) { + strcat(player_command, " "); + strcat(player_command, argv[i++]); + } + log_debug("player command: %s", player_command); + if (execvpe(argv[0], argv, environ) < 0) { + cli_debug("stream player failed (%d): %m", errno); + int i = 0; + cli_debug("printing environment: "); + while (environ[i]) { + cli_debug("%s", environ[i++]); + } + } +} + +void launch_uibc_daemon(int port) { + char *argv[64]; + char portStr[64]; + int i = 0; + argv[i++] = "miracle-uibcctl"; + argv[i++] = "localhost"; + sprintf(portStr, "%d", port); + argv[i++] = portStr; + argv[i] = NULL; + + cli_debug("uibc daemon: %s", argv[0]); + execvpe(argv[0], argv, environ); +} + static void kill_gst(void) { if (sink_pid <= 0) @@ -418,11 +600,11 @@ void ctl_fn_sink_disconnected(struct ctl_sink *s) } } -void ctl_fn_sink_resolution_set(struct ctl_sink *s, int hres, int vres) +void ctl_fn_sink_resolution_set(struct ctl_sink *s) { - cli_printf("SINK set resolution %dx%d\n", hres, vres); + cli_printf("SINK set resolution %dx%d\n", s->hres, s->vres); if (sink_connected) - spawn_gst(hres, vres); + spawn_gst(s); } void ctl_fn_peer_new(struct ctl_peer *p) @@ -601,20 +783,29 @@ void cli_fn_help() */ printf("%s [OPTIONS...] ...\n\n" "Control a dedicated local sink via MiracleCast.\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --log-level Maximum level for log messages\n" - " --audio <0/1> Enable audio support (default %d)\n" - " --scale WxH Scale to resolution\n" - " --res Supported resolutions masks (CEA, VESA, HH)\n" - " default CEA %08X\n" - " default VESA %08X\n" - " default HH %08X\n" + " -h --help Show this help\n" + " --help-commands Show available commands\n" + " --version Show package version\n" + " --log-level Maximum level for log messages\n" + " --log-time Prefix log-messages with timestamp\n" + " --log-date-time Prefix log-messages with date time\n" "\n" - , program_invocation_short_name, gst_audio_en, + " --log-journal-level Maximum level for journal log messages\n" + " --gst-debug [cat:]lvl[,...] List of categories an level of debug\n" + " --audio <0/1> Enable audio support (default %d)\n" + " --scale WxH Scale to resolution\n" + " -p --port Port for rtsp (default %d)\n" + " --uibc Enables UIBC\n" + " -e --external-player Configure player to use\n" + " --res Supported resolutions masks (CEA, VESA, HH)\n" + " default CEA %08X\n" + " default VESA %08X\n" + " default HH %08X\n" + " --help-res Shows available values for res\n" + "\n" + , program_invocation_short_name, gst_audio_en, DEFAULT_RSTP_PORT, wfd_supported_res_cea, wfd_supported_res_vesa, wfd_supported_res_hh ); - wfd_print_resolutions(); /* * 80-char barrier: * 01234567890123456789012345678901234567890123456789012345678901234567890123456789 @@ -632,6 +823,7 @@ static int ctl_interactive(char **argv, int argc) r = ctl_sink_new(&sink, cli_event); if (r < 0) goto error; + sink->protocol_extensions = protocol_extensions; r = ctl_wifi_fetch(wifi); if (r < 0) @@ -657,12 +849,19 @@ static int ctl_main(int argc, char *argv[]) struct ctl_link *l; int r, left; + if (getuid() != 0) { + r = EACCES; + log_notice("Must run as root"); + return r; + } + r = ctl_wifi_new(&wifi, bus); if (r < 0) return r; - left = argc - optind; - r = ctl_interactive(argv + optind, left <= 0 ? 0 : left); + left = argc - optind; + left = left <= 0 ? 0 : left; + r = ctl_interactive(argv + optind, left); /* stop all scans */ shl_dlist_for_each(i, &wifi->links) { @@ -680,31 +879,70 @@ static int parse_argv(int argc, char *argv[]) enum { ARG_VERSION = 0x100, ARG_LOG_LEVEL, + ARG_LOG_TIME, + ARG_LOG_DATE_TIME, + ARG_JOURNAL_LEVEL, + ARG_GST_DEBUG, ARG_AUDIO, ARG_SCALE, ARG_RES, + ARG_HELP_RES, + ARG_UIBC, + ARG_HELP_COMMANDS, }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, + { "help", no_argument, NULL, 'h' }, + { "help-commands", no_argument, NULL, ARG_HELP_COMMANDS }, + { "version" , no_argument, NULL, ARG_VERSION }, + { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, + { "log-time", no_argument, NULL, ARG_LOG_TIME }, + { "log-date-time", no_argument, NULL, ARG_LOG_DATE_TIME }, + { "log-journal-level", required_argument, NULL, ARG_JOURNAL_LEVEL }, + { "gst-debug", required_argument, NULL, ARG_GST_DEBUG }, { "audio", required_argument, NULL, ARG_AUDIO }, { "scale", required_argument, NULL, ARG_SCALE }, { "res", required_argument, NULL, ARG_RES }, + { "help-res", no_argument, NULL, ARG_HELP_RES }, + { "port", required_argument, NULL, 'p' }, + { "uibc", no_argument, NULL, ARG_UIBC }, + { "external-player", required_argument, NULL, 'e' }, {} }; int c; - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + uibc_option = false; + uibc_enabled = false; + external_player = false; + rstp_port = DEFAULT_RSTP_PORT; + + while ((c = getopt_long(argc, argv, "he:p:", options, NULL)) >= 0) { switch (c) { case 'h': - return cli_help(cli_cmds); + cli_fn_help(); + return 0; + case ARG_HELP_COMMANDS: + return cli_help(cli_cmds, 20); + case ARG_HELP_RES: + wfd_print_resolutions(""); + return 0; case ARG_VERSION: puts(PACKAGE_STRING); return 0; case ARG_LOG_LEVEL: cli_max_sev = log_parse_arg(optarg); break; + case ARG_LOG_TIME: + log_init_time(); + break; + case ARG_LOG_DATE_TIME: + log_date_time = true; + break; + case ARG_GST_DEBUG: + gst_debug = optarg; + break; + case ARG_JOURNAL_LEVEL: + log_max_sev = log_parse_arg(optarg); + break; case ARG_AUDIO: gst_audio_en = atoi(optarg); break; @@ -717,6 +955,16 @@ static int parse_argv(int argc, char *argv[]) &wfd_supported_res_vesa, &wfd_supported_res_hh); break; + case 'p': + rstp_port = atoi(optarg); + break; + case 'e': + external_player = true; + player = optarg; + break; + case ARG_UIBC: + uibc_option = true; + break; case '?': return -EINVAL; } @@ -728,9 +976,75 @@ static int parse_argv(int argc, char *argv[]) int main(int argc, char **argv) { int r; + bool free_argv = false; setlocale(LC_ALL, ""); + GKeyFile* gkf = load_ini_file(); + + gchar** autocmds_free = NULL; + if (gkf) { + player = g_key_file_get_string (gkf, "sinkctl", "external-player", NULL); + if (player) { + external_player = true; + } + gchar* log_level; + log_level = g_key_file_get_string (gkf, "sinkctl", "log-journal-level", NULL); + if (log_level) { + log_max_sev = log_parse_arg(log_level); + g_free(log_level); + } + log_level = g_key_file_get_string (gkf, "sinkctl", "log-level", NULL); + if (log_level) { + cli_max_sev = log_parse_arg(log_level); + g_free(log_level); + } + gchar* rstp_port_str = g_key_file_get_string (gkf, "sinkctl", "rstp-port", NULL); + if (rstp_port_str) { + rstp_port = atoi(rstp_port_str); + g_free(rstp_port_str); + } + gchar* autocmd; + autocmd = g_key_file_get_string (gkf, "sinkctl", "autocmd", NULL); + if (autocmd) { + gchar** autocmds = g_strsplit(autocmd, " ", -1); + autocmds_free = autocmds; + while (*autocmds) { + if (strcmp(*autocmds, "") != 0) { + gchar **newv = malloc((argc + 2) * sizeof(gchar*)); + memmove(newv, argv, sizeof(gchar*) * argc); + newv[argc] = *autocmds; + newv[argc+1] = NULL; + argc++; + if (free_argv) { + free(argv); + } + argv = newv; + free_argv = true; + } + autocmds++; + } + g_free(autocmd); + } + gchar** sinkctl_keys; + gsize len = 0; + protocol_extensions = g_hash_table_new(g_str_hash, g_str_equal); + + sinkctl_keys = g_key_file_get_keys (gkf, + "sinkctl", + &len, + NULL); + for (int i = 0; i < (int)len; i++) { + if (g_str_has_prefix(sinkctl_keys[i], "extends.")) { + gchar* orig_key = sinkctl_keys[i]; + gchar* key = orig_key+8; + gchar* value = g_key_file_get_string (gkf, "sinkctl", orig_key, NULL); + g_hash_table_insert(protocol_extensions, key, value); + } + } + g_key_file_free(gkf); + } + r = parse_argv(argc, argv); if (r < 0) return EXIT_FAILURE; @@ -744,6 +1058,10 @@ int main(int argc, char **argv) } r = ctl_main(argc, argv); + g_strfreev(autocmds_free); + if (free_argv) { + free(argv); + } sd_bus_unref(bus); return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; diff --git a/src/ctl/wfd.c b/src/ctl/wfd.c index 87c3922..49ee5d3 100644 --- a/src/ctl/wfd.c +++ b/src/ctl/wfd.c @@ -103,27 +103,27 @@ struct resolution_bitmap resolutions_hh[] = { {0, 0, 0, 0}, }; -void wfd_print_resolutions(void) +void wfd_print_resolutions(char * prefix) { int i; - printf("CEA resolutions:\n"); + printf("%sCEA resolutions:\n", prefix); for (i = 0; resolutions_cea[i].hres != 0; i++) { - printf("\t%2d %08x %4dx%4d@%d\n", + printf("%s\t%2d %08x %4dx%4d@%d\n", prefix, resolutions_cea[i].index, 1 << resolutions_cea[i].index, resolutions_cea[i].hres, resolutions_cea[i].vres, resolutions_cea[i].fps); } - printf("VESA resolutions:\n"); + printf("%sVESA resolutions:\n", prefix); for (i = 0; resolutions_vesa[i].hres != 0; i++) { - printf("\t%2d %08x %4dx%4d@%d\n", + printf("%s\t%2d %08x %4dx%4d@%d\n", prefix, resolutions_vesa[i].index, 1 << resolutions_vesa[i].index, resolutions_vesa[i].hres, resolutions_vesa[i].vres, resolutions_vesa[i].fps); } - printf("HH resolutions:\n"); + printf("%sHH resolutions:\n", prefix); for (i = 0; resolutions_hh[i].hres != 0; i++) { - printf("\t%2d %08x %4dx%4d@%d\n", + printf("%s\t%2d %08x %4dx%4d@%d\n", prefix, resolutions_hh[i].index, 1 << resolutions_hh[i].index, resolutions_hh[i].hres, resolutions_hh[i].vres, resolutions_hh[i].fps); diff --git a/src/ctl/wfd.h b/src/ctl/wfd.h index 2d85111..d9a5831 100644 --- a/src/ctl/wfd.h +++ b/src/ctl/wfd.h @@ -21,7 +21,7 @@ #ifndef WFD_H #define WFD_H -void wfd_print_resolutions(void); +void wfd_print_resolutions(char * prefix); int vfd_get_cea_resolution(uint32_t mask, int *hres, int *vres); int vfd_get_vesa_resolution(uint32_t mask, int *hres, int *vres); int vfd_get_hh_resolution(uint32_t mask, int *hres, int *vres); diff --git a/src/ctl/wifictl.c b/src/ctl/wifictl.c index f8bf7e5..0af1f5c 100644 --- a/src/ctl/wifictl.c +++ b/src/ctl/wifictl.c @@ -31,13 +31,39 @@ #include "ctl.h" #include "shl_macro.h" #include "shl_util.h" +#include "util.h" #include "config.h" +#include + +#define HISTORY_FILENAME ".miracle-wifi.history" + +#define CLI_PROMPT "\001" CLI_BLUE "\002" "[wifictl] # " "\001" CLI_DEFAULT "\002" + static sd_bus *bus; static struct ctl_wifi *wifi; static struct ctl_link *selected_link; +char* get_cli_prompt() +{ + return CLI_PROMPT; +} + +/* + * get history filename + */ + +char* get_history_filename() +{ + return HISTORY_FILENAME; +} + +struct ctl_wifi *get_wifi() +{ + return wifi; +} + /* * cmd list */ @@ -51,26 +77,27 @@ static int cmd_list(char **args, unsigned int n) /* list links */ - cli_printf("%6s %-24s %-30s\n", - "LINK", "INTERFACE", "FRIENDLY-NAME"); + cli_command_printf("%6s %-24s %-30s %-10s\n", + "LINK", "INTERFACE", "FRIENDLY-NAME", "MANAGED"); shl_dlist_for_each(i, &wifi->links) { l = link_from_dlist(i); ++link_cnt; - cli_printf("%6s %-24s %-30s\n", - l->label, - shl_isempty(l->ifname) ? - "" : l->ifname, - shl_isempty(l->friendly_name) ? - "" : l->friendly_name); + cli_command_printf("%6s %-24s %-30s %-10s\n", + l->label, + shl_isempty(l->ifname) ? + "" : l->ifname, + shl_isempty(l->friendly_name) ? + "" : l->friendly_name, + l->managed ? "yes": "no"); } - cli_printf("\n"); + cli_command_printf("\n"); /* list peers */ - cli_printf("%6s %-24s %-30s %-10s\n", + cli_command_printf("%6s %-24s %-30s %-10s\n", "LINK", "PEER-ID", "FRIENDLY-NAME", "CONNECTED"); shl_dlist_for_each(i, &wifi->links) { @@ -80,16 +107,16 @@ static int cmd_list(char **args, unsigned int n) p = peer_from_dlist(j); ++peer_cnt; - cli_printf("%6s %-24s %-30s %-10s\n", - p->l->label, - p->label, - shl_isempty(p->friendly_name) ? - "" : p->friendly_name, - p->connected ? "yes" : "no"); + cli_command_printf("%6s %-24s %-30s %-10s\n", + p->l->label, + p->label, + shl_isempty(p->friendly_name) ? + "" : p->friendly_name, + p->connected ? "yes" : "no"); } } - cli_printf("\n %u peers and %u links listed.\n", peer_cnt, link_cnt); + cli_command_printf("\n %u peers and %u links listed.\n", peer_cnt, link_cnt); return 0; } @@ -119,6 +146,7 @@ static int cmd_select(char **args, unsigned int n) } selected_link = l; + ctl_link_set_wfd_subelements(l, "000600111c4400c8"); cli_printf("link %s selected\n", selected_link->label); return 0; @@ -146,33 +174,34 @@ static int cmd_show(char **args, unsigned int n) } if (l) { - cli_printf("Link=%s\n", l->label); + cli_command_printf("Link=%s\n", l->label); if (l->ifindex > 0) - cli_printf("InterfaceIndex=%u\n", l->ifindex); + cli_command_printf("InterfaceIndex=%u\n", l->ifindex); if (l->ifname && *l->ifname) - cli_printf("InterfaceName=%s\n", l->ifname); + cli_command_printf("InterfaceName=%s\n", l->ifname); if (l->friendly_name && *l->friendly_name) - cli_printf("FriendlyName=%s\n", l->friendly_name); - cli_printf("P2PScanning=%d\n", l->p2p_scanning); + cli_command_printf("FriendlyName=%s\n", l->friendly_name); + cli_command_printf("P2PScanning=%d\n", l->p2p_scanning); if (l->wfd_subelements && *l->wfd_subelements) - cli_printf("WfdSubelements=%s\n", l->wfd_subelements); + cli_command_printf("WfdSubelements=%s\n", l->wfd_subelements); + cli_command_printf("Managed=%d\n", l->managed); } else if (p) { - cli_printf("Peer=%s\n", p->label); + cli_command_printf("Peer=%s\n", p->label); if (p->p2p_mac && *p->p2p_mac) - cli_printf("P2PMac=%s\n", p->p2p_mac); + cli_command_printf("P2PMac=%s\n", p->p2p_mac); if (p->friendly_name && *p->friendly_name) - cli_printf("FriendlyName=%s\n", p->friendly_name); - cli_printf("Connected=%d\n", p->connected); + cli_command_printf("FriendlyName=%s\n", p->friendly_name); + cli_command_printf("Connected=%d\n", p->connected); if (p->interface && *p->interface) - cli_printf("Interface=%s\n", p->interface); + cli_command_printf("Interface=%s\n", p->interface); if (p->local_address && *p->local_address) - cli_printf("LocalAddress=%s\n", p->local_address); + cli_command_printf("LocalAddress=%s\n", p->local_address); if (p->remote_address && *p->remote_address) - cli_printf("RemoteAddress=%s\n", p->remote_address); + cli_command_printf("RemoteAddress=%s\n", p->remote_address); if (p->wfd_subelements && *p->wfd_subelements) - cli_printf("WfdSubelements=%s\n", p->wfd_subelements); + cli_command_printf("WfdSubelements=%s\n", p->wfd_subelements); } else { - cli_printf("Show what?\n"); + cli_command_printf("Show what?\n"); return 0; } @@ -189,7 +218,7 @@ static int cmd_set_friendly_name(char **args, unsigned int n) const char *name; if (n < 1) { - cli_printf("To what?\n"); + cli_command_printf("To what?\n"); return 0; } @@ -214,6 +243,45 @@ static int cmd_set_friendly_name(char **args, unsigned int n) return ctl_link_set_friendly_name(l, name); } +/* + * cmd: set-managed + */ + +static int cmd_set_managed(char **args, unsigned int n) +{ + struct ctl_link *l = NULL; + const char *value; + bool managed = true; + + if (n < 1) { + cli_command_printf("To what?\n"); + return 0; + } + + if (n > 1) { + l = ctl_wifi_search_link(wifi, args[0]); + if (!l) { + cli_error("unknown link %s", args[0]); + return 0; + } + + value = args[1]; + } else { + value = args[0]; + } + + l = l ? : selected_link; + if (!l) { + cli_error("no link selected"); + return 0; + } + + if (!strcmp(value, "no")) { + managed = false; + } + return ctl_link_set_managed(l, managed); +} + /* * cmd: p2p-scan */ @@ -242,6 +310,11 @@ static int cmd_p2p_scan(char **args, unsigned int n) return 0; } + if (!l->managed) { + cli_printf("link %s not managed\n", l->label); + return 0; + } + return ctl_link_set_p2p_scanning(l, !stop); } @@ -263,7 +336,7 @@ static int cmd_connect(char **args, unsigned int n) const char *prov, *pin; if (n < 1) { - cli_printf("To whom?\n"); + cli_command_printf("To whom?\n"); return 0; } @@ -289,6 +362,11 @@ static int cmd_connect(char **args, unsigned int n) pin = ""; } + if (!p->l->managed) { + cli_printf("link %s not managed\n", p->l->label); + return 0; + } + return ctl_peer_connect(p, prov, pin); } @@ -301,7 +379,7 @@ static int cmd_disconnect(char **args, unsigned int n) struct ctl_peer *p; if (n < 1) { - cli_printf("From whom?\n"); + cli_command_printf("From whom?\n"); return 0; } @@ -311,6 +389,11 @@ static int cmd_disconnect(char **args, unsigned int n) return 0; } + if (!p->l->managed) { + cli_printf("link %s not managed\n", p->l->label); + return 0; + } + return ctl_peer_disconnect(p); } @@ -329,16 +412,17 @@ static int cmd_quit(char **args, unsigned int n) */ static const struct cli_cmd cli_cmds[] = { - { "list", NULL, CLI_M, CLI_LESS, 0, cmd_list, "List all objects" }, - { "select", "[link]", CLI_Y, CLI_LESS, 1, cmd_select, "Select default link" }, - { "show", "[link|peer]", CLI_M, CLI_LESS, 1, cmd_show, "Show detailed object information" }, - { "set-friendly-name", "[link] ", CLI_M, CLI_LESS, 2, cmd_set_friendly_name, "Set friendly name of an object" }, - { "p2p-scan", "[link] [stop]", CLI_Y, CLI_LESS, 2, cmd_p2p_scan, "Control neighborhood P2P scanning" }, - { "connect", " [provision] [pin]", CLI_M, CLI_LESS, 3, cmd_connect, "Connect to peer" }, - { "disconnect", "", CLI_M, CLI_EQUAL, 1, cmd_disconnect, "Disconnect from peer" }, - { "quit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, "Quit program" }, - { "exit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, NULL }, - { "help", NULL, CLI_M, CLI_MORE, 0, NULL, "Print help" }, + { "list", NULL, CLI_M, CLI_LESS, 0, cmd_list, "List all objects", {NULL}}, + { "select", "[link]", CLI_Y, CLI_LESS, 1, cmd_select, "Select default link", {links_generator, NULL} }, + { "show", "[link|peer]", CLI_M, CLI_LESS, 1, cmd_show, "Show detailed object information", {links_peers_generator, NULL} }, + { "set-friendly-name", "[link] ", CLI_M, CLI_LESS, 2, cmd_set_friendly_name, "Set friendly name of an object", {links_generator, yes_no_generator} }, + { "set-managed", "[link] ", CLI_M, CLI_LESS, 2, cmd_set_managed, "Manage or unmnage a link" }, + { "p2p-scan", "[link] [stop]", CLI_Y, CLI_LESS, 2, cmd_p2p_scan, "Control neighborhood P2P scanning", {links_generator, NULL} }, + { "connect", " [provision] [pin]", CLI_M, CLI_LESS, 3, cmd_connect, "Connect to peer", {peers_generator, NULL} }, + { "disconnect", "", CLI_M, CLI_EQUAL, 1, cmd_disconnect, "Disconnect from peer", {peers_generator, NULL} }, + { "quit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, "Quit program", {NULL} }, + { "exit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, NULL , {NULL}}, + { "help", NULL, CLI_M, CLI_MORE, 0, NULL, "Print help" , {NULL} }, { }, }; @@ -419,9 +503,13 @@ void cli_fn_help() printf("%s [OPTIONS...] {COMMAND} ...\n\n" "Send control command to or query the MiracleCast Wifi-Manager. If no arguments\n" "are given, an interactive command-line tool is provided.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --log-level Maximum level for log messages\n" + " -h --help Show this help\n" + " --help-commands Show available commands\n" + " --version Show package version\n" + " --log-level Maximum level for log messages\n" + " --log-time Prefix log-messages with timestamp\n" + " --log-date-time Prefix log-messages with date time\n" + " --log-journal-level Maximum level for journal log messages\n" "\n" "Commands:\n" , program_invocation_short_name); @@ -497,11 +585,19 @@ static int parse_argv(int argc, char *argv[]) enum { ARG_VERSION = 0x100, ARG_LOG_LEVEL, + ARG_LOG_TIME, + ARG_LOG_DATE_TIME, + ARG_JOURNAL_LEVEL, + ARG_HELP_COMMANDS, }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, + { "help", no_argument, NULL, 'h' }, + { "help-commands", no_argument, NULL, ARG_HELP_COMMANDS }, + { "version", no_argument, NULL, ARG_VERSION }, + { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, + { "log-time", no_argument, NULL, ARG_LOG_TIME }, + { "log-date-time", no_argument, NULL, ARG_LOG_DATE_TIME }, + { "log-journal-level", required_argument, NULL, ARG_JOURNAL_LEVEL }, {} }; int c; @@ -509,13 +605,25 @@ static int parse_argv(int argc, char *argv[]) while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { switch (c) { case 'h': - return cli_help(cli_cmds); + cli_fn_help(); + return 0; + case ARG_HELP_COMMANDS: + return cli_help(cli_cmds, 20); case ARG_VERSION: puts(PACKAGE_STRING); return 0; case ARG_LOG_LEVEL: cli_max_sev = log_parse_arg(optarg); break; + case ARG_LOG_TIME: + log_init_time(); + break; + case ARG_LOG_DATE_TIME: + log_date_time = true; + break; + case ARG_JOURNAL_LEVEL: + log_max_sev = log_parse_arg(optarg); + break; case '?': return -EINVAL; } @@ -530,6 +638,23 @@ int main(int argc, char **argv) setlocale(LC_ALL, ""); + GKeyFile* gkf = load_ini_file(); + + if (gkf) { + gchar* log_level; + log_level = g_key_file_get_string (gkf, "wifictl", "log-journal-level", NULL); + if (log_level) { + log_max_sev = log_parse_arg(log_level); + g_free(log_level); + } + log_level = g_key_file_get_string (gkf, "wifictl", "log-level", NULL); + if (log_level) { + cli_max_sev = log_parse_arg(log_level); + g_free(log_level); + } + g_key_file_free(gkf); + } + r = parse_argv(argc, argv); if (r < 0) return EXIT_FAILURE; diff --git a/src/dhcp/CMakeLists.txt b/src/dhcp/CMakeLists.txt index 7935d68..d7f8b7b 100644 --- a/src/dhcp/CMakeLists.txt +++ b/src/dhcp/CMakeLists.txt @@ -1,6 +1,3 @@ - -########### next target ############### - set(miracle-dhcp_SRCS dhcp.c gdhcp.h unaligned.h @@ -16,47 +13,15 @@ add_executable(miracle-dhcp ${miracle-dhcp_SRCS}) find_package(PkgConfig) pkg_check_modules (GLIB2 REQUIRED glib-2.0) pkg_check_modules (UDEV REQUIRED libudev) +target_link_libraries(miracle-dhcp miracle-shared) link_directories( ${UDEV_LIBRARY_DIRS}) include_directories( ${UDEV_INCLUDE_DIRS}) target_link_libraries(miracle-dhcp ${UDEV_LIBRARIES}) +target_link_libraries(miracle-dhcp m) link_directories( ${GLIB2_LIBRARY_DIRS}) include_directories( ${GLIB2_INCLUDE_DIRS}) target_link_libraries(miracle-dhcp ${GLIB2_LIBRARIES}) -target_link_libraries(miracle-dhcp miracle-shared) install(TARGETS miracle-dhcp DESTINATION bin) INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/shared) - - -########### install files ############### - - - - -#original Makefile.am contents follow: - -#include $(top_srcdir)/common.am -#bin_PROGRAMS = miracle-dhcp -# -#miracle_dhcp_SOURCES = \ -# dhcp.c \ -# gdhcp.h \ -# unaligned.h \ -# common.h \ -# common.c \ -# ipv4ll.h \ -# ipv4ll.c \ -# client.c \ -# server.c -#miracle_dhcp_CPPFLAGS = \ -# $(AM_CPPFLAGS) \ -# $(DEPS_CFLAGS) \ -# $(GDHCP_CFLAGS) -#miracle_dhcp_LDADD = \ -# ../shared/libmiracle-shared.la \ -# $(DEPS_LIBS) \ -# $(GDHCP_LIBS) -# -# -# diff --git a/src/dhcp/Makefile.am b/src/dhcp/Makefile.am index 2703f48..fb01fff 100644 --- a/src/dhcp/Makefile.am +++ b/src/dhcp/Makefile.am @@ -14,11 +14,11 @@ miracle_dhcp_SOURCES = \ miracle_dhcp_CPPFLAGS = \ $(AM_CPPFLAGS) \ $(DEPS_CFLAGS) \ - $(GDHCP_CFLAGS) + $(GLIB_CFLAGS) miracle_dhcp_LDADD = \ ../shared/libmiracle-shared.la \ $(DEPS_LIBS) \ - $(GDHCP_LIBS) + $(GLIB_LIBS) diff --git a/src/dhcp/client.c b/src/dhcp/client.c index 3d04cd1..7f818ba 100644 --- a/src/dhcp/client.c +++ b/src/dhcp/client.c @@ -2785,7 +2785,7 @@ int g_dhcp_client_start(GDHCPClient *dhcp_client, const char *last_address) addr = ntohl(inet_addr(last_address)); if (addr == 0xFFFFFFFF) { addr = 0; - } else { + } else if (dhcp_client->last_address != last_address) { // Avoiding use-after-free g_free(dhcp_client->last_address); dhcp_client->last_address = g_strdup(last_address); } diff --git a/src/dhcp/dhcp.c b/src/dhcp/dhcp.c index 5457764..3a43af5 100644 --- a/src/dhcp/dhcp.c +++ b/src/dhcp/dhcp.c @@ -35,7 +35,7 @@ * of Wifi-P2P support in common network managers. Once they gain proper * support, we will drop this helper! * - * The "ip" invokation is quite fragile and ugly. However, performing these + * The "ip" invocation is quite fragile and ugly. However, performing these * steps directly involves netlink operations and more. As no-one came up with * patches, yet, we keep the hack. To anyone trying to fix it: Please, spend * this time hacking on NetworkManager, connman and friends instead! If they @@ -67,8 +67,11 @@ #include "shl_log.h" #include "config.h" +#define XSTR(x) STR(x) +#define STR(x) #x + static const char *arg_netdev; -static const char *arg_ip_binary = "/bin/ip"; +static const char *arg_ip_binary = XSTR(IP_BINARY); static bool arg_server; static char arg_local[INET_ADDRSTRLEN]; static char arg_gateway[INET_ADDRSTRLEN]; @@ -747,9 +750,10 @@ static int help(void) " --version Show package version\n" " --log-level Maximum level for log messages\n" " --log-time Prefix log-messages with timestamp\n" + " --log-date-time Prefix log-messages with date time\n" "\n" " --netdev Network device to run on\n" - " --ip-binary Path to 'ip' binary [default: /bin/ip]\n" + " --ip-binary Path to 'ip' binary [default: "XSTR(IP_BINARY)"]\n" " --comm-fd Comm-socket FD passed through execve()\n" "\n" "Server Options:\n" @@ -772,6 +776,7 @@ static int parse_argv(int argc, char *argv[]) ARG_VERSION = 0x100, ARG_LOG_LEVEL, ARG_LOG_TIME, + ARG_LOG_DATE_TIME, ARG_NETDEV, ARG_IP_BINARY, @@ -787,10 +792,11 @@ static int parse_argv(int argc, char *argv[]) ARG_TO, }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, - { "log-time", no_argument, NULL, ARG_LOG_TIME }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, + { "log-time", no_argument, NULL, ARG_LOG_TIME }, + { "log-date-time", no_argument, NULL, ARG_LOG_DATE_TIME }, { "netdev", required_argument, NULL, ARG_NETDEV }, { "ip-binary", required_argument, NULL, ARG_IP_BINARY }, @@ -823,6 +829,9 @@ static int parse_argv(int argc, char *argv[]) case ARG_LOG_TIME: log_init_time(); break; + case ARG_LOG_DATE_TIME: + log_date_time = true; + break; case ARG_NETDEV: arg_netdev = optarg; break; diff --git a/src/dhcp/meson.build b/src/dhcp/meson.build new file mode 100644 index 0000000..9a52302 --- /dev/null +++ b/src/dhcp/meson.build @@ -0,0 +1,11 @@ +miracle_dhcp_srcs = ['dhcp.c', + 'common.c', + 'ipv4ll.c', + 'client.c', + 'server.c' +] +executable('miracle-dhcp', miracle_dhcp_srcs, + install: true, + include_directories: include_directories('../..'), + dependencies: [glib2, udev, libmiracle_shared_dep, m] +) diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..bf7311b --- /dev/null +++ b/src/meson.build @@ -0,0 +1,11 @@ +subdir('shared') +subdir('wifi') +subdir('dhcp') +subdir('ctl') +subdir('uibc') + +executable('miracled', 'miracled.c', + dependencies: [libmiracle_shared_dep, m], + include_directories: include_directories('..'), + install: true +) diff --git a/src/miracled.c b/src/miracled.c index 2fd435f..c193e95 100644 --- a/src/miracled.c +++ b/src/miracled.c @@ -66,6 +66,7 @@ static int help(void) " --version Show package version\n" " --log-level Maximum level for log messages\n" " --log-time Prefix log-messages with timestamp\n" + " --log-date-time Prefix log-messages with date time\n" , program_invocation_short_name); /* * 80-char barrier: @@ -81,12 +82,14 @@ static int parse_argv(int argc, char *argv[]) ARG_VERSION = 0x100, ARG_LOG_LEVEL, ARG_LOG_TIME, + ARG_LOG_DATE_TIME, }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, - { "log-time", no_argument, NULL, ARG_LOG_TIME }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, + { "log-time", no_argument, NULL, ARG_LOG_TIME }, + { "log-date-time", no_argument, NULL, ARG_LOG_DATE_TIME }, {} }; int c; @@ -104,6 +107,9 @@ static int parse_argv(int argc, char *argv[]) case ARG_LOG_TIME: log_init_time(); break; + case ARG_LOG_DATE_TIME: + log_date_time = true; + break; case '?': return -EINVAL; } diff --git a/src/shared/CMakeLists.txt b/src/shared/CMakeLists.txt index f757fba..2dea096 100644 --- a/src/shared/CMakeLists.txt +++ b/src/shared/CMakeLists.txt @@ -1,7 +1,5 @@ set(CMAKE_C_FLAGS "-std=gnu11") -find_package(PkgConfig) -pkg_check_modules (SYSTEMD REQUIRED systemd>=213) set(miracle-shared_SOURCES rtsp.h rtsp.c shl_dlist.h @@ -18,36 +16,4 @@ set(miracle-shared_SOURCES rtsp.h wpas.h wpas.c) add_library(miracle-shared STATIC ${miracle-shared_SOURCES}) -target_link_libraries (miracle-shared systemd) - - -########### install files ############### - - - - -#original Makefile.am contents follow: - -#include $(top_srcdir)/common.am -#noinst_LTLIBRARIES = libmiracle-shared.la -# -#libmiracle_shared_la_SOURCES = \ -# rtsp.h \ -# rtsp.c \ -# shl_dlist.h \ -# shl_htable.h \ -# shl_htable.c \ -# shl_log.h \ -# shl_log.c \ -# shl_macro.h \ -# shl_ring.h \ -# shl_ring.c \ -# shl_util.h \ -# shl_util.c \ -# util.h \ -# wpas.h \ -# wpas.c -#libmiracle_shared_la_LIBADD = -lsystemd -# -# -# +target_link_libraries (miracle-shared ${SESSION_LIBRARIES}) diff --git a/src/shared/Makefile.am b/src/shared/Makefile.am index e79c8ff..766b9dc 100644 --- a/src/shared/Makefile.am +++ b/src/shared/Makefile.am @@ -17,7 +17,8 @@ libmiracle_shared_la_SOURCES = \ util.h \ wpas.h \ wpas.c -libmiracle_shared_la_LIBADD = -lsystemd - - +libmiracle_shared_la_LIBADD = \ + $(DEPS_LIBS) \ + $(GLIB_LIBS) \ + $(LIBM) diff --git a/src/shared/meson.build b/src/shared/meson.build new file mode 100644 index 0000000..4af9e34 --- /dev/null +++ b/src/shared/meson.build @@ -0,0 +1,22 @@ +libmiracle_shared = static_library('miracle-shared', + 'rtsp.h', + 'rtsp.c', + 'shl_dlist.h', + 'shl_htable.h', + 'shl_htable.c', + 'shl_log.h', + 'shl_log.c', + 'shl_macro.h', + 'shl_ring.h', + 'shl_ring.c', + 'shl_util.h', + 'shl_util.c', + 'util.h', + 'wpas.h', + 'wpas.c', + dependencies: [libsystemd] +) +libmiracle_shared_dep = declare_dependency( + include_directories: include_directories('.'), + link_with: libmiracle_shared +) diff --git a/src/shared/rtsp.c b/src/shared/rtsp.c index 9ecf5a5..60895cd 100644 --- a/src/shared/rtsp.c +++ b/src/shared/rtsp.c @@ -2169,7 +2169,6 @@ static int parser_submit_data(struct rtsp *bus, uint8_t *p) p, dec->data_size); if (r < 0) { - free(p); return r; } @@ -2717,7 +2716,7 @@ error: return r; } -static void rtsp_unlink_waiting(struct rtsp_message *m) +static bool rtsp_unlink_waiting(struct rtsp_message *m) { if (m->is_waiting) { sd_event_source_unref(m->timer_source); @@ -2726,7 +2725,9 @@ static void rtsp_unlink_waiting(struct rtsp_message *m) m->is_waiting = false; --m->bus->waiting_cnt; rtsp_message_unref(m); + return true; } + return false; } static void rtsp_link_outgoing(struct rtsp_message *m) @@ -2737,7 +2738,7 @@ static void rtsp_link_outgoing(struct rtsp_message *m) rtsp_message_ref(m); } -static void rtsp_unlink_outgoing(struct rtsp_message *m) +static bool rtsp_unlink_outgoing(struct rtsp_message *m) { if (m->is_outgoing) { shl_dlist_unlink(&m->list); @@ -2745,7 +2746,9 @@ static void rtsp_unlink_outgoing(struct rtsp_message *m) m->is_sending = false; --m->bus->outgoing_cnt; rtsp_message_unref(m); + return true; } + return false; } static int rtsp_incoming_message(struct rtsp_message *m) @@ -2823,10 +2826,11 @@ static int rtsp_write_message(struct rtsp_message *m) if (m->sent >= m->raw_size) { /* no need to wait for answer if no-body listens */ if (!m->cb_fn) - rtsp_unlink_waiting(m); - + if (rtsp_unlink_waiting(m)) + m = NULL; /* might destroy the message */ - rtsp_unlink_outgoing(m); + if (m) + rtsp_unlink_outgoing(m); } return 0; @@ -3246,10 +3250,12 @@ static void rtsp_drop_message(struct rtsp_message *m) /* never interrupt messages while being partly sent */ if (!m->is_sending) - rtsp_unlink_outgoing(m); + if (rtsp_unlink_outgoing(m)) + m = NULL; /* remove from waiting list so neither timeouts nor completions fire */ - rtsp_unlink_waiting(m); + if (m) + rtsp_unlink_waiting(m); } void rtsp_call_async_cancel(struct rtsp *bus, uint64_t cookie) diff --git a/src/shared/shl_log.c b/src/shared/shl_log.c index dc7ef55..feb49b4 100644 --- a/src/shared/shl_log.c +++ b/src/shared/shl_log.c @@ -12,6 +12,9 @@ #include #include #include +#include +#include +#include #include "shl_log.h" /* @@ -36,7 +39,7 @@ static inline void log_unlock() static struct timeval log__ftime; -static bool log__have_time(void) +bool log__have_time(void) { return !(log__ftime.tv_sec == 0 && log__ftime.tv_usec == 0); } @@ -47,7 +50,7 @@ void log_init_time(void) gettimeofday(&log__ftime, NULL); } -static void log__time(long long *sec, long long *usec) +void log__time(long long *sec, long long *usec) { struct timeval t; @@ -83,6 +86,9 @@ const char *LOG_SUBSYSTEM = NULL; */ unsigned int log_max_sev = LOG_NOTICE; +bool log_date_time = false; + +char *gst_debug = NULL; /* * Forward declaration so we can use the locked-versions in other functions @@ -135,25 +141,51 @@ static void log__submit(const char *file, const char *prefix = NULL; FILE *out; long long sec, usec; + time_t now; + struct tm *timeinfo; + struct timeval tv; + char buffertmp[80]; + char buffer[120]; + int millisec; out = stderr; - log__time(&sec, &usec); if (sev < LOG_SEV_NUM && sev > log_max_sev) return; + log__time(&sec, &usec); + + if (log_date_time) { + gettimeofday(&tv, NULL); + millisec = lrint(tv.tv_usec/1000.0); + if (millisec>=1000) { + millisec -=1000; + tv.tv_sec++; + } + + time(&now); + timeinfo = localtime(&now); + + strftime(buffertmp, 80, "%x - %X.%03d", timeinfo); + sprintf(buffer, "%s.%03d", buffertmp, millisec); + } + if (sev < LOG_SEV_NUM) prefix = log__sev2str[sev]; if (prefix) { if (subs) { - if (log__have_time()) + if (log_date_time) + fprintf(out, "[%s] %s: %s: ", buffer, prefix, subs); + else if (log__have_time()) fprintf(out, "[%.4lld.%.6lld] %s: %s: ", sec, usec, prefix, subs); else fprintf(out, "%s: %s: ", prefix, subs); } else { - if (log__have_time()) + if (log_date_time) + fprintf(out, "[%s] %s: ", buffer, prefix); + else if (log__have_time()) fprintf(out, "[%.4lld.%.6lld] %s: ", sec, usec, prefix); else @@ -161,13 +193,18 @@ static void log__submit(const char *file, } } else { if (subs) { - if (log__have_time()) + if (log_date_time) + fprintf(out, "[%s] %s: ", + buffer, subs); + else if (log__have_time()) fprintf(out, "[%.4lld.%.6lld] %s: ", sec, usec, subs); else fprintf(out, "%s: ", subs); } else { - if (log__have_time()) + if (log_date_time) + fprintf(out, "[%s] ", buffer); + else if (log__have_time()) fprintf(out, "[%.4lld.%.6lld] ", sec, usec); } } @@ -239,9 +276,9 @@ void log_llog(void *data, log_submit(file, line, func, subs, sev, format, args); } -int log_parse_arg(char *optarg) +unsigned int log_parse_arg(char *optarg) { - int log_max_sev; + unsigned int log_max_sev; if(!strcasecmp(optarg, "fatal")) { log_max_sev = LOG_FATAL; } else if(!strcasecmp(optarg, "alert")) { @@ -261,7 +298,23 @@ int log_parse_arg(char *optarg) } else if(!strcasecmp(optarg, "trace")) { log_max_sev = LOG_TRACE; } else { - log_max_sev = atoi(optarg); + errno = 0; + char *temp; + long val = strtoul(optarg, &temp, 0); + + if (temp == optarg || *temp != '\0' + || ((val == LONG_MIN || val == LONG_MAX) && errno == ERANGE)) { + log_error("Could not convert '%s' to long and leftover string is: '%s'\n", optarg, temp); + } + if (val > INT_MAX) { + errno = ERANGE; + return INT_MAX; + } + if (val < INT_MIN) { + errno = ERANGE; + return INT_MIN; + } + log_max_sev = (unsigned int) val; } return log_max_sev; } diff --git a/src/shared/shl_log.h b/src/shared/shl_log.h index e645948..61225f2 100644 --- a/src/shared/shl_log.h +++ b/src/shared/shl_log.h @@ -61,6 +61,17 @@ enum log_severity { extern unsigned int log_max_sev; +/* + * Defines if log time should use local time + * Default: false + */ +extern bool log_date_time; + +/* + * Defines the debug configuration for gstreamer + */ +extern char* gst_debug; + /* * Timestamping * Call this to initialize timestamps and cause all log-messages to be prefixed @@ -69,6 +80,10 @@ extern unsigned int log_max_sev; void log_init_time(void); +void log__time(long long *sec, long long *usec); + +bool log__have_time(void); + /* * Log-Functions * These functions pass a log-message to the log-subsystem. Handy helpers are @@ -116,7 +131,7 @@ void log_llog(void *data, const char *format, va_list args); -int log_parse_arg(char *optarg); +unsigned int log_parse_arg(char *optarg); static inline __attribute__((format(printf, 2, 3))) void log_dummyf(unsigned int sev, const char *format, ...) @@ -226,4 +241,9 @@ extern const char *LOG_SUBSYSTEM; #define log_vERR(_r) \ ((void)log_ERR(_r)) +#define log_EUNMANAGED() \ + (log_error("interface unmanaged"), -EFAULT) +#define log_vEUNMANAGED() \ + ((void)log_EUNMANAGED()) + #endif /* SHL_LOG_H */ diff --git a/src/shared/shl_util.c b/src/shared/shl_util.c index 1d3f662..0dc89a2 100644 --- a/src/shared/shl_util.c +++ b/src/shared/shl_util.c @@ -31,7 +31,7 @@ * variants to allow these. * Base-prefix parsing is only done if base=0 is requested. Otherwise, * base-prefixes are forbidden. - * The input string must be ASCII compatbile (which includes UTF8). + * The input string must be ASCII compatible (which includes UTF8). * * We also always check for overflows and return errors (but continue parsing!) * so callers can catch it correctly. @@ -826,8 +826,9 @@ int shl__mkdir_parents(const char *prefix, const char *path, mode_t mode) if (!e || e == path) return 0; - p = strndupa(path, e - path); + p = strndup(path, e - path); r = shl__is_dir(p); + free((char*)p); if (r > 0) return 0; if (r == 0) diff --git a/src/shared/util.h b/src/shared/util.h index 5b85742..0e0ba3c 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -31,6 +31,26 @@ #include #include #include "shl_macro.h" +#include + +static inline GKeyFile* load_ini_file() { + GKeyFile* gkf = NULL; + gchar* config_file; + + gkf = g_key_file_new(); + + config_file = g_build_filename(g_get_home_dir(), ".config", "miraclecastrc", NULL); + if (!g_key_file_load_from_file(gkf, config_file, G_KEY_FILE_NONE, NULL)) { + g_free(config_file); + config_file = g_build_filename(g_get_home_dir(), ".miraclecast", NULL); + if (!g_key_file_load_from_file(gkf, config_file, G_KEY_FILE_NONE, NULL)) { + g_key_file_free(gkf); + gkf = NULL; + } + } + g_free(config_file); + return gkf; +} static inline void cleanup_sd_bus_message(sd_bus_message **ptr) { diff --git a/src/shared/wpas.c b/src/shared/wpas.c index 4b2adea..6882431 100644 --- a/src/shared/wpas.c +++ b/src/shared/wpas.c @@ -742,6 +742,7 @@ static int wpas__parse_message(struct wpas *w, const char *ifname = NULL; unsigned int level; char *pos; + char *orig_raw = raw; int r, num; bool is_event = false; @@ -751,7 +752,7 @@ static int wpas__parse_message(struct wpas *w, ifname = pos; pos = strchrnul(pos, ' '); if (*pos) - *pos++ = 0; + pos++; len -= pos - raw; raw = pos; @@ -811,15 +812,12 @@ static int wpas__parse_message(struct wpas *w, m->sealed = true; m->rawlen = len; - m->raw = malloc(len + 1); + m->raw = strdup(orig_raw); if (!m->raw) return -ENOMEM; - /* copy with 0-terminator */ - memcpy(m->raw, raw, len + 1); - if (ifname) { - m->ifname = strdup(ifname); + m->ifname = strndup(ifname, strchrnul(ifname, ' ') - ifname); if (!m->ifname) return -ENOMEM; } @@ -1056,7 +1054,7 @@ static int wpas__bind_server_socket(int fd, const char *ctrl_path, char *name) } } - strncpy(name, src.sun_path, UNIX_PATH_MAX - 1); + strncpy(name, src.sun_path, UNIX_PATH_MAX); name[UNIX_PATH_MAX - 1] = 0; return 0; diff --git a/src/uibc/CMakeLists.txt b/src/uibc/CMakeLists.txt new file mode 100644 index 0000000..705b934 --- /dev/null +++ b/src/uibc/CMakeLists.txt @@ -0,0 +1,10 @@ +set(miracle-uibcctl_SRCS miracle-uibcctl.h + miracle-uibcctl.c) + +add_executable(miracle-uibcctl ${miracle-uibcctl_SRCS}) +target_link_libraries(miracle-uibcctl miracle-shared) +target_link_libraries(miracle-uibcctl m) + +install(TARGETS miracle-uibcctl DESTINATION bin) + +INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/shared) diff --git a/src/uibc/Makefile.am b/src/uibc/Makefile.am new file mode 100644 index 0000000..7829d8c --- /dev/null +++ b/src/uibc/Makefile.am @@ -0,0 +1,16 @@ +include $(top_srcdir)/common.am + +bin_PROGRAMS = miracle-uibcctl + +miracle_uibcctl_SOURCES = \ + miracle-uibcctl.h \ + miracle-uibcctl.c + +miracle_uibctcl_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + $(DEPS_CFLAGS) + +miracle_uibcctl_LDADD = \ + ../shared/libmiracle-shared.la \ + $(LIBM) + diff --git a/src/uibc/meson.build b/src/uibc/meson.build new file mode 100644 index 0000000..8620ba0 --- /dev/null +++ b/src/uibc/meson.build @@ -0,0 +1,4 @@ +executable('miracle-uibcctl', 'miracle-uibcctl.h', 'miracle-uibcctl.c', + install: true, + dependencies: [m, libmiracle_shared_dep] +) diff --git a/src/uibc/miracle-uibcctl.c b/src/uibc/miracle-uibcctl.c new file mode 100644 index 0000000..27fc6e6 --- /dev/null +++ b/src/uibc/miracle-uibcctl.c @@ -0,0 +1,624 @@ + +#include "miracle-uibcctl.h" + +int main(int argc, char *argv[]) { + //TODO: Add miracle TUI interface + //TODO: Add parsearg + //--daemon (read stdin) + + int portno; + struct hostent *server; + int sockfd; + struct sockaddr_in serv_addr; + char buffer[256]; + int r; + + log_max_sev = LOG_INFO; + + if (argc < 3) { + fprintf(stderr, "Usage:\n"); + fprintf(stderr, " %s \n", argv[0]); + return EXIT_FAILURE; + } + + server = gethostbyname(argv[1]); + portno = atoi(argv[2]); + + log_info("server %s port %d", argv[1], portno); + + if (server == NULL) { + fprintf(stderr,"ERROR, no such host\n"); + exit(0); + } + + sockfd = socket(AF_INET, SOCK_STREAM, 0); + + if (sockfd < 0) { + perror("ERROR opening socket"); + return EXIT_FAILURE; + } + + bzero((char *) &serv_addr, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + bcopy(server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length); + serv_addr.sin_port = htons(portno); + + if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) { + perror("ERROR connecting"); + return EXIT_FAILURE; + } + + bool daemon = true; + while(1) { + if (!daemon) { + printf("enter event ,,,,: "); + } + bzero(buffer, 256); + fgets(buffer, 255, stdin); + if (strlen(buffer) == 0) { + break; + } + if (!daemon) { + printf("input: %s", buffer); + } + char type = buffer[0]; + UibcMessage uibcmessage; + if (type == '0' || type == '1') { + uibcmessage = buildUibcMessage(GENERIC_TOUCH_DOWN, buffer, 1, 1); + } else if (type == '3' || type == '4') { + uibcmessage = buildUibcMessage(GENERIC_KEY_DOWN, buffer, 1, 1); + } else { + if (!daemon) { + printf("unknow event type: %s", buffer); + } + continue; + } + + r = sendUibcMessage(&uibcmessage, sockfd); + if (r) { + return r; + } + } + + close(sockfd); + return EXIT_SUCCESS; +} + +const char *int2binary(int x, int padding) +{ + char *b; + int min_padding = x > 0 ? floor(log2(x)) : 0; + if (padding < min_padding) { + padding = min_padding; + } + + b = (char *)malloc (sizeof (char *) * padding + 1); + strcpy(b, ""); + + int z; + for (z = pow(2,padding); z > 0; z >>= 1) { + strcat(b, ((x & z) == z) ? "1" : "0"); + } + + return b; +} + +int sendUibcMessage(UibcMessage* uibcmessage, int sockfd) { + ssize_t n; + + printf("sending %zu bytes\n", uibcmessage->m_PacketDataLen); + + n = write(sockfd, uibcmessage->m_PacketData , uibcmessage->m_PacketDataLen); + + if (n < 0) { + perror("ERROR writing to socket"); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +UibcMessage buildUibcMessage(MessageType type, + const char* inEventDesc, + double widthRatio, + double heightRatio) { + UibcMessage uibcmessage; + uibcmessage.m_PacketData = NULL; + uibcmessage.m_PacketDataLen = 0; + uibcmessage.m_DataValid = false; + + switch (type) { + case GENERIC_TOUCH_DOWN: + case GENERIC_TOUCH_UP: + case GENERIC_TOUCH_MOVE: + getUIBCGenericTouchPacket(inEventDesc, + &uibcmessage, + widthRatio, + heightRatio); + break; + + case GENERIC_KEY_DOWN: + case GENERIC_KEY_UP: + getUIBCGenericKeyPacket(inEventDesc, &uibcmessage); + break; + + case GENERIC_ZOOM: + getUIBCGenericZoomPacket(inEventDesc, &uibcmessage); + break; + + case GENERIC_VERTICAL_SCROLL: + case GENERIC_HORIZONTAL_SCROLL: + getUIBCGenericScalePacket(inEventDesc, &uibcmessage); + break; + + case GENERIC_ROTATE: + getUIBCGenericRotatePacket(inEventDesc, &uibcmessage); + break; + }; + return uibcmessage; +} + + +// format: "typeId, number of pointers, pointer Id1, X coordnate, Y coordinate, pointer Id2, X coordnate, Y coordnate,..." +void getUIBCGenericTouchPacket(const char *inEventDesc, + UibcMessage* uibcmessage, + double widthRatio, + double heightRatio) { + log_info("getUIBCGenericTouchPacket (%s)", inEventDesc); + char* outData; + int32_t typeId = 0; + int32_t numberOfPointers = 0; + size_t uibcBodyLen = 0; + int32_t genericPacketLen = 0; + int32_t temp; + size_t size; + + char** splitedStr = str_split((char*)inEventDesc, ",", &size); + + if ((int)size - 5 < 0 || ((int)size - 5) % 3 != 0) { + log_error("getUIBCGenericTouchPacket (%s)", "bad input event"); + return; + } + int offset_split = 0; + typeId = atoi(*(splitedStr + offset_split++)); + numberOfPointers = atoi(*(splitedStr + offset_split++)); + + genericPacketLen = numberOfPointers * 5 + 1; + uibcBodyLen = genericPacketLen + 7; // Generic header length = 7 + //Padding to even number + uibcBodyLen = (uibcBodyLen % 2 == 0) ? uibcBodyLen : (uibcBodyLen + 1); + + outData = (char*)malloc(uibcBodyLen); + // UIBC header Octets + int offset_header = 0; + //Version (3 bits),T (1 bit),Reserved(8 bits),Input Category (4 bits) + outData[offset_header++] = 0x00; // 000 0 0000 + outData[offset_header++] = 0x00; // 0000 0000 + + //Length(16 bits) + outData[offset_header++] = (uibcBodyLen >> 8) & 0xFF; // first 8 bytes + outData[offset_header++] = uibcBodyLen & 0xFF; // last 8 bytes + + //Generic Input Body Format + outData[offset_header++] = typeId & 0xFF; // Tyoe ID, 1 octet + + // Length, 2 octets + outData[offset_header++] = (genericPacketLen >> 8) & 0xFF; // first 8 bytes + outData[offset_header++] = genericPacketLen & 0xFF; //last 8 bytes + + // Number of pointers, 1 octet + outData[offset_header++] = numberOfPointers & 0xFF; + + int offset = 0; + log_info("getUIBCGenericTouchPacket numberOfPointers=[%d]\n", numberOfPointers); + for (int i = 0; i < numberOfPointers; i++) { + int splitoffset = offset_split + (i * 3); + temp = atoi(*(splitedStr + splitoffset++)); + offset = offset_header + ( i * 5); + outData[offset++] = temp & 0xFF; + log_info("getUIBCGenericTouchPacket PointerId=[%d]\n", temp); + + temp = atoi(*(splitedStr + splitoffset++)); + temp = (int32_t)((double)temp / widthRatio); + log_info("getUIBCGenericTouchPacket X-coordinate=[%d]\n", temp); + outData[offset++] = (temp >> 8) & 0xFF; + outData[offset++] = temp & 0xFF; + + temp = atoi(*(splitedStr + splitoffset++)); + temp = (int32_t)((double)temp / heightRatio); + log_info("getUIBCGenericTouchPacket Y-coordinate=[%d]\n", temp); + outData[offset++] = (temp >> 8) & 0xFF; + outData[offset++] = temp & 0xFF; + } + + while (offset <= uibcBodyLen) { + outData[offset++] = 0x00; + } + + for (int i = 0; i < size; i++) { + free(*(splitedStr + i)); + } + free(splitedStr); + + binarydump(outData, uibcBodyLen); + uibcmessage->m_DataValid = true; + uibcmessage->m_PacketData = outData; + uibcmessage->m_PacketDataLen = uibcBodyLen; +} + +void hexdump(void *_data, size_t len) +{ + unsigned char *data = _data; + size_t count; + + int line = 15; + for (count = 0; count < len; count++) { + if ((count & line) == 0) { + fprintf(stderr,"%04zu: ", count); + } + fprintf(stderr,"%02x ", *data); + data++; + if ((count & line) == line) { + fprintf(stderr,"\n"); + } + } + if ((count & line) != 0) { + fprintf(stderr,"\n"); + } +} + +void binarydump(void *_data, size_t len) +{ + unsigned char *data = _data; + size_t count; + + int line = 7; + for (count = 0; count < len; count++) { + if ((count & line) == 0) { + fprintf(stderr,"%04zu: ", count); + } + fprintf(stderr,"%s ", int2binary(*data, 8)); + data++; + if ((count & line) == line) { + fprintf(stderr,"\n"); + } + } + if ((count & line) != 0) { + fprintf(stderr,"\n"); + } +} +// format: "typeId, Key code 1(0x00), Key code 2(0x00)" +void getUIBCGenericKeyPacket(const char *inEventDesc, + UibcMessage* uibcmessage) { + log_info("getUIBCGenericKeyPacket (%s)", inEventDesc); + char* outData = uibcmessage->m_PacketData; + int32_t typeId = 0; + int32_t uibcBodyLen = 0; + int32_t genericPacketLen = 0; + int32_t temp = 0; + size_t size; + char** splitedStr = str_split((char*)inEventDesc, ",", &size); + + if (size > 0) { + + if (((int)size) % 3 != 0) { + log_error("getUIBCGenericKeyPacket (%s)", "bad input event"); + return; + } + int i; + for (i = 0; i < size; i++) { + log_info("getUIBCGenericKeyPacket splitedStr tokens=[%s]\n", *(splitedStr + i)); + + switch (i) { + case 0: { + typeId = atoi(*(splitedStr + i)); + log_info("getUIBCGenericKeyPacket typeId=[%d]\n", typeId); + genericPacketLen = 5; + uibcBodyLen = genericPacketLen + 7; // Generic header length = 7 + outData = (char*)malloc(uibcBodyLen + 1); + // UIBC header + outData[0] = 0x00; //Version (3 bits),T (1 bit),Reserved(8 bits),Input Category (4 bits) + outData[1] = 0x00; //Version (3 bits),T (1 bit),Reserved(8 bits),Input Category (4 bits) + outData[2] = (uibcBodyLen >> 8) & 0xFF; //Length(16 bits) + outData[3] = uibcBodyLen & 0xFF; //Length(16 bits) + //Generic Input Body Format + outData[4] = typeId & 0xFF; // Tyoe ID, 1 octet + outData[5] = (genericPacketLen >> 8) & 0xFF; // Length, 2 octets + outData[6] = genericPacketLen & 0xFF; // Length, 2 octets + outData[7] = 0x00; // reserved + break; + } + case 1: { + sscanf(*(splitedStr + i), " 0x%04X", &temp); + if (temp == 0) { + outData[8] = 0x00; + outData[9] = 0x00; + } + log_info("getUIBCGenericKeyPacket key code 1=[%d]\n", temp); + outData[8] = (temp >> 8) & 0xFF; + outData[9] = temp & 0xFF; + + break; + } + case 2: { + sscanf(*(splitedStr + i), " 0x%04X", &temp); + if (temp == 0) { + outData[10] = 0x00; + outData[11] = 0x00; + } + log_info("getUIBCGenericKeyPacket key code 2=[%d]\n", temp); + outData[10] = (temp >> 8) & 0xFF; + outData[11] = temp & 0xFF; + + break; + } + default: { + } + break; + } + free(*(splitedStr + i)); + } + } + free(splitedStr); + + binarydump(outData, uibcBodyLen); + uibcmessage->m_DataValid = true; + uibcmessage->m_PacketData = outData; + uibcmessage->m_PacketDataLen = uibcBodyLen; +} + +// format: "typeId, X coordnate, Y coordnate, integer part, fraction part" +void getUIBCGenericZoomPacket(const char *inEventDesc, UibcMessage* uibcmessage) { + log_info("getUIBCGenericZoomPacket (%s)", inEventDesc); + char* outData = uibcmessage->m_PacketData; + int32_t typeId; + int32_t uibcBodyLen, genericPacketLen; + int32_t xCoord, yCoord, integerPart, FractionPart; + size_t size; + + char** splitedStr = str_split((char*)inEventDesc, ",", &size); + + if (splitedStr) { + int i; + for (i = 0; * (splitedStr + i); i++) { + //log_info("getUIBCGenericZoomPacket splitedStr tokens=[%s]\n", *(splitedStr + i)); + + switch (i) { + case 0: { + typeId = atoi(*(splitedStr + i)); + //log_info("getUIBCGenericZoomPacket typeId=[%d]\n", typeId); + + genericPacketLen = 6; + uibcBodyLen = genericPacketLen + 7; // Generic herder leh = 7 + outData = (char*)malloc(uibcBodyLen + 1); + // UIBC header + outData[0] = 0x00; //Version (3 bits),T (1 bit),Reserved(8 bits),Input Category (4 bits) + outData[1] = 0x00; //Version (3 bits),T (1 bit),Reserved(8 bits),Input Category (4 bits) + outData[2] = (uibcBodyLen >> 8) & 0xFF; //Length(16 bits) + outData[3] = uibcBodyLen & 0xFF; //Length(16 bits) + //Generic Input Body Format + outData[4] = typeId & 0xFF; // Tyoe ID, 1 octet + outData[5] = (genericPacketLen >> 8) & 0xFF; // Length, 2 octets + outData[6] = genericPacketLen & 0xFF; // Length, 2 octets + break; + } + + case 1: { + xCoord = atoi(*(splitedStr + i)); + outData[7] = (xCoord >> 8) & 0xFF; + outData[8] = xCoord & 0xFF; + log_info("getUIBCGenericZoomPacket xCoord=[%d]\n", xCoord); + break; + } + case 2: { + yCoord = atoi(*(splitedStr + i)); + log_info("getUIBCGenericZoomPacket yCoord=[%d]\n", yCoord); + break; + } + case 3: { + integerPart = atoi(*(splitedStr + i)); + outData[11] = integerPart & 0xFF; + //log_info("getUIBCGenericZoomPacket integerPart=[%d]\n", integerPart); + break; + } + case 4: { + FractionPart = atoi(*(splitedStr + i)); + outData[12] = FractionPart & 0xFF; + //log_info("getUIBCGenericZoomPacket FractionPart=[%d]\n", FractionPart); + + break; + } + default: { + break; + } + } + + free(*(splitedStr + i)); + } + free(splitedStr); + } + //hexdump(outData, uibcBodyLen); + uibcmessage->m_DataValid = true; + uibcmessage->m_PacketDataLen = uibcBodyLen; +} + +// format: "typeId, unit, direction, amount to scroll" +void getUIBCGenericScalePacket(const char *inEventDesc, UibcMessage* uibcmessage) { + log_info("getUIBCGenericScalePacket (%s)", inEventDesc); + char* outData = uibcmessage->m_PacketData; + int32_t typeId; + int32_t uibcBodyLen, genericPacketLen; + int32_t temp; + size_t size; + char** splitedStr = str_split((char*)inEventDesc, ",", &size); + + if (splitedStr) { + int i; + for (i = 0; * (splitedStr + i); i++) { + //log_info("getUIBCGenericScalePacket splitedStr tokens=[%s]\n", *(splitedStr + i)); + + switch (i) { + case 0: { + typeId = atoi(*(splitedStr + i)); + //log_info("getUIBCGenericScalePacket typeId=[%d]\n", typeId); + genericPacketLen = 2; + uibcBodyLen = genericPacketLen + 7; // Generic herder leh = 7 + outData = (char*)malloc(uibcBodyLen + 1); + // UIBC header + outData[0] = 0x00; //Version (3 bits),T (1 bit),Reserved(8 bits),Input Category (4 bits) + outData[1] = 0x00; //Version (3 bits),T (1 bit),Reserved(8 bits),Input Category (4 bits) + outData[2] = (uibcBodyLen >> 8) & 0xFF; //Length(16 bits) + outData[3] = uibcBodyLen & 0xFF; //Length(16 bits) + //Generic Input Body Format + outData[4] = typeId & 0xFF; // Tyoe ID, 1 octet + outData[5] = (genericPacketLen >> 8) & 0xFF; // Length, 2 octets + outData[6] = genericPacketLen & 0xFF; // Length, 2 octets + outData[7] = 0x00; // Clear the byte + outData[8] = 0x00; // Clear the byte + /* + B15B14; Scroll Unit Indication bits. + 0b00; the unit is a pixel (normalized with respect to the WFD Source display resolution that is conveyed in an RTSP M4 request message). + 0b01; the unit is a mouse notch (where the application is responsible for representing the number of pixels per notch). + 0b10-0b11; Reserved. + + B13; Scroll Direction Indication bit. + 0b0; Scrolling to the right. Scrolling to the right means the displayed content being shifted to the left from a user perspective. + 0b1; Scrolling to the left. Scrolling to the left means the displayed content being shifted to the right from a user perspective. + +B12:B0; Number of Scroll bits. +Number of units for a Horizontal scroll. +*/ + break; + } + case 1: { + temp = atoi(*(splitedStr + i)); + //log_info("getUIBCGenericScalePacket unit=[%d]\n", temp); + outData[7] = (temp >> 8) & 0xFF; + break; + } + case 2: { + temp = atoi(*(splitedStr + i)); + //log_info("getUIBCGenericScalePacket direction=[%d]\n", temp); + outData[7] |= ((temp >> 10) & 0xFF); + break; + + } + case 3: { + temp = atoi(*(splitedStr + i)); + //log_info("getUIBCGenericScalePacket amount to scroll=[%d]\n", temp); + outData[7] |= ((temp >> 12) & 0xFF); + outData[8] = temp & 0xFF; + + break; + } + default: { + break; + } + } + + free(*(splitedStr + i)); + } + + free(splitedStr); + } + //hexdump(outData, uibcBodyLen); + uibcmessage->m_DataValid = true; + uibcmessage->m_PacketDataLen = uibcBodyLen; +} + +// format: "typeId, integer part, fraction part" +void getUIBCGenericRotatePacket(const char * inEventDesc, UibcMessage* uibcmessage) { + log_info("getUIBCGenericRotatePacket (%s)", inEventDesc); + char* outData = uibcmessage->m_PacketData; + int32_t typeId; + int32_t uibcBodyLen, genericPacketLen; + int32_t integerPart, FractionPart; + size_t size; + + char** splitedStr = str_split((char*)inEventDesc, ",", &size); + + if (splitedStr) { + int i; + for (i = 0; * (splitedStr + i); i++) { + //log_info("getUIBCGenericRotatePacket splitedStr tokens=[%s]\n", *(splitedStr + i)); + + switch (i) { + case 0: { + typeId = atoi(*(splitedStr + i)); + //log_info("getUIBCGenericRotatePacket typeId=[%d]\n", typeId); + genericPacketLen = 2; + uibcBodyLen = genericPacketLen + 7; // Generic herder leh = 7 + outData = (char*)malloc(uibcBodyLen + 1); + // UIBC header + outData[0] = 0x00; //Version (3 bits),T (1 bit),Reserved(8 bits),Input Category (4 bits) + outData[1] = 0x00; //Version (3 bits),T (1 bit),Reserved(8 bits),Input Category (4 bits) + outData[2] = (uibcBodyLen >> 8) & 0xFF; //Length(16 bits) + outData[3] = uibcBodyLen & 0xFF; //Length(16 bits) + //Generic Input Body Format + outData[4] = typeId & 0xFF; // Tyoe ID, 1 octet + outData[5] = (genericPacketLen >> 8) & 0xFF; // Length, 2 octets + outData[6] = genericPacketLen & 0xFF; // Length, 2 octets + break; + } + case 1: { + integerPart = atoi(*(splitedStr + i)); + outData[7] = integerPart & 0xFF; + //log_info("getUIBCGenericRotatePacket integerPart=[%d]\n", integerPart); + break; + } + case 2: { + FractionPart = atoi(*(splitedStr + i)); + outData[8] = FractionPart & 0xFF; + //log_info("getUIBCGenericRotatePacket FractionPart=[%d]\n", FractionPart); + + break; + } + default: { + break; + } + } + free(*(splitedStr + i)); + } + + free(splitedStr); + } + //hexdump(outData, uibcBodyLen); + uibcmessage->m_DataValid = true; + uibcmessage->m_PacketDataLen = uibcBodyLen; +} + + +char** str_split(char* pStr, const char* pDelim, size_t* size) { + char** result = 0; + size_t count = 0; + char* tmp = pStr; + char* tmpStr = NULL; + char* last_comma = 0; + + asprintf(&tmpStr, "%s", pStr); + + /* Count how many elements will be extracted. */ + while (*tmp) { + if (*pDelim == *tmp) { + count++; + last_comma = tmp; + } + tmp++; + } + + /* Add space for trailing token. */ + count += last_comma < (pStr + strlen(pStr) - 1) ? 1 : 0; + + result = (char**)malloc(sizeof(char*) * count); + + *size = count; + + tmp = tmpStr = strdup(pStr); + size_t idx = 0; + char* token; + while ((token = strsep(&tmp, pDelim)) != NULL) { + * (result + idx++) = strdup(token); + } + free(tmpStr); + return result; +} diff --git a/src/uibc/miracle-uibcctl.h b/src/uibc/miracle-uibcctl.h new file mode 100644 index 0000000..1398011 --- /dev/null +++ b/src/uibc/miracle-uibcctl.h @@ -0,0 +1,48 @@ + +#ifndef MIRACLE_UIBCCTL_H +#define MIRACLE_UIBCCTL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "shl_log.h" + +typedef enum { + GENERIC_TOUCH_DOWN = 0, + GENERIC_TOUCH_UP, + GENERIC_TOUCH_MOVE, + GENERIC_KEY_DOWN, + GENERIC_KEY_UP, + GENERIC_ZOOM, + GENERIC_VERTICAL_SCROLL, + GENERIC_HORIZONTAL_SCROLL, + GENERIC_ROTATE +} MessageType; + +typedef struct { + char* m_PacketData; + size_t m_PacketDataLen; + bool m_DataValid; +} UibcMessage; + +UibcMessage buildUibcMessage(MessageType type, const char* inEventDesc, double widthRatio, double heightRatio); + +static char** str_split(char* pStr, const char* pDelim, size_t* size); + +void getUIBCGenericTouchPacket(const char *inEventDesc, UibcMessage* uibcmessage, double widthRatio, double heightRatio); +void getUIBCGenericKeyPacket(const char *inEventDesc, UibcMessage* uibcmessage); +void getUIBCGenericZoomPacket(const char *inEventDesc,UibcMessage* uibcmessage); +void getUIBCGenericScalePacket(const char *inEventDesc, UibcMessage* uibcmessage); +void getUIBCGenericRotatePacket(const char *inEventDesc, UibcMessage* uibcmessage); + +void hexdump(void *_data, size_t len); +void binarydump(void *_data, size_t len); + +int sendUibcMessage(UibcMessage* uibcmessage, int sockfd); +#endif diff --git a/src/wifi/CMakeLists.txt b/src/wifi/CMakeLists.txt index 566df22..5a997aa 100644 --- a/src/wifi/CMakeLists.txt +++ b/src/wifi/CMakeLists.txt @@ -1,6 +1,3 @@ - -########### next target ############### - set(miracle-wifid_SRCS wifid.h wifid.c wifid-dbus.c @@ -12,6 +9,7 @@ add_executable(miracle-wifid ${miracle-wifid_SRCS}) target_link_libraries(miracle-wifid ${KDE4_KDECORE_LIBS}) +cmake_policy(SET CMP0015 NEW) include_directories(shared) link_directories(shared) target_link_libraries(miracle-wifid miracle-shared) @@ -19,40 +17,11 @@ target_link_libraries(miracle-wifid miracle-shared) find_package(PkgConfig) pkg_check_modules (GLIB2 REQUIRED glib-2.0) pkg_check_modules (UDEV REQUIRED libudev) -#link_directories( ${UDEV_LIBRARY_DIRS}) -#include_directories( ${UDEV_INCLUDE_DIRS}) target_link_libraries(miracle-wifid ${UDEV_LIBRARIES}) -#link_directories( ${GLIB2_LIBRARY_DIRS}) -#include_directories( ${GLIB2_INCLUDE_DIRS}) +target_link_libraries(miracle-wifid m) +link_directories( ${GLIB2_LIBRARY_DIRS}) +include_directories( ${GLIB2_INCLUDE_DIRS}) target_link_libraries(miracle-wifid ${GLIB2_LIBRARIES}) install(TARGETS miracle-wifid DESTINATION bin) INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/shared) - -########### install files ############### - -#set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake-extensions/ ) - - - - - -#original Makefile.am contents follow: - -#include $(top_srcdir)/common.am -#bin_PROGRAMS = miracle-wifid -# -#miracle_wifid_SOURCES = \ -# wifid.h \ -# wifid.c \ -# wifid-dbus.c \ -# wifid-link.c \ -# wifid-peer.c \ -# wifid-supplicant.c -#miracle_wifid_CPPFLAGS = \ -# $(AM_CPPFLAGS) \ -# $(DEPS_CFLAGS) -#miracle_wifid_LDADD = \ -# ../shared/libmiracle-shared.la \ -# $(DEPS_LIBS) -# diff --git a/src/wifi/Makefile.am b/src/wifi/Makefile.am index 179d4cc..463ac17 100644 --- a/src/wifi/Makefile.am +++ b/src/wifi/Makefile.am @@ -10,8 +10,10 @@ miracle_wifid_SOURCES = \ wifid-supplicant.c miracle_wifid_CPPFLAGS = \ $(AM_CPPFLAGS) \ - $(DEPS_CFLAGS) + $(DEPS_CFLAGS) \ + $(GLIB_CFLAGS) miracle_wifid_LDADD = \ ../shared/libmiracle-shared.la \ - $(DEPS_LIBS) + $(DEPS_LIBS) \ + $(GLIB_LIBS) diff --git a/src/wifi/meson.build b/src/wifi/meson.build new file mode 100644 index 0000000..c8eac31 --- /dev/null +++ b/src/wifi/meson.build @@ -0,0 +1,14 @@ +inc = include_directories('../..') +miracle_wifid_src = ['wifid.h', + 'wifid.c', + 'wifid-dbus.c', + 'wifid-link.c', + 'wifid-peer.c', + 'wifid-supplicant.c' +] +executable('miracle-wifid', miracle_wifid_src, + include_directories: inc, + install: true, + dependencies: [udev, glib2, libsystemd, libmiracle_shared_dep, m] +) + diff --git a/src/wifi/wifid-dbus.c b/src/wifi/wifid-dbus.c index f462cf4..a856cd2 100644 --- a/src/wifi/wifid-dbus.c +++ b/src/wifi/wifid-dbus.c @@ -309,6 +309,7 @@ static const sd_bus_vtable peer_dbus_vtable[] = { SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_SIGNAL("ProvisionDiscovery", "ss", 0), SD_BUS_SIGNAL("GoNegRequest", "ss", 0), + SD_BUS_SIGNAL("FormationFailure", "s", 0), SD_BUS_VTABLE_END }; @@ -570,6 +571,42 @@ static int link_dbus_set_friendly_name(sd_bus *bus, return link_set_friendly_name(l, name); } +static int link_dbus_get_managed(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *data, + sd_bus_error *err) +{ + struct link *l = data; + int r; + + r = sd_bus_message_append(reply, "b", link_get_managed(l)); + if (r < 0) + return r; + + return 1; +} + +static int link_dbus_set_managed(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *value, + void *data, + sd_bus_error *err) +{ + struct link *l = data; + int val, r; + + r = sd_bus_message_read(value, "b", &val); + if (r < 0) + return r; + + return link_set_managed(l, val); +} + static int link_dbus_get_p2p_scanning(sd_bus *bus, const char *path, const char *interface, @@ -661,6 +698,12 @@ static const sd_bus_vtable link_dbus_vtable[] = { link_dbus_set_friendly_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_WRITABLE_PROPERTY("Managed", + "b", + link_dbus_get_managed, + link_dbus_set_managed, + 0, + SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_WRITABLE_PROPERTY("P2PScanning", "b", link_dbus_get_p2p_scanning, @@ -894,8 +937,12 @@ int manager_dbus_connect(struct manager *m) r = sd_bus_request_name(m->bus, "org.freedesktop.miracle.wifi", 0); if (r < 0) { - log_error("cannot claim org.freedesktop.miracle.wifi bus-name: %d", + if (r == -EEXIST) { + log_info("cannot claim org.freedesktop.miracle.wifi bus-name: it is already being acquired"); + } else { + log_error("cannot claim org.freedesktop.miracle.wifi bus-name: %d", r); + } goto error_silent; } diff --git a/src/wifi/wifid-link.c b/src/wifi/wifid-link.c index ff9eb62..1093fc4 100644 --- a/src/wifi/wifid-link.c +++ b/src/wifi/wifid-link.c @@ -100,6 +100,8 @@ int link_new(struct manager *m, if (out) *out = l; + l->public = true; + link_dbus_added(l); return 0; error: @@ -116,6 +118,9 @@ void link_free(struct link *l) link_set_managed(l, false); + link_dbus_removed(l); + l->public = false; + if (shl_htable_remove_uint(&l->m->links, l->ifindex, NULL)) { log_info("remove link: %s", l->ifname); --l->m->link_cnt; @@ -129,17 +134,66 @@ void link_free(struct link *l) free(l->wfd_subelements); free(l->friendly_name); free(l->ifname); + free(l->config_methods); + free(l->ip_binary); free(l); } -void link_set_managed(struct link *l, bool set) +void link_use_dev(struct link *l) +{ + l->use_dev = true; +} + +bool link_is_using_dev(struct link *l) +{ + return l->use_dev; +} + +int link_set_config_methods(struct link *l, char *config_methods) +{ + char *cm; + + if (!config_methods) + return log_EINVAL(); + + cm = strdup(config_methods); + if (!cm) + return log_ENOMEM(); + + free(l->config_methods); + l->config_methods = cm; + return 0; +} + +int link_set_ip_binary(struct link *l, const char *ip_binary) +{ + char *ipb; + + if (!ip_binary) + return log_EINVAL(); + + ipb = strdup(ip_binary); + if (!ipb) + return log_ENOMEM(); + + free(l->ip_binary); + l->ip_binary = ipb; + return 0; +} + +bool link_get_managed(struct link *l) +{ + return l->managed; +} + +int link_set_managed(struct link *l, bool set) { int r; if (!l) - return log_vEINVAL(); + return log_EINVAL(); if (l->managed == set) - return; + return 0; if (set) { log_info("manage link %s", l->ifname); @@ -147,7 +201,7 @@ void link_set_managed(struct link *l, bool set) r = supplicant_start(l->s); if (r < 0) { log_error("cannot start supplicant on %s", l->ifname); - return; + return -EFAULT; } } else { log_info("link %s no longer managed", l->ifname); @@ -155,6 +209,7 @@ void link_set_managed(struct link *l, bool set) } l->managed = set; + return 0; } int link_renamed(struct link *l, const char *ifname) @@ -224,6 +279,9 @@ int link_set_wfd_subelements(struct link *l, const char *val) if (!l || !val) return log_EINVAL(); + if (!l->managed) + return log_EUNMANAGED(); + t = strdup(val); if (!t) return log_ENOMEM(); @@ -256,6 +314,9 @@ int link_set_p2p_scanning(struct link *l, bool set) if (!l) return log_EINVAL(); + if (!l->managed) + return log_EUNMANAGED(); + if (set) { return supplicant_p2p_start_scan(l->s); } else { @@ -266,7 +327,16 @@ int link_set_p2p_scanning(struct link *l, bool set) bool link_get_p2p_scanning(struct link *l) { - return l && supplicant_p2p_scanning(l->s); + if (!l) { + log_vEINVAL(); + return false; + } + + if (!l->managed) { + return false; + } + + return supplicant_p2p_scanning(l->s); } void link_supplicant_started(struct link *l) @@ -274,9 +344,9 @@ void link_supplicant_started(struct link *l) if (!l || l->public) return; - log_debug("link %s started", l->ifname); - l->public = true; - link_dbus_added(l); + if (l->m->friendly_name && l->managed) + link_set_friendly_name(l, l->m->friendly_name); + log_info("link %s managed", l->ifname); } void link_supplicant_stopped(struct link *l) @@ -284,9 +354,7 @@ void link_supplicant_stopped(struct link *l) if (!l || !l->public) return; - log_debug("link %s stopped", l->ifname); - link_dbus_removed(l); - l->public = false; + log_info("link %s unmanaged", l->ifname); } void link_supplicant_p2p_scan_changed(struct link *l, bool new_value) diff --git a/src/wifi/wifid-peer.c b/src/wifi/wifid-peer.c index a361089..b87d0cc 100644 --- a/src/wifi/wifid-peer.c +++ b/src/wifi/wifid-peer.c @@ -61,7 +61,8 @@ int peer_new(struct link *l, r = log_ENOMEM(); goto error; } - strncpy(p->p2p_mac, mac, MAC_STRLEN - 1); + strncpy(p->p2p_mac, mac, MAC_STRLEN); + p->p2p_mac[MAC_STRLEN - 1] = 0; r = shl_htable_insert_str(&l->peers, &p->p2p_mac, NULL); if (r < 0) { diff --git a/src/wifi/wifid-supplicant.c b/src/wifi/wifid-supplicant.c index f467b47..9d8ef91 100644 --- a/src/wifi/wifid-supplicant.c +++ b/src/wifi/wifid-supplicant.c @@ -19,14 +19,21 @@ #define LOG_SUBSYSTEM "supplicant" +#include "config.h" + #include #include #include #include #include +#include #include #include + +#ifdef ENABLE_SYSTEMD #include +#endif + #include #include "shl_dlist.h" #include "shl_log.h" @@ -34,7 +41,6 @@ #include "util.h" #include "wifid.h" #include "wpas.h" -#include "config.h" struct supplicant_group { unsigned long users; @@ -373,6 +379,7 @@ static int supplicant_group_spawn_dhcp_server(struct supplicant_group *g, sigemptyset(&mask); sigprocmask(SIG_SETMASK, &mask, NULL); +#ifdef ENABLE_SYSTEMD /* redirect stdout/stderr to journal */ sprintf(journal_id, "miracle-dhcp-%s", g->ifname); fd_journal = sd_journal_stream_fd(journal_id, LOG_INFO, false); @@ -381,12 +388,15 @@ static int supplicant_group_spawn_dhcp_server(struct supplicant_group *g, dup2(fd_journal, 1); dup2(fd_journal, 2); } else { +#endif /* no journal? redirect stdout to parent's stderr */ dup2(2, 1); +#ifdef ENABLE_SYSTEMD } +#endif i = 0; - argv[i++] = (char*) BUILD_BINDIR "/miracle-dhcp"; + argv[i++] = (char*) "miracle-dhcp"; argv[i++] = "--server"; argv[i++] = "--prefix"; argv[i++] = prefix; @@ -396,9 +406,15 @@ static int supplicant_group_spawn_dhcp_server(struct supplicant_group *g, argv[i++] = g->ifname; argv[i++] = "--comm-fd"; argv[i++] = commfd; + if (g->s->l->ip_binary) { + argv[i++] = "--ip-binary"; + argv[i++] = g->s->l->ip_binary; + } argv[i] = NULL; - execve(argv[0], argv, environ); + if (execvpe(argv[0], argv, environ)< 0) { + log_error("dhcp failed (%d): %m", errno); + } _exit(1); } @@ -435,6 +451,7 @@ static int supplicant_group_spawn_dhcp_client(struct supplicant_group *g) sigemptyset(&mask); sigprocmask(SIG_SETMASK, &mask, NULL); +#ifdef ENABLE_SYSTEMD /* redirect stdout/stderr to journal */ sprintf(journal_id, "miracle-dhcp-%s", g->ifname); fd_journal = sd_journal_stream_fd(journal_id, LOG_INFO, false); @@ -443,21 +460,30 @@ static int supplicant_group_spawn_dhcp_client(struct supplicant_group *g) dup2(fd_journal, 1); dup2(fd_journal, 2); } else { +#endif /* no journal? redirect stdout to parent's stderr */ dup2(2, 1); +#ifdef ENABLE_SYSTEMD } +#endif i = 0; - argv[i++] = (char*) BUILD_BINDIR "/miracle-dhcp"; + argv[i++] = (char*) "miracle-dhcp"; argv[i++] = "--log-level"; argv[i++] = loglevel; argv[i++] = "--netdev"; argv[i++] = g->ifname; argv[i++] = "--comm-fd"; argv[i++] = commfd; + if (g->s->l->ip_binary) { + argv[i++] = "--ip-binary"; + argv[i++] = g->s->l->ip_binary; + } argv[i] = NULL; - execve(argv[0], argv, environ); + if (execvpe(argv[0], argv, environ) < 0) { + log_error("dhcp failed (%d): %m", errno); + } _exit(1); } @@ -504,7 +530,7 @@ static int supplicant_group_new(struct supplicant *s, j = shl_dlist_entry(i, struct supplicant_group, list); - if (j->subnet == j->subnet) + if (j->subnet == subnet) break; } @@ -871,7 +897,7 @@ static void supplicant_parse_peer(struct supplicant *s, /* TODO: wfd_dev_info only contains the dev-info sub-elem, * while wfd_sublemens contains all. Fix that! The user has no * chance to distinguish both. - * We currently use it only as boolen (set/unset) but once we + * We currently use it only as boolean (set/unset) but once we * parse it we _definitely_ have to provide proper data. */ r = wpas_message_dict_read(m, "wfd_dev_info", 's', &val); if (r >= 0) { @@ -1287,6 +1313,20 @@ static void supplicant_event_p2p_group_removed(struct supplicant *s, supplicant_group_free(g); } +static void supplicant_event_p2p_go_neg_failure(struct supplicant *s, + struct wpas_message *ev) +{ + struct peer *p; + + if (s->pending) { + log_debug("peer %s group owner negotiation failed", + s->pending->friendly_name); + p = s->pending->p; + s->pending = NULL; + peer_supplicant_formation_failure(p, "group owner negotiation failed"); + } +} + static void supplicant_event_p2p_group_formation_failure(struct supplicant *s, struct wpas_message *ev) { @@ -1432,7 +1472,6 @@ static void supplicant_event(struct supplicant *s, struct wpas_message *m) !strcmp(name, "WPS-AP-AVAILABLE-PIN") || !strcmp(name, "CTRL-EVENT-EAP-STATUS") || !strcmp(name, "CTRL-EVENT-EAP-METHOD") || - !strcmp(name, "CTRL-EVENT-EAP-STATUS") || !strcmp(name, "WPS-CRED-RECEIVED") || !strcmp(name, "WPS-AP-AVAILABLE") || !strcmp(name, "WPS-REG-SUCCESS") || @@ -1467,6 +1506,8 @@ static void supplicant_event(struct supplicant *s, struct wpas_message *m) supplicant_event_p2p_group_started(s, m); else if (!strcmp(name, "P2P-GROUP-REMOVED")) supplicant_event_p2p_group_removed(s, m); + else if (!strcmp(name, "P2P-GO-NEG-FAILURE")) + supplicant_event_p2p_go_neg_failure(s, m); else if (!strcmp(name, "P2P-GROUP-FORMATION-FAILURE")) supplicant_event_p2p_group_formation_failure(s, m); else if (!strcmp(name, "AP-STA-CONNECTED")) @@ -2142,6 +2183,9 @@ static int supplicant_global_fn(struct wpas *w, } /* ignore events on the global-iface, we only listen on dev-iface */ + if(link_is_using_dev(s->l) && wpas_message_get_ifname(m)) { + supplicant_event(s, m); + } return 0; @@ -2188,7 +2232,7 @@ static int supplicant_global_attach_fn(struct wpas *w, * Devices with P2P_DEVICE support (instead of direct P2P_GO/CLIENT * support) are broken with a *lot* of wpa_supplicant versions on the * global interface. Therefore, try to open the p2p-dev-* interface - * after the global-ATTACH succeded (which means the iface is properly + * after the global-ATTACH succeeded (which means the iface is properly * set up). If this works, use the p2p-dev-* interface, otherwise, just * copy the global interface over to bus_dev. * Event-forwarding is broken on the global-interface in such cases, @@ -2356,6 +2400,7 @@ static void supplicant_run(struct supplicant *s, const char *binary) sigemptyset(&mask); sigprocmask(SIG_SETMASK, &mask, NULL); +#ifdef ENABLE_SYSTEMD /* redirect stdout/stderr to journal */ sprintf(journal_id, "miracle-wifid-%s-%u", s->l->ifname, s->l->ifindex); @@ -2365,9 +2410,12 @@ static void supplicant_run(struct supplicant *s, const char *binary) dup2(fd_journal, 1); dup2(fd_journal, 2); } else { +#endif /* no journal? redirect stdout to parent's stderr */ dup2(2, 1); +#ifdef ENABLE_SYSTEMD } +#endif /* initialize wpa_supplicant args */ i = 0; @@ -2391,12 +2439,60 @@ static void supplicant_run(struct supplicant *s, const char *binary) argv[i++] = s->l->ifname; argv[i++] = "-g"; argv[i++] = s->global_ctrl; + + if (arg_wpa_syslog) { + argv[i++] = "-s"; + } + argv[i] = NULL; /* execute wpa_supplicant; if it fails, the caller issues exit(1) */ execve(argv[0], argv, environ); } +static int supplicant_find(char **binary) +{ + _shl_free_ char *path = getenv("PATH"); + if(!path) { + return -EINVAL; + } + + path = strdup(path); + if(!path) { + return log_ENOMEM(); + } + + struct stat bin_stat; + char *curr = path, *next; + while(1) { + curr = strtok_r(curr, ":", &next); + if(!curr) { + break; + } + + _shl_free_ char *bin = shl_strcat(curr, "/wpa_supplicant"); + if (!bin) + return log_ENOMEM(); + + if(stat(bin, &bin_stat) < 0) { + if(ENOENT == errno || ENOTDIR == errno) { + goto end; + } + return log_ERRNO(); + } + + if (!access(bin, X_OK)) { + *binary = strdup(bin); + return 0; + } + +end: + curr = NULL; + } + + return -EINVAL; +} + static int supplicant_spawn(struct supplicant *s) { _shl_free_ char *binary = NULL; @@ -2410,14 +2506,16 @@ static int supplicant_spawn(struct supplicant *s) log_debug("spawn supplicant of %s", s->l->ifname); - binary = shl_strcat(arg_wpa_bindir, "/wpa_supplicant"); - if (!binary) - return log_ENOMEM(); - - if (access(binary, X_OK) < 0) { - log_error("execution of wpas (%s) not possible: %m", binary); - return -EINVAL; + if (supplicant_find(&binary) < 0) { + if (binary != NULL) { + log_error("execution of wpas (%s) not possible: %m", binary); + } else { + log_error("execution of wpas not possible: %m"); } + return -EINVAL; + } + + log_info("wpa_supplicant found: %s", binary); pid = fork(); if (pid < 0) { @@ -2532,10 +2630,9 @@ static int supplicant_write_config(struct supplicant *s) "driver_param=%s\n" "ap_scan=%s\n" "# End of configuration\n", - s->l->friendly_name ? : "unknown", + s->l->friendly_name ?: "unknown", "1-0050F204-1", - "pbc", - //"pbc keypad pin display", + s->l->config_methods ?: "pbc", "p2p_device=1", "1"); if (r < 0) { diff --git a/src/wifi/wifid.c b/src/wifi/wifid.c index 9bfd2a8..86fb3ff 100644 --- a/src/wifi/wifid.c +++ b/src/wifi/wifid.c @@ -40,9 +40,16 @@ #include "wifid.h" #include "config.h" -const char *arg_wpa_bindir = "/usr/bin"; +#define XSTR(x) STR(x) +#define STR(x) #x const char *interface_name = NULL; +const char *config_methods = NULL; unsigned int arg_wpa_loglevel = LOG_NOTICE; +bool arg_wpa_syslog = false; +bool use_dev = false; +bool lazy_managed = false; +const char *arg_ip_binary = NULL; + /* * Manager Handling @@ -101,13 +108,22 @@ static void manager_add_udev_link(struct manager *m, if (r < 0) return; - link_set_friendly_name(l, m->friendly_name); + if (m->friendly_name && l->managed) + link_set_friendly_name(l, m->friendly_name); + if (m->config_methods) + link_set_config_methods(l, m->config_methods); + + if(use_dev) + link_use_dev(l); + if(arg_ip_binary) + link_set_ip_binary(l, arg_ip_binary); #ifdef RELY_UDEV - if (udev_device_has_tag(d, "miracle")) { + bool managed = udev_device_has_tag(d, "miracle") && !lazy_managed; #else - if (!interface_name || !strcmp(interface_name, ifname)) { + bool managed = (!interface_name || !strcmp(interface_name, ifname)) && !lazy_managed; #endif + if (managed) { link_set_managed(l, true); } else { log_debug("ignored device: %s", ifname); @@ -147,12 +163,12 @@ static int manager_udev_fn(sd_event_source *source, } #ifdef RELY_UDEV - if (udev_device_has_tag(d, "miracle")) + if (udev_device_has_tag(d, "miracle") && !lazy_managed) link_set_managed(l, true); else link_set_managed(l, false); #else - if (!interface_name || !strcmp(interface_name, ifname)) { + if ((!interface_name || !strcmp(interface_name, ifname)) && !lazy_managed) { link_set_managed(l, true); } else { log_debug("ignored device: %s", ifname); @@ -212,6 +228,7 @@ static void manager_free(struct manager *m) sd_event_unref(m->event); free(m->friendly_name); + free(m->config_methods); free(m); } @@ -224,6 +241,7 @@ static int manager_new(struct manager **out) unsigned int i; sigset_t mask; int r; + char *cm; m = calloc(1, sizeof(*m)); if (!m) @@ -231,6 +249,16 @@ static int manager_new(struct manager **out) shl_htable_init_uint(&m->links); + + if (config_methods) { + cm = strdup(config_methods); + if (!cm) + return log_ENOMEM(); + + free(m->config_methods); + m->config_methods = cm; + } + r = sd_event_default(&m->event); if (r < 0) { log_vERR(r); @@ -454,11 +482,16 @@ static int help(void) " --version Show package version\n" " --log-level Maximum level for log messages\n" " --log-time Prefix log-messages with timestamp\n" + " --log-date-time Prefix log-messages with date time\n" "\n" " -i --interface Choose the interface to use\n" + " --config-methods Define config methods for pairing, default 'pbc'\n" "\n" - " --wpa-bindir wpa_supplicant binary dir [/usr/bin]\n" - " --wpa-loglevel wpa_supplicant log-level\n" + " --wpa-syslog wpa_supplicant use syslog\n" + " --use-dev enable workaround for 'no ifname' issue\n" + " --lazy-managed manage interface only when user decide to do\n" + " --ip-binary path to 'ip' binary [default: "XSTR(IP_BINARY)"]\n" , program_invocation_short_name); /* * 80-char barrier: @@ -474,19 +507,28 @@ static int parse_argv(int argc, char *argv[]) ARG_VERSION = 0x100, ARG_LOG_LEVEL, ARG_LOG_TIME, - - ARG_WPA_BINDIR, + ARG_LOG_DATE_TIME, ARG_WPA_LOGLEVEL, + ARG_WPA_SYSLOG, + ARG_USE_DEV, + ARG_CONFIG_METHODS, + ARG_LAZY_MANAGED, + ARG_IP_BINARY, }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, - { "log-time", no_argument, NULL, ARG_LOG_TIME }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, + { "log-time", no_argument, NULL, ARG_LOG_TIME }, + { "log-date-time", no_argument, NULL, ARG_LOG_DATE_TIME }, - { "wpa-bindir", required_argument, NULL, ARG_WPA_BINDIR }, { "wpa-loglevel", required_argument, NULL, ARG_WPA_LOGLEVEL }, + { "wpa-syslog", no_argument, NULL, ARG_WPA_SYSLOG }, { "interface", required_argument, NULL, 'i' }, + { "use-dev", no_argument, NULL, ARG_USE_DEV }, + { "config-methods", required_argument, NULL, ARG_CONFIG_METHODS }, + { "lazy-managed", no_argument, NULL, ARG_LAZY_MANAGED }, + { "ip-binary", required_argument, NULL, ARG_IP_BINARY }, {} }; int c; @@ -507,13 +549,27 @@ static int parse_argv(int argc, char *argv[]) case ARG_LOG_TIME: log_init_time(); break; - - case ARG_WPA_BINDIR: - arg_wpa_bindir = optarg; + case ARG_LOG_DATE_TIME: + log_date_time = true; + break; + case ARG_USE_DEV: + use_dev = true; + break; + case ARG_CONFIG_METHODS: + config_methods = optarg; + break; + case ARG_LAZY_MANAGED: + lazy_managed = true; break; case ARG_WPA_LOGLEVEL: arg_wpa_loglevel = log_parse_arg(optarg); break; + case ARG_WPA_SYSLOG: + arg_wpa_syslog = true; + break; + case ARG_IP_BINARY: + arg_ip_binary = optarg; + break; case '?': return -EINVAL; } @@ -539,12 +595,30 @@ int main(int argc, char **argv) srand(time(NULL)); + GKeyFile* gkf = load_ini_file(); + + if (gkf) { + gchar* log_level; + log_level = g_key_file_get_string (gkf, "wifid", "log-level", NULL); + if (log_level) { + log_max_sev = log_parse_arg(log_level); + g_free(log_level); + } + g_key_file_free(gkf); + } + r = parse_argv(argc, argv); if (r < 0) return EXIT_FAILURE; if (!r) return EXIT_SUCCESS; + if (getuid() != 0) { + r = EACCES; + log_notice("Must run as root"); + goto finish; + } + r = manager_new(&m); if (r < 0) goto finish; diff --git a/src/wifi/wifid.h b/src/wifi/wifid.h index 5d20b02..db78f18 100644 --- a/src/wifi/wifid.h +++ b/src/wifi/wifid.h @@ -129,12 +129,15 @@ struct link { char *ifname; char *friendly_name; char *wfd_subelements; + char *config_methods; + char *ip_binary; size_t peer_cnt; struct shl_htable peers; bool managed : 1; bool public : 1; + bool use_dev : 1; }; #define link_from_htable(_l) \ @@ -153,9 +156,17 @@ int link_new(struct manager *m, struct link **out); void link_free(struct link *l); -void link_set_managed(struct link *l, bool set); +/* workaround for the 'no ifname' issue */ +void link_use_dev(struct link *l); +bool link_is_using_dev(struct link *l); + +int link_set_ip_binary(struct link *l, const char *ip_binary); + +int link_set_managed(struct link *l, bool set); +bool link_get_managed(struct link *l); int link_renamed(struct link *l, const char *ifname); +int link_set_config_methods(struct link *l, char *config_methods); int link_set_friendly_name(struct link *l, const char *name); const char *link_get_friendly_name(struct link *l); int link_set_wfd_subelements(struct link *l, const char *val); @@ -183,6 +194,7 @@ struct manager { sd_event_source *udev_mon_source; char *friendly_name; + char *config_methods; size_t link_cnt; struct shl_htable links; @@ -203,7 +215,7 @@ void manager_dbus_disconnect(struct manager *m); /* cli arguments */ -extern const char *arg_wpa_bindir; extern unsigned int arg_wpa_loglevel; +extern bool arg_wpa_syslog; #endif /* WIFID_H */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6d4c2eb..a896336 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,31 +1,31 @@ - -########### next target ############### - find_package(PkgConfig) pkg_check_modules (CHECK check) - + if(CHECK_FOUND) - set(test_rtsp_SOURCES test_common.h test_rtsp.c) add_executable(test_rtsp ${test_rtsp_SOURCES}) target_link_libraries(test_rtsp miracle-shared) - target_link_libraries(test_rtsp ${CHECK_LIBRARIES}) target_link_libraries(test_rtsp ${UDEV_LIBRARIES}) target_link_libraries(test_rtsp ${GLIB2_LIBRARIES}) - + target_link_libraries(test_rtsp ${CHECK_LIBRARIES}) + target_link_libraries(test_rtsp ${CHECK_CFLAGS}) + set(test_wpas_SOURCES test_common.h test_wpas.c) add_executable(test_wpas ${test_wpas_SOURCES}) target_link_libraries(test_wpas miracle-shared) - target_link_libraries(test_wpas ${CHECK_LIBRARIES}) target_link_libraries(test_wpas ${UDEV_LIBRARIES}) target_link_libraries(test_wpas ${GLIB2_LIBRARIES}) + target_link_libraries(test_wpas ${CHECK_LIBRARIES}) + target_link_libraries(test_wpas ${CHECK_CFLAGS}) + target_link_libraries(test_wpas m) set(test_valgrind_SOURCES test_common.h test_valgrind.c) add_executable(test_valgrind ${test_valgrind_SOURCES}) target_link_libraries(test_valgrind miracle-shared) - target_link_libraries(test_valgrind ${CHECK_LIBRARIES}) target_link_libraries(test_valgrind ${UDEV_LIBRARIES}) target_link_libraries(test_valgrind ${GLIB2_LIBRARIES}) + target_link_libraries(test_valgrind ${CHECK_LIBRARIES}) + target_link_libraries(test_valgrind ${CHECK_CFLAGS}) INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/src/shared) @@ -39,7 +39,7 @@ if(CHECK_FOUND) add_custom_target(memcheck DEPENDS memcheck-verify - COMMAND for i in $(MEMTESTS) | + COMMAND for i in $$(MEMTESTS) | do | ${VALGRIND} --log-file=${CMAKE_SOURCE_DIR}/$$i.memlog | ${CMAKE_SOURCE_DIR}/$$i >/dev/null || (echo "memcheck failed on: $$i" ; exit 1) ; | @@ -48,69 +48,3 @@ if(CHECK_FOUND) COMMENT "verify memcheck") endif(CHECK_FOUND) - -########### install files ############### - - - - -#original Makefile.am contents follow: - -#include $(top_srcdir)/common.am -#tests = \ -# test_rtsp \ -# test_wpas -# -#if BUILD_HAVE_CHECK -#check_PROGRAMS = $(tests) test_valgrind -#TESTS = $(tests) test_valgrind -#MEMTESTS = $(tests) -#endif -# -#test_sources = \ -# test_common.h -#test_libs = \ -# ../src/shared/libmiracle-shared.la \ -# $(DEPS_LIBS) \ -# $(CHECK_LIBS) -#test_cflags = \ -# $(AM_CPPFLAGS) \ -# $(DEPS_CFLAGS) \ -# $(CHECK_CFLAGS) -# -#test_rtsp_SOURCES = test_rtsp.c $(test_sources) -#test_rtsp_CPPFLAGS = $(test_cflags) -#test_rtsp_LDADD = $(test_libs) -# -#test_valgrind_SOURCES = test_valgrind.c $(test_sources) -#test_valgrind_CPPFLAGS = $(test_cflags) -#test_valgrind_LDADD = $(test_libs) -# -#test_wpas_SOURCES = test_wpas.c $(test_sources) -#test_wpas_CPPFLAGS = $(test_cflags) -#test_wpas_LDADD = $(test_libs) -# -### custom recipes -# -#VALGRIND = CK_FORK=no valgrind --tool=memcheck --leak-check=yes --show-reachable=yes --leak-resolution=high --error-exitcode=1 --suppressions=$(top_builddir)/test.supp -# -## verify that test_valgrind actually leaks data -#memcheck-verify: check -# $(AM_V_GEN)$(VALGRIND) --log-file=/dev/null ./test_valgrind >/dev/null ; test 1 = $$? -# -## run memcheck tests via valgrind -#memcheck: memcheck-verify -# $(AM_V_GEN)for i in $(MEMTESTS) ; do \ -# $(VALGRIND) --log-file=$(top_builddir)/$$i.memlog \ -# $(top_builddir)/$$i >/dev/null || (echo "memcheck failed on: $$i" ; exit 1) ; \ -# done -# -#distcheck-hook: memcheck -#AM_MAKEFLAGS = --no-print-directory -#AUTOMAKE_OPTIONS = color-tests -# -## -## Phony targets -## -# -#.PHONY: memcheck-verify diff --git a/test/meson.build b/test/meson.build new file mode 100644 index 0000000..46400cf --- /dev/null +++ b/test/meson.build @@ -0,0 +1,35 @@ +check = dependency('check', required: false) +deps = [udev, glib2, check, libsystemd, libmiracle_shared_dep, m] + +if check.found() + test_rtsp = executable('test_rtsp', 'test_rtsp.c', dependencies: deps) + + test_wpas = executable('test_wpas', 'test_wpas.c', dependencies: deps) + + test_valgrind = executable('test_valgrind', + 'test_valgrind.c', + dependencies: deps + ) + + test('rtsp test', test_rtsp) + test('wpas test', test_wpas) + test('valgrind test', test_valgrind) + +# set(VALGRIND CK_FORK=no valgrind --tool=memcheck --leak-check=yes --show-reachable=yes --leak-resolution=high --error-exitcode=1 --suppressions=${CMAKE_SOURCE_DIR}/test.supp) +# +# add_custom_target(memcheck-verify +# DEPENDS test_rtsp test_wpas test_valgrind +# COMMAND ${VALGRIND} --log-file=/dev/null ./test_valgrind >/dev/null | +# test 1 = $$? +# COMMENT "verify memcheck") +# +# add_custom_target(memcheck +# DEPENDS memcheck-verify +# COMMAND for i in $(MEMTESTS) | +# do | +# ${VALGRIND} --log-file=${CMAKE_SOURCE_DIR}/$$i.memlog | +# ${CMAKE_SOURCE_DIR}/$$i >/dev/null || (echo "memcheck failed on: $$i" ; exit 1) ; | +# done +# SOURCES test_rtsp test_valgrind test_wpas +# COMMENT "verify memcheck") +endif