How do I use SDL_PeepEvents properly in multiple event loops? - sdl-2

I would like to do a simple thing — in the unit with the logic of handling the window, I want to update the window (the first event loop), and then in another unit with the logic of e.g. keyboard handling, update its state (second event loop). Window events must be put back in the SDL queue to be visible when the keyboard is updated (to be able to check if the window is in focus or not).
In short, there is one event queue (internal to SDL), but there are many loops that read events. If an event needs to be visible in several loops, it must return to the queue after handling it. To make this possible, each loop should iterate over the contents of the queue in such a way as not to process the events that was pushed back to the queue after handling.
For this, I am using SDL_PeepEvents which returns the number of queued events and iterates through the for loop this many times. The test program code looks like this:
uses
SDL2;
procedure UpdateWindow();
var
Event: TSDL_Event;
Index: Integer;
begin
for Index := 0 to SDL_PeepEvents(nil, -1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT) - 1 do
begin
SDL_PollEvent(#Event);
if Event.Type_ = SDL_WINDOWEVENT then
case Event.Window.Event of
SDL_WINDOWEVENT_FOCUS_GAINED: WriteLn('focus gain - window');
SDL_WINDOWEVENT_FOCUS_LOST: WriteLn('focus lost - window');
end;
// put all events back in the queue to be seen in other event loops
SDL_PushEvent(#Event);
end;
end;
procedure UpdateKeyboard();
var
Event: TSDL_Event;
Index: Integer;
begin
for Index := 0 to SDL_PeepEvents(nil, -1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT) - 1 do
begin
SDL_PollEvent(#Event);
// reading window events a second time
if Event.Type_ = SDL_WINDOWEVENT then
case Event.Window.Event of
SDL_WINDOWEVENT_FOCUS_GAINED: WriteLn('focus gain - keyboard');
SDL_WINDOWEVENT_FOCUS_LOST: WriteLn('focus lost - keyboard');
end;
SDL_PushEvent(#Event);
end;
end;
var
Window: PSDL_Window;
Done: Boolean = False;
begin
SDL_Init(SDL_INIT_EVERYTHING);
Window := SDL_CreateWindow('Events test', SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_SHOWN);
while not Done do
begin
SDL_PumpEvents();
UpdateWindow();
UpdateKeyboard();
// clear events queue
SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT);
SDL_Delay(20);
end;
SDL_DestroyWindow(Window);
SDL_Quit();
end.
The above code works, but only sometimes. When the program is started and the window appears, sometimes it says that the window and keyboard are in focus, and sometimes it's just that the window has focus only. This means that the SDL_WINDOWEVENT_FOCUS_GAINED event was handled in the first loop but not seen in the second loop (even though it was added back to the queue).
With the console window and the SDL window visible, I click alternately to both, activating and deactivating the SDL window. Each time the SDL window is activated, two lines should be added to the console (one for the event loop) and the same when deactivating the window. Unfortunately, the content of the console when clicking looks like this:
focus gain - window
focus gain - keyboard
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus gain - keyboard
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
{...}
As you can see, sometimes when activating a window, the second loop doesn't see the SDL_WINDOWEVENT_FOCUS_GAINED event, so it doesn't add the focus gained - keyboard line to the console. This event is lost somewhere even though every event in the first loop is queued back using SDL_PushEvent.
Question: how to properly handle SDL events in multiple loops and how to put handled events back in the queue so that they are visible to multiple loops?
Lazarus 2.2.0, FPC 3.2.2, SDL2-for-Pascal headers and SDL 2.0.22.0 — all 64-bit.

If possible, don't do that at all. Best solution is to fetch events only once. If you want to process events multiple times, nothing prevents you from fetching all events you're interested in into single array and iterating this array as many times as you want. You can fetch all events into big array with single SDL_PeepEvents(SDL_GETEVENT) call.
The problem you have is based on two factors: SDL_PollEvent's return value is 0 when queue is drained; but nowhere in documentation it is stated that SDL_PushEvent puts event into the same processing frame - i.e. PollEvent may indicate queue is drained and return your newly pushed events on next frame. If that happens, in your second loop you process one event incorrectly (as PollEvent said 0 and no data is filled into Event structure), and skip one last event.
But, leaving higher level logic as it is, you have two options:
Use SDL_PeepEvent to fetch event, not SDL_PollEvent. I.e. replace SDL_PollEvent(#Event) with SDL_PeepEvent(#Event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT).
Check return value of SDL_PollEvent and if it returns 0 - you'll have to call it again, as it means no event is popped from queue. This solution is prone to race conditions and you can't guarantee it wouldn't call SDL_PumpEvents under the hood, and your last SDL_FlushEvents may kill some unprocessed events.

Related

Disable modal event processing in WINAPI

As far as I understand, WM_SIZING is a modal message, meaning that the event processing will enter an inner loop and only return to the user when the resizing is over. I also know that this event begins with WM_ENTERSIZEMOVE and ends with WM_EXITSIZEMOVE.
My question is: is there any way to disable this behavior so I can return to my own message loop after every single sizing event?
I know usually updates can be performed by setting a timer - and then killing it at the end of the modal message/event, but I'd like to return the control to my message loop.

MFC app doesn't seem to receive input messages?

We have a MFC Visual-C++ application that is not reacting to any user input.
(Note: Currently only known on one machine. The behavior does recur occasionally, but only after the App has been running for several days!)
The application is redrawn when we switch to it via Alt-Tab (or the Task Bar), but we cannot, for example, activate it's main window by clicking on the title bar.
We have already pulled 4 dumps with WinDbg and checked the active instructions. We always were in some redraw code or somesuch inside the main thread (the GUI thread). We definitely were/are not in a modal message loop and the main thread's stack always looked "OK". (Most/all worker threads were idling, waiting for some event, no suspicious code there either.)
When investigating the problem with Spy++, we see the behavior specified also in this separate question, namely that we seem to get paint and activation messages, but no user input is routed to the application. When I have the application window on the screen, and select it to show messages of the main window,
it will only show "generic" "referesh" messages and nothing else
If I drill deeper, and select all messages for the whole process,
this is what we see:
The app is apparently only processing messages on one hidden sub-window (00CB09F0), and what we see there is a constant stream of 200 WM_PAINT messages per second.
Normally this Sub Window isn't processing any messages at all (except refresh WM_PAINT etc. when Windows sends them). It is normally used as a drawing area and the drawing happens through a WM_TIMER message on it's parent (010A09B8) window. (This WM_TIMER message isn't shown on the hanging app either though.)
The performance profile as shown in process explorer looks like this (100% kernel time, more or less):
I'd say that you have a redraw loop in that window that receives the WM_PAINT flood.
That usually happens if you call Invalidate or similar from the processing of the WM_PAINT message, directly or indirectly.
Other posibility is that, since you say that you are using a timer to redraw the window, the actual drawing is taking more time that the time it self, so the messages pile up in the queue.
Yet another posibility is that you are invalidating the window from a different thread than the one making the painting.
Anyway, you should ensure that you are calling Invalidate*() properly (you showed no code), and never from the OnPaint event. And avoid calling UpdateWindow() as this function can mess things if called without a little care.
I've seen this problem when an exception is thrown from a dialog. MFC's DoModal function disables the main program window then reenables it when the dialog returns; exceptions bypass the reenabling part and the main window remains disabled forever.

Win32 call order

I have two windows that I send scripted input to. The procedure goes as this
BringWindowToTop( window1 );
i = Build input structures( window1 );
SendInput(i);
BringWindowToTop( window2 );
i = Build input structures( window2 );
SendInput(i);
I was having trouble with inputs not being sent and the correct time. I put delays after each call and saw that input from the first SendInput() was processed after window2 is brought to top. Same thing at the end of the loop as well.
Are SendInput calls buffered? If so, how can I make sure of a serial execution of this code?
Thanks
Input, like most messages in Win32, goes through two phases. First it is posted into a queue. At this point the destination window is already determined. Then when the receiving program is idle it is processed. Even though the input might not be processed until after the second window is brought forward in the Z-order, the input messages should have been queued to the first window.
Does the behavior rely only on which window the input goes to, or does the program also have to be frontmost when the message is fully processed?
Anyway, since you are trying to send input to specific windows and not whatever the user made active, why not PostMessage the events such as WM_BUTTONDOWN and WM_KEYPRESS directly to their destinations?
From the MSDN page:
The SendInput function inserts the events in the INPUT structures serially into the keyboard or mouse input stream. These events are not interspersed with other keyboard or mouse input events inserted either by the user (with the keyboard or mouse) or by calls to keybd_event, mouse_event, or other calls to SendInput.
So SendInput is inserting into the input stream where, presumably, BringWindowToTop is executing serially, or inserting into an events queue that is processed first. Perhaps you could find an event to call the second SendInput from, which will be executed after the first window is brought to the front.

Bring form on top of others when clicked on taskbar button in Delphi

Base question: TStatusBar flickers when calling Update procedure. Ways to painlessly fix this
The executed code is in the questions first posts first part ( you can see light grey separating line ) ...
But - problem is that while this code is executed, form does not automatically activate and focus on the top of all other applications.
I have read these articles:
http://www.installationexcellence.com/articles/VistaWithDelphi/Original/Index.html
http://delphi.about.com/od/formsdialogs/l/aa073101b.htm
but according to them it should be working no matter what. I tried all the TApplicationEvents and TForm events with Show; Visible: Repaint; Refresh; BringToFront; ... nothing works.
So - I think I have two options - multithreading or trapping WM_SYSCOMMAND message and in the SC_ACTIVE event simply repaint form. Could this scenario become successful?
None of your linked articles deal with the problem you are having. What you see is the behaviour of a program that does not process Windows messages, so consequently it will not redraw parts that become invalid, and it will not react to keyboard or mouse input (for example moving or resizing with the mouse, or app activation using the taskbar button).
In your code you call StatusBar1.Update, so at least the status bar text is redrawn, but apart from coming to the foreground your application is probably also ignoring move or resize requests.
You need to process Windows messages in a timely manner, so any execution path that takes more than say 200 or 300 milliseconds needs to make sure that messages are handled, otherwise the application will appear unresponsive or hung.
You have basically three options:
Keep the long running code, and insert calls to Application.ProcessMessages - this will allow Windows messages to be processed. Make sure that you keep the code from being entered again, for instance by disabling all the controls that are used to start the operation.
Rework your code in a way that it appears as a sequence of steps, each taking no more than a few 10 milliseconds. Put calls to the code in a timer event handler, or call it from the Application.OnIdle handler.
Call your code in a worker thread, and post messages to the main GUI thread to update your UI.
All these options have their own pros and cons, and for multithreading especially there is a lot of questions and answers already here on SO. It is the most difficult but best option overall when you are working on anything more than a toy program.

Detecting a single mouse click in MFC

In MFC a double-mouse click event triggers the following sequence of messages
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDBCLK
WM_LBUTTONUP
So responding to the WM_LBUTTONDBCLK message allows you to detect a double-click. But if I just want to detect a single-click how to I distinguish it?
But just looking at the WM_LBUTTONUP message isn't enough as it could be a single-click or it could be the first click of a double-click.
How can I successfully identify just a single-click?
(Please allow me to call these events Mouse Up and Mouse Down. My MFC is a little rusty. And there's this stuff called .NET who's been messing up my terminology lately ;-)
Short story: You don't simply want to know about Mouse Click. You need more.
Long story:
Although this is counter-intuitive, it appears that simply wanting a mouse-click is fairly uncommon. Most often, you'll want to perform some processing on Mouse Down and do some further processing on Mouse Up. The trick is that simply tracking Mouse Up messages is not enough: Mouse Down may not have happened in your window. Do you consider it a valid click then? Especially considering that the Mouse Down processing (such as selecting an item) did not occur.
Going further up the reasoning, you should not rely on receiving a Mouse Up after you processed Mouse Down: User may have moved the mouse and released the button somewhere else (think drag'n'drop), in which case, you don't receive the MouseUp event... unless you capture the mouse on MouseDown to make sure you get mouse event up to Mouse Up even if the mouse left your window.
All in all, you end up tracking Mouse Down, capture the mouse and when you receive Mouse Up, just check if you own the capture. If not, the mouse was either double-clicked (no 2nd mouse down) or Mouse Down happened somewhere else hence you most likely don't care about this Mouse Up.
In conclusion: There's no MouseClick message simply because you wouldn't go very far with it: You need to handle more messages and implement more mechanics anyway.
Oh! And if your dealing with an existing control which already handles all this items and selection stuff, such as a listview, chances are it provides with a similar custom notification such as Item Activate or Item Selection Changed.
I just tried this in Delphi, the behavior is the same: even when a double click is happening, a single click event is issued right after the first one of the two.
I solved it using a timer, which works like this:
deactivate timer on WM_LBUTTONDBLCLK (and set bDbl to true)
activate timer on WM_LBUTTONUP if bDbl==false
deactivate on WM_LBUTTONUP if bDbl==true (and reset bDbl)
I set the interval of the timer to the time returned by GetDoubleClickTime.
MSDN says:
The GetDoubleClickTime function
retrieves the current double-click
time for the mouse. A double-click is
a series of two clicks of the mouse
button, the second occurring within a
specified time after the first. The
double-click time is the maximum
number of milliseconds that may occur
between the first and second click of
a double-click.
If the timer happens to fire then you have the real click. In my case the double click interval is 500ms, so any "real click" will be delayed this long.
You could check WM_LBUTTONDOWN has not been called more than once before WM_LBUTTONUP. In practice Windows does this for you, in that if you get a WM_LBUTTONDBCLK you tend not to get a WM_LBUTTONUP.
You can use PreTranslateMessage() to count the messages as they appear. If you've received only the mouse messages corresponding to a single-click, and the system-configured time for double-clicking has expired, you can safely assume it's a single-click.
As far as I know there is no way to know that this is the case as it is happening, which makes sense -- until the time is expired, there's no way to know that a second click is or isn't coming.
that's a little tricky.
I would detect the WM_LBUTTONDOWN & WM_LBUTTONUP combo, store that event somewhere and set a timeout for a second or so. If there isn't a WM_LBUTTONDBCLK during that timeout then you have a single click.
This might imply you need to have another thread running but I think you could accomplish it with one thread.
I think the solution is to start a timer after the first click & then check the elapsed time after at the next immediate click, this will tell you if it is a single click or double click.
You typically look at #MLButtonUp and you would not have single click and double click behavior on the same mouse button.

Resources