Skip to content

Instantly share code, notes, and snippets.

@rikkimax
Last active February 9, 2019 14:23
Show Gist options
  • Save rikkimax/b0ad72d83d23a3f9b0f8a72042c324b7 to your computer and use it in GitHub Desktop.
Save rikkimax/b0ad72d83d23a3f9b0f8a72042c324b7 to your computer and use it in GitHub Desktop.
/**
* LV2 Client implementation
*
* Copyright: Ethan Reker 2018.
* License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
*/
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2018 Filipe Coelho <[email protected]>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
* permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
* TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
module dplug.lv2.client;
import std.string,
std.algorithm.comparison;
import core.stdc.stdlib,
core.stdc.string,
core.stdc.stdio,
core.stdc.math,
core.stdc.stdint;
import dplug.core.vec,
dplug.core.nogc,
dplug.core.math,
dplug.core.lockedqueue,
dplug.core.runtime,
dplug.core.fpcontrol,
dplug.core.thread,
dplug.core.sync,
dplug.core.map;
import dplug.client.client,
dplug.client.daw,
dplug.client.preset,
dplug.client.graphics,
dplug.client.midi,
dplug.client.params;
import dplug.lv2.lv2,
dplug.lv2.lv2util,
dplug.lv2.midi,
dplug.lv2.ui,
dplug.lv2.options,
dplug.lv2.urid,
dplug.lv2.bufsize;
class LV2Client : IHostCommand
{
nothrow:
@nogc:
Client _client;
Map!(string, int)* _uriMap;
this(Client client, Map!(string, int)* uriMapPtr)
{
_client = client;
_client.setHostCommand(this);
_graphicsMutex = makeMutex();
_uriMap = uriMapPtr;
}
void instantiate(const LV2_Descriptor* descriptor, double rate, const char* bundle_path, const(LV2_Feature*)* features)
{
LV2_Options_Option* options = null;
LV2_URID_Map* uridMap = null;
for(int i = 0; features[i] != null; ++i)
{
if (strcmp(features[i].URI, LV2_OPTIONS__options) == 0)
options = cast(LV2_Options_Option*)features[i].data;
else if (strcmp(features[i].URI, LV2_URID__map) == 0)
uridMap = cast(LV2_URID_Map*)features[i].data;
}
// Retrieve max buffer size from options
for (int i=0; options[i].key != 0; ++i)
{
if (options[i].key == assumeNothrowNoGC(uridMap.map)(uridMap.handle, LV2_BUF_SIZE__maxBlockLength))
{
_maxBufferSize = *cast(const(int)*)options[i].value;
_callResetOnNextRun = true;
}
}
// Retrieve index of legalIO that was stored in the uriMap
string uri = cast(string)descriptor.URI[0..strlen(descriptor.URI)];
int legalIOIndex = (*_uriMap)[uri];
LegalIO selectedIO = _client.legalIOs()[legalIOIndex];
_maxInputs = selectedIO.numInputChannels;
_maxOutputs = selectedIO.numOutputChannels;
_numParams = cast(uint)_client.params().length;
_sampleRate = cast(float)rate;
_params = cast(float**)mallocSlice!(float*)(_client.params.length);
_inputs = mallocSlice!(float*)(_maxInputs);
_outputs = mallocSlice!(float*)(_maxOutputs);
}
void cleanup()
{
}
void updateParamFromHost(uint32_t port_index)
{
float* port = _params[port_index];
float paramValue = *port;
_client.setParameterFromHost(port_index, paramValue);
}
void updatePortFromClient(uint32_t port_index, float value)
{
float* port = _params[port_index];
*port = value;
}
void connect_port(uint32_t port, void* data)
{
if(port < _client.params.length)
{
_params[port] = cast(float*)data;
}
else if(port < _maxInputs + _client.params.length)
{
_inputs[port - _client.params.length] = cast(float*)data;
}
else if(port < _maxOutputs + _maxInputs + _client.params.length)
{
_outputs[port - _client.params.length - _maxInputs] = cast(float*)data;
}
else
assert(false, "Error unknown port index");
}
void activate()
{
_callResetOnNextRun = true;
}
void run(uint32_t n_samples)
{
TimeInfo timeInfo;
if(_callResetOnNextRun)
{
_callResetOnNextRun = false;
_client.resetFromHost(_sampleRate, _maxBufferSize, _maxInputs, _maxOutputs);
}
_client.processAudioFromHost(_inputs, _outputs, n_samples, timeInfo);
}
void deactivate()
{
}
void instantiateUI(const LV2UI_Descriptor* descriptor,
const char* plugin_uri,
const char* bundle_path,
LV2UI_Write_Function write_function,
LV2UI_Controller controller,
LV2UI_Widget* widget,
const (LV2_Feature*)* features)
{
import core.stdc.config;
if (widget != null)
{
_graphicsMutex.lock();
c_ulong* windowHandle;
_client.openGUI(windowHandle, null, GraphicsBackend.x11);
*widget = cast(LV2UI_Widget)windowHandle;
_graphicsMutex.unlock();
}
}
void port_event(uint32_t port_index,
uint32_t buffer_size,
uint32_t format,
const void* buffer)
{
_graphicsMutex.lock();
updateParamFromHost(port_index);
_graphicsMutex.unlock();
}
void cleanupUI()
{
_graphicsMutex.lock();
_client.closeGUI();
_graphicsMutex.unlock();
}
override void beginParamEdit(int paramIndex)
{
}
override void paramAutomate(int paramIndex, float value)
{
updatePortFromClient(paramIndex, value);
}
override void endParamEdit(int paramIndex)
{
}
override bool requestResize(int width, int height)
{
return false;
}
// Not properly implemented yet. LV2 should have an extension to get DAW information.
override DAW getDAW()
{
return DAW.Unknown;
}
private:
uint _maxInputs;
uint _maxOutputs;
uint _numParams;
int _maxBufferSize;
bool _callResetOnNextRun;
float** _params;
float*[] _inputs;
float*[] _outputs;
float _sampleRate;
UncheckedMutex _graphicsMutex;
}

