Skip to content

Commit

Permalink
Experiment: plugin GUI using XCB. (#3)
Browse files Browse the repository at this point in the history
GUI doesn't actually do anything besides displaying a host-embedded
window with some basic graphics.
  • Loading branch information
odiroot authored Feb 14, 2024
1 parent d0377f3 commit 4f72c87
Show file tree
Hide file tree
Showing 16 changed files with 433 additions and 57 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
* Demonstration of plugin GUI using raw XCB calls.
The GUI is currently totally non-functional.

## [0.1.3] - 2024-01-24
### Changed
* Plugin version is now source from V mod file.
Expand Down
11 changes: 5 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
SOURCEDIR = src
SOURCEDIR = .
BUILDDIR = build
TARGET = $(BUILDDIR)/hello_world.clap
V_CMD = v -cc gcc -shared -enable-globals

# List all V source files
V_SRC = $(wildcard $(SOURCEDIR)/*.v)

V_SRC := $(shell find $(SOURCEDIR) -name '*.v' -o -name '*.c')

# Debug and Release build options
DEBUG ?= 0
RELEASE ?= 0
ifeq ($(DEBUG),1)
V_FLAGS = -cg -show-c-output
V_FLAGS = -cg
else ifeq ($(DEBUG),2)
V_FLAGS = -cg -show-c-output
else ifeq ($(DEBUG),3)
# Even more debug! Very noisy.
V_FLAGS = -cg -show-c-output -trace-calls
else ifeq ($(RELEASE),1)
Expand Down
14 changes: 14 additions & 0 deletions main.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import vclap
// Exposes the plugin to the host (DAW).

@[markused]
__global clap_entry = vclap.plugin_entry
// This requires modification to `clap/entry.h`.
// Remove "const" so you get:
// CLAP_EXPORT extern clap_plugin_entry_t clap_entry;

fn init() {
$if debug {
eprintln('VCLAP in debug mode')
}
}
22 changes: 0 additions & 22 deletions src/main.v

This file was deleted.

2 changes: 2 additions & 0 deletions vclap/.clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
IndentWidth: 4
4 changes: 3 additions & 1 deletion src/c_abi.v → vclap/c_abi.v
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#flag -I./include
module vclap

#flag -I @VMODROOT/include
#include "clap/clap.h"

enum ClapProcessStatus {
Expand Down
2 changes: 2 additions & 0 deletions src/ext_audio_ports.v → vclap/ext_audio_ports.v
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module vclap

const clap_ext_audio_ports = unsafe { (&char(C.CLAP_EXT_AUDIO_PORTS)).vstring() }
const clap_port_stereo = unsafe { (&char(C.CLAP_PORT_STEREO)).vstring() }
const clap_port_mono = unsafe { (&char(C.CLAP_PORT_MONO)).vstring() }
Expand Down
49 changes: 49 additions & 0 deletions vclap/ext_gui.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
module vclap

const clap_ext_gui = unsafe { (&char(C.CLAP_EXT_GUI)).vstring() }
const clap_window_api_x11 = unsafe { (&char(C.CLAP_WINDOW_API_X11)).vstring() }
const clap_window_api_wayland = unsafe { (&char(C.CLAP_WINDOW_API_WAYLAND)).vstring() }

type C.clap_hwnd = voidptr
type C.clap_nsview = voidptr
type C.clap_xwnd = u32

@[typedef]
struct C.clap_window_t {
api &char
// union {
cocoa C.clap_nsview
x11 C.clap_xwnd
win32 C.clap_hwnd
ptr voidptr
// }
}

@[typedef]
struct C.clap_gui_resize_hints_t {
can_resize_horizontally bool
can_resize_vertically bool
preserve_aspect_ratio bool
aspect_ratio_width u32
aspect_ratio_height u32
}

@[typedef]
struct C.clap_plugin_gui_t {
is_api_supported fn (&C.clap_plugin_t, &char, bool) bool
// voidptr is &&char in original but used in a weird way.
get_preferred_api fn (&C.clap_plugin_t, voidptr, &bool) bool
create fn (&C.clap_plugin_t, &char, bool) bool
destroy fn (&C.clap_plugin_t)
set_scale fn (&C.clap_plugin_t, f64) bool
get_size fn (&C.clap_plugin_t, &u32, &u32) bool
can_resize fn (&C.clap_plugin_t) bool
get_resize_hints fn (&C.clap_plugin_t, &C.clap_gui_resize_hints_t) bool
adjust_size fn (&C.clap_plugin_t, &u32, &u32) bool
set_size fn (&C.clap_plugin_t, &u32, &u32) bool
set_parent fn (&C.clap_plugin_t, &C.clap_window_t) bool
set_transient fn (&C.clap_plugin_t, &C.clap_window_t) bool
suggest_title fn (&C.clap_plugin_t, &char)
show fn (&C.clap_plugin_t) bool
hide fn (&C.clap_plugin_t) bool
}
2 changes: 2 additions & 0 deletions src/ext_latency.v → vclap/ext_latency.v
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module vclap

const clap_ext_latency = unsafe { (&char(C.CLAP_EXT_LATENCY)).vstring() }

@[typedef]
Expand Down
2 changes: 2 additions & 0 deletions src/ext_note_ports.v → vclap/ext_note_ports.v
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module vclap

const clap_ext_note_ports = unsafe { (&char(C.CLAP_EXT_NOTE_PORTS)).vstring() }

@[typedef]
Expand Down
21 changes: 21 additions & 0 deletions vclap/ext_posix_fd.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module vclap

const clap_ext_posix_fd_support = unsafe { (&char(C.CLAP_EXT_POSIX_FD_SUPPORT)).vstring() }

enum ClapPosixFDFlags as u32 {
read = 1 << 0
write = 1 << 1
error = 1 << 2
}

@[typedef]
struct C.clap_host_posix_fd_support_t {
register_fd fn (&C.clap_host_t, int, ClapPosixFDFlags) bool
modify_fd fn (&C.clap_host_t, int, ClapPosixFDFlags) bool
unregister_fd fn (&C.clap_host_t, int) bool
}

@[typedef]
struct C.clap_plugin_posix_fd_support_t {
on_fd fn (&C.clap_plugin_t, int, ClapPosixFDFlags)
}
107 changes: 107 additions & 0 deletions vclap/gui/gui.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
module gui

@[heap]
pub struct GUI {
width u32
height u32
connection &C.xcb_connection_t
window C.xcb_window_t
gc C.xcb_gcontext_t
pub:
fd int
}

pub fn GUI.create(width u32, height u32, title string) &GUI {
// Open the connection to the X server.
conn := unsafe { C.xcb_connect(nil, nil) }
if isnil(conn) {
panic('Unable to open XCB connection.')
}

// Get the first screen.
screen := C.xcb_setup_roots_iterator(C.xcb_get_setup(conn)).data
// Create a window.
window := C.xcb_generate_id(conn)
C.xcb_create_window(conn, C.XCB_COPY_FROM_PARENT, window, screen.root, 0, 0, width,
height, 1, C.XCB_WINDOW_CLASS_INPUT_OUTPUT, screen.root_visual, C.XCB_CW_BACK_PIXEL,
&screen.white_pixel)

// Select the events the window will receive.
event_mask := C.XCB_EVENT_MASK_EXPOSURE | C.XCB_EVENT_MASK_POINTER_MOTION | C.XCB_EVENT_MASK_BUTTON_PRESS | C.XCB_EVENT_MASK_BUTTON_RELEASE | C.XCB_EVENT_MASK_KEY_PRESS | C.XCB_EVENT_MASK_KEY_RELEASE | C.XCB_EVENT_MASK_ENTER_WINDOW | C.XCB_EVENT_MASK_LEAVE_WINDOW | C.XCB_EVENT_MASK_BUTTON_MOTION | C.XCB_EVENT_MASK_KEYMAP_STATE | C.XCB_EVENT_MASK_FOCUS_CHANGE
C.xcb_change_window_attributes(conn, window, C.XCB_CW_EVENT_MASK, &event_mask)

// Inform a window manager not to tamper with the window
// Note: doesn't work in standalone.
C.xcb_change_window_attributes(conn, window, C.XCB_CW_OVERRIDE_REDIRECT, &[1]!)

// Create a graphic context to paint on.
gc := C.xcb_generate_id(conn)
gc_values := [screen.black_pixel, 0]!
C.xcb_create_gc(conn, gc, window, C.XCB_GC_FOREGROUND | C.XCB_GC_GRAPHICS_EXPOSURES,
&gc_values)

C.xcb_flush(conn)

return &GUI{
width: width
height: height
connection: conn
window: window
gc: gc
fd: C.xcb_get_file_descriptor(conn)
}
}

pub fn (g &GUI) destroy() {
C.xcb_free_gc(g.connection, g.gc)
C.xcb_destroy_window(g.connection, g.window)
C.xcb_disconnect(g.connection)
}

pub fn (g &GUI) set_parent(parent u32) {
C.xcb_reparent_window(g.connection, g.window, parent, 0, 0)
C.xcb_flush(g.connection)
}

pub fn (g &GUI) set_visible(visible bool) {
if visible {
C.xcb_configure_window(g.connection, g.window, C.XCB_CONFIG_WINDOW_STACK_MODE,
&[C.XCB_STACK_MODE_ABOVE]!)
C.xcb_map_window(g.connection, g.window)
} else {
C.xcb_unmap_window(g.connection, g.window)
}
C.xcb_flush(g.connection)
}

fn (g &GUI) paint() {
C.xcb_clear_area(g.connection, 0, g.window, 0, 0, g.width, g.height)
// Parameter "slider".
C.xcb_change_gc(g.connection, g.gc, C.XCB_GC_FOREGROUND, &[0]!)
C.xcb_poly_rectangle(g.connection, g.window, g.gc, 1, &C.xcb_rectangle_t{50, 50, 25, 100})
C.xcb_poly_fill_rectangle(g.connection, g.window, g.gc, 1, &C.xcb_rectangle_t{50, 50, 25, 40})
}

pub fn (g &GUI) on_posix_fd() {
C.xcb_flush(g.connection)

for {
event := C.xcb_poll_for_event(g.connection)
if isnil(event) {
break
}
t := event.response_type & ~0x80
match t {
u8(C.XCB_EXPOSE) {
expose_ev := &C.xcb_expose_event_t(event)
if expose_ev.window == g.window {
g.paint()
}
}
else {}
}

C.xcb_flush(g.connection)
C.free(event)
}
}
79 changes: 79 additions & 0 deletions vclap/gui/xcb.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
module gui

#pkgconfig --libs xcb

#include <xcb/xcb.h>

// XCB library
type C.xcb_window_t = u32
type C.xcb_gcontext_t = u32
type C.xcb_visualid_t = u32
type C.xcb_atom_t = u32

@[typedef]
struct C.xcb_connection_t {}

@[typedef]
struct C.xcb_setup_t {}

@[typedef]
struct C.xcb_screen_t {
root C.xcb_window_t
root_visual C.xcb_visualid_t
white_pixel u32
black_pixel u32
}

@[typedef]
struct C.xcb_screen_iterator_t {
data &C.xcb_screen_t
}

@[typedef]
struct C.xcb_gcontext_t {}

@[typedef]
struct C.xcb_rectangle_t {
x i16
y i16
width u16
height u16
}

@[typedef]
struct C.xcb_generic_event_t {
response_type u8
}

@[typedef]
struct C.xcb_expose_event_t {
window C.xcb_window_t
}

fn C.xcb_connect(&char, &int) &C.xcb_connection_t
fn C.xcb_disconnect(&C.xcb_connection_t)
fn C.xcb_flush(&C.xcb_connection_t)

fn C.xcb_get_setup(&C.xcb_connection_t) &C.xcb_setup_t
fn C.xcb_setup_roots_iterator(&C.xcb_setup_t) C.xcb_screen_iterator_t
fn C.xcb_generate_id(&C.xcb_connection_t) u32
fn C.xcb_get_file_descriptor(&C.xcb_connection_t) int

fn C.xcb_create_window(&C.xcb_connection_t, u8, C.xcb_window_t, C.xcb_window_t, i16, i16, u16, u16, u16, u16, C.xcb_visualid_t, u32, voidptr)
fn C.xcb_destroy_window(&C.xcb_connection_t, C.xcb_window_t)
fn C.xcb_configure_window(&C.xcb_connection_t, C.xcb_window_t, u16, voidptr)
fn C.xcb_change_window_attributes(&C.xcb_connection_t, C.xcb_window_t, u32, voidptr)
fn C.xcb_reparent_window(&C.xcb_connection_t, C.xcb_window_t, C.xcb_window_t, i16, i16)
fn C.xcb_map_window(&C.xcb_connection_t, C.xcb_window_t)
fn C.xcb_unmap_window(&C.xcb_connection_t, C.xcb_window_t)

fn C.xcb_create_gc(&C.xcb_connection_t, C.xcb_gcontext_t, C.xcb_window_t, u32, voidptr)
fn C.xcb_free_gc(&C.xcb_connection_t, C.xcb_gcontext_t)
fn C.xcb_change_gc(&C.xcb_connection_t, C.xcb_gcontext_t, u32, voidptr)

fn C.xcb_clear_area(&C.xcb_connection_t, u8, C.xcb_window_t, i16, i16, u16, u16)
fn C.xcb_poly_rectangle(&C.xcb_connection_t, C.xcb_window_t, C.xcb_gcontext_t, u32, &C.xcb_rectangle_t)
fn C.xcb_poly_fill_rectangle(&C.xcb_connection_t, C.xcb_window_t, C.xcb_gcontext_t, u32, &C.xcb_rectangle_t)

fn C.xcb_poll_for_event(&C.xcb_connection_t) &C.xcb_generic_event_t

Loading

0 comments on commit 4f72c87

Please sign in to comment.