From 8b455e1ce455d000d61f0e4aa21efee4e00d75d0 Mon Sep 17 00:00:00 2001 From: Derek Dai Date: Fri, 30 Sep 2016 23:04:53 +0800 Subject: [PATCH] source-impl: Squashed derekdai fork fix byte order of DHCP_SERVER_ID option If miraclecat is GO, miracle-dhcp runs as server, it send out SERVER_ID option in little endian, causes non-GO peer connect to wrong IP address, so RTSP connection can't be established. In scenario like, say Android (GO) <-> miracalcast (non-GO), the DHCP service is provided by Android, in this case, the SERVER_ID is in expected byte order (big endian). set audio=False by default since gstplayer has argument --audio to enable audio support but no way to disable it, audio=False might a better choise. If we left audio=True and incoming stream has video only, the whole pipeline will be blocked and wait for audio stream. init copy & modify calculate array size of resolutions at build time instead of tailing null element match resolution from high to low init source side implementation add option [-f|--full-screen] to switch to full screen mode at start up initial sender implementation integrate miracle-sender with miracast-srcctl stop sender when peer freed source implement + sender talk via dbus miracle-wifid: fix some leaks of wpas_message stability: increase gcc warning level miracle-sender: change bus name from org.freedesktop.miracle.Sender to org.freedesktop.miracle gstplayer: set sync=false to reduce latency miracle-wifid: fix call waitid() with NULL pointer issue reported by valgrind rename dbus name back to org.freedesktop.miracle.Sender and variety kinds of tunning encode video with x264enc temperarily to prevent decoding issue on dongle stop sender properly after session end miracle-srcctl: fix memory management issues Make miracle-wifid conexists with other network tools The new option --lazy-managed will let miracle-wifid don't managed the links automatically. Instead, the link will be managed only when the new DBus property Managed was set to true. So this will be possible that miracle-wifid could be conexists with other network tools like networkmanager. For example, unmange the device in networkmanager with setting the DBus property org.freedesktop.NetworkManager.Device.Managed to false and manage it in miracle-wifid with setting org.freedesktop.miracle.wifi.Link.Managed to true, then both them could works and don't need to kill each other. Besides, there is new command named make-managed in miracle-wifictl and miracle-sinkctla. miracle-wifid: add dbus & systemd service files to support dbus activation miracle-wifid: remove duplicated link & peer dbus nodes by checking prefix miracle-srcctl: fix some warming miracle-wifid: add MACAddress property to link miracle-wfdctl: init commit miracle-*ctl: remove some debugging log messages miracle-*ctl: resolve object parsing issue in ctl_wifi_fetch() while parsing ctl_peer and ctl_link objects, if a ctl_peer appear prior to its related ctl_link, -EINVAL is reported, and miracle-*ctl will quit immediately. miracle-*ctl: fix label leak miracle-wifid: let the format of wfd_dev_info compliant with wfd_subelems to ease parsing miracle-*ctl: remove N_ELEMENTS() and use SHL_ARRAY_LENGTH() instead miracle-*ctl: rename resolution_bitmap to wfd_resolution and add progressive field remove vim settings from files miracle-srcctl: rename ctl_src to wfd_src miracle-wfdctl: first revision which can create TCP connection of WFD session between peers, controled through DBus In this revision, the procedure of session negotiation is not yet completed but only TCP connection can be established between peers. You can try it by run script `res/miracle-wfdctl-demo` to see how it work. miracle-wfdctl-demo: wait for property chainging and object apperance by busctl instead of sleep miracle-*ctl: update links and peers when wifid up or down miracle-wfdctl-demo: cleanup before exit ignore .vimrc from being managed by git miracle-wfdctl: now we can establish WFD session multiple times in this revision, the source side RTSP protocol is not implemented completely, the focus is on stability, DBus interfaces. add debian support prepared by Guo Shuang miracle-wfdctl-demo: add an optional argument to specify link index of NetworkManager miracle-wfdctl: refactoring to remove repeated snippets and centralize the dispatching of request and reply add -fstack-protector-strong to capture more stack issues add miraclecast.links to create symlink for DBus activated systemd services miracle-wfdctl: return -EINVAL instead of being aborted by assert() when rtsp message ID is invalid miracle-*ctl: add *_from_string() and *_to_string() for wfd-video-formats and wfd-audio-codecs miracle-wfdctl: finish migration of source side rtsp protocol remove unused files fix links of services for debian package move down trap command to prevent triggered by runing with no argument remove unused #include miracle-wfdctl: add timeout value and Range header to the reply of PLAY request test compiler flag -fstack-protector-strong before set miracle-wfdctl: run gst-launch to generate video stream with hardcoded environment miracle-wfdctl: fix return uninitialized variable value issue add struct wfd_arg to generalize passing of arguments miracle-*ctl: fix and enhance wfd_subelement parser miracle-wfdctl: remove unused function and improve log message for peer new & delete rename macro arguement from v to _v to prevent name confliction add support for signed and unsigned int for wfd_arg refactoring getter/setter for dict type of wfd_arg to remove declaration of temp variable add wfd_arg_list type of wfd_arg miracle_wfdctl: control flow and passing arguments with wfd_arg instead of keep adding new structure fields miracle-wfdctl: print friendly name when peer shows up miracle-wfdctl: fix mapping from rtsp_message to rtsp_message_id miracle-wfctl: reply 'request not implemented' instead of abort by assert() check gcc version to ensure c11 generic select feature update dependencies for debian based distro miracle-wfdctl: acquire DISPLAY, XAUTHORITY and XDG_RUNTIME_DIR from environment, instead of hardcode miracle-wfdctl: fix wfd_session_end() recursive calling issue miracle-wfdctl: add handler for PAUSE, PLAY and TEARDOWN methods miracle-wfdctl: clarify teardown procedure of session miracle-wfdctl: delay for 100ms before start sending stream to sink miracle-wfdctl: compare wfd-standby and wfd-idr-request by message body miracle-wfdctl: user now can select video mode through DBus interface miracle-wfdctl: fix typo in systemd service file miracle-wfdctl: change session type from uint64_t to unsigned int miracle-wfdctl: do not launch gstreamer if DO_NOT_LAUNCH_GST environment variable is set while miracle-wfdctl running miracle-wfdctl: remove unused Range header from reply add compiler flag to do sanity check of undefined behaviours. relax permissions checking to let non-privileged use miracalcast. we will enhance this by fine grained permission conrol latter. since we relaxed the permissions to talk to wifid and wfdctl, so there is no need to run miracle-wfdctl-demo with sudo. but to kill these two privileged services, the killall still need sudo miracle-wfdctl: integrate sd_event with GMainLoop for running gstreamer in process instead of fork n exec gst-launch miracle-wfdctl: encoding video stream with gstreamer library instead of gst-launch-1.0 miracle-wfdctl-demo: run tcpdump with sudo miracle-wifid: change managed property of link by method call instead of set property due to wpa_supplicant needs time to get ready miracle-wfdctl: include header for resolving compiler warning miracle-wfdctl: add rtcp and audio supporting to gstreamer pipeline miracle-wfdctl: fix session removal dbus notification miracle-wfdctl: fix variety kinds of dbus interface issues miracle-wfdctl: fix sink removal notifcation fix typo in miracle-wifid.service miracle-wfdctl: set XAUTHORITY and DISPLAY before create gstreamer pipeline, and now we can pass device name to pulseaudio miraclw-wfdctl: fix typo of dbus signal names miraclw-wfdctl: now you can cast only specified xid or xname to sink miracle-wfdctl: fine tune gstreamer pipeline miracle-wfdctl: fix argument passing for ximagesrc miracle-wfdctl: send video only stream if no audio device specified update dependencies of systemd services miracle-wfdctl: reply rtsp request with date header in locale en_US.UTF-8 miracle-wrdctl-demo: capture packets on p2p wnic with tshark instead of tcpdump miracle-wfdctl: set ximagesrc as live mode to prvent it preroll in pause state miracle-wfdctl: fine tune codec to compliant with WFD spec + H264 profile/level miracle-wfdctl: disable audio encoding temparary miracle-wfdctl: save captured packets to .pcap file instead of print out on console update dependency information miracle-wfdctl: remove extra NULL parameter miracle-wfdctl: do not expose rtsp_message_id_to_string() to outside world miracle-wfdctl: handle gst messages and reflect state onto session miracle-wfdctl: encode audio with voaacenc instead of avenc_aac to get closer to WFD spec rename miracle-wfdctl to miracle-dispd change argument types of wfd_sink_start_session() To workaround valac issue miracle-wfdctl: fix pointer check issue add a demostration for wifid + dispd reorganize demo, make it looks like running in sequential instead of trigger by async events miracle-dispd: fix monitor geometry calculation issue remove miracle-wfdctl-demo, replaced by demo/miracle-wfdctl demo/miracle-wfdctl: fix some build issues for GDK version older then 3.22 update dependencies in debian/ for demo/ update cmake minimum requirement from 2.8 to 3.0.2 due to usage of version comparasion of if command check valac version to decide whether or not to use new Gdk.Monitor class add install() for demo/miracle-wfdctl add mission dependencies to debian/control miracle-dispd: merge patch from Meelis acquire ownership through Link.Manage()/Link.Unmanage() instead of set Link.Managed property demo/miracle-wfdctl: better handling session tearing down move definitions related to miracle-dispd from src/ctl to src/disp demo/miracle-wfdctl: handle SIGINT to exit gracefully miracle-dispd: relax checking for peer with empty wfd_subelements miracle-dispd: fix symlink for dbus-org.freedesktop.miracle.wfd.service which is an alias of miracle-dispd.service demo/miracle-wfdctl: log message tweaks miracle-dispd: add watchdog and SIGKILL for it to prevent no reponse and unable to be terminated issue. this is a temp solution miracle-wifid: fix signal emittion of FormationFailure miracle-wfdctl: update description of DBus interfaces miracle-dispd: notify systemd about the service readiness miracle-wifid: postpone the emittion of managed signal after the status query demo/miracle-wfdctl: maintain DBus interfaces by .vala files instead of generate from .xml which have only sync methods miracle-dispd: use software codec instead of vaapi accelerated codec miracle-dispd: add error report log to handle_replay() miracle-dispd, demo/miracle-wfdctl: improve error handling and reporting miracle-dispd: fix output resolution to 1080p@30 miracle-wifid: add Link.P2PState for checking P2P supporting status demo/miracle-wfdctl: Wait for P2PState changing before do P2P scanning add missing build-time dependencies fix loop boundary checking miracle-dispd: fix some uninitialized variable accessing issues demo/miracle-wfdctl: remove unused files add meson build supporting remove install script and install scripts with install_data() handle meson optoins properly quote string entries with configuration_data.set_quoted() instead of by ourself with configureation_data.set() remove starting `/` in front of installation path of DBus policy file demo/miracle-wfdctl: rollback method definitions in dbus interfaces from async to sync demo/miracle-wfdctl: teardown session more gracefully miracle-dispd: stablizing and removing memory leak miracle-dispd: put back gstreamer in this version, the pipeline can be stopped properly, but the gst & glib allocated memory can't be release cleanly (which won't accumulate) Change-Id: I242b106158db647070e7852b84f6f90b6bbd96f4 miracle-dispd: remove some compiler warmings Change-Id: Ief2ae8e2992c2fee34fe560af73deb955a9186de demo/miracle-wfdctl: log more info about DBus operation Change-Id: Ic44a6a2ca4585e6aa1983885b4730ccee6fd3d33 remove address sanitizer flag from default options let user or packaging system to decide whether or not to use it Change-Id: I27e7f7eae3f79431a1ec7dc69d5f372677dadbad miracle-wifid: rename link_set_managed() rename to prevent confusing with managed event notification Change-Id: I0f0d01f97156044bbb225e199b2717d5ea9c1a24 miracle-wifid: remove unused function Change-Id: Ic2709a3661914fd9e0708c33649118feededa7f6 miracle-wifid: cleanup change notification of Link.Managed property Change-Id: Ia5b056151e07219f0e6cc34dbc9fc2149f929a36 miracle-wifid: minor tweaks of log messages and identation Change-Id: Id9e98583f3256c83cf48f18cf08740849ded3cca miracle-wifid: fix crashing on link removing issue wifid crashes when link removing (either unplug usb wlan dongle or unload kernel module) due to double Change-Id: I9480a095659752343e505eb41b380d1ab3cd322a miracle-wifid: fine tune P2P state notification timing Change-Id: Idb111b90109010733b96d653c2d6d7855ebc781f miracle-disp: extracting encoder, part1 Change-Id: Ic9293e9cf379d352c75701834f17fa553f0bf52d miracle-dispd: refactory session startup function Change-Id: If1940a5823171d35230d29051d79c9827efc054f use meson default build type instead of debugoptimized Change-Id: I39bdc3e9df61c681b7ec5800a9e57fc7e9971cb0 migrate more gstreamer code to res/gstencoder Change-Id: Id5098e37fb1eabb2906505f26969e6d8cedad519 miracle-dispd: add struct dispd_encoder as encoder client since wait for the availability of newly spawned process and communicate with it is a highly async task, wrapping in a isolated class is a better choise. Change-Id: I7201da49f379c65014123269ed9e0279bcb8c918 miracle-dispd: cleanup and reorganize header Change-Id: I0067d59d89f79af53ae7291894191edb33789ab2 miracle-dispd: rename field from ref_count to ref Change-Id: Ia8138c0609b0100dd2784734287a4b3964beacbd miracle-dispd: unify log message Change-Id: I17c7cb0c64ddaecd09ff59f65dcb0e745f6b7174 miracle-dispd: log if a function call returns error it is easier to trace back to the location where the error occured by logging the return code instead of simply return a error number Change-Id: Ic711af32a1e80d9da9317269521a4745099e30cc miracle-dispd: extract encoder from dispd, part 2 Change-Id: Ie61daa87a83b875013c525fe3f15222bf9057926 miracle-dispd: complete cmake build for extracted encoder Change-Id: I5635bffdb8e1a0a693893e22837de6a8a0071fbe miracle-dispd: improve encoder lifecycle management Change-Id: Ic651e4795e4c9579978fe3e3529d8d61e739ffd1 miracle-dispd: replace assert() with assert_?ret?() for capturing more information about buggy behaviours, more validations and loggings are added Change-Id: I57e11d201324ed10b2218c9e72627759aae619c9 remove valgrind checking since now it is optional Change-Id: I39dc6b3e23307fd039775983b6d68878547523ba miracle-wifid: fix the timing of notification of P2P availability Change-Id: I86c453256b614022db5d941d64984f9eecd4ef39 miracle-dispd: fix definition of wfd_session_is_established() Change-Id: I61638bcb4e17059b5b9e37252894ab35d524d7cf gstencoder: exit if pipeline encounter error, EOS or enter NULL state Change-Id: Id9934d144bf023b301d92b24cc1cafcf2bac3b8e miracle-dispd: add timer to ensure the termination of encoder process Change-Id: I282136c79392137f2501f82009154087ee9649be gstencoder: add license declaration Change-Id: Ia2d7458d028e7d0406c3eb2489211c5daa86149d gstencoder: print final pipeline description to ease debugging Change-Id: Ib5d9bfea0760f401033b7e8982a14ae23f70d601 miracle-dispd: do not link with gstreamer libraries Change-Id: Ie178235579429647de6e7e4cf738ce814e835424 miracle-dispd: minor value returning style tunning Change-Id: I608154ffcbe0f25b4bfa9f78d0f457afe1e4fb89 miracle-dispd: more logging and assertions Change-Id: I0450b9144390b12eed0646c4a4eacf6beed6ae91 miracle-dispd: improve session life cycle management Change-Id: Icb43664439002f76d7f64252f4ba79533d04ba43 miracle-dispd: run encoder with user privilege now we run encoder with unprivileged user and talk through session bus Change-Id: I09b3b8b15e5a7c7e9b883b7c9dbac601c13f458c gstencoder: fix untested execution path which has pipeline syntax issue Change-Id: I1d170666852c33e96c989468fc733d065f777bd4 miracle-wfdctl: add options -b and -r add options -b and -r to control whether or not to acquiring or releasing ownershipt of WNIC Change-Id: Iaa9fdac15a7d1e23053b57500b978e0c026fa50c miracle-dispd: fix session leaking issue Change-Id: I1ececf598fedf601edfd7c7208ffce72a750f8cd miracle-dispd: terminate with SIGTERM instead of SIGKILL Change-Id: I3b46d3d50fc11c8f3487cc241947b556196e8ba6 miracle-dispd: ignore peers which has no valid wfd_supplicant Change-Id: Ifd7225a944fb0312eb3fe0c575b82a9c95c6ab14 miracle-dispd: minor log message tweaks Change-Id: I75cf47bb2640270729d1643487ae5cc7f8617446 fix creation paths of symlinks of DBus activated systemd services prepend $ENV{DESTDIR} to the path which is abs path get from systemd.pc Change-Id: Id07801a1f513fb421f653cb4b2c943d8841a74f6 demo/miracle-wfdctl: broadcast as source only device Change-Id: Id159332fc644e09990abdebffa8fa4b9f8b2fc28 reload systemd units after installation Change-Id: I98cf764f5dbbe5fe06a136e3633a3304e8e4401e miracle-dispd: add hardware accelection video encoding back to gstencoder Change-Id: I577082a70da61b6266e67c0b8611662ae9bc316c build vala code with cmake modules instead of custom_command() Change-Id: Ib0bc561441c3664c00e73e66864597343f3f75e8 remove 'org.freedesktop.' prefix from dbus interface source files Change-Id: Ice1ef49ca1ad3b8f36c8b4a5f8f35769df262eb9 move miracle-wfdctl from demo/ to res/ Change-Id: I37df75135e4713a73d3c63353c9a17dc248e2d7e gstencoder: fallback to sw encoder if vaapi encoder not available Change-Id: I8329fc191d60188c2098434d5db8a4aa2b3fbec2 miracle-dispd: minor interface tweaks betwen wfd_session and wfd_out_session Change-Id: Ib0c65ab64d543d5fce8908f6c47dbfc2acf47763 miracle-dispd: add keep-alive messages Change-Id: Ib8fc055789a1bd0a6df3075b176385407c17a74c miracle-dispd: remove unused log messages Change-Id: I7460f6173815f054c0dc51accf96a531f6faf4b1 miracle-dispd: minor tweaks of object life cycle transfering Change-Id: Iafefe39652d9ade06c58e41f091a30df274cb091 update gstreamer1.0-vaapi version we depend on Change-Id: Iacf53af83c151c6746f778a97201ed3f7bd55504 miracle-wifid: reduce caps from .service file Change-Id: I8434fe9dfcf141156635d7a6f8a9c2aaa4469263 miracle-dispd: reduce caps from .service file Change-Id: Ibfc14aca1c03b8cd6b50a49d5128da8597acb898 miracle-dispd: set no new priviledge flag before exec encoder Change-Id: I1b7f18a704203fc60a35b67f09936095ae6398f0 miracle-dispd: rename files and class names to make naming consistent Change-Id: Id2f5a8d6bbfbbd205dbe25e8b8bc0ddd289591e5 remove tests for security related compiler flags Change-Id: Ia51167abc10c4c8bc03f58883aca06e05879c6e9 miracle-dispctl: fine tune ctrl+c handling Change-Id: Ic5752cea7b64307c3c2d8dd0a8389d3b18ab3339 miracle-wfdctl: enhance handling of user termination (ctrl+c) Change-Id: Ifa2b1ee85b2abf4cc0d9d7f40c452517cb94761d miracle-wfdctl: fix GIOChannel ref leak Change-Id: I12e647f9b4f13aaa0aedf81907aa251675d2be32 miracle-dispd: fix session life-cycle managed issue when a session is destroyed due to peer free event, dispd_out_session must detach from sink to it won't cause double free. Change-Id: I71ad7e977fa71ec84f12f5e914786aa24a650f34 miracle-sinkctl: do not quote the value of 'Public' header of OPTION method Change-Id: I1e3994517f492aa7d9996494110077c81acc41b4 miracle-dispd, gstencoder: enable audio supporting Change-Id: I4d72782516ffff487010cd94cc47ec68133ff257 relates #4 --- .gitignore | 2 + CMakeLists.txt | 22 +- README.md | 12 +- 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 + res/CMakeLists.txt | 133 +- res/create-symlinks | 6 + res/dispctl.vala | 824 +++++++++++++ res/gstencoder.vala | 421 +++++++ res/gstplayer | 11 +- 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 | 64 +- 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 | 10 +- src/ctl/ctl-sink.c | 2 +- src/ctl/ctl-wifi.c | 124 +- src/ctl/ctl.h | 3 + src/ctl/meson.build | 8 +- src/ctl/sinkctl.c | 14 +- src/ctl/wfd.c | 646 ++++++++-- src/ctl/wfd.h | 193 ++- src/ctl/wifictl.c | 24 +- src/dhcp/server.c | 4 +- src/disp/CMakeLists.txt | 37 + 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 | 522 ++++++++ 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 | 76 +- src/wifi/wifid-supplicant.c | 99 +- src/wifi/wifid.c | 51 +- src/wifi/wifid.h | 9 +- 71 files changed, 9451 insertions(+), 330 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 dd6862d..09f6162 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ *.swp *.tar.xz *.trs +*.pcap .deps/ .dirstamp .libs/ @@ -42,3 +43,4 @@ CMakeCache.txt libmiracle-shared.a install_manifest.txt /build/ +.vimrc diff --git a/CMakeLists.txt b/CMakeLists.txt index a073be0..df2dd4f 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) SET(BUILD_BINDIR "${CMAKE_INSTALL_PREFIX}/bin") OPTION(BUILD_ENABLE_DEBUG "Enable Debug" ON ) @@ -27,6 +24,19 @@ find_package(PkgConfig) 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 7be9547..da85c95 100644 --- a/README.md +++ b/README.md @@ -21,10 +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..) + - **readline**: A library which is used to provide command line interface to control wifid, sink, etc.. + - **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**: 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: 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 0000000..aa3a6e7 --- /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 0000000..717c0f5 --- /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 0000000..72c04b1 --- /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 0000000..3fff193 --- /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 0000000..72b7c14 --- /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 0000000..ec63514 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..fbe7745 --- /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 0000000..235ca9b --- /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 0000000..9b76642 --- /dev/null +++ b/debian/postinst @@ -0,0 +1 @@ +systemctl daemon-reload diff --git a/debian/postrm b/debian/postrm new file mode 100644 index 0000000..9b76642 --- /dev/null +++ b/debian/postrm @@ -0,0 +1 @@ +systemctl daemon-reload diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..0182ac8 --- /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 0000000..89ae9db --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/res/CMakeLists.txt b/res/CMakeLists.txt index e67277e..a4055bc 100644 --- a/res/CMakeLists.txt +++ b/res/CMakeLists.txt @@ -1,14 +1,141 @@ -INSTALL( +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) + +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( PROGRAMS miracle-gst gstplayer uibc-viewer DESTINATION bin ) -INSTALL( +install( FILES org.freedesktop.miracle.conf DESTINATION ${SYSCONFDIR}/dbus-1/system.d ) -INSTALL( +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 $ENV{DESTDIR}/etc/dbus-1/system.d + ) + +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 0000000..f0d92c6 --- /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 0000000..15794b9 --- /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: 000600001c4400c8", "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 = "000600001c4400c8"; + } + 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(); + //app.set_default(); + + 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 0000000..03d0bf2 --- /dev/null +++ b/res/gstencoder.vala @@ -0,0 +1,421 @@ +/* + * 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); + + string bus_info = "%s\n%s".printf(conn.unique_name, + BusType.get_address_sync(BusType.SESSION)); + /* 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 2cbbb9b..91e5974 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() + else if 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) @@ -154,7 +156,6 @@ class Player(object): 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)) @@ -206,6 +207,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=1991, 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") @@ -217,6 +219,7 @@ if __name__ == '__main__': # " default HH %08X\n" parser.add_argument("-r", "--resolution", help="Resolution") parser.set_defaults(audio=True) + 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 0000000..d18a1f2 --- /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 037a759..c991418 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') @@ -7,6 +43,36 @@ install_data('miracle-gst', 'gstplayer', 'uibc-viewer', install_dir: get_option('bindir'), install_mode: 'rwxr-xr-x') +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 +) + install_data( 'miracle-wifid', 'miracle-sinkctl', 'miracle-wifictl', install_dir: join_paths(get_option('datadir'), 'bash-completions', 'completions') diff --git a/res/miracle-dispd.service.cmake b/res/miracle-dispd.service.cmake new file mode 100644 index 0000000..879387d --- /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 0000000..e76edc7 --- /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 0000000..6606b76 --- /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 0000000..e82da97 --- /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 0000000..3ffab9a --- /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 0000000..7430a8c --- /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 0000000..3d243fd --- /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 dee41ad..4fc2f82 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">