diff --git a/src/rust/liblektor-rs/Cargo.toml b/src/rust/liblektor-rs/Cargo.toml
index 01f109d32db3eb875bc0a051ac646e2e288b4a0e..6246c10777575bb0b8aef56946f17d07c5c091a4 100644
--- a/src/rust/liblektor-rs/Cargo.toml
+++ b/src/rust/liblektor-rs/Cargo.toml
@@ -17,6 +17,7 @@ license = "MIT"
 [workspace.dependencies]
 log = "0.4"
 libc = "0.2.0"
+lazy_static = "1"
 diesel_migrations = "2"
 diesel = { version = "2", default-features = false, features = ["sqlite"] }
 serde = { version = "^1", default-features = false, features = [
@@ -34,4 +35,3 @@ codegen-units = 1
 [profile.dev]
 debug = true
 opt-level = "s"
-
diff --git a/src/rust/liblektor-rs/lektor_c_compat/Cargo.toml b/src/rust/liblektor-rs/lektor_c_compat/Cargo.toml
index 75e61449adc94ad5544a67a850eb87f6c8e62947..d39190b948e3c7c152b0b4ee990b92a231149cf4 100644
--- a/src/rust/liblektor-rs/lektor_c_compat/Cargo.toml
+++ b/src/rust/liblektor-rs/lektor_c_compat/Cargo.toml
@@ -8,3 +8,4 @@ license.workspace = true
 [dependencies]
 log.workspace = true
 libc.workspace = true
+lazy_static.workspace = true
diff --git a/src/rust/liblektor-rs/lektor_c_compat/src/c_types.rs b/src/rust/liblektor-rs/lektor_c_compat/src/c_types.rs
index f522e478773e7b1b530c3e7e3d2622c319d74b23..acc5975dbe8e258c6f248145b2fc55880a3c3f9e 100644
--- a/src/rust/liblektor-rs/lektor_c_compat/src/c_types.rs
+++ b/src/rust/liblektor-rs/lektor_c_compat/src/c_types.rs
@@ -1,10 +1,124 @@
 //! Types defined in the C part of the project. Don't use them from safe C code,
 //! only use the safe wrappers.
 
-pub struct LktQueue;
-pub struct LktDb;
-pub struct LktUri;
+use crate::*;
 
-pub type LktQueuePtr = *mut LktQueue;
-pub type LktDbPtr = *mut LktDb;
-pub type LktUriPtr = *mut LktUri;
+pub type LktQueuePtr = *mut c_void;
+pub type LktDbPtr = *mut c_void;
+pub type LktUriPtr = *mut c_void;
+
+#[repr(C)]
+pub struct LktEvent {
+    ty: c_uint,
+    attr: *const c_void,
+}
+
+#[repr(C)]
+pub enum LktUriType {
+    Null = 0,
+    Id = 1,
+    Playlist = 2,
+    Type = 3,
+    Author = 4,
+    Category = 5,
+    Lanquage = 6,
+    Query = 7,
+}
+
+#[repr(C)]
+pub enum LktUriValueType {
+    Null = 0,
+    String = 1,
+    Integer = 2,
+}
+
+#[repr(C)]
+pub enum LktPlaybackEvent {
+    Stop = 0,
+    Play = 1,
+    Pause = 2,
+    Toggle = 3,
+}
+
+#[repr(C)]
+pub enum LktDbUpdateEvent {
+    Finished = 0,
+    Progress = 1,
+}
+
+#[repr(C)]
+pub enum LktEventType {
+    Null = 0,
+    PlayPos = (1 << 1),
+    PlayFile = (1 << 2),
+    PlayNext = (1 << 3),
+    PlayPrev = (1 << 4),
+    PlayToggle = (1 << 5),
+    PropVol = (1 << 6),
+    PropDur = (1 << 7),
+    PropTime = (1 << 8),
+    SkipCurrent = (1 << 9),
+    DbUpdating = (1 << 10),
+    DbUpdateTotal = (1 << 11),
+    DbUpdateTick = (1 << 12),
+    TouchKara = (1 << 13),
+}
+
+impl std::ops::BitOr<u32> for LktEventType {
+    type Output = u32;
+    fn bitor(self, rhs: u32) -> Self::Output {
+        (self as u32) | rhs
+    }
+}
+
+impl std::ops::BitOr<LktEventType> for u32 {
+    type Output = u32;
+    fn bitor(self, rhs: LktEventType) -> Self::Output {
+        self | (rhs as u32)
+    }
+}
+
+impl std::ops::BitOr<LktEventType> for LktEventType {
+    type Output = u32;
+    fn bitor(self, rhs: LktEventType) -> Self::Output {
+        (self as u32) | (rhs as u32)
+    }
+}
+
+#[allow(non_snake_case)]
+pub mod LktMacroEvent {
+    use super::LktEventType::*;
+    lazy_static::lazy_static! {
+        pub static ref EVENT_PLAY: u32 = PlayPos | PlayFile | PlayNext | PlayPrev | PlayToggle | SkipCurrent;
+        pub static ref EVENT_PROP: u32 = PropVol | PropDur | PropTime;
+        pub static ref LKT_MACRO_EVENT_UPDATE: u32 = DbUpdating | DbUpdateTick | DbUpdateTotal | TouchKara;
+    }
+}
+
+extern "C" {
+    pub(crate) fn lkt_uri_get_type(_: LktUriPtr) -> LktUriType;
+    pub(crate) fn lkt_uri_get_value_as_str(_: LktUriPtr) -> *const c_char;
+    pub(crate) fn lkt_uri_get_value_as_int(_: LktUriPtr) -> c_int;
+    pub(crate) fn lkt_uri_get_value_type(_: LktUriPtr) -> LktUriValueType;
+    pub(crate) fn lkt_uri_get_column_name(_: LktUriPtr) -> *const c_char;
+}
+
+extern "C" {
+    pub(crate) fn lkt_queue_has_event(_: LktQueuePtr, _: LktEventType) -> bool;
+    pub(crate) fn lkt_queue_send(_: LktQueuePtr, _: LktEventType, _: *const c_void);
+    pub(crate) fn lkt_queue_handle(_: LktQueuePtr) -> LktEvent;
+    pub(crate) fn lkt_queue_make_available(_: LktQueuePtr, _: LktEventType);
+}
+
+unsafe fn str_len(str: *const c_char) -> usize {
+    let mut ret: isize = 0;
+    while (*str.offset(ret as _)) != 0 {
+        ret += 1;
+    }
+    ret as usize
+}
+
+pub(crate) unsafe fn ptr_to_str<'a>(str: *const c_char) -> &'a str {
+    let len = str_len(str);
+    std::str::from_utf8_unchecked(std::slice::from_raw_parts(str as *const u8, len))
+}
diff --git a/src/rust/liblektor-rs/lektor_c_compat/src/lib.rs b/src/rust/liblektor-rs/lektor_c_compat/src/lib.rs
index a946a73eb6fe5b52574236e2663d010eb408aefd..241109ad874e633ea02530352afd0183eeda6ff9 100644
--- a/src/rust/liblektor-rs/lektor_c_compat/src/lib.rs
+++ b/src/rust/liblektor-rs/lektor_c_compat/src/lib.rs
@@ -9,7 +9,7 @@ pub mod c_types;
 mod rs_types;
 pub use rs_types::*;
 
-pub use libc::{c_char, c_int, c_long, c_void, size_t};
+pub use libc::{c_char, c_int, c_long, c_uint, c_void, size_t};
 
 /// The maximal length of a tag in lektor.
 pub const LEKTOR_TAG_MAX: usize = 256;
diff --git a/src/rust/liblektor-rs/lektor_c_compat/src/rs_types.rs b/src/rust/liblektor-rs/lektor_c_compat/src/rs_types.rs
index 5f914a6461764108742b32a15a9ebd08a38958de..7361a85908a18a61941456f5fe80ef5f8d33c570 100644
--- a/src/rust/liblektor-rs/lektor_c_compat/src/rs_types.rs
+++ b/src/rust/liblektor-rs/lektor_c_compat/src/rs_types.rs
@@ -1,21 +1,92 @@
 use crate::c_types::*;
 
 pub struct LktCQueue {
-    c_ptr: LktQueuePtr,
+    ptr: LktQueuePtr,
+}
+
+pub enum LktEventValue<'a> {
+    Null,
+    Integer(usize),
+    String(&'a str),
 }
 
 pub struct LktCUri {
-    c_ptr: LktUriPtr,
+    ptr: LktUriPtr,
 }
 
 impl From<LktQueuePtr> for LktCQueue {
-    fn from(c_ptr: LktQueuePtr) -> Self {
-        Self { c_ptr }
+    fn from(ptr: LktQueuePtr) -> Self {
+        Self { ptr }
     }
 }
 
 impl From<LktUriPtr> for LktCUri {
-    fn from(c_ptr: LktUriPtr) -> Self {
-        Self { c_ptr }
+    fn from(ptr: LktUriPtr) -> Self {
+        Self { ptr }
+    }
+}
+
+impl LktCUri {
+    pub fn get_type(&self) -> LktUriType {
+        unsafe { lkt_uri_get_type(self.ptr) }
+    }
+
+    pub fn get_value_type(&self) -> LktUriValueType {
+        unsafe { lkt_uri_get_value_type(self.ptr) }
+    }
+
+    pub fn get_value_as_str(&self) -> Option<&str> {
+        use LktUriValueType::*;
+        match self.get_value_type() {
+            Null | Integer => None,
+            String => Some(unsafe { ptr_to_str(lkt_uri_get_value_as_str(self.ptr)) }),
+        }
+    }
+
+    pub fn get_value_as_int(&self) -> Option<i32> {
+        use LktUriValueType::*;
+        match self.get_value_type() {
+            Null | String => None,
+            Integer => Some(unsafe { lkt_uri_get_value_as_int(self.ptr) }),
+        }
+    }
+
+    fn get_column_name(&self) -> &str {
+        unsafe { ptr_to_str(lkt_uri_get_column_name(self.ptr)) }
+    }
+}
+
+impl From<LktCUri> for String {
+    fn from(uri: LktCUri) -> Self {
+        match uri.get_value_type() {
+            LktUriValueType::Null => "".to_string(),
+            LktUriValueType::String => uri.get_value_as_str().unwrap_or_default().to_string(),
+            LktUriValueType::Integer => uri.get_value_as_int().unwrap_or_default().to_string(),
+        }
+    }
+}
+
+impl LktCQueue {
+    pub fn has_event(&self, ty: LktEventType) -> bool {
+        unsafe { lkt_queue_has_event(self.ptr, ty) }
+    }
+
+    pub fn send(&self, ty: LktEventType, val: LktEventValue) {
+        use LktEventValue::*;
+        let val = match val {
+            Null => std::ptr::null(),
+            Integer(x) => x as *const _,
+            String(str) => str.as_ptr() as *const _,
+        };
+        unsafe { lkt_queue_send(self.ptr, ty, val) }
+        todo!("verify validity of (ty, val)")
+    }
+
+    pub fn handle(&mut self) -> Option<(LktEventType, LktEventValue)> {
+        todo!("verify validity of (ty, val)")
+    }
+
+    pub fn make_available(&mut self, ty: LktEventType) {
+        unsafe { lkt_queue_make_available(self.ptr, ty) }
     }
 }
diff --git a/src/rust/liblektor-rs/lektor_db/diesel.toml b/src/rust/liblektor-rs/lektor_db/diesel.toml
index 8de80930237919ba388f7d7450085774945cac77..35a12ff0dbc6cb89ff1d400570145f9ed5fcda54 100644
--- a/src/rust/liblektor-rs/lektor_db/diesel.toml
+++ b/src/rust/liblektor-rs/lektor_db/diesel.toml
@@ -2,7 +2,7 @@
 # see https://diesel.rs/guides/configuring-diesel-cli
 
 [print_schema]
-file = "src/database/schema.rs"
+file = "src/schema.rs"
 
 [migrations_directory]
 dir = "migrations"
diff --git a/src/rust/liblektor-rs/lektor_db/migrations/2022-09-30-204512_initial/up.sql b/src/rust/liblektor-rs/lektor_db/migrations/2022-09-30-204512_initial/up.sql
index 7271ce36976b98ba140759733029962e8b2b22ee..dd9b26912a6103763cc53bf8585f5ccba918715f 100644
--- a/src/rust/liblektor-rs/lektor_db/migrations/2022-09-30-204512_initial/up.sql
+++ b/src/rust/liblektor-rs/lektor_db/migrations/2022-09-30-204512_initial/up.sql
@@ -24,7 +24,7 @@ CREATE TABLE kara
   , song_origin TEXT    NOT NULL
   , source_name TEXT    NOT NULL
   , language    TEXT    NOT NULL REFERENCES iso_639_1(code)
-  , file_hash   TEXT    NOT NULL
+  , file_hash   TEXT    NOT NULL UNIQUE
   );
 
 -- We can have multiple kara makers for one kara.
diff --git a/src/rust/liblektor-rs/lektor_db/src/database/schema.rs b/src/rust/liblektor-rs/lektor_db/src/database/schema.rs
deleted file mode 100644
index d45ece40bea531314208616dbd3352cc5b07f7bb..0000000000000000000000000000000000000000
--- a/src/rust/liblektor-rs/lektor_db/src/database/schema.rs
+++ /dev/null
@@ -1,86 +0,0 @@
-// @generated automatically by Diesel CLI.
-
-diesel::table! {
-    history (epoch) {
-        id -> Integer,
-        epoch -> Integer,
-    }
-}
-
-diesel::table! {
-    iso_639_1 (code) {
-        code -> Text,
-        name_en -> Text,
-        is_iso -> Bool,
-        is_macro -> Bool,
-    }
-}
-
-diesel::table! {
-    kara (id) {
-        id -> Integer,
-        is_dl -> Bool,
-        song_title -> Text,
-        song_type -> Text,
-        song_origin -> Text,
-        source_name -> Text,
-        language -> Text,
-        file_hash -> Text,
-    }
-}
-
-diesel::table! {
-    kara_makers (id, name) {
-        id -> Integer,
-        name -> Text,
-    }
-}
-
-diesel::table! {
-    kara_tags (kara_id, tag_id, value) {
-        kara_id -> Integer,
-        tag_id -> Integer,
-        value -> Nullable<Text>,
-    }
-}
-
-diesel::table! {
-    repo (id) {
-        id -> Integer,
-        name -> Text,
-    }
-}
-
-diesel::table! {
-    repo_kara (repo_id, repo_kara_id, local_kara_id) {
-        repo_id -> Integer,
-        repo_kara_id -> Integer,
-        local_kara_id -> Integer,
-    }
-}
-
-diesel::table! {
-    tag (id) {
-        id -> Integer,
-        name -> Text,
-    }
-}
-
-diesel::joinable!(history -> kara (id));
-diesel::joinable!(kara -> iso_639_1 (language));
-diesel::joinable!(kara_makers -> kara (id));
-diesel::joinable!(kara_tags -> kara (kara_id));
-diesel::joinable!(kara_tags -> tag (tag_id));
-diesel::joinable!(repo_kara -> kara (local_kara_id));
-diesel::joinable!(repo_kara -> repo (repo_id));
-
-diesel::allow_tables_to_appear_in_same_query!(
-    history,
-    iso_639_1,
-    kara,
-    kara_makers,
-    kara_tags,
-    repo,
-    repo_kara,
-    tag,
-);