QStateMachine with QSignalTransition loses signal - c++

i have a strange problem with QStateMachine, which i cannot solve for a week.
Brief explanation:
I use QStateMachine in my application for controlling of sending commands to a biomedical device, connected through COM port. The whole state machine is complicated with hundreds of states and frequent substating.
Machine is being generated from device's communication protocol, based on user's preference. For example, if user wants some "reaction" from device, this reaction is composed of several subreactions that consist of elementary steps (instructions for the device). Application builds whole reaction from these elementary steps and starts it on state machine.
Therefore, I've created class CompositeReactionControl (inherited from QState) which allows to build a composite from particular substates.
QSignalTransition instances are used to connect states together.
Problem description:
Sometimes the machine does not transit from one state to another, when finish() signal of previous state is emitted.
NOTES:
I am 100% sure that state is connected.
I am 100% sure that signal is fired.
Event through machine and its states run in separate thread from the rest of application, this particular signal is fired and caught in SAME thread
It happens randomly in all reactions (there is no particular reaction or substate connected with this problem)
Substate which caused problem in one run (reaction), runs without any problem in other runs of same reaction
It happens when StateMachine runs for long time, it never appears before 4-th reaction (no matter which reactions are chosen by user).
CODE
Problematic parts are marked with comment "HERE IS PROBLEM" in the following code...
Adding of new substate to CompositeReactionControl:
/**
* #brief Add new reaction control as a child of this composite
* #param reaction child reaction (inherited from QState)
* #return added child reaction
*/
AbstractReactionControl* CompositeReactionControl::addChildReaction(
AbstractReactionControl* reaction) {
// Check whether reaction is valid
if (reaction == NULL) {
QString msg = tr("Cannot add NULL subreaction to reaction '%1'.");
Logger::getInstance()->addError(msg.arg(getName()), this);
return NULL;
}
// Adopt reaction
reaction->setParent(this);
// Store previous reaction control and add current to list
AbstractReactionControl* prev = controls.size() > 0 ? controls.last() : NULL;
controls.append(reaction);
// Connects current state
connect(reaction, SIGNAL(reactionEntered(core::AbstractCommunicationState*)),
SLOT(onSubReactionEntered(core::AbstractCommunicationState*)));
connect(reaction, SIGNAL(reactionFinished(core::AbstractCommunicationState*)),
SLOT(onSubReactionFinished(core::AbstractCommunicationState*)));
// If previous state does not exist (this is the first one) then set current
// state 'c' as initial state and add transition from 'c' to final state
// Otherwise add transition from previous state to 'c', remove transition from
// previous state to final state and add transition from 'c' to final state
if (prev == NULL) {
this->setInitialState(reaction);
this->firstState = reaction;
} else {
// Remove end transitions from previous state
prev->removeTransition(endTransition1);
prev->removeTransition(endTransition2);
delete endTransition1;
delete endTransition2;
// Replaced with PassTransition
//prev->addTransition(prev, SIGNAL(finished()), reaction);
// HERE IS PROBLEM: I am 100% sure that finished() signal is emitted,
// but sometimes not caught by transition
PassTransition* t = new PassTransition(this, prev, SIGNAL(finished()));
t->setTargetState(reaction);
prev->addTransition(t);
}
// Assign new end transitions to current state
endTransition1 = new RepeatTransition(this, reaction, SIGNAL(finished()));
endTransition2 = new FinishTransition(this, reaction, SIGNAL(finished()));
endTransition1->setTargetState(firstState);
endTransition2->setTargetState(allFinished);
reaction->addTransition(endTransition1);
reaction->addTransition(endTransition2);
// Finish if halt
reaction->addTransition(this, SIGNAL(halt()), allFinished);
// Exit reaction
return reaction;
}
//---------------------------------------------------------------------------
PassTransition:
/**
* #class PassTransition
* #brief Passes control from one substate to another
* #author Michal Rost
* #date 6.11.2013
*/
class PassTransition : public QSignalTransition {
public:
PassTransition(CompositeReactionControl* owner, QObject* sender,
const char *signal) : QSignalTransition(sender, signal) {
this->owner = owner;
}
protected:
CompositeReactionControl* owner;
bool eventTest(QEvent *event) {
// HERE IS PROBLEM: I know that signal is called, but event is not received
QString msg = tr("Event Test %1, source: %2 target: %3");
AbstractReactionControl* s = dynamic_cast<AbstractReactionControl*>(this->sourceState());
AbstractReactionControl* t = dynamic_cast<AbstractReactionControl*>(this->targetState());
common::Logger::getInstance()->addDebug(msg.arg(event->type()).arg(s->getName()).arg(t->getName()), this);
return QSignalTransition::eventTest(event);
}
void onTransition(QEvent *event) {
QSignalTransition::onTransition(event);
QString msg = tr("Transition called");
common::Logger::getInstance()->addDebug(msg, this);
}
};
Log from good case
In this case everything works well. Important is that PassTransition::EventTest receives event of type 192, which is state machine event created from finished() signal of previous state.
[08:33:01:976][core::CompositeReactionControl] State 'send_fluorescence_on' finished, disconnecting...
[08:33:01:976][core::CompositeReactionControl] State 'ProfileData' removed control from 'send_fluorescence_on' substate.
[08:33:01:977][core::ReactionEmitter] Sending state entered. Timer Started.
[08:33:01:977][core::PassTransition] Event Test 0, source: send_fluorescence_on target: send_fluorescence_off
[08:33:01:977][core::PassTransition] Event Test 0, source: measure_fluorescence target: measure_fluorescence
[08:33:01:977][core::PassTransition] Event Test 192, source: send_fluorescence_on target: send_fluorescence_off
[08:33:01:977][core::PassTransition] Transition called
[08:33:01:977][core::CompositeReactionControl] State 'ProfileData' gave control to 'send_fluorescence_off' substate.
Log from bad case
As can be seen, if error RANDOMLY occurs, mentioned event is not received by method PassTransition::EventTest. After 5 second timeout (I tryed even longer intervals), I stop the statemachine and print error.
[08:33:01:991][core::CompositeReactionControl] State 'send_fluorescence_off' finished, disconnecting...
[08:33:01:991][core::CompositeReactionControl] State 'ProfileData' removed control from 'send_fluorescence_off' substate.
[08:33:01:991][core::ReactionEmitter] Sending state entered. Timer Started.
[08:33:01:991][core::PassTransition] Event Test 0, source: send_fluorescence_off target: led_off
[08:33:01:991][core::PassTransition] Event Test 0, source: measure_fluorescence target: measure_fluorescence
[08:33:16:966][core::ReactionEmitter] State machine is frozen. Reaction will be stopped!

