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
=== modified file 'Nux/MainLoopGLib.cpp'
--- Nux/MainLoopGLib.cpp 2012-12-08 01:03:38 +0000
+++ Nux/MainLoopGLib.cpp 2013-02-01 13:11:21 +0000
@@ -9,6 +9,7 @@
9#include "FloatingWindow.h"9#include "FloatingWindow.h"
1010
11#include "WindowThread.h"11#include "WindowThread.h"
12#include "MainLoopGLib.h"
1213
13namespace nux14namespace nux
14{15{
@@ -296,9 +297,104 @@
296 }297 }
297 }298 }
298299
300 namespace
301 {
302 typedef struct _ExternalNuxSource
303 {
304 GSource source;
305 GPollFD pfd;
306 } ExternalNuxSource;
307
308 gboolean ExternalSourcePrepareFunc(GSource *source, gint *timeout)
309 {
310 /* Always block for new events */
311 *timeout = -1;
312 return FALSE;
313 }
314
315 gboolean ExternalSourceCheckFunc(GSource *source)
316 {
317 /* Only return true if there are events waiting for us */
318 ExternalNuxSource *extSource =
319 reinterpret_cast <ExternalNuxSource *>(source);
320 return extSource->pfd.revents == G_IO_IN;
321 }
322
323 gboolean ExternalSourceDispatchFunc(GSource *source,
324 GSourceFunc callback,
325 gpointer user_data)
326 {
327 return (*callback) (user_data);
328 }
329
330 static GSourceFuncs externalGLibFuncs =
331 {
332 &ExternalSourcePrepareFunc,
333 &ExternalSourceCheckFunc,
334 &ExternalSourceDispatchFunc,
335 NULL,
336 /* Technically we shouldn't be touching these, but the compiler
337 * will complain if we don't */
338 0,
339 0
340 };
341 }
342
343 gboolean WindowThread::ExternalSourceCallback(gpointer user_data)
344 {
345 WindowThread::ExternalFdData *data =
346 reinterpret_cast <WindowThread::ExternalFdData *> (user_data);
347
348 data->cb ();
349 return TRUE;
350 }
351
352 void ExternalGLibSources::AddFdToGLibLoop(gint fd,
353 gpointer data,
354 GSourceFunc callback,
355 GMainContext *context)
356 {
357 GSource *source = g_source_new(&externalGLibFuncs, sizeof (ExternalNuxSource));
358 ExternalNuxSource *extSource =
359 reinterpret_cast <ExternalNuxSource *> (source);
360 extSource->pfd.fd = fd;
361 extSource->pfd.events = G_IO_IN;
362 extSource->pfd.revents = 0;
363
364 g_source_add_poll (source, &extSource->pfd);
365 g_source_set_callback (source, callback, data, NULL);
366
367 g_source_attach (source, context);
368 }
369
370 void ExternalGLibSources::RemoveFdFromGLibLoop(gpointer data)
371 {
372 g_source_remove_by_user_data(data);
373 }
374
375 void WindowThread::AddFdToGLibLoop(int fd,
376 gpointer data,
377 GSourceFunc callback)
378 {
379 external_glib_sources_->AddFdToGLibLoop(fd, data, callback, main_loop_glib_context_);
380 }
381
382 void WindowThread::RemoveFdFromGLibLoop(gpointer data)
383 {
384 external_glib_sources_->RemoveFdFromGLibLoop(data);
385 }
386
299 void WindowThread::CleanupGlibLoop()387 void WindowThread::CleanupGlibLoop()
300 {388 {
301 g_source_remove_by_funcs_user_data(&event_funcs, this);389 g_source_remove_by_funcs_user_data(&event_funcs, this);
390
391 for (std::list<ExternalFdData>::iterator it = _external_fds.begin();
392 it != _external_fds.end();
393 ++it)
394 {
395 gpointer data = reinterpret_cast<gpointer>(&(*it));
396 external_glib_sources_->RemoveFdFromGLibLoop(data);
397 }
302 }398 }
303399
304 unsigned int WindowThread::AddGLibTimeout(unsigned int duration)400 unsigned int WindowThread::AddGLibTimeout(unsigned int duration)
305401
=== added file 'Nux/MainLoopGLib.h'
--- Nux/MainLoopGLib.h 1970-01-01 00:00:00 +0000
+++ Nux/MainLoopGLib.h 2013-02-01 13:11:21 +0000
@@ -0,0 +1,21 @@
1#ifndef _NUX_MAIN_LOOP_GLIB_H
2#define _NUX_MAIN_LOOP_GLIB_H
3
4#include <glib.h>
5
6#if (defined(NUX_OS_LINUX) || defined(NUX_USE_GLIB_LOOP_ON_WINDOWS)) && (!defined(NUX_DISABLE_GLIB_LOOP))
7
8namespace nux
9{
10 class ExternalGLibSources
11 {
12 public:
13
14 void AddFdToGLibLoop(gint fd, gpointer data, GSourceFunc callback, GMainContext *context);
15 void RemoveFdFromGLibLoop(gpointer data);
16 };
17}
18
19#endif
20
21#endif
022
=== modified file 'Nux/ProgramFramework/ProgramTemplate.cpp'
--- Nux/ProgramFramework/ProgramTemplate.cpp 2012-09-26 06:44:12 +0000
+++ Nux/ProgramFramework/ProgramTemplate.cpp 2013-02-01 13:11:21 +0000
@@ -17,6 +17,13 @@
17 * Authored by: Jay Taoko <jaytaoko@inalogic.com>17 * Authored by: Jay Taoko <jaytaoko@inalogic.com>
18 *18 *
19 */19 */
20#ifndef _GNU_SOURCE
21#define _GNU_SOURCE
22#endif
23
24#include <fcntl.h>
25#include <unistd.h>
26#include <poll.h>
2027
21#include "Nux.h"28#include "Nux.h"
22#include "VLayout.h"29#include "VLayout.h"
@@ -25,6 +32,58 @@
25#include "TextEntry.h"32#include "TextEntry.h"
26#include "ProgramTemplate.h"33#include "ProgramTemplate.h"
2734
35namespace
36{
37unsigned int MAX_MESSAGE_LEN = 1024;
38
39std::string ReadMessageOnFd(int fd)
40{
41 char buf[MAX_MESSAGE_LEN + 1];
42 ssize_t bytes = read(fd, reinterpret_cast <void *>(buf), MAX_MESSAGE_LEN);
43
44 if (bytes == -1)
45 throw std::runtime_error(std::string("read: ") + strerror(errno));
46
47 buf[bytes] = '\0';
48
49 return std::string(buf);
50}
51
52bool CheckForMessageOnFd(int fd, const std::string &msg)
53{
54 struct pollfd pfd;
55 pfd.events = POLLIN;
56 pfd.revents = 0;
57 pfd.fd = fd;
58
59 if (poll(&pfd, 1, -1) == -1)
60 throw std::runtime_error(std::string("poll: ") + strerror(errno));
61
62 if (ReadMessageOnFd(fd) == msg)
63 return true;
64 else
65 return false;
66}
67
68void WaitForMessageOnFd(int fd, const std::string &msg)
69{
70 while (!CheckForMessageOnFd(fd, msg));
71}
72
73void SendMessageToFd(int fd, const std::string &msg)
74{
75 if (msg.size() > MAX_MESSAGE_LEN)
76 {
77 std::stringstream ss;
78
79 ss << "Only messages up to " << MAX_MESSAGE_LEN << " chars are supported.";
80 throw std::runtime_error(ss.str ());
81 }
82
83 if (write(fd, reinterpret_cast <void *>(const_cast<char *>(msg.c_str())), msg.size()) == -1)
84 throw std::runtime_error(std::string("write: ") + strerror(errno));
85}
86}
2887
29ProgramTemplate::ProgramTemplate(const char* program_name,88ProgramTemplate::ProgramTemplate(const char* program_name,
30 int window_width,89 int window_width,
@@ -51,6 +110,12 @@
51 // Minimum life span is 1 second.110 // Minimum life span is 1 second.
52 program_life_span_ = 1000;111 program_life_span_ = 1000;
53 }112 }
113
114 if (pipe2(test_pipe_, O_CLOEXEC) == -1)
115 throw std::runtime_error(strerror(errno));
116
117 if (pipe2(program_pipe_, O_CLOEXEC) == -1)
118 throw std::runtime_error(strerror(errno));
54}119}
55120
56ProgramTemplate::~ProgramTemplate()121ProgramTemplate::~ProgramTemplate()
@@ -62,7 +127,8 @@
62void ProgramTemplate::Startup()127void ProgramTemplate::Startup()
63{128{
64 nux::NuxInitialize(0);129 nux::NuxInitialize(0);
65 window_thread_ = nux::CreateGUIThread(program_name_.c_str(), window_width_, window_height_, NULL, NULL, NULL);130 window_thread_ = nux::CreateGUIThread(program_name_.c_str(), window_width_, window_height_, NULL,
131 ProgramTemplate::ThreadInitializer, this);
66132
67 window_thread_->window_configuration.connect(sigc::mem_fun(this, &ProgramTemplate::WaitForConfigureEvent));133 window_thread_->window_configuration.connect(sigc::mem_fun(this, &ProgramTemplate::WaitForConfigureEvent));
68}134}
@@ -96,6 +162,41 @@
96 window_thread_->Run(NULL);162 window_thread_->Run(NULL);
97}163}
98164
165void ProgramTemplate::SendMessageToProgram(const std::string &msg)
166{
167 SendMessageToFd(program_pipe_[1], msg);
168}
169
170void ProgramTemplate::WaitForMessageFromProgram(const std::string &msg)
171{
172 WaitForMessageOnFd(test_pipe_[0], msg);
173}
174
175void ProgramTemplate::SendMessageToTest(const std::string &msg)
176{
177 SendMessageToFd(test_pipe_[1], msg);
178}
179
180void ProgramTemplate::MessageReceivedFromTest()
181{
182 HandleProgramMessage(ReadMessageOnFd(program_pipe_[0]));
183}
184
185void ProgramTemplate::ThreadInitializer(nux::NThread *, void *data)
186{
187 using namespace std::placeholders;
188
189 /* Monitor the test-to-app fd for messages */
190 ProgramTemplate *pt = reinterpret_cast<ProgramTemplate *>(data);
191 pt->window_thread_->WatchFdForEvents(pt->program_pipe_[0],
192 std::bind(&ProgramTemplate::MessageReceivedFromTest,
193 pt));
194}
195
196void ProgramTemplate::HandleProgramMessage(const std::string &msg)
197{
198}
199
99bool ProgramTemplate::ReadyToGo()200bool ProgramTemplate::ReadyToGo()
100{201{
101 return window_thread_;202 return window_thread_;
102203
=== modified file 'Nux/ProgramFramework/ProgramTemplate.h'
--- Nux/ProgramFramework/ProgramTemplate.h 2012-01-27 22:37:13 +0000
+++ Nux/ProgramFramework/ProgramTemplate.h 2013-02-01 13:11:21 +0000
@@ -39,8 +39,22 @@
3939
40 bool ReadyToGo();40 bool ReadyToGo();
4141
42 //!< Use with caution
43 //
44 // This WindowThread here will be valid, but if you call it from
45 // another thread, and the call has a side-effect of calling
46 // nux::GetWindowThread, it will likely result in undefined behaviour
47 // since nux::GetWindowThread depends on the same thread that
48 // GetWindowThread () here provides being the currently running
49 // thread, as nux::GetWindowThread operates in terms of thread
50 // local storage.
42 nux::WindowThread* GetWindowThread();51 nux::WindowThread* GetWindowThread();
4352
53 //!< Tells the program to do something, depending on the defined protocol
54 // implemented in HandleProgramMessage
55 void SendMessageToProgram (const std::string &msg);
56 void WaitForMessageFromProgram (const std::string &msg);
57
44public:58public:
45 std::string program_name_;59 std::string program_name_;
46 int program_life_span_; //!< The program will auto-terminate after a delay in milliseconds.60 int program_life_span_; //!< The program will auto-terminate after a delay in milliseconds.
@@ -50,10 +64,22 @@
50 int window_width_;64 int window_width_;
51 int window_height_;65 int window_height_;
5266
67protected:
68
69 void SendMessageToTest(const std::string &msg);
70
53private:71private:
72
73 void MessageReceivedFromTest();
74 static void ThreadInitializer(nux::NThread *, void *);
75
76 virtual void HandleProgramMessage(const std::string &);
77
54 void ProgramExitCall(void* data);78 void ProgramExitCall(void* data);
55 void WaitForConfigureEvent(int x, int y, int width, int height);79 void WaitForConfigureEvent(int x, int y, int width, int height);
56 bool ready_to_go_;80 bool ready_to_go_;
81 int program_pipe_[2];
82 int test_pipe_[2];
57};83};
5884
59#endif // PROGRAMTEMPLATE_H85#endif // PROGRAMTEMPLATE_H
6086
=== modified file 'Nux/WindowThread.cpp'
--- Nux/WindowThread.cpp 2012-11-20 22:13:38 +0000
+++ Nux/WindowThread.cpp 2013-02-01 13:11:21 +0000
@@ -30,6 +30,7 @@
30#include "FloatingWindow.h"30#include "FloatingWindow.h"
3131
32#include "WindowThread.h"32#include "WindowThread.h"
33#include "MainLoopGLib.h"
3334
34namespace nux35namespace nux
35{36{
@@ -57,6 +58,9 @@
57 , embedded_window_(false)58 , embedded_window_(false)
58 , window_size_configuration_event_(false)59 , window_size_configuration_event_(false)
59 , force_rendering_(false)60 , force_rendering_(false)
61#if (defined(NUX_OS_LINUX) || defined(NUX_USE_GLIB_LOOP_ON_WINDOWS)) && (!defined(NUX_DISABLE_GLIB_LOOP))
62 , external_glib_sources_(new ExternalGLibSources)
63#endif
60#ifdef NUX_GESTURES_SUPPORT64#ifdef NUX_GESTURES_SUPPORT
61 , geis_adapter_(new GeisAdapter)65 , geis_adapter_(new GeisAdapter)
62#endif66#endif
@@ -412,6 +416,10 @@
412 return 1;416 return 1;
413 }417 }
414418
419 // Setup the main loop first, so that user_init_func can add new sources
420 // before running it
421 SetupMainLoop();
422
415 if (user_init_func_ && (m_WidgetInitialized == false))423 if (user_init_func_ && (m_WidgetInitialized == false))
416 {424 {
417 (*user_init_func_) (this, initialization_data_);425 (*user_init_func_) (this, initialization_data_);
@@ -421,29 +429,42 @@
421 return MainLoop();429 return MainLoop();
422 }430 }
423431
424 int WindowThread::MainLoop()432 void WindowThread::SetupMainLoop()
425 {433 {
426 if (IsEmbeddedWindow())434 if (IsEmbeddedWindow())
427 {435 {
428 window_compositor_->FormatRenderTargets(graphics_display_->GetWindowWidth(), graphics_display_->GetWindowHeight());436 window_compositor_->FormatRenderTargets(graphics_display_->GetWindowWidth(), graphics_display_->GetWindowHeight());
429 InitGlibLoop();437 InitGlibLoop();
430 RunGlibLoop();
431 return 0;
432 }438 }
433 else439 else
434 {440 {
435 graphics_display_->ShowWindow();441#if (defined(NUX_OS_LINUX) || defined(NUX_USE_GLIB_LOOP_ON_WINDOWS)) && (!defined(NUX_DISABLE_GLIB_LOOP))
442 InitGlibLoop();
443#endif
436 }444 }
445
437 // Called the first time so we can initialize the size of the render targets446 // Called the first time so we can initialize the size of the render targets
438 // At this stage, the size of the window is known.447 // At this stage, the size of the window is known.
439 window_compositor_->FormatRenderTargets(graphics_display_->GetWindowWidth(), graphics_display_->GetWindowHeight());448 window_compositor_->FormatRenderTargets(graphics_display_->GetWindowWidth(), graphics_display_->GetWindowHeight());
449 }
450
451 int WindowThread::MainLoop()
452 {
453 if (IsEmbeddedWindow())
454 {
455 RunGlibLoop();
456 return 0;
457 }
458 else
459 {
460 graphics_display_->ShowWindow();
461 }
440462
441 while (GetThreadState() != THREADSTOP)463 while (GetThreadState() != THREADSTOP)
442 {464 {
443 if (GetThreadState() == THREADRUNNING)465 if (GetThreadState() == THREADRUNNING)
444 {466 {
445#if (defined(NUX_OS_LINUX) || defined(NUX_USE_GLIB_LOOP_ON_WINDOWS)) && (!defined(NUX_DISABLE_GLIB_LOOP))467#if (defined(NUX_OS_LINUX) || defined(NUX_USE_GLIB_LOOP_ON_WINDOWS)) && (!defined(NUX_DISABLE_GLIB_LOOP))
446 InitGlibLoop();
447 RunGlibLoop();468 RunGlibLoop();
448#else469#else
449 ExecutionLoop();470 ExecutionLoop();
@@ -557,12 +578,58 @@
557 graphics_display_->GetSystemEvent(&event);578 graphics_display_->GetSystemEvent(&event);
558 int result = DoProcessEvent(event);579 int result = DoProcessEvent(event);
559#endif580#endif
581
582#if defined(NUX_OS_LINUX)
583 // select on all of our external fds. We can't use poll () here because
584 // ExectionLoop is designed to be a busy-wait system. In reality we should
585 // probably just drop support for this codepath.
586 //
587 // See https://bugs.launchpad.net/nux/+bug/1111216
588
589 fd_set read_fds;
590
591 FD_ZERO(&read_fds);
592
593 for (std::list<ExternalFdData>::iterator it =
594 _external_fds.begin();
595 it != _external_fds.end();
596 ++it)
597 {
598 nuxAssertMsg(it->fd < FD_SETSIZE, "[nux::WindowThread::ExecutionLoop]"\
599 " file descriptor overflow, aborting");
600 FD_SET(it->fd, &read_fds);
601 }
602
603 // Wait 10 us
604 struct timeval timeout;
605 timeout.tv_sec = 0;
606 timeout.tv_usec = 10;
607
608 int select_result = select(_external_fds.size(), &read_fds, NULL, NULL,
609 &timeout);
610
611 nuxAssertMsg(select_result != -1, strerror ("select()");
612
613 // Dispatch any active external fds
614 for (std::list<ExternalFdData>::iterator it =
615 _external_fds.begin();
616 it != _external_fds.end();
617 ++it)
618 {
619 if (FD_ISSET(it->fd, &read_fds))
620 it->cb();
621 }
622
560 if (result != 1)623 if (result != 1)
561 return result;624 return result;
562 }625 }
563626
564 return 1;627 return 1;
565 }628 }
629#else
630 nuxAssertMsg(_external_fds.empty(), "[nux::WindowThread::ExecutionLoop]"\
631 " external fd support is not implemented on Windows");
632#endif // NUX_OS_LINUX
566#endif // GLIB loop or not633#endif // GLIB loop or not
567634
568 unsigned int WindowThread::ProcessEvent(Event &event)635 unsigned int WindowThread::ProcessEvent(Event &event)
@@ -1604,6 +1671,42 @@
1604 return discard_event;1671 return discard_event;
1605 }1672 }
16061673
1674 bool
1675 WindowThread::FindDataByFd(const WindowThread::ExternalFdData &data, int fd)
1676 {
1677 return data.fd == fd;
1678 }
1679
1680 void WindowThread::WatchFdForEvents(int fd, const WindowThread::FdWatchCallback &cb)
1681 {
1682 ExternalFdData data;
1683 data.fd = fd;
1684 data.cb = cb;
1685
1686 _external_fds.push_back(data);
1687#if (defined(NUX_OS_LINUX) || defined(NUX_USE_GLIB_LOOP_ON_WINDOWS)) && (!defined(NUX_DISABLE_GLIB_LOOP))
1688 AddFdToGLibLoop(fd,
1689 reinterpret_cast <gpointer> (&_external_fds.back()),
1690 WindowThread::ExternalSourceCallback);
1691#endif
1692 }
1693
1694 void WindowThread::UnwatchFd(int fd)
1695 {
1696 using namespace std::placeholders;
1697
1698 std::list<ExternalFdData>::iterator it =
1699 std::find_if (_external_fds.begin(), _external_fds.end(),
1700 std::bind(FindDataByFd, _1, fd));
1701
1702 if (it != _external_fds.end())
1703 {
1704#if (defined(NUX_OS_LINUX) || defined(NUX_USE_GLIB_LOOP_ON_WINDOWS)) && (!defined(NUX_DISABLE_GLIB_LOOP))
1705 RemoveFdFromGLibLoop(&(*it));
1706#endif
1707 _external_fds.erase (it);
1708 }
1709 }
16071710
1608 GraphicsDisplay& WindowThread::GetGraphicsDisplay() const1711 GraphicsDisplay& WindowThread::GetGraphicsDisplay() const
1609 {1712 {
16101713
=== modified file 'Nux/WindowThread.h'
--- Nux/WindowThread.h 2012-11-12 20:59:56 +0000
+++ Nux/WindowThread.h 2013-02-01 13:11:21 +0000
@@ -49,7 +49,9 @@
49 class Area;49 class Area;
50 struct ClientAreaDraw;50 struct ClientAreaDraw;
5151
52
52#if (defined(NUX_OS_LINUX) || defined(NUX_USE_GLIB_LOOP_ON_WINDOWS)) && (!defined(NUX_DISABLE_GLIB_LOOP))53#if (defined(NUX_OS_LINUX) || defined(NUX_USE_GLIB_LOOP_ON_WINDOWS)) && (!defined(NUX_DISABLE_GLIB_LOOP))
54 class ExternalGLibSources;
53 gboolean nux_event_dispatch(GSource *source, GSourceFunc callback, gpointer user_data);55 gboolean nux_event_dispatch(GSource *source, GSourceFunc callback, gpointer user_data);
54 gboolean nux_timeout_dispatch(gpointer user_data);56 gboolean nux_timeout_dispatch(gpointer user_data);
55#endif57#endif
@@ -332,6 +334,10 @@
332 GeisAdapter *GetGeisAdapter() const {return geis_adapter_.get();}334 GeisAdapter *GetGeisAdapter() const {return geis_adapter_.get();}
333#endif335#endif
334336
337 typedef std::function<void()> FdWatchCallback;
338 void WatchFdForEvents(int fd, const FdWatchCallback &);
339 void UnwatchFd(int fd);
340
335 protected:341 protected:
336342
337 /*!343 /*!
@@ -474,6 +480,8 @@
474 */480 */
475 unsigned int DoProcessEvent(Event &event);481 unsigned int DoProcessEvent(Event &event);
476482
483 void SetupMainLoop();
484
477 //! Execute the main loop of this thread.485 //! Execute the main loop of this thread.
478 /*!486 /*!
479 Execute the main loop of this thread.487 Execute the main loop of this thread.
@@ -609,6 +617,18 @@
609 */617 */
610 std::map<int, EventInspectorStorage> _event_inspectors_map; //!< map of events inspectors618 std::map<int, EventInspectorStorage> _event_inspectors_map; //!< map of events inspectors
611619
620 typedef struct _ExternalFdData
621 {
622 int fd;
623 FdWatchCallback cb;
624 } ExternalFdData;
625
626 //! List of external sources. This might make more sense as a map,
627 // but providing a struct provides us much nicer GLib integration
628 std::list<ExternalFdData> _external_fds;
629
630 static bool FindDataByFd(const WindowThread::ExternalFdData &data, int fd);
631
612#if (defined(NUX_OS_LINUX) || defined(NUX_USE_GLIB_LOOP_ON_WINDOWS)) && (!defined(NUX_DISABLE_GLIB_LOOP))632#if (defined(NUX_OS_LINUX) || defined(NUX_USE_GLIB_LOOP_ON_WINDOWS)) && (!defined(NUX_DISABLE_GLIB_LOOP))
613 GMainLoop *main_loop_glib_;633 GMainLoop *main_loop_glib_;
614 GMainContext *main_loop_glib_context_;634 GMainContext *main_loop_glib_context_;
@@ -616,12 +636,18 @@
616 friend gboolean nux_timeout_dispatch(gpointer user_data);636 friend gboolean nux_timeout_dispatch(gpointer user_data);
617 std::list<GSource*> child_window_list_;637 std::list<GSource*> child_window_list_;
618638
639 std::unique_ptr<ExternalGLibSources> external_glib_sources_;
640
619 void InitGlibLoop();641 void InitGlibLoop();
620 void RunGlibLoop();642 void RunGlibLoop();
621 void StopGLibLoop();643 void StopGLibLoop();
622 void CleanupGlibLoop();644 void CleanupGlibLoop();
645 void AddFdToGLibLoop(int, gpointer, GSourceFunc);
646 void RemoveFdFromGLibLoop(gpointer);
623 bool AddChildWindowGlibLoop(WindowThread* wnd_thread);647 bool AddChildWindowGlibLoop(WindowThread* wnd_thread);
624648
649 static gboolean ExternalSourceCallback(gpointer user_data);
650
625 unsigned int AddGLibTimeout(unsigned int duration);651 unsigned int AddGLibTimeout(unsigned int duration);
626#else // no GLIB loop652#else // no GLIB loop
627 Event input_event_;653 Event input_event_;
628654
=== modified file 'tests/gtest-nux-windowthread.cpp'
--- tests/gtest-nux-windowthread.cpp 2012-02-24 05:40:23 +0000
+++ tests/gtest-nux-windowthread.cpp 2013-02-01 13:11:21 +0000
@@ -1,3 +1,10 @@
1#ifndef _GNU_SOURCE
2#define _GNU_SOURCE
3#endif
4#include <fcntl.h>
5#include <unistd.h>
6#include <sys/poll.h>
7
1#include <string>8#include <string>
2#include <fstream>9#include <fstream>
310
@@ -10,23 +17,265 @@
1017
1118
12using namespace testing;19using namespace testing;
20using ::testing::Invoke;
1321
14namespace {22namespace {
1523
16TEST(TestWindowThread, TestCreate)24class MockFdDispatch
17{25{
18 nux::NuxInitialize(0);26 public:
19 nux::WindowThread *wnd_thread = nux::CreateNuxWindow("Nux Window", 300, 200,27 MOCK_METHOD0(dispatch, void());
20 nux::WINDOWSTYLE_NORMAL, NULL, false, NULL, NULL);28
2129 MockFdDispatch(int fd) :
30 mFd (fd)
31 {
32 ON_CALL(*this, dispatch())
33 .WillByDefault(Invoke(this, &MockFdDispatch::ClearStream));
34 }
35
36 private:
37
38 void ClearStream()
39 {
40 char buf[2];
41 if (read(mFd, reinterpret_cast <void *>(buf), 2) == -1)
42 throw std::runtime_error(strerror(errno));
43 }
44
45 int mFd;
46};
47
48class TestWindowThread : public ::testing::Test
49{
50 public:
51
52 TestWindowThread()
53 : seconds_to_quit(0)
54 , quit_thread (NULL)
55 {
56 nux::NuxInitialize(0);
57 wnd_thread.reset (nux::CreateNuxWindow("Nux Window", 300, 200,
58 nux::WINDOWSTYLE_NORMAL, NULL,
59 false, TestWindowThread::WindowThreadInit,
60 this));
61
62 if (pipe2(quit_pipe_, O_CLOEXEC) == -1)
63 throw std::runtime_error(strerror(errno));
64
65 if (pipe2(test_quit_pipe_, O_CLOEXEC) == -1)
66 throw std::runtime_error(strerror(errno));
67
68 WatchFdForEventsOnRun(quit_pipe_[0],
69 std::bind(&TestWindowThread::QuitMessage,
70 this));
71
72
73 }
74
75 ~TestWindowThread()
76 {
77 if (quit_thread)
78 delete quit_thread;
79
80 close(quit_pipe_[0]);
81 close(quit_pipe_[1]);
82 close(test_quit_pipe_[0]);
83 close(test_quit_pipe_[1]);
84 }
85
86 protected:
87
88 std::unique_ptr <nux::WindowThread> wnd_thread;
89 unsigned int seconds_to_quit;
90 nux::SystemThread *quit_thread;
91
92 void WatchFdForEventsOnRun(int fd,
93 const nux::WindowThread::FdWatchCallback &cb)
94 {
95 WatchedFd wfd;
96 wfd.fd = fd;
97 wfd.callback = cb;
98
99 watched_fds.push_back(wfd);
100 }
101
102 /* Watchdog threads are really not that great, but ping-ponging between
103 * the window and application threads would over-complicate this test */
104 void QuitAfter(unsigned int seconds)
105 {
106 seconds_to_quit = seconds;
107 quit_thread = nux::CreateSystemThread(NULL, TestWindowThread::QuitTask, this);
108 quit_thread->Start(NULL);
109 }
110
111 void WaitForQuit()
112 {
113 struct pollfd pfd;
114
115 pfd.events = POLLIN;
116 pfd.revents = 0;
117 pfd.fd = test_quit_pipe_[0];
118
119 if (poll(&pfd, 1, -1) == -1)
120 throw std::runtime_error(strerror(errno));
121
122 /* Something was written to the read end of our pipe,
123 * we can continue now */
124 }
125
126 private:
127
128 typedef struct _WatchedFd
129 {
130 int fd;
131 nux::WindowThread::FdWatchCallback callback;
132 } WatchedFd;
133
134 static void WindowThreadInit(nux::NThread *, void *data)
135 {
136 TestWindowThread *tw = reinterpret_cast <TestWindowThread *>(data);
137
138 /* Monitor all nominated fd's */
139 for (std::vector<WatchedFd>::iterator it = tw->watched_fds.begin();
140 it != tw->watched_fds.end();
141 ++it)
142 {
143 tw->wnd_thread->WatchFdForEvents(it->fd, it->callback);
144 }
145 }
146
147 static void QuitTask(nux::NThread *, void *data)
148 {
149 TestWindowThread *tw = reinterpret_cast <TestWindowThread *> (data);
150
151 nux::SleepForMilliseconds(tw->seconds_to_quit * 1000);
152
153 const char *buf = "w\0";
154 if (write(tw->quit_pipe_[1],
155 reinterpret_cast<void *>(const_cast<char *>(buf)),
156 2) == -1)
157 throw std::runtime_error(strerror(errno));
158 }
159
160 void QuitMessage()
161 {
162 wnd_thread->ExitMainLoop();
163
164 /* Send a message indicating that the main loop
165 * exited and we can safely continue */
166 const char *buf = "w\0";
167 if (write(test_quit_pipe_[1],
168 reinterpret_cast<void *>(const_cast<char *>(buf)),
169 2) == -1)
170 throw std::runtime_error(strerror(errno));
171 }
172
173 std::vector<WatchedFd> watched_fds;
174
175 int quit_pipe_[2];
176 int test_quit_pipe_[2];
177};
178
179TEST_F(TestWindowThread, Create)
180{
22 ASSERT_TRUE(wnd_thread != NULL);181 ASSERT_TRUE(wnd_thread != NULL);
23 EXPECT_EQ(wnd_thread->GetWindowTitle(), std::string("Nux Window"));182 EXPECT_EQ(wnd_thread->GetWindowTitle(), std::string("Nux Window"));
24 EXPECT_EQ(wnd_thread->IsModalWindow(), false);183 EXPECT_EQ(wnd_thread->IsModalWindow(), false);
25184
26 EXPECT_EQ(wnd_thread->IsComputingLayout(), false);185 EXPECT_EQ(wnd_thread->IsComputingLayout(), false);
27 EXPECT_EQ(wnd_thread->IsInsideLayoutCycle(), false);186 EXPECT_EQ(wnd_thread->IsInsideLayoutCycle(), false);
28187}
29 delete wnd_thread;188
189TEST_F(TestWindowThread, WatchFd)
190{
191 int pipefd[2];
192
193 if (pipe2 (pipefd, O_CLOEXEC) == -1)
194 throw std::runtime_error(strerror(errno));
195
196 MockFdDispatch dispatch(pipefd[0]);
197
198 EXPECT_CALL(dispatch, dispatch());
199
200 WatchFdForEventsOnRun(pipefd[0], std::bind(&MockFdDispatch::dispatch, &dispatch));
201 const char *buf = "w\0";
202 if (write(pipefd[1], reinterpret_cast <void *> (const_cast <char *> (buf)), 2) == -1)
203 throw std::runtime_error (strerror(errno));
204
205 QuitAfter(3);
206 wnd_thread->Run(NULL);
207
208 /* We must wait for quit before closing the pipes */
209 WaitForQuit();
210
211 close (pipefd[0]);
212 close (pipefd[1]);
213}
214
215TEST_F(TestWindowThread, MultiWatchFd)
216{
217 int pipefd[2], pipefd2[2];
218
219 if (pipe2 (pipefd, O_CLOEXEC) == -1)
220 throw std::runtime_error(strerror(errno));
221 if (pipe2 (pipefd2, O_CLOEXEC) == -1)
222 throw std::runtime_error(strerror(errno));
223
224 MockFdDispatch dispatch(pipefd[0]), dispatch2(pipefd2[0]);
225
226 EXPECT_CALL(dispatch, dispatch());
227 EXPECT_CALL(dispatch2, dispatch());
228
229 WatchFdForEventsOnRun(pipefd[0], std::bind(&MockFdDispatch::dispatch, &dispatch));
230 WatchFdForEventsOnRun(pipefd2[0], std::bind(&MockFdDispatch::dispatch, &dispatch2));
231 const char *buf = "w\0";
232 if (write(pipefd[1], reinterpret_cast <void *> (const_cast <char *> (buf)), 2) == -1)
233 throw std::runtime_error (strerror(errno));
234 if (write(pipefd2[1], reinterpret_cast <void *> (const_cast <char *> (buf)), 2) == -1)
235 throw std::runtime_error (strerror(errno));
236
237 QuitAfter(3);
238 wnd_thread->Run(NULL);
239
240 /* We must wait for quit before closing the pipes */
241 WaitForQuit();
242
243 close (pipefd[0]);
244 close (pipefd[1]);
245 close (pipefd2[0]);
246 close (pipefd2[1]);
247}
248
249TEST_F(TestWindowThread, OneFdEvent)
250{
251 int pipefd[2], pipefd2[2];
252
253 if (pipe2 (pipefd, O_CLOEXEC) == -1)
254 throw std::runtime_error(strerror(errno));
255 if (pipe2 (pipefd2, O_CLOEXEC) == -1)
256 throw std::runtime_error(strerror(errno));
257
258 MockFdDispatch dispatch(pipefd[0]), dispatch2(pipefd2[0]);
259
260 EXPECT_CALL(dispatch, dispatch());
261 EXPECT_CALL(dispatch2, dispatch()).Times(0);
262
263 WatchFdForEventsOnRun(pipefd[0], std::bind(&MockFdDispatch::dispatch, &dispatch));
264 WatchFdForEventsOnRun(pipefd2[0], std::bind(&MockFdDispatch::dispatch, &dispatch2));
265 const char *buf = "w\0";
266 if (write(pipefd[1], reinterpret_cast <void *> (const_cast <char *> (buf)), 2) == -1)
267 throw std::runtime_error (strerror(errno));
268
269 QuitAfter(3);
270 wnd_thread->Run(NULL);
271
272 /* We must wait for quit before closing the pipes */
273 WaitForQuit();
274
275 close (pipefd[0]);
276 close (pipefd[1]);
277 close (pipefd2[0]);
278 close (pipefd2[1]);
30}279}
31280
32281
33282
=== modified file 'tests/xtest-text-entry.cpp'
--- tests/xtest-text-entry.cpp 2012-10-01 23:37:25 +0000
+++ tests/xtest-text-entry.cpp 2013-02-01 13:11:21 +0000
@@ -29,6 +29,12 @@
29#include <X11/keysym.h> 29#include <X11/keysym.h>
30#include "nux_automated_test_framework.h"30#include "nux_automated_test_framework.h"
3131
32namespace
33{
34std::string RESET_KEY_FOCUS_AREA_MSG = "r";
35std::string KEY_FOCUS_AREA_RESET_MSG = "d";
36}
37
32class TextTextEntry: public ProgramTemplate38class TextTextEntry: public ProgramTemplate
33{39{
34public:40public:
@@ -41,12 +47,19 @@
41 void OnActivated();47 void OnActivated();
42 void OnCursorMoved(int);48 void OnCursorMoved(int);
43 void ResetEvents();49 void ResetEvents();
50 void SendResetKeyFocusAreaMessageToProgram();
51
4452
45 nux::TextEntry* text_entry_;53 nux::TextEntry* text_entry_;
4654
47 bool clicked_;55 bool clicked_;
48 bool activated_;56 bool activated_;
49 bool cursor_moved_;57 bool cursor_moved_;
58
59private:
60
61 /* Handled inside the TextEntry WindowThread */
62 void HandleProgramMessage(const std::string &);
50};63};
5164
52TextTextEntry::TextTextEntry(const char* program_name,65TextTextEntry::TextTextEntry(const char* program_name,
@@ -74,6 +87,22 @@
74 cursor_moved_ = false;87 cursor_moved_ = false;
75}88}
7689
90void TextTextEntry::SendResetKeyFocusAreaMessageToProgram()
91{
92 SendMessageToProgram(RESET_KEY_FOCUS_AREA_MSG);
93 WaitForMessageFromProgram(KEY_FOCUS_AREA_RESET_MSG);
94}
95
96void TextTextEntry::HandleProgramMessage(const std::string &m)
97{
98 if (m == RESET_KEY_FOCUS_AREA_MSG)
99 {
100 GetWindowThread()->GetWindowCompositor().SetKeyFocusArea(NULL);
101 GetWindowThread()->GetWindowCompositor().SetKeyFocusArea(text_entry_);
102 SendMessageToTest(KEY_FOCUS_AREA_RESET_MSG);
103 }
104}
105
77void TextTextEntry::TextEntryClick(nux::TextEntry* text_entry)106void TextTextEntry::TextEntryClick(nux::TextEntry* text_entry)
78{107{
79 if (text_entry_ == text_entry)108 if (text_entry_ == text_entry)
@@ -185,8 +214,7 @@
185 test.ViewSendString("Nux");214 test.ViewSendString("Nux");
186 test.TestReportMsg(test_textentry->text_entry_->GetText() == "Nux", "Typed \"Nux\"");215 test.TestReportMsg(test_textentry->text_entry_->GetText() == "Nux", "Typed \"Nux\"");
187216
188 test_textentry->GetWindowThread()->GetWindowCompositor().SetKeyFocusArea(NULL); 217 test_textentry->SendResetKeyFocusAreaMessageToProgram();
189 test_textentry->GetWindowThread()->GetWindowCompositor().SetKeyFocusArea(test_textentry->text_entry_);
190218
191 test_textentry->ResetEvents();219 test_textentry->ResetEvents();
192 test.ViewSendLeft();220 test.ViewSendLeft();
@@ -205,8 +233,7 @@
205 test.ViewSendString("Nux");233 test.ViewSendString("Nux");
206 test.TestReportMsg(test_textentry->text_entry_->GetText() == "Nux", "Typed \"Nux\"");234 test.TestReportMsg(test_textentry->text_entry_->GetText() == "Nux", "Typed \"Nux\"");
207235
208 test_textentry->GetWindowThread()->GetWindowCompositor().SetKeyFocusArea(NULL); 236 test_textentry->SendResetKeyFocusAreaMessageToProgram();
209 test_textentry->GetWindowThread()->GetWindowCompositor().SetKeyFocusArea(test_textentry->text_entry_);
210237
211 test_textentry->ResetEvents();238 test_textentry->ResetEvents();
212 test.ViewSendReturn();239 test.ViewSendReturn();

Subscribers

People subscribed via source and target branches