mirror of
https://github.com/quelsolaar/MergeSource
synced 2025-02-01 09:58:42 -05:00
302 lines
8.8 KiB
C
302 lines
8.8 KiB
C
/*
|
|
* Betray back-end for native X11. By Emil Brink, who is by no means an
|
|
* experienced Xlib programmer. Comments and improvements very welcome.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "betray.h"
|
|
|
|
#ifdef __linux__
|
|
|
|
#include <X11/Xlib.h>
|
|
#include <X11/keysym.h>
|
|
|
|
#define GLX_GLXEXT_PROTOTYPES
|
|
|
|
#include <GL/gl.h>
|
|
#include <GL/glx.h>
|
|
|
|
static struct {
|
|
void (*context)(void); /* User-supplied context-change callback function. */
|
|
Display *dpl; /* The X11 display connection. */
|
|
Window win; /* Our window. */
|
|
GLXContext ctx; /* The OpenGL context connected to the window. */
|
|
XVisualInfo *visual; /* The visual we use. Not very well-defined. */
|
|
Atom wmproto, wmdel; /* Atoms for receiving the WM_DELETE_WINDOW message. */
|
|
boolean warp; /* Flag changed by betray_set_mouse_warp(). */
|
|
int warp_x, warp_y; /* Center coordinates of window, in window space. */
|
|
} x11info;
|
|
|
|
#if defined __GNUC__
|
|
#define UNUSED(decl) decl __attribute__((unused))
|
|
#else
|
|
#define UNUSED(decl) decl
|
|
#endif
|
|
|
|
/* ---------------------------------------------------------------------------------------------- */
|
|
|
|
void betray_set_context_update_func(void (*context_func)(void))
|
|
{
|
|
x11info.context = context_func;
|
|
}
|
|
|
|
boolean betray_internal_set_display(uint size_x, uint size_y, UNUSED(boolean full_screen))
|
|
{
|
|
betray_reshape_view(size_x, size_y); /* Just resize, and be done. */
|
|
x11info.warp_x = size_x / 2;
|
|
x11info.warp_y = size_y / 2;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* This is an atexit() callback, registered by internal_init_display(), below. Clean-up time. */
|
|
static void cb_exit(void)
|
|
{
|
|
if(x11info.dpl != NULL)
|
|
{
|
|
XAutoRepeatOn(x11info.dpl);
|
|
if(x11info.win != None)
|
|
XDestroyWindow(x11info.dpl, x11info.win);
|
|
XCloseDisplay(x11info.dpl);
|
|
}
|
|
}
|
|
|
|
boolean betray_internal_init_display(UNUSED(int argc), UNUSED(char **argv), uint size_x, uint size_y, boolean full_screen, const char *caption)
|
|
{
|
|
int attrs[] = { GLX_RGBA, GLX_DOUBLEBUFFER,
|
|
GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8,
|
|
/*GLX_STENCIL_SIZE, 8*/ GLX_DEPTH_SIZE, 8,
|
|
None };
|
|
XTextProperty prop;
|
|
|
|
if((x11info.dpl = XOpenDisplay(NULL)) == NULL)
|
|
{
|
|
fprintf(stderr, "Betray: Unable to open X11 display connection, aborting\n");
|
|
return FALSE;
|
|
}
|
|
if((x11info.win = XCreateSimpleWindow(x11info.dpl, DefaultRootWindow(x11info.dpl),
|
|
0, 0, size_x, size_y,
|
|
0,
|
|
BlackPixel(x11info.dpl, DefaultScreen(x11info.dpl)),
|
|
BlackPixel(x11info.dpl, DefaultScreen(x11info.dpl)))) == None)
|
|
{
|
|
fprintf(stderr, "Betray: Unable to open X11 window, aborting\n");
|
|
return FALSE;
|
|
}
|
|
XSelectInput(x11info.dpl, x11info.win,
|
|
ButtonPressMask | ButtonReleaseMask |
|
|
KeyPressMask | KeyReleaseMask |
|
|
PointerMotionMask |
|
|
StructureNotifyMask);
|
|
|
|
/* Set the window's caption. */
|
|
XStringListToTextProperty((char **) &caption, 1, &prop);
|
|
XSetWMName(x11info.dpl, x11info.win, &prop);
|
|
/* Enable delete event from window manager. */
|
|
if((x11info.wmproto = XInternAtom(x11info.dpl, "WM_PROTOCOLS", False)) == None)
|
|
{
|
|
fprintf(stderr, "Betray: Unable to look up atom WM_PROTOCOLS\n");
|
|
return FALSE;
|
|
}
|
|
if((x11info.wmdel = XInternAtom(x11info.dpl, "WM_DELETE_WINDOW", False)) != None)
|
|
XSetWMProtocols(x11info.dpl, x11info.win, &x11info.wmdel, 1);
|
|
|
|
XMapWindow(x11info.dpl, x11info.win);
|
|
|
|
|
|
/* At this point, we have a window. Now we need to attach a GL rendering context to it. */
|
|
if((x11info.visual = glXChooseVisual(x11info.dpl, DefaultScreen(x11info.dpl), attrs)) == NULL)
|
|
{
|
|
fprintf(stderr, "Betray: Unable to choose a suitable X Visual\n");
|
|
return FALSE;
|
|
}
|
|
if((x11info.ctx = glXCreateContext(x11info.dpl,
|
|
x11info.visual,
|
|
NULL,
|
|
True)) == NULL)
|
|
{
|
|
fprintf(stderr, "Betray: Unable to create X11 rendering context\n");
|
|
return FALSE;
|
|
}
|
|
if(glXMakeCurrent(x11info.dpl, x11info.win, x11info.ctx) == False)
|
|
{
|
|
fprintf(stderr, "Betray: Unable to make X11 window current\n");
|
|
return FALSE;
|
|
}
|
|
betray_internal_set_display(size_x, size_y, full_screen);
|
|
atexit(cb_exit);
|
|
XAutoRepeatOff(x11info.dpl); /* This is needed to get sensible keyboard events. */
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void * betray_get_gl_proc_address(void)
|
|
{
|
|
#if defined GLX_ARB_get_proc_address
|
|
return glXGetProcAddressARB;
|
|
#else
|
|
return glXGetProcAddress;
|
|
#endif
|
|
}
|
|
|
|
void betray_set_mouse_warp(boolean warp)
|
|
{
|
|
x11info.warp = warp;
|
|
}
|
|
|
|
static int button_map(int button)
|
|
{
|
|
static const int x2betray[] = { 0, 2, 1 };
|
|
|
|
return x2betray[button];
|
|
}
|
|
|
|
/* Grab events for this window from the X11 event queue, and handle them.
|
|
* Returns 0 if we should quit, 1 to keep running.
|
|
*/
|
|
static int x11_event_handler(BInputState *input)
|
|
{
|
|
XEvent ev;
|
|
|
|
/* Flush out any ClientMessage events, looking for WM_DELETE_WINDOW client message. We can't
|
|
* do this below, since XCheckWindowEvent() won't return non-masked types like ClientMessage.
|
|
*/
|
|
while(XCheckTypedEvent(x11info.dpl, ClientMessage, &ev))
|
|
{
|
|
if(ev.xclient.window == x11info.win &&
|
|
ev.xclient.message_type == x11info.wmproto &&
|
|
ev.xclient.format == 32 &&
|
|
(Atom) ev.xclient.data.l[0] == x11info.wmdel)
|
|
return 0;
|
|
}
|
|
|
|
if(x11info.warp)
|
|
input->pointer_x = input->pointer_y = 0.0f;
|
|
|
|
while(XCheckWindowEvent(x11info.dpl, x11info.win,
|
|
~0l,
|
|
&ev))
|
|
{
|
|
switch(ev.type)
|
|
{
|
|
case ButtonPress:
|
|
if(ev.xbutton.button >= 1 && ev.xbutton.button <= 3)
|
|
input->mouse_button[button_map(ev.xbutton.button - 1)] = TRUE;
|
|
break;
|
|
case ButtonRelease:
|
|
if(ev.xbutton.button >= 1 && ev.xbutton.button <= 3)
|
|
input->mouse_button[button_map(ev.xbutton.button - 1)] = FALSE;
|
|
break;
|
|
case KeyPress:
|
|
{
|
|
char buf[32];
|
|
KeySym sym;
|
|
|
|
XLookupString(&ev.xkey, buf, sizeof buf, &sym, NULL);
|
|
if(betray_is_type_in())
|
|
{
|
|
if(sym == XK_Return || sym == XK_KP_Enter)
|
|
betray_end_type_in_mode(FALSE);
|
|
else if(sym == XK_Escape)
|
|
betray_end_type_in_mode(TRUE);
|
|
else if(sym == XK_Delete)
|
|
{
|
|
betray_move_cursor(1);
|
|
betray_delete_character();
|
|
}
|
|
else if(sym == XK_BackSpace)
|
|
betray_delete_character();
|
|
else if(sym == XK_Left)
|
|
betray_move_cursor(-1);
|
|
else if(sym == XK_Right)
|
|
betray_move_cursor(1);
|
|
else if(sym == XK_End)
|
|
betray_move_cursor(32000);
|
|
else if(sym == XK_Home)
|
|
betray_move_cursor(-32000);
|
|
else if(buf[0] >= ' ' && buf[1] == '\0')
|
|
betray_insert_character(buf[0]);
|
|
}
|
|
else
|
|
betray_internal_key_add(input, sym, TRUE);
|
|
}
|
|
break;
|
|
case KeyRelease:
|
|
if(!betray_is_type_in())
|
|
{
|
|
KeySym sym = XLookupKeysym(&ev.xkey, 0);
|
|
|
|
if(ev.xkey.state & (LockMask | ShiftMask))
|
|
XConvertCase(sym, &sym, &sym);
|
|
betray_internal_key_add(input, sym, FALSE);
|
|
}
|
|
break;
|
|
case MotionNotify:
|
|
{
|
|
uint w, h;
|
|
|
|
/* In in warp-mode, disregard events from warp spot. Otherwise, we get plenty of
|
|
* false, negative, deltas after any movement, from the warp call. Badness.
|
|
*/
|
|
if(x11info.warp && ev.xmotion.x == x11info.warp_x && ev.xmotion.y == x11info.warp_y)
|
|
break;
|
|
/* Map the coordinates into the ranges used by Betray. This code is more
|
|
* or less directly lifted from the corresponding handler in the SDL backend.
|
|
*/
|
|
betray_get_screen_mode(&w, &h, NULL);
|
|
input->delta_pointer_x = -input->pointer_x;
|
|
input->pointer_x = (float) ev.xmotion.x / (float) w * 2.0f - 1.0f;
|
|
input->delta_pointer_x += input->pointer_x;
|
|
|
|
input->delta_pointer_y = -input->pointer_y;
|
|
input->pointer_y = ((int) h / 2 - ev.xmotion.y) / (float) h * 2.0f * ((float) h / (float) w);
|
|
input->delta_pointer_y += input->pointer_y;
|
|
/* printf(" mouse at %d,%d -> (%g,%g), delta (%g,%g)\n", ev.xmotion.x, ev.xmotion.y,
|
|
input->pointer_x, input->pointer_y,
|
|
input->delta_pointer_x, input->delta_pointer_y);
|
|
*/ }
|
|
break;
|
|
case ConfigureNotify:
|
|
betray_internal_set_display(ev.xconfigure.width, ev.xconfigure.height, FALSE);
|
|
if(x11info.context != NULL) /* If user has a context function registered, call it. */
|
|
x11info.context();
|
|
break;
|
|
}
|
|
}
|
|
if(x11info.warp)
|
|
XWarpPointer(x11info.dpl, x11info.win, x11info.win, 0, 0, 0, 0, x11info.warp_x, x11info.warp_y);
|
|
return 1;
|
|
}
|
|
|
|
void betray_launch_main_loop(void)
|
|
{
|
|
BInputState *input;
|
|
|
|
input = betray_get_input_state();
|
|
|
|
while(x11_event_handler(input))
|
|
{
|
|
if(input->mouse_button[0] == FALSE && input->last_mouse_button[0] == FALSE)
|
|
{
|
|
input->click_pointer_x = input->pointer_x;
|
|
input->click_pointer_y = input->pointer_y;
|
|
}
|
|
betray_time_update();
|
|
betray_action(BAM_EVENT);
|
|
betray_action(BAM_DRAW);
|
|
input->delta_pointer_x = input->delta_pointer_y = 0;
|
|
input->event_count = 0;
|
|
glXSwapBuffers(x11info.dpl, x11info.win);
|
|
input->time++;
|
|
input->last_mouse_button[0] = input->mouse_button[0];
|
|
input->last_mouse_button[1] = input->mouse_button[1];
|
|
input->last_mouse_button[2] = input->mouse_button[2];
|
|
betray_action(BAM_MAIN);
|
|
}
|
|
/* We don't bother with closing the window, the exit handler does that. */
|
|
}
|
|
|
|
#endif /* BETRAY_X11_SYSTEM_WRAPPER */
|