Snap for 5907899 from fcf03f84cf07ec30816bd6257e9a0aa7f228a497 to r-keystone-qcom-release

Change-Id: I49bd0e12aefe9048b3f9370d18096cdfa4f32bc1
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 57088bc..f2f24fe 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -25,6 +25,8 @@
 
 set(KMSXX_ENABLE_KMSCUBE OFF CACHE BOOL "Enable kmscube")
 
+set(KMSXX_ENABLE_THREADING ON CACHE BOOL "Enable threading for parallelized drawing")
+
 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11 -Wall")
 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wextra -Wno-unused-parameter")
 
diff --git a/METADATA b/METADATA
index 18cb362..e098fe8 100644
--- a/METADATA
+++ b/METADATA
@@ -1,9 +1,5 @@
 name: "kms++"
-description:
-    "libkmsxx is a small C++11 library for kernel mode setting. It tries to "
-    "implement as little extra as possible while bringing the kms API in a C++ "
-    "form to the user. It only implements a subset of what libdrm supports."
-
+description: "libkmsxx is a small C++11 library for kernel mode setting. It tries to implement as little extra as possible while bringing the kms API in a C++ form to the user. It only implements a subset of what libdrm supports."
 third_party {
   url {
     type: HOMEPAGE
@@ -13,7 +9,11 @@
     type: GIT
     value: "https://github.com/tomba/kmsxx"
   }
-  version: "e0067bdc75566629c9143818c8f3970c16c8825e"
-  last_upgrade_date { year: 2018 month: 8 day: 15 }
+  version: "a5545df02b40414c2bf3abc60cf629c5f59d00ec"
   license_type: RECIPROCAL
+  last_upgrade_date {
+    year: 2019
+    month: 7
+    day: 30
+  }
 }
diff --git a/README.md b/README.md
index f0f3b97..feb43d7 100644
--- a/README.md
+++ b/README.md
@@ -88,6 +88,8 @@
 --------------------------------- | -------------
 KMSXX_DISABLE_UNIVERSAL_PLANES    | Set to disable the use of universal planes
 KMSXX_DISABLE_ATOMIC              | Set to disable the use of atomic modesetting
+KMSXX_DEVICE                      | Path to the card device node to use
+KMSXX_DRIVER                      | Name of the driver to use. The format is either "drvname" or "drvname:idx"
 
 ## Python notes
 
diff --git a/ext/pybind11 b/ext/pybind11
index 086d53e..9a19306 160000
--- a/ext/pybind11
+++ b/ext/pybind11
@@ -1 +1 @@
-Subproject commit 086d53e8c66a84d0ec723d5435918c76edd878e8
+Subproject commit 9a19306fbf30642ca331d0ec88e7da54a96860f9
diff --git a/kms++/inc/kms++/card.h b/kms++/inc/kms++/card.h
index c86278d..099d5b5 100644
--- a/kms++/inc/kms++/card.h
+++ b/kms++/inc/kms++/card.h
@@ -3,6 +3,7 @@
 #include <cstdint>
 #include <vector>
 #include <map>
+#include <memory>
 
 #include "decls.h"
 #include "pipeline.h"
@@ -13,8 +14,8 @@
 {
 	friend class Framebuffer;
 public:
-	Card();
-	Card(const std::string& device);
+	Card(const std::string& dev_path = "");
+	Card(const std::string& driver, uint32_t idx);
 	virtual ~Card();
 
 	Card(const Card& other) = delete;
@@ -33,9 +34,11 @@
 	Plane* get_plane(uint32_t id) const;
 	Property* get_prop(uint32_t id) const;
 
-	bool master() const { return m_master; }
+	bool is_master() const { return m_is_master; }
 	bool has_atomic() const { return m_has_atomic; }
 	bool has_has_universal_planes() const { return m_has_universal_planes; }
+	bool has_dumb_buffers() const { return m_has_dumb; }
+	bool has_kms() const;
 
 	const std::vector<Connector*> get_connectors() const { return m_connectors; }
 	const std::vector<Encoder*> get_encoders() const { return m_encoders; }
@@ -51,7 +54,10 @@
 
 	int disable_all();
 
+	const std::string& version_name() const { return m_version_name; }
+
 private:
+	void setup();
 	void restore_modes();
 
 	std::map<uint32_t, DrmObject*> m_obmap;
@@ -64,9 +70,17 @@
 	std::vector<Framebuffer*> m_framebuffers;
 
 	int m_fd;
-	bool m_master;
+	bool m_is_master;
 
 	bool m_has_atomic;
 	bool m_has_universal_planes;
+	bool m_has_dumb;
+
+	int m_version_major;
+	int m_version_minor;
+	int m_version_patchlevel;
+	std::string m_version_name;
+	std::string m_version_date;
+	std::string m_version_desc;
 };
 }
diff --git a/kms++/inc/kms++/connector.h b/kms++/inc/kms++/connector.h
index 3407730..155f916 100644
--- a/kms++/inc/kms++/connector.h
+++ b/kms++/inc/kms++/connector.h
@@ -10,6 +10,13 @@
 
 struct ConnectorPriv;
 
+enum class ConnectorStatus
+{
+	Unknown,
+	Connected,
+	Disconnected,
+};
+
 class Connector : public DrmPropObject
 {
 	friend class Card;
@@ -24,7 +31,9 @@
 	Crtc* get_current_crtc() const;
 	std::vector<Crtc*> get_possible_crtcs() const;
 
+	// true if connected or unknown
 	bool connected() const;
+	ConnectorStatus connector_status() const;
 
 	const std::string& fullname() const { return m_fullname; }
 	uint32_t connector_type() const;
diff --git a/kms++/inc/kms++/drmpropobject.h b/kms++/inc/kms++/drmpropobject.h
index 38de584..d9ba58e 100644
--- a/kms++/inc/kms++/drmpropobject.h
+++ b/kms++/inc/kms++/drmpropobject.h
@@ -23,6 +23,7 @@
 
 	const std::map<uint32_t, uint64_t>& get_prop_map() const { return m_prop_values; }
 
+	int set_prop_value(Property* prop, uint64_t value);
 	int set_prop_value(uint32_t id, uint64_t value);
 	int set_prop_value(const std::string& name, uint64_t value);
 
diff --git a/kms++/inc/kms++/omap/omapcard.h b/kms++/inc/kms++/omap/omapcard.h
index 5c2f3a5..2f1f528 100644
--- a/kms++/inc/kms++/omap/omapcard.h
+++ b/kms++/inc/kms++/omap/omapcard.h
@@ -9,8 +9,7 @@
 class OmapCard : public Card
 {
 public:
-	OmapCard();
-	OmapCard(const std::string& device);
+	OmapCard(const std::string& device = "");
 	virtual ~OmapCard();
 
 	struct omap_device* dev() const { return m_omap_dev; }
diff --git a/kms++/inc/kms++/pixelformats.h b/kms++/inc/kms++/pixelformats.h
index 6392de1..15fee7f 100644
--- a/kms++/inc/kms++/pixelformats.h
+++ b/kms++/inc/kms++/pixelformats.h
@@ -24,14 +24,32 @@
 
 	XRGB8888 = MakeFourCC("XR24"),
 	XBGR8888 = MakeFourCC("XB24"),
+	RGBX8888 = MakeFourCC("RX24"),
+	BGRX8888 = MakeFourCC("BX24"),
+
 	ARGB8888 = MakeFourCC("AR24"),
 	ABGR8888 = MakeFourCC("AB24"),
+	RGBA8888 = MakeFourCC("RA24"),
+	BGRA8888 = MakeFourCC("BA24"),
 
 	RGB888 = MakeFourCC("RG24"),
 	BGR888 = MakeFourCC("BG24"),
 
 	RGB565 = MakeFourCC("RG16"),
 	BGR565 = MakeFourCC("BG16"),
+
+	ARGB4444 = MakeFourCC("AR12"),
+	ARGB1555 = MakeFourCC("AR15"),
+
+	XRGB2101010 = MakeFourCC("XR30"),
+	XBGR2101010 = MakeFourCC("XB30"),
+	RGBX1010102 = MakeFourCC("RX30"),
+	BGRX1010102 = MakeFourCC("BX30"),
+
+	ARGB2101010 = MakeFourCC("AR30"),
+	ABGR2101010 = MakeFourCC("AB30"),
+	RGBA1010102 = MakeFourCC("RA30"),
+	BGRA1010102 = MakeFourCC("BA30"),
 };
 
 static inline PixelFormat FourCCToPixelFormat(const std::string& fourcc)
@@ -49,6 +67,12 @@
 	return std::string(buf);
 }
 
