diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 49f07bab..a9505f7f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,6 +12,13 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 + + - name: Install wayland dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + libwayland-dev \ + libegl-dev \ - name: Build run: | diff --git a/Cargo.lock b/Cargo.lock index 04fcbd3d..dc0e6150 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,6 +100,20 @@ name = "bytemuck" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "byteorder" @@ -263,6 +277,45 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +[[package]] +name = "drm" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98888c4bbd601524c11a7ed63f814b8825f420514f78e96f752c437ae9cbb5d1" +dependencies = [ + "bitflags 2.5.0", + "bytemuck", + "drm-ffi", + "drm-fourcc", + "rustix", +] + +[[package]] +name = "drm-ffi" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97c98727e48b7ccb4f4aea8cfe881e5b07f702d17b7875991881b41af7278d53" +dependencies = [ + "drm-sys", + "rustix", +] + +[[package]] +name = "drm-fourcc" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" + +[[package]] +name = "drm-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd39dde40b6e196c2e8763f23d119ddb1a8714534bf7d77fa97a65b0feda3986" +dependencies = [ + "libc", + "linux-raw-sys 0.6.4", +] + [[package]] name = "encode_unicode" version = "0.3.6" @@ -341,6 +394,60 @@ dependencies = [ "thread_local", ] +[[package]] +name = "gbm" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45bf55ba6dd53ad0ac115046ff999c5324c283444ee6e0be82454c4e8eb2f36a" +dependencies = [ + "bitflags 2.5.0", + "drm", + "drm-fourcc", + "gbm-sys", + "libc", + "wayland-backend", + "wayland-server", +] + +[[package]] +name = "gbm-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fd2d6bf7c0143b38beece05f9a5c4c851a49a8434f62bf58ff28da92b0ddc58" +dependencies = [ + "libc", +] + +[[package]] +name = "gl" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a94edab108827d67608095e269cf862e60d920f144a5026d3dbcfd8b877fb404" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "gl_loader" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e32d96dd5f881490e537041d5532320812ba096097f07fccb4626578da0b99d3" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "glob" version = "0.3.1" @@ -423,6 +530,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "io-lifetimes" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a611371471e98973dbcab4e0ec66c31a10bc356eeb4d54a0e05eac8158fe38c" + [[package]] name = "jobserver" version = "0.1.28" @@ -447,6 +560,22 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + [[package]] name = "lazy_static" version = "1.4.0" @@ -461,9 +590,9 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libloading" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" dependencies = [ "cfg-if", "windows-targets", @@ -473,11 +602,16 @@ dependencies = [ name = "libwayshot" version = "0.3.2-dev" dependencies = [ + "drm", + "gbm", + "gl", "image", + "khronos-egl", "memmap2", "nix 0.27.1", "thiserror", "tracing", + "wayland-backend", "wayland-client", "wayland-protocols", "wayland-protocols-wlr", @@ -499,6 +633,12 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +[[package]] +name = "linux-raw-sys" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0b5399f6804fbab912acbd8878ed3532d506b7c951b8f9f164ef90fef39e3f4" + [[package]] name = "log" version = "0.4.21" @@ -520,6 +660,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -690,7 +839,7 @@ dependencies = [ "bitflags 2.5.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.13", "windows-sys", ] @@ -961,6 +1110,16 @@ dependencies = [ "wayland-scanner", ] +[[package]] +name = "wayland-egl" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "355f652e5a24ae02d2ad536c8fc2d3dcc6c2bd635027cd6103a193e7d75eeda2" +dependencies = [ + "wayland-backend", + "wayland-sys", +] + [[package]] name = "wayland-protocols" version = "0.31.2" @@ -997,6 +1156,20 @@ dependencies = [ "quote", ] +[[package]] +name = "wayland-server" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00e6e4d5c285bc24ba4ed2d5a4bd4febd5fd904451f465973225c8e99772fdb7" +dependencies = [ + "bitflags 2.5.0", + "downcast-rs", + "io-lifetimes", + "rustix", + "wayland-backend", + "wayland-scanner", +] + [[package]] name = "wayland-sys" version = "0.31.1" @@ -1004,10 +1177,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af" dependencies = [ "dlib", + "libc", "log", + "memoffset", "pkg-config", ] +[[package]] +name = "waymirror-egl" +version = "0.1.0" +dependencies = [ + "gl", + "gl_loader", + "khronos-egl", + "libloading", + "libwayshot", + "thiserror", + "tracing", + "tracing-subscriber", + "wayland-backend", + "wayland-client", + "wayland-egl", + "wayland-protocols", +] + [[package]] name = "wayshot" version = "1.3.2-dev" @@ -1151,6 +1344,12 @@ dependencies = [ "wayland-protocols-wlr", ] +[[package]] +name = "xml-rs" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" + [[package]] name = "zeroize" version = "1.7.0" diff --git a/Cargo.toml b/Cargo.toml index 4979f4eb..b13e9351 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["wayshot", "libwayshot"] +members = ["wayshot", "libwayshot","libwayshot/examples/waymirror-egl"] [workspace.package] authors = ["Shinyzenith "] diff --git a/libwayshot/Cargo.toml b/libwayshot/Cargo.toml index 8a9719b0..357622dc 100644 --- a/libwayshot/Cargo.toml +++ b/libwayshot/Cargo.toml @@ -18,3 +18,10 @@ thiserror = "1" wayland-client = "0.31.1" wayland-protocols = { version = "0.31.0", features = ["client", "unstable"] } wayland-protocols-wlr = { version = "0.2.0", features = ["client"] } +wayland-backend = { version = "0.3.3", features = ["client_system"] } + +gbm = "0.15.0" +drm = "0.12.0" + +gl = "0.14.0" +khronos-egl = { version = "6.0.0",features = ["static"] } \ No newline at end of file diff --git a/libwayshot/examples/waymirror-egl/Cargo.lock b/libwayshot/examples/waymirror-egl/Cargo.lock new file mode 100644 index 00000000..310b1996 --- /dev/null +++ b/libwayshot/examples/waymirror-egl/Cargo.lock @@ -0,0 +1,517 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "cc" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "gl" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a94edab108827d67608095e269cf862e60d920f144a5026d3dbcfd8b877fb404" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "gl_loader" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e32d96dd5f881490e537041d5532320812ba096097f07fccb4626578da0b99d3" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libloading" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustix" +version = "0.38.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "syn" +version = "2.0.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "wayland-backend" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d50fa61ce90d76474c87f5fc002828d81b32677340112b4ef08079a9d459a40" +dependencies = [ + "cc", + "downcast-rs", + "rustix", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f" +dependencies = [ + "bitflags", + "rustix", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-egl" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "355f652e5a24ae02d2ad536c8fc2d3dcc6c2bd635027cd6103a193e7d75eeda2" +dependencies = [ + "wayland-backend", + "wayland-sys", +] + +[[package]] +name = "wayland-egl-ctx" +version = "0.1.0" +dependencies = [ + "gl", + "gl_loader", + "khronos-egl", + "thiserror", + "tracing", + "tracing-subscriber", + "wayland-backend", + "wayland-client", + "wayland-egl", + "wayland-protocols", +] + +[[package]] +name = "wayland-protocols" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" +dependencies = [ + "bitflags", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b3a62929287001986fb58c789dce9b67604a397c15c611ad9f747300b6c283" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af" +dependencies = [ + "dlib", + "log", + "pkg-config", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "xml-rs" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" diff --git a/libwayshot/examples/waymirror-egl/Cargo.toml b/libwayshot/examples/waymirror-egl/Cargo.toml new file mode 100644 index 00000000..eb6d9dc8 --- /dev/null +++ b/libwayshot/examples/waymirror-egl/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "waymirror-egl" +version = "0.1.0" +edition = "2021" + +[dependencies] +gl = "0.14.0" +gl_loader = "0.1.2" +khronos-egl = { version = "6.0.0",features = ["static"] } +thiserror = "1.0.58" +tracing = "0.1.40" +tracing-subscriber = "0.3.18" +wayland-backend = { version = "0.3.3", features = ["client_system"] } +wayland-client = { version = "0.31.2" } +wayland-egl = { version = "0.32.0" } +wayland-protocols = { version = "0.31.2", features = ["client"] } +libwayshot={path="../.."} +libloading="0.8.4" \ No newline at end of file diff --git a/libwayshot/examples/waymirror-egl/README.md b/libwayshot/examples/waymirror-egl/README.md new file mode 100644 index 00000000..69d4fe45 --- /dev/null +++ b/libwayshot/examples/waymirror-egl/README.md @@ -0,0 +1,8 @@ +# waymirror-egl +Example code for using the libwayshot DMA-BUF GL screencapture pipeline. + +This example sets up an EGL+OpenGL context, sets up libwayshot and renders the main display onto a rectangle after converting the screencapture into a texture. + +Adapted from https://github.com/Shinyzenith/wayland-egl-ctx, all credits to @Shinyzenith + +Run using `cargo run` inside this directory. diff --git a/libwayshot/examples/waymirror-egl/src/dispatch.rs b/libwayshot/examples/waymirror-egl/src/dispatch.rs new file mode 100644 index 00000000..2e3f38f8 --- /dev/null +++ b/libwayshot/examples/waymirror-egl/src/dispatch.rs @@ -0,0 +1,131 @@ +use crate::state::WaylandEGLState; +use wayland_client::{ + delegate_noop, + protocol::{wl_compositor, wl_registry, wl_surface}, + Connection, Dispatch, QueueHandle, +}; +use wayland_protocols::xdg::shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base}; + +impl Dispatch for WaylandEGLState { + #[tracing::instrument(skip(registry, queue_handle, state), ret, level = "trace")] + fn event( + state: &mut Self, + registry: &wl_registry::WlRegistry, + event: wl_registry::Event, + _: &(), + _: &Connection, + queue_handle: &QueueHandle, + ) { + if let wl_registry::Event::Global { + name, + interface, + version, + } = event + { + match interface.as_str() { + "xdg_wm_base" => { + state.xdg_wm_base = Some(registry.bind::( + name, + version, + queue_handle, + (), + )); + } + "wl_compositor" => { + state.wl_compositor = Some(registry.bind::( + name, + version, + queue_handle, + (), + )); + } + _ => {} + } + } + } +} + +impl Dispatch for WaylandEGLState { + #[tracing::instrument(skip(xdg_wm_base), ret, level = "trace")] + fn event( + _: &mut Self, + xdg_wm_base: &xdg_wm_base::XdgWmBase, + event: xdg_wm_base::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + if let xdg_wm_base::Event::Ping { serial } = event { + xdg_wm_base.pong(serial); + } + } +} + +impl Dispatch for WaylandEGLState { + #[tracing::instrument(skip(xdg_surface), ret, level = "trace")] + fn event( + _: &mut Self, + xdg_surface: &xdg_surface::XdgSurface, + event: xdg_surface::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + if let xdg_surface::Event::Configure { serial } = event { + xdg_surface.ack_configure(serial); + } + } +} + +impl Dispatch for WaylandEGLState { + #[tracing::instrument(skip(), ret, level = "trace")] + fn event( + state: &mut Self, + _: &xdg_toplevel::XdgToplevel, + event: xdg_toplevel::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + match event { + xdg_toplevel::Event::Configure { width, height, .. } => { + if width == 0 || height == 0 { + return; // We do not respect this configure + } + + if state.width != width || state.height != height { + state.width = width; + state.height = height; + + state + .egl_window + .clone() + .unwrap() + .resize(state.width, state.height, 0, 0); + + unsafe { + gl::Viewport(0, 0, state.width, state.height); + } + state.wl_surface.clone().unwrap().commit(); + } + } + xdg_toplevel::Event::Close {} => { + state.running = false; + } + _ => {} + } + } +} + +impl Dispatch for WaylandEGLState { + fn event( + _state: &mut Self, + _proxy: &wl_surface::WlSurface, + _event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} +delegate_noop!(WaylandEGLState: wl_compositor::WlCompositor); diff --git a/libwayshot/examples/waymirror-egl/src/error.rs b/libwayshot/examples/waymirror-egl/src/error.rs new file mode 100644 index 00000000..1860cc85 --- /dev/null +++ b/libwayshot/examples/waymirror-egl/src/error.rs @@ -0,0 +1,22 @@ +use std::result; +use thiserror::Error; + +pub type Result = result::Result; + +#[derive(Error, Debug)] +pub enum WaylandEGLStateError { + #[error("xdg_wm_base global missing")] + XdgWmBaseMissing, + + #[error("wl_compositor global missing")] + WlCompositorMissing, + + #[error("Shader compilation failed")] + GLShaderCompileFailed, + + #[error("Failed to create gl program")] + GLCreateProgramFailed, + + #[error("Failed to link gl program")] + GLLinkProgramFailed, +} diff --git a/libwayshot/examples/waymirror-egl/src/main.rs b/libwayshot/examples/waymirror-egl/src/main.rs new file mode 100644 index 00000000..969851a5 --- /dev/null +++ b/libwayshot/examples/waymirror-egl/src/main.rs @@ -0,0 +1,64 @@ +mod dispatch; +mod error; +mod state; +mod utils; + +use error::Result; +use state::WaylandEGLState; + +pub fn main() -> Result<(), Box> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::DEBUG) + .with_writer(std::io::stderr) + .init(); + + let mut state = WaylandEGLState::new()?; + let mut event_queue = state.wl_connection.new_event_queue(); + + let queue_handle = event_queue.handle(); + let _registry = state.wl_display.get_registry(&queue_handle, ()); + + event_queue.roundtrip(&mut state)?; + state.validate_globals()?; + + state.wl_surface = Some( + state + .wl_compositor + .as_ref() + .unwrap() + .create_surface(&queue_handle, ()), + ); + + state.xdg_surface = Some(state.xdg_wm_base.clone().unwrap().get_xdg_surface( + &state.wl_surface.clone().unwrap(), + &queue_handle, + (), + )); + state.xdg_toplevel = Some( + state + .xdg_surface + .clone() + .unwrap() + .get_toplevel(&queue_handle, ()), + ); + state + .xdg_toplevel + .clone() + .unwrap() + .set_title(state.title.clone()); + state.wl_surface.clone().unwrap().commit(); + + state.init_egl()?; + while state.running { + event_queue.dispatch_pending(&mut state)?; + state.draw(); + state + .egl + .swap_buffers(state.egl_display.unwrap(), state.egl_surface.unwrap())?; + + tracing::trace!("eglSwapBuffers called"); + } + state.deinit()?; + + Ok(()) +} diff --git a/libwayshot/examples/waymirror-egl/src/shaders/frag.glsl b/libwayshot/examples/waymirror-egl/src/shaders/frag.glsl new file mode 100644 index 00000000..8212a377 --- /dev/null +++ b/libwayshot/examples/waymirror-egl/src/shaders/frag.glsl @@ -0,0 +1,11 @@ +#version 300 es + +precision mediump float; +out vec4 FragColor; +uniform sampler2D uTexture; +in vec2 vTexCoord; + +void main() { + vec4 color = texture(uTexture, vTexCoord); + FragColor = vec4 ( 1.0-color.r,1.0-color.g,1.0-color.b, 1.0 ); +} diff --git a/libwayshot/examples/waymirror-egl/src/shaders/vert.glsl b/libwayshot/examples/waymirror-egl/src/shaders/vert.glsl new file mode 100644 index 00000000..9b27c568 --- /dev/null +++ b/libwayshot/examples/waymirror-egl/src/shaders/vert.glsl @@ -0,0 +1,11 @@ +#version 300 es +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec2 aTexCoord; + +out vec2 vTexCoord; + +void main() +{ + gl_Position = vec4(aPos, 1.0); + vTexCoord = vec2(aTexCoord.x, aTexCoord.y); +} \ No newline at end of file diff --git a/libwayshot/examples/waymirror-egl/src/state.rs b/libwayshot/examples/waymirror-egl/src/state.rs new file mode 100644 index 00000000..6b4a8022 --- /dev/null +++ b/libwayshot/examples/waymirror-egl/src/state.rs @@ -0,0 +1,303 @@ +use crate::error::{Result, WaylandEGLStateError}; +use crate::utils::load_shader; + +use libwayshot::WayshotConnection; + +use gl::types::GLuint; +use khronos_egl::{self as egl}; +use std::{ffi::c_void, rc::Rc}; +use wayland_client::{ + protocol::{wl_compositor, wl_display::WlDisplay, wl_surface::WlSurface}, + ConnectError, Connection, Proxy, +}; +use wayland_egl::WlEglSurface; +use wayland_protocols::xdg::shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base}; + +#[derive(Debug)] +pub struct WaylandEGLState { + pub width: i32, + pub height: i32, + pub running: bool, + pub title: String, + + pub wl_connection: Connection, + pub wl_display: WlDisplay, + pub wl_surface: Option, + + pub egl: egl::Instance, + pub egl_window: Option>, + pub egl_display: Option, + pub egl_surface: Option, + pub egl_context: Option, + + pub gl_program: GLuint, + pub gl_texture: GLuint, + + pub xdg_wm_base: Option, + pub xdg_surface: Option, + pub xdg_toplevel: Option, + pub wl_compositor: Option, + + wayshot: WayshotConnection, +} + +impl WaylandEGLState { + #[tracing::instrument] + pub fn new() -> Result { + let server_connection = Connection::connect_to_env()?; + + Ok(Self { + width: 1920, + height: 1080, + running: true, + title: "Waymirror-EGL".into(), + + wl_connection: server_connection.clone(), + wl_display: server_connection.display(), + wl_surface: None, + + egl: khronos_egl::Instance::new(egl::Static), + egl_window: None, + egl_display: None, + egl_surface: None, + egl_context: None, + gl_program: 0, + gl_texture: 0, + + xdg_wm_base: None, + xdg_surface: None, + xdg_toplevel: None, + wl_compositor: None, + wayshot: WayshotConnection::from_connection_with_dmabuf( + server_connection, + "/dev/dri/renderD128", + ) + .unwrap(), + }) + } + + pub fn deinit(&self) -> Result<(), Box> { + unsafe { + gl::DeleteProgram(self.gl_program); + } + + self.egl + .destroy_surface(self.egl_display.unwrap(), self.egl_surface.unwrap())?; + self.egl + .destroy_context(self.egl_display.unwrap(), self.egl_context.unwrap())?; + + self.xdg_surface.clone().unwrap().destroy(); + self.wl_surface.clone().unwrap().destroy(); + + Ok(()) + } + + pub fn init_egl(&mut self) -> Result<(), Box> { + // Init gl + gl_loader::init_gl(); + gl::load_with(|s| gl_loader::get_proc_address(s) as *const _); + + self.egl_window = Some(Rc::new(WlEglSurface::new( + self.wl_surface.clone().unwrap().id(), + self.width, + self.height, + )?)); + + self.egl_display = Some( + unsafe { + self.egl + .get_display(self.wl_display.id().as_ptr() as *mut c_void) + } + .unwrap(), + ); + + self.egl.initialize(self.egl_display.unwrap())?; + + let attributes = [ + egl::SURFACE_TYPE, + egl::WINDOW_BIT, + egl::RENDERABLE_TYPE, + egl::OPENGL_ES2_BIT, + egl::RED_SIZE, + 8, + egl::GREEN_SIZE, + 8, + egl::BLUE_SIZE, + 8, + egl::NONE, + ]; + + let config = self + .egl + .choose_first_config(self.egl_display.unwrap(), &attributes)? + .expect("unable to find an appropriate EGL configuration"); + self.egl_surface = Some(unsafe { + self.egl.create_window_surface( + self.egl_display.unwrap(), + config, + self.egl_window.clone().unwrap().ptr() as egl::NativeWindowType, + None, + )? + }); + + let context_attributes = [egl::CONTEXT_CLIENT_VERSION, 2, egl::NONE]; + self.egl_context = Some(self.egl.create_context( + self.egl_display.unwrap(), + config, + None, + &context_attributes, + )?); + + self.egl.make_current( + self.egl_display.unwrap(), + self.egl_surface, + self.egl_surface, + self.egl_context, + )?; + + self.init_program()?; + + Ok(()) + } + + fn init_program(&mut self) -> Result<()> { + let vert_shader = load_shader( + gl::VERTEX_SHADER, + include_str!("./shaders/vert.glsl").into(), + ) + .unwrap(); + + let frag_shader = load_shader( + gl::FRAGMENT_SHADER, + include_str!("./shaders/frag.glsl").into(), + ) + .unwrap(); + + unsafe { + self.gl_program = gl::CreateProgram(); + } + + if self.gl_program == 0 { + tracing::event!(tracing::Level::ERROR, "glCreateProgramFailed!"); + return Err(WaylandEGLStateError::GLCreateProgramFailed); + } + + unsafe { + gl::AttachShader(self.gl_program, vert_shader); + gl::AttachShader(self.gl_program, frag_shader); + + gl::LinkProgram(self.gl_program); + } + + let mut linked: gl::types::GLint = 1; + unsafe { gl::GetProgramiv(self.gl_program, gl::LINK_STATUS, &mut linked as *mut i32) } + + if linked > 0 { + tracing::event!(tracing::Level::INFO, "Successfully linked the program!"); + } else { + return Err(WaylandEGLStateError::GLLinkProgramFailed); + } + + let vertices: [gl::types::GLfloat; 20] = [ + // positions // texture coords + 1.0, 1.0, 0.0, 1.0, 0.0, // top right + 1.0, -1.0, 0.0, 1.0, 1.0, // bottom right + -1.0, -1.0, 0.0, 0.0, 1.0, // bottom left + -1.0, 1.0, 0.0, 0.0, 0.0, // top left + ]; + let indices: [gl::types::GLint; 6] = [ + 0, 1, 3, // first Triangle + 1, 2, 3, // second Triangle + ]; + let mut vbo: GLuint = 0; + let mut vao: GLuint = 0; + let mut ebo: GLuint = 0; + + unsafe { + gl::GenTextures(1, &mut self.gl_texture); + + self.dmabuf_to_texture(); + + gl::GenVertexArrays(1, &mut vao as *mut u32); + gl::GenBuffers(1, &mut vbo as *mut u32); + gl::GenBuffers(1, &mut ebo as *mut u32); + gl::BindVertexArray(vao); + + gl::BindBuffer(gl::ARRAY_BUFFER, vbo); + gl::BufferData( + gl::ARRAY_BUFFER, + (vertices.len() * std::mem::size_of::()) + as gl::types::GLsizeiptr, + &vertices[0] as *const f32 as *const c_void, + gl::STATIC_DRAW, + ); + + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo); + gl::BufferData( + gl::ELEMENT_ARRAY_BUFFER, + (indices.len() * std::mem::size_of::()) + as gl::types::GLsizeiptr, + &indices[0] as *const i32 as *const c_void, + gl::STATIC_DRAW, + ); + + gl::VertexAttribPointer( + 0, + 3, + gl::FLOAT, + gl::FALSE, + 5 * std::mem::size_of::() as gl::types::GLint, + 0 as *const c_void, + ); + gl::EnableVertexAttribArray(0); + + gl::VertexAttribPointer( + 1, + 2, + gl::FLOAT, + gl::FALSE, + 5 * std::mem::size_of::() as gl::types::GLint, + (3 * std::mem::size_of::()) as *const c_void, + ); + gl::EnableVertexAttribArray(1); + gl::BindBuffer(gl::ARRAY_BUFFER, 0); + } + Ok(()) + } + + pub fn draw(&mut self) { + unsafe { + gl::ClearColor(1.0, 1.0, 0.0, 1.0); + gl::Clear(gl::COLOR_BUFFER_BIT); + // gl::DeleteTextures(1, &mut self.gl_texture); + + gl::UseProgram(self.gl_program); + gl::DrawElements(gl::TRIANGLES, 6, gl::UNSIGNED_INT, 0 as *const c_void); + } + } + + pub fn dmabuf_to_texture(&self) { + unsafe { + gl::BindTexture(gl::TEXTURE_2D, self.gl_texture); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32); + + self.wayshot + .bind_output_frame_to_gl_texture( + true, + &self.wayshot.get_all_outputs()[0].wl_output, + None, + ) + .unwrap(); + } + } + + pub fn validate_globals(&self) -> Result<()> { + if self.xdg_wm_base.is_none() { + return Err(WaylandEGLStateError::XdgWmBaseMissing); + } else if self.wl_compositor.is_none() { + return Err(WaylandEGLStateError::WlCompositorMissing); + } + + Ok(()) + } +} diff --git a/libwayshot/examples/waymirror-egl/src/utils.rs b/libwayshot/examples/waymirror-egl/src/utils.rs new file mode 100644 index 00000000..e532e2d3 --- /dev/null +++ b/libwayshot/examples/waymirror-egl/src/utils.rs @@ -0,0 +1,31 @@ +use crate::error::{Result, WaylandEGLStateError}; +use gl::types::{GLenum, GLint, GLuint}; +use std::{ffi::CString, ptr}; + +pub fn load_shader(shader_type: GLenum, src: String) -> Result { + unsafe { + let shader: GLuint = gl::CreateShader(shader_type); + + if shader == 0 { + return Err(WaylandEGLStateError::GLShaderCompileFailed); + } + + let src_c_str = CString::new(src.as_bytes()).unwrap(); + gl::ShaderSource(shader, 1, &src_c_str.as_ptr(), ptr::null()); + + //gl::ShaderSource(shader, 1, &src_c_str.as_ptr(), std::ptr::null()); + + gl::CompileShader(shader); + + let mut status: GLint = 1; + gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut status as *mut i32); + + if status > 0 { + tracing::event!(tracing::Level::INFO, "Shader compile successfull!",); + } else { + return Err(WaylandEGLStateError::GLShaderCompileFailed); + } + + Ok(shader) + } +} diff --git a/libwayshot/examples/waymirror.rs b/libwayshot/examples/waymirror.rs new file mode 100644 index 00000000..c350131a --- /dev/null +++ b/libwayshot/examples/waymirror.rs @@ -0,0 +1,205 @@ +use libwayshot::WayshotConnection; +use wayland_client::{ + delegate_noop, + protocol::{ + wl_buffer::{self}, + wl_compositor, wl_keyboard, wl_registry, wl_seat, wl_shm, wl_shm_pool, wl_surface, + }, + Connection, Dispatch, QueueHandle, WEnum, +}; + +use wayland_protocols::xdg::shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base}; + +fn main() { + let conn = Connection::connect_to_env().unwrap(); + + let mut event_queue = conn.new_event_queue(); + let qhandle = event_queue.handle(); + + let display = conn.display(); + display.get_registry(&qhandle, ()); + let wayshot = + WayshotConnection::from_connection_with_dmabuf(conn, "/dev/dri/renderD128").unwrap(); + + let mut state = State { + wayshot, + running: true, + base_surface: None, + wm_base: None, + xdg_surface: None, + configured: false, + }; + + println!("Starting the example wayshot dmabuf demo app, press to quit."); + + while state.running { + event_queue.blocking_dispatch(&mut state).unwrap(); + } +} + +struct State { + wayshot: WayshotConnection, + running: bool, + base_surface: Option, + wm_base: Option, + xdg_surface: Option<(xdg_surface::XdgSurface, xdg_toplevel::XdgToplevel)>, + configured: bool, +} + +impl Dispatch for State { + fn event( + state: &mut Self, + registry: &wl_registry::WlRegistry, + event: wl_registry::Event, + _: &(), + _: &Connection, + qh: &QueueHandle, + ) { + if let wl_registry::Event::Global { + name, interface, .. + } = event + { + match &interface[..] { + "wl_compositor" => { + let compositor = + registry.bind::(name, 1, qh, ()); + let surface = compositor.create_surface(qh, ()); + state.base_surface = Some(surface); + + if state.wm_base.is_some() && state.xdg_surface.is_none() { + state.init_xdg_surface(qh); + } + } + "wl_seat" => { + registry.bind::(name, 1, qh, ()); + } + "xdg_wm_base" => { + let wm_base = registry.bind::(name, 1, qh, ()); + state.wm_base = Some(wm_base); + + if state.base_surface.is_some() && state.xdg_surface.is_none() { + state.init_xdg_surface(qh); + } + } + _ => {} + } + } + } +} + +// Ignore events from these object types in this example. +delegate_noop!(State: ignore wl_compositor::WlCompositor); +delegate_noop!(State: ignore wl_surface::WlSurface); +delegate_noop!(State: ignore wl_shm::WlShm); +delegate_noop!(State: ignore wl_shm_pool::WlShmPool); +delegate_noop!(State: ignore wl_buffer::WlBuffer); + +impl State { + fn init_xdg_surface(&mut self, qh: &QueueHandle) { + let wm_base = self.wm_base.as_ref().unwrap(); + let base_surface = self.base_surface.as_ref().unwrap(); + + let xdg_surface = wm_base.get_xdg_surface(base_surface, qh, ()); + let toplevel = xdg_surface.get_toplevel(qh, ()); + toplevel.set_title("DMABuf+wlr-screencpy example!".into()); + + base_surface.commit(); + + self.xdg_surface = Some((xdg_surface, toplevel)); + } +} + +impl Dispatch for State { + fn event( + _: &mut Self, + wm_base: &xdg_wm_base::XdgWmBase, + event: xdg_wm_base::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + if let xdg_wm_base::Event::Ping { serial } = event { + wm_base.pong(serial); + } + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + xdg_surface: &xdg_surface::XdgSurface, + event: xdg_surface::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + if let xdg_surface::Event::Configure { serial, .. } = event { + xdg_surface.ack_configure(serial); + state.configured = true; + let surface = state.base_surface.as_ref().unwrap(); + let (_frame_format, guard, _bo) = state + .wayshot + .capture_output_frame_dmabuf( + true, + &state.wayshot.get_all_outputs()[0].wl_output, + None, + ) + .unwrap(); + surface.attach(Some(&guard.buffer), 0, 0); + surface.commit(); + } + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + _: &xdg_toplevel::XdgToplevel, + event: xdg_toplevel::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + if let xdg_toplevel::Event::Close {} = event { + state.running = false; + } + } +} + +impl Dispatch for State { + fn event( + _: &mut Self, + seat: &wl_seat::WlSeat, + event: wl_seat::Event, + _: &(), + _: &Connection, + qh: &QueueHandle, + ) { + if let wl_seat::Event::Capabilities { + capabilities: WEnum::Value(capabilities), + } = event + { + if capabilities.contains(wl_seat::Capability::Keyboard) { + seat.get_keyboard(qh, ()); + } + } + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + _: &wl_keyboard::WlKeyboard, + event: wl_keyboard::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + if let wl_keyboard::Event::Key { key, .. } = event { + if key == 1 { + // ESC key + state.running = false; + } + } + } +} diff --git a/libwayshot/src/dispatch.rs b/libwayshot/src/dispatch.rs index c8f21fcf..e171cd1b 100644 --- a/libwayshot/src/dispatch.rs +++ b/libwayshot/src/dispatch.rs @@ -1,20 +1,32 @@ use std::{ collections::HashSet, + os::fd::{AsFd, BorrowedFd}, sync::atomic::{AtomicBool, Ordering}, }; use wayland_client::{ delegate_noop, globals::GlobalListContents, protocol::{ - wl_buffer::WlBuffer, wl_compositor::WlCompositor, wl_output, wl_output::WlOutput, - wl_registry, wl_registry::WlRegistry, wl_shm::WlShm, wl_shm_pool::WlShmPool, + wl_buffer::WlBuffer, + wl_compositor::WlCompositor, + wl_output::{self, WlOutput}, + wl_registry::{self, WlRegistry}, + wl_shm::WlShm, + wl_shm_pool::WlShmPool, wl_surface::WlSurface, }, - Connection, Dispatch, QueueHandle, WEnum, - WEnum::Value, + Connection, Dispatch, QueueHandle, + WEnum::{self, Value}, }; -use wayland_protocols::xdg::xdg_output::zv1::client::{ - zxdg_output_manager_v1::ZxdgOutputManagerV1, zxdg_output_v1, zxdg_output_v1::ZxdgOutputV1, +use wayland_protocols::{ + wp::linux_dmabuf::zv1::client::{ + zwp_linux_buffer_params_v1::{self, ZwpLinuxBufferParamsV1}, + zwp_linux_dmabuf_v1::{self, ZwpLinuxDmabufV1}, + }, + xdg::xdg_output::zv1::client::{ + zxdg_output_manager_v1::ZxdgOutputManagerV1, + zxdg_output_v1::{self, ZxdgOutputV1}, + }, }; use wayland_protocols_wlr::layer_shell::v1::client::{ zwlr_layer_shell_v1::ZwlrLayerShellV1, @@ -28,7 +40,7 @@ use wayland_protocols_wlr::screencopy::v1::client::{ use crate::{ output::OutputInfo, region::{LogicalRegion, Position, Size}, - screencopy::FrameFormat, + screencopy::{DMAFrameFormat, FrameFormat}, }; #[derive(Debug)] @@ -169,10 +181,35 @@ pub enum FrameState { pub struct CaptureFrameState { pub formats: Vec, + pub dmabuf_formats: Vec, pub state: Option, pub buffer_done: AtomicBool, } +impl Dispatch for CaptureFrameState { + fn event( + _frame: &mut Self, + _proxy: &ZwpLinuxDmabufV1, + _event: zwp_linux_dmabuf_v1::Event, + _data: &(), + _conn: &Connection, + _qhandle: &wayland_client::QueueHandle, + ) { + } +} + +impl Dispatch for CaptureFrameState { + fn event( + _state: &mut Self, + _proxy: &ZwpLinuxBufferParamsV1, + _event: zwp_linux_buffer_params_v1::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} + impl Dispatch for CaptureFrameState { #[tracing::instrument(skip(frame), ret, level = "trace")] fn event( @@ -191,6 +228,7 @@ impl Dispatch for CaptureFrameState { stride, } => { if let Value(f) = format { + tracing::debug!("Received Buffer event with format: {f:?}"); frame.formats.push(FrameFormat { format: f, size: Size { width, height }, @@ -209,7 +247,19 @@ impl Dispatch for CaptureFrameState { frame.state.replace(FrameState::Failed); } zwlr_screencopy_frame_v1::Event::Damage { .. } => {} - zwlr_screencopy_frame_v1::Event::LinuxDmabuf { .. } => {} + zwlr_screencopy_frame_v1::Event::LinuxDmabuf { + format, + width, + height, + } => { + tracing::debug!( + "Received wlr-screencopy linux_dmabuf event with format: {format} and size {width}x{height}" + ); + frame.dmabuf_formats.push(DMAFrameFormat { + format, + size: Size { width, height }, + }); + } zwlr_screencopy_frame_v1::Event::BufferDone => { frame.buffer_done.store(true, Ordering::SeqCst); } @@ -226,7 +276,7 @@ delegate_noop!(CaptureFrameState: ignore ZwlrScreencopyManagerV1); // TODO: Create a xdg-shell surface, check for the enter event, grab the output from it. pub struct WayshotState {} - +delegate_noop!(WayshotState: ignore ZwpLinuxDmabufV1); impl wayland_client::Dispatch for WayshotState { fn event( _: &mut WayshotState, @@ -278,3 +328,28 @@ impl wayland_client::Dispatch for LayerShellState } } } +pub(crate) struct Card(std::fs::File); + +/// Implementing [`AsFd`] is a prerequisite to implementing the traits found +/// in this crate. Here, we are just calling [`File::as_fd()`] on the inner +/// [`File`]. +impl AsFd for Card { + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } +} +impl drm::Device for Card {} +/// Simple helper methods for opening a `Card`. +impl Card { + pub fn open(path: &str) -> Self { + let mut options = std::fs::OpenOptions::new(); + options.read(true); + options.write(true); + Card(options.open(path).unwrap()) + } +} +#[derive(Debug)] +pub(crate) struct DMABUFState { + pub linux_dmabuf: ZwpLinuxDmabufV1, + pub gbmdev: gbm::Device, +} diff --git a/libwayshot/src/error.rs b/libwayshot/src/error.rs index f3ad8523..10d87b4f 100644 --- a/libwayshot/src/error.rs +++ b/libwayshot/src/error.rs @@ -1,5 +1,7 @@ use std::{io, result}; +use drm::buffer::UnrecognizedFourcc; +use gbm::{DeviceDestroyedError, FdError}; use thiserror::Error; use wayland_client::{ globals::{BindError, GlobalError}, @@ -34,4 +36,16 @@ pub enum Error { ProtocolNotFound(String), #[error("error occurred in freeze callback")] FreezeCallbackError, + #[error("dmabuf configuration not initialized. Did you not use Wayshot::from_connection_with_dmabuf()?")] + NoDMAStateError, + #[error("dmabuf color format provided by compositor is invalid")] + UnrecognizedColorCode(#[from] UnrecognizedFourcc), + #[error("dmabuf device has been destroyed")] + DRMDeviceLost(#[from] DeviceDestroyedError), + #[error("obtaining gbm buffer object file descriptor failed {0}")] + GBMBoFdError(#[from] FdError), + #[error(" EGLImage import from dmabuf failed: {0}")] + EGLError(#[from] khronos_egl::Error), + #[error("No EGLImageTargetTexture2DOES function located, this extension may not be supported")] + EGLImageToTexProcNotFoundError, } diff --git a/libwayshot/src/lib.rs b/libwayshot/src/lib.rs index d0ec88ad..abd32b44 100644 --- a/libwayshot/src/lib.rs +++ b/libwayshot/src/lib.rs @@ -13,17 +13,19 @@ mod screencopy; use std::{ collections::HashSet, + ffi::c_void, fs::File, - os::fd::AsFd, + os::fd::{AsFd, IntoRawFd, OwnedFd}, sync::atomic::{AtomicBool, Ordering}, thread, }; -use dispatch::LayerShellState; +use dispatch::{DMABUFState, LayerShellState}; use image::{imageops::replace, DynamicImage}; +use khronos_egl::{self as egl, Instance}; use memmap2::MmapMut; use region::{EmbeddedRegion, RegionCapturer}; -use screencopy::FrameGuard; +use screencopy::{DMAFrameFormat, DMAFrameGuard, EGLImageGuard, FrameData, FrameGuard}; use tracing::debug; use wayland_client::{ globals::{registry_queue_init, GlobalList}, @@ -32,10 +34,15 @@ use wayland_client::{ wl_output::{Transform, WlOutput}, wl_shm::{self, WlShm}, }, - Connection, EventQueue, + Connection, EventQueue, Proxy, }; -use wayland_protocols::xdg::xdg_output::zv1::client::{ - zxdg_output_manager_v1::ZxdgOutputManagerV1, zxdg_output_v1::ZxdgOutputV1, +use wayland_protocols::{ + wp::linux_dmabuf::zv1::client::{ + zwp_linux_buffer_params_v1, zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, + }, + xdg::xdg_output::zv1::client::{ + zxdg_output_manager_v1::ZxdgOutputManagerV1, zxdg_output_v1::ZxdgOutputV1, + }, }; use wayland_protocols_wlr::{ layer_shell::v1::client::{ @@ -62,6 +69,7 @@ pub mod reexport { use wayland_client::protocol::wl_output; pub use wl_output::{Transform, WlOutput}; } +use gbm::{BufferObject, BufferObjectFlags, Device as GBMDevice}; /// Struct to store wayland connection and globals list. /// # Example usage @@ -75,6 +83,7 @@ pub struct WayshotConnection { pub conn: Connection, pub globals: GlobalList, output_infos: Vec, + dmabuf_state: Option, } impl WayshotConnection { @@ -92,6 +101,34 @@ impl WayshotConnection { conn, globals, output_infos: Vec::new(), + dmabuf_state: None, + }; + + initial_state.refresh_outputs()?; + + Ok(initial_state) + } + + ///Create a WayshotConnection struct having DMA-BUF support + /// Using this connection is required to make use of the dmabuf functions + ///# Parameters + /// - conn: a Wayland connection + /// - device_path: string pointing to the DRI device that is to be used for creating the DMA-BUFs on. For example: "/dev/dri/renderD128" + pub fn from_connection_with_dmabuf(conn: Connection, device_path: &str) -> Result { + let (globals, evq) = registry_queue_init::(&conn)?; + let linux_dmabuf = + globals.bind(&evq.handle(), 4..=ZwpLinuxDmabufV1::interface().version, ())?; + let gpu = dispatch::Card::open(device_path); + // init a GBM device + let gbm = GBMDevice::new(gpu).unwrap(); + let mut initial_state = Self { + conn, + globals, + output_infos: Vec::new(), + dmabuf_state: Some(DMABUFState { + linux_dmabuf, + gbmdev: gbm, + }), }; initial_state.refresh_outputs()?; @@ -166,14 +203,249 @@ impl WayshotConnection { capture_region: Option, ) -> Result<(FrameFormat, FrameGuard)> { let (state, event_queue, frame, frame_format) = - self.capture_output_frame_get_state(cursor_overlay, output, capture_region)?; + self.capture_output_frame_get_state_shm(cursor_overlay, output, capture_region)?; let frame_guard = self.capture_output_frame_inner(state, event_queue, frame, frame_format, fd)?; Ok((frame_format, frame_guard)) } - fn capture_output_frame_get_state( + fn capture_output_frame_shm_from_file( + &self, + cursor_overlay: bool, + output: &WlOutput, + file: &File, + capture_region: Option, + ) -> Result<(FrameFormat, FrameGuard)> { + let (state, event_queue, frame, frame_format) = + self.capture_output_frame_get_state_shm(cursor_overlay as i32, output, capture_region)?; + + file.set_len(frame_format.byte_size())?; + + let frame_guard = + self.capture_output_frame_inner(state, event_queue, frame, frame_format, file)?; + + Ok((frame_format, frame_guard)) + } + /// Helper function/wrapper that uses the OpenGL extension OES_EGL_image to convert the EGLImage obtained from [`WayshotConnection::capture_output_frame_eglimage`] + /// into a OpenGL texture. + /// - The caller is supposed to setup everything required for the texture binding. An example call may look like: + /// ``` + /// gl::BindTexture(gl::TEXTURE_2D, self.gl_texture); + /// gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32); + /// wayshot_conn + /// .bind_output_frame_to_gl_texture( + /// true, + /// &wayshot_conn.get_all_outputs()[0].wl_output, + /// None) + ///``` + /// # Parameters + /// - `cursor_overlay`: A boolean flag indicating whether the cursor should be included in the capture. + /// - `output`: Reference to the `WlOutput` from which the frame is to be captured. + /// - `capture_region`: Optional region specifying a sub-area of the output to capture. If `None`, the entire output is captured. + /// # Returns + /// - If the function was found and called, an OK(()), note that this does not neccesarily mean that binding was successful, only that the function was called. + /// The caller may check for any OpenGL errors using the standard routes. + /// - If the function was not found, [`Error::EGLImageToTexProcNotFoundError`] is returned + pub unsafe fn bind_output_frame_to_gl_texture( + &self, + cursor_overlay: bool, + output: &WlOutput, + capture_region: Option, + ) -> Result<()> { + let egl = khronos_egl::Instance::new(egl::Static); + let eglimage_guard = + self.capture_output_frame_eglimage(&egl, cursor_overlay, output, capture_region)?; + unsafe { + let gl_egl_image_texture_target_2d_oes: unsafe extern "system" fn( + target: gl::types::GLenum, + image: gl::types::GLeglImageOES, + ) -> () = + std::mem::transmute(match egl.get_proc_address("glEGLImageTargetTexture2DOES") { + Some(f) => { + tracing::debug!("glEGLImageTargetTexture2DOES found at address {:#?}", f); + f + } + None => { + tracing::error!("glEGLImageTargetTexture2DOES not found"); + return Err(Error::EGLImageToTexProcNotFoundError); + } + }); + + gl_egl_image_texture_target_2d_oes(gl::TEXTURE_2D, eglimage_guard.image.as_ptr()); + tracing::trace!("glEGLImageTargetTexture2DOES called"); + Ok(()) + } + } + + /// Obtain a screencapture in the form of a EGLImage. + /// The display on which this image is created is obtained from the Wayland Connection. + /// Uses the dma-buf provisions of the wlr-screencopy copy protocol to avoid VRAM->RAM copies + /// It returns the captured frame as an `EGLImage`, wrapped in an `EGLImageGuard` + /// for safe handling and cleanup. + /// # Parameters + /// - `egl_instance`: Reference to an egl API instance obtained from the khronos_egl crate, which is used to create the `EGLImage`. + /// - `cursor_overlay`: A boolean flag indicating whether the cursor should be included in the capture. + /// - `output`: Reference to the `WlOutput` from which the frame is to be captured. + /// - `capture_region`: Optional region specifying a sub-area of the output to capture. If `None`, the entire output is captured. + /// + /// # Returns + /// If successful, an EGLImageGuard which contains a pointer 'image' to the created EGLImage + /// On error, the EGL [error code](https://registry.khronos.org/EGL/sdk/docs/man/html/eglGetError.xhtml) is returned via this crates Error type + pub fn capture_output_frame_eglimage<'a, T: khronos_egl::api::EGL1_5>( + &self, + egl_instance: &'a Instance, + cursor_overlay: bool, + output: &WlOutput, + capture_region: Option, + ) -> Result> { + let egl_display = unsafe { + match egl_instance.get_display(self.conn.display().id().as_ptr() as *mut c_void) { + Some(disp) => disp, + None => return Err(egl_instance.get_error().unwrap().into()), + } + }; + tracing::trace!("eglDisplay obtained from Wayland connection's display"); + + egl_instance.initialize(egl_display)?; + self.capture_output_frame_eglimage_on_display( + &egl_instance, + egl_display, + cursor_overlay, + output, + capture_region, + ) + } + + /// Obtain a screencapture in the form of a EGLImage on the given EGLDisplay. + /// + /// Uses the dma-buf provisions of the wlr-screencopy copy protocol to avoid VRAM->RAM copies + /// It returns the captured frame as an `EGLImage`, wrapped in an `EGLImageGuard` + /// for safe handling and cleanup. + /// # Parameters + /// - `egl_instance`: Reference to an `EGL1_5` instance, which is used to create the `EGLImage`. + /// - `egl_display`: The `EGLDisplay` on which the image should be created. + /// - `cursor_overlay`: A boolean flag indicating whether the cursor should be included in the capture. + /// - `output`: Reference to the `WlOutput` from which the frame is to be captured. + /// - `capture_region`: Optional region specifying a sub-area of the output to capture. If `None`, the entire output is captured. + /// + /// # Returns + /// If successful, an EGLImageGuard which contains a pointer 'image' to the created EGLImage + /// On error, the EGL [error code](https://registry.khronos.org/EGL/sdk/docs/man/html/eglGetError.xhtml) is returned via this crates Error type + pub fn capture_output_frame_eglimage_on_display<'a, T: khronos_egl::api::EGL1_5>( + &self, + egl_instance: &'a Instance, + egl_display: egl::Display, + cursor_overlay: bool, + output: &WlOutput, + capture_region: Option, + ) -> Result> { + type Attrib = egl::Attrib; + let (frame_format, _guard, bo) = + self.capture_output_frame_dmabuf(cursor_overlay, output, capture_region)?; + let modifier: u64 = bo.modifier()?.into(); + let image_attribs = [ + egl::WIDTH as Attrib, + frame_format.size.width as Attrib, + egl::HEIGHT as Attrib, + frame_format.size.height as Attrib, + 0x3271, //EGL_LINUX_DRM_FOURCC_EXT + bo.format().unwrap() as Attrib, + 0x3272, //EGL_DMA_BUF_PLANE0_FD_EXT + bo.fd_for_plane(0).unwrap().into_raw_fd() as Attrib, + 0x3273, //EGL_DMA_BUF_PLANE0_OFFSET_EXT + bo.offset(0).unwrap() as Attrib, + 0x3274, //EGL_DMA_BUF_PLANE0_PITCH_EXT + bo.stride_for_plane(0).unwrap() as Attrib, + 0x3443, //EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT + (modifier as u32) as Attrib, + 0x3444, //EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT + (modifier >> 32) as Attrib, + egl::ATTRIB_NONE as Attrib, + ]; + tracing::debug!( + "Calling eglCreateImage with attributes: {:#?}", + image_attribs + ); + unsafe { + match egl_instance.create_image( + egl_display, + khronos_egl::Context::from_ptr(egl::NO_CONTEXT), + 0x3270, // EGL_LINUX_DMA_BUF_EXT + khronos_egl::ClientBuffer::from_ptr(std::ptr::null_mut()), //NULL + &image_attribs, + ) { + Ok(image) => Ok(EGLImageGuard { + image, + egl_instance, + egl_display, + }), + Err(e) => { + tracing::error!("eglCreateImage call failed with error {e}"); + Err(e.into()) + } + } + } + } + + /// Obtain a screencapture in the form of a WlBuffer backed by a GBM Bufferobject on the GPU. + /// Uses the dma-buf provisions of the wlr-screencopy copy protocol to avoid VRAM->RAM copies + /// The captured frame is returned as a tuple containing the frame format, a guard to manage + /// the WlBuffer's cleanup on drop, and the underlying `BufferObject`. + /// - `cursor_overlay`: A boolean flag indicating whether the cursor should be included in the capture. + /// - `output`: Reference to the `WlOutput` from which the frame is to be captured. + /// - `capture_region`: Optional region specifying a sub-area of the output to capture. If `None`, the entire output is captured. + ///# Returns + /// On success, returns a tuple containing the frame format, + /// a guard to manage the frame's lifecycle, and the GPU-backed `BufferObject`. + /// # Errors + /// - Returns `NoDMAStateError` if the DMA-BUF state is not initialized a the time of initialization of this struct. + pub fn capture_output_frame_dmabuf( + &self, + cursor_overlay: bool, + output: &WlOutput, + capture_region: Option, + ) -> Result<(DMAFrameFormat, DMAFrameGuard, BufferObject<()>)> { + match &self.dmabuf_state { + Some(dmabuf_state) => { + let (state, event_queue, frame, frame_format) = self + .capture_output_frame_get_state_dmabuf( + cursor_overlay as i32, + output, + capture_region, + )?; + let gbm = &dmabuf_state.gbmdev; + let bo = gbm.create_buffer_object::<()>( + frame_format.size.width, + frame_format.size.height, + gbm::Format::try_from(frame_format.format)?, + BufferObjectFlags::RENDERING | BufferObjectFlags::LINEAR, + )?; + + let stride = bo.stride()?; + let modifier: u64 = bo.modifier()?.into(); + tracing::debug!( + "Created GBM Buffer object with input frame format {:#?}, stride {:#?} and modifier {:#?} ", + frame_format, + stride,modifier + ); + let frame_guard = self.capture_output_frame_inner_dmabuf( + state, + event_queue, + frame, + frame_format, + stride, + modifier, + bo.fd_for_plane(0)?, + )?; + + Ok((frame_format, frame_guard, bo)) + } + None => Err(Error::NoDMAStateError), + } + } + + fn capture_output_frame_get_state_shm( &self, cursor_overlay: i32, output: &WlOutput, @@ -186,6 +458,7 @@ impl WayshotConnection { )> { let mut state = CaptureFrameState { formats: Vec::new(), + dmabuf_formats: Vec::new(), state: None, buffer_done: AtomicBool::new(false), }; @@ -208,7 +481,7 @@ impl WayshotConnection { } }; - debug!("Capturing output..."); + tracing::debug!("Capturing output(shm buffer)..."); let frame = if let Some(embedded_region) = capture_region { screencopy_manager.capture_output_region( cursor_overlay, @@ -263,6 +536,144 @@ impl WayshotConnection { Ok((state, event_queue, frame, frame_format)) } + fn capture_output_frame_get_state_dmabuf( + &self, + cursor_overlay: i32, + output: &WlOutput, + capture_region: Option, + ) -> Result<( + CaptureFrameState, + EventQueue, + ZwlrScreencopyFrameV1, + DMAFrameFormat, + )> { + let mut state = CaptureFrameState { + formats: Vec::new(), + dmabuf_formats: Vec::new(), + state: None, + buffer_done: AtomicBool::new(false), + }; + let mut event_queue = self.conn.new_event_queue::(); + let qh = event_queue.handle(); + + // Instantiating screencopy manager. + let screencopy_manager = match self.globals.bind::( + &qh, + 3..=3, + (), + ) { + Ok(x) => x, + Err(e) => { + tracing::error!("Failed to create screencopy manager. Does your compositor implement ZwlrScreencopy?"); + tracing::error!("err: {e}"); + return Err(Error::ProtocolNotFound( + "ZwlrScreencopy Manager not found".to_string(), + )); + } + }; + + tracing::debug!("Capturing output for DMA-BUF API..."); + let frame = if let Some(embedded_region) = capture_region { + screencopy_manager.capture_output_region( + cursor_overlay, + output, + embedded_region.inner.position.x, + embedded_region.inner.position.y, + embedded_region.inner.size.width as i32, + embedded_region.inner.size.height as i32, + &qh, + (), + ) + } else { + screencopy_manager.capture_output(cursor_overlay, output, &qh, ()) + }; + + // Empty internal event buffer until buffer_done is set to true which is when the Buffer done + // event is fired, aka the capture from the compositor is succesful. + while !state.buffer_done.load(Ordering::SeqCst) { + event_queue.blocking_dispatch(&mut state)?; + } + + tracing::trace!( + "Received compositor frame buffer formats: {:#?}", + state.formats + ); + // TODO select appropriate format if there is more than one + let frame_format = state.dmabuf_formats[0]; + tracing::trace!("Selected frame buffer format: {:#?}", frame_format); + + Ok((state, event_queue, frame, frame_format)) + } + + fn capture_output_frame_inner_dmabuf( + &self, + mut state: CaptureFrameState, + mut event_queue: EventQueue, + frame: ZwlrScreencopyFrameV1, + frame_format: DMAFrameFormat, + stride: u32, + modifier: u64, + fd: OwnedFd, + ) -> Result { + match &self.dmabuf_state { + Some(dmabuf_state) => { + // Connecting to wayland environment. + let qh = event_queue.handle(); + + let linux_dmabuf = &dmabuf_state.linux_dmabuf; + let dma_width = frame_format.size.width; + let dma_height = frame_format.size.height; + + let dma_params = linux_dmabuf.create_params(&qh, ()); + + dma_params.add( + fd.as_fd(), + 0, + 0, + stride, + (modifier >> 32) as u32, + (modifier & 0xffffffff) as u32, + ); + tracing::trace!("Called ZwpLinuxBufferParamsV1::create_params "); + let dmabuf_wlbuf = dma_params.create_immed( + dma_width as i32, + dma_height as i32, + frame_format.format, + zwp_linux_buffer_params_v1::Flags::empty(), + &qh, + (), + ); + tracing::trace!("Called ZwpLinuxBufferParamsV1::create_immed to create WlBuffer "); + // Copy the pixel data advertised by the compositor into the buffer we just created. + frame.copy(&dmabuf_wlbuf); + tracing::debug!("wlr-screencopy copy() with dmabuf complete"); + + // On copy the Ready / Failed events are fired by the frame object, so here we check for them. + loop { + // Basically reads, if frame state is not None then... + if let Some(state) = state.state { + match state { + FrameState::Failed => { + tracing::error!("Frame copy failed"); + return Err(Error::FramecopyFailed); + } + FrameState::Finished => { + tracing::trace!("Frame copy finished"); + + return Ok(DMAFrameGuard { + buffer: dmabuf_wlbuf, + }); + } + } + } + + event_queue.blocking_dispatch(&mut state)?; + } + } + None => Err(Error::NoDMAStateError), + } + } + fn capture_output_frame_inner( &self, mut state: CaptureFrameState, @@ -307,6 +718,7 @@ impl WayshotConnection { return Err(Error::FramecopyFailed); } FrameState::Finished => { + tracing::trace!("Frame copy finished"); return Ok(FrameGuard { buffer, shm_pool }); } } @@ -316,24 +728,6 @@ impl WayshotConnection { } } - fn capture_output_frame_shm_from_file( - &self, - cursor_overlay: bool, - output: &WlOutput, - file: &File, - capture_region: Option, - ) -> Result<(FrameFormat, FrameGuard)> { - let (state, event_queue, frame, frame_format) = - self.capture_output_frame_get_state(cursor_overlay as i32, output, capture_region)?; - - file.set_len(frame_format.byte_size())?; - - let frame_guard = - self.capture_output_frame_inner(state, event_queue, frame, frame_format, file)?; - - Ok((frame_format, frame_guard)) - } - /// Get a FrameCopy instance with screenshot pixel data for any wl_output object. #[tracing::instrument(skip_all, fields(output = format!("{output_info}"), region = capture_region.map(|r| format!("{:}", r)).unwrap_or("fullscreen".to_string())))] fn capture_frame_copy( @@ -375,7 +769,7 @@ impl WayshotConnection { let frame_copy = FrameCopy { frame_format, frame_color_type, - frame_mmap, + frame_data: FrameData::Mmap(frame_mmap), transform: output_info.transform, logical_region: capture_region .map(|capture_region| capture_region.logical()) @@ -391,26 +785,13 @@ impl WayshotConnection { output_capture_regions: &[(OutputInfo, Option)], cursor_overlay: bool, ) -> Result> { - let frame_copies = thread::scope(|scope| -> Result<_> { - let join_handles = output_capture_regions - .iter() - .map(|(output_info, capture_region)| { - scope.spawn(move || { - self.capture_frame_copy(cursor_overlay, output_info, *capture_region) - .map(|(frame_copy, frame_guard)| { - (frame_copy, frame_guard, output_info.clone()) - }) - }) - }) - .collect::>(); - - join_handles - .into_iter() - .flat_map(|join_handle| join_handle.join()) - .collect::>() - })?; - - Ok(frame_copies) + output_capture_regions + .iter() + .map(|(output_info, capture_region)| { + self.capture_frame_copy(cursor_overlay, output_info, *capture_region) + .map(|(frame_copy, frame_guard)| (frame_copy, frame_guard, output_info.clone())) + }) + .collect() } /// Create a layer shell surface for each output, diff --git a/libwayshot/src/screencopy.rs b/libwayshot/src/screencopy.rs index 8b01c7c6..9752a17b 100644 --- a/libwayshot/src/screencopy.rs +++ b/libwayshot/src/screencopy.rs @@ -4,6 +4,7 @@ use std::{ time::{SystemTime, UNIX_EPOCH}, }; +use gbm::BufferObject; use image::{ColorType, DynamicImage, ImageBuffer, Pixel}; use memmap2::MmapMut; use nix::{ @@ -32,6 +33,31 @@ impl Drop for FrameGuard { } } +pub struct DMAFrameGuard { + pub buffer: WlBuffer, +} +impl Drop for DMAFrameGuard { + fn drop(&mut self) { + self.buffer.destroy(); + } +} + +pub struct EGLImageGuard<'a, T: khronos_egl::api::EGL1_5> { + pub image: khronos_egl::Image, + pub(crate) egl_instance: &'a khronos_egl::Instance, + pub(crate) egl_display: khronos_egl::Display, +} + +impl<'a, T: khronos_egl::api::EGL1_5> Drop for EGLImageGuard<'a, T> { + fn drop(&mut self) { + self.egl_instance + .destroy_image(self.egl_display, self.image) + .unwrap_or_else(|e| { + tracing::error!("EGLimage destruction had error: {e}"); + }); + } +} + /// Type of frame supported by the compositor. For now we only support Argb8888, Xrgb8888, and /// Xbgr8888. /// @@ -46,6 +72,17 @@ pub struct FrameFormat { pub stride: u32, } +/// Type of DMABUF frame supported by the compositor +/// +/// See `zwlr_screencopy_frame_v1::Event::linux_dmabuf` as it's retrieved from there. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct DMAFrameFormat { + pub format: u32, + /// Size of the frame in pixels. This will always be in "landscape" so a + /// portrait 1080x1920 frame will be 1920x1080 and will need to be rotated! + pub size: Size, +} + impl FrameFormat { /// Returns the size of the frame in bytes, which is the stride * height. pub fn byte_size(&self) -> u64 { @@ -53,30 +90,38 @@ impl FrameFormat { } } -#[tracing::instrument(skip(frame_mmap))] +#[tracing::instrument(skip(frame_data))] fn create_image_buffer

( frame_format: &FrameFormat, - frame_mmap: &MmapMut, + frame_data: &FrameData, ) -> Result>> where P: Pixel, { tracing::debug!("Creating image buffer"); - ImageBuffer::from_vec( - frame_format.size.width, - frame_format.size.height, - frame_mmap.to_vec(), - ) - .ok_or(Error::BufferTooSmall) + match frame_data { + FrameData::Mmap(frame_mmap) => ImageBuffer::from_vec( + frame_format.size.width, + frame_format.size.height, + frame_mmap.to_vec(), + ) + .ok_or(Error::BufferTooSmall), + FrameData::GBMBo(_) => todo!(), + } } +#[derive(Debug)] +pub enum FrameData { + Mmap(MmapMut), + GBMBo(BufferObject<()>), +} /// The copied frame comprising of the FrameFormat, ColorType (Rgba8), and a memory backed shm /// file that holds the image data in it. #[derive(Debug)] pub struct FrameCopy { pub frame_format: FrameFormat, pub frame_color_type: ColorType, - pub frame_mmap: MmapMut, + pub frame_data: FrameData, pub transform: wl_output::Transform, /// Logical region with the transform already applied. pub logical_region: LogicalRegion, @@ -89,10 +134,10 @@ impl TryFrom<&FrameCopy> for DynamicImage { fn try_from(value: &FrameCopy) -> Result { Ok(match value.frame_color_type { ColorType::Rgb8 => { - Self::ImageRgb8(create_image_buffer(&value.frame_format, &value.frame_mmap)?) + Self::ImageRgb8(create_image_buffer(&value.frame_format, &value.frame_data)?) } ColorType::Rgba8 => { - Self::ImageRgba8(create_image_buffer(&value.frame_format, &value.frame_mmap)?) + Self::ImageRgba8(create_image_buffer(&value.frame_format, &value.frame_data)?) } _ => return Err(Error::InvalidColor), })