untitled

xwininfo

xwininfo: Window id: 0x6800001 " "

Root window id: 0x14c (the root window) (has no name) Parent window id: 0x3a0ae2e (has no name) 0 children.

Absolute upper-left X: 832 Absolute upper-left Y: 205 Relative upper-left X: 0 Relative upper-left Y: 28 Width: 500 Height: 500 Depth: 24 Visual: 0x21 Visual Class: TrueColor Border width: 0 Class: InputOutput Colormap: 0x20 (installed) Bit Gravity State: ForgetGravity Window Gravity State: NorthWestGravity Backing Store State: NotUseful Save Under State: no Map State: IsViewable Override Redirect State: no Corners: +832+205 -588+205 -588-270 +832-270 -geometry 500x500+832+177

Bit gravity: ForgetGravity Window gravity: NorthWestGravity Backing-store hint: NotUseful Backing-planes to be preserved: 0xffffffff Backing pixel: 0 Save-unders: No

Someone wants these events: KeyPress KeyRelease ButtonPress ButtonRelease EnterWindow LeaveWindow PointerMotion Exposure StructureNotify FocusChange PropertyChange ColormapChange Do not propagate these events: Override redirection?: No

No window manager hints defined Window manager hints: Displayed on desktop 0 Process id: (unknown) Frame extents: 0, 0, 28, 0

Normal window size hints: Program supplied minimum size: 500 by 500 Program supplied maximum size: 500 by 500 No zoom window size hints defined

No window shape defined No border shape defined

xprop

XKLAVIER_STATE(INTEGER) = 0, 259506944 _GTK_EDGE_CONSTRAINTS(CARDINAL) = 170 _NET_WM_STATE(ATOM) = WM_STATE(WM_STATE): window state: Normal icon window: 0x0 _NET_FRAME_EXTENTS(CARDINAL) = 0, 0, 28, 0 _NET_WM_DESKTOP(CARDINAL) = 0 _NET_WM_ALLOWED_ACTIONS(ATOM) = _NET_WM_ACTION_MOVE, _NET_WM_ACTION_MINIMIZE, _NET_WM_ACTION_SHADE, _NET_WM_ACTION_CHANGE_DESKTOP, _NET_WM_ACTION_CLOSE, _NET_WM_ACTION_ABOVE, _NET_WM_ACTION_BELOW WM_PROTOCOLS(ATOM): protocols WM_DELETE_WINDOW _XEMBED_INFO(CARDINAL) = 0, 4294945664 WM_NORMAL_HINTS(WM_SIZE_HINTS): program specified minimum size: 500 by 500 program specified maximum size: 500 by 500 WM_NAME(STRING) = " "

titled

xwininfo

xwininfo: Window id: 0x5c000af "CLIP It - Track 1"

