Nux

Merge lp:~smspillaz/nux/nux.fix_1097281 into lp:nux

Proposed by Sam Spilsbury
Status: Merged
Approved by: Francis Ginther
Approved revision: 758
Merged at revision: 761
Proposed branch: lp:~smspillaz/nux/nux.fix_1097281
Merge into: lp:nux
Diff against target: 958 lines (+667/-18)
8 files modified
Nux/MainLoopGLib.cpp (+96/-0)
Nux/MainLoopGLib.h (+21/-0)
Nux/ProgramFramework/ProgramTemplate.cpp (+102/-1)
Nux/ProgramFramework/ProgramTemplate.h (+26/-0)
Nux/WindowThread.cpp (+108/-5)
Nux/WindowThread.h (+26/-0)
tests/gtest-nux-windowthread.cpp (+257/-8)
tests/xtest-text-entry.cpp (+31/-4)
To merge this branch: bzr merge lp:~smspillaz/nux/nux.fix_1097281
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Needs Fixing
Brandon Schaefer (community) Approve
Review via email: mp+146078@code.launchpad.net

Commit message

Implement a message-passing system in nux::WindowThread and nux::ProgramFramework.

This makes it possible to define and implement protocols for WindowThreads to
communicate with each other in a thread-safe way without having to resort to
using window system events or the like.

New file descriptors can be added for watching with a watch-callback executed
inside the window thread with nux::WindowThread::WatchFdForEvents. Only
read-data is supported right now.

When there is new data available to be read, the provided FdWatchCallback will
be called by the window thread, and executed inside the window thread. From
there, clients can safely modify data contained by the WindowThread object.

Remove a watched file descriptor with UnwatchFd.

It is safe to add file descriptors for watching during the call to the provided
ThreadUserInitFunc in CreateGUIThread. This is because the main loop
implementation must be created before those file descriptors are added for
watching (particularly in the case of GLib, because nux::WindowThread::InitGlibLoop
will use the default GMainContext the first time it is called, and then create
a new one for each window thereafter.

Added three new tests to demonstrate and cover this functionality.

[==========] Running 3 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 3 tests from TestWindowThread
[ RUN ] TestWindowThread.WatchFd
[ OK ] TestWindowThread.WatchFd (3213 ms)
[ RUN ] TestWindowThread.MultiWatchFd
[ OK ] TestWindowThread.MultiWatchFd (3163 ms)
[ RUN ] TestWindowThread.OneFdEvent
[ OK ] TestWindowThread.OneFdEvent (3155 ms)
[----------] 3 tests from TestWindowThread (9532 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test case ran. (9532 ms total)
[ PASSED ] 3 tests.

Fixed segfaulting test xtest-text-input. That test unsafely mutated object
state contained by the WindowThread running in another thread inside of the
test thread. This resulted in a null pointer dereference because it had a
side effect of calling nux::GetWindowThread () which relies on up-to-date
thread-local-storage. That test now defines a protocol to communicate to
the running program to mutate the relevant state, and then communicate back
when it has completed execution.

(LP: #1097281)

Description of the change

Implement a message-passing system in nux::WindowThread and nux::ProgramFramework.

This makes it possible to define and implement protocols for WindowThreads to
communicate with each other in a thread-safe way without having to resort to
using window system events or the like.

New file descriptors can be added for watching with a watch-callback executed
inside the window thread with nux::WindowThread::WatchFdForEvents. Only
read-data is supported right now.

When there is new data available to be read, the provided FdWatchCallback will
be called by the window thread, and executed inside the window thread. From
there, clients can safely modify data contained by the WindowThread object.

Remove a watched file descriptor with UnwatchFd.

It is safe to add file descriptors for watching during the call to the provided
ThreadUserInitFunc in CreateGUIThread. This is because the main loop
implementation must be created before those file descriptors are added for
watching (particularly in the case of GLib, because nux::WindowThread::InitGlibLoop
will use the default GMainContext the first time it is called, and then create
a new one for each window thereafter.

Added three new tests to demonstrate and cover this functionality.

[==========] Running 3 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 3 tests from TestWindowThread
[ RUN ] TestWindowThread.WatchFd
[ OK ] TestWindowThread.WatchFd (3213 ms)
[ RUN ] TestWindowThread.MultiWatchFd
[ OK ] TestWindowThread.MultiWatchFd (3163 ms)
[ RUN ] TestWindowThread.OneFdEvent
[ OK ] TestWindowThread.OneFdEvent (3155 ms)
[----------] 3 tests from TestWindowThread (9532 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test case ran. (9532 ms total)
[ PASSED ] 3 tests.

Fixed segfaulting test xtest-text-input. That test unsafely mutated object
state contained by the WindowThread running in another thread inside of the
test thread. This resulted in a null pointer dereference because it had a
side effect of calling nux::GetWindowThread () which relies on up-to-date
thread-local-storage. That test now defines a protocol to communicate to
the running program to mutate the relevant state, and then communicate back
when it has completed execution.

(LP: #1097281)

I understand that introducing such a large amount of framework is a hefty change for what appears to be fixing a small bug. However, I feel that it is justified. I'm surprised that as a toolkit Nux didn't even have support for this in the first place, since it is definitely a valid usecase where the underlying framework is controlling program flow. There also didn't appear to be any other (sane and safe) way of implementing the calls to SetKeyFocusArea properly, and I don't know if the test works without them.

Note: xtest-text-entry-deadkeys still fails with this:
Nux: TextEntry created: Ok
X Error of failed request: BadValue (integer parameter out of range for operation)
  Major opcode of failed request: 132 (XTEST)
  Minor opcode of failed request: 2 (X_XTestFakeInput)
  Value in failed request: 0x0
  Serial number of failed request: 276
  Current serial number in output stream: 278

That's a separate issue, which I'll deal with in another review (bug 1112321)

To post a comment you must log in.
Revision history for this message
Brandon Schaefer (brandontschaefer) wrote :

Looks good to me, the idea of it sounds excellent as well. Im just a bit rusty on fd/thread and pipes and would love a seconds reviewer.

review: Approve
Revision history for this message
Marco Trevisan (Treviño) (3v1n0) wrote :

It seems mostly ok to me...

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Francis Ginther (fginther) wrote :

Jenkins job failed due to a configuration issue. Re-approving.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Nux/MainLoopGLib.cpp'
2--- Nux/MainLoopGLib.cpp 2012-12-08 01:03:38 +0000
3+++ Nux/MainLoopGLib.cpp 2013-02-01 13:11:21 +0000
4@@ -9,6 +9,7 @@
5 #include "FloatingWindow.h"
6
7 #include "WindowThread.h"
8+#include "MainLoopGLib.h"
9
10 namespace nux
11 {
12@@ -296,9 +297,104 @@
13 }
14 }
15
16+ namespace
17+ {
18+ typedef struct _ExternalNuxSource
19+ {
20+ GSource source;
21+ GPollFD pfd;
22+ } ExternalNuxSource;
23+
24+ gboolean ExternalSourcePrepareFunc(GSource *source, gint *timeout)
25+ {
26+ /* Always block for new events */
27+ *timeout = -1;
28+ return FALSE;
29+ }
30+
31+ gboolean ExternalSourceCheckFunc(GSource *source)
32+ {
33+ /* Only return true if there are events waiting for us */
34+ ExternalNuxSource *extSource =
35+ reinterpret_cast <ExternalNuxSource *>(source);
36+ return extSource->pfd.revents == G_IO_IN;
37+ }
38+
39+ gboolean ExternalSourceDispatchFunc(GSource *source,
40+ GSourceFunc callback,
41+ gpointer user_data)
42+ {
43+ return (*callback) (user_data);
44+ }
45+
46+ static GSourceFuncs externalGLibFuncs =
47+ {
48+ &ExternalSourcePrepareFunc,
49+ &ExternalSourceCheckFunc,
50+ &ExternalSourceDispatchFunc,
51+ NULL,
52+ /* Technically we shouldn't be touching these, but the compiler
53+ * will complain if we don't */
54+ 0,
55+ 0
56+ };
57+ }
58+
59+ gboolean WindowThread::ExternalSourceCallback(gpointer user_data)
60+ {
61+ WindowThread::ExternalFdData *data =
62+ reinterpret_cast <WindowThread::ExternalFdData *> (user_data);
63+
64+ data->cb ();
65+ return TRUE;
66+ }
67+
68+ void ExternalGLibSources::AddFdToGLibLoop(gint fd,
69+ gpointer data,
70+ GSourceFunc callback,
71+ GMainContext *context)
72+ {
73+ GSource *source = g_source_new(&externalGLibFuncs, sizeof (ExternalNuxSource));
74+ ExternalNuxSource *extSource =
75+ reinterpret_cast <ExternalNuxSource *> (source);
76+ extSource->pfd.fd = fd;
77+ extSource->pfd.events = G_IO_IN;
78+ extSource->pfd.revents = 0;
79+
80+ g_source_add_poll (source, &extSource->pfd);
81+ g_source_set_callback (source, callback, data, NULL);
82+
83+ g_source_attach (source, context);
84+ }
85+
86+ void ExternalGLibSources::RemoveFdFromGLibLoop(gpointer data)
87+ {
88+ g_source_remove_by_user_data(data);
89+ }
90+
91+ void WindowThread::AddFdToGLibLoop(int fd,
92+ gpointer data,
93+ GSourceFunc callback)
94+ {
95+ external_glib_sources_->AddFdToGLibLoop(fd, data, callback, main_loop_glib_context_);
96+ }
97+
98+ void WindowThread::RemoveFdFromGLibLoop(gpointer data)
99+ {
100+ external_glib_sources_->RemoveFdFromGLibLoop(data);
101+ }
102+
103 void WindowThread::CleanupGlibLoop()
104 {
105 g_source_remove_by_funcs_user_data(&event_funcs, this);
106+
107+ for (std::list<ExternalFdData>::iterator it = _external_fds.begin();
108+ it != _external_fds.end();
109+ ++it)
110+ {
111+ gpointer data = reinterpret_cast<gpointer>(&(*it));
112+ external_glib_sources_->RemoveFdFromGLibLoop(data);
113+ }
114 }
115
116 unsigned int WindowThread::AddGLibTimeout(unsigned int duration)
117
118=== added file 'Nux/MainLoopGLib.h'
119--- Nux/MainLoopGLib.h 1970-01-01 00:00:00 +0000
120+++ Nux/MainLoopGLib.h 2013-02-01 13:11:21 +0000
121@@ -0,0 +1,21 @@
122+#ifndef _NUX_MAIN_LOOP_GLIB_H
123+#define _NUX_MAIN_LOOP_GLIB_H
124+
125+#include <glib.h>
126+
127+#if (defined(NUX_OS_LINUX) || defined(NUX_USE_GLIB_LOOP_ON_WINDOWS)) && (!defined(NUX_DISABLE_GLIB_LOOP))
128+
129+namespace nux
130+{
131+ class ExternalGLibSources
132+ {
133+ public:
134+
135+ void AddFdToGLibLoop(gint fd, gpointer data, GSourceFunc callback, GMainContext *context);
136+ void RemoveFdFromGLibLoop(gpointer data);
137+ };
138+}
139+
140+#endif
141+
142+#endif
143
144=== modified file 'Nux/ProgramFramework/ProgramTemplate.cpp'
145--- Nux/ProgramFramework/ProgramTemplate.cpp 2012-09-26 06:44:12 +0000
146+++ Nux/ProgramFramework/ProgramTemplate.cpp 2013-02-01 13:11:21 +0000
147@@ -17,6 +17,13 @@
148 * Authored by: Jay Taoko <jaytaoko@inalogic.com>
149 *
150 */
151+#ifndef _GNU_SOURCE
152+#define _GNU_SOURCE
153+#endif
154+
155+#include <fcntl.h>
156+#include <unistd.h>
157+#include <poll.h>
158
159 #include "Nux.h"
160 #include "VLayout.h"
161@@ -25,6 +32,58 @@
162 #include "TextEntry.h"
163 #include "ProgramTemplate.h"
164
165+namespace
166+{
167+unsigned int MAX_MESSAGE_LEN = 1024;
168+
169+std::string ReadMessageOnFd(int fd)
170+{
171+ char buf[MAX_MESSAGE_LEN + 1];
172+ ssize_t bytes = read(fd, reinterpret_cast <void *>(buf), MAX_MESSAGE_LEN);
173+
174+ if (bytes == -1)
175+ throw std::runtime_error(std::string("read: ") + strerror(errno));
176+
177+ buf[bytes] = '\0';
178+
179+ return std::string(buf);
180+}
181+
182+bool CheckForMessageOnFd(int fd, const std::string &msg)
183+{
184+ struct pollfd pfd;
185+ pfd.events = POLLIN;
186+ pfd.revents = 0;
187+ pfd.fd = fd;
188+
189+ if (poll(&pfd, 1, -1) == -1)
190+ throw std::runtime_error(std::string("poll: ") + strerror(errno));
191+
192+ if (ReadMessageOnFd(fd) == msg)
193+ return true;
194+ else
195+ return false;
196+}
197+
198+void WaitForMessageOnFd(int fd, const std::string &msg)
199+{
200+ while (!CheckForMessageOnFd(fd, msg));
201+}
202+
203+void SendMessageToFd(int fd, const std::string &msg)
204+{
205+ if (msg.size() > MAX_MESSAGE_LEN)
206+ {
207+ std::stringstream ss;
208+
209+ ss << "Only messages up to " << MAX_MESSAGE_LEN << " chars are supported.";
210+ throw std::runtime_error(ss.str ());
211+ }
212+
213+ if (write(fd, reinterpret_cast <void *>(const_cast<char *>(msg.c_str())), msg.size()) == -1)
214+ throw std::runtime_error(std::string("write: ") + strerror(errno));
215+}
216+}
217
218 ProgramTemplate::ProgramTemplate(const char* program_name,
219 int window_width,
220@@ -51,6 +110,12 @@
221 // Minimum life span is 1 second.
222 program_life_span_ = 1000;
223 }
224+
225+ if (pipe2(test_pipe_, O_CLOEXEC) == -1)
226+ throw std::runtime_error(strerror(errno));
227+
228+ if (pipe2(program_pipe_, O_CLOEXEC) == -1)
229+ throw std::runtime_error(strerror(errno));
230 }
231
232 ProgramTemplate::~ProgramTemplate()
233@@ -62,7 +127,8 @@
234 void ProgramTemplate::Startup()
235 {
236 nux::NuxInitialize(0);
237- window_thread_ = nux::CreateGUIThread(program_name_.c_str(), window_width_, window_height_, NULL, NULL, NULL);
238+ window_thread_ = nux::CreateGUIThread(program_name_.c_str(), window_width_, window_height_, NULL,
239+ ProgramTemplate::ThreadInitializer, this);
240
241 window_thread_->window_configuration.connect(sigc::mem_fun(this, &ProgramTemplate::WaitForConfigureEvent));
242 }
243@@ -96,6 +162,41 @@
244 window_thread_->Run(NULL);
245 }
246
247+void ProgramTemplate::SendMessageToProgram(const std::string &msg)
248+{
249+ SendMessageToFd(program_pipe_[1], msg);
250+}
251+
252+void ProgramTemplate::WaitForMessageFromProgram(const std::string &msg)
253+{
254+ WaitForMessageOnFd(test_pipe_[0], msg);
255+}
256+
257+void ProgramTemplate::SendMessageToTest(const std::string &msg)
258+{
259+ SendMessageToFd(test_pipe_[1], msg);
260+}
261+
262+void ProgramTemplate::MessageReceivedFromTest()
263+{
264+ HandleProgramMessage(ReadMessageOnFd(program_pipe_[0]));
265+}
266+
267+void ProgramTemplate::ThreadInitializer(nux::NThread *, void *data)
268+{
269+ using namespace std::placeholders;
270+
271+ /* Monitor the test-to-app fd for messages */
272+ ProgramTemplate *pt = reinterpret_cast<ProgramTemplate *>(data);
273+ pt->window_thread_->WatchFdForEvents(pt->program_pipe_[0],
274+ std::bind(&ProgramTemplate::MessageReceivedFromTest,
275+ pt));
276+}
277+
278+void ProgramTemplate::HandleProgramMessage(const std::string &msg)
279+{
280+}
281+
282 bool ProgramTemplate::ReadyToGo()
283 {
284 return window_thread_;
285
286=== modified file 'Nux/ProgramFramework/ProgramTemplate.h'
287--- Nux/ProgramFramework/ProgramTemplate.h 2012-01-27 22:37:13 +0000
288+++ Nux/ProgramFramework/ProgramTemplate.h 2013-02-01 13:11:21 +0000
289@@ -39,8 +39,22 @@
290
291 bool ReadyToGo();
292
293+ //!< Use with caution
294+ //
295+ // This WindowThread here will be valid, but if you call it from
296+ // another thread, and the call has a side-effect of calling
297+ // nux::GetWindowThread, it will likely result in undefined behaviour
298+ // since nux::GetWindowThread depends on the same thread that
299+ // GetWindowThread () here provides being the currently running
300+ // thread, as nux::GetWindowThread operates in terms of thread
301+ // local storage.
302 nux::WindowThread* GetWindowThread();
303
304+ //!< Tells the program to do something, depending on the defined protocol
305+ // implemented in HandleProgramMessage
306+ void SendMessageToProgram (const std::string &msg);
307+ void WaitForMessageFromProgram (const std::string &msg);
308+
309 public:
310 std::string program_name_;
311 int program_life_span_; //!< The program will auto-terminate after a delay in milliseconds.
312@@ -50,10 +64,22 @@
313 int window_width_;
314 int window_height_;
315
316+protected:
317+
318+ void SendMessageToTest(const std::string &msg);
319+
320 private:
321+
322+ void MessageReceivedFromTest();
323+ static void ThreadInitializer(nux::NThread *, void *);
324+
325+ virtual void HandleProgramMessage(const std::string &);
326+
327 void ProgramExitCall(void* data);
328 void WaitForConfigureEvent(int x, int y, int width, int height);
329 bool ready_to_go_;
330+ int program_pipe_[2];
331+ int test_pipe_[2];
332 };
333
334 #endif // PROGRAMTEMPLATE_H
335
336=== modified file 'Nux/WindowThread.cpp'
337--- Nux/WindowThread.cpp 2012-11-20 22:13:38 +0000
338+++ Nux/WindowThread.cpp 2013-02-01 13:11:21 +0000
339@@ -30,6 +30,7 @@
340 #include "FloatingWindow.h"
341
342 #include "WindowThread.h"
343+#include "MainLoopGLib.h"
344
345 namespace nux
346 {
347@@ -57,6 +58,9 @@
348 , embedded_window_(false)
349 , window_size_configuration_event_(false)
350 , force_rendering_(false)
351+#if (defined(NUX_OS_LINUX) || defined(NUX_USE_GLIB_LOOP_ON_WINDOWS)) && (!defined(NUX_DISABLE_GLIB_LOOP))
352+ , external_glib_sources_(new ExternalGLibSources)
353+#endif
354 #ifdef NUX_GESTURES_SUPPORT
355 , geis_adapter_(new GeisAdapter)
356 #endif
357@@ -412,6 +416,10 @@
358 return 1;
359 }
360
361+ // Setup the main loop first, so that user_init_func can add new sources
362+ // before running it
363+ SetupMainLoop();
364+
365 if (user_init_func_ && (m_WidgetInitialized == false))
366 {
367 (*user_init_func_) (this, initialization_data_);
368@@ -421,29 +429,42 @@
369 return MainLoop();
370 }
371
372- int WindowThread::MainLoop()
373+ void WindowThread::SetupMainLoop()
374 {
375 if (IsEmbeddedWindow())
376 {
377 window_compositor_->FormatRenderTargets(graphics_display_->GetWindowWidth(), graphics_display_->GetWindowHeight());
378 InitGlibLoop();
379- RunGlibLoop();
380- return 0;
381 }
382 else
383 {
384- graphics_display_->ShowWindow();
385+#if (defined(NUX_OS_LINUX) || defined(NUX_USE_GLIB_LOOP_ON_WINDOWS)) && (!defined(NUX_DISABLE_GLIB_LOOP))
386+ InitGlibLoop();
387+#endif
388 }
389+
390 // Called the first time so we can initialize the size of the render targets
391 // At this stage, the size of the window is known.
392 window_compositor_->FormatRenderTargets(graphics_display_->GetWindowWidth(), graphics_display_->GetWindowHeight());
393+ }
394+
395+ int WindowThread::MainLoop()
396+ {
397+ if (IsEmbeddedWindow())
398+ {
399+ RunGlibLoop();
400+ return 0;
401+ }
402+ else
403+ {
404+ graphics_display_->ShowWindow();
405+ }
406
407 while (GetThreadState() != THREADSTOP)
408 {
409 if (GetThreadState() == THREADRUNNING)
410 {
411 #if (defined(NUX_OS_LINUX) || defined(NUX_USE_GLIB_LOOP_ON_WINDOWS)) && (!defined(NUX_DISABLE_GLIB_LOOP))
412- InitGlibLoop();
413 RunGlibLoop();
414 #else
415 ExecutionLoop();
416@@ -557,12 +578,58 @@
417 graphics_display_->GetSystemEvent(&event);
418 int result = DoProcessEvent(event);
419 #endif
420+
421+#if defined(NUX_OS_LINUX)
422+ // select on all of our external fds. We can't use poll () here because
423+ // ExectionLoop is designed to be a busy-wait system. In reality we should
424+ // probably just drop support for this codepath.
425+ //
426+ // See https://bugs.launchpad.net/nux/+bug/1111216
427+
428+ fd_set read_fds;
429+
430+ FD_ZERO(&read_fds);
431+
432+ for (std::list<ExternalFdData>::iterator it =
433+ _external_fds.begin();
434+ it != _external_fds.end();
435+ ++it)
436+ {
437+ nuxAssertMsg(it->fd < FD_SETSIZE, "[nux::WindowThread::ExecutionLoop]"\
438+ " file descriptor overflow, aborting");
439+ FD_SET(it->fd, &read_fds);
440+ }
441+
442+ // Wait 10 us
443+ struct timeval timeout;
444+ timeout.tv_sec = 0;
445+ timeout.tv_usec = 10;
446+
447+ int select_result = select(_external_fds.size(), &read_fds, NULL, NULL,
448+ &timeout);
449+
450+ nuxAssertMsg(select_result != -1, strerror ("select()");
451+
452+ // Dispatch any active external fds
453+ for (std::list<ExternalFdData>::iterator it =
454+ _external_fds.begin();
455+ it != _external_fds.end();
456+ ++it)
457+ {
458+ if (FD_ISSET(it->fd, &read_fds))
459+ it->cb();
460+ }
461+
462 if (result != 1)
463 return result;
464 }
465
466 return 1;
467 }
468+#else
469+ nuxAssertMsg(_external_fds.empty(), "[nux::WindowThread::ExecutionLoop]"\
470+ " external fd support is not implemented on Windows");
471+#endif // NUX_OS_LINUX
472 #endif // GLIB loop or not
473
474 unsigned int WindowThread::ProcessEvent(Event &event)
475@@ -1604,6 +1671,42 @@
476 return discard_event;
477 }
478
479+ bool
480+ WindowThread::FindDataByFd(const WindowThread::ExternalFdData &data, int fd)
481+ {
482+ return data.fd == fd;
483+ }
484+
485+ void WindowThread::WatchFdForEvents(int fd, const WindowThread::FdWatchCallback &cb)
486+ {
487+ ExternalFdData data;
488+ data.fd = fd;
489+ data.cb = cb;
490+
491+ _external_fds.push_back(data);
492+#if (defined(NUX_OS_LINUX) || defined(NUX_USE_GLIB_LOOP_ON_WINDOWS)) && (!defined(NUX_DISABLE_GLIB_LOOP))
493+ AddFdToGLibLoop(fd,
494+ reinterpret_cast <gpointer> (&_external_fds.back()),
495+ WindowThread::ExternalSourceCallback);
496+#endif
497+ }
498+
499+ void WindowThread::UnwatchFd(int fd)
500+ {
501+ using namespace std::placeholders;
502+
503+ std::list<ExternalFdData>::iterator it =
504+ std::find_if (_external_fds.begin(), _external_fds.end(),
505+ std::bind(FindDataByFd, _1, fd));
506+
507+ if (it != _external_fds.end())
508+ {
509+#if (defined(NUX_OS_LINUX) || defined(NUX_USE_GLIB_LOOP_ON_WINDOWS)) && (!defined(NUX_DISABLE_GLIB_LOOP))
510+ RemoveFdFromGLibLoop(&(*it));
511+#endif
512+ _external_fds.erase (it);
513+ }
514+ }
515
516 GraphicsDisplay& WindowThread::GetGraphicsDisplay() const
517 {
518
519=== modified file 'Nux/WindowThread.h'
520--- Nux/WindowThread.h 2012-11-12 20:59:56 +0000
521+++ Nux/WindowThread.h 2013-02-01 13:11:21 +0000
522@@ -49,7 +49,9 @@
523 class Area;
524 struct ClientAreaDraw;
525
526+
527 #if (defined(NUX_OS_LINUX) || defined(NUX_USE_GLIB_LOOP_ON_WINDOWS)) && (!defined(NUX_DISABLE_GLIB_LOOP))
528+ class ExternalGLibSources;
529 gboolean nux_event_dispatch(GSource *source, GSourceFunc callback, gpointer user_data);
530 gboolean nux_timeout_dispatch(gpointer user_data);
531 #endif
532@@ -332,6 +334,10 @@
533 GeisAdapter *GetGeisAdapter() const {return geis_adapter_.get();}
534 #endif
535
536+ typedef std::function<void()> FdWatchCallback;
537+ void WatchFdForEvents(int fd, const FdWatchCallback &);
538+ void UnwatchFd(int fd);
539+
540 protected:
541
542 /*!
543@@ -474,6 +480,8 @@
544 */
545 unsigned int DoProcessEvent(Event &event);
546
547+ void SetupMainLoop();
548+
549 //! Execute the main loop of this thread.
550 /*!
551 Execute the main loop of this thread.
552@@ -609,6 +617,18 @@
553 */
554 std::map<int, EventInspectorStorage> _event_inspectors_map; //!< map of events inspectors
555
556+ typedef struct _ExternalFdData
557+ {
558+ int fd;
559+ FdWatchCallback cb;
560+ } ExternalFdData;
561+
562+ //! List of external sources. This might make more sense as a map,
563+ // but providing a struct provides us much nicer GLib integration
564+ std::list<ExternalFdData> _external_fds;
565+
566+ static bool FindDataByFd(const WindowThread::ExternalFdData &data, int fd);
567+
568 #if (defined(NUX_OS_LINUX) || defined(NUX_USE_GLIB_LOOP_ON_WINDOWS)) && (!defined(NUX_DISABLE_GLIB_LOOP))
569 GMainLoop *main_loop_glib_;
570 GMainContext *main_loop_glib_context_;
571@@ -616,12 +636,18 @@
572 friend gboolean nux_timeout_dispatch(gpointer user_data);
573 std::list<GSource*> child_window_list_;
574
575+ std::unique_ptr<ExternalGLibSources> external_glib_sources_;
576+
577 void InitGlibLoop();
578 void RunGlibLoop();
579 void StopGLibLoop();
580 void CleanupGlibLoop();
581+ void AddFdToGLibLoop(int, gpointer, GSourceFunc);
582+ void RemoveFdFromGLibLoop(gpointer);
583 bool AddChildWindowGlibLoop(WindowThread* wnd_thread);
584
585+ static gboolean ExternalSourceCallback(gpointer user_data);
586+
587 unsigned int AddGLibTimeout(unsigned int duration);
588 #else // no GLIB loop
589 Event input_event_;
590
591=== modified file 'tests/gtest-nux-windowthread.cpp'
592--- tests/gtest-nux-windowthread.cpp 2012-02-24 05:40:23 +0000
593+++ tests/gtest-nux-windowthread.cpp 2013-02-01 13:11:21 +0000
594@@ -1,3 +1,10 @@
595+#ifndef _GNU_SOURCE
596+#define _GNU_SOURCE
597+#endif
598+#include <fcntl.h>
599+#include <unistd.h>
600+#include <sys/poll.h>
601+
602 #include <string>
603 #include <fstream>
604
605@@ -10,23 +17,265 @@
606
607
608 using namespace testing;
609+using ::testing::Invoke;
610
611 namespace {
612
613-TEST(TestWindowThread, TestCreate)
614-{
615- nux::NuxInitialize(0);
616- nux::WindowThread *wnd_thread = nux::CreateNuxWindow("Nux Window", 300, 200,
617- nux::WINDOWSTYLE_NORMAL, NULL, false, NULL, NULL);
618-
619+class MockFdDispatch
620+{
621+ public:
622+ MOCK_METHOD0(dispatch, void());
623+
624+ MockFdDispatch(int fd) :
625+ mFd (fd)
626+ {
627+ ON_CALL(*this, dispatch())
628+ .WillByDefault(Invoke(this, &MockFdDispatch::ClearStream));
629+ }
630+
631+ private:
632+
633+ void ClearStream()
634+ {
635+ char buf[2];
636+ if (read(mFd, reinterpret_cast <void *>(buf), 2) == -1)
637+ throw std::runtime_error(strerror(errno));
638+ }
639+
640+ int mFd;
641+};
642+
643+class TestWindowThread : public ::testing::Test
644+{
645+ public:
646+
647+ TestWindowThread()
648+ : seconds_to_quit(0)
649+ , quit_thread (NULL)
650+ {
651+ nux::NuxInitialize(0);
652+ wnd_thread.reset (nux::CreateNuxWindow("Nux Window", 300, 200,
653+ nux::WINDOWSTYLE_NORMAL, NULL,
654+ false, TestWindowThread::WindowThreadInit,
655+ this));
656+
657+ if (pipe2(quit_pipe_, O_CLOEXEC) == -1)
658+ throw std::runtime_error(strerror(errno));
659+
660+ if (pipe2(test_quit_pipe_, O_CLOEXEC) == -1)
661+ throw std::runtime_error(strerror(errno));
662+
663+ WatchFdForEventsOnRun(quit_pipe_[0],
664+ std::bind(&TestWindowThread::QuitMessage,
665+ this));
666+
667+
668+ }
669+
670+ ~TestWindowThread()
671+ {
672+ if (quit_thread)
673+ delete quit_thread;
674+
675+ close(quit_pipe_[0]);
676+ close(quit_pipe_[1]);
677+ close(test_quit_pipe_[0]);
678+ close(test_quit_pipe_[1]);
679+ }
680+
681+ protected:
682+
683+ std::unique_ptr <nux::WindowThread> wnd_thread;
684+ unsigned int seconds_to_quit;
685+ nux::SystemThread *quit_thread;
686+
687+ void WatchFdForEventsOnRun(int fd,
688+ const nux::WindowThread::FdWatchCallback &cb)
689+ {
690+ WatchedFd wfd;
691+ wfd.fd = fd;
692+ wfd.callback = cb;
693+
694+ watched_fds.push_back(wfd);
695+ }
696+
697+ /* Watchdog threads are really not that great, but ping-ponging between
698+ * the window and application threads would over-complicate this test */
699+ void QuitAfter(unsigned int seconds)
700+ {
701+ seconds_to_quit = seconds;
702+ quit_thread = nux::CreateSystemThread(NULL, TestWindowThread::QuitTask, this);
703+ quit_thread->Start(NULL);
704+ }
705+
706+ void WaitForQuit()
707+ {
708+ struct pollfd pfd;
709+
710+ pfd.events = POLLIN;
711+ pfd.revents = 0;
712+ pfd.fd = test_quit_pipe_[0];
713+
714+ if (poll(&pfd, 1, -1) == -1)
715+ throw std::runtime_error(strerror(errno));
716+
717+ /* Something was written to the read end of our pipe,
718+ * we can continue now */
719+ }
720+
721+ private:
722+
723+ typedef struct _WatchedFd
724+ {
725+ int fd;
726+ nux::WindowThread::FdWatchCallback callback;
727+ } WatchedFd;
728+
729+ static void WindowThreadInit(nux::NThread *, void *data)
730+ {
731+ TestWindowThread *tw = reinterpret_cast <TestWindowThread *>(data);
732+
733+ /* Monitor all nominated fd's */
734+ for (std::vector<WatchedFd>::iterator it = tw->watched_fds.begin();
735+ it != tw->watched_fds.end();
736+ ++it)
737+ {
738+ tw->wnd_thread->WatchFdForEvents(it->fd, it->callback);
739+ }
740+ }
741+
742+ static void QuitTask(nux::NThread *, void *data)
743+ {
744+ TestWindowThread *tw = reinterpret_cast <TestWindowThread *> (data);
745+
746+ nux::SleepForMilliseconds(tw->seconds_to_quit * 1000);
747+
748+ const char *buf = "w\0";
749+ if (write(tw->quit_pipe_[1],
750+ reinterpret_cast<void *>(const_cast<char *>(buf)),
751+ 2) == -1)
752+ throw std::runtime_error(strerror(errno));
753+ }
754+
755+ void QuitMessage()
756+ {
757+ wnd_thread->ExitMainLoop();
758+
759+ /* Send a message indicating that the main loop
760+ * exited and we can safely continue */
761+ const char *buf = "w\0";
762+ if (write(test_quit_pipe_[1],
763+ reinterpret_cast<void *>(const_cast<char *>(buf)),
764+ 2) == -1)
765+ throw std::runtime_error(strerror(errno));
766+ }
767+
768+ std::vector<WatchedFd> watched_fds;
769+
770+ int quit_pipe_[2];
771+ int test_quit_pipe_[2];
772+};
773+
774+TEST_F(TestWindowThread, Create)
775+{
776 ASSERT_TRUE(wnd_thread != NULL);
777 EXPECT_EQ(wnd_thread->GetWindowTitle(), std::string("Nux Window"));
778 EXPECT_EQ(wnd_thread->IsModalWindow(), false);
779
780 EXPECT_EQ(wnd_thread->IsComputingLayout(), false);
781 EXPECT_EQ(wnd_thread->IsInsideLayoutCycle(), false);
782-
783- delete wnd_thread;
784+}
785+
786+TEST_F(TestWindowThread, WatchFd)
787+{
788+ int pipefd[2];
789+
790+ if (pipe2 (pipefd, O_CLOEXEC) == -1)
791+ throw std::runtime_error(strerror(errno));
792+
793+ MockFdDispatch dispatch(pipefd[0]);
794+
795+ EXPECT_CALL(dispatch, dispatch());
796+
797+ WatchFdForEventsOnRun(pipefd[0], std::bind(&MockFdDispatch::dispatch, &dispatch));
798+ const char *buf = "w\0";
799+ if (write(pipefd[1], reinterpret_cast <void *> (const_cast <char *> (buf)), 2) == -1)
800+ throw std::runtime_error (strerror(errno));
801+
802+ QuitAfter(3);
803+ wnd_thread->Run(NULL);
804+
805+ /* We must wait for quit before closing the pipes */
806+ WaitForQuit();
807+
808+ close (pipefd[0]);
809+ close (pipefd[1]);
810+}
811+
812+TEST_F(TestWindowThread, MultiWatchFd)
813+{
814+ int pipefd[2], pipefd2[2];
815+
816+ if (pipe2 (pipefd, O_CLOEXEC) == -1)
817+ throw std::runtime_error(strerror(errno));
818+ if (pipe2 (pipefd2, O_CLOEXEC) == -1)
819+ throw std::runtime_error(strerror(errno));
820+
821+ MockFdDispatch dispatch(pipefd[0]), dispatch2(pipefd2[0]);
822+
823+ EXPECT_CALL(dispatch, dispatch());
824+ EXPECT_CALL(dispatch2, dispatch());
825+
826+ WatchFdForEventsOnRun(pipefd[0], std::bind(&MockFdDispatch::dispatch, &dispatch));
827+ WatchFdForEventsOnRun(pipefd2[0], std::bind(&MockFdDispatch::dispatch, &dispatch2));
828+ const char *buf = "w\0";
829+ if (write(pipefd[1], reinterpret_cast <void *> (const_cast <char *> (buf)), 2) == -1)
830+ throw std::runtime_error (strerror(errno));
831+ if (write(pipefd2[1], reinterpret_cast <void *> (const_cast <char *> (buf)), 2) == -1)
832+ throw std::runtime_error (strerror(errno));
833+
834+ QuitAfter(3);
835+ wnd_thread->Run(NULL);
836+
837+ /* We must wait for quit before closing the pipes */
838+ WaitForQuit();
839+
840+ close (pipefd[0]);
841+ close (pipefd[1]);
842+ close (pipefd2[0]);
843+ close (pipefd2[1]);
844+}
845+
846+TEST_F(TestWindowThread, OneFdEvent)
847+{
848+ int pipefd[2], pipefd2[2];
849+
850+ if (pipe2 (pipefd, O_CLOEXEC) == -1)
851+ throw std::runtime_error(strerror(errno));
852+ if (pipe2 (pipefd2, O_CLOEXEC) == -1)
853+ throw std::runtime_error(strerror(errno));
854+
855+ MockFdDispatch dispatch(pipefd[0]), dispatch2(pipefd2[0]);
856+
857+ EXPECT_CALL(dispatch, dispatch());
858+ EXPECT_CALL(dispatch2, dispatch()).Times(0);
859+
860+ WatchFdForEventsOnRun(pipefd[0], std::bind(&MockFdDispatch::dispatch, &dispatch));
861+ WatchFdForEventsOnRun(pipefd2[0], std::bind(&MockFdDispatch::dispatch, &dispatch2));
862+ const char *buf = "w\0";
863+ if (write(pipefd[1], reinterpret_cast <void *> (const_cast <char *> (buf)), 2) == -1)
864+ throw std::runtime_error (strerror(errno));
865+
866+ QuitAfter(3);
867+ wnd_thread->Run(NULL);
868+
869+ /* We must wait for quit before closing the pipes */
870+ WaitForQuit();
871+
872+ close (pipefd[0]);
873+ close (pipefd[1]);
874+ close (pipefd2[0]);
875+ close (pipefd2[1]);
876 }
877
878
879
880=== modified file 'tests/xtest-text-entry.cpp'
881--- tests/xtest-text-entry.cpp 2012-10-01 23:37:25 +0000
882+++ tests/xtest-text-entry.cpp 2013-02-01 13:11:21 +0000
883@@ -29,6 +29,12 @@
884 #include <X11/keysym.h>
885 #include "nux_automated_test_framework.h"
886
887+namespace
888+{
889+std::string RESET_KEY_FOCUS_AREA_MSG = "r";
890+std::string KEY_FOCUS_AREA_RESET_MSG = "d";
891+}
892+
893 class TextTextEntry: public ProgramTemplate
894 {
895 public:
896@@ -41,12 +47,19 @@
897 void OnActivated();
898 void OnCursorMoved(int);
899 void ResetEvents();
900+ void SendResetKeyFocusAreaMessageToProgram();
901+
902
903 nux::TextEntry* text_entry_;
904
905 bool clicked_;
906 bool activated_;
907 bool cursor_moved_;
908+
909+private:
910+
911+ /* Handled inside the TextEntry WindowThread */
912+ void HandleProgramMessage(const std::string &);
913 };
914
915 TextTextEntry::TextTextEntry(const char* program_name,
916@@ -74,6 +87,22 @@
917 cursor_moved_ = false;
918 }
919
920+void TextTextEntry::SendResetKeyFocusAreaMessageToProgram()
921+{
922+ SendMessageToProgram(RESET_KEY_FOCUS_AREA_MSG);
923+ WaitForMessageFromProgram(KEY_FOCUS_AREA_RESET_MSG);
924+}
925+
926+void TextTextEntry::HandleProgramMessage(const std::string &m)
927+{
928+ if (m == RESET_KEY_FOCUS_AREA_MSG)
929+ {
930+ GetWindowThread()->GetWindowCompositor().SetKeyFocusArea(NULL);
931+ GetWindowThread()->GetWindowCompositor().SetKeyFocusArea(text_entry_);
932+ SendMessageToTest(KEY_FOCUS_AREA_RESET_MSG);
933+ }
934+}
935+
936 void TextTextEntry::TextEntryClick(nux::TextEntry* text_entry)
937 {
938 if (text_entry_ == text_entry)
939@@ -185,8 +214,7 @@
940 test.ViewSendString("Nux");
941 test.TestReportMsg(test_textentry->text_entry_->GetText() == "Nux", "Typed \"Nux\"");
942
943- test_textentry->GetWindowThread()->GetWindowCompositor().SetKeyFocusArea(NULL);
944- test_textentry->GetWindowThread()->GetWindowCompositor().SetKeyFocusArea(test_textentry->text_entry_);
945+ test_textentry->SendResetKeyFocusAreaMessageToProgram();
946
947 test_textentry->ResetEvents();
948 test.ViewSendLeft();
949@@ -205,8 +233,7 @@
950 test.ViewSendString("Nux");
951 test.TestReportMsg(test_textentry->text_entry_->GetText() == "Nux", "Typed \"Nux\"");
952
953- test_textentry->GetWindowThread()->GetWindowCompositor().SetKeyFocusArea(NULL);
954- test_textentry->GetWindowThread()->GetWindowCompositor().SetKeyFocusArea(test_textentry->text_entry_);
955+ test_textentry->SendResetKeyFocusAreaMessageToProgram();
956
957 test_textentry->ResetEvents();
958 test.ViewSendReturn();

Subscribers

People subscribed via source and target branches