+enum class PixelColorType
+{
+	RGB,
+	YUV,
+};
+
 struct PixelFormatPlaneInfo
 {
 	uint8_t bitspp;
@@ -58,6 +82,7 @@
 
 struct PixelFormatInfo
 {
+	PixelColorType type;
 	uint8_t num_planes;
 	struct PixelFormatPlaneInfo planes[4];
 };
diff --git a/kms++/src/card.cpp b/kms++/src/card.cpp
index d71db7c..8de8b82 100644
--- a/kms++/src/card.cpp
+++ b/kms++/src/card.cpp
@@ -6,6 +6,8 @@
 #include <string.h>
 #include <algorithm>
 #include <cerrno>
+#include <algorithm>
+#include <glob.h>
 
 #include <xf86drm.h>
 #include <xf86drmMode.h>
@@ -17,23 +19,150 @@
 namespace kms
 {
 
-Card::Card()
-	: Card("/dev/dri/card0")
+static vector<string> glob(const string& pattern)
 {
+	glob_t glob_result;
+	memset(&glob_result, 0, sizeof(glob_result));
+
+	int r = glob(pattern.c_str(), 0, NULL, &glob_result);
+	if(r != 0) {
+		globfree(&glob_result);
+		throw runtime_error("failed to find DRM cards");
+	}
+
+	vector<string> filenames;
+	for(size_t i = 0; i < glob_result.gl_pathc; ++i)
+		filenames.push_back(string(glob_result.gl_pathv[i]));
+
+	globfree(&glob_result);
+
+	return filenames;
 }
 
-
-Card::Card(const std::string& device)
+static int open_first_kms_device()
 {
-	int fd = open(device.c_str(), O_RDWR | O_CLOEXEC);
+	vector<string> paths = glob("/dev/dri/card*");
+
+	for (const string& path : paths) {
+		int fd = open(path.c_str(), O_RDWR | O_CLOEXEC);
+		if (fd < 0)
+			throw invalid_argument(string(strerror(errno)) + " opening device " + path);
+
+		auto res = drmModeGetResources(fd);
+		if (!res) {
+			close(fd);
+			continue;
+		}
+
+		bool has_kms = res->count_crtcs > 0 && res->count_connectors > 0 && res->count_encoders > 0;
+
+		drmModeFreeResources(res);
+
+		if (has_kms)
+			return fd;
+
+		close(fd);
+	}
+
+	throw runtime_error("No modesetting DRM card found");
+}
+
+static int open_device_by_path(string path)
+{
+	int fd = open(path.c_str(), O_RDWR | O_CLOEXEC);
 	if (fd < 0)
-		throw invalid_argument(string(strerror(errno)) + " opening " + device);
-	m_fd = fd;
+		throw invalid_argument(string(strerror(errno)) + " opening device " + path);
+	return fd;
+}
+
+// open Nth DRM card with the given driver name
+static int open_device_by_driver(string name, uint32_t idx)
+{
+	transform(name.begin(), name.end(), name.begin(), ::tolower);
+
+	uint32_t num_matches = 0;
+	vector<string> paths = glob("/dev/dri/card*");
+
+	for (const string& path : paths) {
+		int fd = open_device_by_path(path);
+
+		drmVersionPtr ver = drmGetVersion(fd);
+		string drv_name = string(ver->name, ver->name_len);
+		drmFreeVersion(ver);
+
+		transform(drv_name.begin(), drv_name.end(), drv_name.begin(), ::tolower);
+
+		if (name == drv_name) {
+			if (idx == num_matches)
+				return fd;
+			num_matches++;
+		}
+
+		close(fd);
+	}
+
+	throw invalid_argument("Failed to find a DRM device " + name + ":" + to_string(idx));
+}
+
+Card::Card(const std::string& dev_path)
+{
+	const char* drv_p = getenv("KMSXX_DRIVER");
+	const char* dev_p = getenv("KMSXX_DEVICE");
+
+	if (!dev_path.empty()) {
+		m_fd = open_device_by_path(dev_path);
+	} else if (dev_p) {
+		string dev(dev_p);
+		m_fd = open_device_by_path(dev);
+	} else if (drv_p) {
+		string drv(drv_p);
+
+		auto isplit = find(drv.begin(), drv.end(), ':');
+
+		if (isplit == drv.begin())
+			throw runtime_error("Invalid KMSXX_DRIVER");
+
+		string name;
+		uint32_t num = 0;
+
+		if (isplit == drv.end()) {
+			name = drv;
+		} else {
+			name = string(drv.begin(), isplit);
+			string numstr = string(isplit + 1, drv.end());
+			num = stoul(numstr);
+		}
+
+		m_fd = open_device_by_driver(name, num);
+	} else {
+		m_fd = open_first_kms_device();
+	}
+
+	setup();
+}
+
+Card::Card(const std::string& driver, uint32_t idx)
+{
+	m_fd = open_device_by_driver(driver, idx);
+
+	setup();
+}
+
+void Card::setup()
+{
+	drmVersionPtr ver = drmGetVersion(m_fd);
+	m_version_major = ver->version_major;
+	m_version_minor = ver->version_minor;
+	m_version_patchlevel = ver->version_patchlevel;
+	m_version_name = string(ver->name, ver->name_len);
+	m_version_date = string(ver->date, ver->date_len);
+	m_version_desc = string(ver->desc, ver->desc_len);
+	drmFreeVersion(ver);
 
 	int r;
 
-	r = drmSetMaster(fd);
-	m_master = r == 0;
+	r = drmSetMaster(m_fd);
+	m_is_master = r == 0;
 
 	if (getenv("KMSXX_DISABLE_UNIVERSAL_PLANES") == 0) {
 		r = drmSetClientCap(m_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
@@ -54,48 +183,47 @@
 #endif
 
 	uint64_t has_dumb;
-	r = drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb);
-	if (r || !has_dumb)
-		throw invalid_argument("Dumb buffers not available");
+	r = drmGetCap(m_fd, DRM_CAP_DUMB_BUFFER, &has_dumb);
+	m_has_dumb = r == 0 && has_dumb;
 
 	auto res = drmModeGetResources(m_fd);
-	if (!res)
-		throw invalid_argument("Can't get card resources");
+	if (res) {
+		for (int i = 0; i < res->count_connectors; ++i) {
+			uint32_t id = res->connectors[i];
+			auto ob = new Connector(*this, id, i);
+			m_obmap[id] = ob;
+			m_connectors.push_back(ob);
+		}
 
-	for (int i = 0; i < res->count_connectors; ++i) {
-		uint32_t id = res->connectors[i];
-		auto ob = new Connector(*this, id, i);
-		m_obmap[id] = ob;
-		m_connectors.push_back(ob);
+		for (int i = 0; i < res->count_crtcs; ++i) {
+			uint32_t id = res->crtcs[i];
+			auto ob = new Crtc(*this, id, i);
+			m_obmap[id] = ob;
+			m_crtcs.push_back(ob);
+		}
+
+		for (int i = 0; i < res->count_encoders; ++i) {
+			uint32_t id = res->encoders[i];
+			auto ob = new Encoder(*this, id, i);
+			m_obmap[id] = ob;
+			m_encoders.push_back(ob);
+		}
+
+		drmModeFreeResources(res);
+
+		auto planeRes = drmModeGetPlaneResources(m_fd);
+		if (planeRes) {
+			for (uint i = 0; i < planeRes->count_planes; ++i) {
+				uint32_t id = planeRes->planes[i];
+				auto ob = new Plane(*this, id, i);
+				m_obmap[id] = ob;
+				m_planes.push_back(ob);
+			}
+
+			drmModeFreePlaneResources(planeRes);
+		}
 	}
 
-	for (int i = 0; i < res->count_crtcs; ++i) {
-		uint32_t id = res->crtcs[i];
-		auto ob = new Crtc(*this, id, i);
-		m_obmap[id] = ob;
-		m_crtcs.push_back(ob);
-	}
-
-	for (int i = 0; i < res->count_encoders; ++i) {
-		uint32_t id = res->encoders[i];
-		auto ob = new Encoder(*this, id, i);
-		m_obmap[id] = ob;
-		m_encoders.push_back(ob);
-	}
-
-	drmModeFreeResources(res);
-
-	auto planeRes = drmModeGetPlaneResources(m_fd);
-
-	for (uint i = 0; i < planeRes->count_planes; ++i) {
-		uint32_t id = planeRes->planes[i];
-		auto ob = new Plane(*this, id, i);
-		m_obmap[id] = ob;
-		m_planes.push_back(ob);
-	}
-
-	drmModeFreePlaneResources(planeRes);
-
 	// collect all possible props
 	for (auto ob : get_objects()) {
 		auto props = drmModeObjectGetProperties(m_fd, ob->id(), ob->object_type());
@@ -136,6 +264,12 @@
 void Card::drop_master()
 {
 	drmDropMaster(fd());
+	m_is_master = false;
+}
+
+bool Card::has_kms() const
+{
+	return m_connectors.size() > 0 && m_encoders.size() > 0 && m_crtcs.size() > 0;
 }
 
 void Card::restore_modes()
diff --git a/kms++/src/connector.cpp b/kms++/src/connector.cpp
index 47759be..a1807da 100644
--- a/kms++/src/connector.cpp
+++ b/kms++/src/connector.cpp
@@ -195,6 +195,18 @@
 			m_priv->drm_connector->connection == DRM_MODE_UNKNOWNCONNECTION;
 }
 
+ConnectorStatus Connector::connector_status() const
+{
+	switch (m_priv->drm_connector->connection) {
+	case DRM_MODE_CONNECTED:
+		return ConnectorStatus::Connected;
+	case DRM_MODE_DISCONNECTED:
+		return ConnectorStatus::Disconnected;
+	default:
+		return ConnectorStatus::Unknown;
+	}
+}
+
 vector<Crtc*> Connector::get_possible_crtcs() const
 {
 	vector<Crtc*> crtcs;
diff --git a/kms++/src/drmpropobject.cpp b/kms++/src/drmpropobject.cpp
index f5a3c97..f91f913 100644
--- a/kms++/src/drmpropobject.cpp
+++ b/kms++/src/drmpropobject.cpp
@@ -80,6 +80,11 @@
 	return unique_ptr<Blob>(new Blob(card(), blob_id));
 }
 
+int DrmPropObject::set_prop_value(Property* prop, uint64_t value)
+{
+	return drmModeObjectSetProperty(card().fd(), this->id(), this->object_type(), prop->id(), value);
+}
+
 int DrmPropObject::set_prop_value(uint32_t id, uint64_t value)
 {
 	return drmModeObjectSetProperty(card().fd(), this->id(), this->object_type(), id, value);
diff --git a/kms++/src/encoder.cpp b/kms++/src/encoder.cpp
index 9cd5304..bfb2ea8 100644
--- a/kms++/src/encoder.cpp
+++ b/kms++/src/encoder.cpp
@@ -28,6 +28,7 @@
 	DEF_ENC(VIRTUAL),
 	DEF_ENC(DSI),
 	{ 7, "DPMST" },
+	{ 8, "DPI" },
 #undef DEF_ENC
 };
 
diff --git a/kms++/src/omap/omapcard.cpp b/kms++/src/omap/omapcard.cpp
index e811b6d..5e21c75 100644
--- a/kms++/src/omap/omapcard.cpp
+++ b/kms++/src/omap/omapcard.cpp
@@ -9,11 +9,6 @@
 
 namespace kms
 {
-OmapCard::OmapCard()
-	: OmapCard("/dev/dri/card0")
-{
-
-}
 
 OmapCard::OmapCard(const string& device)
 	: Card(device)
diff --git a/kms++/src/pixelformats.cpp b/kms++/src/pixelformats.cpp
index 84ea924..ecca41d 100644
--- a/kms++/src/pixelformats.cpp
+++ b/kms++/src/pixelformats.cpp
@@ -8,28 +8,49 @@
 {
 static const map<PixelFormat, PixelFormatInfo> format_info_array = {
 	/* YUV packed */
-	{ PixelFormat::UYVY, { 1, { { 16, 2, 1 } }, } },
-	{ PixelFormat::YUYV, { 1, { { 16, 2, 1 } }, } },
-	{ PixelFormat::YVYU, { 1, { { 16, 2, 1 } }, } },
-	{ PixelFormat::VYUY, { 1, { { 16, 2, 1 } }, } },
+	{ PixelFormat::UYVY, { PixelColorType::YUV, 1, { { 16, 2, 1 } }, } },
+	{ PixelFormat::YUYV, { PixelColorType::YUV, 1, { { 16, 2, 1 } }, } },
+	{ PixelFormat::YVYU, { PixelColorType::YUV, 1, { { 16, 2, 1 } }, } },
+	{ PixelFormat::VYUY, { PixelColorType::YUV, 1, { { 16, 2, 1 } }, } },
 	/* YUV semi-planar */
-	{ PixelFormat::NV12, { 2, { { 8, 1, 1, }, { 8, 2, 2 } }, } },
-	{ PixelFormat::NV21, { 2, { { 8, 1, 1, }, { 8, 2, 2 } }, } },
+	{ PixelFormat::NV12, { PixelColorType::YUV, 2, { { 8, 1, 1, }, { 8, 2, 2 } }, } },
+	{ PixelFormat::NV21, { PixelColorType::YUV, 2, { { 8, 1, 1, }, { 8, 2, 2 } }, } },
 	/* RGB16 */
-	{ PixelFormat::RGB565, { 1, { { 16, 1, 1 } }, } },
-	{ PixelFormat::BGR565, { 1, { { 16, 1, 1 } }, } },
+	{ PixelFormat::RGB565, { PixelColorType::RGB, 1, { { 16, 1, 1 } }, } },
+	{ PixelFormat::BGR565, { PixelColorType::RGB, 1, { { 16, 1, 1 } }, } },
 	/* RGB24 */
-	{ PixelFormat::RGB888, { 1, { { 24, 1, 1 } }, } },
-	{ PixelFormat::BGR888, { 1, { { 24, 1, 1 } }, } },
+	{ PixelFormat::RGB888, { PixelColorType::RGB, 1, { { 24, 1, 1 } }, } },
+	{ PixelFormat::BGR888, { PixelColorType::RGB, 1, { { 24, 1, 1 } }, } },
 	/* RGB32 */
-	{ PixelFormat::XRGB8888, { 1, { { 32, 1, 1 } }, } },
-	{ PixelFormat::XBGR8888, { 1, { { 32, 1, 1 } }, } },
-	{ PixelFormat::ARGB8888, { 1, { { 32, 1, 1 } }, } },
-	{ PixelFormat::ABGR8888, { 1, { { 32, 1, 1 } }, } },
+	{ PixelFormat::XRGB8888, { PixelColorType::RGB, 1, { { 32, 1, 1 } }, } },
+	{ PixelFormat::XBGR8888, { PixelColorType::RGB, 1, { { 32, 1, 1 } }, } },
+	{ PixelFormat::RGBX8888, { PixelColorType::RGB, 1, { { 32, 1, 1 } }, } },
+	{ PixelFormat::BGRX8888, { PixelColorType::RGB, 1, { { 32, 1, 1 } }, } },
+
+	{ PixelFormat::ARGB8888, { PixelColorType::RGB, 1, { { 32, 1, 1 } }, } },
+	{ PixelFormat::ABGR8888, { PixelColorType::RGB, 1, { { 32, 1, 1 } }, } },
+	{ PixelFormat::RGBA8888, { PixelColorType::RGB, 1, { { 32, 1, 1 } }, } },
+	{ PixelFormat::BGRA8888, { PixelColorType::RGB, 1, { { 32, 1, 1 } }, } },
+
+	{ PixelFormat::XRGB2101010, { PixelColorType::RGB, 1, { { 32, 1, 1 } }, } },
+	{ PixelFormat::XBGR2101010, { PixelColorType::RGB, 1, { { 32, 1, 1 } }, } },
+	{ PixelFormat::RGBX1010102, { PixelColorType::RGB, 1, { { 32, 1, 1 } }, } },
+	{ PixelFormat::BGRX1010102, { PixelColorType::RGB, 1, { { 32, 1, 1 } }, } },
+
+	{ PixelFormat::ARGB2101010, { PixelColorType::RGB, 1, { { 32, 1, 1 } }, } },
+	{ PixelFormat::ABGR2101010, { PixelColorType::RGB, 1, { { 32, 1, 1 } }, } },
+	{ PixelFormat::RGBA1010102, { PixelColorType::RGB, 1, { { 32, 1, 1 } }, } },
+	{ PixelFormat::BGRA1010102, { PixelColorType::RGB, 1, { { 32, 1, 1 } }, } },
+
+	{ PixelFormat::ARGB4444, { PixelColorType::RGB, 1, { { 16, 1, 1 } }, } },
+	{ PixelFormat::ARGB1555, { PixelColorType::RGB, 1, { { 16, 1, 1 } }, } },
 };
 
 const struct PixelFormatInfo& get_pixel_format_info(PixelFormat format)
 {
+	if (!format_info_array.count(format))
+		throw invalid_argument("get_pixel_format_info: Unsupported pixelformat");
+
 	return format_info_array.at(format);
 }
 
diff --git a/kms++util/CMakeLists.txt b/kms++util/CMakeLists.txt
index 2fc15e3..70f3b17 100644
--- a/kms++util/CMakeLists.txt
+++ b/kms++util/CMakeLists.txt
@@ -7,7 +7,12 @@
     $<INSTALL_INTERFACE:include>
     PRIVATE src)
 
-target_link_libraries(kms++util kms++ pthread)
+target_link_libraries(kms++util kms++)
+
+if (KMSXX_ENABLE_THREADING)
+    target_link_libraries(kms++util pthread)
+    add_definitions(-DHAS_PTHREAD)
+endif()
 
 set_target_properties(kms++util PROPERTIES
     PUBLIC_HEADER "${PUB_HDRS}")
diff --git a/kms++util/inc/kms++util/color.h b/kms++util/inc/kms++util/color.h
index f378433..2bf6e66 100644
--- a/kms++util/inc/kms++util/color.h
+++ b/kms++util/inc/kms++util/color.h
@@ -25,8 +25,19 @@
 	uint32_t bgr888() const;
 	uint32_t argb8888() const;
 	uint32_t abgr8888() const;
+	uint32_t rgba8888() const;
+	uint32_t bgra8888() const;
+
+	// XXX these functions leave the lowest 2 bits zero
+	uint32_t argb2101010() const;
+	uint32_t abgr2101010() const;
+	uint32_t rgba1010102() const;
+	uint32_t bgra1010102() const;
+
 	uint16_t rgb565() const;
 	uint16_t bgr565() const;
+	uint16_t argb4444() const;
+	uint16_t argb1555() const;
 	YUV yuv(YUVType type = YUVType::BT601_Lim) const;
 
 	uint8_t b;
diff --git a/kms++util/inc/kms++util/kms++util.h b/kms++util/inc/kms++util/kms++util.h
index 8e45b0d..62ec663 100644
--- a/kms++util/inc/kms++util/kms++util.h
+++ b/kms++util/inc/kms++util/kms++util.h
@@ -22,6 +22,7 @@
 void draw_yuv420_macropixel(IFramebuffer& buf, unsigned x, unsigned y,
 				   YUV yuv1, YUV yuv2, YUV yuv3, YUV yuv4);
 void draw_rect(IFramebuffer &fb, uint32_t x, uint32_t y, uint32_t w, uint32_t h, RGB color);
+void draw_circle(IFramebuffer& fb, int32_t xCenter, int32_t yCenter, int32_t radius, RGB color);
 void draw_text(IFramebuffer& buf, uint32_t x, uint32_t y, const std::string& str, RGB color);
 
 void draw_color_bar(IFramebuffer& buf, int old_xpos, int xpos, int width);
diff --git a/kms++util/inc/kms++util/videodevice.h b/kms++util/inc/kms++util/videodevice.h
index 68e2b01..e089bcd 100644
--- a/kms++util/inc/kms++util/videodevice.h
+++ b/kms++util/inc/kms++util/videodevice.h
@@ -71,6 +71,8 @@
 
 	std::vector<kms::PixelFormat> get_formats();
 	void set_format(kms::PixelFormat fmt, uint32_t width, uint32_t height);
+	void get_selection(uint32_t& left, uint32_t& top, uint32_t& width, uint32_t& height);
+	void set_selection(uint32_t& left, uint32_t& top, uint32_t& width, uint32_t& height);
 	void set_queue_size(uint32_t queue_size);
 	void queue(kms::DumbFramebuffer* fb);
 	kms::DumbFramebuffer* dequeue();
diff --git a/kms++util/src/color.cpp b/kms++util/src/color.cpp
index 2e6f217..80e4866 100644
--- a/kms++util/src/color.cpp
+++ b/kms++util/src/color.cpp
@@ -49,6 +49,36 @@
 	return (a << 24) | (b << 16) | (g << 8) | (r << 0);
 }
 
+uint32_t RGB::rgba8888() const
+{
+	return (r << 24) | (g << 16) | (b << 8) | (a << 0);
+}
+
+uint32_t RGB::bgra8888() const
+{
+	return (b << 24) | (g << 16) | (r << 8) | (a << 0);
+}
+
+uint32_t RGB::argb2101010() const
+{
+	return ((a >> 6) << 30) | (r << 22) | (g << 12) | (b << 2);
+}
+
+uint32_t RGB::abgr2101010() const
+{
+	return ((a >> 6) << 30) | (b << 22) | (g << 12) | (r << 2);
+}
+
+uint32_t RGB::rgba1010102() const
+{
+	return (r << 24) | (g << 14) | (b << 4) | (a >> 6);
+}
+
+uint32_t RGB::bgra1010102() const
+{
+	return (b << 24) | (g << 14) | (r << 4) | (a >> 6);
+}
+
 uint16_t RGB::rgb565() const
 {
 	return ((r >> 3) << 11) | ((g >> 2) << 5) | ((b >> 3) << 0);
@@ -59,6 +89,16 @@
 	return ((b >> 3) << 11) | ((g >> 2) << 5) | ((r >> 3) << 0);
 }
 
+uint16_t RGB::argb4444() const
+{
+	return ((a >> 4) << 12) | ((r >> 4) << 8) | ((g >> 4) << 4) | ((b >> 4) << 0);
+}
+
+uint16_t RGB::argb1555() const
+{
+	return ((!!a) << 15) | ((r >> 3) << 10) | ((g >> 3) << 5) | ((b >> 3) << 0);
+}
+
 YUV RGB::yuv(YUVType type) const
 {
 	return YUV(*this, type);
diff --git a/kms++util/src/drawing.cpp b/kms++util/src/drawing.cpp
index 4e5c6c1..194daf8 100644
--- a/kms++util/src/drawing.cpp
+++ b/kms++util/src/drawing.cpp
@@ -1,4 +1,6 @@
 
+#include <cmath>
+
 #include <kms++/kms++.h>
 #include <kms++util/kms++util.h>
 
@@ -26,6 +28,48 @@
 		*p = color.abgr8888();
 		break;
 	}
+	case PixelFormat::RGBX8888:
+	case PixelFormat::RGBA8888:
+	{
+		uint32_t *p = (uint32_t*)(buf.map(0) + buf.stride(0) * y + x * 4);
+		*p = color.rgba8888();
+		break;
+	}
+	case PixelFormat::BGRX8888:
+	case PixelFormat::BGRA8888:
+	{
+		uint32_t *p = (uint32_t*)(buf.map(0) + buf.stride(0) * y + x * 4);
+		*p = color.bgra8888();
+		break;
+	}
+	case PixelFormat::XRGB2101010:
+	case PixelFormat::ARGB2101010:
+	{
+		uint32_t *p = (uint32_t*)(buf.map(0) + buf.stride(0) * y + x * 4);
+		*p = color.argb2101010();
+		break;
+	}
+	case PixelFormat::XBGR2101010:
+	case PixelFormat::ABGR2101010:
+	{
+		uint32_t *p = (uint32_t*)(buf.map(0) + buf.stride(0) * y + x * 4);
+		*p = color.abgr2101010();
+		break;
+	}
+	case PixelFormat::RGBX1010102:
+	case PixelFormat::RGBA1010102:
+	{
+		uint32_t *p = (uint32_t*)(buf.map(0) + buf.stride(0) * y + x * 4);
+		*p = color.rgba1010102();
+		break;
+	}
+	case PixelFormat::BGRX1010102:
+	case PixelFormat::BGRA1010102:
+	{
+		uint32_t *p = (uint32_t*)(buf.map(0) + buf.stride(0) * y + x * 4);
+		*p = color.bgra1010102();
+		break;
+	}
 	case PixelFormat::RGB888:
 	{
 		uint8_t *p = buf.map(0) + buf.stride(0) * y + x * 3;
@@ -54,6 +98,18 @@
 		*p = color.bgr565();
 		break;
 	}
+	case PixelFormat::ARGB4444:
+	{
+		uint16_t *p = (uint16_t*)(buf.map(0) + buf.stride(0) * y + x * 2);
+		*p = color.argb4444();
+		break;
+	}
+	case PixelFormat::ARGB1555:
+	{
+		uint16_t *p = (uint16_t*)(buf.map(0) + buf.stride(0) * y + x * 2);
+		*p = color.argb1555();
+		break;
+	}
 	default:
 		throw std::invalid_argument("invalid pixelformat");
 	}
@@ -166,6 +222,8 @@
 	case PixelFormat::BGR888:
 	case PixelFormat::RGB565:
 	case PixelFormat::BGR565:
+	case PixelFormat::ARGB4444:
+	case PixelFormat::ARGB1555:
 		for (j = 0; j < h; j++) {
 			for (i = 0; i < w; i++) {
 				draw_rgb_pixel(fb, x + i, y + j, color);
@@ -194,7 +252,23 @@
 		}
 		break;
 	default:
-		throw std::invalid_argument("unknown pixelformat");
+		throw std::invalid_argument("draw_rect: unknown pixelformat");
+	}
+}
+
+void draw_horiz_line(IFramebuffer& fb, uint32_t x1, uint32_t x2, uint32_t y, RGB color)
+{
+	for (uint32_t x = x1; x <= x2; ++x)
+		draw_rgb_pixel(fb, x, y, color);
+}
+
+void draw_circle(IFramebuffer& fb, int32_t xCenter, int32_t yCenter, int32_t radius, RGB color)
+{
+	int32_t r2 = radius * radius;
+
+	for (int y = -radius; y <= radius; y++) {
+		int32_t x = (int)(sqrt(r2 - y * y) + 0.5);
+		draw_horiz_line(fb, xCenter - x, xCenter + x, yCenter - y, color);
 	}
 }
 
@@ -222,6 +296,8 @@
 	case PixelFormat::BGR888:
 	case PixelFormat::RGB565:
 	case PixelFormat::BGR565:
+	case PixelFormat::ARGB4444:
+	case PixelFormat::ARGB1555:
 		for (y = 0; y < 8; y++) {
 			for (x = 0; x < 8; x++) {
 				bool b = get_char_pixel(c, x, y);
@@ -262,7 +338,7 @@
 		}
 		break;
 	default:
-		throw std::invalid_argument("unknown pixelformat");
+		throw std::invalid_argument("draw_char: unknown pixelformat");
 	}
 }
 
diff --git a/kms++util/src/testpat.cpp b/kms++util/src/testpat.cpp
index cf43d00..f9a3c8a 100644
--- a/kms++util/src/testpat.cpp
+++ b/kms++util/src/testpat.cpp
@@ -3,7 +3,10 @@
 
 #include <cstring>
 #include <cassert>
+
+#ifdef HAS_PTHREAD
 #include <thread>
+#endif
 
 #include <kms++/kms++.h>
 #include <kms++util/kms++util.h>
@@ -102,15 +105,10 @@
 	unsigned x, y;
 	unsigned w = fb.width();
 
-	switch (fb.format()) {
-	case PixelFormat::XRGB8888:
-	case PixelFormat::XBGR8888:
-	case PixelFormat::ARGB8888:
-	case PixelFormat::ABGR8888:
-	case PixelFormat::RGB888:
-	case PixelFormat::BGR888:
-	case PixelFormat::RGB565:
-	case PixelFormat::BGR565:
+	const PixelFormatInfo& format_info = get_pixel_format_info(fb.format());
+
+	switch (format_info.type) {
+	case PixelColorType::RGB:
 		for (y = start_y; y < end_y; y++) {
 			for (x = 0; x < w; x++) {
 				RGB pixel = get_test_pattern_pixel(fb, x, y);
@@ -119,40 +117,46 @@
 		}
 		break;
 
-	case PixelFormat::UYVY:
-	case PixelFormat::YUYV:
-	case PixelFormat::YVYU:
-	case PixelFormat::VYUY:
-		for (y = start_y; y < end_y; y++) {
-			for (x = 0; x < w; x += 2) {
-				RGB pixel1 = get_test_pattern_pixel(fb, x, y);
-				RGB pixel2 = get_test_pattern_pixel(fb, x + 1, y);
-				draw_yuv422_macropixel(fb, x, y, pixel1.yuv(yuvt), pixel2.yuv(yuvt));
+	case PixelColorType::YUV:
+		switch (format_info.num_planes) {
+		case 1:
+			for (y = start_y; y < end_y; y++) {
+				for (x = 0; x < w; x += 2) {
+					RGB pixel1 = get_test_pattern_pixel(fb, x, y);
+					RGB pixel2 = get_test_pattern_pixel(fb, x + 1, y);
+					draw_yuv422_macropixel(fb, x, y, pixel1.yuv(yuvt), pixel2.yuv(yuvt));
+				}
 			}
+			break;
+
+		case 2:
+			for (y = start_y; y < end_y; y += 2) {
+				for (x = 0; x < w; x += 2) {
+					RGB pixel00 = get_test_pattern_pixel(fb, x, y);
+					RGB pixel10 = get_test_pattern_pixel(fb, x + 1, y);
+					RGB pixel01 = get_test_pattern_pixel(fb, x, y + 1);
+					RGB pixel11 = get_test_pattern_pixel(fb, x + 1, y + 1);
+					draw_yuv420_macropixel(fb, x, y,
+							       pixel00.yuv(yuvt), pixel10.yuv(yuvt),
+							       pixel01.yuv(yuvt), pixel11.yuv(yuvt));
+				}
+			}
+			break;
+
+		default:
+			throw invalid_argument("unsupported number of pixel format planes");
 		}
+
 		break;
 
-	case PixelFormat::NV12:
-	case PixelFormat::NV21:
-		for (y = start_y; y < end_y; y += 2) {
-			for (x = 0; x < w; x += 2) {
-				RGB pixel00 = get_test_pattern_pixel(fb, x, y);
-				RGB pixel10 = get_test_pattern_pixel(fb, x + 1, y);
-				RGB pixel01 = get_test_pattern_pixel(fb, x, y + 1);
-				RGB pixel11 = get_test_pattern_pixel(fb, x + 1, y + 1);
-				draw_yuv420_macropixel(fb, x, y,
-						       pixel00.yuv(yuvt), pixel10.yuv(yuvt),
-						       pixel01.yuv(yuvt), pixel11.yuv(yuvt));
-			}
-		}
-		break;
 	default:
-		throw std::invalid_argument("unknown pixelformat");
+		throw invalid_argument("unsupported pixel format");
 	}
 }
 
 static void draw_test_pattern_impl(IFramebuffer& fb, YUVType yuvt)
 {
+#ifdef HAS_PTHREAD
 	if (fb.height() < 20) {
 		draw_test_pattern_part(fb, 0, fb.height(), yuvt);
 		return;
@@ -179,6 +183,9 @@
 
 	for (thread& t : workers)
 		t.join();
+#else
+	draw_test_pattern_part(fb, 0, fb.height(), yuvt);
+#endif
 }
 
 void draw_test_pattern(IFramebuffer &fb, YUVType yuvt)
diff --git a/kms++util/src/videodevice.cpp b/kms++util/src/videodevice.cpp
index efe1678..cc11357 100644
--- a/kms++util/src/videodevice.cpp
+++ b/kms++util/src/videodevice.cpp
@@ -96,6 +96,63 @@
 	}
 }
 
+static void v4l2_get_selection(int fd, uint32_t& left, uint32_t& top, uint32_t& width, uint32_t& height, uint32_t buf_type)
+{
+	int r;
+	struct v4l2_selection selection;
+
+	if (buf_type == V4L2_BUF_TYPE_VIDEO_OUTPUT ||
+	    buf_type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+		selection.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+		selection.target = V4L2_SEL_TGT_CROP;
+	} else if (buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+		   buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+		selection.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+		selection.target = V4L2_SEL_TGT_COMPOSE;
+	} else {
+		FAIL("buf_type (%d) is not valid\n", buf_type);
+	}
+
+	r = ioctl(fd, VIDIOC_G_SELECTION, &selection);
+	ASSERT(r == 0);
+
+	left = selection.r.left;
+	top = selection.r.top;
+	width = selection.r.width;
+	height = selection.r.height;
+}
+
+static void v4l2_set_selection(int fd, uint32_t& left, uint32_t& top, uint32_t& width, uint32_t& height, uint32_t buf_type)
+{
+	int r;
+	struct v4l2_selection selection;
+
+	if (buf_type == V4L2_BUF_TYPE_VIDEO_OUTPUT ||
+	    buf_type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+		selection.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+		selection.target = V4L2_SEL_TGT_CROP;
+	} else if (buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+		   buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+		selection.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+		selection.target = V4L2_SEL_TGT_COMPOSE;
+	} else {
+		FAIL("buf_type (%d) is not valid\n", buf_type);
+	}
+
+	selection.r.left = left;
+	selection.r.top = top;
+	selection.r.width = width;
+	selection.r.height = height;
+
+	r = ioctl(fd, VIDIOC_S_SELECTION, &selection);
+	ASSERT(r == 0);
+
+	left = selection.r.left;
+	top = selection.r.top;
+	width = selection.r.width;
+	height = selection.r.height;
+}
+
 static void v4l2_request_bufs(int fd, uint32_t queue_size, uint32_t buf_type)
 {
 	v4l2_requestbuffers v4lreqbuf { };
@@ -414,6 +471,16 @@
 	v4l2_set_format(m_fd, fmt, width, height, get_buf_type(m_type));
 }
 
+void VideoStreamer::get_selection(uint32_t& left, uint32_t& top, uint32_t& width, uint32_t& height)
+{
+	v4l2_get_selection(m_fd, left, top, width, height, get_buf_type(m_type));
+}
+
+void VideoStreamer::set_selection(uint32_t& left, uint32_t& top, uint32_t& width, uint32_t& height)
+{
+	v4l2_set_selection(m_fd, left, top, width, height, get_buf_type(m_type));
+}
+
 void VideoStreamer::set_queue_size(uint32_t queue_size)
 {
 	v4l2_request_bufs(m_fd, queue_size, get_buf_type(m_type));
diff --git a/py/pykms/__init__.py b/py/pykms/__init__.py
index 746c917..258b4e0 100644
--- a/py/pykms/__init__.py
+++ b/py/pykms/__init__.py
@@ -80,6 +80,64 @@
     VBLANK = 0x01
     FLIP_COMPLETE = 0x02
 
+#
+# AtomicReq API extensions
+#
+
+def __atomic_req_add_connector(req, conn, crtc):
+    req.add(conn, "CRTC_ID", crtc.id if crtc else 0)
+
+def __atomic_req_add_crtc(req, crtc, mode_blob):
+    if mode_blob:
+        req.add(crtc, {"ACTIVE": 1, "MODE_ID": mode_blob.id})
+    else:
+        req.add(crtc, {"ACTIVE": 0, "MODE_ID": 0})
+
+def __atomic_req_add_plane(req, plane, fb, crtc,
+                           src=None, dst=None, zpos=None,
+                           params={}):
+    if not src and fb:
+        src = (0, 0, fb.width, fb.height)
+
+    if not dst:
+        dst = src
+
+    m = {"FB_ID": fb.id if fb else 0,
+         "CRTC_ID": crtc.id if crtc else 0}
+
+    if src is not None:
+        src_x = int(round(src[0] * 65536))
+        src_y = int(round(src[1] * 65536))
+        src_w = int(round(src[2] * 65536))
+        src_h = int(round(src[3] * 65536))
+
+        m["SRC_X"] = src_x
+        m["SRC_Y"] = src_y
+        m["SRC_W"] = src_w
+        m["SRC_H"] = src_h
+
+    if dst is not None:
+        crtc_x = int(round(dst[0]))
+        crtc_y = int(round(dst[1]))
+        crtc_w = int(round(dst[2]))
+        crtc_h = int(round(dst[3]))
+
+        m["CRTC_X"] = crtc_x
+        m["CRTC_Y"] = crtc_y
+        m["CRTC_W"] = crtc_w
+        m["CRTC_H"] = crtc_h
+
+    if zpos is not None:
+        m["zpos"] = zpos
+
+    m.update(params)
+
+    req.add(plane, m)
+
+pykms.AtomicReq.add_connector = __atomic_req_add_connector
+pykms.AtomicReq.add_crtc = __atomic_req_add_crtc
+pykms.AtomicReq.add_plane = __atomic_req_add_plane
+
 # struct drm_event {
 #   __u32 type;
 #   __u32 length;
diff --git a/py/pykms/pykmsbase.cpp b/py/pykms/pykmsbase.cpp
index 2c97bd7..668e6e3 100644
--- a/py/pykms/pykmsbase.cpp
+++ b/py/pykms/pykmsbase.cpp
@@ -21,6 +21,8 @@
 {
 	py::class_<Card>(m, "Card")
 			.def(py::init<>())
+			.def(py::init<const string&>())
+			.def(py::init<const string&, uint32_t>())
 			.def_property_readonly("fd", &Card::fd)
 			.def_property_readonly("get_first_connected_connector", &Card::get_first_connected_connector)
 
@@ -45,6 +47,8 @@
 
 			.def_property_readonly("has_atomic", &Card::has_atomic)
 			.def("get_prop", (Property* (Card::*)(uint32_t) const)&Card::get_prop)
+
+			.def_property_readonly("version_name", &Card::version_name);
 			;
 
 	py::class_<DrmObject, unique_ptr<DrmObject, py::nodelete>>(m, "DrmObject")
diff --git a/py/pykms/pykmsutil.cpp b/py/pykms/pykmsutil.cpp
index 518d5ea..d5d7fde 100644
--- a/py/pykms/pykmsutil.cpp
+++ b/py/pykms/pykmsutil.cpp
@@ -57,6 +57,9 @@
 	m.def("draw_rect", [](Framebuffer& fb, uint32_t x, uint32_t y, uint32_t w, uint32_t h, RGB color) {
 		draw_rect(fb, x, y, w, h, color);
 	} );
+	m.def("draw_circle", [](Framebuffer& fb, int32_t xCenter, int32_t yCenter, int32_t radius, RGB color) {
+		draw_circle(fb, xCenter, yCenter, radius, color);
+	} );
 	m.def("draw_text", [](Framebuffer& fb, uint32_t x, uint32_t y, const string& str, RGB color) {
 		draw_text(fb, x, y, str, color); } );
 }
diff --git a/py/pykms/pyvid.cpp b/py/pykms/pyvid.cpp
index 92006c4..8b0450a 100644
--- a/py/pykms/pyvid.cpp
+++ b/py/pykms/pyvid.cpp
@@ -30,6 +30,15 @@
 			.def("set_port", &VideoStreamer::set_port)
 			.def_property_readonly("formats", &VideoStreamer::get_formats)
 			.def("set_format", &VideoStreamer::set_format)
+			.def("get_selection", [](VideoStreamer *self) {
+				uint32_t left, top, width, height;
+				self->get_selection(left, top, width, height);
+				return make_tuple(left, top, width, height);
+			} )
+			.def("set_selection", [](VideoStreamer *self, uint32_t left, uint32_t top, uint32_t width, uint32_t height) {
+				self->set_selection(left, top, width, height);
+				return make_tuple(left, top, width, height);
+			} )
 			.def("set_queue_size", &VideoStreamer::set_queue_size)
 			.def("queue", &VideoStreamer::queue)
 			.def("dequeue", &VideoStreamer::dequeue)
diff --git a/py/tests/alpha-test.py b/py/tests/alpha-test.py
index 9ae1539..e329ce4 100755
--- a/py/tests/alpha-test.py
+++ b/py/tests/alpha-test.py
@@ -54,7 +54,7 @@
         "SRC_H": fb.height << 16,
         "CRTC_W": fb.width,
         "CRTC_H": fb.height,
-        "zorder": i,
+        "zpos": i,
     })
 
     time.sleep(1)
diff --git a/py/tests/big_fb.py b/py/tests/big_fb.py
index 54de685..dc08bd2 100755
--- a/py/tests/big_fb.py
+++ b/py/tests/big_fb.py
@@ -89,7 +89,7 @@
                     'CRTC_Y': 0,
                     'CRTC_W': mode.hdisplay,
                     'CRTC_H': mode.vdisplay,
-                    'zorder': 0})
+                    'zpos': 0})
 
     req.commit_sync(allow_modeset = True)
 