Root window id: 0x14c (the root window) (has no name) Parent window id: 0x3a0ae30 (has no name) 1 child: 0x5c000b1 (has no name): () 1x1+-1+-1 +169+194

Absolute upper-left X: 170 Absolute upper-left Y: 195 Relative upper-left X: 10 Relative upper-left Y: 36 Width: 640 Height: 480 Depth: 24 Visual: 0xfb Visual Class: TrueColor Border width: 0 Class: InputOutput Colormap: 0x5c000ae (not installed) Bit Gravity State: NorthWestGravity Window Gravity State: NorthWestGravity Backing Store State: NotUseful Save Under State: no Map State: IsViewable Override Redirect State: no Corners: +170+195 -1110+195 -1110-300 +170-300 -geometry 640x480+170+195

Bit gravity: NorthWestGravity Window gravity: NorthWestGravity Backing-store hint: NotUseful Backing-planes to be preserved: 0xffffffff Backing pixel: 0 Save-unders: No

Someone wants these events: KeyPress KeyRelease ButtonPress ButtonRelease EnterWindow LeaveWindow PointerMotion ButtonMotion Exposure StructureNotify FocusChange PropertyChange ColormapChange Do not propagate these events: Override redirection?: No

Window manager hints: Client accepts input or input focus: Yes Initial state is Normal State Displayed on desktop 0 Window type: Normal Process id: 11350 Frame extents: 0, 0, 28, 0

Normal window size hints: User supplied location: 379, 212 User supplied size: 640 by 480 Program supplied window gravity: StaticGravity No zoom window size hints defined

No window shape defin

xprop

XKLAVIER_STATE(INTEGER) = 0, 259506944 _GTK_EDGE_CONSTRAINTS(CARDINAL) = 170 _NET_WM_STATE(ATOM) = WM_STATE(WM_STATE): window state: Normal icon window: 0x0 _NET_FRAME_EXTENTS(CARDINAL) = 0, 0, 28, 0 _NET_WM_DESKTOP(CARDINAL) = 0 _NET_WM_ALLOWED_ACTIONS(ATOM) = _NET_WM_ACTION_MOVE, _NET_WM_ACTION_RESIZE, _NET_WM_ACTION_FULLSCREEN, _NET_WM_ACTION_MINIMIZE, _NET_WM_ACTION_SHADE, _NET_WM_ACTION_MAXIMIZE_HORZ, _NET_WM_ACTION_MAXIMIZE_VERT, _NET_WM_ACTION_CHANGE_DESKTOP, _NET_WM_ACTION_CLOSE, _NET_WM_ACTION_ABOVE, _NET_WM_ACTION_BELOW _NET_WM_USER_TIME_WINDOW(WINDOW): window id # 0x5c000b1 _NET_WM_ICON_NAME(UTF8_STRING) = XdndAware(ATOM) = BITMAP WM_NAME(STRING) = "CLIP It - Track 1" _NET_WM_NAME(UTF8_STRING) = "CLIP It - Track 1" _MOTIF_WM_HINTS(_MOTIF_WM_HINTS) = 0x3, 0x3e, 0x7e, 0x0, 0x0 _NET_WM_WINDOW_TYPE(ATOM) = _NET_WM_WINDOW_TYPE_NORMAL _XEMBED_INFO(_XEMBED_INFO) = 0x0, 0x1 WM_CLIENT_LEADER(WINDOW): window id # 0x5c00008 WM_HINTS(WM_HINTS): Client accepts input or input focus: True Initial state is Normal State. _NET_WM_PID(CARDINAL) = 11350 _NET_WM_SYNC_REQUEST_COUNTER(CARDINAL) = 96469168 WM_CLASS(STRING) = "qtractor", "qtractor" WM_PROTOCOLS(ATOM): protocols WM_DELETE_WINDOW, WM_TAKE_FOCUS, _NET_WM_PING, _NET_WM_SYNC_REQUEST WM_NORMAL_HINTS(WM_SIZE_HINTS): user specified location: 379, 212 user specified size: 640 by 480 window gravity: Static

host

xwininfo

xwininfo: Window id: 0x5c00006 "Untitled1 [modified] - Qtractor"

Root window id: 0x14c (the root window) (has no name) Parent window id: 0x3a0ab4b (has no name) 1 child: 0x5c00093 (has no name): () 1x1+-1+-1 +186+95