Related

Updating QChart from QLineSeries in a running while loop

I want to make my QChart dynamically update whenever a point is added to the QLineSeries object attached to it, but it seems that this update only occurs after the while loop I am running has finished. I am using said while loop in interface.cpp that calls a function updatePlot() which adds the data point to the line series, but this only updates the chart after the while loop has completely finished. Pseudo code of what is happening here:
qtwindow.cpp
// Constructor that initializes the series which will be passed into the interface
AlgoWindow::AlgoWindow( ..., TradingInterface* interface, ... ) {
...
QLineSeries* series = new QLineSeries();
QLineSeries* benchmark = new QLineSeries();
QChart* chart = new QChart();
chart->addSeries(series);
chart->addSeries(benchmark);
// Also creates custom axes which are attached to each series
...
}
// Slot connected to a button signal
void AlgoWindow::buttonClicked() {
// Runs the backtest
interface->runbacktest(..., series, benchmark, ...);
}
interface.cpp
void TradingInterface::runbacktest(..., QtCharts::QLineSeries* algoplot, QtCharts::QLineSeries* benchplot) {
// Runs a huge while loop that continuously checks for events
while (continue_backtest) {
if (!eventsqueue.isEmpty()) {
// Handle each event for the bar
} else {
// All events have been handled for the day, so plot
updatePlot(algoplot, benchplot);
}
}
}
void TradingInterface::updatePlot(QtCharts::QLineSeries *algoseries,
QtCharts::QLineSeries *benchseries) {
// Get the date and the information to put in each point
long date = portfolio.bars->latestDates.back();
double equitycurve = portfolio.all_holdings.rbegin().operator*().second["equitycurve"];
double benchcurve = benchmarkportfolio.all_holdings.rbegin().operator*.second["equitycurve"];
// Append the new points to their respective QLineSeries
algoseries->append(date * 1000, equitycurve*100);
benchseries->append(date * 1000, benchcurve*100);
}
This gives me no errors and the while loop completes, but the lines are only plotted after runbacktest() exits. It then plots all the data correctly, but all at once.
What I need to happen is for the QChart to update every time the lines are added, which my guess was to use some form of custom signal-slot listener but I have no clue how to go about that. If the graph will not update until after the function completes, is it even possible within the QChart framework?
Also, I have already tried QChart::update() and QChartView::repaint(). Both produced the same results as without.
EDIT: I tried setting up a new thread that emits a signal back to the main thread whenever the data is completed but it seems to have changed nothing. The QChart still does not update until after all the data has been inputted. I added a couple lines to help debug and it seems like the function which emits the signal runs consistently just fine, but the slot function which receives the signal only runs after the thread has finished. Not only that, but slowing the signals down with a sleep does not make it plot slowly (like I thought), as the QChart still refuses to update until after the final update to addData().
Either remove your while loop and perform the work one step at a time with a timer.
Or run your runbacktest function in another thread and send a signal to update the QChart in the UI's thread when the data is ready.
Either way you need to give control back to the event loop so that the chart can be repainted.
The Qt idiom for running an operation “continuously” is to use a zero-duration “timer”. It’s not a timer really, but Qt calls it one.
You can do the operation in chunks that take approximately a millisecond. For this, invert the control flow. Qt doesn't provide too much syntactic sugar for it, but it's easy to remedy.
Convert this code, which maintains a loop:
for (int i = 0; i < 1000; ++i) {
doSomething(i);
}
into this lambda, which is invoked by the event loop:
m_tasks.addTask([this](i = 0) mutable {
doSomething(i);
++i;
return i < 1000;
});
assuming:
class Controller : public QObject {
Tasks m_tasks;
...
};
where the Tasks class maintains a list of tasks to be executed by the event loop:
class Tasks : public QObject {
Q_OBJECT
QBasicTimer timer;
std::list<std::function<bool()>> tasks;
protected:
void timerEvent(QTimerEvent *ev) override {
if (ev->timerId() != timer.timerId())
return;
for (auto it = tasks.begin(); it != tasks.end(); ) {
bool keep = (*it)();
if (!keep)
it = tasks.erase(it);
else
++it;
}
if (tasks.empty())
timer.stop();
}
public:
using QObject :: QObject;
template <typename F> void addTask(F &&fun) {
tasks.emplace_back(std::forward(fun));
if (!timer.isActive())
timer.start(0, this);
}
};

