I'm trying to mimic SFML's PollEvent(Event &event) function in Windows. It seems far more complicated that I imagined. Note that I already encapsulated the window procedure function in my class.
There could be many "window events" in my program - WindowMoved, WindowResized etc.
My first attempt was to have a private data member in the class, defined as WindowEvent *_lastWindowEvent. This variable will be set if PeekMessage() returns a non-zero value, just before DispatchMessage() is called. Then, winProc() will edit _lastWindowEvent, depending on the message it will receive.
The drawback here is that I noticed that winProc() may be called with a MSG parameter regardless of DispatchMessage(), like with the WM_SETCURSOR message.
Then I thought about having instead a std::queue<WindowEvent> in my class, when winProc() continuously pushes WindowEvents to it. The problem here is that sometimes the window procedure function keeps getting messages and won't return. This happens when I drag-move the window (then the WM_MOVING message is continuously called, along with other messages). The code after DispatchMessage() will not run until I release my mouse. This also happens when resizing the window.
Did I grasp anything wrong? How do you think such PollEvent function can be implemented?
Given that PollEvent is primarily for a game loop style design, you can probably poll for what you need while simultaneously servicing the Windows event loop:
class Window
{
HWND _hwnd; // Win32 handle to the window
RECT _lastWindowSize; // last known window size
POINT _lastMousePos; // last known mouse position on window
BYTE _lastKeyboardState[256]; // last known key state
std::list<Event> _events; // unprocessed events
public:
bool PollEvent(Event* pEvent);
};
bool Window::PollEvent(Event* pEvent)
{
// return any previously queued events
if (_events.size() > 0)
{
*pEvent = _events.pop_front();
return true;
}
// process 1 windows event message
if (PeekMessage(&msg, _hWnd, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
if (msg.msg == WM_QUIT)
{
*pEvent = EXIT_EVENT; // special handling for WM_QUIT
return true;
}
}
// -----------------------------------------
// poll keyboard state
BYTE kbState[256];
GetKeyboardState(kbState);
bool isKeyboardEvent = false;
if (memcmp(_lastKeyboardState, kbState, 256) != 0)
{
// not shown
// compute diff of kbState and _lastKeyboardState
// generate a keyboard event and add to the queue
Event kbevt;
kbevt.type = KeyEvent;
kbevt.code = <computed code based on diff above>
_events.push_back(kbevt);
}
memcpy(_lastKeyboardState, kbState, 256);
// -----------------------------------------
// -----------------------------------------
// poll window size changes
RECT rectWindowSize;
int width, height, oldwidth, oldheight;
GetClientRect(&rectWindowSize);
width = rectWindowSize.right - rectWindowSize.left;
height = rectWindowSize.bottom - rectWindowSize.top;
oldwidth = _lastWindowSize.right - _lastWindowSize.left;
oldheight = _lastWindowSize.bottom - _lastWindowSize.top;
if ((width != oldwidth) || (height != oldheight))
{
Event sizeEvent;
sizeEvent.type = SizeEvent;
sizeEvent.width = width;
sizeEvent.height = height;
_events.push_back(kbevt);
}
_lastWindowSize = rectWindowSize;
// -----------------------------------------
// not shown - computing mouse position, joystick position, text stuff
// if at least one event was queued - return it now
if (_events.size() > 0)
{
*pEvent = _events.pop_front();
return true;
}
return false;
}
Related
I have created a SDL2 application, and would like it to minimize to the system tray, rather than appearing in the task bar.
SDL_MinimizeWindow doesn't do what I want, it leaves the task bar icon. Is there a way to achieve this with SDL?
There is no purely SDL2 way to do this, as Cody said, Shell_NotifyIcon is the function needed to create a notification area (system tray) icon.
The code I used to get the icon is
SDL_Window *window = SDL_CreateWindow("Test", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 200, 200, SDL_WINDOW_HIDDEN);
SDL_SysWMinfo info;
SDL_VERSION(&info.version);
NOTIFYICONDATA icon;
if (SDL_GetWindowWMInfo(window, &info))
{
icon.uCallbackMessage = WM_USER + 1;
icon.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
icon.hIcon = LoadIcon(NULL, IDI_INFORMATION);
icon.cbSize = sizeof(icon);
icon.hWnd = info.info.win.window;
strcpy_s(icon.szTip, "Test tip");
bool success = Shell_NotifyIcon(NIM_ADD, &icon);
}
This creates a hidden window, and an icon (using the default information icon).
To interact with this from SDL, you need to enable platform specific window management events, this is done as follows SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
After this, in the main event loop you must test for SDL_SYSWMEVENT, which contains information about how the user has interacted with the notification area icon. This also looks for the minimize event and hides the window which removes it from the task bar. This is achieved in the following snippet
SDL_Event e;
while (SDL_PollEvent(&e) != 0)
{
switch (e.type)
{
case SDL_SYSWMEVENT:
if (e.syswm.msg->msg.win.msg == WM_USER + 1)
{
if (LOWORD(e.syswm.msg->msg.win.lParam) == WM_LBUTTONDBLCLK)
{
SDL_ShowWindow(window);
SDL_RestoreWindow(window);
}
}
break;
case SDL_QUIT:
running = false;
break;
case SDL_WINDOWEVENT:
if (e.window.event == SDL_WINDOWEVENT_MINIMIZED)
SDL_HideWindow(window);
break;
}
}
I am trying to make a dialog based MFC application , where two dialogs needs to be shown sequentially.
What that means is , once the first dialog(modal) is shown and dismissed (by pressing OK), the second dialog needs to be brought up.My requirement is the second dialog should be modeless.
But what I observe is the second dialog is shown but none of the message handling function are being called in response of user messages.I think the message map itself is not working, while the overridden functions(like OnInitdialog) are being called. I tried replacing this modeless dialog with a modal one , and alas, the doModal() itself fails.
Here is the little code:
CFirstDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
CSecondDlg *dlgModeLess = new CSecondDlg();
dlgModeLess->Create(CSecondDlg::IDD,NULL);
m_pMainWnd = dlgModeLess;
dlgModeLess->ShowWindow(SW_SHOW);
dlgModeLess->UpdateWindow();
}
Here is the message map of the second dialog:
BEGIN_MESSAGE_MAP(CSecondDlg, CDialog)
ON_MESSAGE(TRAY_MESSAGE,OnTrayMessage)
ON_BN_CLICKED(IDOK, &CSecongDlg::OnBnClickedOk)
ON_BN_CLICKED(IDC_RADIO1, &CSecondDlg::OnBnClickedRadio1)
END_MESSAGE_MAP()
I think I am doing something conceptually wrong. Kindly share your thoughts on what need to be done to tackle such a scenario.
As I mentioned in a previous post, it is not necessary that the second dialog be non modal.
Just do something like this:
BOOL CMyTestApp::InitInstance()
{
CMyTestDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
COtherDlg Dlg ;
m_pMainWnd = &dlg;
if (Dlg.DoModal() == IDCANCEL)
{
...
}
}
else if (nResponse == IDCANCEL)
{
...
}
return FALSE;
}
When you create a modeless dialog box, control will return to your calling function right away, so you need to declare variable dlgModeLess at a global scope and make sure your program/scope is still active until the dialog box finishes
I have solved this problem and this turned out to be interesting.
It seems that Cdialog::Create() itself is not sufficient for creating a fully operative modeless dialog box. We have to supply a win32 style message loop to it.
So this effectively makes two message loops in the program, one provide my MFC framework and the one that I wrote after returning from IDOK. Here is the modified code.
CSecondDlg *dlgModeLess = new CSecondDlg();
dlgModeLess->Create(CSecondDlg::IDD,NULL);
CTrayIconDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
MSG leftmsg;
PeekMessage(&leftmsg,m_pMainWnd->m_hWnd,0,0,PM_REMOVE);
MSG msg;
BOOL bRet;
while ((bRet = GetMessage(&msg, dlgModeLess->m_hWnd, 0, 0)) != 0)
{
if (bRet == -1)
{
// Handle the error and possibly exit
}
else if (!IsWindow(hWnd) || !IsDialogMessage(hWnd, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
This code works as expected, The interesting thing to note here is the PeekMessage function which removes a WM_QUIT message that is inserted in the thread's message queue once the first dialog is dismissed as we do not want to quite at that point of time. This i believe is done by the MFC framework.
I have some problems,i use xcode write a npapi plugin on mac10.8,I want to draw a picture on the plugin but when i get the pNPWindow->window pointer through NPP_SetWindow(NPP instance, NPWindow* pNPWindow); I find that nNPWindow->window is NULL ,i spend must to find the problem,but i can not,somebody can help me。 sorry,my english so poor。
code is like that,
NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc, char* argn[], char* argv[], NPSavedData* saved)
{
if(instance == NULL)
return NPERR_INVALID_INSTANCE_ERROR;
CPlugin *plugin = new CPlugin(instance);
if(plugin == NULL)
return NPERR_OUT_OF_MEMORY_ERROR;
instance->pdata = (void *)plugin;
NPBool supportsCG = false;
NPError err;
err = browser->getvalue(instance, NPNVsupportsCoreGraphicsBool,&supportsCG);
if (err == NPERR_NO_ERROR && supportsCG)
browser->setvalue(instance,NPPVpluginDrawingModel,(void*)NPDrawingModelCoreGraphics);
return NPERR_NO_ERROR;
}
NPError NPP_SetWindow(NPP instance, NPWindow* pNPWindow)
{
if(instance == NULL)
return NPERR_INVALID_INSTANCE_ERROR;
if(pNPWindow == NULL)
return NPERR_GENERIC_ERROR;
if(pNPWindow->window)
writelog("window != NULL");
if(pNPWindow->window == NULL) //this is he problem pNPWindow->window always NULL
writelog("window == NULL");
return NPERR_NO_ERROR;
}
Anything you're going to use on Mac 10.8 won't support the carbon event model, so window will always be NULL. Assuming that you're trying to use the CoreGraphics drawing model you will get the CGContextRef when the event is fired to draw.
See https://wiki.mozilla.org/NPAPI:CocoaEventModel for more information on the Cocoa event model. The other option you have is the CoreAnimation model (with the InvalidatingCoreAnimation model on firefox and chrome)
You might want to take a look at FireBreath, which works on 10.8 and abstracts all of the complication of this stuff for you.
NPP_SetWindow (NPP npp, NPWindow* pNPWindow)
For many, this will be where the real fun starts — this function is called to tell the plugin which window they are in. From the Gecko SDK (npapi.h):
typedef struct _NPWindow
{
void* window; /* Platform specific window handle */
/* OS/2: x - Position of bottom left corner */
/* OS/2: y - relative to visible netscape window */
int32 x; /* Position of top left corner relative */
int32 y; /* to a netscape page. */
uint32 width; /* Maximum window size */
uint32 height;
NPRect clipRect; /* Clipping rectangle in port coordinates */
/* Used by MAC only. */
void * ws_info; /* Platform-dependent additonal data, linux specific */
NPWindowType type; /* Is this a window or a drawable? */
} NPWindow;
A pointer to this structure is passed in with each call. On windows, the “void* window” will dereference to an HWND. On other platforms, it will likewise be dereferenced as an appropriate type.
Notice that again NPP npp is the first parameter. This will be the case on all NPP functions except NPP_New, where the mimetype is also passed in. Since we created a PluginInstance object and assigned it to npp->pdata in NPP_New, we need to create a small stub function to forward our NPP_New to a method on that object, like so:
// Called by browser whenever the window is changed, including to set up or destroy
NPErrorNPP_SetWindow (NPP npp, NPWindow* pNPWindow)
{
if (npp == NULL)
return NPERR_INVALID_INSTANCE_ERROR;
else if (npp->pdata == NULL)
return NPERR_GENERIC_ERROR;
PluginInstance *inst = (PluginInstance *)npp->pdata;
return inst->NpapiSetWindow(pNPWindow);
}
On windows, when SetWindow is called we need to save the HWND and subclass the window so that we can get our own window event proc.
NPError PluginInstance::NpapiSetWindow (NPWindow* pNPWindow)
{
NPError rv = NPERR_NO_ERROR;
if(pNPWindow == NULL)
return NPERR_GENERIC_ERROR;
// window just created; in initWindow, set initialized to true
if(!this->initialized) {
if(!this->initWindow(pNPWindow)) {
return NPERR_MODULE_LOAD_FAILED_ERROR;
}
}
// Window was already created; just pass on the updates
this->updateWindow(pNPWindow);
return rv;
}
With these functions, we get notified in one function when the window is first set, and another is called each time an update is made.
In my application I have a simple module were I will read files for some process that will take
few seconds..so I thought of displaying a progress bar(using worker thread) while the files are in progress.I have created a thread (code shown below) and also I designed a dialog window with progress control.I used the function MyThreadFunction below to display the progressbar but it just shows only one time and disappears,I am not sure how to make it work.I tried my best inspite of the fact that I am new to threading.
reading files
void ReadMyFiles()
{
for(int i = 0; i < fileCount ; fileCount++)
{
CWinThread* myThread = AfxBeginThread((AFX_THREADPROC)MyThreadFunction,NULL);
tempState = *(checkState + index);
if(tempCheckState == NOCHECKBOX)
{
//my operations
}
else//CHECKED or UNCHECKED
{
//myoperation
}
myThread->PostThreadMessage(WM_QUIT,NULL,NULL);
}
}
thread functions
UINT MyThreadFunction(LPARAM lparam)
{
HWND dialogWnd = CreateWindowEx(0,WC_DIALOG,L"Proccessing...",WS_OVERLAPPEDWINDOW|WS_VISIBLE,
600,300,280,120,NULL,NULL,NULL,NULL);
HWND pBarWnd = CreateWindowEx(NULL,PROGRESS_CLASS,NULL,WS_CHILD|WS_VISIBLE|PBS_MARQUEE,40,20,200,20,
dialogWnd,(HMENU)IDD_PROGRESS,NULL,NULL);
MSG msg;
PostMessage( pBarWnd, PBM_SETRANGE, 0, MAKELPARAM( 0, 100 ) );
PostMessage(pBarWnd,PBM_SETPOS,0,0);
while(PeekMessage(&msg,NULL,NULL,NULL,PM_NOREMOVE))
{
if(msg.message == WM_QUIT)
{
DestroyWindow(dialogWnd);
return 1;
}
AfxGetThread()->PumpMessage();
Sleep(40);
}
return 1;
}
Do you really want to create a new thread and a progress bar for each individual file? Create the thread outside of the for() loop.
But this is not the right way to do it, your main UI is still dead as a doornail. Windows will turn your main window in a ghost with "Not Responding" in the title bar after a couple of seconds. You want to use a worker thread to do the file manipulation and the main thread to display the progress bar using a dialog that can only be closed when the worker uses PostMessage() to indicate completion.
For a scripting utility I need to be able to record a series of keyboard and mouse events that occur when an application has focus. The second part is being able to later send those events to the active window.
I do not need to worry about menus or tracking the identifier of which window receives input.
I know how to do this under Windows but have no idea about Mac OS X.
The first thing i will tell you is that you CAN'T do this without the user enabling support for assitive devices in the accessability control panel. It's some kind of security built into OSX.
Here is a code snippit I am using in one of my applications to do this:
//this method calls a carbon method to attach a global event handler
- (void)attachEventHandlers
{
//create our event type spec for the keyup
EventTypeSpec eventType;
eventType.eventClass = kEventClassKeyboard;
eventType.eventKind = kEventRawKeyUp;
//create a callback for our event to fire in
EventHandlerUPP handlerFunction = NewEventHandlerUPP(globalKeyPress);
//install the event handler
OSStatus err = InstallEventHandler(GetEventMonitorTarget(), handlerFunction, 1, &eventType, self, NULL);
//error checking
if( err )
{
//TODO: need an alert sheet here
NSLog(#"Error registering keyboard handler...%d", err);
}
//create our event type spec for the mouse events
EventTypeSpec eventTypeM;
eventTypeM.eventClass = kEventClassMouse;
eventTypeM.eventKind = kEventMouseUp;
//create a callback for our event to fire in
EventHandlerUPP handlerFunctionM = NewEventHandlerUPP(globalMousePress);
//install the event handler
OSStatus errM = InstallEventHandler(GetEventMonitorTarget(), handlerFunctionM, 1, &eventTypeM, self, NULL);
//error checking
if( errM )
{
//TODO: need an alert sheet here
NSLog(#"Error registering mouse handler...%d", err);
}
}
Here is an example of the callback method i am using:
OSStatus globalKeyPress(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData)
{
NSEvent *anEvent = [NSEvent eventWithEventRef:theEvent];
NSEventType type = [anEvent type];
WarStrokerApplication *application = (WarStrokerApplication*)userData;
//is it a key up event?
if( type == NSKeyUp)
{
//which key is it?
switch( [anEvent keyCode] )
{
case NUMERIC_KEYPAD_PLUS:
//this is the character we are using for our toggle
//call the handler function
[application toggleKeyPressed];
break;
//Comment this line back in to figure out the keykode for a particular character
default:
NSLog(#"Keypressed: %d, **%#**", [anEvent keyCode], [anEvent characters]);
break;
}
}
return CallNextEventHandler(nextHandler, theEvent);
}
For the latter part, posting events, use the CGEvent methods provided in ApplicationServices/ApplicationServices.h
Here's an example function to move the mouse to a specified absolute location:
#include <ApplicationServices/ApplicationServices.h>
int to(int x, int y)
{
CGPoint newloc;
CGEventRef eventRef;
newloc.x = x;
newloc.y = y;
eventRef = CGEventCreateMouseEvent(NULL, kCGEventMouseMoved, newloc,
kCGMouseButtonCenter);
//Apparently, a bug in xcode requires this next line
CGEventSetType(eventRef, kCGEventMouseMoved);
CGEventPost(kCGSessionEventTap, eventRef);
CFRelease(eventRef);
return 0;
}
For tapping mouse events, see Link
I haven't checked this under 10.5 Leopard but on 10.4 it works.