Absolute upper-left X: 187 Absolute upper-left Y: 96 Relative upper-left X: 10 Relative upper-left Y: 36 Width: 1024 Height: 768 Depth: 24 Visual: 0xfb Visual Class: TrueColor Border width: 0 Class: InputOutput Colormap: 0x5c00005 (not installed) Bit Gravity State: NorthWestGravity Window Gravity State: NorthWestGravity Backing Store State: NotUseful Save Under State: no Map State: IsViewable Override Redirect State: no Corners: +187+96 -709+96 -709-111 +187-111 -geometry 1024x768+177+60

Bit gravity: NorthWestGravity Window gravity: NorthWestGravity Backing-store hint: NotUseful Backing-planes to be preserved: 0xffffffff Backing pixel: 0 Save-unders: No

Someone wants these events: KeyPress KeyRelease ButtonPress ButtonRelease EnterWindow LeaveWindow PointerMotion ButtonMotion Exposure StructureNotify FocusChange PropertyChange ColormapChange Do not propagate these events: Override redirection?: No

Window manager hints: Client accepts input or input focus: Yes Initial state is Normal State Displayed on desktop 0 Window type: Normal Process id: 11350 Frame extents: 0, 0, 28, 0

Normal window size hints: User supplied location: 438, 80 User supplied size: 1024 by 768 Program supplied minimum size: 320 by 240 Program supplied window gravity: NorthWestGravity No zoom window size hints defined

No window shape defined No border shape defined

xprop

_NET_WM_USER_TIME_WINDOW(WINDOW): window id # 0x5c00093 XKLAVIER_STATE(INTEGER) = 0, 259506944 _GTK_EDGE_CONSTRAINTS(CARDINAL) = 170 _NET_WM_STATE(ATOM) = WM_STATE(WM_STATE): window state: Normal icon window: 0x0 _NET_FRAME_EXTENTS(CARDINAL) = 0, 0, 28, 0 _NET_WM_DESKTOP(CARDINAL) = 0 _NET_WM_ALLOWED_ACTIONS(ATOM) = _NET_WM_ACTION_MOVE, _NET_WM_ACTION_RESIZE, _NET_WM_ACTION_FULLSCREEN, _NET_WM_ACTION_MINIMIZE, _NET_WM_ACTION_SHADE, _NET_WM_ACTION_MAXIMIZE_HORZ, _NET_WM_ACTION_MAXIMIZE_VERT, _NET_WM_ACTION_CHANGE_DESKTOP, _NET_WM_ACTION_CLOSE, _NET_WM_ACTION_ABOVE, _NET_WM_ACTION_BELOW _NET_WM_ICON(CARDINAL) = Icon (32 x 32):

                 ░░░░           
               ░▒▒▒▒▒▒░░        
              ░▒▒▒▒▒▒▒▒░░       
            ░ ░░░▒▒▒▒▒▒▒░░      
            ░░░░░▒▒▒▒▒▒▒▒░░     
            ░░░░▒▒▒▒▒▒▒▒▒▒░     
            ░░░░▒▒▒▒▒▒▒▒▒▒░░    
           ░ ░░░▒▒▒▒▒▒▒▒▒▒▒░    
           ░░░░▒▒▒▒▒▒▒▒▒▒▒▒░░   
            ░░░▒▒▒▒▒▒▒▒▒▒▒▒░░   
            ░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒░   
           ░░ ▒▓▓▒▒▒▒▒▒▒▒▒▒▒░   
           ▒░ ▒▓▓▒▒▒▒▒▒▒▒▒▒▒░   
          ▒▒▒░░▒▒▒▒▒▒▒▒▒▒▒░▒░   
         ░▒▒▒░ ░▒▒▒▒▒▒▒▒▒░░▒░   
    ░░   ▒░░░░░░░░▒▒░░▒▒░░░▒░   
    ▒░   ▒░   ░░░░░░░░░░░░░▒░   
    ░   ░░░    ░░░░░░░░░░░░▒░   
    ░░░▒░▒░    ░░░░░   ░░░░░    
     ░▒▓▓▓▓▓▒▓▒░░░░░░   ░░▒     
    ░▒▒▒▒▒▓▒▒▒▒▒▒▒░░░░░░▒▒░     
    ░▓▓▓▓▓▓▓▓▓▓▓▓▒ ░░░░░░       
    ░▓▓▓▓▓▓▓▓▓▓▓▓▒              
    ░▓▓▓▓▒▒▒▒▒▒▒▒░              
    ░▒▒▒▒▒▒▒▒▒▒▒▒░              
    ░▒▒▒▒▒▒▒▒▒▒▒▒░              
   ░▒▒▒▒▒▒▒▒▒▒▒▒▒░              
   ▒░▒▒▒▒▒▒▒▒▒▒▒▒░              
   ░░▒▒▒▒▒▒▒▒▒▒▒▒░▒░            

