From b11baff09f78a4a383f817ec35208ae8966ab832 Mon Sep 17 00:00:00 2001
From: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Date: Mon, 2 Jan 2017 16:42:08 +0200
Subject: py: Reorganize source directory

Separate the Python bindings sources from the test scripts. While at it,
remove the unneeded run.sh script.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 py/CMakeLists.txt       |  36 +-----
 py/alpha-test.py        |  67 ----------
 py/cam.py               |  78 ------------
 py/db.py                |  72 -----------
 py/functest.py          |  19 ---
 py/gamma.py             |  41 ------
 py/helpers.py           |  54 --------
 py/iact.py              |  43 -------
 py/kmsmodeview.py       | 317 ---------------------------------------------
 py/pykms.cpp            |  53 --------
 py/pykms/CMakeLists.txt |  26 ++++
 py/pykms/pykms.cpp      |  53 ++++++++
 py/pykms/pykmsbase.cpp  | 157 +++++++++++++++++++++++
 py/pykms/pykmsomap.cpp  |  21 +++
 py/pykms/pykmsutil.cpp  |  49 +++++++
 py/pykms/pyvid.cpp      |  38 ++++++
 py/pykmsbase.cpp        | 157 -----------------------
 py/pykmsomap.cpp        |  21 ---
 py/pykmsutil.cpp        |  49 -------
 py/pyvid.cpp            |  38 ------
 py/run.sh               |   3 -
 py/test.py              |  18 ---
 py/tests/CMakeLists.txt |   7 +
 py/tests/alpha-test.py  |  67 ++++++++++
 py/tests/cam.py         |  78 ++++++++++++
 py/tests/db.py          |  72 +++++++++++
 py/tests/functest.py    |  19 +++
 py/tests/gamma.py       |  41 ++++++
 py/tests/helpers.py     |  54 ++++++++
 py/tests/iact.py        |  43 +++++++
 py/tests/kmsmodeview.py | 317 +++++++++++++++++++++++++++++++++++++++++++++
 py/tests/test.py        |  18 +++
 py/tests/trans-test.py  | 332 ++++++++++++++++++++++++++++++++++++++++++++++++
 py/trans-test.py        | 332 ------------------------------------------------
 34 files changed, 1394 insertions(+), 1396 deletions(-)
 delete mode 100755 py/alpha-test.py
 delete mode 100755 py/cam.py
 delete mode 100755 py/db.py
 delete mode 100755 py/functest.py
 delete mode 100755 py/gamma.py
 delete mode 100644 py/helpers.py
 delete mode 100755 py/iact.py
 delete mode 100755 py/kmsmodeview.py
 delete mode 100644 py/pykms.cpp
 create mode 100644 py/pykms/CMakeLists.txt
 create mode 100644 py/pykms/pykms.cpp
 create mode 100644 py/pykms/pykmsbase.cpp
 create mode 100644 py/pykms/pykmsomap.cpp
 create mode 100644 py/pykms/pykmsutil.cpp
 create mode 100644 py/pykms/pyvid.cpp
 delete mode 100644 py/pykmsbase.cpp
 delete mode 100644 py/pykmsomap.cpp
 delete mode 100644 py/pykmsutil.cpp
 delete mode 100644 py/pyvid.cpp
 delete mode 100755 py/run.sh
 delete mode 100755 py/test.py
 create mode 100644 py/tests/CMakeLists.txt
 create mode 100755 py/tests/alpha-test.py
 create mode 100755 py/tests/cam.py
 create mode 100755 py/tests/db.py
 create mode 100755 py/tests/functest.py
 create mode 100755 py/tests/gamma.py
 create mode 100644 py/tests/helpers.py
 create mode 100755 py/tests/iact.py
 create mode 100755 py/tests/kmsmodeview.py
 create mode 100755 py/tests/test.py
 create mode 100755 py/tests/trans-test.py
 delete mode 100755 py/trans-test.py

(limited to 'py')

