2015-08-18 19:40:26 +00:00
|
|
|
#ifndef APPLICATION_UNIX_CPP
|
2015-05-06 00:50:57 +00:00
|
|
|
#error This file may only be included from CApplicationUnix.cpp
|
|
|
|
#endif
|
|
|
|
|
2015-08-18 22:43:30 +00:00
|
|
|
#include "boo/IApplication.hpp"
|
2015-05-06 00:50:57 +00:00
|
|
|
|
2015-08-28 00:10:46 +00:00
|
|
|
#include <X11/Xlib.h>
|
2015-10-31 04:28:21 +00:00
|
|
|
#include <X11/XKBlib.h>
|
|
|
|
#include <X11/extensions/XInput2.h>
|
2015-08-28 00:10:46 +00:00
|
|
|
#include <GL/glx.h>
|
|
|
|
#include <GL/glxext.h>
|
|
|
|
|
2015-05-13 22:21:13 +00:00
|
|
|
#include <dbus/dbus.h>
|
2015-10-28 01:47:55 +00:00
|
|
|
DBusConnection* RegisterDBus(const char* appName, bool& isFirst);
|
2015-05-13 22:21:13 +00:00
|
|
|
|
2015-10-31 04:28:21 +00:00
|
|
|
#include <LogVisor/LogVisor.hpp>
|
|
|
|
|
2015-05-13 22:21:13 +00:00
|
|
|
#include <sys/param.h>
|
2015-10-29 04:44:38 +00:00
|
|
|
#include <thread>
|
2015-11-17 06:41:32 +00:00
|
|
|
#include <mutex>
|
|
|
|
#include <condition_variable>
|
2015-05-13 22:21:13 +00:00
|
|
|
|
2015-11-30 00:20:20 +00:00
|
|
|
#include "XlibCommon.hpp"
|
|
|
|
#include <X11/cursorfont.h>
|
|
|
|
|
2015-05-06 00:50:57 +00:00
|
|
|
namespace boo
|
|
|
|
{
|
2015-10-31 04:28:21 +00:00
|
|
|
static LogVisor::LogModule Log("boo::ApplicationXCB");
|
2015-11-30 00:20:20 +00:00
|
|
|
XlibCursors X_CURSORS;
|
2015-08-28 00:10:46 +00:00
|
|
|
|
|
|
|
int XCB_GLX_EVENT_BASE = 0;
|
2015-05-12 09:38:37 +00:00
|
|
|
int XINPUT_OPCODE = 0;
|
|
|
|
|
2015-10-31 04:28:21 +00:00
|
|
|
static Window GetWindowOfEvent(XEvent* event, bool& windowEvent)
|
2015-05-09 05:33:48 +00:00
|
|
|
{
|
2015-10-31 04:28:21 +00:00
|
|
|
switch (event->type)
|
2015-05-09 05:33:48 +00:00
|
|
|
{
|
2015-10-31 04:28:21 +00:00
|
|
|
case ClientMessage:
|
2015-05-12 09:38:37 +00:00
|
|
|
{
|
|
|
|
windowEvent = true;
|
2015-10-31 04:28:21 +00:00
|
|
|
return event->xclient.window;
|
2015-05-12 09:38:37 +00:00
|
|
|
}
|
2015-10-31 04:28:21 +00:00
|
|
|
case Expose:
|
2015-05-09 05:33:48 +00:00
|
|
|
{
|
|
|
|
windowEvent = true;
|
2015-10-31 04:28:21 +00:00
|
|
|
return event->xexpose.window;
|
2015-05-09 05:33:48 +00:00
|
|
|
}
|
2015-10-31 04:28:21 +00:00
|
|
|
case ConfigureNotify:
|
2015-05-09 05:33:48 +00:00
|
|
|
{
|
|
|
|
windowEvent = true;
|
2015-10-31 04:28:21 +00:00
|
|
|
return event->xconfigure.window;
|
2015-05-09 05:33:48 +00:00
|
|
|
}
|
2015-10-31 04:28:21 +00:00
|
|
|
case KeyPress:
|
|
|
|
case KeyRelease:
|
2015-05-09 05:33:48 +00:00
|
|
|
{
|
|
|
|
windowEvent = true;
|
2015-10-31 04:28:21 +00:00
|
|
|
return event->xkey.window;
|
2015-05-09 05:33:48 +00:00
|
|
|
}
|
2015-10-31 04:28:21 +00:00
|
|
|
case ButtonPress:
|
|
|
|
case ButtonRelease:
|
2015-05-09 05:33:48 +00:00
|
|
|
{
|
|
|
|
windowEvent = true;
|
2015-12-05 00:41:30 +00:00
|
|
|
return event->xbutton.window;
|
2015-05-09 05:33:48 +00:00
|
|
|
}
|
2015-10-31 04:28:21 +00:00
|
|
|
case MotionNotify:
|
2015-05-09 05:33:48 +00:00
|
|
|
{
|
|
|
|
windowEvent = true;
|
2015-10-31 04:28:21 +00:00
|
|
|
return event->xmotion.window;
|
2015-05-12 09:38:37 +00:00
|
|
|
}
|
2015-11-05 09:28:51 +00:00
|
|
|
case EnterNotify:
|
|
|
|
case LeaveNotify:
|
|
|
|
{
|
|
|
|
windowEvent = true;
|
|
|
|
return event->xcrossing.window;
|
|
|
|
}
|
|
|
|
case FocusIn:
|
|
|
|
case FocusOut:
|
|
|
|
{
|
|
|
|
windowEvent = true;
|
|
|
|
return event->xfocus.window;
|
|
|
|
}
|
2015-10-31 04:28:21 +00:00
|
|
|
case GenericEvent:
|
2015-05-12 09:38:37 +00:00
|
|
|
{
|
2015-10-31 04:28:21 +00:00
|
|
|
if (event->xgeneric.extension == XINPUT_OPCODE)
|
2015-05-12 09:38:37 +00:00
|
|
|
{
|
2015-10-31 04:28:21 +00:00
|
|
|
switch (event->xgeneric.evtype)
|
2015-05-13 08:51:18 +00:00
|
|
|
{
|
2015-10-31 04:28:21 +00:00
|
|
|
case XI_Motion:
|
|
|
|
case XI_TouchBegin:
|
|
|
|
case XI_TouchUpdate:
|
|
|
|
case XI_TouchEnd:
|
2015-05-13 08:51:18 +00:00
|
|
|
{
|
2015-10-31 04:28:21 +00:00
|
|
|
XIDeviceEvent* ev = (XIDeviceEvent*)event;
|
2015-05-12 09:38:37 +00:00
|
|
|
windowEvent = true;
|
|
|
|
return ev->event;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-05-09 05:33:48 +00:00
|
|
|
}
|
|
|
|
}
|
2015-05-13 08:51:18 +00:00
|
|
|
windowEvent = false;
|
|
|
|
return 0;
|
2015-05-09 05:33:48 +00:00
|
|
|
}
|
2015-05-06 00:50:57 +00:00
|
|
|
|
2015-11-02 10:07:15 +00:00
|
|
|
IWindow* _WindowXlibNew(const std::string& title,
|
2015-10-31 04:28:21 +00:00
|
|
|
Display* display, int defaultScreen,
|
|
|
|
GLXContext lastCtx);
|
2015-05-06 00:50:57 +00:00
|
|
|
|
2015-11-02 10:07:15 +00:00
|
|
|
class ApplicationXlib final : public IApplication
|
2015-05-06 00:50:57 +00:00
|
|
|
{
|
2015-05-10 07:02:18 +00:00
|
|
|
IApplicationCallback& m_callback;
|
2015-05-13 22:21:13 +00:00
|
|
|
const std::string m_uniqueName;
|
2015-05-06 00:50:57 +00:00
|
|
|
const std::string m_friendlyName;
|
|
|
|
const std::string m_pname;
|
|
|
|
const std::vector<std::string> m_args;
|
2015-05-09 05:33:48 +00:00
|
|
|
|
2015-05-13 22:21:13 +00:00
|
|
|
/* DBus single-instance */
|
|
|
|
bool m_singleInstance;
|
2015-10-31 04:28:21 +00:00
|
|
|
DBusConnection* m_dbus = nullptr;
|
2015-05-13 22:21:13 +00:00
|
|
|
|
2015-05-09 05:33:48 +00:00
|
|
|
/* All windows */
|
2015-10-31 04:28:21 +00:00
|
|
|
std::unordered_map<Window, IWindow*> m_windows;
|
2015-05-09 05:33:48 +00:00
|
|
|
|
2015-10-31 04:28:21 +00:00
|
|
|
Display* m_xDisp = nullptr;
|
|
|
|
int m_xDefaultScreen = 0;
|
2015-08-28 00:10:46 +00:00
|
|
|
int m_xcbFd, m_dbusFd, m_maxFd;
|
2015-05-06 00:50:57 +00:00
|
|
|
|
|
|
|
void _deletedWindow(IWindow* window)
|
|
|
|
{
|
2015-10-31 04:28:21 +00:00
|
|
|
m_windows.erase((Window)window->getPlatformHandle());
|
2015-05-06 00:50:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public:
|
2015-11-02 10:07:15 +00:00
|
|
|
ApplicationXlib(IApplicationCallback& callback,
|
2015-05-13 22:21:13 +00:00
|
|
|
const std::string& uniqueName,
|
2015-05-06 00:50:57 +00:00
|
|
|
const std::string& friendlyName,
|
|
|
|
const std::string& pname,
|
2015-05-13 22:21:13 +00:00
|
|
|
const std::vector<std::string>& args,
|
|
|
|
bool singleInstance)
|
2015-05-06 00:50:57 +00:00
|
|
|
: m_callback(callback),
|
2015-05-13 22:21:13 +00:00
|
|
|
m_uniqueName(uniqueName),
|
2015-05-06 00:50:57 +00:00
|
|
|
m_friendlyName(friendlyName),
|
|
|
|
m_pname(pname),
|
2015-05-13 22:21:13 +00:00
|
|
|
m_args(args),
|
|
|
|
m_singleInstance(singleInstance)
|
2015-05-09 05:33:48 +00:00
|
|
|
{
|
2015-05-13 22:21:13 +00:00
|
|
|
/* DBus single instance registration */
|
|
|
|
bool isFirst;
|
2015-10-28 01:47:55 +00:00
|
|
|
m_dbus = RegisterDBus(uniqueName.c_str(), isFirst);
|
2015-05-13 22:21:13 +00:00
|
|
|
if (m_singleInstance)
|
|
|
|
{
|
|
|
|
if (!isFirst)
|
|
|
|
{
|
|
|
|
/* This is a duplicate instance, send signal and return */
|
|
|
|
if (args.size())
|
|
|
|
{
|
|
|
|
/* create a signal & check for errors */
|
|
|
|
DBusMessage*
|
|
|
|
msg = dbus_message_new_signal("/boo/signal/FileHandler",
|
|
|
|
"boo.signal.FileHandling",
|
|
|
|
"Open");
|
|
|
|
|
|
|
|
/* append arguments onto signal */
|
|
|
|
DBusMessageIter argsIter;
|
|
|
|
dbus_message_iter_init_append(msg, &argsIter);
|
|
|
|
for (const std::string& arg : args)
|
|
|
|
{
|
|
|
|
const char* sigvalue = arg.c_str();
|
|
|
|
dbus_message_iter_append_basic(&argsIter, DBUS_TYPE_STRING, &sigvalue);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* send the message and flush the connection */
|
|
|
|
dbus_uint32_t serial;
|
|
|
|
dbus_connection_send(m_dbus, msg, &serial);
|
|
|
|
dbus_connection_flush(m_dbus);
|
|
|
|
dbus_message_unref(msg);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* This is the first instance, register for signal */
|
|
|
|
// add a rule for which messages we want to see
|
|
|
|
DBusError err = {};
|
|
|
|
dbus_bus_add_match(m_dbus, "type='signal',interface='boo.signal.FileHandling'", &err);
|
|
|
|
dbus_connection_flush(m_dbus);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-31 04:28:21 +00:00
|
|
|
if (!XInitThreads())
|
|
|
|
{
|
|
|
|
Log.report(LogVisor::FatalError, "X doesn't support multithreading");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Open Xlib Display */
|
|
|
|
m_xDisp = XOpenDisplay(0);
|
|
|
|
if (!m_xDisp)
|
|
|
|
{
|
|
|
|
Log.report(LogVisor::FatalError, "Can't open X display");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_xDefaultScreen = DefaultScreen(m_xDisp);
|
2015-11-30 00:20:20 +00:00
|
|
|
X_CURSORS.m_pointer = XCreateFontCursor(m_xDisp, XC_left_ptr);
|
|
|
|
X_CURSORS.m_hArrow = XCreateFontCursor(m_xDisp, XC_sb_h_double_arrow);
|
|
|
|
X_CURSORS.m_vArrow = XCreateFontCursor(m_xDisp, XC_sb_v_double_arrow);
|
|
|
|
X_CURSORS.m_wait = XCreateFontCursor(m_xDisp, XC_watch);
|
2015-08-28 00:10:46 +00:00
|
|
|
|
2015-05-13 08:51:18 +00:00
|
|
|
/* The xkb extension requests that the X server does not
|
2015-05-09 05:33:48 +00:00
|
|
|
* send repeated keydown events when a key is held */
|
2015-10-31 04:28:21 +00:00
|
|
|
XkbQueryExtension(m_xDisp, &XINPUT_OPCODE, nullptr, nullptr, nullptr, nullptr);
|
|
|
|
XkbSetDetectableAutoRepeat(m_xDisp, True, nullptr);
|
2015-08-28 00:10:46 +00:00
|
|
|
|
|
|
|
/* Get file descriptors of xcb and dbus interfaces */
|
2015-10-31 04:28:21 +00:00
|
|
|
m_xcbFd = ConnectionNumber(m_xDisp);
|
2015-08-28 00:10:46 +00:00
|
|
|
dbus_connection_get_unix_fd(m_dbus, &m_dbusFd);
|
|
|
|
m_maxFd = MAX(m_xcbFd, m_dbusFd);
|
2015-05-13 08:51:18 +00:00
|
|
|
|
2015-10-31 04:28:21 +00:00
|
|
|
XFlush(m_xDisp);
|
2015-05-09 05:33:48 +00:00
|
|
|
}
|
|
|
|
|
2015-11-02 10:07:15 +00:00
|
|
|
~ApplicationXlib()
|
2015-05-09 05:33:48 +00:00
|
|
|
{
|
2015-10-31 04:28:21 +00:00
|
|
|
XCloseDisplay(m_xDisp);
|
2015-05-09 05:33:48 +00:00
|
|
|
}
|
2015-05-06 00:50:57 +00:00
|
|
|
|
|
|
|
EPlatformType getPlatformType() const
|
|
|
|
{
|
2015-11-21 01:12:22 +00:00
|
|
|
return EPlatformType::Xlib;
|
2015-05-06 00:50:57 +00:00
|
|
|
}
|
|
|
|
|
2015-11-17 20:25:17 +00:00
|
|
|
/* Empty handler for SIGINT */
|
|
|
|
static void _sigint(int) {}
|
2015-11-17 01:24:58 +00:00
|
|
|
|
2015-10-28 01:47:55 +00:00
|
|
|
int run()
|
2015-05-06 00:50:57 +00:00
|
|
|
{
|
2015-10-31 04:28:21 +00:00
|
|
|
if (!m_xDisp)
|
2015-10-28 01:47:55 +00:00
|
|
|
return 1;
|
2015-05-13 22:21:13 +00:00
|
|
|
|
2015-11-17 20:25:17 +00:00
|
|
|
/* SIGINT will be used to cancel main thread when client thread ends
|
|
|
|
* (also enables graceful quitting via ctrl-c) */
|
2015-11-17 01:24:58 +00:00
|
|
|
pthread_t mainThread = pthread_self();
|
|
|
|
struct sigaction s;
|
2015-11-17 20:25:17 +00:00
|
|
|
s.sa_handler = _sigint;
|
2015-11-17 01:24:58 +00:00
|
|
|
sigemptyset(&s.sa_mask);
|
|
|
|
s.sa_flags = 0;
|
2015-11-17 20:25:17 +00:00
|
|
|
sigaction(SIGINT, &s, nullptr);
|
|
|
|
|
|
|
|
sigset_t waitmask, origmask;
|
|
|
|
sigemptyset(&waitmask);
|
|
|
|
sigaddset(&waitmask, SIGINT);
|
|
|
|
pthread_sigmask(SIG_BLOCK, &waitmask, &origmask);
|
2015-11-17 01:24:58 +00:00
|
|
|
|
2015-10-29 04:44:38 +00:00
|
|
|
/* Spawn client thread */
|
2015-11-05 09:28:51 +00:00
|
|
|
int clientReturn = INT_MIN;
|
2015-11-17 06:41:32 +00:00
|
|
|
std::mutex initmt;
|
|
|
|
std::condition_variable initcv;
|
|
|
|
std::unique_lock<std::mutex> outerLk(initmt);
|
2015-10-29 04:44:38 +00:00
|
|
|
std::thread clientThread([&]()
|
2015-11-17 01:24:58 +00:00
|
|
|
{
|
2015-11-17 06:41:32 +00:00
|
|
|
std::unique_lock<std::mutex> innerLk(initmt);
|
|
|
|
innerLk.unlock();
|
|
|
|
initcv.notify_one();
|
2015-11-17 01:24:58 +00:00
|
|
|
clientReturn = m_callback.appMain(this);
|
2015-11-17 20:25:17 +00:00
|
|
|
pthread_kill(mainThread, SIGINT);
|
2015-11-17 01:24:58 +00:00
|
|
|
});
|
2015-11-17 06:41:32 +00:00
|
|
|
initcv.wait(outerLk);
|
2015-05-13 08:51:18 +00:00
|
|
|
|
2015-10-29 04:44:38 +00:00
|
|
|
/* Begin application event loop */
|
2015-11-05 09:28:51 +00:00
|
|
|
while (clientReturn == INT_MIN)
|
2015-05-09 05:33:48 +00:00
|
|
|
{
|
2015-10-29 04:44:38 +00:00
|
|
|
fd_set fds;
|
|
|
|
FD_ZERO(&fds);
|
|
|
|
FD_SET(m_xcbFd, &fds);
|
|
|
|
FD_SET(m_dbusFd, &fds);
|
2015-11-17 20:25:17 +00:00
|
|
|
if (pselect(m_maxFd+1, &fds, NULL, NULL, NULL, &origmask) < 0)
|
2015-11-17 01:24:58 +00:00
|
|
|
{
|
2015-11-17 20:25:17 +00:00
|
|
|
/* SIGINT handled here */
|
2015-11-17 01:24:58 +00:00
|
|
|
if (errno == EINTR)
|
|
|
|
break;
|
|
|
|
}
|
2015-10-29 04:44:38 +00:00
|
|
|
|
|
|
|
if (FD_ISSET(m_xcbFd, &fds))
|
2015-05-13 22:21:13 +00:00
|
|
|
{
|
2015-11-12 04:31:59 +00:00
|
|
|
XLockDisplay(m_xDisp);
|
2015-10-31 04:28:21 +00:00
|
|
|
while (XPending(m_xDisp))
|
2015-05-13 22:21:13 +00:00
|
|
|
{
|
2015-10-31 04:28:21 +00:00
|
|
|
XEvent event;
|
|
|
|
XNextEvent(m_xDisp, &event);
|
2015-10-29 04:44:38 +00:00
|
|
|
bool windowEvent;
|
2015-10-31 04:28:21 +00:00
|
|
|
Window evWindow = GetWindowOfEvent(&event, windowEvent);
|
2015-10-29 04:44:38 +00:00
|
|
|
if (windowEvent)
|
|
|
|
{
|
|
|
|
auto window = m_windows.find(evWindow);
|
|
|
|
if (window != m_windows.end())
|
2015-10-31 04:28:21 +00:00
|
|
|
window->second->_incomingEvent(&event);
|
2015-10-29 04:44:38 +00:00
|
|
|
}
|
2015-05-13 22:21:13 +00:00
|
|
|
}
|
2015-11-12 04:31:59 +00:00
|
|
|
XUnlockDisplay(m_xDisp);
|
2015-05-13 22:21:13 +00:00
|
|
|
}
|
|
|
|
|
2015-10-29 04:44:38 +00:00
|
|
|
if (FD_ISSET(m_dbusFd, &fds))
|
2015-05-09 05:33:48 +00:00
|
|
|
{
|
2015-10-29 04:44:38 +00:00
|
|
|
DBusMessage* msg;
|
|
|
|
dbus_connection_read_write(m_dbus, 0);
|
|
|
|
while ((msg = dbus_connection_pop_message(m_dbus)))
|
2015-05-13 22:21:13 +00:00
|
|
|
{
|
2015-10-29 04:44:38 +00:00
|
|
|
/* check if the message is a signal from the correct interface and with the correct name */
|
|
|
|
if (dbus_message_is_signal(msg, "boo.signal.FileHandling", "Open"))
|
2015-05-13 22:21:13 +00:00
|
|
|
{
|
2015-10-29 04:44:38 +00:00
|
|
|
/* read the parameters */
|
|
|
|
std::vector<std::string> paths;
|
|
|
|
DBusMessageIter iter;
|
|
|
|
dbus_message_iter_init(msg, &iter);
|
|
|
|
while (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INVALID)
|
|
|
|
{
|
|
|
|
const char* argVal;
|
|
|
|
dbus_message_iter_get_basic(&iter, &argVal);
|
|
|
|
paths.push_back(argVal);
|
|
|
|
dbus_message_iter_next(&iter);
|
|
|
|
}
|
|
|
|
m_callback.appFilesOpen(this, paths);
|
2015-05-13 22:21:13 +00:00
|
|
|
}
|
2015-10-29 04:44:38 +00:00
|
|
|
dbus_message_unref(msg);
|
2015-05-13 22:21:13 +00:00
|
|
|
}
|
2015-05-09 05:33:48 +00:00
|
|
|
}
|
|
|
|
}
|
2015-10-29 04:44:38 +00:00
|
|
|
|
|
|
|
m_callback.appQuitting(this);
|
|
|
|
clientThread.join();
|
|
|
|
return clientReturn;
|
2015-05-06 00:50:57 +00:00
|
|
|
}
|
2015-05-13 22:21:13 +00:00
|
|
|
|
|
|
|
const std::string& getUniqueName() const
|
|
|
|
{
|
|
|
|
return m_uniqueName;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string& getFriendlyName() const
|
|
|
|
{
|
|
|
|
return m_friendlyName;
|
|
|
|
}
|
2015-05-06 00:50:57 +00:00
|
|
|
|
|
|
|
const std::string& getProcessName() const
|
|
|
|
{
|
|
|
|
return m_pname;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::vector<std::string>& getArgs() const
|
|
|
|
{
|
|
|
|
return m_args;
|
|
|
|
}
|
|
|
|
|
2015-11-01 00:14:32 +00:00
|
|
|
IWindow* newWindow(const std::string& title)
|
2015-05-06 00:50:57 +00:00
|
|
|
{
|
2015-11-02 10:07:15 +00:00
|
|
|
IWindow* newWindow = _WindowXlibNew(title, m_xDisp, m_xDefaultScreen, m_lastGlxCtx);
|
2015-10-31 04:28:21 +00:00
|
|
|
m_windows[(Window)newWindow->getPlatformHandle()] = newWindow;
|
2015-11-01 00:14:32 +00:00
|
|
|
return newWindow;
|
2015-05-06 00:50:57 +00:00
|
|
|
}
|
2015-10-30 06:26:02 +00:00
|
|
|
|
|
|
|
/* Last GLX context */
|
2015-10-31 04:28:21 +00:00
|
|
|
GLXContext m_lastGlxCtx = nullptr;
|
2015-05-06 00:50:57 +00:00
|
|
|
};
|
2015-10-30 06:26:02 +00:00
|
|
|
|
2015-11-02 10:07:15 +00:00
|
|
|
void _XlibUpdateLastGlxCtx(GLXContext lastGlxCtx)
|
2015-10-30 06:26:02 +00:00
|
|
|
{
|
2015-11-02 10:07:15 +00:00
|
|
|
static_cast<ApplicationXlib*>(APP)->m_lastGlxCtx = lastGlxCtx;
|
2015-10-30 06:26:02 +00:00
|
|
|
}
|
2015-05-06 00:50:57 +00:00
|
|
|
|
|
|
|
}
|