gst-plugin-gtk4-0.13.6/.cargo_vcs_info.json0000644000000001500000000000100140570ustar { "git": { "sha1": "a9f6a26907f735b528f74e3f91df4e7a9f3aa469" }, "path_in_vcs": "video/gtk4" }gst-plugin-gtk4-0.13.6/Cargo.lock0000644000001037220000000000100120430ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "async-channel" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ "concurrent-queue", "event-listener-strategy", "futures-core", "pin-project-lite", ] [[package]] name = "atomic_refcell" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c" [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bitflags" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "bumpalo" version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "cairo-rs" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58e62a27cd02fb3f63f82bb31fdda7e6c43141497cbe97e8816d7c914043f55" dependencies = [ "bitflags", "cairo-sys-rs", "glib", "libc", ] [[package]] name = "cairo-sys-rs" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "059cc746549898cbfd9a47754288e5a958756650ef4652bbb6c5f71a6bda4f8b" dependencies = [ "glib-sys", "libc", "system-deps", ] [[package]] name = "cc" version = "1.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" dependencies = [ "shlex", ] [[package]] name = "cfg-expr" version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d4ba6e40bd1184518716a6e1a781bf9160e286d219ccdb8ab2612e74cfe4789" dependencies = [ "smallvec", "target-lexicon", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", "windows-link", ] [[package]] name = "concurrent-queue" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "event-listener" version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" dependencies = [ "concurrent-queue", "parking", "pin-project-lite", ] [[package]] name = "event-listener-strategy" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ "event-listener", "pin-project-lite", ] [[package]] name = "field-offset" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" dependencies = [ "memoffset", "rustc_version", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-macro", "futures-task", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "gdk-pixbuf" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd242894c084f4beed508a56952750bce3e96e85eb68fdc153637daa163e10c" dependencies = [ "gdk-pixbuf-sys", "gio", "glib", "libc", ] [[package]] name = "gdk-pixbuf-sys" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b34f3b580c988bd217e9543a2de59823fafae369d1a055555e5f95a8b130b96" dependencies = [ "gio-sys", "glib-sys", "gobject-sys", "libc", "system-deps", ] [[package]] name = "gdk4" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4850c9d9c1aecd1a3eb14fadc1cdb0ac0a2298037e116264c7473e1740a32d60" dependencies = [ "cairo-rs", "gdk-pixbuf", "gdk4-sys", "gio", "glib", "libc", "pango", ] [[package]] name = "gdk4-sys" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f6eb95798e2b46f279cf59005daf297d5b69555428f185650d71974a910473a" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", "gio-sys", "glib-sys", "gobject-sys", "libc", "pango-sys", "pkg-config", "system-deps", ] [[package]] name = "gdk4-wayland" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd34518488cd624a85e75e82540bc24c72cfeb0aea6bad7faed683ca3977dba0" dependencies = [ "gdk4", "gdk4-wayland-sys", "gio", "glib", "libc", ] [[package]] name = "gdk4-wayland-sys" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c7a0f2332c531d62ee3f14f5e839ac1abac59e9b052adf1495124c00d89a34b" dependencies = [ "glib-sys", "libc", "system-deps", ] [[package]] name = "gdk4-win32" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e54beb3e19bff7ccc05153e043bf0eea9cf76c07a3387fb0151bfa0fbfce9df0" dependencies = [ "gdk4", "gdk4-win32-sys", "gio", "glib", "khronos-egl", "libc", ] [[package]] name = "gdk4-win32-sys" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d554341f26b2f6191aaf2ecb4d0089f3696a40cadb2dba75dad03e48a6dc0eb9" dependencies = [ "gdk4-sys", "glib-sys", "libc", "system-deps", ] [[package]] name = "gdk4-x11" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3e7380a9a206b170e1b52b5f25581406db816c68f4e7140dbef89a9e5b52ac" dependencies = [ "gdk4", "gdk4-x11-sys", "gio", "glib", "libc", ] [[package]] name = "gdk4-x11-sys" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "070bd50a053f90d7fdf6be1d75672ea0f97c0e5da3a10dc6d02e5defcb0db32f" dependencies = [ "gdk4-sys", "glib-sys", "libc", "system-deps", ] [[package]] name = "gio" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2a654c887546d14fdb214cc04641cd30450c9b4fa4525fd989d25fd5a5561e" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-util", "gio-sys", "glib", "libc", "pin-project-lite", "smallvec", ] [[package]] name = "gio-sys" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521e93a7e56fc89e84aea9a52cfc9436816a4b363b030260b699950ff1336c83" dependencies = [ "glib-sys", "gobject-sys", "libc", "system-deps", "windows-sys", ] [[package]] name = "glib" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c501c495842c2b23cdacead803a5a343ca2a5d7a7ddaff14cc5f6cf22cfb92c2" dependencies = [ "bitflags", "futures-channel", "futures-core", "futures-executor", "futures-task", "futures-util", "gio-sys", "glib-macros", "glib-sys", "gobject-sys", "libc", "memchr", "smallvec", ] [[package]] name = "glib-macros" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebe6dc9ce29887c4b3b74d78d5ba473db160a258ae7ed883d23632ac7fed7bc9" dependencies = [ "heck", "proc-macro-crate", "proc-macro2", "quote", "syn", ] [[package]] name = "glib-sys" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ab79e1ed126803a8fb827e3de0e2ff95191912b8db65cee467edb56fc4cc215" dependencies = [ "libc", "system-deps", ] [[package]] name = "gobject-sys" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec9aca94bb73989e3cfdbf8f2e0f1f6da04db4d291c431f444838925c4c63eda" dependencies = [ "glib-sys", "libc", "system-deps", ] [[package]] name = "graphene-rs" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b86dfad7d14251c9acaf1de63bc8754b7e3b4e5b16777b6f5a748208fe9519b" dependencies = [ "glib", "graphene-sys", "libc", ] [[package]] name = "graphene-sys" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df583a85ba2d5e15e1797e40d666057b28bc2f60a67c9c24145e6db2cc3861ea" dependencies = [ "glib-sys", "libc", "pkg-config", "system-deps", ] [[package]] name = "gsk4" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61f5e72f931c8c9f65fbfc89fe0ddc7746f147f822f127a53a9854666ac1f855" dependencies = [ "cairo-rs", "gdk4", "glib", "graphene-rs", "gsk4-sys", "libc", "pango", ] [[package]] name = "gsk4-sys" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "755059de55fa6f85a46bde8caf03e2184c96bfda1f6206163c72fb0ea12436dc" dependencies = [ "cairo-sys-rs", "gdk4-sys", "glib-sys", "gobject-sys", "graphene-sys", "libc", "pango-sys", "system-deps", ] [[package]] name = "gst-plugin-gtk4" version = "0.13.6" dependencies = [ "async-channel", "gdk4-wayland", "gdk4-win32", "gdk4-x11", "gst-plugin-version-helper", "gstreamer", "gstreamer-allocators", "gstreamer-base", "gstreamer-gl", "gstreamer-gl-egl", "gstreamer-gl-wayland", "gstreamer-gl-x11", "gstreamer-video", "gtk4", "once_cell", "windows-sys", ] [[package]] name = "gst-plugin-version-helper" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e5e874f1660252fd2ec81c602066df3633b3a6fcbe2b196f7f93c27cf069b2a" dependencies = [ "chrono", "toml_edit", ] [[package]] name = "gstreamer" version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50ab4c88f731596a2511a6f14cabdd666e0d8efab62a1d58e6ddb57faa96e22e" dependencies = [ "cfg-if", "futures-channel", "futures-core", "futures-util", "glib", "gstreamer-sys", "itertools", "libc", "muldiv", "num-integer", "num-rational", "once_cell", "option-operations", "paste", "pin-project-lite", "smallvec", "thiserror", ] [[package]] name = "gstreamer-allocators" version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45f3c36aa5bc98e5dbfd19a27536373167ab1ad734ffafb84d5d65203fe75208" dependencies = [ "glib", "gstreamer", "gstreamer-allocators-sys", "libc", "once_cell", ] [[package]] name = "gstreamer-allocators-sys" version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c005b6cfae0c7e97b0ea95921f561fc8a8786b734cec25c29363a52305074a10" dependencies = [ "glib-sys", "gobject-sys", "gstreamer-sys", "libc", "system-deps", ] [[package]] name = "gstreamer-base" version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f19a74fd04ffdcb847dd322640f2cf520897129d00a7bcb92fd62a63f3e27404" dependencies = [ "atomic_refcell", "cfg-if", "glib", "gstreamer", "gstreamer-base-sys", "libc", ] [[package]] name = "gstreamer-base-sys" version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f2fb0037b6d3c5b51f60dea11e667910f33be222308ca5a101450018a09840" dependencies = [ "glib-sys", "gobject-sys", "gstreamer-sys", "libc", "system-deps", ] [[package]] name = "gstreamer-gl" version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa19feafc4da2c7635abce0e0768892ff97ad73586bef02d9a60b251d9fe09" dependencies = [ "glib", "gstreamer", "gstreamer-base", "gstreamer-gl-sys", "gstreamer-video", "libc", "once_cell", ] [[package]] name = "gstreamer-gl-egl" version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de1f4247cf2d009b41ab5efb03e4d826b7ccaafb9a75d3ea10e68e46f65e8aa" dependencies = [ "glib", "gstreamer", "gstreamer-gl", "gstreamer-gl-egl-sys", "libc", ] [[package]] name = "gstreamer-gl-egl-sys" version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dda4d852ed107cc48692af4e109e5e4775b6ce1044d13df79f6f431c195096d7" dependencies = [ "glib-sys", "gstreamer-gl-sys", "libc", "system-deps", ] [[package]] name = "gstreamer-gl-sys" version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a832c21d4522ed5e1b8dfc676a45361969216b144fc03af413a38c471f38bcf7" dependencies = [ "glib-sys", "gobject-sys", "gstreamer-base-sys", "gstreamer-sys", "gstreamer-video-sys", "libc", "system-deps", ] [[package]] name = "gstreamer-gl-wayland" version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "603a0b0744c5810932f9c2cb40f0922378202a8875f3db55627049acfe68faf1" dependencies = [ "glib", "gstreamer", "gstreamer-gl", "gstreamer-gl-wayland-sys", "libc", ] [[package]] name = "gstreamer-gl-wayland-sys" version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9701def285d8cdf69664c44ac759b92d18254e1faa5cfadea18810cc289e5cb9" dependencies = [ "glib-sys", "gstreamer-gl-sys", "libc", "system-deps", ] [[package]] name = "gstreamer-gl-x11" version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53fb7df4736fd071e79b47a7c3b3c0aff51cdc9cbfd715c6a9b69903a517a4e6" dependencies = [ "glib", "gstreamer", "gstreamer-gl", "gstreamer-gl-x11-sys", "libc", ] [[package]] name = "gstreamer-gl-x11-sys" version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3fcdab0a4ea49002507c015a918f2942798749101c5e0f50bbdffcd5cd5f21e" dependencies = [ "glib-sys", "gstreamer-gl-sys", "libc", "system-deps", ] [[package]] name = "gstreamer-sys" version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "feea73b4d92dbf9c24a203c9cd0bcc740d584f6b5960d5faf359febf288919b2" dependencies = [ "glib-sys", "gobject-sys", "libc", "system-deps", ] [[package]] name = "gstreamer-video" version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1318b599d77ca4f7702ecbdeac1672d6304cb16b7e5752fabb3ee8260449a666" dependencies = [ "cfg-if", "futures-channel", "glib", "gstreamer", "gstreamer-base", "gstreamer-video-sys", "libc", "once_cell", "thiserror", ] [[package]] name = "gstreamer-video-sys" version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a70f0947f12d253b9de9bc3fd92f981e4d025336c18389c7f08cdf388a99f5c" dependencies = [ "glib-sys", "gobject-sys", "gstreamer-base-sys", "gstreamer-sys", "libc", "system-deps", ] [[package]] name = "gtk4" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1c491051f030994fd0cde6f3c44f3f5640210308cff1298c7673c47408091d" dependencies = [ "cairo-rs", "field-offset", "futures-channel", "gdk-pixbuf", "gdk4", "gio", "glib", "graphene-rs", "gsk4", "gtk4-macros", "gtk4-sys", "libc", "pango", ] [[package]] name = "gtk4-macros" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ed1786c4703dd196baf7e103525ce0cf579b3a63a0570fe653b7ee6bac33999" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn", ] [[package]] name = "gtk4-sys" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41e03b01e54d77c310e1d98647d73f996d04b2f29b9121fe493ea525a7ec03d6" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", "gdk4-sys", "gio-sys", "glib-sys", "gobject-sys", "graphene-sys", "gsk4-sys", "libc", "pango-sys", "system-deps", ] [[package]] name = "hashbrown" version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "iana-time-zone" version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "log", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "indexmap" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "itertools" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "khronos-egl" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ "libc", ] [[package]] name = "libc" version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memoffset" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "muldiv" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "956787520e75e9bd233246045d19f42fb73242759cc57fba9611d940ae96d4b0" [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-rational" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "option-operations" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c26d27bb1aeab65138e4bf7666045169d1717febcc9ff870166be8348b223d0" dependencies = [ "paste", ] [[package]] name = "pango" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d88d37c161f2848f0d9382597f0168484c9335ac800995f3956641abb7002938" dependencies = [ "gio", "glib", "libc", "pango-sys", ] [[package]] name = "pango-sys" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "186909673fc09be354555c302c0b3dcf753cd9fa08dcb8077fa663c80fb243fa" dependencies = [ "glib-sys", "gobject-sys", "libc", "system-deps", ] [[package]] name = "parking" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "proc-macro-crate" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustversion" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "semver" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_spanned" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "syn" version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "system-deps" version = "7.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66d23aaf9f331227789a99e8de4c91bf46703add012bdfd45fdecdfb2975a005" dependencies = [ "cfg-expr", "heck", "pkg-config", "toml", "version-compare", ] [[package]] name = "target-lexicon" version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "toml" version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "version-compare" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "windows-core" version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ "windows-implement", "windows-interface", "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-interface" version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-link" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-result" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" dependencies = [ "windows-link", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] gst-plugin-gtk4-0.13.6/Cargo.toml0000644000000100550000000000100120620ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.71" name = "gst-plugin-gtk4" version = "0.13.6" authors = [ "Bilal Elmoussaoui ", "Jordan Petridis ", "Sebastian Dröge ", ] build = "build.rs" autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "GStreamer GTK 4 sink element" readme = "README.md" license = "MPL-2.0" repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs" [package.metadata.capi] min_version = "0.9.21" [package.metadata.capi.header] enabled = false [package.metadata.capi.library] import_library = false install_subdir = "gstreamer-1.0" versioning = false [package.metadata.capi.pkg_config] requires_private = "gstreamer-1.0, gstreamer-base-1.0, gstreamer-video-1.0, gtk4, gobject-2.0, glib-2.0, gmodule-2.0" [package.metadata.gstreamer] release_date = "2025-05-13" [features] capi = [] default = [] dmabuf = [ "gst-allocators", "gtk_v4_14", "gst-video/v1_24", ] doc = ["gst/v1_18"] gtk_v4_10 = ["gtk/v4_10"] gtk_v4_12 = [ "gtk/v4_12", "gtk_v4_10", ] gtk_v4_14 = [ "gtk/v4_14", "gtk_v4_12", ] gtk_v4_16 = [ "gtk/v4_16", "gtk_v4_14", ] static = [] wayland = ["waylandegl"] waylandegl = [ "gtk/v4_6", "gdk-wayland", "gst-gl", "gst-gl-wayland", ] winegl = [ "gdk-win32/egl", "gst-gl-egl", ] x11egl = [ "gtk/v4_6", "gdk-x11", "gst-gl", "gst-gl-egl", ] x11glx = [ "gtk/v4_6", "gdk-x11", "gst-gl", "gst-gl-x11", ] [lib] name = "gstgtk4" crate-type = [ "cdylib", "rlib", ] path = "src/lib.rs" [[example]] name = "gtksink" path = "examples/gtksink.rs" [dependencies.async-channel] version = "2.0.0" [dependencies.gdk-wayland] version = "0.9" features = ["v4_4"] optional = true package = "gdk4-wayland" [dependencies.gdk-x11] version = "0.9" features = ["v4_4"] optional = true package = "gdk4-x11" [dependencies.gst] version = "0.23" features = ["v1_16"] package = "gstreamer" [dependencies.gst-allocators] version = "0.23" features = ["v1_24"] optional = true package = "gstreamer-allocators" [dependencies.gst-base] version = "0.23" package = "gstreamer-base" [dependencies.gst-gl] version = "0.23" features = ["v1_16"] optional = true package = "gstreamer-gl" [dependencies.gst-gl-egl] version = "0.23" features = ["v1_16"] optional = true package = "gstreamer-gl-egl" [dependencies.gst-gl-wayland] version = "0.23" features = ["v1_16"] optional = true package = "gstreamer-gl-wayland" [dependencies.gst-gl-x11] version = "0.23" features = ["v1_16"] optional = true package = "gstreamer-gl-x11" [dependencies.gst-video] version = "0.23" package = "gstreamer-video" [dependencies.gtk] version = "0.9" package = "gtk4" [dependencies.once_cell] version = "1" [build-dependencies.gst-plugin-version-helper] version = "0.8" [target.'cfg(target_os = "macos")'.dependencies.gst-gl] version = "0.23" features = ["v1_16"] package = "gstreamer-gl" [target.'cfg(target_os = "macos")'.dependencies.gtk] version = "0.9" features = ["v4_6"] package = "gtk4" [target.'cfg(target_os = "windows")'.dependencies.gdk-win32] version = "0.9" features = ["v4_4"] package = "gdk4-win32" [target.'cfg(target_os = "windows")'.dependencies.gst-gl] version = "0.23" features = ["v1_20"] package = "gstreamer-gl" [target.'cfg(target_os = "windows")'.dependencies.gtk] version = "0.9" features = ["v4_6"] package = "gtk4" [target.'cfg(target_os = "windows")'.dependencies.windows-sys] version = ">=0.52, <=0.59" features = [ "Win32_Graphics_OpenGL", "Win32_Foundation", "Win32_Graphics_Gdi", ] gst-plugin-gtk4-0.13.6/Cargo.toml.orig000064400000000000000000000051071046102023000155450ustar 00000000000000[package] name = "gst-plugin-gtk4" version.workspace = true authors = ["Bilal Elmoussaoui ", "Jordan Petridis ", "Sebastian Dröge "] repository.workspace = true license = "MPL-2.0" edition.workspace = true rust-version.workspace = true description = "GStreamer GTK 4 sink element" [dependencies] gtk.workspace = true gdk-wayland = { workspace = true, features = ["v4_4"], optional = true} gdk-x11 = { workspace = true, features = ["v4_4"], optional = true} gst = { workspace = true, features = ["v1_16"] } gst-base.workspace = true gst-video.workspace = true gst-gl = { workspace = true, features = ["v1_16"], optional = true } gst-allocators = { workspace = true, features = ["v1_24"], optional = true } gst-gl-wayland = { workspace = true, features = ["v1_16"], optional = true } gst-gl-x11 = { workspace = true, features = ["v1_16"], optional = true } gst-gl-egl = { workspace = true, features = ["v1_16"], optional = true } async-channel = "2.0.0" once_cell.workspace = true [target.'cfg(target_os = "macos")'.dependencies] gtk = { workspace = true, features = ["v4_6"] } gst-gl = { workspace = true, features = ["v1_16"] } [target.'cfg(target_os = "windows")'.dependencies] gtk = { workspace = true, features = ["v4_6"] } gst-gl = { workspace = true, features = ["v1_20"] } gdk-win32 = { workspace = true, features = ["v4_4"]} windows-sys = { version = ">=0.52, <=0.59", features = ["Win32_Graphics_OpenGL", "Win32_Foundation", "Win32_Graphics_Gdi"] } [lib] name = "gstgtk4" crate-type = ["cdylib", "rlib"] path = "src/lib.rs" [build-dependencies] gst-plugin-version-helper.workspace = true [features] default = [] static = [] # Deprecated wayland = ["waylandegl"] waylandegl = ["gtk/v4_6", "gdk-wayland", "gst-gl", "gst-gl-wayland"] x11glx = ["gtk/v4_6", "gdk-x11", "gst-gl", "gst-gl-x11"] x11egl = ["gtk/v4_6", "gdk-x11", "gst-gl", "gst-gl-egl"] winegl = ["gdk-win32/egl", "gst-gl-egl"] dmabuf = ["gst-allocators", "gtk_v4_14", "gst-video/v1_24"] capi = [] doc = ["gst/v1_18"] gtk_v4_10 = ["gtk/v4_10"] gtk_v4_12 = ["gtk/v4_12", "gtk_v4_10"] gtk_v4_14 = ["gtk/v4_14", "gtk_v4_12"] gtk_v4_16 = ["gtk/v4_16", "gtk_v4_14"] [package.metadata.capi] min_version = "0.9.21" [package.metadata.capi.header] enabled = false [package.metadata.capi.library] install_subdir = "gstreamer-1.0" versioning = false import_library = false [package.metadata.capi.pkg_config] requires_private = "gstreamer-1.0, gstreamer-base-1.0, gstreamer-video-1.0, gtk4, gobject-2.0, glib-2.0, gmodule-2.0" [package.metadata.gstreamer] release_date = "2025-05-13" gst-plugin-gtk4-0.13.6/LICENSE-MPL-2.0000064400000000000000000000405261046102023000146120ustar 00000000000000Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. gst-plugin-gtk4-0.13.6/README.md000064400000000000000000000057351046102023000141440ustar 00000000000000# GTK 4 Sink & Paintable GTK 4 provides `gtk::Video` & `gtk::Picture` for rendering media such as videos. As the default `gtk::Video` widget doesn't offer the possibility to use a custom `gst::Pipeline`. The plugin provides a `gst_video::VideoSink` along with a `gdk::Paintable` that's capable of rendering the sink's frames. The sink can generate GL Textures if the system is capable of it, but it needs to be compiled with either `waylandegl`, `x11glx` or `x11egl` cargo features. On Windows and macOS this is enabled by default. Additionally, the sink can render DMABufs directly on Linux if GTK 4.14 or newer is used. For this the `dmabuf` feature needs to be enabled. Depending on the GTK version that is used and should be supported as minimum, new features or more efficient processing can be opted in with the `gtk_v4_10`, `gtk_v4_12` and `gtk_v4_14` features. The minimum GTK version required by the sink is GTK 4.4 on Linux without GL support, and 4.6 on Windows and macOS, and on Linux with GL support. The sink will provides a simple test window when launched via `gst-launch-1.0` or `gst-play-1.0` or if the environment variable `GST_GTK4_WINDOW=1` is set. Setting `GST_GTK4_WINDOW_FULLSCREEN=1` will make the window launch in fullscreen mode. # Flatpak Integration To build and include the plugin in a Flatpak manifest, you can add the following snippet to your json manifest: ```json { "sdk-extensions": [ "org.freedesktop.Sdk.Extension.rust-stable" ], "build-options": { "append-path": "/usr/lib/sdk/rust-stable/bin", }, "modules": [ { "name": "gst-plugins-rs", "buildsystem": "simple", "sources": [ { "type": "archive", "url": "https://crates.io/api/v1/crates/gst-plugin-gtk4/0.12.5/download", "dest-filename": "gst-plugin-gtk4-0.12.5.tar.gz", "sha256": "56e483cb1452f056ae94ccd5f63bdec697e04c87b30d89eb30c3f934042e1022" }, "gst-plugin-gtk4-sources.json" ], "build-options": { "env": { "CARGO_HOME": "$FLATPAK_BUILDER_BUILDDIR/cargo" } }, "build-commands": [ "cargo cinstall --offline --release --features=waylandegl,x11glx,x11egl,dmabuf --library-type=cdylib --prefix=/app" ] } ] } ``` To generate the additional file `gst-plugin-gtk4-sources.json` which will contain links to all the Cargo dependencies for the plugin to avoid making network requests while building, you need to use the `flatpak-cargo-generator` tool from [flatpak-builder-tools](https://github.com/flatpak/flatpak-builder-tools): ```sh wget https://crates.io/api/v1/crates/gst-plugin-gtk4/0.12.5/download tar -xf download sha256sum download # update the sha256 in the Flatpak manifest cd gst-plugin-gtk4-0.12.5/ /path/to/flatpak-cargo-generator Cargo.lock -o gst-plugin-gtk4-sources.json ``` gst-plugin-gtk4-0.13.6/build.rs000064400000000000000000000003011046102023000143120ustar 00000000000000fn main() { #[cfg(feature = "wayland")] { println!("cargo:warning=\"wayland\" feature is deprecated, use \"waylandegl\" instead"); } gst_plugin_version_helper::info() } gst-plugin-gtk4-0.13.6/examples/gtksink.py000064400000000000000000000055511046102023000165230ustar 00000000000000import gi import sys gi.require_version('GLib', '2.0') from gi.repository import GLib gi.require_version('Gtk', '4.0') from gi.repository import Gtk gi.require_version('Gst', '1.0') from gi.repository import Gst def on_activate(app): pipeline = Gst.Pipeline.new() overlay = Gst.ElementFactory.make('clockoverlay', None) overlay.set_property('font-desc', 'Monospace 42') gtksink = Gst.ElementFactory.make('gtk4paintablesink', None) paintable = gtksink.get_property('paintable') if paintable.props.gl_context: print('Using GL') src = Gst.ElementFactory.make('gltestsrc', None) sink = Gst.ElementFactory.make('glsinkbin', None) sink.set_property('sink', gtksink) else: print('Not using GL') src = Gst.ElementFactory.make('videotestsrc', None) sink = Gst.Bin.new() convert = Gst.ElementFactory.make('videoconvert', None) sink.add(convert) sink.add(gtksink) convert.link(gtksink) sink.add_pad(Gst.GhostPad.new('sink', convert.get_static_pad('sink'))) pipeline.add(src) pipeline.add(overlay) pipeline.add(sink) src.link_filtered(overlay, Gst.Caps.from_string('video/x-raw(ANY), width=640, height=480')) overlay.link(sink) window = Gtk.ApplicationWindow(application=app) window.set_default_size(640, 480) vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) picture = Gtk.Picture.new() picture.set_paintable(paintable) vbox.append(picture) label = Gtk.Label.new('Position: 00:00:00') vbox.append(label) window.set_child(vbox) window.present() app.add_window(window) def on_timeout(): (res, position) = pipeline.query_position(Gst.Format.TIME) if not res or position == -1: label.set_text('Position: 00:00:00') else: seconds = int(position / 1000000000) minutes = int(seconds / 60) hours = int(minutes / 60) seconds = seconds % 60 minutes = minutes % 60 label.set_text(f'Position: {hours:02}:{minutes:02}:{seconds:02}') return True GLib.timeout_add(500, on_timeout) pipeline.set_state(Gst.State.PLAYING) def on_bus_msg(_, msg): match msg.type: case Gst.MessageType.EOS: app.quit() case Gst.MessageType.ERROR: (err, _) = msg.parse_error() print(f'Error from {msg.src.path_string()}: {err}') app.quit() return True bus = pipeline.get_bus() bus.add_watch(GLib.PRIORITY_DEFAULT, on_bus_msg) def on_close(_): window.close() pipeline.set_state(Gst.State.NULL) app.connect('shutdown', on_close) if __name__ == '__main__': Gst.init(None) app = Gtk.Application() app.connect('activate', on_activate) res = app.run(None) sys.exit(res) gst-plugin-gtk4-0.13.6/examples/gtksink.rs000064400000000000000000000106151046102023000165140ustar 00000000000000use gst::prelude::*; use gtk::prelude::*; use gtk::{gdk, gio, glib}; use std::cell::RefCell; fn create_ui(app: >k::Application) { let pipeline = gst::Pipeline::new(); let overlay = gst::ElementFactory::make("clockoverlay") .property("font-desc", "Monospace 42") .build() .unwrap(); let gtksink = gst::ElementFactory::make("gtk4paintablesink") .build() .unwrap(); let paintable = gtksink.property::("paintable"); // TODO: future plans to provide a bin-like element that works with less setup let (src, sink) = if paintable .property::>("gl-context") .is_some() { let src = gst::ElementFactory::make("gltestsrc").build().unwrap(); let sink = gst::ElementFactory::make("glsinkbin") .property("sink", >ksink) .build() .unwrap(); (src, sink) } else { let src = gst::ElementFactory::make("videotestsrc").build().unwrap(); let sink = gst::Bin::default(); let convert = gst::ElementFactory::make("videoconvert").build().unwrap(); sink.add(&convert).unwrap(); sink.add(>ksink).unwrap(); convert.link(>ksink).unwrap(); sink.add_pad(&gst::GhostPad::with_target(&convert.static_pad("sink").unwrap()).unwrap()) .unwrap(); (src, sink.upcast()) }; pipeline.add_many([&src, &overlay, &sink]).unwrap(); let caps = gst_video::VideoCapsBuilder::new() .width(640) .height(480) .any_features() .build(); src.link_filtered(&overlay, &caps).unwrap(); overlay.link(&sink).unwrap(); let window = gtk::ApplicationWindow::new(app); window.set_default_size(640, 480); let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); let gst_widget = gstgtk4::RenderWidget::new(>ksink); vbox.append(&gst_widget); let label = gtk::Label::new(Some("Position: 00:00:00")); vbox.append(&label); window.set_child(Some(&vbox)); window.show(); app.add_window(&window); let pipeline_weak = pipeline.downgrade(); let timeout_id = glib::timeout_add_local(std::time::Duration::from_millis(500), move || { let Some(pipeline) = pipeline_weak.upgrade() else { return glib::ControlFlow::Break; }; let position = pipeline.query_position::(); label.set_text(&format!("Position: {:.0}", position.display())); glib::ControlFlow::Continue }); let bus = pipeline.bus().unwrap(); pipeline .set_state(gst::State::Playing) .expect("Unable to set the pipeline to the `Playing` state"); let app_weak = app.downgrade(); let bus_watch = bus .add_watch_local(move |_, msg| { use gst::MessageView; let Some(app) = app_weak.upgrade() else { return glib::ControlFlow::Break; }; match msg.view() { MessageView::Eos(..) => app.quit(), MessageView::Error(err) => { println!( "Error from {:?}: {} ({:?})", err.src().map(|s| s.path_string()), err.error(), err.debug() ); app.quit(); } _ => (), }; glib::ControlFlow::Continue }) .expect("Failed to add bus watch"); let timeout_id = RefCell::new(Some(timeout_id)); let pipeline = RefCell::new(Some(pipeline)); let bus_watch = RefCell::new(Some(bus_watch)); app.connect_shutdown(move |_| { window.close(); drop(bus_watch.borrow_mut().take()); if let Some(pipeline) = pipeline.borrow_mut().take() { pipeline .set_state(gst::State::Null) .expect("Unable to set the pipeline to the `Null` state"); } if let Some(timeout_id) = timeout_id.borrow_mut().take() { timeout_id.remove(); } }); } fn main() -> glib::ExitCode { gst::init().unwrap(); gtk::init().unwrap(); gstgtk4::plugin_register_static().expect("Failed to register gstgtk4 plugin"); let app = gtk::Application::new(None::<&str>, gio::ApplicationFlags::FLAGS_NONE); app.connect_activate(create_ui); let res = app.run(); unsafe { gst::deinit(); } res } gst-plugin-gtk4-0.13.6/src/lib.rs000064400000000000000000000036341046102023000145640ustar 00000000000000// // Copyright (C) 2021 Bilal Elmoussaoui // Copyright (C) 2021 Jordan Petridis // Copyright (C) 2021 Sebastian Dröge // // This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at // . // // SPDX-License-Identifier: MPL-2.0 #![allow(clippy::non_send_fields_in_send_ty, unused_doc_comments)] /** * plugin-gtk4: * * Since: plugins-rs-0.8.0 */ use gst::glib; mod sink; mod utils; pub use sink::frame::Orientation; pub use sink::paintable::Paintable; // The widget needs to be public so it can be used by the example and element debug window but // we don't want it be part of the official API for now. #[doc(hidden)] pub use sink::render_widget::RenderWidget; pub use sink::PaintableSink; fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { #[cfg(feature = "doc")] { use gst::prelude::*; sink::paintable::Paintable::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty()); sink::frame::Orientation::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty()); } #[cfg(not(feature = "gtk_v4_10"))] { if gtk::micro_version() >= 13 { gst::warning!(sink::imp::CAT, obj = plugin, "GTK 4.13 or newer detected but plugin not compiled with support for this version. Rendering of video frames with alpha will likely be wrong"); } } sink::register(plugin) } gst::plugin_define!( gtk4, env!("CARGO_PKG_DESCRIPTION"), plugin_init, concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")), // FIXME: MPL-2.0 is only allowed since 1.18.3 (as unknown) and 1.20 (as known) "MPL", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_NAME"), env!("CARGO_PKG_REPOSITORY"), env!("BUILD_REL_DATE") ); gst-plugin-gtk4-0.13.6/src/sink/frame.rs000064400000000000000000000632641046102023000160610ustar 00000000000000// // Copyright (C) 2021 Bilal Elmoussaoui // Copyright (C) 2021 Jordan Petridis // Copyright (C) 2021 Sebastian Dröge // // This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at // . // // SPDX-License-Identifier: MPL-2.0 use gst_video::prelude::*; #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] use gst_gl::prelude::*; use gtk::{gdk, glib}; use std::{ collections::{HashMap, HashSet}, ops, }; #[derive(Clone, PartialEq, Eq, Debug)] pub enum VideoInfo { VideoInfo(gst_video::VideoInfo), #[cfg(all(target_os = "linux", feature = "dmabuf"))] DmaDrm(gst_video::VideoInfoDmaDrm), } impl From for VideoInfo { fn from(v: gst_video::VideoInfo) -> Self { VideoInfo::VideoInfo(v) } } #[cfg(all(target_os = "linux", feature = "dmabuf"))] impl From for VideoInfo { fn from(v: gst_video::VideoInfoDmaDrm) -> Self { VideoInfo::DmaDrm(v) } } impl ops::Deref for VideoInfo { type Target = gst_video::VideoInfo; fn deref(&self) -> &Self::Target { match self { VideoInfo::VideoInfo(info) => info, #[cfg(all(target_os = "linux", feature = "dmabuf"))] VideoInfo::DmaDrm(info) => info, } } } impl VideoInfo { #[cfg(all(target_os = "linux", feature = "dmabuf"))] fn dma_drm(&self) -> Option<&gst_video::VideoInfoDmaDrm> { match self { VideoInfo::VideoInfo(..) => None, VideoInfo::DmaDrm(info) => Some(info), } } } #[derive(Debug, PartialEq, Eq, Hash)] pub enum TextureCacheId { Memory(usize), #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] GL(usize), #[cfg(all(target_os = "linux", feature = "dmabuf"))] DmaBuf([i32; 4]), } #[derive(Debug)] enum MappedFrame { SysMem { frame: gst_video::VideoFrame, orientation: Orientation, }, #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] GL { frame: gst_gl::GLVideoFrame, wrapped_context: gst_gl::GLContext, orientation: Orientation, }, #[cfg(all(target_os = "linux", feature = "dmabuf"))] DmaBuf { buffer: gst::Buffer, info: gst_video::VideoInfoDmaDrm, n_planes: u32, fds: [i32; 4], offsets: [usize; 4], strides: [usize; 4], width: u32, height: u32, orientation: Orientation, }, } impl MappedFrame { fn buffer(&self) -> &gst::BufferRef { match self { MappedFrame::SysMem { frame, .. } => frame.buffer(), #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] MappedFrame::GL { frame, .. } => frame.buffer(), #[cfg(all(target_os = "linux", feature = "dmabuf"))] MappedFrame::DmaBuf { buffer, .. } => buffer, } } fn width(&self) -> u32 { match self { MappedFrame::SysMem { frame, .. } => frame.width(), #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] MappedFrame::GL { frame, .. } => frame.width(), #[cfg(all(target_os = "linux", feature = "dmabuf"))] MappedFrame::DmaBuf { info, .. } => info.width(), } } fn height(&self) -> u32 { match self { MappedFrame::SysMem { frame, .. } => frame.height(), #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] MappedFrame::GL { frame, .. } => frame.height(), #[cfg(all(target_os = "linux", feature = "dmabuf"))] MappedFrame::DmaBuf { info, .. } => info.height(), } } fn format_info(&self) -> gst_video::VideoFormatInfo { match self { MappedFrame::SysMem { frame, .. } => frame.format_info(), #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] MappedFrame::GL { frame, .. } => frame.format_info(), #[cfg(all(target_os = "linux", feature = "dmabuf"))] MappedFrame::DmaBuf { info, .. } => info.format_info(), } } fn orientation(&self) -> Orientation { match self { MappedFrame::SysMem { orientation, .. } => *orientation, #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] MappedFrame::GL { orientation, .. } => *orientation, #[cfg(all(target_os = "linux", feature = "dmabuf"))] MappedFrame::DmaBuf { orientation, .. } => *orientation, } } } #[derive(Debug)] pub(crate) struct Frame { frame: MappedFrame, overlays: Vec, } #[derive(Debug, Default, glib::Enum, PartialEq, Eq, Copy, Clone)] #[repr(C)] #[enum_type(name = "GstGtk4PaintableSinkOrientation")] pub enum Orientation { #[default] Auto, Rotate0, Rotate90, Rotate180, Rotate270, FlipRotate0, FlipRotate90, FlipRotate180, FlipRotate270, } impl Orientation { pub fn from_tags(tags: &gst::TagListRef) -> Option { let orientation = tags .generic("image-orientation") .and_then(|v| v.get::().ok())?; Some(match orientation.as_str() { "rotate-0" => Orientation::Rotate0, "rotate-90" => Orientation::Rotate90, "rotate-180" => Orientation::Rotate180, "rotate-270" => Orientation::Rotate270, "flip-rotate-0" => Orientation::FlipRotate0, "flip-rotate-90" => Orientation::FlipRotate90, "flip-rotate-180" => Orientation::FlipRotate180, "flip-rotate-270" => Orientation::FlipRotate270, _ => return None, }) } pub fn is_flip_width_height(self) -> bool { matches!( self, Orientation::Rotate90 | Orientation::Rotate270 | Orientation::FlipRotate90 | Orientation::FlipRotate270 ) } } #[derive(Debug)] struct Overlay { frame: gst_video::VideoFrame, x: i32, y: i32, width: u32, height: u32, global_alpha: f32, } #[derive(Debug)] pub(crate) struct Texture { pub texture: gdk::Texture, pub x: f32, pub y: f32, pub width: f32, pub height: f32, pub global_alpha: f32, pub has_alpha: bool, pub orientation: Orientation, } struct FrameWrapper(gst_video::VideoFrame); impl AsRef<[u8]> for FrameWrapper { fn as_ref(&self) -> &[u8] { self.0.plane_data(0).unwrap() } } fn video_format_to_memory_format(f: gst_video::VideoFormat) -> gdk::MemoryFormat { match f { #[cfg(feature = "gtk_v4_14")] gst_video::VideoFormat::Bgrx => gdk::MemoryFormat::B8g8r8x8, #[cfg(feature = "gtk_v4_14")] gst_video::VideoFormat::Xrgb => gdk::MemoryFormat::X8r8g8b8, #[cfg(feature = "gtk_v4_14")] gst_video::VideoFormat::Rgbx => gdk::MemoryFormat::R8g8b8x8, #[cfg(feature = "gtk_v4_14")] gst_video::VideoFormat::Xbgr => gdk::MemoryFormat::X8b8g8r8, gst_video::VideoFormat::Bgra => gdk::MemoryFormat::B8g8r8a8, gst_video::VideoFormat::Argb => gdk::MemoryFormat::A8r8g8b8, gst_video::VideoFormat::Rgba => gdk::MemoryFormat::R8g8b8a8, gst_video::VideoFormat::Abgr => gdk::MemoryFormat::A8b8g8r8, gst_video::VideoFormat::Rgb => gdk::MemoryFormat::R8g8b8, gst_video::VideoFormat::Bgr => gdk::MemoryFormat::B8g8r8, _ => unreachable!(), } } fn video_frame_to_memory_texture( frame: gst_video::VideoFrame, cached_textures: &mut HashMap, used_textures: &mut HashSet, ) -> (gdk::Texture, f64) { let ptr = frame.plane_data(0).unwrap().as_ptr() as usize; let pixel_aspect_ratio = (frame.info().par().numer() as f64) / (frame.info().par().denom() as f64); if let Some(texture) = cached_textures.get(&TextureCacheId::Memory(ptr)) { used_textures.insert(TextureCacheId::Memory(ptr)); return (texture.clone(), pixel_aspect_ratio); } let format = video_format_to_memory_format(frame.format()); let width = frame.width(); let height = frame.height(); let rowstride = frame.plane_stride()[0] as usize; let texture = gdk::MemoryTexture::new( width as i32, height as i32, format, &glib::Bytes::from_owned(FrameWrapper(frame)), rowstride, ) .upcast::(); cached_textures.insert(TextureCacheId::Memory(ptr), texture.clone()); used_textures.insert(TextureCacheId::Memory(ptr)); (texture, pixel_aspect_ratio) } #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] fn video_frame_to_gl_texture( frame: gst_gl::GLVideoFrame, cached_textures: &mut HashMap, used_textures: &mut HashSet, gdk_context: &gdk::GLContext, #[allow(unused)] wrapped_context: &gst_gl::GLContext, ) -> (gdk::Texture, f64) { let texture_id = frame.texture_id(0).expect("Invalid texture id") as usize; let pixel_aspect_ratio = (frame.info().par().numer() as f64) / (frame.info().par().denom() as f64); if let Some(texture) = cached_textures.get(&TextureCacheId::GL(texture_id)) { used_textures.insert(TextureCacheId::GL(texture_id)); return (texture.clone(), pixel_aspect_ratio); } let width = frame.width(); let height = frame.height(); let sync_meta = frame.buffer().meta::().unwrap(); let texture = unsafe { #[cfg(feature = "gtk_v4_12")] { let format = { let format = video_format_to_memory_format(frame.format()); #[cfg(feature = "gtk_v4_14")] { use gtk::prelude::*; if gdk_context.api() != gdk::GLAPI::GLES || gdk_context.version().0 < 3 { // Map alpha formats to the pre-multiplied versions because we pre-multiply // ourselves if not GLES3 with the new GL renderer is used as the GTK GL // backend does not natively support non-premultiplied formats. match format { gdk::MemoryFormat::B8g8r8a8 => gdk::MemoryFormat::B8g8r8a8Premultiplied, gdk::MemoryFormat::A8r8g8b8 => gdk::MemoryFormat::A8r8g8b8Premultiplied, gdk::MemoryFormat::R8g8b8a8 => gdk::MemoryFormat::R8g8b8a8Premultiplied, gdk::MemoryFormat::A8b8g8r8 => gdk::MemoryFormat::A8r8g8b8Premultiplied, gdk::MemoryFormat::R8g8b8 | gdk::MemoryFormat::B8g8r8 => format, gdk::MemoryFormat::B8g8r8x8 | gdk::MemoryFormat::X8r8g8b8 | gdk::MemoryFormat::R8g8b8x8 | gdk::MemoryFormat::X8b8g8r8 => format, _ => unreachable!(), } } else { format } } #[cfg(not(feature = "gtk_v4_14"))] { // Map alpha formats to the pre-multiplied versions because we pre-multiply // ourselves in pre-4.14 versions as the GTK GL backend does not natively // support non-premultiplied formats match format { gdk::MemoryFormat::B8g8r8a8 => gdk::MemoryFormat::B8g8r8a8Premultiplied, gdk::MemoryFormat::A8r8g8b8 => gdk::MemoryFormat::A8r8g8b8Premultiplied, gdk::MemoryFormat::R8g8b8a8 => gdk::MemoryFormat::R8g8b8a8Premultiplied, gdk::MemoryFormat::A8b8g8r8 => gdk::MemoryFormat::A8r8g8b8Premultiplied, gdk::MemoryFormat::R8g8b8 | gdk::MemoryFormat::B8g8r8 => format, _ => unreachable!(), } } }; let sync_point = (*sync_meta.as_ptr()).data; gdk::GLTextureBuilder::new() .set_context(Some(gdk_context)) .set_id(texture_id as u32) .set_width(width as i32) .set_height(height as i32) .set_format(format) .set_sync(Some(sync_point)) .build_with_release_func(move || { // Unmap and drop the GStreamer GL texture once GTK is done with it and not earlier drop(frame); }) } #[cfg(not(feature = "gtk_v4_12"))] { sync_meta.wait(wrapped_context); gdk::GLTexture::with_release_func( gdk_context, texture_id as u32, width as i32, height as i32, move || { // Unmap and drop the GStreamer GL texture once GTK is done with it and not earlier drop(frame); }, ) } .upcast::() }; cached_textures.insert(TextureCacheId::GL(texture_id), texture.clone()); used_textures.insert(TextureCacheId::GL(texture_id)); (texture, pixel_aspect_ratio) } #[cfg(all(target_os = "linux", feature = "dmabuf"))] #[allow(clippy::too_many_arguments)] fn video_frame_to_dmabuf_texture( buffer: gst::Buffer, cached_textures: &mut HashMap, used_textures: &mut HashSet, info: &gst_video::VideoInfoDmaDrm, n_planes: u32, fds: &[i32; 4], offsets: &[usize; 4], strides: &[usize; 4], width: u32, height: u32, ) -> Result<(gdk::Texture, f64), glib::Error> { let pixel_aspect_ratio = (info.par().numer() as f64) / (info.par().denom() as f64); if let Some(texture) = cached_textures.get(&TextureCacheId::DmaBuf(*fds)) { used_textures.insert(TextureCacheId::DmaBuf(*fds)); return Ok((texture.clone(), pixel_aspect_ratio)); } let builder = gdk::DmabufTextureBuilder::new(); builder.set_display(&gdk::Display::default().unwrap()); builder.set_fourcc(info.fourcc()); builder.set_modifier(info.modifier()); builder.set_width(width); builder.set_height(height); builder.set_n_planes(n_planes); for plane in 0..(n_planes as usize) { builder.set_fd(plane as u32, fds[plane]); builder.set_offset(plane as u32, offsets[plane] as u32); builder.set_stride(plane as u32, strides[plane] as u32); } let texture = unsafe { builder.build_with_release_func(move || { drop(buffer); })? }; cached_textures.insert(TextureCacheId::DmaBuf(*fds), texture.clone()); used_textures.insert(TextureCacheId::DmaBuf(*fds)); Ok((texture, pixel_aspect_ratio)) } impl Frame { pub(crate) fn into_textures( self, #[allow(unused_variables)] gdk_context: Option<&gdk::GLContext>, cached_textures: &mut HashMap, ) -> Result, glib::Error> { let mut textures = Vec::with_capacity(1 + self.overlays.len()); let mut used_textures = HashSet::with_capacity(1 + self.overlays.len()); let width = self.frame.width(); let height = self.frame.height(); let has_alpha = self.frame.format_info().has_alpha(); let orientation = self.frame.orientation(); let (texture, pixel_aspect_ratio) = match self.frame { MappedFrame::SysMem { frame, .. } => { video_frame_to_memory_texture(frame, cached_textures, &mut used_textures) } #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] MappedFrame::GL { frame, wrapped_context, .. } => { let Some(gdk_context) = gdk_context else { // This will fail badly if the video frame was actually mapped as GL texture // but this case can't really happen as we only do that if we actually have a // GDK GL context. unreachable!(); }; video_frame_to_gl_texture( frame, cached_textures, &mut used_textures, gdk_context, &wrapped_context, ) } #[cfg(all(target_os = "linux", feature = "dmabuf"))] MappedFrame::DmaBuf { buffer, info, n_planes, fds, offsets, strides, width, height, .. } => video_frame_to_dmabuf_texture( buffer, cached_textures, &mut used_textures, &info, n_planes, &fds, &offsets, &strides, width, height, )?, }; textures.push(Texture { texture, x: 0.0, y: 0.0, width: width as f32 * pixel_aspect_ratio as f32, height: height as f32, global_alpha: 1.0, has_alpha, orientation, }); for overlay in self.overlays { let has_alpha = overlay.frame.format_info().has_alpha(); let (texture, _pixel_aspect_ratio) = video_frame_to_memory_texture(overlay.frame, cached_textures, &mut used_textures); textures.push(Texture { texture, x: overlay.x as f32, y: overlay.y as f32, width: overlay.width as f32, height: overlay.height as f32, global_alpha: overlay.global_alpha, has_alpha, orientation: Orientation::Rotate0, }); } // Remove textures that were not used this time cached_textures.retain(|id, _| used_textures.contains(id)); Ok(textures) } } impl Frame { pub(crate) fn new( buffer: &gst::Buffer, info: &VideoInfo, orientation: Orientation, #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] wrapped_context: Option< &gst_gl::GLContext, >, #[allow(unused_variables)] #[cfg(not(any(target_os = "macos", target_os = "windows", feature = "gst-gl")))] wrapped_context: Option<&()>, ) -> Result { // Empty buffers get filtered out in show_frame debug_assert!(buffer.n_memory() > 0); #[allow(unused_mut)] let mut frame = None; { // Check we received a buffer with dmabuf memory and if so do some checks before // passing it onwards #[cfg(all(target_os = "linux", feature = "dmabuf"))] if frame.is_none() && buffer .peek_memory(0) .is_memory_type::() { if let Some((vmeta, info)) = Option::zip(buffer.meta::(), info.dma_drm()) { let mut fds = [-1i32; 4]; let mut offsets = [0; 4]; let mut strides = [0; 4]; let n_planes = vmeta.n_planes() as usize; let vmeta_offsets = vmeta.offset(); let vmeta_strides = vmeta.stride(); for plane in 0..n_planes { let Some((range, skip)) = buffer.find_memory(vmeta_offsets[plane]..(vmeta_offsets[plane] + 1)) else { break; }; let mem = buffer.peek_memory(range.start); let Some(mem) = mem.downcast_memory_ref::() else { break; }; let fd = mem.fd(); fds[plane] = fd; offsets[plane] = mem.offset() + skip; strides[plane] = vmeta_strides[plane] as usize; } // All fds valid? if fds[0..n_planes].iter().all(|fd| *fd != -1) { frame = Some(MappedFrame::DmaBuf { buffer: buffer.clone(), info: info.clone(), n_planes: n_planes as u32, fds, offsets, strides, width: vmeta.width(), height: vmeta.height(), orientation, }); } } } #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] { if frame.is_none() { // Check we received a buffer with GL memory and if the context of that memory // can share with the wrapped context around the GDK GL context. // // If not it has to be uploaded to the GPU. let memory_ctx = buffer .peek_memory(0) .downcast_memory_ref::() .and_then(|m| { let ctx = m.context(); if wrapped_context .is_some_and(|wrapped_context| wrapped_context.can_share(ctx)) { Some(ctx) } else { None } }); if let Some(memory_ctx) = memory_ctx { // If there is no GLSyncMeta yet then we need to add one here now, which requires // obtaining a writable buffer. let mapped_frame = if buffer.meta::().is_some() { gst_gl::GLVideoFrame::from_buffer_readable(buffer.clone(), info) .map_err(|_| gst::FlowError::Error)? } else { let mut buffer = buffer.clone(); { let buffer = buffer.make_mut(); gst_gl::GLSyncMeta::add(buffer, memory_ctx); } gst_gl::GLVideoFrame::from_buffer_readable(buffer, info) .map_err(|_| gst::FlowError::Error)? }; // Now that it's guaranteed that there is a sync meta and the frame is mapped, set // a sync point so we can ensure that the texture is ready later when making use of // it as gdk::GLTexture. let meta = mapped_frame.buffer().meta::().unwrap(); meta.set_sync_point(memory_ctx); frame = Some(MappedFrame::GL { frame: mapped_frame, wrapped_context: wrapped_context.unwrap().clone(), orientation, }); } } } } let mut frame = Self { frame: match frame { Some(frame) => frame, None => MappedFrame::SysMem { frame: gst_video::VideoFrame::from_buffer_readable(buffer.clone(), info) .map_err(|_| gst::FlowError::Error)?, orientation, }, }, overlays: vec![], }; frame.overlays = frame .frame .buffer() .iter_meta::() .flat_map(|meta| { meta.overlay() .iter() .filter_map(|rect| { let buffer = rect .pixels_unscaled_argb(gst_video::VideoOverlayFormatFlags::GLOBAL_ALPHA); let (x, y, width, height) = rect.render_rectangle(); let global_alpha = rect.global_alpha(); let vmeta = buffer.meta::().unwrap(); let info = gst_video::VideoInfo::builder( vmeta.format(), vmeta.width(), vmeta.height(), ) .build() .unwrap(); let frame = gst_video::VideoFrame::from_buffer_readable(buffer, &info).ok()?; Some(Overlay { frame, x, y, width, height, global_alpha, }) }) .collect::>() }) .collect(); Ok(frame) } } gst-plugin-gtk4-0.13.6/src/sink/imp.rs000064400000000000000000001432351046102023000155510ustar 00000000000000// // Copyright (C) 2021 Bilal Elmoussaoui // Copyright (C) 2021 Jordan Petridis // Copyright (C) 2021-2024 Sebastian Dröge // // This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at // . // // SPDX-License-Identifier: MPL-2.0 use super::{frame, SinkEvent}; use crate::sink::frame::Frame; use crate::sink::paintable::Paintable; use glib::thread_guard::ThreadGuard; use gtk::glib; use gtk::prelude::*; #[cfg(any( target_os = "macos", target_os = "windows", feature = "gst-gl", feature = "dmabuf" ))] use gtk::gdk; #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] use gst_gl::prelude::GLContextExt as GstGLContextExt; #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] use gst_gl::prelude::*; #[allow(unused_imports)] use gst::prelude::*; use gst::subclass::prelude::*; use gst_base::subclass::prelude::*; #[allow(unused_imports)] use gst_video::{prelude::*, subclass::prelude::*}; use once_cell::sync::Lazy; use std::sync::{ atomic::{AtomicBool, Ordering}, Mutex, MutexGuard, }; use crate::utils; // Global GL context that is created by the first sink and kept around until the end of the // process. This is provided to other elements in the pipeline to make sure they create GL contexts // that are sharing with the GTK GL context. #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] enum GLContext { Uninitialized, Unsupported, Initialized { display: gst_gl::GLDisplay, wrapped_context: gst_gl::GLContext, gdk_context: ThreadGuard, }, } #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] static GL_CONTEXT: Mutex = Mutex::new(GLContext::Uninitialized); pub(crate) static CAT: Lazy = Lazy::new(|| { gst::DebugCategory::new( "gtk4paintablesink", gst::DebugColorFlags::empty(), Some("GTK4 Paintable sink"), ) }); struct StreamConfig { info: Option, /// Orientation from a global scope tag global_orientation: frame::Orientation, /// Orientation from a stream scope tag stream_orientation: Option, } impl Default for StreamConfig { fn default() -> Self { StreamConfig { info: None, global_orientation: frame::Orientation::Rotate0, stream_orientation: None, } } } #[derive(Default)] pub struct PaintableSink { paintable: Mutex>>, window: Mutex>>, config: Mutex, sender: Mutex>>, pending_frame: Mutex>, cached_caps: Mutex>, settings: Mutex, window_resized: AtomicBool, } #[derive(Default)] struct Settings { window_width: u32, window_height: u32, } impl Drop for PaintableSink { fn drop(&mut self) { let mut paintable = self.paintable.lock().unwrap(); if let Some(paintable) = paintable.take() { glib::MainContext::default().invoke(|| drop(paintable)) } } } #[glib::object_subclass] impl ObjectSubclass for PaintableSink { const NAME: &'static str = "GstGtk4PaintableSink"; type Type = super::PaintableSink; type ParentType = gst_video::VideoSink; type Interfaces = (gst::ChildProxy,); } impl ObjectImpl for PaintableSink { fn properties() -> &'static [glib::ParamSpec] { static PROPERTIES: Lazy> = Lazy::new(|| { vec![ glib::ParamSpecObject::builder::("paintable") .nick("Paintable") .blurb("The Paintable the sink renders to") .read_only() .build(), glib::ParamSpecUInt::builder("window-width") .nick("Window width") .blurb("the width of the main widget rendering the paintable") .mutable_playing() .build(), glib::ParamSpecUInt::builder("window-height") .nick("Window height") .blurb("the height of the main widget rendering the paintable") .mutable_playing() .build(), ] }); PROPERTIES.as_ref() } fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { match pspec.name() { "paintable" => { // Fix segfault when GTK3 and GTK4 are loaded (e.g. `gst-inspect-1.0 -a`) // checking if GtkBin is registered to know if libgtk3.so is already present // GtkBin was dropped for GTK4 https://gitlab.gnome.org/GNOME/gtk/-/commit/3c165b3b77 if glib::types::Type::from_name("GtkBin").is_some() { gst::error!(CAT, imp = self, "Skipping the creation of paintable to avoid segfault between GTK3 and GTK4"); return None::<&Paintable>.to_value(); } let mut paintable_guard = self.paintable.lock().unwrap(); let mut created = false; if paintable_guard.is_none() { created = true; self.create_paintable(&mut paintable_guard); } let paintable = match &*paintable_guard { Some(ref paintable) => paintable, None => { gst::error!(CAT, imp = self, "Failed to create paintable"); return None::<&Paintable>.to_value(); } }; // Getter must be called from the main thread if !paintable.is_owner() { gst::error!( CAT, imp = self, "Can't retrieve Paintable from non-main thread" ); return None::<&Paintable>.to_value(); } let paintable = paintable.get_ref().clone(); drop(paintable_guard); if created { let self_ = self.to_owned(); glib::MainContext::default().invoke(move || { let paintable_guard = self_.paintable.lock().unwrap(); if let Some(paintable) = &*paintable_guard { let paintable_clone = paintable.get_ref().clone(); drop(paintable_guard); self_.obj().child_added(&paintable_clone, "paintable"); } }); } paintable.to_value() } "window-width" => { let settings = self.settings.lock().unwrap(); settings.window_width.to_value() } "window-height" => { let settings = self.settings.lock().unwrap(); settings.window_height.to_value() } _ => unimplemented!(), } } fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { match pspec.name() { "window-width" => { let mut settings = self.settings.lock().unwrap(); let value = value.get().expect("type checked upstream"); if settings.window_width != value { self.window_resized.store(true, Ordering::SeqCst); } settings.window_width = value; } "window-height" => { let mut settings = self.settings.lock().unwrap(); let value = value.get().expect("type checked upstream"); if settings.window_height != value { self.window_resized.store(true, Ordering::SeqCst); } settings.window_height = value; } _ => unimplemented!(), } } } impl GstObjectImpl for PaintableSink {} impl ElementImpl for PaintableSink { fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { static ELEMENT_METADATA: Lazy = Lazy::new(|| { gst::subclass::ElementMetadata::new( "GTK 4 Paintable Sink", "Sink/Video", "A GTK 4 Paintable sink", "Bilal Elmoussaoui , Jordan Petridis , Sebastian Dröge ", ) }); Some(&*ELEMENT_METADATA) } fn pad_templates() -> &'static [gst::PadTemplate] { static PAD_TEMPLATES: Lazy> = Lazy::new(|| { // Those are the supported formats by a gdk::Texture let mut caps = gst::Caps::new_empty(); { let caps = caps.get_mut().unwrap(); #[cfg(all(target_os = "linux", feature = "dmabuf"))] { for features in [ [ gst_allocators::CAPS_FEATURE_MEMORY_DMABUF, gst_video::CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, ] .as_slice(), [gst_allocators::CAPS_FEATURE_MEMORY_DMABUF].as_slice(), ] { let c = gst_video::VideoCapsBuilder::new() .format(gst_video::VideoFormat::DmaDrm) .features(features.iter().copied()) .build(); caps.append(c); } } for features in [ #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] Some(gst::CapsFeatures::new([ gst_gl::CAPS_FEATURE_MEMORY_GL_MEMORY, gst_video::CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, ])), #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] Some(gst::CapsFeatures::new([ gst_gl::CAPS_FEATURE_MEMORY_GL_MEMORY, ])), Some(gst::CapsFeatures::new([ "memory:SystemMemory", gst_video::CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, ])), Some(gst::CapsFeatures::new([ gst_video::CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, ])), None, ] { #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] { const GL_FORMATS: &[gst_video::VideoFormat] = &[gst_video::VideoFormat::Rgba, gst_video::VideoFormat::Rgb]; const NON_GL_FORMATS: &[gst_video::VideoFormat] = &[ #[cfg(feature = "gtk_v4_14")] gst_video::VideoFormat::Bgrx, #[cfg(feature = "gtk_v4_14")] gst_video::VideoFormat::Xrgb, #[cfg(feature = "gtk_v4_14")] gst_video::VideoFormat::Rgbx, #[cfg(feature = "gtk_v4_14")] gst_video::VideoFormat::Xbgr, gst_video::VideoFormat::Bgra, gst_video::VideoFormat::Argb, gst_video::VideoFormat::Rgba, gst_video::VideoFormat::Abgr, gst_video::VideoFormat::Rgb, gst_video::VideoFormat::Bgr, ]; let formats = if features.as_ref().is_some_and(|features| { features.contains(gst_gl::CAPS_FEATURE_MEMORY_GL_MEMORY) }) { GL_FORMATS } else { NON_GL_FORMATS }; let mut c = gst_video::video_make_raw_caps(formats).build(); if let Some(features) = features { let c = c.get_mut().unwrap(); if features.contains(gst_gl::CAPS_FEATURE_MEMORY_GL_MEMORY) { c.set("texture-target", "2D") } c.set_features_simple(Some(features)); } caps.append(c); } #[cfg(not(any( target_os = "macos", target_os = "windows", feature = "gst-gl" )))] { const FORMATS: &[gst_video::VideoFormat] = &[ gst_video::VideoFormat::Bgra, gst_video::VideoFormat::Argb, gst_video::VideoFormat::Rgba, gst_video::VideoFormat::Abgr, gst_video::VideoFormat::Rgb, gst_video::VideoFormat::Bgr, ]; let mut c = gst_video::video_make_raw_caps(FORMATS).build(); if let Some(features) = features { let c = c.get_mut().unwrap(); c.set_features_simple(Some(features)); } caps.append(c); } } } vec![gst::PadTemplate::new( "sink", gst::PadDirection::Sink, gst::PadPresence::Always, &caps, ) .unwrap()] }); PAD_TEMPLATES.as_ref() } #[allow(clippy::single_match)] fn change_state( &self, transition: gst::StateChange, ) -> Result { match transition { gst::StateChange::NullToReady => { let create_window = glib::program_name().as_deref() == Some("gst-launch-1.0") || glib::program_name().as_deref() == Some("gst-play-1.0") || std::env::var("GST_GTK4_WINDOW").as_deref() == Ok("1"); if create_window { let res = utils::invoke_on_main_thread(gtk::init); if let Err(err) = res { gst::error!(CAT, imp = self, "Failed to create initialize GTK: {err}"); return Err(gst::StateChangeError); } } let mut paintable_guard = self.paintable.lock().unwrap(); let mut created = false; if paintable_guard.is_none() { created = true; self.create_paintable(&mut paintable_guard); } if paintable_guard.is_none() { gst::error!(CAT, imp = self, "Failed to create paintable"); return Err(gst::StateChangeError); } drop(paintable_guard); if created { let self_ = self.to_owned(); glib::MainContext::default().invoke(move || { let paintable_guard = self_.paintable.lock().unwrap(); if let Some(paintable) = &*paintable_guard { let paintable_clone = paintable.get_ref().clone(); drop(paintable_guard); self_.obj().child_added(&paintable_clone, "paintable"); } }); } // Notify the pipeline about the GL display and wrapped context so that any other // elements in the pipeline ideally use the same / create GL contexts that are // sharing with this one. #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] { let gl_context = GL_CONTEXT.lock().unwrap(); if let GLContext::Initialized { display, wrapped_context, .. } = &*gl_context { let display = display.clone(); let wrapped_context = wrapped_context.clone(); drop(gl_context); gst_gl::gl_element_propagate_display_context(&*self.obj(), &display); let mut ctx = gst::Context::new("gst.gl.app_context", true); { let ctx = ctx.get_mut().unwrap(); ctx.structure_mut().set("context", &wrapped_context); } let _ = self.obj().post_message( gst::message::HaveContext::builder(ctx) .src(&*self.obj()) .build(), ); } } if create_window { self.create_window(); } } _ => (), } let res = self.parent_change_state(transition); match transition { gst::StateChange::PausedToReady => { *self.config.lock().unwrap() = StreamConfig::default(); let _ = self.pending_frame.lock().unwrap().take(); // Flush frames from the GDK paintable but don't wait // for this to finish as this can other deadlock. let self_ = self.to_owned(); glib::MainContext::default().invoke(move || { let paintable = self_.paintable.lock().unwrap(); if let Some(paintable) = &*paintable { paintable.get_ref().handle_flush_frames(); } }); } gst::StateChange::ReadyToNull => { let mut window_guard = self.window.lock().unwrap(); if let Some(window) = window_guard.take() { drop(window_guard); glib::MainContext::default().invoke(move || { let window = window.get_ref(); window.close(); }); } } _ => (), } res } } impl BaseSinkImpl for PaintableSink { fn caps(&self, filter: Option<&gst::Caps>) -> Option { let cached_caps = self .cached_caps .lock() .expect("Failed to lock cached caps mutex") .clone(); let mut tmp_caps = cached_caps.unwrap_or_else(|| { let templ = Self::pad_templates(); templ[0].caps().clone() }); gst::debug!(CAT, imp = self, "Advertising our own caps: {tmp_caps:?}"); if let Some(filter_caps) = filter { gst::debug!( CAT, imp = self, "Intersecting with filter caps: {filter_caps:?}", ); tmp_caps = filter_caps.intersect_with_mode(&tmp_caps, gst::CapsIntersectMode::First); }; gst::debug!(CAT, imp = self, "Returning caps: {tmp_caps:?}"); Some(tmp_caps) } fn set_caps(&self, caps: &gst::Caps) -> Result<(), gst::LoggableError> { gst::debug!(CAT, imp = self, "Setting caps {caps:?}"); #[allow(unused_mut)] let mut video_info = None; #[cfg(all(target_os = "linux", feature = "dmabuf"))] { if let Ok(info) = gst_video::VideoInfoDmaDrm::from_caps(caps) { video_info = Some(info.into()); } } let video_info = match video_info { Some(info) => info, None => gst_video::VideoInfo::from_caps(caps) .map_err(|_| gst::loggable_error!(CAT, "Invalid caps"))? .into(), }; self.config.lock().unwrap().info = Some(video_info); Ok(()) } fn propose_allocation( &self, query: &mut gst::query::Allocation, ) -> Result<(), gst::LoggableError> { gst::debug!(CAT, imp = self, "Proposing Allocation query"); self.parent_propose_allocation(query)?; query.add_allocation_meta::(None); let s = { let settings = self.settings.lock().unwrap(); if (settings.window_width, settings.window_height) != (0, 0) { gst::debug!( CAT, imp = self, "answering alloc query with size {}x{}", settings.window_width, settings.window_height ); self.window_resized.store(false, Ordering::SeqCst); Some( gst::Structure::builder("GstVideoOverlayCompositionMeta") .field("width", settings.window_width) .field("height", settings.window_height) .build(), ) } else { None } }; query.add_allocation_meta::(s.as_deref()); #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] { if let GLContext::Initialized { wrapped_context, .. } = &*GL_CONTEXT.lock().unwrap() { if wrapped_context.check_feature("GL_ARB_sync") || wrapped_context.check_feature("GL_EXT_EGL_sync") { query.add_allocation_meta::(None) } } } Ok(()) } fn query(&self, query: &mut gst::QueryRef) -> bool { gst::log!(CAT, imp = self, "Handling query {:?}", query); match query.view_mut() { #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] gst::QueryViewMut::Context(q) => { // Avoid holding the locks while we respond to the query // The objects are ref-counted anyway. let mut display_clone = None; let mut wrapped_context_clone = None; if let GLContext::Initialized { display, wrapped_context, .. } = &*GL_CONTEXT.lock().unwrap() { display_clone = Some(display.clone()); wrapped_context_clone = Some(wrapped_context.clone()); } if let (Some(display), Some(wrapped_context)) = (display_clone, wrapped_context_clone) { return gst_gl::functions::gl_handle_context_query( &*self.obj(), q, Some(&display), None::<&gst_gl::GLContext>, Some(&wrapped_context), ); } BaseSinkImplExt::parent_query(self, query) } _ => BaseSinkImplExt::parent_query(self, query), } } fn event(&self, event: gst::Event) -> bool { match event.view() { gst::EventView::StreamStart(_) => { let mut config = self.config.lock().unwrap(); config.global_orientation = frame::Orientation::Rotate0; config.stream_orientation = None; } gst::EventView::Tag(ev) => { let mut config = self.config.lock().unwrap(); let tags = ev.tag(); let scope = tags.scope(); let orientation = frame::Orientation::from_tags(tags); if scope == gst::TagScope::Global { config.global_orientation = orientation.unwrap_or(frame::Orientation::Rotate0); } else { config.stream_orientation = orientation; } } _ => (), } self.parent_event(event) } } impl VideoSinkImpl for PaintableSink { fn show_frame(&self, buffer: &gst::Buffer) -> Result { gst::trace!(CAT, imp = self, "Rendering buffer {:?}", buffer); if self.window_resized.swap(false, Ordering::SeqCst) { gst::debug!(CAT, imp = self, "Window size changed, needs to reconfigure"); let obj = self.obj(); let sink = obj.sink_pad(); sink.push_event(gst::event::Reconfigure::builder().build()); } // Empty buffer, nothing to render if buffer.n_memory() == 0 { gst::trace!( CAT, imp = self, "Empty buffer, nothing to render. Returning." ); return Ok(gst::FlowSuccess::Ok); }; let config = self.config.lock().unwrap(); let info = config.info.as_ref().ok_or_else(|| { gst::error!(CAT, imp = self, "Received no caps yet"); gst::FlowError::NotNegotiated })?; let orientation = config .stream_orientation .unwrap_or(config.global_orientation); let wrapped_context = { #[cfg(not(any(target_os = "macos", target_os = "windows", feature = "gst-gl")))] { None } #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] { let gl_context = GL_CONTEXT.lock().unwrap(); if let GLContext::Initialized { wrapped_context, .. } = &*gl_context { Some(wrapped_context.clone()) } else { None } } }; let frame = Frame::new(buffer, info, orientation, wrapped_context.as_ref()).map_err(|err| { gst::error!(CAT, imp = self, "Failed to map video frame"); err })?; self.pending_frame.lock().unwrap().replace(frame); let sender = self.sender.lock().unwrap(); let sender = sender.as_ref().ok_or_else(|| { gst::error!(CAT, imp = self, "Have no main thread sender"); gst::FlowError::Flushing })?; match sender.try_send(SinkEvent::FrameChanged) { Ok(_) => (), Err(async_channel::TrySendError::Full(_)) => { gst::warning!(CAT, imp = self, "Have too many pending frames"); } Err(async_channel::TrySendError::Closed(_)) => { gst::error!(CAT, imp = self, "Have main thread receiver shut down"); return Err(gst::FlowError::Flushing); } } Ok(gst::FlowSuccess::Ok) } } impl PaintableSink { fn pending_frame(&self) -> Option { self.pending_frame.lock().unwrap().take() } fn do_action(&self, action: SinkEvent) -> glib::ControlFlow { let paintable = self.paintable.lock().unwrap(); let paintable = match &*paintable { Some(paintable) => paintable, None => return glib::ControlFlow::Break, }; match action { SinkEvent::FrameChanged => { let Some(frame) = self.pending_frame() else { return glib::ControlFlow::Continue; }; gst::trace!(CAT, imp = self, "Frame changed"); paintable.get_ref().handle_frame_changed(&self.obj(), frame); } } glib::ControlFlow::Continue } fn configure_caps(&self) { #[allow(unused_mut)] let mut tmp_caps = Self::pad_templates()[0].caps().clone(); #[cfg(all(target_os = "linux", feature = "dmabuf"))] { let formats = utils::invoke_on_main_thread(move || { let Some(display) = gdk::Display::default() else { return vec![]; }; let dmabuf_formats = display.dmabuf_formats(); let mut formats = vec![]; let n_formats = dmabuf_formats.n_formats(); for i in 0..n_formats { let (fourcc, modifier) = dmabuf_formats.format(i); if fourcc == 0 || modifier == (u64::MAX >> 8) { continue; } formats.push(gst_video::dma_drm_fourcc_to_string(fourcc, modifier)); } formats }); if formats.is_empty() { // Filter out dmabufs caps from the template pads if we have no supported formats tmp_caps = tmp_caps .iter_with_features() .filter(|(_, features)| { !features.contains(gst_allocators::CAPS_FEATURE_MEMORY_DMABUF) }) .map(|(s, c)| (s.to_owned(), c.to_owned())) .collect::(); } else { let tmp_caps = tmp_caps.make_mut(); for (s, f) in tmp_caps.iter_with_features_mut() { if f.contains(gst_allocators::CAPS_FEATURE_MEMORY_DMABUF) { s.set("drm-format", gst::List::new(&formats)); } } } } #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] { // Filter out GL caps from the template pads if we have no context if !matches!(&*GL_CONTEXT.lock().unwrap(), GLContext::Initialized { .. }) { tmp_caps = tmp_caps .iter_with_features() .filter(|(_, features)| { !features.contains(gst_gl::CAPS_FEATURE_MEMORY_GL_MEMORY) }) .map(|(s, c)| (s.to_owned(), c.to_owned())) .collect::(); } } self.cached_caps .lock() .expect("Failed to lock Mutex") .replace(tmp_caps); } fn create_window(&self) { let self_ = self.to_owned(); glib::MainContext::default().invoke(move || { let mut window_guard = self_.window.lock().unwrap(); if window_guard.is_some() { return; } let window = gtk::Window::new(); let gst_widget = crate::RenderWidget::new(self_.obj().as_ref().upcast_ref()); window.set_child(Some(&gst_widget)); window.set_default_size(640, 480); if std::env::var("GST_GTK4_WINDOW_FULLSCREEN").as_deref() == Ok("1") { window.set_fullscreened(true); } window.connect_close_request({ let self_ = self_.clone(); move |_window| { if self_.window.lock().unwrap().is_some() { gst::element_imp_error!( self_, gst::ResourceError::NotFound, ("Output window was closed") ); } glib::Propagation::Proceed } }); #[cfg(feature = "gtk_v4_10")] window.set_visible(true); #[cfg(not(feature = "gtk_v4_10"))] window.show(); *window_guard = Some(ThreadGuard::new(window)); }); } fn create_paintable(&self, paintable_storage: &mut MutexGuard>>) { #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] { self.initialize_gl_context(); } self.configure_caps(); self.initialize_paintable(paintable_storage); } fn initialize_paintable( &self, paintable_storage: &mut MutexGuard>>, ) { gst::debug!(CAT, imp = self, "Initializing paintable"); // The channel for the SinkEvents let (sender, receiver) = async_channel::bounded(3); // Spawn an async task on the main context to handle the channel messages let main_context = glib::MainContext::default(); let self_ = self.downgrade(); main_context.spawn(async move { while let Ok(action) = receiver.recv().await { let Some(self_) = self_.upgrade() else { break; }; self_.do_action(action); } }); // Create the paintable from the main thread let paintable = utils::invoke_on_main_thread(move || { #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] { let gdk_context = if let GLContext::Initialized { gdk_context, .. } = &*GL_CONTEXT.lock().unwrap() { Some(gdk_context.get_ref().clone()) } else { None }; ThreadGuard::new(Paintable::new(gdk_context)) } #[cfg(not(any(target_os = "macos", target_os = "windows", feature = "gst-gl")))] { ThreadGuard::new(Paintable::new(None)) } }); **paintable_storage = Some(paintable); *self.sender.lock().unwrap() = Some(sender); } #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] fn initialize_gl_context(&self) { gst::debug!(CAT, imp = self, "Realizing GDK GL Context"); let self_ = self.to_owned(); utils::invoke_on_main_thread(move || { self_.initialize_gl_context_main(); }); } #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] fn initialize_gl_context_main(&self) { gst::debug!(CAT, imp = self, "Realizing GDK GL Context from main thread"); let mut gl_context_guard = GL_CONTEXT.lock().unwrap(); if !matches!(&*gl_context_guard, GLContext::Uninitialized) { gst::debug!(CAT, imp = self, "Already initialized GL context before"); return; } *gl_context_guard = GLContext::Unsupported; // This can return NULL but only happens in 2 situations: // * If the function is called before gtk_init // * If the function is called after gdk_display_close(default_display) // Both of which are treated as programming errors. // // However, when we are building the docs, gtk_init doesn't get called // and this would cause the documentation generation to error. // Thus its okayish to return None here and fallback to software // rendering, since this path isn't going to be used by applications // anyway. // // FIXME: add a couple more gtk_init checks across the codebase where // applicable since this is no longer going to panic. let gdk_display = match gdk::Display::default() { Some(display) => display, None => { gst::warning!(CAT, imp = self, "Failed to retrieve GDK display"); return; } }; let gdk_context = match gdk_display.create_gl_context() { Ok(gdk_context) => gdk_context, Err(err) => { gst::warning!(CAT, imp = self, "Failed to create GDK GL Context: {err}"); return; } }; match gdk_context.type_().name() { #[cfg(feature = "x11egl")] "GdkX11GLContextEGL" => (), #[cfg(feature = "x11glx")] "GdkX11GLContextGLX" => (), #[cfg(feature = "waylandegl")] "GdkWaylandGLContext" => (), #[cfg(target_os = "macos")] "GdkMacosGLContext" => (), #[cfg(target_os = "windows")] "GdkWin32GLContextWGL" => (), #[cfg(all(windows, feature = "winegl"))] "GdkWin32GLContextEGL" => (), display => { gst::error!(CAT, imp = self, "Unsupported GDK display {display} for GL"); return; } } gst::info!(CAT, imp = self, "Realizing GDK GL Context",); if let Err(err) = gdk_context.realize() { gst::warning!(CAT, imp = self, "Failed to realize GDK GL Context: {err}"); return; } gst::info!(CAT, imp = self, "Successfully realized GDK GL Context"); gdk_context.make_current(); let res = match gdk_context.type_().name() { #[cfg(feature = "x11egl")] "GdkX11GLContextEGL" => self.initialize_x11egl(gdk_display), #[cfg(feature = "x11glx")] "GdkX11GLContextGLX" => self.initialize_x11glx(gdk_display), #[cfg(feature = "waylandegl")] "GdkWaylandGLContext" => self.initialize_waylandegl(gdk_display), #[cfg(target_os = "macos")] "GdkMacosGLContext" => self.initialize_macosgl(gdk_display), #[cfg(target_os = "windows")] "GdkWin32GLContextWGL" => self.initialize_wgl(gdk_display, &gdk_context), #[cfg(all(target_os = "windows", feature = "winegl"))] "GdkWin32GLContextEGL" => self.initialize_winegl(gdk_display), display_type => { unreachable!("Unsupported GDK display {display_type} for GL"); } }; let (display, wrapped_context) = res.unwrap(); match wrapped_context.activate(true) { Ok(_) => gst::info!(CAT, imp = self, "Successfully activated GL Context"), Err(_) => { gst::error!(CAT, imp = self, "Failed to activate GL context",); return; } }; if let Err(err) = wrapped_context.fill_info() { gst::error!( CAT, imp = self, "Failed to fill info on the GL Context: {err}", ); // Deactivate the context upon failure if wrapped_context.activate(false).is_err() { gst::error!( CAT, imp = self, "Failed to deactivate the context after failing fill info", ); } return; } gst::info!(CAT, imp = self, "Successfully initialized GL Context"); *gl_context_guard = GLContext::Initialized { display, wrapped_context, gdk_context: ThreadGuard::new(gdk_context), }; } #[cfg(feature = "x11egl")] fn initialize_x11egl( &self, display: gdk::Display, ) -> Option<(gst_gl::GLDisplay, gst_gl::GLContext)> { gst::info!( CAT, imp = self, "Initializing GL for x11 EGL backend and display" ); let platform = gst_gl::GLPlatform::EGL; let (gl_api, _, _) = gst_gl::GLContext::current_gl_api(platform); let gl_ctx = gst_gl::GLContext::current_gl_context(platform); if gl_ctx == 0 { gst::error!(CAT, imp = self, "Failed to get handle from GdkGLContext"); return None; } // FIXME: bindings unsafe { use glib::translate::*; let display = display.downcast::().unwrap(); let x11_display = gdk_x11::ffi::gdk_x11_display_get_egl_display(display.to_glib_none().0); if x11_display.is_null() { gst::error!(CAT, imp = self, "Failed to get EGL display"); return None; } let gst_display = gst_gl_egl::ffi::gst_gl_display_egl_new_with_egl_display(x11_display); let gst_display = gst_gl::GLDisplay::from_glib_full(gst_display as *mut gst_gl::ffi::GstGLDisplay); let wrapped_context = gst_gl::GLContext::new_wrapped(&gst_display, gl_ctx, platform, gl_api); let wrapped_context = match wrapped_context { None => { gst::error!(CAT, imp = self, "Failed to create wrapped GL context"); return None; } Some(wrapped_context) => wrapped_context, }; Some((gst_display, wrapped_context)) } } #[cfg(feature = "x11glx")] fn initialize_x11glx( &self, display: gdk::Display, ) -> Option<(gst_gl::GLDisplay, gst_gl::GLContext)> { gst::info!( CAT, imp = self, "Initializing GL for x11 GLX backend and display" ); let platform = gst_gl::GLPlatform::GLX; let (gl_api, _, _) = gst_gl::GLContext::current_gl_api(platform); let gl_ctx = gst_gl::GLContext::current_gl_context(platform); if gl_ctx == 0 { gst::error!(CAT, imp = self, "Failed to get handle from GdkGLContext"); return None; } // FIXME: bindings unsafe { use glib::translate::*; let display = display.downcast::().unwrap(); let x11_display = gdk_x11::ffi::gdk_x11_display_get_xdisplay(display.to_glib_none().0); if x11_display.is_null() { gst::error!(CAT, imp = self, "Failed to get X11 display"); return None; } let gst_display = gst_gl_x11::ffi::gst_gl_display_x11_new_with_display(x11_display); let gst_display = gst_gl::GLDisplay::from_glib_full(gst_display as *mut gst_gl::ffi::GstGLDisplay); let wrapped_context = gst_gl::GLContext::new_wrapped(&gst_display, gl_ctx, platform, gl_api); let wrapped_context = match wrapped_context { None => { gst::error!(CAT, imp = self, "Failed to create wrapped GL context"); return None; } Some(wrapped_context) => wrapped_context, }; Some((gst_display, wrapped_context)) } } #[cfg(feature = "waylandegl")] fn initialize_waylandegl( &self, display: gdk::Display, ) -> Option<(gst_gl::GLDisplay, gst_gl::GLContext)> { gst::info!( CAT, imp = self, "Initializing GL for Wayland EGL backend and display" ); let platform = gst_gl::GLPlatform::EGL; let (gl_api, _, _) = gst_gl::GLContext::current_gl_api(platform); let gl_ctx = gst_gl::GLContext::current_gl_context(platform); if gl_ctx == 0 { gst::error!(CAT, imp = self, "Failed to get handle from GdkGLContext"); return None; } // FIXME: bindings unsafe { use glib::translate::*; // let wayland_display = gdk_wayland::WaylandDisplay::wl_display(display.downcast()); // get the ptr directly since we are going to use it raw let display = display.downcast::().unwrap(); let wayland_display = gdk_wayland::ffi::gdk_wayland_display_get_wl_display(display.to_glib_none().0); if wayland_display.is_null() { gst::error!(CAT, imp = self, "Failed to get Wayland display"); return None; } let gst_display = gst_gl_wayland::ffi::gst_gl_display_wayland_new_with_display(wayland_display); let gst_display = gst_gl::GLDisplay::from_glib_full(gst_display as *mut gst_gl::ffi::GstGLDisplay); let wrapped_context = gst_gl::GLContext::new_wrapped(&gst_display, gl_ctx, platform, gl_api); let wrapped_context = match wrapped_context { None => { gst::error!(CAT, imp = self, "Failed to create wrapped GL context"); return None; } Some(wrapped_context) => wrapped_context, }; Some((gst_display, wrapped_context)) } } #[cfg(target_os = "macos")] fn initialize_macosgl( &self, display: gdk::Display, ) -> Option<(gst_gl::GLDisplay, gst_gl::GLContext)> { gst::info!( CAT, imp = self, "Initializing GL for macOS backend and display" ); let platform = gst_gl::GLPlatform::CGL; let (gl_api, _, _) = gst_gl::GLContext::current_gl_api(platform); let gl_ctx = gst_gl::GLContext::current_gl_context(platform); if gl_ctx == 0 { gst::error!(CAT, imp = self, "Failed to get handle from GdkGLContext"); return None; } let gst_display = gst_gl::GLDisplay::new(); unsafe { let wrapped_context = gst_gl::GLContext::new_wrapped(&gst_display, gl_ctx, platform, gl_api); let wrapped_context = match wrapped_context { None => { gst::error!(CAT, imp = self, "Failed to create wrapped GL context"); return None; } Some(wrapped_context) => wrapped_context, }; Some((gst_display, wrapped_context)) } } #[cfg(target_os = "windows")] fn initialize_wgl( &self, _display: gdk::Display, context: &gdk::GLContext, ) -> Option<(gst_gl::GLDisplay, gst_gl::GLContext)> { gst::info!( CAT, imp = self, "Initializing GL with for Windows WGL backend and display." ); let platform = gst_gl::GLPlatform::WGL; let gl_api = if context.is_legacy() { gst_gl::GLAPI::OPENGL } else { gst_gl::GLAPI::OPENGL3 }; let gl_ctx = gst_gl::GLContext::current_gl_context(platform); if gl_ctx == 0 { gst::error!(CAT, imp = self, "Failed to get handle from GdkGLContext",); return None; } unsafe { let gst_display = if let Some(display) = gst_gl::GLDisplay::with_type(gst_gl::GLDisplayType::WIN32) { display } else { gst::error!(CAT, imp = self, "Failed to get GL display"); return None; }; gst_display.filter_gl_api(gl_api); let wrapped_context = gst_gl::GLContext::new_wrapped(&gst_display, gl_ctx, platform, gl_api); let wrapped_context = match wrapped_context { None => { gst::error!(CAT, imp = self, "Failed to create wrapped GL context"); return None; } Some(wrapped_context) => wrapped_context, }; Some((gst_display, wrapped_context)) } } #[cfg(all(target_os = "windows", feature = "winegl"))] fn initialize_winegl( &self, display: gdk::Display, ) -> Option<(gst_gl::GLDisplay, gst_gl::GLContext)> { gst::info!( CAT, imp = self, "Initializing GL with for Windows EGL backend and display." ); let platform = gst_gl::GLPlatform::EGL; let (gl_api, _, _) = gst_gl::GLContext::current_gl_api(platform); let gl_ctx = gst_gl::GLContext::current_gl_context(platform); if gl_ctx == 0 { gst::error!(CAT, imp = self, "Failed to get handle from GdkGLContext",); return None; } // FIXME: bindings unsafe { use gdk_win32::prelude::*; use glib::translate::*; let d = display.downcast::().unwrap(); let egl_display = d.egl_display().unwrap().as_ptr(); // TODO: On the binary distribution of GStreamer for Windows, this symbol is not there let gst_display = gst_gl_egl::ffi::gst_gl_display_egl_from_gl_display(egl_display.cast()); if gst_display.is_null() { gst::error!(CAT, imp = self, "Failed to get EGL display"); return None; } let gst_display = gst_gl::GLDisplay::from_glib_full(gst_display as *mut gst_gl::ffi::GstGLDisplay); gst_display.filter_gl_api(gl_api); let wrapped_context = gst_gl::GLContext::new_wrapped(&gst_display, gl_ctx, platform, gl_api); let wrapped_context = match wrapped_context { None => { gst::error!(CAT, imp = self, "Failed to create wrapped GL context"); return None; } Some(wrapped_context) => wrapped_context, }; Some((gst_display, wrapped_context)) } } } impl ChildProxyImpl for PaintableSink { fn child_by_index(&self, index: u32) -> Option { if index != 0 { return None; } let paintable = self.paintable.lock().unwrap(); paintable .as_ref() .filter(|p| p.is_owner()) .map(|p| p.get_ref().upcast_ref::().clone()) } fn child_by_name(&self, name: &str) -> Option { if name == "paintable" { return self.child_by_index(0); } None } fn children_count(&self) -> u32 { let paintable = self.paintable.lock().unwrap(); if paintable.is_some() { 1 } else { 0 } } } gst-plugin-gtk4-0.13.6/src/sink/mod.rs000064400000000000000000000050171046102023000155360ustar 00000000000000// // Copyright (C) 2021 Bilal Elmoussaoui // Copyright (C) 2021 Jordan Petridis // Copyright (C) 2021 Sebastian Dröge // // This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at // . // // SPDX-License-Identifier: MPL-2.0 /** * SECTION:element-gtk4paintablesink * * GTK 4 provides `gtk::Video` & `gtk::Picture` for rendering media such as videos. As the default * `gtk::Video` widget doesn't offer the possibility to use a custom `gst::Pipeline`. The plugin * provides a `gst_video::VideoSink` along with a `gdk::Paintable` that's capable of rendering the * sink's frames. * * The sink can generate GL Textures if the system is capable of it, but it needs to be compiled * with either `wayland`, `x11glx` or `x11egl` cargo features. On Windows and macOS this is enabled * by default. * * Additionally, the sink can render DMABufs directly on Linux if GTK 4.14 or newer is used. For * this the `dmabuf` feature needs to be enabled. * * Depending on the GTK version that is used and should be supported as minimum, new features or * more efficient processing can be opted in with the `gtk_v4_10`, `gtk_v4_12` and `gtk_v4_14` * features. The minimum GTK version required by the sink is GTK 4.4 on Linux without GL support, * and 4.6 on Windows and macOS, and on Linux with GL support. * * The sink will provides a simple test window when launched via `gst-launch-1.0` or `gst-play-1.0` * or if the environment variable `GST_GTK4_WINDOW=1` is set. Setting `GST_GTK4_WINDOW_FULLSCREEN=1` * will make the window launch in fullscreen mode. * * {{ videos/gtk4/examples/gtksink.rs }} */ use gtk::glib; use gtk::glib::prelude::*; pub(super) mod frame; pub(super) mod imp; pub(super) mod paintable; pub(super) mod render_widget; enum SinkEvent { FrameChanged, } glib::wrapper! { pub struct PaintableSink(ObjectSubclass) @extends gst_video::VideoSink, gst_base::BaseSink, gst::Element, gst::Object, @implements gst::ChildProxy; } impl PaintableSink { pub fn new(name: Option<&str>) -> Self { glib::Object::builder().property("name", name).build() } } pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { gst::Element::register( Some(plugin), "gtk4paintablesink", gst::Rank::NONE, PaintableSink::static_type(), ) } gst-plugin-gtk4-0.13.6/src/sink/paintable/imp.rs000064400000000000000000000543471046102023000175150ustar 00000000000000// // Copyright (C) 2021 Bilal Elmoussaoui // Copyright (C) 2021 Jordan Petridis // Copyright (C) 2021 Sebastian Dröge // // This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at // . // // SPDX-License-Identifier: MPL-2.0 use gtk::prelude::*; use gtk::subclass::prelude::*; use gtk::{gdk, glib, graphene, gsk}; use crate::sink::frame::{self, Frame, Texture}; use std::cell::{Cell, RefCell}; use std::collections::HashMap; use once_cell::sync::Lazy; static CAT: Lazy = Lazy::new(|| { gst::DebugCategory::new( "gtk4paintable", gst::DebugColorFlags::empty(), Some("GTK4 Paintable Sink Paintable"), ) }); #[derive(Debug)] pub struct Paintable { paintables: RefCell>, cached_textures: RefCell>, gl_context: RefCell>, background_color: Cell, #[cfg(feature = "gtk_v4_10")] scaling_filter: Cell, use_scaling_filter: Cell, force_aspect_ratio: Cell, orientation: Cell, #[cfg(not(feature = "gtk_v4_10"))] premult_shader: gsk::GLShader, } impl Default for Paintable { fn default() -> Self { Self { paintables: Default::default(), cached_textures: Default::default(), gl_context: Default::default(), background_color: Cell::new(gdk::RGBA::BLACK), #[cfg(feature = "gtk_v4_10")] scaling_filter: Cell::new(gsk::ScalingFilter::Linear), use_scaling_filter: Cell::new(false), force_aspect_ratio: Cell::new(false), orientation: Cell::new(frame::Orientation::Auto), #[cfg(not(feature = "gtk_v4_10"))] premult_shader: gsk::GLShader::from_bytes(&glib::Bytes::from_static(include_bytes!( "premult.glsl" ))), } } } #[glib::object_subclass] impl ObjectSubclass for Paintable { const NAME: &'static str = "GstGtk4Paintable"; type Type = super::Paintable; type Interfaces = (gdk::Paintable,); } impl ObjectImpl for Paintable { fn properties() -> &'static [glib::ParamSpec] { static PROPERTIES: Lazy> = Lazy::new(|| { vec![ glib::ParamSpecObject::builder::("gl-context") .nick("GL Context") .blurb("GL context to use for rendering") .construct_only() .build(), glib::ParamSpecUInt::builder("background-color") .nick("Background Color") .blurb("Background color to render behind the video frame and in the borders") .default_value(0) .build(), #[cfg(feature = "gtk_v4_10")] glib::ParamSpecEnum::builder_with_default::( "scaling-filter", gsk::ScalingFilter::Linear, ) .nick("Scaling Filter") .blurb("Scaling filter to use for rendering") .build(), #[cfg(feature = "gtk_v4_10")] glib::ParamSpecBoolean::builder("use-scaling-filter") .nick("Use Scaling Filter") .blurb("Use selected scaling filter or GTK default for rendering") .default_value(false) .build(), glib::ParamSpecBoolean::builder("force-aspect-ratio") .nick("Force Aspect Ratio") .blurb("When enabled, scaling will respect original aspect ratio") .default_value(false) .build(), glib::ParamSpecEnum::builder::("orientation") .nick("Orientation") .blurb("Orientation of the video frames") .build(), ] }); PROPERTIES.as_ref() } fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { match pspec.name() { "gl-context" => self.gl_context.borrow().to_value(), "background-color" => { let color = self.background_color.get(); let v = ((f32::clamp(color.red() * 255.0, 0.0, 255.0) as u32) << 24) | ((f32::clamp(color.green() * 255.0, 0.0, 255.0) as u32) << 16) | ((f32::clamp(color.blue() * 255.0, 0.0, 255.0) as u32) << 8) | (f32::clamp(color.alpha() * 255.0, 0.0, 255.0) as u32); v.to_value() } #[cfg(feature = "gtk_v4_10")] "scaling-filter" => self.scaling_filter.get().to_value(), #[cfg(feature = "gtk_v4_10")] "use-scaling-filter" => self.use_scaling_filter.get().to_value(), "force-aspect-ratio" => self.force_aspect_ratio.get().to_value(), "orientation" => self.orientation.get().to_value(), _ => unimplemented!(), } } fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { match pspec.name() { "gl-context" => { *self.gl_context.borrow_mut() = value.get::>().unwrap(); } "background-color" => { let v = value.get::().unwrap(); let red = ((v & 0xff_00_00_00) >> 24) as f32 / 255.0; let green = ((v & 0x00_ff_00_00) >> 16) as f32 / 255.0; let blue = ((v & 0x00_00_ff_00) >> 8) as f32 / 255.0; let alpha = (v & 0x00_00_00_ff) as f32 / 255.0; self.background_color .set(gdk::RGBA::new(red, green, blue, alpha)) } #[cfg(feature = "gtk_v4_10")] "scaling-filter" => self.scaling_filter.set(value.get().unwrap()), #[cfg(feature = "gtk_v4_10")] "use-scaling-filter" => self.use_scaling_filter.set(value.get().unwrap()), "force-aspect-ratio" => self.force_aspect_ratio.set(value.get().unwrap()), "orientation" => { let new_orientation = value.get().unwrap(); if new_orientation != self.orientation.get() { self.orientation.set(new_orientation); self.obj().invalidate_size(); } } _ => unimplemented!(), } } } impl PaintableImpl for Paintable { fn intrinsic_height(&self) -> i32 { if let Some(paintable) = self.paintables.borrow().first() { if self .effective_orientation(paintable.orientation) .is_flip_width_height() { f32::round(paintable.width) as i32 } else { f32::round(paintable.height) as i32 } } else { 0 } } fn intrinsic_width(&self) -> i32 { if let Some(paintable) = self.paintables.borrow().first() { if self .effective_orientation(paintable.orientation) .is_flip_width_height() { f32::round(paintable.height) as i32 } else { f32::round(paintable.width) as i32 } } else { 0 } } fn intrinsic_aspect_ratio(&self) -> f64 { if let Some(paintable) = self.paintables.borrow().first() { if self .effective_orientation(paintable.orientation) .is_flip_width_height() { paintable.height as f64 / paintable.width as f64 } else { paintable.width as f64 / paintable.height as f64 } } else { 0.0 } } fn snapshot(&self, snapshot: &gdk::Snapshot, width: f64, height: f64) { let snapshot = snapshot.downcast_ref::().unwrap(); let background_color = self.background_color.get(); let force_aspect_ratio = self.force_aspect_ratio.get(); let paintables = self.paintables.borrow(); let Some(first_paintable) = paintables.first() else { gst::trace!(CAT, imp = self, "Snapshotting black frame {width}x{height}"); snapshot.append_color( &background_color, &graphene::Rect::new(0f32, 0f32, width as f32, height as f32), ); return; }; gst::trace!(CAT, imp = self, "Snapshotting frame {width}x{height}"); // The first paintable is the actual video frame and defines the overall size. // // Based on its size relative to the snapshot width/height, all other paintables are // scaled accordingly. // // We also only consider the orientation of the first paintable for now and rotate all // overlays consistently with that to follow the behaviour of glvideoflip. let effective_orientation = self.effective_orientation(first_paintable.orientation); // First do the rotation around the center of the whole snapshot area if effective_orientation != frame::Orientation::Rotate0 { snapshot.translate(&graphene::Point::new( width as f32 / 2.0, height as f32 / 2.0, )); } match effective_orientation { frame::Orientation::Rotate0 => {} frame::Orientation::Rotate90 => { snapshot.rotate(90.0); } frame::Orientation::Rotate180 => { snapshot.scale(-1.0, -1.0); } frame::Orientation::Rotate270 => { snapshot.rotate(270.0); } frame::Orientation::FlipRotate0 => { snapshot.scale(-1.0, 1.0); } frame::Orientation::FlipRotate90 => { snapshot.rotate(90.0); snapshot.scale(-1.0, 1.0); } frame::Orientation::FlipRotate180 => { snapshot.scale(1.0, -1.0); } frame::Orientation::FlipRotate270 => { snapshot.rotate(270.0); snapshot.scale(-1.0, 1.0); } frame::Orientation::Auto => unreachable!(), } if effective_orientation != frame::Orientation::Rotate0 { if effective_orientation.is_flip_width_height() { snapshot.translate(&graphene::Point::new( -height as f32 / 2.0, -width as f32 / 2.0, )); } else { snapshot.translate(&graphene::Point::new( -width as f32 / 2.0, -height as f32 / 2.0, )); } } // The rotation is applied now and we're back at the origin at this point // Width / height of the overall frame that we're drawing. This has to be flipped // if a 90/270 degree rotation is applied. let (frame_width, frame_height) = if effective_orientation.is_flip_width_height() { (first_paintable.height, first_paintable.width) } else { (first_paintable.width, first_paintable.height) }; // Amount of scaling that has to be applied to the main frame and all overlays to fill the // available area let mut scale_x = width / frame_width as f64; let mut scale_y = height / frame_height as f64; // Usually the caller makes sure that the aspect ratio is preserved. To enforce this here // optionally, we scale the frame equally in both directions and center it. In addition the // background color is drawn behind the frame to fill the gaps. // // This is not done by default for performance reasons and usually would draw a <1px // background. if force_aspect_ratio { let mut trans_x = 0.0; let mut trans_y = 0.0; if (scale_x - scale_y).abs() > f64::EPSILON { if scale_x > scale_y { trans_x = (width - (frame_width as f64 * scale_y)) / 2.0; scale_x = scale_y; } else { trans_y = (height - (frame_height as f64 * scale_x)) / 2.0; scale_y = scale_x; } } if !background_color.is_clear() && (trans_x > f64::EPSILON || trans_y > f64::EPSILON) { // Clamping for the bounds below has to be flipped over for 90/270 degree rotations. let (width, height) = if effective_orientation.is_flip_width_height() { (height, width) } else { (width, height) }; snapshot.append_color( &background_color, &graphene::Rect::new(0f32, 0f32, width as f32, height as f32), ); } if effective_orientation.is_flip_width_height() { std::mem::swap(&mut trans_x, &mut trans_y); } snapshot.translate(&graphene::Point::new(trans_x as f32, trans_y as f32)); } // At this point we're at the origin of the area into which the actual video frame is drawn // Make immutable let scale_x = scale_x; let scale_y = scale_y; for ( idx, Texture { texture, x, y, width: paintable_width, height: paintable_height, global_alpha, has_alpha, orientation: _orientation, }, ) in paintables.iter().enumerate() { snapshot.push_opacity(*global_alpha as f64); // Clamping for the bounds below has to be flipped over for 90/270 degree rotations. let (width, height) = if effective_orientation.is_flip_width_height() { (height, width) } else { (width, height) }; let bounds = if !force_aspect_ratio && idx == 0 { // While this should end up with width again, be explicit in this case to avoid // rounding errors and fill the whole area with the video frame. graphene::Rect::new(0.0, 0.0, width as f32, height as f32) } else { // Scale texture position and size with the same scale factor as the main video // frame, and make sure to not render outside (0, 0, width, height). let (rect_x, rect_y) = if effective_orientation.is_flip_width_height() { ( f32::clamp(*y * scale_y as f32, 0.0, width as f32), f32::clamp(*x * scale_x as f32, 0.0, height as f32), ) } else { ( f32::clamp(*x * scale_x as f32, 0.0, width as f32), f32::clamp(*y * scale_y as f32, 0.0, height as f32), ) }; let (texture_width, texture_height) = if effective_orientation.is_flip_width_height() { ( f32::min(*paintable_width * scale_y as f32, width as f32), f32::min(*paintable_height * scale_x as f32, height as f32), ) } else { ( f32::min(*paintable_width * scale_x as f32, width as f32), f32::min(*paintable_height * scale_y as f32, height as f32), ) }; graphene::Rect::new(rect_x, rect_y, texture_width, texture_height) }; // Only premultiply GL textures that expect to be in premultiplied RGBA format. // // For GTK 4.14 or newer we use the correct format directly when building the // texture, but only if a GLES3+ context is used. In that case the NGL renderer is // used by GTK, which supports non-premultiplied formats correctly and fast. // // For GTK 4.10-4.12, or 4.14 and newer if a GLES2 context is used, we use a // self-mask to pre-multiply the alpha. // // For GTK before 4.10, we use a GL shader and hope that it works. #[cfg(feature = "gtk_v4_10")] { let context_requires_premult = { #[cfg(feature = "gtk_v4_14")] { self.gl_context.borrow().as_ref().is_some_and(|context| { context.api() != gdk::GLAPI::GLES || context.version().0 < 3 }) } #[cfg(not(feature = "gtk_v4_14"))] { true } }; let do_premult = context_requires_premult && texture.is::() && *has_alpha; if do_premult { snapshot.push_mask(gsk::MaskMode::Alpha); if self.use_scaling_filter.get() { #[cfg(feature = "gtk_v4_10")] snapshot.append_scaled_texture(texture, self.scaling_filter.get(), &bounds); } else { snapshot.append_texture(texture, &bounds); } snapshot.pop(); // pop mask // color matrix to set alpha of the source to 1.0 as it was // already applied via the mask just above. snapshot.push_color_matrix( &graphene::Matrix::from_float({ [ 1.0, 0.0, 0.0, 0.0, // 0.0, 1.0, 0.0, 0.0, // 0.0, 0.0, 1.0, 0.0, // 0.0, 0.0, 0.0, 0.0, ] }), &graphene::Vec4::new(0.0, 0.0, 0.0, 1.0), ); } if self.use_scaling_filter.get() { #[cfg(feature = "gtk_v4_10")] snapshot.append_scaled_texture(texture, self.scaling_filter.get(), &bounds); } else { snapshot.append_texture(texture, &bounds); } if do_premult { snapshot.pop(); // pop color matrix snapshot.pop(); // pop mask 2 } } #[cfg(not(feature = "gtk_v4_10"))] { let do_premult = texture.is::() && *has_alpha && gtk::micro_version() < 13; if do_premult { snapshot.push_gl_shader( &self.premult_shader, &bounds, gsk::ShaderArgsBuilder::new(&self.premult_shader, None).to_args(), ); } if self.use_scaling_filter.get() { #[cfg(feature = "gtk_v4_10")] snapshot.append_scaled_texture(texture, self.scaling_filter.get(), &bounds); } else { snapshot.append_texture(texture, &bounds); } if do_premult { snapshot.gl_shader_pop_texture(); // pop texture appended above from the shader snapshot.pop(); // pop shader } } snapshot.pop(); // pop opacity } } } impl Paintable { fn effective_orientation( &self, paintable_orientation: frame::Orientation, ) -> frame::Orientation { let orientation = self.orientation.get(); if orientation != frame::Orientation::Auto { return orientation; } assert_ne!(paintable_orientation, frame::Orientation::Auto); paintable_orientation } pub(super) fn handle_frame_changed(&self, sink: &crate::PaintableSink, frame: Frame) { let context = self.gl_context.borrow(); gst::trace!(CAT, imp = self, "Received new frame"); let new_paintables = match frame.into_textures(context.as_ref(), &mut self.cached_textures.borrow_mut()) { Ok(textures) => textures, Err(err) => { gst::element_error!( sink, gst::ResourceError::Failed, ["Failed to transform frame into textures: {err}"] ); return; } }; let flip_width_height = |(width, height, orientation): (u32, u32, frame::Orientation)| { if orientation.is_flip_width_height() { (height, width) } else { (width, height) } }; let new_size = new_paintables .first() .map(|p| { flip_width_height(( f32::round(p.width) as u32, f32::round(p.height) as u32, p.orientation, )) }) .unwrap(); let old_paintables = self.paintables.replace(new_paintables); let old_size = old_paintables.first().map(|p| { flip_width_height(( f32::round(p.width) as u32, f32::round(p.height) as u32, p.orientation, )) }); if Some(new_size) != old_size { gst::debug!( CAT, imp = self, "Size changed from {old_size:?} to {new_size:?}", ); self.obj().invalidate_size(); } self.obj().invalidate_contents(); } pub(super) fn handle_flush_frames(&self) { gst::debug!(CAT, imp = self, "Flushing frames"); self.paintables.borrow_mut().clear(); self.cached_textures.borrow_mut().clear(); self.obj().invalidate_size(); self.obj().invalidate_contents(); } } gst-plugin-gtk4-0.13.6/src/sink/paintable/mod.rs000064400000000000000000000021271046102023000174740ustar 00000000000000// // Copyright (C) 2021 Bilal Elmoussaoui // Copyright (C) 2021 Jordan Petridis // Copyright (C) 2021 Sebastian Dröge // // This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at // . // // SPDX-License-Identifier: MPL-2.0 use crate::sink::frame::Frame; use gtk::subclass::prelude::*; use gtk::{gdk, glib}; mod imp; glib::wrapper! { pub struct Paintable(ObjectSubclass) @implements gdk::Paintable; } impl Paintable { pub(crate) fn new(context: Option) -> Self { glib::Object::builder() .property("gl-context", context) .build() } } impl Paintable { pub(crate) fn handle_frame_changed(&self, sink: &crate::PaintableSink, frame: Frame) { self.imp().handle_frame_changed(sink, frame); } pub(crate) fn handle_flush_frames(&self) { self.imp().handle_flush_frames(); } } gst-plugin-gtk4-0.13.6/src/sink/paintable/premult.glsl000064400000000000000000000003441046102023000207210ustar 00000000000000uniform sampler2D u_texture1; void mainImage( out vec4 fragColor, in vec2 fragCoord, in vec2 resolution, in vec2 uv ) { fragColor = GskTexture(u_texture1, uv); fragColor.rgb = fragColor.rgb * fragColor.a; } gst-plugin-gtk4-0.13.6/src/sink/render_widget/imp.rs000064400000000000000000000077071046102023000203760ustar 00000000000000// // Copyright (C) 2024 Guillaume Desmottes // // This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at // . // // SPDX-License-Identifier: MPL-2.0 use std::cell::{Cell, RefCell}; use gtk::{gdk, glib, prelude::*, subclass::prelude::*}; use once_cell::sync::Lazy; #[derive(Default)] pub struct RenderWidget { element: RefCell>, window_size: Cell<(u32, u32)>, } #[glib::object_subclass] impl ObjectSubclass for RenderWidget { const NAME: &'static str = "GstGtk4ExampleRenderWidget"; type Type = super::RenderWidget; type ParentType = gtk::Widget; fn class_init(klass: &mut Self::Class) { klass.set_layout_manager_type::(); } } impl ObjectImpl for RenderWidget { fn properties() -> &'static [glib::ParamSpec] { static PROPERTIES: Lazy> = Lazy::new(|| { vec![ glib::ParamSpecObject::builder::("element") .nick("Element") .blurb("The GTK4 Paintable Sink GStreamer element") .construct_only() .build(), ] }); PROPERTIES.as_ref() } fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { match pspec.name() { "element" => self.element.borrow().to_value(), _ => unimplemented!(), } } fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { match pspec.name() { "element" => { *self.element.borrow_mut() = value.get::>().unwrap(); } _ => unimplemented!(), } } fn constructed(&self) { self.parent_constructed(); let element = self.element.borrow(); let element = element.as_ref().unwrap(); let paintable = element.property::("paintable"); let picture = gtk::Picture::new(); picture.set_paintable(Some(&paintable)); #[cfg(feature = "gtk_v4_14")] { let offload = gtk::GraphicsOffload::new(Some(&picture)); offload.set_enabled(gtk::GraphicsOffloadEnabled::Enabled); #[cfg(feature = "gtk_v4_16")] { offload.set_black_background(true); } offload.set_parent(self.obj().as_ref()); } #[cfg(not(feature = "gtk_v4_14"))] { picture.set_parent(self.obj().as_ref()); } } fn dispose(&self) { while let Some(child) = self.obj().first_child() { child.unparent(); } } } impl WidgetImpl for RenderWidget { fn snapshot(&self, snapshot: >k::Snapshot) { let window_width = self.obj().width() as u32; let window_height = self.obj().height() as u32; let scale = { #[cfg(feature = "gtk_v4_12")] { if let Some(surface) = self.obj().native().and_then(|native| native.surface()) { surface.scale() } else { self.obj().scale_factor() as f64 } } #[cfg(not(feature = "gtk_v4_12"))] { self.obj().scale_factor() as f64 } }; let new_size = ( f64::ceil(window_width as f64 * scale) as u32, f64::ceil(window_height as f64 * scale) as u32, ); let updated = self.window_size.replace(new_size) != new_size; if updated { let element = self.element.borrow(); let element = element.as_ref().unwrap(); element.set_property("window-width", new_size.0); element.set_property("window-height", new_size.1); } self.parent_snapshot(snapshot) } } gst-plugin-gtk4-0.13.6/src/sink/render_widget/mod.rs000064400000000000000000000012571046102023000203620ustar 00000000000000// // Copyright (C) 2024 Guillaume Desmottes // // This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at // . // // SPDX-License-Identifier: MPL-2.0 use gtk::glib; mod imp; /// Use a simple container widget to automatically pass the window size to gtk4paintablesink. glib::wrapper! { pub struct RenderWidget(ObjectSubclass) @extends gtk::Widget; } impl RenderWidget { pub fn new(element: &gst::Element) -> Self { glib::Object::builder().property("element", element).build() } } gst-plugin-gtk4-0.13.6/src/utils.rs000064400000000000000000000006401046102023000151500ustar 00000000000000use gtk::glib; use std::sync::mpsc; pub(crate) fn invoke_on_main_thread(func: F) -> T where F: FnOnce() -> T + Send + 'static, T: Send + 'static, { let context = glib::MainContext::default(); let (send, recv) = mpsc::channel(); context.invoke(move || { send.send(func()).expect("Somehow we dropped the receiver"); }); recv.recv().expect("Somehow we dropped the sender") }