From fba5eb4a11cdbf072cf36ac6798d1557aba81fc1 Mon Sep 17 00:00:00 2001 From: Alberto Fanjul Date: Tue, 1 Nov 2022 09:11:47 +0100 Subject: [PATCH 1/3] run as source --- .gitignore | 2 + CMakeLists.txt | 22 +- README.md | 17 +- cmake/FindVala.cmake | 65 + cmake/ParseArguments.cmake | 36 + cmake/ValaPrecompile.cmake | 314 +++++ cmake/ValaVersion.cmake | 96 ++ debian/changelog | 11 + debian/compat | 1 + debian/control | 12 + debian/copyright | 38 + debian/postinst | 1 + debian/postrm | 1 + debian/rules | 22 + debian/source/format | 1 + meson.build | 1 - res/CMakeLists.txt | 134 +- res/create-symlinks | 6 + res/dispctl.vala | 824 +++++++++++++ res/gstencoder.vala | 426 +++++++ res/gstplayer | 12 +- res/install-scripts | 8 + res/meson.build | 66 + res/miracle-dispd.service.cmake | 18 + res/miracle-dispd.service.in | 18 + res/miracle-wfd.vala | 50 + res/miracle-wifi.vala | 92 ++ res/miracle-wifid.service.cmake | 19 + res/miracle-wifid.service.in | 19 + res/networkmanager.vala | 114 ++ res/org.freedesktop.miracle.conf | 68 +- res/org.freedesktop.miracle.wfd.service.cmake | 5 + res/org.freedesktop.miracle.wfd.service.in | 5 + ...org.freedesktop.miracle.wifi.service.cmake | 5 + res/org.freedesktop.miracle.wifi.service.in | 5 + res/sigint.c | 64 + res/sigint.h | 29 + res/sigint.vapi | 8 + src/CMakeLists.txt | 1 + src/ctl/CMakeLists.txt | 3 +- src/ctl/ctl-sink.c | 2 +- src/ctl/ctl-sink.h | 2 +- src/ctl/ctl-wifi.c | 144 ++- src/ctl/ctl.h | 3 + src/ctl/sinkctl.c | 159 ++- src/ctl/wfd.c | 632 ++++++++-- src/ctl/wfd.h | 190 +++ src/ctl/wifictl.c | 55 +- src/dhcp/server.c | 4 +- src/disp/CMakeLists.txt | 43 + src/disp/dispd-arg.c | 61 + src/disp/dispd-arg.h | 242 ++++ src/disp/dispd-arg.inc | 192 +++ src/disp/dispd-dbus.c | 943 ++++++++++++++ src/disp/dispd-dbus.h | 47 + src/disp/dispd-encoder.c | 982 +++++++++++++++ src/disp/dispd-encoder.h | 87 ++ src/disp/dispd-out-session.c | 1004 +++++++++++++++ src/disp/dispd-session.c | 1091 +++++++++++++++++ src/disp/dispd-session.h | 146 +++ src/disp/dispd-sink.c | 173 +++ src/disp/dispd.c | 545 ++++++++ src/disp/dispd.h | 223 ++++ src/disp/meson.build | 25 + src/meson.build | 3 + src/shared/shl_log.h | 14 + src/shared/util.h | 6 + src/wifi/wifid-dbus.c | 101 +- src/wifi/wifid-link.c | 67 +- src/wifi/wifid-supplicant.c | 111 +- src/wifi/wifid.c | 43 +- src/wifi/wifid.h | 9 +- 72 files changed, 9583 insertions(+), 405 deletions(-) create mode 100644 cmake/FindVala.cmake create mode 100644 cmake/ParseArguments.cmake create mode 100644 cmake/ValaPrecompile.cmake create mode 100644 cmake/ValaVersion.cmake create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/postinst create mode 100644 debian/postrm create mode 100755 debian/rules create mode 100644 debian/source/format create mode 100755 res/create-symlinks create mode 100644 res/dispctl.vala create mode 100644 res/gstencoder.vala create mode 100755 res/install-scripts create mode 100644 res/miracle-dispd.service.cmake create mode 100644 res/miracle-dispd.service.in create mode 100644 res/miracle-wfd.vala create mode 100644 res/miracle-wifi.vala create mode 100644 res/miracle-wifid.service.cmake create mode 100644 res/miracle-wifid.service.in create mode 100644 res/networkmanager.vala create mode 100644 res/org.freedesktop.miracle.wfd.service.cmake create mode 100644 res/org.freedesktop.miracle.wfd.service.in create mode 100644 res/org.freedesktop.miracle.wifi.service.cmake create mode 100644 res/org.freedesktop.miracle.wifi.service.in create mode 100644 res/sigint.c create mode 100644 res/sigint.h create mode 100644 res/sigint.vapi create mode 100644 src/disp/CMakeLists.txt create mode 100644 src/disp/dispd-arg.c create mode 100644 src/disp/dispd-arg.h create mode 100644 src/disp/dispd-arg.inc create mode 100644 src/disp/dispd-dbus.c create mode 100644 src/disp/dispd-dbus.h create mode 100644 src/disp/dispd-encoder.c create mode 100644 src/disp/dispd-encoder.h create mode 100644 src/disp/dispd-out-session.c create mode 100644 src/disp/dispd-session.c create mode 100644 src/disp/dispd-session.h create mode 100644 src/disp/dispd-sink.c create mode 100644 src/disp/dispd.c create mode 100644 src/disp/dispd.h create mode 100644 src/disp/meson.build diff --git a/.gitignore b/.gitignore index dd6862da..1343bcfd 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ *.swp *.tar.xz *.trs +*.pcap .deps/ .dirstamp .libs/ @@ -41,4 +42,5 @@ cmake_install.cmake CMakeCache.txt libmiracle-shared.a install_manifest.txt +.vimrc /build/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 3edc3dcc..1f182036 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,15 +1,12 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.0.2) + project(Miraclecast) 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) +list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) OPTION(ENABLE_SYSTEMD "Enable Systemd" ON) @@ -41,6 +38,19 @@ 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) pkg_check_modules (SYSTEMD REQUIRED libsystemd) +pkg_check_modules (GSTREAMER REQUIRED gstreamer-1.0) +pkg_check_modules (GSTREAMER_BASE REQUIRED gstreamer-base-1.0) + +set(CMAKE_C_FLAGS "-std=gnu11 -Wall ${CMAKE_C_FLAGS}") +add_definitions(-D_GNU_SOURCE) + +if(CMAKE_COMPILER_IS_GNUCC) + execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpversion + OUTPUT_VARIABLE GCC_VERSION) + if(GCC_VERSION VERSION_LESS 4.9) + message(FATAL_ERROR "gcc >= 4.9 is requred") + endif() +endif() CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/config.h.cmake ${CMAKE_BINARY_DIR}/config.h) diff --git a/README.md b/README.md index b100f416..3e538edb 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ 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. *Note: This is not implemented yet. Please see [#4](../../issues/4).* +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 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. @@ -21,9 +21,20 @@ The MiracleCast projects requires the following software to be installed: - **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) + - readline**: A library which is used to provide command line interface to control wifid, sink, etc.. - - **wpa_supplicant**: MiracleCast spawns wpa_supplicant with a custom config. + - **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 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). + + - gstreamer plugins: here are the gstreamer plugins you need in order to run sinkctl or dispctl + - gstreamer-plugins-base + - gstreamer-plugins-good + - gstreamer-plugins-bad + - gstreamer-plugins-ugly + - gstreamer-plugins-vaapi + - gstreamer-plugins-libav - **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) diff --git a/cmake/FindVala.cmake b/cmake/FindVala.cmake new file mode 100644 index 00000000..aa3a6e7d --- /dev/null +++ b/cmake/FindVala.cmake @@ -0,0 +1,65 @@ +## +# Copyright 2009-2010 Jakob Westhoff. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY JAKOB WESTHOFF ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +# EVENT SHALL JAKOB WESTHOFF OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# The views and conclusions contained in the software and documentation are those +# of the authors and should not be interpreted as representing official policies, +# either expressed or implied, of Jakob Westhoff +## + +## +# Find module for the Vala compiler (valac) +# +# This module determines wheter a Vala compiler is installed on the current +# system and where its executable is. +# +# Call the module using "find_package(Vala) from within your CMakeLists.txt. +# +# The following variables will be set after an invocation: +# +# VALA_FOUND Whether the vala compiler has been found or not +# VALA_EXECUTABLE Full path to the valac executable if it has been found +# VALA_VERSION Version number of the available valac +## + + +# Search for the valac executable in the usual system paths. +find_program(VALA_EXECUTABLE + NAMES valac) + +# Handle the QUIETLY and REQUIRED arguments, which may be given to the find call. +# Furthermore set VALA_FOUND to TRUE if Vala has been found (aka. +# VALA_EXECUTABLE is set) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Vala DEFAULT_MSG VALA_EXECUTABLE) + +mark_as_advanced(VALA_EXECUTABLE) + +# Determine the valac version +if(VALA_FOUND) + execute_process(COMMAND ${VALA_EXECUTABLE} "--version" + OUTPUT_VARIABLE "VALA_VERSION") + string(REPLACE "Vala" "" "VALA_VERSION" ${VALA_VERSION}) + string(STRIP ${VALA_VERSION} "VALA_VERSION") +endif(VALA_FOUND) diff --git a/cmake/ParseArguments.cmake b/cmake/ParseArguments.cmake new file mode 100644 index 00000000..717c0f56 --- /dev/null +++ b/cmake/ParseArguments.cmake @@ -0,0 +1,36 @@ +## +# This is a helper Macro to parse optional arguments in Macros/Functions +# It has been taken from the public CMake wiki. +# See http://www.cmake.org/Wiki/CMakeMacroParseArguments for documentation and +# licensing. +## +macro(parse_arguments prefix arg_names option_names) + set(DEFAULT_ARGS) + foreach(arg_name ${arg_names}) + set(${prefix}_${arg_name}) + endforeach(arg_name) + foreach(option ${option_names}) + set(${prefix}_${option} FALSE) + endforeach(option) + + set(current_arg_name DEFAULT_ARGS) + set(current_arg_list) + foreach(arg ${ARGN}) + set(larg_names ${arg_names}) + list(FIND larg_names "${arg}" is_arg_name) + if(is_arg_name GREATER -1) + set(${prefix}_${current_arg_name} ${current_arg_list}) + set(current_arg_name ${arg}) + set(current_arg_list) + else(is_arg_name GREATER -1) + set(loption_names ${option_names}) + list(FIND loption_names "${arg}" is_option) + if(is_option GREATER -1) + set(${prefix}_${arg} TRUE) + else(is_option GREATER -1) + set(current_arg_list ${current_arg_list} ${arg}) + endif(is_option GREATER -1) + endif(is_arg_name GREATER -1) + endforeach(arg) + set(${prefix}_${current_arg_name} ${current_arg_list}) +endmacro(parse_arguments) diff --git a/cmake/ValaPrecompile.cmake b/cmake/ValaPrecompile.cmake new file mode 100644 index 00000000..72c04b16 --- /dev/null +++ b/cmake/ValaPrecompile.cmake @@ -0,0 +1,314 @@ +## +# Copyright 2009-2010 Jakob Westhoff. All rights reserved. +# Copyright 2012 elementary. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY JAKOB WESTHOFF ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +# EVENT SHALL JAKOB WESTHOFF OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# The views and conclusions contained in the software and documentation are those +# of the authors and should not be interpreted as representing official policies, +# either expressed or implied, of Jakob Westhoff +## + +include(ParseArguments) +find_package(Vala REQUIRED) + +## +# Compile vala files to their c equivalents for further processing. +# +# The "vala_precompile" macro takes care of calling the valac executable on the +# given source to produce c files which can then be processed further using +# default cmake functions. +# +# The first parameter provided is a variable, which will be filled with a list +# of c files outputted by the vala compiler. This list can than be used in +# conjunction with functions like "add_executable" or others to create the +# necessary compile rules with CMake. +# +# The initial variable is followed by a list of .vala files to be compiled. +# Please take care to add every vala file belonging to the currently compiled +# project or library as Vala will otherwise not be able to resolve all +# dependencies. +# +# The following sections may be specified afterwards to provide certain options +# to the vala compiler: +# +# LIBRARY +# Indicates that this is to be compiled as a library. +# +# PACKAGES +# A list of vala packages/libraries to be used during the compile cycle. The +# package names are exactly the same, as they would be passed to the valac +# "--pkg=" option. +# +# OPTIONS +# A list of optional options to be passed to the valac executable. This can be +# used to pass "--thread" for example to enable multi-threading support. +# +# CUSTOM_VAPIS +# A list of custom vapi files to be included for compilation. This can be +# useful to include freshly created vala libraries without having to install +# them in the system. +# +# DEPENDS +# Additional files that may change the results of the outputed C files. +# +# GENERATE_VAPI [INTERNAL] +# Pass all the needed flags to the compiler to create a vapi for +# the compiled library. The provided name will be used for this and a +# .vapi file will be created. If INTERNAL is specified, +# an internal vapi _internal.vapi will be created as well. +# This option implies GENERATE_HEADER, so there is not need use GENERATE_HEADER +# in addition to GENERATE_VAPI unless they require different names. Requires +# that LIBRARY is set. +# +# GENERATE_HEADER [INTERNAL] +# Let the compiler generate a header file for the compiled code. There will +# be a header file being generated called .h. If INTERNAL +# is specified, an internal header _internal.h will be created +# as well. +# +# GENERATE_GIR [TYPELIB] +# Have the compiler generate a GObject-Introspection repository file with +# name: .gir. If TYPELIB is specified, the compiler will also +# create a binary typelib using the GI compiler. Requires that LIBRARY is set. +# +# TYPELIB_OPTIONS +# Additional options to pass to the GI compiler. Requires that GENERATE_GIR +# TYPELIB is set. +# +# GENERATE_SYMBOLS +# Output a .symbols file containing all the exported symbols. +# +# The following call is a simple example to the vala_precompile macro showing +# an example to every of the optional sections: +# +# vala_precompile(VALA_C mytargetname +# LIBRARY +# source1.vala +# source2.vala +# source3.vala +# PACKAGES +# gtk+-2.0 +# gio-1.0 +# posix +# DIRECTORY +# gen +# OPTIONS +# --thread +# CUSTOM_VAPIS +# some_vapi.vapi +# GENERATE_VAPI +# myvapi +# GENERATE_HEADER +# myheader +# GENERATE_GIR TYPELIB +# mygir +# TYPELIB_OPTIONS +# --includedir=some/dir +# GENERATE_SYMBOLS +# mysymbols +# ) +# +# Most important is the variable VALA_C which will contain all the generated c +# file names after the call. +## + +macro(vala_precompile output target_name) + parse_arguments(ARGS + "TARGET;PACKAGES;OPTIONS;TYPELIB_OPTIONS;DIRECTORY;GENERATE_GIR;GENERATE_SYMBOLS;GENERATE_HEADER;GENERATE_VAPI;CUSTOM_VAPIS;DEPENDS" + "LIBRARY" ${ARGN}) + + if(ARGS_DIRECTORY) + set(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${ARGS_DIRECTORY}) + else(ARGS_DIRECTORY) + set(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + endif(ARGS_DIRECTORY) + include_directories(${DIRECTORY}) + set(vala_pkg_opts "") + foreach(pkg ${ARGS_PACKAGES}) + list(APPEND vala_pkg_opts "--pkg=${pkg}") + endforeach(pkg ${ARGS_PACKAGES}) + set(in_files "") + set(out_files "") + set(out_files_display "") + set(${output} "") + + foreach(src ${ARGS_DEFAULT_ARGS}) + # this string(REPLACE ...) is a workaround for a strange behavior when + # the cmake binary directory is a subdirectory of the source directory + # and you include a vala source file from the cmake binary directory. + # For a yet to be determined reason, cmake deletes the generated c file + # before it is compiled, resulting in an error. We fix this by making + # any absolute path that is in the source directory a relative path. + string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/" "" src ${src}) + + string(REGEX MATCH "^/" IS_MATCHED ${src}) + if(${IS_MATCHED} MATCHES "/") + set(src_file_path ${src}) + else() + set(src_file_path ${CMAKE_CURRENT_SOURCE_DIR}/${src}) + endif() + list(APPEND in_files ${src_file_path}) + string(REPLACE ".vala" ".c" src ${src}) + string(REPLACE ".gs" ".c" src ${src}) + if(${IS_MATCHED} MATCHES "/") + get_filename_component(VALA_FILE_NAME ${src} NAME) + set(out_file "${CMAKE_CURRENT_BINARY_DIR}/${VALA_FILE_NAME}") + list(APPEND out_files "${CMAKE_CURRENT_BINARY_DIR}/${VALA_FILE_NAME}") + else() + set(out_file "${DIRECTORY}/${src}") + list(APPEND out_files "${DIRECTORY}/${src}") + endif() + list(APPEND ${output} ${out_file}) + list(APPEND out_files_display "${src}") + endforeach(src ${ARGS_DEFAULT_ARGS}) + + set(custom_vapi_arguments "") + if(ARGS_CUSTOM_VAPIS) + foreach(vapi ${ARGS_CUSTOM_VAPIS}) + if(${vapi} MATCHES ${CMAKE_SOURCE_DIR} OR ${vapi} MATCHES ${CMAKE_BINARY_DIR}) + list(APPEND custom_vapi_arguments ${vapi}) + else (${vapi} MATCHES ${CMAKE_SOURCE_DIR} OR ${vapi} MATCHES ${CMAKE_BINARY_DIR}) + list(APPEND custom_vapi_arguments ${CMAKE_CURRENT_SOURCE_DIR}/${vapi}) + endif(${vapi} MATCHES ${CMAKE_SOURCE_DIR} OR ${vapi} MATCHES ${CMAKE_BINARY_DIR}) + endforeach(vapi ${ARGS_CUSTOM_VAPIS}) + endif(ARGS_CUSTOM_VAPIS) + + set(library_arguments "") + if(ARGS_LIBRARY) + list(APPEND library_arguments "--library=${target_name}") + endif(ARGS_LIBRARY) + + set(vapi_arguments "") + if(ARGS_GENERATE_VAPI) + parse_arguments(ARGS_GENERATE_VAPI "" "INTERNAL" ${ARGS_GENERATE_VAPI}) + list(APPEND out_files "${DIRECTORY}/${ARGS_GENERATE_VAPI_DEFAULT_ARGS}.vapi") + list(APPEND out_files_display "${ARGS_GENERATE_VAPI_DEFAULT_ARGS}.vapi") + list(APPEND vapi_arguments "--vapi=${ARGS_GENERATE_VAPI_DEFAULT_ARGS}.vapi") + list(APPEND vapi_arguments "--vapi-comments") + + # Header and internal header is needed to generate internal vapi + if (NOT ARGS_GENERATE_HEADER) + set(ARGS_GENERATE_HEADER ${ARGS_GENERATE_VAPI_DEFAULT_ARGS}) + endif(NOT ARGS_GENERATE_HEADER) + + if(ARGS_GENERATE_VAPI_INTERNAL) + list(APPEND out_files "${DIRECTORY}/${ARGS_GENERATE_VAPI_DEFAULT_ARGS}_internal.vapi") + list(APPEND out_files_display "${ARGS_GENERATE_VAPI_DEFAULT_ARGS}_internal.vapi") + list(APPEND vapi_arguments "--internal-vapi=${ARGS_GENERATE_VAPI_DEFAULT_ARGS}_internal.vapi") + list(APPEND ARGS_GENERATE_HEADER "INTERNAL") + endif(ARGS_GENERATE_VAPI_INTERNAL) + endif(ARGS_GENERATE_VAPI) + + set(header_arguments "") + if(ARGS_GENERATE_HEADER) + parse_arguments(ARGS_GENERATE_HEADER "" "INTERNAL" ${ARGS_GENERATE_HEADER}) + list(APPEND out_files "${DIRECTORY}/${ARGS_GENERATE_HEADER_DEFAULT_ARGS}.h") + list(APPEND out_files_display "${ARGS_GENERATE_HEADER_DEFAULT_ARGS}.h") + list(APPEND header_arguments "--header=${ARGS_GENERATE_HEADER_DEFAULT_ARGS}.h") + if(ARGS_GENERATE_HEADER_INTERNAL) + list(APPEND out_files "${DIRECTORY}/${ARGS_GENERATE_HEADER_DEFAULT_ARGS}_internal.h") + list(APPEND out_files_display "${ARGS_GENERATE_HEADER_DEFAULT_ARGS}_internal.h") + list(APPEND header_arguments "--internal-header=${ARGS_GENERATE_HEADER_DEFAULT_ARGS}_internal.h") + endif(ARGS_GENERATE_HEADER_INTERNAL) + endif(ARGS_GENERATE_HEADER) + + set(gir_arguments "") + set(gircomp_command "") + if(ARGS_GENERATE_GIR) + parse_arguments(ARGS_GENERATE_GIR "" "TYPELIB" ${ARGS_GENERATE_GIR}) + list(APPEND out_files "${DIRECTORY}/${ARGS_GENERATE_GIR_DEFAULT_ARGS}.gir") + list(APPEND out_files_display "${ARGS_GENERATE_GIR_DEFAULT_ARGS}.gir") + list(APPEND gir_arguments "--gir=${ARGS_GENERATE_GIR_DEFAULT_ARGS}.gir") + + if(ARGS_GENERATE_GIR_TYPELIB) + include (FindGirCompiler) + find_package(GirCompiler REQUIRED) + + add_custom_command( + OUTPUT + "${DIRECTORY}/${ARGS_GENERATE_GIR_DEFAULT_ARGS}.typelib" + COMMAND + ${G_IR_COMPILER_EXECUTABLE} + ARGS + "${DIRECTORY}/${ARGS_GENERATE_GIR_DEFAULT_ARGS}.gir" + "--shared-library=$" + "--output=${DIRECTORY}/${ARGS_GENERATE_GIR_DEFAULT_ARGS}.typelib" + ${ARGS_TYPELIB_OPTIONS} + DEPENDS + "${DIRECTORY}/${ARGS_GENERATE_GIR_DEFAULT_ARGS}.gir" + COMMENT + "Genterating typelib.") + + add_custom_target("${target_name}-typelib" + ALL + DEPENDS + "${DIRECTORY}/${ARGS_GENERATE_GIR_DEFAULT_ARGS}.typelib") + endif(ARGS_GENERATE_GIR_TYPELIB) + endif(ARGS_GENERATE_GIR) + + set(symbols_arguments "") + if(ARGS_GENERATE_SYMBOLS) + list(APPEND out_files "${DIRECTORY}/${ARGS_GENERATE_SYMBOLS}.symbols") + list(APPEND out_files_display "${ARGS_GENERATE_SYMBOLS}.symbols") + set(symbols_arguments "--symbols=${ARGS_GENERATE_SYMBOLS}.symbols") + endif(ARGS_GENERATE_SYMBOLS) + + # Workaround for a bug that would make valac run twice. This file is written + # after the vala compiler generates C source code. + set(OUTPUT_STAMP ${CMAKE_CURRENT_BINARY_DIR}/${target_name}_valac.stamp) + + add_custom_command( + OUTPUT + ${OUTPUT_STAMP} + COMMAND + ${VALA_EXECUTABLE} + ARGS + "-C" + ${header_arguments} + ${library_arguments} + ${vapi_arguments} + ${gir_arguments} + ${symbols_arguments} + "-b" ${CMAKE_CURRENT_SOURCE_DIR} + "-d" ${DIRECTORY} + ${vala_pkg_opts} + ${ARGS_OPTIONS} + "--debug" + ${in_files} + ${custom_vapi_arguments} + COMMAND + touch + ARGS + ${OUTPUT_STAMP} + DEPENDS + ${in_files} + ${ARGS_CUSTOM_VAPIS} + ${ARGS_DEPENDS} + COMMENT + "Generating ${out_files_display}" + ) + + # This command will be run twice for some reason (pass a non-empty string to COMMENT + # in order to see it). Since valac is not executed from here, this won't be a problem. + add_custom_command(OUTPUT ${out_files} DEPENDS ${OUTPUT_STAMP} COMMENT "") +endmacro(vala_precompile) diff --git a/cmake/ValaVersion.cmake b/cmake/ValaVersion.cmake new file mode 100644 index 00000000..3fff193f --- /dev/null +++ b/cmake/ValaVersion.cmake @@ -0,0 +1,96 @@ +## +# Copyright 2009-2010 Jakob Westhoff. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY JAKOB WESTHOFF ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +# EVENT SHALL JAKOB WESTHOFF OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# The views and conclusions contained in the software and documentation are those +# of the authors and should not be interpreted as representing official policies, +# either expressed or implied, of Jakob Westhoff +## + +include(ParseArguments) +find_package(Vala REQUIRED) + +## +# Ensure a certain valac version is available +# +# The initial argument is the version to check for +# +# It may be followed by a optional parameter to specifiy a version range. The +# following options are valid: +# +# EXACT +# Vala needs to be available in the exact version given +# +# MINIMUM +# The provided version is the minimum version. Therefore Vala needs to be +# available in the given version or any higher version +# +# MAXIMUM +# The provided version is the maximum. Therefore Vala needs to be available +# in the given version or any version older than this +# +# If no option is specified the version will be treated as a minimal version. +## +macro(ensure_vala_version version) + parse_arguments(ARGS "" "MINIMUM;MAXIMUM;EXACT" ${ARGN}) + set(compare_message "") + set(error_message "") + if(ARGS_MINIMUM) + set(compare_message "a minimum ") + set(error_message "or greater ") + elseif(ARGS_MAXIMUM) + set(compare_message "a maximum ") + set(error_message "or less ") + endif(ARGS_MINIMUM) + + message(STATUS + "checking for ${compare_message}Vala version of ${version}" + ) + + unset(version_accepted) + + # MINIMUM is the default if no option is specified + if(ARGS_EXACT) + if(${VALA_VERSION} VERSION_EQUAL ${version} ) + set(version_accepted TRUE) + endif(${VALA_VERSION} VERSION_EQUAL ${version}) + elseif(ARGS_MAXIMUM) + if(${VALA_VERSION} VERSION_LESS ${version} OR ${VALA_VERSION} VERSION_EQUAL ${version}) + set(version_accepted TRUE) + endif(${VALA_VERSION} VERSION_LESS ${version} OR ${VALA_VERSION} VERSION_EQUAL ${version}) + else(ARGS_MAXIMUM) + if(${VALA_VERSION} VERSION_GREATER ${version} OR ${VALA_VERSION} VERSION_EQUAL ${version}) + set(version_accepted TRUE) + endif(${VALA_VERSION} VERSION_GREATER ${version} OR ${VALA_VERSION} VERSION_EQUAL ${version}) + endif(ARGS_EXACT) + + if (NOT version_accepted) + message(FATAL_ERROR + "Vala version ${version} ${error_message}is required." + ) + endif(NOT version_accepted) + + message(STATUS + " found Vala, version ${VALA_VERSION}" + ) +endmacro(ensure_vala_version) diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 00000000..72b7c148 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,11 @@ +miraclecast (1.0+r67) experimental; urgency=medium + + * Jenkins Auto Build + + -- Wed, 04 Jan 2017 10:27:21 +0000 + +miraclecast (1.0) unstable; urgency=medium + + * Initial release + + -- Deepin Packages Builder Mon, 05 Dec 2016 13:43:00 +0800 diff --git a/debian/compat b/debian/compat new file mode 100644 index 00000000..ec635144 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 00000000..fbe77456 --- /dev/null +++ b/debian/control @@ -0,0 +1,12 @@ +Source: miraclecast +Section: utils +Priority: optional +Maintainer: Deepin Packages Builder +Build-Depends: debhelper (>= 9), cmake, check, valac, vala-dbus-binding-tool, systemd, libsystemd-dev, libglib2.0-bin, libgtk-3-dev, libgstreamer1.0-dev, libreadline-dev, libudev-dev +Standards-Version: 3.9.8 +Homepage: https://github.com/derekdai/miraclecast + +Package: miraclecast +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, libsystemd0, libudev1, libglib2.0-0, libgstreamer1.0-0, gstreamer1.0-pulseaudio, gstreamer1.0-vaapi (>= 1.10.2), gstreamer1.0-plugins-good, gstreamer1.0-plugins-bad, gstreamer1.0-plugins-ugly, mesa-va-drivers, i965-va-driver, vdpau-va-driver +Description: Connect external monitors to your system via Wifi-Display specification also known as Miracast diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 00000000..235ca9b8 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,38 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: miraclecast +Source: + +Files: * +Copyright: + +License: + + + . + + +# If you want to use GPL v2 or later for the /debian/* files use +# the following clauses, or change it to suit. Delete these two lines +Files: debian/* +Copyright: 2016 Deepin Packages Builder +License: GPL-2+ + This package is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + . + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see + . + On Debian systems, the complete text of the GNU General + Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". + +# Please also look if there are files or directories which have a +# different copyright/license attached and list them here. +# Please avoid picking licenses with terms that are more restrictive than the +# packaged work, as it may make Debian's contributions unacceptable upstream. diff --git a/debian/postinst b/debian/postinst new file mode 100644 index 00000000..9b76642f --- /dev/null +++ b/debian/postinst @@ -0,0 +1 @@ +systemctl daemon-reload diff --git a/debian/postrm b/debian/postrm new file mode 100644 index 00000000..9b76642f --- /dev/null +++ b/debian/postrm @@ -0,0 +1 @@ +systemctl daemon-reload diff --git a/debian/rules b/debian/rules new file mode 100755 index 00000000..0182ac85 --- /dev/null +++ b/debian/rules @@ -0,0 +1,22 @@ +#!/usr/bin/make -f +# See debhelper(7) (uncomment to enable) +# output every command that modifies files on the build system. +#export DH_VERBOSE = 1 + + +# see FEATURE AREAS in dpkg-buildflags(1) +#export DEB_BUILD_MAINT_OPTIONS = hardening=+all + +# see ENVIRONMENT in dpkg-buildflags(1) +# package maintainers to append CFLAGS +#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic +# package maintainers to append LDFLAGS +#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed + + +%: + dh $@ + + +# dh_make generated override targets +# This is example for Cmake (See https://bugs.debian.org/641051 ) diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 00000000..89ae9db8 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/meson.build b/meson.build index 84b20c34..4b064f0f 100644 --- a/meson.build +++ b/meson.build @@ -52,4 +52,3 @@ subdir('res') if get_option('build-tests') subdir('test') endif - diff --git a/res/CMakeLists.txt b/res/CMakeLists.txt index e67277eb..5e77eec1 100644 --- a/res/CMakeLists.txt +++ b/res/CMakeLists.txt @@ -1,14 +1,130 @@ -INSTALL( - PROGRAMS miracle-gst gstplayer uibc-viewer - DESTINATION bin - ) +include(ValaPrecompile) +pkg_check_modules(GIO2 REQUIRED gio-2.0) +pkg_check_modules(GDK3 REQUIRED gdk-3.0) +pkg_check_modules(GST1 REQUIRED gstreamer-1.0) -INSTALL( - FILES org.freedesktop.miracle.conf - DESTINATION ${SYSCONFDIR}/dbus-1/system.d - ) +include_directories( + ${GST1_INCLUDE_DIRS} + ${GDK3_INCLUDE_DIRS} + ${GIO_INCLUDE_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR} + ) + +set(CMAKE_C_FLAGS "-Wno-deprecated-declarations ${CMAKE_C_FLAGS}") +set(CMAKE_C_FLAGS "-Wno-unused-but-set-variable ${CMAKE_C_FLAGS}") +set(CMAKE_C_FLAGS "-Wno-missing-braces ${CMAKE_C_FLAGS}") +if(VALA_VERSION VERSION_GREATER 0.34.0 AND NOT GDK_VERSION_NEWER_THEN_3_22) + list(APPEND VALA_EXTRA_OPTIONS -D GDK3_HAS_MONITOR_CLASS) +endif() + +vala_precompile( + GSTENCODER_SRC gstencoder + gstencoder.vala + OPTIONS + --target-glib=2.50 + PACKAGES + gstreamer-1.0 + gio-2.0 + posix + ) +add_executable(gstencoder ${GSTENCODER_SRC}) +target_link_libraries( + gstencoder + ${GST1_LIBRARIES} + ${GDK3_LIBRARIES} + ${GIO2_LIBRARIES} + ) + +vala_precompile( + DISPCTL_SRC dispctl + dispctl.vala + networkmanager.vala + miracle-wifi.vala + miracle-wfd.vala + GENERATE_HEADER + dispctl.h + CUSTOM_VAPIS + sigint.vapi + OPTIONS + --target-glib=2.50 + ${VALA_EXTRA_OPTIONS} + PACKAGES + gio-2.0 + gdk-3.0 + ) +add_executable(miracle-dispctl ${DISPCTL_SRC} sigint.c) +target_link_libraries(miracle-dispctl ${GIO2_LIBRARIES} ${GDK3_LIBRARIES}) + +########### install files ############### + +install(TARGETS gstencoder DESTINATION bin) + +install(TARGETS miracle-dispctl DESTINATION bin) + +execute_process( + COMMAND ${PKG_CONFIG_EXECUTABLE} --variable=system_bus_services_dir dbus-1 + OUTPUT_VARIABLE DBUS_SYSTEM_SERVICES_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +execute_process( + COMMAND ${PKG_CONFIG_EXECUTABLE} --variable=systemdsystemunitdir systemd + OUTPUT_VARIABLE SYSTEMD_SYSTEM_UNIT_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + +configure_file( + miracle-wifid.service.cmake + miracle-wifid.service + ) +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/miracle-wifid.service + DESTINATION ${SYSTEMD_SYSTEM_UNIT_DIR}/ + ) +install( + CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink miracle-wifid.service \$ENV{DESTDIR}${SYSTEMD_SYSTEM_UNIT_DIR}/dbus-org.freedesktop.miracle.wifi.service)" + ) + +configure_file( + miracle-dispd.service.cmake + miracle-dispd.service + ) +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/miracle-dispd.service + DESTINATION ${SYSTEMD_SYSTEM_UNIT_DIR}/ + ) +install( + CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink miracle-dispd.service \$ENV{DESTDIR}${SYSTEMD_SYSTEM_UNIT_DIR}/dbus-org.freedesktop.miracle.wfd.service)" + ) + +configure_file( + org.freedesktop.miracle.wifi.service.cmake + org.freedesktop.miracle.wifi.service + ) +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/org.freedesktop.miracle.wifi.service + DESTINATION $ENV{DESTDIR}${DBUS_SYSTEM_SERVICES_DIR}/ + ) + +configure_file( + org.freedesktop.miracle.wfd.service.cmake + org.freedesktop.miracle.wfd.service + ) +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/org.freedesktop.miracle.wfd.service + DESTINATION $ENV{DESTDIR}${DBUS_SYSTEM_SERVICES_DIR}/ + ) + +install( + PROGRAMS miracle-gst gstplayer uibc-viewer + DESTINATION bin + ) + +install( + FILES org.freedesktop.miracle.conf + DESTINATION ${SYSCONFDIR}/dbus-1/system.d + ) -INSTALL( +install( FILES miracle-wifid miracle-sinkctl miracle-wifictl DESTINATION ${DATADIR}/bash-completion/completions ) diff --git a/res/create-symlinks b/res/create-symlinks new file mode 100755 index 00000000..f0d92c6d --- /dev/null +++ b/res/create-symlinks @@ -0,0 +1,6 @@ +#!/bin/sh +SYSTEMD_SYSTEM_UNIT_DIR=${DESTDIR}`pkg-config --variable=systemdsystemunitdir systemd` +ln -sfv miracle-wifid.service \ + ${SYSTEMD_SYSTEM_UNIT_DIR}/dbos-org.freedesktop.miracle.wifi.service +ln -sfv miracle-dispd.service \ + ${SYSTEMD_SYSTEM_UNIT_DIR}/dbos-org.freedesktop.miracle.wfd.service diff --git a/res/dispctl.vala b/res/dispctl.vala new file mode 100644 index 00000000..da94876c --- /dev/null +++ b/res/dispctl.vala @@ -0,0 +1,824 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * 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 . + */ + +using Org.Freedesktop.NetworkManager; +using Org.Freedesktop.Miracle.Wifi; +using Org.Freedesktop.Miracle.Wfd; + +const string BUS_NAME_NETWORK_MANAGER = "org.freedesktop.NetworkManager"; +const string BUS_NAME_WIFID = "org.freedesktop.miracle.wifi"; +const string BUS_NAME_DISPD = "org.freedesktop.miracle.wfd"; +const string OBJ_PATH_DEVICE = "/org/freedesktop/NetworkManager/Devices"; +const string OBJ_PATH_LINK = "/org/freedesktop/miracle/wifi/link"; +const string OBJ_PATH_PEER = "/org/freedesktop/miracle/wifi/peer"; +const string OBJ_PATH_SINK = "/org/freedesktop/miracle/wfd/sink"; +const string OBJ_PATH_SESSION = "/org/freedesktop/miracle/wfd/session"; +const string IFACE_DEVICE = "org.freedesktop.NetworkManager.Device"; +const string IFACE_LINK = "org.freedesktop.miracle.wifi.Link"; +const string IFACE_PEER = "org.freedesktop.miracle.wifi.Peer"; +const string IFACE_SINK = "org.freedesktop.miracle.wfd.Sink"; +const string IFACE_SESSION = "org.freedesktop.miracle.wfd.Session"; + +errordomain DispCtlError +{ + NO_SUCH_NIC, + TIMEOUT, + MONITOR_GONE, + FORMATION_ERROR, + NO_P2P_SUPPORT, +} + +private void print(string format, ...) +{ + stderr.printf("%s: ", Environment.get_prgname()); + stderr.vprintf(format, va_list()); + stderr.printf("\n"); +} + +// to deal with sd_bus_path_encode/decode()ed path +private string decode_path(string s) +{ + char c; + StringBuilder d = new StringBuilder(); + for(var i = 0; i < s.length; i ++) { + if(s.data[i] == (uint8) '_') { + c = (char) s.substring(i + 1, 2).to_long(null, 16); + i += 2; + } + else { + c = (char) s.data[i]; + } + d.append_c(c); + } + + return d.str; +} + +private class DispCtl : GLib.Application +{ + static string opt_iface; + static string opt_wfd_subelems; + static string opt_peer_mac; + static string opt_display; + static string opt_authority; + static int opt_monitor_num; + static string opt_audio_device; + static bool opt_dont_borrow_wnic = false; + static bool opt_dont_return_wnic = false; + + protected signal void link_added(string index, Link link); + protected signal void link_removed(string index, Link link); + protected signal void peer_added(string label, Peer peer); + protected signal void peer_removed(string label, Peer peer); + protected signal void sink_added(string label, Sink sink); + protected signal void sink_removed(string label, Sink sink); + protected signal void session_added(string id, Session session); + protected signal void session_removed(string id, Session session); + + DBusObjectManagerClient nm; + DBusObjectManagerClient wifi; + DBusObjectManagerClient wfd; + + HashTable devices; + HashTable links; + HashTable peers; + HashTable sinks; + HashTable sessions; + + string curr_sink_mac; + Gdk.Display display; + Peer curr_peer; + Sink curr_sink; + Session curr_session; + + Cancellable cancellable; + + const GLib.OptionEntry[] option_entries = { + { "interface", 'i', 0, OptionArg.STRING, ref opt_iface, "name of wireless network interface", "WNIC name" }, + { "wfd-subelems", 'w', 0, OptionArg.STRING, ref opt_wfd_subelems, "device infomation. default: 000600901c4400c8", "device info subelems" }, + { "peer-mac", 'p', 0, OptionArg.STRING, ref opt_peer_mac, "MAC address of target peer", "peer MAC" }, + { "authority", 'x', 0, OptionArg.STRING, ref opt_authority, "authority to capture from display. default: XAUTHORITY environment variable", "display authority" }, + { "display", 'd', 0, OptionArg.STRING, ref opt_display, "display name. default: DISPLAY environment variable", "display name" }, + { "monitor-num", 'm', 0, OptionArg.INT, ref opt_monitor_num, "monitor number. default: -1, primary monitor", "monitor number" }, + { "audio-device", 'a', 0, OptionArg.STRING, ref opt_audio_device, "pulseaudio device name", "audio device name" }, + { "dont-borrow", 'b', 0, OptionArg.NONE, ref opt_dont_borrow_wnic, "do not acquire the ownership of WNIC before using it", "don't borrow WNIC" }, + { "dont-return", 'r', 0, OptionArg.NONE, ref opt_dont_return_wnic, "do not release the ownership of WNIC after using it", "don't release WNIC" }, + { null }, + }; + + public DispCtl() + { + Object(application_id: "org.freedesktop.miracle.DispCtl", + flags: ApplicationFlags.FLAGS_NONE); + + devices = new HashTable(str_hash, str_equal); + links = new HashTable(str_hash, str_equal); + peers = new HashTable(str_hash, str_equal); + sinks = new HashTable(str_hash, str_equal); + sessions = new HashTable(str_hash, str_equal); + + cancellable = new Cancellable(); + + add_main_option_entries(option_entries); + } + + private DBusProxy? add_object(string path) throws Error + { + int sep = path.last_index_of_char('/'); + string prefix = path.substring(0, sep); + string key = path.substring(sep + 1); + switch(prefix) { + case OBJ_PATH_DEVICE: + Device d = Bus.get_proxy_sync(BusType.SYSTEM, + BUS_NAME_NETWORK_MANAGER, + path); + if(is_wnic(d.interface) && !devices.contains(d.interface)) { + devices.insert(d.interface, d); + return d as DBusProxy; + } + break; + case OBJ_PATH_LINK: + key = decode_path(key); + Link l = links.lookup(key); + if(null == l) { + l = Bus.get_proxy_sync(BusType.SYSTEM, + BUS_NAME_WIFID, + path); + links.insert(key, l); + info("found wireless interface: %s", l.interface_name); + link_added(key, l); + } + return l as DBusProxy; + case OBJ_PATH_PEER: + key = decode_path(key); + Peer p = peers.lookup(key); + if(null == p) { + p = Bus.get_proxy_sync(BusType.SYSTEM, + BUS_NAME_WIFID, + path); + peers.insert(key, p); + info("peer added: %s (%s)", key, p.friendly_name); + peer_added(key, p); + } + return p as DBusProxy; + case OBJ_PATH_SINK: + key = decode_path(key); + Sink s = sinks.lookup(key); + if(null == s) { + s = Bus.get_proxy_sync(BusType.SYSTEM, + BUS_NAME_DISPD, + path); + sinks.insert(key, s); + info("sink added: %s", key); + sink_added(key, s); + } + return s as DBusProxy; + case OBJ_PATH_SESSION: + key = decode_path(key); + Session s = sessions.lookup(key); + if(null == s) { + s = Bus.get_proxy_sync(BusType.SYSTEM, + BUS_NAME_DISPD, + path); + sessions.insert(key, s); + info("session added: %s", key); + session_added(key, s); + } + return s as DBusProxy; + } + + return null; + } + + private void remove_object(string path) + { + int sep = path.last_index_of_char('/'); + string prefix = path.substring(0, sep); + string key = path.substring(sep + 1); + switch(prefix) { + case OBJ_PATH_DEVICE: + devices.remove(key); + break; + case OBJ_PATH_LINK: + key = decode_path(key); + Link l = links.lookup(key); + if(null == l) { + break; + } + links.remove(key); + link_removed(key, l); + break; + case OBJ_PATH_PEER: + key = decode_path(key); + Peer p = peers.lookup(key); + if(null == p) { + info("removing stray peer: %s", key); + break; + } + if(p == curr_peer) { + curr_peer = null; + } + peers.remove(key); + peer_removed(key, p); + break; + case OBJ_PATH_SINK: + key = decode_path(key); + Sink s = sinks.lookup(key); + if(null == s) { + info("removing stray sink: %s", key); + break; + } + if(s == curr_sink) { + curr_sink = null; + } + sinks.remove(key); + sink_removed(key, s); + break; + case OBJ_PATH_SESSION: + key = decode_path(key); + Session s = sessions.lookup(key); + if(null == s) { + info("removing stray session: %s", key); + break; + } + session_removed(key, s); + sessions.remove(key); + break; + } + } + + private void on_object_added(DBusObjectManager m, DBusObject o) + { + try { + add_object(o.get_object_path()); + } + catch(Error e) { + print("failed to fetch information from DBus for object: %s", + o.get_object_path()); + } + } + + private void on_object_removed(DBusObjectManager m, DBusObject o) + { + remove_object(o.get_object_path()); + } + + private void fetch_info_from_dbus() throws Error + { + info("connecting to wifid..."); + wifi = new DBusObjectManagerClient.for_bus_sync( + BusType.SYSTEM, + DBusObjectManagerClientFlags.NONE, + BUS_NAME_WIFID, + "/org/freedesktop/miracle/wifi", + null, + null); + wifi.object_added.connect(on_object_added); + wifi.object_removed.connect(on_object_removed); + + info("connecting to nm..."); + nm = new DBusObjectManagerClient.for_bus_sync( + BusType.SYSTEM, + DBusObjectManagerClientFlags.NONE, + BUS_NAME_NETWORK_MANAGER, + "/org/freedesktop", + null, + null); + nm.object_added.connect(on_object_added); + nm.object_removed.connect(on_object_removed); + + info("connecting to dispd..."); + wfd = new DBusObjectManagerClient.for_bus_sync( + BusType.SYSTEM, + DBusObjectManagerClientFlags.NONE, + BUS_NAME_DISPD, + "/org/freedesktop/miracle/wfd", + null, + null); + wfd.object_added.connect(on_object_added); + wfd.object_removed.connect(on_object_removed); + + info("fetching from wifid..."); + foreach(var o in wifi.get_objects()) { + add_object(o.get_object_path()); + } + + info("fetching from nm..."); + foreach(var o in nm.get_objects()) { + add_object(o.get_object_path()); + } + + info("fetching from wfd..."); + foreach(var o in wfd.get_objects()) { + add_object(o.get_object_path()); + } + } + + private async void acquire_wnic_ownership() throws Error + { + Device d = find_device_by_name(opt_iface); + if(null != d && d.managed) { + info("NetworkManager is releasing ownership of %s...", opt_iface); + + d.managed = false; + yield wait_prop_changed(d, "Managed"); + } + + Link l = find_link_by_name(opt_iface); + if(null == l) { + throw new DispCtlError.NO_SUCH_NIC("no such wireless adapter: %s", + opt_iface); + } + + if(l.managed) { + info("wifid is releasing ownership of %s...", opt_iface); + l.unmanage(); + yield wait_prop_changed(l, "Managed"); + } + + info("wifid is acquiring ownership of %s...", opt_iface); + l.manage(); + yield wait_prop_changed(l, "Managed"); + } + + private async void start_p2p_scan() throws Error + { + Link? l = find_link_by_name(opt_iface); + if(l.wfd_subelements != opt_wfd_subelems) { + info("update wfd_subelems to broadcast what kind of device we are"); + + l.wfd_subelements = opt_wfd_subelems; + yield wait_prop_changed(l, "WfdSubelements"); + } + + if(-1 == l.p2p_state) { + throw new DispCtlError.NO_P2P_SUPPORT("link %s has no P2P supporting", l.interface_name); + } + else if(0 == l.p2p_state) { + info("wait for P2P supporting status..."); + yield wait_prop_changed(l, "P2PState", 3); + } + + if(!l.p2p_scanning) { + info("start P2P scanning..."); + l.p2p_scanning = true; + yield wait_prop_changed(l, "P2PScanning"); + } + + print("wait for peer '%s'...", opt_peer_mac); + } + + private async void wait_for_target_sink() throws Error + { + if(null != find_sink_by_mac(opt_peer_mac)) { + return; + } + + ulong id = sink_added.connect((l, s) => { + if(null != find_sink_by_mac(opt_peer_mac)) { + wait_for_target_sink.callback(); + } + }); + var cancel_id = cancellable.cancelled.connect(() => { + Idle.add(wait_for_target_sink.callback); + }); + + yield; + + cancellable.disconnect(cancel_id); + disconnect(id); + } + + private async void form_p2p_group() throws Error + { + if(null != curr_sink_mac) { + print("already hang out with sink: %s", curr_sink_mac); + return; + } + + Sink s = find_sink_by_mac(opt_peer_mac); + curr_sink_mac = opt_peer_mac; + + string l = s.peer; + l = decode_path(l.substring(l.last_index_of_char('/') + 1)); + Peer p = peers.lookup(l); + + info("forming P2P group with %s (%s)...", p.p2p_mac, p.friendly_name); + + ulong id = p.formation_failure.connect((r) => { + info("failed to form P2P group: %s", r); + }); + p.connect("auto", ""); + yield wait_prop_changed(p, "Connected", 20); + + (p as Object).disconnect(id); + + curr_peer = p; + + info("P2P group formed"); + } + +#if GDK3_HAS_MONITOR_CLASS + private void get_monitor_geometry(out Gdk.Rectangle g) throws Error + { + Gdk.Monitor m; + if(-1 == opt_monitor_num) { + m = display.get_primary_monitor(); + } + else { + m = display.get_monitor(opt_monitor_num); + } + + if(null == m) { + throw new DispCtlError.MONITOR_GONE("specified monitor disappeared"); + } + + g = m.geometry; + } +#else + private void get_monitor_geometry(out Gdk.Rectangle g) throws Error + { + var s = display.get_default_screen(); + int m = (-1 == opt_monitor_num) + ? s.get_primary_monitor() + : opt_monitor_num; + + if(s.get_n_monitors() <= m) { + throw new DispCtlError.MONITOR_GONE("specified monitor disappeared"); + } + + s.get_monitor_geometry(m, out g); + } +#endif + + private unowned string session_state_to_str(int s) + { + switch(s) { + case 1: + return "connecting"; + case 2: + return "capabilities exchanging"; + case 3: + return "established"; + case 4: + return "seting up session parameters"; + case 5: + return "paused"; + case 6: + return "playing"; + case 7: + return "tearing down"; + case 8: + return "destroyed"; + } + + return "unknown"; + } + + private async void establish_session() throws Error + { + Gdk.Rectangle g; + get_monitor_geometry(out g); + + info("establishing display session..."); + + curr_sink = find_sink_by_mac(opt_peer_mac); + string path = curr_sink.start_session(opt_authority, + @"x://$(opt_display)", + g.x, + g.y, + g.width, + g.height, + null == opt_audio_device ? "" : opt_audio_device); + curr_session = add_object(path) as Session; + + var prop_change_id = (curr_session as DBusProxy).g_properties_changed.connect((props) => { + string k; + Variant v; + foreach(var prop in props) { + prop.get("{sv}", out k, out v); + if(k != "State") { + continue; + } + + info("session status: %s", session_state_to_str(v.get_int32())); + + if(6 == v.get_int32()) { + Idle.add(establish_session.callback); + } + + break; + } + }); + cancellable.set_error_if_cancelled(); + var cancel_id = cancellable.cancelled.connect(() => { + Idle.add(establish_session.callback); + }); + bool timed_out = false; + var timeout_src = new TimeoutSource(10); + timeout_src.set_callback(() => { + timed_out = true; + Idle.add(establish_session.callback); + return false; + }); + + yield; + + timeout_src.destroy(); + cancellable.disconnect(cancel_id); + (curr_session as DBusProxy).disconnect(prop_change_id); + + if(timed_out) { + throw new DispCtlError.TIMEOUT("failed to establish session"); + } + } + + private async void wait_for_session_ending() throws Error + { + info("wait for session ending"); + ulong id = session_removed.connect((id, s) => { + wait_for_session_ending.callback(); + }); + + yield; + + disconnect(id); + + info("session ended"); + } + + private async void release_wnic_ownership() throws Error + { + if(opt_dont_return_wnic) { + return; + } + + cancellable.reset(); + + Link l = find_link_by_name(opt_iface); + if(null == l) { + throw new DispCtlError.NO_SUCH_NIC("no such wireless adapter: %s", + opt_iface); + } + + if(l.managed) { + info("wifid is releasing ownership of %s...", opt_iface); + l.unmanage(); + yield wait_prop_changed(l, "Managed"); + } + + Device d = find_device_by_name(opt_iface); + if(null != d && !d.managed) { + info("NetworkManager is acquiring ownership of %s...", opt_iface); + d.managed = true; + yield wait_prop_changed(d, "Managed"); + } + } + + private async void start_wireless_display() throws Error + { + fetch_info_from_dbus(); + cancellable.set_error_if_cancelled(); + if(!opt_dont_borrow_wnic) { + yield acquire_wnic_ownership(); + } + cancellable.set_error_if_cancelled(); + yield start_p2p_scan(); + cancellable.set_error_if_cancelled(); + yield wait_for_target_sink(); + cancellable.set_error_if_cancelled(); + yield form_p2p_group(); + cancellable.set_error_if_cancelled(); + yield establish_session(); + cancellable.set_error_if_cancelled(); + yield wait_for_session_ending(); + cancellable.set_error_if_cancelled(); + } + + public void stop_wireless_display() + { + info("received termination request"); + + if(null != curr_session) { + info("tearing down wireless display..."); + + try { + curr_session.teardown(); + wait_prop_changed.begin(curr_sink, "Session", 3, () => { + cancellable.cancel(); + }); + } + catch(Error e) { + warning("failed to tearing down normally: %s", e.message); + } + } + else { + cancellable.cancel(); + } + } + + private bool check_options() + { + if(null == opt_peer_mac) { + print("please specify a peer MAC with -p option"); + return false; + } + print("peer-mac=%s", opt_peer_mac); + + if(null == opt_display) { + opt_display = Environment.get_variable("DISPLAY"); + } + print("display=%s", opt_display); + + if(null == opt_authority) { + opt_authority = Environment.get_variable("XAUTHORITY"); + } + print("authority=%s", opt_authority); + + if(null == opt_iface) { + opt_iface = "wlan0"; + } + print("interface=%s", opt_iface); + + if(null == opt_wfd_subelems) { + opt_wfd_subelems = "000600901c4400c8"; + } + print("wfd_subelemens=%s", opt_wfd_subelems); + + display = Gdk.Display.open(opt_display); + if(null == display) { + print("invalid display option: %s", opt_display); + return false; + } + + int n_monitors; +#if GDK3_HAS_MONITOR_CLASS + n_monitors = display.get_n_monitors(); +#else + n_monitors = display.get_default_screen().get_n_monitors(); +#endif + if(-1 > opt_monitor_num || opt_monitor_num >= n_monitors) { + print("invalid screen number option: %d", opt_monitor_num); + return false; + } + print("monitor-num=%d", opt_monitor_num); + + return true; + } + + protected override void activate() + { + if(!check_options()) { + return; + } + + start_wireless_display.begin((o, r) => { + try { + start_wireless_display.end(r); + } + catch(Error e) { + print("failed to cast to wireless display: %s", e.message); + } + + release_wnic_ownership.begin((o, r) => { + try { + release_wnic_ownership.end(r); + } + catch(Error e) { + print("failed to release ownership of wnic: %s", e.message); + } + + release(); + }); + }); + + hold(); + } + + private unowned Device? find_device_by_name(string nic_name) + { + foreach(var d in devices.get_values()) { + if(nic_name == d.interface) { + return d; + } + } + + return null; + } + + private unowned Link? find_link_by_name(string nic_name) + { + foreach(var l in links.get_values()) { + if(nic_name == l.interface_name) { + return l; + } + } + + return null; + } + + private unowned Sink? find_sink_by_mac(string m) + { + foreach(var l in sinks.get_keys()) { + if(l.has_prefix(m)) { + return sinks.lookup(l); + } + } + + return null; + } + + private bool is_wnic(string nic_name) + { + return find_link_by_name(nic_name) != null; + } + + private async void wait_prop_changed(T o, + string name, + uint timeout = 1) throws Error + { + ulong prop_changed_id = (o as DBusProxy).g_properties_changed.connect((props) => { + string k; + Variant v; + foreach(var prop in props) { + prop.get("{sv}", out k, out v); + if(k == name) { + wait_prop_changed.callback(); + break; + } + } + }); + var cancel_id = cancellable.cancelled.connect(() => { + Idle.add(wait_prop_changed.callback); + }); + bool timed_out = false; + Source timeout_src = null; + if(0 < timeout) { + timeout_src = new TimeoutSource.seconds(timeout); + timeout_src.set_callback(() => { + timed_out = true; + wait_prop_changed.callback(); + return false; + }); + timeout_src.attach(null); + } + + yield; + + cancellable.disconnect(cancel_id); + if(null != timeout_src) { + timeout_src.destroy(); + } + (o as DBusProxy).disconnect(prop_changed_id); + + if(timed_out) { + throw new DispCtlError.TIMEOUT("timeout to wait for property %s change", + name); + } + + cancellable.set_error_if_cancelled(); + } +} + +int main(string[]? argv) +{ + Gdk.init(ref argv); + Intl.setlocale(); + Environment.set_prgname(Path.get_basename(argv[0])); + + + Application app = new DispCtl(); + Application.set_default(app); + + Sigint.add_watch((app as DispCtl).stop_wireless_display); + + try { + app.register(); + } + catch(Error e) { + print("failed to startup: %s", e.message); + return 1; + } + + int r = app.run(argv); + + print("Bye"); + + return r; +} diff --git a/res/gstencoder.vala b/res/gstencoder.vala new file mode 100644 index 00000000..9f83bbc0 --- /dev/null +++ b/res/gstencoder.vala @@ -0,0 +1,426 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * 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 . + */ +public enum DispdEncoderConfig +{ + DISPLAY_TYPE, /* string */ + DISPLAY_NAME, /* string */ + MONITOR_NUM, /* uint32 */ + X, /* uint32 */ + Y, /* uint32 */ + WIDTH, /* uint32 */ + HEIGHT, /* uint32 */ + WINDOW_ID, /* uint32 */ + FRAMERATE, /* uint32 */ + SCALE_WIDTH, /* uint32 */ + SCALE_HEIGHT, /* uint32 */ + AUDIO_TYPE, /* string */ + AUDIO_DEV, /* string */ + PEER_ADDRESS, /* string */ + RTP_PORT0, /* uint32 */ + RTP_PORT1, /* uint32 */ + PEER_RTCP_PORT, /* uint32 */ + LOCAL_ADDRESS, /* uint32 */ + LOCAL_RTCP_PORT, /* uint32 */ + H264_PROFILE, + H264_LEVEL, + DEBUG_LEVEL, +} + +[DBus (name = "org.freedesktop.miracle.encoder.error")] +public errordomain DispdEncoderError +{ + CANT_RETURN_UNIQUE_NAME, + UNEXPECTED_EOS, + ENCODER_ERROR, + INVALID_STATE, +} + +[DBus(name = "org.freedesktop.miracle.encoder.state")] +public enum DispdEncoderState +{ + NULL, + CONFIGURED, + READY, + STARTED, + PAUSED, +} + +[DBus(name = "org.freedesktop.miracle.encoder")] +public interface DispdEncoder : GLib.Object +{ + public const string OBJECT_PATH = "/org/freedesktop/miracle/encoder"; + + public abstract DispdEncoderState state { get; protected set; } + public abstract signal void error(string reason); + + public abstract void configure(HashTable configs) throws DispdEncoderError; + public abstract void start() throws DispdEncoderError; + public abstract void pause() throws DispdEncoderError; + public abstract void stop() throws DispdEncoderError; +} + +internal class GstEncoder : DispdEncoder, GLib.Object +{ + private DBusConnection conn; + private HashTable configs; + private Gst.Element pipeline; + private Gst.State pipeline_state = Gst.State.NULL; + private DispdEncoderState _state = DispdEncoderState.NULL; + + public DispdEncoderState state { + get { return _state; } + protected set { + if(_state == value) { + return; + } + + _state = value; + notify_state_changed(); + } + } + + private void notify_state_changed() + { + var builder = new VariantBuilder(VariantType.ARRAY); + var invalid_builder = new VariantBuilder(new VariantType ("as")); + + Variant s = state; + builder.add ("{sv}", "State", s); + + try { + conn.emit_signal(null, + "/org/freedesktop/miracle/encoder", + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + new Variant ("(sa{sv}as)", + "org.freedesktop.miracle.encoder", + builder, + invalid_builder)); + } + catch (Error e) { + warning("failed to emit signal: %s", e.message); + } + } + + string gen_scaler_n_converter_desc(uint32 width, uint32 height) + { + if(null != Gst.ElementFactory.find("vaapih264enc")) { + return ("! vaapipostproc " + + "scale-method=2 " + + "format=3 " + + "force-aspect-ratio=true " + + "! video/x-raw, " + + "format=YV12, " + + "width=%u, " + + "height=%u ").printf(width, height); + } + + info("since vaapih264enc is not available, vaapipostproc can't be " + + "trusted, use videoscale+videoconvert instead"); + + return ("! videoscale method=0 add-borders=true " + + "! video/x-raw, width=%u, height=%u " + + "! videoconvert " + + "! video/x-raw, format=YV12 ").printf(width, height); + } + + string gen_encoder_desc(uint32 framerate) + { + if(null != Gst.ElementFactory.find("vaapih264enc")) { + return ("! vaapih264enc " + + "rate-control=1 " + + "num-slices=1 " + /* in WFD spec, one slice per frame */ + "max-bframes=0 " + /* in H264 CHP, no bframe supporting */ + "cabac=true " + /* in H264 CHP, CABAC entropy codeing is supported, but need more processing to decode */ + "dct8x8=true " + /* in H264 CHP, DTC is supported */ + "cpb-length=1000 " + /* shortent buffer in order to decrease latency */ + "keyframe-period=%u ").printf(framerate); + } + + info("vaapih264enc not available, use x264enc instead"); + + return ("! x264enc pass=4 b-adapt=false key-int-max=%u " + + "speed-preset=4 tune=4 ").printf(framerate); + } + + public void configure(HashTable configs) throws DispdEncoderError + { + uint32 framerate = configs.contains(DispdEncoderConfig.FRAMERATE) + ? configs.get(DispdEncoderConfig.FRAMERATE).get_uint32() + : 30; + uint32 width = configs.contains(DispdEncoderConfig.WIDTH) + ? configs.get(DispdEncoderConfig.WIDTH).get_uint32() + : 1920; + uint32 height = configs.contains(DispdEncoderConfig.HEIGHT) + ? configs.get(DispdEncoderConfig.HEIGHT).get_uint32() + : 1080; + StringBuilder desc = new StringBuilder(); + desc.append_printf( + "ximagesrc " + + "name=vsrc " + + "use-damage=false " + + "show-pointer=false " + + "startx=%u starty=%u endx=%u endy=%u " + + "! video/x-raw, " + + "framerate=%u/1 " + + "%s" + /* scaling & color space convertion */ + "%s" + /* encoding */ + "! h264parse " + + "! video/x-h264, " + + "alignment=nal, " + + "stream-format=byte-stream " + + "%s " + /* add queue if audio enabled */ + "! mpegtsmux " + + "name=muxer " + + "! rtpmp2tpay " + + "! .send_rtp_sink_0 " + + "rtpbin " + + "name=session " + + "rtp-profile=1 " + /* avp */ + "do-retransmission=true " + + "do-sync-event=true " + + "do-lost=true " + + "ntp-time-source=3 " + /* pipeline clock time */ + "buffer-mode=0 " + + "latency=40 " + + "max-misorder-time=50 " + + "! application/x-rtp " + + "! udpsink " + + "sync=false " + + "async=false " + + "host=\"%s\" " + + "port=%u ", + configs.contains(DispdEncoderConfig.X) + ? configs.get(DispdEncoderConfig.X).get_uint32() + : 0, + configs.contains(DispdEncoderConfig.Y) + ? configs.get(DispdEncoderConfig.Y).get_uint32() + : 0, + configs.get(DispdEncoderConfig.X).get_uint32() + width - 1, + configs.get(DispdEncoderConfig.Y).get_uint32() + height - 1, + framerate, + gen_scaler_n_converter_desc(width, height), + gen_encoder_desc(framerate), + configs.contains(DispdEncoderConfig.AUDIO_TYPE) + ? "! queue max-size-buffers=0 max-size-bytes=0" + : "", + configs.contains(DispdEncoderConfig.PEER_ADDRESS) + ? configs.get(DispdEncoderConfig.PEER_ADDRESS).get_string() + : "", + configs.contains(DispdEncoderConfig.RTP_PORT0) + ? configs.get(DispdEncoderConfig.RTP_PORT0).get_uint32() + : 16384); + if(configs.contains(DispdEncoderConfig.LOCAL_RTCP_PORT)) { + desc.append_printf("udpsrc " + + "address=\"%s\" " + + "port=%u " + + "reuse=true " + + "! session.recv_rtcp_sink_0 " + + "session.send_rtcp_src_0 " + + "! udpsink " + + "host=\"%s\" " + + "port=%u " + + "sync=false " + + "async=false ", + configs.contains(DispdEncoderConfig.LOCAL_ADDRESS) + ? configs.get(DispdEncoderConfig.LOCAL_ADDRESS).get_string() + : "", + configs.contains(DispdEncoderConfig.LOCAL_RTCP_PORT) + ? configs.get(DispdEncoderConfig.LOCAL_RTCP_PORT).get_uint32() + : 16385, + configs.contains(DispdEncoderConfig.PEER_ADDRESS) + ? configs.get(DispdEncoderConfig.PEER_ADDRESS).get_string() + : "", + configs.contains(DispdEncoderConfig.PEER_RTCP_PORT) + ? configs.get(DispdEncoderConfig.PEER_RTCP_PORT).get_uint32() + : 16385); + } + + if(configs.contains(DispdEncoderConfig.AUDIO_TYPE)) { + desc.append_printf("pulsesrc " + + "do-timestamp=true " + + "client-name=miraclecast " + + "device=\"%s\" " + + "! avenc_aac " + + "! audio/mpeg, " + + "channels=2, " + + "rate=48000 " + +// "base-profile=lc " + + "! queue " + + "max-size-buffers=0 " + + "max-size-bytes=0 " + + "max-size-time=0 " + + "! muxer. ", + configs.contains(DispdEncoderConfig.AUDIO_DEV) + ? configs.get(DispdEncoderConfig.AUDIO_DEV).get_string() + : ""); + } + + info("final pipeline description: %s", desc.str); + + this.configs = configs; + + try { + pipeline = Gst.parse_launch(desc.str); + } + catch(Error e) { + throw new DispdEncoderError.ENCODER_ERROR("%s", e.message); + } + + var bus = pipeline.get_bus(); + bus.add_signal_watch(); + bus.message.connect(on_pipeline_message); + + pipeline.set_state(Gst.State.READY); + } + + public void start() throws DispdEncoderError + { + check_configs(); + + pipeline.set_state(Gst.State.PLAYING); + } + + public void pause() throws DispdEncoderError + { + check_configs(); + + pipeline.set_state(Gst.State.PAUSED); + } + + public void stop() throws DispdEncoderError + { + if(null == pipeline) { + return; + } + + pipeline.set_state(Gst.State.NULL); + state = DispdEncoderState.NULL; + defered_terminate(); + } + + public async void prepare() throws Error + { + conn = yield Bus.get(BusType.SESSION); + conn.register_object(DispdEncoder.OBJECT_PATH, this as DispdEncoder); + +#if VALA_0_54 + string bus_info = "%s\n%s".printf(conn.unique_name, + BusType.SESSION.get_address_sync ()); +#else + string bus_info = "%s\n%s".printf(conn.unique_name, + BusType.get_address_sync(BusType.SESSION)); +#endif + /* we are ready, tell parent how to communicate with us */ + ssize_t r = Posix.write(3, (void *) bus_info.data, bus_info.length); + if(0 > r) { + throw new DispdEncoderError.CANT_RETURN_UNIQUE_NAME("%s", + Posix.strerror(Posix.errno)); + } + Posix.fsync(3); + } + + private void defered_terminate() + { + Timeout.add(100, () => { + loop.quit(); + return false; + }); + } + + private void check_configs() throws DispdEncoderError + { + if(null == configs || null == pipeline) { + throw new DispdEncoderError.INVALID_STATE("not configure yet"); + } + } + + private void on_pipeline_message(Gst.Message m) + { + Error e; + string d; + + if(m.src != pipeline) { + return; + } + + switch(m.type) { + case Gst.MessageType.EOS: + error("unexpected EOS"); + defered_terminate(); + break; + case Gst.MessageType.ERROR: + m.parse_error(out e, out d); + error("unexpected error: %s\n%s".printf(e.message, d)); + defered_terminate(); + break; + case Gst.MessageType.STATE_CHANGED: + Gst.State oldstate; + m.parse_state_changed(out oldstate, out pipeline_state, null); + info("pipeline state chagned from %s to %s", + oldstate.to_string(), + pipeline_state.to_string()); + switch(pipeline_state) { + case Gst.State.READY: + state = DispdEncoderState.CONFIGURED; + break; + case Gst.State.PLAYING: + state = DispdEncoderState.STARTED; + break; + case Gst.State.PAUSED: + if(Gst.State.PLAYING == oldstate) { + state = DispdEncoderState.PAUSED; + } + break; + } + break; + default: + debug("unhandled message: %s", m.type.to_string()); + break; + } + } +} + +private MainLoop loop; + +int main(string[] argv) +{ + Gst.init(ref argv); + + var encoder = new GstEncoder(); + encoder.prepare.begin((o, r) => { + try { + encoder.prepare.end(r); + } + catch(Error e) { + error("%s", e.message); + } + }); + + loop = new MainLoop(); + loop.run(); + + Posix.close(3); + + Gst.deinit(); + + info("bye"); + + return 0; +} diff --git a/res/gstplayer b/res/gstplayer index f048e6a3..d7ef53d3 100755 --- a/res/gstplayer +++ b/res/gstplayer @@ -48,7 +48,9 @@ class Player(object): if title: self.window.set_title(title) - if hasattr(self,'width') and hasattr(self,'height'): + if kwargs.get("fullscreen"): + self.window.fullscreen() + elif hasattr(self,'width') and hasattr(self,'height'): self.window.set_default_size(self.width, self.height) self.drawingarea = Gtk.DrawingArea() @@ -96,10 +98,10 @@ class Player(object): if scale: gstcommand += "videoscale method=1 ! video/x-raw,width="+str(self.width)+",height="+str(self.height)+" ! " - gstcommand += "autovideosink " + gstcommand += "autovideosink sync=false " if audio: - gstcommand += "demuxer. ! queue max-size-buffers=0 max-size-time=0 ! aacparse ! avdec_aac ! audioconvert ! audioresample ! autoaudiosink " + gstcommand += "demuxer. ! queue max-size-buffers=0 max-size-time=0 ! aacparse ! avdec_aac ! audioconvert ! audioresample ! autoaudiosink sync=false " self.pipeline = Gst.parse_launch(gstcommand) @@ -206,6 +208,7 @@ if __name__ == '__main__': 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("-f", "--full-screen", dest="fullscreen", action="store_true", help="Enter full screen mode") 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") @@ -216,7 +219,8 @@ if __name__ == '__main__': # " default VESA %08X\n" # " default HH %08X\n" parser.add_argument("-r", "--resolution", help="Resolution") - parser.set_defaults(audio=True) + parser.set_defaults(audio=False) + parser.set_defaults(fullscreen=False) args = parser.parse_args() p = Player(**vars(args)) diff --git a/res/install-scripts b/res/install-scripts new file mode 100755 index 00000000..d18a1f2d --- /dev/null +++ b/res/install-scripts @@ -0,0 +1,8 @@ +#!/bin/sh +RESDIR="${MESON_SOURCE_ROOT}/res" +BINDIR="${MESON_INSTALL_DESTDIR_PREFIX}/$1" + +install -dv "$BINDIR" +install -v "$RESDIR"/miracle-gst "$BINDIR" +install -v "$RESDIR"/gstplayer "$BINDIR" +install -v "$RESDIR"/uibc-viewer "$BINDIR" diff --git a/res/meson.build b/res/meson.build index 9a0992c5..34cbd377 100644 --- a/res/meson.build +++ b/res/meson.build @@ -1,3 +1,39 @@ +dbus1 = dependency('dbus-1') +systemd = dependency('systemd') + +dbus_system_services_dir = dbus1.get_pkgconfig_variable('system_bus_services_dir') +systemd_system_unit_dir = systemd.get_pkgconfig_variable('systemdsystemunitdir') + +conf_data = configuration_data() +dbus_name_prefix = 'org.freedesktop.miracle' +conf_data.set('dbus_name_prefix', dbus_name_prefix) +conf_data.set('exec_path', + join_paths(get_option('prefix'), get_option('bindir')) +) + +configure_file(input: 'miracle-wifid.service.in', + output: 'miracle-wifid.service', + configuration: conf_data, + install_dir: systemd_system_unit_dir +) +configure_file(input: 'miracle-dispd.service.in', + output: 'miracle-dispd.service', + configuration: conf_data, + install_dir: systemd_system_unit_dir +) +# create symlinks for DBus activiated services +meson.add_install_script('create-symlinks') + +configure_file(input: 'org.freedesktop.miracle.wifi.service.in', + output: 'org.freedesktop.miracle.wifi.service', + configuration: conf_data, + install_dir: dbus_system_services_dir +) +configure_file(input: 'org.freedesktop.miracle.wfd.service.in', + output: 'org.freedesktop.miracle.wfd.service', + configuration: conf_data, + install_dir: dbus_system_services_dir +) install_data( 'org.freedesktop.miracle.conf', install_dir: join_paths(get_option('sysconfdir'), 'dbus-1', 'system.d') @@ -11,3 +47,33 @@ install_data( 'miracle-wifid', 'miracle-sinkctl', 'miracle-wifictl', install_dir: join_paths(get_option('datadir'), 'bash-completion', 'completions') ) + +gio2 = dependency('gio-2.0') +gdk3 = dependency('gdk-3.0') +gst1 = dependency('gstreamer-1.0') +gst1_base = dependency('gstreamer-base-1.0') + +add_languages('vala') +valac = meson.get_compiler('vala') +valac_extra_args = [] +if valac.version().version_compare('>=0.34') and gdk3.version().version_compare('>=3.22') + valac_extra_args += ['-D', 'GDK3_HAS_MONITOR_CLASS'] +endif + +executable('gstencoder', 'gstencoder.vala', + dependencies: [gst1, gst1_base, gio2], + install: true, + vala_args: ['--pkg=posix']) + +miracle_dispctl_src = ['dispctl.vala', + 'sigint.vapi', + 'sigint.c', + 'networkmanager.vala', + 'miracle-wifi.vala', + 'miracle-wfd.vala' +] +executable('miracle-dispctl', miracle_dispctl_src, + dependencies: [gio2, gdk3], + vala_args: valac_extra_args, + install: true +) diff --git a/res/miracle-dispd.service.cmake b/res/miracle-dispd.service.cmake new file mode 100644 index 00000000..879387d2 --- /dev/null +++ b/res/miracle-dispd.service.cmake @@ -0,0 +1,18 @@ +[Unit] +Description=Miraclecast WiFi Display Service +After=dbus.service +Requires=miracle-wifid.service +After=miracle-wifid.service + +[Service] +BusName=org.freedesktop.miracle.wfd +Environment=LOG_LEVEL=trace +ExecStart=@CMAKE_INSTALL_PREFIX@/bin/miracle-dispd +CapabilityBoundingSet=CAP_NET_BIND_SERVICE \ + CAP_SETGID \ + CAP_SETUID \ + CAP_SETPCAP + +[Install] +WantedBy=multi-user.target +Alias=dbus-org.freedesktop.miracle.wfd.service diff --git a/res/miracle-dispd.service.in b/res/miracle-dispd.service.in new file mode 100644 index 00000000..e76edc7a --- /dev/null +++ b/res/miracle-dispd.service.in @@ -0,0 +1,18 @@ +[Unit] +Description=Miraclecast WiFi Display Service +After=dbus.service +Requires=miracle-wifid.service +After=miracle-wifid.service + +[Service] +BusName=@dbus_name_prefix@.wfd +Environment=LOG_LEVEL=trace +ExecStart=@exec_path@/miracle-dispd +CapabilityBoundingSet=CAP_NET_BIND_SERVICE \ + CAP_SETGID \ + CAP_SETUID \ + CAP_SETPCAP + +[Install] +WantedBy=multi-user.target +Alias=dbus-@dbus_name_prefix@.wfd.service diff --git a/res/miracle-wfd.vala b/res/miracle-wfd.vala new file mode 100644 index 00000000..6606b76d --- /dev/null +++ b/res/miracle-wfd.vala @@ -0,0 +1,50 @@ +/* Generated by vala-dbus-binding-tool 0.4.0. Do not modify! */ +/* Generated with: /usr/bin/vala-dbus-binding-tool --gdbus --no-synced --rename-namespace=org:Org --rename-namespace=freedesktop:Freedesktop --rename-namespace=miracle:Miracle --rename-namespace=wifi:Wifi --rename-namespace=wfd:Wfd --api-path=/home/derekdai/Projects/miraclecast/demo/dbus */ +using GLib; + +namespace Org { + + namespace Freedesktop { + + namespace Miracle { + + namespace Wfd { + + [DBus (name = "org.freedesktop.miracle.wfd.Sink")] + public interface Sink : GLib.Object { + + [DBus (name = "StartSession", timeout = 120000)] + public abstract GLib.ObjectPath start_session(string param0, string param1, uint param2, uint param3, uint param4, uint param5, string param6) throws DBusError, IOError; + + [DBus (name = "Session", timeout = 120000)] + public abstract GLib.ObjectPath session { owned get; } + + [DBus (name = "Peer", timeout = 120000)] + public abstract GLib.ObjectPath peer { owned get; } + } + + [DBus (name = "org.freedesktop.miracle.wfd.Session")] + public interface Session : GLib.Object { + + [DBus (name = "Resume", timeout = 120000)] + public abstract void resume() throws DBusError, IOError; + + [DBus (name = "Pause", timeout = 120000)] + public abstract void pause() throws DBusError, IOError; + + [DBus (name = "Teardown", timeout = 120000)] + public abstract void teardown() throws DBusError, IOError; + + [DBus (name = "Sink", timeout = 120000)] + public abstract GLib.ObjectPath sink { owned get; } + + [DBus (name = "Url", timeout = 120000)] + public abstract string url { owned get; } + + [DBus (name = "State", timeout = 120000)] + public abstract int state { get; } + } + } + } + } +} diff --git a/res/miracle-wifi.vala b/res/miracle-wifi.vala new file mode 100644 index 00000000..e82da976 --- /dev/null +++ b/res/miracle-wifi.vala @@ -0,0 +1,92 @@ +/* Generated by vala-dbus-binding-tool 0.4.0. Do not modify! */ +/* Generated with: /usr/bin/vala-dbus-binding-tool --gdbus --no-synced --rename-namespace=org:Org --rename-namespace=freedesktop:Freedesktop --rename-namespace=miracle:Miracle --rename-namespace=wifi:Wifi --rename-namespace=wfd:Wfd --api-path=/home/derekdai/Projects/miraclecast/demo/dbus */ +using GLib; + +namespace Org { + + namespace Freedesktop { + + namespace Miracle { + + namespace Wifi { + + [DBus (name = "org.freedesktop.miracle.wifi.Peer")] + public interface Peer : GLib.Object { + + [DBus (name = "Connect", timeout = 120000)] + public abstract void connect(string param0, string param1) throws DBusError, IOError; + + [DBus (name = "Disconnect", timeout = 120000)] + public abstract void disconnect() throws DBusError, IOError; + + [DBus (name = "Link", timeout = 120000)] + public abstract GLib.ObjectPath link { owned get; } + + [DBus (name = "P2PMac", timeout = 120000)] + public abstract string p2p_mac { owned get; } + + [DBus (name = "FriendlyName", timeout = 120000)] + public abstract string friendly_name { owned get; } + + [DBus (name = "Connected", timeout = 120000)] + public abstract bool connected { get; } + + [DBus (name = "Interface", timeout = 120000)] + public abstract string interface { owned get; } + + [DBus (name = "LocalAddress", timeout = 120000)] + public abstract string local_address { owned get; } + + [DBus (name = "RemoteAddress", timeout = 120000)] + public abstract string remote_address { owned get; } + + [DBus (name = "WfdSubelements", timeout = 120000)] + public abstract string wfd_subelements { owned get; } + + [DBus (name = "ProvisionDiscovery")] + public signal void provision_discovery(string param0, string param1); + + [DBus (name = "GoNegRequest")] + public signal void go_neg_request(string param0, string param1); + + [DBus (name = "FormationFailure")] + public signal void formation_failure(string param0); + } + + [DBus (name = "org.freedesktop.miracle.wifi.Link")] + public interface Link : GLib.Object { + + [DBus (name = "Manage", timeout = 120000)] + public abstract void manage() throws DBusError, IOError; + + [DBus (name = "Unmanage", timeout = 120000)] + public abstract void unmanage() throws DBusError, IOError; + + [DBus (name = "InterfaceIndex", timeout = 120000)] + public abstract uint interface_index { get; } + + [DBus (name = "MACAddress", timeout = 120000)] + public abstract string m_a_c_address { owned get; } + + [DBus (name = "InterfaceName", timeout = 120000)] + public abstract string interface_name { owned get; } + + [DBus (name = "FriendlyName", timeout = 120000)] + public abstract string friendly_name { owned get; set; } + + [DBus (name = "Managed", timeout = 120000)] + public abstract bool managed { get; } + + [DBus (name = "P2PState", timeout = 120000)] + public abstract int p2p_state { get; } + + [DBus (name = "P2PScanning", timeout = 120000)] + public abstract bool p2p_scanning { get; set; } + + [DBus (name = "WfdSubelements", timeout = 120000)] + public abstract string wfd_subelements { owned get; set; } + } + } + } + } +} diff --git a/res/miracle-wifid.service.cmake b/res/miracle-wifid.service.cmake new file mode 100644 index 00000000..3ffab9ab --- /dev/null +++ b/res/miracle-wifid.service.cmake @@ -0,0 +1,19 @@ +[Unit] +Description=Miraclecast WiFi Daemon +After=dbus.service +Requires=network.target + +[Service] +BusName=org.freedesktop.miracle.wifi +Environment=PATH=/sbin:/usr/bin +ExecStart=@CMAKE_INSTALL_PREFIX@/bin/miracle-wifid \ + --use-dev \ + --log-level trace \ + --lazy-managed +CapabilityBoundingSet=CAP_NET_ADMIN \ + CAP_NET_BIND_SERVICE \ + CAP_NET_RAW + +[Install] +WantedBy=multi-user.target +Alias=dbus-org.freedesktop.miracle.wifi.service diff --git a/res/miracle-wifid.service.in b/res/miracle-wifid.service.in new file mode 100644 index 00000000..7430a8c2 --- /dev/null +++ b/res/miracle-wifid.service.in @@ -0,0 +1,19 @@ +[Unit] +Description=Miraclecast WiFi Daemon +After=dbus.service +Requires=network.target + +[Service] +BusName=@dbus_name_prefix@.wifi +Environment=PATH=/sbin:/usr/bin +ExecStart=@exec_path@/miracle-wifid \ + --use-dev \ + --log-level trace \ + --lazy-managed +CapabilityBoundingSet=CAP_NET_ADMIN \ + CAP_NET_BIND_SERVICE \ + CAP_NET_RAW + +[Install] +WantedBy=multi-user.target +Alias=dbus-@dbus_name_prefix@.wifi.service diff --git a/res/networkmanager.vala b/res/networkmanager.vala new file mode 100644 index 00000000..3d243fd1 --- /dev/null +++ b/res/networkmanager.vala @@ -0,0 +1,114 @@ +/* Generated by vala-dbus-binding-tool 0.4.0. Do not modify! */ +/* Generated with: /usr/bin/vala-dbus-binding-tool --gdbus --no-synced --rename-namespace=org:Org --rename-namespace=freedesktop:Freedesktop --rename-namespace=miracle:Miracle --rename-namespace=wifi:Wifi --rename-namespace=wfd:Wfd --api-path=/home/derekdai/Projects/miraclecast/demo/dbus */ +using GLib; + +namespace Org { + + namespace Freedesktop { + + namespace NetworkManager { + + [DBus (name = "org.freedesktop.NetworkManager.Device")] + public interface Device : GLib.Object { + + [DBus (name = "Reapply", timeout = 120000)] + public abstract void reapply(GLib.HashTable> connection, uint64 version_id, uint flags) throws DBusError, IOError; + + [DBus (name = "GetAppliedConnection", timeout = 120000)] + public abstract void get_applied_connection(uint flags, out GLib.HashTable> connection, out uint64 version_id) throws DBusError, IOError; + + [DBus (name = "Disconnect", timeout = 120000)] + public abstract void disconnect() throws DBusError, IOError; + + [DBus (name = "Delete", timeout = 120000)] + public abstract void delete() throws DBusError, IOError; + + [DBus (name = "StateChanged", timeout = 120000)] + public signal void state_changed(uint new_state, uint old_state, uint reason); + + [DBus (name = "Udi", timeout = 120000)] + public abstract string udi { owned get; } + + [DBus (name = "Interface", timeout = 120000)] + public abstract string interface { owned get; } + + [DBus (name = "IpInterface", timeout = 120000)] + public abstract string ip_interface { owned get; } + + [DBus (name = "Driver", timeout = 120000)] + public abstract string driver { owned get; } + + [DBus (name = "DriverVersion", timeout = 120000)] + public abstract string driver_version { owned get; } + + [DBus (name = "FirmwareVersion", timeout = 120000)] + public abstract string firmware_version { owned get; } + + [DBus (name = "Capabilities", timeout = 120000)] + public abstract uint capabilities { get; } + + [DBus (name = "Ip4Address", timeout = 120000)] + public abstract uint ip4_address { get; } + + [DBus (name = "State", timeout = 120000)] + public abstract uint state { get; } + + [DBus (name = "StateReason", timeout = 120000)] + public abstract DeviceStateReasonStruct state_reason { owned get; } + + [DBus (name = "ActiveConnection", timeout = 120000)] + public abstract GLib.ObjectPath active_connection { owned get; } + + [DBus (name = "Ip4Config", timeout = 120000)] + public abstract GLib.ObjectPath ip4_config { owned get; } + + [DBus (name = "Dhcp4Config", timeout = 120000)] + public abstract GLib.ObjectPath dhcp4_config { owned get; } + + [DBus (name = "Ip6Config", timeout = 120000)] + public abstract GLib.ObjectPath ip6_config { owned get; } + + [DBus (name = "Dhcp6Config", timeout = 120000)] + public abstract GLib.ObjectPath dhcp6_config { owned get; } + + [DBus (name = "Managed", timeout = 120000)] + public abstract bool managed { get; set; } + + [DBus (name = "Autoconnect", timeout = 120000)] + public abstract bool autoconnect { get; set; } + + [DBus (name = "FirmwareMissing", timeout = 120000)] + public abstract bool firmware_missing { get; } + + [DBus (name = "NmPluginMissing", timeout = 120000)] + public abstract bool nm_plugin_missing { get; } + + [DBus (name = "DeviceType", timeout = 120000)] + public abstract uint device_type { get; } + + [DBus (name = "AvailableConnections", timeout = 120000)] + public abstract GLib.ObjectPath[] available_connections { owned get; } + + [DBus (name = "PhysicalPortId", timeout = 120000)] + public abstract string physical_port_id { owned get; } + + [DBus (name = "Mtu", timeout = 120000)] + public abstract uint mtu { get; } + + [DBus (name = "Metered", timeout = 120000)] + public abstract uint metered { get; } + + [DBus (name = "LldpNeighbors", timeout = 120000)] + public abstract GLib.HashTable[] lldp_neighbors { owned get; } + + [DBus (name = "Real", timeout = 120000)] + public abstract bool real { get; } + + public struct DeviceStateReasonStruct { + public uint attr1; + public uint attr2; + } + } + } + } +} diff --git a/res/org.freedesktop.miracle.conf b/res/org.freedesktop.miracle.conf index dee41ad0..4fc2f825 100644 --- a/res/org.freedesktop.miracle.conf +++ b/res/org.freedesktop.miracle.conf @@ -1,6 +1,6 @@ + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">