Add a platform abstraction for 6-DOF input devices.
This commit mostly just moves code around.pull/339/head
parent
6b5db58971
commit
c1f1c7c409
|
@ -158,11 +158,8 @@ if(WIN32 OR APPLE)
|
|||
else()
|
||||
# On Linux and BSDs we're a good citizen and link to system libraries.
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
|
||||
find_package(Backtrace)
|
||||
find_package(SpaceWare)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
find_package(ZLIB REQUIRED)
|
||||
find_package(PNG REQUIRED)
|
||||
find_package(Freetype REQUIRED)
|
||||
|
@ -202,6 +199,7 @@ if(ENABLE_GUI)
|
|||
find_library(APPKIT_LIBRARY AppKit REQUIRED)
|
||||
else()
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(SpaceWare)
|
||||
pkg_check_modules(FONTCONFIG REQUIRED fontconfig)
|
||||
pkg_check_modules(JSONC REQUIRED json-c)
|
||||
pkg_check_modules(GTKMM REQUIRED gtkmm-3.0>=3.18 pangomm-1.4 x11)
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#
|
||||
# SPACEWARE_INCLUDE_DIR - header location
|
||||
# SPACEWARE_LIBRARIES - library to link against
|
||||
# SPACEWARE_FOUND - true if pugixml was found.
|
||||
# SPACEWARE_FOUND - true if libspnav was found.
|
||||
|
||||
if(UNIX)
|
||||
|
||||
|
|
|
@ -405,6 +405,7 @@ void GraphicsWindow::Init() {
|
|||
window->onRender = std::bind(&GraphicsWindow::Paint, this);
|
||||
window->onKeyboardEvent = std::bind(&GraphicsWindow::KeyboardEvent, this, _1);
|
||||
window->onMouseEvent = std::bind(&GraphicsWindow::MouseEvent, this, _1);
|
||||
window->onSixDofEvent = std::bind(&GraphicsWindow::SixDofEvent, this, _1);
|
||||
window->onEditingDone = std::bind(&GraphicsWindow::EditControlDone, this, _1);
|
||||
window->SetMinContentSize(720, 670);
|
||||
PopulateMainMenu();
|
||||
|
|
|
@ -1495,15 +1495,18 @@ void GraphicsWindow::MouseLeave() {
|
|||
SS.extraLine.draw = false;
|
||||
}
|
||||
|
||||
void GraphicsWindow::SpaceNavigatorMoved(double tx, double ty, double tz,
|
||||
double rx, double ry, double rz,
|
||||
bool shiftDown)
|
||||
{
|
||||
void GraphicsWindow::SixDofEvent(Platform::SixDofEvent event) {
|
||||
if(event.type == Platform::SixDofEvent::Type::RELEASE) {
|
||||
ZoomToFit(/*includingInvisibles=*/false, /*useSelection=*/true);
|
||||
Invalidate();
|
||||
return;
|
||||
}
|
||||
|
||||
if(!havePainted) return;
|
||||
Vector out = projRight.Cross(projUp);
|
||||
|
||||
// rotation vector is axis of rotation, and its magnitude is angle
|
||||
Vector aa = Vector::From(rx, ry, rz);
|
||||
Vector aa = Vector::From(event.rotationX, event.rotationY, event.rotationZ);
|
||||
// but it's given with respect to screen projection frame
|
||||
aa = aa.ScaleOutOfCsys(projRight, projUp, out);
|
||||
double aam = aa.Magnitude();
|
||||
|
@ -1516,38 +1519,38 @@ void GraphicsWindow::SpaceNavigatorMoved(double tx, double ty, double tz,
|
|||
if(gs.points == 1 && gs.n == 1) e = SK.GetEntity(gs.point [0]);
|
||||
if(gs.entities == 1 && gs.n == 1) e = SK.GetEntity(gs.entity[0]);
|
||||
if(e) g = SK.GetGroup(e->group);
|
||||
if(g && g->type == Group::Type::LINKED && !shiftDown) {
|
||||
if(g && g->type == Group::Type::LINKED && !event.shiftDown) {
|
||||
// Apply the transformation to a linked part. Gain down the Z
|
||||
// axis, since it's hard to see what you're doing on that one since
|
||||
// it's normal to the screen.
|
||||
Vector t = projRight.ScaledBy(tx/scale).Plus(
|
||||
projUp .ScaledBy(ty/scale).Plus(
|
||||
out .ScaledBy(0.1*tz/scale)));
|
||||
Vector t = projRight.ScaledBy(event.translationX/scale).Plus(
|
||||
projUp .ScaledBy(event.translationY/scale).Plus(
|
||||
out .ScaledBy(0.1*event.translationZ/scale)));
|
||||
Quaternion q = Quaternion::From(aa, aam);
|
||||
|
||||
// If we go five seconds without SpaceNavigator input, or if we've
|
||||
// switched groups, then consider that a new action and save an undo
|
||||
// point.
|
||||
int64_t now = GetMilliseconds();
|
||||
if(now - lastSpaceNavigatorTime > 5000 ||
|
||||
lastSpaceNavigatorGroup.v != g->h.v)
|
||||
if(now - last6DofTime > 5000 ||
|
||||
last6DofGroup.v != g->h.v)
|
||||
{
|
||||
SS.UndoRemember();
|
||||
}
|
||||
|
||||
g->TransformImportedBy(t, q);
|
||||
|
||||
lastSpaceNavigatorTime = now;
|
||||
lastSpaceNavigatorGroup = g->h;
|
||||
last6DofTime = now;
|
||||
last6DofGroup = g->h;
|
||||
SS.MarkGroupDirty(g->h);
|
||||
} else {
|
||||
// Apply the transformation to the view of the everything. The
|
||||
// x and y components are translation; but z component is scale,
|
||||
// not translation, or else it would do nothing in a parallel
|
||||
// projection
|
||||
offset = offset.Plus(projRight.ScaledBy(tx/scale));
|
||||
offset = offset.Plus(projUp.ScaledBy(ty/scale));
|
||||
scale *= exp(0.001*tz);
|
||||
offset = offset.Plus(projRight.ScaledBy(event.translationX/scale));
|
||||
offset = offset.Plus(projUp.ScaledBy(event.translationY/scale));
|
||||
scale *= exp(0.001*event.translationZ);
|
||||
|
||||
if(aam > 0.0) {
|
||||
projRight = projRight.RotatedAbout(aa, -aam);
|
||||
|
@ -1559,9 +1562,3 @@ void GraphicsWindow::SpaceNavigatorMoved(double tx, double ty, double tz,
|
|||
havePainted = false;
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
void GraphicsWindow::SpaceNavigatorButtonUp() {
|
||||
ZoomToFit(/*includingInvisibles=*/false, /*useSelection=*/true);
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
|
|
|
@ -63,154 +63,6 @@ std::vector<SolveSpace::Platform::Path> SolveSpace::GetFontFiles() {
|
|||
}
|
||||
@end
|
||||
|
||||
/*
|
||||
* Normally we would just link to the 3DconnexionClient framework.
|
||||
* We don't want to (are not allowed to) distribute the official
|
||||
* framework, so we're trying to use the one installed on the users
|
||||
* computer. There are some different versions of the framework,
|
||||
* the official one and re-implementations using an open source driver
|
||||
* for older devices (spacenav-plus). So weak-linking isn't an option,
|
||||
* either. The only remaining way is using CFBundle to dynamically
|
||||
* load the library at runtime, and also detect its availability.
|
||||
*
|
||||
* We're also defining everything needed from the 3DconnexionClientAPI,
|
||||
* so we're not depending on the API headers.
|
||||
*/
|
||||
|
||||
#pragma pack(push,2)
|
||||
|
||||
enum {
|
||||
kConnexionClientModeTakeOver = 1,
|
||||
kConnexionClientModePlugin = 2
|
||||
};
|
||||
|
||||
#define kConnexionMsgDeviceState '3dSR'
|
||||
#define kConnexionMaskButtons 0x00FF
|
||||
#define kConnexionMaskAxis 0x3F00
|
||||
|
||||
typedef struct {
|
||||
uint16_t version;
|
||||
uint16_t client;
|
||||
uint16_t command;
|
||||
int16_t param;
|
||||
int32_t value;
|
||||
UInt64 time;
|
||||
uint8_t report[8];
|
||||
uint16_t buttons8;
|
||||
int16_t axis[6];
|
||||
uint16_t address;
|
||||
uint32_t buttons;
|
||||
} ConnexionDeviceState, *ConnexionDeviceStatePtr;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
typedef void (*ConnexionAddedHandlerProc)(io_connect_t);
|
||||
typedef void (*ConnexionRemovedHandlerProc)(io_connect_t);
|
||||
typedef void (*ConnexionMessageHandlerProc)(io_connect_t, natural_t, void *);
|
||||
|
||||
typedef OSErr (*InstallConnexionHandlersProc)(ConnexionMessageHandlerProc, ConnexionAddedHandlerProc, ConnexionRemovedHandlerProc);
|
||||
typedef void (*CleanupConnexionHandlersProc)(void);
|
||||
typedef UInt16 (*RegisterConnexionClientProc)(UInt32, UInt8 *, UInt16, UInt32);
|
||||
typedef void (*UnregisterConnexionClientProc)(UInt16);
|
||||
|
||||
static BOOL connexionShiftIsDown = NO;
|
||||
static UInt16 connexionClient = 0;
|
||||
static UInt32 connexionSignature = 'SoSp';
|
||||
static UInt8 *connexionName = (UInt8 *)"\x10SolveSpace";
|
||||
static CFBundleRef spaceBundle = NULL;
|
||||
static InstallConnexionHandlersProc installConnexionHandlers = NULL;
|
||||
static CleanupConnexionHandlersProc cleanupConnexionHandlers = NULL;
|
||||
static RegisterConnexionClientProc registerConnexionClient = NULL;
|
||||
static UnregisterConnexionClientProc unregisterConnexionClient = NULL;
|
||||
|
||||
static void connexionAdded(io_connect_t con) {}
|
||||
static void connexionRemoved(io_connect_t con) {}
|
||||
static void connexionMessage(io_connect_t con, natural_t type, void *arg) {
|
||||
if (type != kConnexionMsgDeviceState) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConnexionDeviceState *device = (ConnexionDeviceState *)arg;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^(void){
|
||||
SolveSpace::SS.GW.SpaceNavigatorMoved(
|
||||
(double)device->axis[0] * -0.25,
|
||||
(double)device->axis[1] * -0.25,
|
||||
(double)device->axis[2] * 0.25,
|
||||
(double)device->axis[3] * -0.0005,
|
||||
(double)device->axis[4] * -0.0005,
|
||||
(double)device->axis[5] * -0.0005,
|
||||
(connexionShiftIsDown == YES) ? 1 : 0
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
static void connexionInit() {
|
||||
NSString *bundlePath = @"/Library/Frameworks/3DconnexionClient.framework";
|
||||
NSURL *bundleURL = [NSURL fileURLWithPath:bundlePath];
|
||||
spaceBundle = CFBundleCreate(kCFAllocatorDefault, (__bridge CFURLRef)bundleURL);
|
||||
|
||||
// Don't continue if no Spacemouse driver is installed on this machine
|
||||
if (spaceBundle == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
installConnexionHandlers = (InstallConnexionHandlersProc)
|
||||
CFBundleGetFunctionPointerForName(spaceBundle,
|
||||
CFSTR("InstallConnexionHandlers"));
|
||||
|
||||
cleanupConnexionHandlers = (CleanupConnexionHandlersProc)
|
||||
CFBundleGetFunctionPointerForName(spaceBundle,
|
||||
CFSTR("CleanupConnexionHandlers"));
|
||||
|
||||
registerConnexionClient = (RegisterConnexionClientProc)
|
||||
CFBundleGetFunctionPointerForName(spaceBundle,
|
||||
CFSTR("RegisterConnexionClient"));
|
||||
|
||||
unregisterConnexionClient = (UnregisterConnexionClientProc)
|
||||
CFBundleGetFunctionPointerForName(spaceBundle,
|
||||
CFSTR("UnregisterConnexionClient"));
|
||||
|
||||
// Only continue if all required symbols have been loaded
|
||||
if ((installConnexionHandlers == NULL) || (cleanupConnexionHandlers == NULL)
|
||||
|| (registerConnexionClient == NULL) || (unregisterConnexionClient == NULL)) {
|
||||
CFRelease(spaceBundle);
|
||||
spaceBundle = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
installConnexionHandlers(&connexionMessage, &connexionAdded, &connexionRemoved);
|
||||
connexionClient = registerConnexionClient(connexionSignature, connexionName,
|
||||
kConnexionClientModeTakeOver, kConnexionMaskButtons | kConnexionMaskAxis);
|
||||
|
||||
// Monitor modifier flags to detect Shift button state changes
|
||||
[NSEvent addLocalMonitorForEventsMatchingMask:(NSKeyDownMask | NSFlagsChangedMask)
|
||||
handler:^(NSEvent *event) {
|
||||
if (event.modifierFlags & NSShiftKeyMask) {
|
||||
connexionShiftIsDown = YES;
|
||||
}
|
||||
return event;
|
||||
}];
|
||||
|
||||
[NSEvent addLocalMonitorForEventsMatchingMask:(NSKeyUpMask | NSFlagsChangedMask)
|
||||
handler:^(NSEvent *event) {
|
||||
if (!(event.modifierFlags & NSShiftKeyMask)) {
|
||||
connexionShiftIsDown = NO;
|
||||
}
|
||||
return event;
|
||||
}];
|
||||
}
|
||||
|
||||
static void connexionClose() {
|
||||
if (spaceBundle == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
unregisterConnexionClient(connexionClient);
|
||||
cleanupConnexionHandlers();
|
||||
|
||||
CFRelease(spaceBundle);
|
||||
}
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
ApplicationDelegate *delegate = [[ApplicationDelegate alloc] init];
|
||||
|
@ -226,12 +78,12 @@ int main(int argc, const char *argv[]) {
|
|||
SolveSpace::SetLocale("en_US");
|
||||
}
|
||||
|
||||
connexionInit();
|
||||
SolveSpace::Platform::Open3DConnexion();
|
||||
SolveSpace::SS.Init();
|
||||
|
||||
[NSApp run];
|
||||
|
||||
connexionClose();
|
||||
SolveSpace::Platform::Close3DConnexion();
|
||||
SolveSpace::SK.Clear();
|
||||
SolveSpace::SS.Clear();
|
||||
|
||||
|
|
|
@ -47,10 +47,6 @@
|
|||
#include "solvespace.h"
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_SPACEWARE
|
||||
#include <spnav.h>
|
||||
#endif
|
||||
|
||||
namespace SolveSpace {
|
||||
|
||||
void OpenWebsite(const char *url) {
|
||||
|
@ -78,40 +74,6 @@ std::vector<Platform::Path> GetFontFiles() {
|
|||
return fonts;
|
||||
}
|
||||
|
||||
|
||||
/* Space Navigator support */
|
||||
|
||||
#ifdef HAVE_SPACEWARE
|
||||
static GdkFilterReturn GdkSpnavFilter(GdkXEvent *gxevent, GdkEvent *, gpointer) {
|
||||
XEvent *xevent = (XEvent*) gxevent;
|
||||
|
||||
spnav_event sev;
|
||||
if(!spnav_x11_event(xevent, &sev))
|
||||
return GDK_FILTER_CONTINUE;
|
||||
|
||||
switch(sev.type) {
|
||||
case SPNAV_EVENT_MOTION:
|
||||
SS.GW.SpaceNavigatorMoved(
|
||||
(double)sev.motion.x,
|
||||
(double)sev.motion.y,
|
||||
(double)sev.motion.z * -1.0,
|
||||
(double)sev.motion.rx * 0.001,
|
||||
(double)sev.motion.ry * 0.001,
|
||||
(double)sev.motion.rz * -0.001,
|
||||
xevent->xmotion.state & ShiftMask);
|
||||
break;
|
||||
|
||||
case SPNAV_EVENT_BUTTON:
|
||||
if(!sev.button.press && sev.button.bnum == 0) {
|
||||
SS.GW.SpaceNavigatorButtonUp();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return GDK_FILTER_REMOVE;
|
||||
}
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
@ -149,10 +111,6 @@ int main(int argc, char** argv) {
|
|||
style_provider,
|
||||
600 /*Gtk::STYLE_PROVIDER_PRIORITY_APPLICATION*/);
|
||||
|
||||
#ifdef HAVE_SPACEWARE
|
||||
gdk_window_add_filter(NULL, GdkSpnavFilter, NULL);
|
||||
#endif
|
||||
|
||||
const char* const* langNames = g_get_language_names();
|
||||
while(*langNames) {
|
||||
if(SetLocale(*langNames++)) break;
|
||||
|
@ -163,15 +121,6 @@ int main(int argc, char** argv) {
|
|||
|
||||
SS.Init();
|
||||
|
||||
#if defined(HAVE_SPACEWARE) && defined(GDK_WINDOWING_X11)
|
||||
if(GDK_IS_X11_DISPLAY(Gdk::Display::get_default()->gobj())) {
|
||||
// We don't care if it can't be opened; just continue without.
|
||||
spnav_x11_open(gdk_x11_get_default_xdisplay(),
|
||||
gdk_x11_window_get_xid(((Gtk::Window *)SS.GW.window->NativePtr())
|
||||
->get_window()->gobj()));
|
||||
}
|
||||
#endif
|
||||
|
||||
if(argc >= 2) {
|
||||
if(argc > 2) {
|
||||
dbp("Only the first file passed on command line will be opened.");
|
||||
|
|
|
@ -43,6 +43,26 @@ public:
|
|||
};
|
||||
};
|
||||
|
||||
// A 3-DOF input event.
|
||||
struct SixDofEvent {
|
||||
enum class Type {
|
||||
MOTION,
|
||||
PRESS,
|
||||
RELEASE,
|
||||
};
|
||||
|
||||
enum class Button {
|
||||
FIT,
|
||||
};
|
||||
|
||||
Type type;
|
||||
bool shiftDown;
|
||||
bool controlDown;
|
||||
double translationX, translationY, translationZ; // for Type::MOTION
|
||||
double rotationX, rotationY, rotationZ; // for Type::MOTION
|
||||
Button button; // for Type::{PRESS,RELEASE}
|
||||
};
|
||||
|
||||
// A keyboard input event.
|
||||
struct KeyboardEvent {
|
||||
enum class Type {
|
||||
|
@ -191,6 +211,7 @@ public:
|
|||
std::function<void()> onClose;
|
||||
std::function<void(bool)> onFullScreen;
|
||||
std::function<bool(MouseEvent)> onMouseEvent;
|
||||
std::function<void(SixDofEvent)> onSixDofEvent;
|
||||
std::function<bool(KeyboardEvent)> onKeyboardEvent;
|
||||
std::function<void(std::string)> onEditingDone;
|
||||
std::function<void(double)> onScrollbarAdjusted;
|
||||
|
@ -250,6 +271,11 @@ typedef std::shared_ptr<Window> WindowRef;
|
|||
WindowRef CreateWindow(Window::Kind kind = Window::Kind::TOPLEVEL,
|
||||
WindowRef parentWindow = NULL);
|
||||
|
||||
// 3DConnexion support.
|
||||
void Open3DConnexion();
|
||||
void Close3DConnexion();
|
||||
void Request3DConnexionEventsForWindow(WindowRef window);
|
||||
|
||||
// A native dialog that asks for one choice out of several.
|
||||
class MessageDialog {
|
||||
public:
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
//
|
||||
// Copyright 2018 whitequark
|
||||
//-----------------------------------------------------------------------------
|
||||
#include "solvespace.h"
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
@ -24,6 +23,15 @@
|
|||
#include <gtkmm/separatormenuitem.h>
|
||||
#include <gtkmm/window.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#if defined(HAVE_SPACEWARE)
|
||||
# include <spnav.h>
|
||||
# include <gdk/gdkx.h>
|
||||
#endif
|
||||
|
||||
#include "solvespace.h"
|
||||
|
||||
namespace SolveSpace {
|
||||
namespace Platform {
|
||||
|
||||
|
@ -992,6 +1000,78 @@ WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) {
|
|||
return window;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// 3DConnexion support
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void Open3DConnexion() {}
|
||||
void Close3DConnexion() {}
|
||||
|
||||
#if defined(HAVE_SPACEWARE) && defined(GDK_WINDOWING_X11)
|
||||
static GdkFilterReturn GdkSpnavFilter(GdkXEvent *gdkXEvent, GdkEvent *gdkEvent, gpointer data) {
|
||||
XEvent *xEvent = (XEvent *)gdkXEvent;
|
||||
WindowImplGtk *window = (WindowImplGtk *)data;
|
||||
|
||||
spnav_event spnavEvent;
|
||||
if(!spnav_x11_event(xEvent, &spnavEvent)) {
|
||||
return GDK_FILTER_CONTINUE;
|
||||
}
|
||||
|
||||
switch(spnavEvent.type) {
|
||||
case SPNAV_EVENT_MOTION: {
|
||||
SixDofEvent event = {};
|
||||
event.type = SixDofEvent::Type::MOTION;
|
||||
event.translationX = (double)spnavEvent.motion.x;
|
||||
event.translationY = (double)spnavEvent.motion.y;
|
||||
event.translationZ = (double)spnavEvent.motion.z * -1.0;
|
||||
event.rotationX = (double)spnavEvent.motion.rx * 0.001;
|
||||
event.rotationY = (double)spnavEvent.motion.ry * 0.001;
|
||||
event.rotationZ = (double)spnavEvent.motion.rz * -0.001;
|
||||
event.shiftDown = xEvent->xmotion.state & ShiftMask;
|
||||
event.controlDown = xEvent->xmotion.state & ControlMask;
|
||||
if(window->onSixDofEvent) {
|
||||
window->onSixDofEvent(event);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SPNAV_EVENT_BUTTON:
|
||||
SixDofEvent event = {};
|
||||
if(spnavEvent.button.press) {
|
||||
event.type = SixDofEvent::Type::PRESS;
|
||||
} else {
|
||||
event.type = SixDofEvent::Type::RELEASE;
|
||||
}
|
||||
switch(spnavEvent.button.bnum) {
|
||||
case 0: event.button = SixDofEvent::Button::FIT; break;
|
||||
default: return GDK_FILTER_REMOVE;
|
||||
}
|
||||
event.shiftDown = xEvent->xmotion.state & ShiftMask;
|
||||
event.controlDown = xEvent->xmotion.state & ControlMask;
|
||||
if(window->onSixDofEvent) {
|
||||
window->onSixDofEvent(event);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return GDK_FILTER_REMOVE;
|
||||
}
|
||||
|
||||
void Request3DConnexionEventsForWindow(WindowRef window) {
|
||||
std::shared_ptr<WindowImplGtk> windowImpl =
|
||||
std::static_pointer_cast<WindowImplGtk>(window);
|
||||
|
||||
Glib::RefPtr<Gdk::Window> gdkWindow = windowImpl->gtkWindow.get_window();
|
||||
if(GDK_IS_X11_DISPLAY(gdkWindow->get_display()->gobj())) {
|
||||
gdkWindow->add_filter(GdkSpnavFilter, windowImpl.get());
|
||||
spnav_x11_open(gdk_x11_get_default_xdisplay(),
|
||||
gdk_x11_window_get_xid(gdkWindow->gobj()));
|
||||
}
|
||||
}
|
||||
#else
|
||||
void Request3DConnexionEventsForWindow(WindowRef window) {}
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Message dialogs
|
||||
//-----------------------------------------------------------------------------
|
||||
|
|
|
@ -1000,6 +1000,170 @@ WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) {
|
|||
std::static_pointer_cast<WindowImplCocoa>(parentWindow));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// 3DConnexion support
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Normally we would just link to the 3DconnexionClient framework.
|
||||
//
|
||||
// We don't want to (are not allowed to) distribute the official framework, so we're trying
|
||||
// to use the one installed on the users' computer. There are some different versions of
|
||||
// the framework, the official one and re-implementations using an open source driver for
|
||||
// older devices (spacenav-plus). So weak-linking isn't an option, either. The only remaining
|
||||
// way is using CFBundle to dynamically load the library at runtime, and also detect its
|
||||
// availability.
|
||||
//
|
||||
// We're also defining everything needed from the 3DconnexionClientAPI, so we're not depending
|
||||
// on the API headers.
|
||||
|
||||
#pragma pack(push,2)
|
||||
|
||||
enum {
|
||||
kConnexionClientModeTakeOver = 1,
|
||||
kConnexionClientModePlugin = 2
|
||||
};
|
||||
|
||||
#define kConnexionMsgDeviceState '3dSR'
|
||||
#define kConnexionMaskButtons 0x00FF
|
||||
#define kConnexionMaskAxis 0x3F00
|
||||
|
||||
typedef struct {
|
||||
uint16_t version;
|
||||
uint16_t client;
|
||||
uint16_t command;
|
||||
int16_t param;
|
||||
int32_t value;
|
||||
UInt64 time;
|
||||
uint8_t report[8];
|
||||
uint16_t buttons8;
|
||||
int16_t axis[6];
|
||||
uint16_t address;
|
||||
uint32_t buttons;
|
||||
} ConnexionDeviceState, *ConnexionDeviceStatePtr;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
typedef void (*ConnexionAddedHandlerProc)(io_connect_t);
|
||||
typedef void (*ConnexionRemovedHandlerProc)(io_connect_t);
|
||||
typedef void (*ConnexionMessageHandlerProc)(io_connect_t, natural_t, void *);
|
||||
|
||||
typedef OSErr (*InstallConnexionHandlersProc)(ConnexionMessageHandlerProc, ConnexionAddedHandlerProc, ConnexionRemovedHandlerProc);
|
||||
typedef void (*CleanupConnexionHandlersProc)(void);
|
||||
typedef UInt16 (*RegisterConnexionClientProc)(UInt32, UInt8 *, UInt16, UInt32);
|
||||
typedef void (*UnregisterConnexionClientProc)(UInt16);
|
||||
|
||||
static CFBundleRef spaceBundle = nil;
|
||||
static InstallConnexionHandlersProc installConnexionHandlers = NULL;
|
||||
static CleanupConnexionHandlersProc cleanupConnexionHandlers = NULL;
|
||||
static RegisterConnexionClientProc registerConnexionClient = NULL;
|
||||
static UnregisterConnexionClientProc unregisterConnexionClient = NULL;
|
||||
static UInt32 connexionSignature = 'SoSp';
|
||||
static UInt8 *connexionName = (UInt8 *)"\x10SolveSpace";
|
||||
static UInt16 connexionClient = 0;
|
||||
|
||||
static std::vector<std::weak_ptr<Window>> connexionWindows;
|
||||
static bool connexionShiftIsDown = false;
|
||||
static bool connexionCommandIsDown = false;
|
||||
|
||||
static void ConnexionAdded(io_connect_t con) {}
|
||||
static void ConnexionRemoved(io_connect_t con) {}
|
||||
static void ConnexionMessage(io_connect_t con, natural_t type, void *arg) {
|
||||
if (type != kConnexionMsgDeviceState) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConnexionDeviceState *device = (ConnexionDeviceState *)arg;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^(void){
|
||||
SixDofEvent event = {};
|
||||
event.type = SixDofEvent::Type::MOTION;
|
||||
event.translationX = (double)device->axis[0] * -0.25;
|
||||
event.translationY = (double)device->axis[1] * -0.25;
|
||||
event.translationZ = (double)device->axis[2] * 0.25;
|
||||
event.rotationX = (double)device->axis[3] * -0.0005;
|
||||
event.rotationY = (double)device->axis[4] * -0.0005;
|
||||
event.rotationZ = (double)device->axis[5] * -0.0005;
|
||||
event.shiftDown = connexionShiftIsDown;
|
||||
event.controlDown = connexionCommandIsDown;
|
||||
|
||||
for(auto window : connexionWindows) {
|
||||
if(auto windowStrong = window.lock()) {
|
||||
if(windowStrong->onSixDofEvent) {
|
||||
windowStrong->onSixDofEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Open3DConnexion() {
|
||||
NSString *bundlePath = @"/Library/Frameworks/3DconnexionClient.framework";
|
||||
NSURL *bundleURL = [NSURL fileURLWithPath:bundlePath];
|
||||
spaceBundle = CFBundleCreate(kCFAllocatorDefault, (__bridge CFURLRef)bundleURL);
|
||||
|
||||
// Don't continue if no driver is installed on this machine
|
||||
if(spaceBundle == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
installConnexionHandlers = (InstallConnexionHandlersProc)
|
||||
CFBundleGetFunctionPointerForName(spaceBundle,
|
||||
CFSTR("InstallConnexionHandlers"));
|
||||
|
||||
cleanupConnexionHandlers = (CleanupConnexionHandlersProc)
|
||||
CFBundleGetFunctionPointerForName(spaceBundle,
|
||||
CFSTR("CleanupConnexionHandlers"));
|
||||
|
||||
registerConnexionClient = (RegisterConnexionClientProc)
|
||||
CFBundleGetFunctionPointerForName(spaceBundle,
|
||||
CFSTR("RegisterConnexionClient"));
|
||||
|
||||
unregisterConnexionClient = (UnregisterConnexionClientProc)
|
||||
CFBundleGetFunctionPointerForName(spaceBundle,
|
||||
CFSTR("UnregisterConnexionClient"));
|
||||
|
||||
// Only continue if all required symbols have been loaded
|
||||
if((installConnexionHandlers == NULL) || (cleanupConnexionHandlers == NULL)
|
||||
|| (registerConnexionClient == NULL) || (unregisterConnexionClient == NULL)) {
|
||||
CFRelease(spaceBundle);
|
||||
spaceBundle = nil;
|
||||
return;
|
||||
}
|
||||
|
||||
installConnexionHandlers(&ConnexionMessage, &ConnexionAdded, &ConnexionRemoved);
|
||||
connexionClient = registerConnexionClient(connexionSignature, connexionName,
|
||||
kConnexionClientModeTakeOver, kConnexionMaskButtons | kConnexionMaskAxis);
|
||||
|
||||
[NSEvent addLocalMonitorForEventsMatchingMask:(NSKeyDownMask | NSFlagsChangedMask)
|
||||
handler:^(NSEvent *event) {
|
||||
connexionShiftIsDown = (event.modifierFlags & NSShiftKeyMask);
|
||||
connexionCommandIsDown = (event.modifierFlags & NSCommandKeyMask);
|
||||
return event;
|
||||
}];
|
||||
|
||||
[NSEvent addLocalMonitorForEventsMatchingMask:(NSKeyUpMask | NSFlagsChangedMask)
|
||||
handler:^(NSEvent *event) {
|
||||
connexionShiftIsDown = (event.modifierFlags & NSShiftKeyMask);
|
||||
connexionCommandIsDown = (event.modifierFlags & NSCommandKeyMask);
|
||||
return event;
|
||||
}];
|
||||
}
|
||||
|
||||
void Close3DConnexion() {
|
||||
if(spaceBundle == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
unregisterConnexionClient(connexionClient);
|
||||
cleanupConnexionHandlers();
|
||||
|
||||
CFRelease(spaceBundle);
|
||||
}
|
||||
|
||||
void Request3DConnexionEventsForWindow(WindowRef window) {
|
||||
connexionWindows.push_back(window);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Message dialogs
|
||||
//-----------------------------------------------------------------------------
|
||||
|
|
|
@ -113,6 +113,8 @@ WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) {
|
|||
return std::shared_ptr<Window>();
|
||||
}
|
||||
|
||||
void Request3DConnexionEventsForWindow(WindowRef window) {}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Message dialogs
|
||||
//-----------------------------------------------------------------------------
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
#include <commdlg.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#ifndef WM_DPICHANGED
|
||||
#define WM_DPICHANGED 0x02E0
|
||||
#if !defined(WM_DPICHANGED)
|
||||
# define WM_DPICHANGED 0x02E0
|
||||
#endif
|
||||
|
||||
// These interfere with our identifiers.
|
||||
|
@ -21,8 +21,14 @@
|
|||
#undef ERROR
|
||||
|
||||
#if HAVE_OPENGL == 3
|
||||
#define EGLAPI /*static linkage*/
|
||||
#include <EGL/egl.h>
|
||||
# define EGLAPI /*static linkage*/
|
||||
# include <EGL/egl.h>
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_SPACEWARE)
|
||||
# include <si.h>
|
||||
# include <siapp.h>
|
||||
# undef uint32_t
|
||||
#endif
|
||||
|
||||
namespace SolveSpace {
|
||||
|
@ -483,6 +489,11 @@ public:
|
|||
WINDOWPLACEMENT placement = {};
|
||||
int minWidth = 0, minHeight = 0;
|
||||
|
||||
#if defined(HAVE_SPACEWARE)
|
||||
SiOpenData sod = {};
|
||||
SiHdl hSpaceWare = SI_NO_HANDLE;
|
||||
#endif
|
||||
|
||||
std::shared_ptr<MenuBarImplWin32> menuBar;
|
||||
std::string tooltipText;
|
||||
bool scrollbarVisible = false;
|
||||
|
@ -616,6 +627,15 @@ public:
|
|||
sscheck(ReleaseDC(hWindow, hDc));
|
||||
}
|
||||
|
||||
~WindowImplWin32() {
|
||||
sscheck(DestroyWindow(hWindow));
|
||||
#if defined(HAVE_SPACEWARE)
|
||||
if(hSpaceWare != SI_NO_HANDLE) {
|
||||
SiClose(hSpaceWare);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK WndProc(HWND h, UINT msg, WPARAM wParam, LPARAM lParam) {
|
||||
if(handlingFatalError) return TRUE;
|
||||
|
||||
|
@ -628,6 +648,41 @@ public:
|
|||
return DefWindowProc(h, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
#if defined(HAVE_SPACEWARE)
|
||||
if(window->hSpaceWare != SI_NO_HANDLE) {
|
||||
SiGetEventData sged;
|
||||
SiGetEventWinInit(&sged, msg, wParam, lParam);
|
||||
|
||||
SiSpwEvent sse;
|
||||
if(SiGetEvent(window->hSpaceWare, 0, &sged, &sse) == SI_IS_EVENT) {
|
||||
SixDofEvent event = {};
|
||||
event.shiftDown = ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0);
|
||||
event.controlDown = ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0);
|
||||
if(sse.type == SI_MOTION_EVENT) {
|
||||
// The Z axis translation and rotation are both
|
||||
// backwards in the default mapping.
|
||||
event.type = SixDofEvent::Type::MOTION;
|
||||
event.translationX = sse.u.spwData.mData[SI_TX]*1.0,
|
||||
event.translationY = sse.u.spwData.mData[SI_TY]*1.0,
|
||||
event.translationZ = -sse.u.spwData.mData[SI_TZ]*1.0,
|
||||
event.rotationX = sse.u.spwData.mData[SI_RX]*0.001,
|
||||
event.rotationY = sse.u.spwData.mData[SI_RY]*0.001,
|
||||
event.rotationZ = -sse.u.spwData.mData[SI_RZ]*0.001;
|
||||
} else if(sse.type == SI_BUTTON_EVENT) {
|
||||
if(SiButtonPressed(&sse) == SI_APP_FIT_BUTTON) {
|
||||
event.type = SixDofEvent::Type::PRESS;
|
||||
event.button = SixDofEvent::Button::FIT;
|
||||
}
|
||||
if(SiButtonReleased(&sse) == SI_APP_FIT_BUTTON) {
|
||||
event.type = SixDofEvent::Type::RELEASE;
|
||||
event.button = SixDofEvent::Button::FIT;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
switch (msg) {
|
||||
case WM_ERASEBKGND:
|
||||
break;
|
||||
|
@ -1272,6 +1327,42 @@ WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) {
|
|||
std::static_pointer_cast<WindowImplWin32>(parentWindow));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// 3DConnexion support
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#if defined(HAVE_SPACEWARE)
|
||||
static HWND hSpaceWareDriverClass;
|
||||
|
||||
void Open3DConnexion() {
|
||||
HWND hSpaceWareDriverClass = FindWindowW(L"SpaceWare Driver Class", NULL);
|
||||
if(hSpaceWareDriverClass != NULL) {
|
||||
SiInitialize();
|
||||
}
|
||||
}
|
||||
|
||||
void Close3DConnexion() {
|
||||
if(hSpaceWareDriverClass != NULL) {
|
||||
SiTerminate();
|
||||
}
|
||||
}
|
||||
|
||||
void Request3DConnexionEventsForWindow(WindowRef window) {
|
||||
std::shared_ptr<WindowImplWin32> windowImpl =
|
||||
std::static_pointer_cast<WindowImplWin32>(window);
|
||||
if(hSpaceWareDriverClass != NULL) {
|
||||
SiOpenWinInit(&windowImpl->sod, windowImpl->hWindow);
|
||||
windowImpl->hSpaceWare = SiOpen("SolveSpace", SI_ANY_DEVICE, SI_NO_MASK, SI_EVENT,
|
||||
&windowImpl->sod);
|
||||
SiSetUiMode(windowImpl->hSpaceWare, SI_UI_NO_CONTROLS);
|
||||
}
|
||||
}
|
||||
#else
|
||||
void Open3DConnexion() {}
|
||||
void Close3DConnexion() {}
|
||||
void Request3DConnexionEventsForWindow(WindowRef window) {}
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Message dialogs
|
||||
//-----------------------------------------------------------------------------
|
||||
|
|
|
@ -16,20 +16,9 @@
|
|||
#include <commctrl.h>
|
||||
#include <commdlg.h>
|
||||
|
||||
#ifdef HAVE_SPACEWARE
|
||||
# include <si.h>
|
||||
# include <siapp.h>
|
||||
# undef uint32_t // thanks but no thanks
|
||||
#endif
|
||||
|
||||
using Platform::Narrow;
|
||||
using Platform::Widen;
|
||||
|
||||
#ifdef HAVE_SPACEWARE
|
||||
// The 6-DOF input device.
|
||||
SiHdl SpaceNavigator = SI_NO_HANDLE;
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Utility routines
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -56,44 +45,6 @@ std::vector<Platform::Path> SolveSpace::GetFontFiles() {
|
|||
return fonts;
|
||||
}
|
||||
|
||||
#ifdef HAVE_SPACEWARE
|
||||
//-----------------------------------------------------------------------------
|
||||
// Test if a message comes from the SpaceNavigator device. If yes, dispatch
|
||||
// it appropriately and return true. Otherwise, do nothing and return false.
|
||||
//-----------------------------------------------------------------------------
|
||||
static bool ProcessSpaceNavigatorMsg(MSG *msg) {
|
||||
if(SpaceNavigator == SI_NO_HANDLE) return false;
|
||||
|
||||
SiGetEventData sged;
|
||||
SiSpwEvent sse;
|
||||
|
||||
SiGetEventWinInit(&sged, msg->message, msg->wParam, msg->lParam);
|
||||
int ret = SiGetEvent(SpaceNavigator, 0, &sged, &sse);
|
||||
if(ret == SI_NOT_EVENT) return false;
|
||||
// So the device is a SpaceNavigator event, or a SpaceNavigator error.
|
||||
|
||||
if(ret == SI_IS_EVENT) {
|
||||
if(sse.type == SI_MOTION_EVENT) {
|
||||
// The Z axis translation and rotation are both
|
||||
// backwards in the default mapping.
|
||||
double tx = sse.u.spwData.mData[SI_TX]*1.0,
|
||||
ty = sse.u.spwData.mData[SI_TY]*1.0,
|
||||
tz = -sse.u.spwData.mData[SI_TZ]*1.0,
|
||||
rx = sse.u.spwData.mData[SI_RX]*0.001,
|
||||
ry = sse.u.spwData.mData[SI_RY]*0.001,
|
||||
rz = -sse.u.spwData.mData[SI_RZ]*0.001;
|
||||
SS.GW.SpaceNavigatorMoved(tx, ty, tz, rx, ry, rz,
|
||||
!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
|
||||
} else if(sse.type == SI_BUTTON_EVENT) {
|
||||
int button;
|
||||
button = SiButtonReleased(&sse);
|
||||
if(button == SI_APP_FIT_BUTTON) SS.GW.SpaceNavigatorButtonUp();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif // HAVE_SPACEWARE
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Entry point into the program.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -107,19 +58,6 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
|
|||
|
||||
std::vector<std::string> args = InitPlatform(0, NULL);
|
||||
|
||||
#ifdef HAVE_SPACEWARE
|
||||
// Initialize the SpaceBall, if present. Test if the driver is running
|
||||
// first, to avoid a long timeout if it's not.
|
||||
HWND swdc = FindWindowW(L"SpaceWare Driver Class", NULL);
|
||||
if(swdc != NULL) {
|
||||
SiOpenData sod;
|
||||
SiInitialize();
|
||||
SiOpenWinInit(&sod, (HWND)SS.GW.window->NativePtr());
|
||||
SpaceNavigator = SiOpen("GraphicsWnd", SI_ANY_DEVICE, SI_NO_MASK, SI_EVENT, &sod);
|
||||
SiSetUiMode(SpaceNavigator, SI_UI_NO_CONTROLS);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Use the user default locale, then fall back to English.
|
||||
if(!SetLocale((uint16_t)GetUserDefaultLCID())) {
|
||||
SetLocale("en_US");
|
||||
|
@ -136,25 +74,11 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
|
|||
// And now it's the message loop. All calls in to the rest of the code
|
||||
// will be from the wndprocs.
|
||||
MSG msg;
|
||||
DWORD ret;
|
||||
while((ret = GetMessage(&msg, NULL, 0, 0)) != 0) {
|
||||
#ifdef HAVE_SPACEWARE
|
||||
// Is it a message from the six degree of freedom input device?
|
||||
if(ProcessSpaceNavigatorMsg(&msg)) continue;
|
||||
#endif
|
||||
|
||||
// None of the above; so just a normal message to process.
|
||||
while(GetMessage(&msg, NULL, 0, 0)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
|
||||
#ifdef HAVE_SPACEWARE
|
||||
if(swdc != NULL) {
|
||||
if(SpaceNavigator != SI_NO_HANDLE) SiClose(SpaceNavigator);
|
||||
SiTerminate();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Free the memory we've used; anything that remains is a leak.
|
||||
SK.Clear();
|
||||
SS.Clear();
|
||||
|
|
|
@ -136,6 +136,9 @@ void SolveSpaceUI::Init() {
|
|||
TW.window->SetVisible(true);
|
||||
GW.window->SetVisible(true);
|
||||
GW.window->Focus();
|
||||
|
||||
// Do this once the window is created.
|
||||
Request3DConnexionEventsForWindow(GW.window);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
8
src/ui.h
8
src/ui.h
|
@ -795,11 +795,9 @@ public:
|
|||
bool KeyboardEvent(Platform::KeyboardEvent event);
|
||||
void EditControlDone(const std::string &s);
|
||||
|
||||
int64_t lastSpaceNavigatorTime;
|
||||
hGroup lastSpaceNavigatorGroup;
|
||||
void SpaceNavigatorMoved(double tx, double ty, double tz,
|
||||
double rx, double ry, double rz, bool shiftDown);
|
||||
void SpaceNavigatorButtonUp();
|
||||
int64_t last6DofTime;
|
||||
hGroup last6DofGroup;
|
||||
void SixDofEvent(Platform::SixDofEvent event);
|
||||
};
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue