Merge branch 'uisim-xtest'
Make wxUIActionSimulator work with GTK+3, including when using DPI scaling.
This commit is contained in:
@@ -2,11 +2,10 @@
|
||||
// Name: src/unix/uiactionx11.cpp
|
||||
// Purpose: wxUIActionSimulator implementation
|
||||
// Author: Kevin Ollivier, Steven Lamerton, Vadim Zeitlin
|
||||
// Modified by:
|
||||
// Created: 2010-03-06
|
||||
// Copyright: (c) Kevin Ollivier
|
||||
// Copyright: (c) 2010 Kevin Ollivier
|
||||
// (c) 2010 Steven Lamerton
|
||||
// (c) 2010 Vadim Zeitlin
|
||||
// (c) 2010-2016 Vadim Zeitlin
|
||||
// Licence: wxWindows licence
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -18,16 +17,72 @@
|
||||
#include "wx/event.h"
|
||||
#include "wx/evtloop.h"
|
||||
|
||||
#include "wx/private/uiaction.h"
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#if wxUSE_XTEST
|
||||
#include <X11/extensions/XTest.h>
|
||||
#endif
|
||||
|
||||
#include "wx/unix/utilsx11.h"
|
||||
|
||||
#ifdef __WXGTK3__
|
||||
#include <gdk/gdk.h>
|
||||
#include <gtk/gtk.h>
|
||||
#endif
|
||||
|
||||
// Normally we fall back on "plain X" implementation if XTest is not available,
|
||||
// but it's useless to do it when using GTK+ 3 as it's not going to work with
|
||||
// it anyhow because GTK+ 3 needs XInput2 events and not the "classic" ones we
|
||||
// synthesize here, so don't even compile in this code for wxGTK3 port.
|
||||
#define wxUSE_PLAINX_IMPL (!defined(__WXGTK3__))
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
void SendButtonEvent(int button, bool isDown)
|
||||
// Base class for both available X11 implementations.
|
||||
class wxUIActionSimulatorX11Impl : public wxUIActionSimulatorImpl
|
||||
{
|
||||
public:
|
||||
// Return the most appopriate implementation to use: if XTest is available,
|
||||
// use it, otherwise use plain X11 calls.
|
||||
//
|
||||
// The returned pointer is owned by the caller.
|
||||
static wxUIActionSimulatorImpl* New();
|
||||
|
||||
virtual bool MouseMove(long x, long y) wxOVERRIDE;
|
||||
virtual bool MouseDown(int button = wxMOUSE_BTN_LEFT) wxOVERRIDE;
|
||||
virtual bool MouseUp(int button = wxMOUSE_BTN_LEFT) wxOVERRIDE;
|
||||
|
||||
virtual bool DoKey(int keycode, int modifiers, bool isDown) wxOVERRIDE;
|
||||
|
||||
protected:
|
||||
// This ctor takes ownership of the display.
|
||||
explicit wxUIActionSimulatorX11Impl(wxX11Display& display)
|
||||
: m_display(display)
|
||||
{
|
||||
}
|
||||
|
||||
wxX11Display m_display;
|
||||
|
||||
private:
|
||||
// Common implementation of Mouse{Down,Up}() which just forwards to
|
||||
// DoX11Button() after translating wx button to X button constant.
|
||||
bool SendButtonEvent(int button, bool isDown);
|
||||
|
||||
virtual bool DoX11Button(int xbutton, bool isDown) = 0;
|
||||
virtual bool DoX11MouseMove(long x, long y) = 0;
|
||||
virtual bool DoX11Key(KeyCode xkeycode, int modifiers, bool isDown) = 0;
|
||||
|
||||
wxDECLARE_NO_COPY_CLASS(wxUIActionSimulatorX11Impl);
|
||||
};
|
||||
|
||||
bool wxUIActionSimulatorX11Impl::SendButtonEvent(int button, bool isDown)
|
||||
{
|
||||
if ( !m_display )
|
||||
return false;
|
||||
|
||||
int xbutton;
|
||||
switch (button)
|
||||
{
|
||||
@@ -42,12 +97,37 @@ void SendButtonEvent(int button, bool isDown)
|
||||
break;
|
||||
default:
|
||||
wxFAIL_MSG("Unsupported button passed in.");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
wxX11Display display;
|
||||
wxCHECK_RET(display, "No display available!");
|
||||
// Ensure that the event is received by the correct window by processing
|
||||
// all pending events, notably mouse moves.
|
||||
XSync(m_display, False /* don't discard */);
|
||||
|
||||
return DoX11Button(xbutton, isDown);
|
||||
}
|
||||
|
||||
#if wxUSE_PLAINX_IMPL
|
||||
|
||||
// Implementation using just plain X11 calls.
|
||||
class wxUIActionSimulatorPlainX11Impl : public wxUIActionSimulatorX11Impl
|
||||
{
|
||||
public:
|
||||
explicit wxUIActionSimulatorPlainX11Impl(wxX11Display& display)
|
||||
: wxUIActionSimulatorX11Impl(display)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
virtual bool DoX11Button(int xbutton, bool isDown) wxOVERRIDE;
|
||||
virtual bool DoX11MouseMove(long x, long y) wxOVERRIDE;
|
||||
virtual bool DoX11Key(KeyCode xkeycode, int modifiers, bool isDown) wxOVERRIDE;
|
||||
|
||||
wxDECLARE_NO_COPY_CLASS(wxUIActionSimulatorPlainX11Impl);
|
||||
};
|
||||
|
||||
bool wxUIActionSimulatorPlainX11Impl::DoX11Button(int xbutton, bool isDown)
|
||||
{
|
||||
XEvent event;
|
||||
memset(&event, 0x00, sizeof(event));
|
||||
|
||||
@@ -55,7 +135,7 @@ void SendButtonEvent(int button, bool isDown)
|
||||
event.xbutton.button = xbutton;
|
||||
event.xbutton.same_screen = True;
|
||||
|
||||
XQueryPointer(display, display.DefaultRoot(),
|
||||
XQueryPointer(m_display, m_display.DefaultRoot(),
|
||||
&event.xbutton.root, &event.xbutton.window,
|
||||
&event.xbutton.x_root, &event.xbutton.y_root,
|
||||
&event.xbutton.x, &event.xbutton.y, &event.xbutton.state);
|
||||
@@ -64,54 +144,29 @@ void SendButtonEvent(int button, bool isDown)
|
||||
while (event.xbutton.subwindow)
|
||||
{
|
||||
event.xbutton.window = event.xbutton.subwindow;
|
||||
XQueryPointer(display, event.xbutton.window,
|
||||
XQueryPointer(m_display, event.xbutton.window,
|
||||
&event.xbutton.root, &event.xbutton.subwindow,
|
||||
&event.xbutton.x_root, &event.xbutton.y_root,
|
||||
&event.xbutton.x, &event.xbutton.y, &event.xbutton.state);
|
||||
}
|
||||
|
||||
XSendEvent(display, PointerWindow, True, 0xfff, &event);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
bool wxUIActionSimulator::MouseDown(int button)
|
||||
{
|
||||
SendButtonEvent(button, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wxUIActionSimulator::MouseMove(long x, long y)
|
||||
{
|
||||
wxX11Display display;
|
||||
wxASSERT_MSG(display, "No display available!");
|
||||
|
||||
Window root = display.DefaultRoot();
|
||||
XWarpPointer(display, None, root, 0, 0, 0, 0, x, y);
|
||||
|
||||
// At least with wxGTK we must always process the pending events before the
|
||||
// mouse position change really takes effect, so just do it from here
|
||||
// instead of forcing the client code using this function to always use
|
||||
// wxYield() which is unnecessary under the other platforms.
|
||||
if ( wxEventLoopBase* const loop = wxEventLoop::GetActive() )
|
||||
{
|
||||
loop->YieldFor(wxEVT_CATEGORY_USER_INPUT);
|
||||
}
|
||||
XSendEvent(m_display, PointerWindow, True, 0xfff, &event);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wxUIActionSimulator::MouseUp(int button)
|
||||
bool wxUIActionSimulatorPlainX11Impl::DoX11MouseMove(long x, long y)
|
||||
{
|
||||
SendButtonEvent(button, false);
|
||||
Window root = m_display.DefaultRoot();
|
||||
XWarpPointer(m_display, None, root, 0, 0, 0, 0, x, y);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wxUIActionSimulator::DoKey(int keycode, int modifiers, bool isDown)
|
||||
bool
|
||||
wxUIActionSimulatorPlainX11Impl::DoX11Key(KeyCode xkeycode,
|
||||
int modifiers,
|
||||
bool isDown)
|
||||
{
|
||||
wxX11Display display;
|
||||
wxCHECK_MSG(display, false, "No display available!");
|
||||
|
||||
int mask, type;
|
||||
|
||||
if ( isDown )
|
||||
@@ -125,14 +180,9 @@ bool wxUIActionSimulator::DoKey(int keycode, int modifiers, bool isDown)
|
||||
mask = KeyReleaseMask;
|
||||
}
|
||||
|
||||
WXKeySym xkeysym = wxCharCodeWXToX(keycode);
|
||||
KeyCode xkeycode = XKeysymToKeycode(display, xkeysym);
|
||||
if ( xkeycode == NoSymbol )
|
||||
return false;
|
||||
|
||||
Window focus;
|
||||
int revert;
|
||||
XGetInputFocus(display, &focus, &revert);
|
||||
XGetInputFocus(m_display, &focus, &revert);
|
||||
if (focus == None)
|
||||
return false;
|
||||
|
||||
@@ -147,7 +197,7 @@ bool wxUIActionSimulator::DoKey(int keycode, int modifiers, bool isDown)
|
||||
mod |= ControlMask;
|
||||
|
||||
XKeyEvent event;
|
||||
event.display = display;
|
||||
event.display = m_display;
|
||||
event.window = focus;
|
||||
event.root = DefaultRootWindow(event.display);
|
||||
event.subwindow = None;
|
||||
@@ -166,4 +216,141 @@ bool wxUIActionSimulator::DoKey(int keycode, int modifiers, bool isDown)
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // wxUSE_PLAINX_IMPL
|
||||
|
||||
#if wxUSE_XTEST
|
||||
|
||||
// Implementation using XTest extension.
|
||||
class wxUIActionSimulatorXTestImpl : public wxUIActionSimulatorX11Impl
|
||||
{
|
||||
public:
|
||||
explicit wxUIActionSimulatorXTestImpl(wxX11Display& display)
|
||||
: wxUIActionSimulatorX11Impl(display)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
virtual bool DoX11Button(int xbutton, bool isDown) wxOVERRIDE;
|
||||
virtual bool DoX11MouseMove(long x, long y) wxOVERRIDE;
|
||||
virtual bool DoX11Key(KeyCode xkeycode, int modifiers, bool isDown) wxOVERRIDE;
|
||||
|
||||
wxDECLARE_NO_COPY_CLASS(wxUIActionSimulatorXTestImpl);
|
||||
};
|
||||
|
||||
bool wxUIActionSimulatorXTestImpl::DoX11Button(int xbutton, bool isDown)
|
||||
{
|
||||
return XTestFakeButtonEvent(m_display, xbutton, isDown, 0) != 0;
|
||||
}
|
||||
|
||||
bool wxUIActionSimulatorXTestImpl::DoX11MouseMove(long x, long y)
|
||||
{
|
||||
#ifdef __WXGTK3__
|
||||
// We need to take into account the scaling factor as the input coordinates
|
||||
// are in GTK logical "application pixels", while we need the physical
|
||||
// "device pixels" for the X call below, so scale them if we have the
|
||||
// required support at both compile- and run-time.
|
||||
#if GTK_CHECK_VERSION(3,10,0)
|
||||
if ( gtk_check_version(3, 10, 0) == NULL )
|
||||
{
|
||||
if ( GdkScreen* const screen = gdk_screen_get_default() )
|
||||
{
|
||||
// For multi-monitor support we would need to determine to which
|
||||
// monitor the point (x, y) belongs, for now just use the scale
|
||||
// factor of the main one.
|
||||
gint const scale = gdk_screen_get_monitor_scale_factor(screen, 0);
|
||||
x *= scale;
|
||||
y *= scale;
|
||||
}
|
||||
}
|
||||
#endif // GTK+ 3.10+
|
||||
#endif // __WXGTK3__
|
||||
|
||||
return XTestFakeMotionEvent(m_display, -1, x, y, 0) != 0;
|
||||
}
|
||||
|
||||
bool
|
||||
wxUIActionSimulatorXTestImpl::DoX11Key(KeyCode xkeycode,
|
||||
int WXUNUSED(modifiers),
|
||||
bool isDown)
|
||||
{
|
||||
return XTestFakeKeyEvent(m_display, xkeycode, isDown, 0) != 0;
|
||||
}
|
||||
|
||||
#endif // wxUSE_XTEST
|
||||
|
||||
wxUIActionSimulatorImpl* wxUIActionSimulatorX11Impl::New()
|
||||
{
|
||||
wxX11Display display;
|
||||
|
||||
#if wxUSE_XTEST
|
||||
// If we can fall back on plain X implementation, check if XTest extension
|
||||
// is available and if it isn't, use the other one. OTOH if we don't have
|
||||
// the other one anyhow, then testing for XTest availability is useless.
|
||||
#if wxUSE_PLAINX_IMPL
|
||||
int dummy;
|
||||
if ( XTestQueryExtension(display, &dummy, &dummy, &dummy, &dummy) )
|
||||
#endif // wxUSE_PLAINX_IMPL
|
||||
return new wxUIActionSimulatorXTestImpl(display);
|
||||
#endif // wxUSE_XTEST
|
||||
|
||||
#if wxUSE_PLAINX_IMPL
|
||||
return new wxUIActionSimulatorPlainX11Impl(display);
|
||||
#endif // wxUSE_PLAINX_IMPL
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
bool wxUIActionSimulatorX11Impl::MouseDown(int button)
|
||||
{
|
||||
return SendButtonEvent(button, true);
|
||||
}
|
||||
|
||||
bool wxUIActionSimulatorX11Impl::MouseMove(long x, long y)
|
||||
{
|
||||
if ( !m_display )
|
||||
return false;
|
||||
|
||||
if ( !DoX11MouseMove(x, y) )
|
||||
return false;
|
||||
|
||||
// At least with wxGTK we must always process the pending events before the
|
||||
// mouse position change really takes effect, so just do it from here
|
||||
// instead of forcing the client code using this function to always use
|
||||
// wxYield() which is unnecessary under the other platforms.
|
||||
if ( wxEventLoopBase* const loop = wxEventLoop::GetActive() )
|
||||
{
|
||||
loop->YieldFor(wxEVT_CATEGORY_USER_INPUT);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wxUIActionSimulatorX11Impl::MouseUp(int button)
|
||||
{
|
||||
return SendButtonEvent(button, false);
|
||||
}
|
||||
|
||||
bool wxUIActionSimulatorX11Impl::DoKey(int keycode, int modifiers, bool isDown)
|
||||
{
|
||||
if ( !m_display )
|
||||
return false;
|
||||
|
||||
WXKeySym xkeysym = wxCharCodeWXToX(keycode);
|
||||
KeyCode xkeycode = XKeysymToKeycode(m_display, xkeysym);
|
||||
if ( xkeycode == NoSymbol )
|
||||
return false;
|
||||
|
||||
return DoX11Key(xkeycode, modifiers, isDown);
|
||||
}
|
||||
|
||||
wxUIActionSimulator::wxUIActionSimulator()
|
||||
: m_impl(wxUIActionSimulatorX11Impl::New())
|
||||
{
|
||||
}
|
||||
|
||||
wxUIActionSimulator::~wxUIActionSimulator()
|
||||
{
|
||||
delete m_impl;
|
||||
}
|
||||
|
||||
#endif // wxUSE_UIACTIONSIMULATOR
|
||||
|
||||
Reference in New Issue
Block a user