diff --git a/cmake/ConkyBuildOptions.cmake b/cmake/ConkyBuildOptions.cmake index 17be9f0f..c2e8368f 100644 --- a/cmake/ConkyBuildOptions.cmake +++ b/cmake/ConkyBuildOptions.cmake @@ -169,7 +169,6 @@ if(BUILD_X11) option(BUILD_XINERAMA "Build Xinerama support" true) option(BUILD_XDBE "Build Xdbe (double-buffer) support" true) option(BUILD_XFT "Build Xft (freetype fonts) support" true) - option(BUILD_IMLIB2 "Enable Imlib2 support" true) option(BUILD_XSHAPE "Enable Xshape support" true) else(BUILD_X11) set(OWN_WINDOW false CACHE BOOL "Enable own_window support" FORCE) @@ -178,7 +177,6 @@ else(BUILD_X11) set(BUILD_XINERAMA false CACHE BOOL "Build Xinerama support" FORCE) set(BUILD_XDBE false CACHE BOOL "Build Xdbe (double-buffer) support" FORCE) set(BUILD_XFT false CACHE BOOL "Build Xft (freetype fonts) support" FORCE) - set(BUILD_IMLIB2 false CACHE BOOL "Enable Imlib2 support" FORCE) set(BUILD_XSHAPE false CACHE BOOL "Enable Xshape support" FORCE) set(BUILD_NVIDIA false) endif(BUILD_X11) @@ -192,9 +190,14 @@ if(BUILD_WAYLAND) endif(BUILD_WAYLAND) if(BUILD_GUI) + option(BUILD_IMLIB2 "Enable Imlib2 support" true) option(BUILD_MOUSE_EVENTS "Enable mouse event support" true) endif(BUILD_GUI) +if(BUILD_GUI AND BUILD_MOUSE_EVENTS) + option(BUILD_XINPUT "Build Xinput 2 support" true) +endif(BUILD_GUI AND BUILD_MOUSE_EVENTS) + if(OWN_WINDOW) option(BUILD_ARGB "Build ARGB (real transparency) support" true) else(OWN_WINDOW) diff --git a/cmake/ConkyPlatformChecks.cmake b/cmake/ConkyPlatformChecks.cmake index 9701b1e0..4b5943dd 100644 --- a/cmake/ConkyPlatformChecks.cmake +++ b/cmake/ConkyPlatformChecks.cmake @@ -402,6 +402,29 @@ if(BUILD_X11) set(conky_libs ${conky_libs} ${X11_Xfixes_LIB}) endif(BUILD_XFIXES) + + # check for Xinput + if(BUILD_XINPUT) + if(NOT X11_Xinput_FOUND) + message(FATAL_ERROR "Unable to find Xinput library") + endif(NOT X11_Xinput_FOUND) + + set(conky_libs ${conky_libs} ${X11_Xinput_LIB}) + endif(BUILD_XINPUT) + + + if(X11_xcb_FOUND) + set(HAVE_XCB true) + set(conky_libs ${conky_libs} ${X11_xcb_LIB}) + if(X11_xcb_errors_FOUND) + set(HAVE_XCB_ERRORS true) + set(conky_libs ${conky_libs} ${X11_xcb_LIB} ${X11_xcb_errors_LIB}) + else(X11_xcb_errors_FOUND) + set(HAVE_XCB_ERRORS false) + endif(X11_xcb_errors_FOUND) + else(X11_xcb_FOUND) + set(HAVE_XCB false) + endif(X11_xcb_FOUND) else(X11_FOUND) message(FATAL_ERROR "Unable to find X11 library") endif(X11_FOUND) diff --git a/cmake/config.h.in b/cmake/config.h.in index 54936c3f..7efa4641 100644 --- a/cmake/config.h.in +++ b/cmake/config.h.in @@ -42,6 +42,9 @@ #cmakedefine HAVE_CLOCK_GETTIME 1 +#cmakedefine HAVE_XCB 1 +#cmakedefine HAVE_XCB_ERRORS 1 + #cmakedefine BUILD_WAYLAND 1 #cmakedefine BUILD_X11 1 @@ -60,6 +63,8 @@ #cmakedefine BUILD_XFIXES 1 +#cmakedefine BUILD_XINPUT 1 + #cmakedefine BUILD_ARGB 1 #cmakedefine BUILD_XDBE 1 diff --git a/src/display-x11.cc b/src/display-x11.cc index bdb4d461..8e63c1eb 100644 --- a/src/display-x11.cc +++ b/src/display-x11.cc @@ -24,6 +24,7 @@ * */ +#include #include #ifdef BUILD_X11 @@ -44,6 +45,9 @@ #endif /* BUILD_IMLIB2 */ #ifdef BUILD_MOUSE_EVENTS #include "mouse-events.h" +#ifdef BUILD_XINPUT +#include +#endif /* BUILD_XINPUT */ #endif /* BUILD_MOUSE_EVENTS */ #endif /* BUILD_X11 */ @@ -51,6 +55,7 @@ #include #include #include +#include #include "colours.h" #include "conky.h" @@ -229,27 +234,6 @@ bool display_output_x11::shutdown() { return true; } -inline void propagate_unconsumed_event(XEvent &ev, bool is_consumed) { - const uint32_t capture_mask = ButtonPressMask | ButtonReleaseMask | ButtonMotionMask; - - // SAFETY: XEvent is a union and all event types this function gets called for - // share same alignment and accessed fields share same offsets. - if (!is_consumed) { - /* forward the event to the desktop window */ - ev.xmotion.window = window.desktop; - ev.xmotion.x = ev.xmotion.x_root; - ev.xmotion.y = ev.xmotion.y_root; - XSendEvent(display, window.desktop, False, capture_mask, &ev); - if (false && ev.type == ButtonPress) { - XSetInputFocus(display, window.desktop, RevertToNone, ev.xmotion.time); - } else if (false && ev.type == MotionNotify) { - XSetInputFocus(display, window.window, RevertToParent, ev.xmotion.time); - } - } else { - XSetInputFocus(display, window.window, RevertToParent, ev.xmotion.time); - } -} - bool display_output_x11::main_loop_wait(double t) { /* wait for X event or timeout */ if (!display || !window.gc) return true; @@ -397,6 +381,50 @@ bool display_output_x11::main_loop_wait(double t) { bool consumed = false; XNextEvent(display, &ev); + +#ifdef BUILD_XINPUT + if (ev.type == GenericEvent && ev.xcookie.extension == window.xi_opcode) { + XGetEventData(display, &ev.xcookie); + if (ev.xcookie.evtype == XI_Motion) { + auto *data = reinterpret_cast(ev.xcookie.data); + + // XQueryPointer returns wrong results because conky is a weird window + Window query_result = query_x11_window_at_pos(display, data->root_x, data->root_y); + + //printf("over: %#x w:%#x d:%#x r:%#x\n", query_result, window.window, window.desktop, window.root); + + static bool cursor_inside = false; + if ((query_result != 0 && query_result == window.window) || + ((query_result == window.desktop || query_result == window.root || query_result == 0) && data->root_x >= window.x && data->root_x < (window.x + window.width) && + data->root_y >= window.y && data->root_y < (window.y + window.height))) { + //puts("over self"); + if (!cursor_inside) { + llua_mouse_hook(mouse_crossing_event( + mouse_event_t::AREA_ENTER, + data->root_x - window.x, data->root_y - window.x, + data->root_x, data->root_y + )); + } + cursor_inside = true; + } else if (cursor_inside) { + //puts("left"); + llua_mouse_hook(mouse_crossing_event( + mouse_event_t::AREA_LEAVE, + data->root_x - window.x, data->root_y - window.x, + data->root_x, data->root_y + )); + cursor_inside = false; + } else { + //printf("not over self\n"); + } + } + XFreeEventData(display, &ev.xcookie); + continue; + } +#endif /* BUILD_XINPUT */ + + // Any of the remaining events apply to conky window + if (ev.xany.window != window.window) continue; switch (ev.type) { case Expose: { XRectangle r; @@ -406,6 +434,10 @@ bool display_output_x11::main_loop_wait(double t) { r.height = ev.xexpose.height; XUnionRectWithRegion(&r, x11_stuff.region, x11_stuff.region); XSync(display, False); + + // modify for propagation + ev.xexpose.x += window.x; + ev.xexpose.y += window.y; break; } @@ -427,7 +459,7 @@ bool display_output_x11::main_loop_wait(double t) { #ifdef USE_ARGB } #endif - break; + continue; } #ifdef OWN_WINDOW @@ -436,7 +468,7 @@ bool display_output_x11::main_loop_wait(double t) { if (own_window.get(*state)) { set_transparent_background(window.window); } - break; + continue; case ConfigureNotify: if (own_window.get(*state)) { @@ -477,7 +509,7 @@ bool display_output_x11::main_loop_wait(double t) { fixed_pos = 1; } */ } - break; + continue; case ButtonPress: #ifdef BUILD_MOUSE_EVENTS @@ -519,7 +551,6 @@ bool display_output_x11::main_loop_wait(double t) { /* allow conky to hold input focus. */ break; } - propagate_unconsumed_event(ev, consumed); } break; @@ -553,7 +584,6 @@ bool display_output_x11::main_loop_wait(double t) { /* allow conky to hold input focus. */ break; } - propagate_unconsumed_event(ev, consumed); } break; #ifdef BUILD_MOUSE_EVENTS @@ -565,30 +595,25 @@ bool display_output_x11::main_loop_wait(double t) { consumed = llua_mouse_hook(mouse_move_event( ev.xmotion.x, ev.xmotion.y, ev.xmotion.x_root, ev.xmotion.y_root, ev.xmotion.state )); - propagate_unconsumed_event(ev, consumed); break; case LeaveNotify: - XUngrabPointer(display, ev.xcrossing.time); case EnterNotify: - { + if (window.xi_opcode == 0) { bool not_over_conky = ev.xcrossing.x_root <= window.x || ev.xcrossing.y_root <= window.y || ev.xcrossing.x_root >= window.x + window.width || ev.xcrossing.y_root >= window.y + window.height; - static bool pointer_inside = false; if ((not_over_conky && ev.xcrossing.type == LeaveNotify) || - (!pointer_inside && ev.xcrossing.type == EnterNotify)) { + (!not_over_conky && ev.xcrossing.type == EnterNotify)) { llua_mouse_hook(mouse_crossing_event( - ev.xcrossing.type == EnterNotify ? mouse_event_t::AREA_ENTER: mouse_event_t::AREA_LEAVE, + ev.xcrossing.type == EnterNotify ? mouse_event_t::AREA_ENTER : mouse_event_t::AREA_LEAVE, ev.xcrossing.x, ev.xcrossing.y, ev.xcrossing.x_root, ev.xcrossing.y_root )); - pointer_inside = ev.xcrossing.type == EnterNotify; - // can't propagate these events in a way that makes sense } } - break; + // can't propagate these events in a way that makes sense for desktop + continue; #endif /* BUILD_MOUSE_EVENTS */ -#endif - +#endif /* OWN_WINDOW */ default: #ifdef BUILD_XDAMAGE if (ev.type == x11_stuff.event_base + XDamageNotify) { @@ -597,10 +622,20 @@ bool display_output_x11::main_loop_wait(double t) { XFixesSetRegion(display, x11_stuff.part, &dev->area, 1); XFixesUnionRegion(display, x11_stuff.region2, x11_stuff.region2, x11_stuff.part); + continue; // TODO: Propagate damage } #endif /* BUILD_XDAMAGE */ break; } + + if (!consumed) { + propagate_x11_event(ev); + } else { + InputEvent *i_ev = xev_as_input_event(ev); + if (i_ev != nullptr) { + XSetInputFocus(display, window.window, RevertToParent, i_ev->common.time); + } + } } #ifdef BUILD_XDAMAGE @@ -648,6 +683,7 @@ bool display_output_x11::main_loop_wait(double t) { } void display_output_x11::sigterm_cleanup() { + puts("sigtermed"); XDestroyRegion(x11_stuff.region); x11_stuff.region = nullptr; #ifdef BUILD_XDAMAGE @@ -660,6 +696,7 @@ void display_output_x11::sigterm_cleanup() { } void display_output_x11::cleanup() { + puts("normal cleanup"); if (window_created == 1) { int border_total = get_border_total(); diff --git a/src/logging.h b/src/logging.h index 3baaa361..0e529241 100644 --- a/src/logging.h +++ b/src/logging.h @@ -31,6 +31,7 @@ #define _LOGGING_H #include +#include #include #include "config.h" #include "i18n.h" @@ -81,6 +82,7 @@ void NORM_ERR(const char *format, Args &&...args) { /* critical error with additional cleanup */ template +__attribute__((noreturn)) inline void CRIT_ERR_FREE(void *memtofree1, void *memtofree2, const char *format, Args &&...args) { NORM_ERR(format, args...); @@ -92,6 +94,7 @@ inline void CRIT_ERR_FREE(void *memtofree1, void *memtofree2, /* critical error */ template +__attribute__((noreturn)) inline void CRIT_ERR(const char *format, Args &&...args) { CRIT_ERR_FREE(nullptr, nullptr, format, args...); } diff --git a/src/x11.cc b/src/x11.cc index b0dd8e69..b7985f85 100644 --- a/src/x11.cc +++ b/src/x11.cc @@ -28,10 +28,18 @@ */ #include +#include #include "common.h" #include "config.h" #include "conky.h" #include "logging.h" +#include "x11.h" + +#include +#include +#include +#include +#include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wvariadic-macros" @@ -61,10 +69,24 @@ #ifdef BUILD_XFIXES #include #endif /* BUILD_XFIXES */ +#ifdef BUILD_XINPUT +#include +#include +#endif /* BUILD_XINPUT */ +#ifdef HAVE_XCB_ERRORS +#include +#include +#endif /* some basic X11 stuff */ Display *display = nullptr; + +#ifdef HAVE_XCB_ERRORS +xcb_connection_t *xcb_connection; +xcb_errors_context_t *xcb_errors_ctx; +#endif + /* Window stuff */ struct conky_x11_window window; @@ -215,20 +237,77 @@ imlib_cache_size_setting imlib_cache_size; /******************** ************************/ /* WARNING, this type not in Xlib spec */ -static int __attribute__((noreturn)) +static int x11_error_handler(Display *d, XErrorEvent *err) { + char *error_name = nullptr; + bool name_allocated = false; + + char *code_description = nullptr; + bool code_allocated = false; + + #ifdef HAVE_XCB_ERRORS + if (xcb_errors_ctx != nullptr) { + const char *extension; + const char *base_name = xcb_errors_get_name_for_error(xcb_errors_ctx, err->error_code, &extension); + if (extension != nullptr) { + error_name = new char(strlen(base_name) + strlen(extension) + 4); + sprintf(error_name, "%s (%s)", base_name, extension); + name_allocated = true; + } else { + error_name = const_cast(base_name); + } + + const char *major = xcb_errors_get_name_for_major_code(xcb_errors_ctx, err->request_code); + const char *minor = xcb_errors_get_name_for_minor_code(xcb_errors_ctx, err->request_code, err->minor_code); + if (minor != nullptr) { + code_description = new char(strlen(base_name) + strlen(extension) + 4); + sprintf(code_description, "%s - %s", major, minor); + code_allocated = true; + } else { + code_description = const_cast(major); + } + } + #endif + + if (error_name == nullptr) { + if (err->error_code > 0 && err->error_code < 17) { + static std::array NAMES = { + "request", "value", "window", "pixmap", "atom", "cursor", "font", "match", + "drawable", "access", "alloc", "colormap", "G context", "ID choice", + "name", "length", "implementation" + }; + error_name = const_cast(NAMES[err->error_code].c_str()); + } else { + static char code_name_buffer[3]; + error_name = reinterpret_cast(&code_name_buffer); + sprintf(error_name, "%d", err->error_code); + } + } + if (code_description == nullptr) { + code_description = new char(30 + 6 + 1); + sprintf(code_description, "error code: [major: %i, minor: %i]", err->request_code, err->minor_code); + code_allocated = true; + } + NORM_ERR( - "X Error: type %i Display %lx XID %li serial %lu error_code %i " - "request_code %i minor_code %i other Display: %lx\n", - err->type, (long unsigned)err->display, - static_cast(err->resourceid), err->serial, err->error_code, - err->request_code, err->minor_code, (long unsigned)d); - abort(); + "X %s Error:\n" + "Display: %lx, XID: %li, Serial: %lu\n" + "%s", + error_name, + reinterpret_cast(err->display), + static_cast(err->resourceid), err->serial, + code_description + ); + + if (name_allocated) free(error_name); + if (code_allocated) free(code_description); + + return 0; } -static int __attribute__((noreturn)) x11_ioerror_handler(Display *d) { - NORM_ERR("X Error: Display %lx\n", (long unsigned)d); - exit(1); +__attribute__((noreturn)) +static int x11_ioerror_handler(Display *d) { + CRIT_ERR("X IO Error: Display %lx\n", reinterpret_cast(d)); } /* X11 initializer */ @@ -267,6 +346,15 @@ static void init_x11() { update_workarea(); + #ifdef HAVE_XCB_ERRORS + auto connection = xcb_connect(NULL, NULL); + if (!xcb_connection_has_error(connection)) { + if (xcb_errors_context_new(connection, &xcb_errors_ctx) != Success) { + xcb_errors_ctx = nullptr; + } + } + #endif /* HAVE_XCB_ERRORS */ + /* WARNING, this type not in Xlib spec */ XSetErrorHandler(&x11_error_handler); XSetIOErrorHandler(&x11_ioerror_handler); @@ -854,7 +942,7 @@ void x11_init_window(lua::state &l __attribute__((unused)), bool own) { XFlush(display); - long input_mask = ExposureMask | PropertyChangeMask; + int64_t input_mask = ExposureMask | PropertyChangeMask; #ifdef OWN_WINDOW if (own_window.get(l)) { input_mask |= StructureNotifyMask | ButtonPressMask | ButtonReleaseMask; @@ -864,10 +952,40 @@ void x11_init_window(lua::state &l __attribute__((unused)), bool own) { /* it's not recommended to add event masks to special windows in X; causes a * crash */ if (own_window_type.get(l) != TYPE_DESKTOP) { - input_mask |= ButtonPressMask | ButtonReleaseMask | PointerMotionMask | - EnterWindowMask | LeaveWindowMask; + input_mask |= ButtonPressMask | ButtonReleaseMask | PointerMotionMask; + } + bool xinput_ok = false; +#ifdef BUILD_XINPUT + do { // not loop + int _ignored; // segfault if NULL + if (!XQueryExtension(display, "XInputExtension", &window.xi_opcode, &_ignored, &_ignored)) { + // events will still ~work but let the user know why they're buggy + NORM_ERR("XInput extension is not supported by X11!"); + break; + } + + int32_t major = 2, minor = 0; + uint32_t retval = XIQueryVersion(display, &major, &minor); + if (retval != Success) { + NORM_ERR("Error: XInput 2.0 is not supported!"); + break; + } + unsigned char mask_bytes[(XI_LASTEVENT + 7) / 8] = {0}; /* must be zeroed! */ + XISetMask(mask_bytes, XI_Motion); + + XIEventMask ev_masks[1]; + ev_masks[0].deviceid = XIAllDevices; + ev_masks[0].mask_len = sizeof(mask_bytes); + ev_masks[0].mask = mask_bytes; + XISelectEvents(display, window.root, ev_masks, 1); + xinput_ok = true; + } while (false); +#endif /* BUILD_XINPUT */ + if (!xinput_ok && own_window_type.get(l) != TYPE_DESKTOP) { + input_mask |= EnterWindowMask | LeaveWindowMask; } #endif /* BUILD_MOUSE_EVENTS */ + window.event_mask = input_mask; XSelectInput(display, window.window, input_mask); window_created = 1; @@ -1229,3 +1347,74 @@ void print_mouse_speed(struct text_object *obj, char *p, XGetPointerControl(display, &acc_num, &acc_denom, &threshold); snprintf(p, p_max_size, "%d%%", (110 - threshold)); } + +InputEvent *xev_as_input_event(XEvent &ev) { + if (ev.type == KeyPress || ev.type == KeyRelease || + ev.type == ButtonPress || ev.type == ButtonRelease || ev.type == MotionNotify || + ev.type == EnterNotify || ev.type == LeaveNotify) { + return reinterpret_cast(&ev); + } else { + return nullptr; + } +} + +void propagate_x11_event(XEvent &ev) { + InputEvent *i_ev = xev_as_input_event(ev); + /* forward the event to the desktop window */ + if (i_ev != nullptr) { + i_ev->common.window = window.desktop; + i_ev->common.x = i_ev->common.x_root; + i_ev->common.y = i_ev->common.y_root; + } + XSendEvent(display, window.desktop, False, window.event_mask, &ev); + // FIXME (before-merge): Should we be setting input focus after forwarding + // events? + if (false && ev.type == ButtonPress) { + XSetInputFocus(display, window.desktop, RevertToNone, i_ev->common.time); + } else if (false && ev.type == MotionNotify) { + XSetInputFocus(display, window.window, RevertToParent, i_ev->common.time); + } +} + +#ifdef BUILD_MOUSE_EVENTS +Window last_descendant(Display* display, Window parent) { + Window ignored, *children; + uint32_t count; + + Window current = parent; + + while (XQueryTree(display, current, &ignored, &ignored, &children, &count) && count != 0) { + current = children[count - 1]; + XFree(children); + } + + return current; +} + +Window query_x11_window_at_pos(Display* display, int x, int y) { + Window root = DefaultRootWindow(display); + + Window root_return, parent_return, *windows; + unsigned int count; + + Window last = None; + + XWindowAttributes attrs; + if (XQueryTree(display, root, &root_return, &parent_return, &windows, &count) != 0) { + for (unsigned int i = 0; i < count; i++) { + if (XGetWindowAttributes(display, windows[i], &attrs)) { + if (attrs.map_state == IsViewable && x >= attrs.x && x < (attrs.x + attrs.width) && y >= attrs.y && y < (attrs.y + attrs.height)) { + last = windows[i]; + } + } + } + + if (count != 0) { + XFree(windows); + } + } + + return last_descendant(display, last); +} + +#endif /* BUILD_MOUSE_EVENTS */ diff --git a/src/x11.h b/src/x11.h index 6431e4c3..0a574abb 100644 --- a/src/x11.h +++ b/src/x11.h @@ -5,7 +5,7 @@ * Please see COPYING for details * * Copyright (c) 2005-2021 Brenden Matthews, Philip Kovacs, et. al. - * (see AUTHORS) + * (see AUTHORS) * All rights reserved. * * This program is free software: you can redistribute it and/or modify @@ -38,6 +38,7 @@ #endif #include "setting.hh" +#include #ifdef BUILD_ARGB /* true if use_argb_visual=true and argb visual was found*/ @@ -78,6 +79,9 @@ struct conky_x11_window { Colormap colourmap; GC gc; + // Mask containing all events captured by conky + int64_t event_mask; + #ifdef BUILD_XDBE XdbeBackBuffer back_buffer; #else /*BUILD_XDBE*/ @@ -86,6 +90,9 @@ struct conky_x11_window { #ifdef BUILD_XFT XftDraw *xftdraw; #endif /*BUILD_XFT*/ +#ifdef BUILD_XINPUT + int32_t xi_opcode; +#endif /* BUILD_XINPUT */ int width; int height; @@ -106,6 +113,47 @@ void set_struts(int); void x11_init_window(lua::state &l, bool own); void deinit_x11(); +// Fields common to all X11 input events +struct InputEventCommon { + int type; /* event type */ + uint64_t serial; /* # of last request processed by server */ + Bool send_event; /* true if this came from a SendEvent request */ + Display *display; /* Display the event was read from */ + Window window; /* "event" window reported relative to */ + Window root; /* root window that the event occurred on */ + Window subwindow; /* child window */ + Time time; /* milliseconds */ + int32_t x, y; /* pointer x, y coordinates in event window */ + int32_t x_root, y_root; /* coordinates relative to root */ + uint32_t state; /* key or button mask */ +}; + +union InputEvent { + int type; // event type + + InputEventCommon common; + + // Discrete interfaces + XAnyEvent xany; // common event interface + XKeyEvent xkey; // KeyPress & KeyRelease events + XButtonEvent xbutton; // ButtonPress & ButtonRelease events + XMotionEvent xmotion; // MotionNotify event + XCrossingEvent xcrossing; // EnterNotify & LeaveNotify events + + // Ensures InputEvent matches memory layout of XEvent. + // Accessing base variant is as code smell. + XEvent base; +}; + +// Returns InputEvent pointer to provided XEvent is an input event; nullptr otherwise. +InputEvent *xev_as_input_event(XEvent &ev); +void propagate_x11_event(XEvent &ev); + +#ifdef BUILD_MOUSE_EVENTS + +Window query_x11_window_at_pos(Display* display, int x, int y); +#endif /* BUILD_MOUSE_EVENTS */ + #ifdef BUILD_XDBE void xdbe_swap_buffers(void); #else