_NET_WM_ICON_NAME(UTF8_STRING) = XdndAware(ATOM) = BITMAP WM_NAME(STRING) = "Untitled1 [modified] - Qtractor" _NET_WM_NAME(UTF8_STRING) = "Untitled1 [modified] - Qtractor" _MOTIF_WM_HINTS(_MOTIF_WM_HINTS) = 0x3, 0x3e, 0x7e, 0x0, 0x0 _NET_WM_WINDOW_TYPE(ATOM) = _NET_WM_WINDOW_TYPE_NORMAL _XEMBED_INFO(_XEMBED_INFO) = 0x0, 0x1 WM_CLIENT_LEADER(WINDOW): window id # 0x5c00008 WM_HINTS(WM_HINTS): Client accepts input or input focus: True Initial state is Normal State. _NET_WM_PID(CARDINAL) = 11350 _NET_WM_SYNC_REQUEST_COUNTER(CARDINAL) = 96468999 WM_CLASS(STRING) = "qtractor", "qtractor" WM_PROTOCOLS(ATOM): protocols WM_DELETE_WINDOW, WM_TAKE_FOCUS, _NET_WM_PING, _NET_WM_SYNC_REQUEST WM_NORMAL_HINTS(WM_SIZE_HINTS): user specified location: 438, 80 user specified size: 1024 by 768 program specified minimum size: 320 by 240 window gravity: NorthWest