@@ -187,7 +187,7 @@
                             'CRTC_Y': 0,
                             'CRTC_W': mode.hdisplay,
                             'CRTC_H': mode.vdisplay,
-                            'zorder': 0})
+                            'zpos': 0})
 
             screen_offset += mode.hdisplay
 
@@ -219,7 +219,7 @@
                             'CRTC_Y': 0,
                             'CRTC_W': mode.hdisplay,
                             'CRTC_H': mode.vdisplay,
-                            'zorder': 0})
+                            'zpos': 0})
 
             screen_offset += mode.hdisplay
 
diff --git a/py/tests/global_alpha_test.py b/py/tests/global_alpha_test.py
new file mode 100755
index 0000000..6981b72
--- /dev/null
+++ b/py/tests/global_alpha_test.py
@@ -0,0 +1,46 @@
+#!/usr/bin/python3
+
+import pykms
+import time
+
+card = pykms.Card()
+res = pykms.ResourceManager(card)
+conn = res.reserve_connector("")
+crtc = res.reserve_crtc(conn)
+mode = conn.get_default_mode()
+modeb = mode.to_blob(card)
+format = pykms.PixelFormat.ARGB8888
+plane1 = res.reserve_generic_plane(crtc, format)
+plane2 = res.reserve_generic_plane(crtc, format)
+
+print("Got plane1 %d %d plane2 %d %d" %
+      (plane1.idx, plane1.id, plane2.idx, plane2.id))
+
+fb1 = pykms.DumbFramebuffer(card, mode.hdisplay, mode.vdisplay, format);
+pykms.draw_test_pattern(fb1);
+
+fb2 = pykms.DumbFramebuffer(card, mode.hdisplay >> 1, mode.vdisplay >> 1, format);
+pykms.draw_test_pattern(fb2);
+
+alpha = 0
+
+req = pykms.AtomicReq(card)
+req.add(conn, "CRTC_ID", crtc.id)
+req.add(crtc, {"ACTIVE": 1,
+	       "MODE_ID": modeb.id})
+req.add_plane(plane1, fb1, crtc)
+req.add_plane(plane2, fb2, crtc)
+
+r = req.commit_sync(allow_modeset = True)
+assert r == 0, "Initial commit failed: %d" % r
+
+while alpha <= 0xFFFF:
+	print("alpha %d" % (alpha >>  8))
+	req = pykms.AtomicReq(card)
+	req.add(plane2, {"alpha": alpha })
+	r = req.commit_sync()
+	assert r == 0, "alpha change commit failed: %d" % r
+	alpha = alpha + 0xFF
+	time.sleep(0.1)
+
+input("press enter exit\n")
diff --git a/py/tests/plane_csc.py b/py/tests/plane_csc.py
index be92c00..5e6c6b5 100755
--- a/py/tests/plane_csc.py
+++ b/py/tests/plane_csc.py
@@ -4,12 +4,11 @@
 
 card = pykms.Card()
 res = pykms.ResourceManager(card)