diff --git a/py/CMakeLists.txt b/py/CMakeLists.txt
index 69bb845..77f19b4 100644
--- a/py/CMakeLists.txt
+++ b/py/CMakeLists.txt
@@ -1,34 +1,2 @@
-include_directories(${LIBDRM_INCLUDE_DIRS})
-link_directories(${LIBDRM_LIBRARY_DIRS})
-
-pkg_check_modules(PYTHON python3 REQUIRED)
-include_directories(${PYTHON_INCLUDE_DIRS})
-
-if (NOT ${U_CMAKE_BUILD_TYPE} MATCHES DEBUG)
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden")
-endif()
-
-include_directories(${PROJECT_SOURCE_DIR}/ext/pybind11/include)
-
-set(SRCS pykms.cpp pykmsbase.cpp pykmsutil.cpp pyvid.cpp)
-
-if(LIBDRM_OMAP_FOUND)
-    set(SRCS ${SRCS} pykmsomap.cpp)
-endif()
-
-add_library(pykms SHARED ${SRCS})
-target_link_libraries(pykms kms++ kms++util ${LIBDRM_LIBRARIES})
-
-# Don't add a 'lib' prefix to the shared library
-set_target_properties(pykms PROPERTIES PREFIX "")
-
-file(GLOB PY_SRCS "*.py")
-add_custom_target(pyextras SOURCES ${PY_SRCS})
-
-add_test(NAME pytest COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/functest.py")
-set_property(TEST pytest PROPERTY
-        ENVIRONMENT "PYTHONPATH=." "LD_LIBRARY_PATH=."
-)
-
-# XXX Where should pykms.so be installed?
-#install(TARGETS pykms DESTINATION lib)
+add_subdirectory(pykms)
+add_subdirectory(tests)
diff --git a/py/alpha-test.py b/py/alpha-test.py
deleted file mode 100755
index c6ec8ee..0000000
--- a/py/alpha-test.py
+++ /dev/null
@@ -1,67 +0,0 @@
-#!/usr/bin/python3
-
-import pykms
-from helpers import *
-import time
-
-# This hack makes drm initialize the fbcon, setting up the default connector
-card = pykms.Card()
-card = 0
-
-card = pykms.Card()
-res = pykms.ResourceManager(card)
-conn = res.reserve_connector()
-crtc = res.reserve_crtc(conn)
-mode = conn.get_default_mode()
-
-planes = []
-for p in card.planes:
-    if p.supports_crtc(crtc) == False:
-        continue
-    planes.append(p)
-
-if len(planes) != 3:
-    print("Need 3 planes!")
-    exit(1)
-
-disable_planes(card)
-
-w = mode.hdisplay
-h = mode.vdisplay
-
-fbs=[]
-
-for i in range(len(planes)):
-    fbs.append(pykms.DumbFramebuffer(card, w, h, "AR24"))
-
-pykms.draw_rect(fbs[0], 50, 50, 200, 200, pykms.RGB(128, 255, 0, 0))
-pykms.draw_rect(fbs[1], 150, 50, 200, 200, pykms.RGB(128, 0, 255, 0))
-pykms.draw_rect(fbs[2], 50, 150, 200, 200, pykms.RGB(128, 0, 0, 255))
-
-
-set_props(crtc, {
-    "trans-key-mode": 0,
-    "trans-key": 0,
-    "background": 0,
-    "alpha_blender": 1,
-})
-
-for i in range(len(planes)):
-    plane = planes[i]
-    fb = fbs[i]
-
-    print("set crtc {}, plane {}, fb {}".format(crtc.id, p.id, fbs[i].id))
-
-    set_props(plane, {
-        "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,
-    })
-
-    time.sleep(1)
-
-input("press enter to exit\n")
diff --git a/py/cam.py b/py/cam.py
deleted file mode 100755
index b44f8f9..0000000
--- a/py/cam.py
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/usr/bin/python3
-
-import sys
-import selectors
-import pykms
-from helpers import *
-
-
-w = 640
-h = 480
-fmt = pykms.PixelFormat.YUYV
-
-
-# This hack makes drm initialize the fbcon, setting up the default connector
-card = pykms.Card()
-card = 0
-
-card = pykms.Card()
-res = pykms.ResourceManager(card)
-conn = res.reserve_connector()
-crtc = res.reserve_crtc(conn)
-plane = res.reserve_overlay_plane(crtc, fmt)
-
-mode = conn.get_default_mode()
-
-NUM_BUFS = 5
-
-fbs = []
-for i in range(NUM_BUFS):
-    fb = pykms.DumbFramebuffer(card, w, h, fmt)
-    fbs.append(fb)
-
-vidpath = pykms.VideoDevice.get_capture_devices()[0]
-
-vid = pykms.VideoDevice(vidpath)
-cap = vid.capture_streamer
-cap.set_port(0)
-cap.set_format(fmt, w, h)
-cap.set_queue_size(NUM_BUFS)
-
-for fb in fbs:
-    cap.queue(fb)
-
-cap.stream_on()
-
-
-def readvid(conn, mask):
-    fb = cap.dequeue()
-
-    if card.has_atomic:
-        set_props(plane, {
-            "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,
-        })
-    else:
-        crtc.set_plane(plane, fb, 0, 0, fb.width, fb.height,
-            0, 0, fb.width, fb.height)
-
-    cap.queue(fb)
-
-def readkey(conn, mask):
-    #print("KEY EVENT");
-    sys.stdin.readline()
-    exit(0)
-
-sel = selectors.DefaultSelector()
-sel.register(cap.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)
diff --git a/py/db.py b/py/db.py
deleted file mode 100755
index 3ffb716..0000000
--- a/py/db.py
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/usr/bin/python3
-
-import sys
-import pykms
-import selectors
-from helpers import *
-
-bar_width = 20
-bar_speed = 8
-
-class FlipHandler(pykms.PageFlipHandlerBase):
-    def __init__(self):
-        super().__init__()
-        self.bar_xpos = 0
-        self.front_buf = 0
-        self.fb1 = pykms.DumbFramebuffer(card, mode.hdisplay, mode.vdisplay, "XR24");
-        self.fb2 = pykms.DumbFramebuffer(card, mode.hdisplay, mode.vdisplay, "XR24");
-
-    def handle_page_flip(self, frame, time):
-        if self.front_buf == 0:
-            fb = self.fb2
-        else:
-            fb = self.fb1
-
-        self.front_buf = self.front_buf ^ 1
-
-        current_xpos = self.bar_xpos;
-        old_xpos = (current_xpos + (fb.width - bar_width - bar_speed)) % (fb.width - bar_width);
-        new_xpos = (current_xpos + bar_speed) % (fb.width - bar_width);
-
-        self.bar_xpos = new_xpos
-
-        pykms.draw_color_bar(fb, old_xpos, new_xpos, bar_width)
-
-        if card.has_atomic:
-            ctx = pykms.AtomicReq(card)
-            ctx.add(crtc.primary_plane, "FB_ID", fb.id)
-            ctx.commit(self)
-        else:
-            crtc.page_flip(fb, self)
-
-
-card = pykms.Card()
-res = pykms.ResourceManager(card)
-conn = res.reserve_connector()
-crtc = res.reserve_crtc(conn)
-mode = conn.get_default_mode()
-
-fliphandler = FlipHandler()
-
-crtc.set_mode(conn, fliphandler.fb1, mode)
-
-fliphandler.handle_page_flip(0, 0)
-
-def readdrm(conn, mask):
-    #print("EVENT");
-    card.call_page_flip_handlers()
-
-def readkey(conn, mask):
-    #print("KEY EVENT");
-    sys.stdin.readline()
-    exit(0)
-
-sel = selectors.DefaultSelector()
-sel.register(card.fd, selectors.EVENT_READ, readdrm)
-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)
diff --git a/py/functest.py b/py/functest.py
deleted file mode 100755
index 44c29fb..0000000
--- a/py/functest.py
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/usr/bin/python3
-
-import pykms
-from helpers import *
-
-card = pykms.Card()
-res = pykms.ResourceManager(card)
-conn = res.reserve_connector()
-crtc = res.reserve_crtc(conn)
-
-mode = conn.get_default_mode()
-
-fb = pykms.DumbFramebuffer(card, mode.hdisplay, mode.vdisplay, "XR24");
-pykms.draw_test_pattern(fb);
-
-crtc.set_mode(conn, fb, mode)
-
-print("OK")
-
diff --git a/py/gamma.py b/py/gamma.py
deleted file mode 100755
index a6b68cc..0000000
--- a/py/gamma.py
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/usr/bin/python3
-
-import pykms
-from helpers import *
-
-# This hack makes drm initialize the fbcon, setting up the default connector
-card = pykms.Card()
-card = 0
-
-card = pykms.Card()
-res = pykms.ResourceManager(card)
-conn = res.reserve_connector()
-crtc = res.reserve_crtc(conn)
-mode = conn.get_default_mode()
-
-fb = pykms.DumbFramebuffer(card, mode.hdisplay, mode.vdisplay, "XR24");
-pykms.draw_test_pattern(fb);
-
-crtc.set_mode(conn, fb, mode)
-
-len=256
-arr = bytearray(len*2*4)
-view = memoryview(arr).cast("H")
-
-for i in range(len):
-    g = round(65535 * pow(i / float(len), 1 / 2.2))
-
-    view[i * 4 + 0] = g
-    view[i * 4 + 1] = g
-    view[i * 4 + 2] = g
-    view[i * 4 + 3] = 0
-
-gamma = pykms.Blob(card, arr);
-
-set_prop(crtc, "GAMMA_LUT", gamma.id)
-
-input("press enter to remove gamma\n")
-
-set_prop(crtc, "GAMMA_LUT", 0)
-
-input("press enter to exit\n")
diff --git a/py/helpers.py b/py/helpers.py
deleted file mode 100644
index fd67d41..0000000
--- a/py/helpers.py
+++ /dev/null
@@ -1,54 +0,0 @@
-import pykms
-
-def add_props(areq, ob, map):
-    for key, value in map.items():
-        areq.add(ob, key, value)
-
-def props(o):
-    o.refresh_props()
-    map = o.prop_map
-    for propid,propval in map.items():
-        prop = o.card.get_prop(propid)
-        print("%-15s %d (%#x)" % (prop.name, propval, propval))
-
-def set_prop(ob, prop, value):
-    if ob.card.has_atomic:
-        areq = pykms.AtomicReq(ob.card)
-        areq.add(ob, prop, value)
-        if areq.commit_sync() != 0:
-            print("commit failed")
-    else:
-        if ob.set_prop_value(prop, value) != 0:
-            print("setting property failed")
-
-def set_props(ob, map):
-    if ob.card.has_atomic:
-        areq = pykms.AtomicReq(ob.card)
-
-        for key, value in map.items():
-            areq.add(ob, key, value)
-
-        if areq.commit_sync() != 0:
-            print("commit failed")
-    else:
-        for propid,propval in map.items():
-            if ob.set_prop_value(propid, propval) != 0:
-                print("setting property failed")
-
-red = pykms.RGB(255, 0, 0)
-green = pykms.RGB(0, 255, 0)
-blue = pykms.RGB(0, 0, 255)
-yellow = pykms.RGB(255, 255, 0)
-purple = pykms.RGB(255, 0, 255)
-white = pykms.RGB(255, 255, 255)
-cyan = pykms.RGB(0, 255, 255)
-
-def disable_planes(card):
-    areq = pykms.AtomicReq(card)
-
-    for p in card.planes:
-        areq.add(p, "FB_ID", 0)
-        areq.add(p, "CRTC_ID", 0)
-
-    if areq.commit_sync() != 0:
-        print("disabling planes failed")
diff --git a/py/iact.py b/py/iact.py
deleted file mode 100755
index fecd899..0000000
--- a/py/iact.py
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/usr/bin/python3 -i
-
-# This is a base script for interactive kms++ python environment
-
-import pykms
-from time import sleep
-from math import sin
-from math import cos
-from helpers import *
-
-card = pykms.Card()
-res = pykms.ResourceManager(card)
-conn = res.reserve_connector()
-crtc = res.reserve_crtc(conn)
-
-mode = conn.get_default_mode()
-
-fb = pykms.DumbFramebuffer(card, 200, 200, "XR24");
-pykms.draw_test_pattern(fb);
-
-#crtc.set_mode(conn, fb, mode)
-
-i = 0
-for p in card.planes:
-    globals()["plane"+str(i)] = p
-    i=i+1
-
-i = 0
-for c in card.crtcs:
-    globals()["crtc"+str(i)] = c
-    i=i+1
-
-for p in crtc.possible_planes:
-    if p.plane_type == pykms.PlaneType.Overlay:
-        plane = p
-        break
-
-def set_plane(x, y):
-    crtc.set_plane(plane, fb, x, y, fb.width, fb.height, 0, 0, fb.width, fb.height)
-
-set_plane(0, 0)
-
-# for x in range(0, crtc.width() - fb.width()): set_plane(x, int((sin(x/50) + 1) * 100)); sleep(0.01)
diff --git a/py/kmsmodeview.py b/py/kmsmodeview.py
deleted file mode 100755
index 355db02..0000000
--- a/py/kmsmodeview.py
+++ /dev/null
@@ -1,317 +0,0 @@
-#!/usr/bin/python3
-
-import urwid
-import pykms
-
-def exit_on_q(key):
-	if key in ('q', 'Q'):
-		raise urwid.ExitMainLoop()
-	elif key == 'a':
-		apply_mode()
-
-alarm_handle = None
-
-def recalc_info(l, d):
-	global alarm_handle
-
-	alarm_handle = None
-
-	for w in recalc_list:
-		w.recalc()
-
-def div_or_zero(n, d):
-	if d == 0:
-		return 0
-	else:
-		return n / d
-
-class MyIntEdit(urwid.IntEdit):
-	_metaclass_ = urwid.signals.MetaSignals
-	signals = ['value_change']
-
-	def __init__(self, caption, calc=None):
-		self._myval = 0
-		self._disable_change = False
-		self._calc = calc
-		self._updlist = None
-
-		super().__init__(caption, 0)
-
-	def set_edit_text(self, text):
-		global alarm_handle
-
-		super().set_edit_text(text)
-		newtext = super().get_edit_text()
-		new_val = int(newtext) if newtext != "" else 0
-		if new_val != self._myval:
-			self._myval = new_val
-			if not self._disable_change:
-				urwid.emit_signal(self, 'value_change', self, self._myval)
-
-				if alarm_handle == None:
-					alarm_handle = loop.set_alarm_in(0, recalc_info)
-
-				if self._updlist != None:
-					for w in self._updlist:
-						w.recalc()
-
-	def recalc(self):
-		self._disable_change = True
-		self.set_val(self._calc())
-		self._disable_change = False
-
-	def set_val(self, val):
-		self.set_edit_text(str(int(val)))
-
-	def get_val(self):
-		return self._myval
-
-	def set_updlist(self, list):
-		self._updlist = list
-
-	def keypress(self, size, key):
-		if key == '+':
-			self.set_edit_text(str(self.value() + 1))
-		elif key == '-':
-			self.set_edit_text(str(self.value() - 1))
-		else:
-			return super().keypress(size, key)
-
-class MyIntText(urwid.Text):
-	def __init__(self, fmt, calc=None):
-		super().__init__("")
-		self._fmt = fmt
-		self._calc = calc
-
-	def recalc(self):
-		val = self._calc()
-		super().set_text(self._fmt.format(val))
-
-def khz_to_ps(khz):
-	if khz == 0:
-		return 0
-	else:
-		return 1.0 / khz * 1000 * 1000 * 1000
-
-def khz_to_us(khz):
-	if khz == 0:
-		return 0
-	else:
-		return 1.0 / khz * 1000
-
-pclk_khz_widget = MyIntEdit(u"pclk (kHz) ")
-pclk_ps_widget = MyIntText(fmt="pclk {:.2f} ps", calc = lambda: khz_to_ps(pclk_khz_widget.get_val()))
-
-pclk_widgets = [pclk_khz_widget, pclk_ps_widget]
-
-pclk_columns = urwid.LineBox(urwid.Columns(pclk_widgets), title = "Pixel clock")
-
-# Horizontal widgets
-
-hdisp_widget = MyIntEdit(u"hdisp ", calc = lambda: hdisp2_widget.get_val())
-hfp_widget = MyIntEdit(u"hfp   ", calc = lambda: hss_widget.get_val() - hdisp_widget.get_val())
-hsw_widget = MyIntEdit(u"hsw   ", calc = lambda: hse_widget.get_val() - hss_widget.get_val())
-hbp_widget = MyIntEdit(u"hbp   ", calc = lambda: htot_widget.get_val() - hse_widget.get_val())
-
-hdisp2_widget = MyIntEdit(u"hdisp ", calc = lambda: hdisp_widget.get_val())
-hss_widget = MyIntEdit(u"hss   ",
-	calc = lambda: hdisp_widget.get_val() + hfp_widget.get_val())
-hse_widget = MyIntEdit(u"hse   ",
-	calc = lambda: hdisp_widget.get_val() + hfp_widget.get_val() + hsw_widget.get_val())
-htot_widget = MyIntEdit(u"htot  ",
-	calc = lambda: hdisp_widget.get_val() + hfp_widget.get_val() + hsw_widget.get_val() + hbp_widget.get_val())
-
-hwidgets1 = [hdisp_widget, hfp_widget, hsw_widget, hbp_widget]
-hwidgets2 = [hdisp2_widget, hss_widget, hse_widget, htot_widget]
-
-horiz_pile1 = urwid.Pile(hwidgets1)
-horiz_pile2 = urwid.Pile(hwidgets2)
-
-h_columns = urwid.LineBox(urwid.Columns([(15, horiz_pile1), (15, horiz_pile2)]), title = "Horizontal")
-
-# Vertical columns
-
-vdisp_widget = MyIntEdit(u"vdisp ", calc = lambda: vdisp2_widget.get_val())
-vfp_widget = MyIntEdit(u"vfp   ", calc = lambda: vss_widget.get_val() - vdisp_widget.get_val())
-vsw_widget = MyIntEdit(u"vsw   ", calc = lambda: vse_widget.get_val() - vss_widget.get_val())
-vbp_widget = MyIntEdit(u"vbp   ", calc = lambda: vtot_widget.get_val() - vse_widget.get_val())
-
-vdisp2_widget = MyIntEdit(u"vdisp ", calc = lambda: vdisp_widget.get_val())
-vss_widget = MyIntEdit(u"vss   ",
-	calc = lambda: vdisp_widget.get_val() + vfp_widget.get_val())
-vse_widget = MyIntEdit(u"vse   ",
-	calc = lambda: vdisp_widget.get_val() + vfp_widget.get_val() + vsw_widget.get_val())
-vtot_widget = MyIntEdit(u"vtot  ",
-	calc = lambda: vdisp_widget.get_val() + vfp_widget.get_val() + vsw_widget.get_val() + vbp_widget.get_val())
-
-vwidgets1 = [vdisp_widget, vfp_widget, vsw_widget, vbp_widget]
-vwidgets2 = [vdisp2_widget, vss_widget, vse_widget, vtot_widget]
-
-vert_pile1 = urwid.Pile(vwidgets1)
-vert_pile2 = urwid.Pile(vwidgets2)
-
-v_columns = urwid.LineBox(urwid.Columns([(15, vert_pile1), (15, vert_pile2)]), title = "Vertical")
-
-# Info widgets
-
-line_us_widget = MyIntText(fmt="line {:.2f} us",
-	calc = lambda: khz_to_us(pclk_khz_widget.get_val()) * htot_widget.get_val())
-line_khz_widget = MyIntText(fmt="line {:.2f} kHz",
-	calc = lambda: div_or_zero(pclk_khz_widget.get_val(), htot_widget.get_val()))
-
-frame_tot_widget = MyIntText(fmt="tot {} pix",
-	calc = lambda: htot_widget.get_val() * vtot_widget.get_val())
-frame_us_widget = MyIntText(fmt="frame {:.2f} ms",
-	calc = lambda: khz_to_us(pclk_khz_widget.get_val()) * htot_widget.get_val() * vtot_widget.get_val() / 1000)
-frame_khz_widget = MyIntText(fmt="frame {:.2f} Hz",
-	calc = lambda: div_or_zero(pclk_khz_widget.get_val() * 1000,  htot_widget.get_val() * vtot_widget.get_val()))
-
-info_box = urwid.LineBox(urwid.Pile([line_us_widget, line_khz_widget, urwid.Divider(), frame_tot_widget, frame_us_widget, frame_khz_widget]), title = "Info")
-
-# Set update lists
-
-recalc_list = [ pclk_ps_widget, line_us_widget, line_khz_widget, frame_tot_widget, frame_us_widget, frame_khz_widget ]
-
-hdisp_widget.set_updlist([hdisp2_widget, hss_widget, hse_widget, htot_widget])
-hfp_widget.set_updlist([hss_widget, hse_widget, htot_widget])
-hsw_widget.set_updlist([hse_widget, htot_widget])
-hbp_widget.set_updlist([htot_widget])
-hdisp2_widget.set_updlist([hdisp_widget, hfp_widget])
-hss_widget.set_updlist([hfp_widget, hsw_widget])
-hse_widget.set_updlist([hsw_widget, hbp_widget])
-htot_widget.set_updlist([hbp_widget])
-
-vdisp_widget.set_updlist([vdisp2_widget, vss_widget, vse_widget, vtot_widget])
-vfp_widget.set_updlist([vss_widget, vse_widget, vtot_widget])
-vsw_widget.set_updlist([vse_widget, vtot_widget])
-vbp_widget.set_updlist([vtot_widget])
-vdisp2_widget.set_updlist([vdisp_widget, vfp_widget])
-vss_widget.set_updlist([vfp_widget, vsw_widget])
-vse_widget.set_updlist([vsw_widget, vbp_widget])
-vtot_widget.set_updlist([vbp_widget])
-
-# Flags
-
-fb = None
-
-DRM_MODE_FLAG_PHSYNC = (1<<0)
-DRM_MODE_FLAG_NHSYNC = (1<<1)
-DRM_MODE_FLAG_PVSYNC = (1<<2)
-DRM_MODE_FLAG_NVSYNC = (1<<3)
-DRM_MODE_FLAG_INTERLACE = (1<<4)
-DRM_MODE_FLAG_DBLCLK = (1<<12)
-
-def mode_is_ilace(mode):
-	return (mode.flags & DRM_MODE_FLAG_INTERLACE) != 0
-
-def apply_mode():
-	global fb
-
-	mode = pykms.Videomode()
-	mode.clock = pclk_khz_widget.get_val()
-
-	mode.hdisplay = hdisp2_widget.get_val()
-	mode.hsync_start = hss_widget.get_val()
-	mode.hsync_end = hse_widget.get_val()
-	mode.htotal = htot_widget.get_val()
-
-	mode.vdisplay = vdisp2_widget.get_val()
-	mode.vsync_start = vss_widget.get_val()
-	mode.vsync_end = vse_widget.get_val()
-	mode.vtotal = vtot_widget.get_val()
-
-	if ilace_box.state:
-		mode.flags |= DRM_MODE_FLAG_INTERLACE
-
-	if dblclk_box.state:
-		mode.flags |= DRM_MODE_FLAG_DBLCLK
-
-	if hsync_pol.state == True:
-		mode.flags |= DRM_MODE_FLAG_PHSYNC
-	elif hsync_pol.state == False:
-		mode.flags |= DRM_MODE_FLAG_NHSYNC
-
-	if vsync_pol.state == True:
-		mode.flags |= DRM_MODE_FLAG_PVSYNC
-	elif vsync_pol.state == False:
-		mode.flags |= DRM_MODE_FLAG_NVSYNC
-
-	fb = pykms.DumbFramebuffer(card, mode.hdisplay, mode.vdisplay, "XR24");
-	pykms.draw_test_pattern(fb);
-
-	crtc.set_mode(conn, fb, mode)
-
-def read_mode(mode):
-	pclk_khz_widget.set_val(mode.clock)
-	hdisp2_widget.set_val(mode.hdisplay)
-	hss_widget.set_val(mode.hsync_start)
-	hse_widget.set_val(mode.hsync_end)
-	htot_widget.set_val(mode.htotal)
-
-	vdisp2_widget.set_val(mode.vdisplay)
-	vss_widget.set_val(mode.vsync_start)
-	vse_widget.set_val(mode.vsync_end)
-	vtot_widget.set_val(mode.vtotal)
-
-	ilace_box.set_state(mode_is_ilace(mode))
-	dblclk_box.set_state((mode.flags & DRM_MODE_FLAG_DBLCLK) != 0)
-
-	sync = 'mixed'
-	if (mode.flags & DRM_MODE_FLAG_PHSYNC) != 0:
-		sync = True
-	elif (mode.flags & DRM_MODE_FLAG_NHSYNC) != 0:
-		sync = False
-	hsync_pol.set_state(sync)
-
-	sync = 'mixed'
-	if (mode.flags & DRM_MODE_FLAG_PVSYNC) != 0:
-		sync = True
-	elif (mode.flags & DRM_MODE_FLAG_NVSYNC) != 0:
-		sync = False
-	vsync_pol.set_state(sync)
-
-def apply_press(w):
-	apply_mode()
-
-ilace_box = urwid.CheckBox('interlace')
-hsync_pol = urwid.CheckBox('hsync positive', has_mixed=True)
-vsync_pol = urwid.CheckBox('vsync positive', has_mixed=True)
-dblclk_box = urwid.CheckBox('double clock')
-
-flags_pile = urwid.LineBox(urwid.Pile([ilace_box, hsync_pol, vsync_pol, dblclk_box]), title = "Flags")
-
-apply_button = urwid.LineBox(urwid.Padding(urwid.Button('apply', on_press=apply_press)))
-
-# Main
-
-def mode_press(w, mode):
-	read_mode(mode)
-
-def mode_to_str(mode):
-	return "{}@{}{}".format(mode.name, mode.vrefresh, "i" if mode_is_ilace(mode) else "")
-
-mode_buttons = []
-
-card = pykms.Card()
-conn = card.get_first_connected_connector()
-crtc = conn.get_current_crtc()
-modes = conn.get_modes()
-i = 0
-for m in modes:
-	mode_buttons.append(urwid.Button(mode_to_str(m), on_press=mode_press, user_data=m))
-	i += 1
-
-modes_pile = urwid.LineBox(urwid.Pile(mode_buttons), title = "Video modes")
-
-main_pile = urwid.Pile([modes_pile, pclk_columns, urwid.Columns([ h_columns, v_columns ]), info_box, flags_pile, apply_button])
-
-main_columns = urwid.Filler(main_pile, valign='top')
-
-loop = urwid.MainLoop(main_columns, unhandled_input=exit_on_q, handle_mouse=False)
-
-# select the first mode
-mode_press(None, modes[0])
-
-loop.run()
-
-fb = None
diff --git a/py/pykms.cpp b/py/pykms.cpp
deleted file mode 100644
index 2199039..0000000
--- a/py/pykms.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-#include <pybind11/pybind11.h>
-#include <pybind11/stl.h>
-#include <kms++/kms++.h>
-
-namespace py = pybind11;
-
-using namespace kms;
-using namespace std;
-
-void init_pykmstest(py::module &m);
-void init_pykmsbase(py::module &m);
-void init_pyvid(py::module &m);
-
-#if HAS_LIBDRM_OMAP
-void init_pykmsomap(py::module &m);
-#endif
-
-class PyPageFlipHandlerBase : PageFlipHandlerBase
-{
-public:
-	using PageFlipHandlerBase::PageFlipHandlerBase;
-
-	virtual void handle_page_flip(uint32_t frame, double time)
-	{
-		PYBIND11_OVERLOAD_PURE(
-					void,                /* Return type */
-					PageFlipHandlerBase, /* Parent class */
-					handle_page_flip,    /* Name of function */
-					frame, time
-					);
-	}
-};
-
-PYBIND11_PLUGIN(pykms) {
-	py::module m("pykms", "kms bindings");
-
-	init_pykmsbase(m);
-
-	py::class_<PyPageFlipHandlerBase>(m, "PageFlipHandlerBase")
-			.alias<PageFlipHandlerBase>()
-			.def(py::init<>())
-			.def("handle_page_flip", &PageFlipHandlerBase::handle_page_flip)
-			;
-
-	init_pykmstest(m);
-
-	init_pyvid(m);
-
-#if HAS_LIBDRM_OMAP
-	init_pykmsomap(m);
-#endif
-	return m.ptr();
-}
diff --git a/py/pykms/CMakeLists.txt b/py/pykms/CMakeLists.txt
new file mode 100644
index 0000000..3e6e0e1
--- /dev/null
+++ b/py/pykms/CMakeLists.txt
@@ -0,0 +1,26 @@
+include_directories(${LIBDRM_INCLUDE_DIRS})
+link_directories(${LIBDRM_LIBRARY_DIRS})
+
+pkg_check_modules(PYTHON python3 REQUIRED)
+include_directories(${PYTHON_INCLUDE_DIRS})
+
+if (NOT ${U_CMAKE_BUILD_TYPE} MATCHES DEBUG)
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden")
+endif()
+
+include_directories(${PROJECT_SOURCE_DIR}/ext/pybind11/include)
+
+set(SRCS pykms.cpp pykmsbase.cpp pykmsutil.cpp pyvid.cpp)
+
+if(LIBDRM_OMAP_FOUND)
+    set(SRCS ${SRCS} pykmsomap.cpp)
+endif()
+
+add_library(pykms SHARED ${SRCS})
+target_link_libraries(pykms kms++ kms++util ${LIBDRM_LIBRARIES})
+
+# Don't add a 'lib' prefix to the shared library
+set_target_properties(pykms PROPERTIES PREFIX "")
+
+# XXX Where should pykms.so be installed?
+#install(TARGETS pykms DESTINATION lib)
diff --git a/py/pykms/pykms.cpp b/py/pykms/pykms.cpp
new file mode 100644
index 0000000..2199039
--- /dev/null
+++ b/py/pykms/pykms.cpp
@@ -0,0 +1,53 @@
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+#include <kms++/kms++.h>
+
+namespace py = pybind11;
+
+using namespace kms;
+using namespace std;
+
+void init_pykmstest(py::module &m);
+void init_pykmsbase(py::module &m);
+void init_pyvid(py::module &m);
+
+#if HAS_LIBDRM_OMAP
+void init_pykmsomap(py::module &m);
+#endif
+
+class PyPageFlipHandlerBase : PageFlipHandlerBase
+{
+public:
+	using PageFlipHandlerBase::PageFlipHandlerBase;
+
+	virtual void handle_page_flip(uint32_t frame, double time)
+	{
+		PYBIND11_OVERLOAD_PURE(
+					void,                /* Return type */
+					PageFlipHandlerBase, /* Parent class */
+					handle_page_flip,    /* Name of function */
+					frame, time
+					);
+	}
+};
+
+PYBIND11_PLUGIN(pykms) {
+	py::module m("pykms", "kms bindings");
+
+	init_pykmsbase(m);
+
+	py::class_<PyPageFlipHandlerBase>(m, "PageFlipHandlerBase")
+			.alias<PageFlipHandlerBase>()
+			.def(py::init<>())
+			.def("handle_page_flip", &PageFlipHandlerBase::handle_page_flip)
+			;
+
+	init_pykmstest(m);
+
+	init_pyvid(m);
+
+#if HAS_LIBDRM_OMAP
+	init_pykmsomap(m);
+#endif
+	return m.ptr();
+}
diff --git a/py/pykms/pykmsbase.cpp b/py/pykms/pykmsbase.cpp
new file mode 100644
index 0000000..5247158
--- /dev/null
+++ b/py/pykms/pykmsbase.cpp
@@ -0,0 +1,157 @@
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+#include <kms++/kms++.h>
+
+namespace py = pybind11;
+
+using namespace kms;
+using namespace std;
+
+void init_pykmsbase(py::module &m)
+{
+	py::class_<Card>(m, "Card")
+			.def(py::init<>())
+			.def_property_readonly("fd", &Card::fd)
+			.def("get_first_connected_connector", &Card::get_first_connected_connector)
+			.def_property_readonly("connectors", &Card::get_connectors)
+			.def_property_readonly("crtcs", &Card::get_crtcs)
+			.def_property_readonly("encoders", &Card::get_encoders)
+			.def_property_readonly("planes", &Card::get_planes)
+			.def_property_readonly("has_atomic", &Card::has_atomic)
+			.def("call_page_flip_handlers", &Card::call_page_flip_handlers)
+			.def("get_prop", (Property* (Card::*)(uint32_t) const)&Card::get_prop)
+			;
+
+	py::class_<DrmObject, DrmObject*>(m, "DrmObject")
+			.def_property_readonly("id", &DrmObject::id)
+			.def_property_readonly("card", &DrmObject::card)
+			;
+
+	py::class_<DrmPropObject, DrmPropObject*>(m, "DrmPropObject", py::base<DrmObject>())
+			.def("refresh_props", &DrmPropObject::refresh_props)
+			.def_property_readonly("prop_map", &DrmPropObject::get_prop_map)
+			.def("get_prop_value", (uint64_t (DrmPropObject::*)(const string&) const)&DrmPropObject::get_prop_value)
+			.def("set_prop_value",(int (DrmPropObject::*)(const string&, uint64_t)) &DrmPropObject::set_prop_value)
+			.def("get_prop_value_as_blob", &DrmPropObject::get_prop_value_as_blob)
+			;
+
+	py::class_<Connector, Connector*>(m, "Connector",  py::base<DrmPropObject>())
+			.def_property_readonly("fullname", &Connector::fullname)
+			.def("get_default_mode", &Connector::get_default_mode)
+			.def("get_current_crtc", &Connector::get_current_crtc)
+			.def("get_possible_crtcs", &Connector::get_possible_crtcs)
+			.def("get_modes", &Connector::get_modes)
+			.def("get_mode", (Videomode (Connector::*)(const string& mode) const)&Connector::get_mode)
+			.def("get_mode", (Videomode (Connector::*)(unsigned xres, unsigned yres, float refresh, bool ilace) const)&Connector::get_mode)
+			.def("__repr__", [](const Connector& o) { return "<pykms.Connector " + to_string(o.id()) + ">"; })
+			;
+
+	py::class_<Crtc, Crtc*>(m, "Crtc",  py::base<DrmPropObject>())
+			.def("set_mode", &Crtc::set_mode)
+			.def("page_flip", &Crtc::page_flip)
+			.def("set_plane", &Crtc::set_plane)
+			.def_property_readonly("possible_planes", &Crtc::get_possible_planes)
+			.def_property_readonly("primary_plane", &Crtc::get_primary_plane)
+			.def_property_readonly("mode", &Crtc::mode)
+			.def_property_readonly("mode_valid", &Crtc::mode_valid)
+			.def("__repr__", [](const Crtc& o) { return "<pykms.Crtc " + to_string(o.id()) + ">"; })
+			;
+
+	py::class_<Encoder, Encoder*>(m, "Encoder",  py::base<DrmPropObject>())
+			;
+
+	py::class_<Plane, Plane*>(m, "Plane",  py::base<DrmPropObject>())
+			.def("supports_crtc", &Plane::supports_crtc)
+			.def_property_readonly("plane_type", &Plane::plane_type)
+			.def("__repr__", [](const Plane& o) { return "<pykms.Plane " + to_string(o.id()) + ">"; })
+			;
+
+	py::enum_<PlaneType>(m, "PlaneType")
+			.value("Overlay", PlaneType::Overlay)
+			.value("Primary", PlaneType::Primary)
+			.value("Cursor", PlaneType::Cursor)
+			;
+
+	py::class_<Property, Property*>(m, "Property",  py::base<DrmObject>())
+			.def_property_readonly("name", &Property::name)
+			;
+
+	py::class_<Blob>(m, "Blob", py::base<DrmObject>())
+			.def("__init__", [](Blob& instance, Card& card, py::buffer buf) {
+				py::buffer_info info = buf.request();
+				if (info.ndim != 1)
+					throw std::runtime_error("Incompatible buffer dimension!");
+
+				new (&instance) Blob(card, info.ptr, info.size * info.itemsize);
+			})
+			.def_property_readonly("data", &Blob::data)
+			;
+
+	py::class_<Framebuffer>(m, "Framebuffer", py::base<DrmObject>())
+			;
+
+	py::class_<MappedFramebuffer>(m, "MappedFramebuffer", py::base<Framebuffer>())
+			.def_property_readonly("width", &MappedFramebuffer::width)
+			.def_property_readonly("height", &MappedFramebuffer::height)
+			;
+
+	py::class_<DumbFramebuffer>(m, "DumbFramebuffer", py::base<MappedFramebuffer>())
+			.def(py::init<Card&, uint32_t, uint32_t, const string&>(),
+			     py::keep_alive<1, 2>())	// Keep Card alive until this is destructed
+			.def(py::init<Card&, uint32_t, uint32_t, PixelFormat>(),
+			     py::keep_alive<1, 2>())	// Keep Card alive until this is destructed
+			;
+
+	py::enum_<PixelFormat>(m, "PixelFormat")
+			.value("Undefined", PixelFormat::Undefined)
+
+			.value("NV12", PixelFormat::NV12)
+			.value("NV21", PixelFormat::NV21)
+
+			.value("UYVY", PixelFormat::UYVY)
+			.value("YUYV", PixelFormat::YUYV)
+			.value("YVYU", PixelFormat::YVYU)
+			.value("VYUY", PixelFormat::VYUY)
+
+			.value("XRGB8888", PixelFormat::XRGB8888)
+			.value("XBGR8888", PixelFormat::XBGR8888)
+			.value("ARGB8888", PixelFormat::ARGB8888)
+			.value("ABGR8888", PixelFormat::ABGR8888)
+
+			.value("RGB888", PixelFormat::RGB888)
+
+			.value("RGB565", PixelFormat::RGB565)
+			;
+
+	py::class_<Videomode>(m, "Videomode")
+			.def(py::init<>())
+
+			.def_readwrite("name", &Videomode::name)
+
+			.def_readwrite("clock", &Videomode::clock)
+
+			.def_readwrite("hdisplay", &Videomode::hdisplay)
+			.def_readwrite("hsync_start", &Videomode::hsync_start)
+			.def_readwrite("hsync_end", &Videomode::hsync_end)
+			.def_readwrite("htotal", &Videomode::htotal)
+
+			.def_readwrite("vdisplay", &Videomode::vdisplay)
+			.def_readwrite("vsync_start", &Videomode::vsync_start)
+			.def_readwrite("vsync_end", &Videomode::vsync_end)
+			.def_readwrite("vtotal", &Videomode::vtotal)
+
+			.def_readwrite("vrefresh", &Videomode::vrefresh)
+
+			.def_readwrite("flags", &Videomode::flags)
+			.def_readwrite("type", &Videomode::type)
+			;
+
+	py::class_<AtomicReq>(m, "AtomicReq")
+			.def(py::init<Card&>(),
+			     py::keep_alive<1, 2>())	// Keep Card alive until this is destructed
+			.def("add", (void (AtomicReq::*)(DrmPropObject*, const string&, uint64_t)) &AtomicReq::add)
+			.def("test", &AtomicReq::test, py::arg("allow_modeset") = false)
+			.def("commit", &AtomicReq::commit, py::arg("data"), py::arg("allow_modeset") = false)
+			.def("commit_sync", &AtomicReq::commit_sync, py::arg("allow_modeset") = false)
+			;
+}
diff --git a/py/pykms/pykmsomap.cpp b/py/pykms/pykmsomap.cpp
new file mode 100644
index 0000000..525834b
--- /dev/null
+++ b/py/pykms/pykmsomap.cpp
@@ -0,0 +1,21 @@
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+#include <kms++/kms++.h>
+#include <kms++/omap/omapkms++.h>
+
+namespace py = pybind11;
+
+using namespace kms;
+using namespace std;
+
+void init_pykmsomap(py::module &m)
+{
+	py::class_<OmapCard>(m, "OmapCard", py::base<Card>())
+			.def(py::init<>())
+			;
+
+	py::class_<OmapFramebuffer>(m, "OmapFramebuffer", py::base<MappedFramebuffer>())
+			.def(py::init<OmapCard&, uint32_t, uint32_t, PixelFormat>(),
+			     py::keep_alive<1, 2>())	// Keep OmapCard alive until this is destructed
+			;
+}
diff --git a/py/pykms/pykmsutil.cpp b/py/pykms/pykmsutil.cpp
new file mode 100644
index 0000000..b3b7594
--- /dev/null
+++ b/py/pykms/pykmsutil.cpp
@@ -0,0 +1,49 @@
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+#include <kms++/kms++.h>
+#include <kms++util/kms++util.h>
+
+namespace py = pybind11;
+
+using namespace kms;
+using namespace std;
+
+void init_pykmstest(py::module &m)
+{
+	py::class_<RGB>(m, "RGB")
+			.def(py::init<>())
+			.def(py::init<uint8_t, uint8_t, uint8_t&>())
+			.def(py::init<uint8_t, uint8_t, uint8_t, uint8_t&>())
+			.def_property_readonly("rgb888", &RGB::rgb888)
+			.def_property_readonly("argb8888", &RGB::argb8888)
+			.def_property_readonly("abgr8888", &RGB::abgr8888)
+			.def_property_readonly("rgb565", &RGB::rgb565)
+			;
+
+	py::class_<ResourceManager>(m, "ResourceManager")
+			.def(py::init<Card&>())
+			.def("reset", &ResourceManager::reset)
+			.def("reserve_connector", &ResourceManager::reserve_connector,
+			     py::arg("name") = string())
+			.def("reserve_crtc", &ResourceManager::reserve_crtc)
+			.def("reserve_plane", &ResourceManager::reserve_plane,
+			     py::arg("crtc"),
+			     py::arg("type"),
+			     py::arg("format") = PixelFormat::Undefined)
+			.def("reserve_primary_plane", &ResourceManager::reserve_primary_plane,
+			     py::arg("crtc"),
+			     py::arg("format") = PixelFormat::Undefined)
+			.def("reserve_overlay_plane", &ResourceManager::reserve_overlay_plane,
+			     py::arg("crtc"),
+			     py::arg("format") = PixelFormat::Undefined)
+			;
+
+	// Use lambdas to handle IMappedFramebuffer
+	m.def("draw_test_pattern", [](MappedFramebuffer& fb) { draw_test_pattern(fb); } );
+	m.def("draw_color_bar", [](MappedFramebuffer& fb, int old_xpos, int xpos, int width) {
+		draw_color_bar(fb, old_xpos, xpos, width);
+	} );
+	m.def("draw_rect", [](MappedFramebuffer& fb, uint32_t x, uint32_t y, uint32_t w, uint32_t h, RGB color) {
+		draw_rect(fb, x, y, w, h, color);
+	} );
+}
diff --git a/py/pykms/pyvid.cpp b/py/pykms/pyvid.cpp
new file mode 100644
index 0000000..01177d5
--- /dev/null
+++ b/py/pykms/pyvid.cpp
@@ -0,0 +1,38 @@
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+#include <kms++/kms++.h>
+#include <kms++util/kms++util.h>
+#include <kms++util/videodevice.h>
+
+namespace py = pybind11;
+
+using namespace kms;
+using namespace std;
+
+void init_pyvid(py::module &m)
+{
+	py::class_<VideoDevice, VideoDevice*>(m, "VideoDevice")
+			.def(py::init<const string&>())
+			.def_property_readonly("fd", &VideoDevice::fd)
+			.def_property_readonly("has_capture", &VideoDevice::has_capture)
+			.def_property_readonly("has_output", &VideoDevice::has_output)
+			.def_property_readonly("has_m2m", &VideoDevice::has_m2m)
+			.def_property_readonly("capture_streamer", &VideoDevice::get_capture_streamer)
+			.def_property_readonly("output_streamer", &VideoDevice::get_output_streamer)
+			.def_property_readonly("discrete_frame_sizes", &VideoDevice::get_discrete_frame_sizes)
+			.def_property_readonly("frame_sizes", &VideoDevice::get_frame_sizes)
+			.def("get_capture_devices", &VideoDevice::get_capture_devices)
+			;
+
+	py::class_<VideoStreamer, VideoStreamer*>(m, "VideoStreamer")
+			.def_property_readonly("fd", &VideoStreamer::fd)
+			.def_property_readonly("ports", &VideoStreamer::get_ports)
+			.def("set_port", &VideoStreamer::set_port)
+			.def_property_readonly("formats", &VideoStreamer::get_formats)
+			.def("set_format", &VideoStreamer::set_format)
+			.def("set_queue_size", &VideoStreamer::set_queue_size)
+			.def("queue", &VideoStreamer::queue)
+			.def("dequeue", &VideoStreamer::dequeue)
+			.def("stream_on", &VideoStreamer::stream_on)
+			;
+}
diff --git a/py/pykmsbase.cpp b/py/pykmsbase.cpp
deleted file mode 100644
index 5247158..0000000
--- a/py/pykmsbase.cpp
+++ /dev/null
@@ -1,157 +0,0 @@
-#include <pybind11/pybind11.h>
-#include <pybind11/stl.h>
-#include <kms++/kms++.h>
-
-namespace py = pybind11;
-
-using namespace kms;
-using namespace std;
-
-void init_pykmsbase(py::module &m)
-{
-	py::class_<Card>(m, "Card")
-			.def(py::init<>())
-			.def_property_readonly("fd", &Card::fd)
-			.def("get_first_connected_connector", &Card::get_first_connected_connector)
-			.def_property_readonly("connectors", &Card::get_connectors)
-			.def_property_readonly("crtcs", &Card::get_crtcs)
-			.def_property_readonly("encoders", &Card::get_encoders)
-			.def_property_readonly("planes", &Card::get_planes)
-			.def_property_readonly("has_atomic", &Card::has_atomic)
-			.def("call_page_flip_handlers", &Card::call_page_flip_handlers)
-			.def("get_prop", (Property* (Card::*)(uint32_t) const)&Card::get_prop)
-			;
-
-	py::class_<DrmObject, DrmObject*>(m, "DrmObject")
-			.def_property_readonly("id", &DrmObject::id)
-			.def_property_readonly("card", &DrmObject::card)
-			;
-
-	py::class_<DrmPropObject, DrmPropObject*>(m, "DrmPropObject", py::base<DrmObject>())
-			.def("refresh_props", &DrmPropObject::refresh_props)
-			.def_property_readonly("prop_map", &DrmPropObject::get_prop_map)
-			.def("get_prop_value", (uint64_t (DrmPropObject::*)(const string&) const)&DrmPropObject::get_prop_value)
-			.def("set_prop_value",(int (DrmPropObject::*)(const string&, uint64_t)) &DrmPropObject::set_prop_value)
-			.def("get_prop_value_as_blob", &DrmPropObject::get_prop_value_as_blob)
-			;
-
-	py::class_<Connector, Connector*>(m, "Connector",  py::base<DrmPropObject>())
-			.def_property_readonly("fullname", &Connector::fullname)
-			.def("get_default_mode", &Connector::get_default_mode)
-			.def("get_current_crtc", &Connector::get_current_crtc)
-			.def("get_possible_crtcs", &Connector::get_possible_crtcs)
-			.def("get_modes", &Connector::get_modes)
-			.def("get_mode", (Videomode (Connector::*)(const string& mode) const)&Connector::get_mode)
-			.def("get_mode", (Videomode (Connector::*)(unsigned xres, unsigned yres, float refresh, bool ilace) const)&Connector::get_mode)
-			.def("__repr__", [](const Connector& o) { return "<pykms.Connector " + to_string(o.id()) + ">"; })
-			;
-
-	py::class_<Crtc, Crtc*>(m, "Crtc",  py::base<DrmPropObject>())
-			.def("set_mode", &Crtc::set_mode)
-			.def("page_flip", &Crtc::page_flip)
-			.def("set_plane", &Crtc::set_plane)
-			.def_property_readonly("possible_planes", &Crtc::get_possible_planes)
-			.def_property_readonly("primary_plane", &Crtc::get_primary_plane)
-			.def_property_readonly("mode", &Crtc::mode)
-			.def_property_readonly("mode_valid", &Crtc::mode_valid)
-			.def("__repr__", [](const Crtc& o) { return "<pykms.Crtc " + to_string(o.id()) + ">"; })
-			;
-
-	py::class_<Encoder, Encoder*>(m, "Encoder",  py::base<DrmPropObject>())
-			;
-
-	py::class_<Plane, Plane*>(m, "Plane",  py::base<DrmPropObject>())
-			.def("supports_crtc", &Plane::supports_crtc)
-			.def_property_readonly("plane_type", &Plane::plane_type)
-			.def("__repr__", [](const Plane& o) { return "<pykms.Plane " + to_string(o.id()) + ">"; })
-			;
-
-	py::enum_<PlaneType>(m, "PlaneType")
-			.value("Overlay", PlaneType::Overlay)
-			.value("Primary", PlaneType::Primary)
-			.value("Cursor", PlaneType::Cursor)
-			;
-
-	py::class_<Property, Property*>(m, "Property",  py::base<DrmObject>())
-			.def_property_readonly("name", &Property::name)
-			;
-
-	py::class_<Blob>(m, "Blob", py::base<DrmObject>())
-			.def("__init__", [](Blob& instance, Card& card, py::buffer buf) {
-				py::buffer_info info = buf.request();
-				if (info.ndim != 1)
-					throw std::runtime_error("Incompatible buffer dimension!");
-
-				new (&instance) Blob(card, info.ptr, info.size * info.itemsize);
-			})
-			.def_property_readonly("data", &Blob::data)
-			;
-
-	py::class_<Framebuffer>(m, "Framebuffer", py::base<DrmObject>())
-			;
-
-	py::class_<MappedFramebuffer>(m, "MappedFramebuffer", py::base<Framebuffer>())
-			.def_property_readonly("width", &MappedFramebuffer::width)
-			.def_property_readonly("height", &MappedFramebuffer::height)
-			;
-
-	py::class_<DumbFramebuffer>(m, "DumbFramebuffer", py::base<MappedFramebuffer>())
-			.def(py::init<Card&, uint32_t, uint32_t, const string&>(),
-			     py::keep_alive<1, 2>())	// Keep Card alive until this is destructed
-			.def(py::init<Card&, uint32_t, uint32_t, PixelFormat>(),
-			     py::keep_alive<1, 2>())	// Keep Card alive until this is destructed
-			;
-
-	py::enum_<PixelFormat>(m, "PixelFormat")
-			.value("Undefined", PixelFormat::Undefined)
-
-			.value("NV12", PixelFormat::NV12)
-			.value("NV21", PixelFormat::NV21)
-
-			.value("UYVY", PixelFormat::UYVY)
-			.value("YUYV", PixelFormat::YUYV)
-			.value("YVYU", PixelFormat::YVYU)
-			.value("VYUY", PixelFormat::VYUY)
-
-			.value("XRGB8888", PixelFormat::XRGB8888)
-			.value("XBGR8888", PixelFormat::XBGR8888)
-			.value("ARGB8888", PixelFormat::ARGB8888)
-			.value("ABGR8888", PixelFormat::ABGR8888)
-
-			.value("RGB888", PixelFormat::RGB888)
-
-			.value("RGB565", PixelFormat::RGB565)
-			;
-
-	py::class_<Videomode>(m, "Videomode")
-			.def(py::init<>())
-
-			.def_readwrite("name", &Videomode::name)
-
-			.def_readwrite("clock", &Videomode::clock)
-
-			.def_readwrite("hdisplay", &Videomode::hdisplay)
-			.def_readwrite("hsync_start", &Videomode::hsync_start)
-			.def_readwrite("hsync_end", &Videomode::hsync_end)
-			.def_readwrite("htotal", &Videomode::htotal)
-
-			.def_readwrite("vdisplay", &Videomode::vdisplay)
-			.def_readwrite("vsync_start", &Videomode::vsync_start)
-			.def_readwrite("vsync_end", &Videomode::vsync_end)
-			.def_readwrite("vtotal", &Videomode::vtotal)
-
-			.def_readwrite("vrefresh", &Videomode::vrefresh)
-
-			.def_readwrite("flags", &Videomode::flags)
-			.def_readwrite("type", &Videomode::type)
-			;
-
-	py::class_<AtomicReq>(m, "AtomicReq")
-			.def(py::init<Card&>(),
-			     py::keep_alive<1, 2>())	// Keep Card alive until this is destructed
-			.def("add", (void (AtomicReq::*)(DrmPropObject*, const string&, uint64_t)) &AtomicReq::add)
-			.def("test", &AtomicReq::test, py::arg("allow_modeset") = false)
-			.def("commit", &AtomicReq::commit, py::arg("data"), py::arg("allow_modeset") = false)
-			.def("commit_sync", &AtomicReq::commit_sync, py::arg("allow_modeset") = false)
-			;
-}
diff --git a/py/pykmsomap.cpp b/py/pykmsomap.cpp
deleted file mode 100644
index 525834b..0000000
--- a/py/pykmsomap.cpp
+++ /dev/null
@@ -1,21 +0,0 @@
-#include <pybind11/pybind11.h>
-#include <pybind11/stl.h>
-#include <kms++/kms++.h>
-#include <kms++/omap/omapkms++.h>
-
-namespace py = pybind11;
-
-using namespace kms;
-using namespace std;
-
-void init_pykmsomap(py::module &m)
-{
-	py::class_<OmapCard>(m, "OmapCard", py::base<Card>())
-			.def(py::init<>())
-			;
-
-	py::class_<OmapFramebuffer>(m, "OmapFramebuffer", py::base<MappedFramebuffer>())
-			.def(py::init<OmapCard&, uint32_t, uint32_t, PixelFormat>(),
-			     py::keep_alive<1, 2>())	// Keep OmapCard alive until this is destructed
-			;
-}
diff --git a/py/pykmsutil.cpp b/py/pykmsutil.cpp
deleted file mode 100644
index b3b7594..0000000
--- a/py/pykmsutil.cpp
+++ /dev/null
@@ -1,49 +0,0 @@
-#include <pybind11/pybind11.h>
-#include <pybind11/stl.h>
-#include <kms++/kms++.h>
-#include <kms++util/kms++util.h>
-
-namespace py = pybind11;
-
-using namespace kms;
-using namespace std;
-
-void init_pykmstest(py::module &m)
-{
-	py::class_<RGB>(m, "RGB")
-			.def(py::init<>())
-			.def(py::init<uint8_t, uint8_t, uint8_t&>())
-			.def(py::init<uint8_t, uint8_t, uint8_t, uint8_t&>())
-			.def_property_readonly("rgb888", &RGB::rgb888)
-			.def_property_readonly("argb8888", &RGB::argb8888)
-			.def_property_readonly("abgr8888", &RGB::abgr8888)
-			.def_property_readonly("rgb565", &RGB::rgb565)
-			;
-
-	py::class_<ResourceManager>(m, "ResourceManager")
-			.def(py::init<Card&>())
-			.def("reset", &ResourceManager::reset)
-			.def("reserve_connector", &ResourceManager::reserve_connector,
-			     py::arg("name") = string())
-			.def("reserve_crtc", &ResourceManager::reserve_crtc)
-			.def("reserve_plane", &ResourceManager::reserve_plane,
-			     py::arg("crtc"),
-			     py::arg("type"),
-			     py::arg("format") = PixelFormat::Undefined)
-			.def("reserve_primary_plane", &ResourceManager::reserve_primary_plane,
-			     py::arg("crtc"),
-			     py::arg("format") = PixelFormat::Undefined)
-			.def("reserve_overlay_plane", &ResourceManager::reserve_overlay_plane,
-			     py::arg("crtc"),
-			     py::arg("format") = PixelFormat::Undefined)
-			;
-
-	// Use lambdas to handle IMappedFramebuffer
-	m.def("draw_test_pattern", [](MappedFramebuffer& fb) { draw_test_pattern(fb); } );
-	m.def("draw_color_bar", [](MappedFramebuffer& fb, int old_xpos, int xpos, int width) {
-		draw_color_bar(fb, old_xpos, xpos, width);
-	} );
-	m.def("draw_rect", [](MappedFramebuffer& fb, uint32_t x, uint32_t y, uint32_t w, uint32_t h, RGB color) {
-		draw_rect(fb, x, y, w, h, color);
-	} );
-}
diff --git a/py/pyvid.cpp b/py/pyvid.cpp
deleted file mode 100644
index 01177d5..0000000
--- a/py/pyvid.cpp
+++ /dev/null
@@ -1,38 +0,0 @@
-#include <pybind11/pybind11.h>
-#include <pybind11/stl.h>
-#include <kms++/kms++.h>
-#include <kms++util/kms++util.h>
-#include <kms++util/videodevice.h>
-
-namespace py = pybind11;
-
-using namespace kms;
-using namespace std;
-
-void init_pyvid(py::module &m)
-{
-	py::class_<VideoDevice, VideoDevice*>(m, "VideoDevice")
-			.def(py::init<const string&>())
-			.def_property_readonly("fd", &VideoDevice::fd)
-			.def_property_readonly("has_capture", &VideoDevice::has_capture)
-			.def_property_readonly("has_output", &VideoDevice::has_output)
-			.def_property_readonly("has_m2m", &VideoDevice::has_m2m)
-			.def_property_readonly("capture_streamer", &VideoDevice::get_capture_streamer)
-			.def_property_readonly("output_streamer", &VideoDevice::get_output_streamer)
-			.def_property_readonly("discrete_frame_sizes", &VideoDevice::get_discrete_frame_sizes)
-			.def_property_readonly("frame_sizes", &VideoDevice::get_frame_sizes)
-			.def("get_capture_devices", &VideoDevice::get_capture_devices)
-			;
-
-	py::class_<VideoStreamer, VideoStreamer*>(m, "VideoStreamer")
-			.def_property_readonly("fd", &VideoStreamer::fd)
-			.def_property_readonly("ports", &VideoStreamer::get_ports)
-			.def("set_port", &VideoStreamer::set_port)
-			.def_property_readonly("formats", &VideoStreamer::get_formats)
-			.def("set_format", &VideoStreamer::set_format)
-			.def("set_queue_size", &VideoStreamer::set_queue_size)
-			.def("queue", &VideoStreamer::queue)
-			.def("dequeue", &VideoStreamer::dequeue)
-			.def("stream_on", &VideoStreamer::stream_on)
-			;
-}
diff --git a/py/run.sh b/py/run.sh
deleted file mode 100755
index f0ead78..0000000
--- a/py/run.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-PYTHONPATH=build/py/ python3 $*
-
diff --git a/py/test.py b/py/test.py
deleted file mode 100755
index 9c23b5b..0000000
--- a/py/test.py
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/python3
-
-import pykms
-from helpers import *
-
-card = pykms.Card()
-res = pykms.ResourceManager(card)
-conn = res.reserve_connector()
-crtc = res.reserve_crtc(conn)
-
-mode = conn.get_default_mode()
-
-fb = pykms.DumbFramebuffer(card, mode.hdisplay, mode.vdisplay, "XR24");
-pykms.draw_test_pattern(fb);
-
-crtc.set_mode(conn, fb, mode)
-
-input("press enter to exit\n")
diff --git a/py/tests/CMakeLists.txt b/py/tests/CMakeLists.txt
new file mode 100644
index 0000000..a670ed9
--- /dev/null
+++ b/py/tests/CMakeLists.txt
@@ -0,0 +1,7 @@
+file(GLOB PY_SRCS "*.py")
+add_custom_target(pyextras SOURCES ${PY_SRCS})
+
+add_test(NAME pytest COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/functest.py")
+set_property(TEST pytest PROPERTY
+        ENVIRONMENT "PYTHONPATH=." "LD_LIBRARY_PATH=."
+)
diff --git a/py/tests/alpha-test.py b/py/tests/alpha-test.py
new file mode 100755
index 0000000..c6ec8ee
--- /dev/null
+++ b/py/tests/alpha-test.py
@@ -0,0 +1,67 @@
+#!/usr/bin/python3
+
+import pykms
+from helpers import *
+import time
+
+# This hack makes drm initialize the fbcon, setting up the default connector
+card = pykms.Card()
+card = 0
+
+card = pykms.Card()
+res = pykms.ResourceManager(card)
+conn = res.reserve_connector()
+crtc = res.reserve_crtc(conn)
+mode = conn.get_default_mode()
+
+planes = []
+for p in card.planes:
+    if p.supports_crtc(crtc) == False:
+        continue
+    planes.append(p)
+
+if len(planes) != 3:
+    print("Need 3 planes!")
+    exit(1)
+
+disable_planes(card)
+
+w = mode.hdisplay
+h = mode.vdisplay
+
+fbs=[]
+
+for i in range(len(planes)):
+    fbs.append(pykms.DumbFramebuffer(card, w, h, "AR24"))
+
+pykms.draw_rect(fbs[0], 50, 50, 200, 200, pykms.RGB(128, 255, 0, 0))
+pykms.draw_rect(fbs[1], 150, 50, 200, 200, pykms.RGB(128, 0, 255, 0))
+pykms.draw_rect(fbs[2], 50, 150, 200, 200, pykms.RGB(128, 0, 0, 255))
+
+
+set_props(crtc, {
+    "trans-key-mode": 0,
+    "trans-key": 0,
+    "background": 0,
+    "alpha_blender": 1,
+})
+
+for i in range(len(planes)):
+    plane = planes[i]
+    fb = fbs[i]
+
+    print("set crtc {}, plane {}, fb {}".format(crtc.id, p.id, fbs[i].id))
+
+    set_props(plane, {
+        "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,
+    })
+
+    time.sleep(1)
+
+input("press enter to exit\n")
diff --git a/py/tests/cam.py b/py/tests/cam.py
new file mode 100755
index 0000000..b44f8f9
--- /dev/null
+++ b/py/tests/cam.py
@@ -0,0 +1,78 @@
+#!/usr/bin/python3
+
+import sys
+import selectors
+import pykms
+from helpers import *
+
+
+w = 640
+h = 480
+fmt = pykms.PixelFormat.YUYV
+
+
+# This hack makes drm initialize the fbcon, setting up the default connector
+card = pykms.Card()
+card = 0
+
+card = pykms.Card()
+res = pykms.ResourceManager(card)
+conn = res.reserve_connector()
+crtc = res.reserve_crtc(conn)
+plane = res.reserve_overlay_plane(crtc, fmt)
+
+mode = conn.get_default_mode()
+
+NUM_BUFS = 5
+
+fbs = []
+for i in range(NUM_BUFS):
+    fb = pykms.DumbFramebuffer(card, w, h, fmt)
+    fbs.append(fb)
+
+vidpath = pykms.VideoDevice.get_capture_devices()[0]
+
+vid = pykms.VideoDevice(vidpath)
+cap = vid.capture_streamer
+cap.set_port(0)
+cap.set_format(fmt, w, h)
+cap.set_queue_size(NUM_BUFS)
+
+for fb in fbs:
+    cap.queue(fb)
+
+cap.stream_on()
+
+
+def readvid(conn, mask):
+    fb = cap.dequeue()
+
+    if card.has_atomic:
+        set_props(plane, {
+            "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,
+        })
+    else:
+        crtc.set_plane(plane, fb, 0, 0, fb.width, fb.height,
+            0, 0, fb.width, fb.height)
+
+    cap.queue(fb)
+
+def readkey(conn, mask):
+    #print("KEY EVENT");
+    sys.stdin.readline()
+    exit(0)
+
+sel = selectors.DefaultSelector()
+sel.register(cap.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)
diff --git a/py/tests/db.py b/py/tests/db.py
new file mode 100755
index 0000000..3ffb716
--- /dev/null
+++ b/py/tests/db.py
@@ -0,0 +1,72 @@
+#!/usr/bin/python3
+
+import sys
+import pykms
+import selectors
+from helpers import *
+
+bar_width = 20
+bar_speed = 8
+
+class FlipHandler(pykms.PageFlipHandlerBase):
+    def __init__(self):
+        super().__init__()
+        self.bar_xpos = 0
+        self.front_buf = 0
+        self.fb1 = pykms.DumbFramebuffer(card, mode.hdisplay, mode.vdisplay, "XR24");
+        self.fb2 = pykms.DumbFramebuffer(card, mode.hdisplay, mode.vdisplay, "XR24");
+
+    def handle_page_flip(self, frame, time):
+        if self.front_buf == 0:
+            fb = self.fb2
+        else:
+            fb = self.fb1
+
+        self.front_buf = self.front_buf ^ 1
+
+        current_xpos = self.bar_xpos;
+        old_xpos = (current_xpos + (fb.width - bar_width - bar_speed)) % (fb.width - bar_width);
+        new_xpos = (current_xpos + bar_speed) % (fb.width - bar_width);
+
+        self.bar_xpos = new_xpos
+
+        pykms.draw_color_bar(fb, old_xpos, new_xpos, bar_width)
+
+        if card.has_atomic:
+            ctx = pykms.AtomicReq(card)
+            ctx.add(crtc.primary_plane, "FB_ID", fb.id)
+            ctx.commit(self)
+        else:
+            crtc.page_flip(fb, self)
+
+
+card = pykms.Card()
+res = pykms.ResourceManager(card)
+conn = res.reserve_connector()
+crtc = res.reserve_crtc(conn)
+mode = conn.get_default_mode()
+
+fliphandler = FlipHandler()
+
+crtc.set_mode(conn, fliphandler.fb1, mode)
+
+fliphandler.handle_page_flip(0, 0)
+
+def readdrm(conn, mask):
+    #print("EVENT");
+    card.call_page_flip_handlers()
+
+def readkey(conn, mask):
+    #print("KEY EVENT");
+    sys.stdin.readline()
+    exit(0)
+
+sel = selectors.DefaultSelector()
+sel.register(card.fd, selectors.EVENT_READ, readdrm)
+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)
diff --git a/py/tests/functest.py b/py/tests/functest.py
new file mode 100755
index 0000000..44c29fb
--- /dev/null
+++ b/py/tests/functest.py
@@ -0,0 +1,19 @@
+#!/usr/bin/python3
+
+import pykms
+from helpers import *
+
+card = pykms.Card()
+res = pykms.ResourceManager(card)
+conn = res.reserve_connector()
+crtc = res.reserve_crtc(conn)
+
+mode = conn.get_default_mode()
+
+fb = pykms.DumbFramebuffer(card, mode.hdisplay, mode.vdisplay, "XR24");
+pykms.draw_test_pattern(fb);
+
+crtc.set_mode(conn, fb, mode)
+
+print("OK")
+
diff --git a/py/tests/gamma.py b/py/tests/gamma.py
new file mode 100755
index 0000000..a6b68cc
--- /dev/null
+++ b/py/tests/gamma.py
@@ -0,0 +1,41 @@
+#!/usr/bin/python3
+
+import pykms
+from helpers import *
+
+# This hack makes drm initialize the fbcon, setting up the default connector
+card = pykms.Card()
+card = 0
+
+card = pykms.Card()
+res = pykms.ResourceManager(card)
+conn = res.reserve_connector()
+crtc = res.reserve_crtc(conn)
+mode = conn.get_default_mode()
+
+fb = pykms.DumbFramebuffer(card, mode.hdisplay, mode.vdisplay, "XR24");
+pykms.draw_test_pattern(fb);
+
+crtc.set_mode(conn, fb, mode)
+
+len=256
+arr = bytearray(len*2*4)
+view = memoryview(arr).cast("H")
+
+for i in range(len):
+    g = round(65535 * pow(i / float(len), 1 / 2.2))
+
+    view[i * 4 + 0] = g
+    view[i * 4 + 1] = g
+    view[i * 4 + 2] = g
+    view[i * 4 + 3] = 0
+
+gamma = pykms.Blob(card, arr);
+
+set_prop(crtc, "GAMMA_LUT", gamma.id)
+
+input("press enter to remove gamma\n")
+
+set_prop(crtc, "GAMMA_LUT", 0)
+
+input("press enter to exit\n")
diff --git a/py/tests/helpers.py b/py/tests/helpers.py
new file mode 100644
index 0000000..fd67d41
--- /dev/null
+++ b/py/tests/helpers.py
@@ -0,0 +1,54 @@
+import pykms
+
+def add_props(areq, ob, map):
+    for key, value in map.items():
+        areq.add(ob, key, value)
+
+def props(o):
+    o.refresh_props()
+    map = o.prop_map
+    for propid,propval in map.items():
+        prop = o.card.get_prop(propid)
+        print("%-15s %d (%#x)" % (prop.name, propval, propval))
+
+def set_prop(ob, prop, value):
+    if ob.card.has_atomic:
+        areq = pykms.AtomicReq(ob.card)
+        areq.add(ob, prop, value)
+        if areq.commit_sync() != 0:
+            print("commit failed")
+    else:
+        if ob.set_prop_value(prop, value) != 0:
+            print("setting property failed")
+
+def set_props(ob, map):
+    if ob.card.has_atomic:
+        areq = pykms.AtomicReq(ob.card)
+
+        for key, value in map.items():
+            areq.add(ob, key, value)
+
+        if areq.commit_sync() != 0:
+            print("commit failed")
+    else:
+        for propid,propval in map.items():
+            if ob.set_prop_value(propid, propval) != 0:
+                print("setting property failed")
+
+red = pykms.RGB(255, 0, 0)
+green = pykms.RGB(0, 255, 0)
+blue = pykms.RGB(0, 0, 255)
+yellow = pykms.RGB(255, 255, 0)
+purple = pykms.RGB(255, 0, 255)
+white = pykms.RGB(255, 255, 255)
+cyan = pykms.RGB(0, 255, 255)
+
+def disable_planes(card):
+    areq = pykms.AtomicReq(card)
+
+    for p in card.planes:
+        areq.add(p, "FB_ID", 0)
+        areq.add(p, "CRTC_ID", 0)
+
+    if areq.commit_sync() != 0:
+        print("disabling planes failed")
diff --git a/py/tests/iact.py b/py/tests/iact.py
new file mode 100755
index 0000000..fecd899
--- /dev/null
+++ b/py/tests/iact.py
@@ -0,0 +1,43 @@
+#!/usr/bin/python3 -i
+
+# This is a base script for interactive kms++ python environment
+
+import pykms
+from time import sleep
+from math import sin
+from math import cos
+from helpers import *
+
+card = pykms.Card()
+res = pykms.ResourceManager(card)
+conn = res.reserve_connector()
+crtc = res.reserve_crtc(conn)
+
+mode = conn.get_default_mode()
+
+fb = pykms.DumbFramebuffer(card, 200, 200, "XR24");
+pykms.draw_test_pattern(fb);
+
+#crtc.set_mode(conn, fb, mode)
+
+i = 0
+for p in card.planes:
+    globals()["plane"+str(i)] = p
+    i=i+1
+
+i = 0
+for c in card.crtcs:
+    globals()["crtc"+str(i)] = c
+    i=i+1
+
+for p in crtc.possible_planes:
+    if p.plane_type == pykms.PlaneType.Overlay:
+        plane = p
+        break
+
+def set_plane(x, y):
+    crtc.set_plane(plane, fb, x, y, fb.width, fb.height, 0, 0, fb.width, fb.height)
+
+set_plane(0, 0)
+
+# for x in range(0, crtc.width() - fb.width()): set_plane(x, int((sin(x/50) + 1) * 100)); sleep(0.01)
diff --git a/py/tests/kmsmodeview.py b/py/tests/kmsmodeview.py
new file mode 100755
index 0000000..355db02
--- /dev/null
+++ b/py/tests/kmsmodeview.py
@@ -0,0 +1,317 @@
+#!/usr/bin/python3
+
+import urwid
+import pykms
+
+def exit_on_q(key):
+	if key in ('q', 'Q'):
+		raise urwid.ExitMainLoop()
+	elif key == 'a':
+		apply_mode()
+
+alarm_handle = None
+
+def recalc_info(l, d):
+	global alarm_handle
+
+	alarm_handle = None
+
+	for w in recalc_list:
+		w.recalc()
+
+def div_or_zero(n, d):
+	if d == 0:
+		return 0
+	else:
+		return n / d
+
+class MyIntEdit(urwid.IntEdit):
+	_metaclass_ = urwid.signals.MetaSignals
+	signals = ['value_change']
+
+	def __init__(self, caption, calc=None):
+		self._myval = 0
+		self._disable_change = False
+		self._calc = calc
+		self._updlist = None
+
+		super().__init__(caption, 0)
+
+	def set_edit_text(self, text):
+		global alarm_handle
+
+		super().set_edit_text(text)
+		newtext = super().get_edit_text()
+		new_val = int(newtext) if newtext != "" else 0
+		if new_val != self._myval:
+			self._myval = new_val
+			if not self._disable_change:
+				urwid.emit_signal(self, 'value_change', self, self._myval)
+
+				if alarm_handle == None:
+					alarm_handle = loop.set_alarm_in(0, recalc_info)
+
+				if self._updlist != None:
+					for w in self._updlist:
+						w.recalc()
+
+	def recalc(self):
+		self._disable_change = True
+		self.set_val(self._calc())
+		self._disable_change = False
+
+	def set_val(self, val):
+		self.set_edit_text(str(int(val)))
+
+	def get_val(self):
+		return self._myval
+
+	def set_updlist(self, list):
+		self._updlist = list
+
+	def keypress(self, size, key):
+		if key == '+':
+			self.set_edit_text(str(self.value() + 1))
+		elif key == '-':
+			self.set_edit_text(str(self.value() - 1))
+		else:
+			return super().keypress(size, key)
+
+class MyIntText(urwid.Text):
+	def __init__(self, fmt, calc=None):
+		super().__init__("")
+		self._fmt = fmt
+		self._calc = calc
+
+	def recalc(self):
+		val = self._calc()
+		super().set_text(self._fmt.format(val))
+
+def khz_to_ps(khz):
+	if khz == 0:
+		return 0
+	else:
+		return 1.0 / khz * 1000 * 1000 * 1000
+
+def khz_to_us(khz):
+	if khz == 0:
+		return 0
+	else:
+		return 1.0 / khz * 1000
+
+pclk_khz_widget = MyIntEdit(u"pclk (kHz) ")
+pclk_ps_widget = MyIntText(fmt="pclk {:.2f} ps", calc = lambda: khz_to_ps(pclk_khz_widget.get_val()))
+
+pclk_widgets = [pclk_khz_widget, pclk_ps_widget]
+
+pclk_columns = urwid.LineBox(urwid.Columns(pclk_widgets), title = "Pixel clock")
+
+# Horizontal widgets
+
+hdisp_widget = MyIntEdit(u"hdisp ", calc = lambda: hdisp2_widget.get_val())
+hfp_widget = MyIntEdit(u"hfp   ", calc = lambda: hss_widget.get_val() - hdisp_widget.get_val())
+hsw_widget = MyIntEdit(u"hsw   ", calc = lambda: hse_widget.get_val() - hss_widget.get_val())
+hbp_widget = MyIntEdit(u"hbp   ", calc = lambda: htot_widget.get_val() - hse_widget.get_val())
+
+hdisp2_widget = MyIntEdit(u"hdisp ", calc = lambda: hdisp_widget.get_val())
+hss_widget = MyIntEdit(u"hss   ",
+	calc = lambda: hdisp_widget.get_val() + hfp_widget.get_val())
+hse_widget = MyIntEdit(u"hse   ",
+	calc = lambda: hdisp_widget.get_val() + hfp_widget.get_val() + hsw_widget.get_val())
+htot_widget = MyIntEdit(u"htot  ",
+	calc = lambda: hdisp_widget.get_val() + hfp_widget.get_val() + hsw_widget.get_val() + hbp_widget.get_val())
+
+hwidgets1 = [hdisp_widget, hfp_widget, hsw_widget, hbp_widget]
+hwidgets2 = [hdisp2_widget, hss_widget, hse_widget, htot_widget]
+
+horiz_pile1 = urwid.Pile(hwidgets1)
+horiz_pile2 = urwid.Pile(hwidgets2)
+
+h_columns = urwid.LineBox(urwid.Columns([(15, horiz_pile1), (15, horiz_pile2)]), title = "Horizontal")
+
+# Vertical columns
+
+vdisp_widget = MyIntEdit(u"vdisp ", calc = lambda: vdisp2_widget.get_val())
+vfp_widget = MyIntEdit(u"vfp   ", calc = lambda: vss_widget.get_val() - vdisp_widget.get_val())
+vsw_widget = MyIntEdit(u"vsw   ", calc = lambda: vse_widget.get_val() - vss_widget.get_val())
+vbp_widget = MyIntEdit(u"vbp   ", calc = lambda: vtot_widget.get_val() - vse_widget.get_val())
+
+vdisp2_widget = MyIntEdit(u"vdisp ", calc = lambda: vdisp_widget.get_val())
+vss_widget = MyIntEdit(u"vss   ",
+	calc = lambda: vdisp_widget.get_val() + vfp_widget.get_val())
+vse_widget = MyIntEdit(u"vse   ",
+	calc = lambda: vdisp_widget.get_val() + vfp_widget.get_val() + vsw_widget.get_val())
+vtot_widget = MyIntEdit(u"vtot  ",
+	calc = lambda: vdisp_widget.get_val() + vfp_widget.get_val() + vsw_widget.get_val() + vbp_widget.get_val())
+
+vwidgets1 = [vdisp_widget, vfp_widget, vsw_widget, vbp_widget]
+vwidgets2 = [vdisp2_widget, vss_widget, vse_widget, vtot_widget]
+
+vert_pile1 = urwid.Pile(vwidgets1)
+vert_pile2 = urwid.Pile(vwidgets2)
+
+v_columns = urwid.LineBox(urwid.Columns([(15, vert_pile1), (15, vert_pile2)]), title = "Vertical")
+
+# Info widgets
+
+line_us_widget = MyIntText(fmt="line {:.2f} us",
+	calc = lambda: khz_to_us(pclk_khz_widget.get_val()) * htot_widget.get_val())
+line_khz_widget = MyIntText(fmt="line {:.2f} kHz",
+	calc = lambda: div_or_zero(pclk_khz_widget.get_val(), htot_widget.get_val()))
+
+frame_tot_widget = MyIntText(fmt="tot {} pix",
+	calc = lambda: htot_widget.get_val() * vtot_widget.get_val())
+frame_us_widget = MyIntText(fmt="frame {:.2f} ms",
+	calc = lambda: khz_to_us(pclk_khz_widget.get_val()) * htot_widget.get_val() * vtot_widget.get_val() / 1000)
+frame_khz_widget = MyIntText(fmt="frame {:.2f} Hz",
+	calc = lambda: div_or_zero(pclk_khz_widget.get_val() * 1000,  htot_widget.get_val() * vtot_widget.get_val()))
+
+info_box = urwid.LineBox(urwid.Pile([line_us_widget, line_khz_widget, urwid.Divider(), frame_tot_widget, frame_us_widget, frame_khz_widget]), title = "Info")
+
+# Set update lists
+
+recalc_list = [ pclk_ps_widget, line_us_widget, line_khz_widget, frame_tot_widget, frame_us_widget, frame_khz_widget ]
+
+hdisp_widget.set_updlist([hdisp2_widget, hss_widget, hse_widget, htot_widget])
+hfp_widget.set_updlist([hss_widget, hse_widget, htot_widget])
+hsw_widget.set_updlist([hse_widget, htot_widget])
+hbp_widget.set_updlist([htot_widget])
+hdisp2_widget.set_updlist([hdisp_widget, hfp_widget])
+hss_widget.set_updlist([hfp_widget, hsw_widget])
+hse_widget.set_updlist([hsw_widget, hbp_widget])
+htot_widget.set_updlist([hbp_widget])
+
+vdisp_widget.set_updlist([vdisp2_widget, vss_widget, vse_widget, vtot_widget])
+vfp_widget.set_updlist([vss_widget, vse_widget, vtot_widget])
+vsw_widget.set_updlist([vse_widget, vtot_widget])
+vbp_widget.set_updlist([vtot_widget])
+vdisp2_widget.set_updlist([vdisp_widget, vfp_widget])
+vss_widget.set_updlist([vfp_widget, vsw_widget])
+vse_widget.set_updlist([vsw_widget, vbp_widget])
+vtot_widget.set_updlist([vbp_widget])
+
+# Flags
+
+fb = None
+
+DRM_MODE_FLAG_PHSYNC = (1<<0)
+DRM_MODE_FLAG_NHSYNC = (1<<1)
+DRM_MODE_FLAG_PVSYNC = (1<<2)
+DRM_MODE_FLAG_NVSYNC = (1<<3)
+DRM_MODE_FLAG_INTERLACE = (1<<4)
+DRM_MODE_FLAG_DBLCLK = (1<<12)
+
+def mode_is_ilace(mode):
+	return (mode.flags & DRM_MODE_FLAG_INTERLACE) != 0
+
+def apply_mode():
+	global fb
+
+	mode = pykms.Videomode()
+	mode.clock = pclk_khz_widget.get_val()
+
+	mode.hdisplay = hdisp2_widget.get_val()
+	mode.hsync_start = hss_widget.get_val()
+	mode.hsync_end = hse_widget.get_val()
+	mode.htotal = htot_widget.get_val()
+
+	mode.vdisplay = vdisp2_widget.get_val()
+	mode.vsync_start = vss_widget.get_val()
+	mode.vsync_end = vse_widget.get_val()
+	mode.vtotal = vtot_widget.get_val()
+
+	if ilace_box.state:
+		mode.flags |= DRM_MODE_FLAG_INTERLACE
+
+	if dblclk_box.state:
+		mode.flags |= DRM_MODE_FLAG_DBLCLK
+
+	if hsync_pol.state == True:
+		mode.flags |= DRM_MODE_FLAG_PHSYNC
+	elif hsync_pol.state == False:
+		mode.flags |= DRM_MODE_FLAG_NHSYNC
+
+	if vsync_pol.state == True:
+		mode.flags |= DRM_MODE_FLAG_PVSYNC
+	elif vsync_pol.state == False:
+		mode.flags |= DRM_MODE_FLAG_NVSYNC
+
+	fb = pykms.DumbFramebuffer(card, mode.hdisplay, mode.vdisplay, "XR24");
+	pykms.draw_test_pattern(fb);
+
+	crtc.set_mode(conn, fb, mode)
+
+def read_mode(mode):
+	pclk_khz_widget.set_val(mode.clock)
+	hdisp2_widget.set_val(mode.hdisplay)
+	hss_widget.set_val(mode.hsync_start)
+	hse_widget.set_val(mode.hsync_end)
+	htot_widget.set_val(mode.htotal)
+
+	vdisp2_widget.set_val(mode.vdisplay)
+	vss_widget.set_val(mode.vsync_start)
+	vse_widget.set_val(mode.vsync_end)
+	vtot_widget.set_val(mode.vtotal)
+
+	ilace_box.set_state(mode_is_ilace(mode))
+	dblclk_box.set_state((mode.flags & DRM_MODE_FLAG_DBLCLK) != 0)
+
+	sync = 'mixed'
+	if (mode.flags & DRM_MODE_FLAG_PHSYNC) != 0:
+		sync = True
+	elif (mode.flags & DRM_MODE_FLAG_NHSYNC) != 0:
+		sync = False
+	hsync_pol.set_state(sync)
+
+	sync = 'mixed'
+	if (mode.flags & DRM_MODE_FLAG_PVSYNC) != 0:
+		sync = True
+	elif (mode.flags & DRM_MODE_FLAG_NVSYNC) != 0:
+		sync = False
+	vsync_pol.set_state(sync)
+
+def apply_press(w):
+	apply_mode()
+
+ilace_box = urwid.CheckBox('interlace')
+hsync_pol = urwid.CheckBox('hsync positive', has_mixed=True)
+vsync_pol = urwid.CheckBox('vsync positive', has_mixed=True)
+dblclk_box = urwid.CheckBox('double clock')
+
+flags_pile = urwid.LineBox(urwid.Pile([ilace_box, hsync_pol, vsync_pol, dblclk_box]), title = "Flags")
+
+apply_button = urwid.LineBox(urwid.Padding(urwid.Button('apply', on_press=apply_press)))
+
+# Main
+
+def mode_press(w, mode):
+	read_mode(mode)
+
+def mode_to_str(mode):
+	return "{}@{}{}".format(mode.name, mode.vrefresh, "i" if mode_is_ilace(mode) else "")
+
+mode_buttons = []
+
+card = pykms.Card()
+conn = card.get_first_connected_connector()
+crtc = conn.get_current_crtc()
+modes = conn.get_modes()
+i = 0
+for m in modes:
+	mode_buttons.append(urwid.Button(mode_to_str(m), on_press=mode_press, user_data=m))
+	i += 1
+
+modes_pile = urwid.LineBox(urwid.Pile(mode_buttons), title = "Video modes")
+
+main_pile = urwid.Pile([modes_pile, pclk_columns, urwid.Columns([ h_columns, v_columns ]), info_box, flags_pile, apply_button])
+
+main_columns = urwid.Filler(main_pile, valign='top')
+
+loop = urwid.MainLoop(main_columns, unhandled_input=exit_on_q, handle_mouse=False)
+
+# select the first mode
+mode_press(None, modes[0])
+
+loop.run()
+
+fb = None
diff --git a/py/tests/test.py b/py/tests/test.py
new file mode 100755
index 0000000..9c23b5b
--- /dev/null
+++ b/py/tests/test.py
@@ -0,0 +1,18 @@
+#!/usr/bin/python3
+
+import pykms
+from helpers import *
+
+card = pykms.Card()
+res = pykms.ResourceManager(card)
+conn = res.reserve_connector()
+crtc = res.reserve_crtc(conn)
+
+mode = conn.get_default_mode()
+
+fb = pykms.DumbFramebuffer(card, mode.hdisplay, mode.vdisplay, "XR24");
+pykms.draw_test_pattern(fb);
+
+crtc.set_mode(conn, fb, mode)
+
+input("press enter to exit\n")
diff --git a/py/tests/trans-test.py b/py/tests/trans-test.py
new file mode 100755
index 0000000..8c1f964
--- /dev/null
+++ b/py/tests/trans-test.py
@@ -0,0 +1,332 @@
+#!/usr/bin/python3
+
+import pykms
+from helpers import *
+import time
+
+# This hack makes drm initialize the fbcon, setting up the default connector
+card = pykms.Card()
+card = 0
+
+card = pykms.Card()
+res = pykms.ResourceManager(card)
+conn = res.reserve_connector()
+crtc = res.reserve_crtc(conn)
+mode = conn.get_default_mode()
+
+planes = []
+for p in card.planes:
+    if p.supports_crtc(crtc) == False:
+        continue
+    planes.append(p)
+
+disable_planes(card)
+
+w = mode.hdisplay
+h = mode.vdisplay
+
+fbs=[]
+
+def test_am5_trans_dest():
+    fbs.append(pykms.DumbFramebuffer(card, w, h, "XR24"))
+    fbs.append(pykms.DumbFramebuffer(card, w, h, "XR24"))
+
+    fb = fbs[0]
+    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, purple)
+    pykms.draw_rect(fb, 100, 100, 100, 200, green)
+    pykms.draw_rect(fb, 300, 100, 100, 200, red)
+    pykms.draw_rect(fb, 500, 100, 100, 200, white)
+
+    fb = fbs[1]
+    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, cyan)
+    pykms.draw_rect(fb, 250, 100, 200, 200, yellow)
+
+    set_props(crtc, {
+        "trans-key-mode": 1,
+        "trans-key": purple.rgb888,
+        "background": 0,
+        "alpha_blender": 0,
+    })
+
+    plane = 0
+
+    for i in range(0,2):
+        print("set crtc {}, plane {}, fb {}".format(crtc.id, planes[i].id, fbs[i].id))
+
+        plane = planes[i]
+        fb = fbs[i]
+        set_props(plane, {
+            "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,
+        })
+
+        time.sleep(1)
+
+def test_am5_trans_src():
+    fbs.append(pykms.DumbFramebuffer(card, w, h, "XR24"))
+    fbs.append(pykms.DumbFramebuffer(card, w, h, "XR24"))
+
+    fb = fbs[0]
+    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, white)
+    pykms.draw_rect(fb, 200, 200, 100, 100, red)
+    pykms.draw_rect(fb, fb.width - 300, 200, 100, 100, green)
+
+    fb = fbs[1]
+    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, cyan)
+    pykms.draw_rect(fb, 100, 100, 500, 500, purple)
+
+    set_props(crtc, {
+        "trans-key-mode": 2,
+        "trans-key": purple.rgb888,
+        "background": 0,
+        "alpha_blender": 0,
+    })
+
+    plane = 0
+
+    for i in range(0,2):
+        print("set crtc {}, plane {}, fb {}".format(crtc.id, planes[i].id, fbs[i].id))
+
+        plane = planes[i]
+        fb = fbs[i]
+        set_props(plane, {
+            "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,
+        })
+
+        time.sleep(1)
+
+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"))
+
+    fb = fbs[0]
+    pykms.draw_rect(fb, 0, 0, w, h, purple)
+    pykms.draw_rect(fb, 100, 50, 50, 200, green)
+    pykms.draw_rect(fb, 200, 50, 50, 200, red)
+    pykms.draw_rect(fb, 300, 50, 50, 200, white)
+
+    fb = fbs[1]
+    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, blue)
+
+    fb = fbs[2]
+    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, cyan)
+
+    set_props(crtc, {
+        "trans-key-mode": 1,
+        "trans-key": purple.rgb888,
+        "background": 0,
+        "alpha_blender": 0,
+    })
+
+    time.sleep(1)
+
+    plane = planes[0]
+    fb = fbs[0]
+    set_props(plane, {
+        "FB_ID": fb.id,
+        "CRTC_ID": crtc.id,
+        "SRC_W": fb.width << 16,
+        "SRC_H": fb.height << 16,
+        "CRTC_W": w,
+        "CRTC_H": h,
+    })
+
+    time.sleep(1)
+
+    plane = planes[1]
+    fb = fbs[1]
+    set_props(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,
+    })
+
+    time.sleep(1)
+
+    plane = planes[2]
+    fb = fbs[2]
+    set_props(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": w // 3,
+        "CRTC_Y": 0,
+        "CRTC_W": fb.width,
+        "CRTC_H": fb.height,
+    })
+
+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"))
+
+    fb = fbs[0]
+    pykms.draw_rect(fb, 0, 0, w, h, pykms.RGB(128, 255, 255))
+    pykms.draw_rect(fb, 200, 100, 50, 200, red)
+    pykms.draw_rect(fb, w - 200 - 50, 100, 50, 200, green)
+
+    fb = fbs[1]
+    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, blue)
+    pykms.draw_rect(fb, 100, 100, fb.width - 200, fb.height - 200, purple)
+
+    fb = fbs[2]
+    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, cyan)
+    pykms.draw_rect(fb, 100, 100, fb.width - 200, fb.height - 200, purple)
+
+    set_props(crtc, {
+        "trans-key-mode": 2,
+        "trans-key": purple.rgb888,
+        "background": 0,
+        "alpha_blender": 0,
+    })
+
+    time.sleep(1)
+
+    plane = planes[0]
+    fb = fbs[0]
+    set_props(plane, {
+        "FB_ID": fb.id,
+        "CRTC_ID": crtc.id,
+        "SRC_W": fb.width << 16,
+        "SRC_H": fb.height << 16,
+        "CRTC_W": w,
+        "CRTC_H": h,
+    })
+
+    time.sleep(1)
+
+    plane = planes[1]
+    fb = fbs[1]
+    set_props(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,
+    })
+
+    time.sleep(1)
+
+    plane = planes[2]
+    fb = fbs[2]
+    set_props(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": w - fb.width,
+        "CRTC_Y": 0,
+        "CRTC_W": fb.width,
+        "CRTC_H": fb.height,
+    })
+
+def test_am4_alpha_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"))
+
+    fb = fbs[0]
+    pykms.draw_rect(fb, 0, 0, w, h, purple)
+    pykms.draw_rect(fb, 200, 100, 50, 200, red)
+    pykms.draw_rect(fb, w - 200 - 50, 100, 50, 200, green)
+
+    fb = fbs[1]
+    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, blue)
+    pykms.draw_rect(fb, 100, 100, fb.width - 200, fb.height - 200, purple)
+
+    fb = fbs[2]
+    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, cyan)
+    pykms.draw_rect(fb, 100, 100, fb.width - 200, fb.height - 200, purple)
+
+    set_props(crtc, {
+        "trans-key-mode": 1,
+        "trans-key": purple.rgb888,
+        "background": 0,
+        "alpha_blender": 1,
+    })
+
+    time.sleep(1)
+
+    plane = planes[0]
+    fb = fbs[0]
+    set_props(plane, {
+        "FB_ID": fb.id,
+        "CRTC_ID": crtc.id,
+        "SRC_W": fb.width << 16,
+        "SRC_H": fb.height << 16,
+        "CRTC_W": w,
+        "CRTC_H": h,
+    })
+
+    time.sleep(1)
+
+    plane = planes[1]
+    fb = fbs[1]
+    set_props(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,
+    })
+
+    time.sleep(1)
+
+    plane = planes[2]
+    fb = fbs[2]
+    set_props(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": w - fb.width,
+        "CRTC_Y": 0,
+        "CRTC_W": fb.width,
+        "CRTC_H": fb.height,
+    })
+
+
+
+#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")
diff --git a/py/trans-test.py b/py/trans-test.py
deleted file mode 100755
index 8c1f964..0000000
--- a/py/trans-test.py
+++ /dev/null
@@ -1,332 +0,0 @@
-#!/usr/bin/python3
-
-import pykms
-from helpers import *
-import time
-
-# This hack makes drm initialize the fbcon, setting up the default connector
-card = pykms.Card()
-card = 0
-
-card = pykms.Card()
-res = pykms.ResourceManager(card)
-conn = res.reserve_connector()
-crtc = res.reserve_crtc(conn)
-mode = conn.get_default_mode()
-
-planes = []
-for p in card.planes:
-    if p.supports_crtc(crtc) == False:
-        continue
-    planes.append(p)
-
-disable_planes(card)
-
-w = mode.hdisplay
-h = mode.vdisplay
-
-fbs=[]
-
-def test_am5_trans_dest():
-    fbs.append(pykms.DumbFramebuffer(card, w, h, "XR24"))
-    fbs.append(pykms.DumbFramebuffer(card, w, h, "XR24"))
-
-    fb = fbs[0]
-    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, purple)
-    pykms.draw_rect(fb, 100, 100, 100, 200, green)
-    pykms.draw_rect(fb, 300, 100, 100, 200, red)
-    pykms.draw_rect(fb, 500, 100, 100, 200, white)
-
-    fb = fbs[1]
-    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, cyan)
-    pykms.draw_rect(fb, 250, 100, 200, 200, yellow)
-
-    set_props(crtc, {
-        "trans-key-mode": 1,
-        "trans-key": purple.rgb888,
-        "background": 0,
-        "alpha_blender": 0,
-    })
-
-    plane = 0
-
-    for i in range(0,2):
-        print("set crtc {}, plane {}, fb {}".format(crtc.id, planes[i].id, fbs[i].id))
-
-        plane = planes[i]
-        fb = fbs[i]
-        set_props(plane, {
-            "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,
-        })
-
-        time.sleep(1)
-
-def test_am5_trans_src():
-    fbs.append(pykms.DumbFramebuffer(card, w, h, "XR24"))
-    fbs.append(pykms.DumbFramebuffer(card, w, h, "XR24"))
-
-    fb = fbs[0]
-    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, white)
-    pykms.draw_rect(fb, 200, 200, 100, 100, red)
-    pykms.draw_rect(fb, fb.width - 300, 200, 100, 100, green)
-
-    fb = fbs[1]
-    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, cyan)
-    pykms.draw_rect(fb, 100, 100, 500, 500, purple)
-
-    set_props(crtc, {
-        "trans-key-mode": 2,
-        "trans-key": purple.rgb888,
-        "background": 0,
-        "alpha_blender": 0,
-    })
-
-    plane = 0
-
-    for i in range(0,2):
-        print("set crtc {}, plane {}, fb {}".format(crtc.id, planes[i].id, fbs[i].id))
-
-        plane = planes[i]
-        fb = fbs[i]
-        set_props(plane, {
-            "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,
-        })
-
-        time.sleep(1)
-
-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"))
-
-    fb = fbs[0]
-    pykms.draw_rect(fb, 0, 0, w, h, purple)
-    pykms.draw_rect(fb, 100, 50, 50, 200, green)
-    pykms.draw_rect(fb, 200, 50, 50, 200, red)
-    pykms.draw_rect(fb, 300, 50, 50, 200, white)
-
-    fb = fbs[1]
-    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, blue)
-
-    fb = fbs[2]
-    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, cyan)
-
-    set_props(crtc, {
-        "trans-key-mode": 1,
-        "trans-key": purple.rgb888,
-        "background": 0,
-        "alpha_blender": 0,
-    })
-
-    time.sleep(1)
-
-    plane = planes[0]
-    fb = fbs[0]
-    set_props(plane, {
-        "FB_ID": fb.id,
-        "CRTC_ID": crtc.id,
-        "SRC_W": fb.width << 16,
-        "SRC_H": fb.height << 16,
-        "CRTC_W": w,
-        "CRTC_H": h,
-    })
-
-    time.sleep(1)
-
-    plane = planes[1]
-    fb = fbs[1]
-    set_props(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,
-    })
-
-    time.sleep(1)
-
-    plane = planes[2]
-    fb = fbs[2]
-    set_props(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": w // 3,
-        "CRTC_Y": 0,
-        "CRTC_W": fb.width,
-        "CRTC_H": fb.height,
-    })
-
-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"))
-
-    fb = fbs[0]
-    pykms.draw_rect(fb, 0, 0, w, h, pykms.RGB(128, 255, 255))
-    pykms.draw_rect(fb, 200, 100, 50, 200, red)
-    pykms.draw_rect(fb, w - 200 - 50, 100, 50, 200, green)
-
-    fb = fbs[1]
-    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, blue)
-    pykms.draw_rect(fb, 100, 100, fb.width - 200, fb.height - 200, purple)
-
-    fb = fbs[2]
-    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, cyan)
-    pykms.draw_rect(fb, 100, 100, fb.width - 200, fb.height - 200, purple)
-
-    set_props(crtc, {
-        "trans-key-mode": 2,
-        "trans-key": purple.rgb888,
-        "background": 0,
-        "alpha_blender": 0,
-    })
-
-    time.sleep(1)
-
-    plane = planes[0]
-    fb = fbs[0]
-    set_props(plane, {
-        "FB_ID": fb.id,
-        "CRTC_ID": crtc.id,
-        "SRC_W": fb.width << 16,
-        "SRC_H": fb.height << 16,
-        "CRTC_W": w,
-        "CRTC_H": h,
-    })
-
-    time.sleep(1)
-
-    plane = planes[1]
-    fb = fbs[1]
-    set_props(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,
-    })
-
-    time.sleep(1)
-
-    plane = planes[2]
-    fb = fbs[2]
-    set_props(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": w - fb.width,
-        "CRTC_Y": 0,
-        "CRTC_W": fb.width,
-        "CRTC_H": fb.height,
-    })
-
-def test_am4_alpha_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"))
-
-    fb = fbs[0]
-    pykms.draw_rect(fb, 0, 0, w, h, purple)
-    pykms.draw_rect(fb, 200, 100, 50, 200, red)
-    pykms.draw_rect(fb, w - 200 - 50, 100, 50, 200, green)
-
-    fb = fbs[1]
-    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, blue)
-    pykms.draw_rect(fb, 100, 100, fb.width - 200, fb.height - 200, purple)
-
-    fb = fbs[2]
-    pykms.draw_rect(fb, 0, 0, fb.width, fb.height, cyan)
-    pykms.draw_rect(fb, 100, 100, fb.width - 200, fb.height - 200, purple)
-
-    set_props(crtc, {
-        "trans-key-mode": 1,
-        "trans-key": purple.rgb888,
-        "background": 0,
-        "alpha_blender": 1,
-    })
-
-    time.sleep(1)
-
-    plane = planes[0]
-    fb = fbs[0]
-    set_props(plane, {
-        "FB_ID": fb.id,
-        "CRTC_ID": crtc.id,
-        "SRC_W": fb.width << 16,
-        "SRC_H": fb.height << 16,
-        "CRTC_W": w,
-        "CRTC_H": h,
-    })
-
-    time.sleep(1)
-
-    plane = planes[1]
-    fb = fbs[1]
-    set_props(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,
-    })
-
-    time.sleep(1)
-
-    plane = planes[2]
-    fb = fbs[2]
-    set_props(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": w - fb.width,
-        "CRTC_Y": 0,
-        "CRTC_W": fb.width,
-        "CRTC_H": fb.height,
-    })
-
-
-
-#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")
-- 
cgit v1.2.3