/**
* X11 window implementation.
*
* Copyright: Copyright (C) 2017 Richard Andrew Cattermole
* Copyright (C) 2017 Ethan Reker
* Copyright (C) 2017 Lukasz Pelszynski
*
* Bugs:
* - X11 does not support double clicks, it is sometimes emulated https://github.com/glfw/glfw/issues/462
*
* License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
* Authors: Richard (Rikki) Andrew Cattermole
*/
module dplug.window.x11window;
import gfm.math.box;
import core.sys.posix.unistd;
import core.stdc.string;
import core.atomic;
import dplug.window.window;
import dplug.core.runtime;
import dplug.core.nogc;
import dplug.core.thread;
import dplug.core.sync;
import dplug.graphics.image;
import dplug.graphics.view;
nothrow:
@nogc:
version(linux):
import dplug.core.map;
import derelict.x11.X;
import derelict.x11.Xlib;
import derelict.x11.keysym;
import derelict.x11.keysymdef;
import derelict.x11.Xutil;
import derelict.x11.extensions.Xrandr;
import derelict.x11.extensions.randr;
import core.stdc.stdio;
// debug = logX11Window;
// This is an extension to X11, almost always should exist on modern systems
// If it becomes a problem, version out its usage, it'll work just won't be as nice event wise
extern(C) bool XkbSetDetectableAutoRepeat(Display*, bool, bool*);
__gshared XLibInitialized = false;
__gshared Display* _display;
__gshared size_t _white_pixel, _black_pixel;
__gshared int _screen;
enum XA_CARDINAL = 6;
final class X11Window : IWindow
{
nothrow:
@nogc:
private:
// Xlib variables
Visual* _visual;
Window _windowId, _parentWindowId;
Atom _closeAtom;
derelict.x11.Xlib.GC _graphicGC;
XImage* _graphicImage;
int width, height, depth;
// Threads
Thread _eventLoop, _timerLoop;
UncheckedMutex drawMutex;
//Other
IWindowListener listener;
ImageRef!RGBA _wfb; // framebuffer reference
ubyte[4][] _bufferData;
uint lastTimeGot, creationTime, currentTime;
int lastMouseX, lastMouseY;
box2i prevMergedDirtyRect, mergedDirtyRect;
shared(bool) _terminated = false;
Atom _XEMBED;
Atom _XEMBED_INFO;
enum int XEMBED_VERSION = 0;
enum int XEMBED_MAPPED = (1 << 0);
public:
this(void* parentWindow, IWindowListener listener, int width, int height)
{
debug(logX11Window) fprintf(stderr, "X11Window: constructor\n");
drawMutex = makeMutex();
initializeXLib();
int x, y;
this.listener = listener;
if (parentWindow !is null)
{
XWindowAttributes attr;
if (XGetWindowAttributes(_display, cast(Window)parentWindow, &attr)) {
_visual = attr.visual;
} else {
parentWindow = null;
}
}
if (parentWindow is null)
{
_parentWindowId = RootWindow(_display, _screen);
_visual = XDefaultVisual(_display, _screen);
}
else
{
_parentWindowId = cast(Window)parentWindow;
}
x = (DisplayWidth(_display, _screen) - width) / 2;
y = (DisplayHeight(_display, _screen) - height) / 3;
this.width = width;
this.height = height;
depth = 24;
_windowId = XCreateSimpleWindow(_display, _parentWindowId, x, y, width, height, 0, 0, _black_pixel);
XStoreName(_display, _windowId, cast(char*)" ".ptr);
XSizeHints sizeHints;
sizeHints.flags = PMinSize | PMaxSize;
sizeHints.min_width = width;
sizeHints.max_width = width;
sizeHints.min_height = height;
sizeHints.max_height = height;
XSetWMNormalHints(_display, _windowId, &sizeHints);
//Setup XEMBED atoms
_XEMBED = XInternAtom(_display, "_XEMBED", false);
_XEMBED_INFO = XInternAtom(_display, "_XEMBED_INFO", false);
uint[2] data = [XEMBED_VERSION, XEMBED_MAPPED];
XChangeProperty(_display, _windowId, _XEMBED_INFO,
XA_CARDINAL, 32, PropModeReplace,
cast(ubyte*) data, 2);
_closeAtom = XInternAtom(_display, cast(char*)("WM_DELETE_WINDOW".ptr), cast(Bool)false);
XSetWMProtocols(_display, _windowId, &_closeAtom, 1);
if (parentWindow) {
// Embed the window in parent (most VST hosts expose some area for embedding a VST client)
XReparentWindow(_display, _windowId, _parentWindowId, 0, 0);
}
XMapWindow(_display, _windowId);
XFlush(_display);
XSelectInput(_display, _windowId, windowEventMask());
_graphicGC = XCreateGC(_display, _windowId, 0, null);
XSetBackground(_display, _graphicGC, _white_pixel);
XSetForeground(_display, _graphicGC, _black_pixel);
_wfb = listener.onResized(width, height);
creationTime = getTimeMs();
lastTimeGot = creationTime;
emptyMergedBoxes();
_timerLoop = makeThread(&timerLoop);
_timerLoop.start();
_eventLoop = makeThread(&eventLoop);
_eventLoop.start();
}
~this()
{
XDestroyWindow(_display, _windowId);
XFlush(_display);
_timerLoop.join();
_eventLoop.join();
}
void initializeXLib() {
if (!XLibInitialized) {
XInitThreads();
_display = XOpenDisplay(null);
if(_display == null)
assert(false);
_screen = DefaultScreen(_display);
_white_pixel = WhitePixel(_display, _screen);
_black_pixel = BlackPixel(_display, _screen);
XkbSetDetectableAutoRepeat(_display, true, null);
XLibInitialized = true;
}
}
long windowEventMask() {
return ExposureMask | StructureNotifyMask |
KeyReleaseMask | KeyPressMask | ButtonReleaseMask | ButtonPressMask | PointerMotionMask;
}
void swapBuffers(ImageRef!RGBA wfb, box2i[] areasToRedraw)
{
if (_bufferData.length != wfb.w * wfb.h)
{
_bufferData = mallocSlice!(ubyte[4])(wfb.w * wfb.h);
if (_graphicImage !is null)
{
// X11 deallocates _bufferData for us (ugh...)
XDestroyImage(_graphicImage);
}
_graphicImage = XCreateImage(_display, _visual, depth, ZPixmap, 0, cast(char*)_bufferData.ptr, width, height, 32, 0);
size_t i;
foreach(y; 0 .. wfb.h)
{
RGBA[] scanLine = wfb.scanline(y);
foreach(x, ref c; scanLine)
{
_bufferData[i][0] = c.b;
_bufferData[i][1] = c.g;
_bufferData[i][2] = c.r;
_bufferData[i][3] = c.a;
i++;
}
}
}
else
{
foreach(box2i area; areasToRedraw)
{
foreach(y; area.min.y .. area.max.y)
{
RGBA[] scanLine = wfb.scanline(y);
size_t i = y * wfb.w;
i += area.min.x;
foreach(x, ref c; scanLine[area.min.x .. area.max.x])
{
_bufferData[i][0] = c.b;
_bufferData[i][1] = c.g;
_bufferData[i][2] = c.r;
_bufferData[i][3] = c.a;
i++;
}
}
}
}
XPutImage(_display, _windowId, _graphicGC, _graphicImage, 0, 0, 0, 0, cast(uint)width, cast(uint)height);
}
// Implements IWindow
override void waitEventAndDispatch() nothrow @nogc
{
// fprintf(stderr, "X11Window: waitEventAndDispatch()\n");
XEvent event;
// Wait for events for current window
XWindowEvent(_display, _windowId, windowEventMask(), &event);
handleEvents(event, this);
}
void eventLoop() nothrow @nogc
{
// fprintf(stderr, "X11Window: eventLoop()\n");
while (!terminated()) {
waitEventAndDispatch();
}
}
void emptyMergedBoxes() nothrow @nogc
{
prevMergedDirtyRect = box2i(0,0,0,0);
mergedDirtyRect = box2i(0,0,0,0);
}
void sendRepaintIfUIDirty() nothrow @nogc
{
listener.recomputeDirtyAreas();
box2i dirtyRect = listener.getDirtyRectangle();
if (!dirtyRect.empty())
{
prevMergedDirtyRect = mergedDirtyRect;
mergedDirtyRect = mergedDirtyRect.expand(dirtyRect);
// If everything has been drawn by Expose event handler, send Expose event.
// Otherwise merge areas to be redrawn and postpone Expose event.
if (prevMergedDirtyRect.empty() && !mergedDirtyRect.empty()) {
int x = dirtyRect.min.x;
int y = dirtyRect.min.y;
int width = dirtyRect.max.x - x;
int height = dirtyRect.max.y - y;
XEvent evt;
memset(&evt, 0, XEvent.sizeof);
evt.type = Expose;
evt.xexpose.window = _windowId;
evt.xexpose.display = _display;
evt.xexpose.x = 0;
evt.xexpose.y = 0;
evt.xexpose.width = 0;
evt.xexpose.height = 0;
XSendEvent(_display, _windowId, False, ExposureMask, &evt);
XFlush(_display);
}
}
}
void timerLoop() nothrow @nogc
{
debug(logX11Window) fprintf(stderr, "X11Window: timerLoop()\n");
while(!terminated())
{
currentTime = getTimeMs();
float diff = currentTime - lastTimeGot;
double dt = (currentTime - lastTimeGot) * 0.001;
double time = (currentTime - creationTime) * 0.001;
listener.onAnimate(dt, time);
sendRepaintIfUIDirty();
lastTimeGot = currentTime;
//Sleep for ~16.6 milliseconds (60 frames per second rendering)
usleep(16666);
}
}
override bool terminated()
{
debug(logX11Window) fprintf(stderr, "X11Window: terminated()\n");
return atomicLoad(_terminated);
}
override uint getTimeMs()
{
static uint perform() {
import core.sys.posix.sys.time;
timeval tv;
gettimeofday(&tv, null);
return cast(uint)((tv.tv_sec) * 1000 + (tv.tv_usec) / 1000) ;
}
return assumeNothrowNoGC(&perform)();
}
override void* systemHandle()
{
return cast(void*)_windowId;
}
}
void handleEvents(ref XEvent event, X11Window theWindow) nothrow @nogc
{
debug(logX11Window) fprintf(stderr, "X11Window: handleEvents()\n");
with(theWindow)
{
switch(event.type)
{
case KeyPress:
KeySym symbol;
XLookupString(&event.xkey, null, 0, &symbol, null);
listener.onKeyDown(convertKeyFromX11(symbol));
break;
case KeyRelease:
KeySym symbol;
XLookupString(&event.xkey, null, 0, &symbol, null);
listener.onKeyUp(convertKeyFromX11(symbol));
break;
case MapNotify:
case Expose:
// Resize should trigger Expose event, so we don't need to handle it here
drawMutex.lock();
box2i areaToRedraw = mergedDirtyRect;
box2i eventAreaToRedraw = box2i(event.xexpose.x, event.xexpose.y, event.xexpose.x + event.xexpose.width, event.xexpose.y + event.xexpose.height);
areaToRedraw = areaToRedraw.expand(eventAreaToRedraw);
emptyMergedBoxes();
drawMutex.unlock();
if (!areaToRedraw.empty()) {
listener.onDraw(WindowPixelFormat.RGBA8);
box2i[] areasToRedraw = (&areaToRedraw)[0..1];
swapBuffers(_wfb, areasToRedraw);
}
break;
case ConfigureNotify:
if (event.xconfigure.width != width || event.xconfigure.height != height)
{
// Handle resize event
width = event.xconfigure.width;
height = event.xconfigure.height;
_wfb = listener.onResized(width, height);
sendRepaintIfUIDirty();
}
break;
case MotionNotify:
int newMouseX = event.xmotion.x;
int newMouseY = event.xmotion.y;
int dx = newMouseX - lastMouseX;
int dy = newMouseY - lastMouseY;
listener.onMouseMove(newMouseX, newMouseY, dx, dy, mouseStateFromX11(event.xbutton.state));
lastMouseX = newMouseX;
lastMouseY = newMouseY;
break;
case ButtonPress:
int newMouseX = event.xbutton.x;
int newMouseY = event.xbutton.y;
MouseButton button;
if (event.xbutton.button == Button1)
button = MouseButton.left;
else if (event.xbutton.button == Button3)
button = MouseButton.right;
else if (event.xbutton.button == Button2)
button = MouseButton.middle;
else if (event.xbutton.button == Button4)
button = MouseButton.x1;
else if (event.xbutton.button == Button5)
button = MouseButton.x2;
bool isDoubleClick;
lastMouseX = newMouseX;
lastMouseY = newMouseY;
if (event.xbutton.button == Button4 || event.xbutton.button == Button5)
{
listener.onMouseWheel(newMouseX, newMouseY, 0, event.xbutton.button == Button4 ? 1 : -1,
mouseStateFromX11(event.xbutton.state));
}
else
{
listener.onMouseClick(newMouseX, newMouseY, button, isDoubleClick, mouseStateFromX11(event.xbutton.state));
}
break;
case ButtonRelease:
int newMouseX = event.xbutton.x;
int newMouseY = event.xbutton.y;
MouseButton button;
lastMouseX = newMouseX;
lastMouseY = newMouseY;
if (event.xbutton.button == Button1)
button = MouseButton.left;
else if (event.xbutton.button == Button3)
button = MouseButton.right;
else if (event.xbutton.button == Button2)
button = MouseButton.middle;
else if (event.xbutton.button == Button4 || event.xbutton.button == Button5)
break;
listener.onMouseRelease(newMouseX, newMouseY, button, mouseStateFromX11(event.xbutton.state));
break;
case DestroyNotify:
XDestroyImage(_graphicImage);
XFreeGC(_display, _graphicGC);
atomicStore(_terminated, true);
break;
case ClientMessage:
// TODO Possibly not used anymore
if (event.xclient.data.l[0] == _closeAtom)
{
atomicStore(_terminated, true);
XDestroyImage(_graphicImage);
XFreeGC(_display, _graphicGC);
XDestroyWindow(_display, _windowId);
XFlush(_display);
}
break;
default:
break;
}
}
}
Key convertKeyFromX11(KeySym symbol)
{
switch(symbol)
{
case XK_space:
return Key.space;
case XK_Up:
return Key.upArrow;
case XK_Down:
return Key.downArrow;
case XK_Left:
return Key.leftArrow;
case XK_Right:
return Key.rightArrow;
case XK_0: .. case XK_9:
return cast(Key)(Key.digit0 + (symbol - XK_0));
case XK_KP_0: .. case XK_KP_9:
return cast(Key)(Key.digit0 + (symbol - XK_KP_0));
case XK_A: .. case XK_Z:
return cast(Key)(Key.A + (symbol - XK_A));
case XK_a: .. case XK_z:
return cast(Key)(Key.a + (symbol - XK_a));
case XK_Return:
case XK_KP_Enter:
return Key.enter;
case XK_Escape:
return Key.escape;
case XK_BackSpace:
return Key.backspace;
default:
return Key.unsupported;
}
}
MouseState mouseStateFromX11(uint state) {
return MouseState(
(state & Button1Mask) == Button1Mask,
(state & Button3Mask) == Button3Mask,
(state & Button2Mask) == Button2Mask,
false, false,
(state & ControlMask) == ControlMask,
(state & ShiftMask) == ShiftMask,
(state & Mod1Mask) == Mod1Mask);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment