summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcin Radomski <dextero@google.com>2024-03-13 16:54:30 +0000
committerMarcin Radomski <dextero@google.com>2024-03-13 16:55:00 +0000
commita55065196aa142344b2f13f2ab5057b78e7a1acd (patch)
tree2022376e2c2ee9bbaa0acf8edb35eaab210576e5
parent3a0cb8eeb1aebb621aeb6079b668a19b3e270738 (diff)
downloaddrm-a55065196aa142344b2f13f2ab5057b78e7a1acd.tar.gz
Import 'drm' crateupstream
Request Document: go/android-rust-importing-crates For CL Reviewers: go/android3p#cl-review For Build Team: go/ab-third-party-imports Bug: http://b/328180645 Test: m libdrm_rust (with aosp/2997556 checked out locally) Change-Id: I32689719f1bd98740694b39e330518cba6b3e94d
-rw-r--r--.cargo_vcs_info.json6
-rw-r--r--Android.bp26
-rw-r--r--Cargo.lock749
-rw-r--r--Cargo.toml69
-rw-r--r--Cargo.toml.orig31
-rw-r--r--LICENSE23
-rw-r--r--METADATA20
-rw-r--r--MODULE_LICENSE_MIT0
-rw-r--r--OWNERS7
-rw-r--r--README.md80
-rw-r--r--cargo_embargo.json6
-rw-r--r--examples/atomic_modeset.rs198
-rw-r--r--examples/basic.rs33
-rw-r--r--examples/ffi.rs76
-rw-r--r--examples/images/1.pngbin0 -> 1937 bytes
-rw-r--r--examples/images/2.pngbin0 -> 2927 bytes
-rw-r--r--examples/images/3.pngbin0 -> 3594 bytes
-rw-r--r--examples/images/4.pngbin0 -> 2675 bytes
-rw-r--r--examples/kms_interactive.rs208
-rw-r--r--examples/legacy_modeset.rs81
-rw-r--r--examples/list_modes.rs16
-rw-r--r--examples/properties.rs49
-rw-r--r--examples/resources.rs77
-rw-r--r--examples/syncobj.rs53
-rw-r--r--examples/utils/mod.rs66
-rw-r--r--src/buffer/mod.rs120
-rw-r--r--src/control/atomic.rs71
-rw-r--r--src/control/connector.rs297
-rw-r--r--src/control/crtc.rs90
-rw-r--r--src/control/dumbbuffer.rs88
-rw-r--r--src/control/encoder.rs133
-rw-r--r--src/control/framebuffer.rs143
-rw-r--r--src/control/mod.rs1567
-rw-r--r--src/control/plane.rs96
-rw-r--r--src/control/property.rs342
-rw-r--r--src/control/syncobj.rs49
-rw-r--r--src/lib.rs351
-rw-r--r--src/util.rs19
38 files changed, 5240 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..2895e05
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,6 @@
+{
+ "git": {
+ "sha1": "328742fddc675b3370057b382eb54acbc9b48c79"
+ },
+ "path_in_vcs": ""
+} \ No newline at end of file
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..2684b07
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,26 @@
+// This file is generated by cargo_embargo.
+// Do not modify this file as changes will be overridden on upgrade.
+
+// TODO: Add license.
+rust_library {
+ name: "libdrm_rust",
+ host_supported: true,
+ crate_name: "drm",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.11.1",
+ srcs: ["src/lib.rs"],
+ edition: "2021",
+ rustlibs: [
+ "libbitflags",
+ "libbytemuck",
+ "libdrm_ffi",
+ "libdrm_fourcc",
+ "librustix",
+ ],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+ product_available: true,
+ vendor_available: true,
+}
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..3576e19
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,749 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bindgen"
+version = "0.69.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ffcebc3849946a7170a05992aac39da343a90676ab392c51a4280981d6379c2"
+dependencies = [
+ "bitflags 2.4.1",
+ "cexpr",
+ "clang-sys",
+ "lazy_static",
+ "lazycell",
+ "log",
+ "peeking_take_while",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "syn",
+ "which",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+
+[[package]]
+name = "bytemuck"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
+dependencies = [
+ "bytemuck_derive",
+]
+
+[[package]]
+name = "bytemuck_derive"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "cexpr"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clang-sys"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
+]
+
+[[package]]
+name = "clipboard-win"
+version = "5.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c57002a5d9be777c1ef967e33674dac9ebd310d8893e4e3437b14d5f0f6372cc"
+dependencies = [
+ "error-code",
+]
+
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "drm"
+version = "0.11.1"
+dependencies = [
+ "bitflags 2.4.1",
+ "bytemuck",
+ "drm-ffi",
+ "drm-fourcc",
+ "image",
+ "rustix",
+ "rustyline",
+]
+
+[[package]]
+name = "drm-ffi"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41334f8405792483e32ad05fbb9c5680ff4e84491883d2947a4757dc54cb2ac6"
+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.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d09ff881f92f118b11105ba5e34ff8f4adf27b30dae8f12e28c193af1c83176"
+dependencies = [
+ "bindgen",
+ "libc",
+ "linux-raw-sys 0.6.3",
+ "pkg-config",
+]
+
+[[package]]
+name = "either"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
+
+[[package]]
+name = "endian-type"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
+
+[[package]]
+name = "errno"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "error-code"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc"
+
+[[package]]
+name = "fd-lock"
+version = "4.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947"
+dependencies = [
+ "cfg-if",
+ "rustix",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "fdeflate"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "209098dd6dfc4445aa6111f0e98653ac323eaa4dfd212c9ca3931bf9955c31bd"
+dependencies = [
+ "simd-adler32",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "home"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "image"
+version = "0.24.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711"
+dependencies = [
+ "bytemuck",
+ "byteorder",
+ "color_quant",
+ "num-rational",
+ "num-traits",
+ "png",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
+[[package]]
+name = "libc"
+version = "0.2.151"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
+
+[[package]]
+name = "libloading"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
+dependencies = [
+ "cfg-if",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ab96045f1fabcc9fe043d9cb6900c5e1cba5c13f6aaa3d2295b496661924464"
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "memchr"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+ "simd-adler32",
+]
+
+[[package]]
+name = "nibble_vec"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
+dependencies = [
+ "smallvec",
+]
+
+[[package]]
+name = "nix"
+version = "0.27.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
+dependencies = [
+ "bitflags 2.4.1",
+ "cfg-if",
+ "libc",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "peeking_take_while"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a"
+
+[[package]]
+name = "png"
+version = "0.17.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64"
+dependencies = [
+ "bitflags 1.3.2",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "prettyplease"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5"
+dependencies = [
+ "proc-macro2",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "radix_trie"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
+dependencies = [
+ "endian-type",
+ "nibble_vec",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustix"
+version = "0.38.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
+dependencies = [
+ "bitflags 2.4.1",
+ "errno",
+ "libc",
+ "linux-raw-sys 0.4.12",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustyline"
+version = "13.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02a2d683a4ac90aeef5b1013933f6d977bd37d51ff3f4dad829d4931a7e6be86"
+dependencies = [
+ "bitflags 2.4.1",
+ "cfg-if",
+ "clipboard-win",
+ "fd-lock",
+ "home",
+ "libc",
+ "log",
+ "memchr",
+ "nix",
+ "radix_trie",
+ "unicode-segmentation",
+ "unicode-width",
+ "utf8parse",
+ "winapi",
+]
+
+[[package]]
+name = "shlex"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
+
+[[package]]
+name = "simd-adler32"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
+
+[[package]]
+name = "smallvec"
+version = "1.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
+
+[[package]]
+name = "syn"
+version = "2.0.48"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
+[[package]]
+name = "which"
+version = "4.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
+dependencies = [
+ "either",
+ "home",
+ "once_cell",
+ "rustix",
+]
+
+[[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.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.0",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.0",
+ "windows_aarch64_msvc 0.52.0",
+ "windows_i686_gnu 0.52.0",
+ "windows_i686_msvc 0.52.0",
+ "windows_x86_64_gnu 0.52.0",
+ "windows_x86_64_gnullvm 0.52.0",
+ "windows_x86_64_msvc 0.52.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..81d8cb0
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,69 @@
+# 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.65"
+name = "drm"
+version = "0.11.1"
+authors = [
+ "Tyler Slabinski <tslabinski@slabity.net>",
+ "Victoria Brekenfeld <crates-io@drakulix.de>",
+]
+exclude = [
+ ".gitignore",
+ ".github",
+]
+description = "Safe, low-level bindings to the Direct Rendering Manager API"
+readme = "README.md"
+license = "MIT"
+repository = "https://github.com/Smithay/drm-rs"
+
+[dependencies.bitflags]
+version = "2"
+
+[dependencies.bytemuck]
+version = "1.12"
+features = [
+ "extern_crate_alloc",
+ "derive",
+]
+
+[dependencies.drm-ffi]
+version = "0.7.1"
+
+[dependencies.drm-fourcc]
+version = "^2.2.0"
+
+[dependencies.rustix]
+version = "0.38.22"
+features = [
+ "mm",
+ "fs",
+]
+
+[dev-dependencies.image]
+version = "0.24"
+features = ["png"]
+default-features = false
+
+[dev-dependencies.rustix]
+version = "0.38.22"
+features = [
+ "event",
+ "mm",
+]
+
+[dev-dependencies.rustyline]
+version = "13"
+
+[features]
+use_bindgen = ["drm-ffi/use_bindgen"]
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..9f9d9fc
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,31 @@
+[package]
+name = "drm"
+description = "Safe, low-level bindings to the Direct Rendering Manager API"
+repository = "https://github.com/Smithay/drm-rs"
+version = "0.11.1"
+license = "MIT"
+authors = ["Tyler Slabinski <tslabinski@slabity.net>", "Victoria Brekenfeld <crates-io@drakulix.de>"]
+exclude = [".gitignore", ".github"]
+rust-version = "1.65"
+edition = "2021"
+
+[dependencies]
+bitflags = "2"
+bytemuck = { version = "1.12", features = ["extern_crate_alloc", "derive"] }
+drm-ffi = { path = "drm-ffi", version = "0.7.1" }
+drm-fourcc = "^2.2.0"
+rustix = { version = "0.38.22", features = ["mm", "fs"] }
+
+[dev-dependencies]
+image = { version = "0.24", default-features = false, features = ["png"] }
+rustix = { version = "0.38.22", features = ["event", "mm"] }
+rustyline = "13"
+
+[features]
+use_bindgen = ["drm-ffi/use_bindgen"]
+
+[workspace]
+members = [
+ "drm-ffi",
+ "drm-ffi/drm-sys",
+]
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..31aa793
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,23 @@
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..3630f06
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,20 @@
+name: "drm"
+description: "Safe, low-level bindings to the Direct Rendering Manager API"
+third_party {
+ identifier {
+ type: "crates.io"
+ value: "drm"
+ }
+ identifier {
+ type: "Archive"
+ value: "https://static.crates.io/crates/drm/drm-0.11.1.crate"
+ primary_source: true
+ }
+ version: "0.11.1"
+ license_type: NOTICE
+ last_upgrade_date {
+ year: 2024
+ month: 3
+ day: 13
+ }
+}
diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_MIT
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..697f117
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 688011
+include platform/prebuilts/rust:main:/OWNERS
+
+dextero@google.com
+vill@google.com
+nputikhin@google.com
+istvannador@google.com
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b2f46ec
--- /dev/null
+++ b/README.md
@@ -0,0 +1,80 @@
+# drm-rs
+
+[![Crates.io](https://img.shields.io/crates/v/drm.svg)](https://crates.io/crates/drm)
+[![docs.rs](https://docs.rs/drm/badge.svg)](https://docs.rs/drm)
+[![Build Status](https://github.com/Smithay/drm-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/Smithay/drm-rs/actions/workflows/ci.yml)
+
+A safe interface to the Direct Rendering Manager.
+
+## Direct Rendering Manager
+
+The Direct Rendering Manager is a subsystem found on multiple Unix-based
+operating systems that provides a userspace API to graphics hardware.
+See the [Wikipedia article](https://en.wikipedia.org/wiki/Direct_Rendering_Manager)
+for more details.
+
+## Usage
+
+### Basic
+
+The DRM is accessed using [ioctls](https://en.wikipedia.org/wiki/Ioctl)
+on a file representing a graphics card. These can normally be
+found in `/dev/dri`, but can also be opened in other ways (ex. udev).
+
+This crate does not provide a method of opening these files. Instead, the
+user program must provide a way to access the file descriptor representing the
+device through the [AsFd](https://doc.rust-lang.org/std/os/fd/trait.AsFd.html)
+trait. Here is a basic example using `File` as a backend:
+
+```rust
+/// A simple wrapper for a device node.
+pub 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()
+ }
+}
+
+/// 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())
+ }
+}
+```
+
+Finally, you can implement `drm::Device` to gain access to the basic DRM
+functionality:
+
+```rust
+impl drm::Device for Card {}
+
+fn main() {
+ let gpu = Card::open("/dev/dri/card0");
+ println!("{:#?}", gpu.get_driver().unwrap());
+}
+```
+
+### Control (modesetting)
+
+See [`drm::control::Device`](https://docs.rs/drm/*/drm/control/trait.Device.html)
+as well as our mode-setting examples: [`atomic_modeset`](https://github.com/Smithay/drm-rs/blob/develop/examples/atomic_modeset.rs)
+and [`legacy_modeset`](https://github.com/Smithay/drm-rs/blob/develop/examples/legacy_modeset.rs)
+
+### Rendering
+
+Rendering is done by [creating](https://docs.rs/drm/*/drm/control/trait.Device.html#method.add_framebuffer) and
+[attaching](https://docs.rs/drm/*/drm/control/trait.Device.html#method.page_flip) [framebuffers](https://docs.rs/drm/*/drm/control/framebuffer/index.html)
+to [crtcs](https://docs.rs/drm/*/drm/control/crtc/index.html).
+
+A framebuffer is created from anything implementing [`Buffer`](https://docs.rs/drm/*/drm/buffer/trait.Buffer.html) like the always
+available, but very limited, [`DumbBuffer`](https://docs.rs/drm/*/drm/control/dumbbuffer/struct.DumbBuffer.html).
+
+For faster hardware-backed buffers, checkout [gbm.rs](https://github.com/Smithay/gbm.rs).
diff --git a/cargo_embargo.json b/cargo_embargo.json
new file mode 100644
index 0000000..ebae879
--- /dev/null
+++ b/cargo_embargo.json
@@ -0,0 +1,6 @@
+{
+ "run_cargo": false,
+ "module_name_overrides": {
+ "libdrm": "libdrm_rust"
+ }
+}
diff --git a/examples/atomic_modeset.rs b/examples/atomic_modeset.rs
new file mode 100644
index 0000000..56000ee
--- /dev/null
+++ b/examples/atomic_modeset.rs
@@ -0,0 +1,198 @@
+mod utils;
+use crate::utils::*;
+
+use drm::control::Device as ControlDevice;
+use drm::Device as BasicDevice;
+
+use drm::buffer::DrmFourcc;
+
+use drm::control::{self, atomic, connector, crtc, property, AtomicCommitFlags};
+
+pub fn main() {
+ let card = Card::open_global();
+
+ card.set_client_capability(drm::ClientCapability::UniversalPlanes, true)
+ .expect("Unable to request UniversalPlanes capability");
+ card.set_client_capability(drm::ClientCapability::Atomic, true)
+ .expect("Unable to request Atomic capability");
+
+ // Load the information.
+ let res = card
+ .resource_handles()
+ .expect("Could not load normal resource ids.");
+ let coninfo: Vec<connector::Info> = res
+ .connectors()
+ .iter()
+ .flat_map(|con| card.get_connector(*con, true))
+ .collect();
+ let crtcinfo: Vec<crtc::Info> = res
+ .crtcs()
+ .iter()
+ .flat_map(|crtc| card.get_crtc(*crtc))
+ .collect();
+
+ // Filter each connector until we find one that's connected.
+ let con = coninfo
+ .iter()
+ .find(|&i| i.state() == connector::State::Connected)
+ .expect("No connected connectors");
+
+ // Get the first (usually best) mode
+ let &mode = con.modes().first().expect("No modes found on connector");
+
+ let (disp_width, disp_height) = mode.size();
+
+ // Find a crtc and FB
+ let crtc = crtcinfo.first().expect("No crtcs found");
+
+ // Select the pixel format
+ let fmt = DrmFourcc::Xrgb8888;
+
+ // Create a DB
+ // If buffer resolution is above display resolution, a ENOSPC (not enough GPU memory) error may
+ // occur
+ let mut db = card
+ .create_dumb_buffer((disp_width.into(), disp_height.into()), fmt, 32)
+ .expect("Could not create dumb buffer");
+
+ // Map it and grey it out.
+ {
+ let mut map = card
+ .map_dumb_buffer(&mut db)
+ .expect("Could not map dumbbuffer");
+ for b in map.as_mut() {
+ *b = 128;
+ }
+ }
+
+ // Create an FB:
+ let fb = card
+ .add_framebuffer(&db, 24, 32)
+ .expect("Could not create FB");
+
+ let planes = card.plane_handles().expect("Could not list planes");
+ let (better_planes, compatible_planes): (
+ Vec<control::plane::Handle>,
+ Vec<control::plane::Handle>,
+ ) = planes
+ .iter()
+ .filter(|&&plane| {
+ card.get_plane(plane)
+ .map(|plane_info| {
+ let compatible_crtcs = res.filter_crtcs(plane_info.possible_crtcs());
+ compatible_crtcs.contains(&crtc.handle())
+ })
+ .unwrap_or(false)
+ })
+ .partition(|&&plane| {
+ if let Ok(props) = card.get_properties(plane) {
+ for (&id, &val) in props.iter() {
+ if let Ok(info) = card.get_property(id) {
+ if info.name().to_str().map(|x| x == "type").unwrap_or(false) {
+ return val == (drm::control::PlaneType::Primary as u32).into();
+ }
+ }
+ }
+ }
+ false
+ });
+ let plane = *better_planes.first().unwrap_or(&compatible_planes[0]);
+
+ println!("{:#?}", mode);
+ println!("{:#?}", fb);
+ println!("{:#?}", db);
+ println!("{:#?}", plane);
+
+ let con_props = card
+ .get_properties(con.handle())
+ .expect("Could not get props of connector")
+ .as_hashmap(&card)
+ .expect("Could not get a prop from connector");
+ let crtc_props = card
+ .get_properties(crtc.handle())
+ .expect("Could not get props of crtc")
+ .as_hashmap(&card)
+ .expect("Could not get a prop from crtc");
+ let plane_props = card
+ .get_properties(plane)
+ .expect("Could not get props of plane")
+ .as_hashmap(&card)
+ .expect("Could not get a prop from plane");
+
+ let mut atomic_req = atomic::AtomicModeReq::new();
+ atomic_req.add_property(
+ con.handle(),
+ con_props["CRTC_ID"].handle(),
+ property::Value::CRTC(Some(crtc.handle())),
+ );
+ let blob = card
+ .create_property_blob(&mode)
+ .expect("Failed to create blob");
+ atomic_req.add_property(crtc.handle(), crtc_props["MODE_ID"].handle(), blob);
+ atomic_req.add_property(
+ crtc.handle(),
+ crtc_props["ACTIVE"].handle(),
+ property::Value::Boolean(true),
+ );
+ atomic_req.add_property(
+ plane,
+ plane_props["FB_ID"].handle(),
+ property::Value::Framebuffer(Some(fb)),
+ );
+ atomic_req.add_property(
+ plane,
+ plane_props["CRTC_ID"].handle(),
+ property::Value::CRTC(Some(crtc.handle())),
+ );
+ atomic_req.add_property(
+ plane,
+ plane_props["SRC_X"].handle(),
+ property::Value::UnsignedRange(0),
+ );
+ atomic_req.add_property(
+ plane,
+ plane_props["SRC_Y"].handle(),
+ property::Value::UnsignedRange(0),
+ );
+ atomic_req.add_property(
+ plane,
+ plane_props["SRC_W"].handle(),
+ property::Value::UnsignedRange((mode.size().0 as u64) << 16),
+ );
+ atomic_req.add_property(
+ plane,
+ plane_props["SRC_H"].handle(),
+ property::Value::UnsignedRange((mode.size().1 as u64) << 16),
+ );
+ atomic_req.add_property(
+ plane,
+ plane_props["CRTC_X"].handle(),
+ property::Value::SignedRange(0),
+ );
+ atomic_req.add_property(
+ plane,
+ plane_props["CRTC_Y"].handle(),
+ property::Value::SignedRange(0),
+ );
+ atomic_req.add_property(
+ plane,
+ plane_props["CRTC_W"].handle(),
+ property::Value::UnsignedRange(mode.size().0 as u64),
+ );
+ atomic_req.add_property(
+ plane,
+ plane_props["CRTC_H"].handle(),
+ property::Value::UnsignedRange(mode.size().1 as u64),
+ );
+
+ // Set the crtc
+ // On many setups, this requires root access.
+ card.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, atomic_req)
+ .expect("Failed to set mode");
+
+ let five_seconds = ::std::time::Duration::from_millis(5000);
+ ::std::thread::sleep(five_seconds);
+
+ card.destroy_framebuffer(fb).unwrap();
+ card.destroy_dumb_buffer(db).unwrap();
+}
diff --git a/examples/basic.rs b/examples/basic.rs
new file mode 100644
index 0000000..5164fda
--- /dev/null
+++ b/examples/basic.rs
@@ -0,0 +1,33 @@
+/// Check the `util` module to see how the `Card` structure is implemented.
+pub mod utils;
+use crate::utils::*;
+
+pub fn main() {
+ let card = Card::open_global();
+
+ // Attempt to acquire and release master lock
+ println!("Get Master lock: {:?}", card.acquire_master_lock());
+ println!("Release Master lock: {:?}", card.release_master_lock());
+
+ // Get the Bus ID of the device
+ println!("Getting Bus ID: {:?}", card.get_bus_id().unwrap());
+
+ // Figure out driver in use
+ println!("Getting driver info");
+ let driver = card.get_driver().unwrap();
+ println!("\tName: {:?}", driver.name());
+ println!("\tDate: {:?}", driver.date());
+ println!("\tDesc: {:?}", driver.description());
+
+ // Enable all possible client capabilities
+ println!("Setting client capabilities");
+ for &cap in capabilities::CLIENT_CAP_ENUMS {
+ println!("\t{:?}: {:?}", cap, card.set_client_capability(cap, true));
+ }
+
+ // Get driver capabilities
+ println!("Getting driver capabilities");
+ for &cap in capabilities::DRIVER_CAP_ENUMS {
+ println!("\t{:?}: {:?}", cap, card.get_driver_capability(cap));
+ }
+}
diff --git a/examples/ffi.rs b/examples/ffi.rs
new file mode 100644
index 0000000..bcff231
--- /dev/null
+++ b/examples/ffi.rs
@@ -0,0 +1,76 @@
+use drm_ffi as ffi;
+
+use std::fs::{File, OpenOptions};
+use std::os::unix::io::{AsFd, BorrowedFd};
+
+#[derive(Debug)]
+// This is our customized struct that implements the traits in drm.
+struct Card(File);
+
+// Need to implement AsRawFd before we can implement drm::Device
+impl AsFd for Card {
+ fn as_fd(&self) -> BorrowedFd<'_> {
+ self.0.as_fd()
+ }
+}
+
+impl Card {
+ fn open(path: &str) -> Self {
+ let mut options = OpenOptions::new();
+ options.read(true);
+ options.write(true);
+ Card(options.open(path).unwrap())
+ }
+
+ fn open_global() -> Self {
+ Self::open("/dev/dri/card0")
+ }
+}
+
+fn print_busid(fd: BorrowedFd<'_>) {
+ let mut buffer = Vec::new();
+ let busid = ffi::get_bus_id(fd, Some(&mut buffer));
+ println!("{:#?}", busid);
+}
+
+fn print_client(fd: BorrowedFd<'_>) {
+ let client = ffi::get_client(fd, 0);
+ println!("{:#?}", client);
+}
+
+fn print_version(fd: BorrowedFd<'_>) {
+ let mut name = Vec::new();
+ let mut date = Vec::new();
+ let mut desc = Vec::new();
+
+ let version = ffi::get_version(fd, Some(&mut name), Some(&mut date), Some(&mut desc));
+
+ println!("{:#?}", version);
+}
+
+fn print_capabilities(fd: BorrowedFd<'_>) {
+ for cty in 1.. {
+ let cap = ffi::get_capability(fd, cty);
+ match cap {
+ Ok(_) => println!("{:#?}", cap),
+ Err(_) => break,
+ }
+ }
+}
+
+fn print_token(fd: BorrowedFd<'_>) {
+ let token = ffi::auth::get_magic_token(fd);
+ println!("{:#?}", token);
+}
+
+fn main() {
+ let card = Card::open_global();
+ let fd = card.as_fd();
+
+ print_busid(fd);
+ print_client(fd);
+ print_version(fd);
+ print_capabilities(fd);
+ print_token(fd);
+ //print_stats(fd);
+}
diff --git a/examples/images/1.png b/examples/images/1.png
new file mode 100644
index 0000000..0862b32
--- /dev/null
+++ b/examples/images/1.png
Binary files differ
diff --git a/examples/images/2.png b/examples/images/2.png
new file mode 100644
index 0000000..88af7eb
--- /dev/null
+++ b/examples/images/2.png
Binary files differ
diff --git a/examples/images/3.png b/examples/images/3.png
new file mode 100644
index 0000000..4c94b9e
--- /dev/null
+++ b/examples/images/3.png
Binary files differ
diff --git a/examples/images/4.png b/examples/images/4.png
new file mode 100644
index 0000000..4062154
--- /dev/null
+++ b/examples/images/4.png
Binary files differ
diff --git a/examples/kms_interactive.rs b/examples/kms_interactive.rs
new file mode 100644
index 0000000..06c29d1
--- /dev/null
+++ b/examples/kms_interactive.rs
@@ -0,0 +1,208 @@
+/// Check the `util` module to see how the `Card` structure is implemented.
+pub mod utils;
+use crate::utils::*;
+use drm::control::{from_u32, RawResourceHandle};
+
+pub fn main() {
+ let card = Card::open_global();
+
+ // Enable all possible client capabilities
+ for &cap in capabilities::CLIENT_CAP_ENUMS {
+ if let Err(e) = card.set_client_capability(cap, true) {
+ eprintln!("Unable to activate capability {:?}: {}", cap, e);
+ return;
+ }
+ }
+
+ run_repl(&card);
+}
+
+fn run_repl(card: &Card) {
+ // Load a set of numbered images
+ let images = [
+ images::load_image("1.png"),
+ images::load_image("2.png"),
+ images::load_image("3.png"),
+ images::load_image("4.png"),
+ ];
+
+ for image in &images {
+ // Create the Dumbbuffer
+ let fmt = drm::buffer::DrmFourcc::Xrgb8888;
+ let mut db = card
+ .create_dumb_buffer(image.dimensions(), fmt, 32)
+ .unwrap();
+
+ // Create a Framebuffer to represent it
+ let _fb = card.add_framebuffer(&db, 24, 32).unwrap();
+
+ // Load the image into the buffer
+ {
+ let mut mapping = card.map_dumb_buffer(&mut db).unwrap();
+ let buffer = mapping.as_mut();
+ for (img_px, map_px) in image.pixels().zip(buffer.chunks_exact_mut(4)) {
+ // Assuming little endian, it's BGRA
+ map_px[0] = img_px[0]; // Blue
+ map_px[1] = img_px[1]; // Green
+ map_px[2] = img_px[2]; // Red
+ map_px[3] = img_px[3]; // Alpha
+ }
+ };
+ }
+
+ // Using rustyline to create the interactive prompt.
+ let editor_config = rustyline::config::Builder::new()
+ .max_history_size(256)
+ .unwrap()
+ .completion_type(rustyline::config::CompletionType::List)
+ .edit_mode(rustyline::config::EditMode::Vi)
+ .auto_add_history(true)
+ .build();
+ let mut kms_editor = rustyline::Editor::<(), _>::with_config(editor_config).unwrap();
+ let mut atomic_editor = rustyline::Editor::<(), _>::with_config(editor_config).unwrap();
+
+ for line in kms_editor.iter("KMS>> ").map(|x| x.unwrap()) {
+ let args: Vec<_> = line.split_whitespace().collect();
+ match &args[..] {
+ ["CreateAtomicSet"] => {
+ for line in atomic_editor.iter("Atomic>> ").map(|x| x.unwrap()) {
+ let args: Vec<_> = line.split_whitespace().collect();
+ match &args[..] {
+ ["Quit"] => break,
+ args => println!("{:?}", args),
+ }
+ }
+ }
+ // Destroying a framebuffer
+ ["DestroyFramebuffer", handle] => {
+ let handle: u32 = str::parse(handle).unwrap();
+ let handle: drm::control::framebuffer::Handle = from_u32(handle).unwrap();
+ if let Err(err) = card.destroy_framebuffer(handle) {
+ println!("Unable to destroy framebuffer ({:?}): {}", handle, err);
+ }
+ }
+ // Print out all resources
+ ["GetResources"] => {
+ let resources = card.resource_handles().unwrap();
+ println!("\tConnectors: {:?}", resources.connectors());
+ println!("\tEncoders: {:?}", resources.encoders());
+ println!("\tCRTCS: {:?}", resources.crtcs());
+ println!("\tFramebuffers: {:?}", resources.framebuffers());
+ let planes = card.plane_handles().unwrap();
+ println!("\tPlanes: {:?}", planes);
+ }
+ // Print out the values of a specific property
+ ["GetProperty", handle] => {
+ let handle: u32 = str::parse(handle).unwrap();
+ let handle: drm::control::property::Handle = from_u32(handle).unwrap();
+ let property = card.get_property(handle).unwrap();
+ println!("\tName: {:?}", property.name());
+ println!("\tMutable: {:?}", property.mutable());
+ println!("\tAtomic: {:?}", property.atomic());
+ println!("\tValue: {:#?}", property.value_type());
+ }
+ // Get the property-value pairs of a single resource
+ ["GetProperties", handle] => match HandleWithProperties::from_str(card, handle) {
+ Ok(handle) => {
+ let props = match handle {
+ HandleWithProperties::Connector(handle) => {
+ card.get_properties(handle).unwrap()
+ }
+ HandleWithProperties::CRTC(handle) => card.get_properties(handle).unwrap(),
+ HandleWithProperties::Plane(handle) => card.get_properties(handle).unwrap(),
+ };
+ for (id, val) in props.iter() {
+ println!("\tProperty: {:?}\tValue: {:?}", id, val);
+ }
+ }
+ Err(_) => println!("Unknown handle or handle has no properties"),
+ },
+ // Set a property's value on a resource
+ ["SetProperty", handle, property, value] => {
+ let property: u32 = str::parse(property).unwrap();
+ let property: drm::control::property::Handle = from_u32(property).unwrap();
+ let value: u64 = str::parse(value).unwrap();
+
+ match HandleWithProperties::from_str(card, handle) {
+ Ok(handle) => {
+ match handle {
+ HandleWithProperties::Connector(handle) => {
+ println!("\t{:?}", card.set_property(handle, property, value));
+ }
+ HandleWithProperties::CRTC(handle) => {
+ println!("\t{:?}", card.set_property(handle, property, value));
+ }
+ HandleWithProperties::Plane(handle) => {
+ println!("\t{:?}", card.set_property(handle, property, value));
+ }
+ };
+ }
+ Err(_) => println!("Unknown handle or handle has no properties"),
+ };
+ }
+ ["GetModes", handle] => match HandleWithProperties::from_str(card, handle) {
+ Ok(HandleWithProperties::Connector(handle)) => {
+ let modes = card.get_modes(handle).unwrap();
+ for mode in modes {
+ println!("\tName:\t{:?}", mode.name());
+ println!("\t\tSize:\t{:?}", mode.size());
+ println!("\t\tRefresh:\t{:?}", mode.vrefresh());
+ }
+ }
+ _ => println!("Unknown handle or handle is not a connector"),
+ },
+ ["help"] => {
+ println!("CreateAtomicSet");
+ println!("DestroyFramebuffer <handle>");
+ println!("GetResources");
+ println!("GetProperty <handle>");
+ println!("GetProperties <handle>");
+ println!("SetProperty <handle> <poperty> <value>");
+ println!("GetModes <handle>");
+ }
+ ["quit"] => break,
+ [] => (),
+ _ => {
+ println!("Unknown command");
+ }
+ }
+ }
+}
+
+#[allow(clippy::upper_case_acronyms)]
+enum HandleWithProperties {
+ Connector(drm::control::connector::Handle),
+ CRTC(drm::control::crtc::Handle),
+ Plane(drm::control::plane::Handle),
+}
+
+impl HandleWithProperties {
+ // This is a helper command that will take a string of a number and lookup
+ // the corresponding resource.
+ fn from_str(card: &Card, handle: &str) -> Result<Self, ()> {
+ let handle: u32 = str::parse(handle).unwrap();
+ let handle = RawResourceHandle::new(handle).unwrap();
+
+ let rhandles = card.resource_handles().unwrap();
+ for connector in rhandles.connectors().iter().map(|h| (*h).into()) {
+ if handle == connector {
+ return Ok(HandleWithProperties::Connector(handle.into()));
+ }
+ }
+
+ for crtc in rhandles.crtcs().iter().map(|h| (*h).into()) {
+ if handle == crtc {
+ return Ok(HandleWithProperties::CRTC(handle.into()));
+ }
+ }
+
+ let phandles = card.plane_handles().unwrap();
+ for plane in phandles.iter().map(|h| (*h).into()) {
+ if handle == plane {
+ return Ok(HandleWithProperties::Plane(handle.into()));
+ }
+ }
+
+ Err(())
+ }
+}
diff --git a/examples/legacy_modeset.rs b/examples/legacy_modeset.rs
new file mode 100644
index 0000000..204e6d5
--- /dev/null
+++ b/examples/legacy_modeset.rs
@@ -0,0 +1,81 @@
+mod utils;
+use crate::utils::*;
+
+use drm::control::Device as ControlDevice;
+
+use drm::buffer::DrmFourcc;
+
+use drm::control::{connector, crtc};
+
+pub fn main() {
+ let card = Card::open_global();
+
+ // Load the information.
+ let res = card
+ .resource_handles()
+ .expect("Could not load normal resource ids.");
+ let coninfo: Vec<connector::Info> = res
+ .connectors()
+ .iter()
+ .flat_map(|con| card.get_connector(*con, true))
+ .collect();
+ let crtcinfo: Vec<crtc::Info> = res
+ .crtcs()
+ .iter()
+ .flat_map(|crtc| card.get_crtc(*crtc))
+ .collect();
+
+ // Filter each connector until we find one that's connected.
+ let con = coninfo
+ .iter()
+ .find(|&i| i.state() == connector::State::Connected)
+ .expect("No connected connectors");
+
+ // Get the first (usually best) mode
+ let &mode = con.modes().first().expect("No modes found on connector");
+
+ let (disp_width, disp_height) = mode.size();
+
+ // Find a crtc and FB
+ let crtc = crtcinfo.first().expect("No crtcs found");
+
+ // Select the pixel format
+ let fmt = DrmFourcc::Xrgb8888;
+
+ // Create a DB
+ // If buffer resolution is larger than display resolution, an ENOSPC (not enough video memory)
+ // error may occur
+ let mut db = card
+ .create_dumb_buffer((disp_width.into(), disp_height.into()), fmt, 32)
+ .expect("Could not create dumb buffer");
+
+ // Map it and grey it out.
+ {
+ let mut map = card
+ .map_dumb_buffer(&mut db)
+ .expect("Could not map dumbbuffer");
+ for b in map.as_mut() {
+ *b = 128;
+ }
+ }
+
+ // Create an FB:
+ let fb = card
+ .add_framebuffer(&db, 24, 32)
+ .expect("Could not create FB");
+
+ println!("{:#?}", mode);
+ println!("{:#?}", fb);
+ println!("{:#?}", db);
+
+ // Set the crtc
+ // On many setups, this requires root access.
+ card.set_crtc(crtc.handle(), Some(fb), (0, 0), &[con.handle()], Some(mode))
+ .expect("Could not set CRTC");
+
+ let five_seconds = ::std::time::Duration::from_millis(5000);
+ ::std::thread::sleep(five_seconds);
+
+ card.destroy_framebuffer(fb).unwrap();
+ card.destroy_dumb_buffer(db).unwrap();
+}
diff --git a/examples/list_modes.rs b/examples/list_modes.rs
new file mode 100644
index 0000000..bea9b18
--- /dev/null
+++ b/examples/list_modes.rs
@@ -0,0 +1,16 @@
+/// Check the `util` module to see how the `Card` structure is implemented.
+pub mod utils;
+use crate::utils::*;
+
+pub fn main() {
+ let card = Card::open_global();
+
+ let resources = card.resource_handles().unwrap();
+ for connector in resources.connectors().iter() {
+ let info = card.get_connector(*connector, false).unwrap();
+ println!("Connector {:?}: {:?}", info.interface(), info.state());
+ if info.state() == drm::control::connector::State::Connected {
+ println!("\t Modes:\n{:#?}", info.modes());
+ }
+ }
+}
diff --git a/examples/properties.rs b/examples/properties.rs
new file mode 100644
index 0000000..83ffde3
--- /dev/null
+++ b/examples/properties.rs
@@ -0,0 +1,49 @@
+/// Check the `util` module to see how the `Card` structure is implemented.
+pub mod utils;
+use crate::utils::*;
+
+fn print_properties<T: drm::control::ResourceHandle>(card: &Card, handle: T) {
+ let props = card.get_properties(handle).unwrap();
+
+ for (&id, &val) in props.iter() {
+ println!("Property: {:?}", id);
+ let info = card.get_property(id).unwrap();
+ println!("{:?}", info.name());
+ println!("{:#?}", info.value_type());
+ println!("Mutable: {}", info.mutable());
+ println!("Atomic: {}", info.atomic());
+ println!("Value: {:?}", info.value_type().convert_value(val));
+ println!();
+ }
+}
+
+pub fn main() {
+ let card = Card::open_global();
+
+ // Enable all possible client capabilities
+ for &cap in capabilities::CLIENT_CAP_ENUMS {
+ if let Err(e) = card.set_client_capability(cap, true) {
+ eprintln!("Unable to activate capability {:?}: {}", cap, e);
+ return;
+ }
+ }
+
+ let resources = card.resource_handles().unwrap();
+ let plane_res = card.plane_handles().unwrap();
+
+ for &handle in resources.connectors() {
+ print_properties(&card, handle);
+ }
+
+ for &handle in resources.framebuffers() {
+ print_properties(&card, handle);
+ }
+
+ for &handle in resources.crtcs() {
+ print_properties(&card, handle);
+ }
+
+ for handle in plane_res {
+ print_properties(&card, handle);
+ }
+}
diff --git a/examples/resources.rs b/examples/resources.rs
new file mode 100644
index 0000000..0379c3f
--- /dev/null
+++ b/examples/resources.rs
@@ -0,0 +1,77 @@
+/// Check the `util` module to see how the `Card` structure is implemented.
+pub mod utils;
+use crate::utils::*;
+
+pub fn main() {
+ let card = Card::open_global();
+
+ // Enable all possible client capabilities
+ for &cap in capabilities::CLIENT_CAP_ENUMS {
+ if let Err(e) = card.set_client_capability(cap, true) {
+ eprintln!("Unable to activate capability {:?}: {}", cap, e);
+ return;
+ }
+ }
+
+ let resources = card.resource_handles().unwrap();
+ let plane_res = card.plane_handles().unwrap();
+
+ // Print out all card resource handles
+ println!("Connectors:\t{:?}", resources.connectors());
+ println!("Encoders:\t{:?}", resources.encoders());
+ println!("CRTCs:\t\t{:?}", resources.crtcs());
+ println!("Framebuffers:\t{:?}", resources.framebuffers());
+ println!("Planes:\t\t{:?}", plane_res);
+
+ for &handle in resources.connectors() {
+ let info = card.get_connector(handle, false).unwrap();
+ println!("Connector: {:?}", handle);
+ println!("\t{:?}-{}", info.interface(), info.interface_id());
+ println!("\t{:?}", info.state());
+ println!("\t{:?}", info.size());
+ println!("\t{:?}", info.encoders());
+ println!("\t{:?}", info.current_encoder());
+
+ for mode in card.get_modes(handle).unwrap() {
+ println!("{:?}", mode);
+ }
+ }
+ println!("\n");
+
+ for &handle in resources.encoders() {
+ let info = card.get_encoder(handle).unwrap();
+ println!("Encoder: {:?}", handle);
+ println!("\t{:?}", info.kind());
+ println!("\t{:?}", info.crtc());
+ }
+ println!("\n");
+
+ for &handle in resources.crtcs() {
+ let info = card.get_crtc(handle).unwrap();
+ println!("CRTC: {:?}", handle);
+ println!("\tPosition: {:?}", info.position());
+ println!("\tMode: {:?}", info.mode());
+ println!("\tFramebuffer: {:?}", info.framebuffer());
+ println!("\tGamma Length: {:?}", info.gamma_length());
+ }
+ println!("\n");
+
+ for &handle in resources.framebuffers() {
+ let info = card.get_framebuffer(handle).unwrap();
+ println!("Framebuffer: {:?}", handle);
+ println!("\tSize: {:?}", info.size());
+ println!("\tPitch: {:?}", info.pitch());
+ println!("\tBPP: {:?}", info.bpp());
+ println!("\tDepth: {:?}", info.depth());
+ }
+
+ println!("\n");
+
+ for handle in plane_res {
+ let info = card.get_plane(handle).unwrap();
+ println!("Plane: {:?}", handle);
+ println!("\tCRTC: {:?}", info.crtc());
+ println!("\tFramebuffer: {:?}", info.framebuffer());
+ println!("\tFormats: {:?}", info.formats());
+ }
+}
diff --git a/examples/syncobj.rs b/examples/syncobj.rs
new file mode 100644
index 0000000..05fbc50
--- /dev/null
+++ b/examples/syncobj.rs
@@ -0,0 +1,53 @@
+/// Check the `util` module to see how the `Card` structure is implemented.
+pub mod utils;
+
+use crate::utils::*;
+use rustix::event::PollFlags;
+use std::{
+ io,
+ os::unix::io::{AsFd, OwnedFd},
+};
+
+impl Card {
+ fn simulate_command_submission(&self) -> io::Result<OwnedFd> {
+ // Create a temporary syncobj to receive the command fence.
+ let syncobj = self.create_syncobj(false)?;
+
+ let sync_file = {
+ // Fake a command submission by signalling the syncobj immediately. The kernel
+ // attaches a null fence object which is always signalled. Other than this, there
+ // isn't a good way to create and signal a fence object from user-mode, so an actual
+ // device is required to test this properly.
+ //
+ // For a real device, the syncobj handle should be passed to a command submission
+ // which is expected to set a fence to be signalled upon completion.
+ self.syncobj_signal(&[syncobj])?;
+
+ // Export fence set by previous ioctl to file descriptor.
+ self.syncobj_to_fd(syncobj, true)
+ };
+
+ // The sync file descriptor constitutes ownership of the fence, so the syncobj can be
+ // safely destroyed.
+ self.destroy_syncobj(syncobj)?;
+
+ sync_file
+ }
+}
+
+fn main() {
+ let card = Card::open_global();
+ let sync_file = card.simulate_command_submission().unwrap();
+ let fd = sync_file.as_fd();
+
+ // Poll for readability. The DRM fence object will directly wake the thread when signalled.
+ //
+ // Alternatively, Tokio's AsyncFd may be used like so:
+ //
+ // use tokio::io::{Interest, unix::AsyncFd};
+ // let afd = AsyncFd::with_interest(sync_file, Interest::READABLE).unwrap();
+ // let future = async move { afd.readable().await.unwrap().retain_ready() };
+ // future.await;
+ let mut poll_fds = [rustix::event::PollFd::new(&fd, PollFlags::IN)];
+ rustix::event::poll(&mut poll_fds, -1).unwrap();
+}
diff --git a/examples/utils/mod.rs b/examples/utils/mod.rs
new file mode 100644
index 0000000..2001fbe
--- /dev/null
+++ b/examples/utils/mod.rs
@@ -0,0 +1,66 @@
+#![allow(dead_code)]
+
+pub use drm::control::Device as ControlDevice;
+pub use drm::Device;
+
+#[derive(Debug)]
+/// A simple wrapper for a device node.
+pub struct Card(std::fs::File);
+
+/// Implementing `AsFd` is a prerequisite to implementing the traits found
+/// in this crate. Here, we are just calling `as_fd()` on the inner File.
+impl std::os::unix::io::AsFd for Card {
+ fn as_fd(&self) -> std::os::unix::io::BorrowedFd<'_> {
+ self.0.as_fd()
+ }
+}
+
+/// With `AsFd` implemented, we can now implement `drm::Device`.
+impl Device for Card {}
+impl ControlDevice 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())
+ }
+
+ pub fn open_global() -> Self {
+ Self::open("/dev/dri/card0")
+ }
+}
+
+pub mod capabilities {
+ use drm::ClientCapability as CC;
+ pub const CLIENT_CAP_ENUMS: &[CC] = &[CC::Stereo3D, CC::UniversalPlanes, CC::Atomic];
+
+ use drm::DriverCapability as DC;
+ pub const DRIVER_CAP_ENUMS: &[DC] = &[
+ DC::DumbBuffer,
+ DC::VBlankHighCRTC,
+ DC::DumbPreferredDepth,
+ DC::DumbPreferShadow,
+ DC::Prime,
+ DC::MonotonicTimestamp,
+ DC::ASyncPageFlip,
+ DC::CursorWidth,
+ DC::CursorHeight,
+ DC::AddFB2Modifiers,
+ DC::PageFlipTarget,
+ DC::CRTCInVBlankEvent,
+ DC::SyncObj,
+ DC::TimelineSyncObj,
+ ];
+}
+
+pub mod images {
+ use image;
+
+ pub fn load_image(name: &str) -> image::RgbaImage {
+ let path = format!("examples/images/{}", name);
+ image::open(path).unwrap().to_rgba8()
+ }
+}
diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs
new file mode 100644
index 0000000..45de1b5
--- /dev/null
+++ b/src/buffer/mod.rs
@@ -0,0 +1,120 @@
+//! Memory management and buffer functionality that the DRM subsystem exposes.
+//!
+//! # Summary
+//!
+//! The DRM subsystem exposes functionality for managing memory on modern GPU
+//! devices using a system called the Graphics Execution Manager (GEM). This
+//! system manages GPU buffers and exposes them to userspace using 32-bit
+//! handles. These handles are automatically reference counted in the kernel.
+//!
+//! GEM provides a small API for sharing buffers between processes. However, it
+//! does not provide any generic API for creating these. Instead, each driver
+//! provides its own method of creating these buffers. The `libgbm` library
+//! (part of the mesa project) provides a driver agnostic method of creating
+//! these buffers.
+//!
+//! There are two methods of sharing a GEM handle between processes:
+//!
+//! 1. Using `Flink` to globally publish a handle using a 32-bit 'name'. This
+//! requires either holding the DRM Master lock or having the process'
+//! [`AuthToken`](struct@crate::AuthToken) authenticated. However, any process can
+//! open these handles if they know (or even guess) the global name.
+//!
+//! 2. Converting the GEM handle into a PRIME file descriptor, and passing it
+//! like a regular one. This allows better control and security, and is the
+//! recommended method of sharing buffers.
+
+use crate::control;
+pub use drm_fourcc::{DrmFourcc, DrmModifier, DrmVendor, UnrecognizedFourcc, UnrecognizedVendor};
+
+/// A handle to a GEM buffer
+///
+/// # Notes
+///
+/// There are no guarantees that this handle is valid. It is up to the user
+/// to make sure this handle does not outlive the underlying buffer, and to
+/// prevent buffers from leaking by properly closing them after they are done.
+#[repr(transparent)]
+#[derive(Copy, Clone, Hash, PartialEq, Eq)]
+pub struct Handle(control::RawResourceHandle);
+
+// Safety: Handle is repr(transparent) over NonZeroU32
+unsafe impl bytemuck::ZeroableInOption for Handle {}
+unsafe impl bytemuck::PodInOption for Handle {}
+
+impl From<Handle> for control::RawResourceHandle {
+ fn from(handle: Handle) -> Self {
+ handle.0
+ }
+}
+
+impl From<Handle> for u32 {
+ fn from(handle: Handle) -> Self {
+ handle.0.into()
+ }
+}
+
+impl From<control::RawResourceHandle> for Handle {
+ fn from(handle: control::RawResourceHandle) -> Self {
+ Handle(handle)
+ }
+}
+
+impl std::fmt::Debug for Handle {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ f.debug_tuple("buffer::Handle").field(&self.0).finish()
+ }
+}
+
+/// The name of a GEM buffer.
+///
+/// # Notes
+///
+/// There are no guarantees that this name is valid. It is up to the user
+/// to make sure this name does not outlive the underlying buffer, and to
+/// prevent buffers from leaking by properly closing them after they are done.
+#[repr(transparent)]
+#[derive(Copy, Clone, Hash, PartialEq, Eq)]
+pub struct Name(u32);
+
+impl From<Name> for u32 {
+ fn from(name: Name) -> u32 {
+ name.0
+ }
+}
+
+impl std::fmt::Debug for Name {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ f.debug_tuple("buffer::Name").field(&self.0).finish()
+ }
+}
+
+/// Common functionality of all regular buffers.
+pub trait Buffer {
+ /// The width and height of the buffer.
+ fn size(&self) -> (u32, u32);
+ /// The format of the buffer.
+ fn format(&self) -> DrmFourcc;
+ /// The pitch of the buffer.
+ fn pitch(&self) -> u32;
+ /// The handle to the buffer.
+ fn handle(&self) -> Handle;
+}
+
+/// Planar buffers are buffers where each channel/plane is in its own buffer.
+///
+/// Each plane has their own handle, pitch, and offsets.
+pub trait PlanarBuffer {
+ /// The width and height of the buffer.
+ fn size(&self) -> (u32, u32);
+ /// The format of the buffer.
+ fn format(&self) -> DrmFourcc;
+ /// The modifier of the buffer.
+ fn modifier(&self) -> Option<DrmModifier>;
+ /// The pitches of the buffer.
+ fn pitches(&self) -> [u32; 4];
+ /// The handles to the buffer.
+ fn handles(&self) -> [Option<Handle>; 4];
+ /// The offsets of the buffer.
+ fn offsets(&self) -> [u32; 4];
+}
diff --git a/src/control/atomic.rs b/src/control/atomic.rs
new file mode 100644
index 0000000..e2a8c3a
--- /dev/null
+++ b/src/control/atomic.rs
@@ -0,0 +1,71 @@
+//! Helpers for atomic modesetting.
+
+use crate::control;
+
+/// Helper struct to construct atomic commit requests
+#[derive(Debug, Clone, Default)]
+pub struct AtomicModeReq {
+ pub(super) objects: Vec<control::RawResourceHandle>,
+ pub(super) count_props_per_object: Vec<u32>,
+ pub(super) props: Vec<control::property::Handle>,
+ pub(super) values: Vec<control::property::RawValue>,
+}
+
+impl AtomicModeReq {
+ /// Create a new and empty atomic commit request
+ pub fn new() -> AtomicModeReq {
+ Self::default()
+ }
+
+ /// Add a property and value pair for a given raw resource to the request
+ pub fn add_raw_property(
+ &mut self,
+ obj_id: control::RawResourceHandle,
+ prop_id: control::property::Handle,
+ value: control::property::RawValue,
+ ) {
+ // add object if missing (also to count_props_per_object)
+ let (idx, prop_count) = match self.objects.binary_search(&obj_id) {
+ Ok(idx) => (idx, self.count_props_per_object[idx]),
+ Err(new_idx) => {
+ self.objects.insert(new_idx, obj_id);
+ self.count_props_per_object.insert(new_idx, 0);
+ (new_idx, 0)
+ }
+ };
+
+ // get start of our objects props
+ let prop_slice_start = self.count_props_per_object.iter().take(idx).sum::<u32>() as usize;
+ // get end
+ let prop_slice_end = prop_slice_start + prop_count as usize;
+
+ // search for existing prop entry
+ match self.props[prop_slice_start..prop_slice_end]
+ .binary_search_by_key(&Into::<u32>::into(prop_id), |x| (*x).into())
+ {
+ // prop exists, override
+ Ok(prop_idx) => {
+ self.values[prop_slice_start + prop_idx] = value;
+ }
+ Err(prop_idx) => {
+ // increase prop count
+ self.count_props_per_object[idx] += 1;
+ // insert prop, insert value
+ self.props.insert(prop_slice_start + prop_idx, prop_id);
+ self.values.insert(prop_slice_start + prop_idx, value);
+ }
+ }
+ }
+
+ /// Add a property and value pair for a given handle to the request
+ pub fn add_property<H>(
+ &mut self,
+ handle: H,
+ property: control::property::Handle,
+ value: control::property::Value,
+ ) where
+ H: control::ResourceHandle,
+ {
+ self.add_raw_property(handle.into(), property, value.into())
+ }
+}
diff --git a/src/control/connector.rs b/src/control/connector.rs
new file mode 100644
index 0000000..9fc4755
--- /dev/null
+++ b/src/control/connector.rs
@@ -0,0 +1,297 @@
+//! # Connector
+//!
+//! Represents the physical output, such as a DisplayPort or VGA connector.
+//!
+//! A Connector is the physical connection between the display controller and
+//! a display. These objects keep track of connection information and state,
+//! including the modes that the current display supports.
+
+use crate::control;
+use drm_ffi as ffi;
+
+/// A handle to a connector
+#[repr(transparent)]
+#[derive(Copy, Clone, Hash, PartialEq, Eq)]
+pub struct Handle(control::RawResourceHandle);
+
+// Safety: Handle is repr(transparent) over NonZeroU32
+unsafe impl bytemuck::ZeroableInOption for Handle {}
+unsafe impl bytemuck::PodInOption for Handle {}
+
+impl From<Handle> for control::RawResourceHandle {
+ fn from(handle: Handle) -> Self {
+ handle.0
+ }
+}
+
+impl From<Handle> for u32 {
+ fn from(handle: Handle) -> Self {
+ handle.0.into()
+ }
+}
+
+impl From<control::RawResourceHandle> for Handle {
+ fn from(handle: control::RawResourceHandle) -> Self {
+ Handle(handle)
+ }
+}
+
+impl control::ResourceHandle for Handle {
+ const FFI_TYPE: u32 = ffi::DRM_MODE_OBJECT_CONNECTOR;
+}
+
+impl std::fmt::Debug for Handle {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ f.debug_tuple("connector::Handle").field(&self.0).finish()
+ }
+}
+
+/// Information about a connector
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub struct Info {
+ pub(crate) handle: Handle,
+ pub(crate) interface: Interface,
+ pub(crate) interface_id: u32,
+ pub(crate) connection: State,
+ pub(crate) size: Option<(u32, u32)>,
+ pub(crate) modes: Vec<control::Mode>,
+ pub(crate) encoders: Vec<control::encoder::Handle>,
+ pub(crate) curr_enc: Option<control::encoder::Handle>,
+ pub(crate) subpixel: SubPixel,
+}
+
+impl Info {
+ /// Returns the handle to this connector.
+ pub fn handle(&self) -> Handle {
+ self.handle
+ }
+
+ /// Returns the type of `Interface` of this connector.
+ pub fn interface(&self) -> Interface {
+ self.interface
+ }
+
+ /// Returns the interface ID of this connector.
+ ///
+ /// When multiple connectors have the same `Interface`, they will have
+ /// different interface IDs.
+ pub fn interface_id(&self) -> u32 {
+ self.interface_id
+ }
+
+ /// Returns the `State` of this connector.
+ pub fn state(&self) -> State {
+ self.connection
+ }
+
+ /// Returns the size of the display (in millimeters) if connected.
+ pub fn size(&self) -> Option<(u32, u32)> {
+ self.size
+ }
+
+ /// Returns a list of encoders that can be possibly used by this connector.
+ pub fn encoders(&self) -> &[control::encoder::Handle] {
+ &self.encoders
+ }
+
+ /// Returns a list of modes this connector reports as supported.
+ pub fn modes(&self) -> &[control::Mode] {
+ &self.modes
+ }
+
+ /// Returns the current encoder attached to this connector.
+ pub fn current_encoder(&self) -> Option<control::encoder::Handle> {
+ self.curr_enc
+ }
+
+ /// Subpixel order of the connected sink
+ pub fn subpixel(&self) -> SubPixel {
+ self.subpixel
+ }
+}
+
+/// A physical interface type.
+#[allow(missing_docs)]
+#[allow(clippy::upper_case_acronyms)]
+#[non_exhaustive]
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub enum Interface {
+ Unknown,
+ VGA,
+ DVII,
+ DVID,
+ DVIA,
+ Composite,
+ SVideo,
+ LVDS,
+ Component,
+ NinePinDIN,
+ DisplayPort,
+ HDMIA,
+ HDMIB,
+ TV,
+ EmbeddedDisplayPort,
+ Virtual,
+ DSI,
+ DPI,
+ Writeback,
+ SPI,
+ USB,
+}
+
+impl Interface {
+ /// Get interface name as string
+ pub fn as_str(&self) -> &'static str {
+ // source: https://github.com/torvalds/linux/blob/489fa31ea873282b41046d412ec741f93946fc2d/drivers/gpu/drm/drm_connector.c#L89
+ match self {
+ Interface::Unknown => "Unknown",
+ Interface::VGA => "VGA",
+ Interface::DVII => "DVI-I",
+ Interface::DVID => "DVI-D",
+ Interface::DVIA => "DVI-A",
+ Interface::Composite => "Composite",
+ Interface::SVideo => "SVIDEO",
+ Interface::LVDS => "LVDS",
+ Interface::Component => "Component",
+ Interface::NinePinDIN => "DIN",
+ Interface::DisplayPort => "DP",
+ Interface::HDMIA => "HDMI-A",
+ Interface::HDMIB => "HDMI-B",
+ Interface::TV => "TV",
+ Interface::EmbeddedDisplayPort => "eDP",
+ Interface::Virtual => "Virtual",
+ Interface::DSI => "DSI",
+ Interface::DPI => "DPI",
+ Interface::Writeback => "Writeback",
+ Interface::SPI => "SPI",
+ Interface::USB => "USB",
+ }
+ }
+}
+
+impl From<u32> for Interface {
+ fn from(n: u32) -> Self {
+ match n {
+ ffi::DRM_MODE_CONNECTOR_Unknown => Interface::Unknown,
+ ffi::DRM_MODE_CONNECTOR_VGA => Interface::VGA,
+ ffi::DRM_MODE_CONNECTOR_DVII => Interface::DVII,
+ ffi::DRM_MODE_CONNECTOR_DVID => Interface::DVID,
+ ffi::DRM_MODE_CONNECTOR_DVIA => Interface::DVIA,
+ ffi::DRM_MODE_CONNECTOR_Composite => Interface::Composite,
+ ffi::DRM_MODE_CONNECTOR_SVIDEO => Interface::SVideo,
+ ffi::DRM_MODE_CONNECTOR_LVDS => Interface::LVDS,
+ ffi::DRM_MODE_CONNECTOR_Component => Interface::Component,
+ ffi::DRM_MODE_CONNECTOR_9PinDIN => Interface::NinePinDIN,
+ ffi::DRM_MODE_CONNECTOR_DisplayPort => Interface::DisplayPort,
+ ffi::DRM_MODE_CONNECTOR_HDMIA => Interface::HDMIA,
+ ffi::DRM_MODE_CONNECTOR_HDMIB => Interface::HDMIB,
+ ffi::DRM_MODE_CONNECTOR_TV => Interface::TV,
+ ffi::DRM_MODE_CONNECTOR_eDP => Interface::EmbeddedDisplayPort,
+ ffi::DRM_MODE_CONNECTOR_VIRTUAL => Interface::Virtual,
+ ffi::DRM_MODE_CONNECTOR_DSI => Interface::DSI,
+ ffi::DRM_MODE_CONNECTOR_DPI => Interface::DPI,
+ ffi::DRM_MODE_CONNECTOR_WRITEBACK => Interface::Writeback,
+ ffi::DRM_MODE_CONNECTOR_SPI => Interface::SPI,
+ ffi::DRM_MODE_CONNECTOR_USB => Interface::USB,
+ _ => Interface::Unknown,
+ }
+ }
+}
+
+impl From<Interface> for u32 {
+ fn from(interface: Interface) -> Self {
+ match interface {
+ Interface::Unknown => ffi::DRM_MODE_CONNECTOR_Unknown,
+ Interface::VGA => ffi::DRM_MODE_CONNECTOR_VGA,
+ Interface::DVII => ffi::DRM_MODE_CONNECTOR_DVII,
+ Interface::DVID => ffi::DRM_MODE_CONNECTOR_DVID,
+ Interface::DVIA => ffi::DRM_MODE_CONNECTOR_DVIA,
+ Interface::Composite => ffi::DRM_MODE_CONNECTOR_Composite,
+ Interface::SVideo => ffi::DRM_MODE_CONNECTOR_SVIDEO,
+ Interface::LVDS => ffi::DRM_MODE_CONNECTOR_LVDS,
+ Interface::Component => ffi::DRM_MODE_CONNECTOR_Component,
+ Interface::NinePinDIN => ffi::DRM_MODE_CONNECTOR_9PinDIN,
+ Interface::DisplayPort => ffi::DRM_MODE_CONNECTOR_DisplayPort,
+ Interface::HDMIA => ffi::DRM_MODE_CONNECTOR_HDMIA,
+ Interface::HDMIB => ffi::DRM_MODE_CONNECTOR_HDMIB,
+ Interface::TV => ffi::DRM_MODE_CONNECTOR_TV,
+ Interface::EmbeddedDisplayPort => ffi::DRM_MODE_CONNECTOR_eDP,
+ Interface::Virtual => ffi::DRM_MODE_CONNECTOR_VIRTUAL,
+ Interface::DSI => ffi::DRM_MODE_CONNECTOR_DSI,
+ Interface::DPI => ffi::DRM_MODE_CONNECTOR_DPI,
+ Interface::Writeback => ffi::DRM_MODE_CONNECTOR_WRITEBACK,
+ Interface::SPI => ffi::DRM_MODE_CONNECTOR_SPI,
+ Interface::USB => ffi::DRM_MODE_CONNECTOR_USB,
+ }
+ }
+}
+
+/// The state of a connector.
+#[allow(missing_docs)]
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub enum State {
+ Connected,
+ Disconnected,
+ Unknown,
+}
+
+impl From<u32> for State {
+ fn from(n: u32) -> Self {
+ // These variables are not defined in drm_mode.h for some reason.
+ // They were copied from libdrm's xf86DrmMode.h
+ match n {
+ 1 => State::Connected,
+ 2 => State::Disconnected,
+ _ => State::Unknown,
+ }
+ }
+}
+
+impl From<State> for u32 {
+ fn from(state: State) -> Self {
+ // These variables are not defined in drm_mode.h for some reason.
+ // They were copied from libdrm's xf86DrmMode.h
+ match state {
+ State::Connected => 1,
+ State::Disconnected => 2,
+ State::Unknown => 3,
+ }
+ }
+}
+
+/// Subpixel order of the connected sink
+#[non_exhaustive]
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub enum SubPixel {
+ /// Unknown geometry
+ Unknown,
+ /// Horizontal RGB
+ HorizontalRgb,
+ /// Horizontal BGR
+ HorizontalBgr,
+ /// Vertical RGB
+ VerticalRgb,
+ /// Vertical BGR
+ VerticalBgr,
+ /// No geometry
+ None,
+ /// Encountered value not supported by drm-rs
+ NotImplemented,
+}
+
+impl SubPixel {
+ pub(super) fn from_raw(n: u32) -> Self {
+ // These values are not defined in drm_mode.h
+ // They were copied from linux's drm_connector.h
+ // Don't mistake those for ones used in xf86DrmMode.h (libdrm offsets those by 1)
+ match n {
+ 0 => Self::Unknown,
+ 1 => Self::HorizontalRgb,
+ 2 => Self::HorizontalBgr,
+ 3 => Self::VerticalRgb,
+ 4 => Self::VerticalBgr,
+ 5 => Self::None,
+ _ => Self::NotImplemented,
+ }
+ }
+}
diff --git a/src/control/crtc.rs b/src/control/crtc.rs
new file mode 100644
index 0000000..0827043
--- /dev/null
+++ b/src/control/crtc.rs
@@ -0,0 +1,90 @@
+//! # CRTC
+//!
+//! A CRTC is a display controller provided by your device. It's primary job is
+//! to take pixel data and send it to a connector with the proper resolution and
+//! frequencies.
+//!
+//! Specific CRTCs can only be attached to connectors that have an encoder it
+//! supports. For example, you can have a CRTC that can not output to analog
+//! connectors. These are built in hardware limitations.
+//!
+//! Each CRTC has a built in plane, which can have a framebuffer attached to it,
+//! but they can also use pixel data from other planes to perform hardware
+//! compositing.
+
+use crate::control;
+use drm_ffi as ffi;
+
+/// A handle to a specific CRTC
+#[repr(transparent)]
+#[derive(Copy, Clone, Hash, PartialEq, Eq)]
+pub struct Handle(control::RawResourceHandle);
+
+// Safety: Handle is repr(transparent) over NonZeroU32
+unsafe impl bytemuck::ZeroableInOption for Handle {}
+unsafe impl bytemuck::PodInOption for Handle {}
+
+impl From<Handle> for control::RawResourceHandle {
+ fn from(handle: Handle) -> Self {
+ handle.0
+ }
+}
+
+impl From<Handle> for u32 {
+ fn from(handle: Handle) -> Self {
+ handle.0.into()
+ }
+}
+
+impl From<control::RawResourceHandle> for Handle {
+ fn from(handle: control::RawResourceHandle) -> Self {
+ Handle(handle)
+ }
+}
+
+impl control::ResourceHandle for Handle {
+ const FFI_TYPE: u32 = ffi::DRM_MODE_OBJECT_CRTC;
+}
+
+impl std::fmt::Debug for Handle {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ f.debug_tuple("crtc::Handle").field(&self.0).finish()
+ }
+}
+
+/// Information about a specific CRTC
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub struct Info {
+ pub(crate) handle: Handle,
+ pub(crate) position: (u32, u32),
+ pub(crate) mode: Option<control::Mode>,
+ pub(crate) fb: Option<control::framebuffer::Handle>,
+ pub(crate) gamma_length: u32,
+}
+
+impl Info {
+ /// Returns the handle to this CRTC.
+ pub fn handle(&self) -> Handle {
+ self.handle
+ }
+
+ /// Returns the position of the CRTC.
+ pub fn position(&self) -> (u32, u32) {
+ self.position
+ }
+
+ /// Returns the current mode of the CRTC.
+ pub fn mode(&self) -> Option<control::Mode> {
+ self.mode
+ }
+
+ /// Returns the framebuffer currently attached to this CRTC.
+ pub fn framebuffer(&self) -> Option<control::framebuffer::Handle> {
+ self.fb
+ }
+
+ /// Returns the size of the gamma LUT.
+ pub fn gamma_length(&self) -> u32 {
+ self.gamma_length
+ }
+}
diff --git a/src/control/dumbbuffer.rs b/src/control/dumbbuffer.rs
new file mode 100644
index 0000000..d4a7ada
--- /dev/null
+++ b/src/control/dumbbuffer.rs
@@ -0,0 +1,88 @@
+//!
+//! # DumbBuffer
+//!
+//! Memory-supported, slow, but easy & cross-platform buffer implementation
+//!
+
+use crate::buffer;
+
+use std::borrow::{Borrow, BorrowMut};
+use std::ops::{Deref, DerefMut};
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+/// Slow, but generic [`buffer::Buffer`] implementation
+pub struct DumbBuffer {
+ pub(crate) size: (u32, u32),
+ pub(crate) length: usize,
+ pub(crate) format: buffer::DrmFourcc,
+ pub(crate) pitch: u32,
+ pub(crate) handle: buffer::Handle,
+}
+
+/// Mapping of a [`DumbBuffer`]
+pub struct DumbMapping<'a> {
+ pub(crate) _phantom: core::marker::PhantomData<&'a ()>,
+ pub(crate) map: &'a mut [u8],
+}
+
+impl AsRef<[u8]> for DumbMapping<'_> {
+ fn as_ref(&self) -> &[u8] {
+ self.map
+ }
+}
+
+impl AsMut<[u8]> for DumbMapping<'_> {
+ fn as_mut(&mut self) -> &mut [u8] {
+ self.map
+ }
+}
+
+impl Borrow<[u8]> for DumbMapping<'_> {
+ fn borrow(&self) -> &[u8] {
+ self.map
+ }
+}
+
+impl BorrowMut<[u8]> for DumbMapping<'_> {
+ fn borrow_mut(&mut self) -> &mut [u8] {
+ self.map
+ }
+}
+
+impl Deref for DumbMapping<'_> {
+ type Target = [u8];
+
+ fn deref(&self) -> &Self::Target {
+ self.map
+ }
+}
+
+impl DerefMut for DumbMapping<'_> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.map
+ }
+}
+
+impl<'a> Drop for DumbMapping<'a> {
+ fn drop(&mut self) {
+ unsafe {
+ rustix::mm::munmap(self.map.as_mut_ptr() as *mut _, self.map.len())
+ .expect("Unmap failed");
+ }
+ }
+}
+
+impl buffer::Buffer for DumbBuffer {
+ fn size(&self) -> (u32, u32) {
+ self.size
+ }
+ fn format(&self) -> buffer::DrmFourcc {
+ self.format
+ }
+ fn pitch(&self) -> u32 {
+ self.pitch
+ }
+ fn handle(&self) -> buffer::Handle {
+ self.handle
+ }
+}
diff --git a/src/control/encoder.rs b/src/control/encoder.rs
new file mode 100644
index 0000000..fd194b1
--- /dev/null
+++ b/src/control/encoder.rs
@@ -0,0 +1,133 @@
+//! # Encoder
+//!
+//! An encoder is a bridge between a CRTC and a connector that takes the pixel
+//! data of the CRTC and encodes it into a format the connector understands.
+
+use crate::control;
+use drm_ffi as ffi;
+
+/// A handle to an encoder
+#[repr(transparent)]
+#[derive(Copy, Clone, Hash, PartialEq, Eq)]
+pub struct Handle(control::RawResourceHandle);
+
+// Safety: Handle is repr(transparent) over NonZeroU32
+unsafe impl bytemuck::ZeroableInOption for Handle {}
+unsafe impl bytemuck::PodInOption for Handle {}
+
+impl From<Handle> for control::RawResourceHandle {
+ fn from(handle: Handle) -> Self {
+ handle.0
+ }
+}
+
+impl From<Handle> for u32 {
+ fn from(handle: Handle) -> Self {
+ handle.0.into()
+ }
+}
+
+impl From<control::RawResourceHandle> for Handle {
+ fn from(handle: control::RawResourceHandle) -> Self {
+ Handle(handle)
+ }
+}
+
+impl control::ResourceHandle for Handle {
+ const FFI_TYPE: u32 = ffi::DRM_MODE_OBJECT_ENCODER;
+}
+
+impl std::fmt::Debug for Handle {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ f.debug_tuple("encoder::Handle").field(&self.0).finish()
+ }
+}
+
+/// Information about an encoder
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub struct Info {
+ pub(crate) handle: Handle,
+ pub(crate) enc_type: Kind,
+ pub(crate) crtc: Option<control::crtc::Handle>,
+ pub(crate) pos_crtcs: u32,
+ pub(crate) pos_clones: u32,
+}
+
+impl Info {
+ /// Returns the handle to this encoder.
+ pub fn handle(&self) -> Handle {
+ self.handle
+ }
+
+ /// Returns the `Kind` of encoder this is.
+ pub fn kind(&self) -> Kind {
+ self.enc_type
+ }
+
+ /// Returns a handle to the CRTC this encoder is attached to.
+ pub fn crtc(&self) -> Option<control::crtc::Handle> {
+ self.crtc
+ }
+
+ /// Returns a filter for the possible CRTCs that can use this encoder.
+ ///
+ /// Use with [`control::ResourceHandles::filter_crtcs`]
+ /// to receive a list of crtcs.
+ pub fn possible_crtcs(&self) -> control::CrtcListFilter {
+ control::CrtcListFilter(self.pos_crtcs)
+ }
+
+ /// Returns a filter for the possible encoders that clones this one.
+ pub fn possible_clones(&self) {
+ unimplemented!()
+ }
+}
+
+/// The type of encoder.
+#[allow(missing_docs)]
+#[allow(clippy::upper_case_acronyms)]
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub enum Kind {
+ None,
+ DAC,
+ TMDS,
+ LVDS,
+ TVDAC,
+ Virtual,
+ DSI,
+ DPMST,
+ DPI,
+}
+
+impl From<u32> for Kind {
+ fn from(n: u32) -> Self {
+ match n {
+ ffi::DRM_MODE_ENCODER_NONE => Kind::None,
+ ffi::DRM_MODE_ENCODER_DAC => Kind::DAC,
+ ffi::DRM_MODE_ENCODER_TMDS => Kind::TMDS,
+ ffi::DRM_MODE_ENCODER_LVDS => Kind::LVDS,
+ ffi::DRM_MODE_ENCODER_TVDAC => Kind::TVDAC,
+ ffi::DRM_MODE_ENCODER_VIRTUAL => Kind::Virtual,
+ ffi::DRM_MODE_ENCODER_DSI => Kind::DSI,
+ ffi::DRM_MODE_ENCODER_DPMST => Kind::DPMST,
+ ffi::DRM_MODE_ENCODER_DPI => Kind::DPI,
+ _ => Kind::None,
+ }
+ }
+}
+
+impl From<Kind> for u32 {
+ fn from(kind: Kind) -> Self {
+ match kind {
+ Kind::None => ffi::DRM_MODE_ENCODER_NONE,
+ Kind::DAC => ffi::DRM_MODE_ENCODER_DAC,
+ Kind::TMDS => ffi::DRM_MODE_ENCODER_TMDS,
+ Kind::LVDS => ffi::DRM_MODE_ENCODER_LVDS,
+ Kind::TVDAC => ffi::DRM_MODE_ENCODER_TVDAC,
+ Kind::Virtual => ffi::DRM_MODE_ENCODER_VIRTUAL,
+ Kind::DSI => ffi::DRM_MODE_ENCODER_DSI,
+ Kind::DPMST => ffi::DRM_MODE_ENCODER_DPMST,
+ Kind::DPI => ffi::DRM_MODE_ENCODER_DPI,
+ }
+ }
+}
diff --git a/src/control/framebuffer.rs b/src/control/framebuffer.rs
new file mode 100644
index 0000000..1e80ceb
--- /dev/null
+++ b/src/control/framebuffer.rs
@@ -0,0 +1,143 @@
+//! # Framebuffer
+//!
+//! Process specific GPU buffers that can be attached to a plane.
+
+use crate::buffer;
+use crate::control;
+use drm_ffi as ffi;
+use drm_fourcc::{DrmFourcc, DrmModifier};
+
+/// A handle to a framebuffer
+#[repr(transparent)]
+#[derive(Copy, Clone, Hash, PartialEq, Eq)]
+pub struct Handle(control::RawResourceHandle);
+
+// Safety: Handle is repr(transparent) over NonZeroU32
+unsafe impl bytemuck::ZeroableInOption for Handle {}
+unsafe impl bytemuck::PodInOption for Handle {}
+
+impl From<Handle> for control::RawResourceHandle {
+ fn from(handle: Handle) -> Self {
+ handle.0
+ }
+}
+
+impl From<Handle> for u32 {
+ fn from(handle: Handle) -> Self {
+ handle.0.into()
+ }
+}
+
+impl From<control::RawResourceHandle> for Handle {
+ fn from(handle: control::RawResourceHandle) -> Self {
+ Handle(handle)
+ }
+}
+
+impl control::ResourceHandle for Handle {
+ const FFI_TYPE: u32 = ffi::DRM_MODE_OBJECT_FB;
+}
+
+impl std::fmt::Debug for Handle {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ f.debug_tuple("framebuffer::Handle").field(&self.0).finish()
+ }
+}
+
+/// Information about a framebuffer
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub struct Info {
+ pub(crate) handle: Handle,
+ pub(crate) size: (u32, u32),
+ pub(crate) pitch: u32,
+ pub(crate) bpp: u32,
+ pub(crate) depth: u32,
+ pub(crate) buffer: Option<buffer::Handle>,
+}
+
+impl Info {
+ /// Returns the handle to this framebuffer.
+ pub fn handle(&self) -> Handle {
+ self.handle
+ }
+
+ /// Returns the size of this framebuffer.
+ pub fn size(&self) -> (u32, u32) {
+ self.size
+ }
+
+ /// Returns the pitch of this framebuffer.
+ pub fn pitch(&self) -> u32 {
+ self.pitch
+ }
+
+ /// Returns the bits-per-pixel of this framebuffer.
+ pub fn bpp(&self) -> u32 {
+ self.bpp
+ }
+
+ /// Returns the depth of this framebuffer.
+ pub fn depth(&self) -> u32 {
+ self.depth
+ }
+
+ /// Returns the buffer handle of this framebuffer.
+ pub fn buffer(&self) -> Option<buffer::Handle> {
+ self.buffer
+ }
+}
+
+/// Information about a framebuffer (with modifiers)
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub struct PlanarInfo {
+ pub(crate) handle: Handle,
+ pub(crate) size: (u32, u32),
+ pub(crate) pixel_format: DrmFourcc,
+ pub(crate) flags: control::FbCmd2Flags,
+ pub(crate) buffers: [Option<buffer::Handle>; 4],
+ pub(crate) pitches: [u32; 4],
+ pub(crate) offsets: [u32; 4],
+ pub(crate) modifier: Option<DrmModifier>,
+}
+
+impl PlanarInfo {
+ /// Returns the handle to this framebuffer.
+ pub fn handle(&self) -> Handle {
+ self.handle
+ }
+
+ /// Returns the size of this framebuffer.
+ pub fn size(&self) -> (u32, u32) {
+ self.size
+ }
+
+ /// Returns the pixel format of this framebuffer.
+ pub fn pixel_format(&self) -> DrmFourcc {
+ self.pixel_format
+ }
+
+ /// Returns the flags of this framebuffer.
+ pub fn flags(&self) -> control::FbCmd2Flags {
+ self.flags
+ }
+
+ /// Returns the buffer handles of this framebuffer.
+ pub fn buffers(&self) -> [Option<buffer::Handle>; 4] {
+ self.buffers
+ }
+
+ /// Returns the pitches of this framebuffer.
+ pub fn pitches(&self) -> [u32; 4] {
+ self.pitches
+ }
+
+ /// Returns the offsets of this framebuffer.
+ pub fn offsets(&self) -> [u32; 4] {
+ self.offsets
+ }
+
+ /// Returns the modifier of this framebuffer.
+ pub fn modifier(&self) -> Option<DrmModifier> {
+ self.modifier
+ }
+}
diff --git a/src/control/mod.rs b/src/control/mod.rs
new file mode 100644
index 0000000..2b6f946
--- /dev/null
+++ b/src/control/mod.rs
@@ -0,0 +1,1567 @@
+//! Modesetting operations that the DRM subsystem exposes.
+//!
+//! # Summary
+//!
+//! The DRM subsystem provides Kernel Modesetting (KMS) functionality by
+//! exposing the following resource types:
+//!
+//! * FrameBuffer - Specific to an individual process, these wrap around generic
+//! GPU buffers so that they can be attached to a Plane.
+//!
+//! * Planes - Dedicated memory objects which contain a buffer that can then be
+//! scanned out by a CRTC. There exist a few different types of planes depending
+//! on the use case.
+//!
+//! * CRTC - Scanout engines that read pixel data from a Plane and sends it to
+//! a Connector. Each CRTC has at least one Primary Plane.
+//!
+//! * Connector - Represents the physical output, such as a DisplayPort or
+//! VGA connector.
+//!
+//! * Encoder - Encodes pixel data from a CRTC into something a Connector can
+//! understand.
+//!
+//! Further details on each resource can be found in their respective modules.
+//!
+//! # Usage
+//!
+//! To begin using modesetting functionality, the [`Device`] trait
+//! must be implemented on top of the basic [`super::Device`] trait.
+
+use drm_ffi as ffi;
+use drm_fourcc::{DrmFourcc, DrmModifier, UnrecognizedFourcc};
+
+use bytemuck::allocation::TransparentWrapperAlloc;
+use rustix::io::Errno;
+
+pub mod atomic;
+pub mod connector;
+pub mod crtc;
+pub mod dumbbuffer;
+pub mod encoder;
+pub mod framebuffer;
+pub mod plane;
+pub mod syncobj;
+
+pub mod property;
+
+use self::dumbbuffer::*;
+use crate::buffer;
+
+use super::util::*;
+
+use std::collections::HashMap;
+use std::convert::TryFrom;
+use std::error;
+use std::fmt;
+use std::io;
+use std::iter::Zip;
+use std::mem;
+use std::ops::RangeBounds;
+use std::os::unix::io::{AsFd, BorrowedFd, FromRawFd, OwnedFd, RawFd};
+use std::time::Duration;
+
+use core::num::NonZeroU32;
+
+/// Raw handle for a drm resource
+pub type RawResourceHandle = NonZeroU32;
+
+/// Id of a Lease
+pub type LeaseId = NonZeroU32;
+
+/// Handle for a drm resource
+pub trait ResourceHandle:
+ From<RawResourceHandle> + Into<RawResourceHandle> + Into<u32> + Copy + Sized
+{
+ /// Associated encoded object type
+ const FFI_TYPE: u32;
+}
+
+/// Convert from a raw drm object value to a typed Handle
+///
+/// Note: This does no verification on the validity of the original value
+pub fn from_u32<T: From<RawResourceHandle>>(raw: u32) -> Option<T> {
+ RawResourceHandle::new(raw).map(T::from)
+}
+
+/// Error from [`Device::get_planar_framebuffer`]
+#[derive(Debug)]
+pub enum GetPlanarFramebufferError {
+ /// IO error
+ Io(io::Error),
+ /// Unrecognized fourcc format
+ UnrecognizedFourcc(drm_fourcc::UnrecognizedFourcc),
+}
+
+impl fmt::Display for GetPlanarFramebufferError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::Io(err) => write!(f, "{}", err),
+ Self::UnrecognizedFourcc(err) => write!(f, "{}", err),
+ }
+ }
+}
+
+impl error::Error for GetPlanarFramebufferError {
+ fn source(&self) -> Option<&(dyn error::Error + 'static)> {
+ match self {
+ Self::Io(err) => Some(err),
+ Self::UnrecognizedFourcc(err) => Some(err),
+ }
+ }
+}
+
+impl From<io::Error> for GetPlanarFramebufferError {
+ fn from(err: io::Error) -> Self {
+ Self::Io(err)
+ }
+}
+
+impl From<UnrecognizedFourcc> for GetPlanarFramebufferError {
+ fn from(err: UnrecognizedFourcc) -> Self {
+ Self::UnrecognizedFourcc(err)
+ }
+}
+
+/// This trait should be implemented by any object that acts as a DRM device and
+/// provides modesetting functionality.
+///
+/// Like the parent [`super::Device`] trait, this crate does not
+/// provide a concrete object for this trait.
+///
+/// # Example
+/// ```ignore
+/// use drm::control::Device as ControlDevice;
+///
+/// /// Assuming the [`Card`] wrapper already implements [`drm::Device`]
+/// impl ControlDevice for Card {}
+/// ```
+pub trait Device: super::Device {
+ /// Gets the set of resource handles that this device currently controls
+ fn resource_handles(&self) -> io::Result<ResourceHandles> {
+ let mut fbs = Vec::new();
+ let mut crtcs = Vec::new();
+ let mut connectors = Vec::new();
+ let mut encoders = Vec::new();
+
+ let ffi_res = ffi::mode::get_resources(
+ self.as_fd(),
+ Some(&mut fbs),
+ Some(&mut crtcs),
+ Some(&mut connectors),
+ Some(&mut encoders),
+ )?;
+
+ let res = unsafe {
+ ResourceHandles {
+ fbs: transmute_vec_from_u32(fbs),
+ crtcs: transmute_vec_from_u32(crtcs),
+ connectors: transmute_vec_from_u32(connectors),
+ encoders: transmute_vec_from_u32(encoders),
+ width: (ffi_res.min_width, ffi_res.max_width),
+ height: (ffi_res.min_height, ffi_res.max_height),
+ }
+ };
+
+ Ok(res)
+ }
+
+ /// Gets the set of plane handles that this device currently has
+ fn plane_handles(&self) -> io::Result<Vec<plane::Handle>> {
+ let mut planes = Vec::new();
+ let _ = ffi::mode::get_plane_resources(self.as_fd(), Some(&mut planes))?;
+ Ok(unsafe { transmute_vec_from_u32(planes) })
+ }
+
+ /// Returns information about a specific connector
+ ///
+ /// ## Force-probing
+ ///
+ /// If `force_probe` is set to `true` and the DRM client is the current DRM master,
+ /// the kernel will perform a forced probe on the connector to refresh the connector status, modes and EDID.
+ /// A forced-probe can be slow, might cause flickering and the ioctl will block.
+ ///
+ /// - User needs to force-probe connectors to ensure their metadata is up-to-date at startup and after receiving a hot-plug event.
+ /// - User may perform a forced-probe when the user explicitly requests it.
+ /// - User shouldn’t perform a forced-probe in other situations.
+ fn get_connector(
+ &self,
+ handle: connector::Handle,
+ force_probe: bool,
+ ) -> io::Result<connector::Info> {
+ // Maximum number of encoders is 3 due to kernel restrictions
+ let mut encoders = Vec::new();
+ let mut modes = Vec::new();
+
+ let ffi_info = ffi::mode::get_connector(
+ self.as_fd(),
+ handle.into(),
+ None,
+ None,
+ Some(&mut modes),
+ Some(&mut encoders),
+ force_probe,
+ )?;
+
+ let connector = connector::Info {
+ handle,
+ interface: connector::Interface::from(ffi_info.connector_type),
+ interface_id: ffi_info.connector_type_id,
+ connection: connector::State::from(ffi_info.connection),
+ size: match (ffi_info.mm_width, ffi_info.mm_height) {
+ (0, 0) => None,
+ (x, y) => Some((x, y)),
+ },
+ modes: Mode::wrap_vec(modes),
+ encoders: unsafe { transmute_vec_from_u32(encoders) },
+ curr_enc: unsafe { mem::transmute(ffi_info.encoder_id) },
+ subpixel: connector::SubPixel::from_raw(ffi_info.subpixel),
+ };
+
+ Ok(connector)
+ }
+
+ /// Returns information about a specific encoder
+ fn get_encoder(&self, handle: encoder::Handle) -> io::Result<encoder::Info> {
+ let info = ffi::mode::get_encoder(self.as_fd(), handle.into())?;
+
+ let enc = encoder::Info {
+ handle,
+ enc_type: encoder::Kind::from(info.encoder_type),
+ crtc: from_u32(info.crtc_id),
+ pos_crtcs: info.possible_crtcs,
+ pos_clones: info.possible_clones,
+ };
+
+ Ok(enc)
+ }
+
+ /// Returns information about a specific CRTC
+ fn get_crtc(&self, handle: crtc::Handle) -> io::Result<crtc::Info> {
+ let info = ffi::mode::get_crtc(self.as_fd(), handle.into())?;
+
+ let crtc = crtc::Info {
+ handle,
+ position: (info.x, info.y),
+ mode: match info.mode_valid {
+ 0 => None,
+ _ => Some(Mode::from(info.mode)),
+ },
+ fb: from_u32(info.fb_id),
+ gamma_length: info.gamma_size,
+ };
+
+ Ok(crtc)
+ }
+
+ /// Set CRTC state
+ fn set_crtc(
+ &self,
+ handle: crtc::Handle,
+ framebuffer: Option<framebuffer::Handle>,
+ pos: (u32, u32),
+ conns: &[connector::Handle],
+ mode: Option<Mode>,
+ ) -> io::Result<()> {
+ let _info = ffi::mode::set_crtc(
+ self.as_fd(),
+ handle.into(),
+ framebuffer.map(Into::into).unwrap_or(0),
+ pos.0,
+ pos.1,
+ unsafe { &*(conns as *const _ as *const [u32]) },
+ mode.map(|m| m.into()),
+ )?;
+
+ Ok(())
+ }
+
+ /// Returns information about a specific framebuffer
+ fn get_framebuffer(&self, handle: framebuffer::Handle) -> io::Result<framebuffer::Info> {
+ let info = ffi::mode::get_framebuffer(self.as_fd(), handle.into())?;
+
+ let fb = framebuffer::Info {
+ handle,
+ size: (info.width, info.height),
+ pitch: info.pitch,
+ bpp: info.bpp,
+ depth: info.depth,
+ buffer: from_u32(info.handle),
+ };
+
+ Ok(fb)
+ }
+
+ /// Returns information about a specific framebuffer (with modifiers)
+ fn get_planar_framebuffer(
+ &self,
+ handle: framebuffer::Handle,
+ ) -> Result<framebuffer::PlanarInfo, GetPlanarFramebufferError> {
+ let info = ffi::mode::get_framebuffer2(self.as_fd(), handle.into())?;
+
+ let pixel_format = DrmFourcc::try_from(info.pixel_format)?;
+
+ let flags = FbCmd2Flags::from_bits_truncate(info.flags);
+ let modifier = flags
+ .contains(FbCmd2Flags::MODIFIERS)
+ .then(|| DrmModifier::from(info.modifier[0]));
+
+ let fb = framebuffer::PlanarInfo {
+ handle,
+ size: (info.width, info.height),
+ pixel_format,
+ flags,
+ buffers: bytemuck::cast(info.handles),
+ pitches: info.pitches,
+ offsets: info.offsets,
+ modifier,
+ };
+
+ Ok(fb)
+ }
+
+ /// Add a new framebuffer
+ fn add_framebuffer<B>(
+ &self,
+ buffer: &B,
+ depth: u32,
+ bpp: u32,
+ ) -> io::Result<framebuffer::Handle>
+ where
+ B: buffer::Buffer + ?Sized,
+ {
+ let (w, h) = buffer.size();
+ let info = ffi::mode::add_fb(
+ self.as_fd(),
+ w,
+ h,
+ buffer.pitch(),
+ bpp,
+ depth,
+ buffer.handle().into(),
+ )?;
+
+ Ok(from_u32(info.fb_id).unwrap())
+ }
+
+ /// Add framebuffer (with modifiers)
+ fn add_planar_framebuffer<B>(
+ &self,
+ planar_buffer: &B,
+ flags: FbCmd2Flags,
+ ) -> io::Result<framebuffer::Handle>
+ where
+ B: buffer::PlanarBuffer + ?Sized,
+ {
+ let modifier = planar_buffer.modifier();
+ let has_modifier = flags.contains(FbCmd2Flags::MODIFIERS);
+ assert!((has_modifier && modifier.is_some()) || (!has_modifier && modifier.is_none()));
+ let modifier = if let Some(modifier) = modifier {
+ u64::from(modifier)
+ } else {
+ 0
+ };
+
+ let (w, h) = planar_buffer.size();
+ let opt_handles = planar_buffer.handles();
+
+ let handles = bytemuck::cast(opt_handles);
+ let mods = [
+ opt_handles[0].map_or(0, |_| modifier),
+ opt_handles[1].map_or(0, |_| modifier),
+ opt_handles[2].map_or(0, |_| modifier),
+ opt_handles[3].map_or(0, |_| modifier),
+ ];
+
+ let info = ffi::mode::add_fb2(
+ self.as_fd(),
+ w,
+ h,
+ planar_buffer.format() as u32,
+ &handles,
+ &planar_buffer.pitches(),
+ &planar_buffer.offsets(),
+ &mods,
+ flags.bits(),
+ )?;
+
+ Ok(from_u32(info.fb_id).unwrap())
+ }
+
+ /// Mark parts of a framebuffer dirty
+ fn dirty_framebuffer(&self, handle: framebuffer::Handle, clips: &[ClipRect]) -> io::Result<()> {
+ ffi::mode::dirty_fb(self.as_fd(), handle.into(), unsafe {
+ // SAFETY: ClipRect is repr(transparent) for drm_clip_rect
+ core::slice::from_raw_parts(clips.as_ptr() as *const ffi::drm_clip_rect, clips.len())
+ })?;
+ Ok(())
+ }
+
+ /// Destroy a framebuffer
+ fn destroy_framebuffer(&self, handle: framebuffer::Handle) -> io::Result<()> {
+ ffi::mode::rm_fb(self.as_fd(), handle.into())
+ }
+
+ /// Returns information about a specific plane
+ fn get_plane(&self, handle: plane::Handle) -> io::Result<plane::Info> {
+ let mut formats = Vec::new();
+
+ let info = ffi::mode::get_plane(self.as_fd(), handle.into(), Some(&mut formats))?;
+
+ let plane = plane::Info {
+ handle,
+ crtc: from_u32(info.crtc_id),
+ fb: from_u32(info.fb_id),
+ pos_crtcs: info.possible_crtcs,
+ formats: unsafe { transmute_vec_from_u32(formats) },
+ };
+
+ Ok(plane)
+ }
+
+ /// Set plane state.
+ ///
+ /// Providing no framebuffer clears the plane.
+ fn set_plane(
+ &self,
+ handle: plane::Handle,
+ crtc: crtc::Handle,
+ framebuffer: Option<framebuffer::Handle>,
+ flags: u32,
+ crtc_rect: (i32, i32, u32, u32),
+ src_rect: (u32, u32, u32, u32),
+ ) -> io::Result<()> {
+ let _info = ffi::mode::set_plane(
+ self.as_fd(),
+ handle.into(),
+ crtc.into(),
+ framebuffer.map(Into::into).unwrap_or(0),
+ flags,
+ crtc_rect.0,
+ crtc_rect.1,
+ crtc_rect.2,
+ crtc_rect.3,
+ src_rect.0,
+ src_rect.1,
+ src_rect.2,
+ src_rect.3,
+ )?;
+
+ Ok(())
+ }
+
+ /// Returns information about a specific property.
+ fn get_property(&self, handle: property::Handle) -> io::Result<property::Info> {
+ let mut values = Vec::new();
+ let mut enums = Vec::new();
+
+ let info = ffi::mode::get_property(
+ self.as_fd(),
+ handle.into(),
+ Some(&mut values),
+ Some(&mut enums),
+ )?;
+
+ let flags = ModePropFlags::from_bits_truncate(info.flags);
+
+ let val_type = {
+ use self::property::ValueType;
+
+ if flags.contains(ModePropFlags::RANGE) {
+ let min = values[0];
+ let max = values[1];
+
+ match (min, max) {
+ (0, 1) => ValueType::Boolean,
+ (min, max) => ValueType::UnsignedRange(min, max),
+ }
+ } else if flags.contains(ModePropFlags::SIGNED_RANGE) {
+ let min = values[0];
+ let max = values[1];
+
+ ValueType::SignedRange(min as i64, max as i64)
+ } else if flags.contains(ModePropFlags::ENUM) {
+ let enum_values = self::property::EnumValues {
+ values,
+ enums: property::EnumValue::wrap_vec(enums),
+ };
+
+ ValueType::Enum(enum_values)
+ } else if flags.contains(ModePropFlags::BLOB) {
+ ValueType::Blob
+ } else if flags.contains(ModePropFlags::BITMASK) {
+ ValueType::Bitmask
+ } else if flags.contains(ModePropFlags::OBJECT) {
+ match values[0] as u32 {
+ ffi::DRM_MODE_OBJECT_CRTC => ValueType::CRTC,
+ ffi::DRM_MODE_OBJECT_CONNECTOR => ValueType::Connector,
+ ffi::DRM_MODE_OBJECT_ENCODER => ValueType::Encoder,
+ ffi::DRM_MODE_OBJECT_FB => ValueType::Framebuffer,
+ ffi::DRM_MODE_OBJECT_PLANE => ValueType::Plane,
+ ffi::DRM_MODE_OBJECT_PROPERTY => ValueType::Property,
+ ffi::DRM_MODE_OBJECT_BLOB => ValueType::Blob,
+ ffi::DRM_MODE_OBJECT_ANY => ValueType::Object,
+ _ => ValueType::Unknown,
+ }
+ } else {
+ ValueType::Unknown
+ }
+ };
+
+ let property = property::Info {
+ handle,
+ val_type,
+ mutable: !flags.contains(ModePropFlags::IMMUTABLE),
+ atomic: flags.contains(ModePropFlags::ATOMIC),
+ info,
+ };
+
+ Ok(property)
+ }
+
+ /// Sets a property for a specific resource.
+ fn set_property<T: ResourceHandle>(
+ &self,
+ handle: T,
+ prop: property::Handle,
+ value: property::RawValue,
+ ) -> io::Result<()> {
+ ffi::mode::set_property(self.as_fd(), prop.into(), handle.into(), T::FFI_TYPE, value)?;
+
+ Ok(())
+ }
+
+ /// Create a property blob value from a given data blob
+ fn create_property_blob<T>(&self, data: &T) -> io::Result<property::Value<'static>> {
+ let data = unsafe {
+ std::slice::from_raw_parts_mut(data as *const _ as *mut u8, mem::size_of::<T>())
+ };
+ let blob = ffi::mode::create_property_blob(self.as_fd(), data)?;
+
+ Ok(property::Value::Blob(blob.blob_id.into()))
+ }
+
+ /// Get a property blob's data
+ fn get_property_blob(&self, blob: u64) -> io::Result<Vec<u8>> {
+ let mut data = Vec::new();
+ let _ = ffi::mode::get_property_blob(self.as_fd(), blob as u32, Some(&mut data))?;
+ Ok(data)
+ }
+
+ /// Destroy a given property blob value
+ fn destroy_property_blob(&self, blob: u64) -> io::Result<()> {
+ ffi::mode::destroy_property_blob(self.as_fd(), blob as u32)?;
+
+ Ok(())
+ }
+
+ /// Returns the set of [`Mode`]s that a particular connector supports.
+ fn get_modes(&self, handle: connector::Handle) -> io::Result<Vec<Mode>> {
+ let mut modes = Vec::new();
+
+ let _ffi_info = ffi::mode::get_connector(
+ self.as_fd(),
+ handle.into(),
+ None,
+ None,
+ Some(&mut modes),
+ None,
+ false,
+ )?;
+
+ Ok(Mode::wrap_vec(modes))
+ }
+
+ /// Gets a list of property handles and values for this resource.
+ fn get_properties<T: ResourceHandle>(&self, handle: T) -> io::Result<PropertyValueSet> {
+ let mut prop_ids = Vec::new();
+ let mut prop_vals = Vec::new();
+
+ ffi::mode::get_properties(
+ self.as_fd(),
+ handle.into(),
+ T::FFI_TYPE,
+ Some(&mut prop_ids),
+ Some(&mut prop_vals),
+ )?;
+
+ let prop_val_set = PropertyValueSet {
+ prop_ids: unsafe { transmute_vec_from_u32(prop_ids) },
+ prop_vals,
+ };
+
+ Ok(prop_val_set)
+ }
+
+ /// Receive the currently set gamma ramp of a crtc
+ fn get_gamma(
+ &self,
+ crtc: crtc::Handle,
+ red: &mut [u16],
+ green: &mut [u16],
+ blue: &mut [u16],
+ ) -> io::Result<()> {
+ let crtc_info = self.get_crtc(crtc)?;
+ if crtc_info.gamma_length as usize > red.len()
+ || crtc_info.gamma_length as usize > green.len()
+ || crtc_info.gamma_length as usize > blue.len()
+ {
+ return Err(Errno::INVAL.into());
+ }
+
+ ffi::mode::get_gamma(
+ self.as_fd(),
+ crtc.into(),
+ crtc_info.gamma_length as usize,
+ red,
+ green,
+ blue,
+ )?;
+
+ Ok(())
+ }
+
+ /// Set a gamma ramp for the given crtc
+ fn set_gamma(
+ &self,
+ crtc: crtc::Handle,
+ red: &[u16],
+ green: &[u16],
+ blue: &[u16],
+ ) -> io::Result<()> {
+ let crtc_info = self.get_crtc(crtc)?;
+ if crtc_info.gamma_length as usize > red.len()
+ || crtc_info.gamma_length as usize > green.len()
+ || crtc_info.gamma_length as usize > blue.len()
+ {
+ return Err(Errno::INVAL.into());
+ }
+
+ ffi::mode::set_gamma(
+ self.as_fd(),
+ crtc.into(),
+ crtc_info.gamma_length as usize,
+ red,
+ green,
+ blue,
+ )?;
+
+ Ok(())
+ }
+
+ /// Open a GEM buffer handle by name
+ fn open_buffer(&self, name: buffer::Name) -> io::Result<buffer::Handle> {
+ let info = drm_ffi::gem::open(self.as_fd(), name.into())?;
+ Ok(from_u32(info.handle).unwrap())
+ }
+
+ /// Close a GEM buffer handle
+ fn close_buffer(&self, handle: buffer::Handle) -> io::Result<()> {
+ let _info = drm_ffi::gem::close(self.as_fd(), handle.into())?;
+ Ok(())
+ }
+
+ /// Create a new dumb buffer with a given size and pixel format
+ fn create_dumb_buffer(
+ &self,
+ size: (u32, u32),
+ format: buffer::DrmFourcc,
+ bpp: u32,
+ ) -> io::Result<DumbBuffer> {
+ let info = drm_ffi::mode::dumbbuffer::create(self.as_fd(), size.0, size.1, bpp, 0)?;
+
+ let dumb = DumbBuffer {
+ size: (info.width, info.height),
+ length: info.size as usize,
+ format,
+ pitch: info.pitch,
+ handle: from_u32(info.handle).unwrap(),
+ };
+
+ Ok(dumb)
+ }
+ /// Map the buffer for access
+ fn map_dumb_buffer<'a>(&self, buffer: &'a mut DumbBuffer) -> io::Result<DumbMapping<'a>> {
+ let info = drm_ffi::mode::dumbbuffer::map(self.as_fd(), buffer.handle.into(), 0, 0)?;
+
+ let map = {
+ use rustix::mm;
+ let prot = mm::ProtFlags::READ | mm::ProtFlags::WRITE;
+ let flags = mm::MapFlags::SHARED;
+ let fd = self.as_fd();
+ let offset = info.offset as _;
+ unsafe { mm::mmap(std::ptr::null_mut(), buffer.length, prot, flags, fd, offset)? }
+ };
+
+ let mapping = DumbMapping {
+ _phantom: std::marker::PhantomData,
+ map: unsafe { std::slice::from_raw_parts_mut(map as *mut _, buffer.length) },
+ };
+
+ Ok(mapping)
+ }
+
+ /// Free the memory resources of a dumb buffer
+ fn destroy_dumb_buffer(&self, buffer: DumbBuffer) -> io::Result<()> {
+ let _info = drm_ffi::mode::dumbbuffer::destroy(self.as_fd(), buffer.handle.into())?;
+
+ Ok(())
+ }
+
+ /// Sets a hardware-cursor on the given crtc with the image of a given buffer
+ ///
+ /// A buffer argument of [`None`] will clear the cursor.
+ #[deprecated(note = "Usage of deprecated ioctl set_cursor: use a cursor plane instead")]
+ #[allow(deprecated)]
+ fn set_cursor<B>(&self, crtc: crtc::Handle, buffer: Option<&B>) -> io::Result<()>
+ where
+ B: buffer::Buffer + ?Sized,
+ {
+ let (id, w, h) = buffer
+ .map(|buf| {
+ let (w, h) = buf.size();
+ (buf.handle().into(), w, h)
+ })
+ .unwrap_or((0, 0, 0));
+ drm_ffi::mode::set_cursor(self.as_fd(), crtc.into(), id, w, h)?;
+
+ Ok(())
+ }
+
+ /// Sets a hardware-cursor on the given crtc with the image of a given buffer
+ /// and a hotspot marking the click point of the cursor.
+ ///
+ /// A buffer argument of [`None`] will clear the cursor.
+ #[deprecated(note = "Usage of deprecated ioctl set_cursor2: use a cursor plane instead")]
+ #[allow(deprecated)]
+ fn set_cursor2<B>(
+ &self,
+ crtc: crtc::Handle,
+ buffer: Option<&B>,
+ hotspot: (i32, i32),
+ ) -> io::Result<()>
+ where
+ B: buffer::Buffer + ?Sized,
+ {
+ let (id, w, h) = buffer
+ .map(|buf| {
+ let (w, h) = buf.size();
+ (buf.handle().into(), w, h)
+ })
+ .unwrap_or((0, 0, 0));
+ drm_ffi::mode::set_cursor2(self.as_fd(), crtc.into(), id, w, h, hotspot.0, hotspot.1)?;
+
+ Ok(())
+ }
+
+ /// Moves a set cursor on a given crtc
+ #[deprecated(note = "Usage of deprecated ioctl move_cursor: use a cursor plane instead")]
+ #[allow(deprecated)]
+ fn move_cursor(&self, crtc: crtc::Handle, pos: (i32, i32)) -> io::Result<()> {
+ drm_ffi::mode::move_cursor(self.as_fd(), crtc.into(), pos.0, pos.1)?;
+
+ Ok(())
+ }
+
+ /// Request an atomic commit with given flags and property-value pair for a list of objects.
+ fn atomic_commit(
+ &self,
+ flags: AtomicCommitFlags,
+ mut req: atomic::AtomicModeReq,
+ ) -> io::Result<()> {
+ drm_ffi::mode::atomic_commit(
+ self.as_fd(),
+ flags.bits(),
+ unsafe { &mut *(&mut *req.objects as *mut _ as *mut [u32]) },
+ &mut req.count_props_per_object,
+ unsafe { &mut *(&mut *req.props as *mut _ as *mut [u32]) },
+ &mut req.values,
+ )
+ }
+
+ /// Convert a prime file descriptor to a GEM buffer handle
+ fn prime_fd_to_buffer(&self, fd: BorrowedFd<'_>) -> io::Result<buffer::Handle> {
+ let info = ffi::gem::fd_to_handle(self.as_fd(), fd)?;
+ Ok(from_u32(info.handle).unwrap())
+ }
+
+ /// Convert a GEM buffer handle to a prime file descriptor
+ fn buffer_to_prime_fd(&self, handle: buffer::Handle, flags: u32) -> io::Result<OwnedFd> {
+ let info = ffi::gem::handle_to_fd(self.as_fd(), handle.into(), flags)?;
+ Ok(unsafe { OwnedFd::from_raw_fd(info.fd) })
+ }
+
+ /// Queue a page flip on the given crtc
+ fn page_flip(
+ &self,
+ handle: crtc::Handle,
+ framebuffer: framebuffer::Handle,
+ flags: PageFlipFlags,
+ target_sequence: Option<PageFlipTarget>,
+ ) -> io::Result<()> {
+ let mut flags = flags.bits();
+
+ let sequence = match target_sequence {
+ Some(PageFlipTarget::Absolute(n)) => {
+ flags |= ffi::drm_sys::DRM_MODE_PAGE_FLIP_TARGET_ABSOLUTE;
+ n
+ }
+ Some(PageFlipTarget::Relative(n)) => {
+ flags |= ffi::drm_sys::DRM_MODE_PAGE_FLIP_TARGET_RELATIVE;
+ n
+ }
+ None => 0,
+ };
+
+ ffi::mode::page_flip(
+ self.as_fd(),
+ handle.into(),
+ framebuffer.into(),
+ flags,
+ sequence,
+ )?;
+
+ Ok(())
+ }
+
+ /// Creates a syncobj.
+ fn create_syncobj(&self, signalled: bool) -> io::Result<syncobj::Handle> {
+ let info = ffi::syncobj::create(self.as_fd(), signalled)?;
+ Ok(from_u32(info.handle).unwrap())
+ }
+
+ /// Destroys a syncobj.
+ fn destroy_syncobj(&self, handle: syncobj::Handle) -> io::Result<()> {
+ ffi::syncobj::destroy(self.as_fd(), handle.into())?;
+ Ok(())
+ }
+
+ /// Exports a syncobj as an inter-process file descriptor or as a poll()-able sync file.
+ fn syncobj_to_fd(
+ &self,
+ handle: syncobj::Handle,
+ export_sync_file: bool,
+ ) -> io::Result<OwnedFd> {
+ let info = ffi::syncobj::handle_to_fd(self.as_fd(), handle.into(), export_sync_file)?;
+ Ok(unsafe { OwnedFd::from_raw_fd(info.fd) })
+ }
+
+ /// Imports a file descriptor exported by [`Self::syncobj_to_fd`] back into a process-local handle.
+ fn fd_to_syncobj(
+ &self,
+ fd: BorrowedFd<'_>,
+ import_sync_file: bool,
+ ) -> io::Result<syncobj::Handle> {
+ let info = ffi::syncobj::fd_to_handle(self.as_fd(), fd, import_sync_file)?;
+ Ok(from_u32(info.handle).unwrap())
+ }
+
+ /// Waits for one or more syncobjs to become signalled.
+ fn syncobj_wait(
+ &self,
+ handles: &[syncobj::Handle],
+ timeout_nsec: i64,
+ wait_all: bool,
+ wait_for_submit: bool,
+ ) -> io::Result<u32> {
+ let info = ffi::syncobj::wait(
+ self.as_fd(),
+ bytemuck::cast_slice(handles),
+ timeout_nsec,
+ wait_all,
+ wait_for_submit,
+ )?;
+ Ok(info.first_signaled)
+ }
+
+ /// Resets (un-signals) one or more syncobjs.
+ fn syncobj_reset(&self, handles: &[syncobj::Handle]) -> io::Result<()> {
+ ffi::syncobj::reset(self.as_fd(), bytemuck::cast_slice(handles))?;
+ Ok(())
+ }
+
+ /// Signals one or more syncobjs.
+ fn syncobj_signal(&self, handles: &[syncobj::Handle]) -> io::Result<()> {
+ ffi::syncobj::signal(self.as_fd(), bytemuck::cast_slice(handles))?;
+ Ok(())
+ }
+
+ /// Waits for one or more specific timeline syncobj points.
+ fn syncobj_timeline_wait(
+ &self,
+ handles: &[syncobj::Handle],
+ points: &[u64],
+ timeout_nsec: i64,
+ wait_all: bool,
+ wait_for_submit: bool,
+ wait_available: bool,
+ ) -> io::Result<u32> {
+ let info = ffi::syncobj::timeline_wait(
+ self.as_fd(),
+ bytemuck::cast_slice(handles),
+ points,
+ timeout_nsec,
+ wait_all,
+ wait_for_submit,
+ wait_available,
+ )?;
+ Ok(info.first_signaled)
+ }
+
+ /// Queries for state of one or more timeline syncobjs.
+ fn syncobj_timeline_query(
+ &self,
+ handles: &[syncobj::Handle],
+ points: &mut [u64],
+ last_submitted: bool,
+ ) -> io::Result<()> {
+ ffi::syncobj::query(
+ self.as_fd(),
+ bytemuck::cast_slice(handles),
+ points,
+ last_submitted,
+ )?;
+ Ok(())
+ }
+
+ /// Transfers one timeline syncobj point to another.
+ fn syncobj_timeline_transfer(
+ &self,
+ src_handle: syncobj::Handle,
+ dst_handle: syncobj::Handle,
+ src_point: u64,
+ dst_point: u64,
+ ) -> io::Result<()> {
+ ffi::syncobj::transfer(
+ self.as_fd(),
+ src_handle.into(),
+ dst_handle.into(),
+ src_point,
+ dst_point,
+ )?;
+ Ok(())
+ }
+
+ /// Signals one or more specific timeline syncobj points.
+ fn syncobj_timeline_signal(
+ &self,
+ handles: &[syncobj::Handle],
+ points: &[u64],
+ ) -> io::Result<()> {
+ ffi::syncobj::timeline_signal(self.as_fd(), bytemuck::cast_slice(handles), points)?;
+ Ok(())
+ }
+
+ /// Create a drm lease
+ fn create_lease(
+ &self,
+ objects: &[RawResourceHandle],
+ flags: u32,
+ ) -> io::Result<(LeaseId, OwnedFd)> {
+ let lease = ffi::mode::create_lease(self.as_fd(), bytemuck::cast_slice(objects), flags)?;
+ Ok((
+ unsafe { NonZeroU32::new_unchecked(lease.lessee_id) },
+ unsafe { OwnedFd::from_raw_fd(lease.fd as RawFd) },
+ ))
+ }
+
+ /// List active lessees
+ fn list_lessees(&self) -> io::Result<Vec<LeaseId>> {
+ let mut lessees = Vec::new();
+ ffi::mode::list_lessees(self.as_fd(), Some(&mut lessees))?;
+ Ok(unsafe { transmute_vec_from_u32(lessees) })
+ }
+
+ /// Revoke a previously issued drm lease
+ fn revoke_lease(&self, lessee_id: LeaseId) -> io::Result<()> {
+ ffi::mode::revoke_lease(self.as_fd(), lessee_id.get())
+ }
+
+ /// Receive pending events
+ fn receive_events(&self) -> io::Result<Events>
+ where
+ Self: Sized,
+ {
+ let mut event_buf: [u8; 1024] = [0; 1024];
+ let amount = rustix::io::read(self.as_fd(), &mut event_buf)?;
+
+ Ok(Events::with_event_buf(event_buf, amount))
+ }
+}
+
+/// List of leased resources
+pub struct LeaseResources {
+ /// leased crtcs
+ pub crtcs: Vec<crtc::Handle>,
+ /// leased connectors
+ pub connectors: Vec<connector::Handle>,
+ /// leased planes
+ pub planes: Vec<plane::Handle>,
+}
+
+/// Query lease resources
+pub fn get_lease<D: AsFd>(lease: D) -> io::Result<LeaseResources> {
+ let mut crtcs = Vec::new();
+ let mut connectors = Vec::new();
+ let mut planes = Vec::new();
+ let mut objects = Vec::new();
+
+ ffi::mode::get_lease(lease.as_fd(), Some(&mut objects))?;
+
+ let _ = ffi::mode::get_resources(
+ lease.as_fd(),
+ None,
+ Some(&mut crtcs),
+ Some(&mut connectors),
+ None,
+ )?;
+ let _ = ffi::mode::get_plane_resources(lease.as_fd(), Some(&mut planes))?;
+
+ unsafe {
+ Ok(LeaseResources {
+ crtcs: transmute_vec_from_u32::<crtc::Handle>(
+ crtcs
+ .into_iter()
+ .filter(|handle| objects.contains(handle))
+ .collect(),
+ ),
+ connectors: transmute_vec_from_u32::<connector::Handle>(
+ connectors
+ .into_iter()
+ .filter(|handle| objects.contains(handle))
+ .collect(),
+ ),
+ planes: transmute_vec_from_u32::<plane::Handle>(
+ planes
+ .into_iter()
+ .filter(|handle| objects.contains(handle))
+ .collect(),
+ ),
+ })
+ }
+}
+
+bitflags::bitflags! {
+ /// Flags to alter the behaviour of a page flip
+ ///
+ /// Limited to the values in [`ffi::drm_sys::DRM_MODE_PAGE_FLIP_FLAGS`],
+ /// minus [`ffi::drm_sys::DRM_MODE_PAGE_FLIP_TARGET`] bits which are
+ /// passed through [`PageFlipTarget`].
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+ pub struct PageFlipFlags : u32 {
+ /// Request a vblank event on page flip
+ const EVENT = ffi::drm_sys::DRM_MODE_PAGE_FLIP_EVENT;
+ /// Request page flip as soon as possible, not waiting for vblank
+ const ASYNC = ffi::drm_sys::DRM_MODE_PAGE_FLIP_ASYNC;
+ }
+}
+
+/// Target to alter the sequence of page flips
+///
+/// These represent the [`ffi::drm_sys::DRM_MODE_PAGE_FLIP_TARGET`] bits
+/// of [`PageFlipFlags`] wrapped in a regular `enum` due to their
+/// mutual-exclusiveness.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum PageFlipTarget {
+ /// Absolute Vblank Sequence
+ Absolute(u32),
+ /// Relative Vblank Sequence (to the current, when calling)
+ Relative(u32),
+}
+
+/// Iterator over [`Event`]s of a device. Create via [`Device::receive_events()`].
+pub struct Events {
+ event_buf: [u8; 1024],
+ amount: usize,
+ i: usize,
+}
+
+impl Events {
+ /// Create [`Event`]s iterator from buffer read using something other than
+ /// [`Device::receive_events()`].
+ pub fn with_event_buf(event_buf: [u8; 1024], amount: usize) -> Self {
+ Events {
+ event_buf,
+ amount,
+ i: 0,
+ }
+ }
+}
+
+/// An event from a device.
+pub enum Event {
+ /// A vblank happened
+ Vblank(VblankEvent),
+ /// A page flip happened
+ PageFlip(PageFlipEvent),
+ /// Unknown event, raw data provided
+ Unknown(Vec<u8>),
+}
+
+/// Vblank event
+pub struct VblankEvent {
+ /// sequence of the frame
+ pub frame: u32,
+ /// time at which the vblank occurred
+ pub time: Duration,
+ /// crtc that did throw the event
+ pub crtc: crtc::Handle,
+ /// user data that was passed to wait_vblank
+ pub user_data: usize,
+}
+
+/// Page Flip event
+pub struct PageFlipEvent {
+ /// sequence of the frame
+ pub frame: u32,
+ /// duration between events
+ pub duration: Duration,
+ /// crtc that did throw the event
+ pub crtc: crtc::Handle,
+}
+
+impl Iterator for Events {
+ type Item = Event;
+
+ fn next(&mut self) -> Option<Event> {
+ if self.amount > 0 && self.i < self.amount {
+ let event = unsafe { &*(self.event_buf.as_ptr().add(self.i) as *const ffi::drm_event) };
+ self.i += event.length as usize;
+ match event.type_ {
+ ffi::DRM_EVENT_VBLANK => {
+ let vblank_event =
+ unsafe { &*(event as *const _ as *const ffi::drm_event_vblank) };
+ Some(Event::Vblank(VblankEvent {
+ frame: vblank_event.sequence,
+ time: Duration::new(
+ vblank_event.tv_sec as u64,
+ vblank_event.tv_usec * 1000,
+ ),
+ #[allow(clippy::unnecessary_cast)]
+ crtc: from_u32(vblank_event.crtc_id as u32).unwrap(),
+ user_data: vblank_event.user_data as usize,
+ }))
+ }
+ ffi::DRM_EVENT_FLIP_COMPLETE => {
+ let vblank_event =
+ unsafe { &*(event as *const _ as *const ffi::drm_event_vblank) };
+ Some(Event::PageFlip(PageFlipEvent {
+ frame: vblank_event.sequence,
+ duration: Duration::new(
+ vblank_event.tv_sec as u64,
+ vblank_event.tv_usec * 1000,
+ ),
+ crtc: from_u32(if vblank_event.crtc_id != 0 {
+ vblank_event.crtc_id
+ } else {
+ vblank_event.user_data as u32
+ })
+ .unwrap(),
+ }))
+ }
+ _ => Some(Event::Unknown(
+ self.event_buf[self.i - (event.length as usize)..self.i].to_vec(),
+ )),
+ }
+ } else {
+ None
+ }
+ }
+}
+
+/// The set of [`ResourceHandles`] that a
+/// [`Device`] exposes. Excluding Plane resources.
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub struct ResourceHandles {
+ /// Set of [`framebuffer::Handle`]
+ pub fbs: Vec<framebuffer::Handle>,
+ /// Set of [`crtc::Handle`]
+ pub crtcs: Vec<crtc::Handle>,
+ /// Set of [`connector::Handle`]
+ pub connectors: Vec<connector::Handle>,
+ /// Set of [`encoder::Handle`]
+ pub encoders: Vec<encoder::Handle>,
+ width: (u32, u32),
+ height: (u32, u32),
+}
+
+impl ResourceHandles {
+ /// Returns the set of [`connector::Handle`]
+ pub fn connectors(&self) -> &[connector::Handle] {
+ &self.connectors
+ }
+
+ /// Returns the set of [`encoder::Handle`]
+ pub fn encoders(&self) -> &[encoder::Handle] {
+ &self.encoders
+ }
+
+ /// Returns the set of [`crtc::Handle`]
+ pub fn crtcs(&self) -> &[crtc::Handle] {
+ &self.crtcs
+ }
+
+ /// Returns the set of [`framebuffer::Handle`]
+ pub fn framebuffers(&self) -> &[framebuffer::Handle] {
+ &self.fbs
+ }
+
+ /// Returns the supported minimum and maximum width for framebuffers
+ pub fn supported_fb_width(&self) -> impl RangeBounds<u32> {
+ self.width.0..=self.width.1
+ }
+
+ /// Returns the supported minimum and maximum height for framebuffers
+ pub fn supported_fb_height(&self) -> impl RangeBounds<u32> {
+ self.height.0..=self.height.1
+ }
+
+ /// Apply a filter the all crtcs of these resources, resulting in a list of crtcs allowed.
+ pub fn filter_crtcs(&self, filter: CrtcListFilter) -> Vec<crtc::Handle> {
+ self.crtcs
+ .iter()
+ .enumerate()
+ .filter(|&(n, _)| (1 << n) & filter.0 != 0)
+ .map(|(_, &e)| e)
+ .collect()
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+/// A filter that can be used with a [`ResourceHandles`] to determine the set of
+/// Crtcs that can attach to a specific encoder.
+pub struct CrtcListFilter(u32);
+
+/// Resolution and timing information for a display mode.
+#[repr(transparent)]
+#[derive(Copy, Clone, Hash, PartialEq, Eq, bytemuck::TransparentWrapper)]
+pub struct Mode {
+ // We're using the FFI struct because the DRM API expects it when giving it
+ // to a CRTC or creating a blob from it. Rather than rearranging the fields
+ // to convert to/from an abstracted type, just use the raw object.
+ mode: ffi::drm_mode_modeinfo,
+}
+
+impl Mode {
+ /// Returns the name of this mode.
+ pub fn name(&self) -> &std::ffi::CStr {
+ unsafe { std::ffi::CStr::from_ptr(&self.mode.name[0] as _) }
+ }
+
+ /// Returns the clock speed of this mode.
+ pub fn clock(&self) -> u32 {
+ self.mode.clock
+ }
+
+ /// Returns the size (resolution) of the mode.
+ pub fn size(&self) -> (u16, u16) {
+ (self.mode.hdisplay, self.mode.vdisplay)
+ }
+
+ /// Returns the horizontal sync start, end, and total.
+ pub fn hsync(&self) -> (u16, u16, u16) {
+ (self.mode.hsync_start, self.mode.hsync_end, self.mode.htotal)
+ }
+
+ /// Returns the vertical sync start, end, and total.
+ pub fn vsync(&self) -> (u16, u16, u16) {
+ (self.mode.vsync_start, self.mode.vsync_end, self.mode.vtotal)
+ }
+
+ /// Returns the horizontal skew of this mode.
+ pub fn hskew(&self) -> u16 {
+ self.mode.hskew
+ }
+
+ /// Returns the vertical scan of this mode.
+ pub fn vscan(&self) -> u16 {
+ self.mode.vscan
+ }
+
+ /// Returns the vertical refresh rate of this mode
+ pub fn vrefresh(&self) -> u32 {
+ self.mode.vrefresh
+ }
+
+ /// Returns the bitmask of this mode
+ pub fn mode_type(&self) -> ModeTypeFlags {
+ ModeTypeFlags::from_bits_truncate(self.mode.type_)
+ }
+
+ /// Returns the flags of this mode
+ pub fn flags(&self) -> ModeFlags {
+ ModeFlags::from_bits_truncate(self.mode.flags)
+ }
+}
+
+impl From<ffi::drm_mode_modeinfo> for Mode {
+ fn from(raw: ffi::drm_mode_modeinfo) -> Mode {
+ Mode { mode: raw }
+ }
+}
+
+impl From<Mode> for ffi::drm_mode_modeinfo {
+ fn from(mode: Mode) -> Self {
+ mode.mode
+ }
+}
+
+impl fmt::Debug for Mode {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("Mode")
+ .field("name", &self.name())
+ .field("clock", &self.clock())
+ .field("size", &self.size())
+ .field("hsync", &self.hsync())
+ .field("vsync", &self.vsync())
+ .field("hskew", &self.hskew())
+ .field("vscan", &self.vscan())
+ .field("vrefresh", &self.vrefresh())
+ .field("mode_type", &self.mode_type())
+ .finish()
+ }
+}
+
+bitflags::bitflags! {
+ /// Display mode type flags
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+ pub struct ModeTypeFlags : u32 {
+ /// Builtin mode type
+ #[deprecated]
+ const BUILTIN = ffi::DRM_MODE_TYPE_BUILTIN;
+ /// CLOCK_C mode type
+ #[deprecated]
+ const CLOCK_C = ffi::DRM_MODE_TYPE_CLOCK_C;
+ /// CRTC_C mode type
+ #[deprecated]
+ const CRTC_C = ffi::DRM_MODE_TYPE_CRTC_C;
+ /// Preferred mode
+ const PREFERRED = ffi::DRM_MODE_TYPE_PREFERRED;
+ /// Default mode
+ #[deprecated]
+ const DEFAULT = ffi::DRM_MODE_TYPE_DEFAULT;
+ /// User defined mode type
+ const USERDEF = ffi::DRM_MODE_TYPE_USERDEF;
+ /// Mode created by driver
+ const DRIVER = ffi::DRM_MODE_TYPE_DRIVER;
+ /// Bitmask of all valid (non-deprecated) mode type flags
+ const ALL = ffi::DRM_MODE_TYPE_ALL;
+ }
+}
+
+bitflags::bitflags! {
+ /// Display mode flags
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+ pub struct ModeFlags: u32 {
+ /// PHSYNC flag
+ const PHSYNC = ffi::DRM_MODE_FLAG_PHSYNC;
+ /// NHSYNC flag
+ const NHSYNC = ffi::DRM_MODE_FLAG_NHSYNC;
+ /// PVSYNC flag
+ const PVSYNC = ffi::DRM_MODE_FLAG_PVSYNC;
+ /// NVSYNC flag
+ const NVSYNC = ffi::DRM_MODE_FLAG_NVSYNC;
+ /// Interlace flag
+ const INTERLACE = ffi::DRM_MODE_FLAG_INTERLACE;
+ /// DBLSCAN flag
+ const DBLSCAN = ffi::DRM_MODE_FLAG_DBLSCAN;
+ /// CSYNC flag
+ const CSYNC = ffi::DRM_MODE_FLAG_CSYNC;
+ /// PCSYNC flag
+ const PCSYNC = ffi::DRM_MODE_FLAG_PCSYNC;
+ /// NCSYNC flag
+ const NCSYNC = ffi::DRM_MODE_FLAG_NCSYNC;
+ /// HSKEW flag
+ const HSKEW = ffi::DRM_MODE_FLAG_HSKEW;
+ #[deprecated]
+ /// BCAST flag
+ const BCAST = ffi::DRM_MODE_FLAG_BCAST;
+ #[deprecated]
+ /// PIXMUX flag
+ const PIXMUX = ffi::DRM_MODE_FLAG_PIXMUX;
+ /// DBLCLK flag
+ const DBLCLK = ffi::DRM_MODE_FLAG_DBLCLK;
+ /// CLKDIV2 flag
+ const CLKDIV2 = ffi::DRM_MODE_FLAG_CLKDIV2;
+ /// Stereo 3D mode utilizing frame packing
+ const _3D_FRAME_PACKING = ffi::DRM_MODE_FLAG_3D_FRAME_PACKING;
+ /// Stereo 3D mode utilizing alternating fields
+ const _3D_FIELD_ALTERNATIVE = ffi::DRM_MODE_FLAG_3D_FIELD_ALTERNATIVE;
+ /// Stereo 3D mode utilizing alternating lines
+ const _3D_LINE_ALTERNATIVE = ffi::DRM_MODE_FLAG_3D_LINE_ALTERNATIVE;
+ /// Stereo 3D mode utilizing side by side full size image
+ const _3D_SIDE_BY_SIDE_FULL = ffi::DRM_MODE_FLAG_3D_SIDE_BY_SIDE_FULL;
+ /// Stereo 3D mode utilizing depth images
+ const _3D_L_DEPTH = ffi::DRM_MODE_FLAG_3D_L_DEPTH;
+ /// Stereo 3D mode utilizing depth images
+ const _3D_L_DEPTH_GFX_GFX_DEPTH = ffi::DRM_MODE_FLAG_3D_L_DEPTH_GFX_GFX_DEPTH;
+ /// Stereo 3D mode utilizing top and bottom images
+ const _3D_TOP_AND_BOTTOM = ffi::DRM_MODE_FLAG_3D_TOP_AND_BOTTOM;
+ /// Stereo 3D mode utilizing side by side half size image
+ const _3D_SIDE_BY_SIDE_HALF = ffi::DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF;
+ }
+}
+
+/// Type of a plane
+#[repr(u32)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum PlaneType {
+ /// Overlay plane
+ Overlay = ffi::DRM_PLANE_TYPE_OVERLAY,
+ /// Primary plane
+ Primary = ffi::DRM_PLANE_TYPE_PRIMARY,
+ /// Cursor plane
+ Cursor = ffi::DRM_PLANE_TYPE_CURSOR,
+}
+
+/// Wrapper around a set of property IDs and their raw values.
+#[derive(Debug, Clone)]
+pub struct PropertyValueSet {
+ prop_ids: Vec<property::Handle>,
+ prop_vals: Vec<property::RawValue>,
+}
+
+impl PropertyValueSet {
+ /// Returns a HashMap mapping property names to info
+ pub fn as_hashmap(&self, device: &impl Device) -> io::Result<HashMap<String, property::Info>> {
+ let mut map = HashMap::new();
+ for id in self.prop_ids.iter() {
+ let info = device.get_property(*id)?;
+ let name = info.name().to_str().unwrap().to_owned();
+ map.insert(name, info);
+ }
+ Ok(map)
+ }
+
+ /// Returns a pair representing a set of [`property::Handle`] and their raw values
+ pub fn as_props_and_values(&self) -> (&[property::Handle], &[property::RawValue]) {
+ (&self.prop_ids, &self.prop_vals)
+ }
+
+ /// Returns iterator over pairs representing a set of [`property::Handle`] and their raw values
+ pub fn iter(&self) -> impl Iterator<Item = (&property::Handle, &property::RawValue)> {
+ self.into_iter()
+ }
+}
+
+impl<'a> IntoIterator for &'a PropertyValueSet {
+ type Item = (&'a property::Handle, &'a property::RawValue);
+ type IntoIter =
+ Zip<std::slice::Iter<'a, property::Handle>, std::slice::Iter<'a, property::RawValue>>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.prop_ids.iter().zip(self.prop_vals.iter())
+ }
+}
+
+impl IntoIterator for PropertyValueSet {
+ type Item = (property::Handle, property::RawValue);
+ type IntoIter =
+ Zip<std::vec::IntoIter<property::Handle>, std::vec::IntoIter<property::RawValue>>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.prop_ids.into_iter().zip(self.prop_vals)
+ }
+}
+
+/// Describes a rectangular region of a buffer
+#[repr(transparent)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
+pub struct ClipRect(ffi::drm_sys::drm_clip_rect);
+
+impl ClipRect {
+ /// Create a new clipping rectangle.
+ pub fn new(x1: u16, y1: u16, x2: u16, y2: u16) -> Self {
+ Self(ffi::drm_sys::drm_clip_rect { x1, y1, x2, y2 })
+ }
+
+ /// Get the X coordinate of the top left corner of the rectangle.
+ pub fn x1(self) -> u16 {
+ self.0.x1
+ }
+
+ /// Get the Y coordinate of the top left corner of the rectangle.
+ pub fn y1(self) -> u16 {
+ self.0.y1
+ }
+
+ /// Get the X coordinate of the bottom right corner of the rectangle
+ pub fn x2(self) -> u16 {
+ self.0.x2
+ }
+
+ /// Get the Y coordinate of the bottom right corner of the rectangle.
+ pub fn y2(self) -> u16 {
+ self.0.y2
+ }
+}
+
+bitflags::bitflags! {
+ /// Commit flags for atomic mode setting
+ ///
+ /// Limited to the values in [`ffi::drm_sys::DRM_MODE_ATOMIC_FLAGS`].
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+ pub struct AtomicCommitFlags : u32 {
+ /// Generate a page flip event, when the changes are applied
+ const PAGE_FLIP_EVENT = ffi::drm_sys::DRM_MODE_PAGE_FLIP_EVENT;
+ /// Request page flip when the changes are applied, not waiting for vblank
+ const PAGE_FLIP_ASYNC = ffi::drm_sys::DRM_MODE_PAGE_FLIP_ASYNC;
+ /// Test only validity of the request, do not actually apply the requested changes
+ const TEST_ONLY = ffi::drm_sys::DRM_MODE_ATOMIC_TEST_ONLY;
+ /// Do not block on the request and return early
+ const NONBLOCK = ffi::drm_sys::DRM_MODE_ATOMIC_NONBLOCK;
+ /// Allow the changes to trigger a modeset, if necessary
+ ///
+ /// Changes requiring a modeset are rejected otherwise.
+ const ALLOW_MODESET = ffi::drm_sys::DRM_MODE_ATOMIC_ALLOW_MODESET;
+ }
+}
+
+bitflags::bitflags! {
+ /// Mode property flags
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+ pub struct ModePropFlags : u32 {
+ /// Do not use
+ #[deprecated]
+ const PENDING = ffi::DRM_MODE_PROP_PENDING;
+
+ /// Non-extended types: legacy bitmask, one bit per type:
+ const LEGACY_TYPE = ffi::DRM_MODE_PROP_LEGACY_TYPE;
+ /// An unsigned integer that has a min and max value
+ const RANGE = ffi::DRM_MODE_PROP_RANGE;
+ /// Set when this property is informational only and cannot be modified
+ const IMMUTABLE = ffi::DRM_MODE_PROP_IMMUTABLE;
+ /// Enumerated type with text strings
+ const ENUM = ffi::DRM_MODE_PROP_ENUM;
+ /// A chunk of binary data that must be acquired
+ const BLOB = ffi::DRM_MODE_PROP_BLOB;
+ /// Bitmask of enumerated types
+ const BITMASK = ffi::DRM_MODE_PROP_BITMASK;
+
+ /// Extended-types: rather than continue to consume a bit per type,
+ /// grab a chunk of the bits to use as integer type id.
+ const EXTENDED_TYPE = ffi::DRM_MODE_PROP_EXTENDED_TYPE;
+ /// A DRM object that can have a specific type
+ ///
+ /// See `ffi::DRM_MODE_OBJECT_*` for specific types.
+ const OBJECT = ffi::DRM_MODE_PROP_OBJECT;
+ /// A signed integer that has a min and max value
+ const SIGNED_RANGE = ffi::DRM_MODE_PROP_SIGNED_RANGE;
+ /// the [`Self::ATOMIC`] flag is used to hide properties from userspace that
+ /// is not aware of atomic properties. This is mostly to work around
+ /// older userspace (DDX drivers) that read/write each prop they find,
+ /// witout being aware that this could be triggering a lengthy modeset.
+ const ATOMIC = ffi::DRM_MODE_PROP_ATOMIC;
+ }
+}
+
+bitflags::bitflags! {
+ /// Planar framebuffer flags
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+ pub struct FbCmd2Flags : u32 {
+ /// For interlaced framebuffers
+ const INTERLACED = ffi::DRM_MODE_FB_INTERLACED;
+ /// Enables .modifier
+ const MODIFIERS = ffi::DRM_MODE_FB_MODIFIERS;
+ }
+}
diff --git a/src/control/plane.rs b/src/control/plane.rs
new file mode 100644
index 0000000..c287935
--- /dev/null
+++ b/src/control/plane.rs
@@ -0,0 +1,96 @@
+//! # Plane
+//!
+//! Attachment point for a Framebuffer.
+//!
+//! A Plane is a resource that can have a framebuffer attached to it, either for
+//! hardware compositing or displaying directly to a screen. There are three
+//! types of planes available for use:
+//!
+//! * Primary - A CRTC's built-in plane. When attaching a framebuffer to a CRTC,
+//! it is actually being attached to this kind of plane.
+//!
+//! * Overlay - Can be overlaid on top of a primary plane, utilizing extremely
+//! fast hardware compositing.
+//!
+//! * Cursor - Similar to an overlay plane, these are typically used to display
+//! cursor type objects.
+
+use crate::control;
+use drm_ffi as ffi;
+
+/// A handle to a plane
+#[repr(transparent)]
+#[derive(Copy, Clone, Hash, PartialEq, Eq)]
+pub struct Handle(control::RawResourceHandle);
+
+// Safety: Handle is repr(transparent) over NonZeroU32
+unsafe impl bytemuck::ZeroableInOption for Handle {}
+unsafe impl bytemuck::PodInOption for Handle {}
+
+impl From<Handle> for control::RawResourceHandle {
+ fn from(handle: Handle) -> Self {
+ handle.0
+ }
+}
+
+impl From<Handle> for u32 {
+ fn from(handle: Handle) -> Self {
+ handle.0.into()
+ }
+}
+
+impl From<control::RawResourceHandle> for Handle {
+ fn from(handle: control::RawResourceHandle) -> Self {
+ Handle(handle)
+ }
+}
+
+impl control::ResourceHandle for Handle {
+ const FFI_TYPE: u32 = ffi::DRM_MODE_OBJECT_PLANE;
+}
+
+impl std::fmt::Debug for Handle {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ f.debug_tuple("plane::Handle").field(&self.0).finish()
+ }
+}
+
+/// Information about a plane
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub struct Info {
+ pub(crate) handle: Handle,
+ pub(crate) crtc: Option<control::crtc::Handle>,
+ pub(crate) fb: Option<control::framebuffer::Handle>,
+ pub(crate) pos_crtcs: u32,
+ pub(crate) formats: Vec<u32>,
+}
+
+impl Info {
+ /// Returns the handle to this plane.
+ pub fn handle(&self) -> Handle {
+ self.handle
+ }
+
+ /// Returns the CRTC this plane is attached to.
+ pub fn crtc(&self) -> Option<control::crtc::Handle> {
+ self.crtc
+ }
+
+ /// Returns a filter for supported crtcs of this plane.
+ ///
+ /// Use with [`control::ResourceHandles::filter_crtcs`]
+ /// to receive a list of crtcs.
+ pub fn possible_crtcs(&self) -> control::CrtcListFilter {
+ control::CrtcListFilter(self.pos_crtcs)
+ }
+
+ /// Returns the framebuffer this plane is attached to.
+ pub fn framebuffer(&self) -> Option<control::framebuffer::Handle> {
+ self.fb
+ }
+
+ /// Returns the formats this plane supports.
+ pub fn formats(&self) -> &[u32] {
+ &self.formats
+ }
+}
diff --git a/src/control/property.rs b/src/control/property.rs
new file mode 100644
index 0000000..d1b6331
--- /dev/null
+++ b/src/control/property.rs
@@ -0,0 +1,342 @@
+//! # Property
+//!
+//! A property of a modesetting resource.
+//!
+//! All modesetting resources have a set of properties that have values that
+//! can be modified. These properties are modesetting resources themselves, and
+//! may even have their own set of properties.
+//!
+//! Properties may have mutable values attached to them. These can be changed by
+//! either changing the state of a resource (thereby affecting the property),
+//! directly changing the property value itself, or by batching property changes
+//! together and executing them all atomically.
+
+use crate::control::{RawResourceHandle, ResourceHandle};
+use drm_ffi as ffi;
+
+/// A raw property value that does not have a specific property type
+pub type RawValue = u64;
+
+/// A handle to a property
+#[repr(transparent)]
+#[derive(Copy, Clone, Hash, PartialEq, Eq)]
+pub struct Handle(RawResourceHandle);
+
+// Safety: Handle is repr(transparent) over NonZeroU32
+unsafe impl bytemuck::ZeroableInOption for Handle {}
+unsafe impl bytemuck::PodInOption for Handle {}
+
+impl From<Handle> for RawResourceHandle {
+ fn from(handle: Handle) -> Self {
+ handle.0
+ }
+}
+
+impl From<Handle> for u32 {
+ fn from(handle: Handle) -> Self {
+ handle.0.into()
+ }
+}
+
+impl From<RawResourceHandle> for Handle {
+ fn from(handle: RawResourceHandle) -> Self {
+ Handle(handle)
+ }
+}
+
+impl ResourceHandle for Handle {
+ const FFI_TYPE: u32 = ffi::DRM_MODE_OBJECT_PROPERTY;
+}
+
+impl std::fmt::Debug for Handle {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ f.debug_tuple("property::Handle").field(&self.0).finish()
+ }
+}
+
+/// Information about a property
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub struct Info {
+ pub(crate) handle: Handle,
+ pub(crate) val_type: ValueType,
+ pub(crate) mutable: bool,
+ pub(crate) atomic: bool,
+ pub(crate) info: ffi::drm_mode_get_property,
+}
+
+impl Info {
+ /// Returns the handle to this property.
+ pub fn handle(&self) -> Handle {
+ self.handle
+ }
+
+ /// Returns the name of this property.
+ pub fn name(&self) -> &std::ffi::CStr {
+ unsafe { std::ffi::CStr::from_ptr(&self.info.name[0] as _) }
+ }
+
+ /// Returns the ValueType of this property.
+ pub fn value_type(&self) -> ValueType {
+ self.val_type.clone()
+ }
+
+ /// Returns whether this property is mutable.
+ pub fn mutable(&self) -> bool {
+ self.mutable
+ }
+
+ /// Returns whether this property can be atomically updated.
+ pub fn atomic(&self) -> bool {
+ self.atomic
+ }
+}
+
+/// Describes the types of value that a property uses.
+#[allow(clippy::upper_case_acronyms)]
+#[allow(clippy::large_enum_variant)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub enum ValueType {
+ /// A catch-all for any unknown types
+ Unknown,
+ /// A True or False type
+ Boolean,
+ /// An unsigned integer that has a min and max value
+ UnsignedRange(u64, u64),
+ /// A signed integer that has a min and max value
+ SignedRange(i64, i64),
+ /// A set of values that are mutually exclusive
+ Enum(EnumValues),
+ /// A set of values that can be combined
+ Bitmask,
+ /// A chunk of binary data that must be acquired
+ Blob,
+ /// A non-specific DRM object
+ Object,
+ /// A CRTC object
+ CRTC,
+ /// A Connector object
+ Connector,
+ /// An Encoder object
+ Encoder,
+ /// A Framebuffer object
+ Framebuffer,
+ /// A Plane object
+ Plane,
+ /// A Property object
+ Property,
+}
+
+impl ValueType {
+ /// Given a [`RawValue`], convert it into a specific [`Value`]
+ pub fn convert_value(&self, value: RawValue) -> Value {
+ match self {
+ ValueType::Unknown => Value::Unknown(value),
+ ValueType::Boolean => Value::Boolean(value != 0),
+ ValueType::UnsignedRange(_, _) => Value::UnsignedRange(value),
+ ValueType::SignedRange(_, _) => Value::SignedRange(value as i64),
+ ValueType::Enum(values) => Value::Enum(values.get_value_from_raw_value(value)),
+ ValueType::Bitmask => Value::Bitmask(value),
+ ValueType::Blob => Value::Blob(value),
+ ValueType::Object => Value::Object(bytemuck::cast(value as u32)),
+ ValueType::CRTC => Value::CRTC(bytemuck::cast(value as u32)),
+ ValueType::Connector => Value::Connector(bytemuck::cast(value as u32)),
+ ValueType::Encoder => Value::Encoder(bytemuck::cast(value as u32)),
+ ValueType::Framebuffer => Value::Framebuffer(bytemuck::cast(value as u32)),
+ ValueType::Plane => Value::Plane(bytemuck::cast(value as u32)),
+ ValueType::Property => Value::Property(bytemuck::cast(value as u32)),
+ }
+ }
+}
+
+/// The value of a property, in a typed format
+#[allow(missing_docs)]
+#[allow(clippy::upper_case_acronyms)]
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub enum Value<'a> {
+ /// Unknown value
+ Unknown(RawValue),
+ /// Boolean value
+ Boolean(bool),
+ /// Unsigned range value
+ UnsignedRange(u64),
+ /// Signed range value
+ SignedRange(i64),
+ /// Enum Value
+ Enum(Option<&'a EnumValue>),
+ /// Bitmask value
+ Bitmask(u64),
+ /// Opaque (blob) value
+ Blob(u64),
+ /// Unknown object value
+ Object(Option<RawResourceHandle>),
+ /// Crtc object value
+ CRTC(Option<super::crtc::Handle>),
+ /// Connector object value
+ Connector(Option<super::connector::Handle>),
+ /// Encoder object value
+ Encoder(Option<super::encoder::Handle>),
+ /// Framebuffer object value
+ Framebuffer(Option<super::framebuffer::Handle>),
+ /// Plane object value
+ Plane(Option<super::plane::Handle>),
+ /// Property object value
+ Property(Option<Handle>),
+}
+
+impl<'a> From<Value<'a>> for RawValue {
+ fn from(value: Value<'a>) -> Self {
+ match value {
+ Value::Unknown(x) => x,
+ Value::Boolean(true) => 1,
+ Value::Boolean(false) => 0,
+ Value::UnsignedRange(x) => x,
+ Value::SignedRange(x) => x as u64,
+ Value::Enum(val) => val.map_or(0, EnumValue::value),
+ Value::Bitmask(x) => x,
+ Value::Blob(x) => x,
+ Value::Object(x) => bytemuck::cast::<_, u32>(x) as u64,
+ Value::CRTC(x) => bytemuck::cast::<_, u32>(x) as u64,
+ Value::Connector(x) => bytemuck::cast::<_, u32>(x) as u64,
+ Value::Encoder(x) => bytemuck::cast::<_, u32>(x) as u64,
+ Value::Framebuffer(x) => bytemuck::cast::<_, u32>(x) as u64,
+ Value::Plane(x) => bytemuck::cast::<_, u32>(x) as u64,
+ Value::Property(x) => bytemuck::cast::<_, u32>(x) as u64,
+ }
+ }
+}
+
+macro_rules! match_variant {
+ ($this:ident, $variant:ident) => {
+ if let Self::$variant(v) = *$this {
+ Some(v)
+ } else {
+ None
+ }
+ };
+}
+
+impl<'a> Value<'a> {
+ /// Boolean value
+ pub fn as_boolean(&self) -> Option<bool> {
+ match_variant!(self, Boolean)
+ }
+
+ /// Unsigned range value
+ pub fn as_unsigned_range(&self) -> Option<u64> {
+ match_variant!(self, UnsignedRange)
+ }
+
+ /// Signed range value
+ pub fn as_signed_range(&self) -> Option<i64> {
+ match_variant!(self, SignedRange)
+ }
+
+ /// Enum Value
+ pub fn as_enum(&self) -> Option<&'a EnumValue> {
+ match_variant!(self, Enum).flatten()
+ }
+
+ /// Bitmask value
+ pub fn as_bitmask(&self) -> Option<u64> {
+ match_variant!(self, Bitmask)
+ }
+
+ /// Opaque (blob) value
+ pub fn as_blob(&self) -> Option<u64> {
+ match_variant!(self, Blob)
+ }
+
+ /// Unknown object value
+ pub fn as_object(&self) -> Option<RawResourceHandle> {
+ match_variant!(self, Object).flatten()
+ }
+
+ /// Crtc object value
+ pub fn as_crtc(&self) -> Option<super::crtc::Handle> {
+ match_variant!(self, CRTC).flatten()
+ }
+
+ /// Connector object value
+ pub fn as_connector(&self) -> Option<super::connector::Handle> {
+ match_variant!(self, Connector).flatten()
+ }
+
+ /// Encoder object value
+ pub fn as_encoder(&self) -> Option<super::encoder::Handle> {
+ match_variant!(self, Encoder).flatten()
+ }
+
+ /// Framebuffer object value
+ pub fn as_framebuffer(&self) -> Option<super::framebuffer::Handle> {
+ match_variant!(self, Framebuffer).flatten()
+ }
+
+ /// Plane object value
+ pub fn as_plane(&self) -> Option<super::plane::Handle> {
+ match_variant!(self, Plane).flatten()
+ }
+
+ /// Property object value
+ pub fn as_property(&self) -> Option<Handle> {
+ match_variant!(self, Property).flatten()
+ }
+}
+
+/// A single value of [`ValueType::Enum`] type
+#[repr(transparent)]
+#[derive(Copy, Clone, Hash, PartialEq, Eq, bytemuck::TransparentWrapper)]
+pub struct EnumValue(ffi::drm_mode_property_enum);
+
+impl EnumValue {
+ /// Returns the [`RawValue`] of this value
+ pub fn value(&self) -> RawValue {
+ self.0.value
+ }
+
+ /// Returns the name of this value
+ pub fn name(&self) -> &std::ffi::CStr {
+ unsafe { std::ffi::CStr::from_ptr(&self.0.name[0] as _) }
+ }
+}
+
+impl From<ffi::drm_mode_property_enum> for EnumValue {
+ fn from(inner: ffi::drm_mode_property_enum) -> Self {
+ EnumValue(inner)
+ }
+}
+
+impl std::fmt::Debug for EnumValue {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ f.debug_struct("EnumValue")
+ .field("value", &self.value())
+ .field("name", &self.name())
+ .finish()
+ }
+}
+
+/// A set of [`EnumValue`]s for a single property
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub struct EnumValues {
+ pub(crate) values: Vec<u64>,
+ pub(crate) enums: Vec<EnumValue>,
+}
+
+impl EnumValues {
+ /// Returns a tuple containing slices to the [`RawValue`]s and the [`EnumValue`]s
+ pub fn values(&self) -> (&[RawValue], &[EnumValue]) {
+ (&self.values, &self.enums)
+ }
+
+ /// Returns an [`EnumValue`] for a [`RawValue`], or [`None`] if `value` is
+ /// not part of this [`EnumValues`].
+ pub fn get_value_from_raw_value(&self, value: RawValue) -> Option<&EnumValue> {
+ let (values, enums) = self.values();
+ let index = if values.get(value as usize) == Some(&value) {
+ // Early-out: indices match values
+ value as usize
+ } else {
+ values.iter().position(|&v| v == value)?
+ };
+ Some(&enums[index])
+ }
+}
diff --git a/src/control/syncobj.rs b/src/control/syncobj.rs
new file mode 100644
index 0000000..b4106e6
--- /dev/null
+++ b/src/control/syncobj.rs
@@ -0,0 +1,49 @@
+//! # SyncObj
+//!
+//! A SyncObj is a binding point for the DRM subsystem to attach single-use fences which are
+//! signalled when a device task completes. They are typically provided as optional arguments to
+//! device-specific command submission IOCTLs. In practice, they are used to implement Vulkan
+//! fence objects.
+//!
+//! After a submission IOCTL sets a fence into a SyncObj, it may be exported as a sync file
+//! descriptor. This sync file may be epoll()'d for EPOLLIN to implement asynchronous waiting on
+//! multiple events. This file descriptor is also compatible with [`tokio::io::unix::AsyncFd`] for
+//! Rust async/await integration.
+//!
+//! [`tokio::io::unix::AsyncFd`]: <https://docs.rs/tokio/latest/tokio/io/unix/struct.AsyncFd.html>
+
+use crate::control;
+
+/// A handle to a specific syncobj
+#[repr(transparent)]
+#[derive(Copy, Clone, Hash, PartialEq, Eq)]
+pub struct Handle(control::RawResourceHandle);
+
+// Safety: Handle is repr(transparent) over NonZeroU32
+unsafe impl bytemuck::ZeroableInOption for Handle {}
+unsafe impl bytemuck::PodInOption for Handle {}
+unsafe impl bytemuck::NoUninit for Handle {}
+
+impl From<Handle> for control::RawResourceHandle {
+ fn from(handle: Handle) -> Self {
+ handle.0
+ }
+}
+
+impl From<Handle> for u32 {
+ fn from(handle: Handle) -> Self {
+ handle.0.into()
+ }
+}
+
+impl From<control::RawResourceHandle> for Handle {
+ fn from(handle: control::RawResourceHandle) -> Self {
+ Handle(handle)
+ }
+}
+
+impl std::fmt::Debug for Handle {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ f.debug_tuple("syncobj::Handle").field(&self.0).finish()
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..76e80c3
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,351 @@
+//! A safe interface to the Direct Rendering Manager subsystem found in various
+//! operating systems.
+//!
+//! # Summary
+//!
+//! The Direct Rendering Manager (DRM) is subsystem found in various operating
+//! systems that exposes graphical functionality to userspace processes. It can
+//! be used to send data and commands to a GPU driver that implements the
+//! interface.
+//!
+//! Userspace processes can access the DRM by opening a 'device node' (usually
+//! found in `/dev/dri/*`) and using various `ioctl` commands on the open file
+//! descriptor. Most processes use the libdrm library (part of the mesa project)
+//! to execute these commands. This crate takes a more direct approach,
+//! bypassing libdrm and executing the commands directly and doing minimal
+//! abstraction to keep the interface safe.
+//!
+//! While the DRM subsystem exposes many powerful GPU interfaces, it is not
+//! recommended for rendering or GPGPU operations. There are many standards made
+//! for these use cases, and they are far more fitting for those sort of tasks.
+//!
+//! ## Usage
+//!
+//! To begin using this crate, the [`Device`] trait must be
+//! implemented. See the trait's [example section](trait@Device#example) for
+//! details on how to implement it.
+//!
+
+#![warn(missing_docs)]
+
+pub(crate) mod util;
+
+pub mod buffer;
+pub mod control;
+
+use std::ffi::{OsStr, OsString};
+use std::time::Duration;
+use std::{
+ io,
+ os::unix::{ffi::OsStringExt, io::AsFd},
+};
+
+use rustix::io::Errno;
+
+use crate::util::*;
+
+pub use drm_ffi::{DRM_CLOEXEC as CLOEXEC, DRM_RDWR as RDWR};
+
+/// This trait should be implemented by any object that acts as a DRM device. It
+/// is a prerequisite for using any DRM functionality.
+///
+/// This crate does not provide a concrete device object due to the various ways
+/// it can be implemented. The user of this crate is expected to implement it
+/// themselves and derive this trait as necessary. The example below
+/// demonstrates how to do this using a small wrapper.
+///
+/// # Example
+///
+/// ```
+/// use drm::Device;
+///
+/// use std::fs::File;
+/// use std::fs::OpenOptions;
+///
+/// use std::os::unix::io::AsFd;
+/// use std::os::unix::io::BorrowedFd;
+///
+/// #[derive(Debug)]
+/// /// A simple wrapper for a device node.
+/// struct Card(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()
+/// }
+/// }
+///
+/// /// With [`AsFd`] implemented, we can now implement [`drm::Device`].
+/// impl Device for Card {}
+///
+/// impl Card {
+/// /// Simple helper method for opening a [`Card`].
+/// fn open() -> Self {
+/// let mut options = OpenOptions::new();
+/// options.read(true);
+/// options.write(true);
+///
+/// // The normal location of the primary device node on Linux
+/// Card(options.open("/dev/dri/card0").unwrap())
+/// }
+/// }
+/// ```
+pub trait Device: AsFd {
+ /// Acquires the DRM Master lock for this process.
+ ///
+ /// # Notes
+ ///
+ /// Acquiring the DRM Master is done automatically when the primary device
+ /// node is opened. If you opened the primary device node and did not
+ /// acquire the lock, another process likely has the lock.
+ ///
+ /// This function is only available to processes with CAP_SYS_ADMIN
+ /// privileges (usually as root)
+ fn acquire_master_lock(&self) -> io::Result<()> {
+ drm_ffi::auth::acquire_master(self.as_fd())?;
+ Ok(())
+ }
+
+ /// Releases the DRM Master lock for another process to use.
+ fn release_master_lock(&self) -> io::Result<()> {
+ drm_ffi::auth::release_master(self.as_fd())?;
+ Ok(())
+ }
+
+ /// Generates an [`AuthToken`] for this process.
+ #[deprecated(note = "Consider opening a render node instead.")]
+ fn generate_auth_token(&self) -> io::Result<AuthToken> {
+ let token = drm_ffi::auth::get_magic_token(self.as_fd())?;
+ Ok(AuthToken(token.magic))
+ }
+
+ /// Authenticates an [`AuthToken`] from another process.
+ fn authenticate_auth_token(&self, token: AuthToken) -> io::Result<()> {
+ drm_ffi::auth::auth_magic_token(self.as_fd(), token.0)?;
+ Ok(())
+ }
+
+ /// Requests the driver to expose or hide certain capabilities. See
+ /// [`ClientCapability`] for more information.
+ fn set_client_capability(&self, cap: ClientCapability, enable: bool) -> io::Result<()> {
+ drm_ffi::set_capability(self.as_fd(), cap as u64, enable)?;
+ Ok(())
+ }
+
+ /// Gets the bus ID of this device.
+ fn get_bus_id(&self) -> io::Result<OsString> {
+ let mut buffer = Vec::new();
+ let _ = drm_ffi::get_bus_id(self.as_fd(), Some(&mut buffer))?;
+ let bus_id = OsString::from_vec(buffer);
+
+ Ok(bus_id)
+ }
+
+ /// Check to see if our [`AuthToken`] has been authenticated
+ /// by the DRM Master
+ fn authenticated(&self) -> io::Result<bool> {
+ let client = drm_ffi::get_client(self.as_fd(), 0)?;
+ Ok(client.auth == 1)
+ }
+
+ /// Gets the value of a capability.
+ fn get_driver_capability(&self, cap: DriverCapability) -> io::Result<u64> {
+ let cap = drm_ffi::get_capability(self.as_fd(), cap as u64)?;
+ Ok(cap.value)
+ }
+
+ /// # Possible errors:
+ /// - `EFAULT`: Kernel could not copy fields into userspace
+ #[allow(missing_docs)]
+ fn get_driver(&self) -> io::Result<Driver> {
+ let mut name = Vec::new();
+ let mut date = Vec::new();
+ let mut desc = Vec::new();
+
+ let _ = drm_ffi::get_version(
+ self.as_fd(),
+ Some(&mut name),
+ Some(&mut date),
+ Some(&mut desc),
+ )?;
+
+ let name = OsString::from_vec(unsafe { transmute_vec(name) });
+ let date = OsString::from_vec(unsafe { transmute_vec(date) });
+ let desc = OsString::from_vec(unsafe { transmute_vec(desc) });
+
+ let driver = Driver { name, date, desc };
+
+ Ok(driver)
+ }
+
+ /// Waits for a vblank.
+ fn wait_vblank(
+ &self,
+ target_sequence: VblankWaitTarget,
+ flags: VblankWaitFlags,
+ high_crtc: u32,
+ user_data: usize,
+ ) -> io::Result<VblankWaitReply> {
+ use drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_HIGH_CRTC_MASK;
+ use drm_ffi::_DRM_VBLANK_HIGH_CRTC_SHIFT;
+
+ let high_crtc_mask = _DRM_VBLANK_HIGH_CRTC_MASK >> _DRM_VBLANK_HIGH_CRTC_SHIFT;
+ if (high_crtc & !high_crtc_mask) != 0 {
+ return Err(Errno::INVAL.into());
+ }
+
+ let (sequence, wait_type) = match target_sequence {
+ VblankWaitTarget::Absolute(n) => {
+ (n, drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_ABSOLUTE)
+ }
+ VblankWaitTarget::Relative(n) => {
+ (n, drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_RELATIVE)
+ }
+ };
+
+ let type_ = wait_type | (high_crtc << _DRM_VBLANK_HIGH_CRTC_SHIFT) | flags.bits();
+ let reply = drm_ffi::wait_vblank(self.as_fd(), type_, sequence, user_data)?;
+
+ let time = match (reply.tval_sec, reply.tval_usec) {
+ (0, 0) => None,
+ (sec, usec) => Some(Duration::new(sec as u64, (usec * 1000) as u32)),
+ };
+
+ Ok(VblankWaitReply {
+ frame: reply.sequence,
+ time,
+ })
+ }
+}
+
+/// An authentication token, unique to the file descriptor of the device.
+///
+/// This token can be sent to another process that owns the DRM Master lock to
+/// allow unprivileged use of the device, such as rendering.
+///
+/// # Deprecation Notes
+///
+/// This method of authentication is somewhat deprecated. Accessing unprivileged
+/// functionality is best done by opening a render node. However, some other
+/// processes may still use this method of authentication. Therefore, we still
+/// provide functionality for generating and authenticating these tokens.
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub struct AuthToken(u32);
+
+/// Driver version of a device.
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub struct Driver {
+ /// Name of the driver
+ pub name: OsString,
+ /// Date driver was published
+ pub date: OsString,
+ /// Driver description
+ pub desc: OsString,
+}
+
+impl Driver {
+ /// Name of driver
+ pub fn name(&self) -> &OsStr {
+ self.name.as_ref()
+ }
+
+ /// Date driver was published
+ pub fn date(&self) -> &OsStr {
+ self.date.as_ref()
+ }
+
+ /// Driver description
+ pub fn description(&self) -> &OsStr {
+ self.desc.as_ref()
+ }
+}
+
+/// Used to check which capabilities your graphics driver has.
+#[allow(clippy::upper_case_acronyms)]
+#[repr(u64)]
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub enum DriverCapability {
+ /// DumbBuffer support for scanout
+ DumbBuffer = drm_ffi::DRM_CAP_DUMB_BUFFER as u64,
+ /// Unknown
+ VBlankHighCRTC = drm_ffi::DRM_CAP_VBLANK_HIGH_CRTC as u64,
+ /// Preferred depth to use for dumb buffers
+ DumbPreferredDepth = drm_ffi::DRM_CAP_DUMB_PREFERRED_DEPTH as u64,
+ /// Unknown
+ DumbPreferShadow = drm_ffi::DRM_CAP_DUMB_PREFER_SHADOW as u64,
+ /// PRIME handles are supported
+ Prime = drm_ffi::DRM_CAP_PRIME as u64,
+ /// Unknown
+ MonotonicTimestamp = drm_ffi::DRM_CAP_TIMESTAMP_MONOTONIC as u64,
+ /// Asynchronous page flipping support
+ ASyncPageFlip = drm_ffi::DRM_CAP_ASYNC_PAGE_FLIP as u64,
+ /// Width of cursor buffers
+ CursorWidth = drm_ffi::DRM_CAP_CURSOR_WIDTH as u64,
+ /// Height of cursor buffers
+ CursorHeight = drm_ffi::DRM_CAP_CURSOR_HEIGHT as u64,
+ /// Create framebuffers with modifiers
+ AddFB2Modifiers = drm_ffi::DRM_CAP_ADDFB2_MODIFIERS as u64,
+ /// Unknown
+ PageFlipTarget = drm_ffi::DRM_CAP_PAGE_FLIP_TARGET as u64,
+ /// Uses the CRTC's ID in vblank events
+ CRTCInVBlankEvent = drm_ffi::DRM_CAP_CRTC_IN_VBLANK_EVENT as u64,
+ /// SyncObj support
+ SyncObj = drm_ffi::DRM_CAP_SYNCOBJ as u64,
+ /// Timeline SyncObj support
+ TimelineSyncObj = drm_ffi::DRM_CAP_SYNCOBJ_TIMELINE as u64,
+}
+
+/// Used to enable/disable capabilities for the process.
+#[repr(u64)]
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub enum ClientCapability {
+ /// The driver provides 3D screen control
+ Stereo3D = drm_ffi::DRM_CLIENT_CAP_STEREO_3D as u64,
+ /// The driver provides more plane types for modesetting
+ UniversalPlanes = drm_ffi::DRM_CLIENT_CAP_UNIVERSAL_PLANES as u64,
+ /// The driver provides atomic modesetting
+ Atomic = drm_ffi::DRM_CLIENT_CAP_ATOMIC as u64,
+}
+
+/// Used to specify a vblank sequence to wait for
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub enum VblankWaitTarget {
+ /// Wait for a specific vblank sequence number
+ Absolute(u32),
+ /// Wait for a given number of vblanks
+ Relative(u32),
+}
+
+bitflags::bitflags! {
+ /// Flags to alter the behaviour when waiting for a vblank
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+ pub struct VblankWaitFlags : u32 {
+ /// Send event instead of blocking
+ const EVENT = drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_EVENT;
+ /// If missed, wait for next vblank
+ const NEXT_ON_MISS = drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_NEXTONMISS;
+ }
+}
+
+/// Data returned from a vblank wait
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub struct VblankWaitReply {
+ frame: u32,
+ time: Option<Duration>,
+}
+
+impl VblankWaitReply {
+ /// Sequence of the frame
+ pub fn frame(&self) -> u32 {
+ self.frame
+ }
+
+ /// Time at which the vblank occurred. [`None`] if an asynchronous event was
+ /// requested
+ pub fn time(&self) -> Option<Duration> {
+ self.time
+ }
+}
diff --git a/src/util.rs b/src/util.rs
new file mode 100644
index 0000000..6f2b8d6
--- /dev/null
+++ b/src/util.rs
@@ -0,0 +1,19 @@
+//! Utilities used internally by this crate.
+
+use crate::control::{from_u32, RawResourceHandle};
+
+pub unsafe fn transmute_vec<T, U>(from: Vec<T>) -> Vec<U> {
+ let mut from = std::mem::ManuallyDrop::new(from);
+
+ Vec::from_raw_parts(from.as_mut_ptr() as *mut U, from.len(), from.capacity())
+}
+
+pub unsafe fn transmute_vec_from_u32<T: From<RawResourceHandle>>(raw: Vec<u32>) -> Vec<T> {
+ if cfg!(debug_assertions) {
+ raw.into_iter()
+ .map(|handle| from_u32(handle).unwrap())
+ .collect()
+ } else {
+ transmute_vec(raw)
+ }
+}