-conn = res.reserve_connector("HDMI")
+conn = res.reserve_connector("")
 crtc = res.reserve_crtc(conn)
 mode = conn.get_default_mode()
 modeb = mode.to_blob(card)
 plane = res.reserve_generic_plane(crtc, pykms.PixelFormat.UYVY)
-#plane = res.reserve_generic_plane(crtc, pykms.PixelFormat.Undefined)
 
 print("Got plane %d %d" % (plane.idx, plane.id))
 
@@ -27,17 +26,7 @@
 input("Press enter to enable plane idx %d at %s" % (plane.idx, conn.fullname))
 
 req = pykms.AtomicReq(card)
-req.add(plane, {"FB_ID": fb.id,
-                "CRTC_ID": crtc.id,
-                "SRC_X": 0 << 16,
-                "SRC_Y": 0 << 16,
-                "SRC_W": fb.width << 16,
-                "SRC_H": fb.height << 16,
-                "CRTC_X": 0,
-                "CRTC_Y": 0,
-                "CRTC_W": fb.width,
-                "CRTC_H": fb.height,
-                "zorder": 0})
+req.add_plane(plane, fb, crtc)
 r = req.commit_sync()
 print("Plane enable request returned %d\n" % r)
 
diff --git a/py/tests/plane_hog.py b/py/tests/plane_hog.py
index 5bdc937..906c758 100755
--- a/py/tests/plane_hog.py
+++ b/py/tests/plane_hog.py
@@ -77,7 +77,7 @@
                      "CRTC_Y": y,
                      "CRTC_W": fb.width,
                      "CRTC_H": fb.height,
