├── COPYING ├── NEWS ├── README.md ├── build-aux ├── flatpak │ ├── org.gnome.NetworkDisplays.json │ ├── polkit-autogen │ └── polkit-build-Add-option-to-build-without-polkitd.patch └── meson │ └── postinstall.py ├── data ├── appdata │ └── device-list.png ├── icons │ ├── hicolor │ │ ├── scalable │ │ │ └── apps │ │ │ │ └── org.gnome.NetworkDisplays.svg │ │ └── symbolic │ │ │ └── apps │ │ │ └── org.gnome.NetworkDisplays-symbolic.svg │ └── meson.build ├── meson.build ├── org.gnome.NetworkDisplays.appdata.xml.in ├── org.gnome.NetworkDisplays.desktop.in └── org.gnome.NetworkDisplays.gschema.xml ├── meson.build ├── meson_options.txt ├── po ├── LINGUAS ├── POTFILES.in ├── de.po ├── gnome-network-displays.pot ├── it.po ├── meson.build ├── pt_BR.po └── tr.po ├── scripts ├── uncrustify.cfg └── uncrustify.sh └── src ├── gnome-network-displays.gresource.xml ├── main.c ├── meson.build ├── nd-codec-install.c ├── nd-codec-install.h ├── nd-dummy-provider.c ├── nd-dummy-provider.h ├── nd-dummy-wfd-sink.c ├── nd-dummy-wfd-sink.h ├── nd-meta-provider.c ├── nd-meta-provider.h ├── nd-meta-sink.c ├── nd-meta-sink.h ├── nd-provider.c ├── nd-provider.h ├── nd-pulseaudio.c ├── nd-pulseaudio.h ├── nd-screencast-portal.c ├── nd-screencast-portal.h ├── nd-sink-list.c ├── nd-sink-list.h ├── nd-sink-row.c ├── nd-sink-row.h ├── nd-sink-row.ui ├── nd-sink.c ├── nd-sink.h ├── nd-wfd-p2p-provider.c ├── nd-wfd-p2p-provider.h ├── nd-wfd-p2p-registry.c ├── nd-wfd-p2p-registry.h ├── nd-wfd-p2p-sink.c ├── nd-wfd-p2p-sink.h ├── nd-window.c ├── nd-window.h ├── nd-window.ui └── wfd ├── meson.build ├── wfd-audio-codec.c ├── wfd-audio-codec.h ├── wfd-client.c ├── wfd-client.h ├── wfd-media-factory.c ├── wfd-media-factory.h ├── wfd-media.c ├── wfd-media.h ├── wfd-params.c ├── wfd-params.h ├── wfd-resolution.c ├── wfd-resolution.h ├── wfd-server.c ├── wfd-server.h ├── wfd-session-pool.c ├── wfd-session-pool.h ├── wfd-video-codec.c └── wfd-video-codec.h /NEWS: -------------------------------------------------------------------------------- 1 | GNOME Network Displays 0.90.3 2 | ============================= 3 | 4 | * Work around keep-alive problems with Microsoft v2 adapters (#20) 5 | * Disable keep-alive for pure RTSP connections (#81) 6 | * Work around LG devices by using a shorter session ID (#78) 7 | * Plenty of other bugfixes 8 | 9 | GNOME Network Displays 0.90.2 10 | ============================= 11 | 12 | * Improve QOS handling/performance of gstreamer pipeline 13 | * Fix issue with pipeline never stopping at disconnect 14 | 15 | New and updated translations: 16 | * Italian 17 | * Turkish -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WARNING 2 | # This project has been moved to https://gitlab.gnome.org/GNOME/gnome-network-displays 3 | ## All issues have also been moved there. 4 | 5 | -------- 6 | 7 | This is an experimental implementation of Wi-Fi Display (aka Miracast). 8 | 9 | The application will stream the selected monitor if the mutter screencast 10 | portal is available. If it is unavailable, a fallback to X11 based frame 11 | grabbing will happen. As such, it should work fine in almost all setups. 12 | 13 | To get audio streaming, you need to change the audio output in pulseaudio 14 | to use the created "Network-Displays" sink. 15 | 16 | To use it, you will need: 17 | * openh264 or x264 18 | * For audio supporting using AAC one of fdkaacenc, faac or avenc_aac 19 | * NetworkManager version > 1.15.2 20 | 21 | Build 22 | ===== 23 | 24 | To build it locally: 25 | 26 | - clone the repository 27 | - install `meson` 28 | - run `meson build` on the cloned repository 29 | - run `meson install` on the `build` folder created by meson 30 | 31 | Devices 32 | ======= 33 | 34 | The following devices have been tested: 35 | * Measy "Miracast Receiver" Model A2W 36 | - Announces itself as EZMirror/EZCast 37 | - Only supports uncompressed audio (LPCM) which is not supported yet 38 | * LG WebOS TV 39 | * MontoView (Software Revision 2.18.02) 40 | * MiraScreen 41 | 42 | Testing 43 | ======= 44 | 45 | For testing purposes you can run with NETWORK_DISPLAYS_DUMMY=1 set. In that case, a dummy 46 | sink will be provided that allows connecting on localhost using any RTSP capable 47 | client to test WFD streaming. 48 | 49 | You can connect to rtsp://localhost:7236/wfd1.0 then. 50 | 51 | Debugging 52 | ========= 53 | 54 | Codecs 55 | ------ 56 | 57 | Appropriate video/audio encoders will be selected automatically. You should 58 | also get a notification if codecs are missing and an offer to automatically 59 | install these. To debug the codecs themselves, you can modify the default 60 | choice by setting the `NETWORK_DISPLAYS_H264_ENC` and `NETWORK_DISPLAYS_AAC_ENC` 61 | environment variables and specifying the gstreamer element to use (if 62 | supported and detected). Run with `G_MESSAGES_DEBUG=all` to see the selection 63 | at work during connection establishment. 64 | 65 | Connection issues 66 | ----------------- 67 | 68 | P2P WiFi/WiFi Direct and the mechanism to establish a connection is a relatively 69 | complicated process that can fail in a number of different ways and stages. 70 | 71 | As a first step, you should run with `G_MESSAGES_DEBUG=all` to get a better 72 | idea when the failure happens. Your TV/Display may also give an indication 73 | about the progress. Much of the information is also displayed in the UI, but 74 | might only be visible for a very short period of time. 75 | 76 | ### Discovery 77 | 78 | The first required step is successful discovery of the remote device. 79 | This step can already be problematic sometimes. Confirm the following, each 80 | ensuring that support exists: 81 | 82 | * Run `gnome-network-displays` with `G_MESSAGES_DEBUG=all`. This will print 83 | a lot more information. Theoretically it may be that we see the device, but 84 | think that it is not WiFi Display capable (e.g. because the `wpa_supplicant` 85 | support is missing, see further below). Look out for the following messages: 86 | * `WFDP2PRegistry: Got NMClient`: 87 | The connection to NetworkManager works. 88 | * `WFDP2PRegistry: Found a new device, creating provider`: 89 | This means that we have a seemingly usable P2P device installed. 90 | * `WFDP2PProvider: Ignoring peer "XX:XX:XX:XX:XX" (Y) as it has no WFDIEs set`: 91 | This means there is a P2P device, but it does not seem to support WiFi 92 | Display. It may also mean that `wpa_supplicant` is not complied with 93 | the required support, see below. 94 | * `WFDP2PProvider: Found a new sink with peer X on device Y`: 95 | The device has been found, everything should be good. 96 | 97 | * `nmcli device` shows your WiFi device and a corresponding `p2p-dev-X` device. 98 | If you do not see your WiFi device, then NetworkManager is probably not 99 | managing it for some reason. Both the main WiFi device and the P2P device 100 | need to be managed by NetworkManager. 101 | * `iw dev` should show an `Unnamed/non-netdev interface` device with type 102 | `P2P-device`. 103 | 104 | If this is not the case, then the device is likely unsupported. 105 | One example of this are legacy `wext` drivers, which may support P2P 106 | operation, but are not (and will not be) supported by this software. 107 | * NetworkManager only support P2P operation together with `wpa_supplicant`. 108 | The use of `iwd` is currently not supported and you may have enabled it 109 | in NetworkManager. 110 | * If NetworkManager does not even show a device, then, as root, run 111 | 112 | ``` 113 | gdbus call --system --dest fi.w1.wpa_supplicant1 --object-path /fi/w1/wpa_supplicant1 --method org.freedesktop.DBus.Properties.Get fi.w1.wpa_supplicant1 Capabilities 114 | ``` 115 | 116 | This should output something like `(<['ap', 'ibss-rsn', 'p2p', 'pmf', 'mesh', 'ft', 'sha384']>,)`. 117 | Make sure that `p2p` is listed here. If it is not, then `wpa_supplicant` was 118 | compiled without P2P WiFi support (`CONFIG_P2P`). 119 | 120 | * If a device is shown, but it does not work, then, as root, run 121 | 122 | ``` 123 | gdbus call --system --dest fi.w1.wpa_supplicant1 --object-path /fi/w1/wpa_supplicant1 --method org.freedesktop.DBus.Properties.Get fi.w1.wpa_supplicant1 WFDIEs 124 | ``` 125 | 126 | If this runs successfully, it'll return `(<@ay []>,)`. This answer will be 127 | longer when we try to connect to a device, but only at that point. 128 | If it shows an error, then your `wpa_supplicant` was compiled without 129 | WiFi Display support (`CONFIG_WIFI_DISPLAY`). 130 | 131 | 132 | If everything looks good, but you still can't find the TV, then please try the 133 | following: 134 | * Search for it using another device at the same time. 135 | * Try running 136 | ``` 137 | gdbus call --system --dest fi.w1.wpa_supplicant1 --object-path /fi/w1/wpa_supplicant1 --method org.freedesktop.DBus.Properties.Set fi.w1.wpa_supplicant1 WFDIEs "<@ay [0x00, 0x00, 0x06, 0x00, 0x90, 0x1c, 0x44, 0x00, 0xc8]>" 138 | ``` 139 | Doing this will set the WFD Information Elements earlier in the process. It 140 | should not make a difference. But if it does, then some bigger changes will 141 | be needed. 142 | * If you can, try to get a capture of another device discovering it. 143 | ``` 144 | iw phy phyX interface add mon0 type monitor 145 | ip link set mon0 up 146 | ``` 147 | Then connect to it using `wireshark`. You may need to disable your normal 148 | WiFi connection and change the channel using `iw dev`. Explaining the details 149 | is out of scope for this document. 150 | 151 | ### Establishing a P2P Group 152 | 153 | When you click on the TV, the first step is to establish a WiFi P2P connection. 154 | Check whether you see the message: 155 | 156 | ``` 157 | Got state change notification from streaming sink to state ND_SINK_STATE_WAIT_P2P 158 | [...] 159 | Got state change notification from streaming sink to state ND_SINK_STATE_WAIT_SOCKET 160 | ``` 161 | 162 | If you see both of these messages, then we have successfully created a P2P Group. 163 | 164 | ### Network configuration and socket connection 165 | 166 | What happens next is that both side will configure their network. Then the TV 167 | will establish a connection to `gnome-network-displays`. 168 | 169 | Things could go wrong here: 170 | * Usually, NetworkManager will set up the network to be shared using `dnsmasq` 171 | * A local firewall might block DHCP requests/responses or prevent the later connection 172 | 173 | At this point the IP link is there. You can do the following: 174 | 175 | * Look into the NetworkManager log whether you see something obviously wrong 176 | (unlikely, as otherwise it should disconnect immediately). 177 | * The low level link is established (i.e. a P2P network device has been created). 178 | This means, to debug further, we can montior this network device and see what 179 | is happening. We should see a DHCP handshake to configure the network, and 180 | then an attempt to establish an RTSP connection. 181 | 182 | The easiest way to do this is to have `wireshark` running. Then, right after 183 | clicking on the TV, the P2P device will show up in the list. Quickly start a 184 | capture on it. 185 | 186 | Alternatively, use a script like the following (as root): 187 | 188 | ``` 189 | interface=""; while [ -z "$interface" ]; do interface=$( ls /sys/class/net/ | grep -- p2p- ); sleep 0.1; done; echo $interface; tcpdump -i "$interface" -w /tmp/p2p-connection-dump.pcap 190 | ``` 191 | 192 | And then open the created dump file `/tmp/p2p-connection-dump.pcap` in Wireshark. 193 | 194 | ### RTSP stream issues 195 | 196 | Not all devices are compliant, and the standard is a bit odd. If you have 197 | problems where the stream seems to start, but then does not work or stop after 198 | a while. 199 | 200 | Try the following: 201 | * Run with `G_MESSAGES_DEBUG=all` and check if you see something that is wrong. 202 | Note that certain warnings are expected. 203 | * Try grabbing a `tcpdump`/`wireshark` capture (see above) and check whether 204 | you see something weird in the RTSP stream. 205 | * It could also be an issue with the GStreamer pipeline not starting properly. 206 | This can possibly be debugged using e.g. `GST_DEBUG=*:5`, but is generally 207 | harder to pin point. Try it a few times, and check if the problem persists. 208 | -------------------------------------------------------------------------------- /build-aux/flatpak/org.gnome.NetworkDisplays.json: -------------------------------------------------------------------------------- 1 | { 2 | "app-id" : "org.gnome.NetworkDisplays", 3 | "runtime" : "org.gnome.Platform", 4 | "runtime-version" : "3.32", 5 | "sdk" : "org.gnome.Sdk", 6 | "command" : "gnome-network-displays", 7 | "finish-args" : [ 8 | "--share=ipc", 9 | "--share=network", 10 | "--socket=x11", 11 | "--socket=pulseaudio", 12 | "--socket=session-bus", 13 | "--socket=system-bus", 14 | "--socket=wayland", 15 | "--device=all" 16 | ], 17 | "modules" : [ 18 | { 19 | "name" : "gstreamer", 20 | "buildsystem" : "meson", 21 | "builddir" : true, 22 | "config-opts" : [ 23 | "-Ddisable_gtkdoc=true" 24 | ], 25 | "sources" : [ 26 | { 27 | "type" : "git", 28 | "branch" : "1.14.2", 29 | "url" : "git://anongit.freedesktop.org/gstreamer/gstreamer", 30 | "commit" : "afb3d1b3e0d02da8b0eb5bb501356650b38e5644" 31 | } 32 | ] 33 | }, 34 | { 35 | "name" : "gst-plugins-base", 36 | "buildsystem" : "meson", 37 | "config-opts" : [ 38 | "-Ddisable_gtkdoc=true" 39 | ], 40 | "builddir" : true, 41 | "sources" : [ 42 | { 43 | "type" : "git", 44 | "branch" : "1.14.2", 45 | "url" : "git://anongit.freedesktop.org/gstreamer/gst-plugins-base", 46 | "commit" : "24f097564657bd630e68931c932ec9fd992a3785" 47 | } 48 | ] 49 | }, 50 | { 51 | "name" : "gst-plugins-good", 52 | "buildsystem" : "meson", 53 | "builddir" : true, 54 | "config-opts" : [ 55 | "-Ddisable_gtkdoc=true" 56 | ], 57 | "sources" : [ 58 | { 59 | "type" : "git", 60 | "branch" : "1.14.2", 61 | "url" : "git://anongit.freedesktop.org/gstreamer/gst-plugins-good", 62 | "commit" : "4733e9715675d2dc52ed00e58d52388c56411cd2" 63 | } 64 | ] 65 | }, 66 | { 67 | "name" : "gst-plugins-ugly", 68 | "buildsystem" : "meson", 69 | "builddir" : true, 70 | "config-opts" : [ 71 | "-Ddisable_gtkdoc=true" 72 | ], 73 | "sources" : [ 74 | { 75 | "type" : "git", 76 | "branch" : "1.14.2", 77 | "url" : "git://anongit.freedesktop.org/gstreamer/gst-plugins-ugly", 78 | "commit" : "df1bcfa0ba57859eb39610ef53f1eb744ca57465" 79 | } 80 | ] 81 | }, 82 | { 83 | "name" : "gst-plugins-bad", 84 | "buildsystem" : "meson", 85 | "builddir" : true, 86 | "config-opts" : [ 87 | "-Ddisable_gtkdoc=true" 88 | ], 89 | "sources" : [ 90 | { 91 | "type" : "git", 92 | "branch" : "1.14.5", 93 | "url" : "git://anongit.freedesktop.org/gstreamer/gst-plugins-bad.git", 94 | "commit" : "dc1b94a15290172ffcc57c57f2b7ff6ee9322a5d" 95 | } 96 | ] 97 | }, 98 | { 99 | "name" : "gst-rtsp-server", 100 | "buildsystem" : "meson", 101 | "builddir" : true, 102 | "config-opts" : [ 103 | "-Ddisable_gtkdoc=true" 104 | ], 105 | "sources" : [ 106 | { 107 | "type" : "git", 108 | "branch" : "1.14.5", 109 | "url" : "https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server.git", 110 | "commit" : "1ad54eecfdf634ab6eb672c81df1602d004d75b8" 111 | } 112 | ] 113 | }, 114 | { 115 | "name" : "udev", 116 | "config-opts" : [ 117 | "--disable-hwdb", 118 | "--disable-logging", 119 | "--disable-gudev", 120 | "--disable-introspection", 121 | "--disable-keymap", 122 | "--disable-mtd_probe" 123 | ], 124 | "cleanup" : [ 125 | "/include", 126 | "/etc", 127 | "/libexec", 128 | "/sbin", 129 | "/lib/pkgconfig", 130 | "/man", 131 | "/share/aclocal", 132 | "/share/doc", 133 | "/share/gtk-doc", 134 | "/share/man", 135 | "/share/pkgconfig", 136 | "*.la", 137 | "*.a" 138 | ], 139 | "sources" : [ 140 | { 141 | "type" : "git", 142 | "url" : "git://github.com/gentoo/eudev.git" 143 | } 144 | ] 145 | }, 146 | { 147 | "name" : "libndp", 148 | "buildsystem" : "autotools", 149 | "sources" : [ 150 | { 151 | "type" : "git", 152 | "branch" : "v1.6", 153 | "url" : "https://github.com/jpirko/libndp.git", 154 | "commit" : "2f721c4ff519f38f46695a60d9f9d88f35bf3c1d" 155 | } 156 | ] 157 | }, 158 | { 159 | "name" : "polkit", 160 | "config-opts" : [ 161 | "--disable-polkitd", 162 | "--disable-man-pages", 163 | "--disable-introspection", 164 | "--enable-libsystemd-login=no", 165 | "--with-systemdsystemunitdir=no" 166 | ], 167 | "rm-configure" : true, 168 | "cleanup" : [ 169 | "/bin/*", 170 | "/etc/pam.d", 171 | "/etc/dbus-1", 172 | "/share/dbus-1/system-services/*", 173 | "/share/polkit-1", 174 | "/lib/polkit-1" 175 | ], 176 | "sources" : [ 177 | { 178 | "type" : "archive", 179 | "url" : "http://www.freedesktop.org/software/polkit/releases/polkit-0.113.tar.gz", 180 | "sha256" : "e1c095093c654951f78f8618d427faf91cf62abdefed98de40ff65eca6413c81" 181 | }, 182 | { 183 | "type" : "patch", 184 | "path" : "polkit-build-Add-option-to-build-without-polkitd.patch" 185 | }, 186 | { 187 | "type" : "file", 188 | "path" : "polkit-autogen", 189 | "dest-filename" : "autogen.sh" 190 | } 191 | ] 192 | }, 193 | { 194 | "name" : "NetworkManager", 195 | "buildsystem" : "meson", 196 | "build-options" : { 197 | "cflags" : "-ltinfo", 198 | "cxxflags" : "-ltinfo" 199 | }, 200 | "config-opts" : [ 201 | "-Dlibaudit=no", 202 | "-Ddbus_conf_dir=/app/etc/dbus-1/system.d", 203 | "-Ddbus_ifaces_dir=/app/share/dbus-1/interfaces", 204 | "-Ddbus_sys_dir=/app/share/dbus-1/system.d", 205 | "-Ddnsmasq=/usr/bin/true", 206 | "-Ddocs=false", 207 | "-Dintrospection=false", 208 | "-Diptables=/usr/bin/true", 209 | "-Djson_validation=false", 210 | "-Dlibnm_glib=false", 211 | "-Dlibpsl=false", 212 | "-Dmodem_manager=false", 213 | "-Dnmtui=false", 214 | "-Dovs=false", 215 | "-Dppp=false", 216 | "-Dqt=false", 217 | "-Dselinux=false", 218 | "-Dsession_tracking=no", 219 | "-Dsystemdsystemunitdir='no'", 220 | "-Dsystemd_journal=false", 221 | "-Dtests=no", 222 | "-Dvapi=false", 223 | "-Dwifi=true" 224 | ], 225 | "sources" : [ 226 | { 227 | "type" : "git", 228 | "url" : "https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git" 229 | } 230 | ] 231 | }, 232 | { 233 | "name" : "gnome-network-displays", 234 | "buildsystem": "meson", 235 | "sources" : [ 236 | { 237 | "type" : "git", 238 | "url" : "https://github.com/benzea/gnome-network-displays.git" 239 | } 240 | ] 241 | } 242 | ] 243 | } 244 | -------------------------------------------------------------------------------- /build-aux/flatpak/polkit-autogen: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | gtkdocize --flavour no-tmpl 4 | autoreconf -if 5 | -------------------------------------------------------------------------------- /build-aux/flatpak/polkit-build-Add-option-to-build-without-polkitd.patch: -------------------------------------------------------------------------------- 1 | From dab179770380918462d0d76e08b11e4abe55c933 Mon Sep 17 00:00:00 2001 2 | From: Patrick Griffis 3 | Date: Thu, 8 Sep 2016 16:15:54 -0400 4 | Subject: [PATCH] build: Add option to build without polkitd 5 | 6 | This is for any consumer that needs libpolkit but does 7 | not need polkitd. For example an application running in 8 | flatpak. 9 | --- 10 | configure.ac | 29 ++++++++++++++++++++--------- 11 | src/Makefile.am | 6 +++++- 12 | test/Makefile.am | 6 +++++- 13 | 3 files changed, 30 insertions(+), 11 deletions(-) 14 | 15 | diff --git a/configure.ac b/configure.ac 16 | index 97d4222..a08785c 100644 17 | --- a/configure.ac 18 | +++ b/configure.ac 19 | @@ -129,20 +129,30 @@ AC_DEFINE([GLIB_VERSION_MIN_REQUIRED], [GLIB_VERSION_2_30], 20 | AC_DEFINE([GLIB_VERSION_MAX_ALLOWED], [G_ENCODE_VERSION(2,34)], 21 | [Notify us when we'll need to transition away from g_type_init()]) 22 | 23 | + 24 | +AC_ARG_ENABLE([polkitd], 25 | + [AS_HELP_STRING([--disable-polkitd], [Do not build polkitd])], 26 | + [enable_polkitd=$enableval], [enable_polkitd=yes]) 27 | +AM_CONDITIONAL(BUILD_POLKITD, [test x${enable_polkitd} = yes]) 28 | + 29 | + 30 | AC_ARG_WITH(mozjs, AS_HELP_STRING([--with-mozjs=@<:@mozjs185/mozjs-17.0|auto@:>@], 31 | [Specify version of Spidermonkey to use]),, 32 | with_mozjs=auto) 33 | -AS_IF([test x${with_mozjs} != xauto], [ 34 | - PKG_CHECK_MODULES(LIBJS, ${with_mozjs}) 35 | -], [ 36 | - PKG_CHECK_MODULES(LIBJS, [mozjs185], have_mozjs185=yes, have_mozjs185=no) 37 | - AS_IF([test x${have_mozjs185} = xno], [ 38 | - PKG_CHECK_MODULES(LIBJS, [mozjs-17.0], have_mozjs17=yes, 39 | - [AC_MSG_ERROR([Could not find mozjs185 or mozjs-17.0; see http://ftp.mozilla.org/pub/mozilla.org/js/])]) 40 | + 41 | +AS_IF([test x${enable_polkitd} = yes], [ 42 | + AS_IF([test x${with_mozjs} != xauto], [ 43 | + PKG_CHECK_MODULES(LIBJS, ${with_mozjs}) 44 | + ], [ 45 | + PKG_CHECK_MODULES(LIBJS, [mozjs185], have_mozjs185=yes, have_mozjs185=no) 46 | + AS_IF([test x${have_mozjs185} = xno], [ 47 | + PKG_CHECK_MODULES(LIBJS, [mozjs-17.0], have_mozjs17=yes, 48 | + [AC_MSG_ERROR([Could not find mozjs185 or mozjs-17.0; see http://ftp.mozilla.org/pub/mozilla.org/js/])]) 49 | + ]) 50 | ]) 51 | + AC_SUBST(LIBJS_CFLAGS) 52 | + AC_SUBST(LIBJS_LIBS) 53 | ]) 54 | -AC_SUBST(LIBJS_CFLAGS) 55 | -AC_SUBST(LIBJS_LIBS) 56 | 57 | EXPAT_LIB="" 58 | AC_ARG_WITH(expat, [ --with-expat= Use expat from here], 59 | @@ -605,6 +615,7 @@ echo " 60 | Session tracking: ${SESSION_TRACKING} 61 | PAM support: ${have_pam} 62 | systemdsystemunitdir: ${systemdsystemunitdir} 63 | + polkitd: ${enable_polkitd} 64 | polkitd user: ${POLKITD_USER}" 65 | 66 | if test "$have_pam" = yes ; then 67 | diff --git a/src/Makefile.am b/src/Makefile.am 68 | index 09fc7b3..c6fe91b 100644 69 | --- a/src/Makefile.am 70 | +++ b/src/Makefile.am 71 | @@ -1,5 +1,9 @@ 72 | 73 | -SUBDIRS = polkit polkitbackend polkitagent programs 74 | +SUBDIRS = polkit polkitagent programs 75 | + 76 | +if BUILD_POLKITD 77 | +SUBDIRS += polkitbackend 78 | +endif 79 | 80 | if BUILD_EXAMPLES 81 | SUBDIRS += examples 82 | diff --git a/test/Makefile.am b/test/Makefile.am 83 | index 59d0680..d43b0fe 100644 84 | --- a/test/Makefile.am 85 | +++ b/test/Makefile.am 86 | @@ -1,7 +1,11 @@ 87 | 88 | -SUBDIRS = mocklibc . polkit polkitbackend 89 | +SUBDIRS = mocklibc . polkit 90 | AM_CFLAGS = $(GLIB_CFLAGS) 91 | 92 | +if BUILD_POLKITD 93 | +SUBDIRS += polkitbackend 94 | +endif 95 | + 96 | noinst_LTLIBRARIES = libpolkit-test-helper.la 97 | libpolkit_test_helper_la_SOURCES = polkittesthelper.c polkittesthelper.h 98 | libpolkit_test_helper_la_LIBADD = $(GLIB_LIBS) 99 | -- 100 | 2.9.3 101 | 102 | -------------------------------------------------------------------------------- /build-aux/meson/postinstall.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from os import environ, path 4 | from subprocess import call 5 | 6 | prefix = environ.get('MESON_INSTALL_PREFIX', '/usr/local') 7 | datadir = path.join(prefix, 'share') 8 | destdir = environ.get('DESTDIR', '') 9 | 10 | # Package managers set this so we don't need to run 11 | if not destdir: 12 | print('Updating icon cache...') 13 | call(['gtk-update-icon-cache', '-qtf', path.join(datadir, 'icons', 'hicolor')]) 14 | 15 | print('Updating desktop database...') 16 | call(['update-desktop-database', '-q', path.join(datadir, 'applications')]) 17 | 18 | print('Compiling GSettings schemas...') 19 | call(['glib-compile-schemas', path.join(datadir, 'glib-2.0', 'schemas')]) 20 | 21 | 22 | -------------------------------------------------------------------------------- /data/appdata/device-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benzea/gnome-network-displays/74a807b49ba9ee1b20cc2744406489f2b4958cde/data/appdata/device-list.png -------------------------------------------------------------------------------- /data/icons/hicolor/symbolic/apps/org.gnome.NetworkDisplays-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 14 | 15 | 17 | image/svg+xml 18 | 20 | Gnome Symbolic Icon Theme 21 | 22 | 23 | 24 | Gnome Symbolic Icon Theme 26 | 28 | 32 | 37 | 38 | -------------------------------------------------------------------------------- /data/icons/meson.build: -------------------------------------------------------------------------------- 1 | install_subdir('hicolor', install_dir: icondir) -------------------------------------------------------------------------------- /data/meson.build: -------------------------------------------------------------------------------- 1 | subdir('icons') 2 | 3 | desktop_file = i18n.merge_file( 4 | input: 'org.gnome.NetworkDisplays.desktop.in', 5 | output: 'org.gnome.NetworkDisplays.desktop', 6 | type: 'desktop', 7 | po_dir: '../po', 8 | install: true, 9 | install_dir: join_paths(get_option('datadir'), 'applications') 10 | ) 11 | 12 | desktop_utils = find_program('desktop-file-validate', required: false) 13 | if desktop_utils.found() 14 | test('Validate desktop file', desktop_utils, 15 | args: [desktop_file] 16 | ) 17 | endif 18 | 19 | appstream_file = i18n.merge_file( 20 | input: 'org.gnome.NetworkDisplays.appdata.xml.in', 21 | output: 'org.gnome.NetworkDisplays.appdata.xml', 22 | po_dir: '../po', 23 | install: true, 24 | install_dir: join_paths(get_option('datadir'), 'metainfo') 25 | ) 26 | 27 | appstream_util = find_program('appstream-util', required: false) 28 | if appstream_util.found() 29 | test('Validate appstream file', appstream_util, 30 | args: ['validate', appstream_file] 31 | ) 32 | endif 33 | 34 | install_data('org.gnome.NetworkDisplays.gschema.xml', 35 | install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas') 36 | ) 37 | 38 | compile_schemas = find_program('glib-compile-schemas', required: false) 39 | if compile_schemas.found() 40 | test('Validate schema file', compile_schemas, 41 | args: ['--strict', '--dry-run', meson.current_source_dir()] 42 | ) 43 | endif 44 | -------------------------------------------------------------------------------- /data/org.gnome.NetworkDisplays.appdata.xml.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | org.gnome.NetworkDisplays.desktop 4 | CC0-1.0 5 | GPL-3.0-or-later 6 | GNOME Network Displays 7 | Stream the desktop to Wi-Fi Display capable devices 8 | https://github.com/benzea/gnome-screencast 9 | gnome-network-displays 10 | 11 | 12 | https://raw.githubusercontent.com/benzea/gnome-screencast/master/data/appdata/device-list.png 13 | 14 | 15 | 16 |

17 | GNOME Network Displays allows you to cast your desktop to a remote display. Currently 18 | implemented is support for casting to Wi-Fi Display capable devices (a.k.a. Miracast). 19 |

20 |
21 | bberg@gnome.org 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | -------------------------------------------------------------------------------- /data/org.gnome.NetworkDisplays.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=GNOME Network Displays 3 | Exec=gnome-network-displays 4 | # Translators: Do NOT translate or transliterate this text (this is an icon file name)! 5 | Icon=org.gnome.NetworkDisplays 6 | Terminal=false 7 | Type=Application 8 | Categories=AudioVideo; 9 | StartupNotify=true 10 | -------------------------------------------------------------------------------- /data/org.gnome.NetworkDisplays.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('gnome-network-displays', 'c', 2 | version: '0.90.3', 3 | meson_version: '>= 0.46.1', 4 | ) 5 | 6 | gnome = import('gnome') 7 | i18n = import('i18n') 8 | 9 | datadir = get_option('datadir') 10 | icondir = join_paths(datadir, 'icons') 11 | 12 | config_h = configuration_data() 13 | config_h.set_quoted('PACKAGE_VERSION', meson.project_version()) 14 | config_h.set_quoted('GETTEXT_PACKAGE', 'gnome-network-displays') 15 | config_h.set_quoted('LOCALEDIR', join_paths(get_option('prefix'), get_option('localedir'))) 16 | configure_file( 17 | output: 'gnome-network-displays-config.h', 18 | configuration: config_h, 19 | ) 20 | add_project_arguments([ 21 | '-I' + meson.build_root(), 22 | ], language: 'c') 23 | 24 | subdir('data') 25 | subdir('src') 26 | subdir('po') 27 | 28 | meson.add_install_script('build-aux/meson/postinstall.py') 29 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benzea/gnome-network-displays/74a807b49ba9ee1b20cc2744406489f2b4958cde/meson_options.txt -------------------------------------------------------------------------------- /po/LINGUAS: -------------------------------------------------------------------------------- 1 | de 2 | pt_BR 3 | -------------------------------------------------------------------------------- /po/POTFILES.in: -------------------------------------------------------------------------------- 1 | data/org.gnome.NetworkDisplays.desktop.in 2 | data/org.gnome.NetworkDisplays.appdata.xml.in 3 | data/org.gnome.NetworkDisplays.gschema.xml 4 | src/nd-window.ui 5 | src/main.c 6 | src/nd-window.c 7 | 8 | -------------------------------------------------------------------------------- /po/de.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the gnome-network-displays package. 4 | # FIRST AUTHOR , YEAR. 5 | # Benjamin Berg , 2019. 6 | # 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: gnome-network-displays\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2019-06-17 11:15+0200\n" 12 | "PO-Revision-Date: 2019-06-17 11:15+0200\n" 13 | "Last-Translator: Benjamin Berg \n" 14 | "Language-Team: German <->\n" 15 | "Language: de\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=(n != 1)\n" 20 | "X-Generator: Gtranslator 3.32.1\n" 21 | 22 | #: data/org.gnome.NetworkDisplays.desktop.in:3 23 | #: data/org.gnome.NetworkDisplays.appdata.xml.in:6 24 | msgid "GNOME Network Displays" 25 | msgstr "GNOME Netzwerkbildschirme" 26 | 27 | #. Translators: Do NOT translate or transliterate this text (this is an icon file name)! 28 | #: data/org.gnome.NetworkDisplays.desktop.in:6 29 | msgid "org.gnome.NetworkDisplays" 30 | msgstr "" 31 | 32 | #: data/org.gnome.NetworkDisplays.appdata.xml.in:7 33 | msgid "Stream the desktop to Wi-Fi Display capable devices" 34 | msgstr "Zeigt den Desktop auf Netzwerkfähigen Bildschirmen an" 35 | 36 | #: data/org.gnome.NetworkDisplays.appdata.xml.in:16 37 | msgid "" 38 | "GNOME Network Displays allows you to cast your desktop to a remote display. " 39 | "Currently implemented is support for casting to Wi-Fi Display capable " 40 | "devices (a.k.a. Miracast)." 41 | msgstr "" 42 | "GNOME Netzwerkbildschirme erlaubt die Nutzung von netzwerkfähigen Monitoren. " 43 | "Derzeit werden „Wi-Fi Display“ (Miracast) fähige Monitore unterstützt." 44 | 45 | #: src/nd-window.ui:74 46 | msgid "No Wi‑Fi P2P adapters found" 47 | msgstr "Es wurde kein Wi-Fi P2P Adapter gefunden" 48 | 49 | #: src/nd-window.ui:89 50 | msgid "" 51 | "No usable wireless adapters were found. Please verify that Wi‑Fi is enabled " 52 | "and Wi‑Fi P2P operations are available in both NetworkManager and " 53 | "wpa_supplicant." 54 | msgstr "" 55 | "Es wurde kein nutzbares W-Lan Gerät gefunden. Bitte erprüfen Sie, dass W-Lan " 56 | "eingeschaltet und Wi-Fi P2P sowohl von NetworkManager als auch von " 57 | "wpa_supplicant unterstützt wird." 58 | 59 | #: src/nd-window.ui:174 60 | msgid "Available Video Sinks" 61 | msgstr "Gefundene Bildschirme" 62 | 63 | #: src/nd-window.ui:334 64 | msgid "Connecting" 65 | msgstr "Verbindungsaufbau" 66 | 67 | #: src/nd-window.ui:396 src/nd-window.ui:577 68 | msgid "Cancel" 69 | msgstr "Abbrechen" 70 | 71 | #: src/nd-window.ui:499 72 | msgid "Streaming" 73 | msgstr "Streaming" 74 | 75 | #: src/nd-window.ui:556 src/nd-window.ui:782 76 | msgid "" 77 | "One of the following video codec plugins is required.\n" 78 | "Clicking will start the installation process." 79 | msgstr "" 80 | "Einer der folgenden Video-Codec Plugins wird benötigt.\n" 81 | "Ein Klick started die Installation." 82 | 83 | #: src/nd-window.ui:601 src/nd-window.ui:798 84 | msgid "" 85 | "One of the following audio codec plugins is required for audio support.\n" 86 | "Clicking will start the installation process." 87 | msgstr "" 88 | "Einer der folgenden Audio-Codec Plugins wird benötigt um auch Ton zu " 89 | "übertragen.\n" 90 | "Ein Klick started die Installation." 91 | 92 | #: src/nd-window.ui:696 93 | msgid "Error" 94 | msgstr "Fehler" 95 | 96 | #: src/nd-window.ui:758 97 | msgid "Return" 98 | msgstr "Zurück" 99 | 100 | #: src/nd-window.c:149 101 | msgid "Making P2P connection" 102 | msgstr "P2P Verbindung wird aufgebaut" 103 | 104 | #: src/nd-window.c:156 105 | msgid "Establishing connection to sink" 106 | msgstr "Verbindung wird aufgebaut" 107 | 108 | #: src/nd-window.c:163 109 | msgid "Starting to stream" 110 | msgstr "Stream wird gestartet" 111 | -------------------------------------------------------------------------------- /po/gnome-network-displays.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the gnome-network-displays package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: gnome-network-displays\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2019-06-17 11:15+0200\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: data/org.gnome.NetworkDisplays.desktop.in:3 21 | #: data/org.gnome.NetworkDisplays.appdata.xml.in:6 22 | msgid "GNOME Network Displays" 23 | msgstr "" 24 | 25 | #. Translators: Do NOT translate or transliterate this text (this is an icon file name)! 26 | #: data/org.gnome.NetworkDisplays.desktop.in:6 27 | msgid "org.gnome.NetworkDisplays" 28 | msgstr "" 29 | 30 | #: data/org.gnome.NetworkDisplays.appdata.xml.in:7 31 | msgid "Stream the desktop to Wi-Fi Display capable devices" 32 | msgstr "" 33 | 34 | #: data/org.gnome.NetworkDisplays.appdata.xml.in:16 35 | msgid "" 36 | "GNOME Network Displays allows you to cast your desktop to a remote display. " 37 | "Currently implemented is support for casting to Wi-Fi Display capable " 38 | "devices (a.k.a. Miracast)." 39 | msgstr "" 40 | 41 | #: src/nd-window.ui:74 42 | msgid "No Wi‑Fi P2P adapters found" 43 | msgstr "" 44 | 45 | #: src/nd-window.ui:89 46 | msgid "" 47 | "No usable wireless adapters were found. Please verify that Wi‑Fi is enabled " 48 | "and Wi‑Fi P2P operations are available in both NetworkManager and " 49 | "wpa_supplicant." 50 | msgstr "" 51 | 52 | #: src/nd-window.ui:174 53 | msgid "Available Video Sinks" 54 | msgstr "" 55 | 56 | #: src/nd-window.ui:334 57 | msgid "Connecting" 58 | msgstr "" 59 | 60 | #: src/nd-window.ui:396 src/nd-window.ui:577 61 | msgid "Cancel" 62 | msgstr "" 63 | 64 | #: src/nd-window.ui:499 65 | msgid "Streaming" 66 | msgstr "" 67 | 68 | #: src/nd-window.ui:556 src/nd-window.ui:782 69 | msgid "" 70 | "One of the following video codec plugins is required.\n" 71 | "Clicking will start the installation process." 72 | msgstr "" 73 | 74 | #: src/nd-window.ui:601 src/nd-window.ui:798 75 | msgid "" 76 | "One of the following audio codec plugins is required for audio support.\n" 77 | "Clicking will start the installation process." 78 | msgstr "" 79 | 80 | #: src/nd-window.ui:696 81 | msgid "Error" 82 | msgstr "" 83 | 84 | #: src/nd-window.ui:758 85 | msgid "Return" 86 | msgstr "" 87 | 88 | #: src/nd-window.c:149 89 | msgid "Making P2P connection" 90 | msgstr "" 91 | 92 | #: src/nd-window.c:156 93 | msgid "Establishing connection to sink" 94 | msgstr "" 95 | 96 | #: src/nd-window.c:163 97 | msgid "Starting to stream" 98 | msgstr "" 99 | -------------------------------------------------------------------------------- /po/it.po: -------------------------------------------------------------------------------- 1 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 2 | # This file is distributed under the same license as the gnome-network-displays package. 3 | # FIRST AUTHOR , YEAR. 4 | # Benjamin Berg , 2019. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: gnome-network-displays\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2019-06-17 11:15+0200\n" 11 | "PO-Revision-Date: 2019-09-21 14:35+0200\n" 12 | "Last-Translator: albano battistella \n" 13 | "Language-Team: Albano Battistella \n" 14 | "Language: it_IT\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 19 | "X-Generator: Poedit 1.8.7.1\n" 20 | 21 | #: data/org.gnome.NetworkDisplays.desktop.in:3 22 | #: data/org.gnome.NetworkDisplays.appdata.xml.in:6 23 | msgid "GNOME Network Displays" 24 | msgstr "GNOME Network Displays" 25 | 26 | #. Translators: Do NOT translate or transliterate this text (this is an icon file name)! 27 | #: data/org.gnome.NetworkDisplays.desktop.in:6 28 | msgid "org.gnome.NetworkDisplays" 29 | msgstr "org.gnome.NetworkDisplays" 30 | 31 | #: data/org.gnome.NetworkDisplays.appdata.xml.in:7 32 | msgid "Stream the desktop to Wi-Fi Display capable devices" 33 | msgstr "Trasmetti il desktop ai dispositivi compatibili con Wi-Fi Display" 34 | 35 | #: data/org.gnome.NetworkDisplays.appdata.xml.in:16 36 | msgid "" 37 | "GNOME Network Displays allows you to cast your desktop to a remote display. " 38 | "Currently implemented is support for casting to Wi-Fi Display capable " 39 | "devices (a.k.a. Miracast)." 40 | msgstr "" 41 | "GNOME Network Display ti consente di trasmettere il tuo desktop a un display " 42 | "remoto. Attualmente è implementato il supporto per il casting su dispositivi " 43 | "compatibili con Wi-Fi Display (a.k.a. Miracast)." 44 | 45 | #: src/nd-window.ui:74 46 | msgid "No Wi‑Fi P2P adapters found" 47 | msgstr "Nessun adattatore P2P Wi‑Fi trovato" 48 | 49 | #: src/nd-window.ui:89 50 | msgid "" 51 | "No usable wireless adapters were found. Please verify that Wi‑Fi is enabled " 52 | "and Wi‑Fi P2P operations are available in both NetworkManager and " 53 | "wpa_supplicant." 54 | msgstr "" 55 | "Non sono stati trovati adattatori wireless utilizzabili. Verificare che il " 56 | "Wi ‑Fi sia abilitato e che le operazioni P2P Wi‑Fi siano disponibili sia in " 57 | "NetworkManager che in wpa_supplicant." 58 | 59 | #: src/nd-window.ui:174 60 | msgid "Available Video Sinks" 61 | msgstr "Disponibile Video Sinks" 62 | 63 | #: src/nd-window.ui:334 64 | msgid "Connecting" 65 | msgstr "Collegamento" 66 | 67 | #: src/nd-window.ui:396 src/nd-window.ui:577 68 | msgid "Cancel" 69 | msgstr "Cancella" 70 | 71 | #: src/nd-window.ui:499 72 | msgid "Streaming" 73 | msgstr "Streaming" 74 | 75 | #: src/nd-window.ui:556 src/nd-window.ui:782 76 | msgid "" 77 | "One of the following video codec plugins is required.\n" 78 | "Clicking will start the installation process." 79 | msgstr "" 80 | "È richiesto uno dei seguenti plugins di codec video.\n" 81 | "Cliccando si avvia il processo di installazione." 82 | 83 | #: src/nd-window.ui:601 src/nd-window.ui:798 84 | msgid "" 85 | "One of the following audio codec plugins is required for audio support.\n" 86 | "Clicking will start the installation process." 87 | msgstr "" 88 | "Per il supporto audio è necessario uno dei seguenti plugins di codec audio.\n" 89 | "Cliccando si avvia il processo di installazione." 90 | 91 | #: src/nd-window.ui:696 92 | msgid "Error" 93 | msgstr "Errore" 94 | 95 | #: src/nd-window.ui:758 96 | msgid "Return" 97 | msgstr "Ritorna" 98 | 99 | #: src/nd-window.c:149 100 | msgid "Making P2P connection" 101 | msgstr "Connessione P2P" 102 | 103 | #: src/nd-window.c:156 104 | msgid "Establishing connection to sink" 105 | msgstr "Stabilire la connessione al sink" 106 | 107 | #: src/nd-window.c:163 108 | msgid "Starting to stream" 109 | msgstr "Iniziare a trasmettere" 110 | -------------------------------------------------------------------------------- /po/meson.build: -------------------------------------------------------------------------------- 1 | i18n.gettext('gnome-network-displays', preset: 'glib') 2 | -------------------------------------------------------------------------------- /po/pt_BR.po: -------------------------------------------------------------------------------- 1 | # Portuguese translations for gnome-network-displays package 2 | # Traduções em português brasileiro para o pacote gnome-network-displays. 3 | # Copyright (C) 2020 THE gnome-network-displays'S COPYRIGHT HOLDER 4 | # This file is distributed under the same license as the gnome-network-displays package. 5 | # Rafael Fontenelle , 2020. 6 | # 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: gnome-network-displays\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2019-06-17 11:15+0200\n" 12 | "PO-Revision-Date: 2020-06-06 21:39-0300\n" 13 | "Last-Translator: Rafael Fontenelle \n" 14 | "Language-Team: Brazilian Portuguese \n" 15 | "Language: pt_BR\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=(n > 1)\n" 20 | "X-Generator: Gtranslator 3.36.0\n" 21 | "X-Project-Style: gnome\n" 22 | 23 | #: data/org.gnome.NetworkDisplays.desktop.in:3 24 | #: data/org.gnome.NetworkDisplays.appdata.xml.in:6 25 | msgid "GNOME Network Displays" 26 | msgstr "GNOME Tela via Rede" 27 | 28 | #. Translators: Do NOT translate or transliterate this text (this is an icon file name)! 29 | #: data/org.gnome.NetworkDisplays.desktop.in:6 30 | msgid "org.gnome.NetworkDisplays" 31 | msgstr "org.gnome.NetworkDisplays" 32 | 33 | #: data/org.gnome.NetworkDisplays.appdata.xml.in:7 34 | msgid "Stream the desktop to Wi-Fi Display capable devices" 35 | msgstr "" 36 | "Transmita do seu computador para dispositivos com capacidade de Wi-Fi Display" 37 | 38 | #: data/org.gnome.NetworkDisplays.appdata.xml.in:16 39 | msgid "" 40 | "GNOME Network Displays allows you to cast your desktop to a remote display. " 41 | "Currently implemented is support for casting to Wi-Fi Display capable " 42 | "devices (a.k.a. Miracast)." 43 | msgstr "" 44 | "GNOME Tela via Rede permite projetar seu computador em uma tela remota. " 45 | "Atualmente está implementado o suporte à projeção em dispositivos " 46 | "compatíveis com o Wi-Fi Display (também conhecido como Miracast)." 47 | 48 | #: src/nd-window.ui:74 49 | msgid "No Wi‑Fi P2P adapters found" 50 | msgstr "Nenhum adaptador Wi-Fi P2P encontrado" 51 | 52 | #: src/nd-window.ui:89 53 | msgid "" 54 | "No usable wireless adapters were found. Please verify that Wi‑Fi is enabled " 55 | "and Wi‑Fi P2P operations are available in both NetworkManager and " 56 | "wpa_supplicant." 57 | msgstr "" 58 | "Nenhum adaptador sem fio utilizável foi encontrado. Verifique se o Wi-Fi " 59 | "está ativado e as operações de Wi-Fi P2P estão disponíveis no NetworkManager " 60 | "e no wpa_supplicant." 61 | 62 | #: src/nd-window.ui:174 63 | msgid "Available Video Sinks" 64 | msgstr "Coletores de vídeo disponíveis" 65 | 66 | #: src/nd-window.ui:334 67 | msgid "Connecting" 68 | msgstr "Conectando" 69 | 70 | #: src/nd-window.ui:396 src/nd-window.ui:577 71 | msgid "Cancel" 72 | msgstr "Cancelar" 73 | 74 | #: src/nd-window.ui:499 75 | msgid "Streaming" 76 | msgstr "Transmitindo" 77 | 78 | #: src/nd-window.ui:556 src/nd-window.ui:782 79 | msgid "" 80 | "One of the following video codec plugins is required.\n" 81 | "Clicking will start the installation process." 82 | msgstr "" 83 | "Um dos seguintes plugins de codec de vídeo é necessário.\n" 84 | "Clicar vai iniciar o processo de instalação." 85 | 86 | #: src/nd-window.ui:601 src/nd-window.ui:798 87 | msgid "" 88 | "One of the following audio codec plugins is required for audio support.\n" 89 | "Clicking will start the installation process." 90 | msgstr "" 91 | "Um dos seguintes plugins de codec de áudio é necessário para suporte a " 92 | "áudio.\n" 93 | "Clicar vai iniciar o processo de instalação." 94 | 95 | #: src/nd-window.ui:696 96 | msgid "Error" 97 | msgstr "Erro" 98 | 99 | #: src/nd-window.ui:758 100 | msgid "Return" 101 | msgstr "Retornar" 102 | 103 | #: src/nd-window.c:149 104 | msgid "Making P2P connection" 105 | msgstr "Fazendo a conexão P2P" 106 | 107 | #: src/nd-window.c:156 108 | msgid "Establishing connection to sink" 109 | msgstr "Estabelecendo conexão com o coletor" 110 | 111 | #: src/nd-window.c:163 112 | msgid "Starting to stream" 113 | msgstr "Iniciando a transmissão" 114 | -------------------------------------------------------------------------------- /po/tr.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the gnome-network-displays package. 4 | # Gökhan GÖKKAYA , 2019. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: gnome-network-displays\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2019-06-17 11:15+0200\n" 11 | "PO-Revision-Date: 2019-07-26 15:31+0300\n" 12 | "Language-Team: \n" 13 | "MIME-Version: 1.0\n" 14 | "Content-Type: text/plain; charset=UTF-8\n" 15 | "Content-Transfer-Encoding: 8bit\n" 16 | "X-Generator: Poedit 2.0.6\n" 17 | "Last-Translator: Gökhan Gökkaya \n" 18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 19 | "Language: tr\n" 20 | 21 | #: data/org.gnome.NetworkDisplays.desktop.in:3 22 | #: data/org.gnome.NetworkDisplays.appdata.xml.in:6 23 | msgid "GNOME Network Displays" 24 | msgstr "GNOME Ağ Ekranları" 25 | 26 | #. Translators: Do NOT translate or transliterate this text (this is an icon file name)! 27 | #: data/org.gnome.NetworkDisplays.desktop.in:6 28 | msgid "org.gnome.NetworkDisplays" 29 | msgstr "org.gnome.NetworkDisplays" 30 | 31 | #: data/org.gnome.NetworkDisplays.appdata.xml.in:7 32 | msgid "Stream the desktop to Wi-Fi Display capable devices" 33 | msgstr "Masaüstünü Kablosuz Ekran özellikli cihazlara aktarır" 34 | 35 | #: data/org.gnome.NetworkDisplays.appdata.xml.in:16 36 | msgid "" 37 | "GNOME Network Displays allows you to cast your desktop to a remote display. " 38 | "Currently implemented is support for casting to Wi-Fi Display capable " 39 | "devices (a.k.a. Miracast)." 40 | msgstr "" 41 | "GNOME Ağ Ekranları, masaüstünüzü uzaktaki bir ekrana aktarmanıza imkan " 42 | "tanır. Mevcut uyarlama Kablosuz Görüntüleme özellikli cihazlara (Miracast) " 43 | "döküm almayı destekler." 44 | 45 | #: src/nd-window.ui:74 46 | msgid "No Wi‑Fi P2P adapters found" 47 | msgstr "Kablosuz P2P bağdaştırıcı bulunamadı" 48 | 49 | #: src/nd-window.ui:89 50 | msgid "" 51 | "No usable wireless adapters were found. Please verify that Wi‑Fi is enabled " 52 | "and Wi‑Fi P2P operations are available in both NetworkManager and " 53 | "wpa_supplicant." 54 | msgstr "" 55 | "Kullanılabilir kablosuz ağ bağdaştırıcısı bulunamadı. Lütfen kablosuz ağın " 56 | "etkin olduğunu ve kablosuz P2P işlemlerinin hem ağ yöneticisi hem de " 57 | "wpa_supplicant'da mevcut olduğunu kontrol edin." 58 | 59 | #: src/nd-window.ui:174 60 | msgid "Available Video Sinks" 61 | msgstr "Mevcut Video Biriktiricileri" 62 | 63 | #: src/nd-window.ui:334 64 | msgid "Connecting" 65 | msgstr "Bağlanıyor" 66 | 67 | #: src/nd-window.ui:396 src/nd-window.ui:577 68 | msgid "Cancel" 69 | msgstr "İptal" 70 | 71 | #: src/nd-window.ui:499 72 | msgid "Streaming" 73 | msgstr "Aktarılıyor" 74 | 75 | #: src/nd-window.ui:556 src/nd-window.ui:782 76 | msgid "" 77 | "One of the following video codec plugins is required.\n" 78 | "Clicking will start the installation process." 79 | msgstr "" 80 | "Aşağıdaki video çözücü eklentilerinden biri gereklidir.\n" 81 | "Tıkladığınızda yükleme işlemi başlayacaktır." 82 | 83 | #: src/nd-window.ui:601 src/nd-window.ui:798 84 | msgid "" 85 | "One of the following audio codec plugins is required for audio support.\n" 86 | "Clicking will start the installation process." 87 | msgstr "" 88 | "Ses desteği için aşağıdaki ses çözücü eklentilerinden biri gereklidir.\n" 89 | "Tıkladığınızda yükleme işlemi başlayacaktır." 90 | 91 | #: src/nd-window.ui:696 92 | msgid "Error" 93 | msgstr "Hata" 94 | 95 | #: src/nd-window.ui:758 96 | msgid "Return" 97 | msgstr "Geri" 98 | 99 | #: src/nd-window.c:149 100 | msgid "Making P2P connection" 101 | msgstr "P2P bağlantısı kurma" 102 | 103 | #: src/nd-window.c:156 104 | msgid "Establishing connection to sink" 105 | msgstr "Biriktirici için bağlantı kuruluyor" 106 | 107 | #: src/nd-window.c:163 108 | msgid "Starting to stream" 109 | msgstr "Aktarma başlıyor" 110 | -------------------------------------------------------------------------------- /scripts/uncrustify.cfg: -------------------------------------------------------------------------------- 1 | newlines lf 2 | 3 | input_tab_size 8 4 | output_tab_size 8 5 | 6 | string_escape_char 92 7 | string_escape_char2 0 8 | 9 | # indenting 10 | indent_columns 2 11 | indent_with_tabs 0 12 | indent_align_string True 13 | indent_brace 2 14 | indent_braces false 15 | indent_braces_no_func True 16 | indent_func_call_param false 17 | indent_func_def_param false 18 | indent_func_proto_param false 19 | indent_switch_case 0 20 | indent_case_brace 2 21 | indent_paren_close 1 22 | 23 | # spacing 24 | sp_arith Add 25 | sp_assign Add 26 | sp_enum_assign Add 27 | sp_bool Add 28 | sp_compare Add 29 | sp_inside_paren Remove 30 | sp_inside_fparens Remove 31 | sp_func_def_paren Force 32 | sp_func_proto_paren Force 33 | sp_paren_paren Remove 34 | sp_balance_nested_parens False 35 | sp_paren_brace Remove 36 | sp_before_square Remove 37 | sp_before_squares Remove 38 | sp_inside_square Remove 39 | sp_before_ptr_star Add 40 | sp_between_ptr_star Remove 41 | sp_after_comma Add 42 | sp_before_comma Remove 43 | sp_after_cast Add 44 | sp_sizeof_paren Add 45 | sp_not Remove 46 | sp_inv Remove 47 | sp_addr Remove 48 | sp_member Remove 49 | sp_deref Remove 50 | sp_sign Remove 51 | sp_incdec Remove 52 | sp_attribute_paren remove 53 | sp_macro Force 54 | sp_func_call_paren Force 55 | sp_func_call_user_paren Remove 56 | set func_call_user _ N_ C_ g_autoptr g_auto 57 | sp_brace_typedef add 58 | sp_cond_colon add 59 | sp_cond_question add 60 | sp_defined_paren remove 61 | 62 | # alignment 63 | align_keep_tabs False 64 | align_with_tabs False 65 | align_on_tabstop False 66 | align_number_left True 67 | align_func_params True 68 | align_var_def_span 0 69 | align_var_def_amp_style 1 70 | align_var_def_colon true 71 | align_enum_equ_span 0 72 | align_var_struct_span 2 73 | align_var_def_star_style 2 74 | align_var_def_amp_style 2 75 | align_typedef_span 2 76 | align_typedef_func 0 77 | align_typedef_star_style 2 78 | align_typedef_amp_style 2 79 | 80 | # newlines 81 | nl_assign_leave_one_liners True 82 | nl_enum_leave_one_liners False 83 | nl_func_leave_one_liners False 84 | nl_if_leave_one_liners False 85 | nl_end_of_file Add 86 | nl_assign_brace Remove 87 | nl_func_var_def_blk 1 88 | nl_fcall_brace Add 89 | nl_enum_brace Remove 90 | nl_struct_brace Force 91 | nl_union_brace Force 92 | nl_if_brace Force 93 | nl_brace_else Force 94 | nl_elseif_brace Force 95 | nl_else_brace Add 96 | nl_for_brace Force 97 | nl_while_brace Force 98 | nl_do_brace Force 99 | nl_brace_while Force 100 | nl_switch_brace Force 101 | nl_before_case True 102 | nl_after_case False 103 | nl_func_type_name Force 104 | nl_func_proto_type_name Remove 105 | nl_func_paren Remove 106 | nl_func_decl_start Remove 107 | nl_func_decl_args Force 108 | nl_func_decl_end Remove 109 | nl_fdef_brace Force 110 | nl_after_return False 111 | nl_define_macro False 112 | nl_create_if_one_liner False 113 | nl_create_for_one_liner False 114 | nl_create_while_one_liner False 115 | nl_after_semicolon True 116 | nl_multi_line_cond true 117 | 118 | # mod 119 | # I'd like these to be remove, but that removes brackets in if { if { foo } }, which i dislike 120 | # Not clear what to do about that... 121 | mod_full_brace_for Remove 122 | mod_full_brace_if Remove 123 | mod_full_brace_if_chain True 124 | mod_full_brace_while Remove 125 | mod_full_brace_do Remove 126 | mod_full_brace_nl 3 127 | mod_paren_on_return Remove 128 | 129 | # line splitting 130 | #code_width = 78 131 | ls_for_split_full True 132 | ls_func_split_full True 133 | 134 | # positioning 135 | pos_bool Trail 136 | pos_conditional Trail 137 | 138 | # custom keywords 139 | set FOR udev_list_entry_foreach 140 | -------------------------------------------------------------------------------- /scripts/uncrustify.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | SRCROOT=`git rev-parse --show-toplevel` 3 | CFG="$SRCROOT/scripts/uncrustify.cfg" 4 | echo "srcroot: $SRCROOT" 5 | pushd "$SRCROOT" 6 | uncrustify -c "$CFG" --no-backup `git ls-tree --name-only -r HEAD | grep \\\.[ch]$ | grep -v gvdb | grep -v build/` 7 | popd 8 | -------------------------------------------------------------------------------- /src/gnome-network-displays.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | nd-window.ui 5 | nd-sink-row.ui 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* main.c 2 | * 3 | * Copyright 2018 Benjamin Berg 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include "gnome-network-displays-config.h" 22 | #include "nd-window.h" 23 | 24 | static void 25 | on_activate (GtkApplication *app) 26 | { 27 | GtkWindow *window; 28 | 29 | /* It's good practice to check your parameters at the beginning of the 30 | * function. It helps catch errors early and in development instead of 31 | * by your users. 32 | */ 33 | g_assert (GTK_IS_APPLICATION (app)); 34 | 35 | /* Get the current window or create one if necessary. */ 36 | window = gtk_application_get_active_window (app); 37 | if (window == NULL) 38 | { 39 | window = g_object_new (ND_TYPE_WINDOW, 40 | "application", app, 41 | "default-width", 600, 42 | "default-height", 300, 43 | NULL); 44 | } 45 | 46 | /* Ask the window manager/compositor to present the window. */ 47 | gtk_window_present (window); 48 | } 49 | 50 | int 51 | main (int argc, 52 | char *argv[]) 53 | { 54 | g_autoptr(GtkApplication) app = NULL; 55 | int ret; 56 | 57 | /* Set up gettext translations */ 58 | bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); 59 | bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); 60 | textdomain (GETTEXT_PACKAGE); 61 | 62 | gst_init (&argc, &argv); 63 | 64 | /* 65 | * Create a new GtkApplication. The application manages our main loop, 66 | * application windows, integration with the window manager/compositor, and 67 | * desktop features such as file opening and single-instance applications. 68 | */ 69 | app = gtk_application_new ("org.gnome.NetworkDisplays", G_APPLICATION_FLAGS_NONE); 70 | 71 | /* 72 | * We connect to the activate signal to create a window when the application 73 | * has been lauched. Additionally, this signal notifies us when the user 74 | * tries to launch a "second instance" of the application. When they try 75 | * to do that, we'll just present any existing window. 76 | * 77 | * Because we can't pass a pointer to any function type, we have to cast 78 | * our "on_activate" function to a GCallback. 79 | */ 80 | g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL); 81 | 82 | /* 83 | * Run the application. This function will block until the applicaiton 84 | * exits. Upon return, we have our exit code to return to the shell. (This 85 | * is the code you see when you do `echo $?` after running a command in a 86 | * terminal. 87 | * 88 | * Since GtkApplication inherits from GApplication, we use the parent class 89 | * method "run". But we need to cast, which is what the "G_APPLICATION()" 90 | * macro does. 91 | */ 92 | ret = g_application_run (G_APPLICATION (app), argc, argv); 93 | 94 | return ret; 95 | } 96 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | 2 | subdir('wfd') 3 | 4 | gnome_nd_sources = [ 5 | 'main.c', 6 | 'nd-window.c', 7 | 'nd-codec-install.c', 8 | 'nd-sink-list.c', 9 | 'nd-sink-row.c', 10 | 'nd-sink.c', 11 | 'nd-provider.c', 12 | 'nd-meta-sink.c', 13 | 'nd-meta-provider.c', 14 | 'nd-wfd-p2p-sink.c', 15 | 'nd-wfd-p2p-provider.c', 16 | 'nd-wfd-p2p-registry.c', 17 | 'nd-dummy-provider.c', 18 | 'nd-dummy-wfd-sink.c', 19 | 'nd-screencast-portal.c', 20 | 'nd-pulseaudio.c', 21 | ] 22 | 23 | enum_headers = files('nd-sink.h') 24 | 25 | gnome_nd_sources += gnome.mkenums_simple( 26 | 'nd-enum-types', 27 | sources: enum_headers, 28 | ) 29 | 30 | gnome_nd_deps = [ 31 | dependency('gio-2.0', version: '>= 2.50'), 32 | dependency('gtk+-3.0', version: '>= 3.22'), 33 | dependency('libnm', version: '>= 1.15'), 34 | dependency('gstreamer-1.0', version: '>= 1.14'), 35 | dependency('gstreamer-pbutils-1.0', version: '>= 1.14'), 36 | dependency('libpulse-mainloop-glib'), 37 | ] 38 | 39 | gnome_nd_deps += wfd_server_deps 40 | 41 | gnome_nd_sources += gnome.compile_resources('gnome-nd-resources', 42 | 'gnome-network-displays.gresource.xml', 43 | c_name: 'gnome_screencast' 44 | ) 45 | 46 | executable('gnome-network-displays', 47 | gnome_nd_sources, 48 | dependencies: gnome_nd_deps, 49 | install: true, 50 | link_with: wfd_server, 51 | ) 52 | -------------------------------------------------------------------------------- /src/nd-codec-install.c: -------------------------------------------------------------------------------- 1 | #include "nd-codec-install.h" 2 | #include 3 | #include 4 | 5 | struct _NdCodecInstall 6 | { 7 | GtkRevealer parent_instance; 8 | 9 | GStrv codecs; 10 | 11 | GtkListBox *listbox; 12 | GtkLabel *header; 13 | }; 14 | 15 | G_DEFINE_TYPE (NdCodecInstall, nd_codec_install, GTK_TYPE_REVEALER) 16 | 17 | enum { 18 | PROP_0, 19 | PROP_TITLE, 20 | PROP_CODECS, 21 | N_PROPS 22 | }; 23 | 24 | static GParamSpec *properties[N_PROPS]; 25 | 26 | NdCodecInstall * 27 | nd_codec_install_new (void) 28 | { 29 | return g_object_new (ND_TYPE_CODEC_INSTALL, NULL); 30 | } 31 | 32 | static void 33 | nd_codec_install_finalize (GObject *object) 34 | { 35 | NdCodecInstall *self = (NdCodecInstall *) object; 36 | 37 | g_clear_pointer (&self->codecs, g_strfreev); 38 | 39 | G_OBJECT_CLASS (nd_codec_install_parent_class)->finalize (object); 40 | } 41 | 42 | static void 43 | remove_widget (GtkWidget *widget, gpointer user_data) 44 | { 45 | GtkContainer *container = GTK_CONTAINER (user_data); 46 | 47 | gtk_container_remove (container, widget); 48 | } 49 | 50 | static gchar * 51 | get_description (const gchar *codec) 52 | { 53 | if (g_strcmp0 (codec, "openh264enc") == 0) 54 | return g_strdup_printf (_("GStreamer OpenH264 video encoder (%s)"), codec); 55 | else if (g_strcmp0 (codec, "x264enc") == 0) 56 | return g_strdup_printf (_("GStreamer x264 video encoder (%s)"), codec); 57 | else if (g_strcmp0 (codec, "vaapih264enc") == 0) 58 | return g_strdup_printf (_("GStreamer VA-API H264 video encoder (%s)"), codec); 59 | else if (g_strcmp0 (codec, "fdkaacenc") == 0) 60 | return g_strdup_printf (_("GStreamer FDK AAC audio encoder (%s)"), codec); 61 | else if (g_strcmp0 (codec, "avenc_aac") == 0) 62 | return g_strdup_printf (_("GStreamer libav AAC audio encoder (%s)"), codec); 63 | else if (g_strcmp0 (codec, "faac") == 0) 64 | return g_strdup_printf (_("GStreamer Free AAC audio encoder (%s)"), codec); 65 | 66 | return g_strdup_printf (_("GStreamer Element “%s”"), codec); 67 | } 68 | 69 | static void 70 | nd_codec_install_update (NdCodecInstall *self) 71 | { 72 | gchar **codec; 73 | 74 | gtk_container_foreach (GTK_CONTAINER (self->listbox), remove_widget, self->listbox); 75 | 76 | if (self->codecs == NULL) 77 | { 78 | gtk_revealer_set_reveal_child (GTK_REVEALER (self), FALSE); 79 | return; 80 | } 81 | 82 | gtk_revealer_set_reveal_child (GTK_REVEALER (self), self->codecs[0] != NULL); 83 | 84 | for (codec = self->codecs; *codec; codec += 1) 85 | { 86 | GtkWidget *row; 87 | GtkWidget *label; 88 | g_autofree gchar *description = NULL; 89 | 90 | row = gtk_list_box_row_new (); 91 | 92 | description = get_description (*codec); 93 | label = gtk_label_new (description); 94 | gtk_container_add (GTK_CONTAINER (row), label); 95 | gtk_widget_show (label); 96 | g_object_set (label, 97 | "margin", 6, 98 | "xalign", 0.0, 99 | NULL); 100 | 101 | g_object_set_data_full (G_OBJECT (row), "codec", g_strdup (*codec), g_free); 102 | g_object_set_data_full (G_OBJECT (row), "desc", g_steal_pointer (&description), g_free); 103 | 104 | gtk_list_box_insert (self->listbox, row, -1); 105 | gtk_widget_show (row); 106 | } 107 | } 108 | 109 | static void 110 | nd_codec_install_get_property (GObject *object, 111 | guint prop_id, 112 | GValue *value, 113 | GParamSpec *pspec) 114 | { 115 | NdCodecInstall *self = ND_CODEC_INSTALL (object); 116 | 117 | switch (prop_id) 118 | { 119 | case PROP_TITLE: 120 | g_value_set_boxed (value, self->codecs); 121 | break; 122 | 123 | case PROP_CODECS: 124 | g_value_set_string (value, gtk_label_get_text (self->header)); 125 | break; 126 | 127 | default: 128 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 129 | } 130 | } 131 | 132 | static void 133 | nd_codec_install_set_property (GObject *object, 134 | guint prop_id, 135 | const GValue *value, 136 | GParamSpec *pspec) 137 | { 138 | NdCodecInstall *self = ND_CODEC_INSTALL (object); 139 | 140 | switch (prop_id) 141 | { 142 | case PROP_TITLE: 143 | gtk_label_set_text (self->header, g_value_get_string (value)); 144 | break; 145 | 146 | case PROP_CODECS: 147 | g_clear_pointer (&self->codecs, g_strfreev); 148 | self->codecs = g_value_dup_boxed (value); 149 | 150 | nd_codec_install_update (self); 151 | break; 152 | 153 | default: 154 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 155 | } 156 | } 157 | 158 | static void 159 | nd_codec_install_class_init (NdCodecInstallClass *klass) 160 | { 161 | GObjectClass *object_class = G_OBJECT_CLASS (klass); 162 | 163 | object_class->finalize = nd_codec_install_finalize; 164 | object_class->get_property = nd_codec_install_get_property; 165 | object_class->set_property = nd_codec_install_set_property; 166 | 167 | properties[PROP_TITLE] = 168 | g_param_spec_string ("title", "Title", 169 | "String for the title of the list", 170 | _("Please install one of the following GStreamer plugins by clicking below"), 171 | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); 172 | 173 | properties[PROP_CODECS] = 174 | g_param_spec_boxed ("codecs", "Codecs", 175 | "List of required codecs", 176 | G_TYPE_STRV, 177 | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); 178 | 179 | g_object_class_install_properties (object_class, N_PROPS, properties); 180 | 181 | } 182 | 183 | static void 184 | on_install_plugins_done_cb (GstInstallPluginsReturn result, 185 | gpointer user_data) 186 | { 187 | g_debug ("gst_install_plugins_async operation finished"); 188 | } 189 | 190 | static void 191 | on_row_activated_cb (NdCodecInstall *self, GtkListBoxRow *row) 192 | { 193 | GstInstallPluginsContext *ctx = NULL; 194 | const gchar *codecs[2] = { NULL }; 195 | GstInstallPluginsReturn res; 196 | GstElement *elem; 197 | GstMessage *msg; 198 | 199 | ctx = gst_install_plugins_context_new (); 200 | gst_install_plugins_context_set_desktop_id (ctx, "org.gnome.NetworkDisplays"); 201 | 202 | elem = gst_pipeline_new ("dummy"); 203 | msg = gst_missing_element_message_new (elem, g_object_get_data (G_OBJECT (row), "codec")); 204 | codecs[0] = gst_missing_plugin_message_get_installer_detail (msg); 205 | 206 | res = gst_install_plugins_async (codecs, ctx, on_install_plugins_done_cb, NULL); 207 | 208 | /* Try to fall back to calling into gnome-software directly in case the 209 | * GStreamer plugin helper is not installed. 210 | * Not sure if this is sane to do, but it may be worth a shot. */ 211 | if (res == GST_INSTALL_PLUGINS_HELPER_MISSING) 212 | { 213 | GDBusConnection *con = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); 214 | GVariantBuilder params; 215 | g_autofree gchar *desc = NULL; 216 | 217 | desc = g_strdup_printf ("%s|gstreamer1(element-%s)()(%dbit)", 218 | (const gchar *) g_object_get_data (G_OBJECT (row), "desc"), 219 | (const gchar *) g_object_get_data (G_OBJECT (row), "codec"), 220 | (gint) sizeof (gpointer) * 8); 221 | 222 | g_variant_builder_init (¶ms, G_VARIANT_TYPE ("(asssa{sv})")); 223 | g_variant_builder_open (¶ms, G_VARIANT_TYPE ("as")); 224 | g_variant_builder_add (¶ms, "s", desc); 225 | g_variant_builder_close (¶ms); 226 | g_variant_builder_add (¶ms, "s", ""); 227 | g_variant_builder_add (¶ms, "s", "org.gnome.NetworkDisplays"); 228 | g_variant_builder_open (¶ms, G_VARIANT_TYPE ("a{sv}")); 229 | g_variant_builder_close (¶ms); 230 | 231 | g_dbus_connection_call (con, 232 | "org.gnome.Software", 233 | "/org/freedesktop/PackageKit", 234 | "org.freedesktop.PackageKit.Modify2", 235 | "InstallGStreamerResources", 236 | g_variant_builder_end (¶ms), 237 | NULL, 238 | G_DBUS_CALL_FLAGS_NONE, 239 | -1, 240 | NULL, 241 | NULL, 242 | NULL); 243 | } 244 | } 245 | 246 | static void 247 | nd_codec_install_init (NdCodecInstall *self) 248 | { 249 | GtkWidget *box; 250 | GtkWidget *frame; 251 | 252 | g_object_set (self, 253 | "transition-type", GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN, 254 | NULL); 255 | 256 | box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); 257 | g_object_set (box, "margin", 6, NULL); 258 | gtk_container_add (GTK_CONTAINER (self), box); 259 | gtk_widget_show (box); 260 | 261 | self->header = GTK_LABEL (gtk_label_new (g_value_get_string (g_param_spec_get_default_value (properties[PROP_TITLE])))); 262 | g_object_set (self->header, 263 | "wrap", TRUE, 264 | "wrap-mode", GTK_WRAP_WORD, 265 | "xalign", 0.0, 266 | NULL); 267 | gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (self->header)); 268 | gtk_widget_show (GTK_WIDGET (self->header)); 269 | 270 | frame = gtk_frame_new (NULL); 271 | gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN); 272 | gtk_container_add (GTK_CONTAINER (box), frame); 273 | gtk_widget_show (frame); 274 | 275 | self->listbox = GTK_LIST_BOX (gtk_list_box_new ()); 276 | gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (self->listbox)); 277 | gtk_widget_show (GTK_WIDGET (self->listbox)); 278 | 279 | g_signal_connect_object (self->listbox, 280 | "row-activated", 281 | G_CALLBACK (on_row_activated_cb), 282 | self, 283 | G_CONNECT_SWAPPED); 284 | } 285 | -------------------------------------------------------------------------------- /src/nd-codec-install.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | G_BEGIN_DECLS 6 | 7 | #define ND_TYPE_CODEC_INSTALL (nd_codec_install_get_type ()) 8 | 9 | G_DECLARE_FINAL_TYPE (NdCodecInstall, nd_codec_install, ND, CODEC_INSTALL, GtkRevealer) 10 | 11 | NdCodecInstall *nd_codec_install_new (void); 12 | 13 | G_END_DECLS 14 | -------------------------------------------------------------------------------- /src/nd-dummy-provider.c: -------------------------------------------------------------------------------- 1 | /* nd-dummy-provider.c 2 | * 3 | * Copyright 2018 Benjamin Berg 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "gnome-network-displays-config.h" 20 | #include "nd-dummy-provider.h" 21 | #include "nd-dummy-wfd-sink.h" 22 | 23 | struct _NdDummyProvider 24 | { 25 | GObject parent_instance; 26 | 27 | gboolean discover; 28 | 29 | GPtrArray *sinks; 30 | }; 31 | 32 | enum { 33 | PROP_DISCOVER = 1, 34 | 35 | PROP_LAST = PROP_DISCOVER, 36 | }; 37 | 38 | static void nd_dummy_provider_provider_iface_init (NdProviderIface *iface); 39 | static GList * nd_dummy_provider_provider_get_sinks (NdProvider *provider); 40 | 41 | G_DEFINE_TYPE_EXTENDED (NdDummyProvider, nd_dummy_provider, G_TYPE_OBJECT, 0, 42 | G_IMPLEMENT_INTERFACE (ND_TYPE_PROVIDER, 43 | nd_dummy_provider_provider_iface_init); 44 | ) 45 | 46 | static void 47 | nd_dummy_provider_get_property (GObject *object, 48 | guint prop_id, 49 | GValue *value, 50 | GParamSpec *pspec) 51 | { 52 | NdDummyProvider *self = ND_DUMMY_PROVIDER (object); 53 | 54 | switch (prop_id) 55 | { 56 | case PROP_DISCOVER: 57 | g_value_set_boolean (value, self->discover); 58 | break; 59 | 60 | default: 61 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 62 | break; 63 | } 64 | } 65 | 66 | static void 67 | nd_dummy_provider_set_property (GObject *object, 68 | guint prop_id, 69 | const GValue *value, 70 | GParamSpec *pspec) 71 | { 72 | NdDummyProvider *self = ND_DUMMY_PROVIDER (object); 73 | 74 | switch (prop_id) 75 | { 76 | case PROP_DISCOVER: 77 | self->discover = g_value_get_boolean (value); 78 | 79 | break; 80 | 81 | default: 82 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 83 | break; 84 | } 85 | } 86 | 87 | static void 88 | nd_dummy_provider_finalize (GObject *object) 89 | { 90 | NdDummyProvider *dummy_provider = ND_DUMMY_PROVIDER (object); 91 | 92 | g_clear_pointer (&dummy_provider->sinks, g_ptr_array_unref); 93 | 94 | G_OBJECT_CLASS (nd_dummy_provider_parent_class)->finalize (object); 95 | } 96 | 97 | static void 98 | nd_dummy_provider_class_init (NdDummyProviderClass *klass) 99 | { 100 | GObjectClass *object_class = G_OBJECT_CLASS (klass); 101 | 102 | object_class->get_property = nd_dummy_provider_get_property; 103 | object_class->set_property = nd_dummy_provider_set_property; 104 | object_class->finalize = nd_dummy_provider_finalize; 105 | 106 | g_object_class_override_property (object_class, PROP_DISCOVER, "discover"); 107 | } 108 | 109 | static void 110 | nd_dummy_provider_init (NdDummyProvider *dummy_provider) 111 | { 112 | dummy_provider->discover = TRUE; 113 | dummy_provider->sinks = g_ptr_array_new_with_free_func (g_object_unref); 114 | 115 | g_ptr_array_add (dummy_provider->sinks, nd_dummy_wfd_sink_new ()); 116 | } 117 | 118 | /****************************************************************** 119 | * NdProvider interface implementation 120 | ******************************************************************/ 121 | 122 | static void 123 | nd_dummy_provider_provider_iface_init (NdProviderIface *iface) 124 | { 125 | iface->get_sinks = nd_dummy_provider_provider_get_sinks; 126 | } 127 | 128 | static GList * 129 | nd_dummy_provider_provider_get_sinks (NdProvider *provider) 130 | { 131 | NdDummyProvider *dummy_provider = ND_DUMMY_PROVIDER (provider); 132 | GList *res = NULL; 133 | 134 | for (gint i = 0; i < dummy_provider->sinks->len; i++) 135 | res = g_list_prepend (res, dummy_provider->sinks->pdata[i]); 136 | 137 | return res; 138 | } 139 | 140 | NdDummyProvider * 141 | nd_dummy_provider_new (void) 142 | { 143 | return g_object_new (ND_TYPE_DUMMY_PROVIDER, 144 | NULL); 145 | } 146 | -------------------------------------------------------------------------------- /src/nd-dummy-provider.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | G_BEGIN_DECLS 6 | 7 | #define ND_TYPE_DUMMY_PROVIDER (nd_dummy_provider_get_type ()) 8 | 9 | G_DECLARE_FINAL_TYPE (NdDummyProvider, nd_dummy_provider, ND, DUMMY_PROVIDER, GObject) 10 | 11 | NdDummyProvider * nd_dummy_provider_new (void); 12 | 13 | G_END_DECLS 14 | -------------------------------------------------------------------------------- /src/nd-dummy-wfd-sink.c: -------------------------------------------------------------------------------- 1 | /* nd-dummy-wfd-sink.c 2 | * 3 | * Copyright 2018 Benjamin Berg 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "gnome-network-displays-config.h" 20 | #include "nd-dummy-wfd-sink.h" 21 | #include "wfd/wfd-server.h" 22 | #include "wfd/wfd-client.h" 23 | #include "wfd/wfd-media-factory.h" 24 | 25 | struct _NdDummyWFDSink 26 | { 27 | GObject parent_instance; 28 | 29 | NdSinkState state; 30 | 31 | GStrv missing_video_codec; 32 | GStrv missing_audio_codec; 33 | 34 | WfdServer *server; 35 | guint server_source_id; 36 | }; 37 | 38 | enum { 39 | PROP_CLIENT = 1, 40 | PROP_DEVICE, 41 | PROP_PEER, 42 | 43 | PROP_DISPLAY_NAME, 44 | PROP_MATCHES, 45 | PROP_PRIORITY, 46 | PROP_STATE, 47 | PROP_MISSING_VIDEO_CODEC, 48 | PROP_MISSING_AUDIO_CODEC, 49 | 50 | PROP_LAST = PROP_DISPLAY_NAME, 51 | }; 52 | 53 | static void nd_dummy_wfd_sink_sink_iface_init (NdSinkIface *iface); 54 | static NdSink * nd_dummy_wfd_sink_sink_start_stream (NdSink *sink); 55 | static void nd_dummy_wfd_sink_sink_stop_stream (NdSink *sink); 56 | 57 | G_DEFINE_TYPE_EXTENDED (NdDummyWFDSink, nd_dummy_wfd_sink, G_TYPE_OBJECT, 0, 58 | G_IMPLEMENT_INTERFACE (ND_TYPE_SINK, 59 | nd_dummy_wfd_sink_sink_iface_init); 60 | ) 61 | 62 | 63 | static void 64 | nd_dummy_wfd_sink_get_property (GObject *object, 65 | guint prop_id, 66 | GValue *value, 67 | GParamSpec *pspec) 68 | { 69 | NdDummyWFDSink *sink = ND_DUMMY_WFD_SINK (object); 70 | 71 | switch (prop_id) 72 | { 73 | case PROP_DISPLAY_NAME: 74 | g_value_set_string (value, "Dummy WFD Sink"); 75 | break; 76 | 77 | case PROP_MATCHES: 78 | { 79 | g_autoptr(GPtrArray) res = NULL; 80 | res = g_ptr_array_new_with_free_func (g_free); 81 | 82 | g_ptr_array_add (res, g_strdup ("dummy-wfd-sink")); 83 | 84 | g_value_take_boxed (value, g_steal_pointer (&res)); 85 | break; 86 | } 87 | 88 | case PROP_PRIORITY: 89 | g_value_set_int (value, 0); 90 | break; 91 | 92 | case PROP_STATE: 93 | g_value_set_enum (value, sink->state); 94 | break; 95 | 96 | case PROP_MISSING_VIDEO_CODEC: 97 | g_value_set_boxed (value, sink->missing_video_codec); 98 | break; 99 | 100 | case PROP_MISSING_AUDIO_CODEC: 101 | g_value_set_boxed (value, sink->missing_audio_codec); 102 | break; 103 | 104 | default: 105 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 106 | break; 107 | } 108 | } 109 | 110 | void 111 | nd_dummy_wfd_sink_finalize (GObject *object) 112 | { 113 | NdDummyWFDSink *sink = ND_DUMMY_WFD_SINK (object); 114 | 115 | nd_dummy_wfd_sink_sink_stop_stream (ND_SINK (object)); 116 | 117 | g_clear_pointer (&sink->missing_video_codec, g_strfreev); 118 | g_clear_pointer (&sink->missing_audio_codec, g_strfreev); 119 | 120 | G_OBJECT_CLASS (nd_dummy_wfd_sink_parent_class)->finalize (object); 121 | } 122 | 123 | static void 124 | nd_dummy_wfd_sink_class_init (NdDummyWFDSinkClass *klass) 125 | { 126 | GObjectClass *object_class = G_OBJECT_CLASS (klass); 127 | 128 | object_class->get_property = nd_dummy_wfd_sink_get_property; 129 | object_class->finalize = nd_dummy_wfd_sink_finalize; 130 | 131 | g_object_class_override_property (object_class, PROP_DISPLAY_NAME, "display-name"); 132 | g_object_class_override_property (object_class, PROP_MATCHES, "matches"); 133 | g_object_class_override_property (object_class, PROP_PRIORITY, "priority"); 134 | g_object_class_override_property (object_class, PROP_STATE, "state"); 135 | g_object_class_override_property (object_class, PROP_MISSING_VIDEO_CODEC, "missing-video-codec"); 136 | g_object_class_override_property (object_class, PROP_MISSING_AUDIO_CODEC, "missing-audio-codec"); 137 | } 138 | 139 | static void 140 | nd_dummy_wfd_sink_init (NdDummyWFDSink *sink) 141 | { 142 | sink->state = ND_SINK_STATE_DISCONNECTED; 143 | } 144 | 145 | /****************************************************************** 146 | * NdSink interface implementation 147 | ******************************************************************/ 148 | 149 | static void 150 | nd_dummy_wfd_sink_sink_iface_init (NdSinkIface *iface) 151 | { 152 | iface->start_stream = nd_dummy_wfd_sink_sink_start_stream; 153 | iface->stop_stream = nd_dummy_wfd_sink_sink_stop_stream; 154 | } 155 | 156 | static void 157 | play_request_cb (NdDummyWFDSink *sink, GstRTSPContext *ctx, WfdClient *client) 158 | { 159 | g_debug ("NdWfdP2PSink: Got play request from client"); 160 | 161 | sink->state = ND_SINK_STATE_STREAMING; 162 | g_object_notify (G_OBJECT (sink), "state"); 163 | } 164 | 165 | static void 166 | closed_cb (NdDummyWFDSink *sink, WfdClient *client) 167 | { 168 | /* Connection was closed, do a clean shutdown*/ 169 | nd_dummy_wfd_sink_sink_stop_stream (ND_SINK (sink)); 170 | } 171 | 172 | static void 173 | client_connected_cb (NdDummyWFDSink *sink, WfdClient *client, WfdServer *server) 174 | { 175 | g_debug ("NdWfdP2PSink: Got client connection"); 176 | 177 | g_signal_handlers_disconnect_by_func (sink->server, client_connected_cb, sink); 178 | sink->state = ND_SINK_STATE_WAIT_STREAMING; 179 | g_object_notify (G_OBJECT (sink), "state"); 180 | 181 | /* XXX: connect to further events. */ 182 | g_signal_connect_object (client, 183 | "play-request", 184 | (GCallback) play_request_cb, 185 | sink, 186 | G_CONNECT_SWAPPED); 187 | 188 | g_signal_connect_object (client, 189 | "closed", 190 | (GCallback) closed_cb, 191 | sink, 192 | G_CONNECT_SWAPPED); 193 | } 194 | 195 | static GstElement * 196 | server_create_source_cb (NdDummyWFDSink *sink, WfdServer *server) 197 | { 198 | GstElement *res; 199 | 200 | g_signal_emit_by_name (sink, "create-source", &res); 201 | 202 | return res; 203 | } 204 | 205 | static GstElement * 206 | server_create_audio_source_cb (NdDummyWFDSink *sink, WfdServer *server) 207 | { 208 | GstElement *res; 209 | 210 | g_signal_emit_by_name (sink, "create-audio-source", &res); 211 | 212 | return res; 213 | } 214 | 215 | static NdSink * 216 | nd_dummy_wfd_sink_sink_start_stream (NdSink *sink) 217 | { 218 | NdDummyWFDSink *self = ND_DUMMY_WFD_SINK (sink); 219 | gboolean have_basic_codecs; 220 | GStrv missing_video, missing_audio; 221 | 222 | g_return_val_if_fail (self->state == ND_SINK_STATE_DISCONNECTED, NULL); 223 | 224 | g_assert (self->server == NULL); 225 | 226 | have_basic_codecs = wfd_get_missing_codecs (&missing_video, &missing_audio); 227 | 228 | g_clear_pointer (&self->missing_video_codec, g_strfreev); 229 | g_clear_pointer (&self->missing_audio_codec, g_strfreev); 230 | 231 | self->missing_video_codec = g_strdupv (missing_video); 232 | self->missing_audio_codec = g_strdupv (missing_audio); 233 | 234 | g_object_notify (G_OBJECT (self), "missing-video-codec"); 235 | g_object_notify (G_OBJECT (self), "missing-audio-codec"); 236 | 237 | if (!have_basic_codecs) 238 | goto error; 239 | 240 | self->server = wfd_server_new (); 241 | self->server_source_id = gst_rtsp_server_attach (GST_RTSP_SERVER (self->server), NULL); 242 | 243 | if (self->server_source_id == 0) 244 | goto error; 245 | 246 | g_debug ("NdDummyWFDSink: You should now be able to connect to rtsp://localhost:7236/wfd1.0"); 247 | 248 | g_signal_connect_object (self->server, 249 | "client-connected", 250 | (GCallback) client_connected_cb, 251 | self, 252 | G_CONNECT_SWAPPED); 253 | 254 | g_signal_connect_object (self->server, 255 | "create-source", 256 | (GCallback) server_create_source_cb, 257 | self, 258 | G_CONNECT_SWAPPED); 259 | 260 | g_signal_connect_object (self->server, 261 | "create-audio-source", 262 | (GCallback) server_create_audio_source_cb, 263 | self, 264 | G_CONNECT_SWAPPED); 265 | 266 | self->state = ND_SINK_STATE_WAIT_SOCKET; 267 | g_object_notify (G_OBJECT (self), "state"); 268 | 269 | return g_object_ref (sink); 270 | 271 | error: 272 | g_warning ("Error starting stream!"); 273 | self->state = ND_SINK_STATE_ERROR; 274 | g_object_notify (G_OBJECT (self), "state"); 275 | g_clear_object (&self->server); 276 | 277 | return g_object_ref (sink); 278 | } 279 | 280 | void 281 | nd_dummy_wfd_sink_sink_stop_stream (NdSink *sink) 282 | { 283 | NdDummyWFDSink *self = ND_DUMMY_WFD_SINK (sink); 284 | 285 | if (self->server_source_id) 286 | { 287 | g_source_remove (self->server_source_id); 288 | self->server_source_id = 0; 289 | } 290 | 291 | /* Needs to protect against recursion. */ 292 | if (self->server) 293 | { 294 | g_autoptr(WfdServer) server = NULL; 295 | 296 | server = g_steal_pointer (&self->server); 297 | g_signal_handlers_disconnect_by_data (server, self); 298 | wfd_server_purge (server); 299 | } 300 | 301 | self->state = ND_SINK_STATE_DISCONNECTED; 302 | g_object_notify (G_OBJECT (self), "state"); 303 | } 304 | 305 | NdDummyWFDSink * 306 | nd_dummy_wfd_sink_new (void) 307 | { 308 | return g_object_new (ND_TYPE_DUMMY_WFD_SINK, 309 | NULL); 310 | } 311 | -------------------------------------------------------------------------------- /src/nd-dummy-wfd-sink.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "nd-sink.h" 4 | 5 | G_BEGIN_DECLS 6 | 7 | #define ND_TYPE_DUMMY_WFD_SINK (nd_dummy_wfd_sink_get_type ()) 8 | 9 | G_DECLARE_FINAL_TYPE (NdDummyWFDSink, nd_dummy_wfd_sink, ND, DUMMY_WFD_SINK, GObject) 10 | 11 | NdDummyWFDSink * nd_dummy_wfd_sink_new (void); 12 | 13 | G_END_DECLS 14 | -------------------------------------------------------------------------------- /src/nd-meta-provider.c: -------------------------------------------------------------------------------- 1 | /* nd-meta-provider.c 2 | * 3 | * Copyright 2018 Benjamin Berg 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "gnome-network-displays-config.h" 20 | #include "nd-meta-provider.h" 21 | #include "nd-meta-sink.h" 22 | 23 | struct _NdMetaProvider 24 | { 25 | GObject parent_instance; 26 | 27 | gboolean discover; 28 | 29 | GHashTable *deduplicate; 30 | GPtrArray *sinks; 31 | GPtrArray *providers; 32 | }; 33 | 34 | enum { 35 | PROP_HAS_PROVIDERS = 1, 36 | PROP_DISCOVER, 37 | 38 | PROP_LAST = PROP_DISCOVER, 39 | }; 40 | 41 | static GParamSpec * props[PROP_LAST] = { NULL, }; 42 | 43 | static void nd_meta_provider_provider_iface_init (NdProviderIface *iface); 44 | static GList * nd_meta_provider_provider_get_sinks (NdProvider *provider); 45 | 46 | G_DEFINE_TYPE_EXTENDED (NdMetaProvider, nd_meta_provider, G_TYPE_OBJECT, 0, 47 | G_IMPLEMENT_INTERFACE (ND_TYPE_PROVIDER, 48 | nd_meta_provider_provider_iface_init); 49 | ) 50 | 51 | 52 | static void 53 | deduplicate_add_meta_sink (NdMetaProvider *meta_provider, NdMetaSink *meta_sink) 54 | { 55 | g_autoptr(GPtrArray) meta_matches = NULL; 56 | 57 | g_object_get (meta_sink, "matches", &meta_matches, NULL); 58 | g_assert (meta_matches != NULL); 59 | 60 | for (gint i = 0; i < meta_matches->len; i++) 61 | { 62 | gchar *match; 63 | match = meta_matches->pdata[i]; 64 | 65 | g_hash_table_insert (meta_provider->deduplicate, g_strdup (match), meta_sink); 66 | } 67 | } 68 | 69 | static void 70 | provider_sink_added_cb (NdMetaProvider *meta_provider, NdSink *sink, NdProvider *provider) 71 | { 72 | g_autoptr(GPtrArray) sink_matches = NULL; 73 | g_autoptr(GPtrArray) meta_sinks = NULL; 74 | NdMetaSink *meta_sink; 75 | gchar *match; 76 | 77 | g_object_get (sink, "matches", &sink_matches, NULL); 78 | g_assert (sink_matches != NULL); 79 | 80 | meta_sinks = g_ptr_array_new (); 81 | 82 | for (gint i = 0; i < sink_matches->len; i++) 83 | { 84 | match = sink_matches->pdata[i]; 85 | 86 | meta_sink = g_hash_table_lookup (meta_provider->deduplicate, match); 87 | 88 | if (meta_sink) 89 | g_ptr_array_add (meta_sinks, meta_sink); 90 | } 91 | 92 | if (meta_sinks->len > 1) 93 | g_warning ("MetaProvider: Found two meta sinks that belong to the same sink. This should not happen!\n"); 94 | 95 | if (meta_sinks->len > 0) 96 | { 97 | meta_sink = g_ptr_array_remove_index (meta_sinks, 0); 98 | 99 | while (meta_sinks->len > 0) 100 | { 101 | NdMetaSink *merge_meta; 102 | NdSink *merge_sink; 103 | merge_meta = g_ptr_array_remove_index_fast (meta_sinks, 0); 104 | g_signal_emit_by_name (meta_provider, "sink-removed", merge_meta); 105 | 106 | while ((merge_sink = nd_meta_sink_get_sink (merge_meta))) 107 | { 108 | nd_meta_sink_remove_sink (merge_meta, merge_sink); 109 | nd_meta_sink_add_sink (meta_sink, merge_sink); 110 | } 111 | } 112 | 113 | nd_meta_sink_add_sink (meta_sink, sink); 114 | } 115 | else 116 | { 117 | meta_sink = nd_meta_sink_new (sink); 118 | g_signal_emit_by_name (meta_provider, "sink-added", meta_sink); 119 | } 120 | 121 | /* Add/Update matches in the deduplication dictionary */ 122 | deduplicate_add_meta_sink (meta_provider, meta_sink); 123 | } 124 | 125 | static void 126 | provider_sink_removed_cb (NdMetaProvider *meta_provider, NdSink *sink, NdProvider *provider) 127 | { 128 | g_autoptr(GPtrArray) sink_matches = NULL; 129 | NdMetaSink *meta_sink = NULL; 130 | 131 | g_object_get (sink, "matches", &sink_matches, NULL); 132 | g_assert (sink_matches != NULL); 133 | 134 | for (gint i = 0; i < sink_matches->len; i++) 135 | { 136 | gchar *match = sink_matches->pdata[i]; 137 | 138 | /* Find the first matching meta sink and remove any match*/ 139 | if (meta_sink == NULL) 140 | meta_sink = g_hash_table_lookup (meta_provider->deduplicate, match); 141 | 142 | g_hash_table_remove (meta_provider->deduplicate, match); 143 | } 144 | 145 | g_assert (meta_sink != NULL); 146 | if (nd_meta_sink_remove_sink (meta_sink, sink)) 147 | { 148 | /* meta sink is empty, remove it and we are done */ 149 | g_ptr_array_remove (meta_provider->sinks, meta_sink); 150 | g_signal_emit_by_name (meta_provider, "sink-removed", meta_sink); 151 | 152 | return; 153 | } 154 | 155 | /* Add/Update matches in the deduplication dictionary as we will 156 | * have removed too many. */ 157 | deduplicate_add_meta_sink (meta_provider, meta_sink); 158 | } 159 | 160 | static void 161 | nd_meta_provider_get_property (GObject *object, 162 | guint prop_id, 163 | GValue *value, 164 | GParamSpec *pspec) 165 | { 166 | NdMetaProvider *self = ND_META_PROVIDER (object); 167 | 168 | switch (prop_id) 169 | { 170 | case PROP_DISCOVER: 171 | g_value_set_boolean (value, self->discover); 172 | break; 173 | 174 | case PROP_HAS_PROVIDERS: 175 | g_value_set_boolean (value, self->providers->len > 0); 176 | break; 177 | 178 | default: 179 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 180 | break; 181 | } 182 | } 183 | 184 | static void 185 | nd_meta_provider_set_property (GObject *object, 186 | guint prop_id, 187 | const GValue *value, 188 | GParamSpec *pspec) 189 | { 190 | NdMetaProvider *self = ND_META_PROVIDER (object); 191 | 192 | switch (prop_id) 193 | { 194 | case PROP_DISCOVER: 195 | self->discover = g_value_get_boolean (value); 196 | 197 | for (gint i = 0; i < self->providers->len; i++) 198 | g_object_set (g_ptr_array_index (self->providers, i), "discover", self->discover, NULL); 199 | 200 | break; 201 | 202 | default: 203 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 204 | break; 205 | } 206 | } 207 | 208 | static void 209 | nd_meta_provider_finalize (GObject *object) 210 | { 211 | NdMetaProvider *meta_provider = ND_META_PROVIDER (object); 212 | 213 | g_clear_pointer (&meta_provider->deduplicate, g_hash_table_unref); 214 | 215 | g_ptr_array_free (meta_provider->sinks, TRUE); 216 | meta_provider->sinks = NULL; 217 | g_ptr_array_free (meta_provider->providers, TRUE); 218 | meta_provider->providers = NULL; 219 | 220 | /* Rely on provider signals to be disconnected automatically. */ 221 | 222 | G_OBJECT_CLASS (nd_meta_provider_parent_class)->finalize (object); 223 | } 224 | 225 | static void 226 | nd_meta_provider_class_init (NdMetaProviderClass *klass) 227 | { 228 | GObjectClass *object_class = G_OBJECT_CLASS (klass); 229 | 230 | object_class->get_property = nd_meta_provider_get_property; 231 | object_class->set_property = nd_meta_provider_set_property; 232 | object_class->finalize = nd_meta_provider_finalize; 233 | 234 | g_object_class_override_property (object_class, PROP_DISCOVER, "discover"); 235 | 236 | props[PROP_HAS_PROVIDERS] = 237 | g_param_spec_boolean ("has-providers", "Has Providers", 238 | "The meta provider has a provider.", 239 | FALSE, 240 | G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); 241 | 242 | g_object_class_install_properties (object_class, PROP_LAST, props); 243 | } 244 | 245 | static void 246 | nd_meta_provider_init (NdMetaProvider *meta_provider) 247 | { 248 | meta_provider->discover = TRUE; 249 | 250 | meta_provider->sinks = g_ptr_array_new_with_free_func (g_object_unref); 251 | meta_provider->providers = g_ptr_array_new_with_free_func (g_object_unref); 252 | 253 | meta_provider->deduplicate = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); 254 | } 255 | 256 | /****************************************************************** 257 | * NdProvider interface implementation 258 | ******************************************************************/ 259 | 260 | static void 261 | nd_meta_provider_provider_iface_init (NdProviderIface *iface) 262 | { 263 | iface->get_sinks = nd_meta_provider_provider_get_sinks; 264 | } 265 | 266 | static GList * 267 | nd_meta_provider_provider_get_sinks (NdProvider *provider) 268 | { 269 | NdMetaProvider *meta_provider = ND_META_PROVIDER (provider); 270 | GList *res = NULL; 271 | 272 | for (gint i = 0; i < meta_provider->sinks->len; i++) 273 | res = g_list_prepend (res, meta_provider->sinks->pdata[i]); 274 | 275 | return res; 276 | } 277 | 278 | /****************************************************************** 279 | * NdMetaProvider public functions 280 | ******************************************************************/ 281 | 282 | /** 283 | * nd_meta_provider_get_providers 284 | * @meta_provider: a #NdMetaProvider 285 | * 286 | * Retrieve the providers registered with this meta provider. 287 | * 288 | * Returns: (transfer container) (element-type NdProvider): 289 | * A list of all known providers 290 | */ 291 | GList * 292 | nd_meta_provider_get_providers (NdMetaProvider *meta_provider) 293 | { 294 | GList *res = NULL; 295 | 296 | for (gint i = 0; i < meta_provider->providers->len; i++) 297 | res = g_list_prepend (res, meta_provider->providers->pdata[i]); 298 | 299 | return res; 300 | } 301 | 302 | /** 303 | * nd_meta_provider_add_provider 304 | * @meta_provider: a #NdMetaProvider 305 | * @provider: a #NdProvider 306 | * 307 | * Adds the provider to the list of known providers. 308 | */ 309 | void 310 | nd_meta_provider_add_provider (NdMetaProvider *meta_provider, 311 | NdProvider *provider) 312 | { 313 | g_autoptr(GList) list = NULL; 314 | GList *item; 315 | 316 | g_assert (provider); 317 | g_assert (!g_ptr_array_find (meta_provider->providers, provider, NULL)); 318 | 319 | g_ptr_array_add (meta_provider->providers, g_object_ref (provider)); 320 | 321 | g_signal_connect_object (provider, 322 | "sink-added", 323 | (GCallback) provider_sink_added_cb, 324 | meta_provider, 325 | G_CONNECT_SWAPPED); 326 | 327 | g_signal_connect_object (provider, 328 | "sink-removed", 329 | (GCallback) provider_sink_removed_cb, 330 | meta_provider, 331 | G_CONNECT_SWAPPED); 332 | 333 | /* Sync discovery property */ 334 | g_object_set (provider, "discover", meta_provider->discover, NULL); 335 | 336 | /* Explicitly add all existing sinks */ 337 | list = nd_provider_get_sinks (provider); 338 | item = list; 339 | while (item) 340 | { 341 | provider_sink_added_cb (meta_provider, ND_SINK (item->data), provider); 342 | item = item->next; 343 | } 344 | 345 | g_object_notify_by_pspec (G_OBJECT (meta_provider), props[PROP_HAS_PROVIDERS]); 346 | } 347 | 348 | /** 349 | * nd_meta_provider_remove_provider 350 | * @meta_provider: a #NdMetaProvider 351 | * @provider: a #NdProvider 352 | * 353 | * Remove the provider from the list of providers. 354 | */ 355 | void 356 | nd_meta_provider_remove_provider (NdMetaProvider *meta_provider, 357 | NdProvider *provider) 358 | { 359 | g_autoptr(GList) list = NULL; 360 | GList *item; 361 | g_assert (provider); 362 | 363 | g_signal_handlers_disconnect_by_data (provider, meta_provider); 364 | 365 | /* Explicitly remove all existing sinks */ 366 | list = nd_provider_get_sinks (provider); 367 | item = list; 368 | while (item) 369 | { 370 | provider_sink_removed_cb (meta_provider, ND_SINK (item->data), provider); 371 | item = item->next; 372 | } 373 | 374 | g_assert (g_ptr_array_remove (meta_provider->providers, provider)); 375 | 376 | g_object_notify_by_pspec (G_OBJECT (meta_provider), props[PROP_HAS_PROVIDERS]); 377 | } 378 | 379 | NdMetaProvider * 380 | nd_meta_provider_new (void) 381 | { 382 | return g_object_new (ND_TYPE_META_PROVIDER, 383 | NULL); 384 | } 385 | -------------------------------------------------------------------------------- /src/nd-meta-provider.h: -------------------------------------------------------------------------------- 1 | /* nd-meta-provider.h 2 | * 3 | * Copyright 2018 Benjamin Berg 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "nd-provider.h" 22 | 23 | G_BEGIN_DECLS 24 | 25 | #define ND_TYPE_META_PROVIDER (nd_meta_provider_get_type ()) 26 | G_DECLARE_FINAL_TYPE (NdMetaProvider, nd_meta_provider, ND, META_PROVIDER, GObject) 27 | 28 | 29 | NdMetaProvider * nd_meta_provider_new (void); 30 | 31 | GList * nd_meta_provider_get_providers (NdMetaProvider *meta_provider); 32 | 33 | void nd_meta_provider_add_provider (NdMetaProvider *meta_provider, 34 | NdProvider *provider); 35 | void nd_meta_provider_remove_provider (NdMetaProvider *meta_provider, 36 | NdProvider *provider); 37 | 38 | 39 | G_END_DECLS 40 | -------------------------------------------------------------------------------- /src/nd-meta-sink.c: -------------------------------------------------------------------------------- 1 | /* nd-meta-sink.c 2 | * 3 | * Copyright 2018 Benjamin Berg 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "gnome-network-displays-config.h" 20 | #include "nd-meta-sink.h" 21 | 22 | struct _NdMetaSink 23 | { 24 | GObject parent_instance; 25 | 26 | NdSink *current_sink; 27 | GPtrArray *sinks; 28 | }; 29 | 30 | enum { 31 | PROP_SINK = 1, 32 | PROP_SINKS, 33 | 34 | PROP_DISPLAY_NAME, 35 | PROP_MATCHES, 36 | PROP_PRIORITY, 37 | PROP_STATE, 38 | PROP_MISSING_VIDEO_CODEC, 39 | PROP_MISSING_AUDIO_CODEC, 40 | 41 | PROP_LAST = PROP_DISPLAY_NAME, 42 | }; 43 | 44 | static void nd_meta_sink_sink_iface_init (NdSinkIface *iface); 45 | static NdSink * nd_meta_sink_sink_start_stream (NdSink *sink); 46 | static void nd_meta_sink_sink_stop_stream (NdSink *sink); 47 | 48 | G_DEFINE_TYPE_EXTENDED (NdMetaSink, nd_meta_sink, G_TYPE_OBJECT, 0, 49 | G_IMPLEMENT_INTERFACE (ND_TYPE_SINK, 50 | nd_meta_sink_sink_iface_init); 51 | ) 52 | 53 | static GParamSpec * props[PROP_LAST] = { NULL, }; 54 | 55 | static void 56 | nd_meta_sink_notify_sink_cb (NdMetaSink *meta_sink, GParamSpec *pspec, NdSink *sink) 57 | { 58 | /* Propage the cb if this MetaSink also has a property of the same name */ 59 | if (g_object_class_find_property (G_OBJECT_GET_CLASS (meta_sink), pspec->name)) 60 | g_object_notify (G_OBJECT (meta_sink), pspec->name); 61 | } 62 | 63 | static void 64 | nd_meta_sink_update (NdMetaSink *meta_sink) 65 | { 66 | gint i; 67 | gint best_priority = G_MININT; 68 | NdSink *best_sink = NULL; 69 | 70 | for (i = 0; i < meta_sink->sinks->len; i++) 71 | { 72 | NdSink *sink; 73 | gint priority; 74 | sink = g_ptr_array_index (meta_sink->sinks, i); 75 | 76 | g_object_get (sink, "priority", &priority, NULL); 77 | if (priority == best_priority) 78 | g_debug ("MetaSink: Found two sinks with identical priority! Prefered order is undefined.\n"); 79 | 80 | if (priority > best_priority) 81 | best_sink = sink; 82 | } 83 | 84 | /* Nothing has changed */ 85 | if (best_sink == meta_sink->current_sink) 86 | return; 87 | 88 | if (meta_sink->current_sink) 89 | { 90 | g_signal_handlers_disconnect_by_data (meta_sink->current_sink, meta_sink); 91 | g_clear_object (&meta_sink->current_sink); 92 | } 93 | 94 | if (best_sink) 95 | { 96 | meta_sink->current_sink = g_object_ref (best_sink); 97 | g_signal_connect_object (meta_sink->current_sink, 98 | "notify", (GCallback) nd_meta_sink_notify_sink_cb, 99 | meta_sink, G_CONNECT_SWAPPED); 100 | } 101 | else 102 | { 103 | g_debug ("MetaSink: No usable sink is left, object has become invalid."); 104 | } 105 | 106 | /* Notify the pass-through properties */ 107 | g_object_notify (G_OBJECT (meta_sink), "display-name"); 108 | g_object_notify (G_OBJECT (meta_sink), "priority"); 109 | g_object_notify (G_OBJECT (meta_sink), "state"); 110 | g_object_notify (G_OBJECT (meta_sink), "missing-video-codec"); 111 | g_object_notify (G_OBJECT (meta_sink), "missing-audio-codec"); 112 | } 113 | 114 | static void 115 | nd_meta_sink_get_property (GObject *object, 116 | guint prop_id, 117 | GValue *value, 118 | GParamSpec *pspec) 119 | { 120 | NdMetaSink *meta_sink = ND_META_SINK (object); 121 | 122 | switch (prop_id) 123 | { 124 | case PROP_SINK: 125 | g_value_set_object (value, meta_sink->current_sink); 126 | break; 127 | 128 | case PROP_SINKS: 129 | g_value_set_boxed (value, meta_sink->sinks); 130 | break; 131 | 132 | case PROP_DISPLAY_NAME: 133 | if (meta_sink->current_sink) 134 | g_object_get_property (G_OBJECT (meta_sink->current_sink), pspec->name, value); 135 | else 136 | g_value_set_static_string (value, NULL); 137 | break; 138 | 139 | case PROP_MATCHES: 140 | { 141 | g_autoptr(GPtrArray) res = NULL; 142 | res = g_ptr_array_new_with_free_func (g_free); 143 | 144 | for (gint i = 0; i < meta_sink->sinks->len; i++) 145 | { 146 | g_autoptr(GPtrArray) sub_matches = NULL; 147 | 148 | g_object_get (g_ptr_array_index (meta_sink->sinks, i), 149 | "matches", &sub_matches, 150 | NULL); 151 | 152 | for (gint j = 0; j < sub_matches->len; j++) 153 | if (!g_ptr_array_find_with_equal_func (res, g_ptr_array_index (sub_matches, j), g_str_equal, NULL)) 154 | g_ptr_array_add (res, g_strdup (g_ptr_array_index (sub_matches, j))); 155 | } 156 | 157 | g_value_take_boxed (value, g_steal_pointer (&res)); 158 | break; 159 | } 160 | 161 | case PROP_PRIORITY: 162 | if (meta_sink->current_sink) 163 | g_object_get_property (G_OBJECT (meta_sink->current_sink), pspec->name, value); 164 | else 165 | g_value_set_int (value, 0); 166 | break; 167 | 168 | case PROP_STATE: 169 | if (meta_sink->current_sink) 170 | g_object_get_property (G_OBJECT (meta_sink->current_sink), pspec->name, value); 171 | else 172 | g_value_set_enum (value, ND_SINK_STATE_DISCONNECTED); 173 | break; 174 | 175 | case PROP_MISSING_VIDEO_CODEC: 176 | case PROP_MISSING_AUDIO_CODEC: 177 | if (meta_sink->current_sink) 178 | g_object_get_property (G_OBJECT (meta_sink->current_sink), pspec->name, value); 179 | else 180 | g_value_set_boxed (value, NULL); 181 | break; 182 | 183 | default: 184 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 185 | break; 186 | } 187 | } 188 | 189 | static void 190 | nd_meta_sink_set_property (GObject *object, 191 | guint prop_id, 192 | const GValue *value, 193 | GParamSpec *pspec) 194 | { 195 | NdMetaSink *meta_sink = ND_META_SINK (object); 196 | 197 | switch (prop_id) 198 | { 199 | case PROP_SINK: 200 | nd_meta_sink_add_sink (meta_sink, g_value_get_object (value)); 201 | break; 202 | 203 | default: 204 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 205 | break; 206 | } 207 | } 208 | 209 | static void 210 | nd_meta_sink_finalize (GObject *object) 211 | { 212 | NdMetaSink *meta_sink = ND_META_SINK (object); 213 | 214 | g_ptr_array_free (meta_sink->sinks, TRUE); 215 | meta_sink->sinks = NULL; 216 | 217 | g_signal_handlers_disconnect_by_data (meta_sink->current_sink, meta_sink); 218 | g_clear_object (&meta_sink->current_sink); 219 | 220 | G_OBJECT_CLASS (nd_meta_sink_parent_class)->finalize (object); 221 | } 222 | 223 | static void 224 | nd_meta_sink_class_init (NdMetaSinkClass *klass) 225 | { 226 | GObjectClass *object_class = G_OBJECT_CLASS (klass); 227 | 228 | object_class->get_property = nd_meta_sink_get_property; 229 | object_class->set_property = nd_meta_sink_set_property; 230 | object_class->finalize = nd_meta_sink_finalize; 231 | 232 | props[PROP_SINK] = 233 | g_param_spec_object ("sink", "sink", 234 | "The currently selected sink. Writing the property results in the sink to be added (but not necessarily selected).", 235 | ND_TYPE_SINK, 236 | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); 237 | 238 | props[PROP_SINKS] = 239 | g_param_spec_boxed ("sinks", "sinks", 240 | "All sinks that are grouped into the meta sink.", 241 | G_TYPE_PTR_ARRAY, 242 | G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); 243 | 244 | g_object_class_install_properties (object_class, PROP_LAST, props); 245 | 246 | g_object_class_override_property (object_class, PROP_DISPLAY_NAME, "display-name"); 247 | g_object_class_override_property (object_class, PROP_MATCHES, "matches"); 248 | g_object_class_override_property (object_class, PROP_PRIORITY, "priority"); 249 | g_object_class_override_property (object_class, PROP_STATE, "state"); 250 | g_object_class_override_property (object_class, PROP_MISSING_VIDEO_CODEC, "missing-video-codec"); 251 | g_object_class_override_property (object_class, PROP_MISSING_AUDIO_CODEC, "missing-audio-codec"); 252 | } 253 | 254 | static void 255 | nd_meta_sink_init (NdMetaSink *meta_sink) 256 | { 257 | meta_sink->sinks = g_ptr_array_new_with_free_func (g_object_unref); 258 | } 259 | 260 | /****************************************************************** 261 | * NdSink interface implementation 262 | ******************************************************************/ 263 | 264 | static void 265 | nd_meta_sink_sink_iface_init (NdSinkIface *iface) 266 | { 267 | iface->start_stream = nd_meta_sink_sink_start_stream; 268 | iface->stop_stream = nd_meta_sink_sink_stop_stream; 269 | } 270 | 271 | static NdSink * 272 | nd_meta_sink_sink_start_stream (NdSink *sink) 273 | { 274 | NdMetaSink *meta_sink = ND_META_SINK (sink); 275 | 276 | g_assert (meta_sink->current_sink); 277 | 278 | return nd_sink_start_stream (meta_sink->current_sink); 279 | } 280 | 281 | static void 282 | nd_meta_sink_sink_stop_stream (NdSink *sink) 283 | { 284 | /* This must not happen. */ 285 | g_assert_not_reached (); 286 | } 287 | 288 | /****************************************************************** 289 | * NdMetaSink public functions 290 | ******************************************************************/ 291 | 292 | /** 293 | * nd_meta_sink_get_sink 294 | * @meta_sink: a #NdMetaSink 295 | * 296 | * Retrieve the currently selected sink for this meta sink. 297 | * 298 | * Returns: (transfer none): The selected sink 299 | */ 300 | NdSink * 301 | nd_meta_sink_get_sink (NdMetaSink *meta_sink) 302 | { 303 | return meta_sink->current_sink; 304 | } 305 | 306 | /** 307 | * nd_meta_sink_add_sink 308 | * @meta_sink: a #NdMetaSink 309 | * @sink: a #NdSink 310 | * 311 | * Adds the sink to the list of known sinks. 312 | */ 313 | void 314 | nd_meta_sink_add_sink (NdMetaSink *meta_sink, 315 | NdSink *sink) 316 | { 317 | g_assert (!g_ptr_array_find (meta_sink->sinks, sink, NULL)); 318 | 319 | g_ptr_array_add (meta_sink->sinks, g_object_ref (sink)); 320 | 321 | nd_meta_sink_update (meta_sink); 322 | 323 | g_object_notify_by_pspec (G_OBJECT (meta_sink), props[PROP_SINKS]); 324 | g_object_notify (G_OBJECT (meta_sink), "matches"); 325 | } 326 | 327 | /** 328 | * nd_meta_sink_remove_sink 329 | * @meta_sink: a #NdMetaSink 330 | * @sink: a #NdSink 331 | * 332 | * Remove the given sink from the meta sink. If the last child 333 | * sink has been removed, then the metasink becomes invalid. 334 | * 335 | * Returns: 336 | * #TRUE if no sinks are left and the #NdMetaSink has become invalid. 337 | */ 338 | gboolean 339 | nd_meta_sink_remove_sink (NdMetaSink *meta_sink, 340 | NdSink *sink) 341 | { 342 | g_assert (g_ptr_array_remove (meta_sink->sinks, sink)); 343 | 344 | nd_meta_sink_update (meta_sink); 345 | 346 | g_object_notify_by_pspec (G_OBJECT (meta_sink), props[PROP_SINKS]); 347 | g_object_notify (G_OBJECT (meta_sink), "matches"); 348 | 349 | return meta_sink->sinks->len <= 0; 350 | } 351 | 352 | NdMetaSink * 353 | nd_meta_sink_new (NdSink *sink) 354 | { 355 | return g_object_new (ND_TYPE_META_SINK, 356 | "sink", sink, 357 | NULL); 358 | } 359 | -------------------------------------------------------------------------------- /src/nd-meta-sink.h: -------------------------------------------------------------------------------- 1 | /* nd-meta-sink.h 2 | * 3 | * Copyright 2018 Benjamin Berg 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "nd-sink.h" 22 | 23 | G_BEGIN_DECLS 24 | 25 | #define ND_TYPE_META_SINK (nd_meta_sink_get_type ()) 26 | G_DECLARE_FINAL_TYPE (NdMetaSink, nd_meta_sink, ND, META_SINK, GObject) 27 | 28 | NdMetaSink * nd_meta_sink_new (NdSink * sink); 29 | 30 | NdSink * nd_meta_sink_get_sink (NdMetaSink * meta_sink); 31 | void nd_meta_sink_add_sink (NdMetaSink *meta_sink, 32 | NdSink *sink); 33 | gboolean nd_meta_sink_remove_sink (NdMetaSink *meta_sink, 34 | NdSink *sink); 35 | 36 | 37 | G_END_DECLS 38 | -------------------------------------------------------------------------------- /src/nd-provider.c: -------------------------------------------------------------------------------- 1 | /* nd-provider.c 2 | * 3 | * Copyright 2018 Benjamin Berg 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "gnome-network-displays-config.h" 20 | #include "nd-provider.h" 21 | 22 | typedef NdProviderIface NdProviderInterface; 23 | G_DEFINE_INTERFACE (NdProvider, nd_provider, G_TYPE_OBJECT); 24 | 25 | static void 26 | nd_provider_default_init (NdProviderIface *iface) 27 | { 28 | /** 29 | * NdProvider::sink-added: 30 | * @provider: the #NdProvider on which the signal is emitted 31 | * @sink: the #NdSink that was added 32 | * 33 | * The ::sink-added signal is emitted whenever a new sink is added 34 | * to the provider. 35 | */ 36 | g_signal_new ("sink-added", 37 | ND_TYPE_PROVIDER, 38 | 0, 39 | G_STRUCT_OFFSET (NdProviderIface, sink_added), 40 | NULL, 41 | NULL, 42 | NULL, 43 | G_TYPE_NONE, 44 | 1, ND_TYPE_SINK); 45 | 46 | /** 47 | * NdProvider::sink-removed: 48 | * @provider: the #NdProvider on which the signal is emitted 49 | * @sink: the #NdSink that was removed 50 | * 51 | * The ::sink-added signal is emitted whenever a sink is removed 52 | * from the provider. 53 | */ 54 | g_signal_new ("sink-removed", 55 | ND_TYPE_PROVIDER, 56 | 0, 57 | G_STRUCT_OFFSET (NdProviderIface, sink_removed), 58 | NULL, 59 | NULL, 60 | NULL, 61 | G_TYPE_NONE, 62 | 1, ND_TYPE_SINK); 63 | 64 | g_object_interface_install_property (iface, 65 | g_param_spec_boolean ("discover", 66 | "Discover", 67 | "Whether discovery is turned on", 68 | TRUE, 69 | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); 70 | } 71 | 72 | /** 73 | * nd_provider_get_sinks 74 | * @provider: the #NdProvider 75 | * 76 | * Retrieve a list of all sinks known to the provider. 77 | * 78 | * Returns: (element-type NdSink) (transfer container): 79 | * A newly allocated list of sinks. Free with g_list_free(). 80 | */ 81 | GList * 82 | nd_provider_get_sinks (NdProvider *provider) 83 | { 84 | NdProviderIface *iface = ND_PROVIDER_GET_IFACE (provider); 85 | 86 | return iface->get_sinks (provider); 87 | } 88 | -------------------------------------------------------------------------------- /src/nd-provider.h: -------------------------------------------------------------------------------- 1 | /* nd-provider.h 2 | * 3 | * Copyright 2018 Benjamin Berg 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include "nd-sink.h" 23 | 24 | G_BEGIN_DECLS 25 | 26 | #define ND_TYPE_PROVIDER (nd_provider_get_type ()) 27 | #define ND_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ND_TYPE_PROVIDER, NdProvider)) 28 | #define ND_IS_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ND_TYPE_PROVIDER)) 29 | #define ND_PROVIDER_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), ND_TYPE_PROVIDER, NdProviderIface)) 30 | 31 | typedef struct _NdProvider NdProvider; /* dummy typedef */ 32 | typedef struct _NdProviderIface NdProviderIface; 33 | 34 | struct _NdProviderIface 35 | { 36 | /*< private >*/ 37 | GTypeInterface g_iface; 38 | 39 | /*< public >*/ 40 | 41 | /* basics */ 42 | GList * (*get_sinks) (NdProvider *provider); 43 | 44 | 45 | /* signals */ 46 | void (* sink_added) (NdProvider *provider, 47 | NdSink *sink); 48 | void (* sink_removed) (NdProvider *provider, 49 | NdSink *sink); 50 | }; 51 | 52 | GType nd_provider_get_type (void) G_GNUC_CONST; 53 | 54 | GList * nd_provider_get_sinks (NdProvider *provider); 55 | 56 | G_END_DECLS 57 | -------------------------------------------------------------------------------- /src/nd-pulseaudio.c: -------------------------------------------------------------------------------- 1 | #include "nd-pulseaudio.h" 2 | #include 3 | #include 4 | 5 | G_DEFINE_AUTOPTR_CLEANUP_FUNC (pa_proplist, pa_proplist_free) 6 | 7 | #define ND_PA_SINK "gnome_network_displays" 8 | #define ND_PA_MONITOR ND_PA_SINK ".monitor" 9 | 10 | struct _NdPulseaudio 11 | { 12 | GObject parent_instance; 13 | 14 | GTask *init_task; 15 | 16 | pa_glib_mainloop *mainloop; 17 | pa_mainloop_api *mainloop_api; 18 | pa_context *context; 19 | guint null_module_idx; 20 | 21 | pa_operation *operation; 22 | }; 23 | 24 | static void nd_pulseaudio_async_initable_iface_init (GAsyncInitableIface *iface); 25 | static void nd_pulseaudio_async_initable_init_async (GAsyncInitable *initable, 26 | int io_priority, 27 | GCancellable *cancellable, 28 | GAsyncReadyCallback callback, 29 | gpointer user_data); 30 | static gboolean nd_pulseaudio_async_initable_init_finish (GAsyncInitable *initable, 31 | GAsyncResult *res, 32 | GError **error); 33 | 34 | G_DEFINE_TYPE_EXTENDED (NdPulseaudio, nd_pulseaudio, G_TYPE_OBJECT, 0, 35 | G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, 36 | nd_pulseaudio_async_initable_iface_init); 37 | ) 38 | 39 | static void 40 | nd_pulseaudio_async_initable_iface_init (GAsyncInitableIface *iface) 41 | { 42 | iface->init_async = nd_pulseaudio_async_initable_init_async; 43 | iface->init_finish = nd_pulseaudio_async_initable_init_finish; 44 | } 45 | 46 | static gboolean 47 | return_idle_cb (gpointer user_data) 48 | { 49 | GTask *task = user_data; 50 | GError *error = g_object_steal_data (G_OBJECT (task), "result"); 51 | 52 | if (error) 53 | g_task_return_error (task, error); 54 | else 55 | g_task_return_boolean (task, TRUE); 56 | 57 | g_object_unref (task); 58 | 59 | return G_SOURCE_REMOVE; 60 | } 61 | 62 | /* Helper function to return an error from idle */ 63 | static void 64 | return_idle_error (GTask *task, GError *error) 65 | { 66 | g_assert (error); 67 | g_object_set_data_full (G_OBJECT (task), "result", error, (GDestroyNotify) g_error_free); 68 | g_idle_add (return_idle_cb, g_object_ref (task)); 69 | } 70 | 71 | static void 72 | return_idle_success (GTask *task) 73 | { 74 | g_idle_add (return_idle_cb, g_object_ref (task)); 75 | } 76 | 77 | static void 78 | on_pa_null_module_loaded (pa_context *c, 79 | uint32_t idx, 80 | void *userdata) 81 | { 82 | NdPulseaudio *self = ND_PULSEAUDIO (userdata); 83 | 84 | if (idx == PA_INVALID_INDEX) 85 | { 86 | g_debug ("NdPulseaudio: Module load failed!"); 87 | return_idle_error (self->init_task, 88 | g_error_new (G_IO_ERROR, 89 | G_IO_ERROR_FAILED, 90 | "Failed to load NULL module for screencast PA sink")); 91 | g_clear_object (&self->init_task); 92 | return; 93 | } 94 | 95 | g_debug ("NdPulseaudio: Module loaded, we are ready to grab audio! "); 96 | self->null_module_idx = idx; 97 | return_idle_success (self->init_task); 98 | g_clear_object (&self->init_task); 99 | } 100 | 101 | static void 102 | on_pa_nd_sink_got_info (pa_context *c, 103 | const pa_sink_info *i, 104 | int eol, 105 | void *userdata) 106 | { 107 | NdPulseaudio *self = ND_PULSEAUDIO (userdata); 108 | 109 | /* eol is negative for error, and 1 for end of list (with NULL sink info then) */ 110 | 111 | /* We are only ever called once, as we cancel the operation 112 | * if we get what we want. */ 113 | if (eol < 0 && pa_context_errno (self->context) != PA_ERR_NOENTITY) 114 | { 115 | g_debug ("NdPulseaudio: Error querying sink info"); 116 | return_idle_error (self->init_task, 117 | g_error_new (G_IO_ERROR, 118 | G_IO_ERROR_FAILED, 119 | "Error querying sink from PA: %s", 120 | pa_strerror (pa_context_errno (self->context)))); 121 | g_clear_object (&self->init_task); 122 | return; 123 | } 124 | 125 | if (eol == 0) 126 | { 127 | /* This is the case that we could query the sink, so it seems 128 | * like it exists already. Just double check things, and 129 | * return successful initialization. 130 | */ 131 | 132 | /* Cancel the operation as we would be called a second time for 133 | * the list end which would cause am immediate successfull return. */ 134 | pa_operation_cancel (self->operation); 135 | g_clear_pointer (&self->operation, pa_operation_unref); 136 | 137 | g_debug ("NdPulseaudio: Error querying sink info"); 138 | g_debug ("NdPulseaudio: Got a sink info for the expected name"); 139 | 140 | return_idle_success (self->init_task); 141 | g_clear_object (&self->init_task); 142 | return; 143 | } 144 | 145 | g_debug ("NdPulseaudio: Sink does not exist yet, loading module"); 146 | 147 | /* We have reached the list end without being cancelled first. 148 | * This means no screencast sink exist, and we need to create it. */ 149 | self->operation = pa_context_load_module (self->context, 150 | "module-null-sink", 151 | "sink_name=" ND_PA_SINK 152 | " " 153 | "rate=48000 " 154 | "sink_properties=device.description=\"Network-Displays\"" 155 | "device.class=\"sound\"" 156 | "device.icon_name=\"network-wireless\"", 157 | on_pa_null_module_loaded, 158 | self); 159 | } 160 | 161 | static void 162 | nd_pulseaudio_state_cb (pa_context *context, 163 | void *user_data) 164 | { 165 | NdPulseaudio *self = ND_PULSEAUDIO (user_data); 166 | 167 | switch (pa_context_get_state (context)) 168 | { 169 | case PA_CONTEXT_UNCONNECTED: 170 | case PA_CONTEXT_CONNECTING: 171 | case PA_CONTEXT_AUTHORIZING: 172 | case PA_CONTEXT_SETTING_NAME: 173 | break; 174 | 175 | case PA_CONTEXT_READY: 176 | if (!self->init_task) 177 | return; 178 | 179 | g_debug ("NdPulseaudio: Querying sink info by name"); 180 | self->operation = pa_context_get_sink_info_by_name (self->context, 181 | ND_PA_SINK, 182 | on_pa_nd_sink_got_info, 183 | self); 184 | break; 185 | 186 | case PA_CONTEXT_FAILED: 187 | case PA_CONTEXT_TERMINATED: 188 | if (!self->init_task) 189 | return; 190 | 191 | g_debug ("NdPulseaudio: PA context went into failed state during init"); 192 | return_idle_error (self->init_task, 193 | g_error_new (G_IO_ERROR, 194 | G_IO_ERROR_FAILED, 195 | "PA failed")); 196 | g_clear_object (&self->init_task); 197 | break; 198 | 199 | default: 200 | /* FIXME: */ 201 | break; 202 | } 203 | } 204 | 205 | static void 206 | nd_pulseaudio_async_initable_init_async (GAsyncInitable *initable, 207 | int io_priority, 208 | GCancellable *cancellable, 209 | GAsyncReadyCallback callback, 210 | gpointer user_data) 211 | { 212 | NdPulseaudio *self = ND_PULSEAUDIO (initable); 213 | 214 | g_autoptr(pa_proplist) proplist = NULL; 215 | gint res; 216 | 217 | self->mainloop = pa_glib_mainloop_new (g_main_context_default ()); 218 | self->mainloop_api = pa_glib_mainloop_get_api (self->mainloop); 219 | 220 | proplist = pa_proplist_new (); 221 | pa_proplist_sets (proplist, PA_PROP_APPLICATION_NAME, "GNOME Network Displays"); 222 | pa_proplist_sets (proplist, PA_PROP_APPLICATION_ID, "org.gnome.NetworkDisplays"); 223 | /* pa_proplist_sets (proplist, PA_PROP_APPLICATION_ICON_NAME, ); */ 224 | 225 | self->context = pa_context_new_with_proplist (self->mainloop_api, NULL, proplist); 226 | 227 | /* Create our task; we currently don't handle cancellation internally */ 228 | self->init_task = g_task_new (initable, cancellable, callback, user_data); 229 | 230 | pa_context_set_state_callback (self->context, 231 | nd_pulseaudio_state_cb, 232 | self); 233 | 234 | res = pa_context_connect (self->context, NULL, (pa_context_flags_t) PA_CONTEXT_NOFLAGS, NULL); 235 | if (res < 0) 236 | { 237 | g_debug ("NdPulseaudio: Error querying sink info"); 238 | if (self->init_task) 239 | g_task_return_new_error (self->init_task, 240 | G_IO_ERROR, 241 | G_IO_ERROR_FAILED, 242 | "Error connecting to PA: %s", 243 | pa_strerror (pa_context_errno (self->context))); 244 | g_clear_object (&self->init_task); 245 | return; 246 | } 247 | 248 | /* Wait for us to be connected. */ 249 | } 250 | 251 | static gboolean 252 | nd_pulseaudio_async_initable_init_finish (GAsyncInitable *initable, 253 | GAsyncResult *res, 254 | GError **error) 255 | { 256 | return g_task_propagate_boolean (G_TASK (res), error); 257 | } 258 | 259 | 260 | 261 | NdPulseaudio * 262 | nd_pulseaudio_new (void) 263 | { 264 | return g_object_new (ND_TYPE_PULSEAUDIO, NULL); 265 | } 266 | 267 | static void 268 | nd_pulseaudio_finalize (GObject *object) 269 | { 270 | NdPulseaudio *self = (NdPulseaudio *) object; 271 | 272 | if (self->init_task) 273 | { 274 | g_task_return_new_error (self->init_task, 275 | G_IO_ERROR, 276 | G_IO_ERROR_CANCELLED, 277 | "Object finalised, async init was cancelled."); 278 | g_clear_object (&self->init_task); 279 | } 280 | 281 | if (self->operation) 282 | pa_operation_cancel (self->operation); 283 | g_clear_pointer (&self->operation, pa_operation_unref); 284 | 285 | g_clear_pointer (&self->context, pa_context_unref); 286 | self->mainloop_api = NULL; 287 | g_clear_pointer (&self->mainloop, pa_glib_mainloop_free); 288 | 289 | G_OBJECT_CLASS (nd_pulseaudio_parent_class)->finalize (object); 290 | } 291 | 292 | static void 293 | nd_pulseaudio_class_init (NdPulseaudioClass *klass) 294 | { 295 | GObjectClass *object_class = G_OBJECT_CLASS (klass); 296 | 297 | object_class->finalize = nd_pulseaudio_finalize; 298 | } 299 | 300 | static void 301 | nd_pulseaudio_init (NdPulseaudio *self) 302 | { 303 | self->null_module_idx = PA_INVALID_INDEX; 304 | } 305 | 306 | GstElement * 307 | nd_pulseaudio_get_source (NdPulseaudio *self) 308 | { 309 | g_autoptr(GstElement) src = NULL; 310 | 311 | g_assert (self->init_task == NULL); 312 | g_assert (self->context != NULL); 313 | 314 | src = gst_element_factory_make ("pulsesrc", "pulseaudio-source"); 315 | 316 | g_object_set (src, 317 | "device", ND_PA_MONITOR, 318 | "client-name", "GNOME Network Displays Audio Grabber", 319 | "do-timestamp", TRUE, 320 | "server", pa_context_get_server (self->context), 321 | NULL); 322 | 323 | return g_steal_pointer (&src); 324 | } 325 | -------------------------------------------------------------------------------- /src/nd-pulseaudio.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | G_BEGIN_DECLS 8 | 9 | #define ND_TYPE_PULSEAUDIO (nd_pulseaudio_get_type ()) 10 | 11 | G_DECLARE_FINAL_TYPE (NdPulseaudio, nd_pulseaudio, ND, PULSEAUDIO, GObject) 12 | 13 | NdPulseaudio *nd_pulseaudio_new (void); 14 | 15 | GstElement *nd_pulseaudio_get_source (NdPulseaudio *self); 16 | 17 | G_END_DECLS 18 | -------------------------------------------------------------------------------- /src/nd-screencast-portal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | G_BEGIN_DECLS 8 | 9 | #define ND_TYPE_SCREENCAST_PORTAL (nd_screencast_portal_get_type ()) 10 | 11 | G_DECLARE_FINAL_TYPE (NdScreencastPortal, nd_screencast_portal, ND, SCREENCAST_PORTAL, GObject) 12 | 13 | NdScreencastPortal * nd_screencast_portal_new (void); 14 | 15 | GstElement *nd_screencast_portal_get_source (NdScreencastPortal *self); 16 | 17 | G_END_DECLS 18 | -------------------------------------------------------------------------------- /src/nd-sink-list.c: -------------------------------------------------------------------------------- 1 | /* nd-sink-list.c 2 | * 3 | * Copyright 2018 Benjamin Berg 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "gnome-network-displays-config.h" 20 | #include "nd-sink-list.h" 21 | #include "nd-sink.h" 22 | #include "nd-sink-row.h" 23 | 24 | struct _NdSinkList 25 | { 26 | GtkListBox parent_instance; 27 | 28 | NdProvider *provider; 29 | }; 30 | 31 | enum { 32 | PROP_PROVIDER = 1, 33 | PROP_LAST, 34 | }; 35 | 36 | G_DEFINE_TYPE (NdSinkList, nd_sink_list, GTK_TYPE_LIST_BOX) 37 | 38 | static GParamSpec * props[PROP_LAST] = { NULL, }; 39 | 40 | 41 | static void 42 | sink_added_cb (NdSinkList *sink_list, 43 | NdSink *sink, 44 | NdProvider *provider) 45 | { 46 | NdSinkRow *sink_row; 47 | 48 | g_debug ("SinkList: Adding a sink"); 49 | sink_row = nd_sink_row_new (sink); 50 | gtk_container_add (GTK_CONTAINER (sink_list), GTK_WIDGET (sink_row)); 51 | gtk_widget_show (GTK_WIDGET (sink_row)); 52 | } 53 | 54 | static void 55 | foreach_remove_matching_sink (NdSinkRow *sink_row, 56 | NdSink *sink) 57 | { 58 | if (nd_sink_row_get_sink (sink_row) == sink) 59 | gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (GTK_WIDGET (sink_row))), 60 | GTK_WIDGET (sink_row)); 61 | } 62 | 63 | static void 64 | sink_removed_cb (NdSinkList *sink_list, 65 | NdSink *sink, 66 | NdProvider *provider) 67 | { 68 | g_debug ("SinkList: Removing a sink"); 69 | gtk_container_foreach (GTK_CONTAINER (sink_list), 70 | (GtkCallback) foreach_remove_matching_sink, 71 | sink); 72 | } 73 | 74 | static void 75 | nd_sink_list_get_property (GObject *object, 76 | guint prop_id, 77 | GValue *value, 78 | GParamSpec *pspec) 79 | { 80 | NdSinkList *sink_list = ND_SINK_LIST (object); 81 | 82 | switch (prop_id) 83 | { 84 | case PROP_PROVIDER: 85 | g_value_set_object (value, sink_list->provider); 86 | break; 87 | 88 | default: 89 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 90 | break; 91 | } 92 | } 93 | 94 | static void 95 | nd_sink_list_set_property (GObject *object, 96 | guint prop_id, 97 | const GValue *value, 98 | GParamSpec *pspec) 99 | { 100 | NdSinkList *sink_list = ND_SINK_LIST (object); 101 | 102 | switch (prop_id) 103 | { 104 | case PROP_PROVIDER: 105 | nd_sink_list_set_provider (sink_list, g_value_get_object (value)); 106 | break; 107 | 108 | default: 109 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 110 | break; 111 | } 112 | } 113 | 114 | static void 115 | nd_sink_list_finalize (GObject *object) 116 | { 117 | NdSinkList *sink_list = ND_SINK_LIST (object); 118 | 119 | nd_sink_list_set_provider (sink_list, NULL); 120 | 121 | G_OBJECT_CLASS (nd_sink_list_parent_class)->finalize (object); 122 | } 123 | 124 | static void 125 | nd_sink_list_class_init (NdSinkListClass *klass) 126 | { 127 | GObjectClass *object_class = G_OBJECT_CLASS (klass); 128 | 129 | object_class->get_property = nd_sink_list_get_property; 130 | object_class->set_property = nd_sink_list_set_property; 131 | object_class->finalize = nd_sink_list_finalize; 132 | 133 | props[PROP_PROVIDER] = 134 | g_param_spec_object ("provider", "The sink provider", 135 | "The sink provider (usually a MetaProvider) that finds the available sinks.", 136 | ND_TYPE_PROVIDER, 137 | G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); 138 | 139 | g_object_class_install_properties (object_class, PROP_LAST, props); 140 | } 141 | 142 | static void 143 | nd_sink_list_init (NdSinkList *self) 144 | { 145 | } 146 | 147 | /** 148 | * nd_sink_list_get_provider 149 | * @sink_list: a #NdSinkList 150 | * 151 | * Retrieve the sink provider that is used to populate the sink list. 152 | * 153 | * Returns: (transfer none): The sink provider 154 | */ 155 | NdProvider * 156 | nd_sink_list_get_provider (NdSinkList *sink_list) 157 | { 158 | return sink_list->provider; 159 | } 160 | 161 | /** 162 | * nd_sink_list_set_provider 163 | * @sink_list: a #NdSinkList 164 | * 165 | * Retrieve the sink provider that is used to populate the sink list. 166 | */ 167 | void 168 | nd_sink_list_set_provider (NdSinkList *sink_list, 169 | NdProvider *provider) 170 | { 171 | if (sink_list->provider) 172 | { 173 | g_signal_handlers_disconnect_by_data (sink_list->provider, sink_list); 174 | g_clear_object (&sink_list->provider); 175 | } 176 | 177 | if (provider) 178 | { 179 | sink_list->provider = g_object_ref (provider); 180 | 181 | g_signal_connect_object (sink_list->provider, 182 | "sink-added", 183 | (GCallback) sink_added_cb, 184 | sink_list, 185 | G_CONNECT_SWAPPED); 186 | 187 | g_signal_connect_object (sink_list->provider, 188 | "sink-removed", 189 | (GCallback) sink_removed_cb, 190 | sink_list, 191 | G_CONNECT_SWAPPED); 192 | 193 | } 194 | } 195 | 196 | NdSinkList * 197 | nd_sink_list_new (NdProvider *provider) 198 | { 199 | return g_object_new (ND_TYPE_SINK_LIST, 200 | "provider", provider, 201 | NULL); 202 | } 203 | -------------------------------------------------------------------------------- /src/nd-sink-list.h: -------------------------------------------------------------------------------- 1 | /* nd-sink-list.h 2 | * 3 | * Copyright 2018 Benjamin Berg 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include "nd-provider.h" 23 | 24 | G_BEGIN_DECLS 25 | 26 | #define ND_TYPE_SINK_LIST (nd_sink_list_get_type ()) 27 | G_DECLARE_FINAL_TYPE (NdSinkList, nd_sink_list, ND, SINK_LIST, GtkListBox) 28 | 29 | NdSinkList * nd_sink_list_new (NdProvider * provider); 30 | 31 | void nd_sink_list_set_provider (NdSinkList *sink_list, 32 | NdProvider *provider); 33 | NdProvider *nd_sink_list_get_provider (NdSinkList *sink_list); 34 | 35 | G_END_DECLS 36 | -------------------------------------------------------------------------------- /src/nd-sink-row.c: -------------------------------------------------------------------------------- 1 | /* nd-sink-row.c 2 | * 3 | * Copyright 2018 Benjamin Berg 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "gnome-network-displays-config.h" 20 | #include "nd-sink-row.h" 21 | 22 | struct _NdSinkRow 23 | { 24 | GtkListBoxRow parent_instance; 25 | 26 | NdSink *sink; 27 | 28 | /* Template widgets */ 29 | GtkLabel *name_label; 30 | }; 31 | 32 | enum { 33 | PROP_SINK = 1, 34 | PROP_LAST, 35 | }; 36 | 37 | G_DEFINE_TYPE (NdSinkRow, nd_sink_row, GTK_TYPE_LIST_BOX_ROW) 38 | 39 | static GParamSpec * props[PROP_LAST] = { NULL, }; 40 | 41 | 42 | static void 43 | nd_sink_row_sync (NdSinkRow *sink_row) 44 | { 45 | g_autofree gchar *display_name = NULL; 46 | 47 | g_object_get (sink_row->sink, "display-name", &display_name, NULL); 48 | 49 | gtk_label_set_text (sink_row->name_label, display_name); 50 | } 51 | 52 | static void 53 | nd_sink_row_get_property (GObject *object, 54 | guint prop_id, 55 | GValue *value, 56 | GParamSpec *pspec) 57 | { 58 | NdSinkRow *sink_row = ND_SINK_ROW (object); 59 | 60 | switch (prop_id) 61 | { 62 | case PROP_SINK: 63 | g_value_set_object (value, sink_row->sink); 64 | break; 65 | 66 | default: 67 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 68 | break; 69 | } 70 | } 71 | 72 | static void 73 | nd_sink_row_set_property (GObject *object, 74 | guint prop_id, 75 | const GValue *value, 76 | GParamSpec *pspec) 77 | { 78 | NdSinkRow *sink_row = ND_SINK_ROW (object); 79 | 80 | switch (prop_id) 81 | { 82 | case PROP_SINK: 83 | g_assert (sink_row->sink == NULL); 84 | sink_row->sink = g_value_dup_object (value); 85 | 86 | g_assert (sink_row->sink != NULL); 87 | 88 | g_signal_connect_object (sink_row->sink, 89 | "notify", 90 | (GCallback) nd_sink_row_sync, 91 | object, 92 | G_CONNECT_SWAPPED); 93 | nd_sink_row_sync (sink_row); 94 | 95 | break; 96 | 97 | default: 98 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 99 | break; 100 | } 101 | } 102 | 103 | static void 104 | nd_sink_row_finalize (GObject *object) 105 | { 106 | NdSinkRow *sink_row = ND_SINK_ROW (object); 107 | 108 | g_signal_handlers_disconnect_by_data (sink_row->sink, object); 109 | g_clear_object (&sink_row->sink); 110 | 111 | G_OBJECT_CLASS (nd_sink_row_parent_class)->finalize (object); 112 | } 113 | 114 | static void 115 | nd_sink_row_class_init (NdSinkRowClass *klass) 116 | { 117 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); 118 | GObjectClass *object_class = G_OBJECT_CLASS (klass); 119 | 120 | gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/screencast/nd-sink-row.ui"); 121 | gtk_widget_class_bind_template_child (widget_class, NdSinkRow, name_label); 122 | 123 | object_class->get_property = nd_sink_row_get_property; 124 | object_class->set_property = nd_sink_row_set_property; 125 | object_class->finalize = nd_sink_row_finalize; 126 | 127 | props[PROP_SINK] = 128 | g_param_spec_object ("sink", "Sink", 129 | "The sink that the row is representing.", 130 | ND_TYPE_SINK, 131 | G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); 132 | 133 | g_object_class_install_properties (object_class, PROP_LAST, props); 134 | } 135 | 136 | static void 137 | nd_sink_row_init (NdSinkRow *self) 138 | { 139 | gtk_widget_init_template (GTK_WIDGET (self)); 140 | } 141 | 142 | /** 143 | * nd_sink_row_new 144 | * @sink: a #NdSink 145 | * 146 | * Creates a new #NdSinkRow for the given #NdSink. 147 | * 148 | * Returns: 149 | * a newly created #NdSinkRow 150 | */ 151 | NdSinkRow * 152 | nd_sink_row_new (NdSink *sink) 153 | { 154 | return g_object_new (ND_TYPE_SINK_ROW, 155 | "sink", sink, 156 | NULL); 157 | } 158 | 159 | /** 160 | * nd_sink_row_get_sink 161 | * @sink_row: a #NdSinkRow 162 | * 163 | * Retrive the #NdSink for this #NdSinkRow. 164 | * 165 | * Returns: (transfer none): 166 | * the sink for this row 167 | */ 168 | NdSink * 169 | nd_sink_row_get_sink (NdSinkRow *sink_row) 170 | { 171 | return sink_row->sink; 172 | } 173 | -------------------------------------------------------------------------------- /src/nd-sink-row.h: -------------------------------------------------------------------------------- 1 | /* nd-sink-row.h 2 | * 3 | * Copyright 2018 Benjamin Berg 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include "nd-sink.h" 23 | 24 | G_BEGIN_DECLS 25 | 26 | #define ND_TYPE_SINK_ROW (nd_sink_row_get_type ()) 27 | G_DECLARE_FINAL_TYPE (NdSinkRow, nd_sink_row, ND, SINK_ROW, GtkListBoxRow) 28 | 29 | NdSinkRow * nd_sink_row_new (NdSink * sink); 30 | 31 | NdSink * nd_sink_row_get_sink (NdSinkRow *sink_row); 32 | 33 | G_END_DECLS 34 | -------------------------------------------------------------------------------- /src/nd-sink-row.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/nd-sink.c: -------------------------------------------------------------------------------- 1 | /* nd-sink.c 2 | * 3 | * Copyright 2018 Benjamin Berg 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "gnome-network-displays-config.h" 20 | #include "nd-sink.h" 21 | #include 22 | 23 | typedef NdSinkIface NdSinkInterface; 24 | G_DEFINE_INTERFACE (NdSink, nd_sink, G_TYPE_OBJECT); 25 | 26 | static void 27 | nd_sink_default_init (NdSinkIface *iface) 28 | { 29 | iface->start_stream = NULL; 30 | 31 | g_signal_new ("create-source", ND_TYPE_SINK, G_SIGNAL_RUN_LAST, 32 | 0, 33 | g_signal_accumulator_first_wins, NULL, 34 | NULL, 35 | GST_TYPE_ELEMENT, 0); 36 | 37 | g_signal_new ("create-audio-source", ND_TYPE_SINK, G_SIGNAL_RUN_LAST, 38 | 0, 39 | g_signal_accumulator_first_wins, NULL, 40 | NULL, 41 | GST_TYPE_ELEMENT, 0); 42 | 43 | g_object_interface_install_property (iface, 44 | g_param_spec_string ("display-name", 45 | "Display Name", 46 | "The name of the sink to display to the user", 47 | NULL, 48 | G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); 49 | 50 | g_object_interface_install_property (iface, 51 | g_param_spec_boxed ("matches", 52 | "Match strings", 53 | "One or more strings that uniquely identify the sink. This is used for de-duplication and should never change over the lifetime of the object.", 54 | G_TYPE_PTR_ARRAY, 55 | G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); 56 | 57 | g_object_interface_install_property (iface, 58 | g_param_spec_int ("priority", 59 | "Sink Priority", 60 | "The priority of this sink for de-duplication pruposes (a higher priority will be the prefered method).", 61 | G_MININT, 62 | G_MAXINT, 63 | 0, 64 | G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); 65 | 66 | g_object_interface_install_property (iface, 67 | g_param_spec_enum ("state", 68 | "Sink State", 69 | "The current state of the sink.", 70 | ND_TYPE_SINK_STATE, 71 | ND_SINK_STATE_DISCONNECTED, 72 | G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); 73 | 74 | g_object_interface_install_property (iface, 75 | g_param_spec_boxed ("missing-video-codec", 76 | "Missing Video Codec", 77 | "One of the video codecs elements in the list is required.", 78 | G_TYPE_STRV, 79 | G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); 80 | 81 | g_object_interface_install_property (iface, 82 | g_param_spec_boxed ("missing-audio-codec", 83 | "Missing Audio Codec", 84 | "One of the audio codec elements in the list is required.", 85 | G_TYPE_STRV, 86 | G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); 87 | } 88 | 89 | /** 90 | * nd_sink_start_stream 91 | * @sink: the #NdSink 92 | * 93 | * Start streaming to this sink. The returned sink is the one 94 | * that is actually streaming, and may differ from the one this 95 | * is called on (i.e. if this is a MetaSink grouping multiple sinks). 96 | * 97 | * Returns: (transfer full): 98 | * The streaming sink owned by the caller. 99 | */ 100 | NdSink * 101 | nd_sink_start_stream (NdSink *sink) 102 | { 103 | NdSinkIface *iface = ND_SINK_GET_IFACE (sink); 104 | 105 | return iface->start_stream (sink); 106 | } 107 | 108 | /** 109 | * nd_sink_stop_stream 110 | * @sink: the #NdSink 111 | * 112 | * Stop any active streaming or connection attempt to this sink. 113 | */ 114 | void 115 | nd_sink_stop_stream (NdSink *sink) 116 | { 117 | NdSinkIface *iface = ND_SINK_GET_IFACE (sink); 118 | 119 | iface->stop_stream (sink); 120 | } 121 | -------------------------------------------------------------------------------- /src/nd-sink.h: -------------------------------------------------------------------------------- 1 | /* nd-sink.h 2 | * 3 | * Copyright 2018 Benjamin Berg 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include "nd-enum-types.h" 23 | 24 | G_BEGIN_DECLS 25 | 26 | #define ND_TYPE_SINK (nd_sink_get_type ()) 27 | #define ND_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ND_TYPE_SINK, NdSink)) 28 | #define ND_IS_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ND_TYPE_SINK)) 29 | #define ND_SINK_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), ND_TYPE_SINK, NdSinkIface)) 30 | 31 | typedef struct _NdSink NdSink; /* dummy typedef */ 32 | typedef struct _NdSinkIface NdSinkIface; 33 | 34 | typedef enum { 35 | ND_SINK_STATE_DISCONNECTED = 0x0, 36 | ND_SINK_STATE_WAIT_P2P = 0x100, 37 | ND_SINK_STATE_WAIT_SOCKET = 0x110, 38 | ND_SINK_STATE_WAIT_STREAMING = 0x120, 39 | ND_SINK_STATE_STREAMING = 0x1000, 40 | ND_SINK_STATE_ERROR = 0x10000, 41 | } NdSinkState; 42 | 43 | struct _NdSinkIface 44 | { 45 | /*< private >*/ 46 | GTypeInterface g_iface; 47 | 48 | /*< public >*/ 49 | NdSink * (* start_stream) (NdSink *sink); 50 | void (* stop_stream) (NdSink *sink); 51 | }; 52 | 53 | GType nd_sink_get_type (void) G_GNUC_CONST; 54 | 55 | NdSink *nd_sink_start_stream (NdSink *sink); 56 | void nd_sink_stop_stream (NdSink *sink); 57 | 58 | G_DEFINE_AUTOPTR_CLEANUP_FUNC (NdSink, g_object_unref) 59 | 60 | G_END_DECLS 61 | -------------------------------------------------------------------------------- /src/nd-wfd-p2p-provider.c: -------------------------------------------------------------------------------- 1 | /* nd-wfd-p2p-provider.c 2 | * 3 | * Copyright 2018 Benjamin Berg 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "gnome-network-displays-config.h" 20 | #include "nd-wfd-p2p-provider.h" 21 | #include "nd-wfd-p2p-sink.h" 22 | 23 | struct _NdWFDP2PProvider 24 | { 25 | GObject parent_instance; 26 | 27 | GPtrArray *sinks; 28 | 29 | NMClient *nm_client; 30 | NMDevice *nm_device; 31 | 32 | gboolean discover; 33 | guint p2p_find_source_id; 34 | }; 35 | 36 | enum { 37 | PROP_CLIENT = 1, 38 | PROP_DEVICE, 39 | 40 | PROP_DISCOVER, 41 | 42 | PROP_LAST = PROP_DISCOVER, 43 | }; 44 | 45 | static void nd_wfd_p2p_provider_provider_iface_init (NdProviderIface *iface); 46 | static GList * nd_wfd_p2p_provider_provider_get_sinks (NdProvider *provider); 47 | 48 | G_DEFINE_TYPE_EXTENDED (NdWFDP2PProvider, nd_wfd_p2p_provider, G_TYPE_OBJECT, 0, 49 | G_IMPLEMENT_INTERFACE (ND_TYPE_PROVIDER, 50 | nd_wfd_p2p_provider_provider_iface_init); 51 | ) 52 | 53 | static GParamSpec * props[PROP_LAST] = { NULL, }; 54 | 55 | static void 56 | peer_added_cb (NdWFDP2PProvider *provider, NMWifiP2PPeer *peer, NMDevice *device) 57 | { 58 | NdWFDP2PSink *sink = NULL; 59 | GBytes *wfd_ies; 60 | 61 | wfd_ies = nm_wifi_p2p_peer_get_wfd_ies (peer); 62 | 63 | /* Assume this is not a WFD Peer if there are no WFDIEs set. */ 64 | if (!wfd_ies || g_bytes_get_size (wfd_ies) == 0) 65 | { 66 | g_debug ("WFDP2PProvider: Ignoring peer \"%s\" (%s) as it has no WFDIEs set", 67 | nm_wifi_p2p_peer_get_name (peer), 68 | nm_wifi_p2p_peer_get_hw_address (peer)); 69 | return; 70 | } 71 | 72 | g_debug ("WFDP2PProvider: Found a new sink with peer %p on device %p", peer, device); 73 | 74 | sink = nd_wfd_p2p_sink_new (provider->nm_client, provider->nm_device, peer); 75 | 76 | g_ptr_array_add (provider->sinks, sink); 77 | g_signal_emit_by_name (provider, "sink-added", sink); 78 | } 79 | 80 | static void 81 | peer_removed_cb (NdWFDP2PProvider *provider, NMWifiP2PPeer *peer, NMDevice *device) 82 | { 83 | g_debug ("WFDP2PProvider: Peer removed"); 84 | 85 | for (gint i = 0; i < provider->sinks->len; i++) 86 | { 87 | g_autoptr(NdWFDP2PSink) sink = g_object_ref (g_ptr_array_index (provider->sinks, i)); 88 | 89 | if (nd_wfd_p2p_provider_get_device (provider) != device) 90 | continue; 91 | 92 | g_ptr_array_remove_index (provider->sinks, i); 93 | g_signal_emit_by_name (provider, "sink-removed", sink); 94 | break; 95 | } 96 | } 97 | 98 | static void 99 | nd_wfd_p2p_provider_get_property (GObject *object, 100 | guint prop_id, 101 | GValue *value, 102 | GParamSpec *pspec) 103 | { 104 | NdWFDP2PProvider *provider = ND_WFD_P2P_PROVIDER (object); 105 | 106 | switch (prop_id) 107 | { 108 | case PROP_CLIENT: 109 | g_assert (provider->nm_client == NULL); 110 | g_value_set_object (value, provider->nm_client); 111 | break; 112 | 113 | case PROP_DEVICE: 114 | g_assert (provider->nm_device == NULL); 115 | g_value_set_object (value, provider->nm_device); 116 | break; 117 | 118 | case PROP_DISCOVER: 119 | g_value_set_boolean (value, provider->discover); 120 | break; 121 | 122 | default: 123 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 124 | break; 125 | } 126 | } 127 | 128 | static void 129 | log_start_find_error (GObject *source, GAsyncResult *res, gpointer user_data) 130 | { 131 | g_autoptr(GError) error = NULL; 132 | NMDeviceWifiP2P *p2p_dev = NM_DEVICE_WIFI_P2P (source); 133 | 134 | if (!nm_device_wifi_p2p_start_find_finish (p2p_dev, res, &error)) 135 | g_warning ("Could not start P2P find: %s", error->message); 136 | } 137 | 138 | static gboolean 139 | device_restart_find_timeout (gpointer user_data) 140 | { 141 | NdWFDP2PProvider *provider = ND_WFD_P2P_PROVIDER (user_data); 142 | 143 | nm_device_wifi_p2p_start_find (NM_DEVICE_WIFI_P2P (provider->nm_device), NULL, NULL, log_start_find_error, NULL); 144 | 145 | return G_SOURCE_CONTINUE; 146 | } 147 | 148 | static void 149 | nd_wfd_p2p_provider_set_property (GObject *object, 150 | guint prop_id, 151 | const GValue *value, 152 | GParamSpec *pspec) 153 | { 154 | NdWFDP2PProvider *provider = ND_WFD_P2P_PROVIDER (object); 155 | const GPtrArray *peers; 156 | 157 | switch (prop_id) 158 | { 159 | case PROP_CLIENT: 160 | /* Construct only */ 161 | provider->nm_client = g_value_dup_object (value); 162 | break; 163 | 164 | case PROP_DEVICE: 165 | /* Construct only */ 166 | provider->nm_device = g_value_dup_object (value); 167 | 168 | g_signal_connect_object (provider->nm_device, 169 | "peer-added", 170 | (GCallback) peer_added_cb, 171 | provider, 172 | G_CONNECT_SWAPPED); 173 | 174 | g_signal_connect_object (provider->nm_device, 175 | "peer-removed", 176 | (GCallback) peer_removed_cb, 177 | provider, 178 | G_CONNECT_SWAPPED); 179 | 180 | if (provider->discover) 181 | { 182 | nm_device_wifi_p2p_start_find (NM_DEVICE_WIFI_P2P (provider->nm_device), NULL, NULL, log_start_find_error, NULL); 183 | provider->p2p_find_source_id = g_timeout_add_seconds (20, device_restart_find_timeout, provider); 184 | } 185 | 186 | peers = nm_device_wifi_p2p_get_peers (NM_DEVICE_WIFI_P2P (provider->nm_device)); 187 | for (gint i = 0; i < peers->len; i++) 188 | peer_added_cb (provider, g_ptr_array_index (peers, i), provider->nm_device); 189 | 190 | break; 191 | 192 | case PROP_DISCOVER: 193 | provider->discover = g_value_get_boolean (value); 194 | g_debug ("WfdP2PProvider: Discover is now set to %d", provider->discover); 195 | 196 | if (provider->discover) 197 | { 198 | nm_device_wifi_p2p_start_find (NM_DEVICE_WIFI_P2P (provider->nm_device), NULL, NULL, log_start_find_error, NULL); 199 | if (!provider->p2p_find_source_id) 200 | provider->p2p_find_source_id = g_timeout_add_seconds (20, device_restart_find_timeout, provider); 201 | } 202 | else 203 | { 204 | if (provider->p2p_find_source_id) 205 | g_source_remove (provider->p2p_find_source_id); 206 | provider->p2p_find_source_id = 0; 207 | 208 | nm_device_wifi_p2p_stop_find (NM_DEVICE_WIFI_P2P (provider->nm_device), NULL, NULL, NULL); 209 | } 210 | 211 | break; 212 | 213 | default: 214 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 215 | break; 216 | } 217 | } 218 | 219 | static void 220 | nd_wfd_p2p_provider_finalize (GObject *object) 221 | { 222 | NdWFDP2PProvider *provider = ND_WFD_P2P_PROVIDER (object); 223 | 224 | if (provider->p2p_find_source_id) 225 | g_source_remove (provider->p2p_find_source_id); 226 | provider->p2p_find_source_id = 0; 227 | 228 | g_ptr_array_free (provider->sinks, TRUE); 229 | provider->sinks = NULL; 230 | g_clear_object (&provider->nm_client); 231 | g_clear_object (&provider->nm_device); 232 | 233 | G_OBJECT_CLASS (nd_wfd_p2p_provider_parent_class)->finalize (object); 234 | } 235 | 236 | static void 237 | nd_wfd_p2p_provider_class_init (NdWFDP2PProviderClass *klass) 238 | { 239 | GObjectClass *object_class = G_OBJECT_CLASS (klass); 240 | 241 | object_class->get_property = nd_wfd_p2p_provider_get_property; 242 | object_class->set_property = nd_wfd_p2p_provider_set_property; 243 | object_class->finalize = nd_wfd_p2p_provider_finalize; 244 | 245 | props[PROP_CLIENT] = 246 | g_param_spec_object ("client", "Client", 247 | "The NMClient used to find the sink.", 248 | NM_TYPE_CLIENT, 249 | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); 250 | 251 | props[PROP_DEVICE] = 252 | g_param_spec_object ("device", "Device", 253 | "The NMDevice the sink was found on.", 254 | NM_TYPE_DEVICE, 255 | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); 256 | 257 | g_object_class_install_properties (object_class, PROP_LAST, props); 258 | 259 | g_object_class_override_property (object_class, PROP_DISCOVER, "discover"); 260 | } 261 | 262 | static void 263 | nd_wfd_p2p_provider_init (NdWFDP2PProvider *provider) 264 | { 265 | provider->sinks = g_ptr_array_new_with_free_func (g_object_unref); 266 | } 267 | 268 | /****************************************************************** 269 | * NdProvider interface implementation 270 | ******************************************************************/ 271 | 272 | static void 273 | nd_wfd_p2p_provider_provider_iface_init (NdProviderIface *iface) 274 | { 275 | iface->get_sinks = nd_wfd_p2p_provider_provider_get_sinks; 276 | } 277 | 278 | static GList * 279 | nd_wfd_p2p_provider_provider_get_sinks (NdProvider *provider) 280 | { 281 | NdWFDP2PProvider *wfd_p2p_provider = ND_WFD_P2P_PROVIDER (provider); 282 | GList *res = NULL; 283 | 284 | for (gint i = 0; i < wfd_p2p_provider->sinks->len; i++) 285 | res = g_list_prepend (res, g_ptr_array_index (wfd_p2p_provider->sinks, i)); 286 | 287 | return res; 288 | } 289 | 290 | /****************************************************************** 291 | * NdWFDP2PProvider public functions 292 | ******************************************************************/ 293 | 294 | /** 295 | * nd_wfd_p2p_provider_get_client 296 | * @provider: a #NdWFDP2PProvider 297 | * 298 | * Retrieve the #NMClient used to find the device. 299 | * 300 | * Returns: (transfer none): The #NMClient 301 | */ 302 | NMClient * 303 | nd_wfd_p2p_provider_get_client (NdWFDP2PProvider *provider) 304 | { 305 | return provider->nm_client; 306 | } 307 | 308 | /** 309 | * nd_wfd_p2p_provider_get_device 310 | * @provider: a #NdWFDP2PProvider 311 | * 312 | * Retrieve the #NMDevice the provider is for. 313 | * 314 | * Returns: (transfer none): The #NMDevice 315 | */ 316 | NMDevice * 317 | nd_wfd_p2p_provider_get_device (NdWFDP2PProvider *provider) 318 | { 319 | return provider->nm_device; 320 | } 321 | 322 | 323 | NdWFDP2PProvider * 324 | nd_wfd_p2p_provider_new (NMClient *client, NMDevice *device) 325 | { 326 | return g_object_new (ND_TYPE_WFD_P2P_PROVIDER, 327 | "client", client, 328 | "device", device, 329 | NULL); 330 | } 331 | -------------------------------------------------------------------------------- /src/nd-wfd-p2p-provider.h: -------------------------------------------------------------------------------- 1 | /* nd-wfd-p2p-provider.h 2 | * 3 | * Copyright 2018 Benjamin Berg 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include "nd-provider.h" 23 | 24 | G_BEGIN_DECLS 25 | 26 | #define ND_TYPE_WFD_P2P_PROVIDER (nd_wfd_p2p_provider_get_type ()) 27 | G_DECLARE_FINAL_TYPE (NdWFDP2PProvider, nd_wfd_p2p_provider, ND, WFD_P2P_PROVIDER, GObject) 28 | 29 | 30 | NdWFDP2PProvider * nd_wfd_p2p_provider_new (NMClient * client, NMDevice * device); 31 | 32 | NMClient * nd_wfd_p2p_provider_get_client (NdWFDP2PProvider *provider); 33 | NMDevice * nd_wfd_p2p_provider_get_device (NdWFDP2PProvider *provider); 34 | 35 | 36 | G_END_DECLS 37 | -------------------------------------------------------------------------------- /src/nd-wfd-p2p-registry.c: -------------------------------------------------------------------------------- 1 | /* nd-wfd-p2p-registry.c 2 | * 3 | * Copyright 2018 Benjamin Berg 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "gnome-network-displays-config.h" 20 | #include "NetworkManager.h" 21 | #include "nd-wfd-p2p-registry.h" 22 | #include "nd-wfd-p2p-provider.h" 23 | 24 | struct _NdWFDP2PRegistry 25 | { 26 | GObject parent_instance; 27 | 28 | GPtrArray *providers; 29 | NdMetaProvider *meta_provider; 30 | 31 | GCancellable *cancellable; 32 | NMClient *nm_client; 33 | }; 34 | 35 | enum { 36 | PROP_META_PROVIDER = 1, 37 | PROP_LAST, 38 | }; 39 | 40 | G_DEFINE_TYPE (NdWFDP2PRegistry, nd_wfd_p2p_registry, G_TYPE_OBJECT) 41 | 42 | static GParamSpec * props[PROP_LAST] = { NULL, }; 43 | 44 | static void 45 | device_added_cb (NdWFDP2PRegistry *registry, NMDevice *device, NMClient *client) 46 | { 47 | g_autoptr(NdWFDP2PProvider) provider = NULL; 48 | 49 | if (!NM_IS_DEVICE_WIFI_P2P (device)) 50 | return; 51 | 52 | g_debug ("WFDP2PRegistry: Found a new device, creating provider"); 53 | 54 | provider = nd_wfd_p2p_provider_new (client, device); 55 | 56 | g_ptr_array_add (registry->providers, g_object_ref (provider)); 57 | nd_meta_provider_add_provider (registry->meta_provider, 58 | ND_PROVIDER (g_steal_pointer (&provider))); 59 | } 60 | 61 | static void 62 | device_removed_cb (NdWFDP2PRegistry *registry, NMDevice *device, NMClient *client) 63 | { 64 | if (!NM_IS_DEVICE_WIFI_P2P (device)) 65 | return; 66 | 67 | g_debug ("WFDP2PRegistry: Lost a device, removing provider"); 68 | 69 | for (gint i = 0; i < registry->providers->len; i++) 70 | { 71 | NdWFDP2PProvider *provider = g_ptr_array_index (registry->providers, i); 72 | 73 | if (nd_wfd_p2p_provider_get_device (provider) != device) 74 | continue; 75 | 76 | nd_meta_provider_remove_provider (registry->meta_provider, ND_PROVIDER (provider)); 77 | g_ptr_array_remove_index (registry->providers, i); 78 | break; 79 | } 80 | } 81 | 82 | static void 83 | nd_wfd_p2p_registry_get_property (GObject *object, 84 | guint prop_id, 85 | GValue *value, 86 | GParamSpec *pspec) 87 | { 88 | NdWFDP2PRegistry *registry = ND_WFD_P2P_REGISTRY (object); 89 | 90 | switch (prop_id) 91 | { 92 | case PROP_META_PROVIDER: 93 | g_value_set_object (value, registry->meta_provider); 94 | break; 95 | 96 | default: 97 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 98 | break; 99 | } 100 | } 101 | 102 | static void 103 | nd_wfd_p2p_registry_set_property (GObject *object, 104 | guint prop_id, 105 | const GValue *value, 106 | GParamSpec *pspec) 107 | { 108 | NdWFDP2PRegistry *registry = ND_WFD_P2P_REGISTRY (object); 109 | 110 | switch (prop_id) 111 | { 112 | case PROP_META_PROVIDER: 113 | g_assert (registry->meta_provider == NULL); 114 | 115 | registry->meta_provider = g_value_dup_object (value); 116 | 117 | break; 118 | 119 | default: 120 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 121 | break; 122 | } 123 | } 124 | 125 | static void 126 | client_init_async_finished (GObject *source, GAsyncResult *res, gpointer user_data) 127 | { 128 | NdWFDP2PRegistry *registry = NULL; 129 | 130 | g_autoptr(GError) error = NULL; 131 | 132 | if (!g_async_initable_init_finish (G_ASYNC_INITABLE (source), res, &error)) 133 | { 134 | /* Operation was aborted */ 135 | if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) 136 | return; 137 | 138 | registry = ND_WFD_P2P_REGISTRY (user_data); 139 | g_clear_object (®istry->nm_client); 140 | g_warning ("Error initialising NMClient: %s", error->message); 141 | } 142 | 143 | g_debug ("WFDP2PRegistry: Got NMClient"); 144 | 145 | registry = ND_WFD_P2P_REGISTRY (user_data); 146 | 147 | /* Everything good, we already connected and possibly received 148 | * the device-added/device-removed signals. */ 149 | } 150 | 151 | static void 152 | nd_wfd_p2p_registry_constructed (GObject *object) 153 | { 154 | NdWFDP2PRegistry *registry = ND_WFD_P2P_REGISTRY (object); 155 | 156 | registry->cancellable = g_cancellable_new (); 157 | registry->nm_client = g_object_new (NM_TYPE_CLIENT, NULL); 158 | 159 | g_signal_connect_object (registry->nm_client, 160 | "device-added", 161 | (GCallback) device_added_cb, 162 | registry, 163 | G_CONNECT_SWAPPED); 164 | 165 | g_signal_connect_object (registry->nm_client, 166 | "device-removed", 167 | (GCallback) device_removed_cb, 168 | registry, 169 | G_CONNECT_SWAPPED); 170 | 171 | g_async_initable_init_async (G_ASYNC_INITABLE (registry->nm_client), 172 | G_PRIORITY_LOW, 173 | registry->cancellable, 174 | client_init_async_finished, 175 | registry); 176 | } 177 | 178 | static void 179 | nd_wfd_p2p_registry_finalize (GObject *object) 180 | { 181 | NdWFDP2PRegistry *registry = ND_WFD_P2P_REGISTRY (object); 182 | 183 | while (registry->providers->len) 184 | { 185 | nd_meta_provider_remove_provider (registry->meta_provider, 186 | ND_PROVIDER (g_ptr_array_index ( registry->providers, 0))); 187 | g_ptr_array_remove_index_fast (registry->providers, 0); 188 | } 189 | 190 | g_cancellable_cancel (registry->cancellable); 191 | g_clear_object (®istry->cancellable); 192 | g_clear_object (®istry->meta_provider); 193 | g_clear_pointer (®istry->providers, g_ptr_array_unref); 194 | 195 | G_OBJECT_CLASS (nd_wfd_p2p_registry_parent_class)->finalize (object); 196 | } 197 | 198 | static void 199 | nd_wfd_p2p_registry_class_init (NdWFDP2PRegistryClass *klass) 200 | { 201 | GObjectClass *object_class = G_OBJECT_CLASS (klass); 202 | 203 | object_class->get_property = nd_wfd_p2p_registry_get_property; 204 | object_class->set_property = nd_wfd_p2p_registry_set_property; 205 | object_class->constructed = nd_wfd_p2p_registry_constructed; 206 | object_class->finalize = nd_wfd_p2p_registry_finalize; 207 | 208 | props[PROP_META_PROVIDER] = 209 | g_param_spec_object ("meta-provider", "MetaProvider", 210 | "The meta provider to add found providers to.", 211 | ND_TYPE_META_PROVIDER, 212 | G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); 213 | 214 | g_object_class_install_properties (object_class, PROP_LAST, props); 215 | } 216 | 217 | static void 218 | nd_wfd_p2p_registry_init (NdWFDP2PRegistry *registry) 219 | { 220 | registry->providers = g_ptr_array_new_with_free_func (g_object_unref); 221 | } 222 | 223 | NdWFDP2PRegistry * 224 | nd_wfd_p2p_registry_new (NdMetaProvider *meta_provider) 225 | { 226 | return g_object_new (ND_TYPE_WFD_P2P_REGISTRY, 227 | "meta-provider", meta_provider, 228 | NULL); 229 | } 230 | -------------------------------------------------------------------------------- /src/nd-wfd-p2p-registry.h: -------------------------------------------------------------------------------- 1 | /* nd-wfd-p2p-registry.h 2 | * 3 | * Copyright 2018 Benjamin Berg 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "nd-meta-provider.h" 22 | 23 | G_BEGIN_DECLS 24 | 25 | #define ND_TYPE_WFD_P2P_REGISTRY (nd_wfd_p2p_registry_get_type ()) 26 | G_DECLARE_FINAL_TYPE (NdWFDP2PRegistry, nd_wfd_p2p_registry, ND, WFD_P2P_REGISTRY, GObject) 27 | 28 | NdWFDP2PRegistry * nd_wfd_p2p_registry_new (NdMetaProvider * meta_provider); 29 | 30 | G_END_DECLS 31 | -------------------------------------------------------------------------------- /src/nd-wfd-p2p-sink.h: -------------------------------------------------------------------------------- 1 | /* nd-wfd-p2p-sink.h 2 | * 3 | * Copyright 2018 Benjamin Berg 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | #include "nd-sink.h" 24 | 25 | G_BEGIN_DECLS 26 | 27 | #define ND_TYPE_WFD_P2P_SINK (nd_wfd_p2p_sink_get_type ()) 28 | G_DECLARE_FINAL_TYPE (NdWFDP2PSink, nd_wfd_p2p_sink, ND, WFD_P2P_SINK, GObject) 29 | 30 | NdWFDP2PSink * nd_wfd_p2p_sink_new (NMClient * client, 31 | NMDevice * device, 32 | NMWifiP2PPeer * peer); 33 | 34 | NMClient * nd_wfd_p2p_sink_get_client (NdWFDP2PSink * sink); 35 | NMDevice * nd_wfd_p2p_sink_get_device (NdWFDP2PSink * sink); 36 | NMWifiP2PPeer * nd_wfd_p2p_sink_get_peer (NdWFDP2PSink * sink); 37 | 38 | 39 | G_END_DECLS 40 | -------------------------------------------------------------------------------- /src/nd-window.h: -------------------------------------------------------------------------------- 1 | /* nd-window.h 2 | * 3 | * Copyright 2018 Benjamin Berg 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | 23 | G_BEGIN_DECLS 24 | 25 | #define ND_TYPE_WINDOW (gnome_nd_window_get_type ()) 26 | G_DECLARE_FINAL_TYPE (NdWindow, gnome_nd_window, ND, WINDOW, GtkApplicationWindow) 27 | 28 | G_END_DECLS 29 | -------------------------------------------------------------------------------- /src/wfd/meson.build: -------------------------------------------------------------------------------- 1 | 2 | wfd_server_sources = [ 3 | 'wfd-client.c', 4 | 'wfd-media.c', 5 | 'wfd-media-factory.c', 6 | 'wfd-params.c', 7 | 'wfd-resolution.c', 8 | 'wfd-server.c', 9 | 'wfd-session-pool.c', 10 | 'wfd-audio-codec.c', 11 | 'wfd-video-codec.c', 12 | ] 13 | 14 | #enum_headers = files() 15 | # 16 | #gnome_nd_sources += gnome.mkenums_simple( 17 | # 'nd-enum-types', 18 | # sources: enum_headers, 19 | #) 20 | 21 | wfd_server_deps = [ 22 | dependency('gstreamer-video-1.0', version: '>= 1.14'), 23 | dependency('gstreamer-rtsp-1.0', version: '>= 1.14'), 24 | dependency('gstreamer-rtsp-server-1.0', version: '>= 1.14'), 25 | ] 26 | 27 | wfd_server = static_library( 28 | 'wfd-server', 29 | wfd_server_sources, 30 | dependencies: wfd_server_deps, 31 | ) -------------------------------------------------------------------------------- /src/wfd/wfd-audio-codec.c: -------------------------------------------------------------------------------- 1 | #include "wfd-audio-codec.h" 2 | 3 | G_DEFINE_BOXED_TYPE (WfdAudioCodec, wfd_audio_codec, wfd_audio_codec_ref, wfd_audio_codec_unref) 4 | 5 | /** 6 | * wfd_audio_codec_new: 7 | * 8 | * Creates a new #WfdAudioCodec. 9 | * 10 | * Returns: (transfer full): A newly created #WfdAudioCodec 11 | */ 12 | WfdAudioCodec * 13 | wfd_audio_codec_new (void) 14 | { 15 | WfdAudioCodec *self; 16 | 17 | self = g_slice_new0 (WfdAudioCodec); 18 | self->ref_count = 1; 19 | 20 | return self; 21 | } 22 | 23 | /** 24 | * wfd_audio_codec_copy: 25 | * @self: a #WfdAudioCodec 26 | * 27 | * Makes a deep copy of a #WfdAudioCodec. 28 | * 29 | * Returns: (transfer full): A newly created #WfdAudioCodec with the same 30 | * contents as @self 31 | */ 32 | WfdAudioCodec * 33 | wfd_audio_codec_copy (WfdAudioCodec *self) 34 | { 35 | WfdAudioCodec *copy; 36 | 37 | g_return_val_if_fail (self, NULL); 38 | g_return_val_if_fail (self->ref_count, NULL); 39 | 40 | copy = wfd_audio_codec_new (); 41 | 42 | return copy; 43 | } 44 | 45 | static void 46 | wfd_audio_codec_free (WfdAudioCodec *self) 47 | { 48 | g_assert (self); 49 | g_assert_cmpint (self->ref_count, ==, 0); 50 | 51 | g_slice_free (WfdAudioCodec, self); 52 | } 53 | 54 | /** 55 | * wfd_audio_codec_ref: 56 | * @self: A #WfdAudioCodec 57 | * 58 | * Increments the reference count of @self by one. 59 | * 60 | * Returns: (transfer none): @self 61 | */ 62 | WfdAudioCodec * 63 | wfd_audio_codec_ref (WfdAudioCodec *self) 64 | { 65 | g_return_val_if_fail (self, NULL); 66 | g_return_val_if_fail (self->ref_count, NULL); 67 | 68 | g_atomic_int_inc (&self->ref_count); 69 | 70 | return self; 71 | } 72 | 73 | /** 74 | * wfd_audio_codec_unref: 75 | * @self: (transfer none): A #WfdAudioCodec 76 | * 77 | * Decrements the reference count of @self by one, freeing the structure when 78 | * the reference count reaches zero. 79 | */ 80 | void 81 | wfd_audio_codec_unref (WfdAudioCodec *self) 82 | { 83 | g_return_if_fail (self); 84 | g_return_if_fail (self->ref_count); 85 | 86 | if (g_atomic_int_dec_and_test (&self->ref_count)) 87 | wfd_audio_codec_free (self); 88 | } 89 | 90 | WfdAudioCodec * 91 | wfd_audio_codec_new_from_desc (const gchar *descr) 92 | { 93 | g_autoptr(WfdAudioCodec) res = NULL; 94 | g_auto(GStrv) tokens = NULL; 95 | 96 | tokens = g_strsplit (descr, " ", 3); 97 | 98 | if (g_strv_length (tokens) < 3) 99 | return NULL; 100 | 101 | res = wfd_audio_codec_new (); 102 | 103 | if (g_str_equal (tokens[0], "LPCM")) 104 | res->type = WFD_AUDIO_LPCM; 105 | else if (g_str_equal (tokens[0], "AAC")) 106 | res->type = WFD_AUDIO_AAC; 107 | else if (g_str_equal (tokens[0], "AC3")) 108 | res->type = WFD_AUDIO_AC3; 109 | else 110 | return NULL; 111 | 112 | res->modes = g_ascii_strtoll (tokens[1], NULL, 16); 113 | 114 | res->latency_ms = g_ascii_strtoull (tokens[2], NULL, 16) * 5; 115 | 116 | return g_steal_pointer (&res); 117 | } 118 | 119 | gchar * 120 | wfd_audio_get_descriptor (WfdAudioCodec *self) 121 | { 122 | const gchar *type; 123 | 124 | if (self == NULL) 125 | return g_strdup ("none"); 126 | 127 | switch (self->type) 128 | { 129 | case WFD_AUDIO_LPCM: 130 | type = "LPCM"; 131 | break; 132 | 133 | case WFD_AUDIO_AAC: 134 | type = "AAC"; 135 | break; 136 | 137 | case WFD_AUDIO_AC3: 138 | type = "AC3"; 139 | break; 140 | 141 | default: 142 | g_assert_not_reached (); 143 | return g_strdup ("none"); 144 | } 145 | 146 | return g_strdup_printf ("%s %08X %02X", type, self->modes, 0); 147 | } 148 | 149 | 150 | void 151 | wfd_audio_codec_dump (WfdAudioCodec *self) 152 | { 153 | const gchar *type = "invalid"; 154 | 155 | switch (self->type) 156 | { 157 | case WFD_AUDIO_LPCM: 158 | type = "LPCM"; 159 | break; 160 | 161 | case WFD_AUDIO_AAC: 162 | type = "AAC"; 163 | break; 164 | 165 | case WFD_AUDIO_AC3: 166 | type = "AC3"; 167 | break; 168 | 169 | default: 170 | g_assert_not_reached (); 171 | } 172 | 173 | g_debug ("WfdAudioCodec: %s, %" G_GUINT32_FORMAT ", latency: %d", type, self->modes, self->latency_ms); 174 | } 175 | -------------------------------------------------------------------------------- /src/wfd/wfd-audio-codec.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | G_BEGIN_DECLS 6 | 7 | #define WFD_TYPE_AUDIO_CODEC (wfd_audio_codec_get_type ()) 8 | 9 | typedef enum { 10 | WFD_AUDIO_LPCM, 11 | WFD_AUDIO_AAC, 12 | WFD_AUDIO_AC3, 13 | } WfdAudioCodecType; 14 | 15 | typedef struct _WfdAudioCodec WfdAudioCodec; 16 | 17 | struct _WfdAudioCodec 18 | { 19 | WfdAudioCodecType type; 20 | 21 | guint32 modes; 22 | guint latency_ms; 23 | 24 | /*< private >*/ 25 | guint ref_count; 26 | }; 27 | 28 | GType wfd_audio_codec_get_type (void) G_GNUC_CONST; 29 | WfdAudioCodec *wfd_audio_codec_new (void); 30 | WfdAudioCodec *wfd_audio_codec_copy (WfdAudioCodec *self); 31 | WfdAudioCodec *wfd_audio_codec_ref (WfdAudioCodec *self); 32 | void wfd_audio_codec_unref (WfdAudioCodec *self); 33 | 34 | WfdAudioCodec *wfd_audio_codec_new_from_desc (const gchar *descr); 35 | gchar *wfd_audio_get_descriptor (WfdAudioCodec *self); 36 | void wfd_audio_codec_dump (WfdAudioCodec *self); 37 | 38 | 39 | G_DEFINE_AUTOPTR_CLEANUP_FUNC (WfdAudioCodec, wfd_audio_codec_unref) 40 | 41 | G_END_DECLS 42 | -------------------------------------------------------------------------------- /src/wfd/wfd-client.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #pragma GCC diagnostic push 4 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 5 | #include 6 | #pragma GCC diagnostic pop 7 | 8 | G_BEGIN_DECLS 9 | 10 | #define WFD_TYPE_CLIENT (wfd_client_get_type ()) 11 | #define WFD_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), WFD_TYPE_CLIENT, WfdClientClass)) 12 | 13 | G_DECLARE_FINAL_TYPE (WfdClient, wfd_client, WFD, CLIENT, GstRTSPClient) 14 | 15 | WfdClient * wfd_client_new (void); 16 | void wfd_client_query_support (WfdClient *self); 17 | void wfd_client_trigger_method (WfdClient *self, 18 | const gchar *method); 19 | 20 | 21 | G_END_DECLS 22 | -------------------------------------------------------------------------------- /src/wfd/wfd-media-factory.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #pragma GCC diagnostic push 4 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 5 | #include 6 | #pragma GCC diagnostic pop 7 | #include "wfd-params.h" 8 | 9 | G_BEGIN_DECLS 10 | 11 | #define WFD_TYPE_MEDIA_FACTORY (wfd_media_factory_get_type ()) 12 | G_DECLARE_FINAL_TYPE (WfdMediaFactory, wfd_media_factory, WFD, MEDIA_FACTORY, GstRTSPMediaFactory) 13 | 14 | typedef enum { 15 | WFD_QUIRK_NO_IDR = 0x01, 16 | } WfdMediaQuirks; 17 | 18 | WfdMediaFactory * wfd_media_factory_new (void); 19 | 20 | gboolean wfd_get_missing_codecs (GStrv *video, 21 | GStrv *audio); 22 | 23 | /* Just because it is convenient to have next to the pipeline creation code */ 24 | WfdMediaQuirks wfd_configure_media_element (GstBin *bin, 25 | WfdParams *params); 26 | 27 | G_END_DECLS 28 | -------------------------------------------------------------------------------- /src/wfd/wfd-media.c: -------------------------------------------------------------------------------- 1 | #pragma GCC diagnostic push 2 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 3 | #include "gst/rtsp-server/rtsp-media.h" 4 | #pragma GCC diagnostic pop 5 | #include "wfd-media.h" 6 | 7 | struct _WfdMedia 8 | { 9 | GstRTSPMedia parent_instance; 10 | }; 11 | 12 | G_DEFINE_TYPE (WfdMedia, wfd_media, GST_TYPE_RTSP_MEDIA) 13 | 14 | WfdMedia * 15 | wfd_media_new (void) 16 | { 17 | return g_object_new (WFD_TYPE_MEDIA, NULL); 18 | } 19 | 20 | static void 21 | wfd_media_finalize (GObject *object) 22 | { 23 | g_debug ("WfdMedia: Finalize"); 24 | 25 | G_OBJECT_CLASS (wfd_media_parent_class)->finalize (object); 26 | } 27 | 28 | static gboolean 29 | wfd_media_setup_rtpbin (GstRTSPMedia *media, GstElement *rtpbin) 30 | { 31 | g_object_set (rtpbin, 32 | "rtp-profile", 1, /* avp */ 33 | "do-retransmission", TRUE, 34 | "ntp-time-source", 3, /* clock time */ 35 | "max-misorder-time", 50, 36 | "buffer-mode", 0, 37 | "latency", 40, 38 | NULL); 39 | 40 | return TRUE; 41 | } 42 | 43 | static void 44 | wfd_media_class_init (WfdMediaClass *klass) 45 | { 46 | GObjectClass *object_class = G_OBJECT_CLASS (klass); 47 | GstRTSPMediaClass *media_class = GST_RTSP_MEDIA_CLASS (klass); 48 | 49 | object_class->finalize = wfd_media_finalize; 50 | 51 | media_class->setup_rtpbin = wfd_media_setup_rtpbin; 52 | } 53 | 54 | static void 55 | wfd_media_init (WfdMedia *self) 56 | { 57 | gst_rtsp_media_set_stop_on_disconnect (GST_RTSP_MEDIA (self), TRUE); 58 | } 59 | -------------------------------------------------------------------------------- /src/wfd/wfd-media.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | G_BEGIN_DECLS 6 | 7 | #define WFD_TYPE_MEDIA (wfd_media_get_type ()) 8 | 9 | G_DECLARE_FINAL_TYPE (WfdMedia, wfd_media, WFD, MEDIA, GstRTSPMedia) 10 | 11 | WfdMedia * wfd_media_new (void); 12 | 13 | G_END_DECLS 14 | -------------------------------------------------------------------------------- /src/wfd/wfd-params.c: -------------------------------------------------------------------------------- 1 | #include "wfd-params.h" 2 | 3 | G_DEFINE_BOXED_TYPE (WfdParams, wfd_params, wfd_params_copy, wfd_params_free) 4 | 5 | static const gchar * params_m3_mandatory[] = { 6 | "wfd_client_rtp_ports", 7 | "wfd_audio_codecs", 8 | "wfd_video_formats", 9 | }; 10 | 11 | static const gchar * params_m3_optional[] = { 12 | "wfd_3d_video_formats", 13 | "wfd_display_edid", 14 | "wfd_coupled_sink", 15 | "wfd_I2C", 16 | "wfd_standby_resume_capability", 17 | "wfd_connector_type", 18 | "wfd_uibc_capability", 19 | "wfd2_audio_codecs", 20 | "wfd2_video_codecs", 21 | "wfd2_aux_stream_formats", 22 | "wfd2_buffer_length", 23 | "wfd2_audio_playback_status", 24 | "wfd2_video_playback_status", 25 | "wfd2_cta_datablock_collection", 26 | }; 27 | 28 | static const gchar *params_m3_query_extra[] = { 29 | "wfd_display_edid", 30 | "wfd_idr_request_capability", 31 | "microsoft_cursor", 32 | }; 33 | 34 | /** 35 | * wfd_params_new: 36 | * 37 | * Creates a new #WfdParams. 38 | * 39 | * Returns: (transfer full): A newly created #WfdParams 40 | */ 41 | WfdParams * 42 | wfd_params_new (void) 43 | { 44 | WfdParams *self; 45 | WfdVideoCodec *basic_codec; 46 | 47 | g_autoptr(GList) resolutions = NULL; 48 | 49 | self = g_slice_new0 (WfdParams); 50 | 51 | self->primary_rtp_port = 16384; 52 | 53 | /* Add a default video codec. This will be removed when we get something 54 | * proper from the client. 55 | * Doing this is mainly useful for testing with normal RTSP clients. */ 56 | self->video_codecs = g_ptr_array_new_with_free_func ((GDestroyNotify) wfd_video_codec_unref); 57 | basic_codec = wfd_video_codec_new_from_desc (7 << 3, "01 01 00000081 00000000 00000000 00 0000 0000 00 none none"); 58 | g_ptr_array_add (self->video_codecs, basic_codec); 59 | 60 | self->audio_codecs = g_ptr_array_new_with_free_func ((GDestroyNotify) wfd_audio_codec_unref); 61 | 62 | /* Set a default resolution (for testing purposes) */ 63 | self->selected_codec = wfd_video_codec_ref (basic_codec); 64 | self->selected_resolution = wfd_resolution_copy (basic_codec->native); 65 | 66 | return self; 67 | } 68 | 69 | /** 70 | * wfd_params_copy: 71 | * @self: a #WfdParams 72 | * 73 | * Makes a deep copy of a #WfdParams. 74 | * 75 | * Returns: (transfer full): A newly created #WfdParams with the same 76 | * contents as @self 77 | */ 78 | WfdParams * 79 | wfd_params_copy (WfdParams *self) 80 | { 81 | WfdParams *copy; 82 | 83 | g_return_val_if_fail (self, NULL); 84 | 85 | copy = wfd_params_new (); 86 | 87 | copy->primary_rtp_port = self->primary_rtp_port; 88 | copy->secondary_rtp_port = self->secondary_rtp_port; 89 | if (self->edid) 90 | { 91 | copy->edid = g_byte_array_new (); 92 | g_byte_array_append (copy->edid, self->edid->data, self->edid->len); 93 | } 94 | 95 | /* Remove the default codec and all of the ones from the original. */ 96 | g_ptr_array_remove (copy->video_codecs, 0); 97 | for (guint i = 0; i < self->video_codecs->len; i++) 98 | { 99 | WfdVideoCodec *codec = (WfdVideoCodec *) g_ptr_array_index (self->video_codecs, i); 100 | WfdVideoCodec *new_codec = wfd_video_codec_copy (codec); 101 | 102 | g_ptr_array_add (copy->video_codecs, new_codec); 103 | } 104 | 105 | for (guint i = 0; i < self->audio_codecs->len; i++) 106 | { 107 | WfdAudioCodec *codec = (WfdAudioCodec *) g_ptr_array_index (self->audio_codecs, i); 108 | WfdAudioCodec *new_codec = wfd_audio_codec_copy (codec); 109 | 110 | g_ptr_array_add (copy->audio_codecs, new_codec); 111 | } 112 | 113 | if (self->selected_codec) 114 | copy->selected_codec = wfd_video_codec_copy (self->selected_codec); 115 | if (self->selected_resolution) 116 | copy->selected_resolution = wfd_resolution_copy (self->selected_resolution); 117 | if (self->selected_audio_codec) 118 | copy->selected_audio_codec = wfd_audio_codec_copy (self->selected_audio_codec); 119 | 120 | return copy; 121 | } 122 | 123 | /** 124 | * wfd_params_free: 125 | * @self: a #WfdParams 126 | * 127 | * Frees a #WfdParams allocated using wfd_params_new() 128 | * or wfd_params_copy(). 129 | */ 130 | void 131 | wfd_params_free (WfdParams *self) 132 | { 133 | g_return_if_fail (self); 134 | 135 | g_clear_pointer (&self->selected_codec, wfd_video_codec_unref); 136 | g_clear_pointer (&self->selected_resolution, wfd_resolution_free); 137 | g_clear_pointer (&self->selected_audio_codec, wfd_audio_codec_unref); 138 | 139 | g_clear_pointer (&self->video_codecs, g_ptr_array_unref); 140 | g_clear_pointer (&self->audio_codecs, g_ptr_array_unref); 141 | g_clear_pointer (&self->edid, g_byte_array_unref); 142 | g_clear_pointer (&self->profile, g_free); 143 | 144 | g_slice_free (WfdParams, self); 145 | } 146 | 147 | gchar * 148 | wfd_params_m3_query_params (WfdParams *self) 149 | { 150 | g_autoptr(GPtrArray) query_params = NULL; 151 | 152 | query_params = g_ptr_array_sized_new (G_N_ELEMENTS (params_m3_mandatory) + G_N_ELEMENTS (params_m3_query_extra) + 1); 153 | 154 | for (gint i = 0; i < G_N_ELEMENTS (params_m3_mandatory); i++) 155 | g_ptr_array_add (query_params, (gpointer) params_m3_mandatory[i]); 156 | 157 | for (gint i = 0; i < G_N_ELEMENTS (params_m3_query_extra); i++) 158 | g_ptr_array_add (query_params, (gpointer) params_m3_query_extra[i]); 159 | 160 | /* For a newline at the end. */ 161 | g_ptr_array_add (query_params, (gpointer) ""); 162 | g_ptr_array_add (query_params, NULL); 163 | 164 | return g_strjoinv ("\r\n", (GStrv) query_params->pdata); 165 | } 166 | 167 | void 168 | wfd_params_from_sink (WfdParams *self, const guint8 *body, gsize body_size) 169 | { 170 | g_auto(GStrv) lines = NULL; 171 | gchar **line; 172 | g_autofree gchar *body_str = NULL; 173 | 174 | /* Empty body is probably testing, just keep the current values. */ 175 | if (body == NULL) 176 | return; 177 | 178 | body_str = g_strndup ((gchar *) body, body_size); 179 | lines = g_strsplit (body_str, "\n", 0); 180 | 181 | for (line = lines; *line; line++) 182 | { 183 | g_auto(GStrv) split_line = NULL; 184 | gchar *option; 185 | gchar *value; 186 | 187 | g_strstrip (*line); 188 | 189 | /* Ignore empty lines */ 190 | if (**line == '\0') 191 | continue; 192 | 193 | split_line = g_strsplit (*line, ":", 2); 194 | if (g_strv_length (split_line) != 2) 195 | continue; 196 | 197 | option = g_strstrip (split_line[0]); 198 | value = g_strstrip (split_line[1]); 199 | 200 | /* Now, handle the different options (that we support) */ 201 | if (g_str_equal (option, "wfd_client_rtp_ports")) 202 | { 203 | g_auto(GStrv) split_value = NULL; 204 | split_value = g_strsplit (value, " ", 0); 205 | if (g_strv_length (split_value) != 4) 206 | { 207 | g_warning ("WfdParams: Ivalid value wfd_client_rtp_ports: %s", value); 208 | continue; 209 | } 210 | 211 | if (!g_str_equal (split_value[0], "RTP/AVP/UDP;unicast")) 212 | { 213 | g_warning ("WfdParams: Ivalid profile: %s", split_value[0]); 214 | continue; 215 | } 216 | 217 | if (!g_str_equal (split_value[3], "mode=play")) 218 | { 219 | g_warning ("WfdParams: Ivalid mode: %s", split_value[3]); 220 | continue; 221 | } 222 | 223 | g_clear_pointer (&self->profile, g_free); 224 | self->profile = g_strdup (split_value[0]); 225 | 226 | self->primary_rtp_port = g_ascii_strtoll (split_value[1], NULL, 10); 227 | self->secondary_rtp_port = g_ascii_strtoll (split_value[2], NULL, 10); 228 | } 229 | else if (g_str_equal (option, "wfd_video_formats")) 230 | { 231 | g_auto(GStrv) split_value = NULL; 232 | g_auto(GStrv) codec_descriptors = NULL; 233 | char **codec_descriptor; 234 | guint16 native; 235 | 236 | /* Clear video codecs to fill them up again. */ 237 | g_clear_pointer (&self->selected_codec, wfd_video_codec_unref); 238 | g_clear_pointer (&self->selected_resolution, wfd_resolution_free); 239 | g_ptr_array_unref (self->video_codecs); 240 | self->video_codecs = g_ptr_array_new_with_free_func ((GDestroyNotify) wfd_video_codec_unref); 241 | 242 | if (g_str_equal (value, "none")) 243 | continue; 244 | 245 | split_value = g_strsplit (value, " ", 3); 246 | 247 | if (g_strv_length (split_value) != 3) 248 | { 249 | g_warning ("WfdParams: wfd_video_formats is invalid: %s", value); 250 | continue; 251 | } 252 | 253 | native = g_ascii_strtoll (split_value[0], NULL, 16); 254 | /* split_value[1] is the perfered display mode (WFD 1.0 specific), we just ignore it */ 255 | 256 | codec_descriptors = g_strsplit (split_value[2], ",", 0); 257 | for (codec_descriptor = codec_descriptors; *codec_descriptor; codec_descriptor++) 258 | { 259 | g_autoptr(WfdVideoCodec) codec = NULL; 260 | 261 | g_strstrip (*codec_descriptor); 262 | codec = wfd_video_codec_new_from_desc (native, *codec_descriptor); 263 | if (codec) 264 | { 265 | g_debug ("Add codec to params:"); 266 | wfd_video_codec_dump (codec); 267 | g_ptr_array_add (self->video_codecs, g_steal_pointer (&codec)); 268 | } 269 | else 270 | { 271 | g_warning ("WfdParams: Could not parse codec descriptor: %s", *codec_descriptor); 272 | } 273 | } 274 | } 275 | else if (g_str_equal (option, "wfd_audio_codecs")) 276 | { 277 | g_auto(GStrv) codec_descriptors = NULL; 278 | char **codec_descriptor; 279 | 280 | /* Clear audio codecs to fill them up again. */ 281 | g_clear_pointer (&self->selected_audio_codec, wfd_audio_codec_unref); 282 | g_ptr_array_unref (self->audio_codecs); 283 | self->audio_codecs = g_ptr_array_new_with_free_func ((GDestroyNotify) wfd_audio_codec_unref); 284 | 285 | if (g_str_equal (value, "none")) 286 | continue; 287 | 288 | codec_descriptors = g_strsplit (value, ",", 0); 289 | for (codec_descriptor = codec_descriptors; *codec_descriptor; codec_descriptor++) 290 | { 291 | g_autoptr(WfdAudioCodec) codec = NULL; 292 | 293 | g_strstrip (*codec_descriptor); 294 | codec = wfd_audio_codec_new_from_desc (*codec_descriptor); 295 | if (codec) 296 | { 297 | g_debug ("Add audio codec to params:"); 298 | wfd_audio_codec_dump (codec); 299 | g_ptr_array_add (self->audio_codecs, g_steal_pointer (&codec)); 300 | } 301 | else 302 | { 303 | g_warning ("WfdParams: Could not parse codec descriptor: %s", *codec_descriptor); 304 | } 305 | } 306 | } 307 | else if (g_str_equal (option, "wfd_display_edid")) 308 | { 309 | guint length; 310 | g_auto(GStrv) split_value = NULL; 311 | 312 | g_clear_pointer (&self->edid, g_byte_array_unref); 313 | 314 | if (g_str_equal (value, "none")) 315 | continue; 316 | 317 | split_value = g_strsplit (value, " ", 2); 318 | if (g_strv_length (split_value) != 2) 319 | { 320 | g_warning ("WfdParams: Invalid EDID specifier: %s", value); 321 | continue; 322 | } 323 | 324 | length = g_ascii_strtoll (split_value[0], NULL, 10); 325 | 326 | if (128 * 2 * length != strlen (split_value[1])) 327 | { 328 | g_warning ("WfdParams: EDID hex string should be %u characters but is %lu characters", 329 | 128 * 2 * length, strlen (split_value[1])); 330 | continue; 331 | } 332 | 333 | g_clear_pointer (&self->edid, g_byte_array_unref); 334 | /* Convert to binary, two characters at the time from the back, NUL 335 | * terminating in the process. */ 336 | self->edid = g_byte_array_sized_new (128 * length); 337 | for (gint i = 128 * length - 1; i >= 0; i--) 338 | { 339 | gchar byte = (gchar) g_ascii_strtoll (&split_value[1][i * 2], NULL, 16); 340 | split_value[1][i * 2] = '\0'; 341 | self->edid->data[i] = byte; 342 | } 343 | } 344 | else if (g_str_equal (option, "wfd_idr_request_capability")) 345 | { 346 | /* Assume IDR request capable if it is the string one */ 347 | self->idr_request_capability = g_str_equal (value, "1"); 348 | } 349 | else if (g_str_equal (option, "microsoft_cursor")) 350 | { 351 | g_auto(GStrv) split_value = NULL; 352 | 353 | self->ms_cursor_capability = FALSE; 354 | 355 | if (g_str_equal (value, "none")) 356 | continue; 357 | 358 | split_value = g_strsplit (value, " ", 5); 359 | if (g_strv_length (split_value) != 4) 360 | { 361 | g_warning ("WfdParams: Unknown microsoft_cursor value %s", value); 362 | continue; 363 | } 364 | 365 | /* The first argument is either "none" or "full" where "full" means that 366 | * the hardware supports XOR blending (which we don't support). */ 367 | self->ms_cursor_width = g_ascii_strtoll (split_value[1], NULL, 16); 368 | self->ms_cursor_height = g_ascii_strtoll (split_value[2], NULL, 16); 369 | self->ms_cursor_port = g_ascii_strtoll (split_value[3], NULL, 16); 370 | 371 | /* Do some sanity checks, and set capable to TRUE if everything is good. */ 372 | if (self->ms_cursor_width >= 32 && self->ms_cursor_height >= 32 && self->ms_cursor_port >= 0) 373 | self->ms_cursor_capability = TRUE; 374 | else 375 | g_warning ("WfdParams: microsoft_cursor extension has odd values: \"%s\"", value); 376 | } 377 | else 378 | { 379 | g_debug ("WfdParams: Not handling option %s", option); 380 | } 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /src/wfd/wfd-params.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "wfd-video-codec.h" 5 | #include "wfd-audio-codec.h" 6 | 7 | G_BEGIN_DECLS 8 | 9 | #define WFD_TYPE_PARAMS (wfd_params_get_type ()) 10 | 11 | struct _WfdParams 12 | { 13 | /*< public >*/ 14 | gchar *profile; 15 | guint16 primary_rtp_port; 16 | guint16 secondary_rtp_port; 17 | GByteArray *edid; 18 | 19 | gboolean idr_request_capability; 20 | gboolean ms_cursor_capability; 21 | guint16 ms_cursor_width; 22 | guint16 ms_cursor_height; 23 | guint16 ms_cursor_port; 24 | 25 | WfdVideoCodec *selected_codec; 26 | WfdResolution *selected_resolution; 27 | WfdAudioCodec *selected_audio_codec; 28 | 29 | GPtrArray *video_codecs; 30 | GPtrArray *audio_codecs; 31 | }; 32 | 33 | typedef struct _WfdParams WfdParams; 34 | 35 | GType wfd_params_get_type (void) G_GNUC_CONST; 36 | WfdParams *wfd_params_new (void); 37 | WfdParams *wfd_params_copy (WfdParams *self); 38 | void wfd_params_free (WfdParams *self); 39 | 40 | gchar *wfd_params_m3_query_params (WfdParams *self); 41 | void wfd_params_from_sink (WfdParams *self, 42 | const guint8 *body, 43 | gsize body_size); 44 | 45 | G_DEFINE_AUTOPTR_CLEANUP_FUNC (WfdParams, wfd_params_free) 46 | 47 | G_END_DECLS 48 | -------------------------------------------------------------------------------- /src/wfd/wfd-resolution.c: -------------------------------------------------------------------------------- 1 | #include "wfd-resolution.h" 2 | 3 | G_DEFINE_BOXED_TYPE (WfdResolution, wfd_resolution, wfd_resolution_copy, wfd_resolution_free) 4 | 5 | /** 6 | * wfd_resolution_new: 7 | * 8 | * Creates a new #WfdResolution. 9 | * 10 | * Returns: (transfer full): A newly created #WfdResolution 11 | */ 12 | WfdResolution * 13 | wfd_resolution_new (void) 14 | { 15 | WfdResolution *self; 16 | 17 | self = g_slice_new0 (WfdResolution); 18 | 19 | return self; 20 | } 21 | 22 | /** 23 | * wfd_resolution_copy: 24 | * @self: a #WfdResolution 25 | * 26 | * Makes a deep copy of a #WfdResolution. 27 | * 28 | * Returns: (transfer full): A newly created #WfdResolution with the same 29 | * contents as @self 30 | */ 31 | WfdResolution * 32 | wfd_resolution_copy (WfdResolution *self) 33 | { 34 | WfdResolution *copy; 35 | 36 | g_return_val_if_fail (self, NULL); 37 | 38 | copy = wfd_resolution_new (); 39 | copy->width = self->width; 40 | copy->height = self->height; 41 | copy->refresh_rate = self->refresh_rate; 42 | copy->interlaced = self->interlaced; 43 | 44 | return copy; 45 | } 46 | 47 | /** 48 | * wfd_resolution_free: 49 | * @self: a #WfdResolution 50 | * 51 | * Frees a #WfdResolution allocated using wfd_resolution_new() 52 | * or wfd_resolution_copy(). 53 | */ 54 | void 55 | wfd_resolution_free (WfdResolution *self) 56 | { 57 | g_return_if_fail (self); 58 | 59 | g_slice_free (WfdResolution, self); 60 | } 61 | -------------------------------------------------------------------------------- /src/wfd/wfd-resolution.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | G_BEGIN_DECLS 6 | 7 | #define WFD_TYPE_RESOLUTION (wfd_resolution_get_type ()) 8 | 9 | typedef struct _WfdResolution WfdResolution; 10 | 11 | GType wfd_resolution_get_type (void) G_GNUC_CONST; 12 | WfdResolution *wfd_resolution_new (void); 13 | WfdResolution *wfd_resolution_copy (WfdResolution *self); 14 | void wfd_resolution_free (WfdResolution *self); 15 | 16 | G_DEFINE_AUTOPTR_CLEANUP_FUNC (WfdResolution, wfd_resolution_free) 17 | 18 | struct _WfdResolution 19 | { 20 | gint width; 21 | gint height; 22 | gint refresh_rate; 23 | gboolean interlaced; 24 | }; 25 | 26 | G_END_DECLS 27 | -------------------------------------------------------------------------------- /src/wfd/wfd-server.c: -------------------------------------------------------------------------------- 1 | #include "wfd-server.h" 2 | #include "wfd-client.h" 3 | #include "wfd-media-factory.h" 4 | #include "wfd-session-pool.h" 5 | 6 | struct _WfdServer 7 | { 8 | GstRTSPServer parent_instance; 9 | 10 | guint clean_pool_source_id; 11 | }; 12 | 13 | G_DEFINE_TYPE (WfdServer, wfd_server, GST_TYPE_RTSP_SERVER) 14 | 15 | enum { 16 | SIGNAL_CREATE_SOURCE, 17 | SIGNAL_CREATE_AUDIO_SOURCE, 18 | NR_SIGNALS 19 | }; 20 | 21 | static guint signals[NR_SIGNALS]; 22 | 23 | 24 | WfdServer * 25 | wfd_server_new (void) 26 | { 27 | /* We use our own session pool. The only reason to do so is 28 | * to shorten the session ID to 15 characters! 29 | */ 30 | return g_object_new (WFD_TYPE_SERVER, 31 | "session-pool", wfd_session_pool_new (), 32 | NULL); 33 | } 34 | 35 | static void 36 | wfd_server_finalize (GObject *object) 37 | { 38 | WfdServer *self = (WfdServer *) object; 39 | 40 | g_debug ("WfdServer: Finalize"); 41 | 42 | g_source_remove (self->clean_pool_source_id); 43 | self->clean_pool_source_id = 0; 44 | 45 | G_OBJECT_CLASS (wfd_server_parent_class)->finalize (object); 46 | } 47 | 48 | static GstRTSPClient * 49 | wfd_server_create_client (GstRTSPServer *server) 50 | { 51 | g_autoptr(WfdClient) client = NULL; 52 | GstRTSPClient *rtsp_client; 53 | g_autoptr(GstRTSPSessionPool) session_pool = NULL; 54 | g_autoptr(GstRTSPMountPoints) mount_points = NULL; 55 | g_autoptr(GstRTSPAuth) auth = NULL; 56 | g_autoptr(GstRTSPThreadPool) thread_pool = NULL; 57 | 58 | client = wfd_client_new (); 59 | rtsp_client = GST_RTSP_CLIENT (client); 60 | 61 | session_pool = gst_rtsp_server_get_session_pool (server); 62 | gst_rtsp_client_set_session_pool (rtsp_client, session_pool); 63 | mount_points = gst_rtsp_server_get_mount_points (server); 64 | gst_rtsp_client_set_mount_points (rtsp_client, mount_points); 65 | auth = gst_rtsp_server_get_auth (server); 66 | gst_rtsp_client_set_auth (rtsp_client, auth); 67 | thread_pool = gst_rtsp_server_get_thread_pool (server); 68 | gst_rtsp_client_set_thread_pool (rtsp_client, thread_pool); 69 | 70 | return GST_RTSP_CLIENT (g_steal_pointer (&client)); 71 | } 72 | 73 | static gboolean 74 | timeout_query_wfd_support (gpointer user_data) 75 | { 76 | WfdClient *client = WFD_CLIENT (user_data); 77 | 78 | g_object_set_data (G_OBJECT (client), 79 | "wfd-query-support-timeout", 80 | NULL); 81 | 82 | wfd_client_query_support (client); 83 | 84 | return G_SOURCE_REMOVE; 85 | } 86 | 87 | static void 88 | wfd_server_client_closed (GstRTSPServer *server, GstRTSPClient *client) 89 | { 90 | guint query_support_id; 91 | 92 | query_support_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (client), 93 | "wfd-query-support-timeout")); 94 | 95 | if (query_support_id) 96 | g_source_remove (query_support_id); 97 | } 98 | 99 | static void 100 | wfd_server_client_connected (GstRTSPServer *server, GstRTSPClient *client) 101 | { 102 | guint query_support_id; 103 | 104 | query_support_id = g_timeout_add (500, timeout_query_wfd_support, client); 105 | 106 | g_object_set_data (G_OBJECT (client), 107 | "wfd-query-support-timeout", 108 | GUINT_TO_POINTER (query_support_id)); 109 | 110 | g_signal_connect_object (client, 111 | "closed", 112 | (GCallback) wfd_server_client_closed, 113 | server, 114 | G_CONNECT_SWAPPED); 115 | } 116 | 117 | static void 118 | wfd_server_class_init (WfdServerClass *klass) 119 | { 120 | GObjectClass *object_class = G_OBJECT_CLASS (klass); 121 | GstRTSPServerClass *server_class = GST_RTSP_SERVER_CLASS (klass); 122 | 123 | object_class->finalize = wfd_server_finalize; 124 | 125 | server_class->create_client = wfd_server_create_client; 126 | server_class->client_connected = wfd_server_client_connected; 127 | 128 | signals[SIGNAL_CREATE_SOURCE] = 129 | g_signal_new ("create-source", WFD_TYPE_SERVER, G_SIGNAL_RUN_LAST, 130 | 0, 131 | g_signal_accumulator_first_wins, NULL, 132 | NULL, 133 | GST_TYPE_ELEMENT, 0); 134 | 135 | signals[SIGNAL_CREATE_AUDIO_SOURCE] = 136 | g_signal_new ("create-audio-source", WFD_TYPE_SERVER, G_SIGNAL_RUN_LAST, 137 | 0, 138 | g_signal_accumulator_first_wins, NULL, 139 | NULL, 140 | GST_TYPE_ELEMENT, 0); 141 | } 142 | 143 | static gboolean 144 | clean_pool (gpointer user_data) 145 | { 146 | GstRTSPServer *server = GST_RTSP_SERVER (user_data); 147 | 148 | g_autoptr(GstRTSPSessionPool) pool = NULL; 149 | 150 | pool = gst_rtsp_server_get_session_pool (server); 151 | gst_rtsp_session_pool_cleanup (pool); 152 | 153 | return G_SOURCE_CONTINUE; 154 | } 155 | 156 | static GstElement * 157 | factory_source_create_cb (WfdMediaFactory *factory, WfdServer *self) 158 | { 159 | GstElement *res; 160 | 161 | g_signal_emit (self, signals[SIGNAL_CREATE_SOURCE], 0, &res); 162 | 163 | return res; 164 | } 165 | 166 | static GstElement * 167 | factory_audio_source_create_cb (WfdMediaFactory *factory, WfdServer *self) 168 | { 169 | GstElement *res; 170 | 171 | g_signal_emit (self, signals[SIGNAL_CREATE_AUDIO_SOURCE], 0, &res); 172 | 173 | return res; 174 | } 175 | 176 | static void 177 | wfd_server_init (WfdServer *self) 178 | { 179 | g_autoptr(WfdMediaFactory) factory = NULL; 180 | g_autoptr(GstRTSPMountPoints) mount_points = NULL; 181 | /* We need to clean up the pool regularly as it does not happen 182 | * automatically. */ 183 | self->clean_pool_source_id = g_timeout_add_seconds (2, clean_pool, self); 184 | 185 | factory = wfd_media_factory_new (); 186 | g_signal_connect_object (factory, 187 | "create-source", 188 | (GCallback) factory_source_create_cb, 189 | self, 190 | 0); 191 | 192 | g_signal_connect_object (factory, 193 | "create-audio-source", 194 | (GCallback) factory_audio_source_create_cb, 195 | self, 196 | 0); 197 | 198 | mount_points = gst_rtsp_server_get_mount_points (GST_RTSP_SERVER (self)); 199 | gst_rtsp_mount_points_add_factory (mount_points, "/wfd1.0", GST_RTSP_MEDIA_FACTORY (g_steal_pointer (&factory))); 200 | 201 | gst_rtsp_server_set_address (GST_RTSP_SERVER (self), "0.0.0.0"); 202 | gst_rtsp_server_set_service (GST_RTSP_SERVER (self), "7236"); 203 | } 204 | 205 | static GstRTSPFilterResult 206 | pool_filter_remove_cb (GstRTSPSessionPool *pool, 207 | GstRTSPSession *session, 208 | gpointer user_data) 209 | { 210 | return GST_RTSP_FILTER_REMOVE; 211 | } 212 | 213 | static GstRTSPFilterResult 214 | client_filter_remove_cb (GstRTSPServer *server, 215 | GstRTSPClient *client, 216 | gpointer user_data) 217 | { 218 | return GST_RTSP_FILTER_REMOVE; 219 | } 220 | 221 | void 222 | wfd_server_purge (WfdServer *self) 223 | { 224 | GstRTSPServer *server = GST_RTSP_SERVER (self); 225 | 226 | g_autoptr(GstRTSPSessionPool) session_pool = NULL; 227 | g_autoptr(GstRTSPThreadPool) thread_pool = NULL; 228 | 229 | session_pool = gst_rtsp_server_get_session_pool (server); 230 | gst_rtsp_session_pool_filter (session_pool, pool_filter_remove_cb, NULL); 231 | 232 | gst_rtsp_server_client_filter (server, client_filter_remove_cb, NULL); 233 | 234 | thread_pool = gst_rtsp_server_get_thread_pool (server); 235 | gst_rtsp_session_pool_filter (session_pool, pool_filter_remove_cb, NULL); 236 | } 237 | -------------------------------------------------------------------------------- /src/wfd/wfd-server.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #pragma GCC diagnostic push 4 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 5 | #include 6 | #pragma GCC diagnostic pop 7 | 8 | G_BEGIN_DECLS 9 | 10 | #define WFD_TYPE_SERVER (wfd_server_get_type ()) 11 | 12 | G_DECLARE_FINAL_TYPE (WfdServer, wfd_server, WFD, SERVER, GstRTSPServer) 13 | 14 | WfdServer * wfd_server_new (void); 15 | void wfd_server_purge (WfdServer *self); 16 | 17 | G_END_DECLS 18 | -------------------------------------------------------------------------------- /src/wfd/wfd-session-pool.c: -------------------------------------------------------------------------------- 1 | /* nd-meta-provider.c 2 | * 3 | * Copyright 2008 Wim Taymans 4 | * Copyright 2018 Benjamin Berg 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "wfd-session-pool.h" 21 | 22 | struct _WfdSessionPool 23 | { 24 | GstRTSPSessionPool parent_instance; 25 | }; 26 | 27 | G_DEFINE_TYPE (WfdSessionPool, wfd_session_pool, GST_TYPE_RTSP_SESSION_POOL) 28 | 29 | /* The following is copied from GstRTSPSessionPool, the only change is that 30 | * we stop after rather than 16 for the session-id. 31 | * This is required to support some LG TVs which limit the length to 15 bytes 32 | * (i.e. 16 including a NUL byte). 33 | */ 34 | static const gchar session_id_charset[] = 35 | { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 36 | 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 37 | 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 38 | 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', 39 | '8', '9', '-', '_', '.', '+' /* '$' Live555 in VLC strips off $ chars */ 40 | }; 41 | 42 | static gchar* 43 | create_session_id (GstRTSPSessionPool * pool) 44 | { 45 | gchar id[15]; 46 | gint i; 47 | 48 | for (i = 0; i < 15; i++) { 49 | id[i] = 50 | session_id_charset[g_random_int_range (0, 51 | G_N_ELEMENTS (session_id_charset))]; 52 | } 53 | 54 | return g_strndup (id, 15); 55 | } 56 | 57 | static void 58 | wfd_session_pool_class_init (WfdSessionPoolClass *klass) 59 | { 60 | GstRTSPSessionPoolClass *parent_class = GST_RTSP_SESSION_POOL_CLASS (klass); 61 | 62 | parent_class->create_session_id = create_session_id; 63 | } 64 | 65 | static void 66 | wfd_session_pool_init (WfdSessionPool *self) 67 | { 68 | } 69 | 70 | WfdSessionPool * 71 | wfd_session_pool_new (void) 72 | { 73 | return g_object_new (WFD_TYPE_SESSION_POOL, NULL); 74 | } 75 | -------------------------------------------------------------------------------- /src/wfd/wfd-session-pool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #pragma GCC diagnostic push 4 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 5 | #include 6 | #pragma GCC diagnostic pop 7 | 8 | G_BEGIN_DECLS 9 | 10 | #define WFD_TYPE_SESSION_POOL (wfd_session_pool_get_type()) 11 | 12 | G_DECLARE_FINAL_TYPE (WfdSessionPool, wfd_session_pool, WFD, SESSION_POOL, GstRTSPSessionPool) 13 | 14 | WfdSessionPool *wfd_session_pool_new (void); 15 | 16 | G_END_DECLS 17 | -------------------------------------------------------------------------------- /src/wfd/wfd-video-codec.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "wfd-resolution.h" 5 | 6 | G_BEGIN_DECLS 7 | 8 | #define WFD_TYPE_VIDEO_CODEC (wfd_video_codec_get_type ()) 9 | 10 | typedef struct _WfdVideoCodec WfdVideoCodec; 11 | 12 | typedef enum { 13 | WFD_H264_PROFILE_BASE = 0x01, 14 | WFD_H264_PROFILE_HIGH = 0x02, 15 | } WfdH264ProfileFlags; 16 | 17 | 18 | struct _WfdVideoCodec 19 | { 20 | /*< public >*/ 21 | WfdH264ProfileFlags profile; 22 | guint8 level; 23 | guint8 latency; 24 | gint frame_skipping_allowed; 25 | 26 | /*< private >*/ 27 | WfdResolution *native; 28 | 29 | guint32 cea_sup; 30 | guint32 vesa_sup; 31 | guint32 hh_sup; 32 | 33 | guint32 min_slice_size; 34 | gint max_slice_num : 10; 35 | guint16 max_slice_size_ratio : 3; 36 | 37 | guint ref_count; 38 | }; 39 | 40 | GType wfd_video_codec_get_type (void) G_GNUC_CONST; 41 | WfdVideoCodec *wfd_video_codec_new (void); 42 | WfdVideoCodec *wfd_video_codec_copy (WfdVideoCodec *self); 43 | WfdVideoCodec *wfd_video_codec_ref (WfdVideoCodec *self); 44 | void wfd_video_codec_unref (WfdVideoCodec *self); 45 | 46 | WfdVideoCodec *wfd_video_codec_new_from_desc (gint native, 47 | const gchar *descr); 48 | 49 | guint32 wfd_video_codec_get_max_bitrate_kbit (WfdVideoCodec *self); 50 | 51 | GList *wfd_video_codec_get_resolutions (WfdVideoCodec *self); 52 | gchar *wfd_video_codec_get_descriptor_for_resolution (WfdVideoCodec *self, 53 | const WfdResolution *resolution); 54 | 55 | void wfd_video_codec_dump (WfdVideoCodec *self); 56 | 57 | G_DEFINE_AUTOPTR_CLEANUP_FUNC (WfdVideoCodec, wfd_video_codec_unref) 58 | 59 | G_END_DECLS 60 | --------------------------------------------------------------------------------