Add a platform abstraction for 6-DOF input devices.

This commit mostly just moves code around.
pull/339/head
whitequark 2018-07-18 00:48:49 +00:00
parent 6b5db58971
commit c1f1c7c409
14 changed files with 400 additions and 315 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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();

View File

@ -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();
}

View File

@ -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();

View File

@ -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.");

View File

@ -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:

View File

@ -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
//-----------------------------------------------------------------------------

View File

@ -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
//-----------------------------------------------------------------------------

View File

@ -113,6 +113,8 @@ WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) {
return std::shared_ptr<Window>();
}
void Request3DConnexionEventsForWindow(WindowRef window) {}
//-----------------------------------------------------------------------------
// Message dialogs
//-----------------------------------------------------------------------------

View File

@ -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
//-----------------------------------------------------------------------------

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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);
};