-                     "zorder": z})
+                     "zpos": z})
     r = req.commit_sync()
     print("Plane enable request returned %d\n" % r)
 
@@ -125,7 +125,7 @@
                     "CRTC_Y": y,
                     "CRTC_W": fb.width,
                     "CRTC_H": fb.height,
-                    "zorder": z})
+                    "zpos": z})
     r = req.commit_sync(allow_modeset = True)
     print("Plane enable request returned %d\n" % r)
 
diff --git a/py/tests/plane_move.py b/py/tests/plane_move.py
new file mode 100755
index 0000000..2f9dee5
--- /dev/null
+++ b/py/tests/plane_move.py
@@ -0,0 +1,128 @@
+#!/usr/bin/python3
+
+import pykms
+import random
+import time
+import sys
+import select
+import selectors
+
+if len(sys.argv) != 3:
+    print('Usage: plane_move.py <connector0> <connector1>')
+    sys.exit()
+
+card = pykms.Card()
+
+if not card.has_atomic:
+    print('Atomic modesetting is not supported')
+    sys.exit(-1)
+
+res = pykms.ResourceManager(card)
+
+conn_list = []
+crtc_list = []
+mode_list = []
+rootplane_list = []
+fb_list = []
+colors = []
+
+src_w = 300
+src_h = 300
+
+for i in range(2):
+    conn = res.reserve_connector(sys.argv[i + 1])
+    if conn is None:
+        print('Invalid connector: {}'.format(sys.argv[i + 1]))
+        sys.exit(-1)
+
+    if conn.connected() == True:
+        conn_list.append(conn)
+    else:
+        print('connector: {} is not connected'.format(sys.argv[i + 1]))
+        sys.exit(-1)
+
+    crtc = res.reserve_crtc(conn)
+    crtc_list.append(crtc)
+
+    mode = conn.get_default_mode()
+    mode_list.append(mode)
+
+    fb_tmp = pykms.DumbFramebuffer(card, src_w, src_h, 'XR24');
+    fb_list.append(fb_tmp)
+
+    rootplane = res.reserve_primary_plane(crtc, pykms.PixelFormat.XRGB8888)
+    rootplane_list.append(rootplane)
+
+card.disable_planes()
+
+print('Using the following connectors:')
+for i in range(2):
+    print(' {}: {} ({}x{})'.format(conn_list[i].idx, conn_list[i].fullname,
+        mode_list[i].hdisplay, mode_list[i].vdisplay))
+
+colors.append(pykms.red)
+colors.append(pykms.green)
+
+for i in range(2):
+    pykms.draw_rect(fb_list[i], 0, 0, src_w, src_h, colors[i])
+
+for i in range(2):
+    req = pykms.AtomicReq(card)
+    modeb = mode_list[i].to_blob(card)
+    req.add(conn_list[i], 'CRTC_ID', crtc_list[i].id)
+    req.add(crtc_list[i], {'ACTIVE': 1,
+                    'MODE_ID': modeb.id})
+    req.add(rootplane_list[i], {'FB_ID': fb_list[i].id,
+                'CRTC_ID': crtc_list[i].id,
+                'SRC_W': src_w << 16,
+                'SRC_H': src_h << 16,
+                'CRTC_W': src_w,
+                'CRTC_H': src_h})
+
+    req.commit_sync(allow_modeset = True)
+
+print('\nRed box on {}, Green box on {}.'.format(conn_list[0].fullname,
+                                                 conn_list[1].fullname))
+input('ENTER to continue\n')
+
+# FIXME: it should be possible to move plane without disabling it, but the
+# omapdrm driver does not supports it at the moment.
+req = pykms.AtomicReq(card)
+req.add(rootplane_list[0], {"FB_ID": 0,
+                "CRTC_ID": 0})
+req.commit_sync(allow_modeset = True)
+
+req = pykms.AtomicReq(card)
+req.add(rootplane_list[0], {'FB_ID': fb_list[0].id,
+            'CRTC_ID': crtc_list[1].id,
+            'SRC_W': src_w << 16,
+            'SRC_H': src_h << 16,
+            'CRTC_X': 150,
+            'CRTC_Y': 150,
+            'CRTC_W': src_w,
+            'CRTC_H': src_h})
+req.commit_sync(allow_modeset = True)
+
+print('The red box from {} is moved underneath the green box on {}.'.format(
+                                conn_list[0].fullname, conn_list[1].fullname))
+input('ENTER to continue\n')
+
+# FIXME: it should be possible to move plane without disabling it, but the
+# omapdrm driver does not supports it at the moment.
+req = pykms.AtomicReq(card)
+req.add(rootplane_list[1], {"FB_ID": 0,
+                "CRTC_ID": 0})
+req.commit_sync(allow_modeset = True)
+
+req = pykms.AtomicReq(card)
+req.add(rootplane_list[1], {'FB_ID': fb_list[1].id,
+            'CRTC_ID': crtc_list[0].id,
+            'SRC_W': src_w << 16,
+            'SRC_H': src_h << 16,
+            'CRTC_W': src_w,
+            'CRTC_H': src_h})
+req.commit_sync(allow_modeset = True)
+
+print('Green box on {}, Red box on {}.'.format(conn_list[0].fullname,
+                                               conn_list[1].fullname))
+input('ENTER to exit\n')
diff --git a/py/tests/rottest.py b/py/tests/rottest.py
index 8988134..c568e17 100755
--- a/py/tests/rottest.py
+++ b/py/tests/rottest.py
@@ -39,7 +39,7 @@
 #		"CRTC_Y": 0,
 #		"CRTC_W": mode.hdisplay,
 #		"CRTC_H": mode.vdisplay,
-#		"zorder": 0})
+#		"zpos": 0})
 
 req.commit_sync(allow_modeset = True)
 
@@ -86,7 +86,7 @@
 			"CRTC_W": crtc_w,
 			"CRTC_H": crtc_h,
 			"rotation": rot,
-			"zorder": 2})
+			"zpos": 2})
 
 	req.commit_sync(allow_modeset = True)
 
diff --git a/py/tests/scale.py b/py/tests/scale.py
index 0b97051..0cf71dd 100755
--- a/py/tests/scale.py
+++ b/py/tests/scale.py
@@ -3,50 +3,80 @@
 import pykms
 import time
 import random
+import argparse
+
+def plane_commit(card, crtc, plane, fb, x, y, w, h) :
+	req = pykms.AtomicReq(card)
+	req.add_plane(plane, fb, crtc, None, (x, y, w, h))
+	r = req.commit_sync()
+	assert r == 0, "Plane commit failed: %d" % r
+
+
+parser = argparse.ArgumentParser(description='Simple scaling stress test.')
+parser.add_argument('--plane', '-p', dest='plane', default="",
+		    required=False, help='plane number to use')
+parser.add_argument('--connector', '-c', dest='connector', default="",
+		    required=False, help='connector to output')
+
+args = parser.parse_args()
 
 card = pykms.Card()
 res = pykms.ResourceManager(card)
-conn = res.reserve_connector("hdmi")
+conn = res.reserve_connector(args.connector)
 crtc = res.reserve_crtc(conn)
-plane = res.reserve_overlay_plane(crtc)
+format = pykms.PixelFormat.NV12
+
+if args.plane == "":
+	plane = res.reserve_generic_plane(crtc, format)
+else:
+	plane = card.planes[int(args.plane)]
 
 mode = conn.get_default_mode()
-#mode = conn.get_mode(1920, 1080, 60, False)
-
-# Blank framefuffer for primary plane
-fb0 = pykms.DumbFramebuffer(card, mode.hdisplay, mode.vdisplay, "AR24");
-
-crtc.set_mode(conn, fb0, mode)
+modeb = mode.to_blob(card)
+req = pykms.AtomicReq(card)
+req.add(conn, "CRTC_ID", crtc.id)
+req.add(crtc, {"ACTIVE": 1,
+               "MODE_ID": modeb.id})
+r = req.commit_sync(allow_modeset = True)
+assert r == 0, "Initial commit failed: %d" % r
 
 # Initialize framebuffer for the scaled plane
 fbX = 1920
 fbY = 1080
-fb = pykms.DumbFramebuffer(card, fbX, fbY, "RG16");
+fb = pykms.DumbFramebuffer(card, fbX, fbY, format);
 pykms.draw_test_pattern(fb);
 
-# Plane's scaled size and size increments
-W = 72
-H = 54
-Winc = 1
-Hinc = 1
+# max downscale.
+# The values bellow are for DSS5. For DSS7 use 64 for both.
+max_downscale_x=5
+max_downscale_y=8
 
-# Plane's position and position increments
+# Plane's initial scaled size
+W = 640
+H = 480
+
+# Plane's initial position
 X = 0
 Y = 0
+
+# initialize increments
+Winc = 1
+Hinc = 1
 Xinc = 1
 Yinc = 1
+
 while True:
 	print("+%d+%d %dx%d" % (X, Y, W, H))
-	crtc.set_plane(plane, fb, X, Y, W, H, 0, 0, fbX, fbY)
+	plane_commit(card, crtc, plane, fb, X, Y, W, H)
 	W = W + Winc
 	H = H + Hinc
 	if (Winc == 1 and W >= mode.hdisplay - X):
 		Winc = -1
-	if (Winc == -1 and W <= fbX/32):
+	if (Winc == -1 and W <= fbX/max_downscale_x):
 		Winc = 1
 	if (Hinc == 1 and H >= mode.vdisplay - Y):
 		Hinc = -1
-	if (Hinc == -1 and H <= fbY/32):
+	if (Hinc == -1 and H <= fbY/max_downscale_y):
 		Hinc = 1
 	X = X + Xinc
 	Y = Y + Yinc
diff --git a/py/tests/sync.py b/py/tests/sync.py
index e394c8d..935d86e 100755
--- a/py/tests/sync.py
+++ b/py/tests/sync.py
@@ -178,7 +178,7 @@
 
     fb = flip_handler.fb1
     pykms.draw_color_bar(fb, fb.width - bar_width - bar_speed, bar_speed, bar_width)
-    mode_blob = mode.blob(card)
+    mode_blob = mode.to_blob(card)
 
     req = pykms.AtomicReq(card)
     req.add(conn, 'CRTC_ID', crtc.id)
@@ -195,7 +195,7 @@
                 'CRTC_W': fb.width,
                 'CRTC_H': fb.height,
     })
-    ret = req.commit(flip_handler, allow_modeset = True)
+    ret = req.commit(allow_modeset = True)
     if ret < 0:
         raise RuntimeError('Atomic mode set failed with %d' % ret)
 
diff --git a/py/tests/test.py b/py/tests/test.py
index b7bf6bd..83cf16a 100755
--- a/py/tests/test.py
+++ b/py/tests/test.py
@@ -2,36 +2,32 @@
 
 import sys
 import pykms
+import argparse
 
-# draw test pattern via dmabuf?
-dmabuf = False
+parser = argparse.ArgumentParser()
+parser.add_argument("-c", "--connector", default="")
+parser.add_argument("--dmabuf", action="store_true", help="use dmabuf")
+parser.add_argument("--omap", action="store_true", help="use omapcard")
+args = parser.parse_args()
 
-# Use omap?
-omap = False
-
-if omap:
+if args.omap:
 	card = pykms.OmapCard()
 else:
 	card = pykms.Card()
 
-if len(sys.argv) > 1:
-    conn_name = sys.argv[1]
-else:
-    conn_name = ""
-
 res = pykms.ResourceManager(card)
-conn = res.reserve_connector(conn_name)
+conn = res.reserve_connector(args.connector)
 crtc = res.reserve_crtc(conn)
 plane = res.reserve_generic_plane(crtc)
 mode = conn.get_default_mode()
 modeb = mode.to_blob(card)
 
-if omap:
+if args.omap:
 	origfb = pykms.OmapFramebuffer(card, mode.hdisplay, mode.vdisplay, "XR24");
 else:
 	origfb = pykms.DumbFramebuffer(card, mode.hdisplay, mode.vdisplay, "XR24");
 
-if dmabuf:
+if args.dmabuf:
 	fb = pykms.ExtFramebuffer(card, origfb.width, origfb.height, origfb.format,
 		[origfb.fd(0)], [origfb.stride(0)], [origfb.offset(0)])
 else:
@@ -43,22 +39,9 @@
 
 req = pykms.AtomicReq(card)
 
-req.add(conn, "CRTC_ID", crtc.id)
-
-req.add(crtc, {"ACTIVE": 1,
-		"MODE_ID": modeb.id})
-
-req.add(plane, {"FB_ID": fb.id,
-		"CRTC_ID": crtc.id,
-		"SRC_X": 0 << 16,
-		"SRC_Y": 0 << 16,
-		"SRC_W": mode.hdisplay << 16,
-		"SRC_H": mode.vdisplay << 16,
-		"CRTC_X": 0,
-		"CRTC_Y": 0,
-		"CRTC_W": mode.hdisplay,
-		"CRTC_H": mode.vdisplay,
-		"zorder": 0})
+req.add_connector(conn, crtc)
+req.add_crtc(crtc, modeb)
+req.add_plane(plane, fb, crtc, dst=(0, 0, mode.hdisplay, mode.vdisplay))
 
 req.commit_sync(allow_modeset = True)
 
diff --git a/py/tests/trans-test.py b/py/tests/trans-test.py
index 8b8e6c5..3e7b9e3 100755
--- a/py/tests/trans-test.py
+++ b/py/tests/trans-test.py
@@ -2,14 +2,36 @@
 
 import pykms
 import time
+import sys
+import argparse
 
-# This hack makes drm initialize the fbcon, setting up the default connector
-card = pykms.Card()
-card = 0
+tests = {
+    1: "test_am5_trans_dest",
+    2: "test_am5_trans_src",
+    3: "test_am4_normal_trans_dst",
+    4: "test_am4_normal_trans_src",
+    5: "test_am4_alpha_trans_src",
+}
+
+parser = argparse.ArgumentParser()
+parser.add_argument("-c", "--connector", default="")
+parser.add_argument("test", type=int, help="test number 1-" + str(len(tests)))
+args = parser.parse_args()
+
+#if len(sys.argv) != 2:
+#    print("Usage: {} <test-number>".format(sys.argv[0]))
+#    print("  1 - test_am5_trans_dest()")
+#    print("  2 - test_am5_trans_src()")
+#    print("  3 - test_am4_normal_trans_dst()")
+#    print("  4 - test_am4_normal_trans_src()")
+#    print("  5 - test_am4_alpha_trans_src()")
+#    exit(0)
+
+TEST = args.test
 
 card = pykms.Card()
 res = pykms.ResourceManager(card)
-conn = res.reserve_connector()
+conn = res.reserve_connector(args.connector)
 crtc = res.reserve_crtc(conn)
 mode = conn.get_default_mode()
 
@@ -26,19 +48,33 @@
 
 fbs=[]
 
+# See Figure 11-78. DISPC Destination Transparency Color Key Example
 def test_am5_trans_dest():
     fbs.append(pykms.DumbFramebuffer(card, w, h, "XR24"))
     fbs.append(pykms.DumbFramebuffer(card, w, h, "XR24"))
 
     fb = fbs[0]
+    stepX = fb.width // 7
+    stepY = fb.height // 5;
+
     pykms.draw_rect(fb, 0, 0, fb.width, fb.height, pykms.purple)
-    pykms.draw_rect(fb, 100, 100, 100, 200, pykms.green)
-    pykms.draw_rect(fb, 300, 100, 100, 200, pykms.red)
-    pykms.draw_rect(fb, 500, 100, 100, 200, pykms.white)
+    pykms.draw_rect(fb, stepX, stepY,
+                    stepX, fb.height - (stepY * 2),
+                    pykms.green)
+    pykms.draw_rect(fb, stepX * 3, stepY,
+                    stepX, fb.height - (stepY * 2),
+                    pykms.red)
+    pykms.draw_rect(fb, stepX * 5, stepY,
+                    stepX, fb.height - (stepY * 2),
+                    pykms.white)
 
     fb = fbs[1]
