diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | Makefile.in | 18 | ||||
-rw-r--r-- | configure.in | 28 | ||||
-rw-r--r-- | xbmc/windowing/tests/wayland/Makefile.in | 43 | ||||
-rw-r--r-- | xbmc/windowing/tests/wayland/TestEGLNativeTypeWayland.cpp | 1053 | ||||
-rw-r--r-- | xbmc/windowing/tests/wayland/XBMCWaylandTestExtension.cpp | 792 | ||||
-rw-r--r-- | xbmc/windowing/tests/wayland/protocol.xml | 63 |
7 files changed, 1996 insertions, 5 deletions
diff --git a/.gitignore b/.gitignore index 7f59570149..40a084444a 100644 --- a/.gitignore +++ b/.gitignore @@ -723,6 +723,10 @@ lib/cmyth/Makefile # /xbmc/windowing/ /xbmc/windowing/Makefile /xbmc/windowing/egl/Makefile +/xbmc/windowing/tests/wayland/xbmc_wayland_test_client_protocol.h +/xbmc/windowing/tests/wayland/xbmc_wayland_test_protocol.c +/xbmc/windowing/tests/wayland/xbmc_wayland_test_server_protocol.h +/xbmc/windowing/tests/wayland/Makefile # /lib/ffmpeg/ /lib/ffmpeg/config.h diff --git a/Makefile.in b/Makefile.in index 3b21d50a72..77cf789abb 100644 --- a/Makefile.in +++ b/Makefile.in @@ -307,10 +307,20 @@ CHECK_LIBS = xbmc/filesystem/test/filesystemTest.a \ xbmc/utils/test/utilsTest.a \ xbmc/threads/test/threadTest.a \ xbmc/interfaces/python/test/pythonSwigTest.a \ + xbmc/windowing/tests/wayland/test_wayland.a \ xbmc/test/xbmc-test.a + +ifeq (@USE_WAYLAND_TEST_EXTENSION@,1) +WAYLAND_TEST_MODULE = xbmc/windowing/tests/wayland/xbmc-wayland-test-extension.so +$(WAYLAND_TEST_MODULE): force + $(MAKE) -C $(@D) $(@F) +CHECK_EXTENSIONS = $(WAYLAND_TEST_MODULE) +CHECK_LIBADD=@WAYLAND_TEST_LIBS@ +endif + CHECK_PROGRAMS = xbmc-test -CLEAN_FILES += $(CHECK_PROGRAMS) +CLEAN_FILES += $(CHECK_PROGRAMS) $(CHECK_EXTENSIONS) all : $(FINAL_TARGETS) @echo '-----------------------' @@ -674,7 +684,7 @@ ifeq (1,@GTEST_CONFIGURED@) check: testsuite for check_program in $(CHECK_PROGRAMS); do $(CURDIR)/$$check_program; done -testsuite: $(CHECK_PROGRAMS) +testsuite: $(CHECK_EXTENSIONS) $(CHECK_PROGRAMS) testframework: $(GTEST_LIBS) @@ -688,9 +698,9 @@ $(CHECK_LIBS): force xbmc-test: $(CHECK_LIBS) $(OBJSXBMC) $(DYNOBJSXBMC) $(NWAOBJSXBMC) $(GTEST_LIBS) ifeq ($(findstring osx,@ARCH@), osx) - $(SILENT_LD) $(CXX) $(LDFLAGS) $(GTEST_INCLUDES) -o $@ -Wl,-all_load,-ObjC $(CHECK_LIBS) $(DYNOBJSXBMC) $(NWAOBJSXBMC) $(OBJSXBMC) $(GTEST_LIBS) $(LIBS) -rdynamic + $(SILENT_LD) $(CXX) $(LDFLAGS) $(GTEST_INCLUDES) -o $@ -Wl,-all_load,-ObjC $(DYNOBJSXBMC) $(NWAOBJSXBMC) $(OBJSXBMC) $(GTEST_LIBS) $(CHECK_LIBS) $(LIBS) $(CHECK_LIBADD) -rdynamic else - $(SILENT_LD) $(CXX) $(CXXFLAGS) $(LDFLAGS) $(GTEST_INCLUDES) -o $@ -Wl,--whole-archive $(CHECK_LIBS) $(DYNOBJSXBMC) $(OBJSXBMC) -Wl,--no-whole-archive $(NWAOBJSXBMC) $(GTEST_LIBS) $(LIBS) -rdynamic + $(SILENT_LD) $(CXX) $(CXXFLAGS) $(LDFLAGS) $(GTEST_INCLUDES) -o $@ -Wl,--whole-archive $(DYNOBJSXBMC) $(OBJSXBMC) $(GTEST_LIBS) $(CHECK_LIBS) -Wl,--no-whole-archive $(NWAOBJSXBMC) $(LIBS) $(CHECK_LIBADD) -rdynamic endif else # Give a message that the framework is not configured, but don't fail. diff --git a/configure.in b/configure.in index 99319d7e6d..521bd3807a 100644 --- a/configure.in +++ b/configure.in @@ -985,12 +985,37 @@ if test "$use_wayland" = "yes" && test "$host_vendor" != "apple"; then AC_DEFINE([HAVE_WAYLAND], [1], [Define to 1 if you have Wayland libs installed.]) AC_DEFINE([HAVE_XKBCOMMON], [1], [Define to 1 if you have libxkbcommon installed.]) + # If we are also building with tests then we want to build + # wayland tests as well + if test "$configure_gtest" = "yes"; then + have_weston_sdk=no; + PKG_CHECK_MODULES([PIXMAN], + [pixman-1],have_pixman=yes, + [AC_MSG_WARN($missing_library); have_pixman=no]) + PKG_CHECK_MODULES([WESTON], + [weston >= 1.1.90],[have_weston_sdk=yes], + [have_weston_sdk=no; AC_MSG_WARN($missing_library)]) + + AC_CHECK_PROG(WAYLAND_SCANNER, wayland-scanner, "wayland-scanner", "no") + if test "x$WAYLAND_SCANNER" == "xno"; then + AC_MSG_WARN($missing_program) + else + if test "x$have_weston_sdk" == "xyes" && test "x$have_pixman" = "xyes"; then + AC_SUBST(WAYLAND_TEST_INCLUDES,"$WAYLAND_CLIENT_CFLAGS $XKBCOMMON_CFLAGS $PIXMAN_CFLAGS $WESTON_CFLAGS") + AC_SUBST(WAYLAND_TEST_LIBS,"$WAYLAND_CLIENT_LIBS $XKBCOMMON_LIBS $PIXMAN_LIBS $WESTON_LIBS") + AC_DEFINE([HAVE_WESTON_SDK], [1], [Define to 1 if Weston SDK is installed.]) + AC_SUBST(USE_WAYLAND_TEST_EXTENSION, 1) + fi + AC_SUBST(WAYLAND_SCANNER) + AC_DEFINE([HAVE_WAYLAND_XBMC_PROTO],[1],["Define to 1 if the wayland test-protocol will be built"]) + fi + fi + # Disable SDL and X11 builds use_sdl=no use_joystick=no use_x11=no - # Wayland requires the EGL "window system" which in turn only supports # the OpenGL ES API, so enable gles support use_gles=yes @@ -2530,6 +2555,7 @@ OUTPUT_FILES="Makefile \ xbmc/android/jni/Makefile \ xbmc/utils/Makefile \ xbmc/main/Makefile \ + xbmc/windowing/tests/wayland/Makefile \ project/cmake/xbmc-config.cmake" if test "$use_skin_touched" = "yes"; then diff --git a/xbmc/windowing/tests/wayland/Makefile.in b/xbmc/windowing/tests/wayland/Makefile.in new file mode 100644 index 0000000000..1aadd7b300 --- /dev/null +++ b/xbmc/windowing/tests/wayland/Makefile.in @@ -0,0 +1,43 @@ +ifeq (@USE_WAYLAND@,1) +SRCS = TestEGLNativeTypeWayland.cpp + +WAYLAND_TEST_MODULE_PROTOCOL = protocol.xml +WAYLAND_TEST_MODULE_PROTOCOL_SRCS = xbmc_wayland_test_protocol.c +WAYLAND_TEST_MODULE_PROTOCOL_GENERATED_SRCS = $(WAYLAND_TEST_MODULE_PROTOCOL_SRCS) +WAYLAND_TEST_MODULE_PROTOCOL_GENERATED_SRCS += xbmc_wayland_test_server_protocol.h +WAYLAND_TEST_MODULE_PROTOCOL_GENERATED_SRCS += xbmc_wayland_test_client_protocol.h +WAYLAND_TEST_MODULE_SRCS = XBMCWaylandTestExtension.cpp + +INCLUDES += -I@abs_top_srcdir@/lib/gtest/include -I@WAYLAND_TEST_INCLUDES@ +LIB = test_wayland.a + +ifneq (,@WAYLAND_SCANNER@) +SRCS += $(WAYLAND_TEST_MODULE_PROTOCOL_SRCS) +endif + +CLEAN_FILES += $(WAYLAND_TEST_MODULE_PROTOCOL_GENERATED_SRCS) xbmc-wayland-test-extension.so + +include @abs_top_srcdir@/Makefile.include +-include $(patsubst %.cpp,%.P,$(patsubst %.c,%.P,$(patsubst %.h,%.P,$(SRCS)))) + +ifneq (,@WAYLAND_SCANNER@) +TestEGLNativeTypeWayland.cpp : xbmc_wayland_test_client_protocol.h + +$(WAYLAND_TEST_MODULE_SRCS) : $(WAYLAND_TEST_MODULE_PROTOCOL_GENERATED_SRCS) + +xbmc_wayland_test_protocol.c: $(WAYLAND_TEST_MODULE_PROTOCOL) + @WAYLAND_SCANNER@ code < $< > $@ + +xbmc_wayland_test_server_protocol.h: $(WAYLAND_TEST_MODULE_PROTOCOL) + @WAYLAND_SCANNER@ server-header < $< > $@ + +xbmc_wayland_test_client_protocol.h: $(WAYLAND_TEST_MODULE_PROTOCOL) + @WAYLAND_SCANNER@ client-header < $< > $@ + +ifeq (@USE_WAYLAND_TEST_EXTENSION@,1) +xbmc-wayland-test-extension.so: $(WAYLAND_TEST_MODULE_PROTOCOL_SRCS:.c=.o) $(WAYLAND_TEST_MODULE_SRCS:.cpp=.o) + $(SILENT_LD) $(CXX) $(CXXFLAGS) $(LDFLAGS) $(WAYLAND_TEST_LIBS) -shared -o $@ $+ -rdynamic +endif + +endif +endif diff --git a/xbmc/windowing/tests/wayland/TestEGLNativeTypeWayland.cpp b/xbmc/windowing/tests/wayland/TestEGLNativeTypeWayland.cpp new file mode 100644 index 0000000000..9bc4162b66 --- /dev/null +++ b/xbmc/windowing/tests/wayland/TestEGLNativeTypeWayland.cpp @@ -0,0 +1,1053 @@ +/* +* Copyright (C) 2005-2013 Team XBMC +* http://www.xbmc.org +* +* This Program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. +* +* This Program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with XBMC; see the file COPYING. If not, see +* <http://www.gnu.org/licenses/>. +* +*/ +#define WL_EGL_PLATFORM + +#include "system.h" + +/* HAVE_WAYLAND is implicit in HAVE_WAYLAND_XBMC_PROTO */ +#if defined(HAVE_WAYLAND_XBMC_PROTO) + +#include <sstream> +#include <stdexcept> + +#include <iostream> + +#include <boost/array.hpp> +#include <boost/bind.hpp> +#include <boost/tokenizer.hpp> + +#include <gtest/gtest.h> + +#include <unistd.h> +#include <signal.h> + +#include <sys/types.h> +#include <sys/wait.h> + +#include <wayland-client.h> +#include <wayland-client-protocol.h> +#include "xbmc_wayland_test_client_protocol.h" + +#include "windowing/egl/wayland/Callback.h" +#include "windowing/egl/wayland/Display.h" +#include "windowing/egl/wayland/Registry.h" +#include "windowing/egl/wayland/Surface.h" +#include "windowing/egl/EGLNativeTypeWayland.h" +#include "windowing/WinEvents.h" + +#include "windowing/DllWaylandClient.h" + +#include "utils/StringUtils.h" +#include "test/TestUtils.h" + +namespace +{ +static const int DefaultProcessWaitTimeout = 3000; // 3000ms +} + +namespace xbmc +{ +namespace test +{ +namespace wayland +{ +class XBMCWayland : + boost::noncopyable +{ +public: + + XBMCWayland(struct xbmc_wayland *xbmcWayland); + ~XBMCWayland(); + + struct wl_surface * MostRecentSurface(); + + void AddMode(int width, + int height, + uint32_t refresh, + enum wl_output_mode mode); + void MovePointerTo(struct wl_surface *surface, + wl_fixed_t x, + wl_fixed_t y); + void SendButtonTo(struct wl_surface *surface, + uint32_t button, + uint32_t state); + void SendAxisTo(struct wl_surface *, + uint32_t axis, + wl_fixed_t value); + void SendKeyToKeyboard(struct wl_surface *surface, + uint32_t key, + enum wl_keyboard_key_state state); + void SendModifiersToKeyboard(struct wl_surface *surface, + uint32_t depressed, + uint32_t latched, + uint32_t locked, + uint32_t group); + void GiveSurfaceKeyboardFocus(struct wl_surface *surface); + void PingSurface (struct wl_surface *surface, + uint32_t serial); + +private: + + struct xbmc_wayland *m_xbmcWayland; +}; +} + +class Process : + boost::noncopyable +{ +public: + + Process(const CStdString &base, + const CStdString &socket); + ~Process(); + + void WaitForSignal(int signal, int timeout); + void WaitForStatus(int status, int timeout); + + void Interrupt(); + void Terminate(); + void Kill(); + + pid_t Pid(); + +private: + + void SendSignal(int signal); + + void Child(const char *program, + char * const *options); + void ForkError(); + void Parent(); + + pid_t m_pid; +}; +} +} + +namespace xt = xbmc::test; +namespace xw = xbmc::wayland; +namespace xtw = xbmc::test::wayland; +namespace xwe = xbmc::wayland::events; + +xtw::XBMCWayland::XBMCWayland(struct xbmc_wayland *xbmcWayland) : + m_xbmcWayland(xbmcWayland) +{ +} + +xtw::XBMCWayland::~XBMCWayland() +{ + xbmc_wayland_destroy(m_xbmcWayland); +} + +void +xtw::XBMCWayland::AddMode(int width, + int height, + uint32_t refresh, + enum wl_output_mode flags) +{ + xbmc_wayland_add_mode(m_xbmcWayland, + width, + height, + refresh, + static_cast<uint32_t>(flags)); +} + +void +xtw::XBMCWayland::MovePointerTo(struct wl_surface *surface, + wl_fixed_t x, + wl_fixed_t y) +{ + xbmc_wayland_move_pointer_to_on_surface(m_xbmcWayland, + surface, + x, + y); +} + +void +xtw::XBMCWayland::SendButtonTo(struct wl_surface *surface, + uint32_t button, + uint32_t state) +{ + xbmc_wayland_send_button_to_surface(m_xbmcWayland, + surface, + button, + state); +} + +void +xtw::XBMCWayland::SendAxisTo(struct wl_surface *surface, + uint32_t axis, + wl_fixed_t value) +{ + xbmc_wayland_send_axis_to_surface(m_xbmcWayland, + surface, + axis, + value); +} + +void +xtw::XBMCWayland::SendKeyToKeyboard(struct wl_surface *surface, + uint32_t key, + enum wl_keyboard_key_state state) +{ + xbmc_wayland_send_key_to_keyboard(m_xbmcWayland, + surface, + key, + state); +} + +void +xtw::XBMCWayland::SendModifiersToKeyboard(struct wl_surface *surface, + uint32_t depressed, + uint32_t latched, + uint32_t locked, + uint32_t group) +{ + xbmc_wayland_send_modifiers_to_keyboard(m_xbmcWayland, + surface, + depressed, + latched, + locked, + group); +} + +void +xtw::XBMCWayland::GiveSurfaceKeyboardFocus(struct wl_surface *surface) +{ + xbmc_wayland_give_surface_keyboard_focus(m_xbmcWayland, + surface); +} + +void +xtw::XBMCWayland::PingSurface(struct wl_surface *surface, + uint32_t serial) +{ + xbmc_wayland_ping_surface(m_xbmcWayland, surface, serial); +} + +namespace +{ +class TempFileWrapper : + boost::noncopyable +{ +public: + + TempFileWrapper(const CStdString &suffix); + ~TempFileWrapper(); + + void FetchDirectory(CStdString &directory); + void FetchFilename(CStdString &name); +private: + + XFILE::CFile *m_file; +}; + +TempFileWrapper::TempFileWrapper(const CStdString &suffix) : + m_file(CXBMCTestUtils::Instance().CreateTempFile(suffix)) +{ +} + +TempFileWrapper::~TempFileWrapper() +{ + CXBMCTestUtils::Instance().DeleteTempFile(m_file); +} + +void TempFileWrapper::FetchDirectory(CStdString &directory) +{ + directory = CXBMCTestUtils::Instance().TempFileDirectory(m_file); + /* Strip trailing "/" */ + directory.resize(directory.size() - 1); +} + +void TempFileWrapper::FetchFilename(CStdString &name) +{ + CStdString path(CXBMCTestUtils::Instance().TempFilePath(m_file)); + CStdString directory(CXBMCTestUtils::Instance().TempFileDirectory(m_file)); + + name = path.substr(directory.size()); +} + +class SavedTempSocket : + boost::noncopyable +{ +public: + + SavedTempSocket(); + + const CStdString & FetchFilename(); + const CStdString & FetchDirectory(); + +private: + + CStdString m_filename; + CStdString m_directory; +}; + +SavedTempSocket::SavedTempSocket() +{ + TempFileWrapper wrapper(""); + wrapper.FetchDirectory(m_directory); + wrapper.FetchFilename(m_filename); +} + +const CStdString & +SavedTempSocket::FetchFilename() +{ + return m_filename; +} + +const CStdString & +SavedTempSocket::FetchDirectory() +{ + return m_directory; +} + +class TmpEnv : + boost::noncopyable +{ +public: + + TmpEnv(const char *env, const char *val); + ~TmpEnv(); + +private: + + const char *m_env; + const char *m_previous; +}; + +TmpEnv::TmpEnv(const char *env, + const char *val) : + m_env(env), + m_previous(getenv(env)) +{ + setenv(env, val, 1); +} + +TmpEnv::~TmpEnv() +{ + if (m_previous) + setenv(m_env, m_previous, 1); + else + unsetenv(m_env); +} + +std::string +FindBinaryFromPATH(const std::string &binary) +{ + const char *pathEnvironmentCArray = getenv("PATH"); + if (!pathEnvironmentCArray) + throw std::runtime_error("PATH is not set"); + + std::string pathEnvironment(pathEnvironmentCArray); + + typedef boost::char_separator<char> CharSeparator; + typedef boost::tokenizer<CharSeparator> CharTokenizer; + + CharTokenizer paths(pathEnvironment, + CharSeparator(":")); + + for (CharTokenizer::iterator it = paths.begin(); + it != paths.end(); + ++it) + { + std::stringstream possibleBinaryLocationStream; + possibleBinaryLocationStream << *it + << "/" + << binary; + std::string possibleBinaryLocation(possibleBinaryLocationStream.str()); + int ok = access(possibleBinaryLocation.c_str(), X_OK); + + if (ok == -1) + { + switch (errno) + { + case EACCES: + case ENOENT: + continue; + default: + throw std::runtime_error(strerror(errno)); + } + } + + return possibleBinaryLocation.c_str(); + } + + std::stringstream ss; + ss << "Unable to find " + << binary + << " in PATH as it does not exist or is not executable"; + + throw std::runtime_error(ss.str()); +} +} + +xt::Process::Process(const CStdString &xbmcTestBase, + const CStdString &tempFileName) : + m_pid(0) +{ + std::stringstream socketOptionStream; + socketOptionStream << "--socket="; + socketOptionStream << tempFileName.c_str(); + + std::string socketOption(socketOptionStream.str()); + + std::stringstream modulesOptionStream; + modulesOptionStream << "--modules="; + modulesOptionStream << xbmcTestBase.c_str(); + modulesOptionStream << "xbmc/windowing/tests/wayland/xbmc-wayland-test-extension.so"; + + std::string modulesOption(modulesOptionStream.str()); + + std::string program(FindBinaryFromPATH("weston")); + const char *options[] = + { + program.c_str(), + "--backend=headless-backend.so", + modulesOption.c_str(), + socketOption.c_str(), + NULL + }; + + m_pid = fork(); + + switch (m_pid) + { + case 0: + Child(program.c_str(), + const_cast <char * const *>(options)); + case -1: + ForkError(); + default: + Parent(); + } +} + +pid_t +xt::Process::Pid() +{ + return m_pid; +} + +void +xt::Process::Child(const char *program, + char * const *options) +{ + signal(SIGUSR2, SIG_IGN); + + /* Unblock SIGUSR2 */ + sigset_t signalMask; + sigemptyset(&signalMask); + sigaddset(&signalMask, SIGUSR2); + if (sigprocmask(SIG_UNBLOCK, &signalMask, NULL)) + { + std::stringstream ss; + ss << "sigprocmask: " << strerror(errno); + throw std::runtime_error(ss.str()); + } + + if (!getenv("XBMC_WESTON_GTEST_CHILD_STDOUT")) + { + close(STDOUT_FILENO); + close(STDERR_FILENO); + } + + if (execvpe(program, options, environ) == -1) + { + std::stringstream ss; + ss << "execvpe: " << strerror(errno); + throw std::runtime_error(ss.str()); + } +} + +void +xt::Process::Parent() +{ +} + +void +xt::Process::ForkError() +{ + std::stringstream ss; + ss << "fork: " + << strerror(errno); + throw std::runtime_error(ss.str()); +} + +void +xt::Process::WaitForSignal(int signal, int timeout) +{ + sigset_t signalMask; + + if (timeout >= 0) + { + static const uint32_t MsecToNsec = 1000000; + static const uint32_t SecToMsec = 1000; + int seconds = timeout / SecToMsec; + + /* Remove seconds from timeout */ + timeout -= seconds * SecToMsec; + struct timespec ts = { seconds, timeout * MsecToNsec }; + + sigemptyset(&signalMask); + sigaddset(&signalMask, signal); + int received = 0; + + do + { + errno = 0; + received = sigtimedwait(&signalMask, + NULL, + &ts); + if (received == -1) + { + /* Just retry if we got signalled */ + if (errno != EINTR) + { + std::stringstream ss; + ss << "sigtimedwait: " + << strerror(errno); + + throw std::runtime_error(ss.str()); + } + } + } while (errno != 0); + + return; + } + else + { + sigemptyset(&signalMask); + sigaddset(&signalMask, signal); + errno = 0; + int received = sigwaitinfo(&signalMask, NULL); + + if (received != signal) + { + std::stringstream ss; + ss << "sigwaitinfo: " + << strerror(errno); + } + } +} + +namespace +{ +void +WestonMisbehaviourMessage(std::stringstream &ss) +{ + ss << std::endl; + ss << "It is possible that Weston is just shutting down uncleanly " + << " - you should check the stacktrace and run with " + << " ALLOW_WESTON_MISBEHAVIOUR set to suppress this"; +} + +bool +NoMisbehaviour() +{ + return !getenv("ALLOW_WESTON_MISBEHAVIOUR"); +} + +class StatusWaitTimeoutError : + public std::exception +{ +public: + + StatusWaitTimeoutError(int expected, int timeout); + ~StatusWaitTimeoutError() throw() {} + +private: + + const char * what() const throw(); + + int m_expected; + int m_timeout; + + mutable std::string m_what; +}; + +class TerminatedBySignalError : + public std::exception +{ +public: + + TerminatedBySignalError(int expected, int signal); + ~TerminatedBySignalError() throw() {} + +private: + + const char * what() const throw(); + + int m_expected; + int m_signal; + + mutable std::string m_what; +}; + +class AbnormalExitStatusError : + public std::exception +{ +public: + + AbnormalExitStatusError(int expected, int status); + ~AbnormalExitStatusError() throw() {} + +private: + + const char * what() const throw(); + + int m_expected; + int m_status; + + mutable std::string m_what; +}; + +StatusWaitTimeoutError::StatusWaitTimeoutError(int expected, + int timeout) : + m_expected(expected), + m_timeout(timeout) +{ +} + +const char * +StatusWaitTimeoutError::what() const throw() +{ + std::stringstream ss; + ss << "Expected exit status " + << m_expected + << " within " + << m_timeout + << " ms"; + m_what = ss.str(); + return m_what.c_str(); +} + +TerminatedBySignalError::TerminatedBySignalError(int expected, + int signal) : + m_expected(expected), + m_signal(signal) +{ +} + +const char * +TerminatedBySignalError::what() const throw() +{ + std::stringstream ss; + ss << "Expected exit status " + << m_expected + << " but was instead terminated by signal " + << m_signal + << " - " + << strsignal(m_signal); + WestonMisbehaviourMessage(ss); + m_what = ss.str(); + return m_what.c_str(); +} + +AbnormalExitStatusError::AbnormalExitStatusError(int expected, + int status) : + m_expected(expected), + m_status(status) +{ +} + +const char * +AbnormalExitStatusError::what() const throw() +{ + std::stringstream ss; + ss << "Expected exit status " + << m_expected + << " but instead exited with " + << m_status + << " - " + << strsignal(m_status); + WestonMisbehaviourMessage(ss); + m_what = ss.str(); + return m_what.c_str(); +} +} + +void +xt::Process::WaitForStatus(int code, int timeout) +{ + struct timespec startTime; + struct timespec currentTime; + clock_gettime(CLOCK_MONOTONIC, &startTime); + clock_gettime(CLOCK_MONOTONIC, ¤tTime); + + const uint32_t SecToMsec = 1000; + const uint32_t MsecToNsec = 1000000; + + int32_t startTimestamp = startTime.tv_sec * SecToMsec + + startTime.tv_nsec / MsecToNsec; + int32_t currentTimestamp = currentTime.tv_sec * SecToMsec + + currentTime.tv_nsec / MsecToNsec; + + int options = WUNTRACED; + + std::stringstream statusMessage; + + if (timeout >= 0) + options |= WNOHANG; + + do + { + clock_gettime(CLOCK_MONOTONIC, ¤tTime); + + currentTimestamp = currentTime.tv_sec * SecToMsec + + currentTime.tv_nsec / MsecToNsec; + + int returnedStatus; + pid_t pid = waitpid(m_pid, &returnedStatus, options); + + if (pid == m_pid) + { + /* At least one child has exited */ + if (WIFEXITED(returnedStatus)) + { + int returnedExitCode = WEXITSTATUS(returnedStatus); + if (returnedExitCode == code) + return; + + /* Abnormal exit status */ + throw AbnormalExitStatusError(code, returnedExitCode); + } + else if (WIFSIGNALED(returnedStatus)) + { + int returnedSignal = WTERMSIG(returnedStatus); + + /* Signaled and died */ + throw TerminatedBySignalError(code, returnedSignal); + } + } + else if (pid == -1) + { + std::stringstream ss; + ss << "waitpid failed: " + << strerror(errno); + throw std::runtime_error(ss.str()); + } + else if (!pid) + { + struct timespec ts; + ts.tv_sec = 0; + + /* Don't sleep the whole time, we might have just missed + * the signal */ + ts.tv_nsec = timeout * MsecToNsec / 10; + + nanosleep(&ts, NULL); + } + } + while (timeout == -1 || + (timeout > currentTimestamp - startTimestamp)); + + /* If we didn't get out early, it means we timed out */ + throw StatusWaitTimeoutError(code, timeout); +} + +void +xt::Process::SendSignal(int signal) +{ + if (kill(m_pid, signal) == -1) + { + /* Already dead ... lets see if it exited normally */ + if (errno == ESRCH) + { + try + { + WaitForStatus(0, 0); + } + catch (std::runtime_error &err) + { + std::stringstream ss; + ss << err.what() + << " - process was already dead" + << std::endl; + throw std::runtime_error(ss.str()); + } + } + else + { + std::stringstream ss; + ss << "failed to send signal " + << signal + << " to process " + << m_pid + << ": " << strerror(errno); + throw std::runtime_error(ss.str()); + } + } +} + +void +xt::Process::Interrupt() +{ + SendSignal(SIGINT); +} + +void +xt::Process::Terminate() +{ + SendSignal(SIGTERM); +} + +void +xt::Process::Kill() +{ + SendSignal(SIGKILL); +} + +xt::Process::~Process() +{ + typedef void (Process::*SignalAction)(void); + SignalAction deathActions[] = + { + &Process::Interrupt, + &Process::Terminate, + &Process::Kill + }; + + static const size_t deathActionsSize = sizeof(deathActions) / + sizeof(deathActions[0]); + + size_t i = 0; + + std::stringstream processStatusMessages; + + for (; i < deathActionsSize; ++i) + { + try + { + SignalAction func(deathActions[i]); + ((*this).*(func))(); + WaitForStatus(0, DefaultProcessWaitTimeout); + break; + } + catch (const TerminatedBySignalError &err) + { + if (NoMisbehaviour()) + throw; + else + break; + } + catch (const AbnormalExitStatusError &err) + { + if (NoMisbehaviour()) + throw; + else + break; + } + catch (const StatusWaitTimeoutError &err) + { + processStatusMessages << "[TIMEOUT] " + << static_cast<const std::exception &>(err).what() + << std::endl; + } + } + + if (i == deathActionsSize) + { + std::stringstream ss; + ss << "Failed to terminate " + << m_pid + << std::endl; + ss << processStatusMessages; + throw std::runtime_error(ss.str()); + } +} + +namespace +{ +template <typename Iterator> +class SignalGuard : + boost::noncopyable +{ +public: + + SignalGuard(const Iterator &begin, + const Iterator &end); + ~SignalGuard(); +private: + + sigset_t mask; +}; + +template <typename Iterator> +SignalGuard<Iterator>::SignalGuard(const Iterator &begin, + const Iterator &end) +{ + sigemptyset(&mask); + for (Iterator it = begin; + it != end; + ++it) + sigaddset(&mask, *it); + + if (sigprocmask(SIG_BLOCK, &mask, NULL)) + { + std::stringstream ss; + ss << "sigprogmask: " + << strerror(errno); + throw std::runtime_error(ss.str()); + } +} + +template <typename Iterator> +SignalGuard<Iterator>::~SignalGuard() +{ + if (sigprocmask(SIG_UNBLOCK, &mask, NULL)) + CLog::Log(LOGERROR, "Failed to unblock signals"); +} + +typedef boost::array<int, 4> SigArray; +SigArray BlockedSignals = +{ + { + SIGUSR2, + SIGCHLD + } +}; +} + +class WestonTest : + public ::testing::Test +{ +public: + + WestonTest(); + pid_t Pid(); + + virtual void SetUp(); + +protected: + + CEGLNativeTypeWayland m_nativeType; + xw::Display *m_display; + boost::scoped_ptr<xtw::XBMCWayland> m_xbmcWayland; + struct wl_surface *m_mostRecentSurface; + + CStdString m_xbmcTestBase; + SavedTempSocket m_tempSocketName; + TmpEnv m_xdgRuntimeDir; + +private: + + void Global(struct wl_registry *, uint32_t, const char *, uint32_t); + void DisplayAvailable(xw::Display &display); + void SurfaceCreated(xw::Surface &surface); + + SignalGuard<SigArray::iterator> m_signalGuard; + xt::Process m_process; +}; + +WestonTest::WestonTest() : + m_display(NULL), + m_mostRecentSurface(NULL), + m_xbmcTestBase(CXBMCTestUtils::Instance().ReferenceFilePath("")), + /* We want wayland (client and server) to look in our + * temp file directory for the socket */ + m_xdgRuntimeDir("XDG_RUNTIME_DIR", m_tempSocketName.FetchDirectory().c_str()), + /* Block emission of SIGUSR2 so that we can wait on it */ + m_signalGuard(BlockedSignals.begin(), BlockedSignals.end()), + m_process(m_xbmcTestBase, + m_tempSocketName.FetchFilename()) +{ +} + +pid_t +WestonTest::Pid() +{ + return m_process.Pid(); +} + +void +WestonTest::SetUp() +{ + m_process.WaitForSignal(SIGUSR2, DefaultProcessWaitTimeout); +} + +void +WestonTest::Global(struct wl_registry *registry, + uint32_t name, + const char *interface, + uint32_t version) +{ + if (std::string(interface) == "xbmc_wayland") + m_xbmcWayland.reset(new xtw::XBMCWayland(static_cast<xbmc_wayland *>(wl_registry_bind(registry, + name, + &xbmc_wayland_interface, + version)))); +} + +void +WestonTest::DisplayAvailable(xw::Display &display) +{ + m_display = &display; +} + +void +WestonTest::SurfaceCreated(xw::Surface &surface) +{ + m_mostRecentSurface = surface.GetWlSurface(); +} + +TEST_F(WestonTest, TestCheckCompatibilityWithEnvSet) +{ + TmpEnv env("WAYLAND_DISPLAY", m_tempSocketName.FetchFilename().c_str()); + EXPECT_TRUE(m_nativeType.CheckCompatibility()); +} + +TEST_F(WestonTest, TestCheckCompatibilityWithEnvNotSet) +{ + EXPECT_FALSE(m_nativeType.CheckCompatibility()); +} + +class CompatibleWestonTest : + public WestonTest +{ +public: + + CompatibleWestonTest(); + virtual void SetUp(); + +private: + + TmpEnv m_waylandDisplayEnv; +}; + +CompatibleWestonTest::CompatibleWestonTest() : + m_waylandDisplayEnv("WAYLAND_DISPLAY", + m_tempSocketName.FetchFilename().c_str()) +{ +} + +void +CompatibleWestonTest::SetUp() +{ + WestonTest::SetUp(); + ASSERT_TRUE(m_nativeType.CheckCompatibility()); +} + +TEST_F(CompatibleWestonTest, TestConnection) +{ + EXPECT_TRUE(m_nativeType.CreateNativeDisplay()); +} + +#endif diff --git a/xbmc/windowing/tests/wayland/XBMCWaylandTestExtension.cpp b/xbmc/windowing/tests/wayland/XBMCWaylandTestExtension.cpp new file mode 100644 index 0000000000..1520765b66 --- /dev/null +++ b/xbmc/windowing/tests/wayland/XBMCWaylandTestExtension.cpp @@ -0,0 +1,792 @@ +/* +* Copyright (C) 2005-2013 Team XBMC +* http://www.xbmc.org +* +* This Program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. +* +* This Program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with XBMC; see the file COPYING. If not, see +* <http://www.gnu.org/licenses/>. +* +*/ +#include "system.h" + +#include <sstream> +#include <vector> + +#include <boost/scoped_ptr.hpp> +#include <boost/noncopyable.hpp> +#include <boost/function.hpp> +#include <boost/bind.hpp> + +#include <unistd.h> +#include <signal.h> +#include <errno.h> + +extern "C" +{ +/* Work around usage of a reserved keyword + * https://bugs.freedesktop.org/show_bug.cgi?id=63485 */ +#define private cprivate +#include <weston/compositor.h> +#undef private +#include <wayland-server.h> +#include "xbmc_wayland_test_server_protocol.h" +} + +namespace xbmc +{ +namespace test +{ +namespace wayland +{ +class Listener : + boost::noncopyable +{ +public: + + typedef boost::function<void()> Delegate; + + Listener(const Delegate &); + void BindTo(struct wl_signal *); + +private: + + static void MainCallback(struct wl_listener *listener, void *data); + + void Callback(); + + struct wl_listener m_listener; + Delegate m_delegate; +}; +} + +namespace weston +{ +class Compositor : + boost::noncopyable +{ +public: + + Compositor(struct weston_compositor *); + ~Compositor(); + + struct wl_display * Display(); + + struct weston_surface * TopSurface(); + struct weston_mode * LastMode(); + void OnEachMode(const boost::function<void(struct weston_mode *)> &); + struct wl_resource * PointerResource(struct wl_client *client); + struct wl_resource * KeyboardResource(struct wl_client *client); + struct weston_surface * Surface(struct wl_resource *client); + struct weston_keyboard * Keyboard(); + +private: + + static void Unload(Compositor *compositor); + static int Ready(void *); + + struct weston_compositor *m_compositor; + struct wl_event_source *m_readySource; + wayland::Listener m_destroyListener; + struct weston_seat * Seat(); + struct weston_output * FirstOutput(); +}; +} + +namespace wayland +{ +class XBMCWayland : + boost::noncopyable +{ +public: + + ~XBMCWayland(); + + struct wl_resource * GetResource(); + + /* Effectively a factory function for XBMCWayland. Creates an + * instantiation for a client */ + static void BindToClient(struct wl_client *client, + void *data, + uint32_t version, + uint32_t id); + +private: + + /* Constructor is private as this object may only be constructed + * by a client binding to its interface */ + XBMCWayland(struct wl_client *client, + uint32_t id, + weston::Compositor &compositor); + + static void UnbindFromClientCallback(struct wl_resource *); + + static const struct xbmc_wayland_interface m_listener; + + static void AddModeCallback(struct wl_client *, + struct wl_resource *, + int32_t, + int32_t, + uint32_t, + uint32_t); + static void MovePointerToOnSurfaceCallback(struct wl_client *, + struct wl_resource *, + struct wl_resource *, + wl_fixed_t x, + wl_fixed_t y); + static void SendButtonToSurfaceCallback(struct wl_client *, + struct wl_resource *, + struct wl_resource *, + uint32_t, + uint32_t); + static void SendAxisToSurfaceCallback(struct wl_client *, + struct wl_resource *, + struct wl_resource *, + uint32_t, + wl_fixed_t); + static void SendKeyToKeyboardCallback(struct wl_client *, + struct wl_resource *, + struct wl_resource *, + uint32_t, + uint32_t); + static void SendModifiersToKeyboardCallback(struct wl_client *, + struct wl_resource *, + struct wl_resource *, + uint32_t, + uint32_t, + uint32_t, + uint32_t); + static void GiveSurfaceKeyboardFocusCallback(struct wl_client *, + struct wl_resource *, + struct wl_resource *); + static void PingSurfaceCallback(struct wl_client *, + struct wl_resource *, + struct wl_resource *, + uint32_t timestamp); + + void AddMode(struct wl_client *client, + struct wl_resource *resource, + int32_t width, + int32_t height, + uint32_t refresh, + uint32_t flags); + void MovePointerToOnSurface(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface, + wl_fixed_t x, + wl_fixed_t y); + void SendButtonToSurface(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface, + uint32_t button, + uint32_t state); + void SendAxisToSurface(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface, + uint32_t axis, + wl_fixed_t value); + void SendKeyToKeyboard(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surfaceResource, + uint32_t key, + uint32_t state); + void SendModifiersToKeyboard(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surfaceResource, + uint32_t depressed, + uint32_t latched, + uint32_t locked, + uint32_t group); + void GiveSurfaceKeyboardFocus(struct wl_client *clent, + struct wl_resource *resource, + struct wl_resource *surfaceResource); + void PingSurface(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surfaceResource, + uint32_t timestamp); + + weston::Compositor &m_compositor; + struct wl_resource *m_clientXBMCWaylandResource; + + std::vector<struct weston_mode> m_additionalModes; +}; + +const struct xbmc_wayland_interface XBMCWayland::m_listener = +{ + XBMCWayland::AddModeCallback, + XBMCWayland::MovePointerToOnSurfaceCallback, + XBMCWayland::SendButtonToSurfaceCallback, + XBMCWayland::SendAxisToSurfaceCallback, + XBMCWayland::SendKeyToKeyboardCallback, + XBMCWayland::SendModifiersToKeyboardCallback, + XBMCWayland::GiveSurfaceKeyboardFocusCallback, + XBMCWayland::PingSurfaceCallback +}; +} +} +} + +namespace xtw = xbmc::test::wayland; +namespace xtwc = xbmc::test::weston; + +xtw::XBMCWayland::XBMCWayland(struct wl_client *client, + uint32_t id, + xtwc::Compositor &compositor) : + m_compositor(compositor) +{ + m_clientXBMCWaylandResource = + static_cast<struct wl_resource *>(wl_resource_create(client, + &xbmc_wayland_interface, + 1, + id)); + wl_resource_set_implementation (m_clientXBMCWaylandResource, + &m_listener, + this, + XBMCWayland::UnbindFromClientCallback); +} + +xtw::XBMCWayland::~XBMCWayland() +{ + /* Remove all but the first output if we added any */ + for (std::vector<struct weston_mode>::iterator it = m_additionalModes.begin(); + it != m_additionalModes.end(); + ++it) + { + wl_list_remove(&it->link); + } +} + +void +xtw::XBMCWayland::UnbindFromClientCallback(struct wl_resource *r) +{ + delete static_cast<XBMCWayland *>(wl_resource_get_user_data(r)); +} + +void +xtw::XBMCWayland::BindToClient(struct wl_client *client, + void *data, + uint32_t version, + uint32_t id) +{ + xtwc::Compositor *compositor = static_cast<xtwc::Compositor *>(data); + + /* This looks funky - however the constructor will handle registering + * the destructor function with wl_registry so that it gets destroyed + * at the right time */ + new XBMCWayland(client, id, *compositor); +} + +void +xtw::XBMCWayland::AddModeCallback(struct wl_client *c, + struct wl_resource *r, + int32_t w, + int32_t h, + uint32_t re, + uint32_t f) +{ + static_cast<XBMCWayland *>(wl_resource_get_user_data(r))->AddMode(c, r, w, h, re, f); +} + +void +xtw::XBMCWayland::MovePointerToOnSurfaceCallback(struct wl_client *c, + struct wl_resource *r, + struct wl_resource *s, + wl_fixed_t x, + wl_fixed_t y) +{ + static_cast<XBMCWayland *>(wl_resource_get_user_data(r))->MovePointerToOnSurface(c, r, s, x, y); +} + +void +xtw::XBMCWayland::SendButtonToSurfaceCallback(struct wl_client *c, + struct wl_resource *r, + struct wl_resource *s, + uint32_t b, + uint32_t st) +{ + static_cast<XBMCWayland *>(wl_resource_get_user_data(r))->SendButtonToSurface(c, r, s, b, st); +} + +void +xtw::XBMCWayland::SendAxisToSurfaceCallback(struct wl_client *c, + struct wl_resource *r, + struct wl_resource *s, + uint32_t b, + wl_fixed_t v) +{ + static_cast<XBMCWayland *>(wl_resource_get_user_data(r))->SendAxisToSurface(c, r, s, b, v); +} + +void +xtw::XBMCWayland::SendModifiersToKeyboardCallback(struct wl_client *c, + struct wl_resource *r, + struct wl_resource *s, + uint32_t d, + uint32_t la, + uint32_t lo, + uint32_t g) +{ + static_cast<XBMCWayland *>(wl_resource_get_user_data(r))->SendModifiersToKeyboard(c, r, s, d, la, lo, g); +} + +void +xtw::XBMCWayland::SendKeyToKeyboardCallback(struct wl_client *c, + struct wl_resource *r, + struct wl_resource *s, + uint32_t k, + uint32_t st) +{ + static_cast<XBMCWayland *>(wl_resource_get_user_data(r))->SendKeyToKeyboard(c, r, s, k, st); +} + +void +xtw::XBMCWayland::GiveSurfaceKeyboardFocusCallback(struct wl_client *c, + struct wl_resource *r, + struct wl_resource *s) +{ + static_cast<XBMCWayland *>(wl_resource_get_user_data(r))->GiveSurfaceKeyboardFocus(c, r, s); +} + +void +xtw::XBMCWayland::PingSurfaceCallback(struct wl_client *c, + struct wl_resource *r, + struct wl_resource *s, + uint32_t t) +{ + static_cast<XBMCWayland *>(wl_resource_get_user_data(r))->PingSurface(c, r, s, t); +} + +namespace +{ +void ClearFlagsOnOtherModes(struct weston_mode *mode, + uint32_t flags, + struct weston_mode *skip) +{ + if (mode == skip) + return; + + mode->flags &= ~flags; +} +} + +void +xtw::XBMCWayland::AddMode(struct wl_client *client, + struct wl_resource *resource, + int32_t width, + int32_t height, + uint32_t refresh, + uint32_t flags) +{ + const struct weston_mode mode = + { + flags, + width, + height, + refresh + }; + + m_additionalModes.push_back(mode); + struct weston_mode *lastMode = m_compositor.LastMode(); + wl_list_insert(&lastMode->link, &m_additionalModes.back().link); + + /* Clear flags from all other outputs that may have the same flags + * as this one */ + m_compositor.OnEachMode(boost::bind(ClearFlagsOnOtherModes, + _1, + flags, + &m_additionalModes.back())); +} + +namespace +{ +void GetSerialAndTime(struct wl_display *display, + uint32_t &serial, + uint32_t &time) +{ + serial = wl_display_next_serial(display); + + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + + time = ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +} +} + +void +xtw::XBMCWayland::MovePointerToOnSurface(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface, + wl_fixed_t x, + wl_fixed_t y) +{ + struct wl_client *surfaceClient = wl_resource_get_client(surface); + struct wl_resource *pointer = m_compositor.PointerResource(surfaceClient); + struct wl_display *display = wl_client_get_display(surfaceClient); + uint32_t serial, time; + + GetSerialAndTime(display, serial, time); + + wl_pointer_send_motion(pointer, time, x, y); +} + +void +xtw::XBMCWayland::SendButtonToSurface(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface, + uint32_t button, + uint32_t state) +{ + struct wl_client *surfaceClient = wl_resource_get_client(surface); + struct wl_resource *pointer = m_compositor.PointerResource(surfaceClient); + struct wl_display *display = wl_client_get_display(surfaceClient); + uint32_t serial, time; + + GetSerialAndTime(display, serial, time); + + wl_pointer_send_button(pointer, serial, time, button, state); +} + +void +xtw::XBMCWayland::SendAxisToSurface(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface, + uint32_t axis, + wl_fixed_t value) +{ + struct wl_client *surfaceClient = wl_resource_get_client(surface); + struct wl_resource *pointer = m_compositor.PointerResource(surfaceClient); + struct wl_display *display = wl_client_get_display(surfaceClient); + uint32_t serial, time; + + GetSerialAndTime(display, serial, time); + + wl_pointer_send_axis(pointer, time, axis, value); +} + +void +xtw::XBMCWayland::SendKeyToKeyboard(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface, + uint32_t key, + uint32_t state) +{ + struct wl_client *surfaceClient = wl_resource_get_client(surface); + struct wl_resource *keyboard = m_compositor.KeyboardResource(surfaceClient); + struct wl_display *display = wl_client_get_display(surfaceClient); + uint32_t serial, time; + + GetSerialAndTime(display, serial, time); + + wl_keyboard_send_key(keyboard, serial, time, key, state); +} + +void +xtw::XBMCWayland::SendModifiersToKeyboard(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface, + uint32_t depressed, + uint32_t latched, + uint32_t locked, + uint32_t group) +{ + struct wl_client *surfaceClient = wl_resource_get_client(surface); + struct wl_resource *keyboard = m_compositor.KeyboardResource(surfaceClient); + struct wl_display *display = wl_client_get_display(surfaceClient); + uint32_t serial = wl_display_next_serial(display); + + wl_keyboard_send_modifiers(keyboard, + serial, + depressed, + latched, + locked, + group); +} + +void +xtw::XBMCWayland::GiveSurfaceKeyboardFocus(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface) +{ + struct weston_surface *westonSurface = m_compositor.Surface(surface); + struct weston_keyboard *keyboard = m_compositor.Keyboard(); + weston_keyboard_set_focus(keyboard, westonSurface); +} + +void +xtw::XBMCWayland::PingSurface(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface, + uint32_t timestamp) +{ +} + +xtw::Listener::Listener(const Delegate &delegate) : + m_delegate(delegate) +{ + m_listener.notify = Listener::MainCallback; +} + +void +xtw::Listener::MainCallback(struct wl_listener *listener, void *data) +{ + static_cast<Listener *>(data)->Callback(); +} + +void +xtw::Listener::Callback() +{ + m_delegate(); +} + +void +xtw::Listener::BindTo(struct wl_signal *s) +{ + wl_signal_add(s, &m_listener); +} + +xtwc::Compositor::Compositor(struct weston_compositor *c) : + m_compositor(c), + /* This is a workaround for a race condition where the registry + * might not be ready if we send SIGUSR2 right away - so we + * put it on the event loop to happen after the first poll() */ + m_readySource(wl_event_loop_add_timer(wl_display_get_event_loop(Display()), + Compositor::Ready, + this)), + m_destroyListener(boost::bind(Compositor::Unload, this)) +{ + /* Dispatch ASAP */ + wl_event_source_timer_update(m_readySource, 1); + m_destroyListener.BindTo(&c->destroy_signal); + + /* The parent process should have set the SIGUSR2 handler to + * SIG_IGN, throw if it hasn't */ + if (signal(SIGUSR2, SIG_IGN) != SIG_IGN) + { + std::stringstream ss; + throw std::runtime_error("Parent process is not handling SIGUSR2"); + } +} + +int +xtwc::Compositor::Ready(void *data) +{ + xtwc::Compositor *compositor = static_cast<xtwc::Compositor *>(data); + + if (kill(getppid(), SIGUSR2) == -1) + { + std::stringstream ss; + ss << "kill: " + << strerror(errno); + throw std::runtime_error(ss.str()); + } + + wl_event_source_remove(compositor->m_readySource); + compositor->m_readySource = NULL; + + /* Initialize the fake keyboard and pointer on our seat. This is + * effectively manipulating the backend into doing something it + * shouldn't, but we're in control here */ + struct weston_seat *seat = compositor->Seat(); + weston_seat_init_pointer(seat); + + struct xkb_keymap *keymap = + xkb_keymap_new_from_names(compositor->m_compositor->xkb_context, + &compositor->m_compositor->xkb_names, + static_cast<enum xkb_keymap_compile_flags>(0)); + if (!keymap) + throw std::runtime_error("Failed to compile keymap\n"); + + weston_seat_init_keyboard(seat, keymap); + xkb_keymap_unref(keymap); + return 1; +} + +struct weston_output * +xtwc::Compositor::FirstOutput() +{ + struct weston_output *output; + + output = wl_container_of(m_compositor->output_list.prev, + output, + link); + + return output; +} + +struct wl_display * +xtwc::Compositor::Display() +{ + return m_compositor->wl_display; +} + +struct weston_seat * +xtwc::Compositor::Seat() +{ + /* Since it is impossible to get a weston_seat from a weston_compositor + * and we will need that in order to get access to the weston_pointer + * and weston_keyboard, we need to use this hack to get access + * to the seat by casting the weston_compositor to this, which is + * copied from compositor-headless.c . Since weston_compositor is + * the the first member of headless_compositor, it is safe to cast + * from the second to the first */ + struct headless_compositor { + struct weston_compositor compositor; + struct weston_seat seat; + }; + + /* Before we cast, we should check if we are actually running + * on the headless compositor. If not, throw an exception so + * that the user might know what's going on */ + struct weston_output *output = FirstOutput(); + + if (!output) + throw std::runtime_error("Compositor does not have an output"); + + if (std::string(output->model) != "headless") + { + std::stringstream ss; + ss << "Only the compositor-headless.so backend " + << "is supported by this extension. " + << std::endl + << "The current output model detected was " + << output->model; + throw std::logic_error(ss.str()); + } + + struct headless_compositor *hc = + reinterpret_cast<struct headless_compositor *>(m_compositor); + + return &hc->seat; +} + +struct weston_surface * +xtwc::Compositor::TopSurface() +{ + struct weston_surface *surface; + + /* The strange semantics of wl_container_of means that we can't + * return its result directly because it needs to have an + * instantiation of the type */ + surface = wl_container_of(m_compositor->surface_list.prev, + surface, + link); + return surface; +} + +void +xtwc::Compositor::OnEachMode(const boost::function<void(struct weston_mode *)> &action) +{ + struct weston_output *output = FirstOutput(); + struct weston_mode *mode; + + wl_list_for_each(mode, &output->mode_list, link) + { + action(mode); + } +} + +struct weston_mode * +xtwc::Compositor::LastMode() +{ + struct weston_mode *mode; + struct weston_output *output = FirstOutput(); + mode = wl_container_of(output->mode_list.prev, + mode, + link); + + return mode; +} + +struct wl_resource * +xtwc::Compositor::PointerResource(struct wl_client *client) +{ + struct weston_seat *seat = Seat(); + struct wl_resource *r = + wl_resource_find_for_client(&seat->pointer->focus_resource_list, + client); + if (!r) + r = wl_resource_find_for_client(&seat->pointer->resource_list, + client); + + if (!r) + throw std::logic_error ("No pointer resource available for this " + "client "); + return r; +} + +struct wl_resource * +xtwc::Compositor::KeyboardResource(struct wl_client *client) +{ + struct weston_seat *seat = Seat(); + struct wl_resource *r = + wl_resource_find_for_client(&seat->keyboard->focus_resource_list, + client); + if (!r) + r = wl_resource_find_for_client(&seat->keyboard->resource_list, + client); + + if (!r) + throw std::logic_error ("No keyboard resource available for this " + "client "); + return r; +} + +struct weston_surface * +xtwc::Compositor::Surface(struct wl_resource *surface) +{ + struct weston_surface *ws = + reinterpret_cast<struct weston_surface *>(wl_resource_get_user_data(surface)); + + return ws; +} + +void +xtwc::Compositor::Unload(xtwc::Compositor *compositor) +{ + delete compositor; +} + +struct weston_keyboard * +xtwc::Compositor::Keyboard() +{ + struct weston_seat *seat = Seat(); + return seat->keyboard; +} + +xtwc::Compositor::~Compositor() +{ +} + +extern "C" +{ +WL_EXPORT int +module_init(struct weston_compositor *c, + int *argc, + char *argv[]) +{ + /* Using heap allocated memory directly here is awkward, however + * weston knows when we need to destroy our resources + * so we will let it handle it */ + xtwc::Compositor *compositor(new xtwc::Compositor(c)); + /* Register our factory for xbmc_wayland and pass + * xtwc::Compositor to it when it gets created */ + if (wl_global_create(compositor->Display(), + &xbmc_wayland_interface, + 1, + compositor, + xtw::XBMCWayland::BindToClient) == NULL) + return -1; + + return 0; +} +} + diff --git a/xbmc/windowing/tests/wayland/protocol.xml b/xbmc/windowing/tests/wayland/protocol.xml new file mode 100644 index 0000000000..69f5202eba --- /dev/null +++ b/xbmc/windowing/tests/wayland/protocol.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="xbmc_wayland_test"> + <copyright> + Copyright (C) 2005-2013 Team XBMC + http://www.xbmc.org + + This Program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This Program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XBMC; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> + + </copyright> + <interface name="xbmc_wayland" version="1"/> + <request name="add_mode"> + <arg name="width" type="int"/> + <arg name="height" type="int"/> + <arg name="refresh" type="uint"/> + <arg name="flags" type="uint"/> + </request> + <request name="move_pointer_to_on_surface"> + <arg name="surface" type="object" interface="wl_surface"/> + <arg name="x" type="fixed"/> + <arg name="y" type="fixed"/> + </request> + <request name="send_button_to_surface"> + <arg name="surface" type="object" interface="wl_surface"/> + <arg name="button" type="uint"/> + <arg name="state" type="uint"/> + </request> + <request name="send_axis_to_surface"> + <arg name="surface" type="object" interface="wl_surface"/> + <arg name="axis" type="uint"/> + <arg name="value" type="fixed"/> + </request> + <request name="send_key_to_keyboard"> + <arg name="surface" type="object" interface="wl_surface"/> + <arg name="key" type="uint"/> + <arg name="state" type="uint"/> + </request> + <request name="send_modifiers_to_keyboard"> + <arg name="surface" type="object" interface="wl_surface"/> + <arg name="depressed" type="uint"/> + <arg name="latched" type="uint"/> + <arg name="locked" type="uint"/> + <arg name="group" type="uint"/> + </request> + <request name="give_surface_keyboard_focus"> + <arg name="surface" type="object" interface="wl_surface"/> + </request> + <request name="ping_surface"> + <arg name="surface" type="object" interface="wl_surface"/> + <arg name="timestamp" type="uint"/> + </request> +</protocol> |