Coded Ui WaitForControlReady(200) does not break on timeout

Description: MFC Application
Desired action - check if a sub window open. Identify the sub window by a unique button located in the window.
Issue: When window is closed, The following code instead of returning false after 200 ms waits 7 seconds = timeout does not work.
The GeneralUIMap winwindow represents the button, since the button itself does not have unique search properties, and can only be uniquely identified by the WinWindow wrapper GeneralUIMap:
class x{
private UITestControl mainApplicationWindow; //main application window
private WinWindow GeneralUIMap;
private WinButton btnZoomAdd;
X(){
mainApplicationWindow = new WinWindow();
mainApplicationWindow.TechnologyName = "MSAA";
mainApplicationWindow.SearchProperties UITestControl.PropertyNames.Name] = " - Main Window - Welcome";
GeneralUIMap = new WinWindow(mainApplicationWindow);
GeneralUIMap.SearchProperties[WinWindow.PropertyNames.ControlId] = "8546";
btnZoomAdd = new WinButton(GeneralUIMap);
}
public bool isVisible()
{
WinWindow w = new WinWindow(mainApplicationWindow);
w.SearchProperties[WinWindow.PropertyNames.ControlId] = "8546";
try
{
w.WaitForControlReady(200);
}
catch (Exception e)
{
return false;
}
return true;
}
Does the window exist when you wait for it to be ready? If not, the function will raise a null exception. This exception takes time to come since Coded UI tries to find the control before to give up and returns null.
Use WaitForControlExist() or TryFind() as condition before to use WaitForControlReady().
Also WaitForControlReady() will return false if the timeout is reached. But your code actually will always returns true (if there is no exception) since you do not evaluate the value of WaitForControlReady().
Instead of try catch, use this:
if (w.TryFind() && w.WaitForControlReady(200))
{
return true;
else
{
return false;
}
IMO, 200 ms is really a short amount of time for a timeout. It could lead to false errors. In other cases that UI performance validation, you should have reasonable timeouts. The default timeout is 3000 ms in Coded UI, which should be sufficient. But don't hesitate to increase it.
Remember than a timeout will be reached only when the condition is not meet unlike delay where the action will wait until the end regardless the status of the object. Plus, automated tests will still be faster than human over all because of its regularity.
A last remark, your method "IsVisible()" does not exactly return the visible status of the window. With Coded UI, I think you can considere that a control is visible when you have a clickable Point or check the ControlStates:
w.WaitForControlPropertyNotEqual(UITestControl.PropertyNames.State, ControlStates.Invisible)
and maybe too:
w.WaitForControlPropertyNotEqual(UITestControl.PropertyNames.State, ControlStates.Offscreen);

X11: how to implement a global hotkey without focusloss

I have a Qt app which grabs a key with XGrabKey. I want my app to show and hide, if I press the hotkey. Additionally it has to hide if it loses focus. This works well with one drawback: The XGrabKeyboard which is used by XGrabKey, generates a FocusIn ans FocusOut event. This implies that, if I press the hotkey when the app is visible, the app receives the FocusOut event, hides and immendiately after that receives the hotkeyevent and shows again.
Can I somehow avoid the X server to generate these focus events?
Diggin deeper for about a few hours I tinkered a solution that feels nice. It uses Qt QWidget::nativeEvent and libxcb. libxcb seems to be the next gen libX11 wrapper. But it is horribly undocumented. Uncool that Qt does not provide the mode of a QFocusEvent. But I guess thats the bane in everything wanting to be platform agnostic.
Note: This is Qt5, Qt4 had stuff like QWidget::x11info()
.h
class Class : public QWidget
{
Q_OBJECT
public:
Class(QWidget *parent = 0);
~Class();
protected:
virtual bool nativeEvent(const QByteArray &eventType, void *message, long *) override;
};
.cpp
/**************************************************************************//**
* #brief Class::nativeEvent
* This special event handler can be reimplemented in a subclass to receive
* native platform events identified by eventType which are passed in the
* message parameter. In your reimplementation of this function, if you want to
* stop the event being handled by Qt, return true and set result. If you
* return false, this native event is passed back to Qt, which translates the
* event into a Qt event and sends it to the widget.
*
* This method is called for every native event. On X11, eventType is set to
* "xcb_generic_event_t", and the message can be casted to a
* xcb_generic_event_t pointer.
*
* #param eventType
* #param message
* #return Indicator if this event shall be stoped being handled further.
*/
bool Class::nativeEvent(const QByteArray &eventType, void *message, long *)
{
if (eventType == "xcb_generic_event_t")
{
xcb_generic_event_t* event = static_cast<xcb_generic_event_t *>(message);
switch (event->response_type & ~0x80)
{
case XCB_FOCUS_IN: {
xcb_focus_in_event_t *fe = (xcb_focus_in_event_t *)event;
if (fe->mode & (XCB_NOTIFY_MODE_GRAB|XCB_NOTIFY_MODE_UNGRAB)){
return true; // Ignore this events
}
break;
}
case XCB_FOCUS_OUT: {
xcb_focus_out_event_t *fe = (xcb_focus_out_event_t *)event;
if (fe->mode & (XCB_NOTIFY_MODE_GRAB|XCB_NOTIFY_MODE_UNGRAB)){
return true; // Ignore this events
}
break;
}
}
}
return false;
}
I don't think so, but you can check the "mode" field in the XFocusChangeEvent. It can be NotifyNormal, NotifyWhileGrabbed, NotifyGrab, or NotifyUngrab. Grabbing keys should generate events with the last two modes, and so you may choose to ignore events with these modes, though I'm not sure how you would do that with Qt.

Launch a fragment in my Android application from the notification bar

How do I start a fragment in my Android application from a notification in the notification bar?
I've tried to implement this answer of creating my own action and then setting the action to the intent, but I'm unsure how to use it and what is required additionally - like adding something to the Manifest.
I've got a notification class that receives a context, a message and then an action. I then want to filter on that action to determine which fragment to launch, but I don't know how to launch a fragment as opposed to launching an activity.
Here is my Notifications.java class (incomplete):
public class Notifications {
private Context mContext;
public Notifications(Context context) {
this.mContext = context;
}
public static void notify(Context context, String message, String action) {
//Action you invent should include the application package as a prefix — for example: "com.example.project.SHOW_COLOR".
action = "my.package.name.here.frag."+action;
//Construct a user message.
String appName = context.getResources().getString(R.string.app_name);
// Use the Notification manager to send notification
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
// Create a notification using android stat_notify_chat icon.
Notification notification = new Notification(R.drawable.ic_stat_notification, message, 0);
//Sound, lights, vibration.
//REMEMBER PERMISSIONS.
notification.defaults |= Notification.DEFAULT_SOUND;
notification.defaults |= Notification.DEFAULT_VIBRATE;
notification.defaults |= Notification.DEFAULT_LIGHTS;
// Create a pending intent to open the application when the notification is clicked.
//Restart the app.
Intent launchIntent = null;
//Get the action and based on what the action is, launch the application displaying the appropriate fragment.
if (action.equalsIgnoreCase("friend")){
//New friend notification
//Launch application displaying the list of friends
}
if (action.equalsIgnoreCase("article")){
//New article has been posted
//Launch application displaying the news feed fragment
}
if (action.equalsIgnoreCase("points")){
//Points scored notification
//Launch application displaying the user's profile
}
if (action.equalsIgnoreCase("redeemable")){
//New redeemable is offered
//Launch application displaying the list of redeemables
}
if (!action.equalsIgnoreCase("friend")
&& !action.equalsIgnoreCase("article")
&& !action.equalsIgnoreCase("points")
&& !action.equalsIgnoreCase("redeemable")){
//Not specific, so launch the application from scratch displaying the activity feed
launchIntent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
}
if(action != null && launchIntent != null){
launchIntent.setAction(action);
}
// Set the notification and register the pending intent to it
notification.setLatestEventInfo(context, appName, message, pendingIntent);
// Trigger the notification
notificationManager.notify(0, notification);
}
}
So this was actually pretty easy. Hopefully I can help someone else see this too.
I send an action to this notify function. The I add that action to my intent to launch an activity. In my case I open the launching activity, because all the fragments are loaded from within that activity based on what the user does. So I set the action using setAction and the I use that action in the activity as below.
My Notifications.java class changed to this:
public static void notify(Context context, String message, String action) {
action = action.toUpperCase();
// Create a pending intent to open the the application when the notification is clicked.
//Restart the app.
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
if(action != null && launchIntent != null){
launchIntent.setAction(action);
}
PendingIntent pendingIntent = PendingIntent.getActivity(context, -1, launchIntent, PendingIntent.FLAG_UPDATE_CURRENT);
notification.when = System.currentTimeMillis();
notification.flags |= Notification.FLAG_AUTO_CANCEL;
// Set the notification and register the pending intent to it
notification.setLatestEventInfo(context, appName, message, pendingIntent);
// Trigger the notification
notificationManager.notify(0, notification);
}
And then in my activity from where I load the fragments, I get the action and filter it:
Intent intent = getIntent();
try{
String action = intent.getAction().toUpperCase();
if(action != null){
if(action.equalsIgnoreCase(getResources().getString(R.string.notification_action_friend))){
goFrag(getResources().getInteger(R.integer.FRAG_A_INT));
}
if(action.equalsIgnoreCase(getResources().getString(R.string.notification_action_article))){
goFrag(getResources().getInteger(R.integer.FRAG_B_INT));
}
if(action.equalsIgnoreCase(getResources().getString(R.string.notification_action_points))){
goFrag(getResources().getInteger(R.integer.FRAG_C_INT));
}
if(action.equalsIgnoreCase(getResources().getString(R.string.notification_action_redeemable))){
goFrag(getResources().getInteger(R.integer.FRAG_D_INT));
}
if(action.equalsIgnoreCase(getResources().getString(R.string.notification_action_dance))){
goFrag(getResources().getInteger(R.integer.FRAG_E_INT));
}
}else{
Log.d(TAG, "Intent was null");
}
}catch(Exception e){
Log.e(TAG, "Problem consuming action from intent", e);
}
In my goFrag function I replace the fragment if the required fragment is still in memory (meaning the user was there earlier and it hasn't been destroyed yet), or I create a new fragment required.

Mac event tap just delays discarded events

I'm trying to write some code that discards all keyboard and mouse events when enabled on Mac OSX 10.6. My code runs as the root user. The approach I'm taking is to create an event tap that discards all events passed to it (while enabled). The event tap callback function looks like this:
CGEventRef MyTapCallback(CGEventTapProxy proxy,
CGEventType type,
CGEventRef event,
void *refcon)
{
return CKeyLocker::isEnabled() ? NULL : event;
}
And the code I'm using to enable and disable the event tap looks like this:
void CKeyLocker::enable(bool bEnable)
{
if (bEnable == m_bEnabled)
return;
if (bEnable)
{
// which events are we interested in?
CGEventMask evMask = kCGEventMaskForAllEvents;
CFMachPortRef mp = CGEventTapCreate(kCGHIDEventTap,
kCGHeadInsertEventTap,
kCGEventTapOptionDefault,
evMask,
MyTapCallback,
NULL);
if (mp)
{
qDebug() << "Tap created and active. mp =" << mp;
m_enabledTap = mp;
m_bEnabled = true;
}
}
else
{
CGEventTapEnable(m_enabledTap, false);
CFRelease(m_enabledTap);
m_enabledTap =0;
m_bEnabled = false;
qDebug() << "Tap destroyed and inactive";
}
}
This approach works very well while the event tap is active - I can hammer on the keyboard and mouse as much as I want and no events make it through the system. However, when the tap is disabled all the keys I pushed while the tap was active appear in the current window. It's like the event tap is just delaying the events, rather than destroying them, which is odd, since the Mac documentation clearly states:
If the event tap is an active filter, your callback function should return one of the following:
The (possibly modified) event that is passed in. This event is passed back to the event system.
A newly-constructed event. After the new event has been passed back to the event system, the new event will be released along with the original event.
NULL if the event passed in is to be deleted.
I'm returning NULL, but the event doesn't seem to be deleted. Any ideas?
The linked comment does not have an answer from what I see, so I'll dump some info from what I've seen when poking around with this stuff.
First, I have much better luck with CGEventTapCreateForPSN. It's as if the system gives you some leeway for restricting your tap. However, from this example it looks like this is not sufficient.
Next up - and this /may/ be all you need... In your call back, you probably want (and may need) to check for the following events:
switch (type)
{
case kCGEventTapDisabledByTimeout:
case kCGEventTapDisabledByUserInput:
{
CFMachPortRef *pTap = (CFMachPortRef*)refcon;
CGEventTapEnable( *pTap, true );
return NULL;
}
default:
break;
}
Regardless of what the various documentation does or doesn't say, it's been my observation that the OS feels like it's 'probing' for bad callbacks; basically disabling event tap callbacks that are universally eating events. If you re-register in these cases the OS seems to be ok with it, as if saying: OK, you seem to know what you're doing, but I'll probably poke you again in a bit to make sure.
It's really strange, we use event taps for the same purpose (input blocking in a given scenario) and works perfectly 10.4 - 10.8.2. excpet one thing, it should not block or receive events from a password dialog (which is not a big surprise)
What I can see now is different compared to you sample is:
we use kCGTailAppendEventTap instead of kCGHeadInsertEventTap (this should not matter)
we do some event logging in the installed callback
we have some user event data in some self injected events, that filtered out, but apart from this we simply return NULL to drop an unwanted event (like you do), I can confirm, not all events are ignorable!
we turn on/off the event tap this way:
bool SetInputFilter(bool bOn)
{
bool result = false;
CFRunLoopRef runLoopRef = CFRunLoopGetMain();
if (bOn) {
// Create an event tap.
CGEventMask eventMask = kCGEventMaskForAllEvents;
if ((m_eventTapInput = CGEventTapCreate(kCGHIDEventTap,
kCGTailAppendEventTap,
kCGEventTapOptionDefault,
eventMask, CGInputEventCallback, this)) == NULL) {
Log(L"Failed to create event tap");
return result;
}
// Create a run loop source.
m_runLoopEventTapSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, m_eventTapInput, 0);
CFRelease(m_eventTapInput); // CFMachPortCreateRunLoopSource retains m_eventTapInput
if (m_runLoopEventTapSource == NULL) {
Log(L"Failed to create run loop source for event tap");
return result;
}
// Add to the current run loop.
CFRunLoopAddSource(runLoopRef, m_runLoopEventTapSource, kCFRunLoopCommonModes);//kCFRunLoopDefaultMode);
CFRelease(m_runLoopEventTapSource); // CFRunLoopAddSource retains m_runLoopEventTapSource
result = true;
}
else {
// Disable the event tap.
if (m_eventTapInput)
CGEventTapEnable(m_eventTapInput, false);
// Remove our run loop source from the current run loop.
if (runLoopRef && m_runLoopEventTapSource) {
CFRunLoopRemoveSource(runLoopRef, m_runLoopEventTapSource, kCFRunLoopCommonModes);//kCFRunLoopDefaultMode);
m_runLoopEventTapSource = NULL; // removing m_runLoopEventTapSource releases last reference of m_runLoopEventTapSource too
m_eventTapInput = NULL; // removing m_runLoopEventTapSource releases last reference of m_eventTapInput too
}
}
return result;
}
I can verify that returning NULL does effectively delete some events, but i have also seen times when it does not, exactly how it decides what deletions to permit is unclear but it looks like mass deletions seem to be prevented e.g.: when you delete more than 100 events or so in a row.