I'm porting a game from pc to osx and I'm getting stuck with input events. The main game window is a cocoa app that interfaces with a C++ backend. The pc code uses DirectInput and windows messages to generate keyboard and mouse events that the game understands.
When I first started the port I replaced the windows messages with Carbon event handling, but since discovered that Cocoa apps don't fire off carbon events. I did a bit more reading and discovered the HIDManager which seemed to do what I want and was accessible from c++. Using the information in the post here Using IOHIDManager to Get Modifier Key Events I managed to get keyboard input working, but have so far been unable to extend the example code to generate mouse events as well. The code is have is as follows:
void myHIDCallback(void* context, IOReturn result, void* sender, IOHIDValueRef value)
{
IOHIDElementRef elem = IOHIDValueGetElement(value);
if (IOHIDElementGetUsagePage(elem) == 0x07)
{
// Keyboard events
card32 scancode = IOHIDElementGetUsage(elem);
if (scancode >= 4 && scancode <= 231)
{
long pressed = IOHIDValueGetIntegerValue(value);
KEY_EVENT_DETAILS details = { KEY_EVENT_NONE, NUM_KEYS };
for (card32 n=0; n<NUM_KEYS; ++n)
{
if (n_direct_input_mappings[n].direct_input_key == scancode)
{
details.key_type = n_direct_input_mappings[n].key;
break;
}
}
switch (pressed)
{
case 0:
details.event_type = KEY_EVENT_KEY_DOWN;
break;
case 1:
details.event_type = KEY_EVENT_KEY_UP;
break;
}
sApplication->handle_key_event(details);
}
}
else
if (IOHIDElementGetUsagePage(elem) == 0x02)
{
// Mouse events
card32 usage = IOHIDElementGetUsage(elem);
long pressed = IOHIDValueGetIntegerValue(value);
}
else
{
}
}
CFMutableDictionaryRef myCreateDeviceMatchingDictionary(UInt32 usagePage, UInt32 usage)
{
CFMutableDictionaryRef ret = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (ret)
{
CFNumberRef pageNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usagePage);
if (pageNumberRef)
{
CFDictionarySetValue(ret, CFSTR(kIOHIDDeviceUsagePageKey), pageNumberRef);
CFRelease(pageNumberRef);
CFNumberRef usageNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
if (usageNumberRef)
{
CFDictionarySetValue(ret, CFSTR(kIOHIDDeviceUsageKey), usageNumberRef);
CFRelease(usageNumberRef);
return ret;
}
}
CFRelease(ret);
}
return NULL;
}
bool acquire_hardware_controllers(CA::Application& app, CAWindow& window_handle)
{
sApplication = &app;
// Setup the keyboard event handler
sHIDManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
IOHIDManagerOpen(sHIDManager, kIOHIDOptionsTypeNone);
CFMutableDictionaryRef mouse = myCreateDeviceMatchingDictionary(0x01, 2);
CFMutableDictionaryRef keyboard = myCreateDeviceMatchingDictionary(0x01, 6);
CFMutableDictionaryRef keypad = myCreateDeviceMatchingDictionary(0x01, 7);
CFMutableDictionaryRef matchesList[] = {
keyboard,
keypad,
mouse
};
CFArrayRef matches = CFArrayCreate(kCFAllocatorDefault, (const void**)matchesList, 3, NULL);
IOHIDManagerSetDeviceMatchingMultiple(sHIDManager, matches);
IOHIDManagerRegisterInputValueCallback(sHIDManager, myHIDCallback, NULL);
IOHIDManagerScheduleWithRunLoop(sHIDManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
}
If anyone can help me out then that would be great. If I can't get this working then I guess I can move over to Cocoa event handling, but the pc code also has some keyboard and mouse polling that I was hoping to implement as well, and I'm not sure that Cocoa supports polling keyboard state - although I will be happy to be corrected.
TIA.
Related
Does anyone have any idea why the scroll is not tracked when I remotely connect to the computer via VNC? The scroll action itself is tracked, but the delta is always 0 (event->delta_y). I want to be able to detect in which direction I scroll (Up or Down) while working with my application remotely - using VNC. I was trying to go around this problem by using x11. This system tracks mouse scroll events correctly, but I must create a loop to track the event, not what I want. Also I was trying to get ( event->direction ), but it always returns SMOOTH_SCROLL, but should return the particular direction. The code I have now:
bool OcctGtkViewer::onMouseScroll(GdkEventScroll *theEvent)
{
bool isPressed = false;
int init_x, init_y, fin_x, fin_y;
Display *xdisplay = XOpenDisplay(0);
XEvent xevent;
::Window xroot = gdk_x11_window_get_xid(gtk_widget_get_window((GtkWidget *)myGLArea->gobj()));
int scr = DefaultScreen(xdisplay);
const Graphic3d_Vec2d aNewPos2d = myView->Window()->ConvertPointToBacking(Graphic3d_Vec2d(theEvent->x, theEvent->y));
XGrabPointer(xdisplay, xroot, 1, ButtonPressMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
while (1)
{
XNextEvent(xdisplay, &xevent);
if (xevent.type == ButtonPress)
{
switch (xevent.xbutton.button)
{
case Button4:
if (true)
{
const Aspect_ScrollDelta aScroll(Graphic3d_Vec2i(aNewPos2d + Graphic3d_Vec2d(0.5)), 1);
if (UpdateMouseScroll(aScroll))
{
myGLArea->queue_draw();
}
break;
}
case Button5:
if (true)
{
const Aspect_ScrollDelta aScroll(Graphic3d_Vec2i(aNewPos2d + Graphic3d_Vec2d(0.5)), -1);
if (UpdateMouseScroll(aScroll))
{
myGLArea->queue_draw();
}
break;
}
default:
break;
}
}
break;
}
XFlush(xdisplay);
XCloseDisplay(xdisplay);
return true;
}
There are many examples on how to handle the closing of a window using XLib, which can be found on the internet:
http://cboard.cprogramming.com/linux-programming/60466-xwindows-close-window-event.html
https://en.wikibooks.org/wiki/X_Window_Programming/XLib
https://john.nachtimwald.com/2009/11/01/x11-intercept-window-close-event/
There are several more. That said I have tried to implement them in code, as seen below. However when I click on X in the corner of my window I get no event sent to my message loop. Is this because XChcekWindowEvent ignores or does not process Client Messages? If this is not the case what are some other things I should be looking for to get messages from XLib set using SetWMProtocols?
m_impl->m_delete_window = XInternAtom(display, "WM_DELETE_WINDOW", False);
if (!XSetWMProtocols(display, window, &m_impl->m_delete_window, 1)) {
std::cout << "Set Window Protocols Failed" << std::endl;
}
...
while (!terminate) {
while (::XCheckWindowEvent(display, window, events::mask, &x_event)) {
if (x_event.type == ClientMessage) {
std::cout << "Client Message" << std::endl;
if ((Atom)x_event.xclient.data.l[0] == m_impl->m_delete_window) {
terminate = true;
}
}
}
}
The XCheckWindowEvent() will not return ClientMessage. It returns none of the non maskable ones. Work around:
while (XPending(display))
{
XNextEvent(display, &event);
But could create extra work to filter event by window.
BR Pekka
If you want to prevent using XNextEvent (which is not applied in real-time event loops), you may use the following code based on XCheckTypedWindowEvent:
// Globals
Atom wm_protocols;
Atom wm_delete_window;
// Functions
void PreventClose(Display* disp, Window& win)
{
wm_protocols = XInternAtom(disp, "WM_PROTOCOLS", false);
wm_delete_window = XInternAtom(disp, "WM_DELETE_WINDOW", false);
XSetWMProtocols(disp, win, &wm_delete_window, 1);
}
bool IsClosed(Display* disp, Window& win)
{
XEvent e;
if (XCheckTypedWindowEvent(disp, win, ClientMessage, &e))
if (e.xclient.message_type == wm_protocols &&
e.xclient.data.l[0] == wm_delete_window_)
return true;
return false;
}
// Usage
int main()
{
...
PreventClose(disp, win);
do {
if (IsClosed(disp, win))
// break, some actions, etc...
...
} while(true);
return 0;
}
For more info see man 3 XCheckTypedWindowEvent
I know I've seen this asked before, but the answers have been inconclusive or unsatisfactory (from what I could see), so I'd like to pose my situation here. I'm creating a program that has a custom form border (i.e. Form Border Style = None with our own controls around it). The program doesn't have the minimize/close animations, but just snaps, instead.
It is a .NET form (using C++/CLR) - is there something I can do? I've seen other programs do this (for example, Photoshop CS6/CC have the restore animation, but not the minimize one).
Is there a property/style I can apply by overriding the CreateParams? I'm game for "hacky" methods, but changing the form border style in a way that lets the user temporarily see the border isn't a viable option here.
Thanks in advance.
I ended up using a custom animation and figured I would share the relevant code. The following override of the WndProc catches minimizes/restores (including from clicking/double clicking in the taskbar). Be aware, setting the WindowState WILL NOT trigger this - you have to manually send the SC_MINIMIZE lpa with the WM_SYSCOMMAND to the window (or manually animate it). The entire animation code, timer included is below.
//Define a variable so it knows what animation is happening
short fade_mode = 0; //0 is fade in, 1 is minimize, 2 is close
short close_on_close = FALSE; //a variable to tell the close handler to re-animate or not - this allows this->Close(); to trigger the animation but avoids a loop.
//The WndProc
protected: virtual void WndProc(System::Windows::Forms::Message% msg) override {
switch (msg.Msg) {
case WM_SYSCOMMAND:
switch (msg.WParam.ToInt32()) {
case SC_MINIMIZE:
msg.Result = IntPtr::Zero;
fade_mode = 1;
fadetimer->Start();
return;
break;
}
break;
case WM_ACTIVATE: {
if (HIWORD(msg.WParam.ToInt32()) == 0) { //because non-zero wpa here means the form is minimized
this->WindowState = FormWindowState::Normal;
fade_mode = 0;
fadetimer->Start();
msg.Result = IntPtr::Zero;
return;
}
}
}
Form::WndProc(msg);
}
//The button event handlers
private: System::Void btn_close_Click(System::Object^ sender, System::EventArgs^ e) {
this->Close();
}
private: System::Void btn_minimize_Click(System::Object^ sender, System::EventArgs^ e) {
SendMessage(HWND(this->Handle.ToPointer()), WM_SYSCOMMAND, SC_MINIMIZE, NULL);
}
//The event animation code itself (set to a tick of 10ms) and the form closing handler:
private: System::Void fadetimer_Tick(System::Object^ sender, System::EventArgs^ e) {
if (this->IsDisposed == true) { //In the event that the form opened/closed quickly and has not stopped properly, clean up to avoid crashes.
fadetimer->Stop();
return;
}
switch (fade_mode) {
case 0: //fading in
if (this->Opacity < 1)
this->Opacity += 0.2;
else {
fade_mode = -1;
fadetimer->Stop();
}
break;
case 1: //minimizing
if (this->Opacity > 0)
this->Opacity -= 0.2;
else {
fade_mode = -1;
fadetimer->Stop();
this->WindowState = Windows::Forms::FormWindowState::Minimized;
}
break;
case 2: //closing
if (this->Opacity > 0)
this->Opacity -= 0.2;
else {
fade_mode = -1;
fadetimer->Stop();
close_on_close = TRUE;
this->Close();
}
break;
}
}
private: System::Void loginform_FormClosing(System::Object^ sender, System::Windows::Forms::FormClosingEventArgs^ e) {
if (close_on_close == FALSE) {
e->Cancel = true;
fade_mode = 2;
fadetimer->Start();
}
}
Be sure to set your form's opacity to 0% by default - it should automatically fade in when it's first created/shown (mine does, I can't remember if I've done something else that makes it so).
I have an application which uses Xlib library for simulate a mouse on the screen.
I used XQueryPointer and XWarpPointer functions with which I can simulate a mouse movement and also a click. My problem is that I can't "drag" an element (it's an interface with some blocks which you can move within a Qt application written in C++ and launched on Ubuntu).
I use many parts of this extract:
Sending X11 click event doesn't work with some windows
Can this function help me?
::XGrabPointer(mDisplay, window, True,
ButtonPressMask |
ButtonReleaseMask |
PointerMotionMask |
FocusChangeMask |
EnterWindowMask |
LeaveWindowMask,
GrabModeAsync,
GrabModeAsync,
RootWindow(mDisplay, DefaultScreen(mDisplay)),
None,
CurrentTime);
Do you have any idea?
Thank you in advance.
If you're okay with spawning a child process, you can easily use xdotool for this, as in:
xdotool mousedown 1
sleep 0.5
xdotool mousemove_relative --sync 200 200
sleep 0.5
xdotool mouseup 1
http://www.semicomplete.com/projects/xdotool/
http://tuxradar.com/content/xdotool-script-your-mouse
The source code to xdotool would serve as a good starting point if you want to do this yourself in C with the XTest library.
You can achieve this by sending the XMotionEvent to the window. the way X11 know that it is a drag event is by checking the state. So, if the state is OR with the Button1Mask then it means that the Motion is done when the Button1 was pressed.
I am pasting the pseudo code here. You might want to populate the relevant fields of the XMotionEvent as per your use-case.
Display *OpenedDisplay = XOpenDisplay(NULL);
int ret = 0,screen = 0;
XMotionEvent xbpe;
Window dummy;
unsigned int mask,dummyUInt;
int dummyInt;
int screencount = ScreenCount(xbpe.display);
for (int i = 0; i < screencount; i++) {
Screen *screenPointer = ScreenOfDisplay(xbpe.display, i);
ret = XQueryPointer(xbpe.display, RootWindowOfScreen(screenPointer), &dummy, &dummy,
&dummyInt, &dummyInt, &dummyInt, &dummyInt, &mask);
if (ret == True) {
screen = i;
break;
}
}
xbpe.type = MotionNotify;
xbpe.display = OpenedDisplay;
xbpe.window = wid;
xbpe.root = RootWindow(xbpe.display, screen);
xbpe.subwindow = None;
xbpe.time = CurrentTime;
xbpe.x = src_x;
xbpe.y = src_y;
xbpe.state = mask;
xbpe.is_hint = NotifyNormal;
xbpe.same_screen = True; /* Should we detect if window is on the same screen as cursor? */
XWindowAttributes attr;
ret = XGetWindowAttributes(xbpe.display, wid, &attr);
if(ret == 0){
qDebug() << wid <<"XGetWindowAttributes unsuccessfull returning early";
return;
}
/**************************** This is key here *******************/
if(isDrag){
xbpe.state |= Button1Mask;
}
ret = XSendEvent(xbpe.display, wid, False, PointerMotionMask|ButtonMotionMask|MotionNotify, (XEvent *)&xbpe);
XFlush(OpenedDisplay);
XCloseDisplay(OpenedDisplay);
I would recommend navigating the source code of xdotool. It doesn't specifically covers your issue but gives you enough information on populating the fields of X*Event.
I need to break a while loop when the use clicks the close button on the window, but I don't know what to check for. I'm using allegro to run the GUI.
If using Allegro 4: set_close_button_callback()
volatile int hit_closed = 0;
void close_button_proc()
{
hit_closed = 1;
}
// later after creating the display:
set_close_button_callback(close_button_proc);
while (!hit_closed)
{
}
With Allegro 5, it's more like:
al_register_event_source(queue, al_get_display_event_source(display));
// in your event loop:
if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE) {
}
See the manual for all the details.