-    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, pykms.cyan)
-    pykms.draw_rect(fb, 250, 100, 200, 200, pykms.yellow)
+    pykms.draw_rect(fb, 0, 0,
+                    fb.width, fb.height,
+                    pykms.cyan)
+    pykms.draw_circle(fb, (stepX * 3) + (stepX // 2), fb.height // 2,
+                      (fb.height // 2) - stepY,
+                      pykms.yellow)
 
     crtc.set_props({
         "trans-key-mode": 1,
@@ -47,37 +83,65 @@
         "alpha_blender": 0,
     })
 
-    plane = 0
+    print("Purple bg. Green, red, white boxes.")
 
-    for i in range(0,2):
-        print("set crtc {}, plane {}, fb {}".format(crtc.id, planes[i].id, fbs[i].id))
+    plane = planes[0]
+    fb = fbs[0]
+    z = 0
 
-        plane = planes[i]
-        fb = fbs[i]
-        plane.set_props({
-            "FB_ID": fb.id,
-            "CRTC_ID": crtc.id,
-            "SRC_W": fb.width << 16,
-            "SRC_H": fb.height << 16,
-            "CRTC_W": fb.width,
-            "CRTC_H": fb.height,
-            "zorder": i,
-        })
+    plane.set_props({
+        "FB_ID": fb.id,
+        "CRTC_ID": crtc.id,
+        "SRC_W": fb.width << 16,
+        "SRC_H": fb.height << 16,
+        "CRTC_W": fb.width,
+        "CRTC_H": fb.height,
+        "zpos": z,
+    })
 
-        time.sleep(1)
+    input("press enter\n")
 
+    print("Cyan bg. Green, red, white boxes. Yellow circle behind the red box.")
+
+    plane = planes[1]
+    fb = fbs[1]
+    z = 1
+
+    plane.set_props({
+        "FB_ID": fb.id,
+        "CRTC_ID": crtc.id,
+        "SRC_W": fb.width << 16,
+        "SRC_H": fb.height << 16,
+        "CRTC_W": fb.width,
+        "CRTC_H": fb.height,
+        "zpos": z,
+    })
+
+    input("press enter\n")
+
+# See Figure 11-77. DISPC Source Transparency Color Key Example
 def test_am5_trans_src():
     fbs.append(pykms.DumbFramebuffer(card, w, h, "XR24"))
     fbs.append(pykms.DumbFramebuffer(card, w, h, "XR24"))
 
     fb = fbs[0]
+    halfX = fb.width // 2
+    stepX = (fb.width // 2) // 5;
+    stepY = fb.height // 5;
+
     pykms.draw_rect(fb, 0, 0, fb.width, fb.height, pykms.white)
-    pykms.draw_rect(fb, 200, 200, 100, 100, pykms.red)
-    pykms.draw_rect(fb, fb.width - 300, 200, 100, 100, pykms.green)
+    pykms.draw_rect(fb, stepX * 2, stepY * 2,
+                    halfX - (stepX * 4), fb.height - (stepY * 4),
+                    pykms.red)
+    pykms.draw_rect(fb, halfX + stepX * 2, stepY * 2,
+                    halfX - (stepX * 4), fb.height - (stepY * 4),
+                    pykms.green)
 
     fb = fbs[1]
     pykms.draw_rect(fb, 0, 0, fb.width, fb.height, pykms.cyan)
-    pykms.draw_rect(fb, 100, 100, fb.width - 200, fb.height - 200, pykms.purple)
+    pykms.draw_rect(fb, stepX, stepY,
+                    fb.width - (stepX * 2), fb.height - (stepY * 2),
+                    pykms.purple)
 
     crtc.set_props({
         "trans-key-mode": 2,
@@ -86,41 +150,68 @@
         "alpha_blender": 0,
     })
 
-    plane = 0
+    print("White bg. Red and green boxes.")
 
-    for i in range(0,2):
-        print("set crtc {}, plane {}, fb {}".format(crtc.id, planes[i].id, fbs[i].id))
+    plane = planes[0]
+    fb = fbs[0]
+    z = 0
 
-        plane = planes[i]
-        fb = fbs[i]
-        plane.set_props({
-            "FB_ID": fb.id,
-            "CRTC_ID": crtc.id,
-            "SRC_W": fb.width << 16,
-            "SRC_H": fb.height << 16,
-            "CRTC_W": fb.width,
-            "CRTC_H": fb.height,
-            "zorder": 3 if i == 1 else 0,
-        })
+    plane.set_props({
+        "FB_ID": fb.id,
+        "CRTC_ID": crtc.id,
+        "SRC_W": fb.width << 16,
+        "SRC_H": fb.height << 16,
+        "CRTC_W": fb.width,
+        "CRTC_H": fb.height,
+        "zpos": z,
+    })
 
-        time.sleep(1)
+    input("press enter\n")
+
+    print("Cyan bg. Big white box, containing red and green boxes.")
+
+    plane = planes[1]
+    fb = fbs[1]
+    z = 3
+
+    plane.set_props({
+        "FB_ID": fb.id,
+        "CRTC_ID": crtc.id,
+        "SRC_W": fb.width << 16,
+        "SRC_H": fb.height << 16,
+        "CRTC_W": fb.width,
+        "CRTC_H": fb.height,
+        "zpos": z,
+    })
+
+    input("press enter\n")
 
 def test_am4_normal_trans_dst():
     fbs.append(pykms.DumbFramebuffer(card, w, h, "XR24"))
-    fbs.append(pykms.DumbFramebuffer(card, w * 2 // 3, h, "XR24"))
-    fbs.append(pykms.DumbFramebuffer(card, w * 2 // 3, h, "XR24"))
+    fbs.append(pykms.DumbFramebuffer(card, w, h, "XR24"))
 
     fb = fbs[0]
-    pykms.draw_rect(fb, 0, 0, w, h, pykms.purple)
-    pykms.draw_rect(fb, 100, 50, 50, 200, pykms.green)
-    pykms.draw_rect(fb, 200, 50, 50, 200, pykms.red)
-    pykms.draw_rect(fb, 300, 50, 50, 200, pykms.white)
+    stepX = fb.width // 7
+    stepY = fb.height // 5;
+
+    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, pykms.purple)
+    pykms.draw_rect(fb, stepX, stepY,
+                    stepX, fb.height - (stepY * 2),
+                    pykms.green)
+    pykms.draw_rect(fb, stepX * 3, stepY,
+                    stepX, fb.height - (stepY * 2),
+                    pykms.red)
+    pykms.draw_rect(fb, stepX * 5, stepY,
+                    stepX, fb.height - (stepY * 2),
+                    pykms.white)
 
     fb = fbs[1]
-    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, pykms.blue)
-
-    fb = fbs[2]
-    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, pykms.cyan)
+    pykms.draw_rect(fb, 0, 0,
+                    fb.width, fb.height,
+                    pykms.cyan)
+    pykms.draw_circle(fb, (stepX * 3) + (stepX // 2), fb.height // 2,
+                      (fb.height // 2) - stepY,
+                      pykms.yellow)
 
     crtc.set_props({
         "trans-key-mode": 1,
@@ -129,70 +220,64 @@
         "alpha_blender": 0,
     })
 
-    time.sleep(1)
+    print("Purple bg. Green, red, white boxes.")
 
     plane = planes[0]
     fb = fbs[0]
+    z = 0
+
     plane.set_props({
         "FB_ID": fb.id,
         "CRTC_ID": crtc.id,
         "SRC_W": fb.width << 16,
         "SRC_H": fb.height << 16,
-        "CRTC_W": w,
-        "CRTC_H": h,
+        "CRTC_W": fb.width,
+        "CRTC_H": fb.height,
+        "zpos": z,
     })
 
-    time.sleep(1)
+    input("press enter\n")
+
+    print("Cyan bg. Green, red, white boxes. Yellow circle behind the red box.")
 
     plane = planes[1]
     fb = fbs[1]
+    z = 1
+
     plane.set_props({
         "FB_ID": fb.id,
         "CRTC_ID": crtc.id,
-        "SRC_X": 0 << 16,
-        "SRC_Y": 0 << 16,
         "SRC_W": fb.width << 16,
         "SRC_H": fb.height << 16,
-        "CRTC_X": 0,
-        "CRTC_Y": 0,
         "CRTC_W": fb.width,
         "CRTC_H": fb.height,
+        "zpos": z,
     })
 
-    time.sleep(1)
-
-    plane = planes[2]
-    fb = fbs[2]
-    plane.set_props({
-        "FB_ID": fb.id,
-        "CRTC_ID": crtc.id,
-        "SRC_X": 0 << 16,
-        "SRC_Y": 0 << 16,
-        "SRC_W": fb.width << 16,
-        "SRC_H": fb.height << 16,
-        "CRTC_X": w // 3,
-        "CRTC_Y": 0,
-        "CRTC_W": fb.width,
-        "CRTC_H": fb.height,
-    })
+    input("press enter\n")
 
 def test_am4_normal_trans_src():
     fbs.append(pykms.DumbFramebuffer(card, w, h, "XR24"))
-    fbs.append(pykms.DumbFramebuffer(card, w // 2, h, "XR24"))
-    fbs.append(pykms.DumbFramebuffer(card, w // 2, h, "XR24"))
+    fbs.append(pykms.DumbFramebuffer(card, w, h, "XR24"))
 
     fb = fbs[0]
-    pykms.draw_rect(fb, 0, 0, w, h, pykms.RGB(128, 255, 255))
-    pykms.draw_rect(fb, 200, 100, 50, 200, pykms.red)
-    pykms.draw_rect(fb, w - 200 - 50, 100, 50, 200, pykms.green)
+    halfX = fb.width // 2
+    stepX = (fb.width // 2) // 5;
+    stepY = fb.height // 5;
+
+    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, pykms.white)
+    pykms.draw_rect(fb, stepX * 2, stepY * 2,
+                    halfX - (stepX * 4), fb.height - (stepY * 4),
+                    pykms.red)
+    pykms.draw_rect(fb, halfX + stepX * 2, stepY * 2,
+                    halfX - (stepX * 4), fb.height - (stepY * 4),
+                    pykms.green)
 
     fb = fbs[1]
-    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, pykms.blue)
-    pykms.draw_rect(fb, 100, 100, fb.width - 200, fb.height - 200, pykms.purple)
-
-    fb = fbs[2]
     pykms.draw_rect(fb, 0, 0, fb.width, fb.height, pykms.cyan)
-    pykms.draw_rect(fb, 100, 100, fb.width - 200, fb.height - 200, pykms.purple)
+    pykms.draw_rect(fb, stepX, stepY,
+                    fb.width - (stepX * 2), fb.height - (stepY * 2),
+                    pykms.purple)
 
     crtc.set_props({
         "trans-key-mode": 2,
@@ -201,52 +286,41 @@
         "alpha_blender": 0,
     })
 
-    time.sleep(1)
+    print("White bg. Red and green boxes.")
 
     plane = planes[0]
     fb = fbs[0]
+    z = 0
+
     plane.set_props({
         "FB_ID": fb.id,
         "CRTC_ID": crtc.id,
         "SRC_W": fb.width << 16,
         "SRC_H": fb.height << 16,
-        "CRTC_W": w,
-        "CRTC_H": h,
+        "CRTC_W": fb.width,
+        "CRTC_H": fb.height,
+        "zpos": z,
     })
 
-    time.sleep(1)
+    input("press enter\n")
+
+    print("Cyan bg. Big white box, containing red and green boxes.")
 
     plane = planes[1]
     fb = fbs[1]
+    z = 2
+
     plane.set_props({
         "FB_ID": fb.id,
         "CRTC_ID": crtc.id,
-        "SRC_X": 0 << 16,
-        "SRC_Y": 0 << 16,
         "SRC_W": fb.width << 16,
         "SRC_H": fb.height << 16,
-        "CRTC_X": 0,
-        "CRTC_Y": 0,
         "CRTC_W": fb.width,
         "CRTC_H": fb.height,
+        "zpos": z,
     })
 
-    time.sleep(1)
-
-    plane = planes[2]
-    fb = fbs[2]
-    plane.set_props({
-        "FB_ID": fb.id,
-        "CRTC_ID": crtc.id,
-        "SRC_X": 0 << 16,
-        "SRC_Y": 0 << 16,
-        "SRC_W": fb.width << 16,
-        "SRC_H": fb.height << 16,
-        "CRTC_X": w - fb.width,
-        "CRTC_Y": 0,
-        "CRTC_W": fb.width,
-        "CRTC_H": fb.height,
-    })
+    input("press enter\n")
 
 def test_am4_alpha_trans_src():
     fbs.append(pykms.DumbFramebuffer(card, w, h, "XR24"))
@@ -254,26 +328,39 @@
     fbs.append(pykms.DumbFramebuffer(card, w // 2, h, "XR24"))
 
     fb = fbs[0]
+    halfX = fb.width // 2
+    stepX = (fb.width // 2) // 5;
+    stepY = fb.height // 5;
+
     pykms.draw_rect(fb, 0, 0, w, h, pykms.purple)
-    pykms.draw_rect(fb, 200, 100, 50, 200, pykms.red)
-    pykms.draw_rect(fb, w - 200 - 50, 100, 50, 200, pykms.green)
+    pykms.draw_rect(fb, stepX * 2, stepY * 2,
+                    halfX - (stepX * 4), fb.height - (stepY * 4),
+                    pykms.red)
+    pykms.draw_rect(fb, halfX + stepX * 2, stepY * 2,
+                    halfX - (stepX * 4), fb.height - (stepY * 4),
+                    pykms.green)
 
     fb = fbs[1]
     pykms.draw_rect(fb, 0, 0, fb.width, fb.height, pykms.blue)
-    pykms.draw_rect(fb, 100, 100, fb.width - 200, fb.height - 200, pykms.purple)
+    pykms.draw_rect(fb, stepX, stepY,
+                    fb.width - (stepX * 2), fb.height - (stepY * 2),
+                    pykms.purple)
 
     fb = fbs[2]
     pykms.draw_rect(fb, 0, 0, fb.width, fb.height, pykms.cyan)
-    pykms.draw_rect(fb, 100, 100, fb.width - 200, fb.height - 200, pykms.purple)
+    pykms.draw_rect(fb, stepX, stepY,
+                    fb.width - (stepX * 2), fb.height - (stepY * 2),
+                    pykms.purple)
 
     crtc.set_props({
         "trans-key-mode": 1,
         "trans-key": pykms.purple.rgb888,
-        "background": 0,
+        "background": 0x666666,
         "alpha_blender": 1,
     })
 
-    time.sleep(1)
+    print("grey background")
+    input("press enter\n")
 
     plane = planes[0]
     fb = fbs[0]
@@ -286,7 +373,8 @@
         "CRTC_H": h,
     })
 
-    time.sleep(1)
+    print("grey background, red and green boxes")
+    input("press enter\n")
 
     plane = planes[1]
     fb = fbs[1]
@@ -303,7 +391,8 @@
         "CRTC_H": fb.height,
     })
 
-    time.sleep(1)
+    print("left side: blue bg, purple box, red box inside purple. right side: unchanged")
+    input("press enter\n")
 
     plane = planes[2]
     fb = fbs[2]
@@ -320,12 +409,8 @@
         "CRTC_H": fb.height,
     })
 
+    print("left side: unchanged. right side: cyan bg, purple box, green box inside purple.")
+    input("press enter\n")
 
-
-#test_am5_trans_dest()
-test_am5_trans_src()
-#test_am4_normal_trans_dst()
-#test_am4_normal_trans_src()
-#test_am4_alpha_trans_src()
-
-input("press enter to exit\n")
+print(tests[args.test])
+locals()[tests[args.test]]()
diff --git a/py/tests/wb-m2m-crop.py b/py/tests/wb-m2m-crop.py
new file mode 100755
index 0000000..6c4225d
--- /dev/null
+++ b/py/tests/wb-m2m-crop.py
@@ -0,0 +1,123 @@
+#!/usr/bin/python3
+
+import sys
+import selectors
+import pykms
+import argparse
+import time
+
+iw = 640
+ih = 480
+ifmt = pykms.PixelFormat.XRGB8888
+
+ow = 640
+oh = 480
+ofmt = pykms.PixelFormat.XRGB8888
+
+card = pykms.Card()
+res = pykms.ResourceManager(card)
+conn = res.reserve_connector()
+crtc = res.reserve_crtc(conn)
+plane1 = res.reserve_overlay_plane(crtc, ifmt)
+plane2 = res.reserve_overlay_plane(crtc, ofmt)
+
+print("{}, {}".format(plane1.id, plane2.id))
+
+mode = conn.get_default_mode()
+modeb = mode.to_blob(card)
+
+card.disable_planes()
+
+req = pykms.AtomicReq(card)
+req.add(conn, "CRTC_ID", crtc.id)
+req.add(crtc, {"ACTIVE": 1,
+		"MODE_ID": modeb.id})
+req.commit_sync(allow_modeset = True)
+
+NUM_BUFS = 4
+
+src_fbs = []
+dst_fbs = []
+
+for i in range(NUM_BUFS):
+	fb = pykms.DumbFramebuffer(card, iw, ih, ifmt)
+	pykms.draw_test_pattern(fb);
+	pykms.draw_text(fb, iw // 2, 2, str(i), pykms.white);
+	src_fbs.append(fb)
+
+	fb = pykms.DumbFramebuffer(card, ow, oh, ofmt)
+	dst_fbs.append(fb)
+
+# put the planes on the screen, so that WB doesn't take them
+req = pykms.AtomicReq(card)
+req.add_plane(plane1, src_fbs[0], crtc, dst=(0, 0, 400, 480))
+req.add_plane(plane2, dst_fbs[1], crtc, dst=(400, 0, 400, 480))
+r = req.commit_sync(allow_modeset = True)
+assert r == 0
+
+vid = pykms.VideoDevice("/dev/video10")
+
+src_streamer = vid.output_streamer
+dst_streamer = vid.capture_streamer
+
+src_streamer.set_format(ifmt, iw, ih)
+(left, top, width, height) = src_streamer.get_selection()
+print("get: crop -> {},{}-{}x{}".format(left, top, width, height))
+(left, top, width, height) = src_streamer.set_selection(160, 0, 320, 240)
+print("set: crop -> {},{}-{}x{}".format(left, top, width, height))
+
+dst_streamer.set_format(ofmt, ow, oh)
+
+src_streamer.set_queue_size(NUM_BUFS)
+dst_streamer.set_queue_size(NUM_BUFS)
+
+for fb in src_fbs:
+	src_streamer.queue(fb)
+
+for fb in dst_fbs:
+	dst_streamer.queue(fb)
+
+input("press enter\n")
+
+src_streamer.stream_on()
+dst_streamer.stream_on()
+
+loop_count = 0
+
+def readvid(conn, mask):
+	global loop_count
+	print("VID EVENT");
+
+	ifb = src_streamer.dequeue()
+	ofb = dst_streamer.dequeue()
+
+	req = pykms.AtomicReq(card)
+	req.add_plane(plane1, ifb, crtc, dst=(0, 0, 400, 480))
+	req.add_plane(plane2, ofb, crtc, dst=(400, 0, 400, 480))
+	req.commit_sync(allow_modeset = True)
+	time.sleep(1)
+	loop_count += 1
+	if loop_count >= 10:
+		exit(0)
+	print("loop #", loop_count) 
+	src_streamer.queue(ifb)
+	dst_streamer.queue(ofb)
+	
+
+def readkey(conn, mask):
+	print("KEY EVENT");
+	sys.stdin.readline()
+	exit(0)
+
+sel = selectors.PollSelector()
+sel.register(vid.fd, selectors.EVENT_READ, readvid)
+sel.register(sys.stdin, selectors.EVENT_READ, readkey)
+
+while True:
+	events = sel.select()
+	for key, mask in events:
+		callback = key.data
+		callback(key.fileobj, mask)
+
+print("done");
+exit(0)
diff --git a/utils/kmsblank.cpp b/utils/kmsblank.cpp
index 0b51810..286c7f7 100644
--- a/utils/kmsblank.cpp
+++ b/utils/kmsblank.cpp
@@ -27,7 +27,7 @@
 
 int main(int argc, char **argv)
 {
-	string dev_path = "/dev/dri/card0";
+	string dev_path;
 
 	vector<string> conn_strs;
 	uint32_t time = 0;
diff --git a/utils/kmsprint.cpp b/utils/kmsprint.cpp
index 4d355fc..116fead 100644
--- a/utils/kmsprint.cpp
+++ b/utils/kmsprint.cpp
@@ -68,10 +68,17 @@
 	str = sformat("Connector %u (%u) %s",
 		      c.idx(), c.id(), c.fullname().c_str());
 
-	if (c.connected())
+	switch (c.connector_status()) {
+	case ConnectorStatus::Connected:
 		str += " (connected)";
-	else
+		break;
+	case ConnectorStatus::Disconnected:
 		str += " (disconnected)";
+		break;
+	default:
+		str += " (unknown)";
+		break;
+	}
 
 	return str;
 }
@@ -134,7 +141,7 @@
 
 static string format_property(const Property* prop, uint64_t val)
 {
-	string ret = sformat("%s = ", prop->name().c_str());
+	string ret = sformat("%s (%u) = ", prop->name().c_str(), prop->id());
 
 	switch (prop->type()) {
 	case PropertyType::Bitmask:
@@ -477,10 +484,11 @@
 static const char* usage_str =
 		"Usage: kmsprint [OPTIONS]\n\n"
 		"Options:\n"
-		"  -l, --list        Print list instead of tree\n"
-		"  -m, --modes       Print modes\n"
-		"      --xmode       Print modes using X modeline\n"
-		"  -p, --props       Print properties\n"
+		"      --device=DEVICE     DEVICE is the path to DRM card to open\n"
+		"  -l, --list              Print list instead of tree\n"
+		"  -m, --modes             Print modes\n"
+		"      --xmode             Print modes using X modeline\n"
+		"  -p, --props             Print properties\n"
 		;
 
 static void usage()
@@ -490,7 +498,7 @@
 
 int main(int argc, char **argv)
 {
-	string dev_path = "/dev/dri/card0";
+	string dev_path;
 
 	OptionSet optionset = {
 		Option("|device=", [&dev_path](string s)
diff --git a/utils/kmstest.cpp b/utils/kmstest.cpp
index 62b103f..8144117 100644
--- a/utils/kmstest.cpp
+++ b/utils/kmstest.cpp
@@ -234,7 +234,12 @@
 		EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
 	}
 
-	if (!resman.reserve_crtc(output.crtc))
+	if (output.crtc)
+		output.crtc = resman.reserve_crtc(output.crtc);
+	else
+		output.crtc = resman.reserve_crtc(output.connector);
+
+	if (!output.crtc)
 		EXIT("Could not find available crtc");
 }
 
@@ -439,7 +444,7 @@
 	string arg;
 };
 
-static string s_device_path = "/dev/dri/card0";
+static string s_device_path;
 
 static vector<Arg> parse_cmdline(int argc, char **argv)
 {
@@ -632,9 +637,9 @@
 	}
 
 	if (outputs.empty()) {
-		// no outputs defined, show a pattern on all screens
+		// no outputs defined, show a pattern on all connected screens
 		for (Connector* conn : card.get_connectors()) {
-			if (!conn->connected())
+			if (conn->connector_status() != ConnectorStatus::Connected)
 				continue;
 
 			OutputInfo output = { };
@@ -686,10 +691,22 @@
 	return outputs;
 }
 
+static char sync_to_char(SyncPolarity pol)
+{
+	switch (pol) {
+	case SyncPolarity::Positive:
+		return '+';
+	case SyncPolarity::Negative:
+		return '-';
+	default:
+		return '?';
+	}
+}
+
 static std::string videomode_to_string(const Videomode& m)
 {
-	string h = sformat("%u/%u/%u/%u", m.hdisplay, m.hfp(), m.hsw(), m.hbp());
-	string v = sformat("%u/%u/%u/%u", m.vdisplay, m.vfp(), m.vsw(), m.vbp());
+	string h = sformat("%u/%u/%u/%u/%c", m.hdisplay, m.hfp(), m.hsw(), m.hbp(), sync_to_char(m.hsync()));
+	string v = sformat("%u/%u/%u/%u/%c", m.vdisplay, m.vfp(), m.vsw(), m.vbp(), sync_to_char(m.vsync()));
 
 	return sformat("%s %.3f %s %s %u (%.2f) %#x %#x",
 		       m.name.c_str(),
@@ -722,7 +739,7 @@
 
 		if (!o.legacy_fbs.empty()) {
 			auto fb = o.legacy_fbs[0];
-			printf(" (Fb %u %ux%u-%s)", fb->id(), fb->width(), fb->height(), PixelFormatToFourCC(fb->format()).c_str());
+			printf("    Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(), PixelFormatToFourCC(fb->format()).c_str());
 		}
 
 		for (unsigned j = 0; j < o.planes.size(); ++j) {
@@ -764,30 +781,41 @@
 	}
 
 	for (const OutputInfo& o : outputs) {
+		int r;
 		auto conn = o.connector;
 		auto crtc = o.crtc;
 
-		if (!o.conn_props.empty() || !o.crtc_props.empty())
-			printf("WARNING: properties not set without atomic modesetting");
+		for (const PropInfo& prop : o.conn_props) {
+			r = conn->set_prop_value(prop.prop, prop.val);
+			EXIT_IF(r, "failed to set connector property %s\n", prop.name.c_str());
+		}
+
+		for (const PropInfo& prop : o.crtc_props) {
+			r = crtc->set_prop_value(prop.prop, prop.val);
+			EXIT_IF(r, "failed to set crtc property %s\n", prop.name.c_str());
+		}
 
 		if (!o.legacy_fbs.empty()) {
 			auto fb = o.legacy_fbs[0];
-			int r = crtc->set_mode(conn, *fb, o.mode);
+			r = crtc->set_mode(conn, *fb, o.mode);
 			if (r)
 				printf("crtc->set_mode() failed for crtc %u: %s\n",
 				       crtc->id(), strerror(-r));
 		}
 
 		for (const PlaneInfo& p : o.planes) {
+			for (const PropInfo& prop : p.props) {
+				r = p.plane->set_prop_value(prop.prop, prop.val);
+				EXIT_IF(r, "failed to set plane property %s\n", prop.name.c_str());
+			}
+
 			auto fb = p.fbs[0];
-			int r = crtc->set_plane(p.plane, *fb,
+			r = crtc->set_plane(p.plane, *fb,
 						p.x, p.y, p.w, p.h,
 						0, 0, fb->width(), fb->height());
 			if (r)
 				printf("crtc->set_plane() failed for plane %u: %s\n",
 				       p.plane->id(), strerror(-r));
-			if (!p.props.empty())
-				printf("WARNING: properties not set without atomic modesetting");
 		}
 	}
 }
@@ -1085,6 +1113,9 @@
 
 	Card card(s_device_path);
 
+	if (!card.is_master())
+		EXIT("Could not get DRM master permission. Card already in use?");
+
 	if (!card.has_atomic() && s_flip_sync)
 		EXIT("Synchronized flipping requires atomic modesetting");
 
diff --git a/utils/kmsview.cpp b/utils/kmsview.cpp
index 04d005d..eea9dde 100644
--- a/utils/kmsview.cpp
+++ b/utils/kmsview.cpp
@@ -38,7 +38,7 @@
 int main(int argc, char** argv)
 {
 	uint32_t time = 0;
-	string dev_path = "/dev/dri/card0";
+	string dev_path;
 	string conn_name;
 
 	OptionSet optionset = {
diff --git a/utils/wbm2m.cpp b/utils/wbm2m.cpp
index 4a126a8..b69bb28 100644
--- a/utils/wbm2m.cpp
+++ b/utils/wbm2m.cpp
@@ -2,6 +2,7 @@
 #include <poll.h>
 #include <unistd.h>
 #include <algorithm>
+#include <regex>
 #include <fstream>
 #include <map>
 #include <system_error>
@@ -19,7 +20,8 @@
 static const char* usage_str =
 		"Usage: wbm2m [OPTIONS]\n\n"
 		"Options:\n"
-		"  -f, --format=4CC          Output format"
+		"  -f, --format=4CC          Output format\n"
+		"  -c, --crop=CROP           CROP is <x>,<y>-<w>x<h>\n"
 		"  -h, --help                Print this help\n"
 		;
 
@@ -45,6 +47,21 @@
 	s_bar_pos_map[fb] = pos;
 }
 
+static void parse_crop(const string& crop_str, uint32_t& c_left, uint32_t& c_top,
+		       uint32_t& c_width, uint32_t& c_height)
+{
+	const regex crop_re("(\\d+),(\\d+)-(\\d+)x(\\d+)");		// 400,400-400x400
+
+	smatch sm;
+	if (!regex_match(crop_str, sm, crop_re))
+		EXIT("Failed to parse crop option '%s'", crop_str.c_str());
+
+	c_left = stoul(sm[1]);
+	c_top = stoul(sm[2]);
+	c_width = stoul(sm[3]);
+	c_height = stoul(sm[4]);
+}
+
 int main(int argc, char** argv)
 {
 	// XXX get from args
@@ -55,15 +72,21 @@
 
 	const uint32_t dst_width = 800;
 	const uint32_t dst_height = 480;
-	auto dst_fmt = PixelFormat::XRGB8888;
+	uint32_t c_top, c_left, c_width, c_height;
 
-	const string filename = "wb-out.raw";
+	auto dst_fmt = PixelFormat::XRGB8888;
+	bool use_selection = false;
 
 	OptionSet optionset = {
 		Option("f|format=", [&](string s)
 		{
 			dst_fmt = FourCCToPixelFormat(s);
 		}),
+		Option("c|crop=", [&](string s)
+		{
+			parse_crop(s, c_left, c_top, c_width, c_height);
+			use_selection = true;
+		}),
 		Option("h|help", [&]()
 		{
 			puts(usage_str);
@@ -78,6 +101,9 @@
 		exit(-1);
 	}
 
+	const string filename = sformat("wb-out-%ux%u_%4.4s.raw", dst_width, dst_height,
+					PixelFormatToFourCC(dst_fmt).c_str());
+
 	VideoDevice vid("/dev/video10");
 
 	Card card;
@@ -91,6 +117,11 @@
 	out->set_format(src_fmt, src_width, src_height);
 	in->set_format(dst_fmt, dst_width, dst_height);
 
+	if (use_selection) {
+		out->set_selection(c_left, c_top, c_width, c_height);
+		printf("crop -> %u,%u-%ux%u\n", c_left, c_top, c_width, c_height);
+	}
+
 	out->set_queue_size(NUM_SRC_BUFS);
 	in->set_queue_size(NUM_DST_BUFS);