summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsss <sss@dark-alexandr.net>2023-01-17 00:38:19 +0300
committersss <sss@dark-alexandr.net>2023-01-17 00:38:19 +0300
commitcc3f33db7a8d3c4ad373e646b199808e01bc5d9b (patch)
treeec09d690c7656ab5f2cc72607e05fb359c24d8b2
added webrdp public code
-rw-r--r--3rdparty/libev/CMakeLists.txt63
-rw-r--r--3rdparty/libev/Changes617
-rw-r--r--3rdparty/libev/LICENSE37
-rw-r--r--3rdparty/libev/README59
-rw-r--r--3rdparty/libev/Symbols.ev73
-rw-r--r--3rdparty/libev/Symbols.event24
-rw-r--r--3rdparty/libev/TODO14
-rw-r--r--3rdparty/libev/cmake_config.h.in141
-rw-r--r--3rdparty/libev/ev++.h818
-rw-r--r--3rdparty/libev/ev.35819
-rw-r--r--3rdparty/libev/ev.c5627
-rw-r--r--3rdparty/libev/ev.h860
-rw-r--r--3rdparty/libev/ev.pod5741
-rw-r--r--3rdparty/libev/ev_epoll.c298
-rw-r--r--3rdparty/libev/ev_iouring.c694
-rw-r--r--3rdparty/libev/ev_kqueue.c224
-rw-r--r--3rdparty/libev/ev_linuxaio.c620
-rw-r--r--3rdparty/libev/ev_poll.c156
-rw-r--r--3rdparty/libev/ev_port.c192
-rw-r--r--3rdparty/libev/ev_select.c316
-rw-r--r--3rdparty/libev/ev_vars.h249
-rw-r--r--3rdparty/libev/ev_win32.c162
-rw-r--r--3rdparty/libev/ev_wrap.h272
-rw-r--r--3rdparty/libev/event.c425
-rw-r--r--3rdparty/libev/event.h177
-rw-r--r--README.md50
-rwxr-xr-xbuilder.sh254
-rw-r--r--doc/TODO9
-rw-r--r--doc/auth_server_proto.json235
-rw-r--r--doc/auth_server_proto.yaml146
-rw-r--r--doc/config_sample79
-rw-r--r--doc/nginx_config_sample.conf44
-rw-r--r--doc/nginx_location31
-rw-r--r--doc/remote_control_protocol70
-rwxr-xr-xguix_build_env.sh4
-rw-r--r--guix_channels.scm39
-rwxr-xr-xrun_clang_format.sh4
l---------src/.clang-format1
-rw-r--r--src/backend.workspace17
l---------src/core/.clang-format1
-rw-r--r--src/core/CMakeLists.txt167
-rw-r--r--src/core/backend_helpers.c459
-rw-r--r--src/core/backend_helpers.h20
-rw-r--r--src/core/base64_url.h187
-rw-r--r--src/core/cmdline.c122
-rw-r--r--src/core/cmdline.h9
-rw-r--r--src/core/config_file.c480
-rw-r--r--src/core/config_file.h9
-rw-r--r--src/core/core.project221
-rw-r--r--src/core/ctl_task.h18
-rw-r--r--src/core/curl_helpers.c864
-rw-r--r--src/core/curl_helpers.h76
-rw-r--r--src/core/ev_loop.c206
-rw-r--r--src/core/ev_loop.h11
-rw-r--r--src/core/exports.c396
-rw-r--r--src/core/exports.h17
-rw-r--r--src/core/globals.h82
l---------src/core/include/.clang-format1
-rw-r--r--src/core/include/webrdp_api_shared_structures.h222
-rw-r--r--src/core/include/webrdp_api_utils.h43
-rw-r--r--src/core/include/webrdp_core_api.h185
-rw-r--r--src/core/include/webrdp_module_api.h109
-rw-r--r--src/core/json_helpers.c275
-rw-r--r--src/core/json_helpers.h15
-rw-r--r--src/core/log.c164
-rw-r--r--src/core/log.h12
-rw-r--r--src/core/main.c220
-rw-r--r--src/core/remote_control.c435
-rw-r--r--src/core/remote_control.h36
-rw-r--r--src/core/socket_helpers.c143
-rw-r--r--src/core/socket_helpers.h12
-rw-r--r--src/core/task.h59
-rw-r--r--src/core/thread_impl.c387
-rw-r--r--src/core/thread_impl.h23
-rw-r--r--src/core/thread_sync.c75
-rw-r--r--src/core/thread_sync.h23
-rw-r--r--src/core/utilities.c115
-rw-r--r--src/core/utilities.h28
-rw-r--r--src/core/wrdp_thpool.c770
-rw-r--r--src/core/wrdp_thpool.h150
-rw-r--r--src/core/wrdp_thpool_internals.h113
-rw-r--r--src/core/ws_protocol.c1206
-rw-r--r--src/core/ws_protocol.h79
-rw-r--r--src/core/ws_server_internals.h7
-rw-r--r--src/core/ws_session.c636
-rw-r--r--src/core/ws_session.h59
l---------src/rdp/.clang-format1
-rw-r--r--src/rdp/CMakeLists.txt82
l---------src/rdp/include/.clang-format1
-rw-r--r--src/rdp/include/rdp_backend_api.h17
-rw-r--r--src/rdp/rdp.project138
-rw-r--r--src/rdp/rdp_channels.c39
-rw-r--r--src/rdp/rdp_channels.h17
-rw-r--r--src/rdp/rdp_clipboard.c1524
-rw-r--r--src/rdp/rdp_clipboard.h52
-rw-r--r--src/rdp/rdp_display_output.c871
-rw-r--r--src/rdp/rdp_display_output.h17
-rw-r--r--src/rdp/rdp_ft.c129
-rw-r--r--src/rdp/rdp_ft.h17
-rw-r--r--src/rdp/rdp_impl.c637
-rw-r--r--src/rdp/rdp_impl.h127
-rw-r--r--src/rdp/rdp_io.c1492
-rw-r--r--src/rdp/rdp_module.c168
-rw-r--r--src/rdp/rdp_module.h7
-rw-r--r--src/rdp/rdp_png.c89
-rw-r--r--src/rdp/rdp_png.h22
-rw-r--r--src/rdp/rdp_rail.c173
-rw-r--r--src/rdp/rdp_rail.h14
-rw-r--r--src/rdp/rdp_settings.c2278
-rw-r--r--src/rdp/rdp_settings.h15
-rw-r--r--src/rdp/rdp_user_input.c458
-rw-r--r--src/rdp/rdp_user_input.h10
-rw-r--r--src/tools/keygen.c57
-rw-r--r--www/FreeRDP_Logo.pngbin0 -> 40572 bytes
-rw-r--r--www/c_default.pngbin0 -> 202 bytes
-rw-r--r--www/c_none.pngbin0 -> 210 bytes
-rw-r--r--www/css/style-debug.css537
-rw-r--r--www/css/vkb-debug.css282
-rwxr-xr-xwww/favicon.icobin0 -> 2255 bytes
-rw-r--r--www/images/cbd_empty.pngbin0 -> 5426 bytes
-rw-r--r--www/images/cbd_empty_out.pngbin0 -> 7191 bytes
-rw-r--r--www/images/cbd_files.pngbin0 -> 13959 bytes
-rw-r--r--www/images/cbd_text.pngbin0 -> 6331 bytes
-rw-r--r--www/images/cbd_wait.pngbin0 -> 28165 bytes
-rw-r--r--www/images/clipboard_in.pngbin0 -> 4814 bytes
-rw-r--r--www/images/clipboard_out.pngbin0 -> 5437 bytes
-rw-r--r--www/images/loading.gifbin0 -> 18032 bytes
-rw-r--r--www/index.html339
-rw-r--r--www/js/clipboard.js535
-rw-r--r--www/js/file-transfer.js365
-rw-r--r--www/js/modernizr-debug.js441
-rw-r--r--www/js/mootools-1.6.0.js6382
-rw-r--r--www/js/mootools-debug.js5180
-rw-r--r--www/js/popupdeck.js96
-rw-r--r--www/js/rdp-start.js262
-rw-r--r--www/js/simpletabs-debug.js120
-rw-r--r--www/js/vkb-debug.js750
-rw-r--r--www/js/vkbl-de_DE.json8
-rw-r--r--www/js/vkbl-en_US.json12
-rw-r--r--www/js/webrdp-Log.js134
-rw-r--r--www/js/webrdp-debug.js1707
-rw-r--r--www/js/webrdp-ext.js19
-rw-r--r--www/js/webrdp-log.js147
143 files changed, 59376 insertions, 0 deletions
diff --git a/3rdparty/libev/CMakeLists.txt b/3rdparty/libev/CMakeLists.txt
new file mode 100644
index 0000000..1284d51
--- /dev/null
+++ b/3rdparty/libev/CMakeLists.txt
@@ -0,0 +1,63 @@
+cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
+
+project(libev C)
+
+set (libev_VERSION_MAJOR 4)
+set (libev_VERSION_MINOR 33)
+set(VERSION TRUE)
+
+include_directories("${PROJECT_SOURCE_DIR}")
+include_directories("${PROJECT_BINARY_DIR}")
+
+include(CheckSymbolExists)
+
+set(LIBEV_SOURCES ev.c event.c)
+
+check_symbol_exists(epoll_ctl sys/epoll.h HAVE_EPOLL_CTL)
+check_symbol_exists(clock_gettime time.h HAVE_CLOCK_GETTIME)
+find_file(HAVE_DLFCN_H dlfcn.h)
+check_symbol_exists(eventfd sys/eventfd.h HAVE_EVENTFD)
+set(CMAKE_REQUIRED_LIBRARIES -lm)
+check_symbol_exists(floor math.h HAVE_FLOOR)
+check_symbol_exists(inotify_init sys/inotify.h HAVE_INOTIFY_INIT)
+find_file(HAVE_INTTYPES_H inttypes.h)
+check_symbol_exists(kqueue sys/time.h;sys/types.h;sys/event.h HAVE_KQUEUE)
+find_library(HAVE_LIBRT librt.so)
+find_file(HAVE_MEMORY_H memory.h)
+check_symbol_exists(nanosleep time.h HAVE_NANOSLEEP)
+check_symbol_exists(poll poll.h HAVE_POLL)
+find_file(HAVE_POLL_H poll.h)
+check_symbol_exists(port_create port.h HAVE_PORT_CREATE)
+find_file(HAVE_PORT_H port.h)
+check_symbol_exists(select sys/select.h HAVE_SELECT)
+check_symbol_exists(signalfd sys/signalfd.h HAVE_SIGNALFD)
+find_file(HAVE_STDINT_H stdint.h)
+find_file(HAVE_STDLIB_H stdlib.h)
+find_file(HAVE_STRINGS_H strings.h)
+find_file(HAVE_STRING_H string.h)
+find_file(HAVE_SYS_EPOLL_H sys/epoll.h)
+find_file(HAVE_SYS_EVENTFD_H sys/eventfd.h)
+find_file(HAVE_SYS_EVENT_H sys/event.h)
+find_file(HAVE_SYS_INOTIFY_H sys/inotify.h)
+find_file(HAVE_SYS_SELECT_H sys/select.h)
+find_file(HAVE_SYS_SIGNALFD_H sys/signalfd.h)
+find_file(HAVE_SYS_STAT_H sys/stat.h)
+find_file(HAVE_SYS_TYPES_H sys/types.h)
+find_file(HAVE_UNISTD_H unistd.h)
+find_file(HAVE_LINUX_AIO_ABI_H linux/aio_abi.h)
+check_symbol_exists(kernel_rwf_t linux/fs.h HAVE_KERNEL_RWF_T)
+find_file(HAVE_LINUX_FS_H linux/fs.h)
+find_file(HAVE_SYS_TIMERFD_H sys/timerfd.h)
+
+
+configure_file (
+ "${PROJECT_SOURCE_DIR}/cmake_config.h.in"
+ "${PROJECT_BINARY_DIR}/config.h"
+)
+
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DHAVE_CONFIG_H -fPIC -DPIC")
+
+add_library(ev STATIC ${LIBEV_SOURCES})
+
+
+
diff --git a/3rdparty/libev/Changes b/3rdparty/libev/Changes
new file mode 100644
index 0000000..d200b9e
--- /dev/null
+++ b/3rdparty/libev/Changes
@@ -0,0 +1,617 @@
+Revision history for libev, a high-performance and full-featured event loop.
+
+TODO: for next ABI/API change, consider moving EV__IOFDSSET into io->fd instead and provide a getter.
+TODO: document EV_TSTAMP_T
+
+4.33 Wed Mar 18 13:22:29 CET 2020
+ - no changes w.r.t. 4.32.
+
+4.32 (EV only)
+ - the 4.31 timerfd code wrongly changed the priority of the signal
+ fd watcher, which is usually harmless unless signal fds are
+ also used (found via cpan tester service).
+ - the documentation wrongly claimed that user may modify fd and events
+ members in io watchers when the watcher was stopped
+ (found by b_jonas).
+ - new ev_io_modify mutator which changes only the events member,
+ which can be faster. also added ev::io::set (int events) method
+ to ev++.h.
+ - officially allow a zero events mask for io watchers. this should
+ work with older libev versions as well but was not officially
+ allowed before.
+ - do not wake up every minute when timerfd is used to detect timejumps.
+ - do not wake up every minute when periodics are disabled and we have
+ a monotonic clock.
+ - support a lot more "uncommon" compile time configurations,
+ such as ev_embed enabled but ev_timer disabled.
+ - use a start/stop wrapper class to reduce code duplication in
+ ev++.h and make it needlessly more c++-y.
+ - the linux aio backend is no longer compiled in by default.
+ - update to libecb version 0x00010008.
+
+4.31 Fri Dec 20 21:58:29 CET 2019
+ - handle backends with minimum wait time a bit better by not
+ waiting in the presence of already-expired timers
+ (behaviour reported by Felipe Gasper).
+ - new feature: use timerfd to detect timejumps quickly,
+ can be disabled with the new EVFLAG_NOTIMERFD loop flag.
+ - document EV_USE_SIGNALFD feature macro.
+
+4.30 (EV only)
+ - change non-autoconf test for __kernel_rwf_t by testing
+ LINUX_VERSION_CODE, the most direct test I could find.
+ - fix a bug in the io_uring backend that polled the wrong
+ backend fd, causing it to not work in many cases.
+
+4.29 (EV only)
+ - add io uring autoconf and non-autoconf detection.
+ - disable io_uring when some header files are too old.
+
+4.28 (EV only)
+ - linuxaio backend resulted in random memory corruption
+ when loop is forked.
+ - linuxaio backend might have tried to cancel an iocb
+ multiple times (was unable to trigger this).
+ - linuxaio backend now employs a generation counter to
+ avoid handling spurious events from cancelled requests.
+ - io_cancel can return EINTR, deal with it. also, assume
+ io_submit also returns EINTR.
+ - fix some other minor bugs in linuxaio backend.
+ - ev_tstamp type can now be overriden by defining EV_TSTAMP_T.
+ - cleanup: replace expect_true/false and noinline by their
+ libecb counterparts.
+ - move syscall infrastructure from ev_linuxaio.c to ev.c.
+ - prepare io_uring integration.
+ - tweak ev_floor.
+ - epoll, poll, win32 Sleep and other places that use millisecond
+ reslution now all try to round up times.
+ - solaris port backend didn't compile.
+ - abstract time constants into their macros, for more flexibility.
+
+4.27 Thu Jun 27 22:43:44 CEST 2019
+ - linux aio backend almost completely rewritten to work around its
+ limitations.
+ - linux aio backend now requires linux 4.19+.
+ - epoll backend now mandatory for linux aio backend.
+ - fail assertions more aggressively on invalid fd's detected
+ in the event loop, do not just silently fd_kill in case of
+ user error.
+ - ev_io_start/ev_io_stop now verify the watcher fd using
+ a syscall when EV_VERIFY is 2 or higher.
+
+4.26 (EV only)
+ - update to libecb 0x00010006.
+ - new experimental linux aio backend (linux 4.18+).
+ - removed redundant 0-ptr check in ev_once.
+ - updated/extended ev_set_allocator documentation.
+ - replaced EMPTY2 macro by array_needsize_noinit.
+ - minor code cleanups.
+ - epoll backend now uses epoll_create1 also after fork.
+
+4.25 Fri Dec 21 07:49:20 CET 2018
+ - INCOMPATIBLE CHANGE: EV_THROW was renamed to EV_NOEXCEPT
+ (EV_THROW still provided) and now uses noexcept on C++11 or newer.
+ - move the darwin select workaround higher in ev.c, as newer versions of
+ darwin managed to break their broken select even more.
+ - ANDROID => __ANDROID__ (reported by enh@google.com).
+ - disable epoll_create1 on android because it has broken header files
+ and google is unwilling to fix them (reported by enh@google.com).
+ - avoid a minor compilation warning on win32.
+ - c++: remove deprecated dynamic throw() specifications.
+ - c++: improve the (unsupported) bad_loop exception class.
+ - backport perl ev_periodic example to C, untested.
+ - update libecb, biggets change is to include a memory fence
+ in ECB_MEMORY_FENCE_RELEASE on x86/amd64.
+ - minor autoconf/automake modernisation.
+
+4.24 Wed Dec 28 05:19:55 CET 2016
+ - bump version to 4.24, as the release tarball inexplicably
+ didn't have the right version in ev.h, even though the cvs-tagged
+ version did have the right one (reported by Ales Teska).
+
+4.23 Wed Nov 16 18:23:41 CET 2016
+ - move some declarations at the beginning to help certain retarded
+ microsoft compilers, even though their documentation claims
+ otherwise (reported by Ruslan Osmanov).
+
+4.22 Sun Dec 20 22:11:50 CET 2015
+ - when epoll detects unremovable fds in the fd set, rebuild
+ only the epoll descriptor, not the signal pipe, to avoid
+ SIGPIPE in ev_async_send. This doesn't solve it on fork,
+ so document what needs to be done in ev_loop_fork
+ (analyzed by Benjamin Mahler).
+ - remove superfluous sys/timeb.h include on win32
+ (analyzed by Jason Madden).
+ - updated libecb.
+
+4.20 Sat Jun 20 13:01:43 CEST 2015
+ - prefer noexcept over throw () with C++ 11.
+ - update ecb.h due to incompatibilities with c11.
+ - fix a potential aliasing issue when reading and writing
+ watcher callbacks.
+
+4.19 Thu Sep 25 08:18:25 CEST 2014
+ - ev.h wasn't valid C++ anymore, which tripped compilers other than
+ clang, msvc or gcc (analyzed by Raphael 'kena' Poss). Unfortunately,
+ C++ doesn't support typedefs for function pointers fully, so the affected
+ declarations have to spell out the types each time.
+ - when not using autoconf, tighten the check for clock_gettime and related
+ functionality.
+
+4.18 Fri Sep 5 17:55:26 CEST 2014
+ - events on files were not always generated properly with the
+ epoll backend (testcase by Assaf Inbal).
+ - mark event pipe fd as cloexec after a fork (analyzed by Sami Farin).
+ - (ecb) support m68k, m88k and sh (patch by Miod Vallat).
+ - use a reasonable fallback for EV_NSIG instead of erroring out
+ when we can't detect the signal set size.
+ - in the absence of autoconf, do not use the clock syscall
+ on glibc >= 2.17 (avoids the syscall AND -lrt on systems
+ doing clock_gettime in userspace).
+ - ensure extern "C" function pointers are used for externally-visible
+ loop callbacks (not watcher callbacks yet).
+ - (ecb) work around memory barriers and volatile apparently both being
+ broken in visual studio 2008 and later (analysed and patch by Nicolas Noble).
+
+4.15 Fri Mar 1 12:04:50 CET 2013
+ - destroying a non-default loop would stop the global waitpid
+ watcher (Denis Bilenko).
+ - queueing pending watchers of higher priority from a watcher now invokes
+ them in a timely fashion (reported by Denis Bilenko).
+ - add throw() to all libev functions that cannot throw exceptions, for
+ further code size decrease when compiling for C++.
+ - add throw () to callbacks that must not throw exceptions (allocator,
+ syserr, loop acquire/release, periodic reschedule cbs).
+ - fix event_base_loop return code, add event_get_callback, event_base_new,
+ event_base_get_method calls to improve libevent 1.x emulation and add
+ some libevent 2.x functionality (based on a patch by Jeff Davey).
+ - add more memory fences to fix a bug reported by Jeff Davey. Better
+ be overfenced than underprotected.
+ - ev_run now returns a boolean status (true meaning watchers are
+ still active).
+ - ev_once: undef EV_ERROR in ev_kqueue.c, to avoid clashing with
+ libev's EV_ERROR (reported by 191919).
+ - (ecb) add memory fence support for xlC (Darin McBride).
+ - (ecb) add memory fence support for gcc-mips (Anton Kirilov).
+ - (ecb) add memory fence support for gcc-alpha (Christian Weisgerber).
+ - work around some kernels losing file descriptors by leaking
+ the kqueue descriptor in the child.
+ - work around linux inotify not reporting IN_ATTRIB changes for directories
+ in many cases.
+ - include sys/syscall.h instead of plain syscall.h.
+ - check for io watcher loops in ev_verify, check for the most
+ common reported usage bug in ev_io_start.
+ - choose socket vs. WSASocket at compiletime using EV_USE_WSASOCKET.
+ - always use WSASend/WSARecv directly on windows, hoping that this
+ works in all cases (unlike read/write/send/recv...).
+ - try to detect signals around a fork faster (test program by
+ Denis Bilenko).
+ - work around recent glibc versions that leak memory in realloc.
+ - rename ev::embed::set to ev::embed::set_embed to avoid clashing
+ the watcher base set (loop) method.
+ - rewrite the async/signal pipe logic to always keep a valid fd, which
+ simplifies (and hopefully correctifies :) the race checking
+ on fork, at the cost of one extra fd.
+ - add fat, msdos, jffs2, ramfs, ntfs and btrfs to the list of
+ inotify-supporting filesystems.
+ - move orig_CFLAGS assignment to after AC_INIT, as newer autoconf
+ versions ignore it before
+ (https://bugzilla.redhat.com/show_bug.cgi?id=908096).
+ - add some untested android support.
+ - enum expressions must be of type int (reported by Juan Pablo L).
+
+4.11 Sat Feb 4 19:52:39 CET 2012
+ - INCOMPATIBLE CHANGE: ev_timer_again now clears the pending status, as
+ was documented already, but not implemented in the repeating case.
+ - new compiletime symbols: EV_NO_SMP and EV_NO_THREADS.
+ - fix a race where the workaround against the epoll fork bugs
+ caused signals to not be handled anymore.
+ - correct backend_fudge for most backends, and implement a windows
+ specific workaround to avoid looping because we call both
+ select and Sleep, both with different time resolutions.
+ - document range and guarantees of ev_sleep.
+ - document reasonable ranges for periodics interval and offset.
+ - rename backend_fudge to backend_mintime to avoid future confusion :)
+ - change the default periodic reschedule function to hopefully be more
+ exact and correct even in corner cases or in the far future.
+ - do not rely on -lm anymore: use it when available but use our
+ own floor () if it is missing. This should make it easier to embed,
+ as no external libraries are required.
+ - strategically import macros from libecb and mark rarely-used functions
+ as cache-cold (saving almost 2k code size on typical amd64 setups).
+ - add Symbols.ev and Symbols.event files, that were missing.
+ - fix backend_mintime value for epoll (was 1/1024, is 1/1000 now).
+ - fix #3 "be smart about timeouts" to not "deadlock" when
+ timeout == now, also improve the section overall.
+ - avoid "AVOIDING FINISHING BEFORE RETURNING" idiom.
+ - support new EV_API_STATIC mode to make all libev symbols
+ static.
+ - supply default CFLAGS of -g -O3 with gcc when original CFLAGS
+ were empty.
+
+4.04 Wed Feb 16 09:01:51 CET 2011
+ - fix two problems in the native win32 backend, where reuse of fd's
+ with different underlying handles caused handles not to be removed
+ or added to the select set (analyzed and tested by Bert Belder).
+ - do no rely on ceil() in ev_e?poll.c.
+ - backport libev to HP-UX versions before 11 v3.
+ - configure did not detect nanosleep and clock_gettime properly when
+ they are available in the libc (as opposed to -lrt).
+
+4.03 Tue Jan 11 14:37:25 CET 2011
+ - officially support polling files with all backends.
+ - support files, /dev/zero etc. the same way as select in the epoll
+ backend, by generating events on our own.
+ - ports backend: work around solaris bug 6874410 and many related ones
+ (EINTR, maybe more), with no performance loss (note that the solaris
+ bug report is actually wrong, reality is far more bizarre and broken
+ than that).
+ - define EV_READ/EV_WRITE as macros in event.h, as some programs use
+ #ifdef to test for them.
+ - new (experimental) function: ev_feed_signal.
+ - new (to become default) EVFLAG_NOSIGMASK flag.
+ - new EVBACKEND_MASK symbol.
+ - updated COMMON IDIOMS SECTION.
+
+4.01 Fri Nov 5 21:51:29 CET 2010
+ - automake fucked it up, apparently, --add-missing -f is not quite enough
+ to make it update its files, so 4.00 didn't install ev++.h and
+ event.h on make install. grrr.
+ - ev_loop(count|depth) didn't return anything (Robin Haberkorn).
+ - change EV_UNDEF to 0xffffffff to silence some overzealous compilers.
+ - use "(libev) " prefix for all libev error messages now.
+
+4.00 Mon Oct 25 12:32:12 CEST 2010
+ - "PORTING FROM LIBEV 3.X TO 4.X" (in ev.pod) is recommended reading.
+ - ev_embed_stop did not correctly stop the watcher (very good
+ testcase by Vladimir Timofeev).
+ - ev_run will now always update the current loop time - it erroneously
+ didn't when idle watchers were active, causing timers not to fire.
+ - fix a bug where a timeout of zero caused the timer not to fire
+ in the libevent emulation (testcase by Péter Szabó).
+ - applied win32 fixes by Michael Lenaghan (also James Mansion).
+ - replace EV_MINIMAL by EV_FEATURES.
+ - prefer EPOLL_CTL_ADD over EPOLL_CTL_MOD in some more cases, as it
+ seems the former is *much* faster than the latter.
+ - linux kernel version detection (for inotify bug workarounds)
+ did not work properly.
+ - reduce the number of spurious wake-ups with the ports backend.
+ - remove dependency on sys/queue.h on freebsd (patch by Vanilla Hsu).
+ - do async init within ev_async_start, not ev_async_set, which avoids
+ an API quirk where the set function must be called in the C++ API
+ even when there is nothing to set.
+ - add (undocumented) EV_ENABLE when adding events with kqueue,
+ this might help with OS X, which seems to need it despite documenting
+ not to need it (helpfully pointed out by Tilghman Lesher).
+ - do not use poll by default on freebsd, it's broken (what isn't
+ on freebsd...).
+ - allow to embed epoll on kernels >= 2.6.32.
+ - configure now prepends -O3, not appends it, so one can still
+ override it.
+ - ev.pod: greatly expanded the portability section, added a porting
+ section, a description of watcher states and made lots of minor fixes.
+ - disable poll backend on AIX, the poll header spams the namespace
+ and it's not worth working around dead platforms (reported
+ and analyzed by Aivars Kalvans).
+ - improve header file compatibility of the standalone eventfd code
+ in an obscure case.
+ - implement EV_AVOID_STDIO option.
+ - do not use sscanf to parse linux version number (smaller, faster,
+ no sscanf dependency).
+ - new EV_CHILD_ENABLE and EV_SIGNAL_ENABLE configurable settings.
+ - update libev.m4 HAVE_CLOCK_SYSCALL test for newer glibcs.
+ - add section on accept() problems to the manpage.
+ - rename EV_TIMEOUT to EV_TIMER.
+ - rename ev_loop_count/depth/verify/loop/unloop.
+ - remove ev_default_destroy and ev_default_fork.
+ - switch to two-digit minor version.
+ - work around an apparent gentoo compiler bug.
+ - define _DARWIN_UNLIMITED_SELECT. just so.
+ - use enum instead of #define for most constants.
+ - improve compatibility to older C++ compilers.
+ - (experimental) ev_run/ev_default_loop/ev_break/ev_loop_new have now
+ default arguments when compiled as C++.
+ - enable automake dependency tracking.
+ - ev_loop_new no longer leaks memory when loop creation failed.
+ - new ev_cleanup watcher type.
+
+3.9 Thu Dec 31 07:59:59 CET 2009
+ - signalfd is no longer used by default and has to be requested
+ explicitly - this means that easy to catch bugs become hard to
+ catch race conditions, but the users have spoken.
+ - point out the unspecified signal mask in the documentation, and
+ that this is a race condition regardless of EV_SIGNALFD.
+ - backport inotify code to C89.
+ - inotify file descriptors could leak into child processes.
+ - ev_stat watchers could keep an erroneous extra ref on the loop,
+ preventing exit when unregistering all watchers (testcases
+ provided by ry@tinyclouds.org).
+ - implement EV_WIN32_HANDLE_TO_FD and EV_WIN32_CLOSE_FD configuration
+ symbols to make it easier for apps to do their own fd management.
+ - support EV_IDLE_ENABLE being disabled in ev++.h
+ (patch by Didier Spezia).
+ - take advantage of inotify_init1, if available, to set cloexec/nonblock
+ on fd creation, to avoid races.
+ - the signal handling pipe wasn't always initialised under windows
+ (analysed by lekma).
+ - changed minimum glibc requirement from glibc 2.9 to 2.7, for
+ signalfd.
+ - add missing string.h include (Denis F. Latypoff).
+ - only replace ev_stat.prev when we detect an actual difference,
+ so prev is (almost) always different to attr. this might
+ have caused the problems with 04_stat.t.
+ - add ev::timer->remaining () method to C++ API.
+
+3.8 Sun Aug 9 14:30:45 CEST 2009
+ - incompatible change: do not necessarily reset signal handler
+ to SIG_DFL when a sighandler is stopped.
+ - ev_default_destroy did not properly free or zero some members,
+ potentially causing crashes and memory corruption on repeated
+ ev_default_destroy/ev_default_loop calls.
+ - take advantage of signalfd on GNU/Linux systems.
+ - document that the signal mask might be in an unspecified
+ state when using libev's signal handling.
+ - take advantage of some GNU/Linux calls to set cloexec/nonblock
+ on fd creation, to avoid race conditions.
+
+3.7 Fri Jul 17 16:36:32 CEST 2009
+ - ev_unloop and ev_loop wrongly used a global variable to exit loops,
+ instead of using a per-loop variable (bug caught by accident...).
+ - the ev_set_io_collect_interval interpretation has changed.
+ - add new functionality: ev_set_userdata, ev_userdata,
+ ev_set_invoke_pending_cb, ev_set_loop_release_cb,
+ ev_invoke_pending, ev_pending_count, together with a long example
+ about thread locking.
+ - add ev_timer_remaining (as requested by Denis F. Latypoff).
+ - add ev_loop_depth.
+ - calling ev_unloop in fork/prepare watchers will no longer poll
+ for new events.
+ - Denis F. Latypoff corrected many typos in example code snippets.
+ - honor autoconf detection of EV_USE_CLOCK_SYSCALL, also double-
+ check that the syscall number is available before trying to
+ use it (reported by ry@tinyclouds).
+ - use GetSystemTimeAsFileTime instead of _timeb on windows, for
+ slightly higher accuracy.
+ - properly declare ev_loop_verify and ev_now_update even when
+ !EV_MULTIPLICITY.
+ - do not compile in any priority code when EV_MAXPRI == EV_MINPRI.
+ - support EV_MINIMAL==2 for a reduced API.
+ - actually 0-initialise struct sigaction when installing signals.
+ - add section on hibernate and stopped processes to ev_timer docs.
+
+3.6 Tue Apr 28 02:49:30 CEST 2009
+ - multiple timers becoming ready within an event loop iteration
+ will be invoked in the "correct" order now.
+ - do not leave the event loop early just because we have no active
+ watchers, fixing a problem when embedding a kqueue loop
+ that has active kernel events but no registered watchers
+ (reported by blacksand blacksand).
+ - correctly zero the idx values for arrays, so destroying and
+ reinitialising the default loop actually works (patch by
+ Malek Hadj-Ali).
+ - implement ev_suspend and ev_resume.
+ - new EV_CUSTOM revents flag for use by applications.
+ - add documentation section about priorities.
+ - add a glossary to the documentation.
+ - extend the ev_fork description slightly.
+ - optimize a jump out of call_pending.
+
+3.53 Sun Feb 15 02:38:20 CET 2009
+ - fix a bug in event pipe creation on win32 that would cause a
+ failed assertion on event loop creation (patch by Malek Hadj-Ali).
+ - probe for CLOCK_REALTIME support at runtime as well and fall
+ back to gettimeofday if there is an error, to support older
+ operating systems with newer header files/libraries.
+ - prefer gettimeofday over clock_gettime with USE_CLOCK_SYSCALL
+ (default most everywhere), otherwise not.
+
+3.52 Wed Jan 7 21:43:02 CET 2009
+ - fix compilation of select backend in fd_set mode when NFDBITS is
+ missing (to get it to compile on QNX, reported by Rodrigo Campos).
+ - better select-nfds handling when select backend is in fd_set mode.
+ - diagnose fd_set overruns when select backend is in fd_set mode.
+ - due to a thinko, instead of disabling everything but
+ select on the borked OS X platform, everything but select was
+ allowed (reported by Emanuele Giaquinta).
+ - actually verify that local and remote port are matching in
+ libev's socketpair emulation, which makes denial-of-service
+ attacks harder (but not impossible - it's windows). Make sure
+ it even works under vista, which thinks that getpeer/sockname
+ should return fantasy port numbers.
+ - include "libev" in all assertion messages for potentially
+ clearer diagnostics.
+ - event_get_version (libevent compatibility) returned
+ a useless string instead of the expected version string
+ (patch by W.C.A. Wijngaards).
+
+3.51 Wed Dec 24 23:00:11 CET 2008
+ - fix a bug where an inotify watcher was added twice, causing
+ freezes on hash collisions (reported and analysed by Graham Leggett).
+ - new config symbol, EV_USE_CLOCK_SYSCALL, to make libev use
+ a direct syscall - slower, but no dependency on librt et al.
+ - assume negative return values != -1 signals success of port_getn
+ (http://cvs.epicsol.org/cgi/viewcvs.cgi/epic5/source/newio.c?rev=1.52)
+ (no known failure reports, but it doesn't hurt).
+ - fork detection in ev_embed now stops and restarts the watcher
+ automatically.
+ - EXPERIMENTAL: default the method to operator () in ev++.h,
+ to make it nicer to use functors (requested by Benedek László).
+ - fixed const object callbacks in ev++.h.
+ - replaced loop_ref argument of watcher.set (loop) by a direct
+ ev_loop * in ev++.h, to avoid clashes with functor patch.
+ - do not try to watch the empty string via inotify.
+ - inotify watchers could be leaked under certain circumstances.
+ - OS X 10.5 is actually even more broken than earlier versions,
+ so fall back to select on that piece of garbage.
+ - fixed some weirdness in the ev_embed documentation.
+
+3.49 Wed Nov 19 11:26:53 CET 2008
+ - ev_stat watchers will now use inotify as a mere hint on
+ kernels <2.6.25, or if the filesystem is not in the
+ "known to be good" list.
+ - better mingw32 compatibility (it's not as borked as native win32)
+ (analysed by Roger Pack).
+ - include stdio.h in the example program, as too many people are
+ confused by the weird C language otherwise. I guess the next thing
+ I get told is that the "..." ellipses in the examples don't compile
+ with their C compiler.
+
+3.48 Thu Oct 30 09:02:37 CET 2008
+ - further optimise away the EPOLL_CTL_ADD/MOD combo in the epoll
+ backend by assuming the kernel event mask hasn't changed if
+ ADD fails with EEXIST.
+ - work around spurious event notification bugs in epoll by using
+ a 32-bit generation counter. recreate kernel state if we receive
+ spurious notifications or unwanted events. this is very costly,
+ but I didn't come up with this horrible design.
+ - use memset to initialise most arrays now and do away with the
+ init functions.
+ - expand time-out strategies into a "Be smart about timeouts" section.
+ - drop the "struct" from all ev_watcher declarations in the
+ documentation and did other clarifications (yeah, it was a mistake
+ to have a struct AND a function called ev_loop).
+ - fix a bug where ev_default would not initialise the default
+ loop again after it was destroyed with ev_default_destroy.
+ - rename syserr to ev_syserr to avoid name clashes when embedding,
+ do similar changes for event.c.
+
+3.45 Tue Oct 21 21:59:26 CEST 2008
+ - disable inotify usage on linux <2.6.25, as it is broken
+ (reported by Yoann Vandoorselaere).
+ - ev_stat erroneously would try to add inotify watchers
+ even when inotify wasn't available (this should only
+ have a performance impact).
+ - ev_once now passes both timeout and io to the callback if both
+ occur concurrently, instead of giving timeouts precedence.
+ - disable EV_USE_INOTIFY when sys/inotify.h is too old.
+
+3.44 Mon Sep 29 05:18:39 CEST 2008
+ - embed watchers now automatically invoke ev_loop_fork on the
+ embedded loop when the parent loop forks.
+ - new function: ev_now_update (loop).
+ - verify_watcher was not marked static.
+ - improve the "associating..." manpage section.
+ - documentation tweaks here and there.
+
+3.43 Sun Jul 6 05:34:41 CEST 2008
+ - include more include files on windows to get struct _stati64
+ (reported by Chris Hulbert, but doesn't quite fix his issue).
+ - add missing #include <io.h> in ev.c on windows (reported by
+ Matt Tolton).
+
+3.42 Tue Jun 17 12:12:07 CEST 2008
+ - work around yet another windows bug: FD_SET actually adds fd's
+ multiple times to the fd_*SET*, despite official MSN docs claiming
+ otherwise. Reported and well-analysed by Matt Tolton.
+ - define NFDBITS to 0 when EV_SELECT_IS_WINSOCKET to make it compile
+ (reported any analysed by Chris Hulbert).
+ - fix a bug in ev_ebadf (this function is only used to catch
+ programming errors in the libev user). reported by Matt Tolton.
+ - fix a bug in fd_intern on win32 (could lead to compile errors
+ under some circumstances, but would work correctly if it compiles).
+ reported by Matt Tolton.
+ - (try to) work around missing lstat on windows.
+ - pass in the write fd set as except fd set under windows. windows
+ is so uncontrollably lame that it requires this. this means that
+ switching off oobinline is not supported (but tcp/ip doesn't
+ have oob, so that would be stupid anyways.
+ - use posix module symbol to auto-detect monotonic clock presence
+ and some other default values.
+
+3.41 Fri May 23 18:42:54 CEST 2008
+ - work around an obscure bug in winsocket select: if you
+ provide only empty fd sets then select returns WSAEINVAL. how sucky.
+ - improve timer scheduling stability and reduce use of time_epsilon.
+ - use 1-based 2-heap for EV_MINIMAL, simplifies code, reduces
+ codesize and makes for better cache-efficiency.
+ - use 3-based 4-heap for !EV_MINIMAL. this makes better use
+ of cpu cache lines and gives better growth behaviour than
+ 2-based heaps.
+ - cache timestamp within heap for !EV_MINIMAL, to avoid random
+ memory accesses.
+ - document/add EV_USE_4HEAP and EV_HEAP_CACHE_AT.
+ - fix a potential aliasing issue in ev_timer_again.
+ - add/document ev_periodic_at, retract direct access to ->at.
+ - improve ev_stat docs.
+ - add portability requirements section.
+ - fix manpage headers etc.
+ - normalise WSA error codes to lower range on windows.
+ - add consistency check code that can be called automatically
+ or on demand to check for internal structures (ev_loop_verify).
+
+3.31 Wed Apr 16 20:45:04 CEST 2008
+ - added last minute fix for ev_poll.c by Brandon Black.
+
+3.3 Wed Apr 16 19:04:10 CEST 2008
+ - event_base_loopexit should return 0 on success
+ (W.C.A. Wijngaards).
+ - added linux eventfd support.
+ - try to autodetect epoll and inotify support
+ by libc header version if not using autoconf.
+ - new symbols: EV_DEFAULT_UC and EV_DEFAULT_UC_.
+ - declare functions defined in ev.h as inline if
+ C99 or gcc are available.
+ - enable inlining with gcc versions 2 and 3.
+ - work around broken poll implementations potentially
+ not clearing revents field in ev_poll (Brandon Black)
+ (no such systems are known at this time).
+ - work around a bug in realloc on openbsd and darwin,
+ also makes the erroneous valgrind complaints
+ go away (noted by various people).
+ - fix ev_async_pending, add c++ wrapper for ev_async
+ (based on patch sent by Johannes Deisenhofer).
+ - add sensible set method to ev::embed.
+ - made integer constants type int in ev.h.
+
+3.2 Wed Apr 2 17:11:19 CEST 2008
+ - fix a 64 bit overflow issue in the select backend,
+ by using fd_mask instead of int for the mask.
+ - rename internal sighandler to avoid clash with very old perls.
+ - entering ev_loop will not clear the ONESHOT or NONBLOCKING
+ flags of any outer loops anymore.
+ - add ev_async_pending.
+
+3.1 Thu Mar 13 13:45:22 CET 2008
+ - implement ev_async watchers.
+ - only initialise signal pipe on demand.
+ - make use of sig_atomic_t configurable.
+ - improved documentation.
+
+3.0 Mon Jan 28 13:14:47 CET 2008
+ - API/ABI bump to version 3.0.
+ - ev++.h includes "ev.h" by default now, not <ev.h>.
+ - slightly improved documentation.
+ - speed up signal detection after a fork.
+ - only optionally return trace status changed in ev_child
+ watchers.
+ - experimental (and undocumented) loop wrappers for ev++.h.
+
+2.01 Tue Dec 25 08:04:41 CET 2007
+ - separate Changes file.
+ - fix ev_path_set => ev_stat_set typo.
+ - remove event_compat.h from the libev tarball.
+ - change how include files are found.
+ - doc updates.
+ - update licenses, explicitly allow for GPL relicensing.
+
+2.0 Sat Dec 22 17:47:03 CET 2007
+ - new ev_sleep, ev_set_(io|timeout)_collect_interval.
+ - removed epoll from embeddable fd set.
+ - fix embed watchers.
+ - renamed ev_embed.loop to other.
+ - added exported Symbol tables.
+ - undefine member wrapper macros at the end of ev.c.
+ - respect EV_H in ev++.h.
+
+1.86 Tue Dec 18 02:36:57 CET 2007
+ - fix memleak on loop destroy (not relevant for perl).
+
+1.85 Fri Dec 14 20:32:40 CET 2007
+ - fix some aliasing issues w.r.t. timers and periodics
+ (not relevant for perl).
+
+(for historic versions refer to EV/Changes, found in the Perl interface)
+
+0.1 Wed Oct 31 21:31:48 CET 2007
+ - original version; hacked together in <24h.
+
diff --git a/3rdparty/libev/LICENSE b/3rdparty/libev/LICENSE
new file mode 100644
index 0000000..2fdabd4
--- /dev/null
+++ b/3rdparty/libev/LICENSE
@@ -0,0 +1,37 @@
+All files in libev are
+Copyright (c)2007,2008,2009,2010,2011,2012,2013 Marc Alexander Lehmann.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Alternatively, the contents of this package may be used under the terms
+of the GNU General Public License ("GPL") version 2 or any later version,
+in which case the provisions of the GPL are applicable instead of the
+above. If you wish to allow the use of your version of this package only
+under the terms of the GPL and not to allow others to use your version of
+this file under the BSD license, indicate your decision by deleting the
+provisions above and replace them with the notice and other provisions
+required by the GPL in this and the other files of this package. If you do
+not delete the provisions above, a recipient may use your version of this
+file under either the BSD or the GPL.
diff --git a/3rdparty/libev/README b/3rdparty/libev/README
new file mode 100644
index 0000000..fca5fdf
--- /dev/null
+++ b/3rdparty/libev/README
@@ -0,0 +1,59 @@
+libev is a high-performance event loop/event model with lots of features.
+(see benchmark at http://libev.schmorp.de/bench.html)
+
+
+ABOUT
+
+ Homepage: http://software.schmorp.de/pkg/libev
+ Mailinglist: libev@lists.schmorp.de
+ http://lists.schmorp.de/cgi-bin/mailman/listinfo/libev
+ Library Documentation: http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod
+
+ Libev is modelled (very losely) after libevent and the Event perl
+ module, but is faster, scales better and is more correct, and also more
+ featureful. And also smaller. Yay.
+
+ Some of the specialties of libev not commonly found elsewhere are:
+
+ - extensive and detailed, readable documentation (not doxygen garbage).
+ - fully supports fork, can detect fork in various ways and automatically
+ re-arms kernel mechanisms that do not support fork.
+ - highly optimised select, poll, linux epoll, linux aio, bsd kqueue
+ and solaris event ports backends.
+ - filesystem object (path) watching (with optional linux inotify support).
+ - wallclock-based times (using absolute time, cron-like).
+ - relative timers/timeouts (handle time jumps).
+ - fast intra-thread communication between multiple
+ event loops (with optional fast linux eventfd backend).
+ - extremely easy to embed (fully documented, no dependencies,
+ autoconf supported but optional).
+ - very small codebase, no bloated library, simple code.
+ - fully extensible by being able to plug into the event loop,
+ integrate other event loops, integrate other event loop users.
+ - very little memory use (small watchers, small event loop data).
+ - optional C++ interface allowing method and function callbacks
+ at no extra memory or runtime overhead.
+ - optional Perl interface with similar characteristics (capable
+ of running Glib/Gtk2 on libev).
+ - support for other languages (multiple C++ interfaces, D, Ruby,
+ Python) available from third-parties.
+
+ Examples of programs that embed libev: the EV perl module, node.js,
+ auditd, rxvt-unicode, gvpe (GNU Virtual Private Ethernet), the
+ Deliantra MMORPG server (http://www.deliantra.net/), Rubinius (a
+ next-generation Ruby VM), the Ebb web server, the Rev event toolkit.
+
+
+CONTRIBUTORS
+
+ libev was written and designed by Marc Lehmann and Emanuele Giaquinta.
+
+ The following people sent in patches or made other noteworthy
+ contributions to the design (for minor patches, see the Changes
+ file. If I forgot to include you, please shout at me, it was an
+ accident):
+
+ W.C.A. Wijngaards
+ Christopher Layne
+ Chris Brody
+
diff --git a/3rdparty/libev/Symbols.ev b/3rdparty/libev/Symbols.ev
new file mode 100644
index 0000000..fe169fa
--- /dev/null
+++ b/3rdparty/libev/Symbols.ev
@@ -0,0 +1,73 @@
+ev_async_send
+ev_async_start
+ev_async_stop
+ev_backend
+ev_break
+ev_check_start
+ev_check_stop
+ev_child_start
+ev_child_stop
+ev_cleanup_start
+ev_cleanup_stop
+ev_clear_pending
+ev_default_loop
+ev_default_loop_ptr
+ev_depth
+ev_embeddable_backends
+ev_embed_start
+ev_embed_stop
+ev_embed_sweep
+ev_feed_event
+ev_feed_fd_event
+ev_feed_signal
+ev_feed_signal_event
+ev_fork_start
+ev_fork_stop
+ev_idle_start
+ev_idle_stop
+ev_invoke
+ev_invoke_pending
+ev_io_start
+ev_io_stop
+ev_iteration
+ev_loop_destroy
+ev_loop_fork
+ev_loop_new
+ev_now
+ev_now_update
+ev_once
+ev_pending_count
+ev_periodic_again
+ev_periodic_start
+ev_periodic_stop
+ev_prepare_start
+ev_prepare_stop
+ev_recommended_backends
+ev_ref
+ev_resume
+ev_run
+ev_set_allocator
+ev_set_invoke_pending_cb
+ev_set_io_collect_interval
+ev_set_loop_release_cb
+ev_set_syserr_cb
+ev_set_timeout_collect_interval
+ev_set_userdata
+ev_signal_start
+ev_signal_stop
+ev_sleep
+ev_stat_start
+ev_stat_stat
+ev_stat_stop
+ev_supported_backends
+ev_suspend
+ev_time
+ev_timer_again
+ev_timer_remaining
+ev_timer_start
+ev_timer_stop
+ev_unref
+ev_userdata
+ev_verify
+ev_version_major
+ev_version_minor
diff --git a/3rdparty/libev/Symbols.event b/3rdparty/libev/Symbols.event
new file mode 100644
index 0000000..799d424
--- /dev/null
+++ b/3rdparty/libev/Symbols.event
@@ -0,0 +1,24 @@
+event_active
+event_add
+event_base_dispatch
+event_base_free
+event_base_get_method
+event_base_loop
+event_base_loopexit
+event_base_new
+event_base_once
+event_base_priority_init
+event_base_set
+event_del
+event_dispatch
+event_get_callback
+event_get_method
+event_get_version
+event_init
+event_loop
+event_loopexit
+event_once
+event_pending
+event_priority_init
+event_priority_set
+event_set
diff --git a/3rdparty/libev/TODO b/3rdparty/libev/TODO
new file mode 100644
index 0000000..a9d2c91
--- /dev/null
+++ b/3rdparty/libev/TODO
@@ -0,0 +1,14 @@
+TODO: ev_loop_wakeup
+TODO: EV_STANDALONE == NO_HASSLE (do not use clock_gettime in ev_standalone)
+TODO: faq, process a thing in each iteration
+TODO: dbeugging tips, ev_verify, ev_init twice
+TODO: ev_break for immediate exit (EVBREAK_NOW?)
+TODO: ev_feed_child_event
+TODO: document the special problem of signals around fork.
+TODO: store pid for each signal
+TODO: document file descriptor usage per loop
+TODO: store loop pid_t and compare isndie signal handler,store 1 for same, 2 for differign pid, clean up in loop_fork
+TODO: embed watchers need updating when fd changes
+TODO: document portability requirements for atomic pointer access
+TODO: document requirements for function pointers and calling conventions.
+
diff --git a/3rdparty/libev/cmake_config.h.in b/3rdparty/libev/cmake_config.h.in
new file mode 100644
index 0000000..ed0555c
--- /dev/null
+++ b/3rdparty/libev/cmake_config.h.in
@@ -0,0 +1,141 @@
+/* config.h.in. Generated from configure.ac by autoheader. */
+
+/* Define to 1 if you have the `clock_gettime' function. */
+#cmakedefine HAVE_CLOCK_GETTIME 1
+
+/* Define to 1 to use the syscall interface for clock_gettime */
+#cmakedefine HAVE_CLOCK_SYSCALL 1
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#cmakedefine HAVE_DLFCN_H 1
+
+/* Define to 1 if you have the `epoll_ctl' function. */
+#cmakedefine HAVE_EPOLL_CTL 1
+
+/* Define to 1 if you have the `eventfd' function. */
+#cmakedefine HAVE_EVENTFD 1
+
+/* Define to 1 if the floor function is available */
+#cmakedefine HAVE_FLOOR 1
+
+/* Define to 1 if you have the `inotify_init' function. */
+#cmakedefine HAVE_INOTIFY_INIT 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#cmakedefine HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `kqueue' function. */
+#cmakedefine HAVE_KQUEUE 1
+
+/* Define to 1 if you have the `rt' library (-lrt). */
+#cmakedefine HAVE_LIBRT 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#cmakedefine HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `nanosleep' function. */
+#cmakedefine HAVE_NANOSLEEP 1
+
+/* Define to 1 if you have the `poll' function. */
+#cmakedefine HAVE_POLL 1
+
+/* Define to 1 if you have the <poll.h> header file. */
+#cmakedefine HAVE_POLL_H 1
+
+/* Define to 1 if you have the `port_create' function. */
+#cmakedefine HAVE_PORT_CREATE 1
+
+/* Define to 1 if you have the <port.h> header file. */
+#cmakedefine HAVE_PORT_H 1
+
+/* Define to 1 if you have the `select' function. */
+#cmakedefine HAVE_SELECT 1
+
+/* Define to 1 if you have the `signalfd' function. */
+#cmakedefine HAVE_SIGNALFD 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#cmakedefine HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#cmakedefine HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#cmakedefine HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#cmakedefine HAVE_STRING_H 1
+
+/* Define to 1 if you have the <sys/epoll.h> header file. */
+#cmakedefine HAVE_SYS_EPOLL_H 1
+
+/* Define to 1 if you have the <sys/eventfd.h> header file. */
+#cmakedefine HAVE_SYS_EVENTFD_H 1
+
+/* Define to 1 if you have the <sys/event.h> header file. */
+#cmakedefine HAVE_SYS_EVENT_H 1
+
+/* Define to 1 if you have the <sys/inotify.h> header file. */
+#cmakedefine HAVE_SYS_INOTIFY_H 1
+
+/* Define to 1 if you have the <sys/select.h> header file. */
+#cmakedefine HAVE_SYS_SELECT_H 1
+
+/* Define to 1 if you have the <sys/signalfd.h> header file. */
+#cmakedefine HAVE_SYS_SIGNALFD_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#cmakedefine HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#cmakedefine HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#cmakedefine HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the <linux/aio_abi.h> header file. */
+#cmakedefine HAVE_LINUX_AIO_ABI_H 1
+
+
+/* Define to the sub-directory where libtool stores uninstalled libraries. */
+#cmakedefine LT_OBJDIR
+
+/* Name of package */
+#cmakedefine PACKAGE
+
+/* Define to the address where bug reports for this package should be sent. */
+#cmakedefine PACKAGE_BUGREPORT
+
+/* Define to the full name of this package. */
+#cmakedefine PACKAGE_NAME
+
+/* Define to the full name and version of this package. */
+#cmakedefine PACKAGE_STRING
+
+/* Define to the one symbol short name of this package. */
+#cmakedefine PACKAGE_TARNAME
+
+/* Define to the home page for this package. */
+#cmakedefine PACKAGE_URL
+
+/* Define to the version of this package. */
+#cmakedefine PACKAGE_VERSION
+
+/* Define to 1 if you have the ANSI C header files. */
+#cmakedefine STDC_HEADERS
+
+/* Define to 1 if you have the <linux/aio_abi.h> header file. */
+#cmakedefine HAVE_LINUX_AIO_ABI_H 0
+
+/* Define to 1 if linux/fs.h defined kernel_rwf_t */
+#cmakedefine HAVE_KERNEL_RWF_T 0
+
+/* Define to 1 if you have the <linux/fs.h> header file. */
+#cmakedefine HAVE_LINUX_FS_H 0
+
+/* Define to 1 if you have the <sys/timerfd.h> header file. */
+#cmakedefine HAVE_SYS_TIMERFD_H 0
+
+
+/* Version number of package */
+#cmakedefine VERSION "@libev_VERSION_MAJOR@.@libev_VERSION_MINOR@"
diff --git a/3rdparty/libev/ev++.h b/3rdparty/libev/ev++.h
new file mode 100644
index 0000000..22dfcf5
--- /dev/null
+++ b/3rdparty/libev/ev++.h
@@ -0,0 +1,818 @@
+/*
+ * libev simple C++ wrapper classes
+ *
+ * Copyright (c) 2007,2008,2010,2018,2020 Marc Alexander Lehmann <libev@schmorp.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
+ * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
+ * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
+ * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License ("GPL") version 2 or any later version,
+ * in which case the provisions of the GPL are applicable instead of
+ * the above. If you wish to allow the use of your version of this file
+ * only under the terms of the GPL and not to allow others to use your
+ * version of this file under the BSD license, indicate your decision
+ * by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL. If you do not delete the
+ * provisions above, a recipient may use your version of this file under
+ * either the BSD or the GPL.
+ */
+
+#ifndef EVPP_H__
+#define EVPP_H__
+
+#ifdef EV_H
+# include EV_H
+#else
+# include "ev.h"
+#endif
+
+#ifndef EV_USE_STDEXCEPT
+# define EV_USE_STDEXCEPT 1
+#endif
+
+#if EV_USE_STDEXCEPT
+# include <stdexcept>
+#endif
+
+namespace ev {
+
+ typedef ev_tstamp tstamp;
+
+ enum {
+ UNDEF = EV_UNDEF,
+ NONE = EV_NONE,
+ READ = EV_READ,
+ WRITE = EV_WRITE,
+#if EV_COMPAT3
+ TIMEOUT = EV_TIMEOUT,
+#endif
+ TIMER = EV_TIMER,
+ PERIODIC = EV_PERIODIC,
+ SIGNAL = EV_SIGNAL,
+ CHILD = EV_CHILD,
+ STAT = EV_STAT,
+ IDLE = EV_IDLE,
+ CHECK = EV_CHECK,
+ PREPARE = EV_PREPARE,
+ FORK = EV_FORK,
+ ASYNC = EV_ASYNC,
+ EMBED = EV_EMBED,
+# undef ERROR // some systems stupidly #define ERROR
+ ERROR = EV_ERROR
+ };
+
+ enum
+ {
+ AUTO = EVFLAG_AUTO,
+ NOENV = EVFLAG_NOENV,
+ FORKCHECK = EVFLAG_FORKCHECK,
+
+ SELECT = EVBACKEND_SELECT,
+ POLL = EVBACKEND_POLL,
+ EPOLL = EVBACKEND_EPOLL,
+ KQUEUE = EVBACKEND_KQUEUE,
+ DEVPOLL = EVBACKEND_DEVPOLL,
+ PORT = EVBACKEND_PORT
+ };
+
+ enum
+ {
+#if EV_COMPAT3
+ NONBLOCK = EVLOOP_NONBLOCK,
+ ONESHOT = EVLOOP_ONESHOT,
+#endif
+ NOWAIT = EVRUN_NOWAIT,
+ ONCE = EVRUN_ONCE
+ };
+
+ enum how_t
+ {
+ ONE = EVBREAK_ONE,
+ ALL = EVBREAK_ALL
+ };
+
+ struct bad_loop
+#if EV_USE_STDEXCEPT
+ : std::exception
+#endif
+ {
+#if EV_USE_STDEXCEPT
+ const char *what () const EV_NOEXCEPT
+ {
+ return "libev event loop cannot be initialized, bad value of LIBEV_FLAGS?";
+ }
+#endif
+ };
+
+#ifdef EV_AX
+# undef EV_AX
+#endif
+
+#ifdef EV_AX_
+# undef EV_AX_
+#endif
+
+#if EV_MULTIPLICITY
+# define EV_AX raw_loop
+# define EV_AX_ raw_loop,
+#else
+# define EV_AX
+# define EV_AX_
+#endif
+
+ struct loop_ref
+ {
+ loop_ref (EV_P) EV_NOEXCEPT
+#if EV_MULTIPLICITY
+ : EV_AX (EV_A)
+#endif
+ {
+ }
+
+ bool operator == (const loop_ref &other) const EV_NOEXCEPT
+ {
+#if EV_MULTIPLICITY
+ return EV_AX == other.EV_AX;
+#else
+ return true;
+#endif
+ }
+
+ bool operator != (const loop_ref &other) const EV_NOEXCEPT
+ {
+#if EV_MULTIPLICITY
+ return ! (*this == other);
+#else
+ return false;
+#endif
+ }
+
+#if EV_MULTIPLICITY
+ bool operator == (const EV_P) const EV_NOEXCEPT
+ {
+ return this->EV_AX == EV_A;
+ }
+
+ bool operator != (const EV_P) const EV_NOEXCEPT
+ {
+ return ! (*this == EV_A);
+ }
+
+ operator struct ev_loop * () const EV_NOEXCEPT
+ {
+ return EV_AX;
+ }
+
+ operator const struct ev_loop * () const EV_NOEXCEPT
+ {
+ return EV_AX;
+ }
+
+ bool is_default () const EV_NOEXCEPT
+ {
+ return EV_AX == ev_default_loop (0);
+ }
+#endif
+
+#if EV_COMPAT3
+ void loop (int flags = 0)
+ {
+ ev_run (EV_AX_ flags);
+ }
+
+ void unloop (how_t how = ONE) EV_NOEXCEPT
+ {
+ ev_break (EV_AX_ how);
+ }
+#endif
+
+ void run (int flags = 0)
+ {
+ ev_run (EV_AX_ flags);
+ }
+
+ void break_loop (how_t how = ONE) EV_NOEXCEPT
+ {
+ ev_break (EV_AX_ how);
+ }
+
+ void post_fork () EV_NOEXCEPT
+ {
+ ev_loop_fork (EV_AX);
+ }
+
+ unsigned int backend () const EV_NOEXCEPT
+ {
+ return ev_backend (EV_AX);
+ }
+
+ tstamp now () const EV_NOEXCEPT
+ {
+ return ev_now (EV_AX);
+ }
+
+ void ref () EV_NOEXCEPT
+ {
+ ev_ref (EV_AX);
+ }
+
+ void unref () EV_NOEXCEPT
+ {
+ ev_unref (EV_AX);
+ }
+
+#if EV_FEATURE_API
+ unsigned int iteration () const EV_NOEXCEPT
+ {
+ return ev_iteration (EV_AX);
+ }
+
+ unsigned int depth () const EV_NOEXCEPT
+ {
+ return ev_depth (EV_AX);
+ }
+
+ void set_io_collect_interval (tstamp interval) EV_NOEXCEPT
+ {
+ ev_set_io_collect_interval (EV_AX_ interval);
+ }
+
+ void set_timeout_collect_interval (tstamp interval) EV_NOEXCEPT
+ {
+ ev_set_timeout_collect_interval (EV_AX_ interval);
+ }
+#endif
+
+ // function callback
+ void once (int fd, int events, tstamp timeout, void (*cb)(int, void *), void *arg = 0) EV_NOEXCEPT
+ {
+ ev_once (EV_AX_ fd, events, timeout, cb, arg);
+ }
+
+ // method callback
+ template<class K, void (K::*method)(int)>
+ void once (int fd, int events, tstamp timeout, K *object) EV_NOEXCEPT
+ {
+ once (fd, events, timeout, method_thunk<K, method>, object);
+ }
+
+ // default method == operator ()
+ template<class K>
+ void once (int fd, int events, tstamp timeout, K *object) EV_NOEXCEPT
+ {
+ once (fd, events, timeout, method_thunk<K, &K::operator ()>, object);
+ }
+
+ template<class K, void (K::*method)(int)>
+ static void method_thunk (int revents, void *arg)
+ {
+ (static_cast<K *>(arg)->*method)
+ (revents);
+ }
+
+ // no-argument method callback
+ template<class K, void (K::*method)()>
+ void once (int fd, int events, tstamp timeout, K *object) EV_NOEXCEPT
+ {
+ once (fd, events, timeout, method_noargs_thunk<K, method>, object);
+ }
+
+ template<class K, void (K::*method)()>
+ static void method_noargs_thunk (int revents, void *arg)
+ {
+ (static_cast<K *>(arg)->*method)
+ ();
+ }
+
+ // simpler function callback
+ template<void (*cb)(int)>
+ void once (int fd, int events, tstamp timeout) EV_NOEXCEPT
+ {
+ once (fd, events, timeout, simpler_func_thunk<cb>);
+ }
+
+ template<void (*cb)(int)>
+ static void simpler_func_thunk (int revents, void *arg)
+ {
+ (*cb)
+ (revents);
+ }
+
+ // simplest function callback
+ template<void (*cb)()>
+ void once (int fd, int events, tstamp timeout) EV_NOEXCEPT
+ {
+ once (fd, events, timeout, simplest_func_thunk<cb>);
+ }
+
+ template<void (*cb)()>
+ static void simplest_func_thunk (int revents, void *arg)
+ {
+ (*cb)
+ ();
+ }
+
+ void feed_fd_event (int fd, int revents) EV_NOEXCEPT
+ {
+ ev_feed_fd_event (EV_AX_ fd, revents);
+ }
+
+ void feed_signal_event (int signum) EV_NOEXCEPT
+ {
+ ev_feed_signal_event (EV_AX_ signum);
+ }
+
+#if EV_MULTIPLICITY
+ struct ev_loop* EV_AX;
+#endif
+
+ };
+
+#if EV_MULTIPLICITY
+ struct dynamic_loop : loop_ref
+ {
+
+ dynamic_loop (unsigned int flags = AUTO)
+ : loop_ref (ev_loop_new (flags))
+ {
+ if (!EV_AX)
+ throw bad_loop ();
+ }
+
+ ~dynamic_loop () EV_NOEXCEPT
+ {
+ ev_loop_destroy (EV_AX);
+ EV_AX = 0;
+ }
+
+ private:
+
+ dynamic_loop (const dynamic_loop &);
+
+ dynamic_loop & operator= (const dynamic_loop &);
+
+ };
+#endif
+
+ struct default_loop : loop_ref
+ {
+ default_loop (unsigned int flags = AUTO)
+#if EV_MULTIPLICITY
+ : loop_ref (ev_default_loop (flags))
+#endif
+ {
+ if (
+#if EV_MULTIPLICITY
+ !EV_AX
+#else
+ !ev_default_loop (flags)
+#endif
+ )
+ throw bad_loop ();
+ }
+
+ private:
+ default_loop (const default_loop &);
+ default_loop &operator = (const default_loop &);
+ };
+
+ inline loop_ref get_default_loop () EV_NOEXCEPT
+ {
+#if EV_MULTIPLICITY
+ return ev_default_loop (0);
+#else
+ return loop_ref ();
+#endif
+ }
+
+#undef EV_AX
+#undef EV_AX_
+
+#undef EV_PX
+#undef EV_PX_
+#if EV_MULTIPLICITY
+# define EV_PX loop_ref EV_A
+# define EV_PX_ loop_ref EV_A_
+#else
+# define EV_PX
+# define EV_PX_
+#endif
+
+ template<class ev_watcher, class watcher>
+ struct base : ev_watcher
+ {
+ // scoped pause/unpause of a watcher
+ struct freeze_guard
+ {
+ watcher &w;
+ bool active;
+
+ freeze_guard (watcher *self) EV_NOEXCEPT
+ : w (*self), active (w.is_active ())
+ {
+ if (active) w.stop ();
+ }
+
+ ~freeze_guard ()
+ {
+ if (active) w.start ();
+ }
+ };
+
+ #if EV_MULTIPLICITY
+ EV_PX;
+
+ // loop set
+ void set (EV_P) EV_NOEXCEPT
+ {
+ this->EV_A = EV_A;
+ }
+ #endif
+
+ base (EV_PX) EV_NOEXCEPT
+ #if EV_MULTIPLICITY
+ : EV_A (EV_A)
+ #endif
+ {
+ ev_init (this, 0);
+ }
+
+ void set_ (const void *data, void (*cb)(EV_P_ ev_watcher *w, int revents)) EV_NOEXCEPT
+ {
+ this->data = (void *)data;
+ ev_set_cb (static_cast<ev_watcher *>(this), cb);
+ }
+
+ // function callback
+ template<void (*function)(watcher &w, int)>
+ void set (void *data = 0) EV_NOEXCEPT
+ {
+ set_ (data, function_thunk<function>);
+ }
+
+ template<void (*function)(watcher &w, int)>
+ static void function_thunk (EV_P_ ev_watcher *w, int revents)
+ {
+ function
+ (*static_cast<watcher *>(w), revents);
+ }
+
+ // method callback
+ template<class K, void (K::*method)(watcher &w, int)>
+ void set (K *object) EV_NOEXCEPT
+ {
+ set_ (object, method_thunk<K, method>);
+ }
+
+ // default method == operator ()
+ template<class K>
+ void set (K *object) EV_NOEXCEPT
+ {
+ set_ (object, method_thunk<K, &K::operator ()>);
+ }
+
+ template<class K, void (K::*method)(watcher &w, int)>
+ static void method_thunk (EV_P_ ev_watcher *w, int revents)
+ {
+ (static_cast<K *>(w->data)->*method)
+ (*static_cast<watcher *>(w), revents);
+ }
+
+ // no-argument callback
+ template<class K, void (K::*method)()>
+ void set (K *object) EV_NOEXCEPT
+ {
+ set_ (object, method_noargs_thunk<K, method>);
+ }
+
+ template<class K, void (K::*method)()>
+ static void method_noargs_thunk (EV_P_ ev_watcher *w, int revents)
+ {
+ (static_cast<K *>(w->data)->*method)
+ ();
+ }
+
+ void operator ()(int events = EV_UNDEF)
+ {
+ return
+ ev_cb (static_cast<ev_watcher *>(this))
+ (static_cast<ev_watcher *>(this), events);
+ }
+
+ bool is_active () const EV_NOEXCEPT
+ {
+ return ev_is_active (static_cast<const ev_watcher *>(this));
+ }
+
+ bool is_pending () const EV_NOEXCEPT
+ {
+ return ev_is_pending (static_cast<const ev_watcher *>(this));
+ }
+
+ void feed_event (int revents) EV_NOEXCEPT
+ {
+ ev_feed_event (EV_A_ static_cast<ev_watcher *>(this), revents);
+ }
+ };
+
+ inline tstamp now (EV_P) EV_NOEXCEPT
+ {
+ return ev_now (EV_A);
+ }
+
+ inline void delay (tstamp interval) EV_NOEXCEPT
+ {
+ ev_sleep (interval);
+ }
+
+ inline int version_major () EV_NOEXCEPT
+ {
+ return ev_version_major ();
+ }
+
+ inline int version_minor () EV_NOEXCEPT
+ {
+ return ev_version_minor ();
+ }
+
+ inline unsigned int supported_backends () EV_NOEXCEPT
+ {
+ return ev_supported_backends ();
+ }
+
+ inline unsigned int recommended_backends () EV_NOEXCEPT
+ {
+ return ev_recommended_backends ();
+ }
+
+ inline unsigned int embeddable_backends () EV_NOEXCEPT
+ {
+ return ev_embeddable_backends ();
+ }
+
+ inline void set_allocator (void *(*cb)(void *ptr, long size) EV_NOEXCEPT) EV_NOEXCEPT
+ {
+ ev_set_allocator (cb);
+ }
+
+ inline void set_syserr_cb (void (*cb)(const char *msg) EV_NOEXCEPT) EV_NOEXCEPT
+ {
+ ev_set_syserr_cb (cb);
+ }
+
+ #if EV_MULTIPLICITY
+ #define EV_CONSTRUCT(cppstem,cstem) \
+ (EV_PX = get_default_loop ()) EV_NOEXCEPT \
+ : base<ev_ ## cstem, cppstem> (EV_A) \
+ { \
+ }
+ #else
+ #define EV_CONSTRUCT(cppstem,cstem) \
+ () EV_NOEXCEPT \
+ { \
+ }
+ #endif
+
+ /* using a template here would require quite a few more lines,
+ * so a macro solution was chosen */
+ #define EV_BEGIN_WATCHER(cppstem,cstem) \
+ \
+ struct cppstem : base<ev_ ## cstem, cppstem> \
+ { \
+ void start () EV_NOEXCEPT \
+ { \
+ ev_ ## cstem ## _start (EV_A_ static_cast<ev_ ## cstem *>(this)); \
+ } \
+ \
+ void stop () EV_NOEXCEPT \
+ { \
+ ev_ ## cstem ## _stop (EV_A_ static_cast<ev_ ## cstem *>(this)); \
+ } \
+ \
+ cppstem EV_CONSTRUCT(cppstem,cstem) \
+ \
+ ~cppstem () EV_NOEXCEPT \
+ { \
+ stop (); \
+ } \
+ \
+ using base<ev_ ## cstem, cppstem>::set; \
+ \
+ private: \
+ \
+ cppstem (const cppstem &o); \
+ \
+ cppstem &operator =(const cppstem &o); \
+ \
+ public:
+
+ #define EV_END_WATCHER(cppstem,cstem) \
+ };
+
+ EV_BEGIN_WATCHER (io, io)
+ void set (int fd, int events) EV_NOEXCEPT
+ {
+ freeze_guard freeze (this);
+ ev_io_set (static_cast<ev_io *>(this), fd, events);
+ }
+
+ void set (int events) EV_NOEXCEPT
+ {
+ freeze_guard freeze (this);
+ ev_io_modify (static_cast<ev_io *>(this), events);
+ }
+
+ void start (int fd, int events) EV_NOEXCEPT
+ {
+ set (fd, events);
+ start ();
+ }
+ EV_END_WATCHER (io, io)
+
+ EV_BEGIN_WATCHER (timer, timer)
+ void set (ev_tstamp after, ev_tstamp repeat = 0.) EV_NOEXCEPT
+ {
+ freeze_guard freeze (this);
+ ev_timer_set (static_cast<ev_timer *>(this), after, repeat);
+ }
+
+ void start (ev_tstamp after, ev_tstamp repeat = 0.) EV_NOEXCEPT
+ {
+ set (after, repeat);
+ start ();
+ }
+
+ void again () EV_NOEXCEPT
+ {
+ ev_timer_again (EV_A_ static_cast<ev_timer *>(this));
+ }
+
+ ev_tstamp remaining ()
+ {
+ return ev_timer_remaining (EV_A_ static_cast<ev_timer *>(this));
+ }
+ EV_END_WATCHER (timer, timer)
+
+ #if EV_PERIODIC_ENABLE
+ EV_BEGIN_WATCHER (periodic, periodic)
+ void set (ev_tstamp at, ev_tstamp interval = 0.) EV_NOEXCEPT
+ {
+ freeze_guard freeze (this);
+ ev_periodic_set (static_cast<ev_periodic *>(this), at, interval, 0);
+ }
+
+ void start (ev_tstamp at, ev_tstamp interval = 0.) EV_NOEXCEPT
+ {
+ set (at, interval);
+ start ();
+ }
+
+ void again () EV_NOEXCEPT
+ {
+ ev_periodic_again (EV_A_ static_cast<ev_periodic *>(this));
+ }
+ EV_END_WATCHER (periodic, periodic)
+ #endif
+
+ #if EV_SIGNAL_ENABLE
+ EV_BEGIN_WATCHER (sig, signal)
+ void set (int signum) EV_NOEXCEPT
+ {
+ freeze_guard freeze (this);
+ ev_signal_set (static_cast<ev_signal *>(this), signum);
+ }
+
+ void start (int signum) EV_NOEXCEPT
+ {
+ set (signum);
+ start ();
+ }
+ EV_END_WATCHER (sig, signal)
+ #endif
+
+ #if EV_CHILD_ENABLE
+ EV_BEGIN_WATCHER (child, child)
+ void set (int pid, int trace = 0) EV_NOEXCEPT
+ {
+ freeze_guard freeze (this);
+ ev_child_set (static_cast<ev_child *>(this), pid, trace);
+ }
+
+ void start (int pid, int trace = 0) EV_NOEXCEPT
+ {
+ set (pid, trace);
+ start ();
+ }
+ EV_END_WATCHER (child, child)
+ #endif
+
+ #if EV_STAT_ENABLE
+ EV_BEGIN_WATCHER (stat, stat)
+ void set (const char *path, ev_tstamp interval = 0.) EV_NOEXCEPT
+ {
+ freeze_guard freeze (this);
+ ev_stat_set (static_cast<ev_stat *>(this), path, interval);
+ }
+
+ void start (const char *path, ev_tstamp interval = 0.) EV_NOEXCEPT
+ {
+ stop ();
+ set (path, interval);
+ start ();
+ }
+
+ void update () EV_NOEXCEPT
+ {
+ ev_stat_stat (EV_A_ static_cast<ev_stat *>(this));
+ }
+ EV_END_WATCHER (stat, stat)
+ #endif
+
+ #if EV_IDLE_ENABLE
+ EV_BEGIN_WATCHER (idle, idle)
+ void set () EV_NOEXCEPT { }
+ EV_END_WATCHER (idle, idle)
+ #endif
+
+ #if EV_PREPARE_ENABLE
+ EV_BEGIN_WATCHER (prepare, prepare)
+ void set () EV_NOEXCEPT { }
+ EV_END_WATCHER (prepare, prepare)
+ #endif
+
+ #if EV_CHECK_ENABLE
+ EV_BEGIN_WATCHER (check, check)
+ void set () EV_NOEXCEPT { }
+ EV_END_WATCHER (check, check)
+ #endif
+
+ #if EV_EMBED_ENABLE
+ EV_BEGIN_WATCHER (embed, embed)
+ void set_embed (struct ev_loop *embedded_loop) EV_NOEXCEPT
+ {
+ freeze_guard freeze (this);
+ ev_embed_set (static_cast<ev_embed *>(this), embedded_loop);
+ }
+
+ void start (struct ev_loop *embedded_loop) EV_NOEXCEPT
+ {
+ set (embedded_loop);
+ start ();
+ }
+
+ void sweep ()
+ {
+ ev_embed_sweep (EV_A_ static_cast<ev_embed *>(this));
+ }
+ EV_END_WATCHER (embed, embed)
+ #endif
+
+ #if EV_FORK_ENABLE
+ EV_BEGIN_WATCHER (fork, fork)
+ void set () EV_NOEXCEPT { }
+ EV_END_WATCHER (fork, fork)
+ #endif
+
+ #if EV_ASYNC_ENABLE
+ EV_BEGIN_WATCHER (async, async)
+ void send () EV_NOEXCEPT
+ {
+ ev_async_send (EV_A_ static_cast<ev_async *>(this));
+ }
+
+ bool async_pending () EV_NOEXCEPT
+ {
+ return ev_async_pending (static_cast<ev_async *>(this));
+ }
+ EV_END_WATCHER (async, async)
+ #endif
+
+ #undef EV_PX
+ #undef EV_PX_
+ #undef EV_CONSTRUCT
+ #undef EV_BEGIN_WATCHER
+ #undef EV_END_WATCHER
+}
+
+#endif
+
diff --git a/3rdparty/libev/ev.3 b/3rdparty/libev/ev.3
new file mode 100644
index 0000000..af578f0
--- /dev/null
+++ b/3rdparty/libev/ev.3
@@ -0,0 +1,5819 @@
+.\" Automatically generated by Pod::Man 4.11 (Pod::Simple 3.35)
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+. ds C`
+. ds C'
+'br\}
+.\"
+.\" Escape single quotes in literal strings from groff's Unicode transform.
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.\"
+.\" If the F register is >0, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.\"
+.\" Avoid warning from groff about undefined register 'F'.
+.de IX
+..
+.nr rF 0
+.if \n(.g .if rF .nr rF 1
+.if (\n(rF:(\n(.g==0)) \{\
+. if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. if !\nF==2 \{\
+. nr % 0
+. nr F 2
+. \}
+. \}
+.\}
+.rr rF
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "LIBEV 3"
+.TH LIBEV 3 "2020-03-12" "libev-4.31" "libev - high performance full featured event loop"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.if n .ad l
+.nh
+.SH "NAME"
+libev \- a high performance full\-featured event loop written in C
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+.Vb 1
+\& #include <ev.h>
+.Ve
+.SS "\s-1EXAMPLE PROGRAM\s0"
+.IX Subsection "EXAMPLE PROGRAM"
+.Vb 2
+\& // a single header file is required
+\& #include <ev.h>
+\&
+\& #include <stdio.h> // for puts
+\&
+\& // every watcher type has its own typedef\*(Aqd struct
+\& // with the name ev_TYPE
+\& ev_io stdin_watcher;
+\& ev_timer timeout_watcher;
+\&
+\& // all watcher callbacks have a similar signature
+\& // this callback is called when data is readable on stdin
+\& static void
+\& stdin_cb (EV_P_ ev_io *w, int revents)
+\& {
+\& puts ("stdin ready");
+\& // for one\-shot events, one must manually stop the watcher
+\& // with its corresponding stop function.
+\& ev_io_stop (EV_A_ w);
+\&
+\& // this causes all nested ev_run\*(Aqs to stop iterating
+\& ev_break (EV_A_ EVBREAK_ALL);
+\& }
+\&
+\& // another callback, this time for a time\-out
+\& static void
+\& timeout_cb (EV_P_ ev_timer *w, int revents)
+\& {
+\& puts ("timeout");
+\& // this causes the innermost ev_run to stop iterating
+\& ev_break (EV_A_ EVBREAK_ONE);
+\& }
+\&
+\& int
+\& main (void)
+\& {
+\& // use the default event loop unless you have special needs
+\& struct ev_loop *loop = EV_DEFAULT;
+\&
+\& // initialise an io watcher, then start it
+\& // this one will watch for stdin to become readable
+\& ev_io_init (&stdin_watcher, stdin_cb, /*STDIN_FILENO*/ 0, EV_READ);
+\& ev_io_start (loop, &stdin_watcher);
+\&
+\& // initialise a timer watcher, then start it
+\& // simple non\-repeating 5.5 second timeout
+\& ev_timer_init (&timeout_watcher, timeout_cb, 5.5, 0.);
+\& ev_timer_start (loop, &timeout_watcher);
+\&
+\& // now wait for events to arrive
+\& ev_run (loop, 0);
+\&
+\& // break was called, so exit
+\& return 0;
+\& }
+.Ve
+.SH "ABOUT THIS DOCUMENT"
+.IX Header "ABOUT THIS DOCUMENT"
+This document documents the libev software package.
+.PP
+The newest version of this document is also available as an html-formatted
+web page you might find easier to navigate when reading it for the first
+time: <http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod>.
+.PP
+While this document tries to be as complete as possible in documenting
+libev, its usage and the rationale behind its design, it is not a tutorial
+on event-based programming, nor will it introduce event-based programming
+with libev.
+.PP
+Familiarity with event based programming techniques in general is assumed
+throughout this document.
+.SH "WHAT TO READ WHEN IN A HURRY"
+.IX Header "WHAT TO READ WHEN IN A HURRY"
+This manual tries to be very detailed, but unfortunately, this also makes
+it very long. If you just want to know the basics of libev, I suggest
+reading \*(L"\s-1ANATOMY OF A WATCHER\*(R"\s0, then the \*(L"\s-1EXAMPLE PROGRAM\*(R"\s0 above and
+look up the missing functions in \*(L"\s-1GLOBAL FUNCTIONS\*(R"\s0 and the \f(CW\*(C`ev_io\*(C'\fR and
+\&\f(CW\*(C`ev_timer\*(C'\fR sections in \*(L"\s-1WATCHER TYPES\*(R"\s0.
+.SH "ABOUT LIBEV"
+.IX Header "ABOUT LIBEV"
+Libev is an event loop: you register interest in certain events (such as a
+file descriptor being readable or a timeout occurring), and it will manage
+these event sources and provide your program with events.
+.PP
+To do this, it must take more or less complete control over your process
+(or thread) by executing the \fIevent loop\fR handler, and will then
+communicate events via a callback mechanism.
+.PP
+You register interest in certain events by registering so-called \fIevent
+watchers\fR, which are relatively small C structures you initialise with the
+details of the event, and then hand it over to libev by \fIstarting\fR the
+watcher.
+.SS "\s-1FEATURES\s0"
+.IX Subsection "FEATURES"
+Libev supports \f(CW\*(C`select\*(C'\fR, \f(CW\*(C`poll\*(C'\fR, the Linux-specific aio and \f(CW\*(C`epoll\*(C'\fR
+interfaces, the BSD-specific \f(CW\*(C`kqueue\*(C'\fR and the Solaris-specific event port
+mechanisms for file descriptor events (\f(CW\*(C`ev_io\*(C'\fR), the Linux \f(CW\*(C`inotify\*(C'\fR
+interface (for \f(CW\*(C`ev_stat\*(C'\fR), Linux eventfd/signalfd (for faster and cleaner
+inter-thread wakeup (\f(CW\*(C`ev_async\*(C'\fR)/signal handling (\f(CW\*(C`ev_signal\*(C'\fR)) relative
+timers (\f(CW\*(C`ev_timer\*(C'\fR), absolute timers with customised rescheduling
+(\f(CW\*(C`ev_periodic\*(C'\fR), synchronous signals (\f(CW\*(C`ev_signal\*(C'\fR), process status
+change events (\f(CW\*(C`ev_child\*(C'\fR), and event watchers dealing with the event
+loop mechanism itself (\f(CW\*(C`ev_idle\*(C'\fR, \f(CW\*(C`ev_embed\*(C'\fR, \f(CW\*(C`ev_prepare\*(C'\fR and
+\&\f(CW\*(C`ev_check\*(C'\fR watchers) as well as file watchers (\f(CW\*(C`ev_stat\*(C'\fR) and even
+limited support for fork events (\f(CW\*(C`ev_fork\*(C'\fR).
+.PP
+It also is quite fast (see this
+benchmark <http://libev.schmorp.de/bench.html> comparing it to libevent
+for example).
+.SS "\s-1CONVENTIONS\s0"
+.IX Subsection "CONVENTIONS"
+Libev is very configurable. In this manual the default (and most common)
+configuration will be described, which supports multiple event loops. For
+more info about various configuration options please have a look at
+\&\fB\s-1EMBED\s0\fR section in this manual. If libev was configured without support
+for multiple event loops, then all functions taking an initial argument of
+name \f(CW\*(C`loop\*(C'\fR (which is always of type \f(CW\*(C`struct ev_loop *\*(C'\fR) will not have
+this argument.
+.SS "\s-1TIME REPRESENTATION\s0"
+.IX Subsection "TIME REPRESENTATION"
+Libev represents time as a single floating point number, representing
+the (fractional) number of seconds since the (\s-1POSIX\s0) epoch (in practice
+somewhere near the beginning of 1970, details are complicated, don't
+ask). This type is called \f(CW\*(C`ev_tstamp\*(C'\fR, which is what you should use
+too. It usually aliases to the \f(CW\*(C`double\*(C'\fR type in C. When you need to do
+any calculations on it, you should treat it as some floating point value.
+.PP
+Unlike the name component \f(CW\*(C`stamp\*(C'\fR might indicate, it is also used for
+time differences (e.g. delays) throughout libev.
+.SH "ERROR HANDLING"
+.IX Header "ERROR HANDLING"
+Libev knows three classes of errors: operating system errors, usage errors
+and internal errors (bugs).
+.PP
+When libev catches an operating system error it cannot handle (for example
+a system call indicating a condition libev cannot fix), it calls the callback
+set via \f(CW\*(C`ev_set_syserr_cb\*(C'\fR, which is supposed to fix the problem or
+abort. The default is to print a diagnostic message and to call \f(CW\*(C`abort
+()\*(C'\fR.
+.PP
+When libev detects a usage error such as a negative timer interval, then
+it will print a diagnostic message and abort (via the \f(CW\*(C`assert\*(C'\fR mechanism,
+so \f(CW\*(C`NDEBUG\*(C'\fR will disable this checking): these are programming errors in
+the libev caller and need to be fixed there.
+.PP
+Via the \f(CW\*(C`EV_FREQUENT\*(C'\fR macro you can compile in and/or enable extensive
+consistency checking code inside libev that can be used to check for
+internal inconsistencies, suually caused by application bugs.
+.PP
+Libev also has a few internal error-checking \f(CW\*(C`assert\*(C'\fRions. These do not
+trigger under normal circumstances, as they indicate either a bug in libev
+or worse.
+.SH "GLOBAL FUNCTIONS"
+.IX Header "GLOBAL FUNCTIONS"
+These functions can be called anytime, even before initialising the
+library in any way.
+.IP "ev_tstamp ev_time ()" 4
+.IX Item "ev_tstamp ev_time ()"
+Returns the current time as libev would use it. Please note that the
+\&\f(CW\*(C`ev_now\*(C'\fR function is usually faster and also often returns the timestamp
+you actually want to know. Also interesting is the combination of
+\&\f(CW\*(C`ev_now_update\*(C'\fR and \f(CW\*(C`ev_now\*(C'\fR.
+.IP "ev_sleep (ev_tstamp interval)" 4
+.IX Item "ev_sleep (ev_tstamp interval)"
+Sleep for the given interval: The current thread will be blocked
+until either it is interrupted or the given time interval has
+passed (approximately \- it might return a bit earlier even if not
+interrupted). Returns immediately if \f(CW\*(C`interval <= 0\*(C'\fR.
+.Sp
+Basically this is a sub-second-resolution \f(CW\*(C`sleep ()\*(C'\fR.
+.Sp
+The range of the \f(CW\*(C`interval\*(C'\fR is limited \- libev only guarantees to work
+with sleep times of up to one day (\f(CW\*(C`interval <= 86400\*(C'\fR).
+.IP "int ev_version_major ()" 4
+.IX Item "int ev_version_major ()"
+.PD 0
+.IP "int ev_version_minor ()" 4
+.IX Item "int ev_version_minor ()"
+.PD
+You can find out the major and minor \s-1ABI\s0 version numbers of the library
+you linked against by calling the functions \f(CW\*(C`ev_version_major\*(C'\fR and
+\&\f(CW\*(C`ev_version_minor\*(C'\fR. If you want, you can compare against the global
+symbols \f(CW\*(C`EV_VERSION_MAJOR\*(C'\fR and \f(CW\*(C`EV_VERSION_MINOR\*(C'\fR, which specify the
+version of the library your program was compiled against.
+.Sp
+These version numbers refer to the \s-1ABI\s0 version of the library, not the
+release version.
+.Sp
+Usually, it's a good idea to terminate if the major versions mismatch,
+as this indicates an incompatible change. Minor versions are usually
+compatible to older versions, so a larger minor version alone is usually
+not a problem.
+.Sp
+Example: Make sure we haven't accidentally been linked against the wrong
+version (note, however, that this will not detect other \s-1ABI\s0 mismatches,
+such as \s-1LFS\s0 or reentrancy).
+.Sp
+.Vb 3
+\& assert (("libev version mismatch",
+\& ev_version_major () == EV_VERSION_MAJOR
+\& && ev_version_minor () >= EV_VERSION_MINOR));
+.Ve
+.IP "unsigned int ev_supported_backends ()" 4
+.IX Item "unsigned int ev_supported_backends ()"
+Return the set of all backends (i.e. their corresponding \f(CW\*(C`EV_BACKEND_*\*(C'\fR
+value) compiled into this binary of libev (independent of their
+availability on the system you are running on). See \f(CW\*(C`ev_default_loop\*(C'\fR for
+a description of the set values.
+.Sp
+Example: make sure we have the epoll method, because yeah this is cool and
+a must have and can we have a torrent of it please!!!11
+.Sp
+.Vb 2
+\& assert (("sorry, no epoll, no sex",
+\& ev_supported_backends () & EVBACKEND_EPOLL));
+.Ve
+.IP "unsigned int ev_recommended_backends ()" 4
+.IX Item "unsigned int ev_recommended_backends ()"
+Return the set of all backends compiled into this binary of libev and
+also recommended for this platform, meaning it will work for most file
+descriptor types. This set is often smaller than the one returned by
+\&\f(CW\*(C`ev_supported_backends\*(C'\fR, as for example kqueue is broken on most BSDs
+and will not be auto-detected unless you explicitly request it (assuming
+you know what you are doing). This is the set of backends that libev will
+probe for if you specify no backends explicitly.
+.IP "unsigned int ev_embeddable_backends ()" 4
+.IX Item "unsigned int ev_embeddable_backends ()"
+Returns the set of backends that are embeddable in other event loops. This
+value is platform-specific but can include backends not available on the
+current system. To find which embeddable backends might be supported on
+the current system, you would need to look at \f(CW\*(C`ev_embeddable_backends ()
+& ev_supported_backends ()\*(C'\fR, likewise for recommended ones.
+.Sp
+See the description of \f(CW\*(C`ev_embed\*(C'\fR watchers for more info.
+.IP "ev_set_allocator (void *(*cb)(void *ptr, long size) throw ())" 4
+.IX Item "ev_set_allocator (void *(*cb)(void *ptr, long size) throw ())"
+Sets the allocation function to use (the prototype is similar \- the
+semantics are identical to the \f(CW\*(C`realloc\*(C'\fR C89/SuS/POSIX function). It is
+used to allocate and free memory (no surprises here). If it returns zero
+when memory needs to be allocated (\f(CW\*(C`size != 0\*(C'\fR), the library might abort
+or take some potentially destructive action.
+.Sp
+Since some systems (at least OpenBSD and Darwin) fail to implement
+correct \f(CW\*(C`realloc\*(C'\fR semantics, libev will use a wrapper around the system
+\&\f(CW\*(C`realloc\*(C'\fR and \f(CW\*(C`free\*(C'\fR functions by default.
+.Sp
+You could override this function in high-availability programs to, say,
+free some memory if it cannot allocate memory, to use a special allocator,
+or even to sleep a while and retry until some memory is available.
+.Sp
+Example: The following is the \f(CW\*(C`realloc\*(C'\fR function that libev itself uses
+which should work with \f(CW\*(C`realloc\*(C'\fR and \f(CW\*(C`free\*(C'\fR functions of all kinds and
+is probably a good basis for your own implementation.
+.Sp
+.Vb 5
+\& static void *
+\& ev_realloc_emul (void *ptr, long size) EV_NOEXCEPT
+\& {
+\& if (size)
+\& return realloc (ptr, size);
+\&
+\& free (ptr);
+\& return 0;
+\& }
+.Ve
+.Sp
+Example: Replace the libev allocator with one that waits a bit and then
+retries.
+.Sp
+.Vb 8
+\& static void *
+\& persistent_realloc (void *ptr, size_t size)
+\& {
+\& if (!size)
+\& {
+\& free (ptr);
+\& return 0;
+\& }
+\&
+\& for (;;)
+\& {
+\& void *newptr = realloc (ptr, size);
+\&
+\& if (newptr)
+\& return newptr;
+\&
+\& sleep (60);
+\& }
+\& }
+\&
+\& ...
+\& ev_set_allocator (persistent_realloc);
+.Ve
+.IP "ev_set_syserr_cb (void (*cb)(const char *msg) throw ())" 4
+.IX Item "ev_set_syserr_cb (void (*cb)(const char *msg) throw ())"
+Set the callback function to call on a retryable system call error (such
+as failed select, poll, epoll_wait). The message is a printable string
+indicating the system call or subsystem causing the problem. If this
+callback is set, then libev will expect it to remedy the situation, no
+matter what, when it returns. That is, libev will generally retry the
+requested operation, or, if the condition doesn't go away, do bad stuff
+(such as abort).
+.Sp
+Example: This is basically the same thing that libev does internally, too.
+.Sp
+.Vb 6
+\& static void
+\& fatal_error (const char *msg)
+\& {
+\& perror (msg);
+\& abort ();
+\& }
+\&
+\& ...
+\& ev_set_syserr_cb (fatal_error);
+.Ve
+.IP "ev_feed_signal (int signum)" 4
+.IX Item "ev_feed_signal (int signum)"
+This function can be used to \*(L"simulate\*(R" a signal receive. It is completely
+safe to call this function at any time, from any context, including signal
+handlers or random threads.
+.Sp
+Its main use is to customise signal handling in your process, especially
+in the presence of threads. For example, you could block signals
+by default in all threads (and specifying \f(CW\*(C`EVFLAG_NOSIGMASK\*(C'\fR when
+creating any loops), and in one thread, use \f(CW\*(C`sigwait\*(C'\fR or any other
+mechanism to wait for signals, then \*(L"deliver\*(R" them to libev by calling
+\&\f(CW\*(C`ev_feed_signal\*(C'\fR.
+.SH "FUNCTIONS CONTROLLING EVENT LOOPS"
+.IX Header "FUNCTIONS CONTROLLING EVENT LOOPS"
+An event loop is described by a \f(CW\*(C`struct ev_loop *\*(C'\fR (the \f(CW\*(C`struct\*(C'\fR is
+\&\fInot\fR optional in this case unless libev 3 compatibility is disabled, as
+libev 3 had an \f(CW\*(C`ev_loop\*(C'\fR function colliding with the struct name).
+.PP
+The library knows two types of such loops, the \fIdefault\fR loop, which
+supports child process events, and dynamically created event loops which
+do not.
+.IP "struct ev_loop *ev_default_loop (unsigned int flags)" 4
+.IX Item "struct ev_loop *ev_default_loop (unsigned int flags)"
+This returns the \*(L"default\*(R" event loop object, which is what you should
+normally use when you just need \*(L"the event loop\*(R". Event loop objects and
+the \f(CW\*(C`flags\*(C'\fR parameter are described in more detail in the entry for
+\&\f(CW\*(C`ev_loop_new\*(C'\fR.
+.Sp
+If the default loop is already initialised then this function simply
+returns it (and ignores the flags. If that is troubling you, check
+\&\f(CW\*(C`ev_backend ()\*(C'\fR afterwards). Otherwise it will create it with the given
+flags, which should almost always be \f(CW0\fR, unless the caller is also the
+one calling \f(CW\*(C`ev_run\*(C'\fR or otherwise qualifies as \*(L"the main program\*(R".
+.Sp
+If you don't know what event loop to use, use the one returned from this
+function (or via the \f(CW\*(C`EV_DEFAULT\*(C'\fR macro).
+.Sp
+Note that this function is \fInot\fR thread-safe, so if you want to use it
+from multiple threads, you have to employ some kind of mutex (note also
+that this case is unlikely, as loops cannot be shared easily between
+threads anyway).
+.Sp
+The default loop is the only loop that can handle \f(CW\*(C`ev_child\*(C'\fR watchers,
+and to do this, it always registers a handler for \f(CW\*(C`SIGCHLD\*(C'\fR. If this is
+a problem for your application you can either create a dynamic loop with
+\&\f(CW\*(C`ev_loop_new\*(C'\fR which doesn't do that, or you can simply overwrite the
+\&\f(CW\*(C`SIGCHLD\*(C'\fR signal handler \fIafter\fR calling \f(CW\*(C`ev_default_init\*(C'\fR.
+.Sp
+Example: This is the most typical usage.
+.Sp
+.Vb 2
+\& if (!ev_default_loop (0))
+\& fatal ("could not initialise libev, bad $LIBEV_FLAGS in environment?");
+.Ve
+.Sp
+Example: Restrict libev to the select and poll backends, and do not allow
+environment settings to be taken into account:
+.Sp
+.Vb 1
+\& ev_default_loop (EVBACKEND_POLL | EVBACKEND_SELECT | EVFLAG_NOENV);
+.Ve
+.IP "struct ev_loop *ev_loop_new (unsigned int flags)" 4
+.IX Item "struct ev_loop *ev_loop_new (unsigned int flags)"
+This will create and initialise a new event loop object. If the loop
+could not be initialised, returns false.
+.Sp
+This function is thread-safe, and one common way to use libev with
+threads is indeed to create one loop per thread, and using the default
+loop in the \*(L"main\*(R" or \*(L"initial\*(R" thread.
+.Sp
+The flags argument can be used to specify special behaviour or specific
+backends to use, and is usually specified as \f(CW0\fR (or \f(CW\*(C`EVFLAG_AUTO\*(C'\fR).
+.Sp
+The following flags are supported:
+.RS 4
+.ie n .IP """EVFLAG_AUTO""" 4
+.el .IP "\f(CWEVFLAG_AUTO\fR" 4
+.IX Item "EVFLAG_AUTO"
+The default flags value. Use this if you have no clue (it's the right
+thing, believe me).
+.ie n .IP """EVFLAG_NOENV""" 4
+.el .IP "\f(CWEVFLAG_NOENV\fR" 4
+.IX Item "EVFLAG_NOENV"
+If this flag bit is or'ed into the flag value (or the program runs setuid
+or setgid) then libev will \fInot\fR look at the environment variable
+\&\f(CW\*(C`LIBEV_FLAGS\*(C'\fR. Otherwise (the default), this environment variable will
+override the flags completely if it is found in the environment. This is
+useful to try out specific backends to test their performance, to work
+around bugs, or to make libev threadsafe (accessing environment variables
+cannot be done in a threadsafe way, but usually it works if no other
+thread modifies them).
+.ie n .IP """EVFLAG_FORKCHECK""" 4
+.el .IP "\f(CWEVFLAG_FORKCHECK\fR" 4
+.IX Item "EVFLAG_FORKCHECK"
+Instead of calling \f(CW\*(C`ev_loop_fork\*(C'\fR manually after a fork, you can also
+make libev check for a fork in each iteration by enabling this flag.
+.Sp
+This works by calling \f(CW\*(C`getpid ()\*(C'\fR on every iteration of the loop,
+and thus this might slow down your event loop if you do a lot of loop
+iterations and little real work, but is usually not noticeable (on my
+GNU/Linux system for example, \f(CW\*(C`getpid\*(C'\fR is actually a simple 5\-insn
+sequence without a system call and thus \fIvery\fR fast, but my GNU/Linux
+system also has \f(CW\*(C`pthread_atfork\*(C'\fR which is even faster). (Update: glibc
+versions 2.25 apparently removed the \f(CW\*(C`getpid\*(C'\fR optimisation again).
+.Sp
+The big advantage of this flag is that you can forget about fork (and
+forget about forgetting to tell libev about forking, although you still
+have to ignore \f(CW\*(C`SIGPIPE\*(C'\fR) when you use this flag.
+.Sp
+This flag setting cannot be overridden or specified in the \f(CW\*(C`LIBEV_FLAGS\*(C'\fR
+environment variable.
+.ie n .IP """EVFLAG_NOINOTIFY""" 4
+.el .IP "\f(CWEVFLAG_NOINOTIFY\fR" 4
+.IX Item "EVFLAG_NOINOTIFY"
+When this flag is specified, then libev will not attempt to use the
+\&\fIinotify\fR \s-1API\s0 for its \f(CW\*(C`ev_stat\*(C'\fR watchers. Apart from debugging and
+testing, this flag can be useful to conserve inotify file descriptors, as
+otherwise each loop using \f(CW\*(C`ev_stat\*(C'\fR watchers consumes one inotify handle.
+.ie n .IP """EVFLAG_SIGNALFD""" 4
+.el .IP "\f(CWEVFLAG_SIGNALFD\fR" 4
+.IX Item "EVFLAG_SIGNALFD"
+When this flag is specified, then libev will attempt to use the
+\&\fIsignalfd\fR \s-1API\s0 for its \f(CW\*(C`ev_signal\*(C'\fR (and \f(CW\*(C`ev_child\*(C'\fR) watchers. This \s-1API\s0
+delivers signals synchronously, which makes it both faster and might make
+it possible to get the queued signal data. It can also simplify signal
+handling with threads, as long as you properly block signals in your
+threads that are not interested in handling them.
+.Sp
+Signalfd will not be used by default as this changes your signal mask, and
+there are a lot of shoddy libraries and programs (glib's threadpool for
+example) that can't properly initialise their signal masks.
+.ie n .IP """EVFLAG_NOSIGMASK""" 4
+.el .IP "\f(CWEVFLAG_NOSIGMASK\fR" 4
+.IX Item "EVFLAG_NOSIGMASK"
+When this flag is specified, then libev will avoid to modify the signal
+mask. Specifically, this means you have to make sure signals are unblocked
+when you want to receive them.
+.Sp
+This behaviour is useful when you want to do your own signal handling, or
+want to handle signals only in specific threads and want to avoid libev
+unblocking the signals.
+.Sp
+It's also required by \s-1POSIX\s0 in a threaded program, as libev calls
+\&\f(CW\*(C`sigprocmask\*(C'\fR, whose behaviour is officially unspecified.
+.ie n .IP """EVFLAG_NOTIMERFD""" 4
+.el .IP "\f(CWEVFLAG_NOTIMERFD\fR" 4
+.IX Item "EVFLAG_NOTIMERFD"
+When this flag is specified, the libev will avoid using a \f(CW\*(C`timerfd\*(C'\fR to
+detect time jumps. It will still be able to detect time jumps, but takes
+longer and has a lower accuracy in doing so, but saves a file descriptor
+per loop.
+.Sp
+The current implementation only tries to use a \f(CW\*(C`timerfd\*(C'\fR when the first
+\&\f(CW\*(C`ev_periodic\*(C'\fR watcher is started and falls back on other methods if it
+cannot be created, but this behaviour might change in the future.
+.ie n .IP """EVBACKEND_SELECT"" (value 1, portable select backend)" 4
+.el .IP "\f(CWEVBACKEND_SELECT\fR (value 1, portable select backend)" 4
+.IX Item "EVBACKEND_SELECT (value 1, portable select backend)"
+This is your standard \fBselect\fR\|(2) backend. Not \fIcompletely\fR standard, as
+libev tries to roll its own fd_set with no limits on the number of fds,
+but if that fails, expect a fairly low limit on the number of fds when
+using this backend. It doesn't scale too well (O(highest_fd)), but its
+usually the fastest backend for a low number of (low-numbered :) fds.
+.Sp
+To get good performance out of this backend you need a high amount of
+parallelism (most of the file descriptors should be busy). If you are
+writing a server, you should \f(CW\*(C`accept ()\*(C'\fR in a loop to accept as many
+connections as possible during one iteration. You might also want to have
+a look at \f(CW\*(C`ev_set_io_collect_interval ()\*(C'\fR to increase the amount of
+readiness notifications you get per iteration.
+.Sp
+This backend maps \f(CW\*(C`EV_READ\*(C'\fR to the \f(CW\*(C`readfds\*(C'\fR set and \f(CW\*(C`EV_WRITE\*(C'\fR to the
+\&\f(CW\*(C`writefds\*(C'\fR set (and to work around Microsoft Windows bugs, also onto the
+\&\f(CW\*(C`exceptfds\*(C'\fR set on that platform).
+.ie n .IP """EVBACKEND_POLL"" (value 2, poll backend, available everywhere except on windows)" 4
+.el .IP "\f(CWEVBACKEND_POLL\fR (value 2, poll backend, available everywhere except on windows)" 4
+.IX Item "EVBACKEND_POLL (value 2, poll backend, available everywhere except on windows)"
+And this is your standard \fBpoll\fR\|(2) backend. It's more complicated
+than select, but handles sparse fds better and has no artificial
+limit on the number of fds you can use (except it will slow down
+considerably with a lot of inactive fds). It scales similarly to select,
+i.e. O(total_fds). See the entry for \f(CW\*(C`EVBACKEND_SELECT\*(C'\fR, above, for
+performance tips.
+.Sp
+This backend maps \f(CW\*(C`EV_READ\*(C'\fR to \f(CW\*(C`POLLIN | POLLERR | POLLHUP\*(C'\fR, and
+\&\f(CW\*(C`EV_WRITE\*(C'\fR to \f(CW\*(C`POLLOUT | POLLERR | POLLHUP\*(C'\fR.
+.ie n .IP """EVBACKEND_EPOLL"" (value 4, Linux)" 4
+.el .IP "\f(CWEVBACKEND_EPOLL\fR (value 4, Linux)" 4
+.IX Item "EVBACKEND_EPOLL (value 4, Linux)"
+Use the Linux-specific \fBepoll\fR\|(7) interface (for both pre\- and post\-2.6.9
+kernels).
+.Sp
+For few fds, this backend is a bit little slower than poll and select, but
+it scales phenomenally better. While poll and select usually scale like
+O(total_fds) where total_fds is the total number of fds (or the highest
+fd), epoll scales either O(1) or O(active_fds).
+.Sp
+The epoll mechanism deserves honorable mention as the most misdesigned
+of the more advanced event mechanisms: mere annoyances include silently
+dropping file descriptors, requiring a system call per change per file
+descriptor (and unnecessary guessing of parameters), problems with dup,
+returning before the timeout value, resulting in additional iterations
+(and only giving 5ms accuracy while select on the same platform gives
+0.1ms) and so on. The biggest issue is fork races, however \- if a program
+forks then \fIboth\fR parent and child process have to recreate the epoll
+set, which can take considerable time (one syscall per file descriptor)
+and is of course hard to detect.
+.Sp
+Epoll is also notoriously buggy \- embedding epoll fds \fIshould\fR work,
+but of course \fIdoesn't\fR, and epoll just loves to report events for
+totally \fIdifferent\fR file descriptors (even already closed ones, so
+one cannot even remove them from the set) than registered in the set
+(especially on \s-1SMP\s0 systems). Libev tries to counter these spurious
+notifications by employing an additional generation counter and comparing
+that against the events to filter out spurious ones, recreating the set
+when required. Epoll also erroneously rounds down timeouts, but gives you
+no way to know when and by how much, so sometimes you have to busy-wait
+because epoll returns immediately despite a nonzero timeout. And last
+not least, it also refuses to work with some file descriptors which work
+perfectly fine with \f(CW\*(C`select\*(C'\fR (files, many character devices...).
+.Sp
+Epoll is truly the train wreck among event poll mechanisms, a frankenpoll,
+cobbled together in a hurry, no thought to design or interaction with
+others. Oh, the pain, will it ever stop...
+.Sp
+While stopping, setting and starting an I/O watcher in the same iteration
+will result in some caching, there is still a system call per such
+incident (because the same \fIfile descriptor\fR could point to a different
+\&\fIfile description\fR now), so its best to avoid that. Also, \f(CW\*(C`dup ()\*(C'\fR'ed
+file descriptors might not work very well if you register events for both
+file descriptors.
+.Sp
+Best performance from this backend is achieved by not unregistering all
+watchers for a file descriptor until it has been closed, if possible,
+i.e. keep at least one watcher active per fd at all times. Stopping and
+starting a watcher (without re-setting it) also usually doesn't cause
+extra overhead. A fork can both result in spurious notifications as well
+as in libev having to destroy and recreate the epoll object, which can
+take considerable time and thus should be avoided.
+.Sp
+All this means that, in practice, \f(CW\*(C`EVBACKEND_SELECT\*(C'\fR can be as fast or
+faster than epoll for maybe up to a hundred file descriptors, depending on
+the usage. So sad.
+.Sp
+While nominally embeddable in other event loops, this feature is broken in
+a lot of kernel revisions, but probably(!) works in current versions.
+.Sp
+This backend maps \f(CW\*(C`EV_READ\*(C'\fR and \f(CW\*(C`EV_WRITE\*(C'\fR in the same way as
+\&\f(CW\*(C`EVBACKEND_POLL\*(C'\fR.
+.ie n .IP """EVBACKEND_LINUXAIO"" (value 64, Linux)" 4
+.el .IP "\f(CWEVBACKEND_LINUXAIO\fR (value 64, Linux)" 4
+.IX Item "EVBACKEND_LINUXAIO (value 64, Linux)"
+Use the Linux-specific Linux \s-1AIO\s0 (\fInot\fR \f(CWaio(7)\fR but \f(CWio_submit(2)\fR) event interface available in post\-4.18 kernels (but libev
+only tries to use it in 4.19+).
+.Sp
+This is another Linux train wreck of an event interface.
+.Sp
+If this backend works for you (as of this writing, it was very
+experimental), it is the best event interface available on Linux and might
+be well worth enabling it \- if it isn't available in your kernel this will
+be detected and this backend will be skipped.
+.Sp
+This backend can batch oneshot requests and supports a user-space ring
+buffer to receive events. It also doesn't suffer from most of the design
+problems of epoll (such as not being able to remove event sources from
+the epoll set), and generally sounds too good to be true. Because, this
+being the Linux kernel, of course it suffers from a whole new set of
+limitations, forcing you to fall back to epoll, inheriting all its design
+issues.
+.Sp
+For one, it is not easily embeddable (but probably could be done using
+an event fd at some extra overhead). It also is subject to a system wide
+limit that can be configured in \fI/proc/sys/fs/aio\-max\-nr\fR. If no \s-1AIO\s0
+requests are left, this backend will be skipped during initialisation, and
+will switch to epoll when the loop is active.
+.Sp
+Most problematic in practice, however, is that not all file descriptors
+work with it. For example, in Linux 5.1, \s-1TCP\s0 sockets, pipes, event fds,
+files, \fI/dev/null\fR and many others are supported, but ttys do not work
+properly (a known bug that the kernel developers don't care about, see
+<https://lore.kernel.org/patchwork/patch/1047453/>), so this is not
+(yet?) a generic event polling interface.
+.Sp
+Overall, it seems the Linux developers just don't want it to have a
+generic event handling mechanism other than \f(CW\*(C`select\*(C'\fR or \f(CW\*(C`poll\*(C'\fR.
+.Sp
+To work around all these problem, the current version of libev uses its
+epoll backend as a fallback for file descriptor types that do not work. Or
+falls back completely to epoll if the kernel acts up.
+.Sp
+This backend maps \f(CW\*(C`EV_READ\*(C'\fR and \f(CW\*(C`EV_WRITE\*(C'\fR in the same way as
+\&\f(CW\*(C`EVBACKEND_POLL\*(C'\fR.
+.ie n .IP """EVBACKEND_KQUEUE"" (value 8, most \s-1BSD\s0 clones)" 4
+.el .IP "\f(CWEVBACKEND_KQUEUE\fR (value 8, most \s-1BSD\s0 clones)" 4
+.IX Item "EVBACKEND_KQUEUE (value 8, most BSD clones)"
+Kqueue deserves special mention, as at the time this backend was
+implemented, it was broken on all BSDs except NetBSD (usually it doesn't
+work reliably with anything but sockets and pipes, except on Darwin,
+where of course it's completely useless). Unlike epoll, however, whose
+brokenness is by design, these kqueue bugs can be (and mostly have been)
+fixed without \s-1API\s0 changes to existing programs. For this reason it's not
+being \*(L"auto-detected\*(R" on all platforms unless you explicitly specify it
+in the flags (i.e. using \f(CW\*(C`EVBACKEND_KQUEUE\*(C'\fR) or libev was compiled on a
+known-to-be-good (\-enough) system like NetBSD.
+.Sp
+You still can embed kqueue into a normal poll or select backend and use it
+only for sockets (after having made sure that sockets work with kqueue on
+the target platform). See \f(CW\*(C`ev_embed\*(C'\fR watchers for more info.
+.Sp
+It scales in the same way as the epoll backend, but the interface to the
+kernel is more efficient (which says nothing about its actual speed, of
+course). While stopping, setting and starting an I/O watcher does never
+cause an extra system call as with \f(CW\*(C`EVBACKEND_EPOLL\*(C'\fR, it still adds up to
+two event changes per incident. Support for \f(CW\*(C`fork ()\*(C'\fR is very bad (you
+might have to leak fds on fork, but it's more sane than epoll) and it
+drops fds silently in similarly hard-to-detect cases.
+.Sp
+This backend usually performs well under most conditions.
+.Sp
+While nominally embeddable in other event loops, this doesn't work
+everywhere, so you might need to test for this. And since it is broken
+almost everywhere, you should only use it when you have a lot of sockets
+(for which it usually works), by embedding it into another event loop
+(e.g. \f(CW\*(C`EVBACKEND_SELECT\*(C'\fR or \f(CW\*(C`EVBACKEND_POLL\*(C'\fR (but \f(CW\*(C`poll\*(C'\fR is of course
+also broken on \s-1OS X\s0)) and, did I mention it, using it only for sockets.
+.Sp
+This backend maps \f(CW\*(C`EV_READ\*(C'\fR into an \f(CW\*(C`EVFILT_READ\*(C'\fR kevent with
+\&\f(CW\*(C`NOTE_EOF\*(C'\fR, and \f(CW\*(C`EV_WRITE\*(C'\fR into an \f(CW\*(C`EVFILT_WRITE\*(C'\fR kevent with
+\&\f(CW\*(C`NOTE_EOF\*(C'\fR.
+.ie n .IP """EVBACKEND_DEVPOLL"" (value 16, Solaris 8)" 4
+.el .IP "\f(CWEVBACKEND_DEVPOLL\fR (value 16, Solaris 8)" 4
+.IX Item "EVBACKEND_DEVPOLL (value 16, Solaris 8)"
+This is not implemented yet (and might never be, unless you send me an
+implementation). According to reports, \f(CW\*(C`/dev/poll\*(C'\fR only supports sockets
+and is not embeddable, which would limit the usefulness of this backend
+immensely.
+.ie n .IP """EVBACKEND_PORT"" (value 32, Solaris 10)" 4
+.el .IP "\f(CWEVBACKEND_PORT\fR (value 32, Solaris 10)" 4
+.IX Item "EVBACKEND_PORT (value 32, Solaris 10)"
+This uses the Solaris 10 event port mechanism. As with everything on Solaris,
+it's really slow, but it still scales very well (O(active_fds)).
+.Sp
+While this backend scales well, it requires one system call per active
+file descriptor per loop iteration. For small and medium numbers of file
+descriptors a \*(L"slow\*(R" \f(CW\*(C`EVBACKEND_SELECT\*(C'\fR or \f(CW\*(C`EVBACKEND_POLL\*(C'\fR backend
+might perform better.
+.Sp
+On the positive side, this backend actually performed fully to
+specification in all tests and is fully embeddable, which is a rare feat
+among the OS-specific backends (I vastly prefer correctness over speed
+hacks).
+.Sp
+On the negative side, the interface is \fIbizarre\fR \- so bizarre that
+even sun itself gets it wrong in their code examples: The event polling
+function sometimes returns events to the caller even though an error
+occurred, but with no indication whether it has done so or not (yes, it's
+even documented that way) \- deadly for edge-triggered interfaces where you
+absolutely have to know whether an event occurred or not because you have
+to re-arm the watcher.
+.Sp
+Fortunately libev seems to be able to work around these idiocies.
+.Sp
+This backend maps \f(CW\*(C`EV_READ\*(C'\fR and \f(CW\*(C`EV_WRITE\*(C'\fR in the same way as
+\&\f(CW\*(C`EVBACKEND_POLL\*(C'\fR.
+.ie n .IP """EVBACKEND_ALL""" 4
+.el .IP "\f(CWEVBACKEND_ALL\fR" 4
+.IX Item "EVBACKEND_ALL"
+Try all backends (even potentially broken ones that wouldn't be tried
+with \f(CW\*(C`EVFLAG_AUTO\*(C'\fR). Since this is a mask, you can do stuff such as
+\&\f(CW\*(C`EVBACKEND_ALL & ~EVBACKEND_KQUEUE\*(C'\fR.
+.Sp
+It is definitely not recommended to use this flag, use whatever
+\&\f(CW\*(C`ev_recommended_backends ()\*(C'\fR returns, or simply do not specify a backend
+at all.
+.ie n .IP """EVBACKEND_MASK""" 4
+.el .IP "\f(CWEVBACKEND_MASK\fR" 4
+.IX Item "EVBACKEND_MASK"
+Not a backend at all, but a mask to select all backend bits from a
+\&\f(CW\*(C`flags\*(C'\fR value, in case you want to mask out any backends from a flags
+value (e.g. when modifying the \f(CW\*(C`LIBEV_FLAGS\*(C'\fR environment variable).
+.RE
+.RS 4
+.Sp
+If one or more of the backend flags are or'ed into the flags value,
+then only these backends will be tried (in the reverse order as listed
+here). If none are specified, all backends in \f(CW\*(C`ev_recommended_backends
+()\*(C'\fR will be tried.
+.Sp
+Example: Try to create a event loop that uses epoll and nothing else.
+.Sp
+.Vb 3
+\& struct ev_loop *epoller = ev_loop_new (EVBACKEND_EPOLL | EVFLAG_NOENV);
+\& if (!epoller)
+\& fatal ("no epoll found here, maybe it hides under your chair");
+.Ve
+.Sp
+Example: Use whatever libev has to offer, but make sure that kqueue is
+used if available.
+.Sp
+.Vb 1
+\& struct ev_loop *loop = ev_loop_new (ev_recommended_backends () | EVBACKEND_KQUEUE);
+.Ve
+.Sp
+Example: Similarly, on linux, you mgiht want to take advantage of the
+linux aio backend if possible, but fall back to something else if that
+isn't available.
+.Sp
+.Vb 1
+\& struct ev_loop *loop = ev_loop_new (ev_recommended_backends () | EVBACKEND_LINUXAIO);
+.Ve
+.RE
+.IP "ev_loop_destroy (loop)" 4
+.IX Item "ev_loop_destroy (loop)"
+Destroys an event loop object (frees all memory and kernel state
+etc.). None of the active event watchers will be stopped in the normal
+sense, so e.g. \f(CW\*(C`ev_is_active\*(C'\fR might still return true. It is your
+responsibility to either stop all watchers cleanly yourself \fIbefore\fR
+calling this function, or cope with the fact afterwards (which is usually
+the easiest thing, you can just ignore the watchers and/or \f(CW\*(C`free ()\*(C'\fR them
+for example).
+.Sp
+Note that certain global state, such as signal state (and installed signal
+handlers), will not be freed by this function, and related watchers (such
+as signal and child watchers) would need to be stopped manually.
+.Sp
+This function is normally used on loop objects allocated by
+\&\f(CW\*(C`ev_loop_new\*(C'\fR, but it can also be used on the default loop returned by
+\&\f(CW\*(C`ev_default_loop\*(C'\fR, in which case it is not thread-safe.
+.Sp
+Note that it is not advisable to call this function on the default loop
+except in the rare occasion where you really need to free its resources.
+If you need dynamically allocated loops it is better to use \f(CW\*(C`ev_loop_new\*(C'\fR
+and \f(CW\*(C`ev_loop_destroy\*(C'\fR.
+.IP "ev_loop_fork (loop)" 4
+.IX Item "ev_loop_fork (loop)"
+This function sets a flag that causes subsequent \f(CW\*(C`ev_run\*(C'\fR iterations
+to reinitialise the kernel state for backends that have one. Despite
+the name, you can call it anytime you are allowed to start or stop
+watchers (except inside an \f(CW\*(C`ev_prepare\*(C'\fR callback), but it makes most
+sense after forking, in the child process. You \fImust\fR call it (or use
+\&\f(CW\*(C`EVFLAG_FORKCHECK\*(C'\fR) in the child before resuming or calling \f(CW\*(C`ev_run\*(C'\fR.
+.Sp
+In addition, if you want to reuse a loop (via this function or
+\&\f(CW\*(C`EVFLAG_FORKCHECK\*(C'\fR), you \fIalso\fR have to ignore \f(CW\*(C`SIGPIPE\*(C'\fR.
+.Sp
+Again, you \fIhave\fR to call it on \fIany\fR loop that you want to re-use after
+a fork, \fIeven if you do not plan to use the loop in the parent\fR. This is
+because some kernel interfaces *cough* \fIkqueue\fR *cough* do funny things
+during fork.
+.Sp
+On the other hand, you only need to call this function in the child
+process if and only if you want to use the event loop in the child. If
+you just fork+exec or create a new loop in the child, you don't have to
+call it at all (in fact, \f(CW\*(C`epoll\*(C'\fR is so badly broken that it makes a
+difference, but libev will usually detect this case on its own and do a
+costly reset of the backend).
+.Sp
+The function itself is quite fast and it's usually not a problem to call
+it just in case after a fork.
+.Sp
+Example: Automate calling \f(CW\*(C`ev_loop_fork\*(C'\fR on the default loop when
+using pthreads.
+.Sp
+.Vb 5
+\& static void
+\& post_fork_child (void)
+\& {
+\& ev_loop_fork (EV_DEFAULT);
+\& }
+\&
+\& ...
+\& pthread_atfork (0, 0, post_fork_child);
+.Ve
+.IP "int ev_is_default_loop (loop)" 4
+.IX Item "int ev_is_default_loop (loop)"
+Returns true when the given loop is, in fact, the default loop, and false
+otherwise.
+.IP "unsigned int ev_iteration (loop)" 4
+.IX Item "unsigned int ev_iteration (loop)"
+Returns the current iteration count for the event loop, which is identical
+to the number of times libev did poll for new events. It starts at \f(CW0\fR
+and happily wraps around with enough iterations.
+.Sp
+This value can sometimes be useful as a generation counter of sorts (it
+\&\*(L"ticks\*(R" the number of loop iterations), as it roughly corresponds with
+\&\f(CW\*(C`ev_prepare\*(C'\fR and \f(CW\*(C`ev_check\*(C'\fR calls \- and is incremented between the
+prepare and check phases.
+.IP "unsigned int ev_depth (loop)" 4
+.IX Item "unsigned int ev_depth (loop)"
+Returns the number of times \f(CW\*(C`ev_run\*(C'\fR was entered minus the number of
+times \f(CW\*(C`ev_run\*(C'\fR was exited normally, in other words, the recursion depth.
+.Sp
+Outside \f(CW\*(C`ev_run\*(C'\fR, this number is zero. In a callback, this number is
+\&\f(CW1\fR, unless \f(CW\*(C`ev_run\*(C'\fR was invoked recursively (or from another thread),
+in which case it is higher.
+.Sp
+Leaving \f(CW\*(C`ev_run\*(C'\fR abnormally (setjmp/longjmp, cancelling the thread,
+throwing an exception etc.), doesn't count as \*(L"exit\*(R" \- consider this
+as a hint to avoid such ungentleman-like behaviour unless it's really
+convenient, in which case it is fully supported.
+.IP "unsigned int ev_backend (loop)" 4
+.IX Item "unsigned int ev_backend (loop)"
+Returns one of the \f(CW\*(C`EVBACKEND_*\*(C'\fR flags indicating the event backend in
+use.
+.IP "ev_tstamp ev_now (loop)" 4
+.IX Item "ev_tstamp ev_now (loop)"
+Returns the current \*(L"event loop time\*(R", which is the time the event loop
+received events and started processing them. This timestamp does not
+change as long as callbacks are being processed, and this is also the base
+time used for relative timers. You can treat it as the timestamp of the
+event occurring (or more correctly, libev finding out about it).
+.IP "ev_now_update (loop)" 4
+.IX Item "ev_now_update (loop)"
+Establishes the current time by querying the kernel, updating the time
+returned by \f(CW\*(C`ev_now ()\*(C'\fR in the progress. This is a costly operation and
+is usually done automatically within \f(CW\*(C`ev_run ()\*(C'\fR.
+.Sp
+This function is rarely useful, but when some event callback runs for a
+very long time without entering the event loop, updating libev's idea of
+the current time is a good idea.
+.Sp
+See also \*(L"The special problem of time updates\*(R" in the \f(CW\*(C`ev_timer\*(C'\fR section.
+.IP "ev_suspend (loop)" 4
+.IX Item "ev_suspend (loop)"
+.PD 0
+.IP "ev_resume (loop)" 4
+.IX Item "ev_resume (loop)"
+.PD
+These two functions suspend and resume an event loop, for use when the
+loop is not used for a while and timeouts should not be processed.
+.Sp
+A typical use case would be an interactive program such as a game: When
+the user presses \f(CW\*(C`^Z\*(C'\fR to suspend the game and resumes it an hour later it
+would be best to handle timeouts as if no time had actually passed while
+the program was suspended. This can be achieved by calling \f(CW\*(C`ev_suspend\*(C'\fR
+in your \f(CW\*(C`SIGTSTP\*(C'\fR handler, sending yourself a \f(CW\*(C`SIGSTOP\*(C'\fR and calling
+\&\f(CW\*(C`ev_resume\*(C'\fR directly afterwards to resume timer processing.
+.Sp
+Effectively, all \f(CW\*(C`ev_timer\*(C'\fR watchers will be delayed by the time spend
+between \f(CW\*(C`ev_suspend\*(C'\fR and \f(CW\*(C`ev_resume\*(C'\fR, and all \f(CW\*(C`ev_periodic\*(C'\fR watchers
+will be rescheduled (that is, they will lose any events that would have
+occurred while suspended).
+.Sp
+After calling \f(CW\*(C`ev_suspend\*(C'\fR you \fBmust not\fR call \fIany\fR function on the
+given loop other than \f(CW\*(C`ev_resume\*(C'\fR, and you \fBmust not\fR call \f(CW\*(C`ev_resume\*(C'\fR
+without a previous call to \f(CW\*(C`ev_suspend\*(C'\fR.
+.Sp
+Calling \f(CW\*(C`ev_suspend\*(C'\fR/\f(CW\*(C`ev_resume\*(C'\fR has the side effect of updating the
+event loop time (see \f(CW\*(C`ev_now_update\*(C'\fR).
+.IP "bool ev_run (loop, int flags)" 4
+.IX Item "bool ev_run (loop, int flags)"
+Finally, this is it, the event handler. This function usually is called
+after you have initialised all your watchers and you want to start
+handling events. It will ask the operating system for any new events, call
+the watcher callbacks, and then repeat the whole process indefinitely: This
+is why event loops are called \fIloops\fR.
+.Sp
+If the flags argument is specified as \f(CW0\fR, it will keep handling events
+until either no event watchers are active anymore or \f(CW\*(C`ev_break\*(C'\fR was
+called.
+.Sp
+The return value is false if there are no more active watchers (which
+usually means \*(L"all jobs done\*(R" or \*(L"deadlock\*(R"), and true in all other cases
+(which usually means " you should call \f(CW\*(C`ev_run\*(C'\fR again").
+.Sp
+Please note that an explicit \f(CW\*(C`ev_break\*(C'\fR is usually better than
+relying on all watchers to be stopped when deciding when a program has
+finished (especially in interactive programs), but having a program
+that automatically loops as long as it has to and no longer by virtue
+of relying on its watchers stopping correctly, that is truly a thing of
+beauty.
+.Sp
+This function is \fImostly\fR exception-safe \- you can break out of a
+\&\f(CW\*(C`ev_run\*(C'\fR call by calling \f(CW\*(C`longjmp\*(C'\fR in a callback, throwing a \*(C+
+exception and so on. This does not decrement the \f(CW\*(C`ev_depth\*(C'\fR value, nor
+will it clear any outstanding \f(CW\*(C`EVBREAK_ONE\*(C'\fR breaks.
+.Sp
+A flags value of \f(CW\*(C`EVRUN_NOWAIT\*(C'\fR will look for new events, will handle
+those events and any already outstanding ones, but will not wait and
+block your process in case there are no events and will return after one
+iteration of the loop. This is sometimes useful to poll and handle new
+events while doing lengthy calculations, to keep the program responsive.
+.Sp
+A flags value of \f(CW\*(C`EVRUN_ONCE\*(C'\fR will look for new events (waiting if
+necessary) and will handle those and any already outstanding ones. It
+will block your process until at least one new event arrives (which could
+be an event internal to libev itself, so there is no guarantee that a
+user-registered callback will be called), and will return after one
+iteration of the loop.
+.Sp
+This is useful if you are waiting for some external event in conjunction
+with something not expressible using other libev watchers (i.e. "roll your
+own \f(CW\*(C`ev_run\*(C'\fR"). However, a pair of \f(CW\*(C`ev_prepare\*(C'\fR/\f(CW\*(C`ev_check\*(C'\fR watchers is
+usually a better approach for this kind of thing.
+.Sp
+Here are the gory details of what \f(CW\*(C`ev_run\*(C'\fR does (this is for your
+understanding, not a guarantee that things will work exactly like this in
+future versions):
+.Sp
+.Vb 10
+\& \- Increment loop depth.
+\& \- Reset the ev_break status.
+\& \- Before the first iteration, call any pending watchers.
+\& LOOP:
+\& \- If EVFLAG_FORKCHECK was used, check for a fork.
+\& \- If a fork was detected (by any means), queue and call all fork watchers.
+\& \- Queue and call all prepare watchers.
+\& \- If ev_break was called, goto FINISH.
+\& \- If we have been forked, detach and recreate the kernel state
+\& as to not disturb the other process.
+\& \- Update the kernel state with all outstanding changes.
+\& \- Update the "event loop time" (ev_now ()).
+\& \- Calculate for how long to sleep or block, if at all
+\& (active idle watchers, EVRUN_NOWAIT or not having
+\& any active watchers at all will result in not sleeping).
+\& \- Sleep if the I/O and timer collect interval say so.
+\& \- Increment loop iteration counter.
+\& \- Block the process, waiting for any events.
+\& \- Queue all outstanding I/O (fd) events.
+\& \- Update the "event loop time" (ev_now ()), and do time jump adjustments.
+\& \- Queue all expired timers.
+\& \- Queue all expired periodics.
+\& \- Queue all idle watchers with priority higher than that of pending events.
+\& \- Queue all check watchers.
+\& \- Call all queued watchers in reverse order (i.e. check watchers first).
+\& Signals and child watchers are implemented as I/O watchers, and will
+\& be handled here by queueing them when their watcher gets executed.
+\& \- If ev_break has been called, or EVRUN_ONCE or EVRUN_NOWAIT
+\& were used, or there are no active watchers, goto FINISH, otherwise
+\& continue with step LOOP.
+\& FINISH:
+\& \- Reset the ev_break status iff it was EVBREAK_ONE.
+\& \- Decrement the loop depth.
+\& \- Return.
+.Ve
+.Sp
+Example: Queue some jobs and then loop until no events are outstanding
+anymore.
+.Sp
+.Vb 4
+\& ... queue jobs here, make sure they register event watchers as long
+\& ... as they still have work to do (even an idle watcher will do..)
+\& ev_run (my_loop, 0);
+\& ... jobs done or somebody called break. yeah!
+.Ve
+.IP "ev_break (loop, how)" 4
+.IX Item "ev_break (loop, how)"
+Can be used to make a call to \f(CW\*(C`ev_run\*(C'\fR return early (but only after it
+has processed all outstanding events). The \f(CW\*(C`how\*(C'\fR argument must be either
+\&\f(CW\*(C`EVBREAK_ONE\*(C'\fR, which will make the innermost \f(CW\*(C`ev_run\*(C'\fR call return, or
+\&\f(CW\*(C`EVBREAK_ALL\*(C'\fR, which will make all nested \f(CW\*(C`ev_run\*(C'\fR calls return.
+.Sp
+This \*(L"break state\*(R" will be cleared on the next call to \f(CW\*(C`ev_run\*(C'\fR.
+.Sp
+It is safe to call \f(CW\*(C`ev_break\*(C'\fR from outside any \f(CW\*(C`ev_run\*(C'\fR calls, too, in
+which case it will have no effect.
+.IP "ev_ref (loop)" 4
+.IX Item "ev_ref (loop)"
+.PD 0
+.IP "ev_unref (loop)" 4
+.IX Item "ev_unref (loop)"
+.PD
+Ref/unref can be used to add or remove a reference count on the event
+loop: Every watcher keeps one reference, and as long as the reference
+count is nonzero, \f(CW\*(C`ev_run\*(C'\fR will not return on its own.
+.Sp
+This is useful when you have a watcher that you never intend to
+unregister, but that nevertheless should not keep \f(CW\*(C`ev_run\*(C'\fR from
+returning. In such a case, call \f(CW\*(C`ev_unref\*(C'\fR after starting, and \f(CW\*(C`ev_ref\*(C'\fR
+before stopping it.
+.Sp
+As an example, libev itself uses this for its internal signal pipe: It
+is not visible to the libev user and should not keep \f(CW\*(C`ev_run\*(C'\fR from
+exiting if no event watchers registered by it are active. It is also an
+excellent way to do this for generic recurring timers or from within
+third-party libraries. Just remember to \fIunref after start\fR and \fIref
+before stop\fR (but only if the watcher wasn't active before, or was active
+before, respectively. Note also that libev might stop watchers itself
+(e.g. non-repeating timers) in which case you have to \f(CW\*(C`ev_ref\*(C'\fR
+in the callback).
+.Sp
+Example: Create a signal watcher, but keep it from keeping \f(CW\*(C`ev_run\*(C'\fR
+running when nothing else is active.
+.Sp
+.Vb 4
+\& ev_signal exitsig;
+\& ev_signal_init (&exitsig, sig_cb, SIGINT);
+\& ev_signal_start (loop, &exitsig);
+\& ev_unref (loop);
+.Ve
+.Sp
+Example: For some weird reason, unregister the above signal handler again.
+.Sp
+.Vb 2
+\& ev_ref (loop);
+\& ev_signal_stop (loop, &exitsig);
+.Ve
+.IP "ev_set_io_collect_interval (loop, ev_tstamp interval)" 4
+.IX Item "ev_set_io_collect_interval (loop, ev_tstamp interval)"
+.PD 0
+.IP "ev_set_timeout_collect_interval (loop, ev_tstamp interval)" 4
+.IX Item "ev_set_timeout_collect_interval (loop, ev_tstamp interval)"
+.PD
+These advanced functions influence the time that libev will spend waiting
+for events. Both time intervals are by default \f(CW0\fR, meaning that libev
+will try to invoke timer/periodic callbacks and I/O callbacks with minimum
+latency.
+.Sp
+Setting these to a higher value (the \f(CW\*(C`interval\*(C'\fR \fImust\fR be >= \f(CW0\fR)
+allows libev to delay invocation of I/O and timer/periodic callbacks
+to increase efficiency of loop iterations (or to increase power-saving
+opportunities).
+.Sp
+The idea is that sometimes your program runs just fast enough to handle
+one (or very few) event(s) per loop iteration. While this makes the
+program responsive, it also wastes a lot of \s-1CPU\s0 time to poll for new
+events, especially with backends like \f(CW\*(C`select ()\*(C'\fR which have a high
+overhead for the actual polling but can deliver many events at once.
+.Sp
+By setting a higher \fIio collect interval\fR you allow libev to spend more
+time collecting I/O events, so you can handle more events per iteration,
+at the cost of increasing latency. Timeouts (both \f(CW\*(C`ev_periodic\*(C'\fR and
+\&\f(CW\*(C`ev_timer\*(C'\fR) will not be affected. Setting this to a non-null value will
+introduce an additional \f(CW\*(C`ev_sleep ()\*(C'\fR call into most loop iterations. The
+sleep time ensures that libev will not poll for I/O events more often then
+once per this interval, on average (as long as the host time resolution is
+good enough).
+.Sp
+Likewise, by setting a higher \fItimeout collect interval\fR you allow libev
+to spend more time collecting timeouts, at the expense of increased
+latency/jitter/inexactness (the watcher callback will be called
+later). \f(CW\*(C`ev_io\*(C'\fR watchers will not be affected. Setting this to a non-null
+value will not introduce any overhead in libev.
+.Sp
+Many (busy) programs can usually benefit by setting the I/O collect
+interval to a value near \f(CW0.1\fR or so, which is often enough for
+interactive servers (of course not for games), likewise for timeouts. It
+usually doesn't make much sense to set it to a lower value than \f(CW0.01\fR,
+as this approaches the timing granularity of most systems. Note that if
+you do transactions with the outside world and you can't increase the
+parallelity, then this setting will limit your transaction rate (if you
+need to poll once per transaction and the I/O collect interval is 0.01,
+then you can't do more than 100 transactions per second).
+.Sp
+Setting the \fItimeout collect interval\fR can improve the opportunity for
+saving power, as the program will \*(L"bundle\*(R" timer callback invocations that
+are \*(L"near\*(R" in time together, by delaying some, thus reducing the number of
+times the process sleeps and wakes up again. Another useful technique to
+reduce iterations/wake\-ups is to use \f(CW\*(C`ev_periodic\*(C'\fR watchers and make sure
+they fire on, say, one-second boundaries only.
+.Sp
+Example: we only need 0.1s timeout granularity, and we wish not to poll
+more often than 100 times per second:
+.Sp
+.Vb 2
+\& ev_set_timeout_collect_interval (EV_DEFAULT_UC_ 0.1);
+\& ev_set_io_collect_interval (EV_DEFAULT_UC_ 0.01);
+.Ve
+.IP "ev_invoke_pending (loop)" 4
+.IX Item "ev_invoke_pending (loop)"
+This call will simply invoke all pending watchers while resetting their
+pending state. Normally, \f(CW\*(C`ev_run\*(C'\fR does this automatically when required,
+but when overriding the invoke callback this call comes handy. This
+function can be invoked from a watcher \- this can be useful for example
+when you want to do some lengthy calculation and want to pass further
+event handling to another thread (you still have to make sure only one
+thread executes within \f(CW\*(C`ev_invoke_pending\*(C'\fR or \f(CW\*(C`ev_run\*(C'\fR of course).
+.IP "int ev_pending_count (loop)" 4
+.IX Item "int ev_pending_count (loop)"
+Returns the number of pending watchers \- zero indicates that no watchers
+are pending.
+.IP "ev_set_invoke_pending_cb (loop, void (*invoke_pending_cb)(\s-1EV_P\s0))" 4
+.IX Item "ev_set_invoke_pending_cb (loop, void (*invoke_pending_cb)(EV_P))"
+This overrides the invoke pending functionality of the loop: Instead of
+invoking all pending watchers when there are any, \f(CW\*(C`ev_run\*(C'\fR will call
+this callback instead. This is useful, for example, when you want to
+invoke the actual watchers inside another context (another thread etc.).
+.Sp
+If you want to reset the callback, use \f(CW\*(C`ev_invoke_pending\*(C'\fR as new
+callback.
+.IP "ev_set_loop_release_cb (loop, void (*release)(\s-1EV_P\s0) throw (), void (*acquire)(\s-1EV_P\s0) throw ())" 4
+.IX Item "ev_set_loop_release_cb (loop, void (*release)(EV_P) throw (), void (*acquire)(EV_P) throw ())"
+Sometimes you want to share the same loop between multiple threads. This
+can be done relatively simply by putting mutex_lock/unlock calls around
+each call to a libev function.
+.Sp
+However, \f(CW\*(C`ev_run\*(C'\fR can run an indefinite time, so it is not feasible
+to wait for it to return. One way around this is to wake up the event
+loop via \f(CW\*(C`ev_break\*(C'\fR and \f(CW\*(C`ev_async_send\*(C'\fR, another way is to set these
+\&\fIrelease\fR and \fIacquire\fR callbacks on the loop.
+.Sp
+When set, then \f(CW\*(C`release\*(C'\fR will be called just before the thread is
+suspended waiting for new events, and \f(CW\*(C`acquire\*(C'\fR is called just
+afterwards.
+.Sp
+Ideally, \f(CW\*(C`release\*(C'\fR will just call your mutex_unlock function, and
+\&\f(CW\*(C`acquire\*(C'\fR will just call the mutex_lock function again.
+.Sp
+While event loop modifications are allowed between invocations of
+\&\f(CW\*(C`release\*(C'\fR and \f(CW\*(C`acquire\*(C'\fR (that's their only purpose after all), no
+modifications done will affect the event loop, i.e. adding watchers will
+have no effect on the set of file descriptors being watched, or the time
+waited. Use an \f(CW\*(C`ev_async\*(C'\fR watcher to wake up \f(CW\*(C`ev_run\*(C'\fR when you want it
+to take note of any changes you made.
+.Sp
+In theory, threads executing \f(CW\*(C`ev_run\*(C'\fR will be async-cancel safe between
+invocations of \f(CW\*(C`release\*(C'\fR and \f(CW\*(C`acquire\*(C'\fR.
+.Sp
+See also the locking example in the \f(CW\*(C`THREADS\*(C'\fR section later in this
+document.
+.IP "ev_set_userdata (loop, void *data)" 4
+.IX Item "ev_set_userdata (loop, void *data)"
+.PD 0
+.IP "void *ev_userdata (loop)" 4
+.IX Item "void *ev_userdata (loop)"
+.PD
+Set and retrieve a single \f(CW\*(C`void *\*(C'\fR associated with a loop. When
+\&\f(CW\*(C`ev_set_userdata\*(C'\fR has never been called, then \f(CW\*(C`ev_userdata\*(C'\fR returns
+\&\f(CW0\fR.
+.Sp
+These two functions can be used to associate arbitrary data with a loop,
+and are intended solely for the \f(CW\*(C`invoke_pending_cb\*(C'\fR, \f(CW\*(C`release\*(C'\fR and
+\&\f(CW\*(C`acquire\*(C'\fR callbacks described above, but of course can be (ab\-)used for
+any other purpose as well.
+.IP "ev_verify (loop)" 4
+.IX Item "ev_verify (loop)"
+This function only does something when \f(CW\*(C`EV_VERIFY\*(C'\fR support has been
+compiled in, which is the default for non-minimal builds. It tries to go
+through all internal structures and checks them for validity. If anything
+is found to be inconsistent, it will print an error message to standard
+error and call \f(CW\*(C`abort ()\*(C'\fR.
+.Sp
+This can be used to catch bugs inside libev itself: under normal
+circumstances, this function will never abort as of course libev keeps its
+data structures consistent.
+.SH "ANATOMY OF A WATCHER"
+.IX Header "ANATOMY OF A WATCHER"
+In the following description, uppercase \f(CW\*(C`TYPE\*(C'\fR in names stands for the
+watcher type, e.g. \f(CW\*(C`ev_TYPE_start\*(C'\fR can mean \f(CW\*(C`ev_timer_start\*(C'\fR for timer
+watchers and \f(CW\*(C`ev_io_start\*(C'\fR for I/O watchers.
+.PP
+A watcher is an opaque structure that you allocate and register to record
+your interest in some event. To make a concrete example, imagine you want
+to wait for \s-1STDIN\s0 to become readable, you would create an \f(CW\*(C`ev_io\*(C'\fR watcher
+for that:
+.PP
+.Vb 5
+\& static void my_cb (struct ev_loop *loop, ev_io *w, int revents)
+\& {
+\& ev_io_stop (w);
+\& ev_break (loop, EVBREAK_ALL);
+\& }
+\&
+\& struct ev_loop *loop = ev_default_loop (0);
+\&
+\& ev_io stdin_watcher;
+\&
+\& ev_init (&stdin_watcher, my_cb);
+\& ev_io_set (&stdin_watcher, STDIN_FILENO, EV_READ);
+\& ev_io_start (loop, &stdin_watcher);
+\&
+\& ev_run (loop, 0);
+.Ve
+.PP
+As you can see, you are responsible for allocating the memory for your
+watcher structures (and it is \fIusually\fR a bad idea to do this on the
+stack).
+.PP
+Each watcher has an associated watcher structure (called \f(CW\*(C`struct ev_TYPE\*(C'\fR
+or simply \f(CW\*(C`ev_TYPE\*(C'\fR, as typedefs are provided for all watcher structs).
+.PP
+Each watcher structure must be initialised by a call to \f(CW\*(C`ev_init (watcher
+*, callback)\*(C'\fR, which expects a callback to be provided. This callback is
+invoked each time the event occurs (or, in the case of I/O watchers, each
+time the event loop detects that the file descriptor given is readable
+and/or writable).
+.PP
+Each watcher type further has its own \f(CW\*(C`ev_TYPE_set (watcher *, ...)\*(C'\fR
+macro to configure it, with arguments specific to the watcher type. There
+is also a macro to combine initialisation and setting in one call: \f(CW\*(C`ev_TYPE_init (watcher *, callback, ...)\*(C'\fR.
+.PP
+To make the watcher actually watch out for events, you have to start it
+with a watcher-specific start function (\f(CW\*(C`ev_TYPE_start (loop, watcher
+*)\*(C'\fR), and you can stop watching for events at any time by calling the
+corresponding stop function (\f(CW\*(C`ev_TYPE_stop (loop, watcher *)\*(C'\fR.
+.PP
+As long as your watcher is active (has been started but not stopped) you
+must not touch the values stored in it except when explicitly documented
+otherwise. Most specifically you must never reinitialise it or call its
+\&\f(CW\*(C`ev_TYPE_set\*(C'\fR macro.
+.PP
+Each and every callback receives the event loop pointer as first, the
+registered watcher structure as second, and a bitset of received events as
+third argument.
+.PP
+The received events usually include a single bit per event type received
+(you can receive multiple events at the same time). The possible bit masks
+are:
+.ie n .IP """EV_READ""" 4
+.el .IP "\f(CWEV_READ\fR" 4
+.IX Item "EV_READ"
+.PD 0
+.ie n .IP """EV_WRITE""" 4
+.el .IP "\f(CWEV_WRITE\fR" 4
+.IX Item "EV_WRITE"
+.PD
+The file descriptor in the \f(CW\*(C`ev_io\*(C'\fR watcher has become readable and/or
+writable.
+.ie n .IP """EV_TIMER""" 4
+.el .IP "\f(CWEV_TIMER\fR" 4
+.IX Item "EV_TIMER"
+The \f(CW\*(C`ev_timer\*(C'\fR watcher has timed out.
+.ie n .IP """EV_PERIODIC""" 4
+.el .IP "\f(CWEV_PERIODIC\fR" 4
+.IX Item "EV_PERIODIC"
+The \f(CW\*(C`ev_periodic\*(C'\fR watcher has timed out.
+.ie n .IP """EV_SIGNAL""" 4
+.el .IP "\f(CWEV_SIGNAL\fR" 4
+.IX Item "EV_SIGNAL"
+The signal specified in the \f(CW\*(C`ev_signal\*(C'\fR watcher has been received by a thread.
+.ie n .IP """EV_CHILD""" 4
+.el .IP "\f(CWEV_CHILD\fR" 4
+.IX Item "EV_CHILD"
+The pid specified in the \f(CW\*(C`ev_child\*(C'\fR watcher has received a status change.
+.ie n .IP """EV_STAT""" 4
+.el .IP "\f(CWEV_STAT\fR" 4
+.IX Item "EV_STAT"
+The path specified in the \f(CW\*(C`ev_stat\*(C'\fR watcher changed its attributes somehow.
+.ie n .IP """EV_IDLE""" 4
+.el .IP "\f(CWEV_IDLE\fR" 4
+.IX Item "EV_IDLE"
+The \f(CW\*(C`ev_idle\*(C'\fR watcher has determined that you have nothing better to do.
+.ie n .IP """EV_PREPARE""" 4
+.el .IP "\f(CWEV_PREPARE\fR" 4
+.IX Item "EV_PREPARE"
+.PD 0
+.ie n .IP """EV_CHECK""" 4
+.el .IP "\f(CWEV_CHECK\fR" 4
+.IX Item "EV_CHECK"
+.PD
+All \f(CW\*(C`ev_prepare\*(C'\fR watchers are invoked just \fIbefore\fR \f(CW\*(C`ev_run\*(C'\fR starts to
+gather new events, and all \f(CW\*(C`ev_check\*(C'\fR watchers are queued (not invoked)
+just after \f(CW\*(C`ev_run\*(C'\fR has gathered them, but before it queues any callbacks
+for any received events. That means \f(CW\*(C`ev_prepare\*(C'\fR watchers are the last
+watchers invoked before the event loop sleeps or polls for new events, and
+\&\f(CW\*(C`ev_check\*(C'\fR watchers will be invoked before any other watchers of the same
+or lower priority within an event loop iteration.
+.Sp
+Callbacks of both watcher types can start and stop as many watchers as
+they want, and all of them will be taken into account (for example, a
+\&\f(CW\*(C`ev_prepare\*(C'\fR watcher might start an idle watcher to keep \f(CW\*(C`ev_run\*(C'\fR from
+blocking).
+.ie n .IP """EV_EMBED""" 4
+.el .IP "\f(CWEV_EMBED\fR" 4
+.IX Item "EV_EMBED"
+The embedded event loop specified in the \f(CW\*(C`ev_embed\*(C'\fR watcher needs attention.
+.ie n .IP """EV_FORK""" 4
+.el .IP "\f(CWEV_FORK\fR" 4
+.IX Item "EV_FORK"
+The event loop has been resumed in the child process after fork (see
+\&\f(CW\*(C`ev_fork\*(C'\fR).
+.ie n .IP """EV_CLEANUP""" 4
+.el .IP "\f(CWEV_CLEANUP\fR" 4
+.IX Item "EV_CLEANUP"
+The event loop is about to be destroyed (see \f(CW\*(C`ev_cleanup\*(C'\fR).
+.ie n .IP """EV_ASYNC""" 4
+.el .IP "\f(CWEV_ASYNC\fR" 4
+.IX Item "EV_ASYNC"
+The given async watcher has been asynchronously notified (see \f(CW\*(C`ev_async\*(C'\fR).
+.ie n .IP """EV_CUSTOM""" 4
+.el .IP "\f(CWEV_CUSTOM\fR" 4
+.IX Item "EV_CUSTOM"
+Not ever sent (or otherwise used) by libev itself, but can be freely used
+by libev users to signal watchers (e.g. via \f(CW\*(C`ev_feed_event\*(C'\fR).
+.ie n .IP """EV_ERROR""" 4
+.el .IP "\f(CWEV_ERROR\fR" 4
+.IX Item "EV_ERROR"
+An unspecified error has occurred, the watcher has been stopped. This might
+happen because the watcher could not be properly started because libev
+ran out of memory, a file descriptor was found to be closed or any other
+problem. Libev considers these application bugs.
+.Sp
+You best act on it by reporting the problem and somehow coping with the
+watcher being stopped. Note that well-written programs should not receive
+an error ever, so when your watcher receives it, this usually indicates a
+bug in your program.
+.Sp
+Libev will usually signal a few \*(L"dummy\*(R" events together with an error, for
+example it might indicate that a fd is readable or writable, and if your
+callbacks is well-written it can just attempt the operation and cope with
+the error from \fBread()\fR or \fBwrite()\fR. This will not work in multi-threaded
+programs, though, as the fd could already be closed and reused for another
+thing, so beware.
+.SS "\s-1GENERIC WATCHER FUNCTIONS\s0"
+.IX Subsection "GENERIC WATCHER FUNCTIONS"
+.ie n .IP """ev_init"" (ev_TYPE *watcher, callback)" 4
+.el .IP "\f(CWev_init\fR (ev_TYPE *watcher, callback)" 4
+.IX Item "ev_init (ev_TYPE *watcher, callback)"
+This macro initialises the generic portion of a watcher. The contents
+of the watcher object can be arbitrary (so \f(CW\*(C`malloc\*(C'\fR will do). Only
+the generic parts of the watcher are initialised, you \fIneed\fR to call
+the type-specific \f(CW\*(C`ev_TYPE_set\*(C'\fR macro afterwards to initialise the
+type-specific parts. For each type there is also a \f(CW\*(C`ev_TYPE_init\*(C'\fR macro
+which rolls both calls into one.
+.Sp
+You can reinitialise a watcher at any time as long as it has been stopped
+(or never started) and there are no pending events outstanding.
+.Sp
+The callback is always of type \f(CW\*(C`void (*)(struct ev_loop *loop, ev_TYPE *watcher,
+int revents)\*(C'\fR.
+.Sp
+Example: Initialise an \f(CW\*(C`ev_io\*(C'\fR watcher in two steps.
+.Sp
+.Vb 3
+\& ev_io w;
+\& ev_init (&w, my_cb);
+\& ev_io_set (&w, STDIN_FILENO, EV_READ);
+.Ve
+.ie n .IP """ev_TYPE_set"" (ev_TYPE *watcher, [args])" 4
+.el .IP "\f(CWev_TYPE_set\fR (ev_TYPE *watcher, [args])" 4
+.IX Item "ev_TYPE_set (ev_TYPE *watcher, [args])"
+This macro initialises the type-specific parts of a watcher. You need to
+call \f(CW\*(C`ev_init\*(C'\fR at least once before you call this macro, but you can
+call \f(CW\*(C`ev_TYPE_set\*(C'\fR any number of times. You must not, however, call this
+macro on a watcher that is active (it can be pending, however, which is a
+difference to the \f(CW\*(C`ev_init\*(C'\fR macro).
+.Sp
+Although some watcher types do not have type-specific arguments
+(e.g. \f(CW\*(C`ev_prepare\*(C'\fR) you still need to call its \f(CW\*(C`set\*(C'\fR macro.
+.Sp
+See \f(CW\*(C`ev_init\*(C'\fR, above, for an example.
+.ie n .IP """ev_TYPE_init"" (ev_TYPE *watcher, callback, [args])" 4
+.el .IP "\f(CWev_TYPE_init\fR (ev_TYPE *watcher, callback, [args])" 4
+.IX Item "ev_TYPE_init (ev_TYPE *watcher, callback, [args])"
+This convenience macro rolls both \f(CW\*(C`ev_init\*(C'\fR and \f(CW\*(C`ev_TYPE_set\*(C'\fR macro
+calls into a single call. This is the most convenient method to initialise
+a watcher. The same limitations apply, of course.
+.Sp
+Example: Initialise and set an \f(CW\*(C`ev_io\*(C'\fR watcher in one step.
+.Sp
+.Vb 1
+\& ev_io_init (&w, my_cb, STDIN_FILENO, EV_READ);
+.Ve
+.ie n .IP """ev_TYPE_start"" (loop, ev_TYPE *watcher)" 4
+.el .IP "\f(CWev_TYPE_start\fR (loop, ev_TYPE *watcher)" 4
+.IX Item "ev_TYPE_start (loop, ev_TYPE *watcher)"
+Starts (activates) the given watcher. Only active watchers will receive
+events. If the watcher is already active nothing will happen.
+.Sp
+Example: Start the \f(CW\*(C`ev_io\*(C'\fR watcher that is being abused as example in this
+whole section.
+.Sp
+.Vb 1
+\& ev_io_start (EV_DEFAULT_UC, &w);
+.Ve
+.ie n .IP """ev_TYPE_stop"" (loop, ev_TYPE *watcher)" 4
+.el .IP "\f(CWev_TYPE_stop\fR (loop, ev_TYPE *watcher)" 4
+.IX Item "ev_TYPE_stop (loop, ev_TYPE *watcher)"
+Stops the given watcher if active, and clears the pending status (whether
+the watcher was active or not).
+.Sp
+It is possible that stopped watchers are pending \- for example,
+non-repeating timers are being stopped when they become pending \- but
+calling \f(CW\*(C`ev_TYPE_stop\*(C'\fR ensures that the watcher is neither active nor
+pending. If you want to free or reuse the memory used by the watcher it is
+therefore a good idea to always call its \f(CW\*(C`ev_TYPE_stop\*(C'\fR function.
+.IP "bool ev_is_active (ev_TYPE *watcher)" 4
+.IX Item "bool ev_is_active (ev_TYPE *watcher)"
+Returns a true value iff the watcher is active (i.e. it has been started
+and not yet been stopped). As long as a watcher is active you must not modify
+it.
+.IP "bool ev_is_pending (ev_TYPE *watcher)" 4
+.IX Item "bool ev_is_pending (ev_TYPE *watcher)"
+Returns a true value iff the watcher is pending, (i.e. it has outstanding
+events but its callback has not yet been invoked). As long as a watcher
+is pending (but not active) you must not call an init function on it (but
+\&\f(CW\*(C`ev_TYPE_set\*(C'\fR is safe), you must not change its priority, and you must
+make sure the watcher is available to libev (e.g. you cannot \f(CW\*(C`free ()\*(C'\fR
+it).
+.IP "callback ev_cb (ev_TYPE *watcher)" 4
+.IX Item "callback ev_cb (ev_TYPE *watcher)"
+Returns the callback currently set on the watcher.
+.IP "ev_set_cb (ev_TYPE *watcher, callback)" 4
+.IX Item "ev_set_cb (ev_TYPE *watcher, callback)"
+Change the callback. You can change the callback at virtually any time
+(modulo threads).
+.IP "ev_set_priority (ev_TYPE *watcher, int priority)" 4
+.IX Item "ev_set_priority (ev_TYPE *watcher, int priority)"
+.PD 0
+.IP "int ev_priority (ev_TYPE *watcher)" 4
+.IX Item "int ev_priority (ev_TYPE *watcher)"
+.PD
+Set and query the priority of the watcher. The priority is a small
+integer between \f(CW\*(C`EV_MAXPRI\*(C'\fR (default: \f(CW2\fR) and \f(CW\*(C`EV_MINPRI\*(C'\fR
+(default: \f(CW\*(C`\-2\*(C'\fR). Pending watchers with higher priority will be invoked
+before watchers with lower priority, but priority will not keep watchers
+from being executed (except for \f(CW\*(C`ev_idle\*(C'\fR watchers).
+.Sp
+If you need to suppress invocation when higher priority events are pending
+you need to look at \f(CW\*(C`ev_idle\*(C'\fR watchers, which provide this functionality.
+.Sp
+You \fImust not\fR change the priority of a watcher as long as it is active or
+pending.
+.Sp
+Setting a priority outside the range of \f(CW\*(C`EV_MINPRI\*(C'\fR to \f(CW\*(C`EV_MAXPRI\*(C'\fR is
+fine, as long as you do not mind that the priority value you query might
+or might not have been clamped to the valid range.
+.Sp
+The default priority used by watchers when no priority has been set is
+always \f(CW0\fR, which is supposed to not be too high and not be too low :).
+.Sp
+See \*(L"\s-1WATCHER PRIORITY MODELS\*(R"\s0, below, for a more thorough treatment of
+priorities.
+.IP "ev_invoke (loop, ev_TYPE *watcher, int revents)" 4
+.IX Item "ev_invoke (loop, ev_TYPE *watcher, int revents)"
+Invoke the \f(CW\*(C`watcher\*(C'\fR with the given \f(CW\*(C`loop\*(C'\fR and \f(CW\*(C`revents\*(C'\fR. Neither
+\&\f(CW\*(C`loop\*(C'\fR nor \f(CW\*(C`revents\*(C'\fR need to be valid as long as the watcher callback
+can deal with that fact, as both are simply passed through to the
+callback.
+.IP "int ev_clear_pending (loop, ev_TYPE *watcher)" 4
+.IX Item "int ev_clear_pending (loop, ev_TYPE *watcher)"
+If the watcher is pending, this function clears its pending status and
+returns its \f(CW\*(C`revents\*(C'\fR bitset (as if its callback was invoked). If the
+watcher isn't pending it does nothing and returns \f(CW0\fR.
+.Sp
+Sometimes it can be useful to \*(L"poll\*(R" a watcher instead of waiting for its
+callback to be invoked, which can be accomplished with this function.
+.IP "ev_feed_event (loop, ev_TYPE *watcher, int revents)" 4
+.IX Item "ev_feed_event (loop, ev_TYPE *watcher, int revents)"
+Feeds the given event set into the event loop, as if the specified event
+had happened for the specified watcher (which must be a pointer to an
+initialised but not necessarily started event watcher). Obviously you must
+not free the watcher as long as it has pending events.
+.Sp
+Stopping the watcher, letting libev invoke it, or calling
+\&\f(CW\*(C`ev_clear_pending\*(C'\fR will clear the pending event, even if the watcher was
+not started in the first place.
+.Sp
+See also \f(CW\*(C`ev_feed_fd_event\*(C'\fR and \f(CW\*(C`ev_feed_signal_event\*(C'\fR for related
+functions that do not need a watcher.
+.PP
+See also the \*(L"\s-1ASSOCIATING CUSTOM DATA WITH A WATCHER\*(R"\s0 and \*(L"\s-1BUILDING YOUR
+OWN COMPOSITE WATCHERS\*(R"\s0 idioms.
+.SS "\s-1WATCHER STATES\s0"
+.IX Subsection "WATCHER STATES"
+There are various watcher states mentioned throughout this manual \-
+active, pending and so on. In this section these states and the rules to
+transition between them will be described in more detail \- and while these
+rules might look complicated, they usually do \*(L"the right thing\*(R".
+.IP "initialised" 4
+.IX Item "initialised"
+Before a watcher can be registered with the event loop it has to be
+initialised. This can be done with a call to \f(CW\*(C`ev_TYPE_init\*(C'\fR, or calls to
+\&\f(CW\*(C`ev_init\*(C'\fR followed by the watcher-specific \f(CW\*(C`ev_TYPE_set\*(C'\fR function.
+.Sp
+In this state it is simply some block of memory that is suitable for
+use in an event loop. It can be moved around, freed, reused etc. at
+will \- as long as you either keep the memory contents intact, or call
+\&\f(CW\*(C`ev_TYPE_init\*(C'\fR again.
+.IP "started/running/active" 4
+.IX Item "started/running/active"
+Once a watcher has been started with a call to \f(CW\*(C`ev_TYPE_start\*(C'\fR it becomes
+property of the event loop, and is actively waiting for events. While in
+this state it cannot be accessed (except in a few documented ways), moved,
+freed or anything else \- the only legal thing is to keep a pointer to it,
+and call libev functions on it that are documented to work on active watchers.
+.IP "pending" 4
+.IX Item "pending"
+If a watcher is active and libev determines that an event it is interested
+in has occurred (such as a timer expiring), it will become pending. It will
+stay in this pending state until either it is stopped or its callback is
+about to be invoked, so it is not normally pending inside the watcher
+callback.
+.Sp
+The watcher might or might not be active while it is pending (for example,
+an expired non-repeating timer can be pending but no longer active). If it
+is stopped, it can be freely accessed (e.g. by calling \f(CW\*(C`ev_TYPE_set\*(C'\fR),
+but it is still property of the event loop at this time, so cannot be
+moved, freed or reused. And if it is active the rules described in the
+previous item still apply.
+.Sp
+It is also possible to feed an event on a watcher that is not active (e.g.
+via \f(CW\*(C`ev_feed_event\*(C'\fR), in which case it becomes pending without being
+active.
+.IP "stopped" 4
+.IX Item "stopped"
+A watcher can be stopped implicitly by libev (in which case it might still
+be pending), or explicitly by calling its \f(CW\*(C`ev_TYPE_stop\*(C'\fR function. The
+latter will clear any pending state the watcher might be in, regardless
+of whether it was active or not, so stopping a watcher explicitly before
+freeing it is often a good idea.
+.Sp
+While stopped (and not pending) the watcher is essentially in the
+initialised state, that is, it can be reused, moved, modified in any way
+you wish (but when you trash the memory block, you need to \f(CW\*(C`ev_TYPE_init\*(C'\fR
+it again).
+.SS "\s-1WATCHER PRIORITY MODELS\s0"
+.IX Subsection "WATCHER PRIORITY MODELS"
+Many event loops support \fIwatcher priorities\fR, which are usually small
+integers that influence the ordering of event callback invocation
+between watchers in some way, all else being equal.
+.PP
+In libev, watcher priorities can be set using \f(CW\*(C`ev_set_priority\*(C'\fR. See its
+description for the more technical details such as the actual priority
+range.
+.PP
+There are two common ways how these these priorities are being interpreted
+by event loops:
+.PP
+In the more common lock-out model, higher priorities \*(L"lock out\*(R" invocation
+of lower priority watchers, which means as long as higher priority
+watchers receive events, lower priority watchers are not being invoked.
+.PP
+The less common only-for-ordering model uses priorities solely to order
+callback invocation within a single event loop iteration: Higher priority
+watchers are invoked before lower priority ones, but they all get invoked
+before polling for new events.
+.PP
+Libev uses the second (only-for-ordering) model for all its watchers
+except for idle watchers (which use the lock-out model).
+.PP
+The rationale behind this is that implementing the lock-out model for
+watchers is not well supported by most kernel interfaces, and most event
+libraries will just poll for the same events again and again as long as
+their callbacks have not been executed, which is very inefficient in the
+common case of one high-priority watcher locking out a mass of lower
+priority ones.
+.PP
+Static (ordering) priorities are most useful when you have two or more
+watchers handling the same resource: a typical usage example is having an
+\&\f(CW\*(C`ev_io\*(C'\fR watcher to receive data, and an associated \f(CW\*(C`ev_timer\*(C'\fR to handle
+timeouts. Under load, data might be received while the program handles
+other jobs, but since timers normally get invoked first, the timeout
+handler will be executed before checking for data. In that case, giving
+the timer a lower priority than the I/O watcher ensures that I/O will be
+handled first even under adverse conditions (which is usually, but not
+always, what you want).
+.PP
+Since idle watchers use the \*(L"lock-out\*(R" model, meaning that idle watchers
+will only be executed when no same or higher priority watchers have
+received events, they can be used to implement the \*(L"lock-out\*(R" model when
+required.
+.PP
+For example, to emulate how many other event libraries handle priorities,
+you can associate an \f(CW\*(C`ev_idle\*(C'\fR watcher to each such watcher, and in
+the normal watcher callback, you just start the idle watcher. The real
+processing is done in the idle watcher callback. This causes libev to
+continuously poll and process kernel event data for the watcher, but when
+the lock-out case is known to be rare (which in turn is rare :), this is
+workable.
+.PP
+Usually, however, the lock-out model implemented that way will perform
+miserably under the type of load it was designed to handle. In that case,
+it might be preferable to stop the real watcher before starting the
+idle watcher, so the kernel will not have to process the event in case
+the actual processing will be delayed for considerable time.
+.PP
+Here is an example of an I/O watcher that should run at a strictly lower
+priority than the default, and which should only process data when no
+other events are pending:
+.PP
+.Vb 2
+\& ev_idle idle; // actual processing watcher
+\& ev_io io; // actual event watcher
+\&
+\& static void
+\& io_cb (EV_P_ ev_io *w, int revents)
+\& {
+\& // stop the I/O watcher, we received the event, but
+\& // are not yet ready to handle it.
+\& ev_io_stop (EV_A_ w);
+\&
+\& // start the idle watcher to handle the actual event.
+\& // it will not be executed as long as other watchers
+\& // with the default priority are receiving events.
+\& ev_idle_start (EV_A_ &idle);
+\& }
+\&
+\& static void
+\& idle_cb (EV_P_ ev_idle *w, int revents)
+\& {
+\& // actual processing
+\& read (STDIN_FILENO, ...);
+\&
+\& // have to start the I/O watcher again, as
+\& // we have handled the event
+\& ev_io_start (EV_P_ &io);
+\& }
+\&
+\& // initialisation
+\& ev_idle_init (&idle, idle_cb);
+\& ev_io_init (&io, io_cb, STDIN_FILENO, EV_READ);
+\& ev_io_start (EV_DEFAULT_ &io);
+.Ve
+.PP
+In the \*(L"real\*(R" world, it might also be beneficial to start a timer, so that
+low-priority connections can not be locked out forever under load. This
+enables your program to keep a lower latency for important connections
+during short periods of high load, while not completely locking out less
+important ones.
+.SH "WATCHER TYPES"
+.IX Header "WATCHER TYPES"
+This section describes each watcher in detail, but will not repeat
+information given in the last section. Any initialisation/set macros,
+functions and members specific to the watcher type are explained.
+.PP
+Most members are additionally marked with either \fI[read\-only]\fR, meaning
+that, while the watcher is active, you can look at the member and expect
+some sensible content, but you must not modify it (you can modify it while
+the watcher is stopped to your hearts content), or \fI[read\-write]\fR, which
+means you can expect it to have some sensible content while the watcher is
+active, but you can also modify it (within the same thread as the event
+loop, i.e. without creating data races). Modifying it may not do something
+sensible or take immediate effect (or do anything at all), but libev will
+not crash or malfunction in any way.
+.PP
+In any case, the documentation for each member will explain what the
+effects are, and if there are any additional access restrictions.
+.ie n .SS """ev_io"" \- is this file descriptor readable or writable?"
+.el .SS "\f(CWev_io\fP \- is this file descriptor readable or writable?"
+.IX Subsection "ev_io - is this file descriptor readable or writable?"
+I/O watchers check whether a file descriptor is readable or writable
+in each iteration of the event loop, or, more precisely, when reading
+would not block the process and writing would at least be able to write
+some data. This behaviour is called level-triggering because you keep
+receiving events as long as the condition persists. Remember you can stop
+the watcher if you don't want to act on the event and neither want to
+receive future events.
+.PP
+In general you can register as many read and/or write event watchers per
+fd as you want (as long as you don't confuse yourself). Setting all file
+descriptors to non-blocking mode is also usually a good idea (but not
+required if you know what you are doing).
+.PP
+Another thing you have to watch out for is that it is quite easy to
+receive \*(L"spurious\*(R" readiness notifications, that is, your callback might
+be called with \f(CW\*(C`EV_READ\*(C'\fR but a subsequent \f(CW\*(C`read\*(C'\fR(2) will actually block
+because there is no data. It is very easy to get into this situation even
+with a relatively standard program structure. Thus it is best to always
+use non-blocking I/O: An extra \f(CW\*(C`read\*(C'\fR(2) returning \f(CW\*(C`EAGAIN\*(C'\fR is far
+preferable to a program hanging until some data arrives.
+.PP
+If you cannot run the fd in non-blocking mode (for example you should
+not play around with an Xlib connection), then you have to separately
+re-test whether a file descriptor is really ready with a known-to-be good
+interface such as poll (fortunately in the case of Xlib, it already does
+this on its own, so its quite safe to use). Some people additionally
+use \f(CW\*(C`SIGALRM\*(C'\fR and an interval timer, just to be sure you won't block
+indefinitely.
+.PP
+But really, best use non-blocking mode.
+.PP
+\fIThe special problem of disappearing file descriptors\fR
+.IX Subsection "The special problem of disappearing file descriptors"
+.PP
+Some backends (e.g. kqueue, epoll, linuxaio) need to be told about closing
+a file descriptor (either due to calling \f(CW\*(C`close\*(C'\fR explicitly or any other
+means, such as \f(CW\*(C`dup2\*(C'\fR). The reason is that you register interest in some
+file descriptor, but when it goes away, the operating system will silently
+drop this interest. If another file descriptor with the same number then
+is registered with libev, there is no efficient way to see that this is,
+in fact, a different file descriptor.
+.PP
+To avoid having to explicitly tell libev about such cases, libev follows
+the following policy: Each time \f(CW\*(C`ev_io_set\*(C'\fR is being called, libev
+will assume that this is potentially a new file descriptor, otherwise
+it is assumed that the file descriptor stays the same. That means that
+you \fIhave\fR to call \f(CW\*(C`ev_io_set\*(C'\fR (or \f(CW\*(C`ev_io_init\*(C'\fR) when you change the
+descriptor even if the file descriptor number itself did not change.
+.PP
+This is how one would do it normally anyway, the important point is that
+the libev application should not optimise around libev but should leave
+optimisations to libev.
+.PP
+\fIThe special problem of dup'ed file descriptors\fR
+.IX Subsection "The special problem of dup'ed file descriptors"
+.PP
+Some backends (e.g. epoll), cannot register events for file descriptors,
+but only events for the underlying file descriptions. That means when you
+have \f(CW\*(C`dup ()\*(C'\fR'ed file descriptors or weirder constellations, and register
+events for them, only one file descriptor might actually receive events.
+.PP
+There is no workaround possible except not registering events
+for potentially \f(CW\*(C`dup ()\*(C'\fR'ed file descriptors, or to resort to
+\&\f(CW\*(C`EVBACKEND_SELECT\*(C'\fR or \f(CW\*(C`EVBACKEND_POLL\*(C'\fR.
+.PP
+\fIThe special problem of files\fR
+.IX Subsection "The special problem of files"
+.PP
+Many people try to use \f(CW\*(C`select\*(C'\fR (or libev) on file descriptors
+representing files, and expect it to become ready when their program
+doesn't block on disk accesses (which can take a long time on their own).
+.PP
+However, this cannot ever work in the \*(L"expected\*(R" way \- you get a readiness
+notification as soon as the kernel knows whether and how much data is
+there, and in the case of open files, that's always the case, so you
+always get a readiness notification instantly, and your read (or possibly
+write) will still block on the disk I/O.
+.PP
+Another way to view it is that in the case of sockets, pipes, character
+devices and so on, there is another party (the sender) that delivers data
+on its own, but in the case of files, there is no such thing: the disk
+will not send data on its own, simply because it doesn't know what you
+wish to read \- you would first have to request some data.
+.PP
+Since files are typically not-so-well supported by advanced notification
+mechanism, libev tries hard to emulate \s-1POSIX\s0 behaviour with respect
+to files, even though you should not use it. The reason for this is
+convenience: sometimes you want to watch \s-1STDIN\s0 or \s-1STDOUT,\s0 which is
+usually a tty, often a pipe, but also sometimes files or special devices
+(for example, \f(CW\*(C`epoll\*(C'\fR on Linux works with \fI/dev/random\fR but not with
+\&\fI/dev/urandom\fR), and even though the file might better be served with
+asynchronous I/O instead of with non-blocking I/O, it is still useful when
+it \*(L"just works\*(R" instead of freezing.
+.PP
+So avoid file descriptors pointing to files when you know it (e.g. use
+libeio), but use them when it is convenient, e.g. for \s-1STDIN/STDOUT,\s0 or
+when you rarely read from a file instead of from a socket, and want to
+reuse the same code path.
+.PP
+\fIThe special problem of fork\fR
+.IX Subsection "The special problem of fork"
+.PP
+Some backends (epoll, kqueue, linuxaio, iouring) do not support \f(CW\*(C`fork ()\*(C'\fR
+at all or exhibit useless behaviour. Libev fully supports fork, but needs
+to be told about it in the child if you want to continue to use it in the
+child.
+.PP
+To support fork in your child processes, you have to call \f(CW\*(C`ev_loop_fork
+()\*(C'\fR after a fork in the child, enable \f(CW\*(C`EVFLAG_FORKCHECK\*(C'\fR, or resort to
+\&\f(CW\*(C`EVBACKEND_SELECT\*(C'\fR or \f(CW\*(C`EVBACKEND_POLL\*(C'\fR.
+.PP
+\fIThe special problem of \s-1SIGPIPE\s0\fR
+.IX Subsection "The special problem of SIGPIPE"
+.PP
+While not really specific to libev, it is easy to forget about \f(CW\*(C`SIGPIPE\*(C'\fR:
+when writing to a pipe whose other end has been closed, your program gets
+sent a \s-1SIGPIPE,\s0 which, by default, aborts your program. For most programs
+this is sensible behaviour, for daemons, this is usually undesirable.
+.PP
+So when you encounter spurious, unexplained daemon exits, make sure you
+ignore \s-1SIGPIPE\s0 (and maybe make sure you log the exit status of your daemon
+somewhere, as that would have given you a big clue).
+.PP
+\fIThe special problem of \f(BIaccept()\fIing when you can't\fR
+.IX Subsection "The special problem of accept()ing when you can't"
+.PP
+Many implementations of the \s-1POSIX\s0 \f(CW\*(C`accept\*(C'\fR function (for example,
+found in post\-2004 Linux) have the peculiar behaviour of not removing a
+connection from the pending queue in all error cases.
+.PP
+For example, larger servers often run out of file descriptors (because
+of resource limits), causing \f(CW\*(C`accept\*(C'\fR to fail with \f(CW\*(C`ENFILE\*(C'\fR but not
+rejecting the connection, leading to libev signalling readiness on
+the next iteration again (the connection still exists after all), and
+typically causing the program to loop at 100% \s-1CPU\s0 usage.
+.PP
+Unfortunately, the set of errors that cause this issue differs between
+operating systems, there is usually little the app can do to remedy the
+situation, and no known thread-safe method of removing the connection to
+cope with overload is known (to me).
+.PP
+One of the easiest ways to handle this situation is to just ignore it
+\&\- when the program encounters an overload, it will just loop until the
+situation is over. While this is a form of busy waiting, no \s-1OS\s0 offers an
+event-based way to handle this situation, so it's the best one can do.
+.PP
+A better way to handle the situation is to log any errors other than
+\&\f(CW\*(C`EAGAIN\*(C'\fR and \f(CW\*(C`EWOULDBLOCK\*(C'\fR, making sure not to flood the log with such
+messages, and continue as usual, which at least gives the user an idea of
+what could be wrong (\*(L"raise the ulimit!\*(R"). For extra points one could stop
+the \f(CW\*(C`ev_io\*(C'\fR watcher on the listening fd \*(L"for a while\*(R", which reduces \s-1CPU\s0
+usage.
+.PP
+If your program is single-threaded, then you could also keep a dummy file
+descriptor for overload situations (e.g. by opening \fI/dev/null\fR), and
+when you run into \f(CW\*(C`ENFILE\*(C'\fR or \f(CW\*(C`EMFILE\*(C'\fR, close it, run \f(CW\*(C`accept\*(C'\fR,
+close that fd, and create a new dummy fd. This will gracefully refuse
+clients under typical overload conditions.
+.PP
+The last way to handle it is to simply log the error and \f(CW\*(C`exit\*(C'\fR, as
+is often done with \f(CW\*(C`malloc\*(C'\fR failures, but this results in an easy
+opportunity for a DoS attack.
+.PP
+\fIWatcher-Specific Functions\fR
+.IX Subsection "Watcher-Specific Functions"
+.IP "ev_io_init (ev_io *, callback, int fd, int events)" 4
+.IX Item "ev_io_init (ev_io *, callback, int fd, int events)"
+.PD 0
+.IP "ev_io_set (ev_io *, int fd, int events)" 4
+.IX Item "ev_io_set (ev_io *, int fd, int events)"
+.PD
+Configures an \f(CW\*(C`ev_io\*(C'\fR watcher. The \f(CW\*(C`fd\*(C'\fR is the file descriptor to
+receive events for and \f(CW\*(C`events\*(C'\fR is either \f(CW\*(C`EV_READ\*(C'\fR, \f(CW\*(C`EV_WRITE\*(C'\fR, both
+\&\f(CW\*(C`EV_READ | EV_WRITE\*(C'\fR or \f(CW0\fR, to express the desire to receive the given
+events.
+.Sp
+Note that setting the \f(CW\*(C`events\*(C'\fR to \f(CW0\fR and starting the watcher is
+supported, but not specially optimized \- if your program sometimes happens
+to generate this combination this is fine, but if it is easy to avoid
+starting an io watcher watching for no events you should do so.
+.IP "ev_io_modify (ev_io *, int events)" 4
+.IX Item "ev_io_modify (ev_io *, int events)"
+Similar to \f(CW\*(C`ev_io_set\*(C'\fR, but only changes the requested events. Using this
+might be faster with some backends, as libev can assume that the \f(CW\*(C`fd\*(C'\fR
+still refers to the same underlying file description, something it cannot
+do when using \f(CW\*(C`ev_io_set\*(C'\fR.
+.IP "int fd [no\-modify]" 4
+.IX Item "int fd [no-modify]"
+The file descriptor being watched. While it can be read at any time, you
+must not modify this member even when the watcher is stopped \- always use
+\&\f(CW\*(C`ev_io_set\*(C'\fR for that.
+.IP "int events [no\-modify]" 4
+.IX Item "int events [no-modify]"
+The set of events the fd is being watched for, among other flags. Remember
+that this is a bit set \- to test for \f(CW\*(C`EV_READ\*(C'\fR, use \f(CW\*(C`w\->events &
+EV_READ\*(C'\fR, and similarly for \f(CW\*(C`EV_WRITE\*(C'\fR.
+.Sp
+As with \f(CW\*(C`fd\*(C'\fR, you must not modify this member even when the watcher is
+stopped, always use \f(CW\*(C`ev_io_set\*(C'\fR or \f(CW\*(C`ev_io_modify\*(C'\fR for that.
+.PP
+\fIExamples\fR
+.IX Subsection "Examples"
+.PP
+Example: Call \f(CW\*(C`stdin_readable_cb\*(C'\fR when \s-1STDIN_FILENO\s0 has become, well
+readable, but only once. Since it is likely line-buffered, you could
+attempt to read a whole line in the callback.
+.PP
+.Vb 6
+\& static void
+\& stdin_readable_cb (struct ev_loop *loop, ev_io *w, int revents)
+\& {
+\& ev_io_stop (loop, w);
+\& .. read from stdin here (or from w\->fd) and handle any I/O errors
+\& }
+\&
+\& ...
+\& struct ev_loop *loop = ev_default_init (0);
+\& ev_io stdin_readable;
+\& ev_io_init (&stdin_readable, stdin_readable_cb, STDIN_FILENO, EV_READ);
+\& ev_io_start (loop, &stdin_readable);
+\& ev_run (loop, 0);
+.Ve
+.ie n .SS """ev_timer"" \- relative and optionally repeating timeouts"
+.el .SS "\f(CWev_timer\fP \- relative and optionally repeating timeouts"
+.IX Subsection "ev_timer - relative and optionally repeating timeouts"
+Timer watchers are simple relative timers that generate an event after a
+given time, and optionally repeating in regular intervals after that.
+.PP
+The timers are based on real time, that is, if you register an event that
+times out after an hour and you reset your system clock to January last
+year, it will still time out after (roughly) one hour. \*(L"Roughly\*(R" because
+detecting time jumps is hard, and some inaccuracies are unavoidable (the
+monotonic clock option helps a lot here).
+.PP
+The callback is guaranteed to be invoked only \fIafter\fR its timeout has
+passed (not \fIat\fR, so on systems with very low-resolution clocks this
+might introduce a small delay, see \*(L"the special problem of being too
+early\*(R", below). If multiple timers become ready during the same loop
+iteration then the ones with earlier time-out values are invoked before
+ones of the same priority with later time-out values (but this is no
+longer true when a callback calls \f(CW\*(C`ev_run\*(C'\fR recursively).
+.PP
+\fIBe smart about timeouts\fR
+.IX Subsection "Be smart about timeouts"
+.PP
+Many real-world problems involve some kind of timeout, usually for error
+recovery. A typical example is an \s-1HTTP\s0 request \- if the other side hangs,
+you want to raise some error after a while.
+.PP
+What follows are some ways to handle this problem, from obvious and
+inefficient to smart and efficient.
+.PP
+In the following, a 60 second activity timeout is assumed \- a timeout that
+gets reset to 60 seconds each time there is activity (e.g. each time some
+data or other life sign was received).
+.IP "1. Use a timer and stop, reinitialise and start it on activity." 4
+.IX Item "1. Use a timer and stop, reinitialise and start it on activity."
+This is the most obvious, but not the most simple way: In the beginning,
+start the watcher:
+.Sp
+.Vb 2
+\& ev_timer_init (timer, callback, 60., 0.);
+\& ev_timer_start (loop, timer);
+.Ve
+.Sp
+Then, each time there is some activity, \f(CW\*(C`ev_timer_stop\*(C'\fR it, initialise it
+and start it again:
+.Sp
+.Vb 3
+\& ev_timer_stop (loop, timer);
+\& ev_timer_set (timer, 60., 0.);
+\& ev_timer_start (loop, timer);
+.Ve
+.Sp
+This is relatively simple to implement, but means that each time there is
+some activity, libev will first have to remove the timer from its internal
+data structure and then add it again. Libev tries to be fast, but it's
+still not a constant-time operation.
+.ie n .IP "2. Use a timer and re-start it with ""ev_timer_again"" inactivity." 4
+.el .IP "2. Use a timer and re-start it with \f(CWev_timer_again\fR inactivity." 4
+.IX Item "2. Use a timer and re-start it with ev_timer_again inactivity."
+This is the easiest way, and involves using \f(CW\*(C`ev_timer_again\*(C'\fR instead of
+\&\f(CW\*(C`ev_timer_start\*(C'\fR.
+.Sp
+To implement this, configure an \f(CW\*(C`ev_timer\*(C'\fR with a \f(CW\*(C`repeat\*(C'\fR value
+of \f(CW60\fR and then call \f(CW\*(C`ev_timer_again\*(C'\fR at start and each time you
+successfully read or write some data. If you go into an idle state where
+you do not expect data to travel on the socket, you can \f(CW\*(C`ev_timer_stop\*(C'\fR
+the timer, and \f(CW\*(C`ev_timer_again\*(C'\fR will automatically restart it if need be.
+.Sp
+That means you can ignore both the \f(CW\*(C`ev_timer_start\*(C'\fR function and the
+\&\f(CW\*(C`after\*(C'\fR argument to \f(CW\*(C`ev_timer_set\*(C'\fR, and only ever use the \f(CW\*(C`repeat\*(C'\fR
+member and \f(CW\*(C`ev_timer_again\*(C'\fR.
+.Sp
+At start:
+.Sp
+.Vb 3
+\& ev_init (timer, callback);
+\& timer\->repeat = 60.;
+\& ev_timer_again (loop, timer);
+.Ve
+.Sp
+Each time there is some activity:
+.Sp
+.Vb 1
+\& ev_timer_again (loop, timer);
+.Ve
+.Sp
+It is even possible to change the time-out on the fly, regardless of
+whether the watcher is active or not:
+.Sp
+.Vb 2
+\& timer\->repeat = 30.;
+\& ev_timer_again (loop, timer);
+.Ve
+.Sp
+This is slightly more efficient then stopping/starting the timer each time
+you want to modify its timeout value, as libev does not have to completely
+remove and re-insert the timer from/into its internal data structure.
+.Sp
+It is, however, even simpler than the \*(L"obvious\*(R" way to do it.
+.IP "3. Let the timer time out, but then re-arm it as required." 4
+.IX Item "3. Let the timer time out, but then re-arm it as required."
+This method is more tricky, but usually most efficient: Most timeouts are
+relatively long compared to the intervals between other activity \- in
+our example, within 60 seconds, there are usually many I/O events with
+associated activity resets.
+.Sp
+In this case, it would be more efficient to leave the \f(CW\*(C`ev_timer\*(C'\fR alone,
+but remember the time of last activity, and check for a real timeout only
+within the callback:
+.Sp
+.Vb 3
+\& ev_tstamp timeout = 60.;
+\& ev_tstamp last_activity; // time of last activity
+\& ev_timer timer;
+\&
+\& static void
+\& callback (EV_P_ ev_timer *w, int revents)
+\& {
+\& // calculate when the timeout would happen
+\& ev_tstamp after = last_activity \- ev_now (EV_A) + timeout;
+\&
+\& // if negative, it means we the timeout already occurred
+\& if (after < 0.)
+\& {
+\& // timeout occurred, take action
+\& }
+\& else
+\& {
+\& // callback was invoked, but there was some recent
+\& // activity. simply restart the timer to time out
+\& // after "after" seconds, which is the earliest time
+\& // the timeout can occur.
+\& ev_timer_set (w, after, 0.);
+\& ev_timer_start (EV_A_ w);
+\& }
+\& }
+.Ve
+.Sp
+To summarise the callback: first calculate in how many seconds the
+timeout will occur (by calculating the absolute time when it would occur,
+\&\f(CW\*(C`last_activity + timeout\*(C'\fR, and subtracting the current time, \f(CW\*(C`ev_now
+(EV_A)\*(C'\fR from that).
+.Sp
+If this value is negative, then we are already past the timeout, i.e. we
+timed out, and need to do whatever is needed in this case.
+.Sp
+Otherwise, we now the earliest time at which the timeout would trigger,
+and simply start the timer with this timeout value.
+.Sp
+In other words, each time the callback is invoked it will check whether
+the timeout occurred. If not, it will simply reschedule itself to check
+again at the earliest time it could time out. Rinse. Repeat.
+.Sp
+This scheme causes more callback invocations (about one every 60 seconds
+minus half the average time between activity), but virtually no calls to
+libev to change the timeout.
+.Sp
+To start the machinery, simply initialise the watcher and set
+\&\f(CW\*(C`last_activity\*(C'\fR to the current time (meaning there was some activity just
+now), then call the callback, which will \*(L"do the right thing\*(R" and start
+the timer:
+.Sp
+.Vb 3
+\& last_activity = ev_now (EV_A);
+\& ev_init (&timer, callback);
+\& callback (EV_A_ &timer, 0);
+.Ve
+.Sp
+When there is some activity, simply store the current time in
+\&\f(CW\*(C`last_activity\*(C'\fR, no libev calls at all:
+.Sp
+.Vb 2
+\& if (activity detected)
+\& last_activity = ev_now (EV_A);
+.Ve
+.Sp
+When your timeout value changes, then the timeout can be changed by simply
+providing a new value, stopping the timer and calling the callback, which
+will again do the right thing (for example, time out immediately :).
+.Sp
+.Vb 3
+\& timeout = new_value;
+\& ev_timer_stop (EV_A_ &timer);
+\& callback (EV_A_ &timer, 0);
+.Ve
+.Sp
+This technique is slightly more complex, but in most cases where the
+time-out is unlikely to be triggered, much more efficient.
+.IP "4. Wee, just use a double-linked list for your timeouts." 4
+.IX Item "4. Wee, just use a double-linked list for your timeouts."
+If there is not one request, but many thousands (millions...), all
+employing some kind of timeout with the same timeout value, then one can
+do even better:
+.Sp
+When starting the timeout, calculate the timeout value and put the timeout
+at the \fIend\fR of the list.
+.Sp
+Then use an \f(CW\*(C`ev_timer\*(C'\fR to fire when the timeout at the \fIbeginning\fR of
+the list is expected to fire (for example, using the technique #3).
+.Sp
+When there is some activity, remove the timer from the list, recalculate
+the timeout, append it to the end of the list again, and make sure to
+update the \f(CW\*(C`ev_timer\*(C'\fR if it was taken from the beginning of the list.
+.Sp
+This way, one can manage an unlimited number of timeouts in O(1) time for
+starting, stopping and updating the timers, at the expense of a major
+complication, and having to use a constant timeout. The constant timeout
+ensures that the list stays sorted.
+.PP
+So which method the best?
+.PP
+Method #2 is a simple no-brain-required solution that is adequate in most
+situations. Method #3 requires a bit more thinking, but handles many cases
+better, and isn't very complicated either. In most case, choosing either
+one is fine, with #3 being better in typical situations.
+.PP
+Method #1 is almost always a bad idea, and buys you nothing. Method #4 is
+rather complicated, but extremely efficient, something that really pays
+off after the first million or so of active timers, i.e. it's usually
+overkill :)
+.PP
+\fIThe special problem of being too early\fR
+.IX Subsection "The special problem of being too early"
+.PP
+If you ask a timer to call your callback after three seconds, then
+you expect it to be invoked after three seconds \- but of course, this
+cannot be guaranteed to infinite precision. Less obviously, it cannot be
+guaranteed to any precision by libev \- imagine somebody suspending the
+process with a \s-1STOP\s0 signal for a few hours for example.
+.PP
+So, libev tries to invoke your callback as soon as possible \fIafter\fR the
+delay has occurred, but cannot guarantee this.
+.PP
+A less obvious failure mode is calling your callback too early: many event
+loops compare timestamps with a \*(L"elapsed delay >= requested delay\*(R", but
+this can cause your callback to be invoked much earlier than you would
+expect.
+.PP
+To see why, imagine a system with a clock that only offers full second
+resolution (think windows if you can't come up with a broken enough \s-1OS\s0
+yourself). If you schedule a one-second timer at the time 500.9, then the
+event loop will schedule your timeout to elapse at a system time of 500
+(500.9 truncated to the resolution) + 1, or 501.
+.PP
+If an event library looks at the timeout 0.1s later, it will see \*(L"501 >=
+501\*(R" and invoke the callback 0.1s after it was started, even though a
+one-second delay was requested \- this is being \*(L"too early\*(R", despite best
+intentions.
+.PP
+This is the reason why libev will never invoke the callback if the elapsed
+delay equals the requested delay, but only when the elapsed delay is
+larger than the requested delay. In the example above, libev would only invoke
+the callback at system time 502, or 1.1s after the timer was started.
+.PP
+So, while libev cannot guarantee that your callback will be invoked
+exactly when requested, it \fIcan\fR and \fIdoes\fR guarantee that the requested
+delay has actually elapsed, or in other words, it always errs on the \*(L"too
+late\*(R" side of things.
+.PP
+\fIThe special problem of time updates\fR
+.IX Subsection "The special problem of time updates"
+.PP
+Establishing the current time is a costly operation (it usually takes
+at least one system call): \s-1EV\s0 therefore updates its idea of the current
+time only before and after \f(CW\*(C`ev_run\*(C'\fR collects new events, which causes a
+growing difference between \f(CW\*(C`ev_now ()\*(C'\fR and \f(CW\*(C`ev_time ()\*(C'\fR when handling
+lots of events in one iteration.
+.PP
+The relative timeouts are calculated relative to the \f(CW\*(C`ev_now ()\*(C'\fR
+time. This is usually the right thing as this timestamp refers to the time
+of the event triggering whatever timeout you are modifying/starting. If
+you suspect event processing to be delayed and you \fIneed\fR to base the
+timeout on the current time, use something like the following to adjust
+for it:
+.PP
+.Vb 1
+\& ev_timer_set (&timer, after + (ev_time () \- ev_now ()), 0.);
+.Ve
+.PP
+If the event loop is suspended for a long time, you can also force an
+update of the time returned by \f(CW\*(C`ev_now ()\*(C'\fR by calling \f(CW\*(C`ev_now_update
+()\*(C'\fR, although that will push the event time of all outstanding events
+further into the future.
+.PP
+\fIThe special problem of unsynchronised clocks\fR
+.IX Subsection "The special problem of unsynchronised clocks"
+.PP
+Modern systems have a variety of clocks \- libev itself uses the normal
+\&\*(L"wall clock\*(R" clock and, if available, the monotonic clock (to avoid time
+jumps).
+.PP
+Neither of these clocks is synchronised with each other or any other clock
+on the system, so \f(CW\*(C`ev_time ()\*(C'\fR might return a considerably different time
+than \f(CW\*(C`gettimeofday ()\*(C'\fR or \f(CW\*(C`time ()\*(C'\fR. On a GNU/Linux system, for example,
+a call to \f(CW\*(C`gettimeofday\*(C'\fR might return a second count that is one higher
+than a directly following call to \f(CW\*(C`time\*(C'\fR.
+.PP
+The moral of this is to only compare libev-related timestamps with
+\&\f(CW\*(C`ev_time ()\*(C'\fR and \f(CW\*(C`ev_now ()\*(C'\fR, at least if you want better precision than
+a second or so.
+.PP
+One more problem arises due to this lack of synchronisation: if libev uses
+the system monotonic clock and you compare timestamps from \f(CW\*(C`ev_time\*(C'\fR
+or \f(CW\*(C`ev_now\*(C'\fR from when you started your timer and when your callback is
+invoked, you will find that sometimes the callback is a bit \*(L"early\*(R".
+.PP
+This is because \f(CW\*(C`ev_timer\*(C'\fRs work in real time, not wall clock time, so
+libev makes sure your callback is not invoked before the delay happened,
+\&\fImeasured according to the real time\fR, not the system clock.
+.PP
+If your timeouts are based on a physical timescale (e.g. \*(L"time out this
+connection after 100 seconds\*(R") then this shouldn't bother you as it is
+exactly the right behaviour.
+.PP
+If you want to compare wall clock/system timestamps to your timers, then
+you need to use \f(CW\*(C`ev_periodic\*(C'\fRs, as these are based on the wall clock
+time, where your comparisons will always generate correct results.
+.PP
+\fIThe special problems of suspended animation\fR
+.IX Subsection "The special problems of suspended animation"
+.PP
+When you leave the server world it is quite customary to hit machines that
+can suspend/hibernate \- what happens to the clocks during such a suspend?
+.PP
+Some quick tests made with a Linux 2.6.28 indicate that a suspend freezes
+all processes, while the clocks (\f(CW\*(C`times\*(C'\fR, \f(CW\*(C`CLOCK_MONOTONIC\*(C'\fR) continue
+to run until the system is suspended, but they will not advance while the
+system is suspended. That means, on resume, it will be as if the program
+was frozen for a few seconds, but the suspend time will not be counted
+towards \f(CW\*(C`ev_timer\*(C'\fR when a monotonic clock source is used. The real time
+clock advanced as expected, but if it is used as sole clocksource, then a
+long suspend would be detected as a time jump by libev, and timers would
+be adjusted accordingly.
+.PP
+I would not be surprised to see different behaviour in different between
+operating systems, \s-1OS\s0 versions or even different hardware.
+.PP
+The other form of suspend (job control, or sending a \s-1SIGSTOP\s0) will see a
+time jump in the monotonic clocks and the realtime clock. If the program
+is suspended for a very long time, and monotonic clock sources are in use,
+then you can expect \f(CW\*(C`ev_timer\*(C'\fRs to expire as the full suspension time
+will be counted towards the timers. When no monotonic clock source is in
+use, then libev will again assume a timejump and adjust accordingly.
+.PP
+It might be beneficial for this latter case to call \f(CW\*(C`ev_suspend\*(C'\fR
+and \f(CW\*(C`ev_resume\*(C'\fR in code that handles \f(CW\*(C`SIGTSTP\*(C'\fR, to at least get
+deterministic behaviour in this case (you can do nothing against
+\&\f(CW\*(C`SIGSTOP\*(C'\fR).
+.PP
+\fIWatcher-Specific Functions and Data Members\fR
+.IX Subsection "Watcher-Specific Functions and Data Members"
+.IP "ev_timer_init (ev_timer *, callback, ev_tstamp after, ev_tstamp repeat)" 4
+.IX Item "ev_timer_init (ev_timer *, callback, ev_tstamp after, ev_tstamp repeat)"
+.PD 0
+.IP "ev_timer_set (ev_timer *, ev_tstamp after, ev_tstamp repeat)" 4
+.IX Item "ev_timer_set (ev_timer *, ev_tstamp after, ev_tstamp repeat)"
+.PD
+Configure the timer to trigger after \f(CW\*(C`after\*(C'\fR seconds (fractional and
+negative values are supported). If \f(CW\*(C`repeat\*(C'\fR is \f(CW0.\fR, then it will
+automatically be stopped once the timeout is reached. If it is positive,
+then the timer will automatically be configured to trigger again \f(CW\*(C`repeat\*(C'\fR
+seconds later, again, and again, until stopped manually.
+.Sp
+The timer itself will do a best-effort at avoiding drift, that is, if
+you configure a timer to trigger every 10 seconds, then it will normally
+trigger at exactly 10 second intervals. If, however, your program cannot
+keep up with the timer (because it takes longer than those 10 seconds to
+do stuff) the timer will not fire more than once per event loop iteration.
+.IP "ev_timer_again (loop, ev_timer *)" 4
+.IX Item "ev_timer_again (loop, ev_timer *)"
+This will act as if the timer timed out, and restarts it again if it is
+repeating. It basically works like calling \f(CW\*(C`ev_timer_stop\*(C'\fR, updating the
+timeout to the \f(CW\*(C`repeat\*(C'\fR value and calling \f(CW\*(C`ev_timer_start\*(C'\fR.
+.Sp
+The exact semantics are as in the following rules, all of which will be
+applied to the watcher:
+.RS 4
+.IP "If the timer is pending, the pending status is always cleared." 4
+.IX Item "If the timer is pending, the pending status is always cleared."
+.PD 0
+.IP "If the timer is started but non-repeating, stop it (as if it timed out, without invoking it)." 4
+.IX Item "If the timer is started but non-repeating, stop it (as if it timed out, without invoking it)."
+.ie n .IP "If the timer is repeating, make the ""repeat"" value the new timeout and start the timer, if necessary." 4
+.el .IP "If the timer is repeating, make the \f(CWrepeat\fR value the new timeout and start the timer, if necessary." 4
+.IX Item "If the timer is repeating, make the repeat value the new timeout and start the timer, if necessary."
+.RE
+.RS 4
+.PD
+.Sp
+This sounds a bit complicated, see \*(L"Be smart about timeouts\*(R", above, for a
+usage example.
+.RE
+.IP "ev_tstamp ev_timer_remaining (loop, ev_timer *)" 4
+.IX Item "ev_tstamp ev_timer_remaining (loop, ev_timer *)"
+Returns the remaining time until a timer fires. If the timer is active,
+then this time is relative to the current event loop time, otherwise it's
+the timeout value currently configured.
+.Sp
+That is, after an \f(CW\*(C`ev_timer_set (w, 5, 7)\*(C'\fR, \f(CW\*(C`ev_timer_remaining\*(C'\fR returns
+\&\f(CW5\fR. When the timer is started and one second passes, \f(CW\*(C`ev_timer_remaining\*(C'\fR
+will return \f(CW4\fR. When the timer expires and is restarted, it will return
+roughly \f(CW7\fR (likely slightly less as callback invocation takes some time,
+too), and so on.
+.IP "ev_tstamp repeat [read\-write]" 4
+.IX Item "ev_tstamp repeat [read-write]"
+The current \f(CW\*(C`repeat\*(C'\fR value. Will be used each time the watcher times out
+or \f(CW\*(C`ev_timer_again\*(C'\fR is called, and determines the next timeout (if any),
+which is also when any modifications are taken into account.
+.PP
+\fIExamples\fR
+.IX Subsection "Examples"
+.PP
+Example: Create a timer that fires after 60 seconds.
+.PP
+.Vb 5
+\& static void
+\& one_minute_cb (struct ev_loop *loop, ev_timer *w, int revents)
+\& {
+\& .. one minute over, w is actually stopped right here
+\& }
+\&
+\& ev_timer mytimer;
+\& ev_timer_init (&mytimer, one_minute_cb, 60., 0.);
+\& ev_timer_start (loop, &mytimer);
+.Ve
+.PP
+Example: Create a timeout timer that times out after 10 seconds of
+inactivity.
+.PP
+.Vb 5
+\& static void
+\& timeout_cb (struct ev_loop *loop, ev_timer *w, int revents)
+\& {
+\& .. ten seconds without any activity
+\& }
+\&
+\& ev_timer mytimer;
+\& ev_timer_init (&mytimer, timeout_cb, 0., 10.); /* note, only repeat used */
+\& ev_timer_again (&mytimer); /* start timer */
+\& ev_run (loop, 0);
+\&
+\& // and in some piece of code that gets executed on any "activity":
+\& // reset the timeout to start ticking again at 10 seconds
+\& ev_timer_again (&mytimer);
+.Ve
+.ie n .SS """ev_periodic"" \- to cron or not to cron?"
+.el .SS "\f(CWev_periodic\fP \- to cron or not to cron?"
+.IX Subsection "ev_periodic - to cron or not to cron?"
+Periodic watchers are also timers of a kind, but they are very versatile
+(and unfortunately a bit complex).
+.PP
+Unlike \f(CW\*(C`ev_timer\*(C'\fR, periodic watchers are not based on real time (or
+relative time, the physical time that passes) but on wall clock time
+(absolute time, the thing you can read on your calendar or clock). The
+difference is that wall clock time can run faster or slower than real
+time, and time jumps are not uncommon (e.g. when you adjust your
+wrist-watch).
+.PP
+You can tell a periodic watcher to trigger after some specific point
+in time: for example, if you tell a periodic watcher to trigger \*(L"in 10
+seconds\*(R" (by specifying e.g. \f(CW\*(C`ev_now () + 10.\*(C'\fR, that is, an absolute time
+not a delay) and then reset your system clock to January of the previous
+year, then it will take a year or more to trigger the event (unlike an
+\&\f(CW\*(C`ev_timer\*(C'\fR, which would still trigger roughly 10 seconds after starting
+it, as it uses a relative timeout).
+.PP
+\&\f(CW\*(C`ev_periodic\*(C'\fR watchers can also be used to implement vastly more complex
+timers, such as triggering an event on each \*(L"midnight, local time\*(R", or
+other complicated rules. This cannot easily be done with \f(CW\*(C`ev_timer\*(C'\fR
+watchers, as those cannot react to time jumps.
+.PP
+As with timers, the callback is guaranteed to be invoked only when the
+point in time where it is supposed to trigger has passed. If multiple
+timers become ready during the same loop iteration then the ones with
+earlier time-out values are invoked before ones with later time-out values
+(but this is no longer true when a callback calls \f(CW\*(C`ev_run\*(C'\fR recursively).
+.PP
+\fIWatcher-Specific Functions and Data Members\fR
+.IX Subsection "Watcher-Specific Functions and Data Members"
+.IP "ev_periodic_init (ev_periodic *, callback, ev_tstamp offset, ev_tstamp interval, reschedule_cb)" 4
+.IX Item "ev_periodic_init (ev_periodic *, callback, ev_tstamp offset, ev_tstamp interval, reschedule_cb)"
+.PD 0
+.IP "ev_periodic_set (ev_periodic *, ev_tstamp offset, ev_tstamp interval, reschedule_cb)" 4
+.IX Item "ev_periodic_set (ev_periodic *, ev_tstamp offset, ev_tstamp interval, reschedule_cb)"
+.PD
+Lots of arguments, let's sort it out... There are basically three modes of
+operation, and we will explain them from simplest to most complex:
+.RS 4
+.IP "\(bu" 4
+absolute timer (offset = absolute time, interval = 0, reschedule_cb = 0)
+.Sp
+In this configuration the watcher triggers an event after the wall clock
+time \f(CW\*(C`offset\*(C'\fR has passed. It will not repeat and will not adjust when a
+time jump occurs, that is, if it is to be run at January 1st 2011 then it
+will be stopped and invoked when the system clock reaches or surpasses
+this point in time.
+.IP "\(bu" 4
+repeating interval timer (offset = offset within interval, interval > 0, reschedule_cb = 0)
+.Sp
+In this mode the watcher will always be scheduled to time out at the next
+\&\f(CW\*(C`offset + N * interval\*(C'\fR time (for some integer N, which can also be
+negative) and then repeat, regardless of any time jumps. The \f(CW\*(C`offset\*(C'\fR
+argument is merely an offset into the \f(CW\*(C`interval\*(C'\fR periods.
+.Sp
+This can be used to create timers that do not drift with respect to the
+system clock, for example, here is an \f(CW\*(C`ev_periodic\*(C'\fR that triggers each
+hour, on the hour (with respect to \s-1UTC\s0):
+.Sp
+.Vb 1
+\& ev_periodic_set (&periodic, 0., 3600., 0);
+.Ve
+.Sp
+This doesn't mean there will always be 3600 seconds in between triggers,
+but only that the callback will be called when the system time shows a
+full hour (\s-1UTC\s0), or more correctly, when the system time is evenly divisible
+by 3600.
+.Sp
+Another way to think about it (for the mathematically inclined) is that
+\&\f(CW\*(C`ev_periodic\*(C'\fR will try to run the callback in this mode at the next possible
+time where \f(CW\*(C`time = offset (mod interval)\*(C'\fR, regardless of any time jumps.
+.Sp
+The \f(CW\*(C`interval\*(C'\fR \fI\s-1MUST\s0\fR be positive, and for numerical stability, the
+interval value should be higher than \f(CW\*(C`1/8192\*(C'\fR (which is around 100
+microseconds) and \f(CW\*(C`offset\*(C'\fR should be higher than \f(CW0\fR and should have
+at most a similar magnitude as the current time (say, within a factor of
+ten). Typical values for offset are, in fact, \f(CW0\fR or something between
+\&\f(CW0\fR and \f(CW\*(C`interval\*(C'\fR, which is also the recommended range.
+.Sp
+Note also that there is an upper limit to how often a timer can fire (\s-1CPU\s0
+speed for example), so if \f(CW\*(C`interval\*(C'\fR is very small then timing stability
+will of course deteriorate. Libev itself tries to be exact to be about one
+millisecond (if the \s-1OS\s0 supports it and the machine is fast enough).
+.IP "\(bu" 4
+manual reschedule mode (offset ignored, interval ignored, reschedule_cb = callback)
+.Sp
+In this mode the values for \f(CW\*(C`interval\*(C'\fR and \f(CW\*(C`offset\*(C'\fR are both being
+ignored. Instead, each time the periodic watcher gets scheduled, the
+reschedule callback will be called with the watcher as first, and the
+current time as second argument.
+.Sp
+\&\s-1NOTE:\s0 \fIThis callback \s-1MUST NOT\s0 stop or destroy any periodic watcher, ever,
+or make \s-1ANY\s0 other event loop modifications whatsoever, unless explicitly
+allowed by documentation here\fR.
+.Sp
+If you need to stop it, return \f(CW\*(C`now + 1e30\*(C'\fR (or so, fudge fudge) and stop
+it afterwards (e.g. by starting an \f(CW\*(C`ev_prepare\*(C'\fR watcher, which is the
+only event loop modification you are allowed to do).
+.Sp
+The callback prototype is \f(CW\*(C`ev_tstamp (*reschedule_cb)(ev_periodic
+*w, ev_tstamp now)\*(C'\fR, e.g.:
+.Sp
+.Vb 5
+\& static ev_tstamp
+\& my_rescheduler (ev_periodic *w, ev_tstamp now)
+\& {
+\& return now + 60.;
+\& }
+.Ve
+.Sp
+It must return the next time to trigger, based on the passed time value
+(that is, the lowest time value larger than to the second argument). It
+will usually be called just before the callback will be triggered, but
+might be called at other times, too.
+.Sp
+\&\s-1NOTE:\s0 \fIThis callback must always return a time that is higher than or
+equal to the passed \f(CI\*(C`now\*(C'\fI value\fR.
+.Sp
+This can be used to create very complex timers, such as a timer that
+triggers on \*(L"next midnight, local time\*(R". To do this, you would calculate
+the next midnight after \f(CW\*(C`now\*(C'\fR and return the timestamp value for
+this. Here is a (completely untested, no error checking) example on how to
+do this:
+.Sp
+.Vb 1
+\& #include <time.h>
+\&
+\& static ev_tstamp
+\& my_rescheduler (ev_periodic *w, ev_tstamp now)
+\& {
+\& time_t tnow = (time_t)now;
+\& struct tm tm;
+\& localtime_r (&tnow, &tm);
+\&
+\& tm.tm_sec = tm.tm_min = tm.tm_hour = 0; // midnight current day
+\& ++tm.tm_mday; // midnight next day
+\&
+\& return mktime (&tm);
+\& }
+.Ve
+.Sp
+Note: this code might run into trouble on days that have more then two
+midnights (beginning and end).
+.RE
+.RS 4
+.RE
+.IP "ev_periodic_again (loop, ev_periodic *)" 4
+.IX Item "ev_periodic_again (loop, ev_periodic *)"
+Simply stops and restarts the periodic watcher again. This is only useful
+when you changed some parameters or the reschedule callback would return
+a different time than the last time it was called (e.g. in a crond like
+program when the crontabs have changed).
+.IP "ev_tstamp ev_periodic_at (ev_periodic *)" 4
+.IX Item "ev_tstamp ev_periodic_at (ev_periodic *)"
+When active, returns the absolute time that the watcher is supposed
+to trigger next. This is not the same as the \f(CW\*(C`offset\*(C'\fR argument to
+\&\f(CW\*(C`ev_periodic_set\*(C'\fR, but indeed works even in interval and manual
+rescheduling modes.
+.IP "ev_tstamp offset [read\-write]" 4
+.IX Item "ev_tstamp offset [read-write]"
+When repeating, this contains the offset value, otherwise this is the
+absolute point in time (the \f(CW\*(C`offset\*(C'\fR value passed to \f(CW\*(C`ev_periodic_set\*(C'\fR,
+although libev might modify this value for better numerical stability).
+.Sp
+Can be modified any time, but changes only take effect when the periodic
+timer fires or \f(CW\*(C`ev_periodic_again\*(C'\fR is being called.
+.IP "ev_tstamp interval [read\-write]" 4
+.IX Item "ev_tstamp interval [read-write]"
+The current interval value. Can be modified any time, but changes only
+take effect when the periodic timer fires or \f(CW\*(C`ev_periodic_again\*(C'\fR is being
+called.
+.IP "ev_tstamp (*reschedule_cb)(ev_periodic *w, ev_tstamp now) [read\-write]" 4
+.IX Item "ev_tstamp (*reschedule_cb)(ev_periodic *w, ev_tstamp now) [read-write]"
+The current reschedule callback, or \f(CW0\fR, if this functionality is
+switched off. Can be changed any time, but changes only take effect when
+the periodic timer fires or \f(CW\*(C`ev_periodic_again\*(C'\fR is being called.
+.PP
+\fIExamples\fR
+.IX Subsection "Examples"
+.PP
+Example: Call a callback every hour, or, more precisely, whenever the
+system time is divisible by 3600. The callback invocation times have
+potentially a lot of jitter, but good long-term stability.
+.PP
+.Vb 5
+\& static void
+\& clock_cb (struct ev_loop *loop, ev_periodic *w, int revents)
+\& {
+\& ... its now a full hour (UTC, or TAI or whatever your clock follows)
+\& }
+\&
+\& ev_periodic hourly_tick;
+\& ev_periodic_init (&hourly_tick, clock_cb, 0., 3600., 0);
+\& ev_periodic_start (loop, &hourly_tick);
+.Ve
+.PP
+Example: The same as above, but use a reschedule callback to do it:
+.PP
+.Vb 1
+\& #include <math.h>
+\&
+\& static ev_tstamp
+\& my_scheduler_cb (ev_periodic *w, ev_tstamp now)
+\& {
+\& return now + (3600. \- fmod (now, 3600.));
+\& }
+\&
+\& ev_periodic_init (&hourly_tick, clock_cb, 0., 0., my_scheduler_cb);
+.Ve
+.PP
+Example: Call a callback every hour, starting now:
+.PP
+.Vb 4
+\& ev_periodic hourly_tick;
+\& ev_periodic_init (&hourly_tick, clock_cb,
+\& fmod (ev_now (loop), 3600.), 3600., 0);
+\& ev_periodic_start (loop, &hourly_tick);
+.Ve
+.ie n .SS """ev_signal"" \- signal me when a signal gets signalled!"
+.el .SS "\f(CWev_signal\fP \- signal me when a signal gets signalled!"
+.IX Subsection "ev_signal - signal me when a signal gets signalled!"
+Signal watchers will trigger an event when the process receives a specific
+signal one or more times. Even though signals are very asynchronous, libev
+will try its best to deliver signals synchronously, i.e. as part of the
+normal event processing, like any other event.
+.PP
+If you want signals to be delivered truly asynchronously, just use
+\&\f(CW\*(C`sigaction\*(C'\fR as you would do without libev and forget about sharing
+the signal. You can even use \f(CW\*(C`ev_async\*(C'\fR from a signal handler to
+synchronously wake up an event loop.
+.PP
+You can configure as many watchers as you like for the same signal, but
+only within the same loop, i.e. you can watch for \f(CW\*(C`SIGINT\*(C'\fR in your
+default loop and for \f(CW\*(C`SIGIO\*(C'\fR in another loop, but you cannot watch for
+\&\f(CW\*(C`SIGINT\*(C'\fR in both the default loop and another loop at the same time. At
+the moment, \f(CW\*(C`SIGCHLD\*(C'\fR is permanently tied to the default loop.
+.PP
+Only after the first watcher for a signal is started will libev actually
+register something with the kernel. It thus coexists with your own signal
+handlers as long as you don't register any with libev for the same signal.
+.PP
+If possible and supported, libev will install its handlers with
+\&\f(CW\*(C`SA_RESTART\*(C'\fR (or equivalent) behaviour enabled, so system calls should
+not be unduly interrupted. If you have a problem with system calls getting
+interrupted by signals you can block all signals in an \f(CW\*(C`ev_check\*(C'\fR watcher
+and unblock them in an \f(CW\*(C`ev_prepare\*(C'\fR watcher.
+.PP
+\fIThe special problem of inheritance over fork/execve/pthread_create\fR
+.IX Subsection "The special problem of inheritance over fork/execve/pthread_create"
+.PP
+Both the signal mask (\f(CW\*(C`sigprocmask\*(C'\fR) and the signal disposition
+(\f(CW\*(C`sigaction\*(C'\fR) are unspecified after starting a signal watcher (and after
+stopping it again), that is, libev might or might not block the signal,
+and might or might not set or restore the installed signal handler (but
+see \f(CW\*(C`EVFLAG_NOSIGMASK\*(C'\fR).
+.PP
+While this does not matter for the signal disposition (libev never
+sets signals to \f(CW\*(C`SIG_IGN\*(C'\fR, so handlers will be reset to \f(CW\*(C`SIG_DFL\*(C'\fR on
+\&\f(CW\*(C`execve\*(C'\fR), this matters for the signal mask: many programs do not expect
+certain signals to be blocked.
+.PP
+This means that before calling \f(CW\*(C`exec\*(C'\fR (from the child) you should reset
+the signal mask to whatever \*(L"default\*(R" you expect (all clear is a good
+choice usually).
+.PP
+The simplest way to ensure that the signal mask is reset in the child is
+to install a fork handler with \f(CW\*(C`pthread_atfork\*(C'\fR that resets it. That will
+catch fork calls done by libraries (such as the libc) as well.
+.PP
+In current versions of libev, the signal will not be blocked indefinitely
+unless you use the \f(CW\*(C`signalfd\*(C'\fR \s-1API\s0 (\f(CW\*(C`EV_SIGNALFD\*(C'\fR). While this reduces
+the window of opportunity for problems, it will not go away, as libev
+\&\fIhas\fR to modify the signal mask, at least temporarily.
+.PP
+So I can't stress this enough: \fIIf you do not reset your signal mask when
+you expect it to be empty, you have a race condition in your code\fR. This
+is not a libev-specific thing, this is true for most event libraries.
+.PP
+\fIThe special problem of threads signal handling\fR
+.IX Subsection "The special problem of threads signal handling"
+.PP
+\&\s-1POSIX\s0 threads has problematic signal handling semantics, specifically,
+a lot of functionality (sigfd, sigwait etc.) only really works if all
+threads in a process block signals, which is hard to achieve.
+.PP
+When you want to use sigwait (or mix libev signal handling with your own
+for the same signals), you can tackle this problem by globally blocking
+all signals before creating any threads (or creating them with a fully set
+sigprocmask) and also specifying the \f(CW\*(C`EVFLAG_NOSIGMASK\*(C'\fR when creating
+loops. Then designate one thread as \*(L"signal receiver thread\*(R" which handles
+these signals. You can pass on any signals that libev might be interested
+in by calling \f(CW\*(C`ev_feed_signal\*(C'\fR.
+.PP
+\fIWatcher-Specific Functions and Data Members\fR
+.IX Subsection "Watcher-Specific Functions and Data Members"
+.IP "ev_signal_init (ev_signal *, callback, int signum)" 4
+.IX Item "ev_signal_init (ev_signal *, callback, int signum)"
+.PD 0
+.IP "ev_signal_set (ev_signal *, int signum)" 4
+.IX Item "ev_signal_set (ev_signal *, int signum)"
+.PD
+Configures the watcher to trigger on the given signal number (usually one
+of the \f(CW\*(C`SIGxxx\*(C'\fR constants).
+.IP "int signum [read\-only]" 4
+.IX Item "int signum [read-only]"
+The signal the watcher watches out for.
+.PP
+\fIExamples\fR
+.IX Subsection "Examples"
+.PP
+Example: Try to exit cleanly on \s-1SIGINT.\s0
+.PP
+.Vb 5
+\& static void
+\& sigint_cb (struct ev_loop *loop, ev_signal *w, int revents)
+\& {
+\& ev_break (loop, EVBREAK_ALL);
+\& }
+\&
+\& ev_signal signal_watcher;
+\& ev_signal_init (&signal_watcher, sigint_cb, SIGINT);
+\& ev_signal_start (loop, &signal_watcher);
+.Ve
+.ie n .SS """ev_child"" \- watch out for process status changes"
+.el .SS "\f(CWev_child\fP \- watch out for process status changes"
+.IX Subsection "ev_child - watch out for process status changes"
+Child watchers trigger when your process receives a \s-1SIGCHLD\s0 in response to
+some child status changes (most typically when a child of yours dies or
+exits). It is permissible to install a child watcher \fIafter\fR the child
+has been forked (which implies it might have already exited), as long
+as the event loop isn't entered (or is continued from a watcher), i.e.,
+forking and then immediately registering a watcher for the child is fine,
+but forking and registering a watcher a few event loop iterations later or
+in the next callback invocation is not.
+.PP
+Only the default event loop is capable of handling signals, and therefore
+you can only register child watchers in the default event loop.
+.PP
+Due to some design glitches inside libev, child watchers will always be
+handled at maximum priority (their priority is set to \f(CW\*(C`EV_MAXPRI\*(C'\fR by
+libev)
+.PP
+\fIProcess Interaction\fR
+.IX Subsection "Process Interaction"
+.PP
+Libev grabs \f(CW\*(C`SIGCHLD\*(C'\fR as soon as the default event loop is
+initialised. This is necessary to guarantee proper behaviour even if the
+first child watcher is started after the child exits. The occurrence
+of \f(CW\*(C`SIGCHLD\*(C'\fR is recorded asynchronously, but child reaping is done
+synchronously as part of the event loop processing. Libev always reaps all
+children, even ones not watched.
+.PP
+\fIOverriding the Built-In Processing\fR
+.IX Subsection "Overriding the Built-In Processing"
+.PP
+Libev offers no special support for overriding the built-in child
+processing, but if your application collides with libev's default child
+handler, you can override it easily by installing your own handler for
+\&\f(CW\*(C`SIGCHLD\*(C'\fR after initialising the default loop, and making sure the
+default loop never gets destroyed. You are encouraged, however, to use an
+event-based approach to child reaping and thus use libev's support for
+that, so other libev users can use \f(CW\*(C`ev_child\*(C'\fR watchers freely.
+.PP
+\fIStopping the Child Watcher\fR
+.IX Subsection "Stopping the Child Watcher"
+.PP
+Currently, the child watcher never gets stopped, even when the
+child terminates, so normally one needs to stop the watcher in the
+callback. Future versions of libev might stop the watcher automatically
+when a child exit is detected (calling \f(CW\*(C`ev_child_stop\*(C'\fR twice is not a
+problem).
+.PP
+\fIWatcher-Specific Functions and Data Members\fR
+.IX Subsection "Watcher-Specific Functions and Data Members"
+.IP "ev_child_init (ev_child *, callback, int pid, int trace)" 4
+.IX Item "ev_child_init (ev_child *, callback, int pid, int trace)"
+.PD 0
+.IP "ev_child_set (ev_child *, int pid, int trace)" 4
+.IX Item "ev_child_set (ev_child *, int pid, int trace)"
+.PD
+Configures the watcher to wait for status changes of process \f(CW\*(C`pid\*(C'\fR (or
+\&\fIany\fR process if \f(CW\*(C`pid\*(C'\fR is specified as \f(CW0\fR). The callback can look
+at the \f(CW\*(C`rstatus\*(C'\fR member of the \f(CW\*(C`ev_child\*(C'\fR watcher structure to see
+the status word (use the macros from \f(CW\*(C`sys/wait.h\*(C'\fR and see your systems
+\&\f(CW\*(C`waitpid\*(C'\fR documentation). The \f(CW\*(C`rpid\*(C'\fR member contains the pid of the
+process causing the status change. \f(CW\*(C`trace\*(C'\fR must be either \f(CW0\fR (only
+activate the watcher when the process terminates) or \f(CW1\fR (additionally
+activate the watcher when the process is stopped or continued).
+.IP "int pid [read\-only]" 4
+.IX Item "int pid [read-only]"
+The process id this watcher watches out for, or \f(CW0\fR, meaning any process id.
+.IP "int rpid [read\-write]" 4
+.IX Item "int rpid [read-write]"
+The process id that detected a status change.
+.IP "int rstatus [read\-write]" 4
+.IX Item "int rstatus [read-write]"
+The process exit/trace status caused by \f(CW\*(C`rpid\*(C'\fR (see your systems
+\&\f(CW\*(C`waitpid\*(C'\fR and \f(CW\*(C`sys/wait.h\*(C'\fR documentation for details).
+.PP
+\fIExamples\fR
+.IX Subsection "Examples"
+.PP
+Example: \f(CW\*(C`fork()\*(C'\fR a new process and install a child handler to wait for
+its completion.
+.PP
+.Vb 1
+\& ev_child cw;
+\&
+\& static void
+\& child_cb (EV_P_ ev_child *w, int revents)
+\& {
+\& ev_child_stop (EV_A_ w);
+\& printf ("process %d exited with status %x\en", w\->rpid, w\->rstatus);
+\& }
+\&
+\& pid_t pid = fork ();
+\&
+\& if (pid < 0)
+\& // error
+\& else if (pid == 0)
+\& {
+\& // the forked child executes here
+\& exit (1);
+\& }
+\& else
+\& {
+\& ev_child_init (&cw, child_cb, pid, 0);
+\& ev_child_start (EV_DEFAULT_ &cw);
+\& }
+.Ve
+.ie n .SS """ev_stat"" \- did the file attributes just change?"
+.el .SS "\f(CWev_stat\fP \- did the file attributes just change?"
+.IX Subsection "ev_stat - did the file attributes just change?"
+This watches a file system path for attribute changes. That is, it calls
+\&\f(CW\*(C`stat\*(C'\fR on that path in regular intervals (or when the \s-1OS\s0 says it changed)
+and sees if it changed compared to the last time, invoking the callback
+if it did. Starting the watcher \f(CW\*(C`stat\*(C'\fR's the file, so only changes that
+happen after the watcher has been started will be reported.
+.PP
+The path does not need to exist: changing from \*(L"path exists\*(R" to \*(L"path does
+not exist\*(R" is a status change like any other. The condition \*(L"path does not
+exist\*(R" (or more correctly \*(L"path cannot be stat'ed\*(R") is signified by the
+\&\f(CW\*(C`st_nlink\*(C'\fR field being zero (which is otherwise always forced to be at
+least one) and all the other fields of the stat buffer having unspecified
+contents.
+.PP
+The path \fImust not\fR end in a slash or contain special components such as
+\&\f(CW\*(C`.\*(C'\fR or \f(CW\*(C`..\*(C'\fR. The path \fIshould\fR be absolute: If it is relative and
+your working directory changes, then the behaviour is undefined.
+.PP
+Since there is no portable change notification interface available, the
+portable implementation simply calls \f(CWstat(2)\fR regularly on the path
+to see if it changed somehow. You can specify a recommended polling
+interval for this case. If you specify a polling interval of \f(CW0\fR (highly
+recommended!) then a \fIsuitable, unspecified default\fR value will be used
+(which you can expect to be around five seconds, although this might
+change dynamically). Libev will also impose a minimum interval which is
+currently around \f(CW0.1\fR, but that's usually overkill.
+.PP
+This watcher type is not meant for massive numbers of stat watchers,
+as even with OS-supported change notifications, this can be
+resource-intensive.
+.PP
+At the time of this writing, the only OS-specific interface implemented
+is the Linux inotify interface (implementing kqueue support is left as an
+exercise for the reader. Note, however, that the author sees no way of
+implementing \f(CW\*(C`ev_stat\*(C'\fR semantics with kqueue, except as a hint).
+.PP
+\fI\s-1ABI\s0 Issues (Largefile Support)\fR
+.IX Subsection "ABI Issues (Largefile Support)"
+.PP
+Libev by default (unless the user overrides this) uses the default
+compilation environment, which means that on systems with large file
+support disabled by default, you get the 32 bit version of the stat
+structure. When using the library from programs that change the \s-1ABI\s0 to
+use 64 bit file offsets the programs will fail. In that case you have to
+compile libev with the same flags to get binary compatibility. This is
+obviously the case with any flags that change the \s-1ABI,\s0 but the problem is
+most noticeably displayed with ev_stat and large file support.
+.PP
+The solution for this is to lobby your distribution maker to make large
+file interfaces available by default (as e.g. FreeBSD does) and not
+optional. Libev cannot simply switch on large file support because it has
+to exchange stat structures with application programs compiled using the
+default compilation environment.
+.PP
+\fIInotify and Kqueue\fR
+.IX Subsection "Inotify and Kqueue"
+.PP
+When \f(CW\*(C`inotify (7)\*(C'\fR support has been compiled into libev and present at
+runtime, it will be used to speed up change detection where possible. The
+inotify descriptor will be created lazily when the first \f(CW\*(C`ev_stat\*(C'\fR
+watcher is being started.
+.PP
+Inotify presence does not change the semantics of \f(CW\*(C`ev_stat\*(C'\fR watchers
+except that changes might be detected earlier, and in some cases, to avoid
+making regular \f(CW\*(C`stat\*(C'\fR calls. Even in the presence of inotify support
+there are many cases where libev has to resort to regular \f(CW\*(C`stat\*(C'\fR polling,
+but as long as kernel 2.6.25 or newer is used (2.6.24 and older have too
+many bugs), the path exists (i.e. stat succeeds), and the path resides on
+a local filesystem (libev currently assumes only ext2/3, jfs, reiserfs and
+xfs are fully working) libev usually gets away without polling.
+.PP
+There is no support for kqueue, as apparently it cannot be used to
+implement this functionality, due to the requirement of having a file
+descriptor open on the object at all times, and detecting renames, unlinks
+etc. is difficult.
+.PP
+\fI\f(CI\*(C`stat ()\*(C'\fI is a synchronous operation\fR
+.IX Subsection "stat () is a synchronous operation"
+.PP
+Libev doesn't normally do any kind of I/O itself, and so is not blocking
+the process. The exception are \f(CW\*(C`ev_stat\*(C'\fR watchers \- those call \f(CW\*(C`stat
+()\*(C'\fR, which is a synchronous operation.
+.PP
+For local paths, this usually doesn't matter: unless the system is very
+busy or the intervals between stat's are large, a stat call will be fast,
+as the path data is usually in memory already (except when starting the
+watcher).
+.PP
+For networked file systems, calling \f(CW\*(C`stat ()\*(C'\fR can block an indefinite
+time due to network issues, and even under good conditions, a stat call
+often takes multiple milliseconds.
+.PP
+Therefore, it is best to avoid using \f(CW\*(C`ev_stat\*(C'\fR watchers on networked
+paths, although this is fully supported by libev.
+.PP
+\fIThe special problem of stat time resolution\fR
+.IX Subsection "The special problem of stat time resolution"
+.PP
+The \f(CW\*(C`stat ()\*(C'\fR system call only supports full-second resolution portably,
+and even on systems where the resolution is higher, most file systems
+still only support whole seconds.
+.PP
+That means that, if the time is the only thing that changes, you can
+easily miss updates: on the first update, \f(CW\*(C`ev_stat\*(C'\fR detects a change and
+calls your callback, which does something. When there is another update
+within the same second, \f(CW\*(C`ev_stat\*(C'\fR will be unable to detect unless the
+stat data does change in other ways (e.g. file size).
+.PP
+The solution to this is to delay acting on a change for slightly more
+than a second (or till slightly after the next full second boundary), using
+a roughly one-second-delay \f(CW\*(C`ev_timer\*(C'\fR (e.g. \f(CW\*(C`ev_timer_set (w, 0., 1.02);
+ev_timer_again (loop, w)\*(C'\fR).
+.PP
+The \f(CW.02\fR offset is added to work around small timing inconsistencies
+of some operating systems (where the second counter of the current time
+might be be delayed. One such system is the Linux kernel, where a call to
+\&\f(CW\*(C`gettimeofday\*(C'\fR might return a timestamp with a full second later than
+a subsequent \f(CW\*(C`time\*(C'\fR call \- if the equivalent of \f(CW\*(C`time ()\*(C'\fR is used to
+update file times then there will be a small window where the kernel uses
+the previous second to update file times but libev might already execute
+the timer callback).
+.PP
+\fIWatcher-Specific Functions and Data Members\fR
+.IX Subsection "Watcher-Specific Functions and Data Members"
+.IP "ev_stat_init (ev_stat *, callback, const char *path, ev_tstamp interval)" 4
+.IX Item "ev_stat_init (ev_stat *, callback, const char *path, ev_tstamp interval)"
+.PD 0
+.IP "ev_stat_set (ev_stat *, const char *path, ev_tstamp interval)" 4
+.IX Item "ev_stat_set (ev_stat *, const char *path, ev_tstamp interval)"
+.PD
+Configures the watcher to wait for status changes of the given
+\&\f(CW\*(C`path\*(C'\fR. The \f(CW\*(C`interval\*(C'\fR is a hint on how quickly a change is expected to
+be detected and should normally be specified as \f(CW0\fR to let libev choose
+a suitable value. The memory pointed to by \f(CW\*(C`path\*(C'\fR must point to the same
+path for as long as the watcher is active.
+.Sp
+The callback will receive an \f(CW\*(C`EV_STAT\*(C'\fR event when a change was detected,
+relative to the attributes at the time the watcher was started (or the
+last change was detected).
+.IP "ev_stat_stat (loop, ev_stat *)" 4
+.IX Item "ev_stat_stat (loop, ev_stat *)"
+Updates the stat buffer immediately with new values. If you change the
+watched path in your callback, you could call this function to avoid
+detecting this change (while introducing a race condition if you are not
+the only one changing the path). Can also be useful simply to find out the
+new values.
+.IP "ev_statdata attr [read\-only]" 4
+.IX Item "ev_statdata attr [read-only]"
+The most-recently detected attributes of the file. Although the type is
+\&\f(CW\*(C`ev_statdata\*(C'\fR, this is usually the (or one of the) \f(CW\*(C`struct stat\*(C'\fR types
+suitable for your system, but you can only rely on the POSIX-standardised
+members to be present. If the \f(CW\*(C`st_nlink\*(C'\fR member is \f(CW0\fR, then there was
+some error while \f(CW\*(C`stat\*(C'\fRing the file.
+.IP "ev_statdata prev [read\-only]" 4
+.IX Item "ev_statdata prev [read-only]"
+The previous attributes of the file. The callback gets invoked whenever
+\&\f(CW\*(C`prev\*(C'\fR != \f(CW\*(C`attr\*(C'\fR, or, more precisely, one or more of these members
+differ: \f(CW\*(C`st_dev\*(C'\fR, \f(CW\*(C`st_ino\*(C'\fR, \f(CW\*(C`st_mode\*(C'\fR, \f(CW\*(C`st_nlink\*(C'\fR, \f(CW\*(C`st_uid\*(C'\fR,
+\&\f(CW\*(C`st_gid\*(C'\fR, \f(CW\*(C`st_rdev\*(C'\fR, \f(CW\*(C`st_size\*(C'\fR, \f(CW\*(C`st_atime\*(C'\fR, \f(CW\*(C`st_mtime\*(C'\fR, \f(CW\*(C`st_ctime\*(C'\fR.
+.IP "ev_tstamp interval [read\-only]" 4
+.IX Item "ev_tstamp interval [read-only]"
+The specified interval.
+.IP "const char *path [read\-only]" 4
+.IX Item "const char *path [read-only]"
+The file system path that is being watched.
+.PP
+\fIExamples\fR
+.IX Subsection "Examples"
+.PP
+Example: Watch \f(CW\*(C`/etc/passwd\*(C'\fR for attribute changes.
+.PP
+.Vb 10
+\& static void
+\& passwd_cb (struct ev_loop *loop, ev_stat *w, int revents)
+\& {
+\& /* /etc/passwd changed in some way */
+\& if (w\->attr.st_nlink)
+\& {
+\& printf ("passwd current size %ld\en", (long)w\->attr.st_size);
+\& printf ("passwd current atime %ld\en", (long)w\->attr.st_mtime);
+\& printf ("passwd current mtime %ld\en", (long)w\->attr.st_mtime);
+\& }
+\& else
+\& /* you shalt not abuse printf for puts */
+\& puts ("wow, /etc/passwd is not there, expect problems. "
+\& "if this is windows, they already arrived\en");
+\& }
+\&
+\& ...
+\& ev_stat passwd;
+\&
+\& ev_stat_init (&passwd, passwd_cb, "/etc/passwd", 0.);
+\& ev_stat_start (loop, &passwd);
+.Ve
+.PP
+Example: Like above, but additionally use a one-second delay so we do not
+miss updates (however, frequent updates will delay processing, too, so
+one might do the work both on \f(CW\*(C`ev_stat\*(C'\fR callback invocation \fIand\fR on
+\&\f(CW\*(C`ev_timer\*(C'\fR callback invocation).
+.PP
+.Vb 2
+\& static ev_stat passwd;
+\& static ev_timer timer;
+\&
+\& static void
+\& timer_cb (EV_P_ ev_timer *w, int revents)
+\& {
+\& ev_timer_stop (EV_A_ w);
+\&
+\& /* now it\*(Aqs one second after the most recent passwd change */
+\& }
+\&
+\& static void
+\& stat_cb (EV_P_ ev_stat *w, int revents)
+\& {
+\& /* reset the one\-second timer */
+\& ev_timer_again (EV_A_ &timer);
+\& }
+\&
+\& ...
+\& ev_stat_init (&passwd, stat_cb, "/etc/passwd", 0.);
+\& ev_stat_start (loop, &passwd);
+\& ev_timer_init (&timer, timer_cb, 0., 1.02);
+.Ve
+.ie n .SS """ev_idle"" \- when you've got nothing better to do..."
+.el .SS "\f(CWev_idle\fP \- when you've got nothing better to do..."
+.IX Subsection "ev_idle - when you've got nothing better to do..."
+Idle watchers trigger events when no other events of the same or higher
+priority are pending (prepare, check and other idle watchers do not count
+as receiving \*(L"events\*(R").
+.PP
+That is, as long as your process is busy handling sockets or timeouts
+(or even signals, imagine) of the same or higher priority it will not be
+triggered. But when your process is idle (or only lower-priority watchers
+are pending), the idle watchers are being called once per event loop
+iteration \- until stopped, that is, or your process receives more events
+and becomes busy again with higher priority stuff.
+.PP
+The most noteworthy effect is that as long as any idle watchers are
+active, the process will not block when waiting for new events.
+.PP
+Apart from keeping your process non-blocking (which is a useful
+effect on its own sometimes), idle watchers are a good place to do
+\&\*(L"pseudo-background processing\*(R", or delay processing stuff to after the
+event loop has handled all outstanding events.
+.PP
+\fIAbusing an \f(CI\*(C`ev_idle\*(C'\fI watcher for its side-effect\fR
+.IX Subsection "Abusing an ev_idle watcher for its side-effect"
+.PP
+As long as there is at least one active idle watcher, libev will never
+sleep unnecessarily. Or in other words, it will loop as fast as possible.
+For this to work, the idle watcher doesn't need to be invoked at all \- the
+lowest priority will do.
+.PP
+This mode of operation can be useful together with an \f(CW\*(C`ev_check\*(C'\fR watcher,
+to do something on each event loop iteration \- for example to balance load
+between different connections.
+.PP
+See \*(L"Abusing an ev_check watcher for its side-effect\*(R" for a longer
+example.
+.PP
+\fIWatcher-Specific Functions and Data Members\fR
+.IX Subsection "Watcher-Specific Functions and Data Members"
+.IP "ev_idle_init (ev_idle *, callback)" 4
+.IX Item "ev_idle_init (ev_idle *, callback)"
+Initialises and configures the idle watcher \- it has no parameters of any
+kind. There is a \f(CW\*(C`ev_idle_set\*(C'\fR macro, but using it is utterly pointless,
+believe me.
+.PP
+\fIExamples\fR
+.IX Subsection "Examples"
+.PP
+Example: Dynamically allocate an \f(CW\*(C`ev_idle\*(C'\fR watcher, start it, and in the
+callback, free it. Also, use no error checking, as usual.
+.PP
+.Vb 5
+\& static void
+\& idle_cb (struct ev_loop *loop, ev_idle *w, int revents)
+\& {
+\& // stop the watcher
+\& ev_idle_stop (loop, w);
+\&
+\& // now we can free it
+\& free (w);
+\&
+\& // now do something you wanted to do when the program has
+\& // no longer anything immediate to do.
+\& }
+\&
+\& ev_idle *idle_watcher = malloc (sizeof (ev_idle));
+\& ev_idle_init (idle_watcher, idle_cb);
+\& ev_idle_start (loop, idle_watcher);
+.Ve
+.ie n .SS """ev_prepare"" and ""ev_check"" \- customise your event loop!"
+.el .SS "\f(CWev_prepare\fP and \f(CWev_check\fP \- customise your event loop!"
+.IX Subsection "ev_prepare and ev_check - customise your event loop!"
+Prepare and check watchers are often (but not always) used in pairs:
+prepare watchers get invoked before the process blocks and check watchers
+afterwards.
+.PP
+You \fImust not\fR call \f(CW\*(C`ev_run\*(C'\fR (or similar functions that enter the
+current event loop) or \f(CW\*(C`ev_loop_fork\*(C'\fR from either \f(CW\*(C`ev_prepare\*(C'\fR or
+\&\f(CW\*(C`ev_check\*(C'\fR watchers. Other loops than the current one are fine,
+however. The rationale behind this is that you do not need to check
+for recursion in those watchers, i.e. the sequence will always be
+\&\f(CW\*(C`ev_prepare\*(C'\fR, blocking, \f(CW\*(C`ev_check\*(C'\fR so if you have one watcher of each
+kind they will always be called in pairs bracketing the blocking call.
+.PP
+Their main purpose is to integrate other event mechanisms into libev and
+their use is somewhat advanced. They could be used, for example, to track
+variable changes, implement your own watchers, integrate net-snmp or a
+coroutine library and lots more. They are also occasionally useful if
+you cache some data and want to flush it before blocking (for example,
+in X programs you might want to do an \f(CW\*(C`XFlush ()\*(C'\fR in an \f(CW\*(C`ev_prepare\*(C'\fR
+watcher).
+.PP
+This is done by examining in each prepare call which file descriptors
+need to be watched by the other library, registering \f(CW\*(C`ev_io\*(C'\fR watchers
+for them and starting an \f(CW\*(C`ev_timer\*(C'\fR watcher for any timeouts (many
+libraries provide exactly this functionality). Then, in the check watcher,
+you check for any events that occurred (by checking the pending status
+of all watchers and stopping them) and call back into the library. The
+I/O and timer callbacks will never actually be called (but must be valid
+nevertheless, because you never know, you know?).
+.PP
+As another example, the Perl Coro module uses these hooks to integrate
+coroutines into libev programs, by yielding to other active coroutines
+during each prepare and only letting the process block if no coroutines
+are ready to run (it's actually more complicated: it only runs coroutines
+with priority higher than or equal to the event loop and one coroutine
+of lower priority, but only once, using idle watchers to keep the event
+loop from blocking if lower-priority coroutines are active, thus mapping
+low-priority coroutines to idle/background tasks).
+.PP
+When used for this purpose, it is recommended to give \f(CW\*(C`ev_check\*(C'\fR watchers
+highest (\f(CW\*(C`EV_MAXPRI\*(C'\fR) priority, to ensure that they are being run before
+any other watchers after the poll (this doesn't matter for \f(CW\*(C`ev_prepare\*(C'\fR
+watchers).
+.PP
+Also, \f(CW\*(C`ev_check\*(C'\fR watchers (and \f(CW\*(C`ev_prepare\*(C'\fR watchers, too) should not
+activate (\*(L"feed\*(R") events into libev. While libev fully supports this, they
+might get executed before other \f(CW\*(C`ev_check\*(C'\fR watchers did their job. As
+\&\f(CW\*(C`ev_check\*(C'\fR watchers are often used to embed other (non-libev) event
+loops those other event loops might be in an unusable state until their
+\&\f(CW\*(C`ev_check\*(C'\fR watcher ran (always remind yourself to coexist peacefully with
+others).
+.PP
+\fIAbusing an \f(CI\*(C`ev_check\*(C'\fI watcher for its side-effect\fR
+.IX Subsection "Abusing an ev_check watcher for its side-effect"
+.PP
+\&\f(CW\*(C`ev_check\*(C'\fR (and less often also \f(CW\*(C`ev_prepare\*(C'\fR) watchers can also be
+useful because they are called once per event loop iteration. For
+example, if you want to handle a large number of connections fairly, you
+normally only do a bit of work for each active connection, and if there
+is more work to do, you wait for the next event loop iteration, so other
+connections have a chance of making progress.
+.PP
+Using an \f(CW\*(C`ev_check\*(C'\fR watcher is almost enough: it will be called on the
+next event loop iteration. However, that isn't as soon as possible \-
+without external events, your \f(CW\*(C`ev_check\*(C'\fR watcher will not be invoked.
+.PP
+This is where \f(CW\*(C`ev_idle\*(C'\fR watchers come in handy \- all you need is a
+single global idle watcher that is active as long as you have one active
+\&\f(CW\*(C`ev_check\*(C'\fR watcher. The \f(CW\*(C`ev_idle\*(C'\fR watcher makes sure the event loop
+will not sleep, and the \f(CW\*(C`ev_check\*(C'\fR watcher makes sure a callback gets
+invoked. Neither watcher alone can do that.
+.PP
+\fIWatcher-Specific Functions and Data Members\fR
+.IX Subsection "Watcher-Specific Functions and Data Members"
+.IP "ev_prepare_init (ev_prepare *, callback)" 4
+.IX Item "ev_prepare_init (ev_prepare *, callback)"
+.PD 0
+.IP "ev_check_init (ev_check *, callback)" 4
+.IX Item "ev_check_init (ev_check *, callback)"
+.PD
+Initialises and configures the prepare or check watcher \- they have no
+parameters of any kind. There are \f(CW\*(C`ev_prepare_set\*(C'\fR and \f(CW\*(C`ev_check_set\*(C'\fR
+macros, but using them is utterly, utterly, utterly and completely
+pointless.
+.PP
+\fIExamples\fR
+.IX Subsection "Examples"
+.PP
+There are a number of principal ways to embed other event loops or modules
+into libev. Here are some ideas on how to include libadns into libev
+(there is a Perl module named \f(CW\*(C`EV::ADNS\*(C'\fR that does this, which you could
+use as a working example. Another Perl module named \f(CW\*(C`EV::Glib\*(C'\fR embeds a
+Glib main context into libev, and finally, \f(CW\*(C`Glib::EV\*(C'\fR embeds \s-1EV\s0 into the
+Glib event loop).
+.PP
+Method 1: Add \s-1IO\s0 watchers and a timeout watcher in a prepare handler,
+and in a check watcher, destroy them and call into libadns. What follows
+is pseudo-code only of course. This requires you to either use a low
+priority for the check watcher or use \f(CW\*(C`ev_clear_pending\*(C'\fR explicitly, as
+the callbacks for the IO/timeout watchers might not have been called yet.
+.PP
+.Vb 2
+\& static ev_io iow [nfd];
+\& static ev_timer tw;
+\&
+\& static void
+\& io_cb (struct ev_loop *loop, ev_io *w, int revents)
+\& {
+\& }
+\&
+\& // create io watchers for each fd and a timer before blocking
+\& static void
+\& adns_prepare_cb (struct ev_loop *loop, ev_prepare *w, int revents)
+\& {
+\& int timeout = 3600000;
+\& struct pollfd fds [nfd];
+\& // actual code will need to loop here and realloc etc.
+\& adns_beforepoll (ads, fds, &nfd, &timeout, timeval_from (ev_time ()));
+\&
+\& /* the callback is illegal, but won\*(Aqt be called as we stop during check */
+\& ev_timer_init (&tw, 0, timeout * 1e\-3, 0.);
+\& ev_timer_start (loop, &tw);
+\&
+\& // create one ev_io per pollfd
+\& for (int i = 0; i < nfd; ++i)
+\& {
+\& ev_io_init (iow + i, io_cb, fds [i].fd,
+\& ((fds [i].events & POLLIN ? EV_READ : 0)
+\& | (fds [i].events & POLLOUT ? EV_WRITE : 0)));
+\&
+\& fds [i].revents = 0;
+\& ev_io_start (loop, iow + i);
+\& }
+\& }
+\&
+\& // stop all watchers after blocking
+\& static void
+\& adns_check_cb (struct ev_loop *loop, ev_check *w, int revents)
+\& {
+\& ev_timer_stop (loop, &tw);
+\&
+\& for (int i = 0; i < nfd; ++i)
+\& {
+\& // set the relevant poll flags
+\& // could also call adns_processreadable etc. here
+\& struct pollfd *fd = fds + i;
+\& int revents = ev_clear_pending (iow + i);
+\& if (revents & EV_READ ) fd\->revents |= fd\->events & POLLIN;
+\& if (revents & EV_WRITE) fd\->revents |= fd\->events & POLLOUT;
+\&
+\& // now stop the watcher
+\& ev_io_stop (loop, iow + i);
+\& }
+\&
+\& adns_afterpoll (adns, fds, nfd, timeval_from (ev_now (loop));
+\& }
+.Ve
+.PP
+Method 2: This would be just like method 1, but you run \f(CW\*(C`adns_afterpoll\*(C'\fR
+in the prepare watcher and would dispose of the check watcher.
+.PP
+Method 3: If the module to be embedded supports explicit event
+notification (libadns does), you can also make use of the actual watcher
+callbacks, and only destroy/create the watchers in the prepare watcher.
+.PP
+.Vb 5
+\& static void
+\& timer_cb (EV_P_ ev_timer *w, int revents)
+\& {
+\& adns_state ads = (adns_state)w\->data;
+\& update_now (EV_A);
+\&
+\& adns_processtimeouts (ads, &tv_now);
+\& }
+\&
+\& static void
+\& io_cb (EV_P_ ev_io *w, int revents)
+\& {
+\& adns_state ads = (adns_state)w\->data;
+\& update_now (EV_A);
+\&
+\& if (revents & EV_READ ) adns_processreadable (ads, w\->fd, &tv_now);
+\& if (revents & EV_WRITE) adns_processwriteable (ads, w\->fd, &tv_now);
+\& }
+\&
+\& // do not ever call adns_afterpoll
+.Ve
+.PP
+Method 4: Do not use a prepare or check watcher because the module you
+want to embed is not flexible enough to support it. Instead, you can
+override their poll function. The drawback with this solution is that the
+main loop is now no longer controllable by \s-1EV.\s0 The \f(CW\*(C`Glib::EV\*(C'\fR module uses
+this approach, effectively embedding \s-1EV\s0 as a client into the horrible
+libglib event loop.
+.PP
+.Vb 4
+\& static gint
+\& event_poll_func (GPollFD *fds, guint nfds, gint timeout)
+\& {
+\& int got_events = 0;
+\&
+\& for (n = 0; n < nfds; ++n)
+\& // create/start io watcher that sets the relevant bits in fds[n] and increment got_events
+\&
+\& if (timeout >= 0)
+\& // create/start timer
+\&
+\& // poll
+\& ev_run (EV_A_ 0);
+\&
+\& // stop timer again
+\& if (timeout >= 0)
+\& ev_timer_stop (EV_A_ &to);
+\&
+\& // stop io watchers again \- their callbacks should have set
+\& for (n = 0; n < nfds; ++n)
+\& ev_io_stop (EV_A_ iow [n]);
+\&
+\& return got_events;
+\& }
+.Ve
+.ie n .SS """ev_embed"" \- when one backend isn't enough..."
+.el .SS "\f(CWev_embed\fP \- when one backend isn't enough..."
+.IX Subsection "ev_embed - when one backend isn't enough..."
+This is a rather advanced watcher type that lets you embed one event loop
+into another (currently only \f(CW\*(C`ev_io\*(C'\fR events are supported in the embedded
+loop, other types of watchers might be handled in a delayed or incorrect
+fashion and must not be used).
+.PP
+There are primarily two reasons you would want that: work around bugs and
+prioritise I/O.
+.PP
+As an example for a bug workaround, the kqueue backend might only support
+sockets on some platform, so it is unusable as generic backend, but you
+still want to make use of it because you have many sockets and it scales
+so nicely. In this case, you would create a kqueue-based loop and embed
+it into your default loop (which might use e.g. poll). Overall operation
+will be a bit slower because first libev has to call \f(CW\*(C`poll\*(C'\fR and then
+\&\f(CW\*(C`kevent\*(C'\fR, but at least you can use both mechanisms for what they are
+best: \f(CW\*(C`kqueue\*(C'\fR for scalable sockets and \f(CW\*(C`poll\*(C'\fR if you want it to work :)
+.PP
+As for prioritising I/O: under rare circumstances you have the case where
+some fds have to be watched and handled very quickly (with low latency),
+and even priorities and idle watchers might have too much overhead. In
+this case you would put all the high priority stuff in one loop and all
+the rest in a second one, and embed the second one in the first.
+.PP
+As long as the watcher is active, the callback will be invoked every
+time there might be events pending in the embedded loop. The callback
+must then call \f(CW\*(C`ev_embed_sweep (mainloop, watcher)\*(C'\fR to make a single
+sweep and invoke their callbacks (the callback doesn't need to invoke the
+\&\f(CW\*(C`ev_embed_sweep\*(C'\fR function directly, it could also start an idle watcher
+to give the embedded loop strictly lower priority for example).
+.PP
+You can also set the callback to \f(CW0\fR, in which case the embed watcher
+will automatically execute the embedded loop sweep whenever necessary.
+.PP
+Fork detection will be handled transparently while the \f(CW\*(C`ev_embed\*(C'\fR watcher
+is active, i.e., the embedded loop will automatically be forked when the
+embedding loop forks. In other cases, the user is responsible for calling
+\&\f(CW\*(C`ev_loop_fork\*(C'\fR on the embedded loop.
+.PP
+Unfortunately, not all backends are embeddable: only the ones returned by
+\&\f(CW\*(C`ev_embeddable_backends\*(C'\fR are, which, unfortunately, does not include any
+portable one.
+.PP
+So when you want to use this feature you will always have to be prepared
+that you cannot get an embeddable loop. The recommended way to get around
+this is to have a separate variables for your embeddable loop, try to
+create it, and if that fails, use the normal loop for everything.
+.PP
+\fI\f(CI\*(C`ev_embed\*(C'\fI and fork\fR
+.IX Subsection "ev_embed and fork"
+.PP
+While the \f(CW\*(C`ev_embed\*(C'\fR watcher is running, forks in the embedding loop will
+automatically be applied to the embedded loop as well, so no special
+fork handling is required in that case. When the watcher is not running,
+however, it is still the task of the libev user to call \f(CW\*(C`ev_loop_fork ()\*(C'\fR
+as applicable.
+.PP
+\fIWatcher-Specific Functions and Data Members\fR
+.IX Subsection "Watcher-Specific Functions and Data Members"
+.IP "ev_embed_init (ev_embed *, callback, struct ev_loop *embedded_loop)" 4
+.IX Item "ev_embed_init (ev_embed *, callback, struct ev_loop *embedded_loop)"
+.PD 0
+.IP "ev_embed_set (ev_embed *, struct ev_loop *embedded_loop)" 4
+.IX Item "ev_embed_set (ev_embed *, struct ev_loop *embedded_loop)"
+.PD
+Configures the watcher to embed the given loop, which must be
+embeddable. If the callback is \f(CW0\fR, then \f(CW\*(C`ev_embed_sweep\*(C'\fR will be
+invoked automatically, otherwise it is the responsibility of the callback
+to invoke it (it will continue to be called until the sweep has been done,
+if you do not want that, you need to temporarily stop the embed watcher).
+.IP "ev_embed_sweep (loop, ev_embed *)" 4
+.IX Item "ev_embed_sweep (loop, ev_embed *)"
+Make a single, non-blocking sweep over the embedded loop. This works
+similarly to \f(CW\*(C`ev_run (embedded_loop, EVRUN_NOWAIT)\*(C'\fR, but in the most
+appropriate way for embedded loops.
+.IP "struct ev_loop *other [read\-only]" 4
+.IX Item "struct ev_loop *other [read-only]"
+The embedded event loop.
+.PP
+\fIExamples\fR
+.IX Subsection "Examples"
+.PP
+Example: Try to get an embeddable event loop and embed it into the default
+event loop. If that is not possible, use the default loop. The default
+loop is stored in \f(CW\*(C`loop_hi\*(C'\fR, while the embeddable loop is stored in
+\&\f(CW\*(C`loop_lo\*(C'\fR (which is \f(CW\*(C`loop_hi\*(C'\fR in the case no embeddable loop can be
+used).
+.PP
+.Vb 3
+\& struct ev_loop *loop_hi = ev_default_init (0);
+\& struct ev_loop *loop_lo = 0;
+\& ev_embed embed;
+\&
+\& // see if there is a chance of getting one that works
+\& // (remember that a flags value of 0 means autodetection)
+\& loop_lo = ev_embeddable_backends () & ev_recommended_backends ()
+\& ? ev_loop_new (ev_embeddable_backends () & ev_recommended_backends ())
+\& : 0;
+\&
+\& // if we got one, then embed it, otherwise default to loop_hi
+\& if (loop_lo)
+\& {
+\& ev_embed_init (&embed, 0, loop_lo);
+\& ev_embed_start (loop_hi, &embed);
+\& }
+\& else
+\& loop_lo = loop_hi;
+.Ve
+.PP
+Example: Check if kqueue is available but not recommended and create
+a kqueue backend for use with sockets (which usually work with any
+kqueue implementation). Store the kqueue/socket\-only event loop in
+\&\f(CW\*(C`loop_socket\*(C'\fR. (One might optionally use \f(CW\*(C`EVFLAG_NOENV\*(C'\fR, too).
+.PP
+.Vb 3
+\& struct ev_loop *loop = ev_default_init (0);
+\& struct ev_loop *loop_socket = 0;
+\& ev_embed embed;
+\&
+\& if (ev_supported_backends () & ~ev_recommended_backends () & EVBACKEND_KQUEUE)
+\& if ((loop_socket = ev_loop_new (EVBACKEND_KQUEUE))
+\& {
+\& ev_embed_init (&embed, 0, loop_socket);
+\& ev_embed_start (loop, &embed);
+\& }
+\&
+\& if (!loop_socket)
+\& loop_socket = loop;
+\&
+\& // now use loop_socket for all sockets, and loop for everything else
+.Ve
+.ie n .SS """ev_fork"" \- the audacity to resume the event loop after a fork"
+.el .SS "\f(CWev_fork\fP \- the audacity to resume the event loop after a fork"
+.IX Subsection "ev_fork - the audacity to resume the event loop after a fork"
+Fork watchers are called when a \f(CW\*(C`fork ()\*(C'\fR was detected (usually because
+whoever is a good citizen cared to tell libev about it by calling
+\&\f(CW\*(C`ev_loop_fork\*(C'\fR). The invocation is done before the event loop blocks next
+and before \f(CW\*(C`ev_check\*(C'\fR watchers are being called, and only in the child
+after the fork. If whoever good citizen calling \f(CW\*(C`ev_default_fork\*(C'\fR cheats
+and calls it in the wrong process, the fork handlers will be invoked, too,
+of course.
+.PP
+\fIThe special problem of life after fork \- how is it possible?\fR
+.IX Subsection "The special problem of life after fork - how is it possible?"
+.PP
+Most uses of \f(CW\*(C`fork ()\*(C'\fR consist of forking, then some simple calls to set
+up/change the process environment, followed by a call to \f(CW\*(C`exec()\*(C'\fR. This
+sequence should be handled by libev without any problems.
+.PP
+This changes when the application actually wants to do event handling
+in the child, or both parent in child, in effect \*(L"continuing\*(R" after the
+fork.
+.PP
+The default mode of operation (for libev, with application help to detect
+forks) is to duplicate all the state in the child, as would be expected
+when \fIeither\fR the parent \fIor\fR the child process continues.
+.PP
+When both processes want to continue using libev, then this is usually the
+wrong result. In that case, usually one process (typically the parent) is
+supposed to continue with all watchers in place as before, while the other
+process typically wants to start fresh, i.e. without any active watchers.
+.PP
+The cleanest and most efficient way to achieve that with libev is to
+simply create a new event loop, which of course will be \*(L"empty\*(R", and
+use that for new watchers. This has the advantage of not touching more
+memory than necessary, and thus avoiding the copy-on-write, and the
+disadvantage of having to use multiple event loops (which do not support
+signal watchers).
+.PP
+When this is not possible, or you want to use the default loop for
+other reasons, then in the process that wants to start \*(L"fresh\*(R", call
+\&\f(CW\*(C`ev_loop_destroy (EV_DEFAULT)\*(C'\fR followed by \f(CW\*(C`ev_default_loop (...)\*(C'\fR.
+Destroying the default loop will \*(L"orphan\*(R" (not stop) all registered
+watchers, so you have to be careful not to execute code that modifies
+those watchers. Note also that in that case, you have to re-register any
+signal watchers.
+.PP
+\fIWatcher-Specific Functions and Data Members\fR
+.IX Subsection "Watcher-Specific Functions and Data Members"
+.IP "ev_fork_init (ev_fork *, callback)" 4
+.IX Item "ev_fork_init (ev_fork *, callback)"
+Initialises and configures the fork watcher \- it has no parameters of any
+kind. There is a \f(CW\*(C`ev_fork_set\*(C'\fR macro, but using it is utterly pointless,
+really.
+.ie n .SS """ev_cleanup"" \- even the best things end"
+.el .SS "\f(CWev_cleanup\fP \- even the best things end"
+.IX Subsection "ev_cleanup - even the best things end"
+Cleanup watchers are called just before the event loop is being destroyed
+by a call to \f(CW\*(C`ev_loop_destroy\*(C'\fR.
+.PP
+While there is no guarantee that the event loop gets destroyed, cleanup
+watchers provide a convenient method to install cleanup hooks for your
+program, worker threads and so on \- you just to make sure to destroy the
+loop when you want them to be invoked.
+.PP
+Cleanup watchers are invoked in the same way as any other watcher. Unlike
+all other watchers, they do not keep a reference to the event loop (which
+makes a lot of sense if you think about it). Like all other watchers, you
+can call libev functions in the callback, except \f(CW\*(C`ev_cleanup_start\*(C'\fR.
+.PP
+\fIWatcher-Specific Functions and Data Members\fR
+.IX Subsection "Watcher-Specific Functions and Data Members"
+.IP "ev_cleanup_init (ev_cleanup *, callback)" 4
+.IX Item "ev_cleanup_init (ev_cleanup *, callback)"
+Initialises and configures the cleanup watcher \- it has no parameters of
+any kind. There is a \f(CW\*(C`ev_cleanup_set\*(C'\fR macro, but using it is utterly
+pointless, I assure you.
+.PP
+Example: Register an atexit handler to destroy the default loop, so any
+cleanup functions are called.
+.PP
+.Vb 5
+\& static void
+\& program_exits (void)
+\& {
+\& ev_loop_destroy (EV_DEFAULT_UC);
+\& }
+\&
+\& ...
+\& atexit (program_exits);
+.Ve
+.ie n .SS """ev_async"" \- how to wake up an event loop"
+.el .SS "\f(CWev_async\fP \- how to wake up an event loop"
+.IX Subsection "ev_async - how to wake up an event loop"
+In general, you cannot use an \f(CW\*(C`ev_loop\*(C'\fR from multiple threads or other
+asynchronous sources such as signal handlers (as opposed to multiple event
+loops \- those are of course safe to use in different threads).
+.PP
+Sometimes, however, you need to wake up an event loop you do not control,
+for example because it belongs to another thread. This is what \f(CW\*(C`ev_async\*(C'\fR
+watchers do: as long as the \f(CW\*(C`ev_async\*(C'\fR watcher is active, you can signal
+it by calling \f(CW\*(C`ev_async_send\*(C'\fR, which is thread\- and signal safe.
+.PP
+This functionality is very similar to \f(CW\*(C`ev_signal\*(C'\fR watchers, as signals,
+too, are asynchronous in nature, and signals, too, will be compressed
+(i.e. the number of callback invocations may be less than the number of
+\&\f(CW\*(C`ev_async_send\*(C'\fR calls). In fact, you could use signal watchers as a kind
+of \*(L"global async watchers\*(R" by using a watcher on an otherwise unused
+signal, and \f(CW\*(C`ev_feed_signal\*(C'\fR to signal this watcher from another thread,
+even without knowing which loop owns the signal.
+.PP
+\fIQueueing\fR
+.IX Subsection "Queueing"
+.PP
+\&\f(CW\*(C`ev_async\*(C'\fR does not support queueing of data in any way. The reason
+is that the author does not know of a simple (or any) algorithm for a
+multiple-writer-single-reader queue that works in all cases and doesn't
+need elaborate support such as pthreads or unportable memory access
+semantics.
+.PP
+That means that if you want to queue data, you have to provide your own
+queue. But at least I can tell you how to implement locking around your
+queue:
+.IP "queueing from a signal handler context" 4
+.IX Item "queueing from a signal handler context"
+To implement race-free queueing, you simply add to the queue in the signal
+handler but you block the signal handler in the watcher callback. Here is
+an example that does that for some fictitious \s-1SIGUSR1\s0 handler:
+.Sp
+.Vb 1
+\& static ev_async mysig;
+\&
+\& static void
+\& sigusr1_handler (void)
+\& {
+\& sometype data;
+\&
+\& // no locking etc.
+\& queue_put (data);
+\& ev_async_send (EV_DEFAULT_ &mysig);
+\& }
+\&
+\& static void
+\& mysig_cb (EV_P_ ev_async *w, int revents)
+\& {
+\& sometype data;
+\& sigset_t block, prev;
+\&
+\& sigemptyset (&block);
+\& sigaddset (&block, SIGUSR1);
+\& sigprocmask (SIG_BLOCK, &block, &prev);
+\&
+\& while (queue_get (&data))
+\& process (data);
+\&
+\& if (sigismember (&prev, SIGUSR1)
+\& sigprocmask (SIG_UNBLOCK, &block, 0);
+\& }
+.Ve
+.Sp
+(Note: pthreads in theory requires you to use \f(CW\*(C`pthread_setmask\*(C'\fR
+instead of \f(CW\*(C`sigprocmask\*(C'\fR when you use threads, but libev doesn't do it
+either...).
+.IP "queueing from a thread context" 4
+.IX Item "queueing from a thread context"
+The strategy for threads is different, as you cannot (easily) block
+threads but you can easily preempt them, so to queue safely you need to
+employ a traditional mutex lock, such as in this pthread example:
+.Sp
+.Vb 2
+\& static ev_async mysig;
+\& static pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;
+\&
+\& static void
+\& otherthread (void)
+\& {
+\& // only need to lock the actual queueing operation
+\& pthread_mutex_lock (&mymutex);
+\& queue_put (data);
+\& pthread_mutex_unlock (&mymutex);
+\&
+\& ev_async_send (EV_DEFAULT_ &mysig);
+\& }
+\&
+\& static void
+\& mysig_cb (EV_P_ ev_async *w, int revents)
+\& {
+\& pthread_mutex_lock (&mymutex);
+\&
+\& while (queue_get (&data))
+\& process (data);
+\&
+\& pthread_mutex_unlock (&mymutex);
+\& }
+.Ve
+.PP
+\fIWatcher-Specific Functions and Data Members\fR
+.IX Subsection "Watcher-Specific Functions and Data Members"
+.IP "ev_async_init (ev_async *, callback)" 4
+.IX Item "ev_async_init (ev_async *, callback)"
+Initialises and configures the async watcher \- it has no parameters of any
+kind. There is a \f(CW\*(C`ev_async_set\*(C'\fR macro, but using it is utterly pointless,
+trust me.
+.IP "ev_async_send (loop, ev_async *)" 4
+.IX Item "ev_async_send (loop, ev_async *)"
+Sends/signals/activates the given \f(CW\*(C`ev_async\*(C'\fR watcher, that is, feeds
+an \f(CW\*(C`EV_ASYNC\*(C'\fR event on the watcher into the event loop, and instantly
+returns.
+.Sp
+Unlike \f(CW\*(C`ev_feed_event\*(C'\fR, this call is safe to do from other threads,
+signal or similar contexts (see the discussion of \f(CW\*(C`EV_ATOMIC_T\*(C'\fR in the
+embedding section below on what exactly this means).
+.Sp
+Note that, as with other watchers in libev, multiple events might get
+compressed into a single callback invocation (another way to look at
+this is that \f(CW\*(C`ev_async\*(C'\fR watchers are level-triggered: they are set on
+\&\f(CW\*(C`ev_async_send\*(C'\fR, reset when the event loop detects that).
+.Sp
+This call incurs the overhead of at most one extra system call per event
+loop iteration, if the event loop is blocked, and no syscall at all if
+the event loop (or your program) is processing events. That means that
+repeated calls are basically free (there is no need to avoid calls for
+performance reasons) and that the overhead becomes smaller (typically
+zero) under load.
+.IP "bool = ev_async_pending (ev_async *)" 4
+.IX Item "bool = ev_async_pending (ev_async *)"
+Returns a non-zero value when \f(CW\*(C`ev_async_send\*(C'\fR has been called on the
+watcher but the event has not yet been processed (or even noted) by the
+event loop.
+.Sp
+\&\f(CW\*(C`ev_async_send\*(C'\fR sets a flag in the watcher and wakes up the loop. When
+the loop iterates next and checks for the watcher to have become active,
+it will reset the flag again. \f(CW\*(C`ev_async_pending\*(C'\fR can be used to very
+quickly check whether invoking the loop might be a good idea.
+.Sp
+Not that this does \fInot\fR check whether the watcher itself is pending,
+only whether it has been requested to make this watcher pending: there
+is a time window between the event loop checking and resetting the async
+notification, and the callback being invoked.
+.SH "OTHER FUNCTIONS"
+.IX Header "OTHER FUNCTIONS"
+There are some other functions of possible interest. Described. Here. Now.
+.IP "ev_once (loop, int fd, int events, ev_tstamp timeout, callback, arg)" 4
+.IX Item "ev_once (loop, int fd, int events, ev_tstamp timeout, callback, arg)"
+This function combines a simple timer and an I/O watcher, calls your
+callback on whichever event happens first and automatically stops both
+watchers. This is useful if you want to wait for a single event on an fd
+or timeout without having to allocate/configure/start/stop/free one or
+more watchers yourself.
+.Sp
+If \f(CW\*(C`fd\*(C'\fR is less than 0, then no I/O watcher will be started and the
+\&\f(CW\*(C`events\*(C'\fR argument is being ignored. Otherwise, an \f(CW\*(C`ev_io\*(C'\fR watcher for
+the given \f(CW\*(C`fd\*(C'\fR and \f(CW\*(C`events\*(C'\fR set will be created and started.
+.Sp
+If \f(CW\*(C`timeout\*(C'\fR is less than 0, then no timeout watcher will be
+started. Otherwise an \f(CW\*(C`ev_timer\*(C'\fR watcher with after = \f(CW\*(C`timeout\*(C'\fR (and
+repeat = 0) will be started. \f(CW0\fR is a valid timeout.
+.Sp
+The callback has the type \f(CW\*(C`void (*cb)(int revents, void *arg)\*(C'\fR and is
+passed an \f(CW\*(C`revents\*(C'\fR set like normal event callbacks (a combination of
+\&\f(CW\*(C`EV_ERROR\*(C'\fR, \f(CW\*(C`EV_READ\*(C'\fR, \f(CW\*(C`EV_WRITE\*(C'\fR or \f(CW\*(C`EV_TIMER\*(C'\fR) and the \f(CW\*(C`arg\*(C'\fR
+value passed to \f(CW\*(C`ev_once\*(C'\fR. Note that it is possible to receive \fIboth\fR
+a timeout and an io event at the same time \- you probably should give io
+events precedence.
+.Sp
+Example: wait up to ten seconds for data to appear on \s-1STDIN_FILENO.\s0
+.Sp
+.Vb 7
+\& static void stdin_ready (int revents, void *arg)
+\& {
+\& if (revents & EV_READ)
+\& /* stdin might have data for us, joy! */;
+\& else if (revents & EV_TIMER)
+\& /* doh, nothing entered */;
+\& }
+\&
+\& ev_once (STDIN_FILENO, EV_READ, 10., stdin_ready, 0);
+.Ve
+.IP "ev_feed_fd_event (loop, int fd, int revents)" 4
+.IX Item "ev_feed_fd_event (loop, int fd, int revents)"
+Feed an event on the given fd, as if a file descriptor backend detected
+the given events.
+.IP "ev_feed_signal_event (loop, int signum)" 4
+.IX Item "ev_feed_signal_event (loop, int signum)"
+Feed an event as if the given signal occurred. See also \f(CW\*(C`ev_feed_signal\*(C'\fR,
+which is async-safe.
+.SH "COMMON OR USEFUL IDIOMS (OR BOTH)"
+.IX Header "COMMON OR USEFUL IDIOMS (OR BOTH)"
+This section explains some common idioms that are not immediately
+obvious. Note that examples are sprinkled over the whole manual, and this
+section only contains stuff that wouldn't fit anywhere else.
+.SS "\s-1ASSOCIATING CUSTOM DATA WITH A WATCHER\s0"
+.IX Subsection "ASSOCIATING CUSTOM DATA WITH A WATCHER"
+Each watcher has, by default, a \f(CW\*(C`void *data\*(C'\fR member that you can read
+or modify at any time: libev will completely ignore it. This can be used
+to associate arbitrary data with your watcher. If you need more data and
+don't want to allocate memory separately and store a pointer to it in that
+data member, you can also \*(L"subclass\*(R" the watcher type and provide your own
+data:
+.PP
+.Vb 7
+\& struct my_io
+\& {
+\& ev_io io;
+\& int otherfd;
+\& void *somedata;
+\& struct whatever *mostinteresting;
+\& };
+\&
+\& ...
+\& struct my_io w;
+\& ev_io_init (&w.io, my_cb, fd, EV_READ);
+.Ve
+.PP
+And since your callback will be called with a pointer to the watcher, you
+can cast it back to your own type:
+.PP
+.Vb 5
+\& static void my_cb (struct ev_loop *loop, ev_io *w_, int revents)
+\& {
+\& struct my_io *w = (struct my_io *)w_;
+\& ...
+\& }
+.Ve
+.PP
+More interesting and less C\-conformant ways of casting your callback
+function type instead have been omitted.
+.SS "\s-1BUILDING YOUR OWN COMPOSITE WATCHERS\s0"
+.IX Subsection "BUILDING YOUR OWN COMPOSITE WATCHERS"
+Another common scenario is to use some data structure with multiple
+embedded watchers, in effect creating your own watcher that combines
+multiple libev event sources into one \*(L"super-watcher\*(R":
+.PP
+.Vb 6
+\& struct my_biggy
+\& {
+\& int some_data;
+\& ev_timer t1;
+\& ev_timer t2;
+\& }
+.Ve
+.PP
+In this case getting the pointer to \f(CW\*(C`my_biggy\*(C'\fR is a bit more
+complicated: Either you store the address of your \f(CW\*(C`my_biggy\*(C'\fR struct in
+the \f(CW\*(C`data\*(C'\fR member of the watcher (for woozies or \*(C+ coders), or you need
+to use some pointer arithmetic using \f(CW\*(C`offsetof\*(C'\fR inside your watchers (for
+real programmers):
+.PP
+.Vb 1
+\& #include <stddef.h>
+\&
+\& static void
+\& t1_cb (EV_P_ ev_timer *w, int revents)
+\& {
+\& struct my_biggy big = (struct my_biggy *)
+\& (((char *)w) \- offsetof (struct my_biggy, t1));
+\& }
+\&
+\& static void
+\& t2_cb (EV_P_ ev_timer *w, int revents)
+\& {
+\& struct my_biggy big = (struct my_biggy *)
+\& (((char *)w) \- offsetof (struct my_biggy, t2));
+\& }
+.Ve
+.SS "\s-1AVOIDING FINISHING BEFORE RETURNING\s0"
+.IX Subsection "AVOIDING FINISHING BEFORE RETURNING"
+Often you have structures like this in event-based programs:
+.PP
+.Vb 4
+\& callback ()
+\& {
+\& free (request);
+\& }
+\&
+\& request = start_new_request (..., callback);
+.Ve
+.PP
+The intent is to start some \*(L"lengthy\*(R" operation. The \f(CW\*(C`request\*(C'\fR could be
+used to cancel the operation, or do other things with it.
+.PP
+It's not uncommon to have code paths in \f(CW\*(C`start_new_request\*(C'\fR that
+immediately invoke the callback, for example, to report errors. Or you add
+some caching layer that finds that it can skip the lengthy aspects of the
+operation and simply invoke the callback with the result.
+.PP
+The problem here is that this will happen \fIbefore\fR \f(CW\*(C`start_new_request\*(C'\fR
+has returned, so \f(CW\*(C`request\*(C'\fR is not set.
+.PP
+Even if you pass the request by some safer means to the callback, you
+might want to do something to the request after starting it, such as
+canceling it, which probably isn't working so well when the callback has
+already been invoked.
+.PP
+A common way around all these issues is to make sure that
+\&\f(CW\*(C`start_new_request\*(C'\fR \fIalways\fR returns before the callback is invoked. If
+\&\f(CW\*(C`start_new_request\*(C'\fR immediately knows the result, it can artificially
+delay invoking the callback by using a \f(CW\*(C`prepare\*(C'\fR or \f(CW\*(C`idle\*(C'\fR watcher for
+example, or more sneakily, by reusing an existing (stopped) watcher and
+pushing it into the pending queue:
+.PP
+.Vb 2
+\& ev_set_cb (watcher, callback);
+\& ev_feed_event (EV_A_ watcher, 0);
+.Ve
+.PP
+This way, \f(CW\*(C`start_new_request\*(C'\fR can safely return before the callback is
+invoked, while not delaying callback invocation too much.
+.SS "\s-1MODEL/NESTED EVENT LOOP INVOCATIONS AND EXIT CONDITIONS\s0"
+.IX Subsection "MODEL/NESTED EVENT LOOP INVOCATIONS AND EXIT CONDITIONS"
+Often (especially in \s-1GUI\s0 toolkits) there are places where you have
+\&\fImodal\fR interaction, which is most easily implemented by recursively
+invoking \f(CW\*(C`ev_run\*(C'\fR.
+.PP
+This brings the problem of exiting \- a callback might want to finish the
+main \f(CW\*(C`ev_run\*(C'\fR call, but not the nested one (e.g. user clicked \*(L"Quit\*(R", but
+a modal \*(L"Are you sure?\*(R" dialog is still waiting), or just the nested one
+and not the main one (e.g. user clocked \*(L"Ok\*(R" in a modal dialog), or some
+other combination: In these cases, a simple \f(CW\*(C`ev_break\*(C'\fR will not work.
+.PP
+The solution is to maintain \*(L"break this loop\*(R" variable for each \f(CW\*(C`ev_run\*(C'\fR
+invocation, and use a loop around \f(CW\*(C`ev_run\*(C'\fR until the condition is
+triggered, using \f(CW\*(C`EVRUN_ONCE\*(C'\fR:
+.PP
+.Vb 2
+\& // main loop
+\& int exit_main_loop = 0;
+\&
+\& while (!exit_main_loop)
+\& ev_run (EV_DEFAULT_ EVRUN_ONCE);
+\&
+\& // in a modal watcher
+\& int exit_nested_loop = 0;
+\&
+\& while (!exit_nested_loop)
+\& ev_run (EV_A_ EVRUN_ONCE);
+.Ve
+.PP
+To exit from any of these loops, just set the corresponding exit variable:
+.PP
+.Vb 2
+\& // exit modal loop
+\& exit_nested_loop = 1;
+\&
+\& // exit main program, after modal loop is finished
+\& exit_main_loop = 1;
+\&
+\& // exit both
+\& exit_main_loop = exit_nested_loop = 1;
+.Ve
+.SS "\s-1THREAD LOCKING EXAMPLE\s0"
+.IX Subsection "THREAD LOCKING EXAMPLE"
+Here is a fictitious example of how to run an event loop in a different
+thread from where callbacks are being invoked and watchers are
+created/added/removed.
+.PP
+For a real-world example, see the \f(CW\*(C`EV::Loop::Async\*(C'\fR perl module,
+which uses exactly this technique (which is suited for many high-level
+languages).
+.PP
+The example uses a pthread mutex to protect the loop data, a condition
+variable to wait for callback invocations, an async watcher to notify the
+event loop thread and an unspecified mechanism to wake up the main thread.
+.PP
+First, you need to associate some data with the event loop:
+.PP
+.Vb 6
+\& typedef struct {
+\& mutex_t lock; /* global loop lock */
+\& ev_async async_w;
+\& thread_t tid;
+\& cond_t invoke_cv;
+\& } userdata;
+\&
+\& void prepare_loop (EV_P)
+\& {
+\& // for simplicity, we use a static userdata struct.
+\& static userdata u;
+\&
+\& ev_async_init (&u\->async_w, async_cb);
+\& ev_async_start (EV_A_ &u\->async_w);
+\&
+\& pthread_mutex_init (&u\->lock, 0);
+\& pthread_cond_init (&u\->invoke_cv, 0);
+\&
+\& // now associate this with the loop
+\& ev_set_userdata (EV_A_ u);
+\& ev_set_invoke_pending_cb (EV_A_ l_invoke);
+\& ev_set_loop_release_cb (EV_A_ l_release, l_acquire);
+\&
+\& // then create the thread running ev_run
+\& pthread_create (&u\->tid, 0, l_run, EV_A);
+\& }
+.Ve
+.PP
+The callback for the \f(CW\*(C`ev_async\*(C'\fR watcher does nothing: the watcher is used
+solely to wake up the event loop so it takes notice of any new watchers
+that might have been added:
+.PP
+.Vb 5
+\& static void
+\& async_cb (EV_P_ ev_async *w, int revents)
+\& {
+\& // just used for the side effects
+\& }
+.Ve
+.PP
+The \f(CW\*(C`l_release\*(C'\fR and \f(CW\*(C`l_acquire\*(C'\fR callbacks simply unlock/lock the mutex
+protecting the loop data, respectively.
+.PP
+.Vb 6
+\& static void
+\& l_release (EV_P)
+\& {
+\& userdata *u = ev_userdata (EV_A);
+\& pthread_mutex_unlock (&u\->lock);
+\& }
+\&
+\& static void
+\& l_acquire (EV_P)
+\& {
+\& userdata *u = ev_userdata (EV_A);
+\& pthread_mutex_lock (&u\->lock);
+\& }
+.Ve
+.PP
+The event loop thread first acquires the mutex, and then jumps straight
+into \f(CW\*(C`ev_run\*(C'\fR:
+.PP
+.Vb 4
+\& void *
+\& l_run (void *thr_arg)
+\& {
+\& struct ev_loop *loop = (struct ev_loop *)thr_arg;
+\&
+\& l_acquire (EV_A);
+\& pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, 0);
+\& ev_run (EV_A_ 0);
+\& l_release (EV_A);
+\&
+\& return 0;
+\& }
+.Ve
+.PP
+Instead of invoking all pending watchers, the \f(CW\*(C`l_invoke\*(C'\fR callback will
+signal the main thread via some unspecified mechanism (signals? pipe
+writes? \f(CW\*(C`Async::Interrupt\*(C'\fR?) and then waits until all pending watchers
+have been called (in a while loop because a) spurious wakeups are possible
+and b) skipping inter-thread-communication when there are no pending
+watchers is very beneficial):
+.PP
+.Vb 4
+\& static void
+\& l_invoke (EV_P)
+\& {
+\& userdata *u = ev_userdata (EV_A);
+\&
+\& while (ev_pending_count (EV_A))
+\& {
+\& wake_up_other_thread_in_some_magic_or_not_so_magic_way ();
+\& pthread_cond_wait (&u\->invoke_cv, &u\->lock);
+\& }
+\& }
+.Ve
+.PP
+Now, whenever the main thread gets told to invoke pending watchers, it
+will grab the lock, call \f(CW\*(C`ev_invoke_pending\*(C'\fR and then signal the loop
+thread to continue:
+.PP
+.Vb 4
+\& static void
+\& real_invoke_pending (EV_P)
+\& {
+\& userdata *u = ev_userdata (EV_A);
+\&
+\& pthread_mutex_lock (&u\->lock);
+\& ev_invoke_pending (EV_A);
+\& pthread_cond_signal (&u\->invoke_cv);
+\& pthread_mutex_unlock (&u\->lock);
+\& }
+.Ve
+.PP
+Whenever you want to start/stop a watcher or do other modifications to an
+event loop, you will now have to lock:
+.PP
+.Vb 2
+\& ev_timer timeout_watcher;
+\& userdata *u = ev_userdata (EV_A);
+\&
+\& ev_timer_init (&timeout_watcher, timeout_cb, 5.5, 0.);
+\&
+\& pthread_mutex_lock (&u\->lock);
+\& ev_timer_start (EV_A_ &timeout_watcher);
+\& ev_async_send (EV_A_ &u\->async_w);
+\& pthread_mutex_unlock (&u\->lock);
+.Ve
+.PP
+Note that sending the \f(CW\*(C`ev_async\*(C'\fR watcher is required because otherwise
+an event loop currently blocking in the kernel will have no knowledge
+about the newly added timer. By waking up the loop it will pick up any new
+watchers in the next event loop iteration.
+.SS "\s-1THREADS, COROUTINES, CONTINUATIONS, QUEUES... INSTEAD OF CALLBACKS\s0"
+.IX Subsection "THREADS, COROUTINES, CONTINUATIONS, QUEUES... INSTEAD OF CALLBACKS"
+While the overhead of a callback that e.g. schedules a thread is small, it
+is still an overhead. If you embed libev, and your main usage is with some
+kind of threads or coroutines, you might want to customise libev so that
+doesn't need callbacks anymore.
+.PP
+Imagine you have coroutines that you can switch to using a function
+\&\f(CW\*(C`switch_to (coro)\*(C'\fR, that libev runs in a coroutine called \f(CW\*(C`libev_coro\*(C'\fR
+and that due to some magic, the currently active coroutine is stored in a
+global called \f(CW\*(C`current_coro\*(C'\fR. Then you can build your own \*(L"wait for libev
+event\*(R" primitive by changing \f(CW\*(C`EV_CB_DECLARE\*(C'\fR and \f(CW\*(C`EV_CB_INVOKE\*(C'\fR (note
+the differing \f(CW\*(C`;\*(C'\fR conventions):
+.PP
+.Vb 2
+\& #define EV_CB_DECLARE(type) struct my_coro *cb;
+\& #define EV_CB_INVOKE(watcher) switch_to ((watcher)\->cb)
+.Ve
+.PP
+That means instead of having a C callback function, you store the
+coroutine to switch to in each watcher, and instead of having libev call
+your callback, you instead have it switch to that coroutine.
+.PP
+A coroutine might now wait for an event with a function called
+\&\f(CW\*(C`wait_for_event\*(C'\fR. (the watcher needs to be started, as always, but it doesn't
+matter when, or whether the watcher is active or not when this function is
+called):
+.PP
+.Vb 6
+\& void
+\& wait_for_event (ev_watcher *w)
+\& {
+\& ev_set_cb (w, current_coro);
+\& switch_to (libev_coro);
+\& }
+.Ve
+.PP
+That basically suspends the coroutine inside \f(CW\*(C`wait_for_event\*(C'\fR and
+continues the libev coroutine, which, when appropriate, switches back to
+this or any other coroutine.
+.PP
+You can do similar tricks if you have, say, threads with an event queue \-
+instead of storing a coroutine, you store the queue object and instead of
+switching to a coroutine, you push the watcher onto the queue and notify
+any waiters.
+.PP
+To embed libev, see \*(L"\s-1EMBEDDING\*(R"\s0, but in short, it's easiest to create two
+files, \fImy_ev.h\fR and \fImy_ev.c\fR that include the respective libev files:
+.PP
+.Vb 4
+\& // my_ev.h
+\& #define EV_CB_DECLARE(type) struct my_coro *cb;
+\& #define EV_CB_INVOKE(watcher) switch_to ((watcher)\->cb)
+\& #include "../libev/ev.h"
+\&
+\& // my_ev.c
+\& #define EV_H "my_ev.h"
+\& #include "../libev/ev.c"
+.Ve
+.PP
+And then use \fImy_ev.h\fR when you would normally use \fIev.h\fR, and compile
+\&\fImy_ev.c\fR into your project. When properly specifying include paths, you
+can even use \fIev.h\fR as header file name directly.
+.SH "LIBEVENT EMULATION"
+.IX Header "LIBEVENT EMULATION"
+Libev offers a compatibility emulation layer for libevent. It cannot
+emulate the internals of libevent, so here are some usage hints:
+.IP "\(bu" 4
+Only the libevent\-1.4.1\-beta \s-1API\s0 is being emulated.
+.Sp
+This was the newest libevent version available when libev was implemented,
+and is still mostly unchanged in 2010.
+.IP "\(bu" 4
+Use it by including <event.h>, as usual.
+.IP "\(bu" 4
+The following members are fully supported: ev_base, ev_callback,
+ev_arg, ev_fd, ev_res, ev_events.
+.IP "\(bu" 4
+Avoid using ev_flags and the EVLIST_*\-macros, while it is
+maintained by libev, it does not work exactly the same way as in libevent (consider
+it a private \s-1API\s0).
+.IP "\(bu" 4
+Priorities are not currently supported. Initialising priorities
+will fail and all watchers will have the same priority, even though there
+is an ev_pri field.
+.IP "\(bu" 4
+In libevent, the last base created gets the signals, in libev, the
+base that registered the signal gets the signals.
+.IP "\(bu" 4
+Other members are not supported.
+.IP "\(bu" 4
+The libev emulation is \fInot\fR \s-1ABI\s0 compatible to libevent, you need
+to use the libev header file and library.
+.SH "\*(C+ SUPPORT"
+.IX Header " SUPPORT"
+.SS "C \s-1API\s0"
+.IX Subsection "C API"
+The normal C \s-1API\s0 should work fine when used from \*(C+: both ev.h and the
+libev sources can be compiled as \*(C+. Therefore, code that uses the C \s-1API\s0
+will work fine.
+.PP
+Proper exception specifications might have to be added to callbacks passed
+to libev: exceptions may be thrown only from watcher callbacks, all other
+callbacks (allocator, syserr, loop acquire/release and periodic reschedule
+callbacks) must not throw exceptions, and might need a \f(CW\*(C`noexcept\*(C'\fR
+specification. If you have code that needs to be compiled as both C and
+\&\*(C+ you can use the \f(CW\*(C`EV_NOEXCEPT\*(C'\fR macro for this:
+.PP
+.Vb 6
+\& static void
+\& fatal_error (const char *msg) EV_NOEXCEPT
+\& {
+\& perror (msg);
+\& abort ();
+\& }
+\&
+\& ...
+\& ev_set_syserr_cb (fatal_error);
+.Ve
+.PP
+The only \s-1API\s0 functions that can currently throw exceptions are \f(CW\*(C`ev_run\*(C'\fR,
+\&\f(CW\*(C`ev_invoke\*(C'\fR, \f(CW\*(C`ev_invoke_pending\*(C'\fR and \f(CW\*(C`ev_loop_destroy\*(C'\fR (the latter
+because it runs cleanup watchers).
+.PP
+Throwing exceptions in watcher callbacks is only supported if libev itself
+is compiled with a \*(C+ compiler or your C and \*(C+ environments allow
+throwing exceptions through C libraries (most do).
+.SS "\*(C+ \s-1API\s0"
+.IX Subsection " API"
+Libev comes with some simplistic wrapper classes for \*(C+ that mainly allow
+you to use some convenience methods to start/stop watchers and also change
+the callback model to a model using method callbacks on objects.
+.PP
+To use it,
+.PP
+.Vb 1
+\& #include <ev++.h>
+.Ve
+.PP
+This automatically includes \fIev.h\fR and puts all of its definitions (many
+of them macros) into the global namespace. All \*(C+ specific things are
+put into the \f(CW\*(C`ev\*(C'\fR namespace. It should support all the same embedding
+options as \fIev.h\fR, most notably \f(CW\*(C`EV_MULTIPLICITY\*(C'\fR.
+.PP
+Care has been taken to keep the overhead low. The only data member the \*(C+
+classes add (compared to plain C\-style watchers) is the event loop pointer
+that the watcher is associated with (or no additional members at all if
+you disable \f(CW\*(C`EV_MULTIPLICITY\*(C'\fR when embedding libev).
+.PP
+Currently, functions, static and non-static member functions and classes
+with \f(CW\*(C`operator ()\*(C'\fR can be used as callbacks. Other types should be easy
+to add as long as they only need one additional pointer for context. If
+you need support for other types of functors please contact the author
+(preferably after implementing it).
+.PP
+For all this to work, your \*(C+ compiler either has to use the same calling
+conventions as your C compiler (for static member functions), or you have
+to embed libev and compile libev itself as \*(C+.
+.PP
+Here is a list of things available in the \f(CW\*(C`ev\*(C'\fR namespace:
+.ie n .IP """ev::READ"", ""ev::WRITE"" etc." 4
+.el .IP "\f(CWev::READ\fR, \f(CWev::WRITE\fR etc." 4
+.IX Item "ev::READ, ev::WRITE etc."
+These are just enum values with the same values as the \f(CW\*(C`EV_READ\*(C'\fR etc.
+macros from \fIev.h\fR.
+.ie n .IP """ev::tstamp"", ""ev::now""" 4
+.el .IP "\f(CWev::tstamp\fR, \f(CWev::now\fR" 4
+.IX Item "ev::tstamp, ev::now"
+Aliases to the same types/functions as with the \f(CW\*(C`ev_\*(C'\fR prefix.
+.ie n .IP """ev::io"", ""ev::timer"", ""ev::periodic"", ""ev::idle"", ""ev::sig"" etc." 4
+.el .IP "\f(CWev::io\fR, \f(CWev::timer\fR, \f(CWev::periodic\fR, \f(CWev::idle\fR, \f(CWev::sig\fR etc." 4
+.IX Item "ev::io, ev::timer, ev::periodic, ev::idle, ev::sig etc."
+For each \f(CW\*(C`ev_TYPE\*(C'\fR watcher in \fIev.h\fR there is a corresponding class of
+the same name in the \f(CW\*(C`ev\*(C'\fR namespace, with the exception of \f(CW\*(C`ev_signal\*(C'\fR
+which is called \f(CW\*(C`ev::sig\*(C'\fR to avoid clashes with the \f(CW\*(C`signal\*(C'\fR macro
+defined by many implementations.
+.Sp
+All of those classes have these methods:
+.RS 4
+.IP "ev::TYPE::TYPE ()" 4
+.IX Item "ev::TYPE::TYPE ()"
+.PD 0
+.IP "ev::TYPE::TYPE (loop)" 4
+.IX Item "ev::TYPE::TYPE (loop)"
+.IP "ev::TYPE::~TYPE" 4
+.IX Item "ev::TYPE::~TYPE"
+.PD
+The constructor (optionally) takes an event loop to associate the watcher
+with. If it is omitted, it will use \f(CW\*(C`EV_DEFAULT\*(C'\fR.
+.Sp
+The constructor calls \f(CW\*(C`ev_init\*(C'\fR for you, which means you have to call the
+\&\f(CW\*(C`set\*(C'\fR method before starting it.
+.Sp
+It will not set a callback, however: You have to call the templated \f(CW\*(C`set\*(C'\fR
+method to set a callback before you can start the watcher.
+.Sp
+(The reason why you have to use a method is a limitation in \*(C+ which does
+not allow explicit template arguments for constructors).
+.Sp
+The destructor automatically stops the watcher if it is active.
+.IP "w\->set<class, &class::method> (object *)" 4
+.IX Item "w->set<class, &class::method> (object *)"
+This method sets the callback method to call. The method has to have a
+signature of \f(CW\*(C`void (*)(ev_TYPE &, int)\*(C'\fR, it receives the watcher as
+first argument and the \f(CW\*(C`revents\*(C'\fR as second. The object must be given as
+parameter and is stored in the \f(CW\*(C`data\*(C'\fR member of the watcher.
+.Sp
+This method synthesizes efficient thunking code to call your method from
+the C callback that libev requires. If your compiler can inline your
+callback (i.e. it is visible to it at the place of the \f(CW\*(C`set\*(C'\fR call and
+your compiler is good :), then the method will be fully inlined into the
+thunking function, making it as fast as a direct C callback.
+.Sp
+Example: simple class declaration and watcher initialisation
+.Sp
+.Vb 4
+\& struct myclass
+\& {
+\& void io_cb (ev::io &w, int revents) { }
+\& }
+\&
+\& myclass obj;
+\& ev::io iow;
+\& iow.set <myclass, &myclass::io_cb> (&obj);
+.Ve
+.IP "w\->set (object *)" 4
+.IX Item "w->set (object *)"
+This is a variation of a method callback \- leaving out the method to call
+will default the method to \f(CW\*(C`operator ()\*(C'\fR, which makes it possible to use
+functor objects without having to manually specify the \f(CW\*(C`operator ()\*(C'\fR all
+the time. Incidentally, you can then also leave out the template argument
+list.
+.Sp
+The \f(CW\*(C`operator ()\*(C'\fR method prototype must be \f(CW\*(C`void operator ()(watcher &w,
+int revents)\*(C'\fR.
+.Sp
+See the method\-\f(CW\*(C`set\*(C'\fR above for more details.
+.Sp
+Example: use a functor object as callback.
+.Sp
+.Vb 7
+\& struct myfunctor
+\& {
+\& void operator() (ev::io &w, int revents)
+\& {
+\& ...
+\& }
+\& }
+\&
+\& myfunctor f;
+\&
+\& ev::io w;
+\& w.set (&f);
+.Ve
+.IP "w\->set<function> (void *data = 0)" 4
+.IX Item "w->set<function> (void *data = 0)"
+Also sets a callback, but uses a static method or plain function as
+callback. The optional \f(CW\*(C`data\*(C'\fR argument will be stored in the watcher's
+\&\f(CW\*(C`data\*(C'\fR member and is free for you to use.
+.Sp
+The prototype of the \f(CW\*(C`function\*(C'\fR must be \f(CW\*(C`void (*)(ev::TYPE &w, int)\*(C'\fR.
+.Sp
+See the method\-\f(CW\*(C`set\*(C'\fR above for more details.
+.Sp
+Example: Use a plain function as callback.
+.Sp
+.Vb 2
+\& static void io_cb (ev::io &w, int revents) { }
+\& iow.set <io_cb> ();
+.Ve
+.IP "w\->set (loop)" 4
+.IX Item "w->set (loop)"
+Associates a different \f(CW\*(C`struct ev_loop\*(C'\fR with this watcher. You can only
+do this when the watcher is inactive (and not pending either).
+.IP "w\->set ([arguments])" 4
+.IX Item "w->set ([arguments])"
+Basically the same as \f(CW\*(C`ev_TYPE_set\*(C'\fR (except for \f(CW\*(C`ev::embed\*(C'\fR watchers>),
+with the same arguments. Either this method or a suitable start method
+must be called at least once. Unlike the C counterpart, an active watcher
+gets automatically stopped and restarted when reconfiguring it with this
+method.
+.Sp
+For \f(CW\*(C`ev::embed\*(C'\fR watchers this method is called \f(CW\*(C`set_embed\*(C'\fR, to avoid
+clashing with the \f(CW\*(C`set (loop)\*(C'\fR method.
+.Sp
+For \f(CW\*(C`ev::io\*(C'\fR watchers there is an additional \f(CW\*(C`set\*(C'\fR method that acepts a
+new event mask only, and internally calls \f(CW\*(C`ev_io_modfify\*(C'\fR.
+.IP "w\->start ()" 4
+.IX Item "w->start ()"
+Starts the watcher. Note that there is no \f(CW\*(C`loop\*(C'\fR argument, as the
+constructor already stores the event loop.
+.IP "w\->start ([arguments])" 4
+.IX Item "w->start ([arguments])"
+Instead of calling \f(CW\*(C`set\*(C'\fR and \f(CW\*(C`start\*(C'\fR methods separately, it is often
+convenient to wrap them in one call. Uses the same type of arguments as
+the configure \f(CW\*(C`set\*(C'\fR method of the watcher.
+.IP "w\->stop ()" 4
+.IX Item "w->stop ()"
+Stops the watcher if it is active. Again, no \f(CW\*(C`loop\*(C'\fR argument.
+.ie n .IP "w\->again () (""ev::timer"", ""ev::periodic"" only)" 4
+.el .IP "w\->again () (\f(CWev::timer\fR, \f(CWev::periodic\fR only)" 4
+.IX Item "w->again () (ev::timer, ev::periodic only)"
+For \f(CW\*(C`ev::timer\*(C'\fR and \f(CW\*(C`ev::periodic\*(C'\fR, this invokes the corresponding
+\&\f(CW\*(C`ev_TYPE_again\*(C'\fR function.
+.ie n .IP "w\->sweep () (""ev::embed"" only)" 4
+.el .IP "w\->sweep () (\f(CWev::embed\fR only)" 4
+.IX Item "w->sweep () (ev::embed only)"
+Invokes \f(CW\*(C`ev_embed_sweep\*(C'\fR.
+.ie n .IP "w\->update () (""ev::stat"" only)" 4
+.el .IP "w\->update () (\f(CWev::stat\fR only)" 4
+.IX Item "w->update () (ev::stat only)"
+Invokes \f(CW\*(C`ev_stat_stat\*(C'\fR.
+.RE
+.RS 4
+.RE
+.PP
+Example: Define a class with two I/O and idle watchers, start the I/O
+watchers in the constructor.
+.PP
+.Vb 5
+\& class myclass
+\& {
+\& ev::io io ; void io_cb (ev::io &w, int revents);
+\& ev::io io2 ; void io2_cb (ev::io &w, int revents);
+\& ev::idle idle; void idle_cb (ev::idle &w, int revents);
+\&
+\& myclass (int fd)
+\& {
+\& io .set <myclass, &myclass::io_cb > (this);
+\& io2 .set <myclass, &myclass::io2_cb > (this);
+\& idle.set <myclass, &myclass::idle_cb> (this);
+\&
+\& io.set (fd, ev::WRITE); // configure the watcher
+\& io.start (); // start it whenever convenient
+\&
+\& io2.start (fd, ev::READ); // set + start in one call
+\& }
+\& };
+.Ve
+.SH "OTHER LANGUAGE BINDINGS"
+.IX Header "OTHER LANGUAGE BINDINGS"
+Libev does not offer other language bindings itself, but bindings for a
+number of languages exist in the form of third-party packages. If you know
+any interesting language binding in addition to the ones listed here, drop
+me a note.
+.IP "Perl" 4
+.IX Item "Perl"
+The \s-1EV\s0 module implements the full libev \s-1API\s0 and is actually used to test
+libev. \s-1EV\s0 is developed together with libev. Apart from the \s-1EV\s0 core module,
+there are additional modules that implement libev-compatible interfaces
+to \f(CW\*(C`libadns\*(C'\fR (\f(CW\*(C`EV::ADNS\*(C'\fR, but \f(CW\*(C`AnyEvent::DNS\*(C'\fR is preferred nowadays),
+\&\f(CW\*(C`Net::SNMP\*(C'\fR (\f(CW\*(C`Net::SNMP::EV\*(C'\fR) and the \f(CW\*(C`libglib\*(C'\fR event core (\f(CW\*(C`Glib::EV\*(C'\fR
+and \f(CW\*(C`EV::Glib\*(C'\fR).
+.Sp
+It can be found and installed via \s-1CPAN,\s0 its homepage is at
+<http://software.schmorp.de/pkg/EV>.
+.IP "Python" 4
+.IX Item "Python"
+Python bindings can be found at <http://code.google.com/p/pyev/>. It
+seems to be quite complete and well-documented.
+.IP "Ruby" 4
+.IX Item "Ruby"
+Tony Arcieri has written a ruby extension that offers access to a subset
+of the libev \s-1API\s0 and adds file handle abstractions, asynchronous \s-1DNS\s0 and
+more on top of it. It can be found via gem servers. Its homepage is at
+<http://rev.rubyforge.org/>.
+.Sp
+Roger Pack reports that using the link order \f(CW\*(C`\-lws2_32 \-lmsvcrt\-ruby\-190\*(C'\fR
+makes rev work even on mingw.
+.IP "Haskell" 4
+.IX Item "Haskell"
+A haskell binding to libev is available at
+<http://hackage.haskell.org/cgi\-bin/hackage\-scripts/package/hlibev>.
+.IP "D" 4
+.IX Item "D"
+Leandro Lucarella has written a D language binding (\fIev.d\fR) for libev, to
+be found at <http://www.llucax.com.ar/proj/ev.d/index.html>.
+.IP "Ocaml" 4
+.IX Item "Ocaml"
+Erkki Seppala has written Ocaml bindings for libev, to be found at
+<http://modeemi.cs.tut.fi/~flux/software/ocaml\-ev/>.
+.IP "Lua" 4
+.IX Item "Lua"
+Brian Maher has written a partial interface to libev for lua (at the
+time of this writing, only \f(CW\*(C`ev_io\*(C'\fR and \f(CW\*(C`ev_timer\*(C'\fR), to be found at
+<http://github.com/brimworks/lua\-ev>.
+.IP "Javascript" 4
+.IX Item "Javascript"
+Node.js (<http://nodejs.org>) uses libev as the underlying event library.
+.IP "Others" 4
+.IX Item "Others"
+There are others, and I stopped counting.
+.SH "MACRO MAGIC"
+.IX Header "MACRO MAGIC"
+Libev can be compiled with a variety of options, the most fundamental
+of which is \f(CW\*(C`EV_MULTIPLICITY\*(C'\fR. This option determines whether (most)
+functions and callbacks have an initial \f(CW\*(C`struct ev_loop *\*(C'\fR argument.
+.PP
+To make it easier to write programs that cope with either variant, the
+following macros are defined:
+.ie n .IP """EV_A"", ""EV_A_""" 4
+.el .IP "\f(CWEV_A\fR, \f(CWEV_A_\fR" 4
+.IX Item "EV_A, EV_A_"
+This provides the loop \fIargument\fR for functions, if one is required (\*(L"ev
+loop argument\*(R"). The \f(CW\*(C`EV_A\*(C'\fR form is used when this is the sole argument,
+\&\f(CW\*(C`EV_A_\*(C'\fR is used when other arguments are following. Example:
+.Sp
+.Vb 3
+\& ev_unref (EV_A);
+\& ev_timer_add (EV_A_ watcher);
+\& ev_run (EV_A_ 0);
+.Ve
+.Sp
+It assumes the variable \f(CW\*(C`loop\*(C'\fR of type \f(CW\*(C`struct ev_loop *\*(C'\fR is in scope,
+which is often provided by the following macro.
+.ie n .IP """EV_P"", ""EV_P_""" 4
+.el .IP "\f(CWEV_P\fR, \f(CWEV_P_\fR" 4
+.IX Item "EV_P, EV_P_"
+This provides the loop \fIparameter\fR for functions, if one is required (\*(L"ev
+loop parameter\*(R"). The \f(CW\*(C`EV_P\*(C'\fR form is used when this is the sole parameter,
+\&\f(CW\*(C`EV_P_\*(C'\fR is used when other parameters are following. Example:
+.Sp
+.Vb 2
+\& // this is how ev_unref is being declared
+\& static void ev_unref (EV_P);
+\&
+\& // this is how you can declare your typical callback
+\& static void cb (EV_P_ ev_timer *w, int revents)
+.Ve
+.Sp
+It declares a parameter \f(CW\*(C`loop\*(C'\fR of type \f(CW\*(C`struct ev_loop *\*(C'\fR, quite
+suitable for use with \f(CW\*(C`EV_A\*(C'\fR.
+.ie n .IP """EV_DEFAULT"", ""EV_DEFAULT_""" 4
+.el .IP "\f(CWEV_DEFAULT\fR, \f(CWEV_DEFAULT_\fR" 4
+.IX Item "EV_DEFAULT, EV_DEFAULT_"
+Similar to the other two macros, this gives you the value of the default
+loop, if multiple loops are supported (\*(L"ev loop default\*(R"). The default loop
+will be initialised if it isn't already initialised.
+.Sp
+For non-multiplicity builds, these macros do nothing, so you always have
+to initialise the loop somewhere.
+.ie n .IP """EV_DEFAULT_UC"", ""EV_DEFAULT_UC_""" 4
+.el .IP "\f(CWEV_DEFAULT_UC\fR, \f(CWEV_DEFAULT_UC_\fR" 4
+.IX Item "EV_DEFAULT_UC, EV_DEFAULT_UC_"
+Usage identical to \f(CW\*(C`EV_DEFAULT\*(C'\fR and \f(CW\*(C`EV_DEFAULT_\*(C'\fR, but requires that the
+default loop has been initialised (\f(CW\*(C`UC\*(C'\fR == unchecked). Their behaviour
+is undefined when the default loop has not been initialised by a previous
+execution of \f(CW\*(C`EV_DEFAULT\*(C'\fR, \f(CW\*(C`EV_DEFAULT_\*(C'\fR or \f(CW\*(C`ev_default_init (...)\*(C'\fR.
+.Sp
+It is often prudent to use \f(CW\*(C`EV_DEFAULT\*(C'\fR when initialising the first
+watcher in a function but use \f(CW\*(C`EV_DEFAULT_UC\*(C'\fR afterwards.
+.PP
+Example: Declare and initialise a check watcher, utilising the above
+macros so it will work regardless of whether multiple loops are supported
+or not.
+.PP
+.Vb 5
+\& static void
+\& check_cb (EV_P_ ev_timer *w, int revents)
+\& {
+\& ev_check_stop (EV_A_ w);
+\& }
+\&
+\& ev_check check;
+\& ev_check_init (&check, check_cb);
+\& ev_check_start (EV_DEFAULT_ &check);
+\& ev_run (EV_DEFAULT_ 0);
+.Ve
+.SH "EMBEDDING"
+.IX Header "EMBEDDING"
+Libev can (and often is) directly embedded into host
+applications. Examples of applications that embed it include the Deliantra
+Game Server, the \s-1EV\s0 perl module, the \s-1GNU\s0 Virtual Private Ethernet (gvpe)
+and rxvt-unicode.
+.PP
+The goal is to enable you to just copy the necessary files into your
+source directory without having to change even a single line in them, so
+you can easily upgrade by simply copying (or having a checked-out copy of
+libev somewhere in your source tree).
+.SS "\s-1FILESETS\s0"
+.IX Subsection "FILESETS"
+Depending on what features you need you need to include one or more sets of files
+in your application.
+.PP
+\fI\s-1CORE EVENT LOOP\s0\fR
+.IX Subsection "CORE EVENT LOOP"
+.PP
+To include only the libev core (all the \f(CW\*(C`ev_*\*(C'\fR functions), with manual
+configuration (no autoconf):
+.PP
+.Vb 2
+\& #define EV_STANDALONE 1
+\& #include "ev.c"
+.Ve
+.PP
+This will automatically include \fIev.h\fR, too, and should be done in a
+single C source file only to provide the function implementations. To use
+it, do the same for \fIev.h\fR in all files wishing to use this \s-1API\s0 (best
+done by writing a wrapper around \fIev.h\fR that you can include instead and
+where you can put other configuration options):
+.PP
+.Vb 2
+\& #define EV_STANDALONE 1
+\& #include "ev.h"
+.Ve
+.PP
+Both header files and implementation files can be compiled with a \*(C+
+compiler (at least, that's a stated goal, and breakage will be treated
+as a bug).
+.PP
+You need the following files in your source tree, or in a directory
+in your include path (e.g. in libev/ when using \-Ilibev):
+.PP
+.Vb 4
+\& ev.h
+\& ev.c
+\& ev_vars.h
+\& ev_wrap.h
+\&
+\& ev_win32.c required on win32 platforms only
+\&
+\& ev_select.c only when select backend is enabled
+\& ev_poll.c only when poll backend is enabled
+\& ev_epoll.c only when the epoll backend is enabled
+\& ev_linuxaio.c only when the linux aio backend is enabled
+\& ev_iouring.c only when the linux io_uring backend is enabled
+\& ev_kqueue.c only when the kqueue backend is enabled
+\& ev_port.c only when the solaris port backend is enabled
+.Ve
+.PP
+\&\fIev.c\fR includes the backend files directly when enabled, so you only need
+to compile this single file.
+.PP
+\fI\s-1LIBEVENT COMPATIBILITY API\s0\fR
+.IX Subsection "LIBEVENT COMPATIBILITY API"
+.PP
+To include the libevent compatibility \s-1API,\s0 also include:
+.PP
+.Vb 1
+\& #include "event.c"
+.Ve
+.PP
+in the file including \fIev.c\fR, and:
+.PP
+.Vb 1
+\& #include "event.h"
+.Ve
+.PP
+in the files that want to use the libevent \s-1API.\s0 This also includes \fIev.h\fR.
+.PP
+You need the following additional files for this:
+.PP
+.Vb 2
+\& event.h
+\& event.c
+.Ve
+.PP
+\fI\s-1AUTOCONF SUPPORT\s0\fR
+.IX Subsection "AUTOCONF SUPPORT"
+.PP
+Instead of using \f(CW\*(C`EV_STANDALONE=1\*(C'\fR and providing your configuration in
+whatever way you want, you can also \f(CW\*(C`m4_include([libev.m4])\*(C'\fR in your
+\&\fIconfigure.ac\fR and leave \f(CW\*(C`EV_STANDALONE\*(C'\fR undefined. \fIev.c\fR will then
+include \fIconfig.h\fR and configure itself accordingly.
+.PP
+For this of course you need the m4 file:
+.PP
+.Vb 1
+\& libev.m4
+.Ve
+.SS "\s-1PREPROCESSOR SYMBOLS/MACROS\s0"
+.IX Subsection "PREPROCESSOR SYMBOLS/MACROS"
+Libev can be configured via a variety of preprocessor symbols you have to
+define before including (or compiling) any of its files. The default in
+the absence of autoconf is documented for every option.
+.PP
+Symbols marked with \*(L"(h)\*(R" do not change the \s-1ABI,\s0 and can have different
+values when compiling libev vs. including \fIev.h\fR, so it is permissible
+to redefine them before including \fIev.h\fR without breaking compatibility
+to a compiled library. All other symbols change the \s-1ABI,\s0 which means all
+users of libev and the libev code itself must be compiled with compatible
+settings.
+.IP "\s-1EV_COMPAT3\s0 (h)" 4
+.IX Item "EV_COMPAT3 (h)"
+Backwards compatibility is a major concern for libev. This is why this
+release of libev comes with wrappers for the functions and symbols that
+have been renamed between libev version 3 and 4.
+.Sp
+You can disable these wrappers (to test compatibility with future
+versions) by defining \f(CW\*(C`EV_COMPAT3\*(C'\fR to \f(CW0\fR when compiling your
+sources. This has the additional advantage that you can drop the \f(CW\*(C`struct\*(C'\fR
+from \f(CW\*(C`struct ev_loop\*(C'\fR declarations, as libev will provide an \f(CW\*(C`ev_loop\*(C'\fR
+typedef in that case.
+.Sp
+In some future version, the default for \f(CW\*(C`EV_COMPAT3\*(C'\fR will become \f(CW0\fR,
+and in some even more future version the compatibility code will be
+removed completely.
+.IP "\s-1EV_STANDALONE\s0 (h)" 4
+.IX Item "EV_STANDALONE (h)"
+Must always be \f(CW1\fR if you do not use autoconf configuration, which
+keeps libev from including \fIconfig.h\fR, and it also defines dummy
+implementations for some libevent functions (such as logging, which is not
+supported). It will also not define any of the structs usually found in
+\&\fIevent.h\fR that are not directly supported by the libev core alone.
+.Sp
+In standalone mode, libev will still try to automatically deduce the
+configuration, but has to be more conservative.
+.IP "\s-1EV_USE_FLOOR\s0" 4
+.IX Item "EV_USE_FLOOR"
+If defined to be \f(CW1\fR, libev will use the \f(CW\*(C`floor ()\*(C'\fR function for its
+periodic reschedule calculations, otherwise libev will fall back on a
+portable (slower) implementation. If you enable this, you usually have to
+link against libm or something equivalent. Enabling this when the \f(CW\*(C`floor\*(C'\fR
+function is not available will fail, so the safe default is to not enable
+this.
+.IP "\s-1EV_USE_MONOTONIC\s0" 4
+.IX Item "EV_USE_MONOTONIC"
+If defined to be \f(CW1\fR, libev will try to detect the availability of the
+monotonic clock option at both compile time and runtime. Otherwise no
+use of the monotonic clock option will be attempted. If you enable this,
+you usually have to link against librt or something similar. Enabling it
+when the functionality isn't available is safe, though, although you have
+to make sure you link against any libraries where the \f(CW\*(C`clock_gettime\*(C'\fR
+function is hiding in (often \fI\-lrt\fR). See also \f(CW\*(C`EV_USE_CLOCK_SYSCALL\*(C'\fR.
+.IP "\s-1EV_USE_REALTIME\s0" 4
+.IX Item "EV_USE_REALTIME"
+If defined to be \f(CW1\fR, libev will try to detect the availability of the
+real-time clock option at compile time (and assume its availability
+at runtime if successful). Otherwise no use of the real-time clock
+option will be attempted. This effectively replaces \f(CW\*(C`gettimeofday\*(C'\fR
+by \f(CW\*(C`clock_get (CLOCK_REALTIME, ...)\*(C'\fR and will not normally affect
+correctness. See the note about libraries in the description of
+\&\f(CW\*(C`EV_USE_MONOTONIC\*(C'\fR, though. Defaults to the opposite value of
+\&\f(CW\*(C`EV_USE_CLOCK_SYSCALL\*(C'\fR.
+.IP "\s-1EV_USE_CLOCK_SYSCALL\s0" 4
+.IX Item "EV_USE_CLOCK_SYSCALL"
+If defined to be \f(CW1\fR, libev will try to use a direct syscall instead
+of calling the system-provided \f(CW\*(C`clock_gettime\*(C'\fR function. This option
+exists because on GNU/Linux, \f(CW\*(C`clock_gettime\*(C'\fR is in \f(CW\*(C`librt\*(C'\fR, but \f(CW\*(C`librt\*(C'\fR
+unconditionally pulls in \f(CW\*(C`libpthread\*(C'\fR, slowing down single-threaded
+programs needlessly. Using a direct syscall is slightly slower (in
+theory), because no optimised vdso implementation can be used, but avoids
+the pthread dependency. Defaults to \f(CW1\fR on GNU/Linux with glibc 2.x or
+higher, as it simplifies linking (no need for \f(CW\*(C`\-lrt\*(C'\fR).
+.IP "\s-1EV_USE_NANOSLEEP\s0" 4
+.IX Item "EV_USE_NANOSLEEP"
+If defined to be \f(CW1\fR, libev will assume that \f(CW\*(C`nanosleep ()\*(C'\fR is available
+and will use it for delays. Otherwise it will use \f(CW\*(C`select ()\*(C'\fR.
+.IP "\s-1EV_USE_EVENTFD\s0" 4
+.IX Item "EV_USE_EVENTFD"
+If defined to be \f(CW1\fR, then libev will assume that \f(CW\*(C`eventfd ()\*(C'\fR is
+available and will probe for kernel support at runtime. This will improve
+\&\f(CW\*(C`ev_signal\*(C'\fR and \f(CW\*(C`ev_async\*(C'\fR performance and reduce resource consumption.
+If undefined, it will be enabled if the headers indicate GNU/Linux + Glibc
+2.7 or newer, otherwise disabled.
+.IP "\s-1EV_USE_SIGNALFD\s0" 4
+.IX Item "EV_USE_SIGNALFD"
+If defined to be \f(CW1\fR, then libev will assume that \f(CW\*(C`signalfd ()\*(C'\fR is
+available and will probe for kernel support at runtime. This enables
+the use of \s-1EVFLAG_SIGNALFD\s0 for faster and simpler signal handling. If
+undefined, it will be enabled if the headers indicate GNU/Linux + Glibc
+2.7 or newer, otherwise disabled.
+.IP "\s-1EV_USE_TIMERFD\s0" 4
+.IX Item "EV_USE_TIMERFD"
+If defined to be \f(CW1\fR, then libev will assume that \f(CW\*(C`timerfd ()\*(C'\fR is
+available and will probe for kernel support at runtime. This allows
+libev to detect time jumps accurately. If undefined, it will be enabled
+if the headers indicate GNU/Linux + Glibc 2.8 or newer and define
+\&\f(CW\*(C`TFD_TIMER_CANCEL_ON_SET\*(C'\fR, otherwise disabled.
+.IP "\s-1EV_USE_EVENTFD\s0" 4
+.IX Item "EV_USE_EVENTFD"
+If defined to be \f(CW1\fR, then libev will assume that \f(CW\*(C`eventfd ()\*(C'\fR is
+available and will probe for kernel support at runtime. This will improve
+\&\f(CW\*(C`ev_signal\*(C'\fR and \f(CW\*(C`ev_async\*(C'\fR performance and reduce resource consumption.
+If undefined, it will be enabled if the headers indicate GNU/Linux + Glibc
+2.7 or newer, otherwise disabled.
+.IP "\s-1EV_USE_SELECT\s0" 4
+.IX Item "EV_USE_SELECT"
+If undefined or defined to be \f(CW1\fR, libev will compile in support for the
+\&\f(CW\*(C`select\*(C'\fR(2) backend. No attempt at auto-detection will be done: if no
+other method takes over, select will be it. Otherwise the select backend
+will not be compiled in.
+.IP "\s-1EV_SELECT_USE_FD_SET\s0" 4
+.IX Item "EV_SELECT_USE_FD_SET"
+If defined to \f(CW1\fR, then the select backend will use the system \f(CW\*(C`fd_set\*(C'\fR
+structure. This is useful if libev doesn't compile due to a missing
+\&\f(CW\*(C`NFDBITS\*(C'\fR or \f(CW\*(C`fd_mask\*(C'\fR definition or it mis-guesses the bitset layout
+on exotic systems. This usually limits the range of file descriptors to
+some low limit such as 1024 or might have other limitations (winsocket
+only allows 64 sockets). The \f(CW\*(C`FD_SETSIZE\*(C'\fR macro, set before compilation,
+configures the maximum size of the \f(CW\*(C`fd_set\*(C'\fR.
+.IP "\s-1EV_SELECT_IS_WINSOCKET\s0" 4
+.IX Item "EV_SELECT_IS_WINSOCKET"
+When defined to \f(CW1\fR, the select backend will assume that
+select/socket/connect etc. don't understand file descriptors but
+wants osf handles on win32 (this is the case when the select to
+be used is the winsock select). This means that it will call
+\&\f(CW\*(C`_get_osfhandle\*(C'\fR on the fd to convert it to an \s-1OS\s0 handle. Otherwise,
+it is assumed that all these functions actually work on fds, even
+on win32. Should not be defined on non\-win32 platforms.
+.IP "\s-1EV_FD_TO_WIN32_HANDLE\s0(fd)" 4
+.IX Item "EV_FD_TO_WIN32_HANDLE(fd)"
+If \f(CW\*(C`EV_SELECT_IS_WINSOCKET\*(C'\fR is enabled, then libev needs a way to map
+file descriptors to socket handles. When not defining this symbol (the
+default), then libev will call \f(CW\*(C`_get_osfhandle\*(C'\fR, which is usually
+correct. In some cases, programs use their own file descriptor management,
+in which case they can provide this function to map fds to socket handles.
+.IP "\s-1EV_WIN32_HANDLE_TO_FD\s0(handle)" 4
+.IX Item "EV_WIN32_HANDLE_TO_FD(handle)"
+If \f(CW\*(C`EV_SELECT_IS_WINSOCKET\*(C'\fR then libev maps handles to file descriptors
+using the standard \f(CW\*(C`_open_osfhandle\*(C'\fR function. For programs implementing
+their own fd to handle mapping, overwriting this function makes it easier
+to do so. This can be done by defining this macro to an appropriate value.
+.IP "\s-1EV_WIN32_CLOSE_FD\s0(fd)" 4
+.IX Item "EV_WIN32_CLOSE_FD(fd)"
+If programs implement their own fd to handle mapping on win32, then this
+macro can be used to override the \f(CW\*(C`close\*(C'\fR function, useful to unregister
+file descriptors again. Note that the replacement function has to close
+the underlying \s-1OS\s0 handle.
+.IP "\s-1EV_USE_WSASOCKET\s0" 4
+.IX Item "EV_USE_WSASOCKET"
+If defined to be \f(CW1\fR, libev will use \f(CW\*(C`WSASocket\*(C'\fR to create its internal
+communication socket, which works better in some environments. Otherwise,
+the normal \f(CW\*(C`socket\*(C'\fR function will be used, which works better in other
+environments.
+.IP "\s-1EV_USE_POLL\s0" 4
+.IX Item "EV_USE_POLL"
+If defined to be \f(CW1\fR, libev will compile in support for the \f(CW\*(C`poll\*(C'\fR(2)
+backend. Otherwise it will be enabled on non\-win32 platforms. It
+takes precedence over select.
+.IP "\s-1EV_USE_EPOLL\s0" 4
+.IX Item "EV_USE_EPOLL"
+If defined to be \f(CW1\fR, libev will compile in support for the Linux
+\&\f(CW\*(C`epoll\*(C'\fR(7) backend. Its availability will be detected at runtime,
+otherwise another method will be used as fallback. This is the preferred
+backend for GNU/Linux systems. If undefined, it will be enabled if the
+headers indicate GNU/Linux + Glibc 2.4 or newer, otherwise disabled.
+.IP "\s-1EV_USE_LINUXAIO\s0" 4
+.IX Item "EV_USE_LINUXAIO"
+If defined to be \f(CW1\fR, libev will compile in support for the Linux aio
+backend (\f(CW\*(C`EV_USE_EPOLL\*(C'\fR must also be enabled). If undefined, it will be
+enabled on linux, otherwise disabled.
+.IP "\s-1EV_USE_IOURING\s0" 4
+.IX Item "EV_USE_IOURING"
+If defined to be \f(CW1\fR, libev will compile in support for the Linux
+io_uring backend (\f(CW\*(C`EV_USE_EPOLL\*(C'\fR must also be enabled). Due to it's
+current limitations it has to be requested explicitly. If undefined, it
+will be enabled on linux, otherwise disabled.
+.IP "\s-1EV_USE_KQUEUE\s0" 4
+.IX Item "EV_USE_KQUEUE"
+If defined to be \f(CW1\fR, libev will compile in support for the \s-1BSD\s0 style
+\&\f(CW\*(C`kqueue\*(C'\fR(2) backend. Its actual availability will be detected at runtime,
+otherwise another method will be used as fallback. This is the preferred
+backend for \s-1BSD\s0 and BSD-like systems, although on most BSDs kqueue only
+supports some types of fds correctly (the only platform we found that
+supports ptys for example was NetBSD), so kqueue might be compiled in, but
+not be used unless explicitly requested. The best way to use it is to find
+out whether kqueue supports your type of fd properly and use an embedded
+kqueue loop.
+.IP "\s-1EV_USE_PORT\s0" 4
+.IX Item "EV_USE_PORT"
+If defined to be \f(CW1\fR, libev will compile in support for the Solaris
+10 port style backend. Its availability will be detected at runtime,
+otherwise another method will be used as fallback. This is the preferred
+backend for Solaris 10 systems.
+.IP "\s-1EV_USE_DEVPOLL\s0" 4
+.IX Item "EV_USE_DEVPOLL"
+Reserved for future expansion, works like the \s-1USE\s0 symbols above.
+.IP "\s-1EV_USE_INOTIFY\s0" 4
+.IX Item "EV_USE_INOTIFY"
+If defined to be \f(CW1\fR, libev will compile in support for the Linux inotify
+interface to speed up \f(CW\*(C`ev_stat\*(C'\fR watchers. Its actual availability will
+be detected at runtime. If undefined, it will be enabled if the headers
+indicate GNU/Linux + Glibc 2.4 or newer, otherwise disabled.
+.IP "\s-1EV_NO_SMP\s0" 4
+.IX Item "EV_NO_SMP"
+If defined to be \f(CW1\fR, libev will assume that memory is always coherent
+between threads, that is, threads can be used, but threads never run on
+different cpus (or different cpu cores). This reduces dependencies
+and makes libev faster.
+.IP "\s-1EV_NO_THREADS\s0" 4
+.IX Item "EV_NO_THREADS"
+If defined to be \f(CW1\fR, libev will assume that it will never be called from
+different threads (that includes signal handlers), which is a stronger
+assumption than \f(CW\*(C`EV_NO_SMP\*(C'\fR, above. This reduces dependencies and makes
+libev faster.
+.IP "\s-1EV_ATOMIC_T\s0" 4
+.IX Item "EV_ATOMIC_T"
+Libev requires an integer type (suitable for storing \f(CW0\fR or \f(CW1\fR) whose
+access is atomic with respect to other threads or signal contexts. No
+such type is easily found in the C language, so you can provide your own
+type that you know is safe for your purposes. It is used both for signal
+handler \*(L"locking\*(R" as well as for signal and thread safety in \f(CW\*(C`ev_async\*(C'\fR
+watchers.
+.Sp
+In the absence of this define, libev will use \f(CW\*(C`sig_atomic_t volatile\*(C'\fR
+(from \fIsignal.h\fR), which is usually good enough on most platforms.
+.IP "\s-1EV_H\s0 (h)" 4
+.IX Item "EV_H (h)"
+The name of the \fIev.h\fR header file used to include it. The default if
+undefined is \f(CW"ev.h"\fR in \fIevent.h\fR, \fIev.c\fR and \fIev++.h\fR. This can be
+used to virtually rename the \fIev.h\fR header file in case of conflicts.
+.IP "\s-1EV_CONFIG_H\s0 (h)" 4
+.IX Item "EV_CONFIG_H (h)"
+If \f(CW\*(C`EV_STANDALONE\*(C'\fR isn't \f(CW1\fR, this variable can be used to override
+\&\fIev.c\fR's idea of where to find the \fIconfig.h\fR file, similarly to
+\&\f(CW\*(C`EV_H\*(C'\fR, above.
+.IP "\s-1EV_EVENT_H\s0 (h)" 4
+.IX Item "EV_EVENT_H (h)"
+Similarly to \f(CW\*(C`EV_H\*(C'\fR, this macro can be used to override \fIevent.c\fR's idea
+of how the \fIevent.h\fR header can be found, the default is \f(CW"event.h"\fR.
+.IP "\s-1EV_PROTOTYPES\s0 (h)" 4
+.IX Item "EV_PROTOTYPES (h)"
+If defined to be \f(CW0\fR, then \fIev.h\fR will not define any function
+prototypes, but still define all the structs and other symbols. This is
+occasionally useful if you want to provide your own wrapper functions
+around libev functions.
+.IP "\s-1EV_MULTIPLICITY\s0" 4
+.IX Item "EV_MULTIPLICITY"
+If undefined or defined to \f(CW1\fR, then all event-loop-specific functions
+will have the \f(CW\*(C`struct ev_loop *\*(C'\fR as first argument, and you can create
+additional independent event loops. Otherwise there will be no support
+for multiple event loops and there is no first event loop pointer
+argument. Instead, all functions act on the single default loop.
+.Sp
+Note that \f(CW\*(C`EV_DEFAULT\*(C'\fR and \f(CW\*(C`EV_DEFAULT_\*(C'\fR will no longer provide a
+default loop when multiplicity is switched off \- you always have to
+initialise the loop manually in this case.
+.IP "\s-1EV_MINPRI\s0" 4
+.IX Item "EV_MINPRI"
+.PD 0
+.IP "\s-1EV_MAXPRI\s0" 4
+.IX Item "EV_MAXPRI"
+.PD
+The range of allowed priorities. \f(CW\*(C`EV_MINPRI\*(C'\fR must be smaller or equal to
+\&\f(CW\*(C`EV_MAXPRI\*(C'\fR, but otherwise there are no non-obvious limitations. You can
+provide for more priorities by overriding those symbols (usually defined
+to be \f(CW\*(C`\-2\*(C'\fR and \f(CW2\fR, respectively).
+.Sp
+When doing priority-based operations, libev usually has to linearly search
+all the priorities, so having many of them (hundreds) uses a lot of space
+and time, so using the defaults of five priorities (\-2 .. +2) is usually
+fine.
+.Sp
+If your embedding application does not need any priorities, defining these
+both to \f(CW0\fR will save some memory and \s-1CPU.\s0
+.IP "\s-1EV_PERIODIC_ENABLE, EV_IDLE_ENABLE, EV_EMBED_ENABLE, EV_STAT_ENABLE, EV_PREPARE_ENABLE, EV_CHECK_ENABLE, EV_FORK_ENABLE, EV_SIGNAL_ENABLE, EV_ASYNC_ENABLE, EV_CHILD_ENABLE.\s0" 4
+.IX Item "EV_PERIODIC_ENABLE, EV_IDLE_ENABLE, EV_EMBED_ENABLE, EV_STAT_ENABLE, EV_PREPARE_ENABLE, EV_CHECK_ENABLE, EV_FORK_ENABLE, EV_SIGNAL_ENABLE, EV_ASYNC_ENABLE, EV_CHILD_ENABLE."
+If undefined or defined to be \f(CW1\fR (and the platform supports it), then
+the respective watcher type is supported. If defined to be \f(CW0\fR, then it
+is not. Disabling watcher types mainly saves code size.
+.IP "\s-1EV_FEATURES\s0" 4
+.IX Item "EV_FEATURES"
+If you need to shave off some kilobytes of code at the expense of some
+speed (but with the full \s-1API\s0), you can define this symbol to request
+certain subsets of functionality. The default is to enable all features
+that can be enabled on the platform.
+.Sp
+A typical way to use this symbol is to define it to \f(CW0\fR (or to a bitset
+with some broad features you want) and then selectively re-enable
+additional parts you want, for example if you want everything minimal,
+but multiple event loop support, async and child watchers and the poll
+backend, use this:
+.Sp
+.Vb 5
+\& #define EV_FEATURES 0
+\& #define EV_MULTIPLICITY 1
+\& #define EV_USE_POLL 1
+\& #define EV_CHILD_ENABLE 1
+\& #define EV_ASYNC_ENABLE 1
+.Ve
+.Sp
+The actual value is a bitset, it can be a combination of the following
+values (by default, all of these are enabled):
+.RS 4
+.ie n .IP "1 \- faster/larger code" 4
+.el .IP "\f(CW1\fR \- faster/larger code" 4
+.IX Item "1 - faster/larger code"
+Use larger code to speed up some operations.
+.Sp
+Currently this is used to override some inlining decisions (enlarging the
+code size by roughly 30% on amd64).
+.Sp
+When optimising for size, use of compiler flags such as \f(CW\*(C`\-Os\*(C'\fR with
+gcc is recommended, as well as \f(CW\*(C`\-DNDEBUG\*(C'\fR, as libev contains a number of
+assertions.
+.Sp
+The default is off when \f(CW\*(C`_\|_OPTIMIZE_SIZE_\|_\*(C'\fR is defined by your compiler
+(e.g. gcc with \f(CW\*(C`\-Os\*(C'\fR).
+.ie n .IP "2 \- faster/larger data structures" 4
+.el .IP "\f(CW2\fR \- faster/larger data structures" 4
+.IX Item "2 - faster/larger data structures"
+Replaces the small 2\-heap for timer management by a faster 4\-heap, larger
+hash table sizes and so on. This will usually further increase code size
+and can additionally have an effect on the size of data structures at
+runtime.
+.Sp
+The default is off when \f(CW\*(C`_\|_OPTIMIZE_SIZE_\|_\*(C'\fR is defined by your compiler
+(e.g. gcc with \f(CW\*(C`\-Os\*(C'\fR).
+.ie n .IP "4 \- full \s-1API\s0 configuration" 4
+.el .IP "\f(CW4\fR \- full \s-1API\s0 configuration" 4
+.IX Item "4 - full API configuration"
+This enables priorities (sets \f(CW\*(C`EV_MAXPRI\*(C'\fR=2 and \f(CW\*(C`EV_MINPRI\*(C'\fR=\-2), and
+enables multiplicity (\f(CW\*(C`EV_MULTIPLICITY\*(C'\fR=1).
+.ie n .IP "8 \- full \s-1API\s0" 4
+.el .IP "\f(CW8\fR \- full \s-1API\s0" 4
+.IX Item "8 - full API"
+This enables a lot of the \*(L"lesser used\*(R" \s-1API\s0 functions. See \f(CW\*(C`ev.h\*(C'\fR for
+details on which parts of the \s-1API\s0 are still available without this
+feature, and do not complain if this subset changes over time.
+.ie n .IP "16 \- enable all optional watcher types" 4
+.el .IP "\f(CW16\fR \- enable all optional watcher types" 4
+.IX Item "16 - enable all optional watcher types"
+Enables all optional watcher types. If you want to selectively enable
+only some watcher types other than I/O and timers (e.g. prepare,
+embed, async, child...) you can enable them manually by defining
+\&\f(CW\*(C`EV_watchertype_ENABLE\*(C'\fR to \f(CW1\fR instead.
+.ie n .IP "32 \- enable all backends" 4
+.el .IP "\f(CW32\fR \- enable all backends" 4
+.IX Item "32 - enable all backends"
+This enables all backends \- without this feature, you need to enable at
+least one backend manually (\f(CW\*(C`EV_USE_SELECT\*(C'\fR is a good choice).
+.ie n .IP "64 \- enable OS-specific ""helper"" APIs" 4
+.el .IP "\f(CW64\fR \- enable OS-specific ``helper'' APIs" 4
+.IX Item "64 - enable OS-specific helper APIs"
+Enable inotify, eventfd, signalfd and similar OS-specific helper APIs by
+default.
+.RE
+.RS 4
+.Sp
+Compiling with \f(CW\*(C`gcc \-Os \-DEV_STANDALONE \-DEV_USE_EPOLL=1 \-DEV_FEATURES=0\*(C'\fR
+reduces the compiled size of libev from 24.7Kb code/2.8Kb data to 6.5Kb
+code/0.3Kb data on my GNU/Linux amd64 system, while still giving you I/O
+watchers, timers and monotonic clock support.
+.Sp
+With an intelligent-enough linker (gcc+binutils are intelligent enough
+when you use \f(CW\*(C`\-Wl,\-\-gc\-sections \-ffunction\-sections\*(C'\fR) functions unused by
+your program might be left out as well \- a binary starting a timer and an
+I/O watcher then might come out at only 5Kb.
+.RE
+.IP "\s-1EV_API_STATIC\s0" 4
+.IX Item "EV_API_STATIC"
+If this symbol is defined (by default it is not), then all identifiers
+will have static linkage. This means that libev will not export any
+identifiers, and you cannot link against libev anymore. This can be useful
+when you embed libev, only want to use libev functions in a single file,
+and do not want its identifiers to be visible.
+.Sp
+To use this, define \f(CW\*(C`EV_API_STATIC\*(C'\fR and include \fIev.c\fR in the file that
+wants to use libev.
+.Sp
+This option only works when libev is compiled with a C compiler, as \*(C+
+doesn't support the required declaration syntax.
+.IP "\s-1EV_AVOID_STDIO\s0" 4
+.IX Item "EV_AVOID_STDIO"
+If this is set to \f(CW1\fR at compiletime, then libev will avoid using stdio
+functions (printf, scanf, perror etc.). This will increase the code size
+somewhat, but if your program doesn't otherwise depend on stdio and your
+libc allows it, this avoids linking in the stdio library which is quite
+big.
+.Sp
+Note that error messages might become less precise when this option is
+enabled.
+.IP "\s-1EV_NSIG\s0" 4
+.IX Item "EV_NSIG"
+The highest supported signal number, +1 (or, the number of
+signals): Normally, libev tries to deduce the maximum number of signals
+automatically, but sometimes this fails, in which case it can be
+specified. Also, using a lower number than detected (\f(CW32\fR should be
+good for about any system in existence) can save some memory, as libev
+statically allocates some 12\-24 bytes per signal number.
+.IP "\s-1EV_PID_HASHSIZE\s0" 4
+.IX Item "EV_PID_HASHSIZE"
+\&\f(CW\*(C`ev_child\*(C'\fR watchers use a small hash table to distribute workload by
+pid. The default size is \f(CW16\fR (or \f(CW1\fR with \f(CW\*(C`EV_FEATURES\*(C'\fR disabled),
+usually more than enough. If you need to manage thousands of children you
+might want to increase this value (\fImust\fR be a power of two).
+.IP "\s-1EV_INOTIFY_HASHSIZE\s0" 4
+.IX Item "EV_INOTIFY_HASHSIZE"
+\&\f(CW\*(C`ev_stat\*(C'\fR watchers use a small hash table to distribute workload by
+inotify watch id. The default size is \f(CW16\fR (or \f(CW1\fR with \f(CW\*(C`EV_FEATURES\*(C'\fR
+disabled), usually more than enough. If you need to manage thousands of
+\&\f(CW\*(C`ev_stat\*(C'\fR watchers you might want to increase this value (\fImust\fR be a
+power of two).
+.IP "\s-1EV_USE_4HEAP\s0" 4
+.IX Item "EV_USE_4HEAP"
+Heaps are not very cache-efficient. To improve the cache-efficiency of the
+timer and periodics heaps, libev uses a 4\-heap when this symbol is defined
+to \f(CW1\fR. The 4\-heap uses more complicated (longer) code but has noticeably
+faster performance with many (thousands) of watchers.
+.Sp
+The default is \f(CW1\fR, unless \f(CW\*(C`EV_FEATURES\*(C'\fR overrides it, in which case it
+will be \f(CW0\fR.
+.IP "\s-1EV_HEAP_CACHE_AT\s0" 4
+.IX Item "EV_HEAP_CACHE_AT"
+Heaps are not very cache-efficient. To improve the cache-efficiency of the
+timer and periodics heaps, libev can cache the timestamp (\fIat\fR) within
+the heap structure (selected by defining \f(CW\*(C`EV_HEAP_CACHE_AT\*(C'\fR to \f(CW1\fR),
+which uses 8\-12 bytes more per watcher and a few hundred bytes more code,
+but avoids random read accesses on heap changes. This improves performance
+noticeably with many (hundreds) of watchers.
+.Sp
+The default is \f(CW1\fR, unless \f(CW\*(C`EV_FEATURES\*(C'\fR overrides it, in which case it
+will be \f(CW0\fR.
+.IP "\s-1EV_VERIFY\s0" 4
+.IX Item "EV_VERIFY"
+Controls how much internal verification (see \f(CW\*(C`ev_verify ()\*(C'\fR) will
+be done: If set to \f(CW0\fR, no internal verification code will be compiled
+in. If set to \f(CW1\fR, then verification code will be compiled in, but not
+called. If set to \f(CW2\fR, then the internal verification code will be
+called once per loop, which can slow down libev. If set to \f(CW3\fR, then the
+verification code will be called very frequently, which will slow down
+libev considerably.
+.Sp
+Verification errors are reported via C's \f(CW\*(C`assert\*(C'\fR mechanism, so if you
+disable that (e.g. by defining \f(CW\*(C`NDEBUG\*(C'\fR) then no errors will be reported.
+.Sp
+The default is \f(CW1\fR, unless \f(CW\*(C`EV_FEATURES\*(C'\fR overrides it, in which case it
+will be \f(CW0\fR.
+.IP "\s-1EV_COMMON\s0" 4
+.IX Item "EV_COMMON"
+By default, all watchers have a \f(CW\*(C`void *data\*(C'\fR member. By redefining
+this macro to something else you can include more and other types of
+members. You have to define it each time you include one of the files,
+though, and it must be identical each time.
+.Sp
+For example, the perl \s-1EV\s0 module uses something like this:
+.Sp
+.Vb 3
+\& #define EV_COMMON \e
+\& SV *self; /* contains this struct */ \e
+\& SV *cb_sv, *fh /* note no trailing ";" */
+.Ve
+.IP "\s-1EV_CB_DECLARE\s0 (type)" 4
+.IX Item "EV_CB_DECLARE (type)"
+.PD 0
+.IP "\s-1EV_CB_INVOKE\s0 (watcher, revents)" 4
+.IX Item "EV_CB_INVOKE (watcher, revents)"
+.IP "ev_set_cb (ev, cb)" 4
+.IX Item "ev_set_cb (ev, cb)"
+.PD
+Can be used to change the callback member declaration in each watcher,
+and the way callbacks are invoked and set. Must expand to a struct member
+definition and a statement, respectively. See the \fIev.h\fR header file for
+their default definitions. One possible use for overriding these is to
+avoid the \f(CW\*(C`struct ev_loop *\*(C'\fR as first argument in all cases, or to use
+method calls instead of plain function calls in \*(C+.
+.SS "\s-1EXPORTED API SYMBOLS\s0"
+.IX Subsection "EXPORTED API SYMBOLS"
+If you need to re-export the \s-1API\s0 (e.g. via a \s-1DLL\s0) and you need a list of
+exported symbols, you can use the provided \fISymbol.*\fR files which list
+all public symbols, one per line:
+.PP
+.Vb 2
+\& Symbols.ev for libev proper
+\& Symbols.event for the libevent emulation
+.Ve
+.PP
+This can also be used to rename all public symbols to avoid clashes with
+multiple versions of libev linked together (which is obviously bad in
+itself, but sometimes it is inconvenient to avoid this).
+.PP
+A sed command like this will create wrapper \f(CW\*(C`#define\*(C'\fR's that you need to
+include before including \fIev.h\fR:
+.PP
+.Vb 1
+\& <Symbols.ev sed \-e "s/.*/#define & myprefix_&/" >wrap.h
+.Ve
+.PP
+This would create a file \fIwrap.h\fR which essentially looks like this:
+.PP
+.Vb 4
+\& #define ev_backend myprefix_ev_backend
+\& #define ev_check_start myprefix_ev_check_start
+\& #define ev_check_stop myprefix_ev_check_stop
+\& ...
+.Ve
+.SS "\s-1EXAMPLES\s0"
+.IX Subsection "EXAMPLES"
+For a real-world example of a program the includes libev
+verbatim, you can have a look at the \s-1EV\s0 perl module
+(<http://software.schmorp.de/pkg/EV.html>). It has the libev files in
+the \fIlibev/\fR subdirectory and includes them in the \fI\s-1EV/EVAPI\s0.h\fR (public
+interface) and \fI\s-1EV\s0.xs\fR (implementation) files. Only the \fI\s-1EV\s0.xs\fR file
+will be compiled. It is pretty complex because it provides its own header
+file.
+.PP
+The usage in rxvt-unicode is simpler. It has a \fIev_cpp.h\fR header file
+that everybody includes and which overrides some configure choices:
+.PP
+.Vb 8
+\& #define EV_FEATURES 8
+\& #define EV_USE_SELECT 1
+\& #define EV_PREPARE_ENABLE 1
+\& #define EV_IDLE_ENABLE 1
+\& #define EV_SIGNAL_ENABLE 1
+\& #define EV_CHILD_ENABLE 1
+\& #define EV_USE_STDEXCEPT 0
+\& #define EV_CONFIG_H <config.h>
+\&
+\& #include "ev++.h"
+.Ve
+.PP
+And a \fIev_cpp.C\fR implementation file that contains libev proper and is compiled:
+.PP
+.Vb 2
+\& #include "ev_cpp.h"
+\& #include "ev.c"
+.Ve
+.SH "INTERACTION WITH OTHER PROGRAMS, LIBRARIES OR THE ENVIRONMENT"
+.IX Header "INTERACTION WITH OTHER PROGRAMS, LIBRARIES OR THE ENVIRONMENT"
+.SS "\s-1THREADS AND COROUTINES\s0"
+.IX Subsection "THREADS AND COROUTINES"
+\fI\s-1THREADS\s0\fR
+.IX Subsection "THREADS"
+.PP
+All libev functions are reentrant and thread-safe unless explicitly
+documented otherwise, but libev implements no locking itself. This means
+that you can use as many loops as you want in parallel, as long as there
+are no concurrent calls into any libev function with the same loop
+parameter (\f(CW\*(C`ev_default_*\*(C'\fR calls have an implicit default loop parameter,
+of course): libev guarantees that different event loops share no data
+structures that need any locking.
+.PP
+Or to put it differently: calls with different loop parameters can be done
+concurrently from multiple threads, calls with the same loop parameter
+must be done serially (but can be done from different threads, as long as
+only one thread ever is inside a call at any point in time, e.g. by using
+a mutex per loop).
+.PP
+Specifically to support threads (and signal handlers), libev implements
+so-called \f(CW\*(C`ev_async\*(C'\fR watchers, which allow some limited form of
+concurrency on the same event loop, namely waking it up \*(L"from the
+outside\*(R".
+.PP
+If you want to know which design (one loop, locking, or multiple loops
+without or something else still) is best for your problem, then I cannot
+help you, but here is some generic advice:
+.IP "\(bu" 4
+most applications have a main thread: use the default libev loop
+in that thread, or create a separate thread running only the default loop.
+.Sp
+This helps integrating other libraries or software modules that use libev
+themselves and don't care/know about threading.
+.IP "\(bu" 4
+one loop per thread is usually a good model.
+.Sp
+Doing this is almost never wrong, sometimes a better-performance model
+exists, but it is always a good start.
+.IP "\(bu" 4
+other models exist, such as the leader/follower pattern, where one
+loop is handed through multiple threads in a kind of round-robin fashion.
+.Sp
+Choosing a model is hard \- look around, learn, know that usually you can do
+better than you currently do :\-)
+.IP "\(bu" 4
+often you need to talk to some other thread which blocks in the
+event loop.
+.Sp
+\&\f(CW\*(C`ev_async\*(C'\fR watchers can be used to wake them up from other threads safely
+(or from signal contexts...).
+.Sp
+An example use would be to communicate signals or other events that only
+work in the default loop by registering the signal watcher with the
+default loop and triggering an \f(CW\*(C`ev_async\*(C'\fR watcher from the default loop
+watcher callback into the event loop interested in the signal.
+.PP
+See also \*(L"\s-1THREAD LOCKING EXAMPLE\*(R"\s0.
+.PP
+\fI\s-1COROUTINES\s0\fR
+.IX Subsection "COROUTINES"
+.PP
+Libev is very accommodating to coroutines (\*(L"cooperative threads\*(R"):
+libev fully supports nesting calls to its functions from different
+coroutines (e.g. you can call \f(CW\*(C`ev_run\*(C'\fR on the same loop from two
+different coroutines, and switch freely between both coroutines running
+the loop, as long as you don't confuse yourself). The only exception is
+that you must not do this from \f(CW\*(C`ev_periodic\*(C'\fR reschedule callbacks.
+.PP
+Care has been taken to ensure that libev does not keep local state inside
+\&\f(CW\*(C`ev_run\*(C'\fR, and other calls do not usually allow for coroutine switches as
+they do not call any callbacks.
+.SS "\s-1COMPILER WARNINGS\s0"
+.IX Subsection "COMPILER WARNINGS"
+Depending on your compiler and compiler settings, you might get no or a
+lot of warnings when compiling libev code. Some people are apparently
+scared by this.
+.PP
+However, these are unavoidable for many reasons. For one, each compiler
+has different warnings, and each user has different tastes regarding
+warning options. \*(L"Warn-free\*(R" code therefore cannot be a goal except when
+targeting a specific compiler and compiler-version.
+.PP
+Another reason is that some compiler warnings require elaborate
+workarounds, or other changes to the code that make it less clear and less
+maintainable.
+.PP
+And of course, some compiler warnings are just plain stupid, or simply
+wrong (because they don't actually warn about the condition their message
+seems to warn about). For example, certain older gcc versions had some
+warnings that resulted in an extreme number of false positives. These have
+been fixed, but some people still insist on making code warn-free with
+such buggy versions.
+.PP
+While libev is written to generate as few warnings as possible,
+\&\*(L"warn-free\*(R" code is not a goal, and it is recommended not to build libev
+with any compiler warnings enabled unless you are prepared to cope with
+them (e.g. by ignoring them). Remember that warnings are just that:
+warnings, not errors, or proof of bugs.
+.SS "\s-1VALGRIND\s0"
+.IX Subsection "VALGRIND"
+Valgrind has a special section here because it is a popular tool that is
+highly useful. Unfortunately, valgrind reports are very hard to interpret.
+.PP
+If you think you found a bug (memory leak, uninitialised data access etc.)
+in libev, then check twice: If valgrind reports something like:
+.PP
+.Vb 3
+\& ==2274== definitely lost: 0 bytes in 0 blocks.
+\& ==2274== possibly lost: 0 bytes in 0 blocks.
+\& ==2274== still reachable: 256 bytes in 1 blocks.
+.Ve
+.PP
+Then there is no memory leak, just as memory accounted to global variables
+is not a memleak \- the memory is still being referenced, and didn't leak.
+.PP
+Similarly, under some circumstances, valgrind might report kernel bugs
+as if it were a bug in libev (e.g. in realloc or in the poll backend,
+although an acceptable workaround has been found here), or it might be
+confused.
+.PP
+Keep in mind that valgrind is a very good tool, but only a tool. Don't
+make it into some kind of religion.
+.PP
+If you are unsure about something, feel free to contact the mailing list
+with the full valgrind report and an explanation on why you think this
+is a bug in libev (best check the archives, too :). However, don't be
+annoyed when you get a brisk \*(L"this is no bug\*(R" answer and take the chance
+of learning how to interpret valgrind properly.
+.PP
+If you need, for some reason, empty reports from valgrind for your project
+I suggest using suppression lists.
+.SH "PORTABILITY NOTES"
+.IX Header "PORTABILITY NOTES"
+.SS "\s-1GNU/LINUX 32 BIT LIMITATIONS\s0"
+.IX Subsection "GNU/LINUX 32 BIT LIMITATIONS"
+GNU/Linux is the only common platform that supports 64 bit file/large file
+interfaces but \fIdisables\fR them by default.
+.PP
+That means that libev compiled in the default environment doesn't support
+files larger than 2GiB or so, which mainly affects \f(CW\*(C`ev_stat\*(C'\fR watchers.
+.PP
+Unfortunately, many programs try to work around this GNU/Linux issue
+by enabling the large file \s-1API,\s0 which makes them incompatible with the
+standard libev compiled for their system.
+.PP
+Likewise, libev cannot enable the large file \s-1API\s0 itself as this would
+suddenly make it incompatible to the default compile time environment,
+i.e. all programs not using special compile switches.
+.SS "\s-1OS/X AND DARWIN BUGS\s0"
+.IX Subsection "OS/X AND DARWIN BUGS"
+The whole thing is a bug if you ask me \- basically any system interface
+you touch is broken, whether it is locales, poll, kqueue or even the
+OpenGL drivers.
+.PP
+\fI\f(CI\*(C`kqueue\*(C'\fI is buggy\fR
+.IX Subsection "kqueue is buggy"
+.PP
+The kqueue syscall is broken in all known versions \- most versions support
+only sockets, many support pipes.
+.PP
+Libev tries to work around this by not using \f(CW\*(C`kqueue\*(C'\fR by default on this
+rotten platform, but of course you can still ask for it when creating a
+loop \- embedding a socket-only kqueue loop into a select-based one is
+probably going to work well.
+.PP
+\fI\f(CI\*(C`poll\*(C'\fI is buggy\fR
+.IX Subsection "poll is buggy"
+.PP
+Instead of fixing \f(CW\*(C`kqueue\*(C'\fR, Apple replaced their (working) \f(CW\*(C`poll\*(C'\fR
+implementation by something calling \f(CW\*(C`kqueue\*(C'\fR internally around the 10.5.6
+release, so now \f(CW\*(C`kqueue\*(C'\fR \fIand\fR \f(CW\*(C`poll\*(C'\fR are broken.
+.PP
+Libev tries to work around this by not using \f(CW\*(C`poll\*(C'\fR by default on
+this rotten platform, but of course you can still ask for it when creating
+a loop.
+.PP
+\fI\f(CI\*(C`select\*(C'\fI is buggy\fR
+.IX Subsection "select is buggy"
+.PP
+All that's left is \f(CW\*(C`select\*(C'\fR, and of course Apple found a way to fuck this
+one up as well: On \s-1OS/X,\s0 \f(CW\*(C`select\*(C'\fR actively limits the number of file
+descriptors you can pass in to 1024 \- your program suddenly crashes when
+you use more.
+.PP
+There is an undocumented \*(L"workaround\*(R" for this \- defining
+\&\f(CW\*(C`_DARWIN_UNLIMITED_SELECT\*(C'\fR, which libev tries to use, so select \fIshould\fR
+work on \s-1OS/X.\s0
+.SS "\s-1SOLARIS PROBLEMS AND WORKAROUNDS\s0"
+.IX Subsection "SOLARIS PROBLEMS AND WORKAROUNDS"
+\fI\f(CI\*(C`errno\*(C'\fI reentrancy\fR
+.IX Subsection "errno reentrancy"
+.PP
+The default compile environment on Solaris is unfortunately so
+thread-unsafe that you can't even use components/libraries compiled
+without \f(CW\*(C`\-D_REENTRANT\*(C'\fR in a threaded program, which, of course, isn't
+defined by default. A valid, if stupid, implementation choice.
+.PP
+If you want to use libev in threaded environments you have to make sure
+it's compiled with \f(CW\*(C`_REENTRANT\*(C'\fR defined.
+.PP
+\fIEvent port backend\fR
+.IX Subsection "Event port backend"
+.PP
+The scalable event interface for Solaris is called \*(L"event
+ports\*(R". Unfortunately, this mechanism is very buggy in all major
+releases. If you run into high \s-1CPU\s0 usage, your program freezes or you get
+a large number of spurious wakeups, make sure you have all the relevant
+and latest kernel patches applied. No, I don't know which ones, but there
+are multiple ones to apply, and afterwards, event ports actually work
+great.
+.PP
+If you can't get it to work, you can try running the program by setting
+the environment variable \f(CW\*(C`LIBEV_FLAGS=3\*(C'\fR to only allow \f(CW\*(C`poll\*(C'\fR and
+\&\f(CW\*(C`select\*(C'\fR backends.
+.SS "\s-1AIX POLL BUG\s0"
+.IX Subsection "AIX POLL BUG"
+\&\s-1AIX\s0 unfortunately has a broken \f(CW\*(C`poll.h\*(C'\fR header. Libev works around
+this by trying to avoid the poll backend altogether (i.e. it's not even
+compiled in), which normally isn't a big problem as \f(CW\*(C`select\*(C'\fR works fine
+with large bitsets on \s-1AIX,\s0 and \s-1AIX\s0 is dead anyway.
+.SS "\s-1WIN32 PLATFORM LIMITATIONS AND WORKAROUNDS\s0"
+.IX Subsection "WIN32 PLATFORM LIMITATIONS AND WORKAROUNDS"
+\fIGeneral issues\fR
+.IX Subsection "General issues"
+.PP
+Win32 doesn't support any of the standards (e.g. \s-1POSIX\s0) that libev
+requires, and its I/O model is fundamentally incompatible with the \s-1POSIX\s0
+model. Libev still offers limited functionality on this platform in
+the form of the \f(CW\*(C`EVBACKEND_SELECT\*(C'\fR backend, and only supports socket
+descriptors. This only applies when using Win32 natively, not when using
+e.g. cygwin. Actually, it only applies to the microsofts own compilers,
+as every compiler comes with a slightly differently broken/incompatible
+environment.
+.PP
+Lifting these limitations would basically require the full
+re-implementation of the I/O system. If you are into this kind of thing,
+then note that glib does exactly that for you in a very portable way (note
+also that glib is the slowest event library known to man).
+.PP
+There is no supported compilation method available on windows except
+embedding it into other applications.
+.PP
+Sensible signal handling is officially unsupported by Microsoft \- libev
+tries its best, but under most conditions, signals will simply not work.
+.PP
+Not a libev limitation but worth mentioning: windows apparently doesn't
+accept large writes: instead of resulting in a partial write, windows will
+either accept everything or return \f(CW\*(C`ENOBUFS\*(C'\fR if the buffer is too large,
+so make sure you only write small amounts into your sockets (less than a
+megabyte seems safe, but this apparently depends on the amount of memory
+available).
+.PP
+Due to the many, low, and arbitrary limits on the win32 platform and
+the abysmal performance of winsockets, using a large number of sockets
+is not recommended (and not reasonable). If your program needs to use
+more than a hundred or so sockets, then likely it needs to use a totally
+different implementation for windows, as libev offers the \s-1POSIX\s0 readiness
+notification model, which cannot be implemented efficiently on windows
+(due to Microsoft monopoly games).
+.PP
+A typical way to use libev under windows is to embed it (see the embedding
+section for details) and use the following \fIevwrap.h\fR header file instead
+of \fIev.h\fR:
+.PP
+.Vb 2
+\& #define EV_STANDALONE /* keeps ev from requiring config.h */
+\& #define EV_SELECT_IS_WINSOCKET 1 /* configure libev for windows select */
+\&
+\& #include "ev.h"
+.Ve
+.PP
+And compile the following \fIevwrap.c\fR file into your project (make sure
+you do \fInot\fR compile the \fIev.c\fR or any other embedded source files!):
+.PP
+.Vb 2
+\& #include "evwrap.h"
+\& #include "ev.c"
+.Ve
+.PP
+\fIThe winsocket \f(CI\*(C`select\*(C'\fI function\fR
+.IX Subsection "The winsocket select function"
+.PP
+The winsocket \f(CW\*(C`select\*(C'\fR function doesn't follow \s-1POSIX\s0 in that it
+requires socket \fIhandles\fR and not socket \fIfile descriptors\fR (it is
+also extremely buggy). This makes select very inefficient, and also
+requires a mapping from file descriptors to socket handles (the Microsoft
+C runtime provides the function \f(CW\*(C`_open_osfhandle\*(C'\fR for this). See the
+discussion of the \f(CW\*(C`EV_SELECT_USE_FD_SET\*(C'\fR, \f(CW\*(C`EV_SELECT_IS_WINSOCKET\*(C'\fR and
+\&\f(CW\*(C`EV_FD_TO_WIN32_HANDLE\*(C'\fR preprocessor symbols for more info.
+.PP
+The configuration for a \*(L"naked\*(R" win32 using the Microsoft runtime
+libraries and raw winsocket select is:
+.PP
+.Vb 2
+\& #define EV_USE_SELECT 1
+\& #define EV_SELECT_IS_WINSOCKET 1 /* forces EV_SELECT_USE_FD_SET, too */
+.Ve
+.PP
+Note that winsockets handling of fd sets is O(n), so you can easily get a
+complexity in the O(nX) range when using win32.
+.PP
+\fILimited number of file descriptors\fR
+.IX Subsection "Limited number of file descriptors"
+.PP
+Windows has numerous arbitrary (and low) limits on things.
+.PP
+Early versions of winsocket's select only supported waiting for a maximum
+of \f(CW64\fR handles (probably owning to the fact that all windows kernels
+can only wait for \f(CW64\fR things at the same time internally; Microsoft
+recommends spawning a chain of threads and wait for 63 handles and the
+previous thread in each. Sounds great!).
+.PP
+Newer versions support more handles, but you need to define \f(CW\*(C`FD_SETSIZE\*(C'\fR
+to some high number (e.g. \f(CW2048\fR) before compiling the winsocket select
+call (which might be in libev or elsewhere, for example, perl and many
+other interpreters do their own select emulation on windows).
+.PP
+Another limit is the number of file descriptors in the Microsoft runtime
+libraries, which by default is \f(CW64\fR (there must be a hidden \fI64\fR
+fetish or something like this inside Microsoft). You can increase this
+by calling \f(CW\*(C`_setmaxstdio\*(C'\fR, which can increase this limit to \f(CW2048\fR
+(another arbitrary limit), but is broken in many versions of the Microsoft
+runtime libraries. This might get you to about \f(CW512\fR or \f(CW2048\fR sockets
+(depending on windows version and/or the phase of the moon). To get more,
+you need to wrap all I/O functions and provide your own fd management, but
+the cost of calling select (O(nX)) will likely make this unworkable.
+.SS "\s-1PORTABILITY REQUIREMENTS\s0"
+.IX Subsection "PORTABILITY REQUIREMENTS"
+In addition to a working ISO-C implementation and of course the
+backend-specific APIs, libev relies on a few additional extensions:
+.ie n .IP """void (*)(ev_watcher_type *, int revents)"" must have compatible calling conventions regardless of ""ev_watcher_type *""." 4
+.el .IP "\f(CWvoid (*)(ev_watcher_type *, int revents)\fR must have compatible calling conventions regardless of \f(CWev_watcher_type *\fR." 4
+.IX Item "void (*)(ev_watcher_type *, int revents) must have compatible calling conventions regardless of ev_watcher_type *."
+Libev assumes not only that all watcher pointers have the same internal
+structure (guaranteed by \s-1POSIX\s0 but not by \s-1ISO C\s0 for example), but it also
+assumes that the same (machine) code can be used to call any watcher
+callback: The watcher callbacks have different type signatures, but libev
+calls them using an \f(CW\*(C`ev_watcher *\*(C'\fR internally.
+.IP "null pointers and integer zero are represented by 0 bytes" 4
+.IX Item "null pointers and integer zero are represented by 0 bytes"
+Libev uses \f(CW\*(C`memset\*(C'\fR to initialise structs and arrays to \f(CW0\fR bytes, and
+relies on this setting pointers and integers to null.
+.IP "pointer accesses must be thread-atomic" 4
+.IX Item "pointer accesses must be thread-atomic"
+Accessing a pointer value must be atomic, it must both be readable and
+writable in one piece \- this is the case on all current architectures.
+.ie n .IP """sig_atomic_t volatile"" must be thread-atomic as well" 4
+.el .IP "\f(CWsig_atomic_t volatile\fR must be thread-atomic as well" 4
+.IX Item "sig_atomic_t volatile must be thread-atomic as well"
+The type \f(CW\*(C`sig_atomic_t volatile\*(C'\fR (or whatever is defined as
+\&\f(CW\*(C`EV_ATOMIC_T\*(C'\fR) must be atomic with respect to accesses from different
+threads. This is not part of the specification for \f(CW\*(C`sig_atomic_t\*(C'\fR, but is
+believed to be sufficiently portable.
+.ie n .IP """sigprocmask"" must work in a threaded environment" 4
+.el .IP "\f(CWsigprocmask\fR must work in a threaded environment" 4
+.IX Item "sigprocmask must work in a threaded environment"
+Libev uses \f(CW\*(C`sigprocmask\*(C'\fR to temporarily block signals. This is not
+allowed in a threaded program (\f(CW\*(C`pthread_sigmask\*(C'\fR has to be used). Typical
+pthread implementations will either allow \f(CW\*(C`sigprocmask\*(C'\fR in the \*(L"main
+thread\*(R" or will block signals process-wide, both behaviours would
+be compatible with libev. Interaction between \f(CW\*(C`sigprocmask\*(C'\fR and
+\&\f(CW\*(C`pthread_sigmask\*(C'\fR could complicate things, however.
+.Sp
+The most portable way to handle signals is to block signals in all threads
+except the initial one, and run the signal handling loop in the initial
+thread as well.
+.ie n .IP """long"" must be large enough for common memory allocation sizes" 4
+.el .IP "\f(CWlong\fR must be large enough for common memory allocation sizes" 4
+.IX Item "long must be large enough for common memory allocation sizes"
+To improve portability and simplify its \s-1API,\s0 libev uses \f(CW\*(C`long\*(C'\fR internally
+instead of \f(CW\*(C`size_t\*(C'\fR when allocating its data structures. On non-POSIX
+systems (Microsoft...) this might be unexpectedly low, but is still at
+least 31 bits everywhere, which is enough for hundreds of millions of
+watchers.
+.ie n .IP """double"" must hold a time value in seconds with enough accuracy" 4
+.el .IP "\f(CWdouble\fR must hold a time value in seconds with enough accuracy" 4
+.IX Item "double must hold a time value in seconds with enough accuracy"
+The type \f(CW\*(C`double\*(C'\fR is used to represent timestamps. It is required to
+have at least 51 bits of mantissa (and 9 bits of exponent), which is
+good enough for at least into the year 4000 with millisecond accuracy
+(the design goal for libev). This requirement is overfulfilled by
+implementations using \s-1IEEE 754,\s0 which is basically all existing ones.
+.Sp
+With \s-1IEEE 754\s0 doubles, you get microsecond accuracy until at least the
+year 2255 (and millisecond accuracy till the year 287396 \- by then, libev
+is either obsolete or somebody patched it to use \f(CW\*(C`long double\*(C'\fR or
+something like that, just kidding).
+.PP
+If you know of other additional requirements drop me a note.
+.SH "ALGORITHMIC COMPLEXITIES"
+.IX Header "ALGORITHMIC COMPLEXITIES"
+In this section the complexities of (many of) the algorithms used inside
+libev will be documented. For complexity discussions about backends see
+the documentation for \f(CW\*(C`ev_default_init\*(C'\fR.
+.PP
+All of the following are about amortised time: If an array needs to be
+extended, libev needs to realloc and move the whole array, but this
+happens asymptotically rarer with higher number of elements, so O(1) might
+mean that libev does a lengthy realloc operation in rare cases, but on
+average it is much faster and asymptotically approaches constant time.
+.IP "Starting and stopping timer/periodic watchers: O(log skipped_other_timers)" 4
+.IX Item "Starting and stopping timer/periodic watchers: O(log skipped_other_timers)"
+This means that, when you have a watcher that triggers in one hour and
+there are 100 watchers that would trigger before that, then inserting will
+have to skip roughly seven (\f(CW\*(C`ld 100\*(C'\fR) of these watchers.
+.IP "Changing timer/periodic watchers (by autorepeat or calling again): O(log skipped_other_timers)" 4
+.IX Item "Changing timer/periodic watchers (by autorepeat or calling again): O(log skipped_other_timers)"
+That means that changing a timer costs less than removing/adding them,
+as only the relative motion in the event queue has to be paid for.
+.IP "Starting io/check/prepare/idle/signal/child/fork/async watchers: O(1)" 4
+.IX Item "Starting io/check/prepare/idle/signal/child/fork/async watchers: O(1)"
+These just add the watcher into an array or at the head of a list.
+.IP "Stopping check/prepare/idle/fork/async watchers: O(1)" 4
+.IX Item "Stopping check/prepare/idle/fork/async watchers: O(1)"
+.PD 0
+.IP "Stopping an io/signal/child watcher: O(number_of_watchers_for_this_(fd/signal/pid % \s-1EV_PID_HASHSIZE\s0))" 4
+.IX Item "Stopping an io/signal/child watcher: O(number_of_watchers_for_this_(fd/signal/pid % EV_PID_HASHSIZE))"
+.PD
+These watchers are stored in lists, so they need to be walked to find the
+correct watcher to remove. The lists are usually short (you don't usually
+have many watchers waiting for the same fd or signal: one is typical, two
+is rare).
+.IP "Finding the next timer in each loop iteration: O(1)" 4
+.IX Item "Finding the next timer in each loop iteration: O(1)"
+By virtue of using a binary or 4\-heap, the next timer is always found at a
+fixed position in the storage array.
+.IP "Each change on a file descriptor per loop iteration: O(number_of_watchers_for_this_fd)" 4
+.IX Item "Each change on a file descriptor per loop iteration: O(number_of_watchers_for_this_fd)"
+A change means an I/O watcher gets started or stopped, which requires
+libev to recalculate its status (and possibly tell the kernel, depending
+on backend and whether \f(CW\*(C`ev_io_set\*(C'\fR was used).
+.IP "Activating one watcher (putting it into the pending state): O(1)" 4
+.IX Item "Activating one watcher (putting it into the pending state): O(1)"
+.PD 0
+.IP "Priority handling: O(number_of_priorities)" 4
+.IX Item "Priority handling: O(number_of_priorities)"
+.PD
+Priorities are implemented by allocating some space for each
+priority. When doing priority-based operations, libev usually has to
+linearly search all the priorities, but starting/stopping and activating
+watchers becomes O(1) with respect to priority handling.
+.IP "Sending an ev_async: O(1)" 4
+.IX Item "Sending an ev_async: O(1)"
+.PD 0
+.IP "Processing ev_async_send: O(number_of_async_watchers)" 4
+.IX Item "Processing ev_async_send: O(number_of_async_watchers)"
+.IP "Processing signals: O(max_signal_number)" 4
+.IX Item "Processing signals: O(max_signal_number)"
+.PD
+Sending involves a system call \fIiff\fR there were no other \f(CW\*(C`ev_async_send\*(C'\fR
+calls in the current loop iteration and the loop is currently
+blocked. Checking for async and signal events involves iterating over all
+running async watchers or all signal numbers.
+.SH "PORTING FROM LIBEV 3.X TO 4.X"
+.IX Header "PORTING FROM LIBEV 3.X TO 4.X"
+The major version 4 introduced some incompatible changes to the \s-1API.\s0
+.PP
+At the moment, the \f(CW\*(C`ev.h\*(C'\fR header file provides compatibility definitions
+for all changes, so most programs should still compile. The compatibility
+layer might be removed in later versions of libev, so better update to the
+new \s-1API\s0 early than late.
+.ie n .IP """EV_COMPAT3"" backwards compatibility mechanism" 4
+.el .IP "\f(CWEV_COMPAT3\fR backwards compatibility mechanism" 4
+.IX Item "EV_COMPAT3 backwards compatibility mechanism"
+The backward compatibility mechanism can be controlled by
+\&\f(CW\*(C`EV_COMPAT3\*(C'\fR. See \*(L"\s-1PREPROCESSOR SYMBOLS/MACROS\*(R"\s0 in the \*(L"\s-1EMBEDDING\*(R"\s0
+section.
+.ie n .IP """ev_default_destroy"" and ""ev_default_fork"" have been removed" 4
+.el .IP "\f(CWev_default_destroy\fR and \f(CWev_default_fork\fR have been removed" 4
+.IX Item "ev_default_destroy and ev_default_fork have been removed"
+These calls can be replaced easily by their \f(CW\*(C`ev_loop_xxx\*(C'\fR counterparts:
+.Sp
+.Vb 2
+\& ev_loop_destroy (EV_DEFAULT_UC);
+\& ev_loop_fork (EV_DEFAULT);
+.Ve
+.IP "function/symbol renames" 4
+.IX Item "function/symbol renames"
+A number of functions and symbols have been renamed:
+.Sp
+.Vb 3
+\& ev_loop => ev_run
+\& EVLOOP_NONBLOCK => EVRUN_NOWAIT
+\& EVLOOP_ONESHOT => EVRUN_ONCE
+\&
+\& ev_unloop => ev_break
+\& EVUNLOOP_CANCEL => EVBREAK_CANCEL
+\& EVUNLOOP_ONE => EVBREAK_ONE
+\& EVUNLOOP_ALL => EVBREAK_ALL
+\&
+\& EV_TIMEOUT => EV_TIMER
+\&
+\& ev_loop_count => ev_iteration
+\& ev_loop_depth => ev_depth
+\& ev_loop_verify => ev_verify
+.Ve
+.Sp
+Most functions working on \f(CW\*(C`struct ev_loop\*(C'\fR objects don't have an
+\&\f(CW\*(C`ev_loop_\*(C'\fR prefix, so it was removed; \f(CW\*(C`ev_loop\*(C'\fR, \f(CW\*(C`ev_unloop\*(C'\fR and
+associated constants have been renamed to not collide with the \f(CW\*(C`struct
+ev_loop\*(C'\fR anymore and \f(CW\*(C`EV_TIMER\*(C'\fR now follows the same naming scheme
+as all other watcher types. Note that \f(CW\*(C`ev_loop_fork\*(C'\fR is still called
+\&\f(CW\*(C`ev_loop_fork\*(C'\fR because it would otherwise clash with the \f(CW\*(C`ev_fork\*(C'\fR
+typedef.
+.ie n .IP """EV_MINIMAL"" mechanism replaced by ""EV_FEATURES""" 4
+.el .IP "\f(CWEV_MINIMAL\fR mechanism replaced by \f(CWEV_FEATURES\fR" 4
+.IX Item "EV_MINIMAL mechanism replaced by EV_FEATURES"
+The preprocessor symbol \f(CW\*(C`EV_MINIMAL\*(C'\fR has been replaced by a different
+mechanism, \f(CW\*(C`EV_FEATURES\*(C'\fR. Programs using \f(CW\*(C`EV_MINIMAL\*(C'\fR usually compile
+and work, but the library code will of course be larger.
+.SH "GLOSSARY"
+.IX Header "GLOSSARY"
+.IP "active" 4
+.IX Item "active"
+A watcher is active as long as it has been started and not yet stopped.
+See \*(L"\s-1WATCHER STATES\*(R"\s0 for details.
+.IP "application" 4
+.IX Item "application"
+In this document, an application is whatever is using libev.
+.IP "backend" 4
+.IX Item "backend"
+The part of the code dealing with the operating system interfaces.
+.IP "callback" 4
+.IX Item "callback"
+The address of a function that is called when some event has been
+detected. Callbacks are being passed the event loop, the watcher that
+received the event, and the actual event bitset.
+.IP "callback/watcher invocation" 4
+.IX Item "callback/watcher invocation"
+The act of calling the callback associated with a watcher.
+.IP "event" 4
+.IX Item "event"
+A change of state of some external event, such as data now being available
+for reading on a file descriptor, time having passed or simply not having
+any other events happening anymore.
+.Sp
+In libev, events are represented as single bits (such as \f(CW\*(C`EV_READ\*(C'\fR or
+\&\f(CW\*(C`EV_TIMER\*(C'\fR).
+.IP "event library" 4
+.IX Item "event library"
+A software package implementing an event model and loop.
+.IP "event loop" 4
+.IX Item "event loop"
+An entity that handles and processes external events and converts them
+into callback invocations.
+.IP "event model" 4
+.IX Item "event model"
+The model used to describe how an event loop handles and processes
+watchers and events.
+.IP "pending" 4
+.IX Item "pending"
+A watcher is pending as soon as the corresponding event has been
+detected. See \*(L"\s-1WATCHER STATES\*(R"\s0 for details.
+.IP "real time" 4
+.IX Item "real time"
+The physical time that is observed. It is apparently strictly monotonic :)
+.IP "wall-clock time" 4
+.IX Item "wall-clock time"
+The time and date as shown on clocks. Unlike real time, it can actually
+be wrong and jump forwards and backwards, e.g. when you adjust your
+clock.
+.IP "watcher" 4
+.IX Item "watcher"
+A data structure that describes interest in certain events. Watchers need
+to be started (attached to an event loop) before they can receive events.
+.SH "AUTHOR"
+.IX Header "AUTHOR"
+Marc Lehmann <libev@schmorp.de>, with repeated corrections by Mikael
+Magnusson and Emanuele Giaquinta, and minor corrections by many others.
diff --git a/3rdparty/libev/ev.c b/3rdparty/libev/ev.c
new file mode 100644
index 0000000..ec212a1
--- /dev/null
+++ b/3rdparty/libev/ev.c
@@ -0,0 +1,5627 @@
+/*
+ * libev event processing core, watcher management
+ *
+ * Copyright (c) 2007-2019 Marc Alexander Lehmann <libev@schmorp.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
+ * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
+ * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
+ * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License ("GPL") version 2 or any later version,
+ * in which case the provisions of the GPL are applicable instead of
+ * the above. If you wish to allow the use of your version of this file
+ * only under the terms of the GPL and not to allow others to use your
+ * version of this file under the BSD license, indicate your decision
+ * by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL. If you do not delete the
+ * provisions above, a recipient may use your version of this file under
+ * either the BSD or the GPL.
+ */
+
+/* this big block deduces configuration from config.h */
+#ifndef EV_STANDALONE
+# ifdef EV_CONFIG_H
+# include EV_CONFIG_H
+# else
+# include "config.h"
+# endif
+
+# if HAVE_FLOOR
+# ifndef EV_USE_FLOOR
+# define EV_USE_FLOOR 1
+# endif
+# endif
+
+# if HAVE_CLOCK_SYSCALL
+# ifndef EV_USE_CLOCK_SYSCALL
+# define EV_USE_CLOCK_SYSCALL 1
+# ifndef EV_USE_REALTIME
+# define EV_USE_REALTIME 0
+# endif
+# ifndef EV_USE_MONOTONIC
+# define EV_USE_MONOTONIC 1
+# endif
+# endif
+# elif !defined EV_USE_CLOCK_SYSCALL
+# define EV_USE_CLOCK_SYSCALL 0
+# endif
+
+# if HAVE_CLOCK_GETTIME
+# ifndef EV_USE_MONOTONIC
+# define EV_USE_MONOTONIC 1
+# endif
+# ifndef EV_USE_REALTIME
+# define EV_USE_REALTIME 0
+# endif
+# else
+# ifndef EV_USE_MONOTONIC
+# define EV_USE_MONOTONIC 0
+# endif
+# ifndef EV_USE_REALTIME
+# define EV_USE_REALTIME 0
+# endif
+# endif
+
+# if HAVE_NANOSLEEP
+# ifndef EV_USE_NANOSLEEP
+# define EV_USE_NANOSLEEP EV_FEATURE_OS
+# endif
+# else
+# undef EV_USE_NANOSLEEP
+# define EV_USE_NANOSLEEP 0
+# endif
+
+# if HAVE_SELECT && HAVE_SYS_SELECT_H
+# ifndef EV_USE_SELECT
+# define EV_USE_SELECT EV_FEATURE_BACKENDS
+# endif
+# else
+# undef EV_USE_SELECT
+# define EV_USE_SELECT 0
+# endif
+
+# if HAVE_POLL && HAVE_POLL_H
+# ifndef EV_USE_POLL
+# define EV_USE_POLL EV_FEATURE_BACKENDS
+# endif
+# else
+# undef EV_USE_POLL
+# define EV_USE_POLL 0
+# endif
+
+# if HAVE_EPOLL_CTL && HAVE_SYS_EPOLL_H
+# ifndef EV_USE_EPOLL
+# define EV_USE_EPOLL EV_FEATURE_BACKENDS
+# endif
+# else
+# undef EV_USE_EPOLL
+# define EV_USE_EPOLL 0
+# endif
+
+# if HAVE_LINUX_AIO_ABI_H
+# ifndef EV_USE_LINUXAIO
+# define EV_USE_LINUXAIO 0 /* was: EV_FEATURE_BACKENDS, always off by default */
+# endif
+# else
+# undef EV_USE_LINUXAIO
+# define EV_USE_LINUXAIO 0
+# endif
+
+# if HAVE_LINUX_FS_H && HAVE_SYS_TIMERFD_H && HAVE_KERNEL_RWF_T
+# ifndef EV_USE_IOURING
+# define EV_USE_IOURING EV_FEATURE_BACKENDS
+# endif
+# else
+# undef EV_USE_IOURING
+# define EV_USE_IOURING 0
+# endif
+
+# if HAVE_KQUEUE && HAVE_SYS_EVENT_H
+# ifndef EV_USE_KQUEUE
+# define EV_USE_KQUEUE EV_FEATURE_BACKENDS
+# endif
+# else
+# undef EV_USE_KQUEUE
+# define EV_USE_KQUEUE 0
+# endif
+
+# if HAVE_PORT_H && HAVE_PORT_CREATE
+# ifndef EV_USE_PORT
+# define EV_USE_PORT EV_FEATURE_BACKENDS
+# endif
+# else
+# undef EV_USE_PORT
+# define EV_USE_PORT 0
+# endif
+
+# if HAVE_INOTIFY_INIT && HAVE_SYS_INOTIFY_H
+# ifndef EV_USE_INOTIFY
+# define EV_USE_INOTIFY EV_FEATURE_OS
+# endif
+# else
+# undef EV_USE_INOTIFY
+# define EV_USE_INOTIFY 0
+# endif
+
+# if HAVE_SIGNALFD && HAVE_SYS_SIGNALFD_H
+# ifndef EV_USE_SIGNALFD
+# define EV_USE_SIGNALFD EV_FEATURE_OS
+# endif
+# else
+# undef EV_USE_SIGNALFD
+# define EV_USE_SIGNALFD 0
+# endif
+
+# if HAVE_EVENTFD
+# ifndef EV_USE_EVENTFD
+# define EV_USE_EVENTFD EV_FEATURE_OS
+# endif
+# else
+# undef EV_USE_EVENTFD
+# define EV_USE_EVENTFD 0
+# endif
+
+# if HAVE_SYS_TIMERFD_H
+# ifndef EV_USE_TIMERFD
+# define EV_USE_TIMERFD EV_FEATURE_OS
+# endif
+# else
+# undef EV_USE_TIMERFD
+# define EV_USE_TIMERFD 0
+# endif
+
+#endif
+
+/* OS X, in its infinite idiocy, actually HARDCODES
+ * a limit of 1024 into their select. Where people have brains,
+ * OS X engineers apparently have a vacuum. Or maybe they were
+ * ordered to have a vacuum, or they do anything for money.
+ * This might help. Or not.
+ * Note that this must be defined early, as other include files
+ * will rely on this define as well.
+ */
+#define _DARWIN_UNLIMITED_SELECT 1
+
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stddef.h>
+
+#include <stdio.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <limits.h>
+
+#include <signal.h>
+
+#ifdef EV_H
+# include EV_H
+#else
+# include "ev.h"
+#endif
+
+#if EV_NO_THREADS
+# undef EV_NO_SMP
+# define EV_NO_SMP 1
+# undef ECB_NO_THREADS
+# define ECB_NO_THREADS 1
+#endif
+#if EV_NO_SMP
+# undef EV_NO_SMP
+# define ECB_NO_SMP 1
+#endif
+
+#ifndef _WIN32
+# include <sys/time.h>
+# include <sys/wait.h>
+# include <unistd.h>
+#else
+# include <io.h>
+# define WIN32_LEAN_AND_MEAN
+# include <winsock2.h>
+# include <windows.h>
+# ifndef EV_SELECT_IS_WINSOCKET
+# define EV_SELECT_IS_WINSOCKET 1
+# endif
+# undef EV_AVOID_STDIO
+#endif
+
+/* this block tries to deduce configuration from header-defined symbols and defaults */
+
+/* try to deduce the maximum number of signals on this platform */
+#if defined EV_NSIG
+/* use what's provided */
+#elif defined NSIG
+# define EV_NSIG (NSIG)
+#elif defined _NSIG
+# define EV_NSIG (_NSIG)
+#elif defined SIGMAX
+# define EV_NSIG (SIGMAX+1)
+#elif defined SIG_MAX
+# define EV_NSIG (SIG_MAX+1)
+#elif defined _SIG_MAX
+# define EV_NSIG (_SIG_MAX+1)
+#elif defined MAXSIG
+# define EV_NSIG (MAXSIG+1)
+#elif defined MAX_SIG
+# define EV_NSIG (MAX_SIG+1)
+#elif defined SIGARRAYSIZE
+# define EV_NSIG (SIGARRAYSIZE) /* Assume ary[SIGARRAYSIZE] */
+#elif defined _sys_nsig
+# define EV_NSIG (_sys_nsig) /* Solaris 2.5 */
+#else
+# define EV_NSIG (8 * sizeof (sigset_t) + 1)
+#endif
+
+#ifndef EV_USE_FLOOR
+# define EV_USE_FLOOR 0
+#endif
+
+#ifndef EV_USE_CLOCK_SYSCALL
+# if __linux && __GLIBC__ == 2 && __GLIBC_MINOR__ < 17
+# define EV_USE_CLOCK_SYSCALL EV_FEATURE_OS
+# else
+# define EV_USE_CLOCK_SYSCALL 0
+# endif
+#endif
+
+#if !(_POSIX_TIMERS > 0)
+# ifndef EV_USE_MONOTONIC
+# define EV_USE_MONOTONIC 0
+# endif
+# ifndef EV_USE_REALTIME
+# define EV_USE_REALTIME 0
+# endif
+#endif
+
+#ifndef EV_USE_MONOTONIC
+# if defined _POSIX_MONOTONIC_CLOCK && _POSIX_MONOTONIC_CLOCK >= 0
+# define EV_USE_MONOTONIC EV_FEATURE_OS
+# else
+# define EV_USE_MONOTONIC 0
+# endif
+#endif
+
+#ifndef EV_USE_REALTIME
+# define EV_USE_REALTIME !EV_USE_CLOCK_SYSCALL
+#endif
+
+#ifndef EV_USE_NANOSLEEP
+# if _POSIX_C_SOURCE >= 199309L
+# define EV_USE_NANOSLEEP EV_FEATURE_OS
+# else
+# define EV_USE_NANOSLEEP 0
+# endif
+#endif
+
+#ifndef EV_USE_SELECT
+# define EV_USE_SELECT EV_FEATURE_BACKENDS
+#endif
+
+#ifndef EV_USE_POLL
+# ifdef _WIN32
+# define EV_USE_POLL 0
+# else
+# define EV_USE_POLL EV_FEATURE_BACKENDS
+# endif
+#endif
+
+#ifndef EV_USE_EPOLL
+# if __linux && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 4))
+# define EV_USE_EPOLL EV_FEATURE_BACKENDS
+# else
+# define EV_USE_EPOLL 0
+# endif
+#endif
+
+#ifndef EV_USE_KQUEUE
+# define EV_USE_KQUEUE 0
+#endif
+
+#ifndef EV_USE_PORT
+# define EV_USE_PORT 0
+#endif
+
+#ifndef EV_USE_LINUXAIO
+# if __linux /* libev currently assumes linux/aio_abi.h is always available on linux */
+# define EV_USE_LINUXAIO 0 /* was: 1, always off by default */
+# else
+# define EV_USE_LINUXAIO 0
+# endif
+#endif
+
+#ifndef EV_USE_IOURING
+# if __linux /* later checks might disable again */
+# define EV_USE_IOURING 1
+# else
+# define EV_USE_IOURING 0
+# endif
+#endif
+
+#ifndef EV_USE_INOTIFY
+# if __linux && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 4))
+# define EV_USE_INOTIFY EV_FEATURE_OS
+# else
+# define EV_USE_INOTIFY 0
+# endif
+#endif
+
+#ifndef EV_PID_HASHSIZE
+# define EV_PID_HASHSIZE EV_FEATURE_DATA ? 16 : 1
+#endif
+
+#ifndef EV_INOTIFY_HASHSIZE
+# define EV_INOTIFY_HASHSIZE EV_FEATURE_DATA ? 16 : 1
+#endif
+
+#ifndef EV_USE_EVENTFD
+# if __linux && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 7))
+# define EV_USE_EVENTFD EV_FEATURE_OS
+# else
+# define EV_USE_EVENTFD 0
+# endif
+#endif
+
+#ifndef EV_USE_SIGNALFD
+# if __linux && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 7))
+# define EV_USE_SIGNALFD EV_FEATURE_OS
+# else
+# define EV_USE_SIGNALFD 0
+# endif
+#endif
+
+#ifndef EV_USE_TIMERFD
+# if __linux && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 8))
+# define EV_USE_TIMERFD EV_FEATURE_OS
+# else
+# define EV_USE_TIMERFD 0
+# endif
+#endif
+
+#if 0 /* debugging */
+# define EV_VERIFY 3
+# define EV_USE_4HEAP 1
+# define EV_HEAP_CACHE_AT 1
+#endif
+
+#ifndef EV_VERIFY
+# define EV_VERIFY (EV_FEATURE_API ? 1 : 0)
+#endif
+
+#ifndef EV_USE_4HEAP
+# define EV_USE_4HEAP EV_FEATURE_DATA
+#endif
+
+#ifndef EV_HEAP_CACHE_AT
+# define EV_HEAP_CACHE_AT EV_FEATURE_DATA
+#endif
+
+#ifdef __ANDROID__
+/* supposedly, android doesn't typedef fd_mask */
+# undef EV_USE_SELECT
+# define EV_USE_SELECT 0
+/* supposedly, we need to include syscall.h, not sys/syscall.h, so just disable */
+# undef EV_USE_CLOCK_SYSCALL
+# define EV_USE_CLOCK_SYSCALL 0
+#endif
+
+/* aix's poll.h seems to cause lots of trouble */
+#ifdef _AIX
+/* AIX has a completely broken poll.h header */
+# undef EV_USE_POLL
+# define EV_USE_POLL 0
+#endif
+
+/* on linux, we can use a (slow) syscall to avoid a dependency on pthread, */
+/* which makes programs even slower. might work on other unices, too. */
+#if EV_USE_CLOCK_SYSCALL
+# include <sys/syscall.h>
+# ifdef SYS_clock_gettime
+# define clock_gettime(id, ts) syscall (SYS_clock_gettime, (id), (ts))
+# undef EV_USE_MONOTONIC
+# define EV_USE_MONOTONIC 1
+# define EV_NEED_SYSCALL 1
+# else
+# undef EV_USE_CLOCK_SYSCALL
+# define EV_USE_CLOCK_SYSCALL 0
+# endif
+#endif
+
+/* this block fixes any misconfiguration where we know we run into trouble otherwise */
+
+#ifndef CLOCK_MONOTONIC
+# undef EV_USE_MONOTONIC
+# define EV_USE_MONOTONIC 0
+#endif
+
+#ifndef CLOCK_REALTIME
+# undef EV_USE_REALTIME
+# define EV_USE_REALTIME 0
+#endif
+
+#if !EV_STAT_ENABLE
+# undef EV_USE_INOTIFY
+# define EV_USE_INOTIFY 0
+#endif
+
+#if __linux && EV_USE_IOURING
+# include <linux/version.h>
+# if LINUX_VERSION_CODE < KERNEL_VERSION(4,14,0)
+# undef EV_USE_IOURING
+# define EV_USE_IOURING 0
+# endif
+#endif
+
+#if !EV_USE_NANOSLEEP
+/* hp-ux has it in sys/time.h, which we unconditionally include above */
+# if !defined _WIN32 && !defined __hpux
+# include <sys/select.h>
+# endif
+#endif
+
+#if EV_USE_LINUXAIO
+# include <sys/syscall.h>
+# if SYS_io_getevents && EV_USE_EPOLL /* linuxaio backend requires epoll backend */
+# define EV_NEED_SYSCALL 1
+# else
+# undef EV_USE_LINUXAIO
+# define EV_USE_LINUXAIO 0
+# endif
+#endif
+
+#if EV_USE_IOURING
+# include <sys/syscall.h>
+# if !SYS_io_uring_setup && __linux && !__alpha
+# define SYS_io_uring_setup 425
+# define SYS_io_uring_enter 426
+# define SYS_io_uring_wregister 427
+# endif
+# if SYS_io_uring_setup && EV_USE_EPOLL /* iouring backend requires epoll backend */
+# define EV_NEED_SYSCALL 1
+# else
+# undef EV_USE_IOURING
+# define EV_USE_IOURING 0
+# endif
+#endif
+
+#if EV_USE_INOTIFY
+# include <sys/statfs.h>
+# include <sys/inotify.h>
+/* some very old inotify.h headers don't have IN_DONT_FOLLOW */
+# ifndef IN_DONT_FOLLOW
+# undef EV_USE_INOTIFY
+# define EV_USE_INOTIFY 0
+# endif
+#endif
+
+#if EV_USE_EVENTFD
+/* our minimum requirement is glibc 2.7 which has the stub, but not the full header */
+# include <stdint.h>
+# ifndef EFD_NONBLOCK
+# define EFD_NONBLOCK O_NONBLOCK
+# endif
+# ifndef EFD_CLOEXEC
+# ifdef O_CLOEXEC
+# define EFD_CLOEXEC O_CLOEXEC
+# else
+# define EFD_CLOEXEC 02000000
+# endif
+# endif
+EV_CPP(extern "C") int (eventfd) (unsigned int initval, int flags);
+#endif
+
+#if EV_USE_SIGNALFD
+/* our minimum requirement is glibc 2.7 which has the stub, but not the full header */
+# include <stdint.h>
+# ifndef SFD_NONBLOCK
+# define SFD_NONBLOCK O_NONBLOCK
+# endif
+# ifndef SFD_CLOEXEC
+# ifdef O_CLOEXEC
+# define SFD_CLOEXEC O_CLOEXEC
+# else
+# define SFD_CLOEXEC 02000000
+# endif
+# endif
+EV_CPP (extern "C") int (signalfd) (int fd, const sigset_t *mask, int flags);
+
+struct signalfd_siginfo
+{
+ uint32_t ssi_signo;
+ char pad[128 - sizeof (uint32_t)];
+};
+#endif
+
+/* for timerfd, libev core requires TFD_TIMER_CANCEL_ON_SET &c */
+#if EV_USE_TIMERFD
+# include <sys/timerfd.h>
+/* timerfd is only used for periodics */
+# if !(defined (TFD_TIMER_CANCEL_ON_SET) && defined (TFD_CLOEXEC) && defined (TFD_NONBLOCK)) || !EV_PERIODIC_ENABLE
+# undef EV_USE_TIMERFD
+# define EV_USE_TIMERFD 0
+# endif
+#endif
+
+/*****************************************************************************/
+
+#if EV_VERIFY >= 3
+# define EV_FREQUENT_CHECK ev_verify (EV_A)
+#else
+# define EV_FREQUENT_CHECK do { } while (0)
+#endif
+
+/*
+ * This is used to work around floating point rounding problems.
+ * This value is good at least till the year 4000.
+ */
+#define MIN_INTERVAL 0.0001220703125 /* 1/2**13, good till 4000 */
+/*#define MIN_INTERVAL 0.00000095367431640625 /* 1/2**20, good till 2200 */
+
+#define MIN_TIMEJUMP 1. /* minimum timejump that gets detected (if monotonic clock available) */
+#define MAX_BLOCKTIME 59.743 /* never wait longer than this time (to detect time jumps) */
+#define MAX_BLOCKTIME2 1500001.07 /* same, but when timerfd is used to detect jumps, also safe delay to not overflow */
+
+/* find a portable timestamp that is "always" in the future but fits into time_t.
+ * this is quite hard, and we are mostly guessing - we handle 32 bit signed/unsigned time_t,
+ * and sizes larger than 32 bit, and maybe the unlikely floating point time_t */
+#define EV_TSTAMP_HUGE \
+ (sizeof (time_t) >= 8 ? 10000000000000. \
+ : 0 < (time_t)4294967295 ? 4294967295. \
+ : 2147483647.) \
+
+#ifndef EV_TS_CONST
+# define EV_TS_CONST(nv) nv
+# define EV_TS_TO_MSEC(a) a * 1e3 + 0.9999
+# define EV_TS_FROM_USEC(us) us * 1e-6
+# define EV_TV_SET(tv,t) do { tv.tv_sec = (long)t; tv.tv_usec = (long)((t - tv.tv_sec) * 1e6); } while (0)
+# define EV_TS_SET(ts,t) do { ts.tv_sec = (long)t; ts.tv_nsec = (long)((t - ts.tv_sec) * 1e9); } while (0)
+# define EV_TV_GET(tv) ((tv).tv_sec + (tv).tv_usec * 1e-6)
+# define EV_TS_GET(ts) ((ts).tv_sec + (ts).tv_nsec * 1e-9)
+#endif
+
+/* the following is ecb.h embedded into libev - use update_ev_c to update from an external copy */
+/* ECB.H BEGIN */
+/*
+ * libecb - http://software.schmorp.de/pkg/libecb
+ *
+ * Copyright (©) 2009-2015,2018-2020 Marc Alexander Lehmann <libecb@schmorp.de>
+ * Copyright (©) 2011 Emanuele Giaquinta
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
+ * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
+ * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
+ * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License ("GPL") version 2 or any later version,
+ * in which case the provisions of the GPL are applicable instead of
+ * the above. If you wish to allow the use of your version of this file
+ * only under the terms of the GPL and not to allow others to use your
+ * version of this file under the BSD license, indicate your decision
+ * by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL. If you do not delete the
+ * provisions above, a recipient may use your version of this file under
+ * either the BSD or the GPL.
+ */
+
+#ifndef ECB_H
+#define ECB_H
+
+/* 16 bits major, 16 bits minor */
+#define ECB_VERSION 0x00010008
+
+#include <string.h> /* for memcpy */
+
+#if defined (_WIN32) && !defined (__MINGW32__)
+ typedef signed char int8_t;
+ typedef unsigned char uint8_t;
+ typedef signed char int_fast8_t;
+ typedef unsigned char uint_fast8_t;
+ typedef signed short int16_t;
+ typedef unsigned short uint16_t;
+ typedef signed int int_fast16_t;
+ typedef unsigned int uint_fast16_t;
+ typedef signed int int32_t;
+ typedef unsigned int uint32_t;
+ typedef signed int int_fast32_t;
+ typedef unsigned int uint_fast32_t;
+ #if __GNUC__
+ typedef signed long long int64_t;
+ typedef unsigned long long uint64_t;
+ #else /* _MSC_VER || __BORLANDC__ */
+ typedef signed __int64 int64_t;
+ typedef unsigned __int64 uint64_t;
+ #endif
+ typedef int64_t int_fast64_t;
+ typedef uint64_t uint_fast64_t;
+ #ifdef _WIN64
+ #define ECB_PTRSIZE 8
+ typedef uint64_t uintptr_t;
+ typedef int64_t intptr_t;
+ #else
+ #define ECB_PTRSIZE 4
+ typedef uint32_t uintptr_t;
+ typedef int32_t intptr_t;
+ #endif
+#else
+ #include <inttypes.h>
+ #if (defined INTPTR_MAX ? INTPTR_MAX : ULONG_MAX) > 0xffffffffU
+ #define ECB_PTRSIZE 8
+ #else
+ #define ECB_PTRSIZE 4
+ #endif
+#endif
+
+#define ECB_GCC_AMD64 (__amd64 || __amd64__ || __x86_64 || __x86_64__)
+#define ECB_MSVC_AMD64 (_M_AMD64 || _M_X64)
+
+#ifndef ECB_OPTIMIZE_SIZE
+ #if __OPTIMIZE_SIZE__
+ #define ECB_OPTIMIZE_SIZE 1
+ #else
+ #define ECB_OPTIMIZE_SIZE 0
+ #endif
+#endif
+
+/* work around x32 idiocy by defining proper macros */
+#if ECB_GCC_AMD64 || ECB_MSVC_AMD64
+ #if _ILP32
+ #define ECB_AMD64_X32 1
+ #else
+ #define ECB_AMD64 1
+ #endif
+#endif
+
+/* many compilers define _GNUC_ to some versions but then only implement
+ * what their idiot authors think are the "more important" extensions,
+ * causing enormous grief in return for some better fake benchmark numbers.
+ * or so.
+ * we try to detect these and simply assume they are not gcc - if they have
+ * an issue with that they should have done it right in the first place.
+ */
+#if !defined __GNUC_MINOR__ || defined __INTEL_COMPILER || defined __SUNPRO_C || defined __SUNPRO_CC || defined __llvm__ || defined __clang__
+ #define ECB_GCC_VERSION(major,minor) 0
+#else
+ #define ECB_GCC_VERSION(major,minor) (__GNUC__ > (major) || (__GNUC__ == (major) && __GNUC_MINOR__ >= (minor)))
+#endif
+
+#define ECB_CLANG_VERSION(major,minor) (__clang_major__ > (major) || (__clang_major__ == (major) && __clang_minor__ >= (minor)))
+
+#if __clang__ && defined __has_builtin
+ #define ECB_CLANG_BUILTIN(x) __has_builtin (x)
+#else
+ #define ECB_CLANG_BUILTIN(x) 0
+#endif
+
+#if __clang__ && defined __has_extension
+ #define ECB_CLANG_EXTENSION(x) __has_extension (x)
+#else
+ #define ECB_CLANG_EXTENSION(x) 0
+#endif
+
+#define ECB_CPP (__cplusplus+0)
+#define ECB_CPP11 (__cplusplus >= 201103L)
+#define ECB_CPP14 (__cplusplus >= 201402L)
+#define ECB_CPP17 (__cplusplus >= 201703L)
+
+#if ECB_CPP
+ #define ECB_C 0
+ #define ECB_STDC_VERSION 0
+#else
+ #define ECB_C 1
+ #define ECB_STDC_VERSION __STDC_VERSION__
+#endif
+
+#define ECB_C99 (ECB_STDC_VERSION >= 199901L)
+#define ECB_C11 (ECB_STDC_VERSION >= 201112L)
+#define ECB_C17 (ECB_STDC_VERSION >= 201710L)
+
+#if ECB_CPP
+ #define ECB_EXTERN_C extern "C"
+ #define ECB_EXTERN_C_BEG ECB_EXTERN_C {
+ #define ECB_EXTERN_C_END }
+#else
+ #define ECB_EXTERN_C extern
+ #define ECB_EXTERN_C_BEG
+ #define ECB_EXTERN_C_END
+#endif
+
+/*****************************************************************************/
+
+/* ECB_NO_THREADS - ecb is not used by multiple threads, ever */
+/* ECB_NO_SMP - ecb might be used in multiple threads, but only on a single cpu */
+
+#if ECB_NO_THREADS
+ #define ECB_NO_SMP 1
+#endif
+
+#if ECB_NO_SMP
+ #define ECB_MEMORY_FENCE do { } while (0)
+#endif
+
+/* http://www-01.ibm.com/support/knowledgecenter/SSGH3R_13.1.0/com.ibm.xlcpp131.aix.doc/compiler_ref/compiler_builtins.html */
+#if __xlC__ && ECB_CPP
+ #include <builtins.h>
+#endif
+
+#if 1400 <= _MSC_VER
+ #include <intrin.h> /* fence functions _ReadBarrier, also bit search functions _BitScanReverse */
+#endif
+
+#ifndef ECB_MEMORY_FENCE
+ #if ECB_GCC_VERSION(2,5) || defined __INTEL_COMPILER || (__llvm__ && __GNUC__) || __SUNPRO_C >= 0x5110 || __SUNPRO_CC >= 0x5110
+ #define ECB_MEMORY_FENCE_RELAXED __asm__ __volatile__ ("" : : : "memory")
+ #if __i386 || __i386__
+ #define ECB_MEMORY_FENCE __asm__ __volatile__ ("lock; orb $0, -1(%%esp)" : : : "memory")
+ #define ECB_MEMORY_FENCE_ACQUIRE __asm__ __volatile__ ("" : : : "memory")
+ #define ECB_MEMORY_FENCE_RELEASE __asm__ __volatile__ ("" : : : "memory")
+ #elif ECB_GCC_AMD64
+ #define ECB_MEMORY_FENCE __asm__ __volatile__ ("mfence" : : : "memory")
+ #define ECB_MEMORY_FENCE_ACQUIRE __asm__ __volatile__ ("" : : : "memory")
+ #define ECB_MEMORY_FENCE_RELEASE __asm__ __volatile__ ("" : : : "memory")
+ #elif __powerpc__ || __ppc__ || __powerpc64__ || __ppc64__
+ #define ECB_MEMORY_FENCE __asm__ __volatile__ ("sync" : : : "memory")
+ #elif defined __ARM_ARCH_2__ \
+ || defined __ARM_ARCH_3__ || defined __ARM_ARCH_3M__ \
+ || defined __ARM_ARCH_4__ || defined __ARM_ARCH_4T__ \
+ || defined __ARM_ARCH_5__ || defined __ARM_ARCH_5E__ \
+ || defined __ARM_ARCH_5T__ || defined __ARM_ARCH_5TE__ \
+ || defined __ARM_ARCH_5TEJ__
+ /* should not need any, unless running old code on newer cpu - arm doesn't support that */
+ #elif defined __ARM_ARCH_6__ || defined __ARM_ARCH_6J__ \
+ || defined __ARM_ARCH_6K__ || defined __ARM_ARCH_6ZK__ \
+ || defined __ARM_ARCH_6T2__
+ #define ECB_MEMORY_FENCE __asm__ __volatile__ ("mcr p15,0,%0,c7,c10,5" : : "r" (0) : "memory")
+ #elif defined __ARM_ARCH_7__ || defined __ARM_ARCH_7A__ \
+ || defined __ARM_ARCH_7R__ || defined __ARM_ARCH_7M__
+ #define ECB_MEMORY_FENCE __asm__ __volatile__ ("dmb" : : : "memory")
+ #elif __aarch64__
+ #define ECB_MEMORY_FENCE __asm__ __volatile__ ("dmb ish" : : : "memory")
+ #elif (__sparc || __sparc__) && !(__sparc_v8__ || defined __sparcv8)
+ #define ECB_MEMORY_FENCE __asm__ __volatile__ ("membar #LoadStore | #LoadLoad | #StoreStore | #StoreLoad" : : : "memory")
+ #define ECB_MEMORY_FENCE_ACQUIRE __asm__ __volatile__ ("membar #LoadStore | #LoadLoad" : : : "memory")
+ #define ECB_MEMORY_FENCE_RELEASE __asm__ __volatile__ ("membar #LoadStore | #StoreStore")
+ #elif defined __s390__ || defined __s390x__
+ #define ECB_MEMORY_FENCE __asm__ __volatile__ ("bcr 15,0" : : : "memory")
+ #elif defined __mips__
+ /* GNU/Linux emulates sync on mips1 architectures, so we force its use */
+ /* anybody else who still uses mips1 is supposed to send in their version, with detection code. */
+ #define ECB_MEMORY_FENCE __asm__ __volatile__ (".set mips2; sync; .set mips0" : : : "memory")
+ #elif defined __alpha__
+ #define ECB_MEMORY_FENCE __asm__ __volatile__ ("mb" : : : "memory")
+ #elif defined __hppa__
+ #define ECB_MEMORY_FENCE __asm__ __volatile__ ("" : : : "memory")
+ #define ECB_MEMORY_FENCE_RELEASE __asm__ __volatile__ ("")
+ #elif defined __ia64__
+ #define ECB_MEMORY_FENCE __asm__ __volatile__ ("mf" : : : "memory")
+ #elif defined __m68k__
+ #define ECB_MEMORY_FENCE __asm__ __volatile__ ("" : : : "memory")
+ #elif defined __m88k__
+ #define ECB_MEMORY_FENCE __asm__ __volatile__ ("tb1 0,%%r0,128" : : : "memory")
+ #elif defined __sh__
+ #define ECB_MEMORY_FENCE __asm__ __volatile__ ("" : : : "memory")
+ #endif
+ #endif
+#endif
+
+#ifndef ECB_MEMORY_FENCE
+ #if ECB_GCC_VERSION(4,7)
+ /* see comment below (stdatomic.h) about the C11 memory model. */
+ #define ECB_MEMORY_FENCE __atomic_thread_fence (__ATOMIC_SEQ_CST)
+ #define ECB_MEMORY_FENCE_ACQUIRE __atomic_thread_fence (__ATOMIC_ACQUIRE)
+ #define ECB_MEMORY_FENCE_RELEASE __atomic_thread_fence (__ATOMIC_RELEASE)
+ #define ECB_MEMORY_FENCE_RELAXED __atomic_thread_fence (__ATOMIC_RELAXED)
+
+ #elif ECB_CLANG_EXTENSION(c_atomic)
+ /* see comment below (stdatomic.h) about the C11 memory model. */
+ #define ECB_MEMORY_FENCE __c11_atomic_thread_fence (__ATOMIC_SEQ_CST)
+ #define ECB_MEMORY_FENCE_ACQUIRE __c11_atomic_thread_fence (__ATOMIC_ACQUIRE)
+ #define ECB_MEMORY_FENCE_RELEASE __c11_atomic_thread_fence (__ATOMIC_RELEASE)
+ #define ECB_MEMORY_FENCE_RELAXED __c11_atomic_thread_fence (__ATOMIC_RELAXED)
+
+ #elif ECB_GCC_VERSION(4,4) || defined __INTEL_COMPILER || defined __clang__
+ #define ECB_MEMORY_FENCE __sync_synchronize ()
+ #elif _MSC_VER >= 1500 /* VC++ 2008 */
+ /* apparently, microsoft broke all the memory barrier stuff in Visual Studio 2008... */
+ #pragma intrinsic(_ReadBarrier,_WriteBarrier,_ReadWriteBarrier)
+ #define ECB_MEMORY_FENCE _ReadWriteBarrier (); MemoryBarrier()
+ #define ECB_MEMORY_FENCE_ACQUIRE _ReadWriteBarrier (); MemoryBarrier() /* according to msdn, _ReadBarrier is not a load fence */
+ #define ECB_MEMORY_FENCE_RELEASE _WriteBarrier (); MemoryBarrier()
+ #elif _MSC_VER >= 1400 /* VC++ 2005 */
+ #pragma intrinsic(_ReadBarrier,_WriteBarrier,_ReadWriteBarrier)
+ #define ECB_MEMORY_FENCE _ReadWriteBarrier ()
+ #define ECB_MEMORY_FENCE_ACQUIRE _ReadWriteBarrier () /* according to msdn, _ReadBarrier is not a load fence */
+ #define ECB_MEMORY_FENCE_RELEASE _WriteBarrier ()
+ #elif defined _WIN32
+ #include <WinNT.h>
+ #define ECB_MEMORY_FENCE MemoryBarrier () /* actually just xchg on x86... scary */
+ #elif __SUNPRO_C >= 0x5110 || __SUNPRO_CC >= 0x5110
+ #include <mbarrier.h>
+ #define ECB_MEMORY_FENCE __machine_rw_barrier ()
+ #define ECB_MEMORY_FENCE_ACQUIRE __machine_acq_barrier ()
+ #define ECB_MEMORY_FENCE_RELEASE __machine_rel_barrier ()
+ #define ECB_MEMORY_FENCE_RELAXED __compiler_barrier ()
+ #elif __xlC__
+ #define ECB_MEMORY_FENCE __sync ()
+ #endif
+#endif
+
+#ifndef ECB_MEMORY_FENCE
+ #if ECB_C11 && !defined __STDC_NO_ATOMICS__
+ /* we assume that these memory fences work on all variables/all memory accesses, */
+ /* not just C11 atomics and atomic accesses */
+ #include <stdatomic.h>
+ #define ECB_MEMORY_FENCE atomic_thread_fence (memory_order_seq_cst)
+ #define ECB_MEMORY_FENCE_ACQUIRE atomic_thread_fence (memory_order_acquire)
+ #define ECB_MEMORY_FENCE_RELEASE atomic_thread_fence (memory_order_release)
+ #endif
+#endif
+
+#ifndef ECB_MEMORY_FENCE
+ #if !ECB_AVOID_PTHREADS
+ /*
+ * if you get undefined symbol references to pthread_mutex_lock,
+ * or failure to find pthread.h, then you should implement
+ * the ECB_MEMORY_FENCE operations for your cpu/compiler
+ * OR provide pthread.h and link against the posix thread library
+ * of your system.
+ */
+ #include <pthread.h>
+ #define ECB_NEEDS_PTHREADS 1
+ #define ECB_MEMORY_FENCE_NEEDS_PTHREADS 1
+
+ static pthread_mutex_t ecb_mf_lock = PTHREAD_MUTEX_INITIALIZER;
+ #define ECB_MEMORY_FENCE do { pthread_mutex_lock (&ecb_mf_lock); pthread_mutex_unlock (&ecb_mf_lock); } while (0)
+ #endif
+#endif
+
+#if !defined ECB_MEMORY_FENCE_ACQUIRE && defined ECB_MEMORY_FENCE
+ #define ECB_MEMORY_FENCE_ACQUIRE ECB_MEMORY_FENCE
+#endif
+
+#if !defined ECB_MEMORY_FENCE_RELEASE && defined ECB_MEMORY_FENCE
+ #define ECB_MEMORY_FENCE_RELEASE ECB_MEMORY_FENCE
+#endif
+
+#if !defined ECB_MEMORY_FENCE_RELAXED && defined ECB_MEMORY_FENCE
+ #define ECB_MEMORY_FENCE_RELAXED ECB_MEMORY_FENCE /* very heavy-handed */
+#endif
+
+/*****************************************************************************/
+
+#if ECB_CPP
+ #define ecb_inline static inline
+#elif ECB_GCC_VERSION(2,5)
+ #define ecb_inline static __inline__
+#elif ECB_C99
+ #define ecb_inline static inline
+#else
+ #define ecb_inline static
+#endif
+
+#if ECB_GCC_VERSION(3,3)
+ #define ecb_restrict __restrict__
+#elif ECB_C99
+ #define ecb_restrict restrict
+#else
+ #define ecb_restrict
+#endif
+
+typedef int ecb_bool;
+
+#define ECB_CONCAT_(a, b) a ## b
+#define ECB_CONCAT(a, b) ECB_CONCAT_(a, b)
+#define ECB_STRINGIFY_(a) # a
+#define ECB_STRINGIFY(a) ECB_STRINGIFY_(a)
+#define ECB_STRINGIFY_EXPR(expr) ((expr), ECB_STRINGIFY_ (expr))
+
+#define ecb_function_ ecb_inline
+
+#if ECB_GCC_VERSION(3,1) || ECB_CLANG_VERSION(2,8)
+ #define ecb_attribute(attrlist) __attribute__ (attrlist)
+#else
+ #define ecb_attribute(attrlist)
+#endif
+
+#if ECB_GCC_VERSION(3,1) || ECB_CLANG_BUILTIN(__builtin_constant_p)
+ #define ecb_is_constant(expr) __builtin_constant_p (expr)
+#else
+ /* possible C11 impl for integral types
+ typedef struct ecb_is_constant_struct ecb_is_constant_struct;
+ #define ecb_is_constant(expr) _Generic ((1 ? (struct ecb_is_constant_struct *)0 : (void *)((expr) - (expr)), ecb_is_constant_struct *: 0, default: 1)) */
+
+ #define ecb_is_constant(expr) 0
+#endif
+
+#if ECB_GCC_VERSION(3,1) || ECB_CLANG_BUILTIN(__builtin_expect)
+ #define ecb_expect(expr,value) __builtin_expect ((expr),(value))
+#else
+ #define ecb_expect(expr,value) (expr)
+#endif
+
+#if ECB_GCC_VERSION(3,1) || ECB_CLANG_BUILTIN(__builtin_prefetch)
+ #define ecb_prefetch(addr,rw,locality) __builtin_prefetch (addr, rw, locality)
+#else
+ #define ecb_prefetch(addr,rw,locality)
+#endif
+
+/* no emulation for ecb_decltype */
+#if ECB_CPP11
+ // older implementations might have problems with decltype(x)::type, work around it
+ template<class T> struct ecb_decltype_t { typedef T type; };
+ #define ecb_decltype(x) ecb_decltype_t<decltype (x)>::type
+#elif ECB_GCC_VERSION(3,0) || ECB_CLANG_VERSION(2,8)
+ #define ecb_decltype(x) __typeof__ (x)
+#endif
+
+#if _MSC_VER >= 1300
+ #define ecb_deprecated __declspec (deprecated)
+#else
+ #define ecb_deprecated ecb_attribute ((__deprecated__))
+#endif
+
+#if _MSC_VER >= 1500
+ #define ecb_deprecated_message(msg) __declspec (deprecated (msg))
+#elif ECB_GCC_VERSION(4,5)
+ #define ecb_deprecated_message(msg) ecb_attribute ((__deprecated__ (msg))
+#else
+ #define ecb_deprecated_message(msg) ecb_deprecated
+#endif
+
+#if _MSC_VER >= 1400
+ #define ecb_noinline __declspec (noinline)
+#else
+ #define ecb_noinline ecb_attribute ((__noinline__))
+#endif
+
+#define ecb_unused ecb_attribute ((__unused__))
+#define ecb_const ecb_attribute ((__const__))
+#define ecb_pure ecb_attribute ((__pure__))
+
+#if ECB_C11 || __IBMC_NORETURN
+ /* http://www-01.ibm.com/support/knowledgecenter/SSGH3R_13.1.0/com.ibm.xlcpp131.aix.doc/language_ref/noreturn.html */
+ #define ecb_noreturn _Noreturn
+#elif ECB_CPP11
+ #define ecb_noreturn [[noreturn]]
+#elif _MSC_VER >= 1200
+ /* http://msdn.microsoft.com/en-us/library/k6ktzx3s.aspx */
+ #define ecb_noreturn __declspec (noreturn)
+#else
+ #define ecb_noreturn ecb_attribute ((__noreturn__))
+#endif
+
+#if ECB_GCC_VERSION(4,3)
+ #define ecb_artificial ecb_attribute ((__artificial__))
+ #define ecb_hot ecb_attribute ((__hot__))
+ #define ecb_cold ecb_attribute ((__cold__))
+#else
+ #define ecb_artificial
+ #define ecb_hot
+ #define ecb_cold
+#endif
+
+/* put around conditional expressions if you are very sure that the */
+/* expression is mostly true or mostly false. note that these return */
+/* booleans, not the expression. */
+#define ecb_expect_false(expr) ecb_expect (!!(expr), 0)
+#define ecb_expect_true(expr) ecb_expect (!!(expr), 1)
+/* for compatibility to the rest of the world */
+#define ecb_likely(expr) ecb_expect_true (expr)
+#define ecb_unlikely(expr) ecb_expect_false (expr)
+
+/* count trailing zero bits and count # of one bits */
+#if ECB_GCC_VERSION(3,4) \
+ || (ECB_CLANG_BUILTIN(__builtin_clz) && ECB_CLANG_BUILTIN(__builtin_clzll) \
+ && ECB_CLANG_BUILTIN(__builtin_ctz) && ECB_CLANG_BUILTIN(__builtin_ctzll) \
+ && ECB_CLANG_BUILTIN(__builtin_popcount))
+ /* we assume int == 32 bit, long == 32 or 64 bit and long long == 64 bit */
+ #define ecb_ld32(x) (__builtin_clz (x) ^ 31)
+ #define ecb_ld64(x) (__builtin_clzll (x) ^ 63)
+ #define ecb_ctz32(x) __builtin_ctz (x)
+ #define ecb_ctz64(x) __builtin_ctzll (x)
+ #define ecb_popcount32(x) __builtin_popcount (x)
+ /* no popcountll */
+#else
+ ecb_function_ ecb_const int ecb_ctz32 (uint32_t x);
+ ecb_function_ ecb_const int
+ ecb_ctz32 (uint32_t x)
+ {
+#if 1400 <= _MSC_VER && (_M_IX86 || _M_X64 || _M_IA64 || _M_ARM)
+ unsigned long r;
+ _BitScanForward (&r, x);
+ return (int)r;
+#else
+ int r = 0;
+
+ x &= ~x + 1; /* this isolates the lowest bit */
+
+#if ECB_branchless_on_i386
+ r += !!(x & 0xaaaaaaaa) << 0;
+ r += !!(x & 0xcccccccc) << 1;
+ r += !!(x & 0xf0f0f0f0) << 2;
+ r += !!(x & 0xff00ff00) << 3;
+ r += !!(x & 0xffff0000) << 4;
+#else
+ if (x & 0xaaaaaaaa) r += 1;
+ if (x & 0xcccccccc) r += 2;
+ if (x & 0xf0f0f0f0) r += 4;
+ if (x & 0xff00ff00) r += 8;
+ if (x & 0xffff0000) r += 16;
+#endif
+
+ return r;
+#endif
+ }
+
+ ecb_function_ ecb_const int ecb_ctz64 (uint64_t x);
+ ecb_function_ ecb_const int
+ ecb_ctz64 (uint64_t x)
+ {
+#if 1400 <= _MSC_VER && (_M_X64 || _M_IA64 || _M_ARM)
+ unsigned long r;
+ _BitScanForward64 (&r, x);
+ return (int)r;
+#else
+ int shift = x & 0xffffffff ? 0 : 32;
+ return ecb_ctz32 (x >> shift) + shift;
+#endif
+ }
+
+ ecb_function_ ecb_const int ecb_popcount32 (uint32_t x);
+ ecb_function_ ecb_const int
+ ecb_popcount32 (uint32_t x)
+ {
+ x -= (x >> 1) & 0x55555555;
+ x = ((x >> 2) & 0x33333333) + (x & 0x33333333);
+ x = ((x >> 4) + x) & 0x0f0f0f0f;
+ x *= 0x01010101;
+
+ return x >> 24;
+ }
+
+ ecb_function_ ecb_const int ecb_ld32 (uint32_t x);
+ ecb_function_ ecb_const int ecb_ld32 (uint32_t x)
+ {
+#if 1400 <= _MSC_VER && (_M_IX86 || _M_X64 || _M_IA64 || _M_ARM)
+ unsigned long r;
+ _BitScanReverse (&r, x);
+ return (int)r;
+#else
+ int r = 0;
+
+ if (x >> 16) { x >>= 16; r += 16; }
+ if (x >> 8) { x >>= 8; r += 8; }
+ if (x >> 4) { x >>= 4; r += 4; }
+ if (x >> 2) { x >>= 2; r += 2; }
+ if (x >> 1) { r += 1; }
+
+ return r;
+#endif
+ }
+
+ ecb_function_ ecb_const int ecb_ld64 (uint64_t x);
+ ecb_function_ ecb_const int ecb_ld64 (uint64_t x)
+ {
+#if 1400 <= _MSC_VER && (_M_X64 || _M_IA64 || _M_ARM)
+ unsigned long r;
+ _BitScanReverse64 (&r, x);
+ return (int)r;
+#else
+ int r = 0;
+
+ if (x >> 32) { x >>= 32; r += 32; }
+
+ return r + ecb_ld32 (x);
+#endif
+ }
+#endif
+
+ecb_function_ ecb_const ecb_bool ecb_is_pot32 (uint32_t x);
+ecb_function_ ecb_const ecb_bool ecb_is_pot32 (uint32_t x) { return !(x & (x - 1)); }
+ecb_function_ ecb_const ecb_bool ecb_is_pot64 (uint64_t x);
+ecb_function_ ecb_const ecb_bool ecb_is_pot64 (uint64_t x) { return !(x & (x - 1)); }
+
+ecb_function_ ecb_const uint8_t ecb_bitrev8 (uint8_t x);
+ecb_function_ ecb_const uint8_t ecb_bitrev8 (uint8_t x)
+{
+ return ( (x * 0x0802U & 0x22110U)
+ | (x * 0x8020U & 0x88440U)) * 0x10101U >> 16;
+}
+
+ecb_function_ ecb_const uint16_t ecb_bitrev16 (uint16_t x);
+ecb_function_ ecb_const uint16_t ecb_bitrev16 (uint16_t x)
+{
+ x = ((x >> 1) & 0x5555) | ((x & 0x5555) << 1);
+ x = ((x >> 2) & 0x3333) | ((x & 0x3333) << 2);
+ x = ((x >> 4) & 0x0f0f) | ((x & 0x0f0f) << 4);
+ x = ( x >> 8 ) | ( x << 8);
+
+ return x;
+}
+
+ecb_function_ ecb_const uint32_t ecb_bitrev32 (uint32_t x);
+ecb_function_ ecb_const uint32_t ecb_bitrev32 (uint32_t x)
+{
+ x = ((x >> 1) & 0x55555555) | ((x & 0x55555555) << 1);
+ x = ((x >> 2) & 0x33333333) | ((x & 0x33333333) << 2);
+ x = ((x >> 4) & 0x0f0f0f0f) | ((x & 0x0f0f0f0f) << 4);
+ x = ((x >> 8) & 0x00ff00ff) | ((x & 0x00ff00ff) << 8);
+ x = ( x >> 16 ) | ( x << 16);
+
+ return x;
+}
+
+/* popcount64 is only available on 64 bit cpus as gcc builtin */
+/* so for this version we are lazy */
+ecb_function_ ecb_const int ecb_popcount64 (uint64_t x);
+ecb_function_ ecb_const int
+ecb_popcount64 (uint64_t x)
+{
+ return ecb_popcount32 (x) + ecb_popcount32 (x >> 32);
+}
+
+ecb_inline ecb_const uint8_t ecb_rotl8 (uint8_t x, unsigned int count);
+ecb_inline ecb_const uint8_t ecb_rotr8 (uint8_t x, unsigned int count);
+ecb_inline ecb_const uint16_t ecb_rotl16 (uint16_t x, unsigned int count);
+ecb_inline ecb_const uint16_t ecb_rotr16 (uint16_t x, unsigned int count);
+ecb_inline ecb_const uint32_t ecb_rotl32 (uint32_t x, unsigned int count);
+ecb_inline ecb_const uint32_t ecb_rotr32 (uint32_t x, unsigned int count);
+ecb_inline ecb_const uint64_t ecb_rotl64 (uint64_t x, unsigned int count);
+ecb_inline ecb_const uint64_t ecb_rotr64 (uint64_t x, unsigned int count);
+
+ecb_inline ecb_const uint8_t ecb_rotl8 (uint8_t x, unsigned int count) { return (x >> ( 8 - count)) | (x << count); }
+ecb_inline ecb_const uint8_t ecb_rotr8 (uint8_t x, unsigned int count) { return (x << ( 8 - count)) | (x >> count); }
+ecb_inline ecb_const uint16_t ecb_rotl16 (uint16_t x, unsigned int count) { return (x >> (16 - count)) | (x << count); }
+ecb_inline ecb_const uint16_t ecb_rotr16 (uint16_t x, unsigned int count) { return (x << (16 - count)) | (x >> count); }
+ecb_inline ecb_const uint32_t ecb_rotl32 (uint32_t x, unsigned int count) { return (x >> (32 - count)) | (x << count); }
+ecb_inline ecb_const uint32_t ecb_rotr32 (uint32_t x, unsigned int count) { return (x << (32 - count)) | (x >> count); }
+ecb_inline ecb_const uint64_t ecb_rotl64 (uint64_t x, unsigned int count) { return (x >> (64 - count)) | (x << count); }
+ecb_inline ecb_const uint64_t ecb_rotr64 (uint64_t x, unsigned int count) { return (x << (64 - count)) | (x >> count); }
+
+#if ECB_CPP
+
+inline uint8_t ecb_ctz (uint8_t v) { return ecb_ctz32 (v); }
+inline uint16_t ecb_ctz (uint16_t v) { return ecb_ctz32 (v); }
+inline uint32_t ecb_ctz (uint32_t v) { return ecb_ctz32 (v); }
+inline uint64_t ecb_ctz (uint64_t v) { return ecb_ctz64 (v); }
+
+inline bool ecb_is_pot (uint8_t v) { return ecb_is_pot32 (v); }
+inline bool ecb_is_pot (uint16_t v) { return ecb_is_pot32 (v); }
+inline bool ecb_is_pot (uint32_t v) { return ecb_is_pot32 (v); }
+inline bool ecb_is_pot (uint64_t v) { return ecb_is_pot64 (v); }
+
+inline int ecb_ld (uint8_t v) { return ecb_ld32 (v); }
+inline int ecb_ld (uint16_t v) { return ecb_ld32 (v); }
+inline int ecb_ld (uint32_t v) { return ecb_ld32 (v); }
+inline int ecb_ld (uint64_t v) { return ecb_ld64 (v); }
+
+inline int ecb_popcount (uint8_t v) { return ecb_popcount32 (v); }
+inline int ecb_popcount (uint16_t v) { return ecb_popcount32 (v); }
+inline int ecb_popcount (uint32_t v) { return ecb_popcount32 (v); }
+inline int ecb_popcount (uint64_t v) { return ecb_popcount64 (v); }
+
+inline uint8_t ecb_bitrev (uint8_t v) { return ecb_bitrev8 (v); }
+inline uint16_t ecb_bitrev (uint16_t v) { return ecb_bitrev16 (v); }
+inline uint32_t ecb_bitrev (uint32_t v) { return ecb_bitrev32 (v); }
+
+inline uint8_t ecb_rotl (uint8_t v, unsigned int count) { return ecb_rotl8 (v, count); }
+inline uint16_t ecb_rotl (uint16_t v, unsigned int count) { return ecb_rotl16 (v, count); }
+inline uint32_t ecb_rotl (uint32_t v, unsigned int count) { return ecb_rotl32 (v, count); }
+inline uint64_t ecb_rotl (uint64_t v, unsigned int count) { return ecb_rotl64 (v, count); }
+
+inline uint8_t ecb_rotr (uint8_t v, unsigned int count) { return ecb_rotr8 (v, count); }
+inline uint16_t ecb_rotr (uint16_t v, unsigned int count) { return ecb_rotr16 (v, count); }
+inline uint32_t ecb_rotr (uint32_t v, unsigned int count) { return ecb_rotr32 (v, count); }
+inline uint64_t ecb_rotr (uint64_t v, unsigned int count) { return ecb_rotr64 (v, count); }
+
+#endif
+
+#if ECB_GCC_VERSION(4,3) || (ECB_CLANG_BUILTIN(__builtin_bswap32) && ECB_CLANG_BUILTIN(__builtin_bswap64))
+ #if ECB_GCC_VERSION(4,8) || ECB_CLANG_BUILTIN(__builtin_bswap16)
+ #define ecb_bswap16(x) __builtin_bswap16 (x)
+ #else
+ #define ecb_bswap16(x) (__builtin_bswap32 (x) >> 16)
+ #endif
+ #define ecb_bswap32(x) __builtin_bswap32 (x)
+ #define ecb_bswap64(x) __builtin_bswap64 (x)
+#elif _MSC_VER
+ #include <stdlib.h>
+ #define ecb_bswap16(x) ((uint16_t)_byteswap_ushort ((uint16_t)(x)))
+ #define ecb_bswap32(x) ((uint32_t)_byteswap_ulong ((uint32_t)(x)))
+ #define ecb_bswap64(x) ((uint64_t)_byteswap_uint64 ((uint64_t)(x)))
+#else
+ ecb_function_ ecb_const uint16_t ecb_bswap16 (uint16_t x);
+ ecb_function_ ecb_const uint16_t
+ ecb_bswap16 (uint16_t x)
+ {
+ return ecb_rotl16 (x, 8);
+ }
+
+ ecb_function_ ecb_const uint32_t ecb_bswap32 (uint32_t x);
+ ecb_function_ ecb_const uint32_t
+ ecb_bswap32 (uint32_t x)
+ {
+ return (((uint32_t)ecb_bswap16 (x)) << 16) | ecb_bswap16 (x >> 16);
+ }
+
+ ecb_function_ ecb_const uint64_t ecb_bswap64 (uint64_t x);
+ ecb_function_ ecb_const uint64_t
+ ecb_bswap64 (uint64_t x)
+ {
+ return (((uint64_t)ecb_bswap32 (x)) << 32) | ecb_bswap32 (x >> 32);
+ }
+#endif
+
+#if ECB_GCC_VERSION(4,5) || ECB_CLANG_BUILTIN(__builtin_unreachable)
+ #define ecb_unreachable() __builtin_unreachable ()
+#else
+ /* this seems to work fine, but gcc always emits a warning for it :/ */
+ ecb_inline ecb_noreturn void ecb_unreachable (void);
+ ecb_inline ecb_noreturn void ecb_unreachable (void) { }
+#endif
+
+/* try to tell the compiler that some condition is definitely true */
+#define ecb_assume(cond) if (!(cond)) ecb_unreachable (); else 0
+
+ecb_inline ecb_const uint32_t ecb_byteorder_helper (void);
+ecb_inline ecb_const uint32_t
+ecb_byteorder_helper (void)
+{
+ /* the union code still generates code under pressure in gcc, */
+ /* but less than using pointers, and always seems to */
+ /* successfully return a constant. */
+ /* the reason why we have this horrible preprocessor mess */
+ /* is to avoid it in all cases, at least on common architectures */
+ /* or when using a recent enough gcc version (>= 4.6) */
+#if (defined __BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) \
+ || ((__i386 || __i386__ || _M_IX86 || ECB_GCC_AMD64 || ECB_MSVC_AMD64) && !__VOS__)
+ #define ECB_LITTLE_ENDIAN 1
+ return 0x44332211;
+#elif (defined __BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) \
+ || ((__AARCH64EB__ || __MIPSEB__ || __ARMEB__) && !__VOS__)
+ #define ECB_BIG_ENDIAN 1
+ return 0x11223344;
+#else
+ union
+ {
+ uint8_t c[4];
+ uint32_t u;
+ } u = { 0x11, 0x22, 0x33, 0x44 };
+ return u.u;
+#endif
+}
+
+ecb_inline ecb_const ecb_bool ecb_big_endian (void);
+ecb_inline ecb_const ecb_bool ecb_big_endian (void) { return ecb_byteorder_helper () == 0x11223344; }
+ecb_inline ecb_const ecb_bool ecb_little_endian (void);
+ecb_inline ecb_const ecb_bool ecb_little_endian (void) { return ecb_byteorder_helper () == 0x44332211; }
+
+/*****************************************************************************/
+/* unaligned load/store */
+
+ecb_inline uint_fast16_t ecb_be_u16_to_host (uint_fast16_t v) { return ecb_little_endian () ? ecb_bswap16 (v) : v; }
+ecb_inline uint_fast32_t ecb_be_u32_to_host (uint_fast32_t v) { return ecb_little_endian () ? ecb_bswap32 (v) : v; }
+ecb_inline uint_fast64_t ecb_be_u64_to_host (uint_fast64_t v) { return ecb_little_endian () ? ecb_bswap64 (v) : v; }
+
+ecb_inline uint_fast16_t ecb_le_u16_to_host (uint_fast16_t v) { return ecb_big_endian () ? ecb_bswap16 (v) : v; }
+ecb_inline uint_fast32_t ecb_le_u32_to_host (uint_fast32_t v) { return ecb_big_endian () ? ecb_bswap32 (v) : v; }
+ecb_inline uint_fast64_t ecb_le_u64_to_host (uint_fast64_t v) { return ecb_big_endian () ? ecb_bswap64 (v) : v; }
+
+ecb_inline uint_fast16_t ecb_peek_u16_u (const void *ptr) { uint16_t v; memcpy (&v, ptr, sizeof (v)); return v; }
+ecb_inline uint_fast32_t ecb_peek_u32_u (const void *ptr) { uint32_t v; memcpy (&v, ptr, sizeof (v)); return v; }
+ecb_inline uint_fast64_t ecb_peek_u64_u (const void *ptr) { uint64_t v; memcpy (&v, ptr, sizeof (v)); return v; }
+
+ecb_inline uint_fast16_t ecb_peek_be_u16_u (const void *ptr) { return ecb_be_u16_to_host (ecb_peek_u16_u (ptr)); }
+ecb_inline uint_fast32_t ecb_peek_be_u32_u (const void *ptr) { return ecb_be_u32_to_host (ecb_peek_u32_u (ptr)); }
+ecb_inline uint_fast64_t ecb_peek_be_u64_u (const void *ptr) { return ecb_be_u64_to_host (ecb_peek_u64_u (ptr)); }
+
+ecb_inline uint_fast16_t ecb_peek_le_u16_u (const void *ptr) { return ecb_le_u16_to_host (ecb_peek_u16_u (ptr)); }
+ecb_inline uint_fast32_t ecb_peek_le_u32_u (const void *ptr) { return ecb_le_u32_to_host (ecb_peek_u32_u (ptr)); }
+ecb_inline uint_fast64_t ecb_peek_le_u64_u (const void *ptr) { return ecb_le_u64_to_host (ecb_peek_u64_u (ptr)); }
+
+ecb_inline uint_fast16_t ecb_host_to_be_u16 (uint_fast16_t v) { return ecb_little_endian () ? ecb_bswap16 (v) : v; }
+ecb_inline uint_fast32_t ecb_host_to_be_u32 (uint_fast32_t v) { return ecb_little_endian () ? ecb_bswap32 (v) : v; }
+ecb_inline uint_fast64_t ecb_host_to_be_u64 (uint_fast64_t v) { return ecb_little_endian () ? ecb_bswap64 (v) : v; }
+
+ecb_inline uint_fast16_t ecb_host_to_le_u16 (uint_fast16_t v) { return ecb_big_endian () ? ecb_bswap16 (v) : v; }
+ecb_inline uint_fast32_t ecb_host_to_le_u32 (uint_fast32_t v) { return ecb_big_endian () ? ecb_bswap32 (v) : v; }
+ecb_inline uint_fast64_t ecb_host_to_le_u64 (uint_fast64_t v) { return ecb_big_endian () ? ecb_bswap64 (v) : v; }
+
+ecb_inline void ecb_poke_u16_u (void *ptr, uint16_t v) { memcpy (ptr, &v, sizeof (v)); }
+ecb_inline void ecb_poke_u32_u (void *ptr, uint32_t v) { memcpy (ptr, &v, sizeof (v)); }
+ecb_inline void ecb_poke_u64_u (void *ptr, uint64_t v) { memcpy (ptr, &v, sizeof (v)); }
+
+ecb_inline void ecb_poke_be_u16_u (void *ptr, uint_fast16_t v) { ecb_poke_u16_u (ptr, ecb_host_to_be_u16 (v)); }
+ecb_inline void ecb_poke_be_u32_u (void *ptr, uint_fast32_t v) { ecb_poke_u32_u (ptr, ecb_host_to_be_u32 (v)); }
+ecb_inline void ecb_poke_be_u64_u (void *ptr, uint_fast64_t v) { ecb_poke_u64_u (ptr, ecb_host_to_be_u64 (v)); }
+
+ecb_inline void ecb_poke_le_u16_u (void *ptr, uint_fast16_t v) { ecb_poke_u16_u (ptr, ecb_host_to_le_u16 (v)); }
+ecb_inline void ecb_poke_le_u32_u (void *ptr, uint_fast32_t v) { ecb_poke_u32_u (ptr, ecb_host_to_le_u32 (v)); }
+ecb_inline void ecb_poke_le_u64_u (void *ptr, uint_fast64_t v) { ecb_poke_u64_u (ptr, ecb_host_to_le_u64 (v)); }
+
+#if ECB_CPP
+
+inline uint8_t ecb_bswap (uint8_t v) { return v; }
+inline uint16_t ecb_bswap (uint16_t v) { return ecb_bswap16 (v); }
+inline uint32_t ecb_bswap (uint32_t v) { return ecb_bswap32 (v); }
+inline uint64_t ecb_bswap (uint64_t v) { return ecb_bswap64 (v); }
+
+template<typename T> inline T ecb_be_to_host (T v) { return ecb_little_endian () ? ecb_bswap (v) : v; }
+template<typename T> inline T ecb_le_to_host (T v) { return ecb_big_endian () ? ecb_bswap (v) : v; }
+template<typename T> inline T ecb_peek (const void *ptr) { return *(const T *)ptr; }
+template<typename T> inline T ecb_peek_be (const void *ptr) { return ecb_be_to_host (ecb_peek <T> (ptr)); }
+template<typename T> inline T ecb_peek_le (const void *ptr) { return ecb_le_to_host (ecb_peek <T> (ptr)); }
+template<typename T> inline T ecb_peek_u (const void *ptr) { T v; memcpy (&v, ptr, sizeof (v)); return v; }
+template<typename T> inline T ecb_peek_be_u (const void *ptr) { return ecb_be_to_host (ecb_peek_u<T> (ptr)); }
+template<typename T> inline T ecb_peek_le_u (const void *ptr) { return ecb_le_to_host (ecb_peek_u<T> (ptr)); }
+
+template<typename T> inline T ecb_host_to_be (T v) { return ecb_little_endian () ? ecb_bswap (v) : v; }
+template<typename T> inline T ecb_host_to_le (T v) { return ecb_big_endian () ? ecb_bswap (v) : v; }
+template<typename T> inline void ecb_poke (void *ptr, T v) { *(T *)ptr = v; }
+template<typename T> inline void ecb_poke_be (void *ptr, T v) { return ecb_poke <T> (ptr, ecb_host_to_be (v)); }
+template<typename T> inline void ecb_poke_le (void *ptr, T v) { return ecb_poke <T> (ptr, ecb_host_to_le (v)); }
+template<typename T> inline void ecb_poke_u (void *ptr, T v) { memcpy (ptr, &v, sizeof (v)); }
+template<typename T> inline void ecb_poke_be_u (void *ptr, T v) { return ecb_poke_u<T> (ptr, ecb_host_to_be (v)); }
+template<typename T> inline void ecb_poke_le_u (void *ptr, T v) { return ecb_poke_u<T> (ptr, ecb_host_to_le (v)); }
+
+#endif
+
+/*****************************************************************************/
+
+#if ECB_GCC_VERSION(3,0) || ECB_C99
+ #define ecb_mod(m,n) ((m) % (n) + ((m) % (n) < 0 ? (n) : 0))
+#else
+ #define ecb_mod(m,n) ((m) < 0 ? ((n) - 1 - ((-1 - (m)) % (n))) : ((m) % (n)))
+#endif
+
+#if ECB_CPP
+ template<typename T>
+ static inline T ecb_div_rd (T val, T div)
+ {
+ return val < 0 ? - ((-val + div - 1) / div) : (val ) / div;
+ }
+ template<typename T>
+ static inline T ecb_div_ru (T val, T div)
+ {
+ return val < 0 ? - ((-val ) / div) : (val + div - 1) / div;
+ }
+#else
+ #define ecb_div_rd(val,div) ((val) < 0 ? - ((-(val) + (div) - 1) / (div)) : ((val) ) / (div))
+ #define ecb_div_ru(val,div) ((val) < 0 ? - ((-(val) ) / (div)) : ((val) + (div) - 1) / (div))
+#endif
+
+#if ecb_cplusplus_does_not_suck
+ /* does not work for local types (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2657.htm) */
+ template<typename T, int N>
+ static inline int ecb_array_length (const T (&arr)[N])
+ {
+ return N;
+ }
+#else
+ #define ecb_array_length(name) (sizeof (name) / sizeof (name [0]))
+#endif
+
+/*****************************************************************************/
+
+ecb_function_ ecb_const uint32_t ecb_binary16_to_binary32 (uint32_t x);
+ecb_function_ ecb_const uint32_t
+ecb_binary16_to_binary32 (uint32_t x)
+{
+ unsigned int s = (x & 0x8000) << (31 - 15);
+ int e = (x >> 10) & 0x001f;
+ unsigned int m = x & 0x03ff;
+
+ if (ecb_expect_false (e == 31))
+ /* infinity or NaN */
+ e = 255 - (127 - 15);
+ else if (ecb_expect_false (!e))
+ {
+ if (ecb_expect_true (!m))
+ /* zero, handled by code below by forcing e to 0 */
+ e = 0 - (127 - 15);
+ else
+ {
+ /* subnormal, renormalise */
+ unsigned int s = 10 - ecb_ld32 (m);
+
+ m = (m << s) & 0x3ff; /* mask implicit bit */
+ e -= s - 1;
+ }
+ }
+
+ /* e and m now are normalised, or zero, (or inf or nan) */
+ e += 127 - 15;
+
+ return s | (e << 23) | (m << (23 - 10));
+}
+
+ecb_function_ ecb_const uint16_t ecb_binary32_to_binary16 (uint32_t x);
+ecb_function_ ecb_const uint16_t
+ecb_binary32_to_binary16 (uint32_t x)
+{
+ unsigned int s = (x >> 16) & 0x00008000; /* sign bit, the easy part */
+ unsigned int e = ((x >> 23) & 0x000000ff) - (127 - 15); /* the desired exponent */
+ unsigned int m = x & 0x007fffff;
+
+ x &= 0x7fffffff;
+
+ /* if it's within range of binary16 normals, use fast path */
+ if (ecb_expect_true (0x38800000 <= x && x <= 0x477fefff))
+ {
+ /* mantissa round-to-even */
+ m += 0x00000fff + ((m >> (23 - 10)) & 1);
+
+ /* handle overflow */
+ if (ecb_expect_false (m >= 0x00800000))
+ {
+ m >>= 1;
+ e += 1;
+ }
+
+ return s | (e << 10) | (m >> (23 - 10));
+ }
+
+ /* handle large numbers and infinity */
+ if (ecb_expect_true (0x477fefff < x && x <= 0x7f800000))
+ return s | 0x7c00;
+
+ /* handle zero, subnormals and small numbers */
+ if (ecb_expect_true (x < 0x38800000))
+ {
+ /* zero */
+ if (ecb_expect_true (!x))
+ return s;
+
+ /* handle subnormals */
+
+ /* too small, will be zero */
+ if (e < (14 - 24)) /* might not be sharp, but is good enough */
+ return s;
+
+ m |= 0x00800000; /* make implicit bit explicit */
+
+ /* very tricky - we need to round to the nearest e (+10) bit value */
+ {
+ unsigned int bits = 14 - e;
+ unsigned int half = (1 << (bits - 1)) - 1;
+ unsigned int even = (m >> bits) & 1;
+
+ /* if this overflows, we will end up with a normalised number */
+ m = (m + half + even) >> bits;
+ }
+
+ return s | m;
+ }
+
+ /* handle NaNs, preserve leftmost nan bits, but make sure we don't turn them into infinities */
+ m >>= 13;
+
+ return s | 0x7c00 | m | !m;
+}
+
+/*******************************************************************************/
+/* floating point stuff, can be disabled by defining ECB_NO_LIBM */
+
+/* basically, everything uses "ieee pure-endian" floating point numbers */
+/* the only noteworthy exception is ancient armle, which uses order 43218765 */
+#if 0 \
+ || __i386 || __i386__ \
+ || ECB_GCC_AMD64 \
+ || __powerpc__ || __ppc__ || __powerpc64__ || __ppc64__ \
+ || defined __s390__ || defined __s390x__ \
+ || defined __mips__ \
+ || defined __alpha__ \
+ || defined __hppa__ \
+ || defined __ia64__ \
+ || defined __m68k__ \
+ || defined __m88k__ \
+ || defined __sh__ \
+ || defined _M_IX86 || defined ECB_MSVC_AMD64 || defined _M_IA64 \
+ || (defined __arm__ && (defined __ARM_EABI__ || defined __EABI__ || defined __VFP_FP__ || defined _WIN32_WCE || defined __ANDROID__)) \
+ || defined __aarch64__
+ #define ECB_STDFP 1
+#else
+ #define ECB_STDFP 0
+#endif
+
+#ifndef ECB_NO_LIBM
+
+ #include <math.h> /* for frexp*, ldexp*, INFINITY, NAN */
+
+ /* only the oldest of old doesn't have this one. solaris. */
+ #ifdef INFINITY
+ #define ECB_INFINITY INFINITY
+ #else
+ #define ECB_INFINITY HUGE_VAL
+ #endif
+
+ #ifdef NAN
+ #define ECB_NAN NAN
+ #else
+ #define ECB_NAN ECB_INFINITY
+ #endif
+
+ #if ECB_C99 || _XOPEN_VERSION >= 600 || _POSIX_VERSION >= 200112L
+ #define ecb_ldexpf(x,e) ldexpf ((x), (e))
+ #define ecb_frexpf(x,e) frexpf ((x), (e))
+ #else
+ #define ecb_ldexpf(x,e) (float) ldexp ((double) (x), (e))
+ #define ecb_frexpf(x,e) (float) frexp ((double) (x), (e))
+ #endif
+
+ /* convert a float to ieee single/binary32 */
+ ecb_function_ ecb_const uint32_t ecb_float_to_binary32 (float x);
+ ecb_function_ ecb_const uint32_t
+ ecb_float_to_binary32 (float x)
+ {
+ uint32_t r;
+
+ #if ECB_STDFP
+ memcpy (&r, &x, 4);
+ #else
+ /* slow emulation, works for anything but -0 */
+ uint32_t m;
+ int e;
+
+ if (x == 0e0f ) return 0x00000000U;
+ if (x > +3.40282346638528860e+38f) return 0x7f800000U;
+ if (x < -3.40282346638528860e+38f) return 0xff800000U;
+ if (x != x ) return 0x7fbfffffU;
+
+ m = ecb_frexpf (x, &e) * 0x1000000U;
+
+ r = m & 0x80000000U;
+
+ if (r)
+ m = -m;
+
+ if (e <= -126)
+ {
+ m &= 0xffffffU;
+ m >>= (-125 - e);
+ e = -126;
+ }
+
+ r |= (e + 126) << 23;
+ r |= m & 0x7fffffU;
+ #endif
+
+ return r;
+ }
+
+ /* converts an ieee single/binary32 to a float */
+ ecb_function_ ecb_const float ecb_binary32_to_float (uint32_t x);
+ ecb_function_ ecb_const float
+ ecb_binary32_to_float (uint32_t x)
+ {
+ float r;
+
+ #if ECB_STDFP
+ memcpy (&r, &x, 4);
+ #else
+ /* emulation, only works for normals and subnormals and +0 */
+ int neg = x >> 31;
+ int e = (x >> 23) & 0xffU;
+
+ x &= 0x7fffffU;
+
+ if (e)
+ x |= 0x800000U;
+ else
+ e = 1;
+
+ /* we distrust ldexpf a bit and do the 2**-24 scaling by an extra multiply */
+ r = ecb_ldexpf (x * (0.5f / 0x800000U), e - 126);
+
+ r = neg ? -r : r;
+ #endif
+
+ return r;
+ }
+
+ /* convert a double to ieee double/binary64 */
+ ecb_function_ ecb_const uint64_t ecb_double_to_binary64 (double x);
+ ecb_function_ ecb_const uint64_t
+ ecb_double_to_binary64 (double x)
+ {
+ uint64_t r;
+
+ #if ECB_STDFP
+ memcpy (&r, &x, 8);
+ #else
+ /* slow emulation, works for anything but -0 */
+ uint64_t m;
+ int e;
+
+ if (x == 0e0 ) return 0x0000000000000000U;
+ if (x > +1.79769313486231470e+308) return 0x7ff0000000000000U;
+ if (x < -1.79769313486231470e+308) return 0xfff0000000000000U;
+ if (x != x ) return 0X7ff7ffffffffffffU;
+
+ m = frexp (x, &e) * 0x20000000000000U;
+
+ r = m & 0x8000000000000000;;
+
+ if (r)
+ m = -m;
+
+ if (e <= -1022)
+ {
+ m &= 0x1fffffffffffffU;
+ m >>= (-1021 - e);
+ e = -1022;
+ }
+
+ r |= ((uint64_t)(e + 1022)) << 52;
+ r |= m & 0xfffffffffffffU;
+ #endif
+
+ return r;
+ }
+
+ /* converts an ieee double/binary64 to a double */
+ ecb_function_ ecb_const double ecb_binary64_to_double (uint64_t x);
+ ecb_function_ ecb_const double
+ ecb_binary64_to_double (uint64_t x)
+ {
+ double r;
+
+ #if ECB_STDFP
+ memcpy (&r, &x, 8);
+ #else
+ /* emulation, only works for normals and subnormals and +0 */
+ int neg = x >> 63;
+ int e = (x >> 52) & 0x7ffU;
+
+ x &= 0xfffffffffffffU;
+
+ if (e)
+ x |= 0x10000000000000U;
+ else
+ e = 1;
+
+ /* we distrust ldexp a bit and do the 2**-53 scaling by an extra multiply */
+ r = ldexp (x * (0.5 / 0x10000000000000U), e - 1022);
+
+ r = neg ? -r : r;
+ #endif
+
+ return r;
+ }
+
+ /* convert a float to ieee half/binary16 */
+ ecb_function_ ecb_const uint16_t ecb_float_to_binary16 (float x);
+ ecb_function_ ecb_const uint16_t
+ ecb_float_to_binary16 (float x)
+ {
+ return ecb_binary32_to_binary16 (ecb_float_to_binary32 (x));
+ }
+
+ /* convert an ieee half/binary16 to float */
+ ecb_function_ ecb_const float ecb_binary16_to_float (uint16_t x);
+ ecb_function_ ecb_const float
+ ecb_binary16_to_float (uint16_t x)
+ {
+ return ecb_binary32_to_float (ecb_binary16_to_binary32 (x));
+ }
+
+#endif
+
+#endif
+
+/* ECB.H END */
+
+#if ECB_MEMORY_FENCE_NEEDS_PTHREADS
+/* if your architecture doesn't need memory fences, e.g. because it is
+ * single-cpu/core, or if you use libev in a project that doesn't use libev
+ * from multiple threads, then you can define ECB_NO_THREADS when compiling
+ * libev, in which cases the memory fences become nops.
+ * alternatively, you can remove this #error and link against libpthread,
+ * which will then provide the memory fences.
+ */
+# error "memory fences not defined for your architecture, please report"
+#endif
+
+#ifndef ECB_MEMORY_FENCE
+# define ECB_MEMORY_FENCE do { } while (0)
+# define ECB_MEMORY_FENCE_ACQUIRE ECB_MEMORY_FENCE
+# define ECB_MEMORY_FENCE_RELEASE ECB_MEMORY_FENCE
+#endif
+
+#define inline_size ecb_inline
+
+#if EV_FEATURE_CODE
+# define inline_speed ecb_inline
+#else
+# define inline_speed ecb_noinline static
+#endif
+
+/*****************************************************************************/
+/* raw syscall wrappers */
+
+#if EV_NEED_SYSCALL
+
+#include <sys/syscall.h>
+
+/*
+ * define some syscall wrappers for common architectures
+ * this is mostly for nice looks during debugging, not performance.
+ * our syscalls return < 0, not == -1, on error. which is good
+ * enough for linux aio.
+ * TODO: arm is also common nowadays, maybe even mips and x86
+ * TODO: after implementing this, it suddenly looks like overkill, but its hard to remove...
+ */
+#if __GNUC__ && __linux && ECB_AMD64 && !EV_FEATURE_CODE
+ /* the costly errno access probably kills this for size optimisation */
+
+ #define ev_syscall(nr,narg,arg1,arg2,arg3,arg4,arg5,arg6) \
+ ({ \
+ long res; \
+ register unsigned long r6 __asm__ ("r9" ); \
+ register unsigned long r5 __asm__ ("r8" ); \
+ register unsigned long r4 __asm__ ("r10"); \
+ register unsigned long r3 __asm__ ("rdx"); \
+ register unsigned long r2 __asm__ ("rsi"); \
+ register unsigned long r1 __asm__ ("rdi"); \
+ if (narg >= 6) r6 = (unsigned long)(arg6); \
+ if (narg >= 5) r5 = (unsigned long)(arg5); \
+ if (narg >= 4) r4 = (unsigned long)(arg4); \
+ if (narg >= 3) r3 = (unsigned long)(arg3); \
+ if (narg >= 2) r2 = (unsigned long)(arg2); \
+ if (narg >= 1) r1 = (unsigned long)(arg1); \
+ __asm__ __volatile__ ( \
+ "syscall\n\t" \
+ : "=a" (res) \
+ : "0" (nr), "r" (r1), "r" (r2), "r" (r3), "r" (r4), "r" (r5) \
+ : "cc", "r11", "cx", "memory"); \
+ errno = -res; \
+ res; \
+ })
+
+#endif
+
+#ifdef ev_syscall
+ #define ev_syscall0(nr) ev_syscall (nr, 0, 0, 0, 0, 0, 0, 0)
+ #define ev_syscall1(nr,arg1) ev_syscall (nr, 1, arg1, 0, 0, 0, 0, 0)
+ #define ev_syscall2(nr,arg1,arg2) ev_syscall (nr, 2, arg1, arg2, 0, 0, 0, 0)
+ #define ev_syscall3(nr,arg1,arg2,arg3) ev_syscall (nr, 3, arg1, arg2, arg3, 0, 0, 0)
+ #define ev_syscall4(nr,arg1,arg2,arg3,arg4) ev_syscall (nr, 3, arg1, arg2, arg3, arg4, 0, 0)
+ #define ev_syscall5(nr,arg1,arg2,arg3,arg4,arg5) ev_syscall (nr, 5, arg1, arg2, arg3, arg4, arg5, 0)
+ #define ev_syscall6(nr,arg1,arg2,arg3,arg4,arg5,arg6) ev_syscall (nr, 6, arg1, arg2, arg3, arg4, arg5,arg6)
+#else
+ #define ev_syscall0(nr) syscall (nr)
+ #define ev_syscall1(nr,arg1) syscall (nr, arg1)
+ #define ev_syscall2(nr,arg1,arg2) syscall (nr, arg1, arg2)
+ #define ev_syscall3(nr,arg1,arg2,arg3) syscall (nr, arg1, arg2, arg3)
+ #define ev_syscall4(nr,arg1,arg2,arg3,arg4) syscall (nr, arg1, arg2, arg3, arg4)
+ #define ev_syscall5(nr,arg1,arg2,arg3,arg4,arg5) syscall (nr, arg1, arg2, arg3, arg4, arg5)
+ #define ev_syscall6(nr,arg1,arg2,arg3,arg4,arg5,arg6) syscall (nr, arg1, arg2, arg3, arg4, arg5,arg6)
+#endif
+
+#endif
+
+/*****************************************************************************/
+
+#define NUMPRI (EV_MAXPRI - EV_MINPRI + 1)
+
+#if EV_MINPRI == EV_MAXPRI
+# define ABSPRI(w) (((W)w), 0)
+#else
+# define ABSPRI(w) (((W)w)->priority - EV_MINPRI)
+#endif
+
+#define EMPTY /* required for microsofts broken pseudo-c compiler */
+
+typedef ev_watcher *W;
+typedef ev_watcher_list *WL;
+typedef ev_watcher_time *WT;
+
+#define ev_active(w) ((W)(w))->active
+#define ev_at(w) ((WT)(w))->at
+
+#if EV_USE_REALTIME
+/* sig_atomic_t is used to avoid per-thread variables or locking but still */
+/* giving it a reasonably high chance of working on typical architectures */
+static EV_ATOMIC_T have_realtime; /* did clock_gettime (CLOCK_REALTIME) work? */
+#endif
+
+#if EV_USE_MONOTONIC
+static EV_ATOMIC_T have_monotonic; /* did clock_gettime (CLOCK_MONOTONIC) work? */
+#endif
+
+#ifndef EV_FD_TO_WIN32_HANDLE
+# define EV_FD_TO_WIN32_HANDLE(fd) _get_osfhandle (fd)
+#endif
+#ifndef EV_WIN32_HANDLE_TO_FD
+# define EV_WIN32_HANDLE_TO_FD(handle) _open_osfhandle (handle, 0)
+#endif
+#ifndef EV_WIN32_CLOSE_FD
+# define EV_WIN32_CLOSE_FD(fd) close (fd)
+#endif
+
+#ifdef _WIN32
+# include "ev_win32.c"
+#endif
+
+/*****************************************************************************/
+
+#if EV_USE_LINUXAIO
+# include <linux/aio_abi.h> /* probably only needed for aio_context_t */
+#endif
+
+/* define a suitable floor function (only used by periodics atm) */
+
+#if EV_USE_FLOOR
+# include <math.h>
+# define ev_floor(v) floor (v)
+#else
+
+#include <float.h>
+
+/* a floor() replacement function, should be independent of ev_tstamp type */
+ecb_noinline
+static ev_tstamp
+ev_floor (ev_tstamp v)
+{
+ /* the choice of shift factor is not terribly important */
+#if FLT_RADIX != 2 /* assume FLT_RADIX == 10 */
+ const ev_tstamp shift = sizeof (unsigned long) >= 8 ? 10000000000000000000. : 1000000000.;
+#else
+ const ev_tstamp shift = sizeof (unsigned long) >= 8 ? 18446744073709551616. : 4294967296.;
+#endif
+
+ /* special treatment for negative arguments */
+ if (ecb_expect_false (v < 0.))
+ {
+ ev_tstamp f = -ev_floor (-v);
+
+ return f - (f == v ? 0 : 1);
+ }
+
+ /* argument too large for an unsigned long? then reduce it */
+ if (ecb_expect_false (v >= shift))
+ {
+ ev_tstamp f;
+
+ if (v == v - 1.)
+ return v; /* very large numbers are assumed to be integer */
+
+ f = shift * ev_floor (v * (1. / shift));
+ return f + ev_floor (v - f);
+ }
+
+ /* fits into an unsigned long */
+ return (unsigned long)v;
+}
+
+#endif
+
+/*****************************************************************************/
+
+#ifdef __linux
+# include <sys/utsname.h>
+#endif
+
+ecb_noinline ecb_cold
+static unsigned int
+ev_linux_version (void)
+{
+#ifdef __linux
+ unsigned int v = 0;
+ struct utsname buf;
+ int i;
+ char *p = buf.release;
+
+ if (uname (&buf))
+ return 0;
+
+ for (i = 3+1; --i; )
+ {
+ unsigned int c = 0;
+
+ for (;;)
+ {
+ if (*p >= '0' && *p <= '9')
+ c = c * 10 + *p++ - '0';
+ else
+ {
+ p += *p == '.';
+ break;
+ }
+ }
+
+ v = (v << 8) | c;
+ }
+
+ return v;
+#else
+ return 0;
+#endif
+}
+
+/*****************************************************************************/
+
+#if EV_AVOID_STDIO
+ecb_noinline ecb_cold
+static void
+ev_printerr (const char *msg)
+{
+ write (STDERR_FILENO, msg, strlen (msg));
+}
+#endif
+
+static void (*syserr_cb)(const char *msg) EV_NOEXCEPT;
+
+ecb_cold
+void
+ev_set_syserr_cb (void (*cb)(const char *msg) EV_NOEXCEPT) EV_NOEXCEPT
+{
+ syserr_cb = cb;
+}
+
+ecb_noinline ecb_cold
+static void
+ev_syserr (const char *msg)
+{
+ if (!msg)
+ msg = "(libev) system error";
+
+ if (syserr_cb)
+ syserr_cb (msg);
+ else
+ {
+#if EV_AVOID_STDIO
+ ev_printerr (msg);
+ ev_printerr (": ");
+ ev_printerr (strerror (errno));
+ ev_printerr ("\n");
+#else
+ perror (msg);
+#endif
+ abort ();
+ }
+}
+
+static void *
+ev_realloc_emul (void *ptr, long size) EV_NOEXCEPT
+{
+ /* some systems, notably openbsd and darwin, fail to properly
+ * implement realloc (x, 0) (as required by both ansi c-89 and
+ * the single unix specification, so work around them here.
+ * recently, also (at least) fedora and debian started breaking it,
+ * despite documenting it otherwise.
+ */
+
+ if (size)
+ return realloc (ptr, size);
+
+ free (ptr);
+ return 0;
+}
+
+static void *(*alloc)(void *ptr, long size) EV_NOEXCEPT = ev_realloc_emul;
+
+ecb_cold
+void
+ev_set_allocator (void *(*cb)(void *ptr, long size) EV_NOEXCEPT) EV_NOEXCEPT
+{
+ alloc = cb;
+}
+
+inline_speed void *
+ev_realloc (void *ptr, long size)
+{
+ ptr = alloc (ptr, size);
+
+ if (!ptr && size)
+ {
+#if EV_AVOID_STDIO
+ ev_printerr ("(libev) memory allocation failed, aborting.\n");
+#else
+ fprintf (stderr, "(libev) cannot allocate %ld bytes, aborting.", size);
+#endif
+ abort ();
+ }
+
+ return ptr;
+}
+
+#define ev_malloc(size) ev_realloc (0, (size))
+#define ev_free(ptr) ev_realloc ((ptr), 0)
+
+/*****************************************************************************/
+
+/* set in reify when reification needed */
+#define EV_ANFD_REIFY 1
+
+/* file descriptor info structure */
+typedef struct
+{
+ WL head;
+ unsigned char events; /* the events watched for */
+ unsigned char reify; /* flag set when this ANFD needs reification (EV_ANFD_REIFY, EV__IOFDSET) */
+ unsigned char emask; /* some backends store the actual kernel mask in here */
+ unsigned char eflags; /* flags field for use by backends */
+#if EV_USE_EPOLL
+ unsigned int egen; /* generation counter to counter epoll bugs */
+#endif
+#if EV_SELECT_IS_WINSOCKET || EV_USE_IOCP
+ SOCKET handle;
+#endif
+#if EV_USE_IOCP
+ OVERLAPPED or, ow;
+#endif
+} ANFD;
+
+/* stores the pending event set for a given watcher */
+typedef struct
+{
+ W w;
+ int events; /* the pending event set for the given watcher */
+} ANPENDING;
+
+#if EV_USE_INOTIFY
+/* hash table entry per inotify-id */
+typedef struct
+{
+ WL head;
+} ANFS;
+#endif
+
+/* Heap Entry */
+#if EV_HEAP_CACHE_AT
+ /* a heap element */
+ typedef struct {
+ ev_tstamp at;
+ WT w;
+ } ANHE;
+
+ #define ANHE_w(he) (he).w /* access watcher, read-write */
+ #define ANHE_at(he) (he).at /* access cached at, read-only */
+ #define ANHE_at_cache(he) (he).at = (he).w->at /* update at from watcher */
+#else
+ /* a heap element */
+ typedef WT ANHE;
+
+ #define ANHE_w(he) (he)
+ #define ANHE_at(he) (he)->at
+ #define ANHE_at_cache(he)
+#endif
+
+#if EV_MULTIPLICITY
+
+ struct ev_loop
+ {
+ ev_tstamp ev_rt_now;
+ #define ev_rt_now ((loop)->ev_rt_now)
+ #define VAR(name,decl) decl;
+ #include "ev_vars.h"
+ #undef VAR
+ };
+ #include "ev_wrap.h"
+
+ static struct ev_loop default_loop_struct;
+ EV_API_DECL struct ev_loop *ev_default_loop_ptr = 0; /* needs to be initialised to make it a definition despite extern */
+
+#else
+
+ EV_API_DECL ev_tstamp ev_rt_now = EV_TS_CONST (0.); /* needs to be initialised to make it a definition despite extern */
+ #define VAR(name,decl) static decl;
+ #include "ev_vars.h"
+ #undef VAR
+
+ static int ev_default_loop_ptr;
+
+#endif
+
+#if EV_FEATURE_API
+# define EV_RELEASE_CB if (ecb_expect_false (release_cb)) release_cb (EV_A)
+# define EV_ACQUIRE_CB if (ecb_expect_false (acquire_cb)) acquire_cb (EV_A)
+# define EV_INVOKE_PENDING invoke_cb (EV_A)
+#else
+# define EV_RELEASE_CB (void)0
+# define EV_ACQUIRE_CB (void)0
+# define EV_INVOKE_PENDING ev_invoke_pending (EV_A)
+#endif
+
+#define EVBREAK_RECURSE 0x80
+
+/*****************************************************************************/
+
+#ifndef EV_HAVE_EV_TIME
+ev_tstamp
+ev_time (void) EV_NOEXCEPT
+{
+#if EV_USE_REALTIME
+ if (ecb_expect_true (have_realtime))
+ {
+ struct timespec ts;
+ clock_gettime (CLOCK_REALTIME, &ts);
+ return EV_TS_GET (ts);
+ }
+#endif
+
+ {
+ struct timeval tv;
+ gettimeofday (&tv, 0);
+ return EV_TV_GET (tv);
+ }
+}
+#endif
+
+inline_size ev_tstamp
+get_clock (void)
+{
+#if EV_USE_MONOTONIC
+ if (ecb_expect_true (have_monotonic))
+ {
+ struct timespec ts;
+ clock_gettime (CLOCK_MONOTONIC, &ts);
+ return EV_TS_GET (ts);
+ }
+#endif
+
+ return ev_time ();
+}
+
+#if EV_MULTIPLICITY
+ev_tstamp
+ev_now (EV_P) EV_NOEXCEPT
+{
+ return ev_rt_now;
+}
+#endif
+
+void
+ev_sleep (ev_tstamp delay) EV_NOEXCEPT
+{
+ if (delay > EV_TS_CONST (0.))
+ {
+#if EV_USE_NANOSLEEP
+ struct timespec ts;
+
+ EV_TS_SET (ts, delay);
+ nanosleep (&ts, 0);
+#elif defined _WIN32
+ /* maybe this should round up, as ms is very low resolution */
+ /* compared to select (µs) or nanosleep (ns) */
+ Sleep ((unsigned long)(EV_TS_TO_MSEC (delay)));
+#else
+ struct timeval tv;
+
+ /* here we rely on sys/time.h + sys/types.h + unistd.h providing select */
+ /* something not guaranteed by newer posix versions, but guaranteed */
+ /* by older ones */
+ EV_TV_SET (tv, delay);
+ select (0, 0, 0, 0, &tv);
+#endif
+ }
+}
+
+/*****************************************************************************/
+
+#define MALLOC_ROUND 4096 /* prefer to allocate in chunks of this size, must be 2**n and >> 4 longs */
+
+/* find a suitable new size for the given array, */
+/* hopefully by rounding to a nice-to-malloc size */
+inline_size int
+array_nextsize (int elem, int cur, int cnt)
+{
+ int ncur = cur + 1;
+
+ do
+ ncur <<= 1;
+ while (cnt > ncur);
+
+ /* if size is large, round to MALLOC_ROUND - 4 * longs to accommodate malloc overhead */
+ if (elem * ncur > MALLOC_ROUND - sizeof (void *) * 4)
+ {
+ ncur *= elem;
+ ncur = (ncur + elem + (MALLOC_ROUND - 1) + sizeof (void *) * 4) & ~(MALLOC_ROUND - 1);
+ ncur = ncur - sizeof (void *) * 4;
+ ncur /= elem;
+ }
+
+ return ncur;
+}
+
+ecb_noinline ecb_cold
+static void *
+array_realloc (int elem, void *base, int *cur, int cnt)
+{
+ *cur = array_nextsize (elem, *cur, cnt);
+ return ev_realloc (base, elem * *cur);
+}
+
+#define array_needsize_noinit(base,offset,count)
+
+#define array_needsize_zerofill(base,offset,count) \
+ memset ((void *)(base + offset), 0, sizeof (*(base)) * (count))
+
+#define array_needsize(type,base,cur,cnt,init) \
+ if (ecb_expect_false ((cnt) > (cur))) \
+ { \
+ ecb_unused int ocur_ = (cur); \
+ (base) = (type *)array_realloc \
+ (sizeof (type), (base), &(cur), (cnt)); \
+ init ((base), ocur_, ((cur) - ocur_)); \
+ }
+
+#if 0
+#define array_slim(type,stem) \
+ if (stem ## max < array_roundsize (stem ## cnt >> 2)) \
+ { \
+ stem ## max = array_roundsize (stem ## cnt >> 1); \
+ base = (type *)ev_realloc (base, sizeof (type) * (stem ## max));\
+ fprintf (stderr, "slimmed down " # stem " to %d\n", stem ## max);/*D*/\
+ }
+#endif
+
+#define array_free(stem, idx) \
+ ev_free (stem ## s idx); stem ## cnt idx = stem ## max idx = 0; stem ## s idx = 0
+
+/*****************************************************************************/
+
+/* dummy callback for pending events */
+ecb_noinline
+static void
+pendingcb (EV_P_ ev_prepare *w, int revents)
+{
+}
+
+ecb_noinline
+void
+ev_feed_event (EV_P_ void *w, int revents) EV_NOEXCEPT
+{
+ W w_ = (W)w;
+ int pri = ABSPRI (w_);
+
+ if (ecb_expect_false (w_->pending))
+ pendings [pri][w_->pending - 1].events |= revents;
+ else
+ {
+ w_->pending = ++pendingcnt [pri];
+ array_needsize (ANPENDING, pendings [pri], pendingmax [pri], w_->pending, array_needsize_noinit);
+ pendings [pri][w_->pending - 1].w = w_;
+ pendings [pri][w_->pending - 1].events = revents;
+ }
+
+ pendingpri = NUMPRI - 1;
+}
+
+inline_speed void
+feed_reverse (EV_P_ W w)
+{
+ array_needsize (W, rfeeds, rfeedmax, rfeedcnt + 1, array_needsize_noinit);
+ rfeeds [rfeedcnt++] = w;
+}
+
+inline_size void
+feed_reverse_done (EV_P_ int revents)
+{
+ do
+ ev_feed_event (EV_A_ rfeeds [--rfeedcnt], revents);
+ while (rfeedcnt);
+}
+
+inline_speed void
+queue_events (EV_P_ W *events, int eventcnt, int type)
+{
+ int i;
+
+ for (i = 0; i < eventcnt; ++i)
+ ev_feed_event (EV_A_ events [i], type);
+}
+
+/*****************************************************************************/
+
+inline_speed void
+fd_event_nocheck (EV_P_ int fd, int revents)
+{
+ ANFD *anfd = anfds + fd;
+ ev_io *w;
+
+ for (w = (ev_io *)anfd->head; w; w = (ev_io *)((WL)w)->next)
+ {
+ int ev = w->events & revents;
+
+ if (ev)
+ ev_feed_event (EV_A_ (W)w, ev);
+ }
+}
+
+/* do not submit kernel events for fds that have reify set */
+/* because that means they changed while we were polling for new events */
+inline_speed void
+fd_event (EV_P_ int fd, int revents)
+{
+ ANFD *anfd = anfds + fd;
+
+ if (ecb_expect_true (!anfd->reify))
+ fd_event_nocheck (EV_A_ fd, revents);
+}
+
+void
+ev_feed_fd_event (EV_P_ int fd, int revents) EV_NOEXCEPT
+{
+ if (fd >= 0 && fd < anfdmax)
+ fd_event_nocheck (EV_A_ fd, revents);
+}
+
+/* make sure the external fd watch events are in-sync */
+/* with the kernel/libev internal state */
+inline_size void
+fd_reify (EV_P)
+{
+ int i;
+
+ /* most backends do not modify the fdchanges list in backend_modfiy.
+ * except io_uring, which has fixed-size buffers which might force us
+ * to handle events in backend_modify, causing fdchanges to be amended,
+ * which could result in an endless loop.
+ * to avoid this, we do not dynamically handle fds that were added
+ * during fd_reify. that means that for those backends, fdchangecnt
+ * might be non-zero during poll, which must cause them to not block.
+ * to not put too much of a burden on other backends, this detail
+ * needs to be handled in the backend.
+ */
+ int changecnt = fdchangecnt;
+
+#if EV_SELECT_IS_WINSOCKET || EV_USE_IOCP
+ for (i = 0; i < changecnt; ++i)
+ {
+ int fd = fdchanges [i];
+ ANFD *anfd = anfds + fd;
+
+ if (anfd->reify & EV__IOFDSET && anfd->head)
+ {
+ SOCKET handle = EV_FD_TO_WIN32_HANDLE (fd);
+
+ if (handle != anfd->handle)
+ {
+ unsigned long arg;
+
+ assert (("libev: only socket fds supported in this configuration", ioctlsocket (handle, FIONREAD, &arg) == 0));
+
+ /* handle changed, but fd didn't - we need to do it in two steps */
+ backend_modify (EV_A_ fd, anfd->events, 0);
+ anfd->events = 0;
+ anfd->handle = handle;
+ }
+ }
+ }
+#endif
+
+ for (i = 0; i < changecnt; ++i)
+ {
+ int fd = fdchanges [i];
+ ANFD *anfd = anfds + fd;
+ ev_io *w;
+
+ unsigned char o_events = anfd->events;
+ unsigned char o_reify = anfd->reify;
+
+ anfd->reify = 0;
+
+ /*if (ecb_expect_true (o_reify & EV_ANFD_REIFY)) probably a deoptimisation */
+ {
+ anfd->events = 0;
+
+ for (w = (ev_io *)anfd->head; w; w = (ev_io *)((WL)w)->next)
+ anfd->events |= (unsigned char)w->events;
+
+ if (o_events != anfd->events)
+ o_reify = EV__IOFDSET; /* actually |= */
+ }
+
+ if (o_reify & EV__IOFDSET)
+ backend_modify (EV_A_ fd, o_events, anfd->events);
+ }
+
+ /* normally, fdchangecnt hasn't changed. if it has, then new fds have been added.
+ * this is a rare case (see beginning comment in this function), so we copy them to the
+ * front and hope the backend handles this case.
+ */
+ if (ecb_expect_false (fdchangecnt != changecnt))
+ memmove (fdchanges, fdchanges + changecnt, (fdchangecnt - changecnt) * sizeof (*fdchanges));
+
+ fdchangecnt -= changecnt;
+}
+
+/* something about the given fd changed */
+inline_size
+void
+fd_change (EV_P_ int fd, int flags)
+{
+ unsigned char reify = anfds [fd].reify;
+ anfds [fd].reify = reify | flags;
+
+ if (ecb_expect_true (!reify))
+ {
+ ++fdchangecnt;
+ array_needsize (int, fdchanges, fdchangemax, fdchangecnt, array_needsize_noinit);
+ fdchanges [fdchangecnt - 1] = fd;
+ }
+}
+
+/* the given fd is invalid/unusable, so make sure it doesn't hurt us anymore */
+inline_speed ecb_cold void
+fd_kill (EV_P_ int fd)
+{
+ ev_io *w;
+
+ while ((w = (ev_io *)anfds [fd].head))
+ {
+ ev_io_stop (EV_A_ w);
+ ev_feed_event (EV_A_ (W)w, EV_ERROR | EV_READ | EV_WRITE);
+ }
+}
+
+/* check whether the given fd is actually valid, for error recovery */
+inline_size ecb_cold int
+fd_valid (int fd)
+{
+#ifdef _WIN32
+ return EV_FD_TO_WIN32_HANDLE (fd) != -1;
+#else
+ return fcntl (fd, F_GETFD) != -1;
+#endif
+}
+
+/* called on EBADF to verify fds */
+ecb_noinline ecb_cold
+static void
+fd_ebadf (EV_P)
+{
+ int fd;
+
+ for (fd = 0; fd < anfdmax; ++fd)
+ if (anfds [fd].events)
+ if (!fd_valid (fd) && errno == EBADF)
+ fd_kill (EV_A_ fd);
+}
+
+/* called on ENOMEM in select/poll to kill some fds and retry */
+ecb_noinline ecb_cold
+static void
+fd_enomem (EV_P)
+{
+ int fd;
+
+ for (fd = anfdmax; fd--; )
+ if (anfds [fd].events)
+ {
+ fd_kill (EV_A_ fd);
+ break;
+ }
+}
+
+/* usually called after fork if backend needs to re-arm all fds from scratch */
+ecb_noinline
+static void
+fd_rearm_all (EV_P)
+{
+ int fd;
+
+ for (fd = 0; fd < anfdmax; ++fd)
+ if (anfds [fd].events)
+ {
+ anfds [fd].events = 0;
+ anfds [fd].emask = 0;
+ fd_change (EV_A_ fd, EV__IOFDSET | EV_ANFD_REIFY);
+ }
+}
+
+/* used to prepare libev internal fd's */
+/* this is not fork-safe */
+inline_speed void
+fd_intern (int fd)
+{
+#ifdef _WIN32
+ unsigned long arg = 1;
+ ioctlsocket (EV_FD_TO_WIN32_HANDLE (fd), FIONBIO, &arg);
+#else
+ fcntl (fd, F_SETFD, FD_CLOEXEC);
+ fcntl (fd, F_SETFL, O_NONBLOCK);
+#endif
+}
+
+/*****************************************************************************/
+
+/*
+ * the heap functions want a real array index. array index 0 is guaranteed to not
+ * be in-use at any time. the first heap entry is at array [HEAP0]. DHEAP gives
+ * the branching factor of the d-tree.
+ */
+
+/*
+ * at the moment we allow libev the luxury of two heaps,
+ * a small-code-size 2-heap one and a ~1.5kb larger 4-heap
+ * which is more cache-efficient.
+ * the difference is about 5% with 50000+ watchers.
+ */
+#if EV_USE_4HEAP
+
+#define DHEAP 4
+#define HEAP0 (DHEAP - 1) /* index of first element in heap */
+#define HPARENT(k) ((((k) - HEAP0 - 1) / DHEAP) + HEAP0)
+#define UPHEAP_DONE(p,k) ((p) == (k))
+
+/* away from the root */
+inline_speed void
+downheap (ANHE *heap, int N, int k)
+{
+ ANHE he = heap [k];
+ ANHE *E = heap + N + HEAP0;
+
+ for (;;)
+ {
+ ev_tstamp minat;
+ ANHE *minpos;
+ ANHE *pos = heap + DHEAP * (k - HEAP0) + HEAP0 + 1;
+
+ /* find minimum child */
+ if (ecb_expect_true (pos + DHEAP - 1 < E))
+ {
+ /* fast path */ (minpos = pos + 0), (minat = ANHE_at (*minpos));
+ if ( minat > ANHE_at (pos [1])) (minpos = pos + 1), (minat = ANHE_at (*minpos));
+ if ( minat > ANHE_at (pos [2])) (minpos = pos + 2), (minat = ANHE_at (*minpos));
+ if ( minat > ANHE_at (pos [3])) (minpos = pos + 3), (minat = ANHE_at (*minpos));
+ }
+ else if (pos < E)
+ {
+ /* slow path */ (minpos = pos + 0), (minat = ANHE_at (*minpos));
+ if (pos + 1 < E && minat > ANHE_at (pos [1])) (minpos = pos + 1), (minat = ANHE_at (*minpos));
+ if (pos + 2 < E && minat > ANHE_at (pos [2])) (minpos = pos + 2), (minat = ANHE_at (*minpos));
+ if (pos + 3 < E && minat > ANHE_at (pos [3])) (minpos = pos + 3), (minat = ANHE_at (*minpos));
+ }
+ else
+ break;
+
+ if (ANHE_at (he) <= minat)
+ break;
+
+ heap [k] = *minpos;
+ ev_active (ANHE_w (*minpos)) = k;
+
+ k = minpos - heap;
+ }
+
+ heap [k] = he;
+ ev_active (ANHE_w (he)) = k;
+}
+
+#else /* not 4HEAP */
+
+#define HEAP0 1
+#define HPARENT(k) ((k) >> 1)
+#define UPHEAP_DONE(p,k) (!(p))
+
+/* away from the root */
+inline_speed void
+downheap (ANHE *heap, int N, int k)
+{
+ ANHE he = heap [k];
+
+ for (;;)
+ {
+ int c = k << 1;
+
+ if (c >= N + HEAP0)
+ break;
+
+ c += c + 1 < N + HEAP0 && ANHE_at (heap [c]) > ANHE_at (heap [c + 1])
+ ? 1 : 0;
+
+ if (ANHE_at (he) <= ANHE_at (heap [c]))
+ break;
+
+ heap [k] = heap [c];
+ ev_active (ANHE_w (heap [k])) = k;
+
+ k = c;
+ }
+
+ heap [k] = he;
+ ev_active (ANHE_w (he)) = k;
+}
+#endif
+
+/* towards the root */
+inline_speed void
+upheap (ANHE *heap, int k)
+{
+ ANHE he = heap [k];
+
+ for (;;)
+ {
+ int p = HPARENT (k);
+
+ if (UPHEAP_DONE (p, k) || ANHE_at (heap [p]) <= ANHE_at (he))
+ break;
+
+ heap [k] = heap [p];
+ ev_active (ANHE_w (heap [k])) = k;
+ k = p;
+ }
+
+ heap [k] = he;
+ ev_active (ANHE_w (he)) = k;
+}
+
+/* move an element suitably so it is in a correct place */
+inline_size void
+adjustheap (ANHE *heap, int N, int k)
+{
+ if (k > HEAP0 && ANHE_at (heap [k]) <= ANHE_at (heap [HPARENT (k)]))
+ upheap (heap, k);
+ else
+ downheap (heap, N, k);
+}
+
+/* rebuild the heap: this function is used only once and executed rarely */
+inline_size void
+reheap (ANHE *heap, int N)
+{
+ int i;
+
+ /* we don't use floyds algorithm, upheap is simpler and is more cache-efficient */
+ /* also, this is easy to implement and correct for both 2-heaps and 4-heaps */
+ for (i = 0; i < N; ++i)
+ upheap (heap, i + HEAP0);
+}
+
+/*****************************************************************************/
+
+/* associate signal watchers to a signal */
+typedef struct
+{
+ EV_ATOMIC_T pending;
+#if EV_MULTIPLICITY
+ EV_P;
+#endif
+ WL head;
+} ANSIG;
+
+static ANSIG signals [EV_NSIG - 1];
+
+/*****************************************************************************/
+
+#if EV_SIGNAL_ENABLE || EV_ASYNC_ENABLE
+
+ecb_noinline ecb_cold
+static void
+evpipe_init (EV_P)
+{
+ if (!ev_is_active (&pipe_w))
+ {
+ int fds [2];
+
+# if EV_USE_EVENTFD
+ fds [0] = -1;
+ fds [1] = eventfd (0, EFD_NONBLOCK | EFD_CLOEXEC);
+ if (fds [1] < 0 && errno == EINVAL)
+ fds [1] = eventfd (0, 0);
+
+ if (fds [1] < 0)
+# endif
+ {
+ while (pipe (fds))
+ ev_syserr ("(libev) error creating signal/async pipe");
+
+ fd_intern (fds [0]);
+ }
+
+ evpipe [0] = fds [0];
+
+ if (evpipe [1] < 0)
+ evpipe [1] = fds [1]; /* first call, set write fd */
+ else
+ {
+ /* on subsequent calls, do not change evpipe [1] */
+ /* so that evpipe_write can always rely on its value. */
+ /* this branch does not do anything sensible on windows, */
+ /* so must not be executed on windows */
+
+ dup2 (fds [1], evpipe [1]);
+ close (fds [1]);
+ }
+
+ fd_intern (evpipe [1]);
+
+ ev_io_set (&pipe_w, evpipe [0] < 0 ? evpipe [1] : evpipe [0], EV_READ);
+ ev_io_start (EV_A_ &pipe_w);
+ ev_unref (EV_A); /* watcher should not keep loop alive */
+ }
+}
+
+inline_speed void
+evpipe_write (EV_P_ EV_ATOMIC_T *flag)
+{
+ ECB_MEMORY_FENCE; /* push out the write before this function was called, acquire flag */
+
+ if (ecb_expect_true (*flag))
+ return;
+
+ *flag = 1;
+ ECB_MEMORY_FENCE_RELEASE; /* make sure flag is visible before the wakeup */
+
+ pipe_write_skipped = 1;
+
+ ECB_MEMORY_FENCE; /* make sure pipe_write_skipped is visible before we check pipe_write_wanted */
+
+ if (pipe_write_wanted)
+ {
+ int old_errno;
+
+ pipe_write_skipped = 0;
+ ECB_MEMORY_FENCE_RELEASE;
+
+ old_errno = errno; /* save errno because write will clobber it */
+
+#if EV_USE_EVENTFD
+ if (evpipe [0] < 0)
+ {
+ uint64_t counter = 1;
+ write (evpipe [1], &counter, sizeof (uint64_t));
+ }
+ else
+#endif
+ {
+#ifdef _WIN32
+ WSABUF buf;
+ DWORD sent;
+ buf.buf = (char *)&buf;
+ buf.len = 1;
+ WSASend (EV_FD_TO_WIN32_HANDLE (evpipe [1]), &buf, 1, &sent, 0, 0, 0);
+#else
+ write (evpipe [1], &(evpipe [1]), 1);
+#endif
+ }
+
+ errno = old_errno;
+ }
+}
+
+/* called whenever the libev signal pipe */
+/* got some events (signal, async) */
+static void
+pipecb (EV_P_ ev_io *iow, int revents)
+{
+ int i;
+
+ if (revents & EV_READ)
+ {
+#if EV_USE_EVENTFD
+ if (evpipe [0] < 0)
+ {
+ uint64_t counter;
+ read (evpipe [1], &counter, sizeof (uint64_t));
+ }
+ else
+#endif
+ {
+ char dummy[4];
+#ifdef _WIN32
+ WSABUF buf;
+ DWORD recvd;
+ DWORD flags = 0;
+ buf.buf = dummy;
+ buf.len = sizeof (dummy);
+ WSARecv (EV_FD_TO_WIN32_HANDLE (evpipe [0]), &buf, 1, &recvd, &flags, 0, 0);
+#else
+ read (evpipe [0], &dummy, sizeof (dummy));
+#endif
+ }
+ }
+
+ pipe_write_skipped = 0;
+
+ ECB_MEMORY_FENCE; /* push out skipped, acquire flags */
+
+#if EV_SIGNAL_ENABLE
+ if (sig_pending)
+ {
+ sig_pending = 0;
+
+ ECB_MEMORY_FENCE;
+
+ for (i = EV_NSIG - 1; i--; )
+ if (ecb_expect_false (signals [i].pending))
+ ev_feed_signal_event (EV_A_ i + 1);
+ }
+#endif
+
+#if EV_ASYNC_ENABLE
+ if (async_pending)
+ {
+ async_pending = 0;
+
+ ECB_MEMORY_FENCE;
+
+ for (i = asynccnt; i--; )
+ if (asyncs [i]->sent)
+ {
+ asyncs [i]->sent = 0;
+ ECB_MEMORY_FENCE_RELEASE;
+ ev_feed_event (EV_A_ asyncs [i], EV_ASYNC);
+ }
+ }
+#endif
+}
+
+/*****************************************************************************/
+
+void
+ev_feed_signal (int signum) EV_NOEXCEPT
+{
+#if EV_MULTIPLICITY
+ EV_P;
+ ECB_MEMORY_FENCE_ACQUIRE;
+ EV_A = signals [signum - 1].loop;
+
+ if (!EV_A)
+ return;
+#endif
+
+ signals [signum - 1].pending = 1;
+ evpipe_write (EV_A_ &sig_pending);
+}
+
+static void
+ev_sighandler (int signum)
+{
+#ifdef _WIN32
+ signal (signum, ev_sighandler);
+#endif
+
+ ev_feed_signal (signum);
+}
+
+ecb_noinline
+void
+ev_feed_signal_event (EV_P_ int signum) EV_NOEXCEPT
+{
+ WL w;
+
+ if (ecb_expect_false (signum <= 0 || signum >= EV_NSIG))
+ return;
+
+ --signum;
+
+#if EV_MULTIPLICITY
+ /* it is permissible to try to feed a signal to the wrong loop */
+ /* or, likely more useful, feeding a signal nobody is waiting for */
+
+ if (ecb_expect_false (signals [signum].loop != EV_A))
+ return;
+#endif
+
+ signals [signum].pending = 0;
+ ECB_MEMORY_FENCE_RELEASE;
+
+ for (w = signals [signum].head; w; w = w->next)
+ ev_feed_event (EV_A_ (W)w, EV_SIGNAL);
+}
+
+#if EV_USE_SIGNALFD
+static void
+sigfdcb (EV_P_ ev_io *iow, int revents)
+{
+ struct signalfd_siginfo si[2], *sip; /* these structs are big */
+
+ for (;;)
+ {
+ ssize_t res = read (sigfd, si, sizeof (si));
+
+ /* not ISO-C, as res might be -1, but works with SuS */
+ for (sip = si; (char *)sip < (char *)si + res; ++sip)
+ ev_feed_signal_event (EV_A_ sip->ssi_signo);
+
+ if (res < (ssize_t)sizeof (si))
+ break;
+ }
+}
+#endif
+
+#endif
+
+/*****************************************************************************/
+
+#if EV_CHILD_ENABLE
+static WL childs [EV_PID_HASHSIZE];
+
+static ev_signal childev;
+
+#ifndef WIFCONTINUED
+# define WIFCONTINUED(status) 0
+#endif
+
+/* handle a single child status event */
+inline_speed void
+child_reap (EV_P_ int chain, int pid, int status)
+{
+ ev_child *w;
+ int traced = WIFSTOPPED (status) || WIFCONTINUED (status);
+
+ for (w = (ev_child *)childs [chain & ((EV_PID_HASHSIZE) - 1)]; w; w = (ev_child *)((WL)w)->next)
+ {
+ if ((w->pid == pid || !w->pid)
+ && (!traced || (w->flags & 1)))
+ {
+ ev_set_priority (w, EV_MAXPRI); /* need to do it *now*, this *must* be the same prio as the signal watcher itself */
+ w->rpid = pid;
+ w->rstatus = status;
+ ev_feed_event (EV_A_ (W)w, EV_CHILD);
+ }
+ }
+}
+
+#ifndef WCONTINUED
+# define WCONTINUED 0
+#endif
+
+/* called on sigchld etc., calls waitpid */
+static void
+childcb (EV_P_ ev_signal *sw, int revents)
+{
+ int pid, status;
+
+ /* some systems define WCONTINUED but then fail to support it (linux 2.4) */
+ if (0 >= (pid = waitpid (-1, &status, WNOHANG | WUNTRACED | WCONTINUED)))
+ if (!WCONTINUED
+ || errno != EINVAL
+ || 0 >= (pid = waitpid (-1, &status, WNOHANG | WUNTRACED)))
+ return;
+
+ /* make sure we are called again until all children have been reaped */
+ /* we need to do it this way so that the callback gets called before we continue */
+ ev_feed_event (EV_A_ (W)sw, EV_SIGNAL);
+
+ child_reap (EV_A_ pid, pid, status);
+ if ((EV_PID_HASHSIZE) > 1)
+ child_reap (EV_A_ 0, pid, status); /* this might trigger a watcher twice, but feed_event catches that */
+}
+
+#endif
+
+/*****************************************************************************/
+
+#if EV_USE_TIMERFD
+
+static void periodics_reschedule (EV_P);
+
+static void
+timerfdcb (EV_P_ ev_io *iow, int revents)
+{
+ struct itimerspec its = { 0 };
+
+ its.it_value.tv_sec = ev_rt_now + (int)MAX_BLOCKTIME2;
+ timerfd_settime (timerfd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &its, 0);
+
+ ev_rt_now = ev_time ();
+ /* periodics_reschedule only needs ev_rt_now */
+ /* but maybe in the future we want the full treatment. */
+ /*
+ now_floor = EV_TS_CONST (0.);
+ time_update (EV_A_ EV_TSTAMP_HUGE);
+ */
+#if EV_PERIODIC_ENABLE
+ periodics_reschedule (EV_A);
+#endif
+}
+
+ecb_noinline ecb_cold
+static void
+evtimerfd_init (EV_P)
+{
+ if (!ev_is_active (&timerfd_w))
+ {
+ timerfd = timerfd_create (CLOCK_REALTIME, TFD_NONBLOCK | TFD_CLOEXEC);
+
+ if (timerfd >= 0)
+ {
+ fd_intern (timerfd); /* just to be sure */
+
+ ev_io_init (&timerfd_w, timerfdcb, timerfd, EV_READ);
+ ev_set_priority (&timerfd_w, EV_MINPRI);
+ ev_io_start (EV_A_ &timerfd_w);
+ ev_unref (EV_A); /* watcher should not keep loop alive */
+
+ /* (re-) arm timer */
+ timerfdcb (EV_A_ 0, 0);
+ }
+ }
+}
+
+#endif
+
+/*****************************************************************************/
+
+#if EV_USE_IOCP
+# include "ev_iocp.c"
+#endif
+#if EV_USE_PORT
+# include "ev_port.c"
+#endif
+#if EV_USE_KQUEUE
+# include "ev_kqueue.c"
+#endif
+#if EV_USE_EPOLL
+# include "ev_epoll.c"
+#endif
+#if EV_USE_LINUXAIO
+# include "ev_linuxaio.c"
+#endif
+#if EV_USE_IOURING
+# include "ev_iouring.c"
+#endif
+#if EV_USE_POLL
+# include "ev_poll.c"
+#endif
+#if EV_USE_SELECT
+# include "ev_select.c"
+#endif
+
+ecb_cold int
+ev_version_major (void) EV_NOEXCEPT
+{
+ return EV_VERSION_MAJOR;
+}
+
+ecb_cold int
+ev_version_minor (void) EV_NOEXCEPT
+{
+ return EV_VERSION_MINOR;
+}
+
+/* return true if we are running with elevated privileges and should ignore env variables */
+inline_size ecb_cold int
+enable_secure (void)
+{
+#ifdef _WIN32
+ return 0;
+#else
+ return getuid () != geteuid ()
+ || getgid () != getegid ();
+#endif
+}
+
+ecb_cold
+unsigned int
+ev_supported_backends (void) EV_NOEXCEPT
+{
+ unsigned int flags = 0;
+
+ if (EV_USE_PORT ) flags |= EVBACKEND_PORT;
+ if (EV_USE_KQUEUE ) flags |= EVBACKEND_KQUEUE;
+ if (EV_USE_EPOLL ) flags |= EVBACKEND_EPOLL;
+ if (EV_USE_LINUXAIO ) flags |= EVBACKEND_LINUXAIO;
+ if (EV_USE_IOURING && ev_linux_version () >= 0x050601) flags |= EVBACKEND_IOURING; /* 5.6.1+ */
+ if (EV_USE_POLL ) flags |= EVBACKEND_POLL;
+ if (EV_USE_SELECT ) flags |= EVBACKEND_SELECT;
+
+ return flags;
+}
+
+ecb_cold
+unsigned int
+ev_recommended_backends (void) EV_NOEXCEPT
+{
+ unsigned int flags = ev_supported_backends ();
+
+#ifndef __NetBSD__
+ /* kqueue is borked on everything but netbsd apparently */
+ /* it usually doesn't work correctly on anything but sockets and pipes */
+ flags &= ~EVBACKEND_KQUEUE;
+#endif
+#ifdef __APPLE__
+ /* only select works correctly on that "unix-certified" platform */
+ flags &= ~EVBACKEND_KQUEUE; /* horribly broken, even for sockets */
+ flags &= ~EVBACKEND_POLL; /* poll is based on kqueue from 10.5 onwards */
+#endif
+#ifdef __FreeBSD__
+ flags &= ~EVBACKEND_POLL; /* poll return value is unusable (http://forums.freebsd.org/archive/index.php/t-10270.html) */
+#endif
+
+ /* TODO: linuxaio is very experimental */
+#if !EV_RECOMMEND_LINUXAIO
+ flags &= ~EVBACKEND_LINUXAIO;
+#endif
+ /* TODO: linuxaio is super experimental */
+#if !EV_RECOMMEND_IOURING
+ flags &= ~EVBACKEND_IOURING;
+#endif
+
+ return flags;
+}
+
+ecb_cold
+unsigned int
+ev_embeddable_backends (void) EV_NOEXCEPT
+{
+ int flags = EVBACKEND_EPOLL | EVBACKEND_KQUEUE | EVBACKEND_PORT | EVBACKEND_IOURING;
+
+ /* epoll embeddability broken on all linux versions up to at least 2.6.23 */
+ if (ev_linux_version () < 0x020620) /* disable it on linux < 2.6.32 */
+ flags &= ~EVBACKEND_EPOLL;
+
+ /* EVBACKEND_LINUXAIO is theoretically embeddable, but suffers from a performance overhead */
+
+ return flags;
+}
+
+unsigned int
+ev_backend (EV_P) EV_NOEXCEPT
+{
+ return backend;
+}
+
+#if EV_FEATURE_API
+unsigned int
+ev_iteration (EV_P) EV_NOEXCEPT
+{
+ return loop_count;
+}
+
+unsigned int
+ev_depth (EV_P) EV_NOEXCEPT
+{
+ return loop_depth;
+}
+
+void
+ev_set_io_collect_interval (EV_P_ ev_tstamp interval) EV_NOEXCEPT
+{
+ io_blocktime = interval;
+}
+
+void
+ev_set_timeout_collect_interval (EV_P_ ev_tstamp interval) EV_NOEXCEPT
+{
+ timeout_blocktime = interval;
+}
+
+void
+ev_set_userdata (EV_P_ void *data) EV_NOEXCEPT
+{
+ userdata = data;
+}
+
+void *
+ev_userdata (EV_P) EV_NOEXCEPT
+{
+ return userdata;
+}
+
+void
+ev_set_invoke_pending_cb (EV_P_ ev_loop_callback invoke_pending_cb) EV_NOEXCEPT
+{
+ invoke_cb = invoke_pending_cb;
+}
+
+void
+ev_set_loop_release_cb (EV_P_ void (*release)(EV_P) EV_NOEXCEPT, void (*acquire)(EV_P) EV_NOEXCEPT) EV_NOEXCEPT
+{
+ release_cb = release;
+ acquire_cb = acquire;
+}
+#endif
+
+/* initialise a loop structure, must be zero-initialised */
+ecb_noinline ecb_cold
+static void
+loop_init (EV_P_ unsigned int flags) EV_NOEXCEPT
+{
+ if (!backend)
+ {
+ origflags = flags;
+
+#if EV_USE_REALTIME
+ if (!have_realtime)
+ {
+ struct timespec ts;
+
+ if (!clock_gettime (CLOCK_REALTIME, &ts))
+ have_realtime = 1;
+ }
+#endif
+
+#if EV_USE_MONOTONIC
+ if (!have_monotonic)
+ {
+ struct timespec ts;
+
+ if (!clock_gettime (CLOCK_MONOTONIC, &ts))
+ have_monotonic = 1;
+ }
+#endif
+
+ /* pid check not overridable via env */
+#ifndef _WIN32
+ if (flags & EVFLAG_FORKCHECK)
+ curpid = getpid ();
+#endif
+
+ if (!(flags & EVFLAG_NOENV)
+ && !enable_secure ()
+ && getenv ("LIBEV_FLAGS"))
+ flags = atoi (getenv ("LIBEV_FLAGS"));
+
+ ev_rt_now = ev_time ();
+ mn_now = get_clock ();
+ now_floor = mn_now;
+ rtmn_diff = ev_rt_now - mn_now;
+#if EV_FEATURE_API
+ invoke_cb = ev_invoke_pending;
+#endif
+
+ io_blocktime = 0.;
+ timeout_blocktime = 0.;
+ backend = 0;
+ backend_fd = -1;
+ sig_pending = 0;
+#if EV_ASYNC_ENABLE
+ async_pending = 0;
+#endif
+ pipe_write_skipped = 0;
+ pipe_write_wanted = 0;
+ evpipe [0] = -1;
+ evpipe [1] = -1;
+#if EV_USE_INOTIFY
+ fs_fd = flags & EVFLAG_NOINOTIFY ? -1 : -2;
+#endif
+#if EV_USE_SIGNALFD
+ sigfd = flags & EVFLAG_SIGNALFD ? -2 : -1;
+#endif
+#if EV_USE_TIMERFD
+ timerfd = flags & EVFLAG_NOTIMERFD ? -1 : -2;
+#endif
+
+ if (!(flags & EVBACKEND_MASK))
+ flags |= ev_recommended_backends ();
+
+#if EV_USE_IOCP
+ if (!backend && (flags & EVBACKEND_IOCP )) backend = iocp_init (EV_A_ flags);
+#endif
+#if EV_USE_PORT
+ if (!backend && (flags & EVBACKEND_PORT )) backend = port_init (EV_A_ flags);
+#endif
+#if EV_USE_KQUEUE
+ if (!backend && (flags & EVBACKEND_KQUEUE )) backend = kqueue_init (EV_A_ flags);
+#endif
+#if EV_USE_IOURING
+ if (!backend && (flags & EVBACKEND_IOURING )) backend = iouring_init (EV_A_ flags);
+#endif
+#if EV_USE_LINUXAIO
+ if (!backend && (flags & EVBACKEND_LINUXAIO)) backend = linuxaio_init (EV_A_ flags);
+#endif
+#if EV_USE_EPOLL
+ if (!backend && (flags & EVBACKEND_EPOLL )) backend = epoll_init (EV_A_ flags);
+#endif
+#if EV_USE_POLL
+ if (!backend && (flags & EVBACKEND_POLL )) backend = poll_init (EV_A_ flags);
+#endif
+#if EV_USE_SELECT
+ if (!backend && (flags & EVBACKEND_SELECT )) backend = select_init (EV_A_ flags);
+#endif
+
+ ev_prepare_init (&pending_w, pendingcb);
+
+#if EV_SIGNAL_ENABLE || EV_ASYNC_ENABLE
+ ev_init (&pipe_w, pipecb);
+ ev_set_priority (&pipe_w, EV_MAXPRI);
+#endif
+ }
+}
+
+/* free up a loop structure */
+ecb_cold
+void
+ev_loop_destroy (EV_P)
+{
+ int i;
+
+#if EV_MULTIPLICITY
+ /* mimic free (0) */
+ if (!EV_A)
+ return;
+#endif
+
+#if EV_CLEANUP_ENABLE
+ /* queue cleanup watchers (and execute them) */
+ if (ecb_expect_false (cleanupcnt))
+ {
+ queue_events (EV_A_ (W *)cleanups, cleanupcnt, EV_CLEANUP);
+ EV_INVOKE_PENDING;
+ }
+#endif
+
+#if EV_CHILD_ENABLE
+ if (ev_is_default_loop (EV_A) && ev_is_active (&childev))
+ {
+ ev_ref (EV_A); /* child watcher */
+ ev_signal_stop (EV_A_ &childev);
+ }
+#endif
+
+ if (ev_is_active (&pipe_w))
+ {
+ /*ev_ref (EV_A);*/
+ /*ev_io_stop (EV_A_ &pipe_w);*/
+
+ if (evpipe [0] >= 0) EV_WIN32_CLOSE_FD (evpipe [0]);
+ if (evpipe [1] >= 0) EV_WIN32_CLOSE_FD (evpipe [1]);
+ }
+
+#if EV_USE_SIGNALFD
+ if (ev_is_active (&sigfd_w))
+ close (sigfd);
+#endif
+
+#if EV_USE_TIMERFD
+ if (ev_is_active (&timerfd_w))
+ close (timerfd);
+#endif
+
+#if EV_USE_INOTIFY
+ if (fs_fd >= 0)
+ close (fs_fd);
+#endif
+
+ if (backend_fd >= 0)
+ close (backend_fd);
+
+#if EV_USE_IOCP
+ if (backend == EVBACKEND_IOCP ) iocp_destroy (EV_A);
+#endif
+#if EV_USE_PORT
+ if (backend == EVBACKEND_PORT ) port_destroy (EV_A);
+#endif
+#if EV_USE_KQUEUE
+ if (backend == EVBACKEND_KQUEUE ) kqueue_destroy (EV_A);
+#endif
+#if EV_USE_IOURING
+ if (backend == EVBACKEND_IOURING ) iouring_destroy (EV_A);
+#endif
+#if EV_USE_LINUXAIO
+ if (backend == EVBACKEND_LINUXAIO) linuxaio_destroy (EV_A);
+#endif
+#if EV_USE_EPOLL
+ if (backend == EVBACKEND_EPOLL ) epoll_destroy (EV_A);
+#endif
+#if EV_USE_POLL
+ if (backend == EVBACKEND_POLL ) poll_destroy (EV_A);
+#endif
+#if EV_USE_SELECT
+ if (backend == EVBACKEND_SELECT ) select_destroy (EV_A);
+#endif
+
+ for (i = NUMPRI; i--; )
+ {
+ array_free (pending, [i]);
+#if EV_IDLE_ENABLE
+ array_free (idle, [i]);
+#endif
+ }
+
+ ev_free (anfds); anfds = 0; anfdmax = 0;
+
+ /* have to use the microsoft-never-gets-it-right macro */
+ array_free (rfeed, EMPTY);
+ array_free (fdchange, EMPTY);
+ array_free (timer, EMPTY);
+#if EV_PERIODIC_ENABLE
+ array_free (periodic, EMPTY);
+#endif
+#if EV_FORK_ENABLE
+ array_free (fork, EMPTY);
+#endif
+#if EV_CLEANUP_ENABLE
+ array_free (cleanup, EMPTY);
+#endif
+ array_free (prepare, EMPTY);
+ array_free (check, EMPTY);
+#if EV_ASYNC_ENABLE
+ array_free (async, EMPTY);
+#endif
+
+ backend = 0;
+
+#if EV_MULTIPLICITY
+ if (ev_is_default_loop (EV_A))
+#endif
+ ev_default_loop_ptr = 0;
+#if EV_MULTIPLICITY
+ else
+ ev_free (EV_A);
+#endif
+}
+
+#if EV_USE_INOTIFY
+inline_size void infy_fork (EV_P);
+#endif
+
+inline_size void
+loop_fork (EV_P)
+{
+#if EV_USE_PORT
+ if (backend == EVBACKEND_PORT ) port_fork (EV_A);
+#endif
+#if EV_USE_KQUEUE
+ if (backend == EVBACKEND_KQUEUE ) kqueue_fork (EV_A);
+#endif
+#if EV_USE_IOURING
+ if (backend == EVBACKEND_IOURING ) iouring_fork (EV_A);
+#endif
+#if EV_USE_LINUXAIO
+ if (backend == EVBACKEND_LINUXAIO) linuxaio_fork (EV_A);
+#endif
+#if EV_USE_EPOLL
+ if (backend == EVBACKEND_EPOLL ) epoll_fork (EV_A);
+#endif
+#if EV_USE_INOTIFY
+ infy_fork (EV_A);
+#endif
+
+ if (postfork != 2)
+ {
+ #if EV_USE_SIGNALFD
+ /* surprisingly, nothing needs to be done for signalfd, accoridng to docs, it does the right thing on fork */
+ #endif
+
+ #if EV_USE_TIMERFD
+ if (ev_is_active (&timerfd_w))
+ {
+ ev_ref (EV_A);
+ ev_io_stop (EV_A_ &timerfd_w);
+
+ close (timerfd);
+ timerfd = -2;
+
+ evtimerfd_init (EV_A);
+ /* reschedule periodics, in case we missed something */
+ ev_feed_event (EV_A_ &timerfd_w, EV_CUSTOM);
+ }
+ #endif
+
+ #if EV_SIGNAL_ENABLE || EV_ASYNC_ENABLE
+ if (ev_is_active (&pipe_w))
+ {
+ /* pipe_write_wanted must be false now, so modifying fd vars should be safe */
+
+ ev_ref (EV_A);
+ ev_io_stop (EV_A_ &pipe_w);
+
+ if (evpipe [0] >= 0)
+ EV_WIN32_CLOSE_FD (evpipe [0]);
+
+ evpipe_init (EV_A);
+ /* iterate over everything, in case we missed something before */
+ ev_feed_event (EV_A_ &pipe_w, EV_CUSTOM);
+ }
+ #endif
+ }
+
+ postfork = 0;
+}
+
+#if EV_MULTIPLICITY
+
+ecb_cold
+struct ev_loop *
+ev_loop_new (unsigned int flags) EV_NOEXCEPT
+{
+ EV_P = (struct ev_loop *)ev_malloc (sizeof (struct ev_loop));
+
+ memset (EV_A, 0, sizeof (struct ev_loop));
+ loop_init (EV_A_ flags);
+
+ if (ev_backend (EV_A))
+ return EV_A;
+
+ ev_free (EV_A);
+ return 0;
+}
+
+#endif /* multiplicity */
+
+#if EV_VERIFY
+ecb_noinline ecb_cold
+static void
+verify_watcher (EV_P_ W w)
+{
+ assert (("libev: watcher has invalid priority", ABSPRI (w) >= 0 && ABSPRI (w) < NUMPRI));
+
+ if (w->pending)
+ assert (("libev: pending watcher not on pending queue", pendings [ABSPRI (w)][w->pending - 1].w == w));
+}
+
+ecb_noinline ecb_cold
+static void
+verify_heap (EV_P_ ANHE *heap, int N)
+{
+ int i;
+
+ for (i = HEAP0; i < N + HEAP0; ++i)
+ {
+ assert (("libev: active index mismatch in heap", ev_active (ANHE_w (heap [i])) == i));
+ assert (("libev: heap condition violated", i == HEAP0 || ANHE_at (heap [HPARENT (i)]) <= ANHE_at (heap [i])));
+ assert (("libev: heap at cache mismatch", ANHE_at (heap [i]) == ev_at (ANHE_w (heap [i]))));
+
+ verify_watcher (EV_A_ (W)ANHE_w (heap [i]));
+ }
+}
+
+ecb_noinline ecb_cold
+static void
+array_verify (EV_P_ W *ws, int cnt)
+{
+ while (cnt--)
+ {
+ assert (("libev: active index mismatch", ev_active (ws [cnt]) == cnt + 1));
+ verify_watcher (EV_A_ ws [cnt]);
+ }
+}
+#endif
+
+#if EV_FEATURE_API
+void ecb_cold
+ev_verify (EV_P) EV_NOEXCEPT
+{
+#if EV_VERIFY
+ int i;
+ WL w, w2;
+
+ assert (activecnt >= -1);
+
+ assert (fdchangemax >= fdchangecnt);
+ for (i = 0; i < fdchangecnt; ++i)
+ assert (("libev: negative fd in fdchanges", fdchanges [i] >= 0));
+
+ assert (anfdmax >= 0);
+ for (i = 0; i < anfdmax; ++i)
+ {
+ int j = 0;
+
+ for (w = w2 = anfds [i].head; w; w = w->next)
+ {
+ verify_watcher (EV_A_ (W)w);
+
+ if (j++ & 1)
+ {
+ assert (("libev: io watcher list contains a loop", w != w2));
+ w2 = w2->next;
+ }
+
+ assert (("libev: inactive fd watcher on anfd list", ev_active (w) == 1));
+ assert (("libev: fd mismatch between watcher and anfd", ((ev_io *)w)->fd == i));
+ }
+ }
+
+ assert (timermax >= timercnt);
+ verify_heap (EV_A_ timers, timercnt);
+
+#if EV_PERIODIC_ENABLE
+ assert (periodicmax >= periodiccnt);
+ verify_heap (EV_A_ periodics, periodiccnt);
+#endif
+
+ for (i = NUMPRI; i--; )
+ {
+ assert (pendingmax [i] >= pendingcnt [i]);
+#if EV_IDLE_ENABLE
+ assert (idleall >= 0);
+ assert (idlemax [i] >= idlecnt [i]);
+ array_verify (EV_A_ (W *)idles [i], idlecnt [i]);
+#endif
+ }
+
+#if EV_FORK_ENABLE
+ assert (forkmax >= forkcnt);
+ array_verify (EV_A_ (W *)forks, forkcnt);
+#endif
+
+#if EV_CLEANUP_ENABLE
+ assert (cleanupmax >= cleanupcnt);
+ array_verify (EV_A_ (W *)cleanups, cleanupcnt);
+#endif
+
+#if EV_ASYNC_ENABLE
+ assert (asyncmax >= asynccnt);
+ array_verify (EV_A_ (W *)asyncs, asynccnt);
+#endif
+
+#if EV_PREPARE_ENABLE
+ assert (preparemax >= preparecnt);
+ array_verify (EV_A_ (W *)prepares, preparecnt);
+#endif
+
+#if EV_CHECK_ENABLE
+ assert (checkmax >= checkcnt);
+ array_verify (EV_A_ (W *)checks, checkcnt);
+#endif
+
+# if 0
+#if EV_CHILD_ENABLE
+ for (w = (ev_child *)childs [chain & ((EV_PID_HASHSIZE) - 1)]; w; w = (ev_child *)((WL)w)->next)
+ for (signum = EV_NSIG; signum--; ) if (signals [signum].pending)
+#endif
+# endif
+#endif
+}
+#endif
+
+#if EV_MULTIPLICITY
+ecb_cold
+struct ev_loop *
+#else
+int
+#endif
+ev_default_loop (unsigned int flags) EV_NOEXCEPT
+{
+ if (!ev_default_loop_ptr)
+ {
+#if EV_MULTIPLICITY
+ EV_P = ev_default_loop_ptr = &default_loop_struct;
+#else
+ ev_default_loop_ptr = 1;
+#endif
+
+ loop_init (EV_A_ flags);
+
+ if (ev_backend (EV_A))
+ {
+#if EV_CHILD_ENABLE
+ ev_signal_init (&childev, childcb, SIGCHLD);
+ ev_set_priority (&childev, EV_MAXPRI);
+ ev_signal_start (EV_A_ &childev);
+ ev_unref (EV_A); /* child watcher should not keep loop alive */
+#endif
+ }
+ else
+ ev_default_loop_ptr = 0;
+ }
+
+ return ev_default_loop_ptr;
+}
+
+void
+ev_loop_fork (EV_P) EV_NOEXCEPT
+{
+ postfork = 1;
+}
+
+/*****************************************************************************/
+
+void
+ev_invoke (EV_P_ void *w, int revents)
+{
+ EV_CB_INVOKE ((W)w, revents);
+}
+
+unsigned int
+ev_pending_count (EV_P) EV_NOEXCEPT
+{
+ int pri;
+ unsigned int count = 0;
+
+ for (pri = NUMPRI; pri--; )
+ count += pendingcnt [pri];
+
+ return count;
+}
+
+ecb_noinline
+void
+ev_invoke_pending (EV_P)
+{
+ pendingpri = NUMPRI;
+
+ do
+ {
+ --pendingpri;
+
+ /* pendingpri possibly gets modified in the inner loop */
+ while (pendingcnt [pendingpri])
+ {
+ ANPENDING *p = pendings [pendingpri] + --pendingcnt [pendingpri];
+
+ p->w->pending = 0;
+ EV_CB_INVOKE (p->w, p->events);
+ EV_FREQUENT_CHECK;
+ }
+ }
+ while (pendingpri);
+}
+
+#if EV_IDLE_ENABLE
+/* make idle watchers pending. this handles the "call-idle */
+/* only when higher priorities are idle" logic */
+inline_size void
+idle_reify (EV_P)
+{
+ if (ecb_expect_false (idleall))
+ {
+ int pri;
+
+ for (pri = NUMPRI; pri--; )
+ {
+ if (pendingcnt [pri])
+ break;
+
+ if (idlecnt [pri])
+ {
+ queue_events (EV_A_ (W *)idles [pri], idlecnt [pri], EV_IDLE);
+ break;
+ }
+ }
+ }
+}
+#endif
+
+/* make timers pending */
+inline_size void
+timers_reify (EV_P)
+{
+ EV_FREQUENT_CHECK;
+
+ if (timercnt && ANHE_at (timers [HEAP0]) < mn_now)
+ {
+ do
+ {
+ ev_timer *w = (ev_timer *)ANHE_w (timers [HEAP0]);
+
+ /*assert (("libev: inactive timer on timer heap detected", ev_is_active (w)));*/
+
+ /* first reschedule or stop timer */
+ if (w->repeat)
+ {
+ ev_at (w) += w->repeat;
+ if (ev_at (w) < mn_now)
+ ev_at (w) = mn_now;
+
+ assert (("libev: negative ev_timer repeat value found while processing timers", w->repeat > EV_TS_CONST (0.)));
+
+ ANHE_at_cache (timers [HEAP0]);
+ downheap (timers, timercnt, HEAP0);
+ }
+ else
+ ev_timer_stop (EV_A_ w); /* nonrepeating: stop timer */
+
+ EV_FREQUENT_CHECK;
+ feed_reverse (EV_A_ (W)w);
+ }
+ while (timercnt && ANHE_at (timers [HEAP0]) < mn_now);
+
+ feed_reverse_done (EV_A_ EV_TIMER);
+ }
+}
+
+#if EV_PERIODIC_ENABLE
+
+ecb_noinline
+static void
+periodic_recalc (EV_P_ ev_periodic *w)
+{
+ ev_tstamp interval = w->interval > MIN_INTERVAL ? w->interval : MIN_INTERVAL;
+ ev_tstamp at = w->offset + interval * ev_floor ((ev_rt_now - w->offset) / interval);
+
+ /* the above almost always errs on the low side */
+ while (at <= ev_rt_now)
+ {
+ ev_tstamp nat = at + w->interval;
+
+ /* when resolution fails us, we use ev_rt_now */
+ if (ecb_expect_false (nat == at))
+ {
+ at = ev_rt_now;
+ break;
+ }
+
+ at = nat;
+ }
+
+ ev_at (w) = at;
+}
+
+/* make periodics pending */
+inline_size void
+periodics_reify (EV_P)
+{
+ EV_FREQUENT_CHECK;
+
+ while (periodiccnt && ANHE_at (periodics [HEAP0]) < ev_rt_now)
+ {
+ do
+ {
+ ev_periodic *w = (ev_periodic *)ANHE_w (periodics [HEAP0]);
+
+ /*assert (("libev: inactive timer on periodic heap detected", ev_is_active (w)));*/
+
+ /* first reschedule or stop timer */
+ if (w->reschedule_cb)
+ {
+ ev_at (w) = w->reschedule_cb (w, ev_rt_now);
+
+ assert (("libev: ev_periodic reschedule callback returned time in the past", ev_at (w) >= ev_rt_now));
+
+ ANHE_at_cache (periodics [HEAP0]);
+ downheap (periodics, periodiccnt, HEAP0);
+ }
+ else if (w->interval)
+ {
+ periodic_recalc (EV_A_ w);
+ ANHE_at_cache (periodics [HEAP0]);
+ downheap (periodics, periodiccnt, HEAP0);
+ }
+ else
+ ev_periodic_stop (EV_A_ w); /* nonrepeating: stop timer */
+
+ EV_FREQUENT_CHECK;
+ feed_reverse (EV_A_ (W)w);
+ }
+ while (periodiccnt && ANHE_at (periodics [HEAP0]) < ev_rt_now);
+
+ feed_reverse_done (EV_A_ EV_PERIODIC);
+ }
+}
+
+/* simply recalculate all periodics */
+/* TODO: maybe ensure that at least one event happens when jumping forward? */
+ecb_noinline ecb_cold
+static void
+periodics_reschedule (EV_P)
+{
+ int i;
+
+ /* adjust periodics after time jump */
+ for (i = HEAP0; i < periodiccnt + HEAP0; ++i)
+ {
+ ev_periodic *w = (ev_periodic *)ANHE_w (periodics [i]);
+
+ if (w->reschedule_cb)
+ ev_at (w) = w->reschedule_cb (w, ev_rt_now);
+ else if (w->interval)
+ periodic_recalc (EV_A_ w);
+
+ ANHE_at_cache (periodics [i]);
+ }
+
+ reheap (periodics, periodiccnt);
+}
+#endif
+
+/* adjust all timers by a given offset */
+ecb_noinline ecb_cold
+static void
+timers_reschedule (EV_P_ ev_tstamp adjust)
+{
+ int i;
+
+ for (i = 0; i < timercnt; ++i)
+ {
+ ANHE *he = timers + i + HEAP0;
+ ANHE_w (*he)->at += adjust;
+ ANHE_at_cache (*he);
+ }
+}
+
+/* fetch new monotonic and realtime times from the kernel */
+/* also detect if there was a timejump, and act accordingly */
+inline_speed void
+time_update (EV_P_ ev_tstamp max_block)
+{
+#if EV_USE_MONOTONIC
+ if (ecb_expect_true (have_monotonic))
+ {
+ int i;
+ ev_tstamp odiff = rtmn_diff;
+
+ mn_now = get_clock ();
+
+ /* only fetch the realtime clock every 0.5*MIN_TIMEJUMP seconds */
+ /* interpolate in the meantime */
+ if (ecb_expect_true (mn_now - now_floor < EV_TS_CONST (MIN_TIMEJUMP * .5)))
+ {
+ ev_rt_now = rtmn_diff + mn_now;
+ return;
+ }
+
+ now_floor = mn_now;
+ ev_rt_now = ev_time ();
+
+ /* loop a few times, before making important decisions.
+ * on the choice of "4": one iteration isn't enough,
+ * in case we get preempted during the calls to
+ * ev_time and get_clock. a second call is almost guaranteed
+ * to succeed in that case, though. and looping a few more times
+ * doesn't hurt either as we only do this on time-jumps or
+ * in the unlikely event of having been preempted here.
+ */
+ for (i = 4; --i; )
+ {
+ ev_tstamp diff;
+ rtmn_diff = ev_rt_now - mn_now;
+
+ diff = odiff - rtmn_diff;
+
+ if (ecb_expect_true ((diff < EV_TS_CONST (0.) ? -diff : diff) < EV_TS_CONST (MIN_TIMEJUMP)))
+ return; /* all is well */
+
+ ev_rt_now = ev_time ();
+ mn_now = get_clock ();
+ now_floor = mn_now;
+ }
+
+ /* no timer adjustment, as the monotonic clock doesn't jump */
+ /* timers_reschedule (EV_A_ rtmn_diff - odiff) */
+# if EV_PERIODIC_ENABLE
+ periodics_reschedule (EV_A);
+# endif
+ }
+ else
+#endif
+ {
+ ev_rt_now = ev_time ();
+
+ if (ecb_expect_false (mn_now > ev_rt_now || ev_rt_now > mn_now + max_block + EV_TS_CONST (MIN_TIMEJUMP)))
+ {
+ /* adjust timers. this is easy, as the offset is the same for all of them */
+ timers_reschedule (EV_A_ ev_rt_now - mn_now);
+#if EV_PERIODIC_ENABLE
+ periodics_reschedule (EV_A);
+#endif
+ }
+
+ mn_now = ev_rt_now;
+ }
+}
+
+int
+ev_run (EV_P_ int flags)
+{
+#if EV_FEATURE_API
+ ++loop_depth;
+#endif
+
+ assert (("libev: ev_loop recursion during release detected", loop_done != EVBREAK_RECURSE));
+
+ loop_done = EVBREAK_CANCEL;
+
+ EV_INVOKE_PENDING; /* in case we recurse, ensure ordering stays nice and clean */
+
+ do
+ {
+#if EV_VERIFY >= 2
+ ev_verify (EV_A);
+#endif
+
+#ifndef _WIN32
+ if (ecb_expect_false (curpid)) /* penalise the forking check even more */
+ if (ecb_expect_false (getpid () != curpid))
+ {
+ curpid = getpid ();
+ postfork = 1;
+ }
+#endif
+
+#if EV_FORK_ENABLE
+ /* we might have forked, so queue fork handlers */
+ if (ecb_expect_false (postfork))
+ if (forkcnt)
+ {
+ queue_events (EV_A_ (W *)forks, forkcnt, EV_FORK);
+ EV_INVOKE_PENDING;
+ }
+#endif
+
+#if EV_PREPARE_ENABLE
+ /* queue prepare watchers (and execute them) */
+ if (ecb_expect_false (preparecnt))
+ {
+ queue_events (EV_A_ (W *)prepares, preparecnt, EV_PREPARE);
+ EV_INVOKE_PENDING;
+ }
+#endif
+
+ if (ecb_expect_false (loop_done))
+ break;
+
+ /* we might have forked, so reify kernel state if necessary */
+ if (ecb_expect_false (postfork))
+ loop_fork (EV_A);
+
+ /* update fd-related kernel structures */
+ fd_reify (EV_A);
+
+ /* calculate blocking time */
+ {
+ ev_tstamp waittime = 0.;
+ ev_tstamp sleeptime = 0.;
+
+ /* remember old timestamp for io_blocktime calculation */
+ ev_tstamp prev_mn_now = mn_now;
+
+ /* update time to cancel out callback processing overhead */
+ time_update (EV_A_ EV_TS_CONST (EV_TSTAMP_HUGE));
+
+ /* from now on, we want a pipe-wake-up */
+ pipe_write_wanted = 1;
+
+ ECB_MEMORY_FENCE; /* make sure pipe_write_wanted is visible before we check for potential skips */
+
+ if (ecb_expect_true (!(flags & EVRUN_NOWAIT || idleall || !activecnt || pipe_write_skipped)))
+ {
+ waittime = EV_TS_CONST (MAX_BLOCKTIME);
+
+#if EV_USE_TIMERFD
+ /* sleep a lot longer when we can reliably detect timejumps */
+ if (ecb_expect_true (timerfd >= 0))
+ waittime = EV_TS_CONST (MAX_BLOCKTIME2);
+#endif
+#if !EV_PERIODIC_ENABLE
+ /* without periodics but with monotonic clock there is no need */
+ /* for any time jump detection, so sleep longer */
+ if (ecb_expect_true (have_monotonic))
+ waittime = EV_TS_CONST (MAX_BLOCKTIME2);
+#endif
+
+ if (timercnt)
+ {
+ ev_tstamp to = ANHE_at (timers [HEAP0]) - mn_now;
+ if (waittime > to) waittime = to;
+ }
+
+#if EV_PERIODIC_ENABLE
+ if (periodiccnt)
+ {
+ ev_tstamp to = ANHE_at (periodics [HEAP0]) - ev_rt_now;
+ if (waittime > to) waittime = to;
+ }
+#endif
+
+ /* don't let timeouts decrease the waittime below timeout_blocktime */
+ if (ecb_expect_false (waittime < timeout_blocktime))
+ waittime = timeout_blocktime;
+
+ /* now there are two more special cases left, either we have
+ * already-expired timers, so we should not sleep, or we have timers
+ * that expire very soon, in which case we need to wait for a minimum
+ * amount of time for some event loop backends.
+ */
+ if (ecb_expect_false (waittime < backend_mintime))
+ waittime = waittime <= EV_TS_CONST (0.)
+ ? EV_TS_CONST (0.)
+ : backend_mintime;
+
+ /* extra check because io_blocktime is commonly 0 */
+ if (ecb_expect_false (io_blocktime))
+ {
+ sleeptime = io_blocktime - (mn_now - prev_mn_now);
+
+ if (sleeptime > waittime - backend_mintime)
+ sleeptime = waittime - backend_mintime;
+
+ if (ecb_expect_true (sleeptime > EV_TS_CONST (0.)))
+ {
+ ev_sleep (sleeptime);
+ waittime -= sleeptime;
+ }
+ }
+ }
+
+#if EV_FEATURE_API
+ ++loop_count;
+#endif
+ assert ((loop_done = EVBREAK_RECURSE, 1)); /* assert for side effect */
+ backend_poll (EV_A_ waittime);
+ assert ((loop_done = EVBREAK_CANCEL, 1)); /* assert for side effect */
+
+ pipe_write_wanted = 0; /* just an optimisation, no fence needed */
+
+ ECB_MEMORY_FENCE_ACQUIRE;
+ if (pipe_write_skipped)
+ {
+ assert (("libev: pipe_w not active, but pipe not written", ev_is_active (&pipe_w)));
+ ev_feed_event (EV_A_ &pipe_w, EV_CUSTOM);
+ }
+
+ /* update ev_rt_now, do magic */
+ time_update (EV_A_ waittime + sleeptime);
+ }
+
+ /* queue pending timers and reschedule them */
+ timers_reify (EV_A); /* relative timers called last */
+#if EV_PERIODIC_ENABLE
+ periodics_reify (EV_A); /* absolute timers called first */
+#endif
+
+#if EV_IDLE_ENABLE
+ /* queue idle watchers unless other events are pending */
+ idle_reify (EV_A);
+#endif
+
+#if EV_CHECK_ENABLE
+ /* queue check watchers, to be executed first */
+ if (ecb_expect_false (checkcnt))
+ queue_events (EV_A_ (W *)checks, checkcnt, EV_CHECK);
+#endif
+
+ EV_INVOKE_PENDING;
+ }
+ while (ecb_expect_true (
+ activecnt
+ && !loop_done
+ && !(flags & (EVRUN_ONCE | EVRUN_NOWAIT))
+ ));
+
+ if (loop_done == EVBREAK_ONE)
+ loop_done = EVBREAK_CANCEL;
+
+#if EV_FEATURE_API
+ --loop_depth;
+#endif
+
+ return activecnt;
+}
+
+void
+ev_break (EV_P_ int how) EV_NOEXCEPT
+{
+ loop_done = how;
+}
+
+void
+ev_ref (EV_P) EV_NOEXCEPT
+{
+ ++activecnt;
+}
+
+void
+ev_unref (EV_P) EV_NOEXCEPT
+{
+ --activecnt;
+}
+
+void
+ev_now_update (EV_P) EV_NOEXCEPT
+{
+ time_update (EV_A_ EV_TSTAMP_HUGE);
+}
+
+void
+ev_suspend (EV_P) EV_NOEXCEPT
+{
+ ev_now_update (EV_A);
+}
+
+void
+ev_resume (EV_P) EV_NOEXCEPT
+{
+ ev_tstamp mn_prev = mn_now;
+
+ ev_now_update (EV_A);
+ timers_reschedule (EV_A_ mn_now - mn_prev);
+#if EV_PERIODIC_ENABLE
+ /* TODO: really do this? */
+ periodics_reschedule (EV_A);
+#endif
+}
+
+/*****************************************************************************/
+/* singly-linked list management, used when the expected list length is short */
+
+inline_size void
+wlist_add (WL *head, WL elem)
+{
+ elem->next = *head;
+ *head = elem;
+}
+
+inline_size void
+wlist_del (WL *head, WL elem)
+{
+ while (*head)
+ {
+ if (ecb_expect_true (*head == elem))
+ {
+ *head = elem->next;
+ break;
+ }
+
+ head = &(*head)->next;
+ }
+}
+
+/* internal, faster, version of ev_clear_pending */
+inline_speed void
+clear_pending (EV_P_ W w)
+{
+ if (w->pending)
+ {
+ pendings [ABSPRI (w)][w->pending - 1].w = (W)&pending_w;
+ w->pending = 0;
+ }
+}
+
+int
+ev_clear_pending (EV_P_ void *w) EV_NOEXCEPT
+{
+ W w_ = (W)w;
+ int pending = w_->pending;
+
+ if (ecb_expect_true (pending))
+ {
+ ANPENDING *p = pendings [ABSPRI (w_)] + pending - 1;
+ p->w = (W)&pending_w;
+ w_->pending = 0;
+ return p->events;
+ }
+ else
+ return 0;
+}
+
+inline_size void
+pri_adjust (EV_P_ W w)
+{
+ int pri = ev_priority (w);
+ pri = pri < EV_MINPRI ? EV_MINPRI : pri;
+ pri = pri > EV_MAXPRI ? EV_MAXPRI : pri;
+ ev_set_priority (w, pri);
+}
+
+inline_speed void
+ev_start (EV_P_ W w, int active)
+{
+ pri_adjust (EV_A_ w);
+ w->active = active;
+ ev_ref (EV_A);
+}
+
+inline_size void
+ev_stop (EV_P_ W w)
+{
+ ev_unref (EV_A);
+ w->active = 0;
+}
+
+/*****************************************************************************/
+
+ecb_noinline
+void
+ev_io_start (EV_P_ ev_io *w) EV_NOEXCEPT
+{
+ int fd = w->fd;
+
+ if (ecb_expect_false (ev_is_active (w)))
+ return;
+
+ assert (("libev: ev_io_start called with negative fd", fd >= 0));
+ assert (("libev: ev_io_start called with illegal event mask", !(w->events & ~(EV__IOFDSET | EV_READ | EV_WRITE))));
+
+#if EV_VERIFY >= 2
+ assert (("libev: ev_io_start called on watcher with invalid fd", fd_valid (fd)));
+#endif
+ EV_FREQUENT_CHECK;
+
+ ev_start (EV_A_ (W)w, 1);
+ array_needsize (ANFD, anfds, anfdmax, fd + 1, array_needsize_zerofill);
+ wlist_add (&anfds[fd].head, (WL)w);
+
+ /* common bug, apparently */
+ assert (("libev: ev_io_start called with corrupted watcher", ((WL)w)->next != (WL)w));
+
+ fd_change (EV_A_ fd, w->events & EV__IOFDSET | EV_ANFD_REIFY);
+ w->events &= ~EV__IOFDSET;
+
+ EV_FREQUENT_CHECK;
+}
+
+ecb_noinline
+void
+ev_io_stop (EV_P_ ev_io *w) EV_NOEXCEPT
+{
+ clear_pending (EV_A_ (W)w);
+ if (ecb_expect_false (!ev_is_active (w)))
+ return;
+
+ assert (("libev: ev_io_stop called with illegal fd (must stay constant after start!)", w->fd >= 0 && w->fd < anfdmax));
+
+#if EV_VERIFY >= 2
+ assert (("libev: ev_io_stop called on watcher with invalid fd", fd_valid (w->fd)));
+#endif
+ EV_FREQUENT_CHECK;
+
+ wlist_del (&anfds[w->fd].head, (WL)w);
+ ev_stop (EV_A_ (W)w);
+
+ fd_change (EV_A_ w->fd, EV_ANFD_REIFY);
+
+ EV_FREQUENT_CHECK;
+}
+
+ecb_noinline
+void
+ev_timer_start (EV_P_ ev_timer *w) EV_NOEXCEPT
+{
+ if (ecb_expect_false (ev_is_active (w)))
+ return;
+
+ ev_at (w) += mn_now;
+
+ assert (("libev: ev_timer_start called with negative timer repeat value", w->repeat >= 0.));
+
+ EV_FREQUENT_CHECK;
+
+ ++timercnt;
+ ev_start (EV_A_ (W)w, timercnt + HEAP0 - 1);
+ array_needsize (ANHE, timers, timermax, ev_active (w) + 1, array_needsize_noinit);
+ ANHE_w (timers [ev_active (w)]) = (WT)w;
+ ANHE_at_cache (timers [ev_active (w)]);
+ upheap (timers, ev_active (w));
+
+ EV_FREQUENT_CHECK;
+
+ /*assert (("libev: internal timer heap corruption", timers [ev_active (w)] == (WT)w));*/
+}
+
+ecb_noinline
+void
+ev_timer_stop (EV_P_ ev_timer *w) EV_NOEXCEPT
+{
+ clear_pending (EV_A_ (W)w);
+ if (ecb_expect_false (!ev_is_active (w)))
+ return;
+
+ EV_FREQUENT_CHECK;
+
+ {
+ int active = ev_active (w);
+
+ assert (("libev: internal timer heap corruption", ANHE_w (timers [active]) == (WT)w));
+
+ --timercnt;
+
+ if (ecb_expect_true (active < timercnt + HEAP0))
+ {
+ timers [active] = timers [timercnt + HEAP0];
+ adjustheap (timers, timercnt, active);
+ }
+ }
+
+ ev_at (w) -= mn_now;
+
+ ev_stop (EV_A_ (W)w);
+
+ EV_FREQUENT_CHECK;
+}
+
+ecb_noinline
+void
+ev_timer_again (EV_P_ ev_timer *w) EV_NOEXCEPT
+{
+ EV_FREQUENT_CHECK;
+
+ clear_pending (EV_A_ (W)w);
+
+ if (ev_is_active (w))
+ {
+ if (w->repeat)
+ {
+ ev_at (w) = mn_now + w->repeat;
+ ANHE_at_cache (timers [ev_active (w)]);
+ adjustheap (timers, timercnt, ev_active (w));
+ }
+ else
+ ev_timer_stop (EV_A_ w);
+ }
+ else if (w->repeat)
+ {
+ ev_at (w) = w->repeat;
+ ev_timer_start (EV_A_ w);
+ }
+
+ EV_FREQUENT_CHECK;
+}
+
+ev_tstamp
+ev_timer_remaining (EV_P_ ev_timer *w) EV_NOEXCEPT
+{
+ return ev_at (w) - (ev_is_active (w) ? mn_now : EV_TS_CONST (0.));
+}
+
+#if EV_PERIODIC_ENABLE
+ecb_noinline
+void
+ev_periodic_start (EV_P_ ev_periodic *w) EV_NOEXCEPT
+{
+ if (ecb_expect_false (ev_is_active (w)))
+ return;
+
+#if EV_USE_TIMERFD
+ if (timerfd == -2)
+ evtimerfd_init (EV_A);
+#endif
+
+ if (w->reschedule_cb)
+ ev_at (w) = w->reschedule_cb (w, ev_rt_now);
+ else if (w->interval)
+ {
+ assert (("libev: ev_periodic_start called with negative interval value", w->interval >= 0.));
+ periodic_recalc (EV_A_ w);
+ }
+ else
+ ev_at (w) = w->offset;
+
+ EV_FREQUENT_CHECK;
+
+ ++periodiccnt;
+ ev_start (EV_A_ (W)w, periodiccnt + HEAP0 - 1);
+ array_needsize (ANHE, periodics, periodicmax, ev_active (w) + 1, array_needsize_noinit);
+ ANHE_w (periodics [ev_active (w)]) = (WT)w;
+ ANHE_at_cache (periodics [ev_active (w)]);
+ upheap (periodics, ev_active (w));
+
+ EV_FREQUENT_CHECK;
+
+ /*assert (("libev: internal periodic heap corruption", ANHE_w (periodics [ev_active (w)]) == (WT)w));*/
+}
+
+ecb_noinline
+void
+ev_periodic_stop (EV_P_ ev_periodic *w) EV_NOEXCEPT
+{
+ clear_pending (EV_A_ (W)w);
+ if (ecb_expect_false (!ev_is_active (w)))
+ return;
+
+ EV_FREQUENT_CHECK;
+
+ {
+ int active = ev_active (w);
+
+ assert (("libev: internal periodic heap corruption", ANHE_w (periodics [active]) == (WT)w));
+
+ --periodiccnt;
+
+ if (ecb_expect_true (active < periodiccnt + HEAP0))
+ {
+ periodics [active] = periodics [periodiccnt + HEAP0];
+ adjustheap (periodics, periodiccnt, active);
+ }
+ }
+
+ ev_stop (EV_A_ (W)w);
+
+ EV_FREQUENT_CHECK;
+}
+
+ecb_noinline
+void
+ev_periodic_again (EV_P_ ev_periodic *w) EV_NOEXCEPT
+{
+ /* TODO: use adjustheap and recalculation */
+ ev_periodic_stop (EV_A_ w);
+ ev_periodic_start (EV_A_ w);
+}
+#endif
+
+#ifndef SA_RESTART
+# define SA_RESTART 0
+#endif
+
+#if EV_SIGNAL_ENABLE
+
+ecb_noinline
+void
+ev_signal_start (EV_P_ ev_signal *w) EV_NOEXCEPT
+{
+ if (ecb_expect_false (ev_is_active (w)))
+ return;
+
+ assert (("libev: ev_signal_start called with illegal signal number", w->signum > 0 && w->signum < EV_NSIG));
+
+#if EV_MULTIPLICITY
+ assert (("libev: a signal must not be attached to two different loops",
+ !signals [w->signum - 1].loop || signals [w->signum - 1].loop == loop));
+
+ signals [w->signum - 1].loop = EV_A;
+ ECB_MEMORY_FENCE_RELEASE;
+#endif
+
+ EV_FREQUENT_CHECK;
+
+#if EV_USE_SIGNALFD
+ if (sigfd == -2)
+ {
+ sigfd = signalfd (-1, &sigfd_set, SFD_NONBLOCK | SFD_CLOEXEC);
+ if (sigfd < 0 && errno == EINVAL)
+ sigfd = signalfd (-1, &sigfd_set, 0); /* retry without flags */
+
+ if (sigfd >= 0)
+ {
+ fd_intern (sigfd); /* doing it twice will not hurt */
+
+ sigemptyset (&sigfd_set);
+
+ ev_io_init (&sigfd_w, sigfdcb, sigfd, EV_READ);
+ ev_set_priority (&sigfd_w, EV_MAXPRI);
+ ev_io_start (EV_A_ &sigfd_w);
+ ev_unref (EV_A); /* signalfd watcher should not keep loop alive */
+ }
+ }
+
+ if (sigfd >= 0)
+ {
+ /* TODO: check .head */
+ sigaddset (&sigfd_set, w->signum);
+ sigprocmask (SIG_BLOCK, &sigfd_set, 0);
+
+ signalfd (sigfd, &sigfd_set, 0);
+ }
+#endif
+
+ ev_start (EV_A_ (W)w, 1);
+ wlist_add (&signals [w->signum - 1].head, (WL)w);
+
+ if (!((WL)w)->next)
+# if EV_USE_SIGNALFD
+ if (sigfd < 0) /*TODO*/
+# endif
+ {
+# ifdef _WIN32
+ evpipe_init (EV_A);
+
+ signal (w->signum, ev_sighandler);
+# else
+ struct sigaction sa;
+
+ evpipe_init (EV_A);
+
+ sa.sa_handler = ev_sighandler;
+ sigfillset (&sa.sa_mask);
+ sa.sa_flags = SA_RESTART; /* if restarting works we save one iteration */
+ sigaction (w->signum, &sa, 0);
+
+ if (origflags & EVFLAG_NOSIGMASK)
+ {
+ sigemptyset (&sa.sa_mask);
+ sigaddset (&sa.sa_mask, w->signum);
+ sigprocmask (SIG_UNBLOCK, &sa.sa_mask, 0);
+ }
+#endif
+ }
+
+ EV_FREQUENT_CHECK;
+}
+
+ecb_noinline
+void
+ev_signal_stop (EV_P_ ev_signal *w) EV_NOEXCEPT
+{
+ clear_pending (EV_A_ (W)w);
+ if (ecb_expect_false (!ev_is_active (w)))
+ return;
+
+ EV_FREQUENT_CHECK;
+
+ wlist_del (&signals [w->signum - 1].head, (WL)w);
+ ev_stop (EV_A_ (W)w);
+
+ if (!signals [w->signum - 1].head)
+ {
+#if EV_MULTIPLICITY
+ signals [w->signum - 1].loop = 0; /* unattach from signal */
+#endif
+#if EV_USE_SIGNALFD
+ if (sigfd >= 0)
+ {
+ sigset_t ss;
+
+ sigemptyset (&ss);
+ sigaddset (&ss, w->signum);
+ sigdelset (&sigfd_set, w->signum);
+
+ signalfd (sigfd, &sigfd_set, 0);
+ sigprocmask (SIG_UNBLOCK, &ss, 0);
+ }
+ else
+#endif
+ signal (w->signum, SIG_DFL);
+ }
+
+ EV_FREQUENT_CHECK;
+}
+
+#endif
+
+#if EV_CHILD_ENABLE
+
+void
+ev_child_start (EV_P_ ev_child *w) EV_NOEXCEPT
+{
+#if EV_MULTIPLICITY
+ assert (("libev: child watchers are only supported in the default loop", loop == ev_default_loop_ptr));
+#endif
+ if (ecb_expect_false (ev_is_active (w)))
+ return;
+
+ EV_FREQUENT_CHECK;
+
+ ev_start (EV_A_ (W)w, 1);
+ wlist_add (&childs [w->pid & ((EV_PID_HASHSIZE) - 1)], (WL)w);
+
+ EV_FREQUENT_CHECK;
+}
+
+void
+ev_child_stop (EV_P_ ev_child *w) EV_NOEXCEPT
+{
+ clear_pending (EV_A_ (W)w);
+ if (ecb_expect_false (!ev_is_active (w)))
+ return;
+
+ EV_FREQUENT_CHECK;
+
+ wlist_del (&childs [w->pid & ((EV_PID_HASHSIZE) - 1)], (WL)w);
+ ev_stop (EV_A_ (W)w);
+
+ EV_FREQUENT_CHECK;
+}
+
+#endif
+
+#if EV_STAT_ENABLE
+
+# ifdef _WIN32
+# undef lstat
+# define lstat(a,b) _stati64 (a,b)
+# endif
+
+#define DEF_STAT_INTERVAL 5.0074891
+#define NFS_STAT_INTERVAL 30.1074891 /* for filesystems potentially failing inotify */
+#define MIN_STAT_INTERVAL 0.1074891
+
+ecb_noinline static void stat_timer_cb (EV_P_ ev_timer *w_, int revents);
+
+#if EV_USE_INOTIFY
+
+/* the * 2 is to allow for alignment padding, which for some reason is >> 8 */
+# define EV_INOTIFY_BUFSIZE (sizeof (struct inotify_event) * 2 + NAME_MAX)
+
+ecb_noinline
+static void
+infy_add (EV_P_ ev_stat *w)
+{
+ w->wd = inotify_add_watch (fs_fd, w->path,
+ IN_ATTRIB | IN_DELETE_SELF | IN_MOVE_SELF | IN_MODIFY
+ | IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO
+ | IN_DONT_FOLLOW | IN_MASK_ADD);
+
+ if (w->wd >= 0)
+ {
+ struct statfs sfs;
+
+ /* now local changes will be tracked by inotify, but remote changes won't */
+ /* unless the filesystem is known to be local, we therefore still poll */
+ /* also do poll on <2.6.25, but with normal frequency */
+
+ if (!fs_2625)
+ w->timer.repeat = w->interval ? w->interval : DEF_STAT_INTERVAL;
+ else if (!statfs (w->path, &sfs)
+ && (sfs.f_type == 0x1373 /* devfs */
+ || sfs.f_type == 0x4006 /* fat */
+ || sfs.f_type == 0x4d44 /* msdos */
+ || sfs.f_type == 0xEF53 /* ext2/3 */
+ || sfs.f_type == 0x72b6 /* jffs2 */
+ || sfs.f_type == 0x858458f6 /* ramfs */
+ || sfs.f_type == 0x5346544e /* ntfs */
+ || sfs.f_type == 0x3153464a /* jfs */
+ || sfs.f_type == 0x9123683e /* btrfs */
+ || sfs.f_type == 0x52654973 /* reiser3 */
+ || sfs.f_type == 0x01021994 /* tmpfs */
+ || sfs.f_type == 0x58465342 /* xfs */))
+ w->timer.repeat = 0.; /* filesystem is local, kernel new enough */
+ else
+ w->timer.repeat = w->interval ? w->interval : NFS_STAT_INTERVAL; /* remote, use reduced frequency */
+ }
+ else
+ {
+ /* can't use inotify, continue to stat */
+ w->timer.repeat = w->interval ? w->interval : DEF_STAT_INTERVAL;
+
+ /* if path is not there, monitor some parent directory for speedup hints */
+ /* note that exceeding the hardcoded path limit is not a correctness issue, */
+ /* but an efficiency issue only */
+ if ((errno == ENOENT || errno == EACCES) && strlen (w->path) < 4096)
+ {
+ char path [4096];
+ strcpy (path, w->path);
+
+ do
+ {
+ int mask = IN_MASK_ADD | IN_DELETE_SELF | IN_MOVE_SELF
+ | (errno == EACCES ? IN_ATTRIB : IN_CREATE | IN_MOVED_TO);
+
+ char *pend = strrchr (path, '/');
+
+ if (!pend || pend == path)
+ break;
+
+ *pend = 0;
+ w->wd = inotify_add_watch (fs_fd, path, mask);
+ }
+ while (w->wd < 0 && (errno == ENOENT || errno == EACCES));
+ }
+ }
+
+ if (w->wd >= 0)
+ wlist_add (&fs_hash [w->wd & ((EV_INOTIFY_HASHSIZE) - 1)].head, (WL)w);
+
+ /* now re-arm timer, if required */
+ if (ev_is_active (&w->timer)) ev_ref (EV_A);
+ ev_timer_again (EV_A_ &w->timer);
+ if (ev_is_active (&w->timer)) ev_unref (EV_A);
+}
+
+ecb_noinline
+static void
+infy_del (EV_P_ ev_stat *w)
+{
+ int slot;
+ int wd = w->wd;
+
+ if (wd < 0)
+ return;
+
+ w->wd = -2;
+ slot = wd & ((EV_INOTIFY_HASHSIZE) - 1);
+ wlist_del (&fs_hash [slot].head, (WL)w);
+
+ /* remove this watcher, if others are watching it, they will rearm */
+ inotify_rm_watch (fs_fd, wd);
+}
+
+ecb_noinline
+static void
+infy_wd (EV_P_ int slot, int wd, struct inotify_event *ev)
+{
+ if (slot < 0)
+ /* overflow, need to check for all hash slots */
+ for (slot = 0; slot < (EV_INOTIFY_HASHSIZE); ++slot)
+ infy_wd (EV_A_ slot, wd, ev);
+ else
+ {
+ WL w_;
+
+ for (w_ = fs_hash [slot & ((EV_INOTIFY_HASHSIZE) - 1)].head; w_; )
+ {
+ ev_stat *w = (ev_stat *)w_;
+ w_ = w_->next; /* lets us remove this watcher and all before it */
+
+ if (w->wd == wd || wd == -1)
+ {
+ if (ev->mask & (IN_IGNORED | IN_UNMOUNT | IN_DELETE_SELF))
+ {
+ wlist_del (&fs_hash [slot & ((EV_INOTIFY_HASHSIZE) - 1)].head, (WL)w);
+ w->wd = -1;
+ infy_add (EV_A_ w); /* re-add, no matter what */
+ }
+
+ stat_timer_cb (EV_A_ &w->timer, 0);
+ }
+ }
+ }
+}
+
+static void
+infy_cb (EV_P_ ev_io *w, int revents)
+{
+ char buf [EV_INOTIFY_BUFSIZE];
+ int ofs;
+ int len = read (fs_fd, buf, sizeof (buf));
+
+ for (ofs = 0; ofs < len; )
+ {
+ struct inotify_event *ev = (struct inotify_event *)(buf + ofs);
+ infy_wd (EV_A_ ev->wd, ev->wd, ev);
+ ofs += sizeof (struct inotify_event) + ev->len;
+ }
+}
+
+inline_size ecb_cold
+void
+ev_check_2625 (EV_P)
+{
+ /* kernels < 2.6.25 are borked
+ * http://www.ussg.indiana.edu/hypermail/linux/kernel/0711.3/1208.html
+ */
+ if (ev_linux_version () < 0x020619)
+ return;
+
+ fs_2625 = 1;
+}
+
+inline_size int
+infy_newfd (void)
+{
+#if defined IN_CLOEXEC && defined IN_NONBLOCK
+ int fd = inotify_init1 (IN_CLOEXEC | IN_NONBLOCK);
+ if (fd >= 0)
+ return fd;
+#endif
+ return inotify_init ();
+}
+
+inline_size void
+infy_init (EV_P)
+{
+ if (fs_fd != -2)
+ return;
+
+ fs_fd = -1;
+
+ ev_check_2625 (EV_A);
+
+ fs_fd = infy_newfd ();
+
+ if (fs_fd >= 0)
+ {
+ fd_intern (fs_fd);
+ ev_io_init (&fs_w, infy_cb, fs_fd, EV_READ);
+ ev_set_priority (&fs_w, EV_MAXPRI);
+ ev_io_start (EV_A_ &fs_w);
+ ev_unref (EV_A);
+ }
+}
+
+inline_size void
+infy_fork (EV_P)
+{
+ int slot;
+
+ if (fs_fd < 0)
+ return;
+
+ ev_ref (EV_A);
+ ev_io_stop (EV_A_ &fs_w);
+ close (fs_fd);
+ fs_fd = infy_newfd ();
+
+ if (fs_fd >= 0)
+ {
+ fd_intern (fs_fd);
+ ev_io_set (&fs_w, fs_fd, EV_READ);
+ ev_io_start (EV_A_ &fs_w);
+ ev_unref (EV_A);
+ }
+
+ for (slot = 0; slot < (EV_INOTIFY_HASHSIZE); ++slot)
+ {
+ WL w_ = fs_hash [slot].head;
+ fs_hash [slot].head = 0;
+
+ while (w_)
+ {
+ ev_stat *w = (ev_stat *)w_;
+ w_ = w_->next; /* lets us add this watcher */
+
+ w->wd = -1;
+
+ if (fs_fd >= 0)
+ infy_add (EV_A_ w); /* re-add, no matter what */
+ else
+ {
+ w->timer.repeat = w->interval ? w->interval : DEF_STAT_INTERVAL;
+ if (ev_is_active (&w->timer)) ev_ref (EV_A);
+ ev_timer_again (EV_A_ &w->timer);
+ if (ev_is_active (&w->timer)) ev_unref (EV_A);
+ }
+ }
+ }
+}
+
+#endif
+
+#ifdef _WIN32
+# define EV_LSTAT(p,b) _stati64 (p, b)
+#else
+# define EV_LSTAT(p,b) lstat (p, b)
+#endif
+
+void
+ev_stat_stat (EV_P_ ev_stat *w) EV_NOEXCEPT
+{
+ if (lstat (w->path, &w->attr) < 0)
+ w->attr.st_nlink = 0;
+ else if (!w->attr.st_nlink)
+ w->attr.st_nlink = 1;
+}
+
+ecb_noinline
+static void
+stat_timer_cb (EV_P_ ev_timer *w_, int revents)
+{
+ ev_stat *w = (ev_stat *)(((char *)w_) - offsetof (ev_stat, timer));
+
+ ev_statdata prev = w->attr;
+ ev_stat_stat (EV_A_ w);
+
+ /* memcmp doesn't work on netbsd, they.... do stuff to their struct stat */
+ if (
+ prev.st_dev != w->attr.st_dev
+ || prev.st_ino != w->attr.st_ino
+ || prev.st_mode != w->attr.st_mode
+ || prev.st_nlink != w->attr.st_nlink
+ || prev.st_uid != w->attr.st_uid
+ || prev.st_gid != w->attr.st_gid
+ || prev.st_rdev != w->attr.st_rdev
+ || prev.st_size != w->attr.st_size
+ || prev.st_atime != w->attr.st_atime
+ || prev.st_mtime != w->attr.st_mtime
+ || prev.st_ctime != w->attr.st_ctime
+ ) {
+ /* we only update w->prev on actual differences */
+ /* in case we test more often than invoke the callback, */
+ /* to ensure that prev is always different to attr */
+ w->prev = prev;
+
+ #if EV_USE_INOTIFY
+ if (fs_fd >= 0)
+ {
+ infy_del (EV_A_ w);
+ infy_add (EV_A_ w);
+ ev_stat_stat (EV_A_ w); /* avoid race... */
+ }
+ #endif
+
+ ev_feed_event (EV_A_ w, EV_STAT);
+ }
+}
+
+void
+ev_stat_start (EV_P_ ev_stat *w) EV_NOEXCEPT
+{
+ if (ecb_expect_false (ev_is_active (w)))
+ return;
+
+ ev_stat_stat (EV_A_ w);
+
+ if (w->interval < MIN_STAT_INTERVAL && w->interval)
+ w->interval = MIN_STAT_INTERVAL;
+
+ ev_timer_init (&w->timer, stat_timer_cb, 0., w->interval ? w->interval : DEF_STAT_INTERVAL);
+ ev_set_priority (&w->timer, ev_priority (w));
+
+#if EV_USE_INOTIFY
+ infy_init (EV_A);
+
+ if (fs_fd >= 0)
+ infy_add (EV_A_ w);
+ else
+#endif
+ {
+ ev_timer_again (EV_A_ &w->timer);
+ ev_unref (EV_A);
+ }
+
+ ev_start (EV_A_ (W)w, 1);
+
+ EV_FREQUENT_CHECK;
+}
+
+void
+ev_stat_stop (EV_P_ ev_stat *w) EV_NOEXCEPT
+{
+ clear_pending (EV_A_ (W)w);
+ if (ecb_expect_false (!ev_is_active (w)))
+ return;
+
+ EV_FREQUENT_CHECK;
+
+#if EV_USE_INOTIFY
+ infy_del (EV_A_ w);
+#endif
+
+ if (ev_is_active (&w->timer))
+ {
+ ev_ref (EV_A);
+ ev_timer_stop (EV_A_ &w->timer);
+ }
+
+ ev_stop (EV_A_ (W)w);
+
+ EV_FREQUENT_CHECK;
+}
+#endif
+
+#if EV_IDLE_ENABLE
+void
+ev_idle_start (EV_P_ ev_idle *w) EV_NOEXCEPT
+{
+ if (ecb_expect_false (ev_is_active (w)))
+ return;
+
+ pri_adjust (EV_A_ (W)w);
+
+ EV_FREQUENT_CHECK;
+
+ {
+ int active = ++idlecnt [ABSPRI (w)];
+
+ ++idleall;
+ ev_start (EV_A_ (W)w, active);
+
+ array_needsize (ev_idle *, idles [ABSPRI (w)], idlemax [ABSPRI (w)], active, array_needsize_noinit);
+ idles [ABSPRI (w)][active - 1] = w;
+ }
+
+ EV_FREQUENT_CHECK;
+}
+
+void
+ev_idle_stop (EV_P_ ev_idle *w) EV_NOEXCEPT
+{
+ clear_pending (EV_A_ (W)w);
+ if (ecb_expect_false (!ev_is_active (w)))
+ return;
+
+ EV_FREQUENT_CHECK;
+
+ {
+ int active = ev_active (w);
+
+ idles [ABSPRI (w)][active - 1] = idles [ABSPRI (w)][--idlecnt [ABSPRI (w)]];
+ ev_active (idles [ABSPRI (w)][active - 1]) = active;
+
+ ev_stop (EV_A_ (W)w);
+ --idleall;
+ }
+
+ EV_FREQUENT_CHECK;
+}
+#endif
+
+#if EV_PREPARE_ENABLE
+void
+ev_prepare_start (EV_P_ ev_prepare *w) EV_NOEXCEPT
+{
+ if (ecb_expect_false (ev_is_active (w)))
+ return;
+
+ EV_FREQUENT_CHECK;
+
+ ev_start (EV_A_ (W)w, ++preparecnt);
+ array_needsize (ev_prepare *, prepares, preparemax, preparecnt, array_needsize_noinit);
+ prepares [preparecnt - 1] = w;
+
+ EV_FREQUENT_CHECK;
+}
+
+void
+ev_prepare_stop (EV_P_ ev_prepare *w) EV_NOEXCEPT
+{
+ clear_pending (EV_A_ (W)w);
+ if (ecb_expect_false (!ev_is_active (w)))
+ return;
+
+ EV_FREQUENT_CHECK;
+
+ {
+ int active = ev_active (w);
+
+ prepares [active - 1] = prepares [--preparecnt];
+ ev_active (prepares [active - 1]) = active;
+ }
+
+ ev_stop (EV_A_ (W)w);
+
+ EV_FREQUENT_CHECK;
+}
+#endif
+
+#if EV_CHECK_ENABLE
+void
+ev_check_start (EV_P_ ev_check *w) EV_NOEXCEPT
+{
+ if (ecb_expect_false (ev_is_active (w)))
+ return;
+
+ EV_FREQUENT_CHECK;
+
+ ev_start (EV_A_ (W)w, ++checkcnt);
+ array_needsize (ev_check *, checks, checkmax, checkcnt, array_needsize_noinit);
+ checks [checkcnt - 1] = w;
+
+ EV_FREQUENT_CHECK;
+}
+
+void
+ev_check_stop (EV_P_ ev_check *w) EV_NOEXCEPT
+{
+ clear_pending (EV_A_ (W)w);
+ if (ecb_expect_false (!ev_is_active (w)))
+ return;
+
+ EV_FREQUENT_CHECK;
+
+ {
+ int active = ev_active (w);
+
+ checks [active - 1] = checks [--checkcnt];
+ ev_active (checks [active - 1]) = active;
+ }
+
+ ev_stop (EV_A_ (W)w);
+
+ EV_FREQUENT_CHECK;
+}
+#endif
+
+#if EV_EMBED_ENABLE
+ecb_noinline
+void
+ev_embed_sweep (EV_P_ ev_embed *w) EV_NOEXCEPT
+{
+ ev_run (w->other, EVRUN_NOWAIT);
+}
+
+static void
+embed_io_cb (EV_P_ ev_io *io, int revents)
+{
+ ev_embed *w = (ev_embed *)(((char *)io) - offsetof (ev_embed, io));
+
+ if (ev_cb (w))
+ ev_feed_event (EV_A_ (W)w, EV_EMBED);
+ else
+ ev_run (w->other, EVRUN_NOWAIT);
+}
+
+static void
+embed_prepare_cb (EV_P_ ev_prepare *prepare, int revents)
+{
+ ev_embed *w = (ev_embed *)(((char *)prepare) - offsetof (ev_embed, prepare));
+
+ {
+ EV_P = w->other;
+
+ while (fdchangecnt)
+ {
+ fd_reify (EV_A);
+ ev_run (EV_A_ EVRUN_NOWAIT);
+ }
+ }
+}
+
+#if EV_FORK_ENABLE
+static void
+embed_fork_cb (EV_P_ ev_fork *fork_w, int revents)
+{
+ ev_embed *w = (ev_embed *)(((char *)fork_w) - offsetof (ev_embed, fork));
+
+ ev_embed_stop (EV_A_ w);
+
+ {
+ EV_P = w->other;
+
+ ev_loop_fork (EV_A);
+ ev_run (EV_A_ EVRUN_NOWAIT);
+ }
+
+ ev_embed_start (EV_A_ w);
+}
+#endif
+
+#if 0
+static void
+embed_idle_cb (EV_P_ ev_idle *idle, int revents)
+{
+ ev_idle_stop (EV_A_ idle);
+}
+#endif
+
+void
+ev_embed_start (EV_P_ ev_embed *w) EV_NOEXCEPT
+{
+ if (ecb_expect_false (ev_is_active (w)))
+ return;
+
+ {
+ EV_P = w->other;
+ assert (("libev: loop to be embedded is not embeddable", backend & ev_embeddable_backends ()));
+ ev_io_init (&w->io, embed_io_cb, backend_fd, EV_READ);
+ }
+
+ EV_FREQUENT_CHECK;
+
+ ev_set_priority (&w->io, ev_priority (w));
+ ev_io_start (EV_A_ &w->io);
+
+ ev_prepare_init (&w->prepare, embed_prepare_cb);
+ ev_set_priority (&w->prepare, EV_MINPRI);
+ ev_prepare_start (EV_A_ &w->prepare);
+
+#if EV_FORK_ENABLE
+ ev_fork_init (&w->fork, embed_fork_cb);
+ ev_fork_start (EV_A_ &w->fork);
+#endif
+
+ /*ev_idle_init (&w->idle, e,bed_idle_cb);*/
+
+ ev_start (EV_A_ (W)w, 1);
+
+ EV_FREQUENT_CHECK;
+}
+
+void
+ev_embed_stop (EV_P_ ev_embed *w) EV_NOEXCEPT
+{
+ clear_pending (EV_A_ (W)w);
+ if (ecb_expect_false (!ev_is_active (w)))
+ return;
+
+ EV_FREQUENT_CHECK;
+
+ ev_io_stop (EV_A_ &w->io);
+ ev_prepare_stop (EV_A_ &w->prepare);
+#if EV_FORK_ENABLE
+ ev_fork_stop (EV_A_ &w->fork);
+#endif
+
+ ev_stop (EV_A_ (W)w);
+
+ EV_FREQUENT_CHECK;
+}
+#endif
+
+#if EV_FORK_ENABLE
+void
+ev_fork_start (EV_P_ ev_fork *w) EV_NOEXCEPT
+{
+ if (ecb_expect_false (ev_is_active (w)))
+ return;
+
+ EV_FREQUENT_CHECK;
+
+ ev_start (EV_A_ (W)w, ++forkcnt);
+ array_needsize (ev_fork *, forks, forkmax, forkcnt, array_needsize_noinit);
+ forks [forkcnt - 1] = w;
+
+ EV_FREQUENT_CHECK;
+}
+
+void
+ev_fork_stop (EV_P_ ev_fork *w) EV_NOEXCEPT
+{
+ clear_pending (EV_A_ (W)w);
+ if (ecb_expect_false (!ev_is_active (w)))
+ return;
+
+ EV_FREQUENT_CHECK;
+
+ {
+ int active = ev_active (w);
+
+ forks [active - 1] = forks [--forkcnt];
+ ev_active (forks [active - 1]) = active;
+ }
+
+ ev_stop (EV_A_ (W)w);
+
+ EV_FREQUENT_CHECK;
+}
+#endif
+
+#if EV_CLEANUP_ENABLE
+void
+ev_cleanup_start (EV_P_ ev_cleanup *w) EV_NOEXCEPT
+{
+ if (ecb_expect_false (ev_is_active (w)))
+ return;
+
+ EV_FREQUENT_CHECK;
+
+ ev_start (EV_A_ (W)w, ++cleanupcnt);
+ array_needsize (ev_cleanup *, cleanups, cleanupmax, cleanupcnt, array_needsize_noinit);
+ cleanups [cleanupcnt - 1] = w;
+
+ /* cleanup watchers should never keep a refcount on the loop */
+ ev_unref (EV_A);
+ EV_FREQUENT_CHECK;
+}
+
+void
+ev_cleanup_stop (EV_P_ ev_cleanup *w) EV_NOEXCEPT
+{
+ clear_pending (EV_A_ (W)w);
+ if (ecb_expect_false (!ev_is_active (w)))
+ return;
+
+ EV_FREQUENT_CHECK;
+ ev_ref (EV_A);
+
+ {
+ int active = ev_active (w);
+
+ cleanups [active - 1] = cleanups [--cleanupcnt];
+ ev_active (cleanups [active - 1]) = active;
+ }
+
+ ev_stop (EV_A_ (W)w);
+
+ EV_FREQUENT_CHECK;
+}
+#endif
+
+#if EV_ASYNC_ENABLE
+void
+ev_async_start (EV_P_ ev_async *w) EV_NOEXCEPT
+{
+ if (ecb_expect_false (ev_is_active (w)))
+ return;
+
+ w->sent = 0;
+
+ evpipe_init (EV_A);
+
+ EV_FREQUENT_CHECK;
+
+ ev_start (EV_A_ (W)w, ++asynccnt);
+ array_needsize (ev_async *, asyncs, asyncmax, asynccnt, array_needsize_noinit);
+ asyncs [asynccnt - 1] = w;
+
+ EV_FREQUENT_CHECK;
+}
+
+void
+ev_async_stop (EV_P_ ev_async *w) EV_NOEXCEPT
+{
+ clear_pending (EV_A_ (W)w);
+ if (ecb_expect_false (!ev_is_active (w)))
+ return;
+
+ EV_FREQUENT_CHECK;
+
+ {
+ int active = ev_active (w);
+
+ asyncs [active - 1] = asyncs [--asynccnt];
+ ev_active (asyncs [active - 1]) = active;
+ }
+
+ ev_stop (EV_A_ (W)w);
+
+ EV_FREQUENT_CHECK;
+}
+
+void
+ev_async_send (EV_P_ ev_async *w) EV_NOEXCEPT
+{
+ w->sent = 1;
+ evpipe_write (EV_A_ &async_pending);
+}
+#endif
+
+/*****************************************************************************/
+
+struct ev_once
+{
+ ev_io io;
+ ev_timer to;
+ void (*cb)(int revents, void *arg);
+ void *arg;
+};
+
+static void
+once_cb (EV_P_ struct ev_once *once, int revents)
+{
+ void (*cb)(int revents, void *arg) = once->cb;
+ void *arg = once->arg;
+
+ ev_io_stop (EV_A_ &once->io);
+ ev_timer_stop (EV_A_ &once->to);
+ ev_free (once);
+
+ cb (revents, arg);
+}
+
+static void
+once_cb_io (EV_P_ ev_io *w, int revents)
+{
+ struct ev_once *once = (struct ev_once *)(((char *)w) - offsetof (struct ev_once, io));
+
+ once_cb (EV_A_ once, revents | ev_clear_pending (EV_A_ &once->to));
+}
+
+static void
+once_cb_to (EV_P_ ev_timer *w, int revents)
+{
+ struct ev_once *once = (struct ev_once *)(((char *)w) - offsetof (struct ev_once, to));
+
+ once_cb (EV_A_ once, revents | ev_clear_pending (EV_A_ &once->io));
+}
+
+void
+ev_once (EV_P_ int fd, int events, ev_tstamp timeout, void (*cb)(int revents, void *arg), void *arg) EV_NOEXCEPT
+{
+ struct ev_once *once = (struct ev_once *)ev_malloc (sizeof (struct ev_once));
+
+ once->cb = cb;
+ once->arg = arg;
+
+ ev_init (&once->io, once_cb_io);
+ if (fd >= 0)
+ {
+ ev_io_set (&once->io, fd, events);
+ ev_io_start (EV_A_ &once->io);
+ }
+
+ ev_init (&once->to, once_cb_to);
+ if (timeout >= 0.)
+ {
+ ev_timer_set (&once->to, timeout, 0.);
+ ev_timer_start (EV_A_ &once->to);
+ }
+}
+
+/*****************************************************************************/
+
+#if EV_WALK_ENABLE
+ecb_cold
+void
+ev_walk (EV_P_ int types, void (*cb)(EV_P_ int type, void *w)) EV_NOEXCEPT
+{
+ int i, j;
+ ev_watcher_list *wl, *wn;
+
+ if (types & (EV_IO | EV_EMBED))
+ for (i = 0; i < anfdmax; ++i)
+ for (wl = anfds [i].head; wl; )
+ {
+ wn = wl->next;
+
+#if EV_EMBED_ENABLE
+ if (ev_cb ((ev_io *)wl) == embed_io_cb)
+ {
+ if (types & EV_EMBED)
+ cb (EV_A_ EV_EMBED, ((char *)wl) - offsetof (struct ev_embed, io));
+ }
+ else
+#endif
+#if EV_USE_INOTIFY
+ if (ev_cb ((ev_io *)wl) == infy_cb)
+ ;
+ else
+#endif
+ if ((ev_io *)wl != &pipe_w)
+ if (types & EV_IO)
+ cb (EV_A_ EV_IO, wl);
+
+ wl = wn;
+ }
+
+ if (types & (EV_TIMER | EV_STAT))
+ for (i = timercnt + HEAP0; i-- > HEAP0; )
+#if EV_STAT_ENABLE
+ /*TODO: timer is not always active*/
+ if (ev_cb ((ev_timer *)ANHE_w (timers [i])) == stat_timer_cb)
+ {
+ if (types & EV_STAT)
+ cb (EV_A_ EV_STAT, ((char *)ANHE_w (timers [i])) - offsetof (struct ev_stat, timer));
+ }
+ else
+#endif
+ if (types & EV_TIMER)
+ cb (EV_A_ EV_TIMER, ANHE_w (timers [i]));
+
+#if EV_PERIODIC_ENABLE
+ if (types & EV_PERIODIC)
+ for (i = periodiccnt + HEAP0; i-- > HEAP0; )
+ cb (EV_A_ EV_PERIODIC, ANHE_w (periodics [i]));
+#endif
+
+#if EV_IDLE_ENABLE
+ if (types & EV_IDLE)
+ for (j = NUMPRI; j--; )
+ for (i = idlecnt [j]; i--; )
+ cb (EV_A_ EV_IDLE, idles [j][i]);
+#endif
+
+#if EV_FORK_ENABLE
+ if (types & EV_FORK)
+ for (i = forkcnt; i--; )
+ if (ev_cb (forks [i]) != embed_fork_cb)
+ cb (EV_A_ EV_FORK, forks [i]);
+#endif
+
+#if EV_ASYNC_ENABLE
+ if (types & EV_ASYNC)
+ for (i = asynccnt; i--; )
+ cb (EV_A_ EV_ASYNC, asyncs [i]);
+#endif
+
+#if EV_PREPARE_ENABLE
+ if (types & EV_PREPARE)
+ for (i = preparecnt; i--; )
+# if EV_EMBED_ENABLE
+ if (ev_cb (prepares [i]) != embed_prepare_cb)
+# endif
+ cb (EV_A_ EV_PREPARE, prepares [i]);
+#endif
+
+#if EV_CHECK_ENABLE
+ if (types & EV_CHECK)
+ for (i = checkcnt; i--; )
+ cb (EV_A_ EV_CHECK, checks [i]);
+#endif
+
+#if EV_SIGNAL_ENABLE
+ if (types & EV_SIGNAL)
+ for (i = 0; i < EV_NSIG - 1; ++i)
+ for (wl = signals [i].head; wl; )
+ {
+ wn = wl->next;
+ cb (EV_A_ EV_SIGNAL, wl);
+ wl = wn;
+ }
+#endif
+
+#if EV_CHILD_ENABLE
+ if (types & EV_CHILD)
+ for (i = (EV_PID_HASHSIZE); i--; )
+ for (wl = childs [i]; wl; )
+ {
+ wn = wl->next;
+ cb (EV_A_ EV_CHILD, wl);
+ wl = wn;
+ }
+#endif
+/* EV_STAT 0x00001000 /* stat data changed */
+/* EV_EMBED 0x00010000 /* embedded event loop needs sweep */
+}
+#endif
+
+#if EV_MULTIPLICITY
+ #include "ev_wrap.h"
+#endif
+
diff --git a/3rdparty/libev/ev.h b/3rdparty/libev/ev.h
new file mode 100644
index 0000000..4669c39
--- /dev/null
+++ b/3rdparty/libev/ev.h
@@ -0,0 +1,860 @@
+/*
+ * libev native API header
+ *
+ * Copyright (c) 2007-2020 Marc Alexander Lehmann <libev@schmorp.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
+ * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
+ * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
+ * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License ("GPL") version 2 or any later version,
+ * in which case the provisions of the GPL are applicable instead of
+ * the above. If you wish to allow the use of your version of this file
+ * only under the terms of the GPL and not to allow others to use your
+ * version of this file under the BSD license, indicate your decision
+ * by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL. If you do not delete the
+ * provisions above, a recipient may use your version of this file under
+ * either the BSD or the GPL.
+ */
+
+#ifndef EV_H_
+#define EV_H_
+
+#ifdef __cplusplus
+# define EV_CPP(x) x
+# if __cplusplus >= 201103L
+# define EV_NOEXCEPT noexcept
+# else
+# define EV_NOEXCEPT
+# endif
+#else
+# define EV_CPP(x)
+# define EV_NOEXCEPT
+#endif
+#define EV_THROW EV_NOEXCEPT /* pre-4.25, do not use in new code */
+
+EV_CPP(extern "C" {)
+
+/*****************************************************************************/
+
+/* pre-4.0 compatibility */
+#ifndef EV_COMPAT3
+# define EV_COMPAT3 1
+#endif
+
+#ifndef EV_FEATURES
+# if defined __OPTIMIZE_SIZE__
+# define EV_FEATURES 0x7c
+# else
+# define EV_FEATURES 0x7f
+# endif
+#endif
+
+#define EV_FEATURE_CODE ((EV_FEATURES) & 1)
+#define EV_FEATURE_DATA ((EV_FEATURES) & 2)
+#define EV_FEATURE_CONFIG ((EV_FEATURES) & 4)
+#define EV_FEATURE_API ((EV_FEATURES) & 8)
+#define EV_FEATURE_WATCHERS ((EV_FEATURES) & 16)
+#define EV_FEATURE_BACKENDS ((EV_FEATURES) & 32)
+#define EV_FEATURE_OS ((EV_FEATURES) & 64)
+
+/* these priorities are inclusive, higher priorities will be invoked earlier */
+#ifndef EV_MINPRI
+# define EV_MINPRI (EV_FEATURE_CONFIG ? -2 : 0)
+#endif
+#ifndef EV_MAXPRI
+# define EV_MAXPRI (EV_FEATURE_CONFIG ? +2 : 0)
+#endif
+
+#ifndef EV_MULTIPLICITY
+# define EV_MULTIPLICITY EV_FEATURE_CONFIG
+#endif
+
+#ifndef EV_PERIODIC_ENABLE
+# define EV_PERIODIC_ENABLE EV_FEATURE_WATCHERS
+#endif
+
+#ifndef EV_STAT_ENABLE
+# define EV_STAT_ENABLE EV_FEATURE_WATCHERS
+#endif
+
+#ifndef EV_PREPARE_ENABLE
+# define EV_PREPARE_ENABLE EV_FEATURE_WATCHERS
+#endif
+
+#ifndef EV_CHECK_ENABLE
+# define EV_CHECK_ENABLE EV_FEATURE_WATCHERS
+#endif
+
+#ifndef EV_IDLE_ENABLE
+# define EV_IDLE_ENABLE EV_FEATURE_WATCHERS
+#endif
+
+#ifndef EV_FORK_ENABLE
+# define EV_FORK_ENABLE EV_FEATURE_WATCHERS
+#endif
+
+#ifndef EV_CLEANUP_ENABLE
+# define EV_CLEANUP_ENABLE EV_FEATURE_WATCHERS
+#endif
+
+#ifndef EV_SIGNAL_ENABLE
+# define EV_SIGNAL_ENABLE EV_FEATURE_WATCHERS
+#endif
+
+#ifndef EV_CHILD_ENABLE
+# ifdef _WIN32
+# define EV_CHILD_ENABLE 0
+# else
+# define EV_CHILD_ENABLE EV_FEATURE_WATCHERS
+#endif
+#endif
+
+#ifndef EV_ASYNC_ENABLE
+# define EV_ASYNC_ENABLE EV_FEATURE_WATCHERS
+#endif
+
+#ifndef EV_EMBED_ENABLE
+# define EV_EMBED_ENABLE EV_FEATURE_WATCHERS
+#endif
+
+#ifndef EV_WALK_ENABLE
+# define EV_WALK_ENABLE 0 /* not yet */
+#endif
+
+/*****************************************************************************/
+
+#if EV_CHILD_ENABLE && !EV_SIGNAL_ENABLE
+# undef EV_SIGNAL_ENABLE
+# define EV_SIGNAL_ENABLE 1
+#endif
+
+/*****************************************************************************/
+
+#ifndef EV_TSTAMP_T
+# define EV_TSTAMP_T double
+#endif
+typedef EV_TSTAMP_T ev_tstamp;
+
+#include <string.h> /* for memmove */
+
+#ifndef EV_ATOMIC_T
+# include <signal.h>
+# define EV_ATOMIC_T sig_atomic_t volatile
+#endif
+
+#if EV_STAT_ENABLE
+# ifdef _WIN32
+# include <time.h>
+# include <sys/types.h>
+# endif
+# include <sys/stat.h>
+#endif
+
+/* support multiple event loops? */
+#if EV_MULTIPLICITY
+struct ev_loop;
+# define EV_P struct ev_loop *loop /* a loop as sole parameter in a declaration */
+# define EV_P_ EV_P, /* a loop as first of multiple parameters */
+# define EV_A loop /* a loop as sole argument to a function call */
+# define EV_A_ EV_A, /* a loop as first of multiple arguments */
+# define EV_DEFAULT_UC ev_default_loop_uc_ () /* the default loop, if initialised, as sole arg */
+# define EV_DEFAULT_UC_ EV_DEFAULT_UC, /* the default loop as first of multiple arguments */
+# define EV_DEFAULT ev_default_loop (0) /* the default loop as sole arg */
+# define EV_DEFAULT_ EV_DEFAULT, /* the default loop as first of multiple arguments */
+#else
+# define EV_P void
+# define EV_P_
+# define EV_A
+# define EV_A_
+# define EV_DEFAULT
+# define EV_DEFAULT_
+# define EV_DEFAULT_UC
+# define EV_DEFAULT_UC_
+# undef EV_EMBED_ENABLE
+#endif
+
+/* EV_INLINE is used for functions in header files */
+#if __STDC_VERSION__ >= 199901L || __GNUC__ >= 3
+# define EV_INLINE static inline
+#else
+# define EV_INLINE static
+#endif
+
+#ifdef EV_API_STATIC
+# define EV_API_DECL static
+#else
+# define EV_API_DECL extern
+#endif
+
+/* EV_PROTOTYPES can be used to switch of prototype declarations */
+#ifndef EV_PROTOTYPES
+# define EV_PROTOTYPES 1
+#endif
+
+/*****************************************************************************/
+
+#define EV_VERSION_MAJOR 4
+#define EV_VERSION_MINOR 33
+
+/* eventmask, revents, events... */
+enum {
+ EV_UNDEF = (int)0xFFFFFFFF, /* guaranteed to be invalid */
+ EV_NONE = 0x00, /* no events */
+ EV_READ = 0x01, /* ev_io detected read will not block */
+ EV_WRITE = 0x02, /* ev_io detected write will not block */
+ EV__IOFDSET = 0x80, /* internal use only */
+ EV_IO = EV_READ, /* alias for type-detection */
+ EV_TIMER = 0x00000100, /* timer timed out */
+#if EV_COMPAT3
+ EV_TIMEOUT = EV_TIMER, /* pre 4.0 API compatibility */
+#endif
+ EV_PERIODIC = 0x00000200, /* periodic timer timed out */
+ EV_SIGNAL = 0x00000400, /* signal was received */
+ EV_CHILD = 0x00000800, /* child/pid had status change */
+ EV_STAT = 0x00001000, /* stat data changed */
+ EV_IDLE = 0x00002000, /* event loop is idling */
+ EV_PREPARE = 0x00004000, /* event loop about to poll */
+ EV_CHECK = 0x00008000, /* event loop finished poll */
+ EV_EMBED = 0x00010000, /* embedded event loop needs sweep */
+ EV_FORK = 0x00020000, /* event loop resumed in child */
+ EV_CLEANUP = 0x00040000, /* event loop resumed in child */
+ EV_ASYNC = 0x00080000, /* async intra-loop signal */
+ EV_CUSTOM = 0x01000000, /* for use by user code */
+ EV_ERROR = (int)0x80000000 /* sent when an error occurs */
+};
+
+/* can be used to add custom fields to all watchers, while losing binary compatibility */
+#ifndef EV_COMMON
+# define EV_COMMON void *data;
+#endif
+
+#ifndef EV_CB_DECLARE
+# define EV_CB_DECLARE(type) void (*cb)(EV_P_ struct type *w, int revents);
+#endif
+#ifndef EV_CB_INVOKE
+# define EV_CB_INVOKE(watcher,revents) (watcher)->cb (EV_A_ (watcher), (revents))
+#endif
+
+/* not official, do not use */
+#define EV_CB(type,name) void name (EV_P_ struct ev_ ## type *w, int revents)
+
+/*
+ * struct member types:
+ * private: you may look at them, but not change them,
+ * and they might not mean anything to you.
+ * ro: can be read anytime, but only changed when the watcher isn't active.
+ * rw: can be read and modified anytime, even when the watcher is active.
+ *
+ * some internal details that might be helpful for debugging:
+ *
+ * active is either 0, which means the watcher is not active,
+ * or the array index of the watcher (periodics, timers)
+ * or the array index + 1 (most other watchers)
+ * or simply 1 for watchers that aren't in some array.
+ * pending is either 0, in which case the watcher isn't,
+ * or the array index + 1 in the pendings array.
+ */
+
+#if EV_MINPRI == EV_MAXPRI
+# define EV_DECL_PRIORITY
+#elif !defined (EV_DECL_PRIORITY)
+# define EV_DECL_PRIORITY int priority;
+#endif
+
+/* shared by all watchers */
+#define EV_WATCHER(type) \
+ int active; /* private */ \
+ int pending; /* private */ \
+ EV_DECL_PRIORITY /* private */ \
+ EV_COMMON /* rw */ \
+ EV_CB_DECLARE (type) /* private */
+
+#define EV_WATCHER_LIST(type) \
+ EV_WATCHER (type) \
+ struct ev_watcher_list *next; /* private */
+
+#define EV_WATCHER_TIME(type) \
+ EV_WATCHER (type) \
+ ev_tstamp at; /* private */
+
+/* base class, nothing to see here unless you subclass */
+typedef struct ev_watcher
+{
+ EV_WATCHER (ev_watcher)
+} ev_watcher;
+
+/* base class, nothing to see here unless you subclass */
+typedef struct ev_watcher_list
+{
+ EV_WATCHER_LIST (ev_watcher_list)
+} ev_watcher_list;
+
+/* base class, nothing to see here unless you subclass */
+typedef struct ev_watcher_time
+{
+ EV_WATCHER_TIME (ev_watcher_time)
+} ev_watcher_time;
+
+/* invoked when fd is either EV_READable or EV_WRITEable */
+/* revent EV_READ, EV_WRITE */
+typedef struct ev_io
+{
+ EV_WATCHER_LIST (ev_io)
+
+ int fd; /* ro */
+ int events; /* ro */
+} ev_io;
+
+/* invoked after a specific time, repeatable (based on monotonic clock) */
+/* revent EV_TIMEOUT */
+typedef struct ev_timer
+{
+ EV_WATCHER_TIME (ev_timer)
+
+ ev_tstamp repeat; /* rw */
+} ev_timer;
+
+/* invoked at some specific time, possibly repeating at regular intervals (based on UTC) */
+/* revent EV_PERIODIC */
+typedef struct ev_periodic
+{
+ EV_WATCHER_TIME (ev_periodic)
+
+ ev_tstamp offset; /* rw */
+ ev_tstamp interval; /* rw */
+ ev_tstamp (*reschedule_cb)(struct ev_periodic *w, ev_tstamp now) EV_NOEXCEPT; /* rw */
+} ev_periodic;
+
+/* invoked when the given signal has been received */
+/* revent EV_SIGNAL */
+typedef struct ev_signal
+{
+ EV_WATCHER_LIST (ev_signal)
+
+ int signum; /* ro */
+} ev_signal;
+
+/* invoked when sigchld is received and waitpid indicates the given pid */
+/* revent EV_CHILD */
+/* does not support priorities */
+typedef struct ev_child
+{
+ EV_WATCHER_LIST (ev_child)
+
+ int flags; /* private */
+ int pid; /* ro */
+ int rpid; /* rw, holds the received pid */
+ int rstatus; /* rw, holds the exit status, use the macros from sys/wait.h */
+} ev_child;
+
+#if EV_STAT_ENABLE
+/* st_nlink = 0 means missing file or other error */
+# ifdef _WIN32
+typedef struct _stati64 ev_statdata;
+# else
+typedef struct stat ev_statdata;
+# endif
+
+/* invoked each time the stat data changes for a given path */
+/* revent EV_STAT */
+typedef struct ev_stat
+{
+ EV_WATCHER_LIST (ev_stat)
+
+ ev_timer timer; /* private */
+ ev_tstamp interval; /* ro */
+ const char *path; /* ro */
+ ev_statdata prev; /* ro */
+ ev_statdata attr; /* ro */
+
+ int wd; /* wd for inotify, fd for kqueue */
+} ev_stat;
+#endif
+
+/* invoked when the nothing else needs to be done, keeps the process from blocking */
+/* revent EV_IDLE */
+typedef struct ev_idle
+{
+ EV_WATCHER (ev_idle)
+} ev_idle;
+
+/* invoked for each run of the mainloop, just before the blocking call */
+/* you can still change events in any way you like */
+/* revent EV_PREPARE */
+typedef struct ev_prepare
+{
+ EV_WATCHER (ev_prepare)
+} ev_prepare;
+
+/* invoked for each run of the mainloop, just after the blocking call */
+/* revent EV_CHECK */
+typedef struct ev_check
+{
+ EV_WATCHER (ev_check)
+} ev_check;
+
+/* the callback gets invoked before check in the child process when a fork was detected */
+/* revent EV_FORK */
+typedef struct ev_fork
+{
+ EV_WATCHER (ev_fork)
+} ev_fork;
+
+/* is invoked just before the loop gets destroyed */
+/* revent EV_CLEANUP */
+typedef struct ev_cleanup
+{
+ EV_WATCHER (ev_cleanup)
+} ev_cleanup;
+
+#if EV_EMBED_ENABLE
+/* used to embed an event loop inside another */
+/* the callback gets invoked when the event loop has handled events, and can be 0 */
+typedef struct ev_embed
+{
+ EV_WATCHER (ev_embed)
+
+ struct ev_loop *other; /* ro */
+#undef EV_IO_ENABLE
+#define EV_IO_ENABLE 1
+ ev_io io; /* private */
+#undef EV_PREPARE_ENABLE
+#define EV_PREPARE_ENABLE 1
+ ev_prepare prepare; /* private */
+ ev_check check; /* unused */
+ ev_timer timer; /* unused */
+ ev_periodic periodic; /* unused */
+ ev_idle idle; /* unused */
+ ev_fork fork; /* private */
+ ev_cleanup cleanup; /* unused */
+} ev_embed;
+#endif
+
+#if EV_ASYNC_ENABLE
+/* invoked when somebody calls ev_async_send on the watcher */
+/* revent EV_ASYNC */
+typedef struct ev_async
+{
+ EV_WATCHER (ev_async)
+
+ EV_ATOMIC_T sent; /* private */
+} ev_async;
+
+# define ev_async_pending(w) (+(w)->sent)
+#endif
+
+/* the presence of this union forces similar struct layout */
+union ev_any_watcher
+{
+ struct ev_watcher w;
+ struct ev_watcher_list wl;
+
+ struct ev_io io;
+ struct ev_timer timer;
+ struct ev_periodic periodic;
+ struct ev_signal signal;
+ struct ev_child child;
+#if EV_STAT_ENABLE
+ struct ev_stat stat;
+#endif
+#if EV_IDLE_ENABLE
+ struct ev_idle idle;
+#endif
+ struct ev_prepare prepare;
+ struct ev_check check;
+#if EV_FORK_ENABLE
+ struct ev_fork fork;
+#endif
+#if EV_CLEANUP_ENABLE
+ struct ev_cleanup cleanup;
+#endif
+#if EV_EMBED_ENABLE
+ struct ev_embed embed;
+#endif
+#if EV_ASYNC_ENABLE
+ struct ev_async async;
+#endif
+};
+
+/* flag bits for ev_default_loop and ev_loop_new */
+enum {
+ /* the default */
+ EVFLAG_AUTO = 0x00000000U, /* not quite a mask */
+ /* flag bits */
+ EVFLAG_NOENV = 0x01000000U, /* do NOT consult environment */
+ EVFLAG_FORKCHECK = 0x02000000U, /* check for a fork in each iteration */
+ /* debugging/feature disable */
+ EVFLAG_NOINOTIFY = 0x00100000U, /* do not attempt to use inotify */
+#if EV_COMPAT3
+ EVFLAG_NOSIGFD = 0, /* compatibility to pre-3.9 */
+#endif
+ EVFLAG_SIGNALFD = 0x00200000U, /* attempt to use signalfd */
+ EVFLAG_NOSIGMASK = 0x00400000U, /* avoid modifying the signal mask */
+ EVFLAG_NOTIMERFD = 0x00800000U /* avoid creating a timerfd */
+};
+
+/* method bits to be ored together */
+enum {
+ EVBACKEND_SELECT = 0x00000001U, /* available just about anywhere */
+ EVBACKEND_POLL = 0x00000002U, /* !win, !aix, broken on osx */
+ EVBACKEND_EPOLL = 0x00000004U, /* linux */
+ EVBACKEND_KQUEUE = 0x00000008U, /* bsd, broken on osx */
+ EVBACKEND_DEVPOLL = 0x00000010U, /* solaris 8 */ /* NYI */
+ EVBACKEND_PORT = 0x00000020U, /* solaris 10 */
+ EVBACKEND_LINUXAIO = 0x00000040U, /* linux AIO, 4.19+ */
+ EVBACKEND_IOURING = 0x00000080U, /* linux io_uring, 5.1+ */
+ EVBACKEND_ALL = 0x000000FFU, /* all known backends */
+ EVBACKEND_MASK = 0x0000FFFFU /* all future backends */
+};
+
+#if EV_PROTOTYPES
+EV_API_DECL int ev_version_major (void) EV_NOEXCEPT;
+EV_API_DECL int ev_version_minor (void) EV_NOEXCEPT;
+
+EV_API_DECL unsigned int ev_supported_backends (void) EV_NOEXCEPT;
+EV_API_DECL unsigned int ev_recommended_backends (void) EV_NOEXCEPT;
+EV_API_DECL unsigned int ev_embeddable_backends (void) EV_NOEXCEPT;
+
+EV_API_DECL ev_tstamp ev_time (void) EV_NOEXCEPT;
+EV_API_DECL void ev_sleep (ev_tstamp delay) EV_NOEXCEPT; /* sleep for a while */
+
+/* Sets the allocation function to use, works like realloc.
+ * It is used to allocate and free memory.
+ * If it returns zero when memory needs to be allocated, the library might abort
+ * or take some potentially destructive action.
+ * The default is your system realloc function.
+ */
+EV_API_DECL void ev_set_allocator (void *(*cb)(void *ptr, long size) EV_NOEXCEPT) EV_NOEXCEPT;
+
+/* set the callback function to call on a
+ * retryable syscall error
+ * (such as failed select, poll, epoll_wait)
+ */
+EV_API_DECL void ev_set_syserr_cb (void (*cb)(const char *msg) EV_NOEXCEPT) EV_NOEXCEPT;
+
+#if EV_MULTIPLICITY
+
+/* the default loop is the only one that handles signals and child watchers */
+/* you can call this as often as you like */
+EV_API_DECL struct ev_loop *ev_default_loop (unsigned int flags EV_CPP (= 0)) EV_NOEXCEPT;
+
+#ifdef EV_API_STATIC
+EV_API_DECL struct ev_loop *ev_default_loop_ptr;
+#endif
+
+EV_INLINE struct ev_loop *
+ev_default_loop_uc_ (void) EV_NOEXCEPT
+{
+ extern struct ev_loop *ev_default_loop_ptr;
+
+ return ev_default_loop_ptr;
+}
+
+EV_INLINE int
+ev_is_default_loop (EV_P) EV_NOEXCEPT
+{
+ return EV_A == EV_DEFAULT_UC;
+}
+
+/* create and destroy alternative loops that don't handle signals */
+EV_API_DECL struct ev_loop *ev_loop_new (unsigned int flags EV_CPP (= 0)) EV_NOEXCEPT;
+
+EV_API_DECL ev_tstamp ev_now (EV_P) EV_NOEXCEPT; /* time w.r.t. timers and the eventloop, updated after each poll */
+
+#else
+
+EV_API_DECL int ev_default_loop (unsigned int flags EV_CPP (= 0)) EV_NOEXCEPT; /* returns true when successful */
+
+EV_API_DECL ev_tstamp ev_rt_now;
+
+EV_INLINE ev_tstamp
+ev_now (void) EV_NOEXCEPT
+{
+ return ev_rt_now;
+}
+
+/* looks weird, but ev_is_default_loop (EV_A) still works if this exists */
+EV_INLINE int
+ev_is_default_loop (void) EV_NOEXCEPT
+{
+ return 1;
+}
+
+#endif /* multiplicity */
+
+/* destroy event loops, also works for the default loop */
+EV_API_DECL void ev_loop_destroy (EV_P);
+
+/* this needs to be called after fork, to duplicate the loop */
+/* when you want to re-use it in the child */
+/* you can call it in either the parent or the child */
+/* you can actually call it at any time, anywhere :) */
+EV_API_DECL void ev_loop_fork (EV_P) EV_NOEXCEPT;
+
+EV_API_DECL unsigned int ev_backend (EV_P) EV_NOEXCEPT; /* backend in use by loop */
+
+EV_API_DECL void ev_now_update (EV_P) EV_NOEXCEPT; /* update event loop time */
+
+#if EV_WALK_ENABLE
+/* walk (almost) all watchers in the loop of a given type, invoking the */
+/* callback on every such watcher. The callback might stop the watcher, */
+/* but do nothing else with the loop */
+EV_API_DECL void ev_walk (EV_P_ int types, void (*cb)(EV_P_ int type, void *w)) EV_NOEXCEPT;
+#endif
+
+#endif /* prototypes */
+
+/* ev_run flags values */
+enum {
+ EVRUN_NOWAIT = 1, /* do not block/wait */
+ EVRUN_ONCE = 2 /* block *once* only */
+};
+
+/* ev_break how values */
+enum {
+ EVBREAK_CANCEL = 0, /* undo unloop */
+ EVBREAK_ONE = 1, /* unloop once */
+ EVBREAK_ALL = 2 /* unloop all loops */
+};
+
+#if EV_PROTOTYPES
+EV_API_DECL int ev_run (EV_P_ int flags EV_CPP (= 0));
+EV_API_DECL void ev_break (EV_P_ int how EV_CPP (= EVBREAK_ONE)) EV_NOEXCEPT; /* break out of the loop */
+
+/*
+ * ref/unref can be used to add or remove a refcount on the mainloop. every watcher
+ * keeps one reference. if you have a long-running watcher you never unregister that
+ * should not keep ev_loop from running, unref() after starting, and ref() before stopping.
+ */
+EV_API_DECL void ev_ref (EV_P) EV_NOEXCEPT;
+EV_API_DECL void ev_unref (EV_P) EV_NOEXCEPT;
+
+/*
+ * convenience function, wait for a single event, without registering an event watcher
+ * if timeout is < 0, do wait indefinitely
+ */
+EV_API_DECL void ev_once (EV_P_ int fd, int events, ev_tstamp timeout, void (*cb)(int revents, void *arg), void *arg) EV_NOEXCEPT;
+
+EV_API_DECL void ev_invoke_pending (EV_P); /* invoke all pending watchers */
+
+# if EV_FEATURE_API
+EV_API_DECL unsigned int ev_iteration (EV_P) EV_NOEXCEPT; /* number of loop iterations */
+EV_API_DECL unsigned int ev_depth (EV_P) EV_NOEXCEPT; /* #ev_loop enters - #ev_loop leaves */
+EV_API_DECL void ev_verify (EV_P) EV_NOEXCEPT; /* abort if loop data corrupted */
+
+EV_API_DECL void ev_set_io_collect_interval (EV_P_ ev_tstamp interval) EV_NOEXCEPT; /* sleep at least this time, default 0 */
+EV_API_DECL void ev_set_timeout_collect_interval (EV_P_ ev_tstamp interval) EV_NOEXCEPT; /* sleep at least this time, default 0 */
+
+/* advanced stuff for threading etc. support, see docs */
+EV_API_DECL void ev_set_userdata (EV_P_ void *data) EV_NOEXCEPT;
+EV_API_DECL void *ev_userdata (EV_P) EV_NOEXCEPT;
+typedef void (*ev_loop_callback)(EV_P);
+EV_API_DECL void ev_set_invoke_pending_cb (EV_P_ ev_loop_callback invoke_pending_cb) EV_NOEXCEPT;
+/* C++ doesn't allow the use of the ev_loop_callback typedef here, so we need to spell it out */
+EV_API_DECL void ev_set_loop_release_cb (EV_P_ void (*release)(EV_P) EV_NOEXCEPT, void (*acquire)(EV_P) EV_NOEXCEPT) EV_NOEXCEPT;
+
+EV_API_DECL unsigned int ev_pending_count (EV_P) EV_NOEXCEPT; /* number of pending events, if any */
+
+/*
+ * stop/start the timer handling.
+ */
+EV_API_DECL void ev_suspend (EV_P) EV_NOEXCEPT;
+EV_API_DECL void ev_resume (EV_P) EV_NOEXCEPT;
+#endif
+
+#endif
+
+/* these may evaluate ev multiple times, and the other arguments at most once */
+/* either use ev_init + ev_TYPE_set, or the ev_TYPE_init macro, below, to first initialise a watcher */
+#define ev_init(ev,cb_) do { \
+ ((ev_watcher *)(void *)(ev))->active = \
+ ((ev_watcher *)(void *)(ev))->pending = 0; \
+ ev_set_priority ((ev), 0); \
+ ev_set_cb ((ev), cb_); \
+} while (0)
+
+#define ev_io_modify(ev,events_) do { (ev)->events = (ev)->events & EV__IOFDSET | (events_); } while (0)
+#define ev_io_set(ev,fd_,events_) do { (ev)->fd = (fd_); (ev)->events = (events_) | EV__IOFDSET; } while (0)
+#define ev_timer_set(ev,after_,repeat_) do { ((ev_watcher_time *)(ev))->at = (after_); (ev)->repeat = (repeat_); } while (0)
+#define ev_periodic_set(ev,ofs_,ival_,rcb_) do { (ev)->offset = (ofs_); (ev)->interval = (ival_); (ev)->reschedule_cb = (rcb_); } while (0)
+#define ev_signal_set(ev,signum_) do { (ev)->signum = (signum_); } while (0)
+#define ev_child_set(ev,pid_,trace_) do { (ev)->pid = (pid_); (ev)->flags = !!(trace_); } while (0)
+#define ev_stat_set(ev,path_,interval_) do { (ev)->path = (path_); (ev)->interval = (interval_); (ev)->wd = -2; } while (0)
+#define ev_idle_set(ev) /* nop, yes, this is a serious in-joke */
+#define ev_prepare_set(ev) /* nop, yes, this is a serious in-joke */
+#define ev_check_set(ev) /* nop, yes, this is a serious in-joke */
+#define ev_embed_set(ev,other_) do { (ev)->other = (other_); } while (0)
+#define ev_fork_set(ev) /* nop, yes, this is a serious in-joke */
+#define ev_cleanup_set(ev) /* nop, yes, this is a serious in-joke */
+#define ev_async_set(ev) /* nop, yes, this is a serious in-joke */
+
+#define ev_io_init(ev,cb,fd,events) do { ev_init ((ev), (cb)); ev_io_set ((ev),(fd),(events)); } while (0)
+#define ev_timer_init(ev,cb,after,repeat) do { ev_init ((ev), (cb)); ev_timer_set ((ev),(after),(repeat)); } while (0)
+#define ev_periodic_init(ev,cb,ofs,ival,rcb) do { ev_init ((ev), (cb)); ev_periodic_set ((ev),(ofs),(ival),(rcb)); } while (0)
+#define ev_signal_init(ev,cb,signum) do { ev_init ((ev), (cb)); ev_signal_set ((ev), (signum)); } while (0)
+#define ev_child_init(ev,cb,pid,trace) do { ev_init ((ev), (cb)); ev_child_set ((ev),(pid),(trace)); } while (0)
+#define ev_stat_init(ev,cb,path,interval) do { ev_init ((ev), (cb)); ev_stat_set ((ev),(path),(interval)); } while (0)
+#define ev_idle_init(ev,cb) do { ev_init ((ev), (cb)); ev_idle_set ((ev)); } while (0)
+#define ev_prepare_init(ev,cb) do { ev_init ((ev), (cb)); ev_prepare_set ((ev)); } while (0)
+#define ev_check_init(ev,cb) do { ev_init ((ev), (cb)); ev_check_set ((ev)); } while (0)
+#define ev_embed_init(ev,cb,other) do { ev_init ((ev), (cb)); ev_embed_set ((ev),(other)); } while (0)
+#define ev_fork_init(ev,cb) do { ev_init ((ev), (cb)); ev_fork_set ((ev)); } while (0)
+#define ev_cleanup_init(ev,cb) do { ev_init ((ev), (cb)); ev_cleanup_set ((ev)); } while (0)
+#define ev_async_init(ev,cb) do { ev_init ((ev), (cb)); ev_async_set ((ev)); } while (0)
+
+#define ev_is_pending(ev) (0 + ((ev_watcher *)(void *)(ev))->pending) /* ro, true when watcher is waiting for callback invocation */
+#define ev_is_active(ev) (0 + ((ev_watcher *)(void *)(ev))->active) /* ro, true when the watcher has been started */
+
+#define ev_cb_(ev) (ev)->cb /* rw */
+#define ev_cb(ev) (memmove (&ev_cb_ (ev), &((ev_watcher *)(ev))->cb, sizeof (ev_cb_ (ev))), (ev)->cb)
+
+#if EV_MINPRI == EV_MAXPRI
+# define ev_priority(ev) ((ev), EV_MINPRI)
+# define ev_set_priority(ev,pri) ((ev), (pri))
+#else
+# define ev_priority(ev) (+(((ev_watcher *)(void *)(ev))->priority))
+# define ev_set_priority(ev,pri) ( (ev_watcher *)(void *)(ev))->priority = (pri)
+#endif
+
+#define ev_periodic_at(ev) (+((ev_watcher_time *)(ev))->at)
+
+#ifndef ev_set_cb
+/* memmove is used here to avoid strict aliasing violations, and hopefully is optimized out by any reasonable compiler */
+# define ev_set_cb(ev,cb_) (ev_cb_ (ev) = (cb_), memmove (&((ev_watcher *)(ev))->cb, &ev_cb_ (ev), sizeof (ev_cb_ (ev))))
+#endif
+
+/* stopping (enabling, adding) a watcher does nothing if it is already running */
+/* stopping (disabling, deleting) a watcher does nothing unless it's already running */
+#if EV_PROTOTYPES
+
+/* feeds an event into a watcher as if the event actually occurred */
+/* accepts any ev_watcher type */
+EV_API_DECL void ev_feed_event (EV_P_ void *w, int revents) EV_NOEXCEPT;
+EV_API_DECL void ev_feed_fd_event (EV_P_ int fd, int revents) EV_NOEXCEPT;
+#if EV_SIGNAL_ENABLE
+EV_API_DECL void ev_feed_signal (int signum) EV_NOEXCEPT;
+EV_API_DECL void ev_feed_signal_event (EV_P_ int signum) EV_NOEXCEPT;
+#endif
+EV_API_DECL void ev_invoke (EV_P_ void *w, int revents);
+EV_API_DECL int ev_clear_pending (EV_P_ void *w) EV_NOEXCEPT;
+
+EV_API_DECL void ev_io_start (EV_P_ ev_io *w) EV_NOEXCEPT;
+EV_API_DECL void ev_io_stop (EV_P_ ev_io *w) EV_NOEXCEPT;
+
+EV_API_DECL void ev_timer_start (EV_P_ ev_timer *w) EV_NOEXCEPT;
+EV_API_DECL void ev_timer_stop (EV_P_ ev_timer *w) EV_NOEXCEPT;
+/* stops if active and no repeat, restarts if active and repeating, starts if inactive and repeating */
+EV_API_DECL void ev_timer_again (EV_P_ ev_timer *w) EV_NOEXCEPT;
+/* return remaining time */
+EV_API_DECL ev_tstamp ev_timer_remaining (EV_P_ ev_timer *w) EV_NOEXCEPT;
+
+#if EV_PERIODIC_ENABLE
+EV_API_DECL void ev_periodic_start (EV_P_ ev_periodic *w) EV_NOEXCEPT;
+EV_API_DECL void ev_periodic_stop (EV_P_ ev_periodic *w) EV_NOEXCEPT;
+EV_API_DECL void ev_periodic_again (EV_P_ ev_periodic *w) EV_NOEXCEPT;
+#endif
+
+/* only supported in the default loop */
+#if EV_SIGNAL_ENABLE
+EV_API_DECL void ev_signal_start (EV_P_ ev_signal *w) EV_NOEXCEPT;
+EV_API_DECL void ev_signal_stop (EV_P_ ev_signal *w) EV_NOEXCEPT;
+#endif
+
+/* only supported in the default loop */
+# if EV_CHILD_ENABLE
+EV_API_DECL void ev_child_start (EV_P_ ev_child *w) EV_NOEXCEPT;
+EV_API_DECL void ev_child_stop (EV_P_ ev_child *w) EV_NOEXCEPT;
+# endif
+
+# if EV_STAT_ENABLE
+EV_API_DECL void ev_stat_start (EV_P_ ev_stat *w) EV_NOEXCEPT;
+EV_API_DECL void ev_stat_stop (EV_P_ ev_stat *w) EV_NOEXCEPT;
+EV_API_DECL void ev_stat_stat (EV_P_ ev_stat *w) EV_NOEXCEPT;
+# endif
+
+# if EV_IDLE_ENABLE
+EV_API_DECL void ev_idle_start (EV_P_ ev_idle *w) EV_NOEXCEPT;
+EV_API_DECL void ev_idle_stop (EV_P_ ev_idle *w) EV_NOEXCEPT;
+# endif
+
+#if EV_PREPARE_ENABLE
+EV_API_DECL void ev_prepare_start (EV_P_ ev_prepare *w) EV_NOEXCEPT;
+EV_API_DECL void ev_prepare_stop (EV_P_ ev_prepare *w) EV_NOEXCEPT;
+#endif
+
+#if EV_CHECK_ENABLE
+EV_API_DECL void ev_check_start (EV_P_ ev_check *w) EV_NOEXCEPT;
+EV_API_DECL void ev_check_stop (EV_P_ ev_check *w) EV_NOEXCEPT;
+#endif
+
+# if EV_FORK_ENABLE
+EV_API_DECL void ev_fork_start (EV_P_ ev_fork *w) EV_NOEXCEPT;
+EV_API_DECL void ev_fork_stop (EV_P_ ev_fork *w) EV_NOEXCEPT;
+# endif
+
+# if EV_CLEANUP_ENABLE
+EV_API_DECL void ev_cleanup_start (EV_P_ ev_cleanup *w) EV_NOEXCEPT;
+EV_API_DECL void ev_cleanup_stop (EV_P_ ev_cleanup *w) EV_NOEXCEPT;
+# endif
+
+# if EV_EMBED_ENABLE
+/* only supported when loop to be embedded is in fact embeddable */
+EV_API_DECL void ev_embed_start (EV_P_ ev_embed *w) EV_NOEXCEPT;
+EV_API_DECL void ev_embed_stop (EV_P_ ev_embed *w) EV_NOEXCEPT;
+EV_API_DECL void ev_embed_sweep (EV_P_ ev_embed *w) EV_NOEXCEPT;
+# endif
+
+# if EV_ASYNC_ENABLE
+EV_API_DECL void ev_async_start (EV_P_ ev_async *w) EV_NOEXCEPT;
+EV_API_DECL void ev_async_stop (EV_P_ ev_async *w) EV_NOEXCEPT;
+EV_API_DECL void ev_async_send (EV_P_ ev_async *w) EV_NOEXCEPT;
+# endif
+
+#if EV_COMPAT3
+ #define EVLOOP_NONBLOCK EVRUN_NOWAIT
+ #define EVLOOP_ONESHOT EVRUN_ONCE
+ #define EVUNLOOP_CANCEL EVBREAK_CANCEL
+ #define EVUNLOOP_ONE EVBREAK_ONE
+ #define EVUNLOOP_ALL EVBREAK_ALL
+ #if EV_PROTOTYPES
+ EV_INLINE void ev_loop (EV_P_ int flags) { ev_run (EV_A_ flags); }
+ EV_INLINE void ev_unloop (EV_P_ int how ) { ev_break (EV_A_ how ); }
+ EV_INLINE void ev_default_destroy (void) { ev_loop_destroy (EV_DEFAULT); }
+ EV_INLINE void ev_default_fork (void) { ev_loop_fork (EV_DEFAULT); }
+ #if EV_FEATURE_API
+ EV_INLINE unsigned int ev_loop_count (EV_P) { return ev_iteration (EV_A); }
+ EV_INLINE unsigned int ev_loop_depth (EV_P) { return ev_depth (EV_A); }
+ EV_INLINE void ev_loop_verify (EV_P) { ev_verify (EV_A); }
+ #endif
+ #endif
+#else
+ typedef struct ev_loop ev_loop;
+#endif
+
+#endif
+
+EV_CPP(})
+
+#endif
+
diff --git a/3rdparty/libev/ev.pod b/3rdparty/libev/ev.pod
new file mode 100644
index 0000000..e4eeb50
--- /dev/null
+++ b/3rdparty/libev/ev.pod
@@ -0,0 +1,5741 @@
+=encoding utf-8
+
+=head1 NAME
+
+libev - a high performance full-featured event loop written in C
+
+=head1 SYNOPSIS
+
+ #include <ev.h>
+
+=head2 EXAMPLE PROGRAM
+
+ // a single header file is required
+ #include <ev.h>
+
+ #include <stdio.h> // for puts
+
+ // every watcher type has its own typedef'd struct
+ // with the name ev_TYPE
+ ev_io stdin_watcher;
+ ev_timer timeout_watcher;
+
+ // all watcher callbacks have a similar signature
+ // this callback is called when data is readable on stdin
+ static void
+ stdin_cb (EV_P_ ev_io *w, int revents)
+ {
+ puts ("stdin ready");
+ // for one-shot events, one must manually stop the watcher
+ // with its corresponding stop function.
+ ev_io_stop (EV_A_ w);
+
+ // this causes all nested ev_run's to stop iterating
+ ev_break (EV_A_ EVBREAK_ALL);
+ }
+
+ // another callback, this time for a time-out
+ static void
+ timeout_cb (EV_P_ ev_timer *w, int revents)
+ {
+ puts ("timeout");
+ // this causes the innermost ev_run to stop iterating
+ ev_break (EV_A_ EVBREAK_ONE);
+ }
+
+ int
+ main (void)
+ {
+ // use the default event loop unless you have special needs
+ struct ev_loop *loop = EV_DEFAULT;
+
+ // initialise an io watcher, then start it
+ // this one will watch for stdin to become readable
+ ev_io_init (&stdin_watcher, stdin_cb, /*STDIN_FILENO*/ 0, EV_READ);
+ ev_io_start (loop, &stdin_watcher);
+
+ // initialise a timer watcher, then start it
+ // simple non-repeating 5.5 second timeout
+ ev_timer_init (&timeout_watcher, timeout_cb, 5.5, 0.);
+ ev_timer_start (loop, &timeout_watcher);
+
+ // now wait for events to arrive
+ ev_run (loop, 0);
+
+ // break was called, so exit
+ return 0;
+ }
+
+=head1 ABOUT THIS DOCUMENT
+
+This document documents the libev software package.
+
+The newest version of this document is also available as an html-formatted
+web page you might find easier to navigate when reading it for the first
+time: L<http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod>.
+
+While this document tries to be as complete as possible in documenting
+libev, its usage and the rationale behind its design, it is not a tutorial
+on event-based programming, nor will it introduce event-based programming
+with libev.
+
+Familiarity with event based programming techniques in general is assumed
+throughout this document.
+
+=head1 WHAT TO READ WHEN IN A HURRY
+
+This manual tries to be very detailed, but unfortunately, this also makes
+it very long. If you just want to know the basics of libev, I suggest
+reading L</ANATOMY OF A WATCHER>, then the L</EXAMPLE PROGRAM> above and
+look up the missing functions in L</GLOBAL FUNCTIONS> and the C<ev_io> and
+C<ev_timer> sections in L</WATCHER TYPES>.
+
+=head1 ABOUT LIBEV
+
+Libev is an event loop: you register interest in certain events (such as a
+file descriptor being readable or a timeout occurring), and it will manage
+these event sources and provide your program with events.
+
+To do this, it must take more or less complete control over your process
+(or thread) by executing the I<event loop> handler, and will then
+communicate events via a callback mechanism.
+
+You register interest in certain events by registering so-called I<event
+watchers>, which are relatively small C structures you initialise with the
+details of the event, and then hand it over to libev by I<starting> the
+watcher.
+
+=head2 FEATURES
+
+Libev supports C<select>, C<poll>, the Linux-specific aio and C<epoll>
+interfaces, the BSD-specific C<kqueue> and the Solaris-specific event port
+mechanisms for file descriptor events (C<ev_io>), the Linux C<inotify>
+interface (for C<ev_stat>), Linux eventfd/signalfd (for faster and cleaner
+inter-thread wakeup (C<ev_async>)/signal handling (C<ev_signal>)) relative
+timers (C<ev_timer>), absolute timers with customised rescheduling
+(C<ev_periodic>), synchronous signals (C<ev_signal>), process status
+change events (C<ev_child>), and event watchers dealing with the event
+loop mechanism itself (C<ev_idle>, C<ev_embed>, C<ev_prepare> and
+C<ev_check> watchers) as well as file watchers (C<ev_stat>) and even
+limited support for fork events (C<ev_fork>).
+
+It also is quite fast (see this
+L<benchmark|http://libev.schmorp.de/bench.html> comparing it to libevent
+for example).
+
+=head2 CONVENTIONS
+
+Libev is very configurable. In this manual the default (and most common)
+configuration will be described, which supports multiple event loops. For
+more info about various configuration options please have a look at
+B<EMBED> section in this manual. If libev was configured without support
+for multiple event loops, then all functions taking an initial argument of
+name C<loop> (which is always of type C<struct ev_loop *>) will not have
+this argument.
+
+=head2 TIME REPRESENTATION
+
+Libev represents time as a single floating point number, representing
+the (fractional) number of seconds since the (POSIX) epoch (in practice
+somewhere near the beginning of 1970, details are complicated, don't
+ask). This type is called C<ev_tstamp>, which is what you should use
+too. It usually aliases to the C<double> type in C. When you need to do
+any calculations on it, you should treat it as some floating point value.
+
+Unlike the name component C<stamp> might indicate, it is also used for
+time differences (e.g. delays) throughout libev.
+
+=head1 ERROR HANDLING
+
+Libev knows three classes of errors: operating system errors, usage errors
+and internal errors (bugs).
+
+When libev catches an operating system error it cannot handle (for example
+a system call indicating a condition libev cannot fix), it calls the callback
+set via C<ev_set_syserr_cb>, which is supposed to fix the problem or
+abort. The default is to print a diagnostic message and to call C<abort
+()>.
+
+When libev detects a usage error such as a negative timer interval, then
+it will print a diagnostic message and abort (via the C<assert> mechanism,
+so C<NDEBUG> will disable this checking): these are programming errors in
+the libev caller and need to be fixed there.
+
+Via the C<EV_FREQUENT> macro you can compile in and/or enable extensive
+consistency checking code inside libev that can be used to check for
+internal inconsistencies, suually caused by application bugs.
+
+Libev also has a few internal error-checking C<assert>ions. These do not
+trigger under normal circumstances, as they indicate either a bug in libev
+or worse.
+
+
+=head1 GLOBAL FUNCTIONS
+
+These functions can be called anytime, even before initialising the
+library in any way.
+
+=over 4
+
+=item ev_tstamp ev_time ()
+
+Returns the current time as libev would use it. Please note that the
+C<ev_now> function is usually faster and also often returns the timestamp
+you actually want to know. Also interesting is the combination of
+C<ev_now_update> and C<ev_now>.
+
+=item ev_sleep (ev_tstamp interval)
+
+Sleep for the given interval: The current thread will be blocked
+until either it is interrupted or the given time interval has
+passed (approximately - it might return a bit earlier even if not
+interrupted). Returns immediately if C<< interval <= 0 >>.
+
+Basically this is a sub-second-resolution C<sleep ()>.
+
+The range of the C<interval> is limited - libev only guarantees to work
+with sleep times of up to one day (C<< interval <= 86400 >>).
+
+=item int ev_version_major ()
+
+=item int ev_version_minor ()
+
+You can find out the major and minor ABI version numbers of the library
+you linked against by calling the functions C<ev_version_major> and
+C<ev_version_minor>. If you want, you can compare against the global
+symbols C<EV_VERSION_MAJOR> and C<EV_VERSION_MINOR>, which specify the
+version of the library your program was compiled against.
+
+These version numbers refer to the ABI version of the library, not the
+release version.
+
+Usually, it's a good idea to terminate if the major versions mismatch,
+as this indicates an incompatible change. Minor versions are usually
+compatible to older versions, so a larger minor version alone is usually
+not a problem.
+
+Example: Make sure we haven't accidentally been linked against the wrong
+version (note, however, that this will not detect other ABI mismatches,
+such as LFS or reentrancy).
+
+ assert (("libev version mismatch",
+ ev_version_major () == EV_VERSION_MAJOR
+ && ev_version_minor () >= EV_VERSION_MINOR));
+
+=item unsigned int ev_supported_backends ()
+
+Return the set of all backends (i.e. their corresponding C<EV_BACKEND_*>
+value) compiled into this binary of libev (independent of their
+availability on the system you are running on). See C<ev_default_loop> for
+a description of the set values.
+
+Example: make sure we have the epoll method, because yeah this is cool and
+a must have and can we have a torrent of it please!!!11
+
+ assert (("sorry, no epoll, no sex",
+ ev_supported_backends () & EVBACKEND_EPOLL));
+
+=item unsigned int ev_recommended_backends ()
+
+Return the set of all backends compiled into this binary of libev and
+also recommended for this platform, meaning it will work for most file
+descriptor types. This set is often smaller than the one returned by
+C<ev_supported_backends>, as for example kqueue is broken on most BSDs
+and will not be auto-detected unless you explicitly request it (assuming
+you know what you are doing). This is the set of backends that libev will
+probe for if you specify no backends explicitly.
+
+=item unsigned int ev_embeddable_backends ()
+
+Returns the set of backends that are embeddable in other event loops. This
+value is platform-specific but can include backends not available on the
+current system. To find which embeddable backends might be supported on
+the current system, you would need to look at C<ev_embeddable_backends ()
+& ev_supported_backends ()>, likewise for recommended ones.
+
+See the description of C<ev_embed> watchers for more info.
+
+=item ev_set_allocator (void *(*cb)(void *ptr, long size) throw ())
+
+Sets the allocation function to use (the prototype is similar - the
+semantics are identical to the C<realloc> C89/SuS/POSIX function). It is
+used to allocate and free memory (no surprises here). If it returns zero
+when memory needs to be allocated (C<size != 0>), the library might abort
+or take some potentially destructive action.
+
+Since some systems (at least OpenBSD and Darwin) fail to implement
+correct C<realloc> semantics, libev will use a wrapper around the system
+C<realloc> and C<free> functions by default.
+
+You could override this function in high-availability programs to, say,
+free some memory if it cannot allocate memory, to use a special allocator,
+or even to sleep a while and retry until some memory is available.
+
+Example: The following is the C<realloc> function that libev itself uses
+which should work with C<realloc> and C<free> functions of all kinds and
+is probably a good basis for your own implementation.
+
+ static void *
+ ev_realloc_emul (void *ptr, long size) EV_NOEXCEPT
+ {
+ if (size)
+ return realloc (ptr, size);
+
+ free (ptr);
+ return 0;
+ }
+
+Example: Replace the libev allocator with one that waits a bit and then
+retries.
+
+ static void *
+ persistent_realloc (void *ptr, size_t size)
+ {
+ if (!size)
+ {
+ free (ptr);
+ return 0;
+ }
+
+ for (;;)
+ {
+ void *newptr = realloc (ptr, size);
+
+ if (newptr)
+ return newptr;
+
+ sleep (60);
+ }
+ }
+
+ ...
+ ev_set_allocator (persistent_realloc);
+
+=item ev_set_syserr_cb (void (*cb)(const char *msg) throw ())
+
+Set the callback function to call on a retryable system call error (such
+as failed select, poll, epoll_wait). The message is a printable string
+indicating the system call or subsystem causing the problem. If this
+callback is set, then libev will expect it to remedy the situation, no
+matter what, when it returns. That is, libev will generally retry the
+requested operation, or, if the condition doesn't go away, do bad stuff
+(such as abort).
+
+Example: This is basically the same thing that libev does internally, too.
+
+ static void
+ fatal_error (const char *msg)
+ {
+ perror (msg);
+ abort ();
+ }
+
+ ...
+ ev_set_syserr_cb (fatal_error);
+
+=item ev_feed_signal (int signum)
+
+This function can be used to "simulate" a signal receive. It is completely
+safe to call this function at any time, from any context, including signal
+handlers or random threads.
+
+Its main use is to customise signal handling in your process, especially
+in the presence of threads. For example, you could block signals
+by default in all threads (and specifying C<EVFLAG_NOSIGMASK> when
+creating any loops), and in one thread, use C<sigwait> or any other
+mechanism to wait for signals, then "deliver" them to libev by calling
+C<ev_feed_signal>.
+
+=back
+
+=head1 FUNCTIONS CONTROLLING EVENT LOOPS
+
+An event loop is described by a C<struct ev_loop *> (the C<struct> is
+I<not> optional in this case unless libev 3 compatibility is disabled, as
+libev 3 had an C<ev_loop> function colliding with the struct name).
+
+The library knows two types of such loops, the I<default> loop, which
+supports child process events, and dynamically created event loops which
+do not.
+
+=over 4
+
+=item struct ev_loop *ev_default_loop (unsigned int flags)
+
+This returns the "default" event loop object, which is what you should
+normally use when you just need "the event loop". Event loop objects and
+the C<flags> parameter are described in more detail in the entry for
+C<ev_loop_new>.
+
+If the default loop is already initialised then this function simply
+returns it (and ignores the flags. If that is troubling you, check
+C<ev_backend ()> afterwards). Otherwise it will create it with the given
+flags, which should almost always be C<0>, unless the caller is also the
+one calling C<ev_run> or otherwise qualifies as "the main program".
+
+If you don't know what event loop to use, use the one returned from this
+function (or via the C<EV_DEFAULT> macro).
+
+Note that this function is I<not> thread-safe, so if you want to use it
+from multiple threads, you have to employ some kind of mutex (note also
+that this case is unlikely, as loops cannot be shared easily between
+threads anyway).
+
+The default loop is the only loop that can handle C<ev_child> watchers,
+and to do this, it always registers a handler for C<SIGCHLD>. If this is
+a problem for your application you can either create a dynamic loop with
+C<ev_loop_new> which doesn't do that, or you can simply overwrite the
+C<SIGCHLD> signal handler I<after> calling C<ev_default_init>.
+
+Example: This is the most typical usage.
+
+ if (!ev_default_loop (0))
+ fatal ("could not initialise libev, bad $LIBEV_FLAGS in environment?");
+
+Example: Restrict libev to the select and poll backends, and do not allow
+environment settings to be taken into account:
+
+ ev_default_loop (EVBACKEND_POLL | EVBACKEND_SELECT | EVFLAG_NOENV);
+
+=item struct ev_loop *ev_loop_new (unsigned int flags)
+
+This will create and initialise a new event loop object. If the loop
+could not be initialised, returns false.
+
+This function is thread-safe, and one common way to use libev with
+threads is indeed to create one loop per thread, and using the default
+loop in the "main" or "initial" thread.
+
+The flags argument can be used to specify special behaviour or specific
+backends to use, and is usually specified as C<0> (or C<EVFLAG_AUTO>).
+
+The following flags are supported:
+
+=over 4
+
+=item C<EVFLAG_AUTO>
+
+The default flags value. Use this if you have no clue (it's the right
+thing, believe me).
+
+=item C<EVFLAG_NOENV>
+
+If this flag bit is or'ed into the flag value (or the program runs setuid
+or setgid) then libev will I<not> look at the environment variable
+C<LIBEV_FLAGS>. Otherwise (the default), this environment variable will
+override the flags completely if it is found in the environment. This is
+useful to try out specific backends to test their performance, to work
+around bugs, or to make libev threadsafe (accessing environment variables
+cannot be done in a threadsafe way, but usually it works if no other
+thread modifies them).
+
+=item C<EVFLAG_FORKCHECK>
+
+Instead of calling C<ev_loop_fork> manually after a fork, you can also
+make libev check for a fork in each iteration by enabling this flag.
+
+This works by calling C<getpid ()> on every iteration of the loop,
+and thus this might slow down your event loop if you do a lot of loop
+iterations and little real work, but is usually not noticeable (on my
+GNU/Linux system for example, C<getpid> is actually a simple 5-insn
+sequence without a system call and thus I<very> fast, but my GNU/Linux
+system also has C<pthread_atfork> which is even faster). (Update: glibc
+versions 2.25 apparently removed the C<getpid> optimisation again).
+
+The big advantage of this flag is that you can forget about fork (and
+forget about forgetting to tell libev about forking, although you still
+have to ignore C<SIGPIPE>) when you use this flag.
+
+This flag setting cannot be overridden or specified in the C<LIBEV_FLAGS>
+environment variable.
+
+=item C<EVFLAG_NOINOTIFY>
+
+When this flag is specified, then libev will not attempt to use the
+I<inotify> API for its C<ev_stat> watchers. Apart from debugging and
+testing, this flag can be useful to conserve inotify file descriptors, as
+otherwise each loop using C<ev_stat> watchers consumes one inotify handle.
+
+=item C<EVFLAG_SIGNALFD>
+
+When this flag is specified, then libev will attempt to use the
+I<signalfd> API for its C<ev_signal> (and C<ev_child>) watchers. This API
+delivers signals synchronously, which makes it both faster and might make
+it possible to get the queued signal data. It can also simplify signal
+handling with threads, as long as you properly block signals in your
+threads that are not interested in handling them.
+
+Signalfd will not be used by default as this changes your signal mask, and
+there are a lot of shoddy libraries and programs (glib's threadpool for
+example) that can't properly initialise their signal masks.
+
+=item C<EVFLAG_NOSIGMASK>
+
+When this flag is specified, then libev will avoid to modify the signal
+mask. Specifically, this means you have to make sure signals are unblocked
+when you want to receive them.
+
+This behaviour is useful when you want to do your own signal handling, or
+want to handle signals only in specific threads and want to avoid libev
+unblocking the signals.
+
+It's also required by POSIX in a threaded program, as libev calls
+C<sigprocmask>, whose behaviour is officially unspecified.
+
+=item C<EVFLAG_NOTIMERFD>
+
+When this flag is specified, the libev will avoid using a C<timerfd> to
+detect time jumps. It will still be able to detect time jumps, but takes
+longer and has a lower accuracy in doing so, but saves a file descriptor
+per loop.
+
+The current implementation only tries to use a C<timerfd> when the first
+C<ev_periodic> watcher is started and falls back on other methods if it
+cannot be created, but this behaviour might change in the future.
+
+=item C<EVBACKEND_SELECT> (value 1, portable select backend)
+
+This is your standard select(2) backend. Not I<completely> standard, as
+libev tries to roll its own fd_set with no limits on the number of fds,
+but if that fails, expect a fairly low limit on the number of fds when
+using this backend. It doesn't scale too well (O(highest_fd)), but its
+usually the fastest backend for a low number of (low-numbered :) fds.
+
+To get good performance out of this backend you need a high amount of
+parallelism (most of the file descriptors should be busy). If you are
+writing a server, you should C<accept ()> in a loop to accept as many
+connections as possible during one iteration. You might also want to have
+a look at C<ev_set_io_collect_interval ()> to increase the amount of
+readiness notifications you get per iteration.
+
+This backend maps C<EV_READ> to the C<readfds> set and C<EV_WRITE> to the
+C<writefds> set (and to work around Microsoft Windows bugs, also onto the
+C<exceptfds> set on that platform).
+
+=item C<EVBACKEND_POLL> (value 2, poll backend, available everywhere except on windows)
+
+And this is your standard poll(2) backend. It's more complicated
+than select, but handles sparse fds better and has no artificial
+limit on the number of fds you can use (except it will slow down
+considerably with a lot of inactive fds). It scales similarly to select,
+i.e. O(total_fds). See the entry for C<EVBACKEND_SELECT>, above, for
+performance tips.
+
+This backend maps C<EV_READ> to C<POLLIN | POLLERR | POLLHUP>, and
+C<EV_WRITE> to C<POLLOUT | POLLERR | POLLHUP>.
+
+=item C<EVBACKEND_EPOLL> (value 4, Linux)
+
+Use the Linux-specific epoll(7) interface (for both pre- and post-2.6.9
+kernels).
+
+For few fds, this backend is a bit little slower than poll and select, but
+it scales phenomenally better. While poll and select usually scale like
+O(total_fds) where total_fds is the total number of fds (or the highest
+fd), epoll scales either O(1) or O(active_fds).
+
+The epoll mechanism deserves honorable mention as the most misdesigned
+of the more advanced event mechanisms: mere annoyances include silently
+dropping file descriptors, requiring a system call per change per file
+descriptor (and unnecessary guessing of parameters), problems with dup,
+returning before the timeout value, resulting in additional iterations
+(and only giving 5ms accuracy while select on the same platform gives
+0.1ms) and so on. The biggest issue is fork races, however - if a program
+forks then I<both> parent and child process have to recreate the epoll
+set, which can take considerable time (one syscall per file descriptor)
+and is of course hard to detect.
+
+Epoll is also notoriously buggy - embedding epoll fds I<should> work,
+but of course I<doesn't>, and epoll just loves to report events for
+totally I<different> file descriptors (even already closed ones, so
+one cannot even remove them from the set) than registered in the set
+(especially on SMP systems). Libev tries to counter these spurious
+notifications by employing an additional generation counter and comparing
+that against the events to filter out spurious ones, recreating the set
+when required. Epoll also erroneously rounds down timeouts, but gives you
+no way to know when and by how much, so sometimes you have to busy-wait
+because epoll returns immediately despite a nonzero timeout. And last
+not least, it also refuses to work with some file descriptors which work
+perfectly fine with C<select> (files, many character devices...).
+
+Epoll is truly the train wreck among event poll mechanisms, a frankenpoll,
+cobbled together in a hurry, no thought to design or interaction with
+others. Oh, the pain, will it ever stop...
+
+While stopping, setting and starting an I/O watcher in the same iteration
+will result in some caching, there is still a system call per such
+incident (because the same I<file descriptor> could point to a different
+I<file description> now), so its best to avoid that. Also, C<dup ()>'ed
+file descriptors might not work very well if you register events for both
+file descriptors.
+
+Best performance from this backend is achieved by not unregistering all
+watchers for a file descriptor until it has been closed, if possible,
+i.e. keep at least one watcher active per fd at all times. Stopping and
+starting a watcher (without re-setting it) also usually doesn't cause
+extra overhead. A fork can both result in spurious notifications as well
+as in libev having to destroy and recreate the epoll object, which can
+take considerable time and thus should be avoided.
+
+All this means that, in practice, C<EVBACKEND_SELECT> can be as fast or
+faster than epoll for maybe up to a hundred file descriptors, depending on
+the usage. So sad.
+
+While nominally embeddable in other event loops, this feature is broken in
+a lot of kernel revisions, but probably(!) works in current versions.
+
+This backend maps C<EV_READ> and C<EV_WRITE> in the same way as
+C<EVBACKEND_POLL>.
+
+=item C<EVBACKEND_LINUXAIO> (value 64, Linux)
+
+Use the Linux-specific Linux AIO (I<not> C<< aio(7) >> but C<<
+io_submit(2) >>) event interface available in post-4.18 kernels (but libev
+only tries to use it in 4.19+).
+
+This is another Linux train wreck of an event interface.
+
+If this backend works for you (as of this writing, it was very
+experimental), it is the best event interface available on Linux and might
+be well worth enabling it - if it isn't available in your kernel this will
+be detected and this backend will be skipped.
+
+This backend can batch oneshot requests and supports a user-space ring
+buffer to receive events. It also doesn't suffer from most of the design
+problems of epoll (such as not being able to remove event sources from
+the epoll set), and generally sounds too good to be true. Because, this
+being the Linux kernel, of course it suffers from a whole new set of
+limitations, forcing you to fall back to epoll, inheriting all its design
+issues.
+
+For one, it is not easily embeddable (but probably could be done using
+an event fd at some extra overhead). It also is subject to a system wide
+limit that can be configured in F</proc/sys/fs/aio-max-nr>. If no AIO
+requests are left, this backend will be skipped during initialisation, and
+will switch to epoll when the loop is active.
+
+Most problematic in practice, however, is that not all file descriptors
+work with it. For example, in Linux 5.1, TCP sockets, pipes, event fds,
+files, F</dev/null> and many others are supported, but ttys do not work
+properly (a known bug that the kernel developers don't care about, see
+L<https://lore.kernel.org/patchwork/patch/1047453/>), so this is not
+(yet?) a generic event polling interface.
+
+Overall, it seems the Linux developers just don't want it to have a
+generic event handling mechanism other than C<select> or C<poll>.
+
+To work around all these problem, the current version of libev uses its
+epoll backend as a fallback for file descriptor types that do not work. Or
+falls back completely to epoll if the kernel acts up.
+
+This backend maps C<EV_READ> and C<EV_WRITE> in the same way as
+C<EVBACKEND_POLL>.
+
+=item C<EVBACKEND_KQUEUE> (value 8, most BSD clones)
+
+Kqueue deserves special mention, as at the time this backend was
+implemented, it was broken on all BSDs except NetBSD (usually it doesn't
+work reliably with anything but sockets and pipes, except on Darwin,
+where of course it's completely useless). Unlike epoll, however, whose
+brokenness is by design, these kqueue bugs can be (and mostly have been)
+fixed without API changes to existing programs. For this reason it's not
+being "auto-detected" on all platforms unless you explicitly specify it
+in the flags (i.e. using C<EVBACKEND_KQUEUE>) or libev was compiled on a
+known-to-be-good (-enough) system like NetBSD.
+
+You still can embed kqueue into a normal poll or select backend and use it
+only for sockets (after having made sure that sockets work with kqueue on
+the target platform). See C<ev_embed> watchers for more info.
+
+It scales in the same way as the epoll backend, but the interface to the
+kernel is more efficient (which says nothing about its actual speed, of
+course). While stopping, setting and starting an I/O watcher does never
+cause an extra system call as with C<EVBACKEND_EPOLL>, it still adds up to
+two event changes per incident. Support for C<fork ()> is very bad (you
+might have to leak fds on fork, but it's more sane than epoll) and it
+drops fds silently in similarly hard-to-detect cases.
+
+This backend usually performs well under most conditions.
+
+While nominally embeddable in other event loops, this doesn't work
+everywhere, so you might need to test for this. And since it is broken
+almost everywhere, you should only use it when you have a lot of sockets
+(for which it usually works), by embedding it into another event loop
+(e.g. C<EVBACKEND_SELECT> or C<EVBACKEND_POLL> (but C<poll> is of course
+also broken on OS X)) and, did I mention it, using it only for sockets.
+
+This backend maps C<EV_READ> into an C<EVFILT_READ> kevent with
+C<NOTE_EOF>, and C<EV_WRITE> into an C<EVFILT_WRITE> kevent with
+C<NOTE_EOF>.
+
+=item C<EVBACKEND_DEVPOLL> (value 16, Solaris 8)
+
+This is not implemented yet (and might never be, unless you send me an
+implementation). According to reports, C</dev/poll> only supports sockets
+and is not embeddable, which would limit the usefulness of this backend
+immensely.
+
+=item C<EVBACKEND_PORT> (value 32, Solaris 10)
+
+This uses the Solaris 10 event port mechanism. As with everything on Solaris,
+it's really slow, but it still scales very well (O(active_fds)).
+
+While this backend scales well, it requires one system call per active
+file descriptor per loop iteration. For small and medium numbers of file
+descriptors a "slow" C<EVBACKEND_SELECT> or C<EVBACKEND_POLL> backend
+might perform better.
+
+On the positive side, this backend actually performed fully to
+specification in all tests and is fully embeddable, which is a rare feat
+among the OS-specific backends (I vastly prefer correctness over speed
+hacks).
+
+On the negative side, the interface is I<bizarre> - so bizarre that
+even sun itself gets it wrong in their code examples: The event polling
+function sometimes returns events to the caller even though an error
+occurred, but with no indication whether it has done so or not (yes, it's
+even documented that way) - deadly for edge-triggered interfaces where you
+absolutely have to know whether an event occurred or not because you have
+to re-arm the watcher.
+
+Fortunately libev seems to be able to work around these idiocies.
+
+This backend maps C<EV_READ> and C<EV_WRITE> in the same way as
+C<EVBACKEND_POLL>.
+
+=item C<EVBACKEND_ALL>
+
+Try all backends (even potentially broken ones that wouldn't be tried
+with C<EVFLAG_AUTO>). Since this is a mask, you can do stuff such as
+C<EVBACKEND_ALL & ~EVBACKEND_KQUEUE>.
+
+It is definitely not recommended to use this flag, use whatever
+C<ev_recommended_backends ()> returns, or simply do not specify a backend
+at all.
+
+=item C<EVBACKEND_MASK>
+
+Not a backend at all, but a mask to select all backend bits from a
+C<flags> value, in case you want to mask out any backends from a flags
+value (e.g. when modifying the C<LIBEV_FLAGS> environment variable).
+
+=back
+
+If one or more of the backend flags are or'ed into the flags value,
+then only these backends will be tried (in the reverse order as listed
+here). If none are specified, all backends in C<ev_recommended_backends
+()> will be tried.
+
+Example: Try to create a event loop that uses epoll and nothing else.
+
+ struct ev_loop *epoller = ev_loop_new (EVBACKEND_EPOLL | EVFLAG_NOENV);
+ if (!epoller)
+ fatal ("no epoll found here, maybe it hides under your chair");
+
+Example: Use whatever libev has to offer, but make sure that kqueue is
+used if available.
+
+ struct ev_loop *loop = ev_loop_new (ev_recommended_backends () | EVBACKEND_KQUEUE);
+
+Example: Similarly, on linux, you mgiht want to take advantage of the
+linux aio backend if possible, but fall back to something else if that
+isn't available.
+
+ struct ev_loop *loop = ev_loop_new (ev_recommended_backends () | EVBACKEND_LINUXAIO);
+
+=item ev_loop_destroy (loop)
+
+Destroys an event loop object (frees all memory and kernel state
+etc.). None of the active event watchers will be stopped in the normal
+sense, so e.g. C<ev_is_active> might still return true. It is your
+responsibility to either stop all watchers cleanly yourself I<before>
+calling this function, or cope with the fact afterwards (which is usually
+the easiest thing, you can just ignore the watchers and/or C<free ()> them
+for example).
+
+Note that certain global state, such as signal state (and installed signal
+handlers), will not be freed by this function, and related watchers (such
+as signal and child watchers) would need to be stopped manually.
+
+This function is normally used on loop objects allocated by
+C<ev_loop_new>, but it can also be used on the default loop returned by
+C<ev_default_loop>, in which case it is not thread-safe.
+
+Note that it is not advisable to call this function on the default loop
+except in the rare occasion where you really need to free its resources.
+If you need dynamically allocated loops it is better to use C<ev_loop_new>
+and C<ev_loop_destroy>.
+
+=item ev_loop_fork (loop)
+
+This function sets a flag that causes subsequent C<ev_run> iterations
+to reinitialise the kernel state for backends that have one. Despite
+the name, you can call it anytime you are allowed to start or stop
+watchers (except inside an C<ev_prepare> callback), but it makes most
+sense after forking, in the child process. You I<must> call it (or use
+C<EVFLAG_FORKCHECK>) in the child before resuming or calling C<ev_run>.
+
+In addition, if you want to reuse a loop (via this function or
+C<EVFLAG_FORKCHECK>), you I<also> have to ignore C<SIGPIPE>.
+
+Again, you I<have> to call it on I<any> loop that you want to re-use after
+a fork, I<even if you do not plan to use the loop in the parent>. This is
+because some kernel interfaces *cough* I<kqueue> *cough* do funny things
+during fork.
+
+On the other hand, you only need to call this function in the child
+process if and only if you want to use the event loop in the child. If
+you just fork+exec or create a new loop in the child, you don't have to
+call it at all (in fact, C<epoll> is so badly broken that it makes a
+difference, but libev will usually detect this case on its own and do a
+costly reset of the backend).
+
+The function itself is quite fast and it's usually not a problem to call
+it just in case after a fork.
+
+Example: Automate calling C<ev_loop_fork> on the default loop when
+using pthreads.
+
+ static void
+ post_fork_child (void)
+ {
+ ev_loop_fork (EV_DEFAULT);
+ }
+
+ ...
+ pthread_atfork (0, 0, post_fork_child);
+
+=item int ev_is_default_loop (loop)
+
+Returns true when the given loop is, in fact, the default loop, and false
+otherwise.
+
+=item unsigned int ev_iteration (loop)
+
+Returns the current iteration count for the event loop, which is identical
+to the number of times libev did poll for new events. It starts at C<0>
+and happily wraps around with enough iterations.
+
+This value can sometimes be useful as a generation counter of sorts (it
+"ticks" the number of loop iterations), as it roughly corresponds with
+C<ev_prepare> and C<ev_check> calls - and is incremented between the
+prepare and check phases.
+
+=item unsigned int ev_depth (loop)
+
+Returns the number of times C<ev_run> was entered minus the number of
+times C<ev_run> was exited normally, in other words, the recursion depth.
+
+Outside C<ev_run>, this number is zero. In a callback, this number is
+C<1>, unless C<ev_run> was invoked recursively (or from another thread),
+in which case it is higher.
+
+Leaving C<ev_run> abnormally (setjmp/longjmp, cancelling the thread,
+throwing an exception etc.), doesn't count as "exit" - consider this
+as a hint to avoid such ungentleman-like behaviour unless it's really
+convenient, in which case it is fully supported.
+
+=item unsigned int ev_backend (loop)
+
+Returns one of the C<EVBACKEND_*> flags indicating the event backend in
+use.
+
+=item ev_tstamp ev_now (loop)
+
+Returns the current "event loop time", which is the time the event loop
+received events and started processing them. This timestamp does not
+change as long as callbacks are being processed, and this is also the base
+time used for relative timers. You can treat it as the timestamp of the
+event occurring (or more correctly, libev finding out about it).
+
+=item ev_now_update (loop)
+
+Establishes the current time by querying the kernel, updating the time
+returned by C<ev_now ()> in the progress. This is a costly operation and
+is usually done automatically within C<ev_run ()>.
+
+This function is rarely useful, but when some event callback runs for a
+very long time without entering the event loop, updating libev's idea of
+the current time is a good idea.
+
+See also L</The special problem of time updates> in the C<ev_timer> section.
+
+=item ev_suspend (loop)
+
+=item ev_resume (loop)
+
+These two functions suspend and resume an event loop, for use when the
+loop is not used for a while and timeouts should not be processed.
+
+A typical use case would be an interactive program such as a game: When
+the user presses C<^Z> to suspend the game and resumes it an hour later it
+would be best to handle timeouts as if no time had actually passed while
+the program was suspended. This can be achieved by calling C<ev_suspend>
+in your C<SIGTSTP> handler, sending yourself a C<SIGSTOP> and calling
+C<ev_resume> directly afterwards to resume timer processing.
+
+Effectively, all C<ev_timer> watchers will be delayed by the time spend
+between C<ev_suspend> and C<ev_resume>, and all C<ev_periodic> watchers
+will be rescheduled (that is, they will lose any events that would have
+occurred while suspended).
+
+After calling C<ev_suspend> you B<must not> call I<any> function on the
+given loop other than C<ev_resume>, and you B<must not> call C<ev_resume>
+without a previous call to C<ev_suspend>.
+
+Calling C<ev_suspend>/C<ev_resume> has the side effect of updating the
+event loop time (see C<ev_now_update>).
+
+=item bool ev_run (loop, int flags)
+
+Finally, this is it, the event handler. This function usually is called
+after you have initialised all your watchers and you want to start
+handling events. It will ask the operating system for any new events, call
+the watcher callbacks, and then repeat the whole process indefinitely: This
+is why event loops are called I<loops>.
+
+If the flags argument is specified as C<0>, it will keep handling events
+until either no event watchers are active anymore or C<ev_break> was
+called.
+
+The return value is false if there are no more active watchers (which
+usually means "all jobs done" or "deadlock"), and true in all other cases
+(which usually means " you should call C<ev_run> again").
+
+Please note that an explicit C<ev_break> is usually better than
+relying on all watchers to be stopped when deciding when a program has
+finished (especially in interactive programs), but having a program
+that automatically loops as long as it has to and no longer by virtue
+of relying on its watchers stopping correctly, that is truly a thing of
+beauty.
+
+This function is I<mostly> exception-safe - you can break out of a
+C<ev_run> call by calling C<longjmp> in a callback, throwing a C++
+exception and so on. This does not decrement the C<ev_depth> value, nor
+will it clear any outstanding C<EVBREAK_ONE> breaks.
+
+A flags value of C<EVRUN_NOWAIT> will look for new events, will handle
+those events and any already outstanding ones, but will not wait and
+block your process in case there are no events and will return after one
+iteration of the loop. This is sometimes useful to poll and handle new
+events while doing lengthy calculations, to keep the program responsive.
+
+A flags value of C<EVRUN_ONCE> will look for new events (waiting if
+necessary) and will handle those and any already outstanding ones. It
+will block your process until at least one new event arrives (which could
+be an event internal to libev itself, so there is no guarantee that a
+user-registered callback will be called), and will return after one
+iteration of the loop.
+
+This is useful if you are waiting for some external event in conjunction
+with something not expressible using other libev watchers (i.e. "roll your
+own C<ev_run>"). However, a pair of C<ev_prepare>/C<ev_check> watchers is
+usually a better approach for this kind of thing.
+
+Here are the gory details of what C<ev_run> does (this is for your
+understanding, not a guarantee that things will work exactly like this in
+future versions):
+
+ - Increment loop depth.
+ - Reset the ev_break status.
+ - Before the first iteration, call any pending watchers.
+ LOOP:
+ - If EVFLAG_FORKCHECK was used, check for a fork.
+ - If a fork was detected (by any means), queue and call all fork watchers.
+ - Queue and call all prepare watchers.
+ - If ev_break was called, goto FINISH.
+ - If we have been forked, detach and recreate the kernel state
+ as to not disturb the other process.
+ - Update the kernel state with all outstanding changes.
+ - Update the "event loop time" (ev_now ()).
+ - Calculate for how long to sleep or block, if at all
+ (active idle watchers, EVRUN_NOWAIT or not having
+ any active watchers at all will result in not sleeping).
+ - Sleep if the I/O and timer collect interval say so.
+ - Increment loop iteration counter.
+ - Block the process, waiting for any events.
+ - Queue all outstanding I/O (fd) events.
+ - Update the "event loop time" (ev_now ()), and do time jump adjustments.
+ - Queue all expired timers.
+ - Queue all expired periodics.
+ - Queue all idle watchers with priority higher than that of pending events.
+ - Queue all check watchers.
+ - Call all queued watchers in reverse order (i.e. check watchers first).
+ Signals and child watchers are implemented as I/O watchers, and will
+ be handled here by queueing them when their watcher gets executed.
+ - If ev_break has been called, or EVRUN_ONCE or EVRUN_NOWAIT
+ were used, or there are no active watchers, goto FINISH, otherwise
+ continue with step LOOP.
+ FINISH:
+ - Reset the ev_break status iff it was EVBREAK_ONE.
+ - Decrement the loop depth.
+ - Return.
+
+Example: Queue some jobs and then loop until no events are outstanding
+anymore.
+
+ ... queue jobs here, make sure they register event watchers as long
+ ... as they still have work to do (even an idle watcher will do..)
+ ev_run (my_loop, 0);
+ ... jobs done or somebody called break. yeah!
+
+=item ev_break (loop, how)
+
+Can be used to make a call to C<ev_run> return early (but only after it
+has processed all outstanding events). The C<how> argument must be either
+C<EVBREAK_ONE>, which will make the innermost C<ev_run> call return, or
+C<EVBREAK_ALL>, which will make all nested C<ev_run> calls return.
+
+This "break state" will be cleared on the next call to C<ev_run>.
+
+It is safe to call C<ev_break> from outside any C<ev_run> calls, too, in
+which case it will have no effect.
+
+=item ev_ref (loop)
+
+=item ev_unref (loop)
+
+Ref/unref can be used to add or remove a reference count on the event
+loop: Every watcher keeps one reference, and as long as the reference
+count is nonzero, C<ev_run> will not return on its own.
+
+This is useful when you have a watcher that you never intend to
+unregister, but that nevertheless should not keep C<ev_run> from
+returning. In such a case, call C<ev_unref> after starting, and C<ev_ref>
+before stopping it.
+
+As an example, libev itself uses this for its internal signal pipe: It
+is not visible to the libev user and should not keep C<ev_run> from
+exiting if no event watchers registered by it are active. It is also an
+excellent way to do this for generic recurring timers or from within
+third-party libraries. Just remember to I<unref after start> and I<ref
+before stop> (but only if the watcher wasn't active before, or was active
+before, respectively. Note also that libev might stop watchers itself
+(e.g. non-repeating timers) in which case you have to C<ev_ref>
+in the callback).
+
+Example: Create a signal watcher, but keep it from keeping C<ev_run>
+running when nothing else is active.
+
+ ev_signal exitsig;
+ ev_signal_init (&exitsig, sig_cb, SIGINT);
+ ev_signal_start (loop, &exitsig);
+ ev_unref (loop);
+
+Example: For some weird reason, unregister the above signal handler again.
+
+ ev_ref (loop);
+ ev_signal_stop (loop, &exitsig);
+
+=item ev_set_io_collect_interval (loop, ev_tstamp interval)
+
+=item ev_set_timeout_collect_interval (loop, ev_tstamp interval)
+
+These advanced functions influence the time that libev will spend waiting
+for events. Both time intervals are by default C<0>, meaning that libev
+will try to invoke timer/periodic callbacks and I/O callbacks with minimum
+latency.
+
+Setting these to a higher value (the C<interval> I<must> be >= C<0>)
+allows libev to delay invocation of I/O and timer/periodic callbacks
+to increase efficiency of loop iterations (or to increase power-saving
+opportunities).
+
+The idea is that sometimes your program runs just fast enough to handle
+one (or very few) event(s) per loop iteration. While this makes the
+program responsive, it also wastes a lot of CPU time to poll for new
+events, especially with backends like C<select ()> which have a high
+overhead for the actual polling but can deliver many events at once.
+
+By setting a higher I<io collect interval> you allow libev to spend more
+time collecting I/O events, so you can handle more events per iteration,
+at the cost of increasing latency. Timeouts (both C<ev_periodic> and
+C<ev_timer>) will not be affected. Setting this to a non-null value will
+introduce an additional C<ev_sleep ()> call into most loop iterations. The
+sleep time ensures that libev will not poll for I/O events more often then
+once per this interval, on average (as long as the host time resolution is
+good enough).
+
+Likewise, by setting a higher I<timeout collect interval> you allow libev
+to spend more time collecting timeouts, at the expense of increased
+latency/jitter/inexactness (the watcher callback will be called
+later). C<ev_io> watchers will not be affected. Setting this to a non-null
+value will not introduce any overhead in libev.
+
+Many (busy) programs can usually benefit by setting the I/O collect
+interval to a value near C<0.1> or so, which is often enough for
+interactive servers (of course not for games), likewise for timeouts. It
+usually doesn't make much sense to set it to a lower value than C<0.01>,
+as this approaches the timing granularity of most systems. Note that if
+you do transactions with the outside world and you can't increase the
+parallelity, then this setting will limit your transaction rate (if you
+need to poll once per transaction and the I/O collect interval is 0.01,
+then you can't do more than 100 transactions per second).
+
+Setting the I<timeout collect interval> can improve the opportunity for
+saving power, as the program will "bundle" timer callback invocations that
+are "near" in time together, by delaying some, thus reducing the number of
+times the process sleeps and wakes up again. Another useful technique to
+reduce iterations/wake-ups is to use C<ev_periodic> watchers and make sure
+they fire on, say, one-second boundaries only.
+
+Example: we only need 0.1s timeout granularity, and we wish not to poll
+more often than 100 times per second:
+
+ ev_set_timeout_collect_interval (EV_DEFAULT_UC_ 0.1);
+ ev_set_io_collect_interval (EV_DEFAULT_UC_ 0.01);
+
+=item ev_invoke_pending (loop)
+
+This call will simply invoke all pending watchers while resetting their
+pending state. Normally, C<ev_run> does this automatically when required,
+but when overriding the invoke callback this call comes handy. This
+function can be invoked from a watcher - this can be useful for example
+when you want to do some lengthy calculation and want to pass further
+event handling to another thread (you still have to make sure only one
+thread executes within C<ev_invoke_pending> or C<ev_run> of course).
+
+=item int ev_pending_count (loop)
+
+Returns the number of pending watchers - zero indicates that no watchers
+are pending.
+
+=item ev_set_invoke_pending_cb (loop, void (*invoke_pending_cb)(EV_P))
+
+This overrides the invoke pending functionality of the loop: Instead of
+invoking all pending watchers when there are any, C<ev_run> will call
+this callback instead. This is useful, for example, when you want to
+invoke the actual watchers inside another context (another thread etc.).
+
+If you want to reset the callback, use C<ev_invoke_pending> as new
+callback.
+
+=item ev_set_loop_release_cb (loop, void (*release)(EV_P) throw (), void (*acquire)(EV_P) throw ())
+
+Sometimes you want to share the same loop between multiple threads. This
+can be done relatively simply by putting mutex_lock/unlock calls around
+each call to a libev function.
+
+However, C<ev_run> can run an indefinite time, so it is not feasible
+to wait for it to return. One way around this is to wake up the event
+loop via C<ev_break> and C<ev_async_send>, another way is to set these
+I<release> and I<acquire> callbacks on the loop.
+
+When set, then C<release> will be called just before the thread is
+suspended waiting for new events, and C<acquire> is called just
+afterwards.
+
+Ideally, C<release> will just call your mutex_unlock function, and
+C<acquire> will just call the mutex_lock function again.
+
+While event loop modifications are allowed between invocations of
+C<release> and C<acquire> (that's their only purpose after all), no
+modifications done will affect the event loop, i.e. adding watchers will
+have no effect on the set of file descriptors being watched, or the time
+waited. Use an C<ev_async> watcher to wake up C<ev_run> when you want it
+to take note of any changes you made.
+
+In theory, threads executing C<ev_run> will be async-cancel safe between
+invocations of C<release> and C<acquire>.
+
+See also the locking example in the C<THREADS> section later in this
+document.
+
+=item ev_set_userdata (loop, void *data)
+
+=item void *ev_userdata (loop)
+
+Set and retrieve a single C<void *> associated with a loop. When
+C<ev_set_userdata> has never been called, then C<ev_userdata> returns
+C<0>.
+
+These two functions can be used to associate arbitrary data with a loop,
+and are intended solely for the C<invoke_pending_cb>, C<release> and
+C<acquire> callbacks described above, but of course can be (ab-)used for
+any other purpose as well.
+
+=item ev_verify (loop)
+
+This function only does something when C<EV_VERIFY> support has been
+compiled in, which is the default for non-minimal builds. It tries to go
+through all internal structures and checks them for validity. If anything
+is found to be inconsistent, it will print an error message to standard
+error and call C<abort ()>.
+
+This can be used to catch bugs inside libev itself: under normal
+circumstances, this function will never abort as of course libev keeps its
+data structures consistent.
+
+=back
+
+
+=head1 ANATOMY OF A WATCHER
+
+In the following description, uppercase C<TYPE> in names stands for the
+watcher type, e.g. C<ev_TYPE_start> can mean C<ev_timer_start> for timer
+watchers and C<ev_io_start> for I/O watchers.
+
+A watcher is an opaque structure that you allocate and register to record
+your interest in some event. To make a concrete example, imagine you want
+to wait for STDIN to become readable, you would create an C<ev_io> watcher
+for that:
+
+ static void my_cb (struct ev_loop *loop, ev_io *w, int revents)
+ {
+ ev_io_stop (w);
+ ev_break (loop, EVBREAK_ALL);
+ }
+
+ struct ev_loop *loop = ev_default_loop (0);
+
+ ev_io stdin_watcher;
+
+ ev_init (&stdin_watcher, my_cb);
+ ev_io_set (&stdin_watcher, STDIN_FILENO, EV_READ);
+ ev_io_start (loop, &stdin_watcher);
+
+ ev_run (loop, 0);
+
+As you can see, you are responsible for allocating the memory for your
+watcher structures (and it is I<usually> a bad idea to do this on the
+stack).
+
+Each watcher has an associated watcher structure (called C<struct ev_TYPE>
+or simply C<ev_TYPE>, as typedefs are provided for all watcher structs).
+
+Each watcher structure must be initialised by a call to C<ev_init (watcher
+*, callback)>, which expects a callback to be provided. This callback is
+invoked each time the event occurs (or, in the case of I/O watchers, each
+time the event loop detects that the file descriptor given is readable
+and/or writable).
+
+Each watcher type further has its own C<< ev_TYPE_set (watcher *, ...) >>
+macro to configure it, with arguments specific to the watcher type. There
+is also a macro to combine initialisation and setting in one call: C<<
+ev_TYPE_init (watcher *, callback, ...) >>.
+
+To make the watcher actually watch out for events, you have to start it
+with a watcher-specific start function (C<< ev_TYPE_start (loop, watcher
+*) >>), and you can stop watching for events at any time by calling the
+corresponding stop function (C<< ev_TYPE_stop (loop, watcher *) >>.
+
+As long as your watcher is active (has been started but not stopped) you
+must not touch the values stored in it except when explicitly documented
+otherwise. Most specifically you must never reinitialise it or call its
+C<ev_TYPE_set> macro.
+
+Each and every callback receives the event loop pointer as first, the
+registered watcher structure as second, and a bitset of received events as
+third argument.
+
+The received events usually include a single bit per event type received
+(you can receive multiple events at the same time). The possible bit masks
+are:
+
+=over 4
+
+=item C<EV_READ>
+
+=item C<EV_WRITE>
+
+The file descriptor in the C<ev_io> watcher has become readable and/or
+writable.
+
+=item C<EV_TIMER>
+
+The C<ev_timer> watcher has timed out.
+
+=item C<EV_PERIODIC>
+
+The C<ev_periodic> watcher has timed out.
+
+=item C<EV_SIGNAL>
+
+The signal specified in the C<ev_signal> watcher has been received by a thread.
+
+=item C<EV_CHILD>
+
+The pid specified in the C<ev_child> watcher has received a status change.
+
+=item C<EV_STAT>
+
+The path specified in the C<ev_stat> watcher changed its attributes somehow.
+
+=item C<EV_IDLE>
+
+The C<ev_idle> watcher has determined that you have nothing better to do.
+
+=item C<EV_PREPARE>
+
+=item C<EV_CHECK>
+
+All C<ev_prepare> watchers are invoked just I<before> C<ev_run> starts to
+gather new events, and all C<ev_check> watchers are queued (not invoked)
+just after C<ev_run> has gathered them, but before it queues any callbacks
+for any received events. That means C<ev_prepare> watchers are the last
+watchers invoked before the event loop sleeps or polls for new events, and
+C<ev_check> watchers will be invoked before any other watchers of the same
+or lower priority within an event loop iteration.
+
+Callbacks of both watcher types can start and stop as many watchers as
+they want, and all of them will be taken into account (for example, a
+C<ev_prepare> watcher might start an idle watcher to keep C<ev_run> from
+blocking).
+
+=item C<EV_EMBED>
+
+The embedded event loop specified in the C<ev_embed> watcher needs attention.
+
+=item C<EV_FORK>
+
+The event loop has been resumed in the child process after fork (see
+C<ev_fork>).
+
+=item C<EV_CLEANUP>
+
+The event loop is about to be destroyed (see C<ev_cleanup>).
+
+=item C<EV_ASYNC>
+
+The given async watcher has been asynchronously notified (see C<ev_async>).
+
+=item C<EV_CUSTOM>
+
+Not ever sent (or otherwise used) by libev itself, but can be freely used
+by libev users to signal watchers (e.g. via C<ev_feed_event>).
+
+=item C<EV_ERROR>
+
+An unspecified error has occurred, the watcher has been stopped. This might
+happen because the watcher could not be properly started because libev
+ran out of memory, a file descriptor was found to be closed or any other
+problem. Libev considers these application bugs.
+
+You best act on it by reporting the problem and somehow coping with the
+watcher being stopped. Note that well-written programs should not receive
+an error ever, so when your watcher receives it, this usually indicates a
+bug in your program.
+
+Libev will usually signal a few "dummy" events together with an error, for
+example it might indicate that a fd is readable or writable, and if your
+callbacks is well-written it can just attempt the operation and cope with
+the error from read() or write(). This will not work in multi-threaded
+programs, though, as the fd could already be closed and reused for another
+thing, so beware.
+
+=back
+
+=head2 GENERIC WATCHER FUNCTIONS
+
+=over 4
+
+=item C<ev_init> (ev_TYPE *watcher, callback)
+
+This macro initialises the generic portion of a watcher. The contents
+of the watcher object can be arbitrary (so C<malloc> will do). Only
+the generic parts of the watcher are initialised, you I<need> to call
+the type-specific C<ev_TYPE_set> macro afterwards to initialise the
+type-specific parts. For each type there is also a C<ev_TYPE_init> macro
+which rolls both calls into one.
+
+You can reinitialise a watcher at any time as long as it has been stopped
+(or never started) and there are no pending events outstanding.
+
+The callback is always of type C<void (*)(struct ev_loop *loop, ev_TYPE *watcher,
+int revents)>.
+
+Example: Initialise an C<ev_io> watcher in two steps.
+
+ ev_io w;
+ ev_init (&w, my_cb);
+ ev_io_set (&w, STDIN_FILENO, EV_READ);
+
+=item C<ev_TYPE_set> (ev_TYPE *watcher, [args])
+
+This macro initialises the type-specific parts of a watcher. You need to
+call C<ev_init> at least once before you call this macro, but you can
+call C<ev_TYPE_set> any number of times. You must not, however, call this
+macro on a watcher that is active (it can be pending, however, which is a
+difference to the C<ev_init> macro).
+
+Although some watcher types do not have type-specific arguments
+(e.g. C<ev_prepare>) you still need to call its C<set> macro.
+
+See C<ev_init>, above, for an example.
+
+=item C<ev_TYPE_init> (ev_TYPE *watcher, callback, [args])
+
+This convenience macro rolls both C<ev_init> and C<ev_TYPE_set> macro
+calls into a single call. This is the most convenient method to initialise
+a watcher. The same limitations apply, of course.
+
+Example: Initialise and set an C<ev_io> watcher in one step.
+
+ ev_io_init (&w, my_cb, STDIN_FILENO, EV_READ);
+
+=item C<ev_TYPE_start> (loop, ev_TYPE *watcher)
+
+Starts (activates) the given watcher. Only active watchers will receive
+events. If the watcher is already active nothing will happen.
+
+Example: Start the C<ev_io> watcher that is being abused as example in this
+whole section.
+
+ ev_io_start (EV_DEFAULT_UC, &w);
+
+=item C<ev_TYPE_stop> (loop, ev_TYPE *watcher)
+
+Stops the given watcher if active, and clears the pending status (whether
+the watcher was active or not).
+
+It is possible that stopped watchers are pending - for example,
+non-repeating timers are being stopped when they become pending - but
+calling C<ev_TYPE_stop> ensures that the watcher is neither active nor
+pending. If you want to free or reuse the memory used by the watcher it is
+therefore a good idea to always call its C<ev_TYPE_stop> function.
+
+=item bool ev_is_active (ev_TYPE *watcher)
+
+Returns a true value iff the watcher is active (i.e. it has been started
+and not yet been stopped). As long as a watcher is active you must not modify
+it.
+
+=item bool ev_is_pending (ev_TYPE *watcher)
+
+Returns a true value iff the watcher is pending, (i.e. it has outstanding
+events but its callback has not yet been invoked). As long as a watcher
+is pending (but not active) you must not call an init function on it (but
+C<ev_TYPE_set> is safe), you must not change its priority, and you must
+make sure the watcher is available to libev (e.g. you cannot C<free ()>
+it).
+
+=item callback ev_cb (ev_TYPE *watcher)
+
+Returns the callback currently set on the watcher.
+
+=item ev_set_cb (ev_TYPE *watcher, callback)
+
+Change the callback. You can change the callback at virtually any time
+(modulo threads).
+
+=item ev_set_priority (ev_TYPE *watcher, int priority)
+
+=item int ev_priority (ev_TYPE *watcher)
+
+Set and query the priority of the watcher. The priority is a small
+integer between C<EV_MAXPRI> (default: C<2>) and C<EV_MINPRI>
+(default: C<-2>). Pending watchers with higher priority will be invoked
+before watchers with lower priority, but priority will not keep watchers
+from being executed (except for C<ev_idle> watchers).
+
+If you need to suppress invocation when higher priority events are pending
+you need to look at C<ev_idle> watchers, which provide this functionality.
+
+You I<must not> change the priority of a watcher as long as it is active or
+pending.
+
+Setting a priority outside the range of C<EV_MINPRI> to C<EV_MAXPRI> is
+fine, as long as you do not mind that the priority value you query might
+or might not have been clamped to the valid range.
+
+The default priority used by watchers when no priority has been set is
+always C<0>, which is supposed to not be too high and not be too low :).
+
+See L</WATCHER PRIORITY MODELS>, below, for a more thorough treatment of
+priorities.
+
+=item ev_invoke (loop, ev_TYPE *watcher, int revents)
+
+Invoke the C<watcher> with the given C<loop> and C<revents>. Neither
+C<loop> nor C<revents> need to be valid as long as the watcher callback
+can deal with that fact, as both are simply passed through to the
+callback.
+
+=item int ev_clear_pending (loop, ev_TYPE *watcher)
+
+If the watcher is pending, this function clears its pending status and
+returns its C<revents> bitset (as if its callback was invoked). If the
+watcher isn't pending it does nothing and returns C<0>.
+
+Sometimes it can be useful to "poll" a watcher instead of waiting for its
+callback to be invoked, which can be accomplished with this function.
+
+=item ev_feed_event (loop, ev_TYPE *watcher, int revents)
+
+Feeds the given event set into the event loop, as if the specified event
+had happened for the specified watcher (which must be a pointer to an
+initialised but not necessarily started event watcher). Obviously you must
+not free the watcher as long as it has pending events.
+
+Stopping the watcher, letting libev invoke it, or calling
+C<ev_clear_pending> will clear the pending event, even if the watcher was
+not started in the first place.
+
+See also C<ev_feed_fd_event> and C<ev_feed_signal_event> for related
+functions that do not need a watcher.
+
+=back
+
+See also the L</ASSOCIATING CUSTOM DATA WITH A WATCHER> and L</BUILDING YOUR
+OWN COMPOSITE WATCHERS> idioms.
+
+=head2 WATCHER STATES
+
+There are various watcher states mentioned throughout this manual -
+active, pending and so on. In this section these states and the rules to
+transition between them will be described in more detail - and while these
+rules might look complicated, they usually do "the right thing".
+
+=over 4
+
+=item initialised
+
+Before a watcher can be registered with the event loop it has to be
+initialised. This can be done with a call to C<ev_TYPE_init>, or calls to
+C<ev_init> followed by the watcher-specific C<ev_TYPE_set> function.
+
+In this state it is simply some block of memory that is suitable for
+use in an event loop. It can be moved around, freed, reused etc. at
+will - as long as you either keep the memory contents intact, or call
+C<ev_TYPE_init> again.
+
+=item started/running/active
+
+Once a watcher has been started with a call to C<ev_TYPE_start> it becomes
+property of the event loop, and is actively waiting for events. While in
+this state it cannot be accessed (except in a few documented ways), moved,
+freed or anything else - the only legal thing is to keep a pointer to it,
+and call libev functions on it that are documented to work on active watchers.
+
+=item pending
+
+If a watcher is active and libev determines that an event it is interested
+in has occurred (such as a timer expiring), it will become pending. It will
+stay in this pending state until either it is stopped or its callback is
+about to be invoked, so it is not normally pending inside the watcher
+callback.
+
+The watcher might or might not be active while it is pending (for example,
+an expired non-repeating timer can be pending but no longer active). If it
+is stopped, it can be freely accessed (e.g. by calling C<ev_TYPE_set>),
+but it is still property of the event loop at this time, so cannot be
+moved, freed or reused. And if it is active the rules described in the
+previous item still apply.
+
+It is also possible to feed an event on a watcher that is not active (e.g.
+via C<ev_feed_event>), in which case it becomes pending without being
+active.
+
+=item stopped
+
+A watcher can be stopped implicitly by libev (in which case it might still
+be pending), or explicitly by calling its C<ev_TYPE_stop> function. The
+latter will clear any pending state the watcher might be in, regardless
+of whether it was active or not, so stopping a watcher explicitly before
+freeing it is often a good idea.
+
+While stopped (and not pending) the watcher is essentially in the
+initialised state, that is, it can be reused, moved, modified in any way
+you wish (but when you trash the memory block, you need to C<ev_TYPE_init>
+it again).
+
+=back
+
+=head2 WATCHER PRIORITY MODELS
+
+Many event loops support I<watcher priorities>, which are usually small
+integers that influence the ordering of event callback invocation
+between watchers in some way, all else being equal.
+
+In libev, watcher priorities can be set using C<ev_set_priority>. See its
+description for the more technical details such as the actual priority
+range.
+
+There are two common ways how these these priorities are being interpreted
+by event loops:
+
+In the more common lock-out model, higher priorities "lock out" invocation
+of lower priority watchers, which means as long as higher priority
+watchers receive events, lower priority watchers are not being invoked.
+
+The less common only-for-ordering model uses priorities solely to order
+callback invocation within a single event loop iteration: Higher priority
+watchers are invoked before lower priority ones, but they all get invoked
+before polling for new events.
+
+Libev uses the second (only-for-ordering) model for all its watchers
+except for idle watchers (which use the lock-out model).
+
+The rationale behind this is that implementing the lock-out model for
+watchers is not well supported by most kernel interfaces, and most event
+libraries will just poll for the same events again and again as long as
+their callbacks have not been executed, which is very inefficient in the
+common case of one high-priority watcher locking out a mass of lower
+priority ones.
+
+Static (ordering) priorities are most useful when you have two or more
+watchers handling the same resource: a typical usage example is having an
+C<ev_io> watcher to receive data, and an associated C<ev_timer> to handle
+timeouts. Under load, data might be received while the program handles
+other jobs, but since timers normally get invoked first, the timeout
+handler will be executed before checking for data. In that case, giving
+the timer a lower priority than the I/O watcher ensures that I/O will be
+handled first even under adverse conditions (which is usually, but not
+always, what you want).
+
+Since idle watchers use the "lock-out" model, meaning that idle watchers
+will only be executed when no same or higher priority watchers have
+received events, they can be used to implement the "lock-out" model when
+required.
+
+For example, to emulate how many other event libraries handle priorities,
+you can associate an C<ev_idle> watcher to each such watcher, and in
+the normal watcher callback, you just start the idle watcher. The real
+processing is done in the idle watcher callback. This causes libev to
+continuously poll and process kernel event data for the watcher, but when
+the lock-out case is known to be rare (which in turn is rare :), this is
+workable.
+
+Usually, however, the lock-out model implemented that way will perform
+miserably under the type of load it was designed to handle. In that case,
+it might be preferable to stop the real watcher before starting the
+idle watcher, so the kernel will not have to process the event in case
+the actual processing will be delayed for considerable time.
+
+Here is an example of an I/O watcher that should run at a strictly lower
+priority than the default, and which should only process data when no
+other events are pending:
+
+ ev_idle idle; // actual processing watcher
+ ev_io io; // actual event watcher
+
+ static void
+ io_cb (EV_P_ ev_io *w, int revents)
+ {
+ // stop the I/O watcher, we received the event, but
+ // are not yet ready to handle it.
+ ev_io_stop (EV_A_ w);
+
+ // start the idle watcher to handle the actual event.
+ // it will not be executed as long as other watchers
+ // with the default priority are receiving events.
+ ev_idle_start (EV_A_ &idle);
+ }
+
+ static void
+ idle_cb (EV_P_ ev_idle *w, int revents)
+ {
+ // actual processing
+ read (STDIN_FILENO, ...);
+
+ // have to start the I/O watcher again, as
+ // we have handled the event
+ ev_io_start (EV_P_ &io);
+ }
+
+ // initialisation
+ ev_idle_init (&idle, idle_cb);
+ ev_io_init (&io, io_cb, STDIN_FILENO, EV_READ);
+ ev_io_start (EV_DEFAULT_ &io);
+
+In the "real" world, it might also be beneficial to start a timer, so that
+low-priority connections can not be locked out forever under load. This
+enables your program to keep a lower latency for important connections
+during short periods of high load, while not completely locking out less
+important ones.
+
+
+=head1 WATCHER TYPES
+
+This section describes each watcher in detail, but will not repeat
+information given in the last section. Any initialisation/set macros,
+functions and members specific to the watcher type are explained.
+
+Most members are additionally marked with either I<[read-only]>, meaning
+that, while the watcher is active, you can look at the member and expect
+some sensible content, but you must not modify it (you can modify it while
+the watcher is stopped to your hearts content), or I<[read-write]>, which
+means you can expect it to have some sensible content while the watcher is
+active, but you can also modify it (within the same thread as the event
+loop, i.e. without creating data races). Modifying it may not do something
+sensible or take immediate effect (or do anything at all), but libev will
+not crash or malfunction in any way.
+
+In any case, the documentation for each member will explain what the
+effects are, and if there are any additional access restrictions.
+
+=head2 C<ev_io> - is this file descriptor readable or writable?
+
+I/O watchers check whether a file descriptor is readable or writable
+in each iteration of the event loop, or, more precisely, when reading
+would not block the process and writing would at least be able to write
+some data. This behaviour is called level-triggering because you keep
+receiving events as long as the condition persists. Remember you can stop
+the watcher if you don't want to act on the event and neither want to
+receive future events.
+
+In general you can register as many read and/or write event watchers per
+fd as you want (as long as you don't confuse yourself). Setting all file
+descriptors to non-blocking mode is also usually a good idea (but not
+required if you know what you are doing).
+
+Another thing you have to watch out for is that it is quite easy to
+receive "spurious" readiness notifications, that is, your callback might
+be called with C<EV_READ> but a subsequent C<read>(2) will actually block
+because there is no data. It is very easy to get into this situation even
+with a relatively standard program structure. Thus it is best to always
+use non-blocking I/O: An extra C<read>(2) returning C<EAGAIN> is far
+preferable to a program hanging until some data arrives.
+
+If you cannot run the fd in non-blocking mode (for example you should
+not play around with an Xlib connection), then you have to separately
+re-test whether a file descriptor is really ready with a known-to-be good
+interface such as poll (fortunately in the case of Xlib, it already does
+this on its own, so its quite safe to use). Some people additionally
+use C<SIGALRM> and an interval timer, just to be sure you won't block
+indefinitely.
+
+But really, best use non-blocking mode.
+
+=head3 The special problem of disappearing file descriptors
+
+Some backends (e.g. kqueue, epoll, linuxaio) need to be told about closing
+a file descriptor (either due to calling C<close> explicitly or any other
+means, such as C<dup2>). The reason is that you register interest in some
+file descriptor, but when it goes away, the operating system will silently
+drop this interest. If another file descriptor with the same number then
+is registered with libev, there is no efficient way to see that this is,
+in fact, a different file descriptor.
+
+To avoid having to explicitly tell libev about such cases, libev follows
+the following policy: Each time C<ev_io_set> is being called, libev
+will assume that this is potentially a new file descriptor, otherwise
+it is assumed that the file descriptor stays the same. That means that
+you I<have> to call C<ev_io_set> (or C<ev_io_init>) when you change the
+descriptor even if the file descriptor number itself did not change.
+
+This is how one would do it normally anyway, the important point is that
+the libev application should not optimise around libev but should leave
+optimisations to libev.
+
+=head3 The special problem of dup'ed file descriptors
+
+Some backends (e.g. epoll), cannot register events for file descriptors,
+but only events for the underlying file descriptions. That means when you
+have C<dup ()>'ed file descriptors or weirder constellations, and register
+events for them, only one file descriptor might actually receive events.
+
+There is no workaround possible except not registering events
+for potentially C<dup ()>'ed file descriptors, or to resort to
+C<EVBACKEND_SELECT> or C<EVBACKEND_POLL>.
+
+=head3 The special problem of files
+
+Many people try to use C<select> (or libev) on file descriptors
+representing files, and expect it to become ready when their program
+doesn't block on disk accesses (which can take a long time on their own).
+
+However, this cannot ever work in the "expected" way - you get a readiness
+notification as soon as the kernel knows whether and how much data is
+there, and in the case of open files, that's always the case, so you
+always get a readiness notification instantly, and your read (or possibly
+write) will still block on the disk I/O.
+
+Another way to view it is that in the case of sockets, pipes, character
+devices and so on, there is another party (the sender) that delivers data
+on its own, but in the case of files, there is no such thing: the disk
+will not send data on its own, simply because it doesn't know what you
+wish to read - you would first have to request some data.
+
+Since files are typically not-so-well supported by advanced notification
+mechanism, libev tries hard to emulate POSIX behaviour with respect
+to files, even though you should not use it. The reason for this is
+convenience: sometimes you want to watch STDIN or STDOUT, which is
+usually a tty, often a pipe, but also sometimes files or special devices
+(for example, C<epoll> on Linux works with F</dev/random> but not with
+F</dev/urandom>), and even though the file might better be served with
+asynchronous I/O instead of with non-blocking I/O, it is still useful when
+it "just works" instead of freezing.
+
+So avoid file descriptors pointing to files when you know it (e.g. use
+libeio), but use them when it is convenient, e.g. for STDIN/STDOUT, or
+when you rarely read from a file instead of from a socket, and want to
+reuse the same code path.
+
+=head3 The special problem of fork
+
+Some backends (epoll, kqueue, linuxaio, iouring) do not support C<fork ()>
+at all or exhibit useless behaviour. Libev fully supports fork, but needs
+to be told about it in the child if you want to continue to use it in the
+child.
+
+To support fork in your child processes, you have to call C<ev_loop_fork
+()> after a fork in the child, enable C<EVFLAG_FORKCHECK>, or resort to
+C<EVBACKEND_SELECT> or C<EVBACKEND_POLL>.
+
+=head3 The special problem of SIGPIPE
+
+While not really specific to libev, it is easy to forget about C<SIGPIPE>:
+when writing to a pipe whose other end has been closed, your program gets
+sent a SIGPIPE, which, by default, aborts your program. For most programs
+this is sensible behaviour, for daemons, this is usually undesirable.
+
+So when you encounter spurious, unexplained daemon exits, make sure you
+ignore SIGPIPE (and maybe make sure you log the exit status of your daemon
+somewhere, as that would have given you a big clue).
+
+=head3 The special problem of accept()ing when you can't
+
+Many implementations of the POSIX C<accept> function (for example,
+found in post-2004 Linux) have the peculiar behaviour of not removing a
+connection from the pending queue in all error cases.
+
+For example, larger servers often run out of file descriptors (because
+of resource limits), causing C<accept> to fail with C<ENFILE> but not
+rejecting the connection, leading to libev signalling readiness on
+the next iteration again (the connection still exists after all), and
+typically causing the program to loop at 100% CPU usage.
+
+Unfortunately, the set of errors that cause this issue differs between
+operating systems, there is usually little the app can do to remedy the
+situation, and no known thread-safe method of removing the connection to
+cope with overload is known (to me).
+
+One of the easiest ways to handle this situation is to just ignore it
+- when the program encounters an overload, it will just loop until the
+situation is over. While this is a form of busy waiting, no OS offers an
+event-based way to handle this situation, so it's the best one can do.
+
+A better way to handle the situation is to log any errors other than
+C<EAGAIN> and C<EWOULDBLOCK>, making sure not to flood the log with such
+messages, and continue as usual, which at least gives the user an idea of
+what could be wrong ("raise the ulimit!"). For extra points one could stop
+the C<ev_io> watcher on the listening fd "for a while", which reduces CPU
+usage.
+
+If your program is single-threaded, then you could also keep a dummy file
+descriptor for overload situations (e.g. by opening F</dev/null>), and
+when you run into C<ENFILE> or C<EMFILE>, close it, run C<accept>,
+close that fd, and create a new dummy fd. This will gracefully refuse
+clients under typical overload conditions.
+
+The last way to handle it is to simply log the error and C<exit>, as
+is often done with C<malloc> failures, but this results in an easy
+opportunity for a DoS attack.
+
+=head3 Watcher-Specific Functions
+
+=over 4
+
+=item ev_io_init (ev_io *, callback, int fd, int events)
+
+=item ev_io_set (ev_io *, int fd, int events)
+
+Configures an C<ev_io> watcher. The C<fd> is the file descriptor to
+receive events for and C<events> is either C<EV_READ>, C<EV_WRITE>, both
+C<EV_READ | EV_WRITE> or C<0>, to express the desire to receive the given
+events.
+
+Note that setting the C<events> to C<0> and starting the watcher is
+supported, but not specially optimized - if your program sometimes happens
+to generate this combination this is fine, but if it is easy to avoid
+starting an io watcher watching for no events you should do so.
+
+=item ev_io_modify (ev_io *, int events)
+
+Similar to C<ev_io_set>, but only changes the requested events. Using this
+might be faster with some backends, as libev can assume that the C<fd>
+still refers to the same underlying file description, something it cannot
+do when using C<ev_io_set>.
+
+=item int fd [no-modify]
+
+The file descriptor being watched. While it can be read at any time, you
+must not modify this member even when the watcher is stopped - always use
+C<ev_io_set> for that.
+
+=item int events [no-modify]
+
+The set of events the fd is being watched for, among other flags. Remember
+that this is a bit set - to test for C<EV_READ>, use C<< w->events &
+EV_READ >>, and similarly for C<EV_WRITE>.
+
+As with C<fd>, you must not modify this member even when the watcher is
+stopped, always use C<ev_io_set> or C<ev_io_modify> for that.
+
+=back
+
+=head3 Examples
+
+Example: Call C<stdin_readable_cb> when STDIN_FILENO has become, well
+readable, but only once. Since it is likely line-buffered, you could
+attempt to read a whole line in the callback.
+
+ static void
+ stdin_readable_cb (struct ev_loop *loop, ev_io *w, int revents)
+ {
+ ev_io_stop (loop, w);
+ .. read from stdin here (or from w->fd) and handle any I/O errors
+ }
+
+ ...
+ struct ev_loop *loop = ev_default_init (0);
+ ev_io stdin_readable;
+ ev_io_init (&stdin_readable, stdin_readable_cb, STDIN_FILENO, EV_READ);
+ ev_io_start (loop, &stdin_readable);
+ ev_run (loop, 0);
+
+
+=head2 C<ev_timer> - relative and optionally repeating timeouts
+
+Timer watchers are simple relative timers that generate an event after a
+given time, and optionally repeating in regular intervals after that.
+
+The timers are based on real time, that is, if you register an event that
+times out after an hour and you reset your system clock to January last
+year, it will still time out after (roughly) one hour. "Roughly" because
+detecting time jumps is hard, and some inaccuracies are unavoidable (the
+monotonic clock option helps a lot here).
+
+The callback is guaranteed to be invoked only I<after> its timeout has
+passed (not I<at>, so on systems with very low-resolution clocks this
+might introduce a small delay, see "the special problem of being too
+early", below). If multiple timers become ready during the same loop
+iteration then the ones with earlier time-out values are invoked before
+ones of the same priority with later time-out values (but this is no
+longer true when a callback calls C<ev_run> recursively).
+
+=head3 Be smart about timeouts
+
+Many real-world problems involve some kind of timeout, usually for error
+recovery. A typical example is an HTTP request - if the other side hangs,
+you want to raise some error after a while.
+
+What follows are some ways to handle this problem, from obvious and
+inefficient to smart and efficient.
+
+In the following, a 60 second activity timeout is assumed - a timeout that
+gets reset to 60 seconds each time there is activity (e.g. each time some
+data or other life sign was received).
+
+=over 4
+
+=item 1. Use a timer and stop, reinitialise and start it on activity.
+
+This is the most obvious, but not the most simple way: In the beginning,
+start the watcher:
+
+ ev_timer_init (timer, callback, 60., 0.);
+ ev_timer_start (loop, timer);
+
+Then, each time there is some activity, C<ev_timer_stop> it, initialise it
+and start it again:
+
+ ev_timer_stop (loop, timer);
+ ev_timer_set (timer, 60., 0.);
+ ev_timer_start (loop, timer);
+
+This is relatively simple to implement, but means that each time there is
+some activity, libev will first have to remove the timer from its internal
+data structure and then add it again. Libev tries to be fast, but it's
+still not a constant-time operation.
+
+=item 2. Use a timer and re-start it with C<ev_timer_again> inactivity.
+
+This is the easiest way, and involves using C<ev_timer_again> instead of
+C<ev_timer_start>.
+
+To implement this, configure an C<ev_timer> with a C<repeat> value
+of C<60> and then call C<ev_timer_again> at start and each time you
+successfully read or write some data. If you go into an idle state where
+you do not expect data to travel on the socket, you can C<ev_timer_stop>
+the timer, and C<ev_timer_again> will automatically restart it if need be.
+
+That means you can ignore both the C<ev_timer_start> function and the
+C<after> argument to C<ev_timer_set>, and only ever use the C<repeat>
+member and C<ev_timer_again>.
+
+At start:
+
+ ev_init (timer, callback);
+ timer->repeat = 60.;
+ ev_timer_again (loop, timer);
+
+Each time there is some activity:
+
+ ev_timer_again (loop, timer);
+
+It is even possible to change the time-out on the fly, regardless of
+whether the watcher is active or not:
+
+ timer->repeat = 30.;
+ ev_timer_again (loop, timer);
+
+This is slightly more efficient then stopping/starting the timer each time
+you want to modify its timeout value, as libev does not have to completely
+remove and re-insert the timer from/into its internal data structure.
+
+It is, however, even simpler than the "obvious" way to do it.
+
+=item 3. Let the timer time out, but then re-arm it as required.
+
+This method is more tricky, but usually most efficient: Most timeouts are
+relatively long compared to the intervals between other activity - in
+our example, within 60 seconds, there are usually many I/O events with
+associated activity resets.
+
+In this case, it would be more efficient to leave the C<ev_timer> alone,
+but remember the time of last activity, and check for a real timeout only
+within the callback:
+
+ ev_tstamp timeout = 60.;
+ ev_tstamp last_activity; // time of last activity
+ ev_timer timer;
+
+ static void
+ callback (EV_P_ ev_timer *w, int revents)
+ {
+ // calculate when the timeout would happen
+ ev_tstamp after = last_activity - ev_now (EV_A) + timeout;
+
+ // if negative, it means we the timeout already occurred
+ if (after < 0.)
+ {
+ // timeout occurred, take action
+ }
+ else
+ {
+ // callback was invoked, but there was some recent
+ // activity. simply restart the timer to time out
+ // after "after" seconds, which is the earliest time
+ // the timeout can occur.
+ ev_timer_set (w, after, 0.);
+ ev_timer_start (EV_A_ w);
+ }
+ }
+
+To summarise the callback: first calculate in how many seconds the
+timeout will occur (by calculating the absolute time when it would occur,
+C<last_activity + timeout>, and subtracting the current time, C<ev_now
+(EV_A)> from that).
+
+If this value is negative, then we are already past the timeout, i.e. we
+timed out, and need to do whatever is needed in this case.
+
+Otherwise, we now the earliest time at which the timeout would trigger,
+and simply start the timer with this timeout value.
+
+In other words, each time the callback is invoked it will check whether
+the timeout occurred. If not, it will simply reschedule itself to check
+again at the earliest time it could time out. Rinse. Repeat.
+
+This scheme causes more callback invocations (about one every 60 seconds
+minus half the average time between activity), but virtually no calls to
+libev to change the timeout.
+
+To start the machinery, simply initialise the watcher and set
+C<last_activity> to the current time (meaning there was some activity just
+now), then call the callback, which will "do the right thing" and start
+the timer:
+
+ last_activity = ev_now (EV_A);
+ ev_init (&timer, callback);
+ callback (EV_A_ &timer, 0);
+
+When there is some activity, simply store the current time in
+C<last_activity>, no libev calls at all:
+
+ if (activity detected)
+ last_activity = ev_now (EV_A);
+
+When your timeout value changes, then the timeout can be changed by simply
+providing a new value, stopping the timer and calling the callback, which
+will again do the right thing (for example, time out immediately :).
+
+ timeout = new_value;
+ ev_timer_stop (EV_A_ &timer);
+ callback (EV_A_ &timer, 0);
+
+This technique is slightly more complex, but in most cases where the
+time-out is unlikely to be triggered, much more efficient.
+
+=item 4. Wee, just use a double-linked list for your timeouts.
+
+If there is not one request, but many thousands (millions...), all
+employing some kind of timeout with the same timeout value, then one can
+do even better:
+
+When starting the timeout, calculate the timeout value and put the timeout
+at the I<end> of the list.
+
+Then use an C<ev_timer> to fire when the timeout at the I<beginning> of
+the list is expected to fire (for example, using the technique #3).
+
+When there is some activity, remove the timer from the list, recalculate
+the timeout, append it to the end of the list again, and make sure to
+update the C<ev_timer> if it was taken from the beginning of the list.
+
+This way, one can manage an unlimited number of timeouts in O(1) time for
+starting, stopping and updating the timers, at the expense of a major
+complication, and having to use a constant timeout. The constant timeout
+ensures that the list stays sorted.
+
+=back
+
+So which method the best?
+
+Method #2 is a simple no-brain-required solution that is adequate in most
+situations. Method #3 requires a bit more thinking, but handles many cases
+better, and isn't very complicated either. In most case, choosing either
+one is fine, with #3 being better in typical situations.
+
+Method #1 is almost always a bad idea, and buys you nothing. Method #4 is
+rather complicated, but extremely efficient, something that really pays
+off after the first million or so of active timers, i.e. it's usually
+overkill :)
+
+=head3 The special problem of being too early
+
+If you ask a timer to call your callback after three seconds, then
+you expect it to be invoked after three seconds - but of course, this
+cannot be guaranteed to infinite precision. Less obviously, it cannot be
+guaranteed to any precision by libev - imagine somebody suspending the
+process with a STOP signal for a few hours for example.
+
+So, libev tries to invoke your callback as soon as possible I<after> the
+delay has occurred, but cannot guarantee this.
+
+A less obvious failure mode is calling your callback too early: many event
+loops compare timestamps with a "elapsed delay >= requested delay", but
+this can cause your callback to be invoked much earlier than you would
+expect.
+
+To see why, imagine a system with a clock that only offers full second
+resolution (think windows if you can't come up with a broken enough OS
+yourself). If you schedule a one-second timer at the time 500.9, then the
+event loop will schedule your timeout to elapse at a system time of 500
+(500.9 truncated to the resolution) + 1, or 501.
+
+If an event library looks at the timeout 0.1s later, it will see "501 >=
+501" and invoke the callback 0.1s after it was started, even though a
+one-second delay was requested - this is being "too early", despite best
+intentions.
+
+This is the reason why libev will never invoke the callback if the elapsed
+delay equals the requested delay, but only when the elapsed delay is
+larger than the requested delay. In the example above, libev would only invoke
+the callback at system time 502, or 1.1s after the timer was started.
+
+So, while libev cannot guarantee that your callback will be invoked
+exactly when requested, it I<can> and I<does> guarantee that the requested
+delay has actually elapsed, or in other words, it always errs on the "too
+late" side of things.
+
+=head3 The special problem of time updates
+
+Establishing the current time is a costly operation (it usually takes
+at least one system call): EV therefore updates its idea of the current
+time only before and after C<ev_run> collects new events, which causes a
+growing difference between C<ev_now ()> and C<ev_time ()> when handling
+lots of events in one iteration.
+
+The relative timeouts are calculated relative to the C<ev_now ()>
+time. This is usually the right thing as this timestamp refers to the time
+of the event triggering whatever timeout you are modifying/starting. If
+you suspect event processing to be delayed and you I<need> to base the
+timeout on the current time, use something like the following to adjust
+for it:
+
+ ev_timer_set (&timer, after + (ev_time () - ev_now ()), 0.);
+
+If the event loop is suspended for a long time, you can also force an
+update of the time returned by C<ev_now ()> by calling C<ev_now_update
+()>, although that will push the event time of all outstanding events
+further into the future.
+
+=head3 The special problem of unsynchronised clocks
+
+Modern systems have a variety of clocks - libev itself uses the normal
+"wall clock" clock and, if available, the monotonic clock (to avoid time
+jumps).
+
+Neither of these clocks is synchronised with each other or any other clock
+on the system, so C<ev_time ()> might return a considerably different time
+than C<gettimeofday ()> or C<time ()>. On a GNU/Linux system, for example,
+a call to C<gettimeofday> might return a second count that is one higher
+than a directly following call to C<time>.
+
+The moral of this is to only compare libev-related timestamps with
+C<ev_time ()> and C<ev_now ()>, at least if you want better precision than
+a second or so.
+
+One more problem arises due to this lack of synchronisation: if libev uses
+the system monotonic clock and you compare timestamps from C<ev_time>
+or C<ev_now> from when you started your timer and when your callback is
+invoked, you will find that sometimes the callback is a bit "early".
+
+This is because C<ev_timer>s work in real time, not wall clock time, so
+libev makes sure your callback is not invoked before the delay happened,
+I<measured according to the real time>, not the system clock.
+
+If your timeouts are based on a physical timescale (e.g. "time out this
+connection after 100 seconds") then this shouldn't bother you as it is
+exactly the right behaviour.
+
+If you want to compare wall clock/system timestamps to your timers, then
+you need to use C<ev_periodic>s, as these are based on the wall clock
+time, where your comparisons will always generate correct results.
+
+=head3 The special problems of suspended animation
+
+When you leave the server world it is quite customary to hit machines that
+can suspend/hibernate - what happens to the clocks during such a suspend?
+
+Some quick tests made with a Linux 2.6.28 indicate that a suspend freezes
+all processes, while the clocks (C<times>, C<CLOCK_MONOTONIC>) continue
+to run until the system is suspended, but they will not advance while the
+system is suspended. That means, on resume, it will be as if the program
+was frozen for a few seconds, but the suspend time will not be counted
+towards C<ev_timer> when a monotonic clock source is used. The real time
+clock advanced as expected, but if it is used as sole clocksource, then a
+long suspend would be detected as a time jump by libev, and timers would
+be adjusted accordingly.
+
+I would not be surprised to see different behaviour in different between
+operating systems, OS versions or even different hardware.
+
+The other form of suspend (job control, or sending a SIGSTOP) will see a
+time jump in the monotonic clocks and the realtime clock. If the program
+is suspended for a very long time, and monotonic clock sources are in use,
+then you can expect C<ev_timer>s to expire as the full suspension time
+will be counted towards the timers. When no monotonic clock source is in
+use, then libev will again assume a timejump and adjust accordingly.
+
+It might be beneficial for this latter case to call C<ev_suspend>
+and C<ev_resume> in code that handles C<SIGTSTP>, to at least get
+deterministic behaviour in this case (you can do nothing against
+C<SIGSTOP>).
+
+=head3 Watcher-Specific Functions and Data Members
+
+=over 4
+
+=item ev_timer_init (ev_timer *, callback, ev_tstamp after, ev_tstamp repeat)
+
+=item ev_timer_set (ev_timer *, ev_tstamp after, ev_tstamp repeat)
+
+Configure the timer to trigger after C<after> seconds (fractional and
+negative values are supported). If C<repeat> is C<0.>, then it will
+automatically be stopped once the timeout is reached. If it is positive,
+then the timer will automatically be configured to trigger again C<repeat>
+seconds later, again, and again, until stopped manually.
+
+The timer itself will do a best-effort at avoiding drift, that is, if
+you configure a timer to trigger every 10 seconds, then it will normally
+trigger at exactly 10 second intervals. If, however, your program cannot
+keep up with the timer (because it takes longer than those 10 seconds to
+do stuff) the timer will not fire more than once per event loop iteration.
+
+=item ev_timer_again (loop, ev_timer *)
+
+This will act as if the timer timed out, and restarts it again if it is
+repeating. It basically works like calling C<ev_timer_stop>, updating the
+timeout to the C<repeat> value and calling C<ev_timer_start>.
+
+The exact semantics are as in the following rules, all of which will be
+applied to the watcher:
+
+=over 4
+
+=item If the timer is pending, the pending status is always cleared.
+
+=item If the timer is started but non-repeating, stop it (as if it timed
+out, without invoking it).
+
+=item If the timer is repeating, make the C<repeat> value the new timeout
+and start the timer, if necessary.
+
+=back
+
+This sounds a bit complicated, see L</Be smart about timeouts>, above, for a
+usage example.
+
+=item ev_tstamp ev_timer_remaining (loop, ev_timer *)
+
+Returns the remaining time until a timer fires. If the timer is active,
+then this time is relative to the current event loop time, otherwise it's
+the timeout value currently configured.
+
+That is, after an C<ev_timer_set (w, 5, 7)>, C<ev_timer_remaining> returns
+C<5>. When the timer is started and one second passes, C<ev_timer_remaining>
+will return C<4>. When the timer expires and is restarted, it will return
+roughly C<7> (likely slightly less as callback invocation takes some time,
+too), and so on.
+
+=item ev_tstamp repeat [read-write]
+
+The current C<repeat> value. Will be used each time the watcher times out
+or C<ev_timer_again> is called, and determines the next timeout (if any),
+which is also when any modifications are taken into account.
+
+=back
+
+=head3 Examples
+
+Example: Create a timer that fires after 60 seconds.
+
+ static void
+ one_minute_cb (struct ev_loop *loop, ev_timer *w, int revents)
+ {
+ .. one minute over, w is actually stopped right here
+ }
+
+ ev_timer mytimer;
+ ev_timer_init (&mytimer, one_minute_cb, 60., 0.);
+ ev_timer_start (loop, &mytimer);
+
+Example: Create a timeout timer that times out after 10 seconds of
+inactivity.
+
+ static void
+ timeout_cb (struct ev_loop *loop, ev_timer *w, int revents)
+ {
+ .. ten seconds without any activity
+ }
+
+ ev_timer mytimer;
+ ev_timer_init (&mytimer, timeout_cb, 0., 10.); /* note, only repeat used */
+ ev_timer_again (&mytimer); /* start timer */
+ ev_run (loop, 0);
+
+ // and in some piece of code that gets executed on any "activity":
+ // reset the timeout to start ticking again at 10 seconds
+ ev_timer_again (&mytimer);
+
+
+=head2 C<ev_periodic> - to cron or not to cron?
+
+Periodic watchers are also timers of a kind, but they are very versatile
+(and unfortunately a bit complex).
+
+Unlike C<ev_timer>, periodic watchers are not based on real time (or
+relative time, the physical time that passes) but on wall clock time
+(absolute time, the thing you can read on your calendar or clock). The
+difference is that wall clock time can run faster or slower than real
+time, and time jumps are not uncommon (e.g. when you adjust your
+wrist-watch).
+
+You can tell a periodic watcher to trigger after some specific point
+in time: for example, if you tell a periodic watcher to trigger "in 10
+seconds" (by specifying e.g. C<ev_now () + 10.>, that is, an absolute time
+not a delay) and then reset your system clock to January of the previous
+year, then it will take a year or more to trigger the event (unlike an
+C<ev_timer>, which would still trigger roughly 10 seconds after starting
+it, as it uses a relative timeout).
+
+C<ev_periodic> watchers can also be used to implement vastly more complex
+timers, such as triggering an event on each "midnight, local time", or
+other complicated rules. This cannot easily be done with C<ev_timer>
+watchers, as those cannot react to time jumps.
+
+As with timers, the callback is guaranteed to be invoked only when the
+point in time where it is supposed to trigger has passed. If multiple
+timers become ready during the same loop iteration then the ones with
+earlier time-out values are invoked before ones with later time-out values
+(but this is no longer true when a callback calls C<ev_run> recursively).
+
+=head3 Watcher-Specific Functions and Data Members
+
+=over 4
+
+=item ev_periodic_init (ev_periodic *, callback, ev_tstamp offset, ev_tstamp interval, reschedule_cb)
+
+=item ev_periodic_set (ev_periodic *, ev_tstamp offset, ev_tstamp interval, reschedule_cb)
+
+Lots of arguments, let's sort it out... There are basically three modes of
+operation, and we will explain them from simplest to most complex:
+
+=over 4
+
+=item * absolute timer (offset = absolute time, interval = 0, reschedule_cb = 0)
+
+In this configuration the watcher triggers an event after the wall clock
+time C<offset> has passed. It will not repeat and will not adjust when a
+time jump occurs, that is, if it is to be run at January 1st 2011 then it
+will be stopped and invoked when the system clock reaches or surpasses
+this point in time.
+
+=item * repeating interval timer (offset = offset within interval, interval > 0, reschedule_cb = 0)
+
+In this mode the watcher will always be scheduled to time out at the next
+C<offset + N * interval> time (for some integer N, which can also be
+negative) and then repeat, regardless of any time jumps. The C<offset>
+argument is merely an offset into the C<interval> periods.
+
+This can be used to create timers that do not drift with respect to the
+system clock, for example, here is an C<ev_periodic> that triggers each
+hour, on the hour (with respect to UTC):
+
+ ev_periodic_set (&periodic, 0., 3600., 0);
+
+This doesn't mean there will always be 3600 seconds in between triggers,
+but only that the callback will be called when the system time shows a
+full hour (UTC), or more correctly, when the system time is evenly divisible
+by 3600.
+
+Another way to think about it (for the mathematically inclined) is that
+C<ev_periodic> will try to run the callback in this mode at the next possible
+time where C<time = offset (mod interval)>, regardless of any time jumps.
+
+The C<interval> I<MUST> be positive, and for numerical stability, the
+interval value should be higher than C<1/8192> (which is around 100
+microseconds) and C<offset> should be higher than C<0> and should have
+at most a similar magnitude as the current time (say, within a factor of
+ten). Typical values for offset are, in fact, C<0> or something between
+C<0> and C<interval>, which is also the recommended range.
+
+Note also that there is an upper limit to how often a timer can fire (CPU
+speed for example), so if C<interval> is very small then timing stability
+will of course deteriorate. Libev itself tries to be exact to be about one
+millisecond (if the OS supports it and the machine is fast enough).
+
+=item * manual reschedule mode (offset ignored, interval ignored, reschedule_cb = callback)
+
+In this mode the values for C<interval> and C<offset> are both being
+ignored. Instead, each time the periodic watcher gets scheduled, the
+reschedule callback will be called with the watcher as first, and the
+current time as second argument.
+
+NOTE: I<This callback MUST NOT stop or destroy any periodic watcher, ever,
+or make ANY other event loop modifications whatsoever, unless explicitly
+allowed by documentation here>.
+
+If you need to stop it, return C<now + 1e30> (or so, fudge fudge) and stop
+it afterwards (e.g. by starting an C<ev_prepare> watcher, which is the
+only event loop modification you are allowed to do).
+
+The callback prototype is C<ev_tstamp (*reschedule_cb)(ev_periodic
+*w, ev_tstamp now)>, e.g.:
+
+ static ev_tstamp
+ my_rescheduler (ev_periodic *w, ev_tstamp now)
+ {
+ return now + 60.;
+ }
+
+It must return the next time to trigger, based on the passed time value
+(that is, the lowest time value larger than to the second argument). It
+will usually be called just before the callback will be triggered, but
+might be called at other times, too.
+
+NOTE: I<< This callback must always return a time that is higher than or
+equal to the passed C<now> value >>.
+
+This can be used to create very complex timers, such as a timer that
+triggers on "next midnight, local time". To do this, you would calculate
+the next midnight after C<now> and return the timestamp value for
+this. Here is a (completely untested, no error checking) example on how to
+do this:
+
+ #include <time.h>
+
+ static ev_tstamp
+ my_rescheduler (ev_periodic *w, ev_tstamp now)
+ {
+ time_t tnow = (time_t)now;
+ struct tm tm;
+ localtime_r (&tnow, &tm);
+
+ tm.tm_sec = tm.tm_min = tm.tm_hour = 0; // midnight current day
+ ++tm.tm_mday; // midnight next day
+
+ return mktime (&tm);
+ }
+
+Note: this code might run into trouble on days that have more then two
+midnights (beginning and end).
+
+=back
+
+=item ev_periodic_again (loop, ev_periodic *)
+
+Simply stops and restarts the periodic watcher again. This is only useful
+when you changed some parameters or the reschedule callback would return
+a different time than the last time it was called (e.g. in a crond like
+program when the crontabs have changed).
+
+=item ev_tstamp ev_periodic_at (ev_periodic *)
+
+When active, returns the absolute time that the watcher is supposed
+to trigger next. This is not the same as the C<offset> argument to
+C<ev_periodic_set>, but indeed works even in interval and manual
+rescheduling modes.
+
+=item ev_tstamp offset [read-write]
+
+When repeating, this contains the offset value, otherwise this is the
+absolute point in time (the C<offset> value passed to C<ev_periodic_set>,
+although libev might modify this value for better numerical stability).
+
+Can be modified any time, but changes only take effect when the periodic
+timer fires or C<ev_periodic_again> is being called.
+
+=item ev_tstamp interval [read-write]
+
+The current interval value. Can be modified any time, but changes only
+take effect when the periodic timer fires or C<ev_periodic_again> is being
+called.
+
+=item ev_tstamp (*reschedule_cb)(ev_periodic *w, ev_tstamp now) [read-write]
+
+The current reschedule callback, or C<0>, if this functionality is
+switched off. Can be changed any time, but changes only take effect when
+the periodic timer fires or C<ev_periodic_again> is being called.
+
+=back
+
+=head3 Examples
+
+Example: Call a callback every hour, or, more precisely, whenever the
+system time is divisible by 3600. The callback invocation times have
+potentially a lot of jitter, but good long-term stability.
+
+ static void
+ clock_cb (struct ev_loop *loop, ev_periodic *w, int revents)
+ {
+ ... its now a full hour (UTC, or TAI or whatever your clock follows)
+ }
+
+ ev_periodic hourly_tick;
+ ev_periodic_init (&hourly_tick, clock_cb, 0., 3600., 0);
+ ev_periodic_start (loop, &hourly_tick);
+
+Example: The same as above, but use a reschedule callback to do it:
+
+ #include <math.h>
+
+ static ev_tstamp
+ my_scheduler_cb (ev_periodic *w, ev_tstamp now)
+ {
+ return now + (3600. - fmod (now, 3600.));
+ }
+
+ ev_periodic_init (&hourly_tick, clock_cb, 0., 0., my_scheduler_cb);
+
+Example: Call a callback every hour, starting now:
+
+ ev_periodic hourly_tick;
+ ev_periodic_init (&hourly_tick, clock_cb,
+ fmod (ev_now (loop), 3600.), 3600., 0);
+ ev_periodic_start (loop, &hourly_tick);
+
+
+=head2 C<ev_signal> - signal me when a signal gets signalled!
+
+Signal watchers will trigger an event when the process receives a specific
+signal one or more times. Even though signals are very asynchronous, libev
+will try its best to deliver signals synchronously, i.e. as part of the
+normal event processing, like any other event.
+
+If you want signals to be delivered truly asynchronously, just use
+C<sigaction> as you would do without libev and forget about sharing
+the signal. You can even use C<ev_async> from a signal handler to
+synchronously wake up an event loop.
+
+You can configure as many watchers as you like for the same signal, but
+only within the same loop, i.e. you can watch for C<SIGINT> in your
+default loop and for C<SIGIO> in another loop, but you cannot watch for
+C<SIGINT> in both the default loop and another loop at the same time. At
+the moment, C<SIGCHLD> is permanently tied to the default loop.
+
+Only after the first watcher for a signal is started will libev actually
+register something with the kernel. It thus coexists with your own signal
+handlers as long as you don't register any with libev for the same signal.
+
+If possible and supported, libev will install its handlers with
+C<SA_RESTART> (or equivalent) behaviour enabled, so system calls should
+not be unduly interrupted. If you have a problem with system calls getting
+interrupted by signals you can block all signals in an C<ev_check> watcher
+and unblock them in an C<ev_prepare> watcher.
+
+=head3 The special problem of inheritance over fork/execve/pthread_create
+
+Both the signal mask (C<sigprocmask>) and the signal disposition
+(C<sigaction>) are unspecified after starting a signal watcher (and after
+stopping it again), that is, libev might or might not block the signal,
+and might or might not set or restore the installed signal handler (but
+see C<EVFLAG_NOSIGMASK>).
+
+While this does not matter for the signal disposition (libev never
+sets signals to C<SIG_IGN>, so handlers will be reset to C<SIG_DFL> on
+C<execve>), this matters for the signal mask: many programs do not expect
+certain signals to be blocked.
+
+This means that before calling C<exec> (from the child) you should reset
+the signal mask to whatever "default" you expect (all clear is a good
+choice usually).
+
+The simplest way to ensure that the signal mask is reset in the child is
+to install a fork handler with C<pthread_atfork> that resets it. That will
+catch fork calls done by libraries (such as the libc) as well.
+
+In current versions of libev, the signal will not be blocked indefinitely
+unless you use the C<signalfd> API (C<EV_SIGNALFD>). While this reduces
+the window of opportunity for problems, it will not go away, as libev
+I<has> to modify the signal mask, at least temporarily.
+
+So I can't stress this enough: I<If you do not reset your signal mask when
+you expect it to be empty, you have a race condition in your code>. This
+is not a libev-specific thing, this is true for most event libraries.
+
+=head3 The special problem of threads signal handling
+
+POSIX threads has problematic signal handling semantics, specifically,
+a lot of functionality (sigfd, sigwait etc.) only really works if all
+threads in a process block signals, which is hard to achieve.
+
+When you want to use sigwait (or mix libev signal handling with your own
+for the same signals), you can tackle this problem by globally blocking
+all signals before creating any threads (or creating them with a fully set
+sigprocmask) and also specifying the C<EVFLAG_NOSIGMASK> when creating
+loops. Then designate one thread as "signal receiver thread" which handles
+these signals. You can pass on any signals that libev might be interested
+in by calling C<ev_feed_signal>.
+
+=head3 Watcher-Specific Functions and Data Members
+
+=over 4
+
+=item ev_signal_init (ev_signal *, callback, int signum)
+
+=item ev_signal_set (ev_signal *, int signum)
+
+Configures the watcher to trigger on the given signal number (usually one
+of the C<SIGxxx> constants).
+
+=item int signum [read-only]
+
+The signal the watcher watches out for.
+
+=back
+
+=head3 Examples
+
+Example: Try to exit cleanly on SIGINT.
+
+ static void
+ sigint_cb (struct ev_loop *loop, ev_signal *w, int revents)
+ {
+ ev_break (loop, EVBREAK_ALL);
+ }
+
+ ev_signal signal_watcher;
+ ev_signal_init (&signal_watcher, sigint_cb, SIGINT);
+ ev_signal_start (loop, &signal_watcher);
+
+
+=head2 C<ev_child> - watch out for process status changes
+
+Child watchers trigger when your process receives a SIGCHLD in response to
+some child status changes (most typically when a child of yours dies or
+exits). It is permissible to install a child watcher I<after> the child
+has been forked (which implies it might have already exited), as long
+as the event loop isn't entered (or is continued from a watcher), i.e.,
+forking and then immediately registering a watcher for the child is fine,
+but forking and registering a watcher a few event loop iterations later or
+in the next callback invocation is not.
+
+Only the default event loop is capable of handling signals, and therefore
+you can only register child watchers in the default event loop.
+
+Due to some design glitches inside libev, child watchers will always be
+handled at maximum priority (their priority is set to C<EV_MAXPRI> by
+libev)
+
+=head3 Process Interaction
+
+Libev grabs C<SIGCHLD> as soon as the default event loop is
+initialised. This is necessary to guarantee proper behaviour even if the
+first child watcher is started after the child exits. The occurrence
+of C<SIGCHLD> is recorded asynchronously, but child reaping is done
+synchronously as part of the event loop processing. Libev always reaps all
+children, even ones not watched.
+
+=head3 Overriding the Built-In Processing
+
+Libev offers no special support for overriding the built-in child
+processing, but if your application collides with libev's default child
+handler, you can override it easily by installing your own handler for
+C<SIGCHLD> after initialising the default loop, and making sure the
+default loop never gets destroyed. You are encouraged, however, to use an
+event-based approach to child reaping and thus use libev's support for
+that, so other libev users can use C<ev_child> watchers freely.
+
+=head3 Stopping the Child Watcher
+
+Currently, the child watcher never gets stopped, even when the
+child terminates, so normally one needs to stop the watcher in the
+callback. Future versions of libev might stop the watcher automatically
+when a child exit is detected (calling C<ev_child_stop> twice is not a
+problem).
+
+=head3 Watcher-Specific Functions and Data Members
+
+=over 4
+
+=item ev_child_init (ev_child *, callback, int pid, int trace)
+
+=item ev_child_set (ev_child *, int pid, int trace)
+
+Configures the watcher to wait for status changes of process C<pid> (or
+I<any> process if C<pid> is specified as C<0>). The callback can look
+at the C<rstatus> member of the C<ev_child> watcher structure to see
+the status word (use the macros from C<sys/wait.h> and see your systems
+C<waitpid> documentation). The C<rpid> member contains the pid of the
+process causing the status change. C<trace> must be either C<0> (only
+activate the watcher when the process terminates) or C<1> (additionally
+activate the watcher when the process is stopped or continued).
+
+=item int pid [read-only]
+
+The process id this watcher watches out for, or C<0>, meaning any process id.
+
+=item int rpid [read-write]
+
+The process id that detected a status change.
+
+=item int rstatus [read-write]
+
+The process exit/trace status caused by C<rpid> (see your systems
+C<waitpid> and C<sys/wait.h> documentation for details).
+
+=back
+
+=head3 Examples
+
+Example: C<fork()> a new process and install a child handler to wait for
+its completion.
+
+ ev_child cw;
+
+ static void
+ child_cb (EV_P_ ev_child *w, int revents)
+ {
+ ev_child_stop (EV_A_ w);
+ printf ("process %d exited with status %x\n", w->rpid, w->rstatus);
+ }
+
+ pid_t pid = fork ();
+
+ if (pid < 0)
+ // error
+ else if (pid == 0)
+ {
+ // the forked child executes here
+ exit (1);
+ }
+ else
+ {
+ ev_child_init (&cw, child_cb, pid, 0);
+ ev_child_start (EV_DEFAULT_ &cw);
+ }
+
+
+=head2 C<ev_stat> - did the file attributes just change?
+
+This watches a file system path for attribute changes. That is, it calls
+C<stat> on that path in regular intervals (or when the OS says it changed)
+and sees if it changed compared to the last time, invoking the callback
+if it did. Starting the watcher C<stat>'s the file, so only changes that
+happen after the watcher has been started will be reported.
+
+The path does not need to exist: changing from "path exists" to "path does
+not exist" is a status change like any other. The condition "path does not
+exist" (or more correctly "path cannot be stat'ed") is signified by the
+C<st_nlink> field being zero (which is otherwise always forced to be at
+least one) and all the other fields of the stat buffer having unspecified
+contents.
+
+The path I<must not> end in a slash or contain special components such as
+C<.> or C<..>. The path I<should> be absolute: If it is relative and
+your working directory changes, then the behaviour is undefined.
+
+Since there is no portable change notification interface available, the
+portable implementation simply calls C<stat(2)> regularly on the path
+to see if it changed somehow. You can specify a recommended polling
+interval for this case. If you specify a polling interval of C<0> (highly
+recommended!) then a I<suitable, unspecified default> value will be used
+(which you can expect to be around five seconds, although this might
+change dynamically). Libev will also impose a minimum interval which is
+currently around C<0.1>, but that's usually overkill.
+
+This watcher type is not meant for massive numbers of stat watchers,
+as even with OS-supported change notifications, this can be
+resource-intensive.
+
+At the time of this writing, the only OS-specific interface implemented
+is the Linux inotify interface (implementing kqueue support is left as an
+exercise for the reader. Note, however, that the author sees no way of
+implementing C<ev_stat> semantics with kqueue, except as a hint).
+
+=head3 ABI Issues (Largefile Support)
+
+Libev by default (unless the user overrides this) uses the default
+compilation environment, which means that on systems with large file
+support disabled by default, you get the 32 bit version of the stat
+structure. When using the library from programs that change the ABI to
+use 64 bit file offsets the programs will fail. In that case you have to
+compile libev with the same flags to get binary compatibility. This is
+obviously the case with any flags that change the ABI, but the problem is
+most noticeably displayed with ev_stat and large file support.
+
+The solution for this is to lobby your distribution maker to make large
+file interfaces available by default (as e.g. FreeBSD does) and not
+optional. Libev cannot simply switch on large file support because it has
+to exchange stat structures with application programs compiled using the
+default compilation environment.
+
+=head3 Inotify and Kqueue
+
+When C<inotify (7)> support has been compiled into libev and present at
+runtime, it will be used to speed up change detection where possible. The
+inotify descriptor will be created lazily when the first C<ev_stat>
+watcher is being started.
+
+Inotify presence does not change the semantics of C<ev_stat> watchers
+except that changes might be detected earlier, and in some cases, to avoid
+making regular C<stat> calls. Even in the presence of inotify support
+there are many cases where libev has to resort to regular C<stat> polling,
+but as long as kernel 2.6.25 or newer is used (2.6.24 and older have too
+many bugs), the path exists (i.e. stat succeeds), and the path resides on
+a local filesystem (libev currently assumes only ext2/3, jfs, reiserfs and
+xfs are fully working) libev usually gets away without polling.
+
+There is no support for kqueue, as apparently it cannot be used to
+implement this functionality, due to the requirement of having a file
+descriptor open on the object at all times, and detecting renames, unlinks
+etc. is difficult.
+
+=head3 C<stat ()> is a synchronous operation
+
+Libev doesn't normally do any kind of I/O itself, and so is not blocking
+the process. The exception are C<ev_stat> watchers - those call C<stat
+()>, which is a synchronous operation.
+
+For local paths, this usually doesn't matter: unless the system is very
+busy or the intervals between stat's are large, a stat call will be fast,
+as the path data is usually in memory already (except when starting the
+watcher).
+
+For networked file systems, calling C<stat ()> can block an indefinite
+time due to network issues, and even under good conditions, a stat call
+often takes multiple milliseconds.
+
+Therefore, it is best to avoid using C<ev_stat> watchers on networked
+paths, although this is fully supported by libev.
+
+=head3 The special problem of stat time resolution
+
+The C<stat ()> system call only supports full-second resolution portably,
+and even on systems where the resolution is higher, most file systems
+still only support whole seconds.
+
+That means that, if the time is the only thing that changes, you can
+easily miss updates: on the first update, C<ev_stat> detects a change and
+calls your callback, which does something. When there is another update
+within the same second, C<ev_stat> will be unable to detect unless the
+stat data does change in other ways (e.g. file size).
+
+The solution to this is to delay acting on a change for slightly more
+than a second (or till slightly after the next full second boundary), using
+a roughly one-second-delay C<ev_timer> (e.g. C<ev_timer_set (w, 0., 1.02);
+ev_timer_again (loop, w)>).
+
+The C<.02> offset is added to work around small timing inconsistencies
+of some operating systems (where the second counter of the current time
+might be be delayed. One such system is the Linux kernel, where a call to
+C<gettimeofday> might return a timestamp with a full second later than
+a subsequent C<time> call - if the equivalent of C<time ()> is used to
+update file times then there will be a small window where the kernel uses
+the previous second to update file times but libev might already execute
+the timer callback).
+
+=head3 Watcher-Specific Functions and Data Members
+
+=over 4
+
+=item ev_stat_init (ev_stat *, callback, const char *path, ev_tstamp interval)
+
+=item ev_stat_set (ev_stat *, const char *path, ev_tstamp interval)
+
+Configures the watcher to wait for status changes of the given
+C<path>. The C<interval> is a hint on how quickly a change is expected to
+be detected and should normally be specified as C<0> to let libev choose
+a suitable value. The memory pointed to by C<path> must point to the same
+path for as long as the watcher is active.
+
+The callback will receive an C<EV_STAT> event when a change was detected,
+relative to the attributes at the time the watcher was started (or the
+last change was detected).
+
+=item ev_stat_stat (loop, ev_stat *)
+
+Updates the stat buffer immediately with new values. If you change the
+watched path in your callback, you could call this function to avoid
+detecting this change (while introducing a race condition if you are not
+the only one changing the path). Can also be useful simply to find out the
+new values.
+
+=item ev_statdata attr [read-only]
+
+The most-recently detected attributes of the file. Although the type is
+C<ev_statdata>, this is usually the (or one of the) C<struct stat> types
+suitable for your system, but you can only rely on the POSIX-standardised
+members to be present. If the C<st_nlink> member is C<0>, then there was
+some error while C<stat>ing the file.
+
+=item ev_statdata prev [read-only]
+
+The previous attributes of the file. The callback gets invoked whenever
+C<prev> != C<attr>, or, more precisely, one or more of these members
+differ: C<st_dev>, C<st_ino>, C<st_mode>, C<st_nlink>, C<st_uid>,
+C<st_gid>, C<st_rdev>, C<st_size>, C<st_atime>, C<st_mtime>, C<st_ctime>.
+
+=item ev_tstamp interval [read-only]
+
+The specified interval.
+
+=item const char *path [read-only]
+
+The file system path that is being watched.
+
+=back
+
+=head3 Examples
+
+Example: Watch C</etc/passwd> for attribute changes.
+
+ static void
+ passwd_cb (struct ev_loop *loop, ev_stat *w, int revents)
+ {
+ /* /etc/passwd changed in some way */
+ if (w->attr.st_nlink)
+ {
+ printf ("passwd current size %ld\n", (long)w->attr.st_size);
+ printf ("passwd current atime %ld\n", (long)w->attr.st_mtime);
+ printf ("passwd current mtime %ld\n", (long)w->attr.st_mtime);
+ }
+ else
+ /* you shalt not abuse printf for puts */
+ puts ("wow, /etc/passwd is not there, expect problems. "
+ "if this is windows, they already arrived\n");
+ }
+
+ ...
+ ev_stat passwd;
+
+ ev_stat_init (&passwd, passwd_cb, "/etc/passwd", 0.);
+ ev_stat_start (loop, &passwd);
+
+Example: Like above, but additionally use a one-second delay so we do not
+miss updates (however, frequent updates will delay processing, too, so
+one might do the work both on C<ev_stat> callback invocation I<and> on
+C<ev_timer> callback invocation).
+
+ static ev_stat passwd;
+ static ev_timer timer;
+
+ static void
+ timer_cb (EV_P_ ev_timer *w, int revents)
+ {
+ ev_timer_stop (EV_A_ w);
+
+ /* now it's one second after the most recent passwd change */
+ }
+
+ static void
+ stat_cb (EV_P_ ev_stat *w, int revents)
+ {
+ /* reset the one-second timer */
+ ev_timer_again (EV_A_ &timer);
+ }
+
+ ...
+ ev_stat_init (&passwd, stat_cb, "/etc/passwd", 0.);
+ ev_stat_start (loop, &passwd);
+ ev_timer_init (&timer, timer_cb, 0., 1.02);
+
+
+=head2 C<ev_idle> - when you've got nothing better to do...
+
+Idle watchers trigger events when no other events of the same or higher
+priority are pending (prepare, check and other idle watchers do not count
+as receiving "events").
+
+That is, as long as your process is busy handling sockets or timeouts
+(or even signals, imagine) of the same or higher priority it will not be
+triggered. But when your process is idle (or only lower-priority watchers
+are pending), the idle watchers are being called once per event loop
+iteration - until stopped, that is, or your process receives more events
+and becomes busy again with higher priority stuff.
+
+The most noteworthy effect is that as long as any idle watchers are
+active, the process will not block when waiting for new events.
+
+Apart from keeping your process non-blocking (which is a useful
+effect on its own sometimes), idle watchers are a good place to do
+"pseudo-background processing", or delay processing stuff to after the
+event loop has handled all outstanding events.
+
+=head3 Abusing an C<ev_idle> watcher for its side-effect
+
+As long as there is at least one active idle watcher, libev will never
+sleep unnecessarily. Or in other words, it will loop as fast as possible.
+For this to work, the idle watcher doesn't need to be invoked at all - the
+lowest priority will do.
+
+This mode of operation can be useful together with an C<ev_check> watcher,
+to do something on each event loop iteration - for example to balance load
+between different connections.
+
+See L</Abusing an ev_check watcher for its side-effect> for a longer
+example.
+
+=head3 Watcher-Specific Functions and Data Members
+
+=over 4
+
+=item ev_idle_init (ev_idle *, callback)
+
+Initialises and configures the idle watcher - it has no parameters of any
+kind. There is a C<ev_idle_set> macro, but using it is utterly pointless,
+believe me.
+
+=back
+
+=head3 Examples
+
+Example: Dynamically allocate an C<ev_idle> watcher, start it, and in the
+callback, free it. Also, use no error checking, as usual.
+
+ static void
+ idle_cb (struct ev_loop *loop, ev_idle *w, int revents)
+ {
+ // stop the watcher
+ ev_idle_stop (loop, w);
+
+ // now we can free it
+ free (w);
+
+ // now do something you wanted to do when the program has
+ // no longer anything immediate to do.
+ }
+
+ ev_idle *idle_watcher = malloc (sizeof (ev_idle));
+ ev_idle_init (idle_watcher, idle_cb);
+ ev_idle_start (loop, idle_watcher);
+
+
+=head2 C<ev_prepare> and C<ev_check> - customise your event loop!
+
+Prepare and check watchers are often (but not always) used in pairs:
+prepare watchers get invoked before the process blocks and check watchers
+afterwards.
+
+You I<must not> call C<ev_run> (or similar functions that enter the
+current event loop) or C<ev_loop_fork> from either C<ev_prepare> or
+C<ev_check> watchers. Other loops than the current one are fine,
+however. The rationale behind this is that you do not need to check
+for recursion in those watchers, i.e. the sequence will always be
+C<ev_prepare>, blocking, C<ev_check> so if you have one watcher of each
+kind they will always be called in pairs bracketing the blocking call.
+
+Their main purpose is to integrate other event mechanisms into libev and
+their use is somewhat advanced. They could be used, for example, to track
+variable changes, implement your own watchers, integrate net-snmp or a
+coroutine library and lots more. They are also occasionally useful if
+you cache some data and want to flush it before blocking (for example,
+in X programs you might want to do an C<XFlush ()> in an C<ev_prepare>
+watcher).
+
+This is done by examining in each prepare call which file descriptors
+need to be watched by the other library, registering C<ev_io> watchers
+for them and starting an C<ev_timer> watcher for any timeouts (many
+libraries provide exactly this functionality). Then, in the check watcher,
+you check for any events that occurred (by checking the pending status
+of all watchers and stopping them) and call back into the library. The
+I/O and timer callbacks will never actually be called (but must be valid
+nevertheless, because you never know, you know?).
+
+As another example, the Perl Coro module uses these hooks to integrate
+coroutines into libev programs, by yielding to other active coroutines
+during each prepare and only letting the process block if no coroutines
+are ready to run (it's actually more complicated: it only runs coroutines
+with priority higher than or equal to the event loop and one coroutine
+of lower priority, but only once, using idle watchers to keep the event
+loop from blocking if lower-priority coroutines are active, thus mapping
+low-priority coroutines to idle/background tasks).
+
+When used for this purpose, it is recommended to give C<ev_check> watchers
+highest (C<EV_MAXPRI>) priority, to ensure that they are being run before
+any other watchers after the poll (this doesn't matter for C<ev_prepare>
+watchers).
+
+Also, C<ev_check> watchers (and C<ev_prepare> watchers, too) should not
+activate ("feed") events into libev. While libev fully supports this, they
+might get executed before other C<ev_check> watchers did their job. As
+C<ev_check> watchers are often used to embed other (non-libev) event
+loops those other event loops might be in an unusable state until their
+C<ev_check> watcher ran (always remind yourself to coexist peacefully with
+others).
+
+=head3 Abusing an C<ev_check> watcher for its side-effect
+
+C<ev_check> (and less often also C<ev_prepare>) watchers can also be
+useful because they are called once per event loop iteration. For
+example, if you want to handle a large number of connections fairly, you
+normally only do a bit of work for each active connection, and if there
+is more work to do, you wait for the next event loop iteration, so other
+connections have a chance of making progress.
+
+Using an C<ev_check> watcher is almost enough: it will be called on the
+next event loop iteration. However, that isn't as soon as possible -
+without external events, your C<ev_check> watcher will not be invoked.
+
+This is where C<ev_idle> watchers come in handy - all you need is a
+single global idle watcher that is active as long as you have one active
+C<ev_check> watcher. The C<ev_idle> watcher makes sure the event loop
+will not sleep, and the C<ev_check> watcher makes sure a callback gets
+invoked. Neither watcher alone can do that.
+
+=head3 Watcher-Specific Functions and Data Members
+
+=over 4
+
+=item ev_prepare_init (ev_prepare *, callback)
+
+=item ev_check_init (ev_check *, callback)
+
+Initialises and configures the prepare or check watcher - they have no
+parameters of any kind. There are C<ev_prepare_set> and C<ev_check_set>
+macros, but using them is utterly, utterly, utterly and completely
+pointless.
+
+=back
+
+=head3 Examples
+
+There are a number of principal ways to embed other event loops or modules
+into libev. Here are some ideas on how to include libadns into libev
+(there is a Perl module named C<EV::ADNS> that does this, which you could
+use as a working example. Another Perl module named C<EV::Glib> embeds a
+Glib main context into libev, and finally, C<Glib::EV> embeds EV into the
+Glib event loop).
+
+Method 1: Add IO watchers and a timeout watcher in a prepare handler,
+and in a check watcher, destroy them and call into libadns. What follows
+is pseudo-code only of course. This requires you to either use a low
+priority for the check watcher or use C<ev_clear_pending> explicitly, as
+the callbacks for the IO/timeout watchers might not have been called yet.
+
+ static ev_io iow [nfd];
+ static ev_timer tw;
+
+ static void
+ io_cb (struct ev_loop *loop, ev_io *w, int revents)
+ {
+ }
+
+ // create io watchers for each fd and a timer before blocking
+ static void
+ adns_prepare_cb (struct ev_loop *loop, ev_prepare *w, int revents)
+ {
+ int timeout = 3600000;
+ struct pollfd fds [nfd];
+ // actual code will need to loop here and realloc etc.
+ adns_beforepoll (ads, fds, &nfd, &timeout, timeval_from (ev_time ()));
+
+ /* the callback is illegal, but won't be called as we stop during check */
+ ev_timer_init (&tw, 0, timeout * 1e-3, 0.);
+ ev_timer_start (loop, &tw);
+
+ // create one ev_io per pollfd
+ for (int i = 0; i < nfd; ++i)
+ {
+ ev_io_init (iow + i, io_cb, fds [i].fd,
+ ((fds [i].events & POLLIN ? EV_READ : 0)
+ | (fds [i].events & POLLOUT ? EV_WRITE : 0)));
+
+ fds [i].revents = 0;
+ ev_io_start (loop, iow + i);
+ }
+ }
+
+ // stop all watchers after blocking
+ static void
+ adns_check_cb (struct ev_loop *loop, ev_check *w, int revents)
+ {
+ ev_timer_stop (loop, &tw);
+
+ for (int i = 0; i < nfd; ++i)
+ {
+ // set the relevant poll flags
+ // could also call adns_processreadable etc. here
+ struct pollfd *fd = fds + i;
+ int revents = ev_clear_pending (iow + i);
+ if (revents & EV_READ ) fd->revents |= fd->events & POLLIN;
+ if (revents & EV_WRITE) fd->revents |= fd->events & POLLOUT;
+
+ // now stop the watcher
+ ev_io_stop (loop, iow + i);
+ }
+
+ adns_afterpoll (adns, fds, nfd, timeval_from (ev_now (loop));
+ }
+
+Method 2: This would be just like method 1, but you run C<adns_afterpoll>
+in the prepare watcher and would dispose of the check watcher.
+
+Method 3: If the module to be embedded supports explicit event
+notification (libadns does), you can also make use of the actual watcher
+callbacks, and only destroy/create the watchers in the prepare watcher.
+
+ static void
+ timer_cb (EV_P_ ev_timer *w, int revents)
+ {
+ adns_state ads = (adns_state)w->data;
+ update_now (EV_A);
+
+ adns_processtimeouts (ads, &tv_now);
+ }
+
+ static void
+ io_cb (EV_P_ ev_io *w, int revents)
+ {
+ adns_state ads = (adns_state)w->data;
+ update_now (EV_A);
+
+ if (revents & EV_READ ) adns_processreadable (ads, w->fd, &tv_now);
+ if (revents & EV_WRITE) adns_processwriteable (ads, w->fd, &tv_now);
+ }
+
+ // do not ever call adns_afterpoll
+
+Method 4: Do not use a prepare or check watcher because the module you
+want to embed is not flexible enough to support it. Instead, you can
+override their poll function. The drawback with this solution is that the
+main loop is now no longer controllable by EV. The C<Glib::EV> module uses
+this approach, effectively embedding EV as a client into the horrible
+libglib event loop.
+
+ static gint
+ event_poll_func (GPollFD *fds, guint nfds, gint timeout)
+ {
+ int got_events = 0;
+
+ for (n = 0; n < nfds; ++n)
+ // create/start io watcher that sets the relevant bits in fds[n] and increment got_events
+
+ if (timeout >= 0)
+ // create/start timer
+
+ // poll
+ ev_run (EV_A_ 0);
+
+ // stop timer again
+ if (timeout >= 0)
+ ev_timer_stop (EV_A_ &to);
+
+ // stop io watchers again - their callbacks should have set
+ for (n = 0; n < nfds; ++n)
+ ev_io_stop (EV_A_ iow [n]);
+
+ return got_events;
+ }
+
+
+=head2 C<ev_embed> - when one backend isn't enough...
+
+This is a rather advanced watcher type that lets you embed one event loop
+into another (currently only C<ev_io> events are supported in the embedded
+loop, other types of watchers might be handled in a delayed or incorrect
+fashion and must not be used).
+
+There are primarily two reasons you would want that: work around bugs and
+prioritise I/O.
+
+As an example for a bug workaround, the kqueue backend might only support
+sockets on some platform, so it is unusable as generic backend, but you
+still want to make use of it because you have many sockets and it scales
+so nicely. In this case, you would create a kqueue-based loop and embed
+it into your default loop (which might use e.g. poll). Overall operation
+will be a bit slower because first libev has to call C<poll> and then
+C<kevent>, but at least you can use both mechanisms for what they are
+best: C<kqueue> for scalable sockets and C<poll> if you want it to work :)
+
+As for prioritising I/O: under rare circumstances you have the case where
+some fds have to be watched and handled very quickly (with low latency),
+and even priorities and idle watchers might have too much overhead. In
+this case you would put all the high priority stuff in one loop and all
+the rest in a second one, and embed the second one in the first.
+
+As long as the watcher is active, the callback will be invoked every
+time there might be events pending in the embedded loop. The callback
+must then call C<ev_embed_sweep (mainloop, watcher)> to make a single
+sweep and invoke their callbacks (the callback doesn't need to invoke the
+C<ev_embed_sweep> function directly, it could also start an idle watcher
+to give the embedded loop strictly lower priority for example).
+
+You can also set the callback to C<0>, in which case the embed watcher
+will automatically execute the embedded loop sweep whenever necessary.
+
+Fork detection will be handled transparently while the C<ev_embed> watcher
+is active, i.e., the embedded loop will automatically be forked when the
+embedding loop forks. In other cases, the user is responsible for calling
+C<ev_loop_fork> on the embedded loop.
+
+Unfortunately, not all backends are embeddable: only the ones returned by
+C<ev_embeddable_backends> are, which, unfortunately, does not include any
+portable one.
+
+So when you want to use this feature you will always have to be prepared
+that you cannot get an embeddable loop. The recommended way to get around
+this is to have a separate variables for your embeddable loop, try to
+create it, and if that fails, use the normal loop for everything.
+
+=head3 C<ev_embed> and fork
+
+While the C<ev_embed> watcher is running, forks in the embedding loop will
+automatically be applied to the embedded loop as well, so no special
+fork handling is required in that case. When the watcher is not running,
+however, it is still the task of the libev user to call C<ev_loop_fork ()>
+as applicable.
+
+=head3 Watcher-Specific Functions and Data Members
+
+=over 4
+
+=item ev_embed_init (ev_embed *, callback, struct ev_loop *embedded_loop)
+
+=item ev_embed_set (ev_embed *, struct ev_loop *embedded_loop)
+
+Configures the watcher to embed the given loop, which must be
+embeddable. If the callback is C<0>, then C<ev_embed_sweep> will be
+invoked automatically, otherwise it is the responsibility of the callback
+to invoke it (it will continue to be called until the sweep has been done,
+if you do not want that, you need to temporarily stop the embed watcher).
+
+=item ev_embed_sweep (loop, ev_embed *)
+
+Make a single, non-blocking sweep over the embedded loop. This works
+similarly to C<ev_run (embedded_loop, EVRUN_NOWAIT)>, but in the most
+appropriate way for embedded loops.
+
+=item struct ev_loop *other [read-only]
+
+The embedded event loop.
+
+=back
+
+=head3 Examples
+
+Example: Try to get an embeddable event loop and embed it into the default
+event loop. If that is not possible, use the default loop. The default
+loop is stored in C<loop_hi>, while the embeddable loop is stored in
+C<loop_lo> (which is C<loop_hi> in the case no embeddable loop can be
+used).
+
+ struct ev_loop *loop_hi = ev_default_init (0);
+ struct ev_loop *loop_lo = 0;
+ ev_embed embed;
+
+ // see if there is a chance of getting one that works
+ // (remember that a flags value of 0 means autodetection)
+ loop_lo = ev_embeddable_backends () & ev_recommended_backends ()
+ ? ev_loop_new (ev_embeddable_backends () & ev_recommended_backends ())
+ : 0;
+
+ // if we got one, then embed it, otherwise default to loop_hi
+ if (loop_lo)
+ {
+ ev_embed_init (&embed, 0, loop_lo);
+ ev_embed_start (loop_hi, &embed);
+ }
+ else
+ loop_lo = loop_hi;
+
+Example: Check if kqueue is available but not recommended and create
+a kqueue backend for use with sockets (which usually work with any
+kqueue implementation). Store the kqueue/socket-only event loop in
+C<loop_socket>. (One might optionally use C<EVFLAG_NOENV>, too).
+
+ struct ev_loop *loop = ev_default_init (0);
+ struct ev_loop *loop_socket = 0;
+ ev_embed embed;
+
+ if (ev_supported_backends () & ~ev_recommended_backends () & EVBACKEND_KQUEUE)
+ if ((loop_socket = ev_loop_new (EVBACKEND_KQUEUE))
+ {
+ ev_embed_init (&embed, 0, loop_socket);
+ ev_embed_start (loop, &embed);
+ }
+
+ if (!loop_socket)
+ loop_socket = loop;
+
+ // now use loop_socket for all sockets, and loop for everything else
+
+
+=head2 C<ev_fork> - the audacity to resume the event loop after a fork
+
+Fork watchers are called when a C<fork ()> was detected (usually because
+whoever is a good citizen cared to tell libev about it by calling
+C<ev_loop_fork>). The invocation is done before the event loop blocks next
+and before C<ev_check> watchers are being called, and only in the child
+after the fork. If whoever good citizen calling C<ev_default_fork> cheats
+and calls it in the wrong process, the fork handlers will be invoked, too,
+of course.
+
+=head3 The special problem of life after fork - how is it possible?
+
+Most uses of C<fork ()> consist of forking, then some simple calls to set
+up/change the process environment, followed by a call to C<exec()>. This
+sequence should be handled by libev without any problems.
+
+This changes when the application actually wants to do event handling
+in the child, or both parent in child, in effect "continuing" after the
+fork.
+
+The default mode of operation (for libev, with application help to detect
+forks) is to duplicate all the state in the child, as would be expected
+when I<either> the parent I<or> the child process continues.
+
+When both processes want to continue using libev, then this is usually the
+wrong result. In that case, usually one process (typically the parent) is
+supposed to continue with all watchers in place as before, while the other
+process typically wants to start fresh, i.e. without any active watchers.
+
+The cleanest and most efficient way to achieve that with libev is to
+simply create a new event loop, which of course will be "empty", and
+use that for new watchers. This has the advantage of not touching more
+memory than necessary, and thus avoiding the copy-on-write, and the
+disadvantage of having to use multiple event loops (which do not support
+signal watchers).
+
+When this is not possible, or you want to use the default loop for
+other reasons, then in the process that wants to start "fresh", call
+C<ev_loop_destroy (EV_DEFAULT)> followed by C<ev_default_loop (...)>.
+Destroying the default loop will "orphan" (not stop) all registered
+watchers, so you have to be careful not to execute code that modifies
+those watchers. Note also that in that case, you have to re-register any
+signal watchers.
+
+=head3 Watcher-Specific Functions and Data Members
+
+=over 4
+
+=item ev_fork_init (ev_fork *, callback)
+
+Initialises and configures the fork watcher - it has no parameters of any
+kind. There is a C<ev_fork_set> macro, but using it is utterly pointless,
+really.
+
+=back
+
+
+=head2 C<ev_cleanup> - even the best things end
+
+Cleanup watchers are called just before the event loop is being destroyed
+by a call to C<ev_loop_destroy>.
+
+While there is no guarantee that the event loop gets destroyed, cleanup
+watchers provide a convenient method to install cleanup hooks for your
+program, worker threads and so on - you just to make sure to destroy the
+loop when you want them to be invoked.
+
+Cleanup watchers are invoked in the same way as any other watcher. Unlike
+all other watchers, they do not keep a reference to the event loop (which
+makes a lot of sense if you think about it). Like all other watchers, you
+can call libev functions in the callback, except C<ev_cleanup_start>.
+
+=head3 Watcher-Specific Functions and Data Members
+
+=over 4
+
+=item ev_cleanup_init (ev_cleanup *, callback)
+
+Initialises and configures the cleanup watcher - it has no parameters of
+any kind. There is a C<ev_cleanup_set> macro, but using it is utterly
+pointless, I assure you.
+
+=back
+
+Example: Register an atexit handler to destroy the default loop, so any
+cleanup functions are called.
+
+ static void
+ program_exits (void)
+ {
+ ev_loop_destroy (EV_DEFAULT_UC);
+ }
+
+ ...
+ atexit (program_exits);
+
+
+=head2 C<ev_async> - how to wake up an event loop
+
+In general, you cannot use an C<ev_loop> from multiple threads or other
+asynchronous sources such as signal handlers (as opposed to multiple event
+loops - those are of course safe to use in different threads).
+
+Sometimes, however, you need to wake up an event loop you do not control,
+for example because it belongs to another thread. This is what C<ev_async>
+watchers do: as long as the C<ev_async> watcher is active, you can signal
+it by calling C<ev_async_send>, which is thread- and signal safe.
+
+This functionality is very similar to C<ev_signal> watchers, as signals,
+too, are asynchronous in nature, and signals, too, will be compressed
+(i.e. the number of callback invocations may be less than the number of
+C<ev_async_send> calls). In fact, you could use signal watchers as a kind
+of "global async watchers" by using a watcher on an otherwise unused
+signal, and C<ev_feed_signal> to signal this watcher from another thread,
+even without knowing which loop owns the signal.
+
+=head3 Queueing
+
+C<ev_async> does not support queueing of data in any way. The reason
+is that the author does not know of a simple (or any) algorithm for a
+multiple-writer-single-reader queue that works in all cases and doesn't
+need elaborate support such as pthreads or unportable memory access
+semantics.
+
+That means that if you want to queue data, you have to provide your own
+queue. But at least I can tell you how to implement locking around your
+queue:
+
+=over 4
+
+=item queueing from a signal handler context
+
+To implement race-free queueing, you simply add to the queue in the signal
+handler but you block the signal handler in the watcher callback. Here is
+an example that does that for some fictitious SIGUSR1 handler:
+
+ static ev_async mysig;
+
+ static void
+ sigusr1_handler (void)
+ {
+ sometype data;
+
+ // no locking etc.
+ queue_put (data);
+ ev_async_send (EV_DEFAULT_ &mysig);
+ }
+
+ static void
+ mysig_cb (EV_P_ ev_async *w, int revents)
+ {
+ sometype data;
+ sigset_t block, prev;
+
+ sigemptyset (&block);
+ sigaddset (&block, SIGUSR1);
+ sigprocmask (SIG_BLOCK, &block, &prev);
+
+ while (queue_get (&data))
+ process (data);
+
+ if (sigismember (&prev, SIGUSR1)
+ sigprocmask (SIG_UNBLOCK, &block, 0);
+ }
+
+(Note: pthreads in theory requires you to use C<pthread_setmask>
+instead of C<sigprocmask> when you use threads, but libev doesn't do it
+either...).
+
+=item queueing from a thread context
+
+The strategy for threads is different, as you cannot (easily) block
+threads but you can easily preempt them, so to queue safely you need to
+employ a traditional mutex lock, such as in this pthread example:
+
+ static ev_async mysig;
+ static pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;
+
+ static void
+ otherthread (void)
+ {
+ // only need to lock the actual queueing operation
+ pthread_mutex_lock (&mymutex);
+ queue_put (data);
+ pthread_mutex_unlock (&mymutex);
+
+ ev_async_send (EV_DEFAULT_ &mysig);
+ }
+
+ static void
+ mysig_cb (EV_P_ ev_async *w, int revents)
+ {
+ pthread_mutex_lock (&mymutex);
+
+ while (queue_get (&data))
+ process (data);
+
+ pthread_mutex_unlock (&mymutex);
+ }
+
+=back
+
+
+=head3 Watcher-Specific Functions and Data Members
+
+=over 4
+
+=item ev_async_init (ev_async *, callback)
+
+Initialises and configures the async watcher - it has no parameters of any
+kind. There is a C<ev_async_set> macro, but using it is utterly pointless,
+trust me.
+
+=item ev_async_send (loop, ev_async *)
+
+Sends/signals/activates the given C<ev_async> watcher, that is, feeds
+an C<EV_ASYNC> event on the watcher into the event loop, and instantly
+returns.
+
+Unlike C<ev_feed_event>, this call is safe to do from other threads,
+signal or similar contexts (see the discussion of C<EV_ATOMIC_T> in the
+embedding section below on what exactly this means).
+
+Note that, as with other watchers in libev, multiple events might get
+compressed into a single callback invocation (another way to look at
+this is that C<ev_async> watchers are level-triggered: they are set on
+C<ev_async_send>, reset when the event loop detects that).
+
+This call incurs the overhead of at most one extra system call per event
+loop iteration, if the event loop is blocked, and no syscall at all if
+the event loop (or your program) is processing events. That means that
+repeated calls are basically free (there is no need to avoid calls for
+performance reasons) and that the overhead becomes smaller (typically
+zero) under load.
+
+=item bool = ev_async_pending (ev_async *)
+
+Returns a non-zero value when C<ev_async_send> has been called on the
+watcher but the event has not yet been processed (or even noted) by the
+event loop.
+
+C<ev_async_send> sets a flag in the watcher and wakes up the loop. When
+the loop iterates next and checks for the watcher to have become active,
+it will reset the flag again. C<ev_async_pending> can be used to very
+quickly check whether invoking the loop might be a good idea.
+
+Not that this does I<not> check whether the watcher itself is pending,
+only whether it has been requested to make this watcher pending: there
+is a time window between the event loop checking and resetting the async
+notification, and the callback being invoked.
+
+=back
+
+
+=head1 OTHER FUNCTIONS
+
+There are some other functions of possible interest. Described. Here. Now.
+
+=over 4
+
+=item ev_once (loop, int fd, int events, ev_tstamp timeout, callback, arg)
+
+This function combines a simple timer and an I/O watcher, calls your
+callback on whichever event happens first and automatically stops both
+watchers. This is useful if you want to wait for a single event on an fd
+or timeout without having to allocate/configure/start/stop/free one or
+more watchers yourself.
+
+If C<fd> is less than 0, then no I/O watcher will be started and the
+C<events> argument is being ignored. Otherwise, an C<ev_io> watcher for
+the given C<fd> and C<events> set will be created and started.
+
+If C<timeout> is less than 0, then no timeout watcher will be
+started. Otherwise an C<ev_timer> watcher with after = C<timeout> (and
+repeat = 0) will be started. C<0> is a valid timeout.
+
+The callback has the type C<void (*cb)(int revents, void *arg)> and is
+passed an C<revents> set like normal event callbacks (a combination of
+C<EV_ERROR>, C<EV_READ>, C<EV_WRITE> or C<EV_TIMER>) and the C<arg>
+value passed to C<ev_once>. Note that it is possible to receive I<both>
+a timeout and an io event at the same time - you probably should give io
+events precedence.
+
+Example: wait up to ten seconds for data to appear on STDIN_FILENO.
+
+ static void stdin_ready (int revents, void *arg)
+ {
+ if (revents & EV_READ)
+ /* stdin might have data for us, joy! */;
+ else if (revents & EV_TIMER)
+ /* doh, nothing entered */;
+ }
+
+ ev_once (STDIN_FILENO, EV_READ, 10., stdin_ready, 0);
+
+=item ev_feed_fd_event (loop, int fd, int revents)
+
+Feed an event on the given fd, as if a file descriptor backend detected
+the given events.
+
+=item ev_feed_signal_event (loop, int signum)
+
+Feed an event as if the given signal occurred. See also C<ev_feed_signal>,
+which is async-safe.
+
+=back
+
+
+=head1 COMMON OR USEFUL IDIOMS (OR BOTH)
+
+This section explains some common idioms that are not immediately
+obvious. Note that examples are sprinkled over the whole manual, and this
+section only contains stuff that wouldn't fit anywhere else.
+
+=head2 ASSOCIATING CUSTOM DATA WITH A WATCHER
+
+Each watcher has, by default, a C<void *data> member that you can read
+or modify at any time: libev will completely ignore it. This can be used
+to associate arbitrary data with your watcher. If you need more data and
+don't want to allocate memory separately and store a pointer to it in that
+data member, you can also "subclass" the watcher type and provide your own
+data:
+
+ struct my_io
+ {
+ ev_io io;
+ int otherfd;
+ void *somedata;
+ struct whatever *mostinteresting;
+ };
+
+ ...
+ struct my_io w;
+ ev_io_init (&w.io, my_cb, fd, EV_READ);
+
+And since your callback will be called with a pointer to the watcher, you
+can cast it back to your own type:
+
+ static void my_cb (struct ev_loop *loop, ev_io *w_, int revents)
+ {
+ struct my_io *w = (struct my_io *)w_;
+ ...
+ }
+
+More interesting and less C-conformant ways of casting your callback
+function type instead have been omitted.
+
+=head2 BUILDING YOUR OWN COMPOSITE WATCHERS
+
+Another common scenario is to use some data structure with multiple
+embedded watchers, in effect creating your own watcher that combines
+multiple libev event sources into one "super-watcher":
+
+ struct my_biggy
+ {
+ int some_data;
+ ev_timer t1;
+ ev_timer t2;
+ }
+
+In this case getting the pointer to C<my_biggy> is a bit more
+complicated: Either you store the address of your C<my_biggy> struct in
+the C<data> member of the watcher (for woozies or C++ coders), or you need
+to use some pointer arithmetic using C<offsetof> inside your watchers (for
+real programmers):
+
+ #include <stddef.h>
+
+ static void
+ t1_cb (EV_P_ ev_timer *w, int revents)
+ {
+ struct my_biggy big = (struct my_biggy *)
+ (((char *)w) - offsetof (struct my_biggy, t1));
+ }
+
+ static void
+ t2_cb (EV_P_ ev_timer *w, int revents)
+ {
+ struct my_biggy big = (struct my_biggy *)
+ (((char *)w) - offsetof (struct my_biggy, t2));
+ }
+
+=head2 AVOIDING FINISHING BEFORE RETURNING
+
+Often you have structures like this in event-based programs:
+
+ callback ()
+ {
+ free (request);
+ }
+
+ request = start_new_request (..., callback);
+
+The intent is to start some "lengthy" operation. The C<request> could be
+used to cancel the operation, or do other things with it.
+
+It's not uncommon to have code paths in C<start_new_request> that
+immediately invoke the callback, for example, to report errors. Or you add
+some caching layer that finds that it can skip the lengthy aspects of the
+operation and simply invoke the callback with the result.
+
+The problem here is that this will happen I<before> C<start_new_request>
+has returned, so C<request> is not set.
+
+Even if you pass the request by some safer means to the callback, you
+might want to do something to the request after starting it, such as
+canceling it, which probably isn't working so well when the callback has
+already been invoked.
+
+A common way around all these issues is to make sure that
+C<start_new_request> I<always> returns before the callback is invoked. If
+C<start_new_request> immediately knows the result, it can artificially
+delay invoking the callback by using a C<prepare> or C<idle> watcher for
+example, or more sneakily, by reusing an existing (stopped) watcher and
+pushing it into the pending queue:
+
+ ev_set_cb (watcher, callback);
+ ev_feed_event (EV_A_ watcher, 0);
+
+This way, C<start_new_request> can safely return before the callback is
+invoked, while not delaying callback invocation too much.
+
+=head2 MODEL/NESTED EVENT LOOP INVOCATIONS AND EXIT CONDITIONS
+
+Often (especially in GUI toolkits) there are places where you have
+I<modal> interaction, which is most easily implemented by recursively
+invoking C<ev_run>.
+
+This brings the problem of exiting - a callback might want to finish the
+main C<ev_run> call, but not the nested one (e.g. user clicked "Quit", but
+a modal "Are you sure?" dialog is still waiting), or just the nested one
+and not the main one (e.g. user clocked "Ok" in a modal dialog), or some
+other combination: In these cases, a simple C<ev_break> will not work.
+
+The solution is to maintain "break this loop" variable for each C<ev_run>
+invocation, and use a loop around C<ev_run> until the condition is
+triggered, using C<EVRUN_ONCE>:
+
+ // main loop
+ int exit_main_loop = 0;
+
+ while (!exit_main_loop)
+ ev_run (EV_DEFAULT_ EVRUN_ONCE);
+
+ // in a modal watcher
+ int exit_nested_loop = 0;
+
+ while (!exit_nested_loop)
+ ev_run (EV_A_ EVRUN_ONCE);
+
+To exit from any of these loops, just set the corresponding exit variable:
+
+ // exit modal loop
+ exit_nested_loop = 1;
+
+ // exit main program, after modal loop is finished
+ exit_main_loop = 1;
+
+ // exit both
+ exit_main_loop = exit_nested_loop = 1;
+
+=head2 THREAD LOCKING EXAMPLE
+
+Here is a fictitious example of how to run an event loop in a different
+thread from where callbacks are being invoked and watchers are
+created/added/removed.
+
+For a real-world example, see the C<EV::Loop::Async> perl module,
+which uses exactly this technique (which is suited for many high-level
+languages).
+
+The example uses a pthread mutex to protect the loop data, a condition
+variable to wait for callback invocations, an async watcher to notify the
+event loop thread and an unspecified mechanism to wake up the main thread.
+
+First, you need to associate some data with the event loop:
+
+ typedef struct {
+ mutex_t lock; /* global loop lock */
+ ev_async async_w;
+ thread_t tid;
+ cond_t invoke_cv;
+ } userdata;
+
+ void prepare_loop (EV_P)
+ {
+ // for simplicity, we use a static userdata struct.
+ static userdata u;
+
+ ev_async_init (&u->async_w, async_cb);
+ ev_async_start (EV_A_ &u->async_w);
+
+ pthread_mutex_init (&u->lock, 0);
+ pthread_cond_init (&u->invoke_cv, 0);
+
+ // now associate this with the loop
+ ev_set_userdata (EV_A_ u);
+ ev_set_invoke_pending_cb (EV_A_ l_invoke);
+ ev_set_loop_release_cb (EV_A_ l_release, l_acquire);
+
+ // then create the thread running ev_run
+ pthread_create (&u->tid, 0, l_run, EV_A);
+ }
+
+The callback for the C<ev_async> watcher does nothing: the watcher is used
+solely to wake up the event loop so it takes notice of any new watchers
+that might have been added:
+
+ static void
+ async_cb (EV_P_ ev_async *w, int revents)
+ {
+ // just used for the side effects
+ }
+
+The C<l_release> and C<l_acquire> callbacks simply unlock/lock the mutex
+protecting the loop data, respectively.
+
+ static void
+ l_release (EV_P)
+ {
+ userdata *u = ev_userdata (EV_A);
+ pthread_mutex_unlock (&u->lock);
+ }
+
+ static void
+ l_acquire (EV_P)
+ {
+ userdata *u = ev_userdata (EV_A);
+ pthread_mutex_lock (&u->lock);
+ }
+
+The event loop thread first acquires the mutex, and then jumps straight
+into C<ev_run>:
+
+ void *
+ l_run (void *thr_arg)
+ {
+ struct ev_loop *loop = (struct ev_loop *)thr_arg;
+
+ l_acquire (EV_A);
+ pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, 0);
+ ev_run (EV_A_ 0);
+ l_release (EV_A);
+
+ return 0;
+ }
+
+Instead of invoking all pending watchers, the C<l_invoke> callback will
+signal the main thread via some unspecified mechanism (signals? pipe
+writes? C<Async::Interrupt>?) and then waits until all pending watchers
+have been called (in a while loop because a) spurious wakeups are possible
+and b) skipping inter-thread-communication when there are no pending
+watchers is very beneficial):
+
+ static void
+ l_invoke (EV_P)
+ {
+ userdata *u = ev_userdata (EV_A);
+
+ while (ev_pending_count (EV_A))
+ {
+ wake_up_other_thread_in_some_magic_or_not_so_magic_way ();
+ pthread_cond_wait (&u->invoke_cv, &u->lock);
+ }
+ }
+
+Now, whenever the main thread gets told to invoke pending watchers, it
+will grab the lock, call C<ev_invoke_pending> and then signal the loop
+thread to continue:
+
+ static void
+ real_invoke_pending (EV_P)
+ {
+ userdata *u = ev_userdata (EV_A);
+
+ pthread_mutex_lock (&u->lock);
+ ev_invoke_pending (EV_A);
+ pthread_cond_signal (&u->invoke_cv);
+ pthread_mutex_unlock (&u->lock);
+ }
+
+Whenever you want to start/stop a watcher or do other modifications to an
+event loop, you will now have to lock:
+
+ ev_timer timeout_watcher;
+ userdata *u = ev_userdata (EV_A);
+
+ ev_timer_init (&timeout_watcher, timeout_cb, 5.5, 0.);
+
+ pthread_mutex_lock (&u->lock);
+ ev_timer_start (EV_A_ &timeout_watcher);
+ ev_async_send (EV_A_ &u->async_w);
+ pthread_mutex_unlock (&u->lock);
+
+Note that sending the C<ev_async> watcher is required because otherwise
+an event loop currently blocking in the kernel will have no knowledge
+about the newly added timer. By waking up the loop it will pick up any new
+watchers in the next event loop iteration.
+
+=head2 THREADS, COROUTINES, CONTINUATIONS, QUEUES... INSTEAD OF CALLBACKS
+
+While the overhead of a callback that e.g. schedules a thread is small, it
+is still an overhead. If you embed libev, and your main usage is with some
+kind of threads or coroutines, you might want to customise libev so that
+doesn't need callbacks anymore.
+
+Imagine you have coroutines that you can switch to using a function
+C<switch_to (coro)>, that libev runs in a coroutine called C<libev_coro>
+and that due to some magic, the currently active coroutine is stored in a
+global called C<current_coro>. Then you can build your own "wait for libev
+event" primitive by changing C<EV_CB_DECLARE> and C<EV_CB_INVOKE> (note
+the differing C<;> conventions):
+
+ #define EV_CB_DECLARE(type) struct my_coro *cb;
+ #define EV_CB_INVOKE(watcher) switch_to ((watcher)->cb)
+
+That means instead of having a C callback function, you store the
+coroutine to switch to in each watcher, and instead of having libev call
+your callback, you instead have it switch to that coroutine.
+
+A coroutine might now wait for an event with a function called
+C<wait_for_event>. (the watcher needs to be started, as always, but it doesn't
+matter when, or whether the watcher is active or not when this function is
+called):
+
+ void
+ wait_for_event (ev_watcher *w)
+ {
+ ev_set_cb (w, current_coro);
+ switch_to (libev_coro);
+ }
+
+That basically suspends the coroutine inside C<wait_for_event> and
+continues the libev coroutine, which, when appropriate, switches back to
+this or any other coroutine.
+
+You can do similar tricks if you have, say, threads with an event queue -
+instead of storing a coroutine, you store the queue object and instead of
+switching to a coroutine, you push the watcher onto the queue and notify
+any waiters.
+
+To embed libev, see L</EMBEDDING>, but in short, it's easiest to create two
+files, F<my_ev.h> and F<my_ev.c> that include the respective libev files:
+
+ // my_ev.h
+ #define EV_CB_DECLARE(type) struct my_coro *cb;
+ #define EV_CB_INVOKE(watcher) switch_to ((watcher)->cb)
+ #include "../libev/ev.h"
+
+ // my_ev.c
+ #define EV_H "my_ev.h"
+ #include "../libev/ev.c"
+
+And then use F<my_ev.h> when you would normally use F<ev.h>, and compile
+F<my_ev.c> into your project. When properly specifying include paths, you
+can even use F<ev.h> as header file name directly.
+
+
+=head1 LIBEVENT EMULATION
+
+Libev offers a compatibility emulation layer for libevent. It cannot
+emulate the internals of libevent, so here are some usage hints:
+
+=over 4
+
+=item * Only the libevent-1.4.1-beta API is being emulated.
+
+This was the newest libevent version available when libev was implemented,
+and is still mostly unchanged in 2010.
+
+=item * Use it by including <event.h>, as usual.
+
+=item * The following members are fully supported: ev_base, ev_callback,
+ev_arg, ev_fd, ev_res, ev_events.
+
+=item * Avoid using ev_flags and the EVLIST_*-macros, while it is
+maintained by libev, it does not work exactly the same way as in libevent (consider
+it a private API).
+
+=item * Priorities are not currently supported. Initialising priorities
+will fail and all watchers will have the same priority, even though there
+is an ev_pri field.
+
+=item * In libevent, the last base created gets the signals, in libev, the
+base that registered the signal gets the signals.
+
+=item * Other members are not supported.
+
+=item * The libev emulation is I<not> ABI compatible to libevent, you need
+to use the libev header file and library.
+
+=back
+
+=head1 C++ SUPPORT
+
+=head2 C API
+
+The normal C API should work fine when used from C++: both ev.h and the
+libev sources can be compiled as C++. Therefore, code that uses the C API
+will work fine.
+
+Proper exception specifications might have to be added to callbacks passed
+to libev: exceptions may be thrown only from watcher callbacks, all other
+callbacks (allocator, syserr, loop acquire/release and periodic reschedule
+callbacks) must not throw exceptions, and might need a C<noexcept>
+specification. If you have code that needs to be compiled as both C and
+C++ you can use the C<EV_NOEXCEPT> macro for this:
+
+ static void
+ fatal_error (const char *msg) EV_NOEXCEPT
+ {
+ perror (msg);
+ abort ();
+ }
+
+ ...
+ ev_set_syserr_cb (fatal_error);
+
+The only API functions that can currently throw exceptions are C<ev_run>,
+C<ev_invoke>, C<ev_invoke_pending> and C<ev_loop_destroy> (the latter
+because it runs cleanup watchers).
+
+Throwing exceptions in watcher callbacks is only supported if libev itself
+is compiled with a C++ compiler or your C and C++ environments allow
+throwing exceptions through C libraries (most do).
+
+=head2 C++ API
+
+Libev comes with some simplistic wrapper classes for C++ that mainly allow
+you to use some convenience methods to start/stop watchers and also change
+the callback model to a model using method callbacks on objects.
+
+To use it,
+
+ #include <ev++.h>
+
+This automatically includes F<ev.h> and puts all of its definitions (many
+of them macros) into the global namespace. All C++ specific things are
+put into the C<ev> namespace. It should support all the same embedding
+options as F<ev.h>, most notably C<EV_MULTIPLICITY>.
+
+Care has been taken to keep the overhead low. The only data member the C++
+classes add (compared to plain C-style watchers) is the event loop pointer
+that the watcher is associated with (or no additional members at all if
+you disable C<EV_MULTIPLICITY> when embedding libev).
+
+Currently, functions, static and non-static member functions and classes
+with C<operator ()> can be used as callbacks. Other types should be easy
+to add as long as they only need one additional pointer for context. If
+you need support for other types of functors please contact the author
+(preferably after implementing it).
+
+For all this to work, your C++ compiler either has to use the same calling
+conventions as your C compiler (for static member functions), or you have
+to embed libev and compile libev itself as C++.
+
+Here is a list of things available in the C<ev> namespace:
+
+=over 4
+
+=item C<ev::READ>, C<ev::WRITE> etc.
+
+These are just enum values with the same values as the C<EV_READ> etc.
+macros from F<ev.h>.
+
+=item C<ev::tstamp>, C<ev::now>
+
+Aliases to the same types/functions as with the C<ev_> prefix.
+
+=item C<ev::io>, C<ev::timer>, C<ev::periodic>, C<ev::idle>, C<ev::sig> etc.
+
+For each C<ev_TYPE> watcher in F<ev.h> there is a corresponding class of
+the same name in the C<ev> namespace, with the exception of C<ev_signal>
+which is called C<ev::sig> to avoid clashes with the C<signal> macro
+defined by many implementations.
+
+All of those classes have these methods:
+
+=over 4
+
+=item ev::TYPE::TYPE ()
+
+=item ev::TYPE::TYPE (loop)
+
+=item ev::TYPE::~TYPE
+
+The constructor (optionally) takes an event loop to associate the watcher
+with. If it is omitted, it will use C<EV_DEFAULT>.
+
+The constructor calls C<ev_init> for you, which means you have to call the
+C<set> method before starting it.
+
+It will not set a callback, however: You have to call the templated C<set>
+method to set a callback before you can start the watcher.
+
+(The reason why you have to use a method is a limitation in C++ which does
+not allow explicit template arguments for constructors).
+
+The destructor automatically stops the watcher if it is active.
+
+=item w->set<class, &class::method> (object *)
+
+This method sets the callback method to call. The method has to have a
+signature of C<void (*)(ev_TYPE &, int)>, it receives the watcher as
+first argument and the C<revents> as second. The object must be given as
+parameter and is stored in the C<data> member of the watcher.
+
+This method synthesizes efficient thunking code to call your method from
+the C callback that libev requires. If your compiler can inline your
+callback (i.e. it is visible to it at the place of the C<set> call and
+your compiler is good :), then the method will be fully inlined into the
+thunking function, making it as fast as a direct C callback.
+
+Example: simple class declaration and watcher initialisation
+
+ struct myclass
+ {
+ void io_cb (ev::io &w, int revents) { }
+ }
+
+ myclass obj;
+ ev::io iow;
+ iow.set <myclass, &myclass::io_cb> (&obj);
+
+=item w->set (object *)
+
+This is a variation of a method callback - leaving out the method to call
+will default the method to C<operator ()>, which makes it possible to use
+functor objects without having to manually specify the C<operator ()> all
+the time. Incidentally, you can then also leave out the template argument
+list.
+
+The C<operator ()> method prototype must be C<void operator ()(watcher &w,
+int revents)>.
+
+See the method-C<set> above for more details.
+
+Example: use a functor object as callback.
+
+ struct myfunctor
+ {
+ void operator() (ev::io &w, int revents)
+ {
+ ...
+ }
+ }
+
+ myfunctor f;
+
+ ev::io w;
+ w.set (&f);
+
+=item w->set<function> (void *data = 0)
+
+Also sets a callback, but uses a static method or plain function as
+callback. The optional C<data> argument will be stored in the watcher's
+C<data> member and is free for you to use.
+
+The prototype of the C<function> must be C<void (*)(ev::TYPE &w, int)>.
+
+See the method-C<set> above for more details.
+
+Example: Use a plain function as callback.
+
+ static void io_cb (ev::io &w, int revents) { }
+ iow.set <io_cb> ();
+
+=item w->set (loop)
+
+Associates a different C<struct ev_loop> with this watcher. You can only
+do this when the watcher is inactive (and not pending either).
+
+=item w->set ([arguments])
+
+Basically the same as C<ev_TYPE_set> (except for C<ev::embed> watchers>),
+with the same arguments. Either this method or a suitable start method
+must be called at least once. Unlike the C counterpart, an active watcher
+gets automatically stopped and restarted when reconfiguring it with this
+method.
+
+For C<ev::embed> watchers this method is called C<set_embed>, to avoid
+clashing with the C<set (loop)> method.
+
+For C<ev::io> watchers there is an additional C<set> method that acepts a
+new event mask only, and internally calls C<ev_io_modfify>.
+
+=item w->start ()
+
+Starts the watcher. Note that there is no C<loop> argument, as the
+constructor already stores the event loop.
+
+=item w->start ([arguments])
+
+Instead of calling C<set> and C<start> methods separately, it is often
+convenient to wrap them in one call. Uses the same type of arguments as
+the configure C<set> method of the watcher.
+
+=item w->stop ()
+
+Stops the watcher if it is active. Again, no C<loop> argument.
+
+=item w->again () (C<ev::timer>, C<ev::periodic> only)
+
+For C<ev::timer> and C<ev::periodic>, this invokes the corresponding
+C<ev_TYPE_again> function.
+
+=item w->sweep () (C<ev::embed> only)
+
+Invokes C<ev_embed_sweep>.
+
+=item w->update () (C<ev::stat> only)
+
+Invokes C<ev_stat_stat>.
+
+=back
+
+=back
+
+Example: Define a class with two I/O and idle watchers, start the I/O
+watchers in the constructor.
+
+ class myclass
+ {
+ ev::io io ; void io_cb (ev::io &w, int revents);
+ ev::io io2 ; void io2_cb (ev::io &w, int revents);
+ ev::idle idle; void idle_cb (ev::idle &w, int revents);
+
+ myclass (int fd)
+ {
+ io .set <myclass, &myclass::io_cb > (this);
+ io2 .set <myclass, &myclass::io2_cb > (this);
+ idle.set <myclass, &myclass::idle_cb> (this);
+
+ io.set (fd, ev::WRITE); // configure the watcher
+ io.start (); // start it whenever convenient
+
+ io2.start (fd, ev::READ); // set + start in one call
+ }
+ };
+
+
+=head1 OTHER LANGUAGE BINDINGS
+
+Libev does not offer other language bindings itself, but bindings for a
+number of languages exist in the form of third-party packages. If you know
+any interesting language binding in addition to the ones listed here, drop
+me a note.
+
+=over 4
+
+=item Perl
+
+The EV module implements the full libev API and is actually used to test
+libev. EV is developed together with libev. Apart from the EV core module,
+there are additional modules that implement libev-compatible interfaces
+to C<libadns> (C<EV::ADNS>, but C<AnyEvent::DNS> is preferred nowadays),
+C<Net::SNMP> (C<Net::SNMP::EV>) and the C<libglib> event core (C<Glib::EV>
+and C<EV::Glib>).
+
+It can be found and installed via CPAN, its homepage is at
+L<http://software.schmorp.de/pkg/EV>.
+
+=item Python
+
+Python bindings can be found at L<http://code.google.com/p/pyev/>. It
+seems to be quite complete and well-documented.
+
+=item Ruby
+
+Tony Arcieri has written a ruby extension that offers access to a subset
+of the libev API and adds file handle abstractions, asynchronous DNS and
+more on top of it. It can be found via gem servers. Its homepage is at
+L<http://rev.rubyforge.org/>.
+
+Roger Pack reports that using the link order C<-lws2_32 -lmsvcrt-ruby-190>
+makes rev work even on mingw.
+
+=item Haskell
+
+A haskell binding to libev is available at
+L<http://hackage.haskell.org/cgi-bin/hackage-scripts/package/hlibev>.
+
+=item D
+
+Leandro Lucarella has written a D language binding (F<ev.d>) for libev, to
+be found at L<http://www.llucax.com.ar/proj/ev.d/index.html>.
+
+=item Ocaml
+
+Erkki Seppala has written Ocaml bindings for libev, to be found at
+L<http://modeemi.cs.tut.fi/~flux/software/ocaml-ev/>.
+
+=item Lua
+
+Brian Maher has written a partial interface to libev for lua (at the
+time of this writing, only C<ev_io> and C<ev_timer>), to be found at
+L<http://github.com/brimworks/lua-ev>.
+
+=item Javascript
+
+Node.js (L<http://nodejs.org>) uses libev as the underlying event library.
+
+=item Others
+
+There are others, and I stopped counting.
+
+=back
+
+
+=head1 MACRO MAGIC
+
+Libev can be compiled with a variety of options, the most fundamental
+of which is C<EV_MULTIPLICITY>. This option determines whether (most)
+functions and callbacks have an initial C<struct ev_loop *> argument.
+
+To make it easier to write programs that cope with either variant, the
+following macros are defined:
+
+=over 4
+
+=item C<EV_A>, C<EV_A_>
+
+This provides the loop I<argument> for functions, if one is required ("ev
+loop argument"). The C<EV_A> form is used when this is the sole argument,
+C<EV_A_> is used when other arguments are following. Example:
+
+ ev_unref (EV_A);
+ ev_timer_add (EV_A_ watcher);
+ ev_run (EV_A_ 0);
+
+It assumes the variable C<loop> of type C<struct ev_loop *> is in scope,
+which is often provided by the following macro.
+
+=item C<EV_P>, C<EV_P_>
+
+This provides the loop I<parameter> for functions, if one is required ("ev
+loop parameter"). The C<EV_P> form is used when this is the sole parameter,
+C<EV_P_> is used when other parameters are following. Example:
+
+ // this is how ev_unref is being declared
+ static void ev_unref (EV_P);
+
+ // this is how you can declare your typical callback
+ static void cb (EV_P_ ev_timer *w, int revents)
+
+It declares a parameter C<loop> of type C<struct ev_loop *>, quite
+suitable for use with C<EV_A>.
+
+=item C<EV_DEFAULT>, C<EV_DEFAULT_>
+
+Similar to the other two macros, this gives you the value of the default
+loop, if multiple loops are supported ("ev loop default"). The default loop
+will be initialised if it isn't already initialised.
+
+For non-multiplicity builds, these macros do nothing, so you always have
+to initialise the loop somewhere.
+
+=item C<EV_DEFAULT_UC>, C<EV_DEFAULT_UC_>
+
+Usage identical to C<EV_DEFAULT> and C<EV_DEFAULT_>, but requires that the
+default loop has been initialised (C<UC> == unchecked). Their behaviour
+is undefined when the default loop has not been initialised by a previous
+execution of C<EV_DEFAULT>, C<EV_DEFAULT_> or C<ev_default_init (...)>.
+
+It is often prudent to use C<EV_DEFAULT> when initialising the first
+watcher in a function but use C<EV_DEFAULT_UC> afterwards.
+
+=back
+
+Example: Declare and initialise a check watcher, utilising the above
+macros so it will work regardless of whether multiple loops are supported
+or not.
+
+ static void
+ check_cb (EV_P_ ev_timer *w, int revents)
+ {
+ ev_check_stop (EV_A_ w);
+ }
+
+ ev_check check;
+ ev_check_init (&check, check_cb);
+ ev_check_start (EV_DEFAULT_ &check);
+ ev_run (EV_DEFAULT_ 0);
+
+=head1 EMBEDDING
+
+Libev can (and often is) directly embedded into host
+applications. Examples of applications that embed it include the Deliantra
+Game Server, the EV perl module, the GNU Virtual Private Ethernet (gvpe)
+and rxvt-unicode.
+
+The goal is to enable you to just copy the necessary files into your
+source directory without having to change even a single line in them, so
+you can easily upgrade by simply copying (or having a checked-out copy of
+libev somewhere in your source tree).
+
+=head2 FILESETS
+
+Depending on what features you need you need to include one or more sets of files
+in your application.
+
+=head3 CORE EVENT LOOP
+
+To include only the libev core (all the C<ev_*> functions), with manual
+configuration (no autoconf):
+
+ #define EV_STANDALONE 1
+ #include "ev.c"
+
+This will automatically include F<ev.h>, too, and should be done in a
+single C source file only to provide the function implementations. To use
+it, do the same for F<ev.h> in all files wishing to use this API (best
+done by writing a wrapper around F<ev.h> that you can include instead and
+where you can put other configuration options):
+
+ #define EV_STANDALONE 1
+ #include "ev.h"
+
+Both header files and implementation files can be compiled with a C++
+compiler (at least, that's a stated goal, and breakage will be treated
+as a bug).
+
+You need the following files in your source tree, or in a directory
+in your include path (e.g. in libev/ when using -Ilibev):
+
+ ev.h
+ ev.c
+ ev_vars.h
+ ev_wrap.h
+
+ ev_win32.c required on win32 platforms only
+
+ ev_select.c only when select backend is enabled
+ ev_poll.c only when poll backend is enabled
+ ev_epoll.c only when the epoll backend is enabled
+ ev_linuxaio.c only when the linux aio backend is enabled
+ ev_iouring.c only when the linux io_uring backend is enabled
+ ev_kqueue.c only when the kqueue backend is enabled
+ ev_port.c only when the solaris port backend is enabled
+
+F<ev.c> includes the backend files directly when enabled, so you only need
+to compile this single file.
+
+=head3 LIBEVENT COMPATIBILITY API
+
+To include the libevent compatibility API, also include:
+
+ #include "event.c"
+
+in the file including F<ev.c>, and:
+
+ #include "event.h"
+
+in the files that want to use the libevent API. This also includes F<ev.h>.
+
+You need the following additional files for this:
+
+ event.h
+ event.c
+
+=head3 AUTOCONF SUPPORT
+
+Instead of using C<EV_STANDALONE=1> and providing your configuration in
+whatever way you want, you can also C<m4_include([libev.m4])> in your
+F<configure.ac> and leave C<EV_STANDALONE> undefined. F<ev.c> will then
+include F<config.h> and configure itself accordingly.
+
+For this of course you need the m4 file:
+
+ libev.m4
+
+=head2 PREPROCESSOR SYMBOLS/MACROS
+
+Libev can be configured via a variety of preprocessor symbols you have to
+define before including (or compiling) any of its files. The default in
+the absence of autoconf is documented for every option.
+
+Symbols marked with "(h)" do not change the ABI, and can have different
+values when compiling libev vs. including F<ev.h>, so it is permissible
+to redefine them before including F<ev.h> without breaking compatibility
+to a compiled library. All other symbols change the ABI, which means all
+users of libev and the libev code itself must be compiled with compatible
+settings.
+
+=over 4
+
+=item EV_COMPAT3 (h)
+
+Backwards compatibility is a major concern for libev. This is why this
+release of libev comes with wrappers for the functions and symbols that
+have been renamed between libev version 3 and 4.
+
+You can disable these wrappers (to test compatibility with future
+versions) by defining C<EV_COMPAT3> to C<0> when compiling your
+sources. This has the additional advantage that you can drop the C<struct>
+from C<struct ev_loop> declarations, as libev will provide an C<ev_loop>
+typedef in that case.
+
+In some future version, the default for C<EV_COMPAT3> will become C<0>,
+and in some even more future version the compatibility code will be
+removed completely.
+
+=item EV_STANDALONE (h)
+
+Must always be C<1> if you do not use autoconf configuration, which
+keeps libev from including F<config.h>, and it also defines dummy
+implementations for some libevent functions (such as logging, which is not
+supported). It will also not define any of the structs usually found in
+F<event.h> that are not directly supported by the libev core alone.
+
+In standalone mode, libev will still try to automatically deduce the
+configuration, but has to be more conservative.
+
+=item EV_USE_FLOOR
+
+If defined to be C<1>, libev will use the C<floor ()> function for its
+periodic reschedule calculations, otherwise libev will fall back on a
+portable (slower) implementation. If you enable this, you usually have to
+link against libm or something equivalent. Enabling this when the C<floor>
+function is not available will fail, so the safe default is to not enable
+this.
+
+=item EV_USE_MONOTONIC
+
+If defined to be C<1>, libev will try to detect the availability of the
+monotonic clock option at both compile time and runtime. Otherwise no
+use of the monotonic clock option will be attempted. If you enable this,
+you usually have to link against librt or something similar. Enabling it
+when the functionality isn't available is safe, though, although you have
+to make sure you link against any libraries where the C<clock_gettime>
+function is hiding in (often F<-lrt>). See also C<EV_USE_CLOCK_SYSCALL>.
+
+=item EV_USE_REALTIME
+
+If defined to be C<1>, libev will try to detect the availability of the
+real-time clock option at compile time (and assume its availability
+at runtime if successful). Otherwise no use of the real-time clock
+option will be attempted. This effectively replaces C<gettimeofday>
+by C<clock_get (CLOCK_REALTIME, ...)> and will not normally affect
+correctness. See the note about libraries in the description of
+C<EV_USE_MONOTONIC>, though. Defaults to the opposite value of
+C<EV_USE_CLOCK_SYSCALL>.
+
+=item EV_USE_CLOCK_SYSCALL
+
+If defined to be C<1>, libev will try to use a direct syscall instead
+of calling the system-provided C<clock_gettime> function. This option
+exists because on GNU/Linux, C<clock_gettime> is in C<librt>, but C<librt>
+unconditionally pulls in C<libpthread>, slowing down single-threaded
+programs needlessly. Using a direct syscall is slightly slower (in
+theory), because no optimised vdso implementation can be used, but avoids
+the pthread dependency. Defaults to C<1> on GNU/Linux with glibc 2.x or
+higher, as it simplifies linking (no need for C<-lrt>).
+
+=item EV_USE_NANOSLEEP
+
+If defined to be C<1>, libev will assume that C<nanosleep ()> is available
+and will use it for delays. Otherwise it will use C<select ()>.
+
+=item EV_USE_EVENTFD
+
+If defined to be C<1>, then libev will assume that C<eventfd ()> is
+available and will probe for kernel support at runtime. This will improve
+C<ev_signal> and C<ev_async> performance and reduce resource consumption.
+If undefined, it will be enabled if the headers indicate GNU/Linux + Glibc
+2.7 or newer, otherwise disabled.
+
+=item EV_USE_SIGNALFD
+
+If defined to be C<1>, then libev will assume that C<signalfd ()> is
+available and will probe for kernel support at runtime. This enables
+the use of EVFLAG_SIGNALFD for faster and simpler signal handling. If
+undefined, it will be enabled if the headers indicate GNU/Linux + Glibc
+2.7 or newer, otherwise disabled.
+
+=item EV_USE_TIMERFD
+
+If defined to be C<1>, then libev will assume that C<timerfd ()> is
+available and will probe for kernel support at runtime. This allows
+libev to detect time jumps accurately. If undefined, it will be enabled
+if the headers indicate GNU/Linux + Glibc 2.8 or newer and define
+C<TFD_TIMER_CANCEL_ON_SET>, otherwise disabled.
+
+=item EV_USE_EVENTFD
+
+If defined to be C<1>, then libev will assume that C<eventfd ()> is
+available and will probe for kernel support at runtime. This will improve
+C<ev_signal> and C<ev_async> performance and reduce resource consumption.
+If undefined, it will be enabled if the headers indicate GNU/Linux + Glibc
+2.7 or newer, otherwise disabled.
+
+=item EV_USE_SELECT
+
+If undefined or defined to be C<1>, libev will compile in support for the
+C<select>(2) backend. No attempt at auto-detection will be done: if no
+other method takes over, select will be it. Otherwise the select backend
+will not be compiled in.
+
+=item EV_SELECT_USE_FD_SET
+
+If defined to C<1>, then the select backend will use the system C<fd_set>
+structure. This is useful if libev doesn't compile due to a missing
+C<NFDBITS> or C<fd_mask> definition or it mis-guesses the bitset layout
+on exotic systems. This usually limits the range of file descriptors to
+some low limit such as 1024 or might have other limitations (winsocket
+only allows 64 sockets). The C<FD_SETSIZE> macro, set before compilation,
+configures the maximum size of the C<fd_set>.
+
+=item EV_SELECT_IS_WINSOCKET
+
+When defined to C<1>, the select backend will assume that
+select/socket/connect etc. don't understand file descriptors but
+wants osf handles on win32 (this is the case when the select to
+be used is the winsock select). This means that it will call
+C<_get_osfhandle> on the fd to convert it to an OS handle. Otherwise,
+it is assumed that all these functions actually work on fds, even
+on win32. Should not be defined on non-win32 platforms.
+
+=item EV_FD_TO_WIN32_HANDLE(fd)
+
+If C<EV_SELECT_IS_WINSOCKET> is enabled, then libev needs a way to map
+file descriptors to socket handles. When not defining this symbol (the
+default), then libev will call C<_get_osfhandle>, which is usually
+correct. In some cases, programs use their own file descriptor management,
+in which case they can provide this function to map fds to socket handles.
+
+=item EV_WIN32_HANDLE_TO_FD(handle)
+
+If C<EV_SELECT_IS_WINSOCKET> then libev maps handles to file descriptors
+using the standard C<_open_osfhandle> function. For programs implementing
+their own fd to handle mapping, overwriting this function makes it easier
+to do so. This can be done by defining this macro to an appropriate value.
+
+=item EV_WIN32_CLOSE_FD(fd)
+
+If programs implement their own fd to handle mapping on win32, then this
+macro can be used to override the C<close> function, useful to unregister
+file descriptors again. Note that the replacement function has to close
+the underlying OS handle.
+
+=item EV_USE_WSASOCKET
+
+If defined to be C<1>, libev will use C<WSASocket> to create its internal
+communication socket, which works better in some environments. Otherwise,
+the normal C<socket> function will be used, which works better in other
+environments.
+
+=item EV_USE_POLL
+
+If defined to be C<1>, libev will compile in support for the C<poll>(2)
+backend. Otherwise it will be enabled on non-win32 platforms. It
+takes precedence over select.
+
+=item EV_USE_EPOLL
+
+If defined to be C<1>, libev will compile in support for the Linux
+C<epoll>(7) backend. Its availability will be detected at runtime,
+otherwise another method will be used as fallback. This is the preferred
+backend for GNU/Linux systems. If undefined, it will be enabled if the
+headers indicate GNU/Linux + Glibc 2.4 or newer, otherwise disabled.
+
+=item EV_USE_LINUXAIO
+
+If defined to be C<1>, libev will compile in support for the Linux aio
+backend (C<EV_USE_EPOLL> must also be enabled). If undefined, it will be
+enabled on linux, otherwise disabled.
+
+=item EV_USE_IOURING
+
+If defined to be C<1>, libev will compile in support for the Linux
+io_uring backend (C<EV_USE_EPOLL> must also be enabled). Due to it's
+current limitations it has to be requested explicitly. If undefined, it
+will be enabled on linux, otherwise disabled.
+
+=item EV_USE_KQUEUE
+
+If defined to be C<1>, libev will compile in support for the BSD style
+C<kqueue>(2) backend. Its actual availability will be detected at runtime,
+otherwise another method will be used as fallback. This is the preferred
+backend for BSD and BSD-like systems, although on most BSDs kqueue only
+supports some types of fds correctly (the only platform we found that
+supports ptys for example was NetBSD), so kqueue might be compiled in, but
+not be used unless explicitly requested. The best way to use it is to find
+out whether kqueue supports your type of fd properly and use an embedded
+kqueue loop.
+
+=item EV_USE_PORT
+
+If defined to be C<1>, libev will compile in support for the Solaris
+10 port style backend. Its availability will be detected at runtime,
+otherwise another method will be used as fallback. This is the preferred
+backend for Solaris 10 systems.
+
+=item EV_USE_DEVPOLL
+
+Reserved for future expansion, works like the USE symbols above.
+
+=item EV_USE_INOTIFY
+
+If defined to be C<1>, libev will compile in support for the Linux inotify
+interface to speed up C<ev_stat> watchers. Its actual availability will
+be detected at runtime. If undefined, it will be enabled if the headers
+indicate GNU/Linux + Glibc 2.4 or newer, otherwise disabled.
+
+=item EV_NO_SMP
+
+If defined to be C<1>, libev will assume that memory is always coherent
+between threads, that is, threads can be used, but threads never run on
+different cpus (or different cpu cores). This reduces dependencies
+and makes libev faster.
+
+=item EV_NO_THREADS
+
+If defined to be C<1>, libev will assume that it will never be called from
+different threads (that includes signal handlers), which is a stronger
+assumption than C<EV_NO_SMP>, above. This reduces dependencies and makes
+libev faster.
+
+=item EV_ATOMIC_T
+
+Libev requires an integer type (suitable for storing C<0> or C<1>) whose
+access is atomic with respect to other threads or signal contexts. No
+such type is easily found in the C language, so you can provide your own
+type that you know is safe for your purposes. It is used both for signal
+handler "locking" as well as for signal and thread safety in C<ev_async>
+watchers.
+
+In the absence of this define, libev will use C<sig_atomic_t volatile>
+(from F<signal.h>), which is usually good enough on most platforms.
+
+=item EV_H (h)
+
+The name of the F<ev.h> header file used to include it. The default if
+undefined is C<"ev.h"> in F<event.h>, F<ev.c> and F<ev++.h>. This can be
+used to virtually rename the F<ev.h> header file in case of conflicts.
+
+=item EV_CONFIG_H (h)
+
+If C<EV_STANDALONE> isn't C<1>, this variable can be used to override
+F<ev.c>'s idea of where to find the F<config.h> file, similarly to
+C<EV_H>, above.
+
+=item EV_EVENT_H (h)
+
+Similarly to C<EV_H>, this macro can be used to override F<event.c>'s idea
+of how the F<event.h> header can be found, the default is C<"event.h">.
+
+=item EV_PROTOTYPES (h)
+
+If defined to be C<0>, then F<ev.h> will not define any function
+prototypes, but still define all the structs and other symbols. This is
+occasionally useful if you want to provide your own wrapper functions
+around libev functions.
+
+=item EV_MULTIPLICITY
+
+If undefined or defined to C<1>, then all event-loop-specific functions
+will have the C<struct ev_loop *> as first argument, and you can create
+additional independent event loops. Otherwise there will be no support
+for multiple event loops and there is no first event loop pointer
+argument. Instead, all functions act on the single default loop.
+
+Note that C<EV_DEFAULT> and C<EV_DEFAULT_> will no longer provide a
+default loop when multiplicity is switched off - you always have to
+initialise the loop manually in this case.
+
+=item EV_MINPRI
+
+=item EV_MAXPRI
+
+The range of allowed priorities. C<EV_MINPRI> must be smaller or equal to
+C<EV_MAXPRI>, but otherwise there are no non-obvious limitations. You can
+provide for more priorities by overriding those symbols (usually defined
+to be C<-2> and C<2>, respectively).
+
+When doing priority-based operations, libev usually has to linearly search
+all the priorities, so having many of them (hundreds) uses a lot of space
+and time, so using the defaults of five priorities (-2 .. +2) is usually
+fine.
+
+If your embedding application does not need any priorities, defining these
+both to C<0> will save some memory and CPU.
+
+=item EV_PERIODIC_ENABLE, EV_IDLE_ENABLE, EV_EMBED_ENABLE, EV_STAT_ENABLE,
+EV_PREPARE_ENABLE, EV_CHECK_ENABLE, EV_FORK_ENABLE, EV_SIGNAL_ENABLE,
+EV_ASYNC_ENABLE, EV_CHILD_ENABLE.
+
+If undefined or defined to be C<1> (and the platform supports it), then
+the respective watcher type is supported. If defined to be C<0>, then it
+is not. Disabling watcher types mainly saves code size.
+
+=item EV_FEATURES
+
+If you need to shave off some kilobytes of code at the expense of some
+speed (but with the full API), you can define this symbol to request
+certain subsets of functionality. The default is to enable all features
+that can be enabled on the platform.
+
+A typical way to use this symbol is to define it to C<0> (or to a bitset
+with some broad features you want) and then selectively re-enable
+additional parts you want, for example if you want everything minimal,
+but multiple event loop support, async and child watchers and the poll
+backend, use this:
+
+ #define EV_FEATURES 0
+ #define EV_MULTIPLICITY 1
+ #define EV_USE_POLL 1
+ #define EV_CHILD_ENABLE 1
+ #define EV_ASYNC_ENABLE 1
+
+The actual value is a bitset, it can be a combination of the following
+values (by default, all of these are enabled):
+
+=over 4
+
+=item C<1> - faster/larger code
+
+Use larger code to speed up some operations.
+
+Currently this is used to override some inlining decisions (enlarging the
+code size by roughly 30% on amd64).
+
+When optimising for size, use of compiler flags such as C<-Os> with
+gcc is recommended, as well as C<-DNDEBUG>, as libev contains a number of
+assertions.
+
+The default is off when C<__OPTIMIZE_SIZE__> is defined by your compiler
+(e.g. gcc with C<-Os>).
+
+=item C<2> - faster/larger data structures
+
+Replaces the small 2-heap for timer management by a faster 4-heap, larger
+hash table sizes and so on. This will usually further increase code size
+and can additionally have an effect on the size of data structures at
+runtime.
+
+The default is off when C<__OPTIMIZE_SIZE__> is defined by your compiler
+(e.g. gcc with C<-Os>).
+
+=item C<4> - full API configuration
+
+This enables priorities (sets C<EV_MAXPRI>=2 and C<EV_MINPRI>=-2), and
+enables multiplicity (C<EV_MULTIPLICITY>=1).
+
+=item C<8> - full API
+
+This enables a lot of the "lesser used" API functions. See C<ev.h> for
+details on which parts of the API are still available without this
+feature, and do not complain if this subset changes over time.
+
+=item C<16> - enable all optional watcher types
+
+Enables all optional watcher types. If you want to selectively enable
+only some watcher types other than I/O and timers (e.g. prepare,
+embed, async, child...) you can enable them manually by defining
+C<EV_watchertype_ENABLE> to C<1> instead.
+
+=item C<32> - enable all backends
+
+This enables all backends - without this feature, you need to enable at
+least one backend manually (C<EV_USE_SELECT> is a good choice).
+
+=item C<64> - enable OS-specific "helper" APIs
+
+Enable inotify, eventfd, signalfd and similar OS-specific helper APIs by
+default.
+
+=back
+
+Compiling with C<gcc -Os -DEV_STANDALONE -DEV_USE_EPOLL=1 -DEV_FEATURES=0>
+reduces the compiled size of libev from 24.7Kb code/2.8Kb data to 6.5Kb
+code/0.3Kb data on my GNU/Linux amd64 system, while still giving you I/O
+watchers, timers and monotonic clock support.
+
+With an intelligent-enough linker (gcc+binutils are intelligent enough
+when you use C<-Wl,--gc-sections -ffunction-sections>) functions unused by
+your program might be left out as well - a binary starting a timer and an
+I/O watcher then might come out at only 5Kb.
+
+=item EV_API_STATIC
+
+If this symbol is defined (by default it is not), then all identifiers
+will have static linkage. This means that libev will not export any
+identifiers, and you cannot link against libev anymore. This can be useful
+when you embed libev, only want to use libev functions in a single file,
+and do not want its identifiers to be visible.
+
+To use this, define C<EV_API_STATIC> and include F<ev.c> in the file that
+wants to use libev.
+
+This option only works when libev is compiled with a C compiler, as C++
+doesn't support the required declaration syntax.
+
+=item EV_AVOID_STDIO
+
+If this is set to C<1> at compiletime, then libev will avoid using stdio
+functions (printf, scanf, perror etc.). This will increase the code size
+somewhat, but if your program doesn't otherwise depend on stdio and your
+libc allows it, this avoids linking in the stdio library which is quite
+big.
+
+Note that error messages might become less precise when this option is
+enabled.
+
+=item EV_NSIG
+
+The highest supported signal number, +1 (or, the number of
+signals): Normally, libev tries to deduce the maximum number of signals
+automatically, but sometimes this fails, in which case it can be
+specified. Also, using a lower number than detected (C<32> should be
+good for about any system in existence) can save some memory, as libev
+statically allocates some 12-24 bytes per signal number.
+
+=item EV_PID_HASHSIZE
+
+C<ev_child> watchers use a small hash table to distribute workload by
+pid. The default size is C<16> (or C<1> with C<EV_FEATURES> disabled),
+usually more than enough. If you need to manage thousands of children you
+might want to increase this value (I<must> be a power of two).
+
+=item EV_INOTIFY_HASHSIZE
+
+C<ev_stat> watchers use a small hash table to distribute workload by
+inotify watch id. The default size is C<16> (or C<1> with C<EV_FEATURES>
+disabled), usually more than enough. If you need to manage thousands of
+C<ev_stat> watchers you might want to increase this value (I<must> be a
+power of two).
+
+=item EV_USE_4HEAP
+
+Heaps are not very cache-efficient. To improve the cache-efficiency of the
+timer and periodics heaps, libev uses a 4-heap when this symbol is defined
+to C<1>. The 4-heap uses more complicated (longer) code but has noticeably
+faster performance with many (thousands) of watchers.
+
+The default is C<1>, unless C<EV_FEATURES> overrides it, in which case it
+will be C<0>.
+
+=item EV_HEAP_CACHE_AT
+
+Heaps are not very cache-efficient. To improve the cache-efficiency of the
+timer and periodics heaps, libev can cache the timestamp (I<at>) within
+the heap structure (selected by defining C<EV_HEAP_CACHE_AT> to C<1>),
+which uses 8-12 bytes more per watcher and a few hundred bytes more code,
+but avoids random read accesses on heap changes. This improves performance
+noticeably with many (hundreds) of watchers.
+
+The default is C<1>, unless C<EV_FEATURES> overrides it, in which case it
+will be C<0>.
+
+=item EV_VERIFY
+
+Controls how much internal verification (see C<ev_verify ()>) will
+be done: If set to C<0>, no internal verification code will be compiled
+in. If set to C<1>, then verification code will be compiled in, but not
+called. If set to C<2>, then the internal verification code will be
+called once per loop, which can slow down libev. If set to C<3>, then the
+verification code will be called very frequently, which will slow down
+libev considerably.
+
+Verification errors are reported via C's C<assert> mechanism, so if you
+disable that (e.g. by defining C<NDEBUG>) then no errors will be reported.
+
+The default is C<1>, unless C<EV_FEATURES> overrides it, in which case it
+will be C<0>.
+
+=item EV_COMMON
+
+By default, all watchers have a C<void *data> member. By redefining
+this macro to something else you can include more and other types of
+members. You have to define it each time you include one of the files,
+though, and it must be identical each time.
+
+For example, the perl EV module uses something like this:
+
+ #define EV_COMMON \
+ SV *self; /* contains this struct */ \
+ SV *cb_sv, *fh /* note no trailing ";" */
+
+=item EV_CB_DECLARE (type)
+
+=item EV_CB_INVOKE (watcher, revents)
+
+=item ev_set_cb (ev, cb)
+
+Can be used to change the callback member declaration in each watcher,
+and the way callbacks are invoked and set. Must expand to a struct member
+definition and a statement, respectively. See the F<ev.h> header file for
+their default definitions. One possible use for overriding these is to
+avoid the C<struct ev_loop *> as first argument in all cases, or to use
+method calls instead of plain function calls in C++.
+
+=back
+
+=head2 EXPORTED API SYMBOLS
+
+If you need to re-export the API (e.g. via a DLL) and you need a list of
+exported symbols, you can use the provided F<Symbol.*> files which list
+all public symbols, one per line:
+
+ Symbols.ev for libev proper
+ Symbols.event for the libevent emulation
+
+This can also be used to rename all public symbols to avoid clashes with
+multiple versions of libev linked together (which is obviously bad in
+itself, but sometimes it is inconvenient to avoid this).
+
+A sed command like this will create wrapper C<#define>'s that you need to
+include before including F<ev.h>:
+
+ <Symbols.ev sed -e "s/.*/#define & myprefix_&/" >wrap.h
+
+This would create a file F<wrap.h> which essentially looks like this:
+
+ #define ev_backend myprefix_ev_backend
+ #define ev_check_start myprefix_ev_check_start
+ #define ev_check_stop myprefix_ev_check_stop
+ ...
+
+=head2 EXAMPLES
+
+For a real-world example of a program the includes libev
+verbatim, you can have a look at the EV perl module
+(L<http://software.schmorp.de/pkg/EV.html>). It has the libev files in
+the F<libev/> subdirectory and includes them in the F<EV/EVAPI.h> (public
+interface) and F<EV.xs> (implementation) files. Only the F<EV.xs> file
+will be compiled. It is pretty complex because it provides its own header
+file.
+
+The usage in rxvt-unicode is simpler. It has a F<ev_cpp.h> header file
+that everybody includes and which overrides some configure choices:
+
+ #define EV_FEATURES 8
+ #define EV_USE_SELECT 1
+ #define EV_PREPARE_ENABLE 1
+ #define EV_IDLE_ENABLE 1
+ #define EV_SIGNAL_ENABLE 1
+ #define EV_CHILD_ENABLE 1
+ #define EV_USE_STDEXCEPT 0
+ #define EV_CONFIG_H <config.h>
+
+ #include "ev++.h"
+
+And a F<ev_cpp.C> implementation file that contains libev proper and is compiled:
+
+ #include "ev_cpp.h"
+ #include "ev.c"
+
+=head1 INTERACTION WITH OTHER PROGRAMS, LIBRARIES OR THE ENVIRONMENT
+
+=head2 THREADS AND COROUTINES
+
+=head3 THREADS
+
+All libev functions are reentrant and thread-safe unless explicitly
+documented otherwise, but libev implements no locking itself. This means
+that you can use as many loops as you want in parallel, as long as there
+are no concurrent calls into any libev function with the same loop
+parameter (C<ev_default_*> calls have an implicit default loop parameter,
+of course): libev guarantees that different event loops share no data
+structures that need any locking.
+
+Or to put it differently: calls with different loop parameters can be done
+concurrently from multiple threads, calls with the same loop parameter
+must be done serially (but can be done from different threads, as long as
+only one thread ever is inside a call at any point in time, e.g. by using
+a mutex per loop).
+
+Specifically to support threads (and signal handlers), libev implements
+so-called C<ev_async> watchers, which allow some limited form of
+concurrency on the same event loop, namely waking it up "from the
+outside".
+
+If you want to know which design (one loop, locking, or multiple loops
+without or something else still) is best for your problem, then I cannot
+help you, but here is some generic advice:
+
+=over 4
+
+=item * most applications have a main thread: use the default libev loop
+in that thread, or create a separate thread running only the default loop.
+
+This helps integrating other libraries or software modules that use libev
+themselves and don't care/know about threading.
+
+=item * one loop per thread is usually a good model.
+
+Doing this is almost never wrong, sometimes a better-performance model
+exists, but it is always a good start.
+
+=item * other models exist, such as the leader/follower pattern, where one
+loop is handed through multiple threads in a kind of round-robin fashion.
+
+Choosing a model is hard - look around, learn, know that usually you can do
+better than you currently do :-)
+
+=item * often you need to talk to some other thread which blocks in the
+event loop.
+
+C<ev_async> watchers can be used to wake them up from other threads safely
+(or from signal contexts...).
+
+An example use would be to communicate signals or other events that only
+work in the default loop by registering the signal watcher with the
+default loop and triggering an C<ev_async> watcher from the default loop
+watcher callback into the event loop interested in the signal.
+
+=back
+
+See also L</THREAD LOCKING EXAMPLE>.
+
+=head3 COROUTINES
+
+Libev is very accommodating to coroutines ("cooperative threads"):
+libev fully supports nesting calls to its functions from different
+coroutines (e.g. you can call C<ev_run> on the same loop from two
+different coroutines, and switch freely between both coroutines running
+the loop, as long as you don't confuse yourself). The only exception is
+that you must not do this from C<ev_periodic> reschedule callbacks.
+
+Care has been taken to ensure that libev does not keep local state inside
+C<ev_run>, and other calls do not usually allow for coroutine switches as
+they do not call any callbacks.
+
+=head2 COMPILER WARNINGS
+
+Depending on your compiler and compiler settings, you might get no or a
+lot of warnings when compiling libev code. Some people are apparently
+scared by this.
+
+However, these are unavoidable for many reasons. For one, each compiler
+has different warnings, and each user has different tastes regarding
+warning options. "Warn-free" code therefore cannot be a goal except when
+targeting a specific compiler and compiler-version.
+
+Another reason is that some compiler warnings require elaborate
+workarounds, or other changes to the code that make it less clear and less
+maintainable.
+
+And of course, some compiler warnings are just plain stupid, or simply
+wrong (because they don't actually warn about the condition their message
+seems to warn about). For example, certain older gcc versions had some
+warnings that resulted in an extreme number of false positives. These have
+been fixed, but some people still insist on making code warn-free with
+such buggy versions.
+
+While libev is written to generate as few warnings as possible,
+"warn-free" code is not a goal, and it is recommended not to build libev
+with any compiler warnings enabled unless you are prepared to cope with
+them (e.g. by ignoring them). Remember that warnings are just that:
+warnings, not errors, or proof of bugs.
+
+
+=head2 VALGRIND
+
+Valgrind has a special section here because it is a popular tool that is
+highly useful. Unfortunately, valgrind reports are very hard to interpret.
+
+If you think you found a bug (memory leak, uninitialised data access etc.)
+in libev, then check twice: If valgrind reports something like:
+
+ ==2274== definitely lost: 0 bytes in 0 blocks.
+ ==2274== possibly lost: 0 bytes in 0 blocks.
+ ==2274== still reachable: 256 bytes in 1 blocks.
+
+Then there is no memory leak, just as memory accounted to global variables
+is not a memleak - the memory is still being referenced, and didn't leak.
+
+Similarly, under some circumstances, valgrind might report kernel bugs
+as if it were a bug in libev (e.g. in realloc or in the poll backend,
+although an acceptable workaround has been found here), or it might be
+confused.
+
+Keep in mind that valgrind is a very good tool, but only a tool. Don't
+make it into some kind of religion.
+
+If you are unsure about something, feel free to contact the mailing list
+with the full valgrind report and an explanation on why you think this
+is a bug in libev (best check the archives, too :). However, don't be
+annoyed when you get a brisk "this is no bug" answer and take the chance
+of learning how to interpret valgrind properly.
+
+If you need, for some reason, empty reports from valgrind for your project
+I suggest using suppression lists.
+
+
+=head1 PORTABILITY NOTES
+
+=head2 GNU/LINUX 32 BIT LIMITATIONS
+
+GNU/Linux is the only common platform that supports 64 bit file/large file
+interfaces but I<disables> them by default.
+
+That means that libev compiled in the default environment doesn't support
+files larger than 2GiB or so, which mainly affects C<ev_stat> watchers.
+
+Unfortunately, many programs try to work around this GNU/Linux issue
+by enabling the large file API, which makes them incompatible with the
+standard libev compiled for their system.
+
+Likewise, libev cannot enable the large file API itself as this would
+suddenly make it incompatible to the default compile time environment,
+i.e. all programs not using special compile switches.
+
+=head2 OS/X AND DARWIN BUGS
+
+The whole thing is a bug if you ask me - basically any system interface
+you touch is broken, whether it is locales, poll, kqueue or even the
+OpenGL drivers.
+
+=head3 C<kqueue> is buggy
+
+The kqueue syscall is broken in all known versions - most versions support
+only sockets, many support pipes.
+
+Libev tries to work around this by not using C<kqueue> by default on this
+rotten platform, but of course you can still ask for it when creating a
+loop - embedding a socket-only kqueue loop into a select-based one is
+probably going to work well.
+
+=head3 C<poll> is buggy
+
+Instead of fixing C<kqueue>, Apple replaced their (working) C<poll>
+implementation by something calling C<kqueue> internally around the 10.5.6
+release, so now C<kqueue> I<and> C<poll> are broken.
+
+Libev tries to work around this by not using C<poll> by default on
+this rotten platform, but of course you can still ask for it when creating
+a loop.
+
+=head3 C<select> is buggy
+
+All that's left is C<select>, and of course Apple found a way to fuck this
+one up as well: On OS/X, C<select> actively limits the number of file
+descriptors you can pass in to 1024 - your program suddenly crashes when
+you use more.
+
+There is an undocumented "workaround" for this - defining
+C<_DARWIN_UNLIMITED_SELECT>, which libev tries to use, so select I<should>
+work on OS/X.
+
+=head2 SOLARIS PROBLEMS AND WORKAROUNDS
+
+=head3 C<errno> reentrancy
+
+The default compile environment on Solaris is unfortunately so
+thread-unsafe that you can't even use components/libraries compiled
+without C<-D_REENTRANT> in a threaded program, which, of course, isn't
+defined by default. A valid, if stupid, implementation choice.
+
+If you want to use libev in threaded environments you have to make sure
+it's compiled with C<_REENTRANT> defined.
+
+=head3 Event port backend
+
+The scalable event interface for Solaris is called "event
+ports". Unfortunately, this mechanism is very buggy in all major
+releases. If you run into high CPU usage, your program freezes or you get
+a large number of spurious wakeups, make sure you have all the relevant
+and latest kernel patches applied. No, I don't know which ones, but there
+are multiple ones to apply, and afterwards, event ports actually work
+great.
+
+If you can't get it to work, you can try running the program by setting
+the environment variable C<LIBEV_FLAGS=3> to only allow C<poll> and
+C<select> backends.
+
+=head2 AIX POLL BUG
+
+AIX unfortunately has a broken C<poll.h> header. Libev works around
+this by trying to avoid the poll backend altogether (i.e. it's not even
+compiled in), which normally isn't a big problem as C<select> works fine
+with large bitsets on AIX, and AIX is dead anyway.
+
+=head2 WIN32 PLATFORM LIMITATIONS AND WORKAROUNDS
+
+=head3 General issues
+
+Win32 doesn't support any of the standards (e.g. POSIX) that libev
+requires, and its I/O model is fundamentally incompatible with the POSIX
+model. Libev still offers limited functionality on this platform in
+the form of the C<EVBACKEND_SELECT> backend, and only supports socket
+descriptors. This only applies when using Win32 natively, not when using
+e.g. cygwin. Actually, it only applies to the microsofts own compilers,
+as every compiler comes with a slightly differently broken/incompatible
+environment.
+
+Lifting these limitations would basically require the full
+re-implementation of the I/O system. If you are into this kind of thing,
+then note that glib does exactly that for you in a very portable way (note
+also that glib is the slowest event library known to man).
+
+There is no supported compilation method available on windows except
+embedding it into other applications.
+
+Sensible signal handling is officially unsupported by Microsoft - libev
+tries its best, but under most conditions, signals will simply not work.
+
+Not a libev limitation but worth mentioning: windows apparently doesn't
+accept large writes: instead of resulting in a partial write, windows will
+either accept everything or return C<ENOBUFS> if the buffer is too large,
+so make sure you only write small amounts into your sockets (less than a
+megabyte seems safe, but this apparently depends on the amount of memory
+available).
+
+Due to the many, low, and arbitrary limits on the win32 platform and
+the abysmal performance of winsockets, using a large number of sockets
+is not recommended (and not reasonable). If your program needs to use
+more than a hundred or so sockets, then likely it needs to use a totally
+different implementation for windows, as libev offers the POSIX readiness
+notification model, which cannot be implemented efficiently on windows
+(due to Microsoft monopoly games).
+
+A typical way to use libev under windows is to embed it (see the embedding
+section for details) and use the following F<evwrap.h> header file instead
+of F<ev.h>:
+
+ #define EV_STANDALONE /* keeps ev from requiring config.h */
+ #define EV_SELECT_IS_WINSOCKET 1 /* configure libev for windows select */
+
+ #include "ev.h"
+
+And compile the following F<evwrap.c> file into your project (make sure
+you do I<not> compile the F<ev.c> or any other embedded source files!):
+
+ #include "evwrap.h"
+ #include "ev.c"
+
+=head3 The winsocket C<select> function
+
+The winsocket C<select> function doesn't follow POSIX in that it
+requires socket I<handles> and not socket I<file descriptors> (it is
+also extremely buggy). This makes select very inefficient, and also
+requires a mapping from file descriptors to socket handles (the Microsoft
+C runtime provides the function C<_open_osfhandle> for this). See the
+discussion of the C<EV_SELECT_USE_FD_SET>, C<EV_SELECT_IS_WINSOCKET> and
+C<EV_FD_TO_WIN32_HANDLE> preprocessor symbols for more info.
+
+The configuration for a "naked" win32 using the Microsoft runtime
+libraries and raw winsocket select is:
+
+ #define EV_USE_SELECT 1
+ #define EV_SELECT_IS_WINSOCKET 1 /* forces EV_SELECT_USE_FD_SET, too */
+
+Note that winsockets handling of fd sets is O(n), so you can easily get a
+complexity in the O(n²) range when using win32.
+
+=head3 Limited number of file descriptors
+
+Windows has numerous arbitrary (and low) limits on things.
+
+Early versions of winsocket's select only supported waiting for a maximum
+of C<64> handles (probably owning to the fact that all windows kernels
+can only wait for C<64> things at the same time internally; Microsoft
+recommends spawning a chain of threads and wait for 63 handles and the
+previous thread in each. Sounds great!).
+
+Newer versions support more handles, but you need to define C<FD_SETSIZE>
+to some high number (e.g. C<2048>) before compiling the winsocket select
+call (which might be in libev or elsewhere, for example, perl and many
+other interpreters do their own select emulation on windows).
+
+Another limit is the number of file descriptors in the Microsoft runtime
+libraries, which by default is C<64> (there must be a hidden I<64>
+fetish or something like this inside Microsoft). You can increase this
+by calling C<_setmaxstdio>, which can increase this limit to C<2048>
+(another arbitrary limit), but is broken in many versions of the Microsoft
+runtime libraries. This might get you to about C<512> or C<2048> sockets
+(depending on windows version and/or the phase of the moon). To get more,
+you need to wrap all I/O functions and provide your own fd management, but
+the cost of calling select (O(n²)) will likely make this unworkable.
+
+=head2 PORTABILITY REQUIREMENTS
+
+In addition to a working ISO-C implementation and of course the
+backend-specific APIs, libev relies on a few additional extensions:
+
+=over 4
+
+=item C<void (*)(ev_watcher_type *, int revents)> must have compatible
+calling conventions regardless of C<ev_watcher_type *>.
+
+Libev assumes not only that all watcher pointers have the same internal
+structure (guaranteed by POSIX but not by ISO C for example), but it also
+assumes that the same (machine) code can be used to call any watcher
+callback: The watcher callbacks have different type signatures, but libev
+calls them using an C<ev_watcher *> internally.
+
+=item null pointers and integer zero are represented by 0 bytes
+
+Libev uses C<memset> to initialise structs and arrays to C<0> bytes, and
+relies on this setting pointers and integers to null.
+
+=item pointer accesses must be thread-atomic
+
+Accessing a pointer value must be atomic, it must both be readable and
+writable in one piece - this is the case on all current architectures.
+
+=item C<sig_atomic_t volatile> must be thread-atomic as well
+
+The type C<sig_atomic_t volatile> (or whatever is defined as
+C<EV_ATOMIC_T>) must be atomic with respect to accesses from different
+threads. This is not part of the specification for C<sig_atomic_t>, but is
+believed to be sufficiently portable.
+
+=item C<sigprocmask> must work in a threaded environment
+
+Libev uses C<sigprocmask> to temporarily block signals. This is not
+allowed in a threaded program (C<pthread_sigmask> has to be used). Typical
+pthread implementations will either allow C<sigprocmask> in the "main
+thread" or will block signals process-wide, both behaviours would
+be compatible with libev. Interaction between C<sigprocmask> and
+C<pthread_sigmask> could complicate things, however.
+
+The most portable way to handle signals is to block signals in all threads
+except the initial one, and run the signal handling loop in the initial
+thread as well.
+
+=item C<long> must be large enough for common memory allocation sizes
+
+To improve portability and simplify its API, libev uses C<long> internally
+instead of C<size_t> when allocating its data structures. On non-POSIX
+systems (Microsoft...) this might be unexpectedly low, but is still at
+least 31 bits everywhere, which is enough for hundreds of millions of
+watchers.
+
+=item C<double> must hold a time value in seconds with enough accuracy
+
+The type C<double> is used to represent timestamps. It is required to
+have at least 51 bits of mantissa (and 9 bits of exponent), which is
+good enough for at least into the year 4000 with millisecond accuracy
+(the design goal for libev). This requirement is overfulfilled by
+implementations using IEEE 754, which is basically all existing ones.
+
+With IEEE 754 doubles, you get microsecond accuracy until at least the
+year 2255 (and millisecond accuracy till the year 287396 - by then, libev
+is either obsolete or somebody patched it to use C<long double> or
+something like that, just kidding).
+
+=back
+
+If you know of other additional requirements drop me a note.
+
+
+=head1 ALGORITHMIC COMPLEXITIES
+
+In this section the complexities of (many of) the algorithms used inside
+libev will be documented. For complexity discussions about backends see
+the documentation for C<ev_default_init>.
+
+All of the following are about amortised time: If an array needs to be
+extended, libev needs to realloc and move the whole array, but this
+happens asymptotically rarer with higher number of elements, so O(1) might
+mean that libev does a lengthy realloc operation in rare cases, but on
+average it is much faster and asymptotically approaches constant time.
+
+=over 4
+
+=item Starting and stopping timer/periodic watchers: O(log skipped_other_timers)
+
+This means that, when you have a watcher that triggers in one hour and
+there are 100 watchers that would trigger before that, then inserting will
+have to skip roughly seven (C<ld 100>) of these watchers.
+
+=item Changing timer/periodic watchers (by autorepeat or calling again): O(log skipped_other_timers)
+
+That means that changing a timer costs less than removing/adding them,
+as only the relative motion in the event queue has to be paid for.
+
+=item Starting io/check/prepare/idle/signal/child/fork/async watchers: O(1)
+
+These just add the watcher into an array or at the head of a list.
+
+=item Stopping check/prepare/idle/fork/async watchers: O(1)
+
+=item Stopping an io/signal/child watcher: O(number_of_watchers_for_this_(fd/signal/pid % EV_PID_HASHSIZE))
+
+These watchers are stored in lists, so they need to be walked to find the
+correct watcher to remove. The lists are usually short (you don't usually
+have many watchers waiting for the same fd or signal: one is typical, two
+is rare).
+
+=item Finding the next timer in each loop iteration: O(1)
+
+By virtue of using a binary or 4-heap, the next timer is always found at a
+fixed position in the storage array.
+
+=item Each change on a file descriptor per loop iteration: O(number_of_watchers_for_this_fd)
+
+A change means an I/O watcher gets started or stopped, which requires
+libev to recalculate its status (and possibly tell the kernel, depending
+on backend and whether C<ev_io_set> was used).
+
+=item Activating one watcher (putting it into the pending state): O(1)
+
+=item Priority handling: O(number_of_priorities)
+
+Priorities are implemented by allocating some space for each
+priority. When doing priority-based operations, libev usually has to
+linearly search all the priorities, but starting/stopping and activating
+watchers becomes O(1) with respect to priority handling.
+
+=item Sending an ev_async: O(1)
+
+=item Processing ev_async_send: O(number_of_async_watchers)
+
+=item Processing signals: O(max_signal_number)
+
+Sending involves a system call I<iff> there were no other C<ev_async_send>
+calls in the current loop iteration and the loop is currently
+blocked. Checking for async and signal events involves iterating over all
+running async watchers or all signal numbers.
+
+=back
+
+
+=head1 PORTING FROM LIBEV 3.X TO 4.X
+
+The major version 4 introduced some incompatible changes to the API.
+
+At the moment, the C<ev.h> header file provides compatibility definitions
+for all changes, so most programs should still compile. The compatibility
+layer might be removed in later versions of libev, so better update to the
+new API early than late.
+
+=over 4
+
+=item C<EV_COMPAT3> backwards compatibility mechanism
+
+The backward compatibility mechanism can be controlled by
+C<EV_COMPAT3>. See L</"PREPROCESSOR SYMBOLS/MACROS"> in the L</EMBEDDING>
+section.
+
+=item C<ev_default_destroy> and C<ev_default_fork> have been removed
+
+These calls can be replaced easily by their C<ev_loop_xxx> counterparts:
+
+ ev_loop_destroy (EV_DEFAULT_UC);
+ ev_loop_fork (EV_DEFAULT);
+
+=item function/symbol renames
+
+A number of functions and symbols have been renamed:
+
+ ev_loop => ev_run
+ EVLOOP_NONBLOCK => EVRUN_NOWAIT
+ EVLOOP_ONESHOT => EVRUN_ONCE
+
+ ev_unloop => ev_break
+ EVUNLOOP_CANCEL => EVBREAK_CANCEL
+ EVUNLOOP_ONE => EVBREAK_ONE
+ EVUNLOOP_ALL => EVBREAK_ALL
+
+ EV_TIMEOUT => EV_TIMER
+
+ ev_loop_count => ev_iteration
+ ev_loop_depth => ev_depth
+ ev_loop_verify => ev_verify
+
+Most functions working on C<struct ev_loop> objects don't have an
+C<ev_loop_> prefix, so it was removed; C<ev_loop>, C<ev_unloop> and
+associated constants have been renamed to not collide with the C<struct
+ev_loop> anymore and C<EV_TIMER> now follows the same naming scheme
+as all other watcher types. Note that C<ev_loop_fork> is still called
+C<ev_loop_fork> because it would otherwise clash with the C<ev_fork>
+typedef.
+
+=item C<EV_MINIMAL> mechanism replaced by C<EV_FEATURES>
+
+The preprocessor symbol C<EV_MINIMAL> has been replaced by a different
+mechanism, C<EV_FEATURES>. Programs using C<EV_MINIMAL> usually compile
+and work, but the library code will of course be larger.
+
+=back
+
+
+=head1 GLOSSARY
+
+=over 4
+
+=item active
+
+A watcher is active as long as it has been started and not yet stopped.
+See L</WATCHER STATES> for details.
+
+=item application
+
+In this document, an application is whatever is using libev.
+
+=item backend
+
+The part of the code dealing with the operating system interfaces.
+
+=item callback
+
+The address of a function that is called when some event has been
+detected. Callbacks are being passed the event loop, the watcher that
+received the event, and the actual event bitset.
+
+=item callback/watcher invocation
+
+The act of calling the callback associated with a watcher.
+
+=item event
+
+A change of state of some external event, such as data now being available
+for reading on a file descriptor, time having passed or simply not having
+any other events happening anymore.
+
+In libev, events are represented as single bits (such as C<EV_READ> or
+C<EV_TIMER>).
+
+=item event library
+
+A software package implementing an event model and loop.
+
+=item event loop
+
+An entity that handles and processes external events and converts them
+into callback invocations.
+
+=item event model
+
+The model used to describe how an event loop handles and processes
+watchers and events.
+
+=item pending
+
+A watcher is pending as soon as the corresponding event has been
+detected. See L</WATCHER STATES> for details.
+
+=item real time
+
+The physical time that is observed. It is apparently strictly monotonic :)
+
+=item wall-clock time
+
+The time and date as shown on clocks. Unlike real time, it can actually
+be wrong and jump forwards and backwards, e.g. when you adjust your
+clock.
+
+=item watcher
+
+A data structure that describes interest in certain events. Watchers need
+to be started (attached to an event loop) before they can receive events.
+
+=back
+
+=head1 AUTHOR
+
+Marc Lehmann <libev@schmorp.de>, with repeated corrections by Mikael
+Magnusson and Emanuele Giaquinta, and minor corrections by many others.
+
diff --git a/3rdparty/libev/ev_epoll.c b/3rdparty/libev/ev_epoll.c
new file mode 100644
index 0000000..58cfa68
--- /dev/null
+++ b/3rdparty/libev/ev_epoll.c
@@ -0,0 +1,298 @@
+/*
+ * libev epoll fd activity backend
+ *
+ * Copyright (c) 2007,2008,2009,2010,2011,2016,2017,2019 Marc Alexander Lehmann <libev@schmorp.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
+ * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
+ * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
+ * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License ("GPL") version 2 or any later version,
+ * in which case the provisions of the GPL are applicable instead of
+ * the above. If you wish to allow the use of your version of this file
+ * only under the terms of the GPL and not to allow others to use your
+ * version of this file under the BSD license, indicate your decision
+ * by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL. If you do not delete the
+ * provisions above, a recipient may use your version of this file under
+ * either the BSD or the GPL.
+ */
+
+/*
+ * general notes about epoll:
+ *
+ * a) epoll silently removes fds from the fd set. as nothing tells us
+ * that an fd has been removed otherwise, we have to continually
+ * "rearm" fds that we suspect *might* have changed (same
+ * problem with kqueue, but much less costly there).
+ * b) the fact that ADD != MOD creates a lot of extra syscalls due to a)
+ * and seems not to have any advantage.
+ * c) the inability to handle fork or file descriptors (think dup)
+ * limits the applicability over poll, so this is not a generic
+ * poll replacement.
+ * d) epoll doesn't work the same as select with many file descriptors
+ * (such as files). while not critical, no other advanced interface
+ * seems to share this (rather non-unixy) limitation.
+ * e) epoll claims to be embeddable, but in practise you never get
+ * a ready event for the epoll fd (broken: <=2.6.26, working: >=2.6.32).
+ * f) epoll_ctl returning EPERM means the fd is always ready.
+ *
+ * lots of "weird code" and complication handling in this file is due
+ * to these design problems with epoll, as we try very hard to avoid
+ * epoll_ctl syscalls for common usage patterns and handle the breakage
+ * ensuing from receiving events for closed and otherwise long gone
+ * file descriptors.
+ */
+
+#include <sys/epoll.h>
+
+#define EV_EMASK_EPERM 0x80
+
+static void
+epoll_modify (EV_P_ int fd, int oev, int nev)
+{
+ struct epoll_event ev;
+ unsigned char oldmask;
+
+ /*
+ * we handle EPOLL_CTL_DEL by ignoring it here
+ * on the assumption that the fd is gone anyways
+ * if that is wrong, we have to handle the spurious
+ * event in epoll_poll.
+ * if the fd is added again, we try to ADD it, and, if that
+ * fails, we assume it still has the same eventmask.
+ */
+ if (!nev)
+ return;
+
+ oldmask = anfds [fd].emask;
+ anfds [fd].emask = nev;
+
+ /* store the generation counter in the upper 32 bits, the fd in the lower 32 bits */
+ ev.data.u64 = (uint64_t)(uint32_t)fd
+ | ((uint64_t)(uint32_t)++anfds [fd].egen << 32);
+ ev.events = (nev & EV_READ ? EPOLLIN : 0)
+ | (nev & EV_WRITE ? EPOLLOUT : 0);
+
+ if (ecb_expect_true (!epoll_ctl (backend_fd, oev && oldmask != nev ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, fd, &ev)))
+ return;
+
+ if (ecb_expect_true (errno == ENOENT))
+ {
+ /* if ENOENT then the fd went away, so try to do the right thing */
+ if (!nev)
+ goto dec_egen;
+
+ if (!epoll_ctl (backend_fd, EPOLL_CTL_ADD, fd, &ev))
+ return;
+ }
+ else if (ecb_expect_true (errno == EEXIST))
+ {
+ /* EEXIST means we ignored a previous DEL, but the fd is still active */
+ /* if the kernel mask is the same as the new mask, we assume it hasn't changed */
+ if (oldmask == nev)
+ goto dec_egen;
+
+ if (!epoll_ctl (backend_fd, EPOLL_CTL_MOD, fd, &ev))
+ return;
+ }
+ else if (ecb_expect_true (errno == EPERM))
+ {
+ /* EPERM means the fd is always ready, but epoll is too snobbish */
+ /* to handle it, unlike select or poll. */
+ anfds [fd].emask = EV_EMASK_EPERM;
+
+ /* add fd to epoll_eperms, if not already inside */
+ if (!(oldmask & EV_EMASK_EPERM))
+ {
+ array_needsize (int, epoll_eperms, epoll_epermmax, epoll_epermcnt + 1, array_needsize_noinit);
+ epoll_eperms [epoll_epermcnt++] = fd;
+ }
+
+ return;
+ }
+ else
+ assert (("libev: I/O watcher with invalid fd found in epoll_ctl", errno != EBADF && errno != ELOOP && errno != EINVAL));
+
+ fd_kill (EV_A_ fd);
+
+dec_egen:
+ /* we didn't successfully call epoll_ctl, so decrement the generation counter again */
+ --anfds [fd].egen;
+}
+
+static void
+epoll_poll (EV_P_ ev_tstamp timeout)
+{
+ int i;
+ int eventcnt;
+
+ if (ecb_expect_false (epoll_epermcnt))
+ timeout = EV_TS_CONST (0.);
+
+ /* epoll wait times cannot be larger than (LONG_MAX - 999UL) / HZ msecs, which is below */
+ /* the default libev max wait time, however. */
+ EV_RELEASE_CB;
+ eventcnt = epoll_wait (backend_fd, epoll_events, epoll_eventmax, EV_TS_TO_MSEC (timeout));
+ EV_ACQUIRE_CB;
+
+ if (ecb_expect_false (eventcnt < 0))
+ {
+ if (errno != EINTR)
+ ev_syserr ("(libev) epoll_wait");
+
+ return;
+ }
+
+ for (i = 0; i < eventcnt; ++i)
+ {
+ struct epoll_event *ev = epoll_events + i;
+
+ int fd = (uint32_t)ev->data.u64; /* mask out the lower 32 bits */
+ int want = anfds [fd].events;
+ int got = (ev->events & (EPOLLOUT | EPOLLERR | EPOLLHUP) ? EV_WRITE : 0)
+ | (ev->events & (EPOLLIN | EPOLLERR | EPOLLHUP) ? EV_READ : 0);
+
+ /*
+ * check for spurious notification.
+ * this only finds spurious notifications on egen updates
+ * other spurious notifications will be found by epoll_ctl, below
+ * we assume that fd is always in range, as we never shrink the anfds array
+ */
+ if (ecb_expect_false ((uint32_t)anfds [fd].egen != (uint32_t)(ev->data.u64 >> 32)))
+ {
+ /* recreate kernel state */
+ postfork |= 2;
+ continue;
+ }
+
+ if (ecb_expect_false (got & ~want))
+ {
+ anfds [fd].emask = want;
+
+ /*
+ * we received an event but are not interested in it, try mod or del
+ * this often happens because we optimistically do not unregister fds
+ * when we are no longer interested in them, but also when we get spurious
+ * notifications for fds from another process. this is partially handled
+ * above with the gencounter check (== our fd is not the event fd), and
+ * partially here, when epoll_ctl returns an error (== a child has the fd
+ * but we closed it).
+ * note: for events such as POLLHUP, where we can't know whether it refers
+ * to EV_READ or EV_WRITE, we might issue redundant EPOLL_CTL_MOD calls.
+ */
+ ev->events = (want & EV_READ ? EPOLLIN : 0)
+ | (want & EV_WRITE ? EPOLLOUT : 0);
+
+ /* pre-2.6.9 kernels require a non-null pointer with EPOLL_CTL_DEL, */
+ /* which is fortunately easy to do for us. */
+ if (epoll_ctl (backend_fd, want ? EPOLL_CTL_MOD : EPOLL_CTL_DEL, fd, ev))
+ {
+ postfork |= 2; /* an error occurred, recreate kernel state */
+ continue;
+ }
+ }
+
+ fd_event (EV_A_ fd, got);
+ }
+
+ /* if the receive array was full, increase its size */
+ if (ecb_expect_false (eventcnt == epoll_eventmax))
+ {
+ ev_free (epoll_events);
+ epoll_eventmax = array_nextsize (sizeof (struct epoll_event), epoll_eventmax, epoll_eventmax + 1);
+ epoll_events = (struct epoll_event *)ev_malloc (sizeof (struct epoll_event) * epoll_eventmax);
+ }
+
+ /* now synthesize events for all fds where epoll fails, while select works... */
+ for (i = epoll_epermcnt; i--; )
+ {
+ int fd = epoll_eperms [i];
+ unsigned char events = anfds [fd].events & (EV_READ | EV_WRITE);
+
+ if (anfds [fd].emask & EV_EMASK_EPERM && events)
+ fd_event (EV_A_ fd, events);
+ else
+ {
+ epoll_eperms [i] = epoll_eperms [--epoll_epermcnt];
+ anfds [fd].emask = 0;
+ }
+ }
+}
+
+static int
+epoll_epoll_create (void)
+{
+ int fd;
+
+#if defined EPOLL_CLOEXEC && !defined __ANDROID__
+ fd = epoll_create1 (EPOLL_CLOEXEC);
+
+ if (fd < 0 && (errno == EINVAL || errno == ENOSYS))
+#endif
+ {
+ fd = epoll_create (256);
+
+ if (fd >= 0)
+ fcntl (fd, F_SETFD, FD_CLOEXEC);
+ }
+
+ return fd;
+}
+
+inline_size
+int
+epoll_init (EV_P_ int flags)
+{
+ if ((backend_fd = epoll_epoll_create ()) < 0)
+ return 0;
+
+ backend_mintime = EV_TS_CONST (1e-3); /* epoll does sometimes return early, this is just to avoid the worst */
+ backend_modify = epoll_modify;
+ backend_poll = epoll_poll;
+
+ epoll_eventmax = 64; /* initial number of events receivable per poll */
+ epoll_events = (struct epoll_event *)ev_malloc (sizeof (struct epoll_event) * epoll_eventmax);
+
+ return EVBACKEND_EPOLL;
+}
+
+inline_size
+void
+epoll_destroy (EV_P)
+{
+ ev_free (epoll_events);
+ array_free (epoll_eperm, EMPTY);
+}
+
+ecb_cold
+static void
+epoll_fork (EV_P)
+{
+ close (backend_fd);
+
+ while ((backend_fd = epoll_epoll_create ()) < 0)
+ ev_syserr ("(libev) epoll_create");
+
+ fd_rearm_all (EV_A);
+}
+
diff --git a/3rdparty/libev/ev_iouring.c b/3rdparty/libev/ev_iouring.c
new file mode 100644
index 0000000..bfd3de6
--- /dev/null
+++ b/3rdparty/libev/ev_iouring.c
@@ -0,0 +1,694 @@
+/*
+ * libev linux io_uring fd activity backend
+ *
+ * Copyright (c) 2019-2020 Marc Alexander Lehmann <libev@schmorp.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
+ * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
+ * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
+ * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License ("GPL") version 2 or any later version,
+ * in which case the provisions of the GPL are applicable instead of
+ * the above. If you wish to allow the use of your version of this file
+ * only under the terms of the GPL and not to allow others to use your
+ * version of this file under the BSD license, indicate your decision
+ * by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL. If you do not delete the
+ * provisions above, a recipient may use your version of this file under
+ * either the BSD or the GPL.
+ */
+
+/*
+ * general notes about linux io_uring:
+ *
+ * a) it's the best interface I have seen so far. on linux.
+ * b) best is not necessarily very good.
+ * c) it's better than the aio mess, doesn't suffer from the fork problems
+ * of linux aio or epoll and so on and so on. and you could do event stuff
+ * without any syscalls. what's not to like?
+ * d) ok, it's vastly more complex, but that's ok, really.
+ * e) why two mmaps instead of one? one would be more space-efficient,
+ * and I can't see what benefit two would have (other than being
+ * somehow resizable/relocatable, but that's apparently not possible).
+ * f) hmm, it's practically undebuggable (gdb can't access the memory, and
+ * the bizarre way structure offsets are communicated makes it hard to
+ * just print the ring buffer heads, even *iff* the memory were visible
+ * in gdb. but then, that's also ok, really.
+ * g) well, you cannot specify a timeout when waiting for events. no,
+ * seriously, the interface doesn't support a timeout. never seen _that_
+ * before. sure, you can use a timerfd, but that's another syscall
+ * you could have avoided. overall, this bizarre omission smells
+ * like a µ-optimisation by the io_uring author for his personal
+ * applications, to the detriment of everybody else who just wants
+ * an event loop. but, umm, ok, if that's all, it could be worse.
+ * (from what I gather from the author Jens Axboe, it simply didn't
+ * occur to him, and he made good on it by adding an unlimited nuber
+ * of timeouts later :).
+ * h) initially there was a hardcoded limit of 4096 outstanding events.
+ * later versions not only bump this to 32k, but also can handle
+ * an unlimited amount of events, so this only affects the batch size.
+ * i) unlike linux aio, you *can* register more then the limit
+ * of fd events. while early verisons of io_uring signalled an overflow
+ * and you ended up getting wet. 5.5+ does not do this anymore.
+ * j) but, oh my! it had exactly the same bugs as the linux aio backend,
+ * where some undocumented poll combinations just fail. fortunately,
+ * after finally reaching the author, he was more than willing to fix
+ * this probably in 5.6+.
+ * k) overall, the *API* itself is, I dare to say, not a total trainwreck.
+ * once the bugs ae fixed (probably in 5.6+), it will be without
+ * competition.
+ */
+
+/* TODO: use internal TIMEOUT */
+/* TODO: take advantage of single mmap, NODROP etc. */
+/* TODO: resize cq/sq size independently */
+
+#include <sys/timerfd.h>
+#include <sys/mman.h>
+#include <poll.h>
+#include <stdint.h>
+
+#define IOURING_INIT_ENTRIES 32
+
+/*****************************************************************************/
+/* syscall wrapdadoop - this section has the raw api/abi definitions */
+
+#include <linux/fs.h>
+#include <linux/types.h>
+
+/* mostly directly taken from the kernel or documentation */
+
+struct io_uring_sqe
+{
+ __u8 opcode;
+ __u8 flags;
+ __u16 ioprio;
+ __s32 fd;
+ union {
+ __u64 off;
+ __u64 addr2;
+ };
+ __u64 addr;
+ __u32 len;
+ union {
+ __kernel_rwf_t rw_flags;
+ __u32 fsync_flags;
+ __u16 poll_events;
+ __u32 sync_range_flags;
+ __u32 msg_flags;
+ __u32 timeout_flags;
+ __u32 accept_flags;
+ __u32 cancel_flags;
+ __u32 open_flags;
+ __u32 statx_flags;
+ };
+ __u64 user_data;
+ union {
+ __u16 buf_index;
+ __u64 __pad2[3];
+ };
+};
+
+struct io_uring_cqe
+{
+ __u64 user_data;
+ __s32 res;
+ __u32 flags;
+};
+
+struct io_sqring_offsets
+{
+ __u32 head;
+ __u32 tail;
+ __u32 ring_mask;
+ __u32 ring_entries;
+ __u32 flags;
+ __u32 dropped;
+ __u32 array;
+ __u32 resv1;
+ __u64 resv2;
+};
+
+struct io_cqring_offsets
+{
+ __u32 head;
+ __u32 tail;
+ __u32 ring_mask;
+ __u32 ring_entries;
+ __u32 overflow;
+ __u32 cqes;
+ __u64 resv[2];
+};
+
+struct io_uring_params
+{
+ __u32 sq_entries;
+ __u32 cq_entries;
+ __u32 flags;
+ __u32 sq_thread_cpu;
+ __u32 sq_thread_idle;
+ __u32 features;
+ __u32 resv[4];
+ struct io_sqring_offsets sq_off;
+ struct io_cqring_offsets cq_off;
+};
+
+#define IORING_SETUP_CQSIZE 0x00000008
+
+#define IORING_OP_POLL_ADD 6
+#define IORING_OP_POLL_REMOVE 7
+#define IORING_OP_TIMEOUT 11
+#define IORING_OP_TIMEOUT_REMOVE 12
+
+/* relative or absolute, reference clock is CLOCK_MONOTONIC */
+struct iouring_kernel_timespec
+{
+ int64_t tv_sec;
+ long long tv_nsec;
+};
+
+#define IORING_TIMEOUT_ABS 0x00000001
+
+#define IORING_ENTER_GETEVENTS 0x01
+
+#define IORING_OFF_SQ_RING 0x00000000ULL
+#define IORING_OFF_CQ_RING 0x08000000ULL
+#define IORING_OFF_SQES 0x10000000ULL
+
+#define IORING_FEAT_SINGLE_MMAP 0x00000001
+#define IORING_FEAT_NODROP 0x00000002
+#define IORING_FEAT_SUBMIT_STABLE 0x00000004
+
+inline_size
+int
+evsys_io_uring_setup (unsigned entries, struct io_uring_params *params)
+{
+ return ev_syscall2 (SYS_io_uring_setup, entries, params);
+}
+
+inline_size
+int
+evsys_io_uring_enter (int fd, unsigned to_submit, unsigned min_complete, unsigned flags, const sigset_t *sig, size_t sigsz)
+{
+ return ev_syscall6 (SYS_io_uring_enter, fd, to_submit, min_complete, flags, sig, sigsz);
+}
+
+/*****************************************************************************/
+/* actual backed implementation */
+
+/* we hope that volatile will make the compiler access this variables only once */
+#define EV_SQ_VAR(name) *(volatile unsigned *)((char *)iouring_sq_ring + iouring_sq_ ## name)
+#define EV_CQ_VAR(name) *(volatile unsigned *)((char *)iouring_cq_ring + iouring_cq_ ## name)
+
+/* the index array */
+#define EV_SQ_ARRAY ((unsigned *)((char *)iouring_sq_ring + iouring_sq_array))
+
+/* the submit/completion queue entries */
+#define EV_SQES ((struct io_uring_sqe *) iouring_sqes)
+#define EV_CQES ((struct io_uring_cqe *)((char *)iouring_cq_ring + iouring_cq_cqes))
+
+inline_speed
+int
+iouring_enter (EV_P_ ev_tstamp timeout)
+{
+ int res;
+
+ EV_RELEASE_CB;
+
+ res = evsys_io_uring_enter (iouring_fd, iouring_to_submit, 1,
+ timeout > EV_TS_CONST (0.) ? IORING_ENTER_GETEVENTS : 0, 0, 0);
+
+ assert (("libev: io_uring_enter did not consume all sqes", (res < 0 || res == iouring_to_submit)));
+
+ iouring_to_submit = 0;
+
+ EV_ACQUIRE_CB;
+
+ return res;
+}
+
+/* TODO: can we move things around so we don't need this forward-reference? */
+static void
+iouring_poll (EV_P_ ev_tstamp timeout);
+
+static
+struct io_uring_sqe *
+iouring_sqe_get (EV_P)
+{
+ unsigned tail;
+
+ for (;;)
+ {
+ tail = EV_SQ_VAR (tail);
+
+ if (ecb_expect_true (tail + 1 - EV_SQ_VAR (head) <= EV_SQ_VAR (ring_entries)))
+ break; /* whats the problem, we have free sqes */
+
+ /* queue full, need to flush and possibly handle some events */
+
+#if EV_FEATURE_CODE
+ /* first we ask the kernel nicely, most often this frees up some sqes */
+ int res = iouring_enter (EV_A_ EV_TS_CONST (0.));
+
+ ECB_MEMORY_FENCE_ACQUIRE; /* better safe than sorry */
+
+ if (res >= 0)
+ continue; /* yes, it worked, try again */
+#endif
+
+ /* some problem, possibly EBUSY - do the full poll and let it handle any issues */
+
+ iouring_poll (EV_A_ EV_TS_CONST (0.));
+ /* iouring_poll should have done ECB_MEMORY_FENCE_ACQUIRE for us */
+ }
+
+ /*assert (("libev: io_uring queue full after flush", tail + 1 - EV_SQ_VAR (head) <= EV_SQ_VAR (ring_entries)));*/
+
+ return EV_SQES + (tail & EV_SQ_VAR (ring_mask));
+}
+
+inline_size
+struct io_uring_sqe *
+iouring_sqe_submit (EV_P_ struct io_uring_sqe *sqe)
+{
+ unsigned idx = sqe - EV_SQES;
+
+ EV_SQ_ARRAY [idx] = idx;
+ ECB_MEMORY_FENCE_RELEASE;
+ ++EV_SQ_VAR (tail);
+ /*ECB_MEMORY_FENCE_RELEASE; /* for the time being we assume this is not needed */
+ ++iouring_to_submit;
+}
+
+/*****************************************************************************/
+
+/* when the timerfd expires we simply note the fact,
+ * as the purpose of the timerfd is to wake us up, nothing else.
+ * the next iteration should re-set it.
+ */
+static void
+iouring_tfd_cb (EV_P_ struct ev_io *w, int revents)
+{
+ iouring_tfd_to = EV_TSTAMP_HUGE;
+}
+
+/* called for full and partial cleanup */
+ecb_cold
+static int
+iouring_internal_destroy (EV_P)
+{
+ close (iouring_tfd);
+ close (iouring_fd);
+
+ if (iouring_sq_ring != MAP_FAILED) munmap (iouring_sq_ring, iouring_sq_ring_size);
+ if (iouring_cq_ring != MAP_FAILED) munmap (iouring_cq_ring, iouring_cq_ring_size);
+ if (iouring_sqes != MAP_FAILED) munmap (iouring_sqes , iouring_sqes_size );
+
+ if (ev_is_active (&iouring_tfd_w))
+ {
+ ev_ref (EV_A);
+ ev_io_stop (EV_A_ &iouring_tfd_w);
+ }
+}
+
+ecb_cold
+static int
+iouring_internal_init (EV_P)
+{
+ struct io_uring_params params = { 0 };
+
+ iouring_to_submit = 0;
+
+ iouring_tfd = -1;
+ iouring_sq_ring = MAP_FAILED;
+ iouring_cq_ring = MAP_FAILED;
+ iouring_sqes = MAP_FAILED;
+
+ if (!have_monotonic) /* cannot really happen, but what if11 */
+ return -1;
+
+ for (;;)
+ {
+ iouring_fd = evsys_io_uring_setup (iouring_entries, &params);
+
+ if (iouring_fd >= 0)
+ break; /* yippie */
+
+ if (errno != EINVAL)
+ return -1; /* we failed */
+
+#if TODO
+ if ((~params.features) & (IORING_FEAT_NODROP | IORING_FEATURE_SINGLE_MMAP | IORING_FEAT_SUBMIT_STABLE))
+ return -1; /* we require the above features */
+#endif
+
+ /* EINVAL: lots of possible reasons, but maybe
+ * it is because we hit the unqueryable hardcoded size limit
+ */
+
+ /* we hit the limit already, give up */
+ if (iouring_max_entries)
+ return -1;
+
+ /* first time we hit EINVAL? assume we hit the limit, so go back and retry */
+ iouring_entries >>= 1;
+ iouring_max_entries = iouring_entries;
+ }
+
+ iouring_sq_ring_size = params.sq_off.array + params.sq_entries * sizeof (unsigned);
+ iouring_cq_ring_size = params.cq_off.cqes + params.cq_entries * sizeof (struct io_uring_cqe);
+ iouring_sqes_size = params.sq_entries * sizeof (struct io_uring_sqe);
+
+ iouring_sq_ring = mmap (0, iouring_sq_ring_size, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_POPULATE, iouring_fd, IORING_OFF_SQ_RING);
+ iouring_cq_ring = mmap (0, iouring_cq_ring_size, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_POPULATE, iouring_fd, IORING_OFF_CQ_RING);
+ iouring_sqes = mmap (0, iouring_sqes_size, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_POPULATE, iouring_fd, IORING_OFF_SQES);
+
+ if (iouring_sq_ring == MAP_FAILED || iouring_cq_ring == MAP_FAILED || iouring_sqes == MAP_FAILED)
+ return -1;
+
+ iouring_sq_head = params.sq_off.head;
+ iouring_sq_tail = params.sq_off.tail;
+ iouring_sq_ring_mask = params.sq_off.ring_mask;
+ iouring_sq_ring_entries = params.sq_off.ring_entries;
+ iouring_sq_flags = params.sq_off.flags;
+ iouring_sq_dropped = params.sq_off.dropped;
+ iouring_sq_array = params.sq_off.array;
+
+ iouring_cq_head = params.cq_off.head;
+ iouring_cq_tail = params.cq_off.tail;
+ iouring_cq_ring_mask = params.cq_off.ring_mask;
+ iouring_cq_ring_entries = params.cq_off.ring_entries;
+ iouring_cq_overflow = params.cq_off.overflow;
+ iouring_cq_cqes = params.cq_off.cqes;
+
+ iouring_tfd = timerfd_create (CLOCK_MONOTONIC, TFD_CLOEXEC);
+
+ if (iouring_tfd < 0)
+ return iouring_tfd;
+
+ iouring_tfd_to = EV_TSTAMP_HUGE;
+
+ return 0;
+}
+
+ecb_cold
+static void
+iouring_fork (EV_P)
+{
+ iouring_internal_destroy (EV_A);
+
+ while (iouring_internal_init (EV_A) < 0)
+ ev_syserr ("(libev) io_uring_setup");
+
+ fd_rearm_all (EV_A);
+
+ ev_io_stop (EV_A_ &iouring_tfd_w);
+ ev_io_set (EV_A_ &iouring_tfd_w, iouring_tfd, EV_READ);
+ ev_io_start (EV_A_ &iouring_tfd_w);
+}
+
+/*****************************************************************************/
+
+static void
+iouring_modify (EV_P_ int fd, int oev, int nev)
+{
+ if (oev)
+ {
+ /* we assume the sqe's are all "properly" initialised */
+ struct io_uring_sqe *sqe = iouring_sqe_get (EV_A);
+ sqe->opcode = IORING_OP_POLL_REMOVE;
+ sqe->fd = fd;
+ /* Jens Axboe notified me that user_data is not what is documented, but is
+ * some kind of unique ID that has to match, otherwise the request cannot
+ * be removed. Since we don't *really* have that, we pass in the old
+ * generation counter - if that fails, too bad, it will hopefully be removed
+ * at close time and then be ignored. */
+ sqe->addr = (uint32_t)fd | ((__u64)(uint32_t)anfds [fd].egen << 32);
+ sqe->user_data = (uint64_t)-1;
+ iouring_sqe_submit (EV_A_ sqe);
+
+ /* increment generation counter to avoid handling old events */
+ ++anfds [fd].egen;
+ }
+
+ if (nev)
+ {
+ struct io_uring_sqe *sqe = iouring_sqe_get (EV_A);
+ sqe->opcode = IORING_OP_POLL_ADD;
+ sqe->fd = fd;
+ sqe->addr = 0;
+ sqe->user_data = (uint32_t)fd | ((__u64)(uint32_t)anfds [fd].egen << 32);
+ sqe->poll_events =
+ (nev & EV_READ ? POLLIN : 0)
+ | (nev & EV_WRITE ? POLLOUT : 0);
+ iouring_sqe_submit (EV_A_ sqe);
+ }
+}
+
+inline_size
+void
+iouring_tfd_update (EV_P_ ev_tstamp timeout)
+{
+ ev_tstamp tfd_to = mn_now + timeout;
+
+ /* we assume there will be many iterations per timer change, so
+ * we only re-set the timerfd when we have to because its expiry
+ * is too late.
+ */
+ if (ecb_expect_false (tfd_to < iouring_tfd_to))
+ {
+ struct itimerspec its;
+
+ iouring_tfd_to = tfd_to;
+ EV_TS_SET (its.it_interval, 0.);
+ EV_TS_SET (its.it_value, tfd_to);
+
+ if (timerfd_settime (iouring_tfd, TFD_TIMER_ABSTIME, &its, 0) < 0)
+ assert (("libev: iouring timerfd_settime failed", 0));
+ }
+}
+
+inline_size
+void
+iouring_process_cqe (EV_P_ struct io_uring_cqe *cqe)
+{
+ int fd = cqe->user_data & 0xffffffffU;
+ uint32_t gen = cqe->user_data >> 32;
+ int res = cqe->res;
+
+ /* user_data -1 is a remove that we are not atm. interested in */
+ if (cqe->user_data == (uint64_t)-1)
+ return;
+
+ assert (("libev: io_uring fd must be in-bounds", fd >= 0 && fd < anfdmax));
+
+ /* documentation lies, of course. the result value is NOT like
+ * normal syscalls, but like linux raw syscalls, i.e. negative
+ * error numbers. fortunate, as otherwise there would be no way
+ * to get error codes at all. still, why not document this?
+ */
+
+ /* ignore event if generation doesn't match */
+ /* other than skipping removal events, */
+ /* this should actually be very rare */
+ if (ecb_expect_false (gen != (uint32_t)anfds [fd].egen))
+ return;
+
+ if (ecb_expect_false (res < 0))
+ {
+ /*TODO: EINVAL handling (was something failed with this fd)*/
+
+ if (res == -EBADF)
+ {
+ assert (("libev: event loop rejected bad fd", res != -EBADF));
+ fd_kill (EV_A_ fd);
+ }
+ else
+ {
+ errno = -res;
+ ev_syserr ("(libev) IORING_OP_POLL_ADD");
+ }
+
+ return;
+ }
+
+ /* feed events, we do not expect or handle POLLNVAL */
+ fd_event (
+ EV_A_
+ fd,
+ (res & (POLLOUT | POLLERR | POLLHUP) ? EV_WRITE : 0)
+ | (res & (POLLIN | POLLERR | POLLHUP) ? EV_READ : 0)
+ );
+
+ /* io_uring is oneshot, so we need to re-arm the fd next iteration */
+ /* this also means we usually have to do at least one syscall per iteration */
+ anfds [fd].events = 0;
+ fd_change (EV_A_ fd, EV_ANFD_REIFY);
+}
+
+/* called when the event queue overflows */
+ecb_cold
+static void
+iouring_overflow (EV_P)
+{
+ /* we have two options, resize the queue (by tearing down
+ * everything and recreating it, or living with it
+ * and polling.
+ * we implement this by resizing the queue, and, if that fails,
+ * we just recreate the state on every failure, which
+ * kind of is a very inefficient poll.
+ * one danger is, due to the bios toward lower fds,
+ * we will only really get events for those, so
+ * maybe we need a poll() fallback, after all.
+ */
+ /*EV_CQ_VAR (overflow) = 0;*/ /* need to do this if we keep the state and poll manually */
+
+ fd_rearm_all (EV_A);
+
+ /* we double the size until we hit the hard-to-probe maximum */
+ if (!iouring_max_entries)
+ {
+ iouring_entries <<= 1;
+ iouring_fork (EV_A);
+ }
+ else
+ {
+ /* we hit the kernel limit, we should fall back to something else.
+ * we can either poll() a few times and hope for the best,
+ * poll always, or switch to epoll.
+ * TODO: is this necessary with newer kernels?
+ */
+
+ iouring_internal_destroy (EV_A);
+
+ /* this should make it so that on return, we don't call any uring functions */
+ iouring_to_submit = 0;
+
+ for (;;)
+ {
+ backend = epoll_init (EV_A_ 0);
+
+ if (backend)
+ break;
+
+ ev_syserr ("(libev) iouring switch to epoll");
+ }
+ }
+}
+
+/* handle any events in the completion queue, return true if there were any */
+static int
+iouring_handle_cq (EV_P)
+{
+ unsigned head, tail, mask;
+
+ head = EV_CQ_VAR (head);
+ ECB_MEMORY_FENCE_ACQUIRE;
+ tail = EV_CQ_VAR (tail);
+
+ if (head == tail)
+ return 0;
+
+ /* it can only overflow if we have events, yes, yes? */
+ if (ecb_expect_false (EV_CQ_VAR (overflow)))
+ {
+ iouring_overflow (EV_A);
+ return 1;
+ }
+
+ mask = EV_CQ_VAR (ring_mask);
+
+ do
+ iouring_process_cqe (EV_A_ &EV_CQES [head++ & mask]);
+ while (head != tail);
+
+ EV_CQ_VAR (head) = head;
+ ECB_MEMORY_FENCE_RELEASE;
+
+ return 1;
+}
+
+static void
+iouring_poll (EV_P_ ev_tstamp timeout)
+{
+ /* if we have events, no need for extra syscalls, but we might have to queue events */
+ /* we also clar the timeout if there are outstanding fdchanges */
+ /* the latter should only happen if both the sq and cq are full, most likely */
+ /* because we have a lot of event sources that immediately complete */
+ /* TODO: fdchacngecnt is always 0 because fd_reify does not have two buffers yet */
+ if (iouring_handle_cq (EV_A) || fdchangecnt)
+ timeout = EV_TS_CONST (0.);
+ else
+ /* no events, so maybe wait for some */
+ iouring_tfd_update (EV_A_ timeout);
+
+ /* only enter the kernel if we have something to submit, or we need to wait */
+ if (timeout || iouring_to_submit)
+ {
+ int res = iouring_enter (EV_A_ timeout);
+
+ if (ecb_expect_false (res < 0))
+ if (errno == EINTR)
+ /* ignore */;
+ else if (errno == EBUSY)
+ /* cq full, cannot submit - should be rare because we flush the cq first, so simply ignore */;
+ else
+ ev_syserr ("(libev) iouring setup");
+ else
+ iouring_handle_cq (EV_A);
+ }
+}
+
+inline_size
+int
+iouring_init (EV_P_ int flags)
+{
+ iouring_entries = IOURING_INIT_ENTRIES;
+ iouring_max_entries = 0;
+
+ if (iouring_internal_init (EV_A) < 0)
+ {
+ iouring_internal_destroy (EV_A);
+ return 0;
+ }
+
+ ev_io_init (&iouring_tfd_w, iouring_tfd_cb, iouring_tfd, EV_READ);
+ ev_set_priority (&iouring_tfd_w, EV_MINPRI);
+ ev_io_start (EV_A_ &iouring_tfd_w);
+ ev_unref (EV_A); /* watcher should not keep loop alive */
+
+ backend_modify = iouring_modify;
+ backend_poll = iouring_poll;
+
+ return EVBACKEND_IOURING;
+}
+
+inline_size
+void
+iouring_destroy (EV_P)
+{
+ iouring_internal_destroy (EV_A);
+}
+
diff --git a/3rdparty/libev/ev_kqueue.c b/3rdparty/libev/ev_kqueue.c
new file mode 100644
index 0000000..69c5147
--- /dev/null
+++ b/3rdparty/libev/ev_kqueue.c
@@ -0,0 +1,224 @@
+/*
+ * libev kqueue backend
+ *
+ * Copyright (c) 2007,2008,2009,2010,2011,2012,2013,2016,2019 Marc Alexander Lehmann <libev@schmorp.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
+ * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
+ * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
+ * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License ("GPL") version 2 or any later version,
+ * in which case the provisions of the GPL are applicable instead of
+ * the above. If you wish to allow the use of your version of this file
+ * only under the terms of the GPL and not to allow others to use your
+ * version of this file under the BSD license, indicate your decision
+ * by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL. If you do not delete the
+ * provisions above, a recipient may use your version of this file under
+ * either the BSD or the GPL.
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/event.h>
+#include <string.h>
+#include <errno.h>
+
+inline_speed
+void
+kqueue_change (EV_P_ int fd, int filter, int flags, int fflags)
+{
+ ++kqueue_changecnt;
+ array_needsize (struct kevent, kqueue_changes, kqueue_changemax, kqueue_changecnt, array_needsize_noinit);
+
+ EV_SET (&kqueue_changes [kqueue_changecnt - 1], fd, filter, flags, fflags, 0, 0);
+}
+
+/* OS X at least needs this */
+#ifndef EV_ENABLE
+# define EV_ENABLE 0
+#endif
+#ifndef NOTE_EOF
+# define NOTE_EOF 0
+#endif
+
+static void
+kqueue_modify (EV_P_ int fd, int oev, int nev)
+{
+ if (oev != nev)
+ {
+ if (oev & EV_READ)
+ kqueue_change (EV_A_ fd, EVFILT_READ , EV_DELETE, 0);
+
+ if (oev & EV_WRITE)
+ kqueue_change (EV_A_ fd, EVFILT_WRITE, EV_DELETE, 0);
+ }
+
+ /* to detect close/reopen reliably, we have to re-add */
+ /* event requests even when oev == nev */
+
+ if (nev & EV_READ)
+ kqueue_change (EV_A_ fd, EVFILT_READ , EV_ADD | EV_ENABLE, NOTE_EOF);
+
+ if (nev & EV_WRITE)
+ kqueue_change (EV_A_ fd, EVFILT_WRITE, EV_ADD | EV_ENABLE, NOTE_EOF);
+}
+
+static void
+kqueue_poll (EV_P_ ev_tstamp timeout)
+{
+ int res, i;
+ struct timespec ts;
+
+ /* need to resize so there is enough space for errors */
+ if (kqueue_changecnt > kqueue_eventmax)
+ {
+ ev_free (kqueue_events);
+ kqueue_eventmax = array_nextsize (sizeof (struct kevent), kqueue_eventmax, kqueue_changecnt);
+ kqueue_events = (struct kevent *)ev_malloc (sizeof (struct kevent) * kqueue_eventmax);
+ }
+
+ EV_RELEASE_CB;
+ EV_TS_SET (ts, timeout);
+ res = kevent (backend_fd, kqueue_changes, kqueue_changecnt, kqueue_events, kqueue_eventmax, &ts);
+ EV_ACQUIRE_CB;
+ kqueue_changecnt = 0;
+
+ if (ecb_expect_false (res < 0))
+ {
+ if (errno != EINTR)
+ ev_syserr ("(libev) kqueue kevent");
+
+ return;
+ }
+
+ for (i = 0; i < res; ++i)
+ {
+ int fd = kqueue_events [i].ident;
+
+ if (ecb_expect_false (kqueue_events [i].flags & EV_ERROR))
+ {
+ int err = kqueue_events [i].data;
+
+ /* we are only interested in errors for fds that we are interested in :) */
+ if (anfds [fd].events)
+ {
+ if (err == ENOENT) /* resubmit changes on ENOENT */
+ kqueue_modify (EV_A_ fd, 0, anfds [fd].events);
+ else if (err == EBADF) /* on EBADF, we re-check the fd */
+ {
+ if (fd_valid (fd))
+ kqueue_modify (EV_A_ fd, 0, anfds [fd].events);
+ else
+ {
+ assert (("libev: kqueue found invalid fd", 0));
+ fd_kill (EV_A_ fd);
+ }
+ }
+ else /* on all other errors, we error out on the fd */
+ {
+ assert (("libev: kqueue found invalid fd", 0));
+ fd_kill (EV_A_ fd);
+ }
+ }
+ }
+ else
+ fd_event (
+ EV_A_
+ fd,
+ kqueue_events [i].filter == EVFILT_READ ? EV_READ
+ : kqueue_events [i].filter == EVFILT_WRITE ? EV_WRITE
+ : 0
+ );
+ }
+
+ if (ecb_expect_false (res == kqueue_eventmax))
+ {
+ ev_free (kqueue_events);
+ kqueue_eventmax = array_nextsize (sizeof (struct kevent), kqueue_eventmax, kqueue_eventmax + 1);
+ kqueue_events = (struct kevent *)ev_malloc (sizeof (struct kevent) * kqueue_eventmax);
+ }
+}
+
+inline_size
+int
+kqueue_init (EV_P_ int flags)
+{
+ /* initialize the kernel queue */
+ kqueue_fd_pid = getpid ();
+ if ((backend_fd = kqueue ()) < 0)
+ return 0;
+
+ fcntl (backend_fd, F_SETFD, FD_CLOEXEC); /* not sure if necessary, hopefully doesn't hurt */
+
+ backend_mintime = EV_TS_CONST (1e-9); /* apparently, they did the right thing in freebsd */
+ backend_modify = kqueue_modify;
+ backend_poll = kqueue_poll;
+
+ kqueue_eventmax = 64; /* initial number of events receivable per poll */
+ kqueue_events = (struct kevent *)ev_malloc (sizeof (struct kevent) * kqueue_eventmax);
+
+ kqueue_changes = 0;
+ kqueue_changemax = 0;
+ kqueue_changecnt = 0;
+
+ return EVBACKEND_KQUEUE;
+}
+
+inline_size
+void
+kqueue_destroy (EV_P)
+{
+ ev_free (kqueue_events);
+ ev_free (kqueue_changes);
+}
+
+inline_size
+void
+kqueue_fork (EV_P)
+{
+ /* some BSD kernels don't just destroy the kqueue itself,
+ * but also close the fd, which isn't documented, and
+ * impossible to support properly.
+ * we remember the pid of the kqueue call and only close
+ * the fd if the pid is still the same.
+ * this leaks fds on sane kernels, but BSD interfaces are
+ * notoriously buggy and rarely get fixed.
+ */
+ pid_t newpid = getpid ();
+
+ if (newpid == kqueue_fd_pid)
+ close (backend_fd);
+
+ kqueue_fd_pid = newpid;
+ while ((backend_fd = kqueue ()) < 0)
+ ev_syserr ("(libev) kqueue");
+
+ fcntl (backend_fd, F_SETFD, FD_CLOEXEC);
+
+ /* re-register interest in fds */
+ fd_rearm_all (EV_A);
+}
+
+/* sys/event.h defines EV_ERROR */
+#undef EV_ERROR
+
diff --git a/3rdparty/libev/ev_linuxaio.c b/3rdparty/libev/ev_linuxaio.c
new file mode 100644
index 0000000..4687a70
--- /dev/null
+++ b/3rdparty/libev/ev_linuxaio.c
@@ -0,0 +1,620 @@
+/*
+ * libev linux aio fd activity backend
+ *
+ * Copyright (c) 2019 Marc Alexander Lehmann <libev@schmorp.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
+ * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
+ * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
+ * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License ("GPL") version 2 or any later version,
+ * in which case the provisions of the GPL are applicable instead of
+ * the above. If you wish to allow the use of your version of this file
+ * only under the terms of the GPL and not to allow others to use your
+ * version of this file under the BSD license, indicate your decision
+ * by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL. If you do not delete the
+ * provisions above, a recipient may use your version of this file under
+ * either the BSD or the GPL.
+ */
+
+/*
+ * general notes about linux aio:
+ *
+ * a) at first, the linux aio IOCB_CMD_POLL functionality introduced in
+ * 4.18 looks too good to be true: both watchers and events can be
+ * batched, and events can even be handled in userspace using
+ * a ring buffer shared with the kernel. watchers can be canceled
+ * regardless of whether the fd has been closed. no problems with fork.
+ * ok, the ring buffer is 200% undocumented (there isn't even a
+ * header file), but otherwise, it's pure bliss!
+ * b) ok, watchers are one-shot, so you have to re-arm active ones
+ * on every iteration. so much for syscall-less event handling,
+ * but at least these re-arms can be batched, no big deal, right?
+ * c) well, linux as usual: the documentation lies to you: io_submit
+ * sometimes returns EINVAL because the kernel doesn't feel like
+ * handling your poll mask - ttys can be polled for POLLOUT,
+ * POLLOUT|POLLIN, but polling for POLLIN fails. just great,
+ * so we have to fall back to something else (hello, epoll),
+ * but at least the fallback can be slow, because these are
+ * exceptional cases, right?
+ * d) hmm, you have to tell the kernel the maximum number of watchers
+ * you want to queue when initialising the aio context. but of
+ * course the real limit is magically calculated in the kernel, and
+ * is often higher then we asked for. so we just have to destroy
+ * the aio context and re-create it a bit larger if we hit the limit.
+ * (starts to remind you of epoll? well, it's a bit more deterministic
+ * and less gambling, but still ugly as hell).
+ * e) that's when you find out you can also hit an arbitrary system-wide
+ * limit. or the kernel simply doesn't want to handle your watchers.
+ * what the fuck do we do then? you guessed it, in the middle
+ * of event handling we have to switch to 100% epoll polling. and
+ * that better is as fast as normal epoll polling, so you practically
+ * have to use the normal epoll backend with all its quirks.
+ * f) end result of this train wreck: it inherits all the disadvantages
+ * from epoll, while adding a number on its own. why even bother to use
+ * it? because if conditions are right and your fds are supported and you
+ * don't hit a limit, this backend is actually faster, doesn't gamble with
+ * your fds, batches watchers and events and doesn't require costly state
+ * recreates. well, until it does.
+ * g) all of this makes this backend use almost twice as much code as epoll.
+ * which in turn uses twice as much code as poll. and that#s not counting
+ * the fact that this backend also depends on the epoll backend, making
+ * it three times as much code as poll, or kqueue.
+ * h) bleah. why can't linux just do kqueue. sure kqueue is ugly, but by now
+ * it's clear that whatever linux comes up with is far, far, far worse.
+ */
+
+#include <sys/time.h> /* actually linux/time.h, but we must assume they are compatible */
+#include <poll.h>
+#include <linux/aio_abi.h>
+
+/*****************************************************************************/
+/* syscall wrapdadoop - this section has the raw api/abi definitions */
+
+#include <sys/syscall.h> /* no glibc wrappers */
+
+/* aio_abi.h is not versioned in any way, so we cannot test for its existance */
+#define IOCB_CMD_POLL 5
+
+/* taken from linux/fs/aio.c. yup, that's a .c file.
+ * not only is this totally undocumented, not even the source code
+ * can tell you what the future semantics of compat_features and
+ * incompat_features are, or what header_length actually is for.
+ */
+#define AIO_RING_MAGIC 0xa10a10a1
+#define EV_AIO_RING_INCOMPAT_FEATURES 0
+struct aio_ring
+{
+ unsigned id; /* kernel internal index number */
+ unsigned nr; /* number of io_events */
+ unsigned head; /* Written to by userland or by kernel. */
+ unsigned tail;
+
+ unsigned magic;
+ unsigned compat_features;
+ unsigned incompat_features;
+ unsigned header_length; /* size of aio_ring */
+
+ struct io_event io_events[0];
+};
+
+inline_size
+int
+evsys_io_setup (unsigned nr_events, aio_context_t *ctx_idp)
+{
+ return ev_syscall2 (SYS_io_setup, nr_events, ctx_idp);
+}
+
+inline_size
+int
+evsys_io_destroy (aio_context_t ctx_id)
+{
+ return ev_syscall1 (SYS_io_destroy, ctx_id);
+}
+
+inline_size
+int
+evsys_io_submit (aio_context_t ctx_id, long nr, struct iocb *cbp[])
+{
+ return ev_syscall3 (SYS_io_submit, ctx_id, nr, cbp);
+}
+
+inline_size
+int
+evsys_io_cancel (aio_context_t ctx_id, struct iocb *cbp, struct io_event *result)
+{
+ return ev_syscall3 (SYS_io_cancel, ctx_id, cbp, result);
+}
+
+inline_size
+int
+evsys_io_getevents (aio_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout)
+{
+ return ev_syscall5 (SYS_io_getevents, ctx_id, min_nr, nr, events, timeout);
+}
+
+/*****************************************************************************/
+/* actual backed implementation */
+
+ecb_cold
+static int
+linuxaio_nr_events (EV_P)
+{
+ /* we start with 16 iocbs and incraese from there
+ * that's tiny, but the kernel has a rather low system-wide
+ * limit that can be reached quickly, so let's be parsimonious
+ * with this resource.
+ * Rest assured, the kernel generously rounds up small and big numbers
+ * in different ways (but doesn't seem to charge you for it).
+ * The 15 here is because the kernel usually has a power of two as aio-max-nr,
+ * and this helps to take advantage of that limit.
+ */
+
+ /* we try to fill 4kB pages exactly.
+ * the ring buffer header is 32 bytes, every io event is 32 bytes.
+ * the kernel takes the io requests number, doubles it, adds 2
+ * and adds the ring buffer.
+ * the way we use this is by starting low, and then roughly doubling the
+ * size each time we hit a limit.
+ */
+
+ int requests = 15 << linuxaio_iteration;
+ int one_page = (4096
+ / sizeof (struct io_event) ) / 2; /* how many fit into one page */
+ int first_page = ((4096 - sizeof (struct aio_ring))
+ / sizeof (struct io_event) - 2) / 2; /* how many fit into the first page */
+
+ /* if everything fits into one page, use count exactly */
+ if (requests > first_page)
+ /* otherwise, round down to full pages and add the first page */
+ requests = requests / one_page * one_page + first_page;
+
+ return requests;
+}
+
+/* we use out own wrapper structure in case we ever want to do something "clever" */
+typedef struct aniocb
+{
+ struct iocb io;
+ /*int inuse;*/
+} *ANIOCBP;
+
+inline_size
+void
+linuxaio_array_needsize_iocbp (ANIOCBP *base, int offset, int count)
+{
+ while (count--)
+ {
+ /* TODO: quite the overhead to allocate every iocb separately, maybe use our own allocator? */
+ ANIOCBP iocb = (ANIOCBP)ev_malloc (sizeof (*iocb));
+
+ /* full zero initialise is probably not required at the moment, but
+ * this is not well documented, so we better do it.
+ */
+ memset (iocb, 0, sizeof (*iocb));
+
+ iocb->io.aio_lio_opcode = IOCB_CMD_POLL;
+ iocb->io.aio_fildes = offset;
+
+ base [offset++] = iocb;
+ }
+}
+
+ecb_cold
+static void
+linuxaio_free_iocbp (EV_P)
+{
+ while (linuxaio_iocbpmax--)
+ ev_free (linuxaio_iocbps [linuxaio_iocbpmax]);
+
+ linuxaio_iocbpmax = 0; /* next resize will completely reallocate the array, at some overhead */
+}
+
+static void
+linuxaio_modify (EV_P_ int fd, int oev, int nev)
+{
+ array_needsize (ANIOCBP, linuxaio_iocbps, linuxaio_iocbpmax, fd + 1, linuxaio_array_needsize_iocbp);
+ ANIOCBP iocb = linuxaio_iocbps [fd];
+ ANFD *anfd = &anfds [fd];
+
+ if (ecb_expect_false (iocb->io.aio_reqprio < 0))
+ {
+ /* we handed this fd over to epoll, so undo this first */
+ /* we do it manually because the optimisations on epoll_modify won't do us any good */
+ epoll_ctl (backend_fd, EPOLL_CTL_DEL, fd, 0);
+ anfd->emask = 0;
+ iocb->io.aio_reqprio = 0;
+ }
+ else if (ecb_expect_false (iocb->io.aio_buf))
+ {
+ /* iocb active, so cancel it first before resubmit */
+ /* this assumes we only ever get one call per fd per loop iteration */
+ for (;;)
+ {
+ /* on all relevant kernels, io_cancel fails with EINPROGRESS on "success" */
+ if (ecb_expect_false (evsys_io_cancel (linuxaio_ctx, &iocb->io, (struct io_event *)0) == 0))
+ break;
+
+ if (ecb_expect_true (errno == EINPROGRESS))
+ break;
+
+ /* the EINPROGRESS test is for nicer error message. clumsy. */
+ if (errno != EINTR)
+ {
+ assert (("libev: linuxaio unexpected io_cancel failed", errno != EINTR && errno != EINPROGRESS));
+ break;
+ }
+ }
+
+ /* increment generation counter to avoid handling old events */
+ ++anfd->egen;
+ }
+
+ iocb->io.aio_buf = (nev & EV_READ ? POLLIN : 0)
+ | (nev & EV_WRITE ? POLLOUT : 0);
+
+ if (nev)
+ {
+ iocb->io.aio_data = (uint32_t)fd | ((__u64)(uint32_t)anfd->egen << 32);
+
+ /* queue iocb up for io_submit */
+ /* this assumes we only ever get one call per fd per loop iteration */
+ ++linuxaio_submitcnt;
+ array_needsize (struct iocb *, linuxaio_submits, linuxaio_submitmax, linuxaio_submitcnt, array_needsize_noinit);
+ linuxaio_submits [linuxaio_submitcnt - 1] = &iocb->io;
+ }
+}
+
+static void
+linuxaio_epoll_cb (EV_P_ struct ev_io *w, int revents)
+{
+ epoll_poll (EV_A_ 0);
+}
+
+inline_speed
+void
+linuxaio_fd_rearm (EV_P_ int fd)
+{
+ anfds [fd].events = 0;
+ linuxaio_iocbps [fd]->io.aio_buf = 0;
+ fd_change (EV_A_ fd, EV_ANFD_REIFY);
+}
+
+static void
+linuxaio_parse_events (EV_P_ struct io_event *ev, int nr)
+{
+ while (nr)
+ {
+ int fd = ev->data & 0xffffffff;
+ uint32_t gen = ev->data >> 32;
+ int res = ev->res;
+
+ assert (("libev: iocb fd must be in-bounds", fd >= 0 && fd < anfdmax));
+
+ /* only accept events if generation counter matches */
+ if (ecb_expect_true (gen == (uint32_t)anfds [fd].egen))
+ {
+ /* feed events, we do not expect or handle POLLNVAL */
+ fd_event (
+ EV_A_
+ fd,
+ (res & (POLLOUT | POLLERR | POLLHUP) ? EV_WRITE : 0)
+ | (res & (POLLIN | POLLERR | POLLHUP) ? EV_READ : 0)
+ );
+
+ /* linux aio is oneshot: rearm fd. TODO: this does more work than strictly needed */
+ linuxaio_fd_rearm (EV_A_ fd);
+ }
+
+ --nr;
+ ++ev;
+ }
+}
+
+/* get any events from ring buffer, return true if any were handled */
+static int
+linuxaio_get_events_from_ring (EV_P)
+{
+ struct aio_ring *ring = (struct aio_ring *)linuxaio_ctx;
+ unsigned head, tail;
+
+ /* the kernel reads and writes both of these variables, */
+ /* as a C extension, we assume that volatile use here */
+ /* both makes reads atomic and once-only */
+ head = *(volatile unsigned *)&ring->head;
+ ECB_MEMORY_FENCE_ACQUIRE;
+ tail = *(volatile unsigned *)&ring->tail;
+
+ if (head == tail)
+ return 0;
+
+ /* parse all available events, but only once, to avoid starvation */
+ if (ecb_expect_true (tail > head)) /* normal case around */
+ linuxaio_parse_events (EV_A_ ring->io_events + head, tail - head);
+ else /* wrapped around */
+ {
+ linuxaio_parse_events (EV_A_ ring->io_events + head, ring->nr - head);
+ linuxaio_parse_events (EV_A_ ring->io_events, tail);
+ }
+
+ ECB_MEMORY_FENCE_RELEASE;
+ /* as an extension to C, we hope that the volatile will make this atomic and once-only */
+ *(volatile unsigned *)&ring->head = tail;
+
+ return 1;
+}
+
+inline_size
+int
+linuxaio_ringbuf_valid (EV_P)
+{
+ struct aio_ring *ring = (struct aio_ring *)linuxaio_ctx;
+
+ return ecb_expect_true (ring->magic == AIO_RING_MAGIC)
+ && ring->incompat_features == EV_AIO_RING_INCOMPAT_FEATURES
+ && ring->header_length == sizeof (struct aio_ring); /* TODO: or use it to find io_event[0]? */
+}
+
+/* read at least one event from kernel, or timeout */
+inline_size
+void
+linuxaio_get_events (EV_P_ ev_tstamp timeout)
+{
+ struct timespec ts;
+ struct io_event ioev[8]; /* 256 octet stack space */
+ int want = 1; /* how many events to request */
+ int ringbuf_valid = linuxaio_ringbuf_valid (EV_A);
+
+ if (ecb_expect_true (ringbuf_valid))
+ {
+ /* if the ring buffer has any events, we don't wait or call the kernel at all */
+ if (linuxaio_get_events_from_ring (EV_A))
+ return;
+
+ /* if the ring buffer is empty, and we don't have a timeout, then don't call the kernel */
+ if (!timeout)
+ return;
+ }
+ else
+ /* no ringbuffer, request slightly larger batch */
+ want = sizeof (ioev) / sizeof (ioev [0]);
+
+ /* no events, so wait for some
+ * for fairness reasons, we do this in a loop, to fetch all events
+ */
+ for (;;)
+ {
+ int res;
+
+ EV_RELEASE_CB;
+
+ EV_TS_SET (ts, timeout);
+ res = evsys_io_getevents (linuxaio_ctx, 1, want, ioev, &ts);
+
+ EV_ACQUIRE_CB;
+
+ if (res < 0)
+ if (errno == EINTR)
+ /* ignored, retry */;
+ else
+ ev_syserr ("(libev) linuxaio io_getevents");
+ else if (res)
+ {
+ /* at least one event available, handle them */
+ linuxaio_parse_events (EV_A_ ioev, res);
+
+ if (ecb_expect_true (ringbuf_valid))
+ {
+ /* if we have a ring buffer, handle any remaining events in it */
+ linuxaio_get_events_from_ring (EV_A);
+
+ /* at this point, we should have handled all outstanding events */
+ break;
+ }
+ else if (res < want)
+ /* otherwise, if there were fewere events than we wanted, we assume there are no more */
+ break;
+ }
+ else
+ break; /* no events from the kernel, we are done */
+
+ timeout = EV_TS_CONST (0.); /* only wait in the first iteration */
+ }
+}
+
+inline_size
+int
+linuxaio_io_setup (EV_P)
+{
+ linuxaio_ctx = 0;
+ return evsys_io_setup (linuxaio_nr_events (EV_A), &linuxaio_ctx);
+}
+
+static void
+linuxaio_poll (EV_P_ ev_tstamp timeout)
+{
+ int submitted;
+
+ /* first phase: submit new iocbs */
+
+ /* io_submit might return less than the requested number of iocbs */
+ /* this is, afaics, only because of errors, but we go by the book and use a loop, */
+ /* which allows us to pinpoint the erroneous iocb */
+ for (submitted = 0; submitted < linuxaio_submitcnt; )
+ {
+ int res = evsys_io_submit (linuxaio_ctx, linuxaio_submitcnt - submitted, linuxaio_submits + submitted);
+
+ if (ecb_expect_false (res < 0))
+ if (errno == EINVAL)
+ {
+ /* This happens for unsupported fds, officially, but in my testing,
+ * also randomly happens for supported fds. We fall back to good old
+ * poll() here, under the assumption that this is a very rare case.
+ * See https://lore.kernel.org/patchwork/patch/1047453/ to see
+ * discussion about such a case (ttys) where polling for POLLIN
+ * fails but POLLIN|POLLOUT works.
+ */
+ struct iocb *iocb = linuxaio_submits [submitted];
+ epoll_modify (EV_A_ iocb->aio_fildes, 0, anfds [iocb->aio_fildes].events);
+ iocb->aio_reqprio = -1; /* mark iocb as epoll */
+
+ res = 1; /* skip this iocb - another iocb, another chance */
+ }
+ else if (errno == EAGAIN)
+ {
+ /* This happens when the ring buffer is full, or some other shit we
+ * don't know and isn't documented. Most likely because we have too
+ * many requests and linux aio can't be assed to handle them.
+ * In this case, we try to allocate a larger ring buffer, freeing
+ * ours first. This might fail, in which case we have to fall back to 100%
+ * epoll.
+ * God, how I hate linux not getting its act together. Ever.
+ */
+ evsys_io_destroy (linuxaio_ctx);
+ linuxaio_submitcnt = 0;
+
+ /* rearm all fds with active iocbs */
+ {
+ int fd;
+ for (fd = 0; fd < linuxaio_iocbpmax; ++fd)
+ if (linuxaio_iocbps [fd]->io.aio_buf)
+ linuxaio_fd_rearm (EV_A_ fd);
+ }
+
+ ++linuxaio_iteration;
+ if (linuxaio_io_setup (EV_A) < 0)
+ {
+ /* TODO: rearm all and recreate epoll backend from scratch */
+ /* TODO: might be more prudent? */
+
+ /* to bad, we can't get a new aio context, go 100% epoll */
+ linuxaio_free_iocbp (EV_A);
+ ev_io_stop (EV_A_ &linuxaio_epoll_w);
+ ev_ref (EV_A);
+ linuxaio_ctx = 0;
+
+ backend = EVBACKEND_EPOLL;
+ backend_modify = epoll_modify;
+ backend_poll = epoll_poll;
+ }
+
+ timeout = EV_TS_CONST (0.);
+ /* it's easiest to handle this mess in another iteration */
+ return;
+ }
+ else if (errno == EBADF)
+ {
+ assert (("libev: event loop rejected bad fd", errno != EBADF));
+ fd_kill (EV_A_ linuxaio_submits [submitted]->aio_fildes);
+
+ res = 1; /* skip this iocb */
+ }
+ else if (errno == EINTR) /* not seen in reality, not documented */
+ res = 0; /* silently ignore and retry */
+ else
+ {
+ ev_syserr ("(libev) linuxaio io_submit");
+ res = 0;
+ }
+
+ submitted += res;
+ }
+
+ linuxaio_submitcnt = 0;
+
+ /* second phase: fetch and parse events */
+
+ linuxaio_get_events (EV_A_ timeout);
+}
+
+inline_size
+int
+linuxaio_init (EV_P_ int flags)
+{
+ /* would be great to have a nice test for IOCB_CMD_POLL instead */
+ /* also: test some semi-common fd types, such as files and ttys in recommended_backends */
+ /* 4.18 introduced IOCB_CMD_POLL, 4.19 made epoll work, and we need that */
+ if (ev_linux_version () < 0x041300)
+ return 0;
+
+ if (!epoll_init (EV_A_ 0))
+ return 0;
+
+ linuxaio_iteration = 0;
+
+ if (linuxaio_io_setup (EV_A) < 0)
+ {
+ epoll_destroy (EV_A);
+ return 0;
+ }
+
+ ev_io_init (&linuxaio_epoll_w, linuxaio_epoll_cb, backend_fd, EV_READ);
+ ev_set_priority (&linuxaio_epoll_w, EV_MAXPRI);
+ ev_io_start (EV_A_ &linuxaio_epoll_w);
+ ev_unref (EV_A); /* watcher should not keep loop alive */
+
+ backend_modify = linuxaio_modify;
+ backend_poll = linuxaio_poll;
+
+ linuxaio_iocbpmax = 0;
+ linuxaio_iocbps = 0;
+
+ linuxaio_submits = 0;
+ linuxaio_submitmax = 0;
+ linuxaio_submitcnt = 0;
+
+ return EVBACKEND_LINUXAIO;
+}
+
+inline_size
+void
+linuxaio_destroy (EV_P)
+{
+ epoll_destroy (EV_A);
+ linuxaio_free_iocbp (EV_A);
+ evsys_io_destroy (linuxaio_ctx); /* fails in child, aio context is destroyed */
+}
+
+ecb_cold
+static void
+linuxaio_fork (EV_P)
+{
+ linuxaio_submitcnt = 0; /* all pointers were invalidated */
+ linuxaio_free_iocbp (EV_A); /* this frees all iocbs, which is very heavy-handed */
+ evsys_io_destroy (linuxaio_ctx); /* fails in child, aio context is destroyed */
+
+ linuxaio_iteration = 0; /* we start over in the child */
+
+ while (linuxaio_io_setup (EV_A) < 0)
+ ev_syserr ("(libev) linuxaio io_setup");
+
+ /* forking epoll should also effectively unregister all fds from the backend */
+ epoll_fork (EV_A);
+ /* epoll_fork already did this. hopefully */
+ /*fd_rearm_all (EV_A);*/
+
+ ev_io_stop (EV_A_ &linuxaio_epoll_w);
+ ev_io_set (EV_A_ &linuxaio_epoll_w, backend_fd, EV_READ);
+ ev_io_start (EV_A_ &linuxaio_epoll_w);
+}
+
diff --git a/3rdparty/libev/ev_poll.c b/3rdparty/libev/ev_poll.c
new file mode 100644
index 0000000..e5508dd
--- /dev/null
+++ b/3rdparty/libev/ev_poll.c
@@ -0,0 +1,156 @@
+/*
+ * libev poll fd activity backend
+ *
+ * Copyright (c) 2007,2008,2009,2010,2011,2016,2019 Marc Alexander Lehmann <libev@schmorp.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
+ * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
+ * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
+ * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License ("GPL") version 2 or any later version,
+ * in which case the provisions of the GPL are applicable instead of
+ * the above. If you wish to allow the use of your version of this file
+ * only under the terms of the GPL and not to allow others to use your
+ * version of this file under the BSD license, indicate your decision
+ * by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL. If you do not delete the
+ * provisions above, a recipient may use your version of this file under
+ * either the BSD or the GPL.
+ */
+
+#include <poll.h>
+
+inline_size
+void
+array_needsize_pollidx (int *base, int offset, int count)
+{
+ /* using memset (.., -1, ...) is tempting, we we try
+ * to be ultraportable
+ */
+ base += offset;
+ while (count--)
+ *base++ = -1;
+}
+
+static void
+poll_modify (EV_P_ int fd, int oev, int nev)
+{
+ int idx;
+
+ if (oev == nev)
+ return;
+
+ array_needsize (int, pollidxs, pollidxmax, fd + 1, array_needsize_pollidx);
+
+ idx = pollidxs [fd];
+
+ if (idx < 0) /* need to allocate a new pollfd */
+ {
+ pollidxs [fd] = idx = pollcnt++;
+ array_needsize (struct pollfd, polls, pollmax, pollcnt, array_needsize_noinit);
+ polls [idx].fd = fd;
+ }
+
+ assert (polls [idx].fd == fd);
+
+ if (nev)
+ polls [idx].events =
+ (nev & EV_READ ? POLLIN : 0)
+ | (nev & EV_WRITE ? POLLOUT : 0);
+ else /* remove pollfd */
+ {
+ pollidxs [fd] = -1;
+
+ if (ecb_expect_true (idx < --pollcnt))
+ {
+ polls [idx] = polls [pollcnt];
+ pollidxs [polls [idx].fd] = idx;
+ }
+ }
+}
+
+static void
+poll_poll (EV_P_ ev_tstamp timeout)
+{
+ struct pollfd *p;
+ int res;
+
+ EV_RELEASE_CB;
+ res = poll (polls, pollcnt, EV_TS_TO_MSEC (timeout));
+ EV_ACQUIRE_CB;
+
+ if (ecb_expect_false (res < 0))
+ {
+ if (errno == EBADF)
+ fd_ebadf (EV_A);
+ else if (errno == ENOMEM && !syserr_cb)
+ fd_enomem (EV_A);
+ else if (errno != EINTR)
+ ev_syserr ("(libev) poll");
+ }
+ else
+ for (p = polls; res; ++p)
+ {
+ assert (("libev: poll returned illegal result, broken BSD kernel?", p < polls + pollcnt));
+
+ if (ecb_expect_false (p->revents)) /* this expect is debatable */
+ {
+ --res;
+
+ if (ecb_expect_false (p->revents & POLLNVAL))
+ {
+ assert (("libev: poll found invalid fd in poll set", 0));
+ fd_kill (EV_A_ p->fd);
+ }
+ else
+ fd_event (
+ EV_A_
+ p->fd,
+ (p->revents & (POLLOUT | POLLERR | POLLHUP) ? EV_WRITE : 0)
+ | (p->revents & (POLLIN | POLLERR | POLLHUP) ? EV_READ : 0)
+ );
+ }
+ }
+}
+
+inline_size
+int
+poll_init (EV_P_ int flags)
+{
+ backend_mintime = EV_TS_CONST (1e-3);
+ backend_modify = poll_modify;
+ backend_poll = poll_poll;
+
+ pollidxs = 0; pollidxmax = 0;
+ polls = 0; pollmax = 0; pollcnt = 0;
+
+ return EVBACKEND_POLL;
+}
+
+inline_size
+void
+poll_destroy (EV_P)
+{
+ ev_free (pollidxs);
+ ev_free (polls);
+}
+
diff --git a/3rdparty/libev/ev_port.c b/3rdparty/libev/ev_port.c
new file mode 100644
index 0000000..f4cd9d9
--- /dev/null
+++ b/3rdparty/libev/ev_port.c
@@ -0,0 +1,192 @@
+/*
+ * libev solaris event port backend
+ *
+ * Copyright (c) 2007,2008,2009,2010,2011,2019 Marc Alexander Lehmann <libev@schmorp.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
+ * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
+ * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
+ * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License ("GPL") version 2 or any later version,
+ * in which case the provisions of the GPL are applicable instead of
+ * the above. If you wish to allow the use of your version of this file
+ * only under the terms of the GPL and not to allow others to use your
+ * version of this file under the BSD license, indicate your decision
+ * by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL. If you do not delete the
+ * provisions above, a recipient may use your version of this file under
+ * either the BSD or the GPL.
+ */
+
+/* useful reading:
+ *
+ * http://bugs.opensolaris.org/view_bug.do?bug_id=6268715 (random results)
+ * http://bugs.opensolaris.org/view_bug.do?bug_id=6455223 (just totally broken)
+ * http://bugs.opensolaris.org/view_bug.do?bug_id=6873782 (manpage ETIME)
+ * http://bugs.opensolaris.org/view_bug.do?bug_id=6874410 (implementation ETIME)
+ * http://www.mail-archive.com/networking-discuss@opensolaris.org/msg11898.html ETIME vs. nget
+ * http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/lib/libc/port/gen/event_port.c (libc)
+ * http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/uts/common/fs/portfs/port.c#1325 (kernel)
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <poll.h>
+#include <port.h>
+#include <string.h>
+#include <errno.h>
+
+inline_speed
+void
+port_associate_and_check (EV_P_ int fd, int ev)
+{
+ if (0 >
+ port_associate (
+ backend_fd, PORT_SOURCE_FD, fd,
+ (ev & EV_READ ? POLLIN : 0)
+ | (ev & EV_WRITE ? POLLOUT : 0),
+ 0
+ )
+ )
+ {
+ if (errno == EBADFD)
+ {
+ assert (("libev: port_associate found invalid fd", errno != EBADFD));
+ fd_kill (EV_A_ fd);
+ }
+ else
+ ev_syserr ("(libev) port_associate");
+ }
+}
+
+static void
+port_modify (EV_P_ int fd, int oev, int nev)
+{
+ /* we need to reassociate no matter what, as closes are
+ * once more silently being discarded.
+ */
+ if (!nev)
+ {
+ if (oev)
+ port_dissociate (backend_fd, PORT_SOURCE_FD, fd);
+ }
+ else
+ port_associate_and_check (EV_A_ fd, nev);
+}
+
+static void
+port_poll (EV_P_ ev_tstamp timeout)
+{
+ int res, i;
+ struct timespec ts;
+ uint_t nget = 1;
+
+ /* we initialise this to something we will skip in the loop, as */
+ /* port_getn can return with nget unchanged, but no indication */
+ /* whether it was the original value or has been updated :/ */
+ port_events [0].portev_source = 0;
+
+ EV_RELEASE_CB;
+ EV_TS_SET (ts, timeout);
+ res = port_getn (backend_fd, port_events, port_eventmax, &nget, &ts);
+ EV_ACQUIRE_CB;
+
+ /* port_getn may or may not set nget on error */
+ /* so we rely on port_events [0].portev_source not being updated */
+ if (res == -1 && errno != ETIME && errno != EINTR)
+ ev_syserr ("(libev) port_getn (see http://bugs.opensolaris.org/view_bug.do?bug_id=6268715, try LIBEV_FLAGS=3 env variable)");
+
+ for (i = 0; i < nget; ++i)
+ {
+ if (port_events [i].portev_source == PORT_SOURCE_FD)
+ {
+ int fd = port_events [i].portev_object;
+
+ fd_event (
+ EV_A_
+ fd,
+ (port_events [i].portev_events & (POLLOUT | POLLERR | POLLHUP) ? EV_WRITE : 0)
+ | (port_events [i].portev_events & (POLLIN | POLLERR | POLLHUP) ? EV_READ : 0)
+ );
+
+ fd_change (EV_A_ fd, EV__IOFDSET);
+ }
+ }
+
+ if (ecb_expect_false (nget == port_eventmax))
+ {
+ ev_free (port_events);
+ port_eventmax = array_nextsize (sizeof (port_event_t), port_eventmax, port_eventmax + 1);
+ port_events = (port_event_t *)ev_malloc (sizeof (port_event_t) * port_eventmax);
+ }
+}
+
+inline_size
+int
+port_init (EV_P_ int flags)
+{
+ /* Initialize the kernel queue */
+ if ((backend_fd = port_create ()) < 0)
+ return 0;
+
+ assert (("libev: PORT_SOURCE_FD must not be zero", PORT_SOURCE_FD));
+
+ fcntl (backend_fd, F_SETFD, FD_CLOEXEC); /* not sure if necessary, hopefully doesn't hurt */
+
+ /* if my reading of the opensolaris kernel sources are correct, then
+ * opensolaris does something very stupid: it checks if the time has already
+ * elapsed and doesn't round up if that is the case, otherwise it DOES round
+ * up. Since we can't know what the case is, we need to guess by using a
+ * "large enough" timeout. Normally, 1e-9 would be correct.
+ */
+ backend_mintime = EV_TS_CONST (1e-3); /* needed to compensate for port_getn returning early */
+ backend_modify = port_modify;
+ backend_poll = port_poll;
+
+ port_eventmax = 64; /* initial number of events receivable per poll */
+ port_events = (port_event_t *)ev_malloc (sizeof (port_event_t) * port_eventmax);
+
+ return EVBACKEND_PORT;
+}
+
+inline_size
+void
+port_destroy (EV_P)
+{
+ ev_free (port_events);
+}
+
+inline_size
+void
+port_fork (EV_P)
+{
+ close (backend_fd);
+
+ while ((backend_fd = port_create ()) < 0)
+ ev_syserr ("(libev) port");
+
+ fcntl (backend_fd, F_SETFD, FD_CLOEXEC);
+
+ /* re-register interest in fds */
+ fd_rearm_all (EV_A);
+}
+
diff --git a/3rdparty/libev/ev_select.c b/3rdparty/libev/ev_select.c
new file mode 100644
index 0000000..b862c81
--- /dev/null
+++ b/3rdparty/libev/ev_select.c
@@ -0,0 +1,316 @@
+/*
+ * libev select fd activity backend
+ *
+ * Copyright (c) 2007,2008,2009,2010,2011 Marc Alexander Lehmann <libev@schmorp.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
+ * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
+ * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
+ * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License ("GPL") version 2 or any later version,
+ * in which case the provisions of the GPL are applicable instead of
+ * the above. If you wish to allow the use of your version of this file
+ * only under the terms of the GPL and not to allow others to use your
+ * version of this file under the BSD license, indicate your decision
+ * by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL. If you do not delete the
+ * provisions above, a recipient may use your version of this file under
+ * either the BSD or the GPL.
+ */
+
+#ifndef _WIN32
+/* for unix systems */
+# include <inttypes.h>
+# ifndef __hpux
+/* for REAL unix systems */
+# include <sys/select.h>
+# endif
+#endif
+
+#ifndef EV_SELECT_USE_FD_SET
+# ifdef NFDBITS
+# define EV_SELECT_USE_FD_SET 0
+# else
+# define EV_SELECT_USE_FD_SET 1
+# endif
+#endif
+
+#if EV_SELECT_IS_WINSOCKET
+# undef EV_SELECT_USE_FD_SET
+# define EV_SELECT_USE_FD_SET 1
+# undef NFDBITS
+# define NFDBITS 0
+#endif
+
+#if !EV_SELECT_USE_FD_SET
+# define NFDBYTES (NFDBITS / 8)
+#endif
+
+#include <string.h>
+
+static void
+select_modify (EV_P_ int fd, int oev, int nev)
+{
+ if (oev == nev)
+ return;
+
+ {
+#if EV_SELECT_USE_FD_SET
+
+ #if EV_SELECT_IS_WINSOCKET
+ SOCKET handle = anfds [fd].handle;
+ #else
+ int handle = fd;
+ #endif
+
+ assert (("libev: fd >= FD_SETSIZE passed to fd_set-based select backend", fd < FD_SETSIZE));
+
+ /* FD_SET is broken on windows (it adds the fd to a set twice or more,
+ * which eventually leads to overflows). Need to call it only on changes.
+ */
+ #if EV_SELECT_IS_WINSOCKET
+ if ((oev ^ nev) & EV_READ)
+ #endif
+ if (nev & EV_READ)
+ FD_SET (handle, (fd_set *)vec_ri);
+ else
+ FD_CLR (handle, (fd_set *)vec_ri);
+
+ #if EV_SELECT_IS_WINSOCKET
+ if ((oev ^ nev) & EV_WRITE)
+ #endif
+ if (nev & EV_WRITE)
+ FD_SET (handle, (fd_set *)vec_wi);
+ else
+ FD_CLR (handle, (fd_set *)vec_wi);
+
+#else
+
+ int word = fd / NFDBITS;
+ fd_mask mask = 1UL << (fd % NFDBITS);
+
+ if (ecb_expect_false (vec_max <= word))
+ {
+ int new_max = word + 1;
+
+ vec_ri = ev_realloc (vec_ri, new_max * NFDBYTES);
+ vec_ro = ev_realloc (vec_ro, new_max * NFDBYTES); /* could free/malloc */
+ vec_wi = ev_realloc (vec_wi, new_max * NFDBYTES);
+ vec_wo = ev_realloc (vec_wo, new_max * NFDBYTES); /* could free/malloc */
+ #ifdef _WIN32
+ vec_eo = ev_realloc (vec_eo, new_max * NFDBYTES); /* could free/malloc */
+ #endif
+
+ for (; vec_max < new_max; ++vec_max)
+ ((fd_mask *)vec_ri) [vec_max] =
+ ((fd_mask *)vec_wi) [vec_max] = 0;
+ }
+
+ ((fd_mask *)vec_ri) [word] |= mask;
+ if (!(nev & EV_READ))
+ ((fd_mask *)vec_ri) [word] &= ~mask;
+
+ ((fd_mask *)vec_wi) [word] |= mask;
+ if (!(nev & EV_WRITE))
+ ((fd_mask *)vec_wi) [word] &= ~mask;
+#endif
+ }
+}
+
+static void
+select_poll (EV_P_ ev_tstamp timeout)
+{
+ struct timeval tv;
+ int res;
+ int fd_setsize;
+
+ EV_RELEASE_CB;
+ EV_TV_SET (tv, timeout);
+
+#if EV_SELECT_USE_FD_SET
+ fd_setsize = sizeof (fd_set);
+#else
+ fd_setsize = vec_max * NFDBYTES;
+#endif
+
+ memcpy (vec_ro, vec_ri, fd_setsize);
+ memcpy (vec_wo, vec_wi, fd_setsize);
+
+#ifdef _WIN32
+ /* pass in the write set as except set.
+ * the idea behind this is to work around a windows bug that causes
+ * errors to be reported as an exception and not by setting
+ * the writable bit. this is so uncontrollably lame.
+ */
+ memcpy (vec_eo, vec_wi, fd_setsize);
+ res = select (vec_max * NFDBITS, (fd_set *)vec_ro, (fd_set *)vec_wo, (fd_set *)vec_eo, &tv);
+#elif EV_SELECT_USE_FD_SET
+ fd_setsize = anfdmax < FD_SETSIZE ? anfdmax : FD_SETSIZE;
+ res = select (fd_setsize, (fd_set *)vec_ro, (fd_set *)vec_wo, 0, &tv);
+#else
+ res = select (vec_max * NFDBITS, (fd_set *)vec_ro, (fd_set *)vec_wo, 0, &tv);
+#endif
+ EV_ACQUIRE_CB;
+
+ if (ecb_expect_false (res < 0))
+ {
+ #if EV_SELECT_IS_WINSOCKET
+ errno = WSAGetLastError ();
+ #endif
+ #ifdef WSABASEERR
+ /* on windows, select returns incompatible error codes, fix this */
+ if (errno >= WSABASEERR && errno < WSABASEERR + 1000)
+ if (errno == WSAENOTSOCK)
+ errno = EBADF;
+ else
+ errno -= WSABASEERR;
+ #endif
+
+ #ifdef _WIN32
+ /* select on windows erroneously returns EINVAL when no fd sets have been
+ * provided (this is documented). what microsoft doesn't tell you that this bug
+ * exists even when the fd sets _are_ provided, so we have to check for this bug
+ * here and emulate by sleeping manually.
+ * we also get EINVAL when the timeout is invalid, but we ignore this case here
+ * and assume that EINVAL always means: you have to wait manually.
+ */
+ if (errno == EINVAL)
+ {
+ if (timeout)
+ {
+ unsigned long ms = EV_TS_TO_MSEC (timeout);
+ Sleep (ms ? ms : 1);
+ }
+
+ return;
+ }
+ #endif
+
+ if (errno == EBADF)
+ fd_ebadf (EV_A);
+ else if (errno == ENOMEM && !syserr_cb)
+ fd_enomem (EV_A);
+ else if (errno != EINTR)
+ ev_syserr ("(libev) select");
+
+ return;
+ }
+
+#if EV_SELECT_USE_FD_SET
+
+ {
+ int fd;
+
+ for (fd = 0; fd < anfdmax; ++fd)
+ if (anfds [fd].events)
+ {
+ int events = 0;
+ #if EV_SELECT_IS_WINSOCKET
+ SOCKET handle = anfds [fd].handle;
+ #else
+ int handle = fd;
+ #endif
+
+ if (FD_ISSET (handle, (fd_set *)vec_ro)) events |= EV_READ;
+ if (FD_ISSET (handle, (fd_set *)vec_wo)) events |= EV_WRITE;
+ #ifdef _WIN32
+ if (FD_ISSET (handle, (fd_set *)vec_eo)) events |= EV_WRITE;
+ #endif
+
+ if (ecb_expect_true (events))
+ fd_event (EV_A_ fd, events);
+ }
+ }
+
+#else
+
+ {
+ int word, bit;
+ for (word = vec_max; word--; )
+ {
+ fd_mask word_r = ((fd_mask *)vec_ro) [word];
+ fd_mask word_w = ((fd_mask *)vec_wo) [word];
+ #ifdef _WIN32
+ word_w |= ((fd_mask *)vec_eo) [word];
+ #endif
+
+ if (word_r || word_w)
+ for (bit = NFDBITS; bit--; )
+ {
+ fd_mask mask = 1UL << bit;
+ int events = 0;
+
+ events |= word_r & mask ? EV_READ : 0;
+ events |= word_w & mask ? EV_WRITE : 0;
+
+ if (ecb_expect_true (events))
+ fd_event (EV_A_ word * NFDBITS + bit, events);
+ }
+ }
+ }
+
+#endif
+}
+
+inline_size
+int
+select_init (EV_P_ int flags)
+{
+ backend_mintime = EV_TS_CONST (1e-6);
+ backend_modify = select_modify;
+ backend_poll = select_poll;
+
+#if EV_SELECT_USE_FD_SET
+ vec_ri = ev_malloc (sizeof (fd_set)); FD_ZERO ((fd_set *)vec_ri);
+ vec_ro = ev_malloc (sizeof (fd_set));
+ vec_wi = ev_malloc (sizeof (fd_set)); FD_ZERO ((fd_set *)vec_wi);
+ vec_wo = ev_malloc (sizeof (fd_set));
+ #ifdef _WIN32
+ vec_eo = ev_malloc (sizeof (fd_set));
+ #endif
+#else
+ vec_max = 0;
+ vec_ri = 0;
+ vec_ro = 0;
+ vec_wi = 0;
+ vec_wo = 0;
+ #ifdef _WIN32
+ vec_eo = 0;
+ #endif
+#endif
+
+ return EVBACKEND_SELECT;
+}
+
+inline_size
+void
+select_destroy (EV_P)
+{
+ ev_free (vec_ri);
+ ev_free (vec_ro);
+ ev_free (vec_wi);
+ ev_free (vec_wo);
+ #ifdef _WIN32
+ ev_free (vec_eo);
+ #endif
+}
+
diff --git a/3rdparty/libev/ev_vars.h b/3rdparty/libev/ev_vars.h
new file mode 100644
index 0000000..fb0c583
--- /dev/null
+++ b/3rdparty/libev/ev_vars.h
@@ -0,0 +1,249 @@
+/*
+ * loop member variable declarations
+ *
+ * Copyright (c) 2007,2008,2009,2010,2011,2012,2013,2019 Marc Alexander Lehmann <libev@schmorp.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
+ * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
+ * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
+ * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License ("GPL") version 2 or any later version,
+ * in which case the provisions of the GPL are applicable instead of
+ * the above. If you wish to allow the use of your version of this file
+ * only under the terms of the GPL and not to allow others to use your
+ * version of this file under the BSD license, indicate your decision
+ * by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL. If you do not delete the
+ * provisions above, a recipient may use your version of this file under
+ * either the BSD or the GPL.
+ */
+
+#define VARx(type,name) VAR(name, type name)
+
+VARx(ev_tstamp, now_floor) /* last time we refreshed rt_time */
+VARx(ev_tstamp, mn_now) /* monotonic clock "now" */
+VARx(ev_tstamp, rtmn_diff) /* difference realtime - monotonic time */
+
+/* for reverse feeding of events */
+VARx(W *, rfeeds)
+VARx(int, rfeedmax)
+VARx(int, rfeedcnt)
+
+VAR (pendings, ANPENDING *pendings [NUMPRI])
+VAR (pendingmax, int pendingmax [NUMPRI])
+VAR (pendingcnt, int pendingcnt [NUMPRI])
+VARx(int, pendingpri) /* highest priority currently pending */
+VARx(ev_prepare, pending_w) /* dummy pending watcher */
+
+VARx(ev_tstamp, io_blocktime)
+VARx(ev_tstamp, timeout_blocktime)
+
+VARx(int, backend)
+VARx(int, activecnt) /* total number of active events ("refcount") */
+VARx(EV_ATOMIC_T, loop_done) /* signal by ev_break */
+
+VARx(int, backend_fd)
+VARx(ev_tstamp, backend_mintime) /* assumed typical timer resolution */
+VAR (backend_modify, void (*backend_modify)(EV_P_ int fd, int oev, int nev))
+VAR (backend_poll , void (*backend_poll)(EV_P_ ev_tstamp timeout))
+
+VARx(ANFD *, anfds)
+VARx(int, anfdmax)
+
+VAR (evpipe, int evpipe [2])
+VARx(ev_io, pipe_w)
+VARx(EV_ATOMIC_T, pipe_write_wanted)
+VARx(EV_ATOMIC_T, pipe_write_skipped)
+
+#if !defined(_WIN32) || EV_GENWRAP
+VARx(pid_t, curpid)
+#endif
+
+VARx(char, postfork) /* true if we need to recreate kernel state after fork */
+
+#if EV_USE_SELECT || EV_GENWRAP
+VARx(void *, vec_ri)
+VARx(void *, vec_ro)
+VARx(void *, vec_wi)
+VARx(void *, vec_wo)
+#if defined(_WIN32) || EV_GENWRAP
+VARx(void *, vec_eo)
+#endif
+VARx(int, vec_max)
+#endif
+
+#if EV_USE_POLL || EV_GENWRAP
+VARx(struct pollfd *, polls)
+VARx(int, pollmax)
+VARx(int, pollcnt)
+VARx(int *, pollidxs) /* maps fds into structure indices */
+VARx(int, pollidxmax)
+#endif
+
+#if EV_USE_EPOLL || EV_GENWRAP
+VARx(struct epoll_event *, epoll_events)
+VARx(int, epoll_eventmax)
+VARx(int *, epoll_eperms)
+VARx(int, epoll_epermcnt)
+VARx(int, epoll_epermmax)
+#endif
+
+#if EV_USE_LINUXAIO || EV_GENWRAP
+VARx(aio_context_t, linuxaio_ctx)
+VARx(int, linuxaio_iteration)
+VARx(struct aniocb **, linuxaio_iocbps)
+VARx(int, linuxaio_iocbpmax)
+VARx(struct iocb **, linuxaio_submits)
+VARx(int, linuxaio_submitcnt)
+VARx(int, linuxaio_submitmax)
+VARx(ev_io, linuxaio_epoll_w)
+#endif
+
+#if EV_USE_IOURING || EV_GENWRAP
+VARx(int, iouring_fd)
+VARx(unsigned, iouring_to_submit);
+VARx(int, iouring_entries)
+VARx(int, iouring_max_entries)
+VARx(void *, iouring_sq_ring)
+VARx(void *, iouring_cq_ring)
+VARx(void *, iouring_sqes)
+VARx(uint32_t, iouring_sq_ring_size)
+VARx(uint32_t, iouring_cq_ring_size)
+VARx(uint32_t, iouring_sqes_size)
+VARx(uint32_t, iouring_sq_head)
+VARx(uint32_t, iouring_sq_tail)
+VARx(uint32_t, iouring_sq_ring_mask)
+VARx(uint32_t, iouring_sq_ring_entries)
+VARx(uint32_t, iouring_sq_flags)
+VARx(uint32_t, iouring_sq_dropped)
+VARx(uint32_t, iouring_sq_array)
+VARx(uint32_t, iouring_cq_head)
+VARx(uint32_t, iouring_cq_tail)
+VARx(uint32_t, iouring_cq_ring_mask)
+VARx(uint32_t, iouring_cq_ring_entries)
+VARx(uint32_t, iouring_cq_overflow)
+VARx(uint32_t, iouring_cq_cqes)
+VARx(ev_tstamp, iouring_tfd_to)
+VARx(int, iouring_tfd)
+VARx(ev_io, iouring_tfd_w)
+#endif
+
+#if EV_USE_KQUEUE || EV_GENWRAP
+VARx(pid_t, kqueue_fd_pid)
+VARx(struct kevent *, kqueue_changes)
+VARx(int, kqueue_changemax)
+VARx(int, kqueue_changecnt)
+VARx(struct kevent *, kqueue_events)
+VARx(int, kqueue_eventmax)
+#endif
+
+#if EV_USE_PORT || EV_GENWRAP
+VARx(struct port_event *, port_events)
+VARx(int, port_eventmax)
+#endif
+
+#if EV_USE_IOCP || EV_GENWRAP
+VARx(HANDLE, iocp)
+#endif
+
+VARx(int *, fdchanges)
+VARx(int, fdchangemax)
+VARx(int, fdchangecnt)
+
+VARx(ANHE *, timers)
+VARx(int, timermax)
+VARx(int, timercnt)
+
+#if EV_PERIODIC_ENABLE || EV_GENWRAP
+VARx(ANHE *, periodics)
+VARx(int, periodicmax)
+VARx(int, periodiccnt)
+#endif
+
+#if EV_IDLE_ENABLE || EV_GENWRAP
+VAR (idles, ev_idle **idles [NUMPRI])
+VAR (idlemax, int idlemax [NUMPRI])
+VAR (idlecnt, int idlecnt [NUMPRI])
+#endif
+VARx(int, idleall) /* total number */
+
+VARx(struct ev_prepare **, prepares)
+VARx(int, preparemax)
+VARx(int, preparecnt)
+
+VARx(struct ev_check **, checks)
+VARx(int, checkmax)
+VARx(int, checkcnt)
+
+#if EV_FORK_ENABLE || EV_GENWRAP
+VARx(struct ev_fork **, forks)
+VARx(int, forkmax)
+VARx(int, forkcnt)
+#endif
+
+#if EV_CLEANUP_ENABLE || EV_GENWRAP
+VARx(struct ev_cleanup **, cleanups)
+VARx(int, cleanupmax)
+VARx(int, cleanupcnt)
+#endif
+
+#if EV_ASYNC_ENABLE || EV_GENWRAP
+VARx(EV_ATOMIC_T, async_pending)
+VARx(struct ev_async **, asyncs)
+VARx(int, asyncmax)
+VARx(int, asynccnt)
+#endif
+
+#if EV_USE_INOTIFY || EV_GENWRAP
+VARx(int, fs_fd)
+VARx(ev_io, fs_w)
+VARx(char, fs_2625) /* whether we are running in linux 2.6.25 or newer */
+VAR (fs_hash, ANFS fs_hash [EV_INOTIFY_HASHSIZE])
+#endif
+
+VARx(EV_ATOMIC_T, sig_pending)
+#if EV_USE_SIGNALFD || EV_GENWRAP
+VARx(int, sigfd)
+VARx(ev_io, sigfd_w)
+VARx(sigset_t, sigfd_set)
+#endif
+
+#if EV_USE_TIMERFD || EV_GENWRAP
+VARx(int, timerfd) /* timerfd for time jump detection */
+VARx(ev_io, timerfd_w)
+#endif
+
+VARx(unsigned int, origflags) /* original loop flags */
+
+#if EV_FEATURE_API || EV_GENWRAP
+VARx(unsigned int, loop_count) /* total number of loop iterations/blocks */
+VARx(unsigned int, loop_depth) /* #ev_run enters - #ev_run leaves */
+
+VARx(void *, userdata)
+/* C++ doesn't support the ev_loop_callback typedef here. stinks. */
+VAR (release_cb, void (*release_cb)(EV_P) EV_NOEXCEPT)
+VAR (acquire_cb, void (*acquire_cb)(EV_P) EV_NOEXCEPT)
+VAR (invoke_cb , ev_loop_callback invoke_cb)
+#endif
+
+#undef VARx
+
diff --git a/3rdparty/libev/ev_win32.c b/3rdparty/libev/ev_win32.c
new file mode 100644
index 0000000..97344c3
--- /dev/null
+++ b/3rdparty/libev/ev_win32.c
@@ -0,0 +1,162 @@
+/*
+ * libev win32 compatibility cruft (_not_ a backend)
+ *
+ * Copyright (c) 2007,2008,2009 Marc Alexander Lehmann <libev@schmorp.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
+ * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
+ * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
+ * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License ("GPL") version 2 or any later version,
+ * in which case the provisions of the GPL are applicable instead of
+ * the above. If you wish to allow the use of your version of this file
+ * only under the terms of the GPL and not to allow others to use your
+ * version of this file under the BSD license, indicate your decision
+ * by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL. If you do not delete the
+ * provisions above, a recipient may use your version of this file under
+ * either the BSD or the GPL.
+ */
+
+#ifdef _WIN32
+
+/* note: the comment below could not be substantiated, but what would I care */
+/* MSDN says this is required to handle SIGFPE */
+/* my wild guess would be that using something floating-pointy is required */
+/* for the crt to do something about it */
+volatile double SIGFPE_REQ = 0.0f;
+
+static SOCKET
+ev_tcp_socket (void)
+{
+#if EV_USE_WSASOCKET
+ return WSASocket (AF_INET, SOCK_STREAM, 0, 0, 0, 0);
+#else
+ return socket (AF_INET, SOCK_STREAM, 0);
+#endif
+}
+
+/* oh, the humanity! */
+static int
+ev_pipe (int filedes [2])
+{
+ struct sockaddr_in addr = { 0 };
+ int addr_size = sizeof (addr);
+ struct sockaddr_in adr2;
+ int adr2_size = sizeof (adr2);
+ SOCKET listener;
+ SOCKET sock [2] = { -1, -1 };
+
+ if ((listener = ev_tcp_socket ()) == INVALID_SOCKET)
+ return -1;
+
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
+ addr.sin_port = 0;
+
+ if (bind (listener, (struct sockaddr *)&addr, addr_size))
+ goto fail;
+
+ if (getsockname (listener, (struct sockaddr *)&addr, &addr_size))
+ goto fail;
+
+ if (listen (listener, 1))
+ goto fail;
+
+ if ((sock [0] = ev_tcp_socket ()) == INVALID_SOCKET)
+ goto fail;
+
+ if (connect (sock [0], (struct sockaddr *)&addr, addr_size))
+ goto fail;
+
+ /* TODO: returns INVALID_SOCKET on winsock accept, not < 0. fix it */
+ /* when convenient, probably by just removing error checking altogether? */
+ if ((sock [1] = accept (listener, 0, 0)) < 0)
+ goto fail;
+
+ /* windows vista returns fantasy port numbers for sockets:
+ * example for two interconnected tcp sockets:
+ *
+ * (Socket::unpack_sockaddr_in getsockname $sock0)[0] == 53364
+ * (Socket::unpack_sockaddr_in getpeername $sock0)[0] == 53363
+ * (Socket::unpack_sockaddr_in getsockname $sock1)[0] == 53363
+ * (Socket::unpack_sockaddr_in getpeername $sock1)[0] == 53365
+ *
+ * wow! tridirectional sockets!
+ *
+ * this way of checking ports seems to work:
+ */
+ if (getpeername (sock [0], (struct sockaddr *)&addr, &addr_size))
+ goto fail;
+
+ if (getsockname (sock [1], (struct sockaddr *)&adr2, &adr2_size))
+ goto fail;
+
+ errno = WSAEINVAL;
+ if (addr_size != adr2_size
+ || addr.sin_addr.s_addr != adr2.sin_addr.s_addr /* just to be sure, I mean, it's windows */
+ || addr.sin_port != adr2.sin_port)
+ goto fail;
+
+ closesocket (listener);
+
+#if EV_SELECT_IS_WINSOCKET
+ filedes [0] = EV_WIN32_HANDLE_TO_FD (sock [0]);
+ filedes [1] = EV_WIN32_HANDLE_TO_FD (sock [1]);
+#else
+ /* when select isn't winsocket, we also expect socket, connect, accept etc.
+ * to work on fds */
+ filedes [0] = sock [0];
+ filedes [1] = sock [1];
+#endif
+
+ return 0;
+
+fail:
+ closesocket (listener);
+
+ if (sock [0] != INVALID_SOCKET) closesocket (sock [0]);
+ if (sock [1] != INVALID_SOCKET) closesocket (sock [1]);
+
+ return -1;
+}
+
+#undef pipe
+#define pipe(filedes) ev_pipe (filedes)
+
+#define EV_HAVE_EV_TIME 1
+ev_tstamp
+ev_time (void)
+{
+ FILETIME ft;
+ ULARGE_INTEGER ui;
+
+ GetSystemTimeAsFileTime (&ft);
+ ui.u.LowPart = ft.dwLowDateTime;
+ ui.u.HighPart = ft.dwHighDateTime;
+
+ /* also, msvc cannot convert ulonglong to double... yes, it is that sucky */
+ return EV_TS_FROM_USEC (((LONGLONG)(ui.QuadPart - 116444736000000000) * 1e-1));
+}
+
+#endif
+
diff --git a/3rdparty/libev/ev_wrap.h b/3rdparty/libev/ev_wrap.h
new file mode 100644
index 0000000..45d793c
--- /dev/null
+++ b/3rdparty/libev/ev_wrap.h
@@ -0,0 +1,272 @@
+/* DO NOT EDIT, automatically generated by update_ev_wrap */
+#ifndef EV_WRAP_H
+#define EV_WRAP_H
+#define acquire_cb ((loop)->acquire_cb)
+#define activecnt ((loop)->activecnt)
+#define anfdmax ((loop)->anfdmax)
+#define anfds ((loop)->anfds)
+#define async_pending ((loop)->async_pending)
+#define asynccnt ((loop)->asynccnt)
+#define asyncmax ((loop)->asyncmax)
+#define asyncs ((loop)->asyncs)
+#define backend ((loop)->backend)
+#define backend_fd ((loop)->backend_fd)
+#define backend_mintime ((loop)->backend_mintime)
+#define backend_modify ((loop)->backend_modify)
+#define backend_poll ((loop)->backend_poll)
+#define checkcnt ((loop)->checkcnt)
+#define checkmax ((loop)->checkmax)
+#define checks ((loop)->checks)
+#define cleanupcnt ((loop)->cleanupcnt)
+#define cleanupmax ((loop)->cleanupmax)
+#define cleanups ((loop)->cleanups)
+#define curpid ((loop)->curpid)
+#define epoll_epermcnt ((loop)->epoll_epermcnt)
+#define epoll_epermmax ((loop)->epoll_epermmax)
+#define epoll_eperms ((loop)->epoll_eperms)
+#define epoll_eventmax ((loop)->epoll_eventmax)
+#define epoll_events ((loop)->epoll_events)
+#define evpipe ((loop)->evpipe)
+#define fdchangecnt ((loop)->fdchangecnt)
+#define fdchangemax ((loop)->fdchangemax)
+#define fdchanges ((loop)->fdchanges)
+#define forkcnt ((loop)->forkcnt)
+#define forkmax ((loop)->forkmax)
+#define forks ((loop)->forks)
+#define fs_2625 ((loop)->fs_2625)
+#define fs_fd ((loop)->fs_fd)
+#define fs_hash ((loop)->fs_hash)
+#define fs_w ((loop)->fs_w)
+#define idleall ((loop)->idleall)
+#define idlecnt ((loop)->idlecnt)
+#define idlemax ((loop)->idlemax)
+#define idles ((loop)->idles)
+#define invoke_cb ((loop)->invoke_cb)
+#define io_blocktime ((loop)->io_blocktime)
+#define iocp ((loop)->iocp)
+#define iouring_cq_cqes ((loop)->iouring_cq_cqes)
+#define iouring_cq_head ((loop)->iouring_cq_head)
+#define iouring_cq_overflow ((loop)->iouring_cq_overflow)
+#define iouring_cq_ring ((loop)->iouring_cq_ring)
+#define iouring_cq_ring_entries ((loop)->iouring_cq_ring_entries)
+#define iouring_cq_ring_mask ((loop)->iouring_cq_ring_mask)
+#define iouring_cq_ring_size ((loop)->iouring_cq_ring_size)
+#define iouring_cq_tail ((loop)->iouring_cq_tail)
+#define iouring_entries ((loop)->iouring_entries)
+#define iouring_fd ((loop)->iouring_fd)
+#define iouring_max_entries ((loop)->iouring_max_entries)
+#define iouring_sq_array ((loop)->iouring_sq_array)
+#define iouring_sq_dropped ((loop)->iouring_sq_dropped)
+#define iouring_sq_flags ((loop)->iouring_sq_flags)
+#define iouring_sq_head ((loop)->iouring_sq_head)
+#define iouring_sq_ring ((loop)->iouring_sq_ring)
+#define iouring_sq_ring_entries ((loop)->iouring_sq_ring_entries)
+#define iouring_sq_ring_mask ((loop)->iouring_sq_ring_mask)
+#define iouring_sq_ring_size ((loop)->iouring_sq_ring_size)
+#define iouring_sq_tail ((loop)->iouring_sq_tail)
+#define iouring_sqes ((loop)->iouring_sqes)
+#define iouring_sqes_size ((loop)->iouring_sqes_size)
+#define iouring_tfd ((loop)->iouring_tfd)
+#define iouring_tfd_to ((loop)->iouring_tfd_to)
+#define iouring_tfd_w ((loop)->iouring_tfd_w)
+#define iouring_to_submit ((loop)->iouring_to_submit)
+#define kqueue_changecnt ((loop)->kqueue_changecnt)
+#define kqueue_changemax ((loop)->kqueue_changemax)
+#define kqueue_changes ((loop)->kqueue_changes)
+#define kqueue_eventmax ((loop)->kqueue_eventmax)
+#define kqueue_events ((loop)->kqueue_events)
+#define kqueue_fd_pid ((loop)->kqueue_fd_pid)
+#define linuxaio_ctx ((loop)->linuxaio_ctx)
+#define linuxaio_epoll_w ((loop)->linuxaio_epoll_w)
+#define linuxaio_iocbpmax ((loop)->linuxaio_iocbpmax)
+#define linuxaio_iocbps ((loop)->linuxaio_iocbps)
+#define linuxaio_iteration ((loop)->linuxaio_iteration)
+#define linuxaio_submitcnt ((loop)->linuxaio_submitcnt)
+#define linuxaio_submitmax ((loop)->linuxaio_submitmax)
+#define linuxaio_submits ((loop)->linuxaio_submits)
+#define loop_count ((loop)->loop_count)
+#define loop_depth ((loop)->loop_depth)
+#define loop_done ((loop)->loop_done)
+#define mn_now ((loop)->mn_now)
+#define now_floor ((loop)->now_floor)
+#define origflags ((loop)->origflags)
+#define pending_w ((loop)->pending_w)
+#define pendingcnt ((loop)->pendingcnt)
+#define pendingmax ((loop)->pendingmax)
+#define pendingpri ((loop)->pendingpri)
+#define pendings ((loop)->pendings)
+#define periodiccnt ((loop)->periodiccnt)
+#define periodicmax ((loop)->periodicmax)
+#define periodics ((loop)->periodics)
+#define pipe_w ((loop)->pipe_w)
+#define pipe_write_skipped ((loop)->pipe_write_skipped)
+#define pipe_write_wanted ((loop)->pipe_write_wanted)
+#define pollcnt ((loop)->pollcnt)
+#define pollidxmax ((loop)->pollidxmax)
+#define pollidxs ((loop)->pollidxs)
+#define pollmax ((loop)->pollmax)
+#define polls ((loop)->polls)
+#define port_eventmax ((loop)->port_eventmax)
+#define port_events ((loop)->port_events)
+#define postfork ((loop)->postfork)
+#define preparecnt ((loop)->preparecnt)
+#define preparemax ((loop)->preparemax)
+#define prepares ((loop)->prepares)
+#define release_cb ((loop)->release_cb)
+#define rfeedcnt ((loop)->rfeedcnt)
+#define rfeedmax ((loop)->rfeedmax)
+#define rfeeds ((loop)->rfeeds)
+#define rtmn_diff ((loop)->rtmn_diff)
+#define sig_pending ((loop)->sig_pending)
+#define sigfd ((loop)->sigfd)
+#define sigfd_set ((loop)->sigfd_set)
+#define sigfd_w ((loop)->sigfd_w)
+#define timeout_blocktime ((loop)->timeout_blocktime)
+#define timercnt ((loop)->timercnt)
+#define timerfd ((loop)->timerfd)
+#define timerfd_w ((loop)->timerfd_w)
+#define timermax ((loop)->timermax)
+#define timers ((loop)->timers)
+#define userdata ((loop)->userdata)
+#define vec_eo ((loop)->vec_eo)
+#define vec_max ((loop)->vec_max)
+#define vec_ri ((loop)->vec_ri)
+#define vec_ro ((loop)->vec_ro)
+#define vec_wi ((loop)->vec_wi)
+#define vec_wo ((loop)->vec_wo)
+#else
+#undef EV_WRAP_H
+#undef acquire_cb
+#undef activecnt
+#undef anfdmax
+#undef anfds
+#undef async_pending
+#undef asynccnt
+#undef asyncmax
+#undef asyncs
+#undef backend
+#undef backend_fd
+#undef backend_mintime
+#undef backend_modify
+#undef backend_poll
+#undef checkcnt
+#undef checkmax
+#undef checks
+#undef cleanupcnt
+#undef cleanupmax
+#undef cleanups
+#undef curpid
+#undef epoll_epermcnt
+#undef epoll_epermmax
+#undef epoll_eperms
+#undef epoll_eventmax
+#undef epoll_events
+#undef evpipe
+#undef fdchangecnt
+#undef fdchangemax
+#undef fdchanges
+#undef forkcnt
+#undef forkmax
+#undef forks
+#undef fs_2625
+#undef fs_fd
+#undef fs_hash
+#undef fs_w
+#undef idleall
+#undef idlecnt
+#undef idlemax
+#undef idles
+#undef invoke_cb
+#undef io_blocktime
+#undef iocp
+#undef iouring_cq_cqes
+#undef iouring_cq_head
+#undef iouring_cq_overflow
+#undef iouring_cq_ring
+#undef iouring_cq_ring_entries
+#undef iouring_cq_ring_mask
+#undef iouring_cq_ring_size
+#undef iouring_cq_tail
+#undef iouring_entries
+#undef iouring_fd
+#undef iouring_max_entries
+#undef iouring_sq_array
+#undef iouring_sq_dropped
+#undef iouring_sq_flags
+#undef iouring_sq_head
+#undef iouring_sq_ring
+#undef iouring_sq_ring_entries
+#undef iouring_sq_ring_mask
+#undef iouring_sq_ring_size
+#undef iouring_sq_tail
+#undef iouring_sqes
+#undef iouring_sqes_size
+#undef iouring_tfd
+#undef iouring_tfd_to
+#undef iouring_tfd_w
+#undef iouring_to_submit
+#undef kqueue_changecnt
+#undef kqueue_changemax
+#undef kqueue_changes
+#undef kqueue_eventmax
+#undef kqueue_events
+#undef kqueue_fd_pid
+#undef linuxaio_ctx
+#undef linuxaio_epoll_w
+#undef linuxaio_iocbpmax
+#undef linuxaio_iocbps
+#undef linuxaio_iteration
+#undef linuxaio_submitcnt
+#undef linuxaio_submitmax
+#undef linuxaio_submits
+#undef loop_count
+#undef loop_depth
+#undef loop_done
+#undef mn_now
+#undef now_floor
+#undef origflags
+#undef pending_w
+#undef pendingcnt
+#undef pendingmax
+#undef pendingpri
+#undef pendings
+#undef periodiccnt
+#undef periodicmax
+#undef periodics
+#undef pipe_w
+#undef pipe_write_skipped
+#undef pipe_write_wanted
+#undef pollcnt
+#undef pollidxmax
+#undef pollidxs
+#undef pollmax
+#undef polls
+#undef port_eventmax
+#undef port_events
+#undef postfork
+#undef preparecnt
+#undef preparemax
+#undef prepares
+#undef release_cb
+#undef rfeedcnt
+#undef rfeedmax
+#undef rfeeds
+#undef rtmn_diff
+#undef sig_pending
+#undef sigfd
+#undef sigfd_set
+#undef sigfd_w
+#undef timeout_blocktime
+#undef timercnt
+#undef timerfd
+#undef timerfd_w
+#undef timermax
+#undef timers
+#undef userdata
+#undef vec_eo
+#undef vec_max
+#undef vec_ri
+#undef vec_ro
+#undef vec_wi
+#undef vec_wo
+#endif
diff --git a/3rdparty/libev/event.c b/3rdparty/libev/event.c
new file mode 100644
index 0000000..5586cd3
--- /dev/null
+++ b/3rdparty/libev/event.c
@@ -0,0 +1,425 @@
+/*
+ * libevent compatibility layer
+ *
+ * Copyright (c) 2007,2008,2009,2010,2012 Marc Alexander Lehmann <libev@schmorp.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
+ * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
+ * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
+ * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License ("GPL") version 2 or any later version,
+ * in which case the provisions of the GPL are applicable instead of
+ * the above. If you wish to allow the use of your version of this file
+ * only under the terms of the GPL and not to allow others to use your
+ * version of this file under the BSD license, indicate your decision
+ * by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL. If you do not delete the
+ * provisions above, a recipient may use your version of this file under
+ * either the BSD or the GPL.
+ */
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#ifdef EV_EVENT_H
+# include EV_EVENT_H
+#else
+# include "event.h"
+#endif
+
+#if EV_MULTIPLICITY
+# define dLOOPev struct ev_loop *loop = (struct ev_loop *)ev->ev_base
+# define dLOOPbase struct ev_loop *loop = (struct ev_loop *)base
+#else
+# define dLOOPev
+# define dLOOPbase
+#endif
+
+/* never accessed, will always be cast from/to ev_loop */
+struct event_base
+{
+ int dummy;
+};
+
+static struct event_base *ev_x_cur;
+
+static ev_tstamp
+ev_tv_get (struct timeval *tv)
+{
+ if (tv)
+ {
+ ev_tstamp after = tv->tv_sec + tv->tv_usec * 1e-6;
+ return after ? after : 1e-6;
+ }
+ else
+ return -1.;
+}
+
+#define EVENT_STRINGIFY(s) # s
+#define EVENT_VERSION(a,b) EVENT_STRINGIFY (a) "." EVENT_STRINGIFY (b)
+
+const char *
+event_get_version (void)
+{
+ /* returns ABI, not API or library, version */
+ return EVENT_VERSION (EV_VERSION_MAJOR, EV_VERSION_MINOR);
+}
+
+const char *
+event_get_method (void)
+{
+ return "libev";
+}
+
+void *event_init (void)
+{
+#if EV_MULTIPLICITY
+ if (ev_x_cur)
+ ev_x_cur = (struct event_base *)ev_loop_new (EVFLAG_AUTO);
+ else
+ ev_x_cur = (struct event_base *)ev_default_loop (EVFLAG_AUTO);
+#else
+ assert (("libev: multiple event bases not supported when not compiled with EV_MULTIPLICITY", !ev_x_cur));
+
+ ev_x_cur = (struct event_base *)(long)ev_default_loop (EVFLAG_AUTO);
+#endif
+
+ return ev_x_cur;
+}
+
+const char *
+event_base_get_method (const struct event_base *base)
+{
+ return "libev";
+}
+
+struct event_base *
+event_base_new (void)
+{
+#if EV_MULTIPLICITY
+ return (struct event_base *)ev_loop_new (EVFLAG_AUTO);
+#else
+ assert (("libev: multiple event bases not supported when not compiled with EV_MULTIPLICITY"));
+ return NULL;
+#endif
+}
+
+void event_base_free (struct event_base *base)
+{
+ dLOOPbase;
+
+#if EV_MULTIPLICITY
+ if (!ev_is_default_loop (loop))
+ ev_loop_destroy (loop);
+#endif
+}
+
+int event_dispatch (void)
+{
+ return event_base_dispatch (ev_x_cur);
+}
+
+#ifdef EV_STANDALONE
+void event_set_log_callback (event_log_cb cb)
+{
+ /* nop */
+}
+#endif
+
+int event_loop (int flags)
+{
+ return event_base_loop (ev_x_cur, flags);
+}
+
+int event_loopexit (struct timeval *tv)
+{
+ return event_base_loopexit (ev_x_cur, tv);
+}
+
+event_callback_fn event_get_callback
+(const struct event *ev)
+{
+ return ev->ev_callback;
+}
+
+static void
+ev_x_cb (struct event *ev, int revents)
+{
+ revents &= EV_READ | EV_WRITE | EV_TIMER | EV_SIGNAL;
+
+ ev->ev_res = revents;
+ ev->ev_callback (ev->ev_fd, (short)revents, ev->ev_arg);
+}
+
+static void
+ev_x_cb_sig (EV_P_ struct ev_signal *w, int revents)
+{
+ struct event *ev = (struct event *)(((char *)w) - offsetof (struct event, iosig.sig));
+
+ if (revents & EV_ERROR)
+ event_del (ev);
+
+ ev_x_cb (ev, revents);
+}
+
+static void
+ev_x_cb_io (EV_P_ struct ev_io *w, int revents)
+{
+ struct event *ev = (struct event *)(((char *)w) - offsetof (struct event, iosig.io));
+
+ if ((revents & EV_ERROR) || !(ev->ev_events & EV_PERSIST))
+ event_del (ev);
+
+ ev_x_cb (ev, revents);
+}
+
+static void
+ev_x_cb_to (EV_P_ struct ev_timer *w, int revents)
+{
+ struct event *ev = (struct event *)(((char *)w) - offsetof (struct event, to));
+
+ event_del (ev);
+
+ ev_x_cb (ev, revents);
+}
+
+void event_set (struct event *ev, int fd, short events, void (*cb)(int, short, void *), void *arg)
+{
+ if (events & EV_SIGNAL)
+ ev_init (&ev->iosig.sig, ev_x_cb_sig);
+ else
+ ev_init (&ev->iosig.io, ev_x_cb_io);
+
+ ev_init (&ev->to, ev_x_cb_to);
+
+ ev->ev_base = ev_x_cur; /* not threadsafe, but it's how libevent works */
+ ev->ev_fd = fd;
+ ev->ev_events = events;
+ ev->ev_pri = 0;
+ ev->ev_callback = cb;
+ ev->ev_arg = arg;
+ ev->ev_res = 0;
+ ev->ev_flags = EVLIST_INIT;
+}
+
+int event_once (int fd, short events, void (*cb)(int, short, void *), void *arg, struct timeval *tv)
+{
+ return event_base_once (ev_x_cur, fd, events, cb, arg, tv);
+}
+
+int event_add (struct event *ev, struct timeval *tv)
+{
+ dLOOPev;
+
+ if (ev->ev_events & EV_SIGNAL)
+ {
+ if (!ev_is_active (&ev->iosig.sig))
+ {
+ ev_signal_set (&ev->iosig.sig, ev->ev_fd);
+ ev_signal_start (EV_A_ &ev->iosig.sig);
+
+ ev->ev_flags |= EVLIST_SIGNAL;
+ }
+ }
+ else if (ev->ev_events & (EV_READ | EV_WRITE))
+ {
+ if (!ev_is_active (&ev->iosig.io))
+ {
+ ev_io_set (&ev->iosig.io, ev->ev_fd, ev->ev_events & (EV_READ | EV_WRITE));
+ ev_io_start (EV_A_ &ev->iosig.io);
+
+ ev->ev_flags |= EVLIST_INSERTED;
+ }
+ }
+
+ if (tv)
+ {
+ ev->to.repeat = ev_tv_get (tv);
+ ev_timer_again (EV_A_ &ev->to);
+ ev->ev_flags |= EVLIST_TIMEOUT;
+ }
+ else
+ {
+ ev_timer_stop (EV_A_ &ev->to);
+ ev->ev_flags &= ~EVLIST_TIMEOUT;
+ }
+
+ ev->ev_flags |= EVLIST_ACTIVE;
+
+ return 0;
+}
+
+int event_del (struct event *ev)
+{
+ dLOOPev;
+
+ if (ev->ev_events & EV_SIGNAL)
+ ev_signal_stop (EV_A_ &ev->iosig.sig);
+ else if (ev->ev_events & (EV_READ | EV_WRITE))
+ ev_io_stop (EV_A_ &ev->iosig.io);
+
+ if (ev_is_active (&ev->to))
+ ev_timer_stop (EV_A_ &ev->to);
+
+ ev->ev_flags = EVLIST_INIT;
+
+ return 0;
+}
+
+void event_active (struct event *ev, int res, short ncalls)
+{
+ dLOOPev;
+
+ if (res & EV_TIMEOUT)
+ ev_feed_event (EV_A_ &ev->to, res & EV_TIMEOUT);
+
+ if (res & EV_SIGNAL)
+ ev_feed_event (EV_A_ &ev->iosig.sig, res & EV_SIGNAL);
+
+ if (res & (EV_READ | EV_WRITE))
+ ev_feed_event (EV_A_ &ev->iosig.io, res & (EV_READ | EV_WRITE));
+}
+
+int event_pending (struct event *ev, short events, struct timeval *tv)
+{
+ short revents = 0;
+ dLOOPev;
+
+ if (ev->ev_events & EV_SIGNAL)
+ {
+ /* sig */
+ if (ev_is_active (&ev->iosig.sig) || ev_is_pending (&ev->iosig.sig))
+ revents |= EV_SIGNAL;
+ }
+ else if (ev->ev_events & (EV_READ | EV_WRITE))
+ {
+ /* io */
+ if (ev_is_active (&ev->iosig.io) || ev_is_pending (&ev->iosig.io))
+ revents |= ev->ev_events & (EV_READ | EV_WRITE);
+ }
+
+ if (ev->ev_events & EV_TIMEOUT || ev_is_active (&ev->to) || ev_is_pending (&ev->to))
+ {
+ revents |= EV_TIMEOUT;
+
+ if (tv)
+ {
+ ev_tstamp at = ev_now (EV_A);
+
+ tv->tv_sec = (long)at;
+ tv->tv_usec = (long)((at - (ev_tstamp)tv->tv_sec) * 1e6);
+ }
+ }
+
+ return events & revents;
+}
+
+int event_priority_init (int npri)
+{
+ return event_base_priority_init (ev_x_cur, npri);
+}
+
+int event_priority_set (struct event *ev, int pri)
+{
+ ev->ev_pri = pri;
+
+ return 0;
+}
+
+int event_base_set (struct event_base *base, struct event *ev)
+{
+ ev->ev_base = base;
+
+ return 0;
+}
+
+int event_base_loop (struct event_base *base, int flags)
+{
+ dLOOPbase;
+
+ return !ev_run (EV_A_ flags);
+}
+
+int event_base_dispatch (struct event_base *base)
+{
+ return event_base_loop (base, 0);
+}
+
+static void
+ev_x_loopexit_cb (int revents, void *base)
+{
+ dLOOPbase;
+
+ ev_break (EV_A_ EVBREAK_ONE);
+}
+
+int event_base_loopexit (struct event_base *base, struct timeval *tv)
+{
+ ev_tstamp after = ev_tv_get (tv);
+ dLOOPbase;
+
+ ev_once (EV_A_ -1, 0, after >= 0. ? after : 0., ev_x_loopexit_cb, (void *)base);
+
+ return 0;
+}
+
+struct ev_x_once
+{
+ int fd;
+ void (*cb)(int, short, void *);
+ void *arg;
+};
+
+static void
+ev_x_once_cb (int revents, void *arg)
+{
+ struct ev_x_once *once = (struct ev_x_once *)arg;
+
+ once->cb (once->fd, (short)revents, once->arg);
+ free (once);
+}
+
+int event_base_once (struct event_base *base, int fd, short events, void (*cb)(int, short, void *), void *arg, struct timeval *tv)
+{
+ struct ev_x_once *once = (struct ev_x_once *)malloc (sizeof (struct ev_x_once));
+ dLOOPbase;
+
+ if (!once)
+ return -1;
+
+ once->fd = fd;
+ once->cb = cb;
+ once->arg = arg;
+
+ ev_once (EV_A_ fd, events & (EV_READ | EV_WRITE), ev_tv_get (tv), ev_x_once_cb, (void *)once);
+
+ return 0;
+}
+
+int event_base_priority_init (struct event_base *base, int npri)
+{
+ /*dLOOPbase;*/
+
+ return 0;
+}
+
diff --git a/3rdparty/libev/event.h b/3rdparty/libev/event.h
new file mode 100644
index 0000000..aa81928
--- /dev/null
+++ b/3rdparty/libev/event.h
@@ -0,0 +1,177 @@
+/*
+ * libevent compatibility header, only core events supported
+ *
+ * Copyright (c) 2007,2008,2010,2012 Marc Alexander Lehmann <libev@schmorp.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
+ * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
+ * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
+ * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License ("GPL") version 2 or any later version,
+ * in which case the provisions of the GPL are applicable instead of
+ * the above. If you wish to allow the use of your version of this file
+ * only under the terms of the GPL and not to allow others to use your
+ * version of this file under the BSD license, indicate your decision
+ * by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL. If you do not delete the
+ * provisions above, a recipient may use your version of this file under
+ * either the BSD or the GPL.
+ */
+
+#ifndef EVENT_H_
+#define EVENT_H_
+
+#ifdef EV_H
+# include EV_H
+#else
+# include "ev.h"
+#endif
+
+#ifndef EVLOOP_NONBLOCK
+# define EVLOOP_NONBLOCK EVRUN_NOWAIT
+#endif
+#ifndef EVLOOP_ONESHOT
+# define EVLOOP_ONESHOT EVRUN_ONCE
+#endif
+#ifndef EV_TIMEOUT
+# define EV_TIMEOUT EV_TIMER
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* we need sys/time.h for struct timeval only */
+#if !defined (WIN32) || defined (__MINGW32__)
+# include <time.h> /* mingw seems to need this, for whatever reason */
+# include <sys/time.h>
+#endif
+
+struct event_base;
+
+#define EVLIST_TIMEOUT 0x01
+#define EVLIST_INSERTED 0x02
+#define EVLIST_SIGNAL 0x04
+#define EVLIST_ACTIVE 0x08
+#define EVLIST_INTERNAL 0x10
+#define EVLIST_INIT 0x80
+
+typedef void (*event_callback_fn)(int, short, void *);
+
+struct event
+{
+ /* libev watchers we map onto */
+ union {
+ struct ev_io io;
+ struct ev_signal sig;
+ } iosig;
+ struct ev_timer to;
+
+ /* compatibility slots */
+ struct event_base *ev_base;
+ event_callback_fn ev_callback;
+ void *ev_arg;
+ int ev_fd;
+ int ev_pri;
+ int ev_res;
+ int ev_flags;
+ short ev_events;
+};
+
+event_callback_fn event_get_callback (const struct event *ev);
+
+#define EV_READ EV_READ
+#define EV_WRITE EV_WRITE
+#define EV_PERSIST 0x10
+#define EV_ET 0x20 /* nop */
+
+#define EVENT_SIGNAL(ev) ((int) (ev)->ev_fd)
+#define EVENT_FD(ev) ((int) (ev)->ev_fd)
+
+#define event_initialized(ev) ((ev)->ev_flags & EVLIST_INIT)
+
+#define evtimer_add(ev,tv) event_add (ev, tv)
+#define evtimer_set(ev,cb,data) event_set (ev, -1, 0, cb, data)
+#define evtimer_del(ev) event_del (ev)
+#define evtimer_pending(ev,tv) event_pending (ev, EV_TIMEOUT, tv)
+#define evtimer_initialized(ev) event_initialized (ev)
+
+#define timeout_add(ev,tv) evtimer_add (ev, tv)
+#define timeout_set(ev,cb,data) evtimer_set (ev, cb, data)
+#define timeout_del(ev) evtimer_del (ev)
+#define timeout_pending(ev,tv) evtimer_pending (ev, tv)
+#define timeout_initialized(ev) evtimer_initialized (ev)
+
+#define signal_add(ev,tv) event_add (ev, tv)
+#define signal_set(ev,sig,cb,data) event_set (ev, sig, EV_SIGNAL | EV_PERSIST, cb, data)
+#define signal_del(ev) event_del (ev)
+#define signal_pending(ev,tv) event_pending (ev, EV_SIGNAL, tv)
+#define signal_initialized(ev) event_initialized (ev)
+
+const char *event_get_version (void);
+const char *event_get_method (void);
+
+void *event_init (void);
+void event_base_free (struct event_base *base);
+
+#define EVLOOP_ONCE EVLOOP_ONESHOT
+int event_loop (int);
+int event_loopexit (struct timeval *tv);
+int event_dispatch (void);
+
+#define _EVENT_LOG_DEBUG 0
+#define _EVENT_LOG_MSG 1
+#define _EVENT_LOG_WARN 2
+#define _EVENT_LOG_ERR 3
+typedef void (*event_log_cb)(int severity, const char *msg);
+void event_set_log_callback(event_log_cb cb);
+
+void event_set (struct event *ev, int fd, short events, void (*cb)(int, short, void *), void *arg);
+int event_once (int fd, short events, void (*cb)(int, short, void *), void *arg, struct timeval *tv);
+
+int event_add (struct event *ev, struct timeval *tv);
+int event_del (struct event *ev);
+void event_active (struct event *ev, int res, short ncalls); /* ncalls is being ignored */
+
+int event_pending (struct event *ev, short, struct timeval *tv);
+
+int event_priority_init (int npri);
+int event_priority_set (struct event *ev, int pri);
+
+struct event_base *event_base_new (void);
+const char *event_base_get_method (const struct event_base *);
+int event_base_set (struct event_base *base, struct event *ev);
+int event_base_loop (struct event_base *base, int);
+int event_base_loopexit (struct event_base *base, struct timeval *tv);
+int event_base_dispatch (struct event_base *base);
+int event_base_once (struct event_base *base, int fd, short events, void (*cb)(int, short, void *), void *arg, struct timeval *tv);
+int event_base_priority_init (struct event_base *base, int fd);
+
+/* next line is different in the libevent+libev version */
+/*libevent-include*/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2fd839f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,50 @@
+# WEBRDP
+**webrdp** is modular multi-protocol *(currently only windows RDP implemented)* remote desktop client designed to be accessed via web ui by any modern browser.
+## Design:
+1. **webrdp** binary itself - is a small core application which implements load balancing, networking, websocket based internal protocol and remote desktop protocol module loading, currently all built into one static binary, with a little effort it is possible to implement dynamic module loading.
+2. static web data *(html + js code)* which implements client side protocol, drawing functions and user input handling
+## Compiling:
+### webrdp:
+1. to simplify compilation process - `builder.sh` script placed into repository root directory. this script allow to speed up build process, run `./builder.sh -h` to see available actions. building via script will also fetch and build few dependencies with appropriate versions which may be unavailable or have unsupported versions installed on current system.
+2. **webrdp** itself can be built from **codelite** workspace, but it is recommended to compile required dependencies via `builder.sh` script before any attempts to compile webrdp from codelite.
+3. you can try to build/link against your system libraries, but this is not recommended and unsupported, also you will require to edit project's settings in codelite and/or edit `builder.sh` script.
+4. it is also possible to build in guix/nix container *(in this case you will unable to compile webrdp from codelite directly)*, script `guix_build_env.sh` to create suitable guix environment is available, this may be necessary on systems with incompatible base libraries for example libressl instead of openssl *(my system)*
+### static web data:
+1. static web data is ready for use, does not need to compile anything, just upload to properly configured http(s) server.
+## Configuration:
+### webrdp:
+you can read config sample with comments at `doc/config_sample` this file always contain up to date information about all available settings
+### static web data:
+you can read config example for nginx web server at `doc/nginx_location`
+
+## Runing:
+### webrdp:
+you can see all available command line options via `-h` command line option.
+
+most settings read from config file. default config file locations are:
+
+* < path > passed by `-c` command line option
+* ~/.config/webrdp/config
+* /etc/webrdp/config
+### static web data:
+just start properly configure web server as usual.
+## Debugging:
+### General:
+webrdp itself does not have anything sepcial for debugging process, you can debug it just as any other C code with preffered debugger.
+### SafeConnect:
+as **webrdp** is often *(always?)* used as part of **SafeConnect** solution, it's wise to have information about SafeConnect configuration in order to have convenient way to debug **webrdp** with **SafeConnect**. SafeConnect configuration steps description follows:
+
+1. install **SafeConnect**, do initial configuration.
+2. switch to debug mode via `sc-playbook /opt/sc-ansible/configs/debug-local.yml` command in root shell on **SafeConnect** machine
+3. on web ui of **SafeConnect** go to "Ресурсы" and add new resource with following settings:
+* "Протокол": RDP
+* "Адрес": address of some machine with working RDP server **(regular machine with rdp server, not webrdp)**
+* "Порт": port on which RDP server is listening (default 3389)
+* "Название": resource name - does not matter
+4. on web ui of **SafeConnect** go to "Учетные записи" and add new account, where:
+* "Логин": login name on destination RDP server **(not webrdp server)**
+* "Пароль": password on destination RDP server **(not webrdp server)**
+* "Название": name - does not matter
+5. on web ui of **SafeConnect** go to "Политики" and edit "Глобальная политика" (press button with pencil on the top left column), in opened window you need to set `gate_address` to URI pointing web server hosting **static web data (not directly to webrdp port/socket), it may be necessary to edit `www/index.html` depending on web server setup, by default static web data expect to have webrdp accesible at /wrdp on current domain**
+6. on web ui of **SafeConnect** go to "Задания" click "Добавить" in opened window on "Исполнители" table click "Добавить" in opened window click on checkbox near "Газвание группы" at left, to select all available groups then click "Применить", on "Доступы" table click "Добавить", in opened window at "Ресурс" list select resource created at step 3, at "Удаленная учетная запись" list select account created at step 4. *NOTE: to see available entries in lists, start typing something at editbox*
+7. on web ui of **SafeConnect** go to "Главная", if all preveous steps done properly, you should see resource created at step 3, now you can finally start debugging of webrdp by clicking on this resource and on opened windows clicking on entry in list \ No newline at end of file
diff --git a/builder.sh b/builder.sh
new file mode 100755
index 0000000..eeb512d
--- /dev/null
+++ b/builder.sh
@@ -0,0 +1,254 @@
+#!/bin/bash
+#set -x
+
+SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
+
+BUILD_TYPE="Debug"
+ACTION=""
+CPU_CORE_COUNT=1
+COMPILER="clang"
+CFLAGS_RELEASE="-O2 -msse -msse2"
+CFLAGS_DEBUG="-O0 -g3 -ggdb3 -DDEBUG"
+
+if [ $(uname) == "Linux" ]; then
+ CPU_CORE_COUNT=$(getconf _NPROCESSORS_ONLN)
+elif [ $(uname) == "FreeBSD" ]; then
+ CPU_CORE_COUNT=$(getconf NPROCESSORS_ONLN)
+fi
+
+print_help()
+{
+ printf "INFO:\n\tsimple script to build webrdp."
+ printf "usage:\n\t-a\tAction:\t\tmake,clean,prepare\n\t-c\tCompiler:\tgcc,clang\n\t-b\tBuild type:\tRelease,Debug\n\t-h\tThis message\n"
+ exit 0
+}
+
+while getopts ":a:c:b:h" opt; do
+ case $opt in
+ a)
+ ACTION=$OPTARG
+ ;;
+ c)
+ COMPILER=$OPTARG
+ ;;
+ b)
+ BUILD_TYPE=$OPTARG
+ ;;
+ h)
+ print_help
+ ;;
+ esac
+done
+
+sanity_check()
+{
+ if [ -z "${ACTION}" ]; then
+ echo "Error: Action must be specified"
+ print_help
+ fi
+ if [[ "${ACTION}" != "make" && "${ACTION}" != "clean" && "${ACTION}" != "prepare" ]]; then
+ echo "Error: wrong action \"${ACTION}\""
+ print_help
+ fi
+ if [ "${ACTION}" == "make" ]; then
+ if [[ "${COMPILER}" != "gcc" && "${COMPILER}" != "clang" ]];then
+ echo "Error: wrong compiler \"${COMPILER}\""
+ print_help
+ fi
+ if [[ "${BUILD_TYPE}" != "Debug" && "${BUILD_TYPE}" != "Release" ]]; then
+ echo "Error: wrong build type \"${BUILD_TYPE}\""
+ print_help
+ fi
+ fi
+}
+
+
+case "${COMPILER}" in
+ clang)
+ CC=clang
+ CXX=clang++
+ ;;
+ gcc)
+ CC=gcc
+ CXX=g++
+ ;;
+esac
+
+sanity_check
+
+function clean {
+ rm -rf "${SCRIPTPATH}"/3rdparty/libev/build
+ rm -rf "${SCRIPTPATH}"/3rdparty/wslay/build
+ rm -rf "${SCRIPTPATH}"/3rdparty/FreeRDP/build
+ rm -rf "${SCRIPTPATH}"/3rdparty/curl/build
+ rm -rf "${SCRIPTPATH}"/src/core/build
+ rm -rf "${SCRIPTPATH}"/src/rdp/build
+}
+
+function prepare {
+ git fetch --all
+ git submodule foreach git reset --hard
+ git submodule update --init
+ if [ $(uname -a | grep -c "Debian") -ge 1 ]; then
+ su -c "apt install -y clang zlib1g-dev libssl-dev libpng-dev libunwind8-dev cmake gcc g++"
+ fi
+}
+
+function makeapp {
+ #build libev (static by default)
+ cd "${SCRIPTPATH}"/3rdparty/libev
+ mkdir build
+ cd build
+ cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_C_COMPILER=$CC -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_CXX_FLAGS="-fPIC" -DCMAKE_C_FLAGS_DEBUG="${CFLAGS_DEBUG}" -DCMAKE_CXX_FLAGS_DEBUG="${CFLAGS_DEBUG}" -DCMAKE_C_FLAGS_RELEASE="${CFLAGS_RELEASE}" -DCMAKE_CXX_FLAGS_RELEASE="${CFLAGS_RELEASE}" ../
+ make -j${CPU_CORE_COUNT}
+
+ #build wslay (static by default)
+ cd "${SCRIPTPATH}"/3rdparty/wslay
+ mkdir build
+ cd build
+ cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_C_COMPILER=$CC -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_CXX_FLAGS="-fPIC" -DCMAKE_C_FLAGS_DEBUG="${CFLAGS_DEBUG}" -DCMAKE_CXX_FLAGS_DEBUG="${CFLAGS_DEBUG}" -DCMAKE_C_FLAGS_RELEASE="${CFLAGS_RELEASE}" -DCMAKE_CXX_FLAGS_RELEASE="${CFLAGS_RELEASE}" ../
+ make -j${CPU_CORE_COUNT}
+
+ #build static version of libfreerdp2
+ cd "${SCRIPTPATH}"/3rdparty/FreeRDP
+ git reset --hard
+ git clean -df
+
+ mkdir build
+ cd build
+ FREERDP_CMAKE_OPTIONS=(
+ -DCMAKE_BUILD_TYPE=$BUILD_TYPE
+ -DWITH_LIBSYSTEMD=OFF
+ -DBUILD_SHARED_LIBS=OFF
+ -DWITH_CHANNELS=ON
+ -DWITH_CLIENT_CHANNELS=ON
+ -DCHANNEL_URBDRC=OFF
+ -DCHANNEL_URBDRC_CLIENT=OFF
+ -DCHANNEL_RDPDR_CLIENT=ON
+ -DCHANNEL_AUDIN_CLIENT=OFF
+ -DCHANNEL_AUDIN=OFF
+ -DCHANNEL_AINPUT_CLIENT=OFF
+ -DCHANNEL_SMARTCARD_CLIENT=OFF
+ -DWITH_CUPS=OFF
+ -DCHANNEL_RDPDR=ON
+ -DCHANNEL_PRINTER=OFF
+ -DWITH_LIBSYSTEMD=OFF
+ -DWITH_ALSA=OFF
+ -DWITH_PULSE=OFF
+ -DWITH_OSS=OFF
+ -DWITH_X11=OFF
+ -DWITH_FFMPEG=OFF
+ -DWITH_GSTREAMER_1_0=OFF
+ -DWITH_GSTREAMER_0_10=OFF
+ -DBUILD_TESTING=OFF
+ -DCMAKE_INSTALL_PREFIX:PATH=./
+ -DWITH_SERVER=OFF
+ -DWITH_SERVER_INTERFACE=OFF
+ -DWITH_WAYLAND=OFF
+ -DWITH_SERVER_CHANNELS=OFF
+ -DWITH_CLIENT=OFF
+ -DWITH_CLIENT_INTERFACE=ON
+ -DWITH_MANPAGES=OFF
+ -DWITH_XEXT=OFF
+ -DWITH_XRANDR=OFF
+ -DWITH_PCSC=OFF
+ -DCMAKE_C_COMPILER=$CC
+ -DCMAKE_CXX_COMPILER=$CXX
+ -DCMAKE_C_FLAGS="-fPIC"
+ -DCMAKE_CXX_FLAGS="-fPIC"
+ -DCMAKE_C_FLAGS_DEBUG="${CFLAGS_DEBUG}"
+ -DCMAKE_CXX_FLAGS_DEBUG="${CFLAGS_DEBUG}"
+ -DCMAKE_C_FLAGS_RELEASE="${CFLAGS_RELEASE}"
+ -DCMAKE_CXX_FLAGS_RELEASE="${CFLAGS_RELEASE}"
+ )
+ if [ ${BUILD_TYPE} == "Debug" ]; then
+ CURL_CMAKE_OPTIONS+=(
+ -DENABLE_DEBUG=ON
+ -DWITH_DEBUG_ALL=ON
+ )
+ fi
+ cmake "${FREERDP_CMAKE_OPTIONS[@]}" ../
+ make -j${CPU_CORE_COUNT}
+
+ #build curl
+ cd "${SCRIPTPATH}"/3rdparty/curl
+ mkdir build
+ cd build
+ CURL_CMAKE_OPTIONS=(
+ -DCMAKE_BUILD_TYPE=${BUILD_TYPE}
+ -DBUILD_SHARED_LIBS=OFF
+ -DCURL_DISABLE_LDAP=ON
+ -DCURL_DISABLE_LDAPS=ON
+ -DCURL_USE_LIBSSH2=OFF
+
+ -DBUILD_CURL_EXE=OFF
+ -DBUILD_TESTING=OFF
+ -DCURL_DISABLE_FILE=ON
+ -DCURL_DISABLE_DICT=ON
+ -DCURL_DISABLE_FTP=ON
+ -DCURL_DISABLE_GOPHER=OFF
+ -DCURL_DISABLE_IMAP=OFF
+ -DCURL_DISABLE_POP3=OFF
+ -DCURL_DISABLE_RTSP=OFF
+ -DCURL_DISABLE_SMTP=OFF
+ -DCURL_DISABLE_TELNET=ON
+ -DCURL_DISABLE_TFTP=OFF
+ -DENABLE_MANUAL=OFF
+#curl compiled in clang does not work properly on my system //sss
+ -DCMAKE_C_COMPILER=gcc
+ -DCMAKE_CXX_COMPILER=g++
+
+ -DCMAKE_C_FLAGS="-fPIC"
+ -DCMAKE_CXX_FLAGS="-fPIC"
+ -DCMAKE_C_FLAGS_DEBUG="${CFLAGS_DEBUG}"
+ -DCMAKE_CXX_FLAGS_DEBUG="${CFLAGS_DEBUG}"
+ -DCMAKE_C_FLAGS_RELEASE="${CFLAGS_RELEASE}"
+ -DCMAKE_CXX_FLAGS_RELEASE="${CFLAGS_RELEASE}"
+ )
+ CURL_LIB_NAME="libcurl.a"
+ if [ ${BUILD_TYPE} == "Debug" ]; then
+ CURL_LIB_NAME="libcurl-d.a"
+ CURL_CMAKE_OPTIONS+=( -DENABLE_DEBUG=ON )
+ fi
+ cmake "${CURL_CMAKE_OPTIONS[@]}" ../
+ make -j${CPU_CORE_COUNT}
+ mv ./lib/${CURL_LIB_NAME} ./libcurl.a
+
+ #build rdp backend module
+ cd "$SCRIPTPATH"/src/rdp
+ mkdir build
+ cd build
+ cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_C_COMPILER=$CC -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_FLAGS_DEBUG="${CFLAGS_DEBUG}" -DCMAKE_CXX_FLAGS_DEBUG="${CFLAGS_DEBUG}" -DCMAKE_C_FLAGS_RELEASE="${CFLAGS_RELEASE}" -DCMAKE_CXX_FLAGS_RELEASE="${CFLAGS_RELEASE}" ../
+ make -j${CPU_CORE_COUNT}
+
+ #build core
+ local MY_CORE_CFLAGS=""
+ cd "$SCRIPTPATH"/src/core
+ mkdir build
+ cd build
+ cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_C_COMPILER=$CC -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_FLAGS_DEBUG="${CFLAGS_DEBUG}" -DCMAKE_CXX_FLAGS_DEBUG="${CFLAGS_DEBUG}" -DCMAKE_C_FLAGS_RELEASE="${CFLAGS_RELEASE}" -DCMAKE_CXX_FLAGS_RELEASE="${CFLAGS_RELEASE}" ../
+ make -j${CPU_CORE_COUNT}
+
+ #move built binary to target directory
+ cd "$SCRIPTPATH"
+ mkdir build
+ rm build/core-$BUILD_TYPE
+ mv src/core/build/core build/core-$BUILD_TYPE
+ cd "$SCRIPTPATH"/src/tools
+ ${CC} ${CFLAGS_RELEASE} keygen.c -o "$SCRIPTPATH"/build/keygen
+}
+
+case "${ACTION}" in
+ make)
+ prepare
+# clean
+ makeapp
+ ;;
+ prepare)
+ prepare
+ ;;
+ clean)
+ clean
+ ;;
+
+esac
diff --git a/doc/TODO b/doc/TODO
new file mode 100644
index 0000000..9f0b7d6
--- /dev/null
+++ b/doc/TODO
@@ -0,0 +1,9 @@
+Optimization:
+ core:
+ ws_protocol:
+ 1. zero copy sending api
+ ws-protocol:
+ 1. compaction (currently all data stored as arrays of int32, including char (int8) arrays)
+ rdp module:
+ 1. use internal resource management via new switchable transport freerdp api
+
diff --git a/doc/auth_server_proto.json b/doc/auth_server_proto.json
new file mode 100644
index 0000000..b9ff117
--- /dev/null
+++ b/doc/auth_server_proto.json
@@ -0,0 +1,235 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "version": "1.0.0",
+ "title": "nstportal api",
+ "description": "Nst Portal api discriber",
+ "contact": {
+ "name": "New Safty Technology",
+ "email": "support@newinfosec.ru",
+ "url": "https://www.newinfosec.ru"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "https://www.apache.org/licenses/LICENSE-2.0.html"
+ }
+ },
+ "servers": [
+ {
+ "url": "/"
+ }
+ ],
+ "security": [
+ {
+ "cookieAuth": []
+ },
+ {
+ "CSRFtoken": []
+ }
+ ],
+ "paths": {
+ "/getconf/": {
+ "get": {
+ "security": [],
+ "description": "Returns all connection params for reqested session",
+ "operationId": "getConf",
+ "parameters": [
+ {
+ "name": "sid",
+ "in": "query",
+ "required": true,
+ "style": "form",
+ "schema": {
+ "$ref": "#/components/schemas/Sid"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "config response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Config"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ }
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "security": [],
+ "description": "Returns all connection params for reqested session",
+ "operationId": "getConf",
+ "parameters": [
+ {
+ "name": "sid",
+ "in": "query",
+ "required": true,
+ "style": "form",
+ "schema": {
+ "$ref": "#/components/schemas/Sid"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "config response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Config"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Answer": {
+ "description": "Base for any server answers",
+ "type": "string",
+ "enum": [
+ "setconf",
+ "getconf",
+ "error"
+ ]
+ },
+ "Sid": {
+ "description": "Sid(token) - случайный набор байт(64) в формате base64 urlsafe, подписанный hmac-sha256\n",
+ "type": "string",
+ "example": "xvwuWLOqAwIK2rlw9OoMLteD5aWKdzXbbApzLRKop4Vooc4CmEV3ZP9sHlrYMVHdNbHHdS9YtH5DFVycSZQmWG_TqjtRd06e2AQJ1OugAsdbWe7_p6GZ-9qO74k5CTkD"
+ },
+ "Config": {
+ "required": [
+ "answer"
+ ],
+ "properties": {
+ "answer": {
+ "$ref": "#/components/schemas/Answer"
+ },
+ "settings": {
+ "$ref": "#/components/schemas/Settings"
+ },
+ "host": {
+ "type": "string",
+ "example": "localhost",
+ "description": "адрес конечного ресурса или SI"
+ },
+ "login": {
+ "type": "string"
+ },
+ "password": {
+ "type": "string"
+ },
+ "port": {
+ "type": "integer"
+ },
+ "proto": {
+ "type": "string",
+ "enum": [
+ "ssh",
+ "rdp",
+ "http"
+ ]
+ },
+ "sid": {
+ "$ref": "#/components/schemas/Sid"
+ }
+ }
+ },
+ "Settings": {
+ "properties": {
+ "pcb": {
+ "type": "boolean"
+ },
+ "fntlm": {
+ "type": "boolean"
+ },
+ "nowallp": {
+ "type": "boolean"
+ },
+ "nowdrag": {
+ "type": "boolean"
+ },
+ "nomani": {
+ "type": "boolean"
+ },
+ "notheme": {
+ "type": "boolean"
+ },
+ "nonla": {
+ "type": "boolean"
+ },
+ "notls": {
+ "type": "boolean"
+ },
+ "dtsize": {
+ "type": "string",
+ "example": "1280x720"
+ },
+ "libsettings": {
+ "$ref": "#/components/schemas/LibSettings"
+ }
+ }
+ },
+ "LibSettings": {
+ "description": "\"https://github.com/FreeRDP/FreeRDP/blob/master/libfreerdp/core/settings.c\"\n",
+ "properties": {
+ "a": {
+ "type": "integer",
+ "enum": [
+ 0,
+ 1
+ ]
+ }
+ }
+ },
+ "Error": {
+ "required": [
+ "message"
+ ],
+ "properties": {
+ "message": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "securitySchemes": {
+ "cookieAuth": {
+ "type": "apiKey",
+ "in": "cookie",
+ "name": "portal_user"
+ },
+ "CSRFtoken": {
+ "type": "apiKey",
+ "in": "cookie",
+ "name": "xsrf"
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/doc/auth_server_proto.yaml b/doc/auth_server_proto.yaml
new file mode 100644
index 0000000..ee6f8cd
--- /dev/null
+++ b/doc/auth_server_proto.yaml
@@ -0,0 +1,146 @@
+openapi: 3.0.0
+info:
+ version: 1.0.0
+ title: nstportal api
+ description: Nst Portal api discriber
+ contact:
+ name: New Safty Technology
+ email: support@newinfosec.ru
+ url: 'https://www.newinfosec.ru'
+ license:
+ name: Apache 2.0
+ url: 'https://www.apache.org/licenses/LICENSE-2.0.html'
+servers:
+ - url: /
+security:
+ - cookieAuth: []
+ - CSRFtoken: []
+paths:
+ '/getconf/':
+ get:
+ security: []
+ description: Returns all connection params for reqested session
+ operationId: getConf
+ parameters:
+ - name: sid
+ in: query # path will also works
+ required: true
+ style: form
+ schema:
+ $ref: '#/components/schemas/Sid'
+ responses:
+ '200':
+ description: config response
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Config'
+ default:
+ description: unexpected error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ post:
+ security: []
+ description: Returns all connection params for reqested session
+ operationId: getConf
+ parameters:
+ - name: sid
+ in: query # path will also works
+ required: true
+ style: form
+ schema:
+ $ref: '#/components/schemas/Sid'
+ responses:
+ '200':
+ description: config response
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Config'
+ default:
+ description: unexpected error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+components:
+ schemas:
+ Answer:
+ description: Base for any server answers
+ type: string
+ enum: ['setconf', 'getconf', 'error']
+ Sid:
+ description: |
+ Sid(token) - случайный набор байт(64) в формате base64 urlsafe, подписанный hmac-sha256
+ type: string
+ example: "xvwuWLOqAwIK2rlw9OoMLteD5aWKdzXbbApzLRKop4Vooc4CmEV3ZP9sHlrYMVHdNbHHdS9YtH5DFVycSZQmWG_TqjtRd06e2AQJ1OugAsdbWe7_p6GZ-9qO74k5CTkD"
+ Config:
+ required:
+ - answer
+ properties:
+ answer:
+ $ref: '#/components/schemas/Answer'
+ settings:
+ $ref: '#/components/schemas/Settings'
+ host:
+ type: string
+ example: localhost
+ description: адрес конечного ресурса или SI
+ login:
+ type: string
+ password:
+ type: string
+ port:
+ type: integer
+ proto:
+ type: string
+ enum: ['ssh', 'rdp', 'http']
+ sid:
+ $ref: '#/components/schemas/Sid'
+ Settings:
+ properties:
+ pcb:
+ type: boolean
+ fntlm:
+ type: boolean
+ nowallp:
+ type: boolean
+ nowdrag:
+ type: boolean
+ nomani:
+ type: boolean
+ notheme:
+ type: boolean
+ nonla:
+ type: boolean
+ notls:
+ type: boolean
+ dtsize:
+ type: string
+ example: "1280x720"
+ libsettings:
+ $ref: '#/components/schemas/LibSettings'
+ LibSettings:
+ description: |
+ "https://github.com/FreeRDP/FreeRDP/blob/master/libfreerdp/core/settings.c"
+ properties:
+ a:
+ type: integer
+ enum: [ 0, 1 ]
+ Error:
+ required:
+ - message
+ properties:
+ message:
+ type: string
+ securitySchemes:
+ cookieAuth:
+ type: apiKey
+ in: cookie
+ name: portal_user
+ CSRFtoken:
+ type: apiKey
+ in: cookie
+ name: xsrf \ No newline at end of file
diff --git a/doc/config_sample b/doc/config_sample
new file mode 100644
index 0000000..d5a1a27
--- /dev/null
+++ b/doc/config_sample
@@ -0,0 +1,79 @@
+// this is described example of webrdp config file
+// NOTE: json format does not support comments, all comment line begining with "//" must be removed from file before using it
+{
+ // fork to background after start (not implemented yet)
+ // default: false
+ "daemon": false,
+
+ // set log_level to one of "error", "warning", "info", "debug", "trace"
+ // default: "error"
+ "log_level": "error"
+
+ // set worker thread count, 0 for one thread per logical cpu core in current system
+ // default: 0
+ "thread_count": 0,
+
+ // set maximum tasks per worker thread
+ // default: 1024
+ "tasks_per_thread": 1024,
+
+ // set http (websocket in plain non-ssl mode) listen port, set -1 to disable
+ // default: 8080
+ "ws_port": 8080,
+
+ // set http (websocket in plain non-ssl mode) socket path, set empty to disable
+ // default: disabled
+ "ws_socket_path": "/var/run/wrdp_sock",
+
+ // set external auth server url to handle token based authentication,
+ // can be any protocol supported by bundled version of libcurl
+ // default: unset
+ "auth_server_url": "protocol://server/path",
+
+ // set base64 encoded 512bit token verification key, mandatory option
+ "secret_key_verify": "key string",
+
+ // set base64 encoded 512bit token signing key
+ // "verify key" used if unset
+ "secret_key_sign": "key string",
+
+ // set control listen port, set -1 to disable
+ // default: 13666
+ "ctl_port": 13666,
+
+ // set control socket path, set empty to disable
+ // default: disabled
+ "ctl_socket_path": "",
+
+ // set control server CAfile pointing to file containing CA certificate(s) in PEM format, The file can contain several CA certificates, set empty to disable
+ // default: disabled
+ "ctl_ssl_cafile": "",
+
+ // set control server CApath pointing to directory containing CA certificates in PEM format, The files each contain one CA certificate. The files are looked up by the CA subject name hash value, which must hence be available.
+ // set empty to disable
+ // default: disabled
+ "ctl_ssl_capath": "",
+
+ // if both "ctl_ssl_cafile" and "ctl_ssl_capath" not set, system store will be used
+
+
+ // set control server ssl certificate in PEM format, mandatory option
+ "ctl_ssl_cert": "/path/to/cert.pem"
+
+ // set control server ssl key in PEM format, madatory option
+ "ctl_ssl_key": "/path/to/key.pem"
+
+
+
+ // set global session defaults for all backends
+ "session": {
+ // set maximum session time in sceonds, 0 for no limit
+ // default: 0
+ "session_time_limit": 0,
+
+ // set session idle timeout in seconds, 0 for no limit
+ // default: 0
+ "session_idle_timeout": 0
+ }
+}
+
diff --git a/doc/nginx_config_sample.conf b/doc/nginx_config_sample.conf
new file mode 100644
index 0000000..2358b86
--- /dev/null
+++ b/doc/nginx_config_sample.conf
@@ -0,0 +1,44 @@
+worker_processes 1;
+pcre_jit on;
+error_log /tmp/nginx_error.log error;
+pid ./nginx.pid;
+
+events {
+ worker_connections 512;
+ use epoll;
+ accept_mutex off;
+ multi_accept on;
+}
+
+http {
+ charset utf-8;
+ access_log off;
+ server_tokens off;
+ sendfile on;
+ sendfile_max_chunk 4M;
+ client_max_body_size 16m;
+ map $http_upigrade $connection_upgrade {
+ default Upgrade;
+ '' close;
+ }
+ server {
+ listen 192.168.0.2:8080;
+ location /rdp {
+ alias /home/sss/git/work/rozhuk_ivan/backend/www;
+ index index.html;
+
+ }
+ location /wrdp {
+
+ #address of webrdp listen port/socket should be set here
+ proxy_pass http://192.168.0.2:8081;
+
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection $connection_upgrade;
+ }
+ }
+
+
+}
+
diff --git a/doc/nginx_location b/doc/nginx_location
new file mode 100644
index 0000000..8b5e396
--- /dev/null
+++ b/doc/nginx_location
@@ -0,0 +1,31 @@
+#nginx location example
+
+#....
+
+#NOTE: "Upgrade", first letter must be large
+map $http_upgrade $connection_upgrade {
+ default Upgrade;
+ '' close;
+}
+
+#......
+
+server {
+
+ #....
+
+ #NOTE: no trailing slash '/' in location
+ location /wrdp {
+
+ #address of webrdp listen port/socket should be set here
+ proxy_pass http://192.168.0.2:8080;
+
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection $connection_upgrade;
+ }
+
+ #....
+
+}
+
diff --git a/doc/remote_control_protocol b/doc/remote_control_protocol
new file mode 100644
index 0000000..c42c379
--- /dev/null
+++ b/doc/remote_control_protocol
@@ -0,0 +1,70 @@
+// this file describing remote control protocol in json format
+/ /NOTE: json format does not support comments, all comment line begining with "//" must be removed from file before using it
+
+
+// get list of ids of all runing sessions
+// client->server
+{
+ "cmd": "get_session_list"
+}
+// server->client
+{
+ "sessions": [
+ "id",
+ "id",
+ ...
+ ]
+}
+
+// destroy session
+// client->server
+{
+ "cmd": "kill",
+ // one or more session id got by "get_session_list"
+ "sessions": [
+ "sid",
+ ...
+ ],
+ //message for client
+ "message": "you`re fired",
+ // set random string as sequence id to associate response with this exact request
+ // optional parameter
+ "seq_id": "random_id"
+}
+
+// update connection settings
+// client->server
+{
+ "cmd": "update",
+ // one or more session id got by "get_session_list"
+ "sessions": [
+ {
+ "sid": "sid",
+ "settings": ""
+ },
+ ...
+ ],
+ // set random string as sequence id to associate response with this exact request
+ // optional parameter
+ "seq_id": "random_id"
+}
+
+// server->client
+{
+ // status can be one of: "ok", "error"
+ "status": "ok",
+
+ // response may contain error description in case of error
+ // optional parameter
+ "error_description": "some readable information about error",
+
+ // one ore more session id
+ "sessions": [
+ "sid",
+ ...
+ ],
+
+ // string passed as "seq_id" in associated request
+ // optional parameter
+ "seq_id": "random_id"
+}
diff --git a/guix_build_env.sh b/guix_build_env.sh
new file mode 100755
index 0000000..c07003a
--- /dev/null
+++ b/guix_build_env.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+guix time-machine --commit=d34371c92b495d2386d4dc19ab635d5626cf11aa --channels=./guix_channels.scm -- environment --ad-hoc openssl fuse libusb linux-libre-headers zlib libunwind libpng --pure pkg-config cmake bash grep make clang binutils lld coreutils gcc make git sed --container
+
diff --git a/guix_channels.scm b/guix_channels.scm
new file mode 100644
index 0000000..787f173
--- /dev/null
+++ b/guix_channels.scm
@@ -0,0 +1,39 @@
+(cons*
+ (channel
+ (name 'guix)
+ (url "https://git.savannah.gnu.org/git/guix.git"))
+ ; ;; Enable signature verification:
+ ; (introduction
+ ; (make-channel-introduction
+ ; "897c1a470da759236cc11798f4e0a5f7d4d59fbc"
+ ; (openpgp-fingerprint
+ ; "2A39 3FFF 68F4 EF7A 3D29 12AF 6F51 20A0 22FB B2D5"))))
+ ; (channel
+ ; (name 'guix-gaming-games)
+ ; (url "https://gitlab.com/guix-gaming-channels/games.git")
+ ; ;; Enable signature verification:
+ ; (introduction
+ ; (make-channel-introduction
+ ; "c23d64f1b8cc086659f8781b27ab6c7314c5cca5"
+ ; (openpgp-fingerprint
+ ; "50F3 3E2E 5B0C 3D90 0424 ABE8 9BDC F497 A4BB CC7F")))
+ ; )
+ ; (channel
+ ; (name 'duke-nukem-3d)
+ ; (url "https://gitlab.com/guix-gaming-channels/duke-nukem-3d.git"))
+ ; (channel
+ ; (name 'quake-3)
+ ; (url "https://gitlab.com/guix-gaming-channels/quake-3.git"))
+ ; (channel
+ ; (name 'katco-non-free)
+ ; (url "https://github.com/kat-co/guix-channels.git")
+ ; (branch "non-free"))
+ ; (channel
+ ; (name 'katco-upstream-staging)
+ ; (url "https://github.com/kat-co/guix-channels.git")
+ ; (branch "upstream-staging"))
+ ; (channel
+ ; (name 'wigust)
+ ; (url "https://notabug.org/wigust/guix-wigust"))
+ %default-channels)
+
diff --git a/run_clang_format.sh b/run_clang_format.sh
new file mode 100755
index 0000000..b7c6025
--- /dev/null
+++ b/run_clang_format.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+find ./src/ -type f \( -iname "*.c" -o -iname "*.cpp" -o -iname "*.cc" -o -iname "*.h" -o -iname "*.hpp" \) -exec clang-format --style=file -i '{}' \;
+
diff --git a/src/.clang-format b/src/.clang-format
new file mode 120000
index 0000000..7cab60c
--- /dev/null
+++ b/src/.clang-format
@@ -0,0 +1 @@
+../.clang-format \ No newline at end of file
diff --git a/src/backend.workspace b/src/backend.workspace
new file mode 100644
index 0000000..cc2b815
--- /dev/null
+++ b/src/backend.workspace
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CodeLite_Workspace Name="backend" Database="" Version="10.0.0">
+ <Project Name="core" Path="core/core.project" Active="Yes"/>
+ <Project Name="rdp" Path="rdp/rdp.project" Active="No"/>
+ <BuildMatrix>
+ <WorkspaceConfiguration Name="Debug" Selected="yes">
+ <Environment/>
+ <Project Name="core" ConfigName="Debug"/>
+ <Project Name="rdp" ConfigName="Debug"/>
+ </WorkspaceConfiguration>
+ <WorkspaceConfiguration Name="Release" Selected="no">
+ <Environment/>
+ <Project Name="core" ConfigName="Release"/>
+ <Project Name="rdp" ConfigName="Release"/>
+ </WorkspaceConfiguration>
+ </BuildMatrix>
+</CodeLite_Workspace>
diff --git a/src/core/.clang-format b/src/core/.clang-format
new file mode 120000
index 0000000..2d11237
--- /dev/null
+++ b/src/core/.clang-format
@@ -0,0 +1 @@
+../../.clang-format \ No newline at end of file
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
new file mode 100644
index 0000000..162f75e
--- /dev/null
+++ b/src/core/CMakeLists.txt
@@ -0,0 +1,167 @@
+# -*- CMakeLists.txt generated by CodeLite IDE. Do not edit by hand -*-
+
+cmake_minimum_required(VERSION 2.8.11)
+
+# Project name
+project(core)
+
+# This setting is useful for providing JSON file used by CodeLite for code completion
+set(CMAKE_EXPORT_COMPILE_COMMANDS 1)
+
+set(CONFIGURATION_NAME "Debug")
+
+# Define some variables
+set(PROJECT_core_PATH "${CMAKE_CURRENT_LIST_DIR}")
+set(WORKSPACE_PATH "${CMAKE_CURRENT_LIST_DIR}/..")
+
+
+
+#{{{{ User Code 1
+# Place your code here
+#}}}}
+
+include_directories(
+ .
+ ../../3rdparty/json.h
+ ./include
+ ../rdp/include
+ ../../3rdparty/wslay/lib/includes
+ ../../3rdparty/wslay/build/lib/includes
+ ../../3rdparty/libev
+ ../../3rdparty/picohttpparser
+ ../../3rdparty/libcb/include
+ ../../3rdparty/curl/build/lib
+ ../../3rdparty/curl/include
+)
+
+
+# Compiler options
+add_definitions(-Wall)
+add_definitions(-std=c99)
+add_definitions(-fno-strict-aliasing)
+add_definitions(-pthread)
+add_definitions(-D_POSIX_C_SOURCE=200112L)
+add_definitions(-D_XOPEN_SOURCE=500)
+add_definitions(-D_GNU_SOURCE)
+
+# Linker options
+#set(LINK_OPTIONS -pthread)
+
+
+link_libraries(
+"-pthread \
+-lssl \
+-lcrypto \
+-ldl \
+-lm \
+-lrt \
+-lpng \
+-lz \
+../../rdp/build/librdp.a \
+../../../3rdparty/libev/build/libev.a \
+../../../3rdparty/wslay/build/lib/libwslay.a \
+../../../3rdparty/FreeRDP/build/client/common/libfreerdp-client3.a \
+../../../3rdparty/FreeRDP/build/channels/rdpgfx/client/librdpgfx-client.a \
+../../../3rdparty/FreeRDP/build/channels/video/client/libvideo-client.a \
+../../../3rdparty/FreeRDP/build/channels/geometry/client/libgeometry-client.a \
+../../../3rdparty/FreeRDP/build/channels/parallel/client/libparallel-client.a \
+../../../3rdparty/FreeRDP/build/channels/drive/client/libdrive-client.a \
+../../../3rdparty/FreeRDP/build/channels/cliprdr/client/libcliprdr-client.a \
+../../../3rdparty/FreeRDP/build/channels/drdynvc/client/libdrdynvc-client.a \
+../../../3rdparty/FreeRDP/build/channels/remdesk/client/libremdesk-client.a \
+../../../3rdparty/FreeRDP/build/channels/rdpei/client/librdpei-client.a \
+../../../3rdparty/FreeRDP/build/channels/encomsp/client/libencomsp-client.a \
+../../../3rdparty/FreeRDP/build/channels/disp/client/libdisp-client.a \
+../../../3rdparty/FreeRDP/build/channels/echo/client/libecho-client.a \
+../../../3rdparty/FreeRDP/build/channels/rail/client/librail-client.a \
+../../../3rdparty/FreeRDP/build/channels/serial/client/libserial-client.a \
+../../../3rdparty/FreeRDP/build/channels/rdpsnd/client/librdpsnd-client.a \
+../../../3rdparty/FreeRDP/build/channels/rdpsnd/client/fake/librdpsnd-client-fake.a \
+../../../3rdparty/FreeRDP/build/channels/smartcard/client/libsmartcard-client.a \
+../../../3rdparty/FreeRDP/build/channels/rdp2tcp/client/librdp2tcp-client.a \
+../../../3rdparty/FreeRDP/build/channels/ainput/client/libainput-client.a \
+../../../3rdparty/FreeRDP/build/channels/rdpdr/client/librdpdr-client.a \
+../../../3rdparty/FreeRDP/build/client/common/libfreerdp-client3.a \
+../../../3rdparty/FreeRDP/build/libfreerdp/libfreerdp3.a \
+../../../3rdparty/FreeRDP/build/winpr/libwinpr/libwinpr3.a \
+../../../3rdparty/curl/build/libcurl.a \
+-ldl"
+)
+if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
+ add_definitions(-DDEBUG)
+ link_libraries(
+ "-pthread \
+ -lssl \
+ -lcrypto \
+ -lm \
+ -lrt \
+ -lpng \
+ -lz \
+ -lunwind \
+ -lunwind-x86_64 \
+ ../../rdp/build/librdp.a \
+ ../../../3rdparty/libev/build/libev.a \
+ ../../../3rdparty/wslay/build/lib/libwslay.a \
+ ../../../3rdparty/FreeRDP/build/client/common/libfreerdp-client3.a \
+ ../../../3rdparty/FreeRDP/build/channels/geometry/client/libgeometry-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/rdpgfx/client/librdpgfx-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/video/client/libvideo-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/parallel/client/libparallel-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/drive/client/libdrive-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/cliprdr/client/libcliprdr-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/drdynvc/client/libdrdynvc-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/remdesk/client/libremdesk-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/rdpei/client/librdpei-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/encomsp/client/libencomsp-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/disp/client/libdisp-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/echo/client/libecho-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/rail/client/librail-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/serial/client/libserial-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/rdpsnd/client/librdpsnd-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/rdpsnd/client/fake/librdpsnd-client-fake.a \
+ ../../../3rdparty/FreeRDP/build/channels/smartcard/client/libsmartcard-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/rdp2tcp/client/librdp2tcp-client.a \
+ ../../../3rdparty/FreeRDP/build/libfreerdp/libfreerdp3.a \
+ ../../../3rdparty/FreeRDP/build/winpr/libwinpr/libwinpr3.a \
+ ../../../3rdparty/curl/build/libcurl.a \
+ -ldl"
+ )
+endif()
+
+
+# Library path
+set(CMAKE_LDFLAGS "${CMAKE_LDFLAGS} -L. ")
+
+# Define the C sources
+set ( C_SRCS
+ ${CMAKE_CURRENT_LIST_DIR}/config_file.c
+ ${CMAKE_CURRENT_LIST_DIR}/cmdline.c
+ ${CMAKE_CURRENT_LIST_DIR}/ws_protocol.c
+ ${CMAKE_CURRENT_LIST_DIR}/ev_loop.c
+ ${CMAKE_CURRENT_LIST_DIR}/main.c
+ ${CMAKE_CURRENT_LIST_DIR}/utilities.c
+ ${CMAKE_CURRENT_LIST_DIR}/../../3rdparty/picohttpparser/picohttpparser.c
+ ${CMAKE_CURRENT_LIST_DIR}/thread_impl.c
+ ${CMAKE_CURRENT_LIST_DIR}/exports.c
+ ${CMAKE_CURRENT_LIST_DIR}/wrdp_thpool.c
+ ${CMAKE_CURRENT_LIST_DIR}/ws_session.c
+ ${CMAKE_CURRENT_LIST_DIR}/json_helpers.c
+ ${CMAKE_CURRENT_LIST_DIR}/curl_helpers.c
+ ${CMAKE_CURRENT_LIST_DIR}/backend_helpers.c
+ ${CMAKE_CURRENT_LIST_DIR}/remote_control.c
+ ${CMAKE_CURRENT_LIST_DIR}/socket_helpers.c
+ ${CMAKE_CURRENT_LIST_DIR}/thread_sync.c
+ ${CMAKE_CURRENT_LIST_DIR}/log.c
+)
+
+set_source_files_properties(
+ ${C_SRCS} PROPERTIES COMPILE_FLAGS
+ " -std=c99 -Wall -fno-strict-aliasing -pthread -D_POSIX_C_SOURCE=200112L -D_XOPEN_SOURCE=500 -D_GNU_SOURCE")
+
+if(WIN32)
+ enable_language(RC)
+ set(CMAKE_RC_COMPILE_OBJECT
+ "<CMAKE_RC_COMPILER> ${RC_OPTIONS} -O coff -i <SOURCE> -o <OBJECT>")
+endif(WIN32)
+
+add_executable(core ${RC_SRCS} ${CXX_SRCS} ${C_SRCS})
diff --git a/src/core/backend_helpers.c b/src/core/backend_helpers.c
new file mode 100644
index 0000000..ab40fac
--- /dev/null
+++ b/src/core/backend_helpers.c
@@ -0,0 +1,459 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "globals.h"
+#include "ws_session.h"
+#include "wrdp_thpool_internals.h"
+#include "webrdp_module_api.h"
+#include "task.h"
+#include "rdp_backend_api.h"
+#include "thread_sync.h"
+#include "utilities.h"
+#include "thread_impl.h"
+#include "curl_helpers.h"
+
+#include <errno.h>
+#include "base64_url.h"
+
+bool
+backend_validate(wrdp_backend_module *backend)
+{
+ if (!backend->callbacks_module->init)
+ {
+ return false;
+ }
+ if (!backend->callbacks_input->kcomb
+ && !backend->callbacks_input->kpress
+ && !backend->callbacks_input->kupdown
+ && !backend->callbacks_input->mouse
+ && !backend->callbacks_input->unicode)
+ {
+ /* backend can't handle any input */
+ return false;
+ }
+ return true;
+}
+
+static bool
+validate_backend_name(const char *name)
+{
+ if (!strcmp(name, "rdp"))
+ {
+ return true;
+ }
+ return false;
+}
+
+void
+backend_remove_from_pool(wrdp_backend_module *backend)
+{
+ wrdp_thpool_task *task = backend->wrdp_thpool_task;
+
+ user_pool_msg *pmsg = calloc(1, sizeof(user_pool_msg));
+ if (!pmsg)
+ {
+ perror("calloc");
+ return;
+ }
+ pmsg->type = msg_type_destroy_ws_backend_info;
+ pmsg->backend = backend;
+
+ wrdp_thpool_send_msg_to_pool(task->thread->pool, pmsg);
+}
+
+static bool
+backend_create(const char *name, ws_session *session)
+{
+ wrdp_backend_module *backend = 0;
+ task_info *info = session->task_info;
+ wrdp_thpool_task *task = 0;
+
+ if (session->sid_base64)
+ {
+ size_t sid_len = strlen(session->sid_base64);
+ char *attach_sid = malloc(sid_len + 1);
+ memcpy(attach_sid, session->sid_base64, sid_len);
+ attach_sid[sid_len] = 0;
+ session->attach_sid_base64 = attach_sid;
+ }
+
+ if (!validate_backend_name(name))
+ {
+ return false;
+ }
+ backend = calloc(1, sizeof(wrdp_backend_module));
+ if (!backend)
+ {
+ perror("calloc");
+ return false;
+ }
+ bool module_initialized = false;
+ info->backend = backend;
+ backend->task_info = info;
+ session->task_info = info;
+ info->wrdp_thpool_task = session->wrdp_thpool_task;
+ backend->wrdp_thpool_task = session->wrdp_thpool_task;
+ task = session->wrdp_thpool_task;
+ task->userdata = info;
+ {
+ struct ws_session_list_entry_s *entry
+ = calloc(1, sizeof(struct ws_session_list_entry_s));
+ if (!entry)
+ {
+ perror("calloc");
+ goto error;
+ }
+ entry->session = session;
+ backend->sessions_list_head
+ = calloc(1, sizeof(SLIST_HEAD(h, ws_session_list_entry_s)));
+ SLIST_HEAD(sessions_head,
+ ws_session_list_entry_s) *sessions_list_head_p
+ = backend->sessions_list_head;
+ SLIST_INIT(sessions_list_head_p);
+ SLIST_INSERT_HEAD(sessions_list_head_p, entry, entries);
+ info->settings = g_globals.settings.ws_session_defaults;
+ }
+ SLIST_INIT(&(session->curls_easy_head));
+
+ backend->callbacks_input = calloc(1, sizeof(wrdp_backend_cb_input));
+ if (!backend->callbacks_input)
+ {
+ perror("calloc");
+ goto error;
+ }
+ backend->callbacks_module = calloc(1, sizeof(wrdp_backend_cb_module));
+ if (!backend->callbacks_module)
+ {
+ perror("calloc");
+ goto error;
+ }
+ backend->callbacks_clipbrd
+ = calloc(1, sizeof(wrdp_backend_cb_clipboard));
+ if (!backend->callbacks_clipbrd)
+ {
+ perror("calloc");
+ goto error;
+ }
+ backend->callbacks_ft = calloc(1, sizeof(wrdp_backend_cb_filetransfer));
+ if (!backend->callbacks_ft)
+ {
+ perror("calloc");
+ goto error;
+ }
+ if (!strcmp(name, "rdp"))
+ {
+ module_initialized = rdp_create(g_globals.exports, backend);
+ if (!module_initialized)
+ {
+ goto error;
+ }
+ }
+ strcpy(info->backend_name, name);
+ info->backend = backend;
+ if (!backend_validate(info->backend))
+ {
+ info->backend = 0;
+ goto error;
+ }
+ info->backend->callbacks_module->set_task_info(
+ info, info->backend->backend_internals);
+ {
+ /* TODO: do it in main thread */
+ user_pool_msg *pmsg = calloc(1, sizeof(user_pool_msg));
+ wrdp_thpool_task *task = session->wrdp_thpool_task;
+ if (!pmsg)
+ {
+ perror("calloc");
+ goto error;
+ }
+ pmsg->type = msg_type_backend_created;
+ pmsg->backend = backend;
+ wrdp_thpool_send_msg_to_pool(task->thread->pool, pmsg);
+ }
+ {
+ wrdp_thpool_task *task = session->wrdp_thpool_task;
+ info->ev_timer_watcher = calloc(1, sizeof(ev_timer));
+ if (!info->ev_timer_watcher)
+ {
+ perror("calloc");
+ goto error;
+ }
+ ev_timer_init(
+ info->ev_timer_watcher, task_timeouts_check_cb, 1., 0.);
+ info->ev_timer_watcher->data = info;
+ info->ev_timer_watcher->repeat = 1;
+ ev_timer_again(
+ task->thread->ev_th_loop, info->ev_timer_watcher);
+ }
+ return true;
+error:
+ if (backend)
+ {
+ if (backend->callbacks_input)
+ {
+ free(backend->callbacks_input);
+ }
+ if (backend->callbacks_module)
+ {
+ free(backend->callbacks_module);
+ }
+ if (backend->callbacks_clipbrd)
+ {
+ free(backend->callbacks_clipbrd);
+ }
+ if (backend->callbacks_ft)
+ {
+ free(backend->callbacks_ft);
+ }
+ backend_remove_from_pool(backend);
+ }
+ return false;
+}
+
+void
+backend_destroy(wrdp_backend_module *backend)
+{
+ if (backend->callbacks_input)
+ {
+ free(backend->callbacks_input);
+ }
+ if (backend->callbacks_module)
+ {
+ free(backend->callbacks_module);
+ }
+ if (backend->callbacks_ft)
+ {
+ free(backend->callbacks_ft);
+ }
+ if (backend->callbacks_clipbrd)
+ {
+ free(backend->callbacks_clipbrd);
+ }
+ if (backend->sessions_list_head)
+ {
+ free(backend->sessions_list_head);
+ }
+ backend_remove_from_pool(backend);
+}
+
+extern void task_destroy_timers(wrdp_thpool_task *task);
+
+void
+backend_task_destroy(wrdp_thpool_task *task)
+{
+ task_destroy_timers(task);
+
+ task_info *info = task->userdata;
+ backend_destroy(info->backend);
+
+ free(info);
+}
+
+static void
+ws_session_child_init_cb(wrdp_thpool_task *task, void *userdata)
+{
+ ws_session *session = userdata;
+ session->wrdp_thpool_task = task;
+}
+
+static bool
+backend_find(ws_session *session)
+{
+ for (struct backend_s *t1 = LIST_FIRST(&(g_globals.backends_head)); t1;
+ t1 = LIST_NEXT(t1, entries))
+ {
+ //TODO: WTF?
+ if (!t1->backend)
+ continue;
+
+ SLIST_HEAD(sessions_head,
+ ws_session_list_entry_s) *sessions_list_head_p
+ = t1->backend->sessions_list_head;
+ for (struct ws_session_list_entry_s *s
+ = SLIST_FIRST(sessions_list_head_p);
+ s; s = SLIST_NEXT(s, entries))
+ {
+ if (s->session && session->attach_sid_base64
+ && s->session->sid_base64
+ && !strcmp(session->attach_sid_base64,
+ s->session->sid_base64))
+ {
+ if (session == s->session)
+ {
+ return true;
+ }
+ struct ws_session_list_entry_s *entry = calloc(
+ 1, sizeof(struct ws_session_list_entry_s));
+ wrdp_thpool_task *t;
+ if (!entry)
+ {
+ session->session_state
+ = ws_session_error;
+ perror("calloc");
+ return false;
+ }
+ entry->session = session;
+ session->task_info = t1->backend->task_info;
+ SLIST_INSERT_HEAD(
+ sessions_list_head_p, entry, entries);
+ t = t1->backend->wrdp_thpool_task;
+ if (!wrdp_thread_pool_move_task_to_thread(
+ g_globals.thpool, ws_run_session,
+ ws_stop_session, t->thread->thread_id,
+ ws_session_child_init_cb,
+ session->wrdp_thpool_task, session))
+ {
+ /* TODO: cleanup ? */
+ return false;
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool
+backend_get(const char *name, ws_session *session)
+{
+ bool found = false;
+ if (session->attach_sid_base64)
+ {
+ found = backend_find(session);
+ }
+ else
+ {
+ found = backend_create(name, session);
+ }
+ return found;
+}
+
+void
+backend_fill_settings(ws_session *session)
+{
+ while (!SLIST_EMPTY(&(session->backend_settings_head)))
+ {
+ task_info *info = session->task_info;
+ struct backend_setting_s *s
+ = SLIST_FIRST(&(session->backend_settings_head));
+ SLIST_REMOVE_HEAD(&(session->backend_settings_head), entries);
+ if (s->type == setting_int)
+ {
+ info->backend->callbacks_module->set_setting_int(
+ &(s->setting_int),
+ info->backend->backend_internals);
+ free(s->setting_int.name);
+ free(s);
+ }
+ else if (s->type == setting_string)
+ {
+ info->backend->callbacks_module->set_setting_str(
+ &(s->setting_string),
+ info->backend->backend_internals);
+ free(s->setting_string.name);
+ free(s->setting_string.value);
+ free(s);
+ }
+ }
+}
+
+bool
+handle_backend_setting_int(const char *name, int64_t val, ws_session *session)
+{
+ task_info *info = session->task_info;
+ if (!info || !info->backend)
+ {
+ if (!SLIST_EMPTY(&(session->backend_settings_head)))
+ {
+ for (struct backend_setting_s *s
+ = SLIST_FIRST(&(session->backend_settings_head));
+ s; s = SLIST_NEXT(s, entries))
+ {
+ if (s->type != setting_int)
+ continue;
+ if (strcmp(s->setting_int.name, name))
+ continue;
+ s->setting_int.value = val;
+ return true;
+ }
+ }
+ struct backend_setting_s *s
+ = calloc(1, sizeof(struct backend_setting_s));
+ if (!s)
+ {
+ perror("calloc");
+ return false;
+ }
+ s->setting_int.name = strdup(name);
+ s->setting_int.value = val;
+ s->type = setting_int;
+ SLIST_INSERT_HEAD(
+ &(session->backend_settings_head), s, entries);
+ }
+ else
+ {
+ backend_setting_int s;
+ s.name = (char *)name;
+ s.value = val;
+ if (!info->backend->callbacks_module->set_setting_int(
+ &s, info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+handle_backend_setting_string(
+ const char *name, const char *val, ws_session *session)
+{
+ task_info *info = session->task_info;
+ if (!info || !info->backend)
+ {
+ if (!SLIST_EMPTY(&(session->backend_settings_head)))
+ {
+ for (struct backend_setting_s *s
+ = SLIST_FIRST(&(session->backend_settings_head));
+ s; s = SLIST_NEXT(s, entries))
+ {
+ if (s->type != setting_string)
+ continue;
+ if (strcmp(s->setting_string.name, name))
+ continue;
+ free(s->setting_string.value);
+ s->setting_string.value = strdup(val);
+ return true;
+ }
+ }
+ struct backend_setting_s *s
+ = calloc(1, sizeof(struct backend_setting_s));
+ if (!s)
+ {
+ perror("calloc");
+ return false;
+ }
+ s->setting_string.name = strdup(name);
+ s->setting_string.value = strdup(val);
+ s->type = setting_string;
+ SLIST_INSERT_HEAD(
+ &(session->backend_settings_head), s, entries);
+ }
+ else
+ {
+ backend_setting_str s;
+ s.name = (char *)name;
+ s.value = (char *)val;
+ info->backend->callbacks_module->set_setting_str(
+ &s, info->backend->backend_internals);
+ }
+ return true;
+}
diff --git a/src/core/backend_helpers.h b/src/core/backend_helpers.h
new file mode 100644
index 0000000..c93be98
--- /dev/null
+++ b/src/core/backend_helpers.h
@@ -0,0 +1,20 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+bool backend_validate(wrdp_backend_module *backend);
+
+bool backend_get(const char *name, ws_session *session);
+void backend_destroy(wrdp_backend_module *backend);
+
+void backend_fill_settings(ws_session *session);
+
+bool handle_backend_setting_int(
+ const char *name, int64_t val, ws_session *session);
+
+bool handle_backend_setting_string(
+ const char *name, const char *val, ws_session *session);
diff --git a/src/core/base64_url.h b/src/core/base64_url.h
new file mode 100644
index 0000000..0f7741a
--- /dev/null
+++ b/src/core/base64_url.h
@@ -0,0 +1,187 @@
+/*-
+ * Copyright (c) 2003 - 2016 Rozhuk Ivan <rozhuk.im@gmail.com>
+ * Copyright (c) 2018 sss <sss at dark-alexandr dot net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#pragma once
+
+static const uint8_t *base64_url_tbl_coding = (const uint8_t
+ *)"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+static const uint8_t base64_url_tbl_decoding[256] = {64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 62, 64, 64, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
+ 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
+ 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 63, 64, 26, 27, 28, 29, 30, 31, 32,
+ 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64};
+
+static inline int
+base64_url_encode(uint8_t *src, size_t src_size, uint8_t *dst, size_t dst_size,
+ size_t *enc_size)
+{
+ size_t tm, src_m3_size;
+ register uint8_t *wpos, *rpos, *src_m3_max;
+
+ if (NULL == src || 0 == src_size)
+ return (EINVAL);
+ /* dst buf size calculation. */
+ tm = (src_size / 3);
+ src_m3_size = (tm * 3);
+ if (src_m3_size != src_size)
+ { /* is multiple of 3? */
+ tm++;
+ }
+ tm *= 4;
+ if (NULL != enc_size)
+ {
+ (*enc_size) = tm;
+ }
+ if (dst_size < tm) /* Is dst buf too small? */
+ return (ENOBUFS);
+ if (NULL == dst)
+ return (EINVAL);
+ wpos = dst;
+ rpos = src;
+ /* Main loop: encode 3 -> 4 */
+ for (src_m3_max = (src + src_m3_size); rpos < src_m3_max; rpos += 3)
+ {
+ (*wpos++) = base64_url_tbl_coding[rpos[0] >> 2]; /* c1 */
+ (*wpos++)
+ = base64_url_tbl_coding[((rpos[0] << 4) & 0x30)
+ | ((rpos[1] >> 4) & 0x0f)]; /* c2 */
+ (*wpos++)
+ = base64_url_tbl_coding[((rpos[1] << 2) & 0x3c)
+ | ((rpos[2] >> 6) & 0x03)]; /* c3 */
+ (*wpos++) = base64_url_tbl_coding[rpos[2] & 0x3f]; /* c4 */
+ }
+ /* Tail special encoding. */
+ if (src_size != src_m3_size)
+ { /* If src_size was not a multiple of 3: 1-2 bytes tail special coding.
+ */
+ (*wpos++) = base64_url_tbl_coding[rpos[0] >> 2]; /* c1 */
+ if (1 == (src_size - src_m3_size))
+ { /* 1 byte tail. */
+ (*wpos++) = base64_url_tbl_coding[(
+ (rpos[0] << 4) & 0x30)]; /* c2 */
+ (*wpos++) = '='; /* c3: tail padding. */
+ }
+ else
+ { /* 2 bytes tail. */
+ (*wpos++)
+ = base64_url_tbl_coding[((rpos[0] << 4) & 0x30)
+ | ((rpos[1] >> 4)
+ & 0x0f)]; /* c2 */
+ (*wpos++) = base64_url_tbl_coding[(
+ (rpos[1] << 2) & 0x3c)]; /* c3 */
+ }
+ (*wpos++) = '='; /* c4: tail padding. */
+ }
+ (*wpos) = 0;
+#if 0
+ if ((wpos - dst) != tm) { /* Must be euqual! */
+ (*enc_size) = (wpos - dst);
+ }
+#endif
+
+ return (0);
+}
+
+static inline int
+base64_url_decode(uint8_t *src, size_t src_size, uint8_t *dst, size_t dst_size,
+ size_t *dcd_size)
+{
+ size_t tm, src_m4_size;
+ register uint8_t *wpos, *rpos, *src_m4_max;
+
+ if (NULL == src || 2 > src_size)
+ return (EINVAL);
+ /* Remove tail padding. */
+ for (; 0 < src_size; src_size--)
+ {
+ if ('=' != src[(src_size - 1)])
+ break;
+ }
+ if (2 > src_size) /* Check again: at least 2 byte needed for decoder. */
+ return (EINVAL);
+ /* dst buf size calculation. */
+ tm = (src_size / 4);
+ src_m4_size = (tm * 4);
+ if (src_m4_size != src_size)
+ { /* is multiple of 4? */
+ tm++;
+ }
+ tm *= 3;
+ if (dst_size < tm)
+ { /* Is dst buf too small? */
+ if (NULL != dcd_size)
+ {
+ (*dcd_size) = tm;
+ }
+ return (ENOBUFS);
+ }
+ if (NULL == dst)
+ return (EINVAL);
+ wpos = dst;
+ rpos = src;
+ /* Main loop: decode 4 -> 3 */
+ for (src_m4_max = (src + src_m4_size); rpos < src_m4_max; rpos += 4)
+ {
+ (*wpos++) = (base64_url_tbl_decoding[rpos[0]] << 2
+ | base64_url_tbl_decoding[rpos[1]] >> 4);
+ (*wpos++) = (base64_url_tbl_decoding[rpos[1]] << 4
+ | base64_url_tbl_decoding[rpos[2]] >> 2);
+ (*wpos++) = (base64_url_tbl_decoding[rpos[2]] << 6
+ | base64_url_tbl_decoding[rpos[3]]);
+ }
+ /* Tail special decoding. */
+ switch ((src_size - src_m4_size))
+ {
+ case 2:
+ (*wpos++) = (base64_url_tbl_decoding[rpos[0]] << 2
+ | base64_url_tbl_decoding[rpos[1]] >> 4);
+ break;
+ case 3:
+ (*wpos++) = (base64_url_tbl_decoding[rpos[0]] << 2
+ | base64_url_tbl_decoding[rpos[1]] >> 4);
+ (*wpos++) = (base64_url_tbl_decoding[rpos[1]] << 4
+ | base64_url_tbl_decoding[rpos[2]] >> 2);
+ break;
+ }
+ (*wpos) = 0;
+ if (NULL != dcd_size)
+ { /* Real decoded size can be smaller than calculated. */
+ (*dcd_size) = (wpos - dst);
+ }
+
+ return (0);
+}
diff --git a/src/core/cmdline.c b/src/core/cmdline.c
new file mode 100644
index 0000000..b37e1d7
--- /dev/null
+++ b/src/core/cmdline.c
@@ -0,0 +1,122 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <getopt.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "config_file.h"
+#include "globals.h"
+
+static void
+print_help(const char *prog_path)
+{
+ printf("Usage: %s [-c:d....]\nOptions:\n"
+ "\t-c\t--config_file\t\"Use settings from"
+ " specified file instead of default config file\"\n"
+ "\t-d\t--daemon\t\"Daemon mode (fork to background)\"\n"
+ "\t--http_port\t\"Set port for http/ws server\"\n"
+ "\t--help\t\"Print tthis help message\"\n",
+ prog_path);
+ exit(EXIT_FAILURE);
+}
+
+void
+handle_cmdline_args(int argc, char **argv)
+{
+ int c;
+ char config_file_path[_POSIX_PATH_MAX] = {0};
+ bool daemon = false, wrong_options = false;
+ while (1)
+ {
+ int option_index = 0;
+ static struct option long_options[] = {
+ {"config_file", required_argument, 0, 0},
+ {"daemon", no_argument, 0, 0},
+ {"ws_port", required_argument, 0, 0},
+ {"help", no_argument, 0, 0}
+ };
+ c = getopt_long(
+ argc, argv, "c:dp:", long_options, &option_index);
+ if (c == -1)
+ break;
+ switch (c)
+ {
+ case 0:
+ {
+ if (!strcmp(long_options[option_index].name,
+ "config_file")
+ && optarg)
+ {
+ strncpy(config_file_path, optarg,
+ _POSIX_PATH_MAX - 1);
+ config_file_path[_POSIX_PATH_MAX - 1]
+ = 0;
+ }
+ else if (!strcmp(
+ long_options[option_index].name,
+ "ws_port")
+ && optarg)
+ g_globals.settings.ws_port
+ = atoi(optarg);
+ else if (!strcmp(
+ long_options[option_index].name,
+ "daemon"))
+ daemon = true;
+ else if (!strcmp(
+ long_options[option_index].name,
+ "help"))
+ print_help(argv[0]);
+ }
+ break;
+ case 'c':
+ {
+ if (optarg) //clang static analyzer...
+ {
+ strncpy(config_file_path, optarg,
+ _POSIX_PATH_MAX - 1);
+ config_file_path[_POSIX_PATH_MAX - 1]
+ = 0;
+ }
+ }
+ break;
+ case 'p':
+ {
+ if (optarg) //clang static analyzer...
+ {
+ g_globals.settings.ws_port
+ = atoi(optarg);
+ }
+ }
+ break;
+ case 'd':
+ {
+ daemon = true;
+ }
+ break;
+ case '?':
+ {
+ wrong_options = true;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if (wrong_options)
+ {
+ print_help(argv[0]);
+ }
+ else
+ {
+ g_globals.settings.daemon = daemon;
+ find_config_file(config_file_path);
+ }
+}
diff --git a/src/core/cmdline.h b/src/core/cmdline.h
new file mode 100644
index 0000000..26142d9
--- /dev/null
+++ b/src/core/cmdline.h
@@ -0,0 +1,9 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+void handle_cmdline_args(int argc, char **argv);
diff --git a/src/core/config_file.c b/src/core/config_file.c
new file mode 100644
index 0000000..e09fb5d
--- /dev/null
+++ b/src/core/config_file.c
@@ -0,0 +1,480 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <limits.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+//json.h lib
+#include <json.h>
+#include "json_helpers.h"
+
+#include <errno.h>
+#include "base64_url.h"
+
+#include "globals.h"
+#include "utilities.h"
+
+#include "webrdp_core_api.h"
+
+enum option_type
+{
+ option_type_bool,
+ option_type_string,
+ option_type_int,
+ option_type_unsupported
+};
+
+struct option_s
+{
+ char name[128];
+ union
+ {
+ char val_string[260];
+ bool val_bool;
+ int64_t val_int;
+ };
+ enum option_type type;
+};
+
+static int64_t
+json_cfg_option_extract_int64(struct option_s *option)
+{
+ switch (option->type)
+ {
+ case option_type_int:
+ return option->val_int;
+ break;
+ case option_type_string:
+ return atoll(option->val_string);
+ break;
+ default:
+ return 0;
+ }
+ return 0;
+}
+
+static bool
+json_cfg_option_extract_bool(struct option_s *option)
+{
+ switch (option->type)
+ {
+ case option_type_bool:
+ return option->val_bool;
+ break;
+ case option_type_int:
+ return option->val_int;
+ break;
+ case option_type_string:
+ return atoi(option->val_string);
+ break;
+ default:
+ return false;
+ break;
+ }
+ return false;
+}
+
+static void
+handle_session_option(struct option_s *option)
+{
+ if (!strcmp(option->name, "session_time_limit"))
+ {
+ g_globals.settings.ws_session_defaults.session_time_limit
+ = json_cfg_option_extract_int64(option);
+ }
+ else if (!strcmp(option->name, "session_idle_timeout"))
+ {
+ g_globals.settings.ws_session_defaults.session_idle_timeout
+ = json_cfg_option_extract_int64(option);
+ }
+}
+
+static uint8_t
+get_log_level(const char *log_level_str)
+{
+ uint8_t lvl = wrdp_log_level_error;
+ if (!strcmp(log_level_str, "error"))
+ {
+ lvl = wrdp_log_level_error;
+ }
+ else if (!strcmp(log_level_str, "warning"))
+ {
+ lvl = wrdp_log_level_warning;
+ }
+ else if (!strcmp(log_level_str, "info"))
+ {
+ lvl = wrdp_log_level_info;
+ }
+ else if (!strcmp(log_level_str, "debug"))
+ {
+ lvl = wrdp_log_level_debug;
+ }
+ else if (!strcmp(log_level_str, "trace"))
+ {
+ lvl = wrdp_log_level_trace;
+ }
+#ifndef DEBUG
+ if (lvl > wrdp_log_level_info)
+ {
+ printf(
+ "log_level higher than info does not supported for relase"
+ " builds, downgrade to info\n");
+ lvl = wrdp_log_level_info;
+ }
+#endif /* DEBUG */
+ return lvl;
+}
+
+static void
+handle_global_option(struct option_s *option)
+{
+ if (!strcmp(option->name, "daemon"))
+ {
+ if (!g_globals.settings.daemon)
+ {
+ g_globals.settings.daemon
+ = json_cfg_option_extract_bool(option);
+ }
+ }
+ else if (!strcmp(option->name, "log_level"))
+ {
+ g_globals.settings.log_level
+ = get_log_level(option->val_string);
+ }
+ else if (!strcmp(option->name, "thread_count"))
+ {
+ g_globals.settings.thread_count
+ = json_cfg_option_extract_int64(option);
+ }
+ else if (!strcmp(option->name, "tasks_per_thread"))
+ {
+ g_globals.settings.tasks_per_thread
+ = json_cfg_option_extract_int64(option);
+ }
+ else if (!strcmp(option->name, "ws_port"))
+ {
+ g_globals.settings.ws_port
+ = json_cfg_option_extract_int64(option);
+ }
+ else if (!strcmp(option->name, "ws_socket_path"))
+ {
+ g_globals.settings.ws_socket_path = strdup(option->val_string);
+ }
+ else if (!strcmp(option->name, "ctl_port"))
+ {
+ g_globals.settings.ctl_port
+ = json_cfg_option_extract_int64(option);
+ }
+ else if (!strcmp(option->name, "ctl_socket_path"))
+ {
+ g_globals.settings.ctl_socket_path = strdup(option->val_string);
+ }
+ else if (!strcmp(option->name, "ctl_ssl_cafile"))
+ {
+ g_globals.settings.ctl_ssl_cafile = strdup(option->val_string);
+ }
+ else if (!strcmp(option->name, "ctl_ssl_capath"))
+ {
+ g_globals.settings.ctl_ssl_capath = strdup(option->val_string);
+ }
+ else if (!strcmp(option->name, "ctl_ssl_cert"))
+ {
+ g_globals.settings.ctl_ssl_cert = strdup(option->val_string);
+ }
+ else if (!strcmp(option->name, "ctl_ssl_key"))
+ {
+ g_globals.settings.ctl_ssl_key = strdup(option->val_string);
+ }
+ else if (!strcmp(option->name, "auth_server_url"))
+ {
+ g_globals.settings.auth_server_url = strdup(option->val_string);
+ }
+ else if (!strcmp(option->name, "secret_key_verify"))
+ {
+ size_t dec_size = 0;
+ errno = base64_url_decode((uint8_t *)(option->val_string),
+ strlen(option->val_string),
+ (uint8_t *)g_globals.settings.secret_key_verify, 66,
+ &dec_size);
+ if (errno)
+ {
+ perror("handle_global_option: base64_url_decode");
+ exit(EXIT_FAILURE);
+ }
+ if (dec_size != 64)
+ {
+ printf("Failed to decode verification secret key"
+ " (wrong decoded key length)\n");
+ exit(EXIT_FAILURE);
+ }
+ }
+ else if (!strcmp(option->name, "secret_key_sign"))
+ {
+ size_t dec_size = 0;
+ errno = base64_url_decode((uint8_t *)(option->val_string),
+ strlen(option->val_string),
+ (uint8_t *)g_globals.settings.secret_key_sign, 66,
+ &dec_size);
+ if (errno)
+ {
+ perror("handle_global_option: base64_url_decode");
+ exit(EXIT_FAILURE);
+ }
+ if (dec_size != 64)
+ {
+ printf("Failed to decode signing secret key"
+ " (wrong decoded key length)\n");
+ exit(EXIT_FAILURE);
+ }
+ }
+}
+
+static void
+handle_option(struct option_s *option, const char *prefix, size_t prefix_len)
+{
+ /* TODO: refactoring */
+ if (option->type == option_type_unsupported)
+ {
+ printf("Unsupported option type found in config file\n");
+ return;
+ }
+ if (prefix && prefix_len > 0)
+ {
+ size_t session_len = strlen("session");
+ if (session_len != prefix_len)
+ {
+ goto unknown_prefix;
+ }
+ if (!strncmp("session", prefix, prefix_len))
+ {
+ handle_session_option(option);
+ }
+ return;
+ unknown_prefix:
+ printf("Unknown second level option ignored\n");
+ return;
+ }
+ handle_global_option(option);
+}
+
+static void
+print_option(struct option_s *option, const char *prefix, size_t prefix_len)
+{
+ printf("Found option: ");
+ if (prefix && prefix_len > 0)
+ {
+ printf("%.*s.", (int)prefix_len, prefix);
+ }
+ printf("%s == ", option->name);
+ switch (option->type)
+ {
+ case option_type_bool:
+ {
+ if (option->val_bool)
+ printf("true");
+ else
+ printf("false");
+ printf(" (bool)\n");
+ }
+ break;
+ case option_type_int:
+ {
+ printf("%ld (int)\n", option->val_int);
+ }
+ break;
+ case option_type_string:
+ printf("%s (string)\n", option->val_string);
+ break;
+ case option_type_unsupported:
+ printf("unsupported option type\n");
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+handle_json_option(struct json_object_element_s *json_option,
+ const char *prefix, size_t prefix_len)
+{
+ struct option_s option;
+ memset(&option, 0, sizeof(struct option_s));
+ strncpy(option.name, json_option->name->string,
+ json_option->name->string_size);
+ option.name[json_option->name->string_size] = 0;
+ switch (json_option->value->type)
+ {
+ case json_type_false:
+ {
+ option.val_bool = false;
+ option.type = option_type_bool;
+ }
+ break;
+ case json_type_true:
+ {
+ option.val_bool = true;
+ option.type = option_type_bool;
+ }
+ break;
+ case json_type_number:
+ {
+ option.val_int = json_option_extract_int64(json_option);
+ option.type = option_type_int;
+ }
+ break;
+ case json_type_string:
+ {
+ json_option_extract_string(
+ json_option, (char *)&(option.val_string));
+ option.type = option_type_string;
+ }
+ break;
+ case json_type_object:
+ {
+ struct json_object_s *jsopt
+ = (struct json_object_s *)
+ json_option->value->payload;
+ struct json_object_element_s *jsoption = jsopt->start;
+ while (jsoption)
+ {
+ handle_json_option(jsoption,
+ json_option->name->string,
+ json_option->name->string_size);
+ jsoption = jsoption->next;
+ }
+ return;
+ }
+ break;
+ default:
+ {
+ option.type = option_type_unsupported;
+ }
+ break;
+ }
+ handle_option(&option, prefix, prefix_len);
+ print_option(&option, prefix, prefix_len);
+}
+
+static void
+parse_json(char *buf, size_t buf_size)
+{
+ struct json_value_s *root = json_parse(buf, buf_size);
+ if (!root)
+ {
+ printf("Failed to parse config (not valid json ?)\n");
+ exit(EXIT_FAILURE);
+ }
+ struct json_object_s *object = (struct json_object_s *)root->payload;
+ struct json_object_element_s *option = object->start;
+ while (option)
+ {
+ handle_json_option(option, 0, 0);
+ option = option->next;
+ }
+ free(buf);
+ free(root);
+}
+
+static void
+parse_config_file(const char *path)
+{
+ char *buf = 0;
+ FILE *cfg = 0;
+ size_t size = 0;
+ {
+ struct stat st;
+ stat(path, &st);
+ size = st.st_size;
+
+ buf = (char *)malloc(size);
+ if (!buf)
+ {
+ perror("malloc");
+ exit(EXIT_FAILURE);
+ }
+ }
+ cfg = fopen(path, "r");
+ if (fread(buf, 1, size, cfg) != size)
+ {
+ printf("Failed to read config file\n");
+ fclose(cfg);
+ exit(EXIT_FAILURE);
+ }
+ fclose(cfg);
+ printf("\tLoaded.\n");
+ parse_json(buf, size);
+}
+
+void
+find_config_file(char *config_file_path)
+{
+ bool file_exists = false;
+ if (config_file_path[0])
+ {
+ printf("Trying to load config file from specified path: %s...",
+ config_file_path);
+ if (!is_regular_file(config_file_path))
+ {
+ printf("\tFile does not exists or not regular file\n");
+ exit(EXIT_FAILURE);
+ }
+ else
+ {
+ file_exists = true;
+ printf("\tFile exists...");
+ }
+ }
+ else
+ {
+ struct passwd *pw = getpwuid(getuid());
+ const char *homedir = pw->pw_dir;
+ snprintf(config_file_path, _POSIX_PATH_MAX - 1,
+ "%s/.config/%s/config", homedir, PROG_NAME);
+ printf("Trying to load config file from default path: %s...",
+ config_file_path);
+ if (!is_regular_file(config_file_path))
+ {
+ printf("\tFile does not exists or not regular file\n");
+ config_file_path[0] = 0;
+ snprintf(config_file_path, _POSIX_PATH_MAX - 1,
+ "/etc/%s/config", PROG_NAME);
+ }
+ else
+ {
+ file_exists = true;
+ printf("\tFile exists...");
+ }
+ if (!file_exists)
+ {
+ printf("Trying to load config file from default path: "
+ "%s...",
+ config_file_path);
+ if (!is_regular_file(config_file_path))
+ printf("\tFile does not exists or not regular "
+ "file\n");
+ else
+ {
+ file_exists = true;
+ printf("\tFile exists...");
+ }
+ }
+ if (!file_exists)
+ printf("Default config file not found and custom"
+ " config file not specififed, running with "
+ "internal "
+ "defaults.\n");
+ }
+ if (file_exists)
+ parse_config_file(config_file_path);
+}
diff --git a/src/core/config_file.h b/src/core/config_file.h
new file mode 100644
index 0000000..55d6413
--- /dev/null
+++ b/src/core/config_file.h
@@ -0,0 +1,9 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+void find_config_file(char *config_file_path);
diff --git a/src/core/core.project b/src/core/core.project
new file mode 100644
index 0000000..16459f0
--- /dev/null
+++ b/src/core/core.project
@@ -0,0 +1,221 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CodeLite_Project Name="core" Version="11000" InternalType="Console">
+ <Plugins>
+ <Plugin Name="qmake">
+ <![CDATA[00010001N0005Debug000000000000]]>
+ </Plugin>
+ </Plugins>
+ <Description/>
+ <Dependencies/>
+ <VirtualDirectory Name="src">
+ <File Name="task.h"/>
+ <File Name="log.c"/>
+ <File Name="log.h"/>
+ <File Name="thread_sync.h"/>
+ <File Name="thread_sync.c"/>
+ <File Name="ws_session.h"/>
+ <File Name="ws_session.c"/>
+ <File Name="ctl_task.h"/>
+ <File Name="socket_helpers.h"/>
+ <File Name="socket_helpers.c"/>
+ <File Name="remote_control.h"/>
+ <File Name="remote_control.c"/>
+ <File Name="base64_url.h"/>
+ <File Name="backend_helpers.h"/>
+ <File Name="backend_helpers.c"/>
+ <File Name="curl_helpers.c"/>
+ <File Name="curl_helpers.h"/>
+ <File Name="json_helpers.h"/>
+ <File Name="json_helpers.c"/>
+ <File Name="exports.h"/>
+ <File Name="exports.c"/>
+ <File Name="ws_protocol.h"/>
+ <File Name="ws_protocol.c"/>
+ <File Name="../../3rdparty/picohttpparser/picohttpparser.c"/>
+ <File Name="ws_server_internals.h"/>
+ <File Name="wrdp_thpool_internals.h"/>
+ <File Name="thread_impl.h"/>
+ <File Name="thread_impl.c"/>
+ <File Name="wrdp_thpool.h"/>
+ <File Name="wrdp_thpool.c"/>
+ <File Name="ev_loop.h"/>
+ <File Name="ev_loop.c"/>
+ <File Name="cmdline.h"/>
+ <File Name="cmdline.c"/>
+ <File Name="config_file.h"/>
+ <File Name="config_file.c"/>
+ <File Name="globals.h"/>
+ <VirtualDirectory Name="include">
+ <File Name="include/webrdp_api_utils.h"/>
+ <File Name="include/webrdp_api_shared_structures.h"/>
+ <File Name="include/webrdp_module_api.h"/>
+ <File Name="include/webrdp_core_api.h"/>
+ </VirtualDirectory>
+ <File Name="utilities.h"/>
+ <File Name="utilities.c"/>
+ <File Name="main.c"/>
+ </VirtualDirectory>
+ <Dependencies Name="Debug">
+ <Project Name="rdp"/>
+ </Dependencies>
+ <Dependencies Name="Release">
+ <Project Name="rdp"/>
+ </Dependencies>
+ <Settings Type="Executable">
+ <GlobalSettings>
+ <Compiler Options="" C_Options="-std=c99;-pthread;-D_POSIX_C_SOURCE=200112L;-D_XOPEN_SOURCE=500;-D_GNU_SOURCE;" Assembler="">
+ <IncludePath Value="."/>
+ <IncludePath Value="../../3rdparty/json.h"/>
+ <IncludePath Value="./include"/>
+ <IncludePath Value="../rdp/include"/>
+ <IncludePath Value="../../3rdparty/wslay/lib/includes"/>
+ <IncludePath Value="../../3rdparty/wslay/build/lib/includes"/>
+ <IncludePath Value="../../3rdparty/libev"/>
+ <IncludePath Value="../../3rdparty/picohttpparser"/>
+ <IncludePath Value="../../3rdparty/libcb/include"/>
+ <IncludePath Value="../../3rdparty/curl/build/lib"/>
+ <IncludePath Value="../../3rdparty/curl/include"/>
+ </Compiler>
+ <Linker Options="-pthread;-rpath .;-fsanitize=address;-l:rdp.a;-l:libev.a;-l:libwslay.a;-l:libfreerdp3.a;-l:libwinpr3.a;-l:libfreerdp-client3.a;-l:libgeometry-client.a;-l:librdpgfx-client.a;-l:libvideo-client.a;-l:libparallel-client.a;-l:libdrive-client.a;-l:libcliprdr-client.a;-l:libdrdynvc-client.a;-l:libremdesk-client.a;-l:librdpei-client.a;-l:libencomsp-client.a;-l:libdisp-client.a;-l:libecho-client.a;-l:librail-client.a;-l:libserial-client.a;-l:librdpsnd-client.a;-l:librdpsnd-client-fake.a;-l:libsmartcard-client.a;-l:libfreerdp3.a;-l:librdpdr-client.a;-l:libwinpr3.a;-l:librdp2tcp-client.a;-l:libainput-client.a;-l:libcurl.a;-l:libfreerdp-client3.a">
+ <LibraryPath Value="."/>
+ <LibraryPath Value="./lib"/>
+ <LibraryPath Value="$(WorkspacePath)/rdp/$(ConfigurationName)/"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/libfreerdp"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/winpr/libwinpr"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/client/common"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/geometry/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/rdpgfx/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/video/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/parallel/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/drive/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/cliprdr/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/drdynvc/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/remdesk/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/rdpei/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/encomsp/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/audin/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/disp/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/echo/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/rail/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/serial/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/rdpsnd/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/rdpsnd/client/fake"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/smartcard/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/audin/client/oss"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/rdpsnd/client/oss"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/rdp2tcp/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/rdpdr/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/urbdrc/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/printer/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/ainput/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/rdpdr/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/curl/build"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/libev/build"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/wslay/build/lib"/>
+ <Library Value="ssl"/>
+ <Library Value="crypto"/>
+ <Library Value="dl"/>
+ <Library Value="rt"/>
+ <Library Value="png"/>
+ <Library Value="z"/>
+ <Library Value="unwind"/>
+ <Library Value="unwind-x86_64"/>
+ </Linker>
+ <ResourceCompiler Options=""/>
+ </GlobalSettings>
+ <Configuration Name="Debug" CompilerType="clang" DebuggerType="GNU gdb debugger" Type="Executable" BuildCmpWithGlobalSettings="append" BuildLnkWithGlobalSettings="append" BuildResWithGlobalSettings="append">
+ <Compiler Options="-g;-O0;-Wall;-DDEBUG;-fsanitize=address" C_Options="-pg;-g;-O0;-Wall;-DDEBUG;-fsanitize=address" Assembler="" Required="yes" PreCompiledHeader="" PCHInCommandLine="no" PCHFlags="" PCHFlagsPolicy="0"/>
+ <Linker Options="-pg;-fsanitize=address" Required="yes"/>
+ <ResourceCompiler Options="" Required="no"/>
+ <General OutputFile="$(IntermediateDirectory)/$(ProjectName)" IntermediateDirectory="./Debug" Command="./$(ProjectName)" CommandArguments="" UseSeparateDebugArgs="no" DebugArguments="" WorkingDirectory="$(IntermediateDirectory)" PauseExecWhenProcTerminates="yes" IsGUIProgram="no" IsEnabled="yes"/>
+ <BuildSystem Name="Default"/>
+ <Environment EnvVarSetName="&lt;Use Defaults&gt;" DbgSetName="&lt;Use Defaults&gt;">
+ <![CDATA[]]>
+ </Environment>
+ <Debugger IsRemote="no" RemoteHostName="" RemoteHostPort="" DebuggerPath="" IsExtended="no">
+ <DebuggerSearchPaths/>
+ <PostConnectCommands/>
+ <StartupCommands/>
+ </Debugger>
+ <PreBuild/>
+ <PostBuild/>
+ <CustomBuild Enabled="no">
+ <RebuildCommand/>
+ <CleanCommand/>
+ <BuildCommand/>
+ <PreprocessFileCommand/>
+ <SingleFileCommand/>
+ <MakefileGenerationCommand/>
+ <ThirdPartyToolName>None</ThirdPartyToolName>
+ <WorkingDirectory/>
+ </CustomBuild>
+ <AdditionalRules>
+ <CustomPostBuild/>
+ <CustomPreBuild/>
+ </AdditionalRules>
+ <Completion EnableCpp11="no" EnableCpp14="no">
+ <ClangCmpFlagsC/>
+ <ClangCmpFlags/>
+ <ClangPP/>
+ <SearchPaths/>
+ </Completion>
+ </Configuration>
+ <Configuration Name="Release" CompilerType="clang" DebuggerType="GNU gdb debugger" Type="Executable" BuildCmpWithGlobalSettings="append" BuildLnkWithGlobalSettings="append" BuildResWithGlobalSettings="append">
+ <Compiler Options="-O2;-Wall" C_Options="-O2;-std=c99;-Wall;-fno-strict-aliasing;-pthread;-D_POSIX_C_SOURCE=200112L;-D_XOPEN_SOURCE=500;-D_GNU_SOURCE" Assembler="" Required="yes" PreCompiledHeader="" PCHInCommandLine="no" PCHFlags="" PCHFlagsPolicy="0">
+ <IncludePath Value="."/>
+ <IncludePath Value="../../3rdparty/json.h"/>
+ <IncludePath Value="./include"/>
+ <IncludePath Value="../rdp/include"/>
+ <IncludePath Value="../../3rdparty/wslay/lib/includes"/>
+ <IncludePath Value="../../3rdparty/wslay/build/lib/includes"/>
+ <IncludePath Value="../../3rdparty/libev"/>
+ <IncludePath Value="../../3rdparty/picohttpparser"/>
+ <IncludePath Value="../../3rdparty/libcb/include"/>
+ <IncludePath Value="../../3rdparty/curl/build/lib"/>
+ <IncludePath Value="../../3rdparty/curl/include"/>
+ <Preprocessor Value="NDEBUG"/>
+ </Compiler>
+ <Linker Options="-pthread;../rdp/Release/rdp.a;../../3rdparty/libev/build/libev.a;../../3rdparty/wslay/build/lib/libwslay.a;../../3rdparty/FreeRDP/build/client/common/libfreerdp-client2.a;../../3rdparty/FreeRDP/build/channels/geometry/client/libgeometry-client.a;../../3rdparty/FreeRDP/build/channels/rdpgfx/client/librdpgfx-client.a;../../3rdparty/FreeRDP/build/channels/video/client/libvideo-client.a;../../3rdparty/FreeRDP/build/channels/parallel/client/libparallel-client.a;../../3rdparty/FreeRDP/build/channels/drive/client/libdrive-client.a;../../3rdparty/FreeRDP/build/channels/cliprdr/client/libcliprdr-client.a;../../3rdparty/FreeRDP/build/channels/drdynvc/client/libdrdynvc-client.a;../../3rdparty/FreeRDP/build/channels/tsmf/client/libtsmf-client.a;../../3rdparty/FreeRDP/build/channels/remdesk/client/libremdesk-client.a;../../3rdparty/FreeRDP/build/channels/rdpdr/client/librdpdr-client.a;../../3rdparty/FreeRDP/build/channels/rdpei/client/librdpei-client.a;../../3rdparty/FreeRDP/build/channels/encomsp/client/libencomsp-client.a;../../3rdparty/FreeRDP/build/channels/audin/client/libaudin-client.a;../../3rdparty/FreeRDP/build/channels/disp/client/libdisp-client.a;../../3rdparty/FreeRDP/build/channels/echo/client/libecho-client.a;../../3rdparty/FreeRDP/build/channels/rail/client/librail-client.a;../../3rdparty/FreeRDP/build/channels/serial/client/libserial-client.a;../../3rdparty/FreeRDP/build/channels/rdpsnd/client/librdpsnd-client.a;../../3rdparty/FreeRDP/build/channels/rdpsnd/client/fake/librdpsnd-client-fake.a;../../3rdparty/FreeRDP/build/channels/smartcard/client/libsmartcard-client.a;../../3rdparty/FreeRDP/build/libfreerdp/libfreerdp2.a;../../3rdparty/FreeRDP/build/winpr/libwinpr/libwinpr2.a;../../3rdparty/curl/build/libcurl.a" Required="yes">
+ <Library Value="ssl"/>
+ <Library Value="crypto"/>
+ <Library Value="dl"/>
+ <Library Value="rt"/>
+ <Library Value="png"/>
+ <Library Value="z"/>
+ </Linker>
+ <ResourceCompiler Options="" Required="no"/>
+ <General OutputFile="$(IntermediateDirectory)/$(ProjectName)" IntermediateDirectory="./Release" Command="./$(ProjectName)" CommandArguments="" UseSeparateDebugArgs="no" DebugArguments="" WorkingDirectory="$(IntermediateDirectory)" PauseExecWhenProcTerminates="yes" IsGUIProgram="no" IsEnabled="yes"/>
+ <BuildSystem Name="Default"/>
+ <Environment EnvVarSetName="&lt;Use Defaults&gt;" DbgSetName="&lt;Use Defaults&gt;">
+ <![CDATA[]]>
+ </Environment>
+ <Debugger IsRemote="no" RemoteHostName="" RemoteHostPort="" DebuggerPath="" IsExtended="no">
+ <DebuggerSearchPaths/>
+ <PostConnectCommands/>
+ <StartupCommands/>
+ </Debugger>
+ <PreBuild/>
+ <PostBuild/>
+ <CustomBuild Enabled="no">
+ <RebuildCommand/>
+ <CleanCommand/>
+ <BuildCommand/>
+ <PreprocessFileCommand/>
+ <SingleFileCommand/>
+ <MakefileGenerationCommand/>
+ <ThirdPartyToolName>None</ThirdPartyToolName>
+ <WorkingDirectory/>
+ </CustomBuild>
+ <AdditionalRules>
+ <CustomPostBuild/>
+ <CustomPreBuild/>
+ </AdditionalRules>
+ <Completion EnableCpp11="no" EnableCpp14="no">
+ <ClangCmpFlagsC/>
+ <ClangCmpFlags/>
+ <ClangPP/>
+ <SearchPaths/>
+ </Completion>
+ </Configuration>
+ </Settings>
+</CodeLite_Project>
diff --git a/src/core/ctl_task.h b/src/core/ctl_task.h
new file mode 100644
index 0000000..7535abd
--- /dev/null
+++ b/src/core/ctl_task.h
@@ -0,0 +1,18 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+typedef struct ctl_task_info_s
+{
+
+ /* control session instance */
+ ctl_session *session;
+
+ /* pointer to wrdp_thpool_task* used by some internals */
+ void *wrdp_thpool_task;
+
+} ctl_task_info;
diff --git a/src/core/curl_helpers.c b/src/core/curl_helpers.c
new file mode 100644
index 0000000..b24ce51
--- /dev/null
+++ b/src/core/curl_helpers.c
@@ -0,0 +1,864 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <ev.h>
+#include <curl/curl.h>
+#include <wslay/wslay.h>
+
+#include "wrdp_thpool.h"
+#include "wrdp_thpool_internals.h"
+
+#include "globals.h"
+#include "curl_helpers.h"
+#include "ws_protocol.h"
+#include "wrdp_thpool.h"
+#include "webrdp_module_api.h"
+#include "ws_session.h"
+#include "task.h"
+#include "thread_impl.h"
+#include "exports.h"
+
+#include "webrdp_core_api.h"
+#include "log.h"
+
+/* global curl code */
+
+/* TODO: per thread watcher list */
+
+struct socket_watcher_s;
+typedef struct socket_watcher_s socket_watcher;
+
+struct socket_watcher_s
+{
+ ev_io *watcher;
+ int fd, what;
+ wrdp_thpool_thread *t;
+ CURL *easy_handle;
+ socket_watcher *prev, *next;
+};
+
+typedef struct
+{
+ CURLM *cm;
+ socket_watcher *watcher_list;
+} curl_thread_data;
+
+static socket_watcher *
+find_socket_watcher(int fd, int what, socket_watcher **head)
+{
+ socket_watcher *w = *head;
+ while (w)
+ {
+ if (w->fd == fd)
+ {
+ if (what != -1 && w->what == what)
+ {
+ return w;
+ }
+ }
+ w = w->next;
+ }
+ return 0;
+}
+
+static socket_watcher *
+find_watcher_by_handle(CURL *easy_handle, socket_watcher **head)
+{
+ socket_watcher *w = *head;
+ while (w)
+ {
+ if (w->easy_handle == easy_handle)
+ {
+ return w;
+ }
+ w = w->next;
+ }
+ return 0;
+}
+
+static void
+append_socket_watcher(socket_watcher *_w, socket_watcher **head)
+{
+ _w->next = *head;
+ if (*head)
+ {
+ (*head)->prev = _w;
+ }
+ *head = _w;
+}
+
+typedef struct
+{
+ CURLM *multi;
+ wrdp_thpool_thread *t;
+} curl_socket_data;
+
+typedef struct
+{
+ curl_socket_data *csd;
+ int fd;
+} c_f_i_cb_data;
+
+static void
+remove_socket_watcher_impl(socket_watcher *w, socket_watcher **head)
+{
+ if (w->prev)
+ {
+ w->prev->next = w->next;
+ }
+ if (w->next)
+ {
+ w->next->prev = w->prev;
+ }
+ if (w == *head)
+ {
+ *head = w->next;
+ }
+ if (w->watcher)
+ {
+ if (ev_is_active(w->watcher))
+ {
+ ev_io_stop(w->t->ev_th_loop, w->watcher);
+ }
+ if (w->watcher->data)
+ {
+ free(w->watcher->data);
+ }
+ free(w->watcher);
+ }
+ free(w);
+}
+
+static void
+remove_socket_watcher(int fd, int what, socket_watcher **head)
+{
+ socket_watcher *w = 0;
+ for (w = find_socket_watcher(fd, what, head); w;
+ w = find_socket_watcher(fd, what, head))
+ {
+ remove_socket_watcher_impl(w, head);
+ }
+}
+
+static void
+remove_all_watchers(CURL *easy_handle, socket_watcher **head)
+{
+ socket_watcher *w = 0;
+ for (w = find_watcher_by_handle(easy_handle, head); w;
+ w = find_watcher_by_handle(easy_handle, head))
+ {
+ remove_socket_watcher_impl(w, head);
+ }
+}
+
+static void
+curl_check_finished(CURLM *cm)
+{
+ CURLMsg *msg = 0;
+ int finished = 0;
+ while ((msg = curl_multi_info_read(cm, &finished)))
+ {
+ {
+ const char *msg = "curl transfer finished";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_trace, 0);
+ }
+ {
+ curl_request_info *cri = 0;
+ if (msg->msg != CURLMSG_DONE)
+ {
+ //TODO: handle errors
+ continue;
+ }
+ curl_easy_getinfo(
+ msg->easy_handle, CURLINFO_PRIVATE, &cri);
+ if (!cri)
+ {
+ continue;
+ }
+ curl_multi_remove_handle(cm, msg->easy_handle);
+ ws_session *t = cri->session;
+ task_info *info = t->task_info;
+ wrdp_thpool_task *task = 0;
+ if (info)
+ task = info->wrdp_thpool_task;
+ if (task)
+ {
+ curl_thread_data *ctd
+ = task->thread->pool->userdata;
+ remove_all_watchers(msg->easy_handle,
+ &(ctd[task->thread->thread_id]
+ .watcher_list));
+ }
+ if (!cri->data)
+ {
+ continue;
+ }
+ if (info && info->stopped)
+ {
+ goto clean;
+ }
+ if (msg->data.result != CURLE_OK)
+ {
+ char msg_str[64], log_str[64];
+ log_msg_info i = {0};
+ snprintf(log_str, 63,
+ "curl transfer failed"
+ " with error:\n\t%s",
+ curl_easy_strerror(msg->data.result));
+ i.buf = (uint8_t *)log_str;
+ i.task_info = info;
+ i.ws_session = t;
+ i.level = wrdp_log_level_error;
+ log_msg_ex(&i);
+ snprintf(msg_str, 63, "curl_transfer_error_%d",
+ (int)(msg->data.result));
+ send_error_msg(msg_str, info);
+ t->session_state = ws_session_error;
+ goto clean;
+ }
+ cri->data[cri->data_size - 1] = 0;
+#ifdef DEBUG
+ /* print server request/reply data */
+ {
+ size_t buf_size = cri->data_size + 256;
+ char buf[buf_size];
+ log_msg_info i = {0};
+ i.task_info = info;
+ i.ws_session = t;
+ snprintf(buf, buf_size - 1,
+ "curl raw data:\nrequest type: %s\ndata:\n "
+ "%.*s",
+ cri->type == curl_request_type_get ? "GET" :
+ "POST",
+ (int)cri->data_size, cri->data);
+ i.buf = (uint8_t *)buf;
+ i.level = wrdp_log_level_trace;
+ log_msg_ex(&i);
+ }
+#endif
+
+ if (cri->user_data_handler_cb)
+ {
+ if (!cri->user_data_handler_cb(
+ cri->data, cri->session, cri->userdata))
+ {
+ cri->session->session_state
+ = ws_session_error;
+ task_destroy_client_connection(
+ cri->session);
+ }
+ }
+ clean:
+ if (!SLIST_EMPTY(&(t->curls_easy_head)))
+ {
+ struct curls_easy_s *curl_node
+ = SLIST_FIRST(&(t->curls_easy_head));
+ while (curl_node)
+ {
+ CURL *curl = curl_node->curl;
+ struct curls_easy_s *node = curl_node;
+
+ curl_node
+ = SLIST_NEXT(curl_node, entries);
+ if (curl == msg->easy_handle)
+ {
+ SLIST_REMOVE(
+ &t->curls_easy_head, node,
+ curls_easy_s, entries);
+ free(node);
+ }
+ }
+ }
+ if (cri->free_cb)
+ cri->free_cb(cri->userdata);
+ free(cri->data);
+ cri->data = 0;
+ free(cri);
+ curl_easy_cleanup(msg->easy_handle);
+ {
+ log_msg(
+ (const uint8_t
+ *)"clearing finished curl transfer",
+ 0, wrdp_log_level_trace, 0);
+ }
+ }
+ }
+}
+
+void
+curl_list_session_destroy(ws_session *session)
+{
+ CURLM *cm = session->curlm;
+ if (!SLIST_EMPTY(&(session->curls_easy_head)))
+ {
+ struct curls_easy_s *curl_node
+ = SLIST_FIRST(&(session->curls_easy_head));
+ while (curl_node)
+ {
+ CURL *curl = curl_node->curl;
+
+ curl_request_info *cri = 0;
+ curl_easy_getinfo(curl, CURLINFO_PRIVATE, &cri);
+ {
+ struct curls_easy_s *node = curl_node;
+
+ curl_node = SLIST_NEXT(curl_node, entries);
+ SLIST_REMOVE(&(session->curls_easy_head), node,
+ curls_easy_s, entries);
+ free(node);
+ }
+ if (!cri)
+ {
+ curl_multi_remove_handle(cm, curl);
+ curl_easy_cleanup(curl);
+ continue;
+ }
+ curl_multi_remove_handle(cm, curl);
+ curl_easy_cleanup(curl);
+ if (!cri->data)
+ {
+ continue;
+ }
+ free(cri->data);
+ free(cri);
+ }
+ }
+}
+
+static void
+curl_fd_io_cb(EV_P_ ev_io *w, int revents)
+{
+ c_f_i_cb_data *d = w->data;
+ int pending = 0;
+ int ev_bitmask = 0;
+ if (revents & EV_READ && revents & EV_WRITE)
+ {
+ ev_bitmask |= CURL_POLL_INOUT;
+ }
+ else if (revents & EV_READ)
+ {
+ ev_bitmask |= CURL_POLL_IN;
+ }
+ else if (revents & EV_WRITE)
+ {
+ ev_bitmask |= CURL_POLL_OUT;
+ }
+ CURLMcode code = curl_multi_socket_action(
+ d->csd->multi, d->fd, ev_bitmask, &pending);
+ if (code != CURLM_OK)
+ {
+ char msg[128];
+ snprintf(msg, 127,
+ "curl_multi_socket_action failed with error: %d", code);
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ }
+ curl_check_finished(d->csd->multi);
+}
+
+static int
+curl_socket_cb(CURL *easy, /* easy handle */
+ curl_socket_t s, /* socket */
+ int what, /* describes the socket */
+ void *userp, /* private callback pointer */
+ void *socketp) /* private socket pointer */
+{
+ curl_socket_data *csd = userp;
+ if (what == CURL_POLL_REMOVE)
+ {
+ {
+ curl_thread_data *ctd = csd->t->pool->userdata;
+ remove_socket_watcher(
+ s, -1, &(ctd[csd->t->thread_id].watcher_list));
+ }
+ goto finish;
+ }
+ socket_watcher *sw = calloc(1, sizeof(socket_watcher));
+ if (!sw)
+ {
+ perror("calloc");
+ goto finish;
+ }
+ ev_io *w = calloc(1, sizeof(ev_io));
+ if (!w)
+ {
+ free(sw);
+ perror("calloc");
+ goto finish;
+ }
+ c_f_i_cb_data *d = calloc(1, sizeof(c_f_i_cb_data));
+ if (!d)
+ {
+ free(sw);
+ free(w);
+ perror("calloc");
+ goto finish;
+ }
+ d->csd = csd;
+ d->fd = s;
+ sw->t = csd->t;
+ sw->watcher = w;
+ sw->fd = s;
+ sw->what = what;
+ sw->easy_handle = easy;
+ w->data = d;
+ {
+ curl_thread_data *ctd = csd->t->pool->userdata;
+ append_socket_watcher(
+ sw, &(ctd[csd->t->thread_id].watcher_list));
+ }
+ if (what == CURL_POLL_IN)
+ {
+ ev_io_init(w, curl_fd_io_cb, s, EV_READ);
+ }
+ else if (what == CURL_POLL_OUT)
+ {
+ ev_io_init(w, curl_fd_io_cb, s, EV_WRITE);
+ }
+ else if (what == CURL_POLL_INOUT)
+ {
+ ev_io_init(w, curl_fd_io_cb, s, EV_READ | EV_WRITE);
+ }
+ ev_io_start(csd->t->ev_th_loop, w);
+finish:
+ curl_check_finished(csd->multi);
+ return CURLM_OK;
+}
+
+static void
+timeouts_check_cb(EV_P_ ev_timer *w, int revents)
+{
+ /* TODO: test this */
+ CURLM *multi = w->data;
+ int handles = 0;
+ free(w);
+ curl_check_finished(multi);
+ CURLMcode code
+ = curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, &handles);
+ if (code != CURLM_OK)
+ {
+ char msg[128];
+ snprintf(msg, 127,
+ "curl_multi_socket_action failed with error: %d", code);
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ }
+}
+
+static int
+curl_timer_cb(CURLM *multi, long timeout_ms, void *userp)
+{
+ if (timeout_ms > 0)
+ {
+ int handles = 0;
+ CURLMcode code = curl_multi_socket_action(
+ multi, CURL_SOCKET_TIMEOUT, 0, &handles);
+ if (code != CURLM_OK)
+ {
+ char msg[128];
+ snprintf(msg, 127,
+ "curl_multi_socket_action failed with error: %d",
+ code);
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ }
+ return 0;
+ }
+ wrdp_thpool_thread *t = userp;
+ ev_timer *timer = calloc(1, sizeof(ev_timer));
+ if (!timer)
+ {
+ perror("calloc");
+ return -1;
+ }
+ ev_timer_init(timer, timeouts_check_cb, timeout_ms / 1000.0, 0.);
+ timer->data = multi;
+ ev_timer_start(t->ev_th_loop, timer);
+ return 0;
+}
+
+void
+per_thread_curl_init(void *user_pool_data, wrdp_thpool_thread *t)
+{
+ curl_thread_data *ctd = user_pool_data;
+ ctd[t->thread_id].cm = curl_multi_init();
+ if (!ctd[t->thread_id].cm)
+ {
+ printf("FATAL: curl_multi_init failed\n");
+ exit(EXIT_FAILURE);
+ }
+ curl_socket_data *csd = calloc(1, sizeof(curl_socket_data));
+ if (!csd)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ csd->multi = ctd[t->thread_id].cm;
+ csd->t = t;
+ if (curl_multi_setopt(
+ ctd[t->thread_id].cm, CURLMOPT_SOCKETFUNCTION, curl_socket_cb)
+ != CURLM_OK)
+ {
+ printf("curl_multi_setopt(curlms[t->thread_id],"
+ "CURLMOPT_SOCKETFUNCTION, curl_socket_cb) failed\n");
+ exit(EXIT_FAILURE);
+ }
+ if (curl_multi_setopt(ctd[t->thread_id].cm, CURLMOPT_SOCKETDATA, csd)
+ != CURLM_OK)
+ {
+ printf("curl_multi_setopt(curlms[t->thread_id], "
+ "CURLMOPT_SOCKETDATA,"
+ " csd) failed failed\n");
+ exit(EXIT_FAILURE);
+ }
+ if (curl_multi_setopt(
+ ctd[t->thread_id].cm, CURLMOPT_TIMERFUNCTION, curl_timer_cb)
+ != CURLM_OK)
+ {
+ printf("curl_multi_setopt(curlms[t->thread_id], "
+ "CURLMOPT_TIMERFUNCTION,"
+ "curl_timer_cb) failed\n");
+ exit(EXIT_FAILURE);
+ }
+ if (curl_multi_setopt(ctd[t->thread_id].cm, CURLMOPT_TIMERDATA, t)
+ != CURLM_OK)
+ {
+ printf("curl_multi_setopt(curlms[t->thread_id], "
+ "CURLMOPT_TIMERDATA,"
+ " t) failed\n");
+ exit(EXIT_FAILURE);
+ }
+}
+
+void *
+init_curl_global()
+{
+ curl_thread_data *ret;
+ CURLcode cc = curl_global_init(CURL_GLOBAL_ALL | CURL_GLOBAL_ACK_EINTR);
+ if (cc != CURLE_OK)
+ {
+ printf("FATAL: curl_global_init failed\n");
+ exit(EXIT_FAILURE);
+ }
+ ret = calloc(g_globals.settings.thread_count, sizeof(curl_thread_data));
+ if (!ret)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ return ret;
+}
+
+/* transfer related code */
+
+static size_t
+curl_read_cb(char *buffer, size_t size, size_t nitems, void *userdata)
+{
+ curl_request_info *cri = userdata;
+ if (!cri || !cri->data)
+ {
+ return 0;
+ }
+ ws_session *t = cri->session;
+ task_info *info = t->task_info;
+ if (info && info->stopped)
+ return 0;
+ size_t len = size * nitems;
+ if (len > (cri->data_size - cri->written - 1))
+ {
+ char msg[128];
+ snprintf(msg, 127,
+ "curl_read_cb: error: buffer"
+ " of size %ld is too small\n",
+ cri->data_size);
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ goto error;
+ }
+ if (!len)
+ {
+ const char *msg = "curl_read_cb: error: zero length reply";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ goto error;
+ }
+ memcpy(cri->data + cri->written, buffer, len);
+ cri->data[len] = 0;
+ cri->written += len;
+ curl_check_finished(cri->cm);
+ return len;
+error:
+ if (cri->free_cb)
+ {
+ cri->free_cb(cri->userdata);
+ }
+ free(cri->data);
+ free(cri);
+ return 0;
+}
+
+char *
+get_url(const char *token, curl_request_type type)
+{
+ switch (type)
+ {
+ case curl_request_type_get:
+ {
+ size_t len
+ = strlen(token)
+ + strlen(g_globals.settings.auth_server_url)
+ + strlen("?") + 1;
+ char *url = malloc(len);
+ if (!url)
+ {
+ perror("malloc");
+ return NULL;
+ }
+ snprintf(url, len, "%s?%s",
+ g_globals.settings.auth_server_url, token);
+ return url;
+ }
+ break;
+ case curl_request_type_post:
+ {
+ char *url = malloc(
+ strlen(g_globals.settings.auth_server_url) + 1);
+ if (!url)
+ {
+ perror("malloc");
+ return NULL;
+ }
+ strcpy(url, g_globals.settings.auth_server_url);
+ return url;
+ }
+ break;
+ default:
+ return NULL;
+ }
+}
+
+curl_request_info *
+curl_init_request(ws_session *session, curl_request_type type, uint8_t *data,
+ size_t data_size,
+ bool (*user_data_handler_cb)(uint8_t *, ws_session *, void *),
+ void (*free_cb)(void *), void *userdata)
+{
+ curl_request_info *request = calloc(1, sizeof(*request));
+ request->type = type;
+ request->session = session;
+ if (type == curl_request_type_get)
+ {
+ request->data_size = 4096;
+ request->data = malloc(request->data_size);
+ request->data[0] = 0;
+ }
+ if (type == curl_request_type_post)
+ {
+ request->data_size = data_size;
+ request->data = malloc(request->data_size + 1);
+ memcpy(request->data, data, data_size);
+ request->data[data_size] = 0;
+ }
+ request->url = get_url(session->token_base64, type);
+ request->user_data_handler_cb = user_data_handler_cb;
+ request->free_cb = free_cb;
+ request->userdata = userdata;
+
+ return request;
+}
+
+bool
+curl_request(curl_request_info *r)
+{
+ CURL *c = 0;
+ curl_thread_data *ctd = 0;
+ CURLM *cm = 0;
+ ws_session *session = 0;
+ task_info *t_info = 0;
+ wrdp_thpool_task *task = 0;
+ if (!r)
+ return false;
+ session = r->session;
+ if (!session)
+ {
+ return false;
+ }
+ t_info = session->task_info;
+ if (!t_info)
+ {
+ return false;
+ }
+ task = t_info->wrdp_thpool_task;
+ if (!task)
+ {
+ return false;
+ }
+ ctd = task->thread->pool->userdata;
+ cm = ctd[task->thread->thread_id].cm;
+ r->cm = cm;
+ session->curlm = cm;
+ c = curl_easy_init();
+ if (!r->url || !r->data)
+ {
+ goto curl_easy_setopt_error;
+ }
+ if (!c)
+ {
+ goto curl_easy_setopt_error;
+ }
+ if (curl_easy_setopt(c, CURLOPT_URL, r->url) != CURLE_OK)
+ {
+ const char *msg
+ = "curl_easy_setopt (c, CURLOPT_URL, url) failed";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ goto curl_easy_setopt_error;
+ }
+ switch (r->type)
+ {
+ case curl_request_type_get:
+ if (curl_easy_setopt(c, CURLOPT_WRITEDATA, r)
+ != CURLE_OK)
+ {
+ const char *msg
+ = "curl_easy_setopt (c, CURLOPT_WRITEDATA,"
+ " crd) failed";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ goto curl_easy_setopt_error;
+ }
+ if (curl_easy_setopt(
+ c, CURLOPT_WRITEFUNCTION, curl_read_cb)
+ != CURLE_OK)
+ {
+ const char *msg = "curl_easy_setopt (c, "
+ "CURLOPT_WRITEFUNCTION,"
+ " curl_read_cb) failed";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ goto curl_easy_setopt_error;
+ }
+ break;
+ case curl_request_type_post:
+ if (curl_easy_setopt(c, CURLOPT_POST, 1L) != CURLE_OK)
+ {
+ const char *msg
+ = "curl_easy_setopt (c, CURLOPT_POST,"
+ " 1L) failed";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ goto curl_easy_setopt_error;
+ }
+ if (curl_easy_setopt(
+ c, CURLOPT_POSTFIELDSIZE, r->data_size)
+ != CURLE_OK)
+ {
+ const char *msg
+ = "curl_easy_setopt (c, CURLOPT_WRITEDATA,"
+ " crd) failed";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ goto curl_easy_setopt_error;
+ }
+ if (curl_easy_setopt(c, CURLOPT_POSTFIELDS, r->data)
+ != CURLE_OK)
+ {
+ const char *msg
+ = "curl_easy_setopt (c, CURLOPT_WRITEDATA,"
+ " crd) failed";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ goto curl_easy_setopt_error;
+ }
+ {
+ log_msg_info i = {0};
+ size_t buf_size = r->data_size + 256;
+ uint8_t buf[buf_size];
+ i.task_info = t_info;
+ i.ws_session = session;
+ i.level = wrdp_log_level_trace;
+ snprintf((char *)buf, buf_size - 1,
+ "curl postfields data: %.*s",
+ (int)r->data_size, r->data);
+ i.buf = buf;
+ log_msg_ex(&i);
+ }
+ break;
+ default:
+ {
+ const char *msg = "request type is wrong";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ goto curl_easy_setopt_error;
+ }
+ break;
+ }
+ if (curl_easy_setopt(c, CURLOPT_PRIVATE, r) != CURLE_OK)
+ {
+ const char *msg = "curl_easy_setopt (c, CURLOPT_WRITEDATA,"
+ " crd) failed";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ goto curl_easy_setopt_error;
+ }
+#ifdef DEBUG
+ curl_easy_setopt(c, CURLOPT_SSL_VERIFYPEER, 0);
+ curl_easy_setopt(c, CURLOPT_SSL_VERIFYHOST, 0);
+#endif
+ if (curl_multi_add_handle(cm, c) != CURLM_OK)
+ {
+ const char *msg = "curl_multi_add_handle failed";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ free(r->url);
+ return false;
+ }
+
+ {
+ struct curls_easy_s *entry
+ = calloc(1, sizeof(struct curls_easy_s));
+ if (!entry)
+ {
+ perror("calloc");
+ goto curl_easy_setopt_error;
+ }
+ entry->session = session;
+ entry->curl = c;
+
+ SLIST_INSERT_HEAD(&(session->curls_easy_head), entry, entries);
+ }
+
+ free(r->url);
+ return true;
+curl_easy_setopt_error:
+ if (r->free_cb)
+ r->free_cb(r->userdata);
+ free(r->url);
+ free(r->data);
+ free(r);
+ return false;
+}
+
+size_t
+curl_prepare_post_request_data(uint8_t **buf, ws_session *session)
+{
+ size_t data_size = strlen("{\"sid\": \"%s\", \"type\": "
+ "\"sessionupdate\", \"status\": %d}")
+ - 4 + //format
+ strlen(session->token_base64)
+ + 1; //one digit (session_state)
+ uint8_t *data = malloc(data_size + 1);
+ sprintf((char *)data,
+ "{\"sid\": \"%s\", \"type\": \"sessionupdate\", \"status\": %d}",
+ session->token_base64, session->session_state);
+ *buf = data;
+ return data_size;
+}
diff --git a/src/core/curl_helpers.h b/src/core/curl_helpers.h
new file mode 100644
index 0000000..e1f4205
--- /dev/null
+++ b/src/core/curl_helpers.h
@@ -0,0 +1,76 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+
+#pragma once
+
+void *init_curl_global();
+
+void per_thread_curl_init(void *user_pool_data, wrdp_thpool_thread *t);
+
+bool curl_contact_auth_server(const char *token, void *user_data);
+
+typedef enum curl_request_type_
+{
+ curl_request_type_get,
+ curl_request_type_post
+} curl_request_type;
+
+typedef struct _request_info
+{
+ /* CURLM */
+ void *cm;
+
+ /* type of HTTP request */
+ curl_request_type type;
+ ws_session *session;
+
+ /* buffer to be written (GET) / to be sent (POST) */
+ uint8_t *data;
+
+ /* capacity of data */
+ size_t data_size;
+
+ /* size of handled data */
+ size_t written;
+
+ /* URL to use in the request; must be URL-encoded in the following
+ * format: scheme://host:port/path */
+ char *url;
+
+ /* handler callback
+ * may be NULL
+ * return false on failure */
+ bool (*user_data_handler_cb)(
+ uint8_t *buf, ws_session *session, void *userdata);
+
+ /* callback to clean up userdata */
+ void (*free_cb)(void *userdata);
+
+ /* user specified data passed to all callbacks */
+ void *userdata;
+} curl_request_info;
+
+curl_request_info *curl_init_request(ws_session *session,
+ curl_request_type type, uint8_t *data, size_t data_size,
+ bool (*user_data_handler_cb)(
+ uint8_t *buf, ws_session *session, void *userdata),
+ void (*free_cb)(void *userdata), void *userdata);
+
+bool curl_request(curl_request_info *r);
+
+char *get_url(const char *s, curl_request_type type);
+
+void curl_list_session_destroy(ws_session *session);
+
+struct curls_easy_s
+{
+ ws_session *session;
+ void *curl;
+ SLIST_ENTRY(curls_easy_s) entries;
+};
+
+size_t curl_prepare_post_request_data(uint8_t **buf, ws_session *session);
diff --git a/src/core/ev_loop.c b/src/core/ev_loop.c
new file mode 100644
index 0000000..e084e81
--- /dev/null
+++ b/src/core/ev_loop.c
@@ -0,0 +1,206 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <ev.h>
+
+#include <webrdp_module_api.h>
+
+#include "globals.h"
+#include "remote_control.h"
+#include "thread_impl.h"
+#include "wrdp_thpool.h"
+#include "wrdp_thpool_internals.h"
+#include "ws_session.h"
+#include "socket_helpers.h"
+#include "task.h"
+#include "remote_control.h"
+
+#include "log.h"
+
+static ws_session *
+ws_create_session(int connection_fd)
+{
+ ws_session *session = 0;
+ task_info *info = 0;
+ session = calloc(1, sizeof(ws_session));
+ if (!session)
+ {
+ perror("calloc");
+ goto error;
+ }
+ info = calloc(1, sizeof(task_info));
+ if (!info)
+ {
+ perror("calloc");
+ goto error;
+ }
+ session->task_info = info;
+ session->session_state = ws_session_initial;
+ session->connection_fd = connection_fd;
+ SLIST_INIT(&(session->backend_settings_head));
+ return session;
+error:
+ if (session)
+ {
+ if (session->task_info)
+ {
+ free(session->task_info);
+ }
+ free(session);
+ }
+ return 0;
+}
+
+static void
+ws_server_cb(EV_P_ ev_io *w, int revents)
+{
+ int *sock = w->data;
+ int con = accept_new_connection(*sock);
+ if (con != -1)
+ {
+ ws_session *session = 0;
+ {
+ const char *msg = "accepting new ws connection";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_trace, 0);
+ }
+ session = ws_create_session(con);
+ if (!session)
+ {
+ close(con);
+ return;
+ }
+ if (!wrdp_thread_pool_add_task(g_globals.thpool, ws_run_session,
+ ws_session_init_cb, session))
+ {
+ const char *msg
+ = "Failed to move task to pool: pool is full";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ return;
+ }
+ }
+}
+
+static void
+ctl_server_cb(EV_P_ ev_io *w, int revents)
+{
+ int *sock = w->data;
+ int con = accept_new_connection(*sock);
+ if (con != -1)
+ {
+ const char *msg = "accepting new ctl connection";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_trace, 0);
+ ctl_task_info *info = ctl_create_task(con);
+ if (!info)
+ {
+ close(con);
+ return;
+ }
+ if (!wrdp_thread_pool_add_task(
+ g_globals.thpool, ctl_run_task, 0, info))
+ {
+ const char *msg
+ = "Failed to move task to pool: pool is full";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ return;
+ }
+ /* TODO: */
+ }
+}
+
+void
+run_ev_loop_main(int ws_tcp_sock, int ws_unix_sock, int ctl_server_sock_tcp,
+ int ctl_server_sock_unix)
+{
+ if (ws_tcp_sock == -1 && ws_unix_sock == -1)
+ {
+ const char *msg
+ = "ws: both tcp port and unix socket does not set";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ exit(EXIT_FAILURE);
+ }
+ if (ctl_server_sock_tcp == -1 && ctl_server_sock_unix == -1)
+ {
+ const char *msg
+ = "ctl: both tcp port and unix socket does not set";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ exit(EXIT_FAILURE);
+ }
+ struct ev_loop *ev_loop = EV_DEFAULT;
+ ev_io *ws_ev_sock_acceptor, *ws_ev_unix_acceptor, *ctl_ev_sock_acceptor,
+ *ctl_ev_unix_acceptor;
+ if (ws_tcp_sock != -1)
+ {
+ int *sock = calloc(1, sizeof(int));
+ if (!sock)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ ws_ev_sock_acceptor = calloc(1, sizeof(ev_io));
+ *sock = ws_tcp_sock;
+ ev_io_init(
+ ws_ev_sock_acceptor, ws_server_cb, ws_tcp_sock, EV_READ);
+ ws_ev_sock_acceptor->data = sock;
+ ev_io_start(ev_loop, ws_ev_sock_acceptor);
+ }
+ if (ws_unix_sock != -1)
+ {
+ int *sock = calloc(1, sizeof(int));
+ if (!sock)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ ws_ev_unix_acceptor = calloc(1, sizeof(ev_io));
+ *sock = ws_unix_sock;
+ ev_io_init(
+ ws_ev_unix_acceptor, ws_server_cb, ws_unix_sock, EV_READ);
+ ws_ev_unix_acceptor->data = sock;
+ ev_io_start(ev_loop, ws_ev_unix_acceptor);
+ }
+ if (ctl_server_sock_tcp != -1)
+ {
+ int *sock = calloc(1, sizeof(int));
+ if (!sock)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ ctl_ev_sock_acceptor = calloc(1, sizeof(ev_io));
+ *sock = ctl_server_sock_tcp;
+ ev_io_init(ctl_ev_sock_acceptor, ctl_server_cb,
+ ctl_server_sock_tcp, EV_READ);
+ ctl_ev_sock_acceptor->data = sock;
+ ev_io_start(ev_loop, ctl_ev_sock_acceptor);
+ }
+ if (ctl_server_sock_unix != -1)
+ {
+ int *sock = calloc(1, sizeof(int));
+ if (!sock)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ ctl_ev_unix_acceptor = calloc(1, sizeof(ev_io));
+ *sock = ctl_server_sock_unix;
+ ev_io_init(ctl_ev_unix_acceptor, ctl_server_cb,
+ ctl_server_sock_unix, EV_READ);
+ ctl_ev_unix_acceptor->data = sock;
+ ev_io_start(ev_loop, ctl_ev_unix_acceptor);
+ }
+ ev_run(ev_loop, 0);
+}
diff --git a/src/core/ev_loop.h b/src/core/ev_loop.h
new file mode 100644
index 0000000..5be49c4
--- /dev/null
+++ b/src/core/ev_loop.h
@@ -0,0 +1,11 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+
+#pragma once
+
+void run_ev_loop_main(int ws_tcp_sock, int ws_unix_sock,
+ int ctl_server_sock_tcp, int ctl_server_sock_unix);
diff --git a/src/core/exports.c b/src/core/exports.c
new file mode 100644
index 0000000..2f336be
--- /dev/null
+++ b/src/core/exports.c
@@ -0,0 +1,396 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+#include <ev.h>
+#include <wslay/wslay.h>
+
+#include "ws_session.h"
+#include "ws_protocol.h"
+#include "webrdp_module_api.h"
+#include "wrdp_thpool_internals.h"
+#include "globals.h"
+#include "task.h"
+
+#include "thread_impl.h"
+
+#include "utilities.h"
+#include "log.h"
+
+void
+send_text_msg(const char *msg, void *_task_info)
+{
+ ws_send_text((uint8_t *)msg, strlen(msg), _task_info);
+}
+
+static void
+send_typed_text_msg(const char *type, const char *msg, void *_task_info)
+{
+ size_t len = strlen(msg) + strlen(type);
+ uint8_t *buf = malloc(len);
+#ifdef DEBUG
+ log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_trace, 0);
+#endif
+ if (!buf)
+ {
+ perror("malloc");
+ return;
+ }
+ memset(buf, 0, len);
+ memcpy(buf, type, strlen(type));
+ memcpy(buf + strlen(type), msg, len - strlen(type));
+ ws_send_text(buf, len, _task_info);
+ free(buf);
+}
+
+void
+send_text_info_msg(const char *msg, void *task_info)
+{
+ send_typed_text_msg("I:", msg, task_info);
+}
+
+void
+send_text_warning_msg(const char *msg, void *task_info)
+{
+ send_typed_text_msg("W:", msg, task_info);
+}
+
+void
+send_text_debug_msg(const char *msg, void *task_info)
+{
+ send_typed_text_msg("D:", msg, task_info);
+}
+
+void
+send_error_msg(const char *msg, void *task_info)
+{
+ send_typed_text_msg("E:", msg, task_info);
+}
+
+void
+send_ctl_msg(const char *msg, void *task_info)
+{
+ send_typed_text_msg("0:", msg, task_info);
+}
+
+static void
+send_termination_msg(void *task_info)
+{
+ ws_send_text((uint8_t *)"T:", 2, task_info);
+}
+
+static void
+send_typed_proto_msg(
+ uint8_t *buf, size_t size, ws_output_codes type, void *task_info)
+{
+ uint8_t *msg = ws_pack_msg(buf, size, type);
+ ws_send_binary(msg, size + 4, task_info);
+ free(msg);
+}
+
+static void
+send_begin_paint(void *task_info)
+{
+ send_typed_proto_msg(0, 0, ws_out_beginpaint, task_info);
+}
+
+static void
+send_end_paint(void *task_info)
+{
+ send_typed_proto_msg(0, 0, ws_out_endpaint, task_info);
+}
+
+static void
+send_bitmap(
+ const wrdp_core_display_bmp *bmp, uint8_t *bmp_data, void *task_info)
+{
+ size_t buf_size = sizeof(wrdp_core_display_bmp) + bmp->size;
+ uint8_t *buf = malloc(buf_size);
+ if (!buf)
+ {
+ perror("malloc");
+ return;
+ }
+ memcpy(buf, bmp, sizeof(wrdp_core_display_bmp));
+ memcpy(buf + sizeof(wrdp_core_display_bmp), bmp_data, bmp->size);
+ send_typed_proto_msg(buf, buf_size, ws_out_bitmap, task_info);
+ free(buf);
+}
+
+static void
+send_opaque_rect_order(
+ const wrdp_core_display_opaque_rect_order *oro, void *task_info)
+{
+ send_typed_proto_msg((uint8_t *)oro,
+ sizeof(wrdp_core_display_opaque_rect_order), ws_out_opaquerect,
+ task_info);
+}
+
+static void
+send_set_bounds(const wrdp_core_display_bounds *bounds, void *task_info)
+{
+ send_typed_proto_msg((uint8_t *)bounds,
+ sizeof(wrdp_core_display_bounds), ws_out_setbounds, task_info);
+}
+
+static void
+send_pat_blt(const wrdp_core_display_patblt_order *patblt, void *task_info)
+{
+ send_typed_proto_msg((uint8_t *)patblt,
+ sizeof(wrdp_core_display_patblt_order), ws_out_patblt, task_info);
+}
+
+static void
+send_multi_opaque_rect(
+ const wrdp_core_display_m_opaque_rect *mrect, void *task_info)
+{
+ size_t buf_size
+ = (sizeof(uint32_t) * 2)
+ + (sizeof(wrdp_core_display_delta_rect) * mrect->num_rect);
+ uint8_t *buf = malloc(buf_size);
+ if (!buf)
+ {
+ perror("malloc");
+ return;
+ }
+ memcpy(buf, (uint8_t *)&(mrect->color), 4);
+ memcpy(buf + 4, (uint8_t *)&(mrect->num_rect), 4);
+ memcpy(buf + 4 + 4, (uint8_t *)mrect->rects,
+ mrect->num_rect * sizeof(wrdp_core_display_delta_rect));
+
+ send_typed_proto_msg(buf, buf_size, ws_out_multi_opaquerect, task_info);
+ free(buf);
+}
+
+static void
+send_scr_blt(const wrdp_core_display_scr_blt *scr_blt, void *task_info)
+{
+ send_typed_proto_msg((uint8_t *)&scr_blt,
+ sizeof(wrdp_core_display_scr_blt), ws_out_scr_btl, task_info);
+}
+
+static void
+send_ptr_new(const wrdp_core_display_cursor_info *ptr, void *task_info)
+{
+ size_t buf_size = sizeof(wrdp_core_display_cursor) - sizeof(uint8_t *)
+ + sizeof(wrdp_core_display_cursor_info)
+ - sizeof(uint32_t)
+ - sizeof(wrdp_core_display_cursor *) + ptr->data_size;
+ uint8_t *buf = malloc(buf_size);
+ if (!buf)
+ {
+ perror("malloc");
+ return;
+ }
+ memcpy(buf, ptr,
+ sizeof(wrdp_core_display_cursor_info) - sizeof(uint32_t)
+ - sizeof(wrdp_core_display_cursor *));
+ memcpy(buf + sizeof(wrdp_core_display_cursor_info) - sizeof(uint32_t)
+ - sizeof(wrdp_core_display_cursor *),
+ ptr->cur, sizeof(wrdp_core_display_cursor) - sizeof(uint8_t *));
+ memcpy(buf + sizeof(wrdp_core_display_cursor_info) - sizeof(uint32_t)
+ - sizeof(wrdp_core_display_cursor *)
+ + sizeof(wrdp_core_display_cursor) - sizeof(uint8_t *),
+ ptr->cur->data, ptr->data_size);
+ send_typed_proto_msg(buf, buf_size, ws_out_ptr_new, task_info);
+ free(buf);
+}
+
+static void
+send_ptr_free(uint32_t ptr_id, void *task_info)
+{
+ send_typed_proto_msg(
+ (uint8_t *)&ptr_id, sizeof(uint32_t), ws_out_ptr_free, task_info);
+}
+
+static void
+send_ptr_set(uint32_t ptr_id, void *task_info)
+{
+ send_typed_proto_msg(
+ (uint8_t *)&ptr_id, sizeof(uint32_t), ws_out_ptr_set, task_info);
+}
+
+static void
+send_ptr_set_null(void *task_info)
+{
+ send_typed_proto_msg(0, 0, ws_out_ptr_set_null, task_info);
+}
+
+static void
+send_ptr_set_default(void *task_info)
+{
+ send_typed_proto_msg(0, 0, ws_out_ptr_set_default, task_info);
+}
+
+static void
+clipboard_changed(uint8_t *fmts, size_t fmts_count, void *task_info)
+{
+ send_typed_proto_msg(
+ fmts, fmts_count, ws_out_clpbrd_changed, task_info);
+}
+
+static void
+clipboard_send_data(uint8_t *buf, size_t buf_size, void *task_info)
+{
+ send_typed_proto_msg(buf, buf_size, ws_out_clpbrd_data, task_info);
+}
+
+static void
+clipboard_request_data(uint8_t data_fmt, void *task_info)
+{
+ send_typed_proto_msg(
+ &data_fmt, 1, ws_out_clpbrd_request_data, task_info);
+}
+
+static void
+ft_request(const wrdp_backend_ft_file_request *req, void *task_info)
+{
+ send_typed_proto_msg((uint8_t *)req,
+ sizeof(wrdp_backend_ft_file_request), ws_out_ft_request, task_info);
+}
+
+static void
+ft_send_chunk(
+ const wrdp_backend_ft_chunk *chunk, uint8_t *data, void *task_info)
+{
+ size_t buf_size = sizeof(wrdp_backend_ft_chunk) + chunk->size;
+ uint8_t *buf = malloc(buf_size);
+ if (!buf)
+ {
+ perror("malloc");
+ return;
+ }
+ memcpy(buf, chunk, sizeof(wrdp_backend_ft_chunk));
+ memcpy(buf + sizeof(wrdp_backend_ft_chunk), data, chunk->size);
+ send_typed_proto_msg(buf, buf_size, ws_out_ft_chunk, task_info);
+ free(buf);
+}
+
+static void
+ft_finish(const wrdp_backend_ft_status *status, void *task_info)
+{
+ send_typed_proto_msg((uint8_t *)status, sizeof(wrdp_backend_ft_status),
+ ws_out_ft_finish, task_info);
+}
+
+static void *
+get_libev_loop(void *_task_info)
+{
+ task_info *t = _task_info;
+ wrdp_thpool_task *pt = t->wrdp_thpool_task;
+ return pt->thread->ev_th_loop;
+}
+
+static void
+task_finished(bool success, void *_task_info)
+{
+ /* TODO: handle task failure */
+ destroy_task(_task_info);
+}
+
+static void
+reset_idle(void *_task_info)
+{
+ task_info *t = _task_info;
+ t->idle_time = 0;
+}
+
+bool
+init_exports()
+{
+ g_globals.exports = calloc(1, sizeof(wrdp_core_exports));
+ if (!g_globals.exports)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ g_globals.exports->api_core = calloc(1, sizeof(wrdp_core_cb_core));
+ if (!g_globals.exports->api_core)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ g_globals.exports->api_msgs = calloc(1, sizeof(wrdp_core_cb_msgs));
+ if (!g_globals.exports->api_msgs)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ g_globals.exports->api_paint = calloc(1, sizeof(wrdp_core_cb_paint));
+ if (!g_globals.exports->api_paint)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ g_globals.exports->api_clipboard
+ = calloc(1, sizeof(wrdp_core_cb_clipboard));
+ if (!g_globals.exports->api_clipboard)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ g_globals.exports->api_filetransfers
+ = calloc(1, sizeof(wrdp_core_cb_filetransfer));
+ if (!g_globals.exports->api_filetransfers)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ g_globals.exports->api_utils = calloc(1, sizeof(wrdp_core_cb_utils));
+ if (!g_globals.exports->api_utils)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+
+ g_globals.exports->api_paint->send_begin_paint = send_begin_paint;
+ g_globals.exports->api_paint->send_bitmap = send_bitmap;
+ g_globals.exports->api_paint->send_end_paint = send_end_paint;
+ g_globals.exports->api_paint->send_multi_opaque_rect
+ = send_multi_opaque_rect;
+ g_globals.exports->api_paint->send_opaque_rect_order
+ = send_opaque_rect_order;
+ g_globals.exports->api_paint->send_pat_blt = send_pat_blt;
+ g_globals.exports->api_paint->send_ptr_free = send_ptr_free;
+ g_globals.exports->api_paint->send_ptr_new = send_ptr_new;
+ g_globals.exports->api_paint->send_ptr_set = send_ptr_set;
+ g_globals.exports->api_paint->send_ptr_set_default
+ = send_ptr_set_default;
+ g_globals.exports->api_paint->send_ptr_set_null = send_ptr_set_null;
+ g_globals.exports->api_paint->send_scr_blt = send_scr_blt;
+ g_globals.exports->api_paint->send_set_bounds = send_set_bounds;
+
+ g_globals.exports->api_msgs->send_text_msg = send_text_msg;
+ g_globals.exports->api_msgs->send_text_info_msg = send_text_info_msg;
+ g_globals.exports->api_msgs->send_text_warning_msg
+ = send_text_warning_msg;
+ g_globals.exports->api_msgs->send_text_debug_msg = send_text_debug_msg;
+ g_globals.exports->api_msgs->send_ctl_msg = send_ctl_msg;
+ g_globals.exports->api_msgs->send_error_msg = send_error_msg;
+ g_globals.exports->api_msgs->send_termination_msg
+ = send_termination_msg;
+ g_globals.exports->api_core->get_libev_loop = get_libev_loop;
+ g_globals.exports->api_core->task_finished = task_finished;
+ g_globals.exports->api_core->reset_idle = reset_idle;
+
+ g_globals.exports->api_clipboard->send_data = clipboard_send_data;
+ g_globals.exports->api_clipboard->clipboard_changed = clipboard_changed;
+ g_globals.exports->api_clipboard->request_data = clipboard_request_data;
+
+ g_globals.exports->api_filetransfers->ft_finish = ft_finish;
+ g_globals.exports->api_filetransfers->ft_request = ft_request;
+ g_globals.exports->api_filetransfers->ft_send_chunk = ft_send_chunk;
+
+ g_globals.exports->api_utils->hex_print = hex_print;
+ g_globals.exports->api_utils->log_msg = log_msg;
+ g_globals.exports->api_utils->log_msg_ex = log_msg_ex;
+ return true;
+}
diff --git a/src/core/exports.h b/src/core/exports.h
new file mode 100644
index 0000000..fe23c4c
--- /dev/null
+++ b/src/core/exports.h
@@ -0,0 +1,17 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+void send_error_msg(const char *msg, void *task_info);
+void send_text_msg(const char *msg, void *task_info);
+void send_text_info_msg(const char *msg, void *task_info);
+void send_text_warning_msg(const char *msg, void *task_info);
+void send_text_debug_msg(const char *msg, void *task_info);
+void send_ctl_msg(const char *msg, void *task_info);
+void send_termination_msg(void *task_info);
+
+bool init_exports();
diff --git a/src/core/globals.h b/src/core/globals.h
new file mode 100644
index 0000000..5d12d64
--- /dev/null
+++ b/src/core/globals.h
@@ -0,0 +1,82 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include <limits.h>
+#include <stdbool.h>
+
+#include <sys/queue.h>
+
+#include "webrdp_core_api.h"
+#include "webrdp_module_api.h"
+#include "wrdp_thpool.h"
+
+#define PROG_NAME "webrdp"
+
+#include "ws_session.h"
+#include "task.h"
+
+typedef struct
+{
+ /* set worker thread count and max tasks per thread for thread pool */
+ int32_t thread_count, tasks_per_thread;
+
+ /* set core log level to one of "wrdp_log_level_e" */
+ uint8_t log_level;
+
+ /* set true to run in background (daemon mode) */
+ bool daemon;
+
+ /* global session settings defaults */
+ ws_session_settings ws_session_defaults;
+
+ /* websocket server settings:
+ */
+
+ /* port for http(s)/ws server */
+ int32_t ws_port, ctl_port;
+
+ /* unix socket path */
+ char *ws_socket_path, *ctl_socket_path;
+
+ /* url of external auth server */
+ char *auth_server_url;
+
+ /* 512bit key used for hmac
+ * used additional 2 bytes in buffer
+ * required for base64decode
+ */
+ char secret_key_verify[66], secret_key_sign[66];
+
+ /* paths to cafile and/or cafile dir */
+ char *ctl_ssl_cafile, *ctl_ssl_capath, *ctl_ssl_cert, *ctl_ssl_key;
+
+} global_settings;
+
+struct task_s
+{
+ task_info *task;
+ LIST_ENTRY(task_s) entries;
+};
+
+struct backend_s
+{
+ wrdp_backend_module *backend;
+ LIST_ENTRY(backend_s) entries;
+};
+
+typedef struct
+{
+ global_settings settings;
+ wrdp_thpool *thpool;
+ wrdp_core_exports *exports;
+ LIST_HEAD(backend_head_, backend_s) backends_head;
+} globals;
+
+extern globals g_globals;
+
+void shutdown_core();
diff --git a/src/core/include/.clang-format b/src/core/include/.clang-format
new file mode 120000
index 0000000..3260daf
--- /dev/null
+++ b/src/core/include/.clang-format
@@ -0,0 +1 @@
+../../../.clang-format \ No newline at end of file
diff --git a/src/core/include/webrdp_api_shared_structures.h b/src/core/include/webrdp_api_shared_structures.h
new file mode 100644
index 0000000..c5ffbef
--- /dev/null
+++ b/src/core/include/webrdp_api_shared_structures.h
@@ -0,0 +1,222 @@
+#pragma once
+
+/* generic structures */
+
+typedef struct
+{
+ char *name, *value;
+} backend_setting_str;
+
+typedef struct
+{
+ char *name;
+ int64_t value;
+} backend_setting_int;
+
+typedef enum
+{
+ ws_keycomb_ctrlaltdel_press,
+ ws_keycomb_alttab_press,
+ ws_keycomb_alttab_release,
+ ws_keycomb_unused
+} ws_input_keycomb;
+
+/* user input structures */
+
+typedef struct __attribute__((__packed__))
+{
+ uint32_t x, y, flags;
+} ws_input_mouse;
+
+typedef struct __attribute__((__packed__))
+{
+ uint32_t code;
+ bool down;
+} ws_input_kupdown;
+
+typedef struct __attribute__((__packed__))
+{
+ uint32_t code, shiftstate;
+} ws_input_kpress;
+
+typedef struct __attribute__((__packed__))
+{
+ const uint32_t *input;
+ size_t length;
+} ws_input_unicode;
+
+/* display output structures */
+
+typedef struct __attribute__((__packed__))
+{
+ uint32_t x; /* Destination X */
+ uint32_t y; /* Destination Y */
+ uint32_t width; /* Width */
+ uint32_t height; /* Height */
+ uint32_t dest_width; /* Destination Width */
+ uint32_t dest_height; /* Destination Height */
+ uint32_t bpp; /* Bits per Pixel */
+ uint32_t compressed; /* Flag: Compressed */
+ uint32_t size; /* DataSize */
+} wrdp_core_display_bmp;
+
+typedef struct __attribute__((__packed__))
+{
+ int32_t left;
+ int32_t top;
+ int32_t right;
+ int32_t bottom;
+} wrdp_core_display_bounds;
+
+typedef struct __attribute__((__packed__))
+{
+ int32_t nLeftRect;
+ int32_t nTopRect;
+ int32_t nWidth;
+ int32_t nHeight;
+ uint32_t color;
+} wrdp_core_display_opaque_rect_order;
+
+typedef struct __attribute__((__packed__))
+{
+ int32_t x;
+ int32_t y;
+ int32_t w;
+ int32_t h;
+ uint32_t fg;
+ uint32_t rop;
+} wrdp_core_display_patblt_order;
+
+typedef struct __attribute__((__packed__))
+{
+ int32_t left;
+ int32_t top;
+ int32_t width;
+ int32_t height;
+} wrdp_core_display_delta_rect;
+
+typedef struct __attribute__((__packed__))
+{
+ uint32_t color;
+ uint32_t num_rect;
+ wrdp_core_display_delta_rect *rects;
+} wrdp_core_display_m_opaque_rect;
+
+typedef struct __attribute__((__packed__))
+{
+ uint32_t rop;
+ int32_t x;
+ int32_t y;
+ int32_t w;
+ int32_t h;
+ int32_t sx;
+ int32_t sy;
+} wrdp_core_display_scr_blt;
+
+typedef struct __attribute__((__packed__))
+{
+ uint8_t resL; /* 2 bytes reserved must always be 0 */
+ uint8_t resH;
+ uint8_t imgTypeL; /* 2 bytes image type. 1 = ICO, 2 = CUR */
+ uint8_t imgTypeH;
+ uint8_t nbImgL; /* 2 bytes number of images */
+ uint8_t nbImgH;
+ uint8_t width; /* 1 byte image width in pixels */
+ uint8_t height; /* 1 byte image height in pixels */
+ uint8_t nbColors; /* 1 bytenumber of colors. 0 if not using a color
+ pallete */
+ uint8_t res; /* 1 byte reserved, should be 0 */
+ uint8_t
+ xPosL; /* 2 bytes of hot spot x (for ICO, color planes, 0 or 1) */
+ uint8_t xPosH;
+ uint8_t yPosL; /* 2 bytes of hot spot y (for ICO bits per pixel) */
+ uint8_t yPosH;
+ uint8_t sizeLL; /* 4 bytes of image size */
+ uint8_t sizeLH;
+ uint8_t sizeHL;
+ uint8_t sizeHH;
+ uint8_t offsetLL; /* 4 bytes of offset of the data */
+ uint8_t offsetLH;
+ uint8_t offsetHL;
+ uint8_t offsetHH;
+ uint8_t *data;
+} wrdp_core_display_cursor;
+
+typedef struct
+{
+ uint32_t id;
+ uint32_t hx;
+ uint32_t hy;
+ uint32_t data_size;
+ wrdp_core_display_cursor *cur;
+} wrdp_core_display_cursor_info;
+
+/* clipboard structures */
+
+typedef enum
+{
+ clip_format_unsupported = -1,
+ clip_format_raw,
+ clip_format_text,
+ clip_format_unicode,
+ clip_format_file_list
+} wrdp_enum_clip_format;
+
+typedef struct __attribute__((__packed__))
+{
+ /* DRAFT! */
+ wrdp_enum_clip_format format;
+} wrdp_backend_clipbrd_data_request;
+
+typedef struct __attribute__((__packed__))
+{
+ /* DRAFT! */
+ uint8_t count, *formats;
+} wrdp_backend_clipbrd_fmts;
+
+typedef struct __attribute__((__packed__))
+{
+ /* DRAFT! */
+ /* TODO: */
+ uint32_t size;
+ uint8_t *data;
+} wrdp_backend_clipbrd_data;
+
+/* filetransfer structures */
+
+typedef struct __attribute__((__packed__))
+{
+ uint16_t filename_len;
+ uint32_t file_id;
+ uint64_t file_size;
+} wrdp_backend_ft_list_entry;
+
+typedef struct __attribute__((__packed__))
+{
+ uint32_t file_id;
+ uint64_t req_size;
+ uint64_t file_offset;
+} wrdp_backend_ft_file_request;
+
+typedef enum
+{
+ ft_status_success = 0,
+ ft_status_failure,
+ ft_status_already_runing
+} wrdp_backend_enum_ft_init_status;
+
+typedef struct __attribute__((__packed__))
+{
+ /* DRAFT! */
+ /* TODO: */
+ uint32_t file_id, transfer_id;
+ uint8_t status;
+} wrdp_backend_ft_status;
+
+typedef struct __attribute__((__packed__))
+{
+ /* DRAFT! */
+ /* TODO: */
+ uint32_t transfer_id;
+ uint32_t size;
+} wrdp_backend_ft_chunk;
diff --git a/src/core/include/webrdp_api_utils.h b/src/core/include/webrdp_api_utils.h
new file mode 100644
index 0000000..170ca2b
--- /dev/null
+++ b/src/core/include/webrdp_api_utils.h
@@ -0,0 +1,43 @@
+#pragma once
+
+typedef enum
+{
+ wrdp_log_level_error,
+ wrdp_log_level_warning,
+ wrdp_log_level_info,
+ wrdp_log_level_debug,
+ wrdp_log_level_trace
+} wrdp_log_level_e;
+
+static const uint16_t wrdp_log_flag_binary = 0x0001;
+
+typedef struct _log_msg_info
+{
+ /* message buffer:
+ * containing message which will be displayed
+ *
+ * mandatory parameter */
+ const uint8_t *buf;
+
+ /* message size:
+ * size of message in buiffer which must be displayed
+ *
+ * optional in case of null terminated string, mandatory otherwise */
+ size_t buf_size;
+
+ /* log level for this message
+ *
+ * optional, default is 'wrdp_log_level_error' */
+ wrdp_log_level_e level;
+
+ /* message flags:
+ * currently supported flags is:
+ * 'wrdp_log_flag_binary': print message as hex data
+ * optional, default is plain text */
+ uint16_t flags;
+
+ /* pointers to various data structures which may be usefule
+ * during debugging
+ * optional */
+ void *task_info, *wrdp_thpool_task, *ws_session;
+} log_msg_info;
diff --git a/src/core/include/webrdp_core_api.h b/src/core/include/webrdp_core_api.h
new file mode 100644
index 0000000..36ef948
--- /dev/null
+++ b/src/core/include/webrdp_core_api.h
@@ -0,0 +1,185 @@
+#pragma once
+/* API functions exported by core which can be used in backend modules will
+ * be defined here.
+ */
+
+#include "webrdp_api_shared_structures.h"
+#include "webrdp_api_utils.h"
+
+typedef struct
+{
+ /* Send text message */
+ void (*send_text_msg)(const char *msg, void *task_info);
+
+ /* Send text info message */
+ void (*send_text_info_msg)(const char *msg, void *task_info);
+
+ /* Send text warning message */
+ void (*send_text_warning_msg)(const char *msg, void *task_info);
+
+ /* Send text debug message */
+ void (*send_text_debug_msg)(const char *msg, void *task_info);
+
+ /* Send error message, this also causing disconnect
+ * TODO: do not send termination message on errors, use this instead
+ * OR disable automatic disconnect on errors
+ */
+ void (*send_error_msg)(const char *msg, void *task_info);
+
+ /* Send control message to js client
+ * TODO: do we need to provide it for backend modules ?
+ */
+ void (*send_ctl_msg)(const char *msg, void *task_info);
+
+ /* Send session termination message on quit */
+ void (*send_termination_msg)(void *task_info);
+} wrdp_core_cb_msgs;
+
+typedef struct
+{
+ void (*send_begin_paint)(void *task_info);
+ void (*send_end_paint)(void *task_info);
+
+ /* Single bitmap */
+ void (*send_bitmap)(const wrdp_core_display_bmp *bmp, uint8_t *bmp_data,
+ void *task_info);
+
+ /* Primary: OPAQUE_RECT_ORDER */
+ void (*send_opaque_rect_order)(
+ const wrdp_core_display_opaque_rect_order *oro, void *task_info);
+
+ /* SetBounds */
+ void (*send_set_bounds)(
+ const wrdp_core_display_bounds *bounds, void *task_info);
+
+ /* PatBlt */
+ void (*send_pat_blt)(
+ const wrdp_core_display_patblt_order *patblt, void *task_info);
+
+ /* Multi Opaque rect */
+ void (*send_multi_opaque_rect)(
+ const wrdp_core_display_m_opaque_rect *mrect, void *task_info);
+
+ /* ScrBlt */
+ void (*send_scr_blt)(
+ const wrdp_core_display_scr_blt *scr_blt, void *task_info);
+
+ /* PTR_NEW
+ * uint32_t id, xhot, yhot
+ */
+ void (*send_ptr_new)(
+ const wrdp_core_display_cursor_info *ptr, void *task_info);
+
+ /* PTR_FREE
+ * uint32_t id
+ */
+ void (*send_ptr_free)(uint32_t ptr_id, void *task_info);
+
+ /* PTR_SET
+ * uint32_t id
+ */
+ void (*send_ptr_set)(uint32_t ptr_id, void *task_info);
+
+ /* PTR_SETNULL
+ */
+ void (*send_ptr_set_null)(void *task_info);
+
+ /* PTR_SETDEFAULT
+ */
+ void (*send_ptr_set_default)(void *task_info);
+
+} wrdp_core_cb_paint;
+
+typedef struct
+{
+ /* DRAFT! */
+ /* TODO: */
+
+ /*
+ * uint8_t* wrdp_enum_clip_format array
+ */
+ void (*clipboard_changed)(
+ uint8_t *fmts, size_t fmts_count, void *task_info);
+
+ /*
+ * clipboard data array
+ */
+ void (*send_data)(uint8_t *buf, size_t buf_size, void *task_info);
+
+ /*
+ * request clipboard data from client
+ */
+ void (*request_data)(uint8_t data_fmt, void *task_info);
+
+} wrdp_core_cb_clipboard;
+
+typedef struct
+{
+ /* DRAFT! */
+
+ /*
+ * TODO:
+ */
+
+ void (*ft_request)(
+ const wrdp_backend_ft_file_request *req, void *task_info);
+
+ /*
+ * TODO:
+ */
+ void (*ft_send_chunk)(
+ const wrdp_backend_ft_chunk *chunk, uint8_t *data, void *task_info);
+
+ /*
+ * TODO:
+ */
+ void (*ft_finish)(
+ const wrdp_backend_ft_status *status, void *task_info);
+
+} wrdp_core_cb_filetransfer;
+
+typedef struct
+{
+ /* access to variables allocated by core: */
+
+ /* get libev ev_loop* for calling thread */
+ void *(*get_libev_loop)(void *task_info);
+
+ /* task runtime state functions: */
+
+ /* inform core about task is finished */
+ void (*task_finished)(bool success, void *task_info);
+
+ /* reset task idle time to 0 */
+ void (*reset_idle)();
+} wrdp_core_cb_core;
+
+typedef struct
+{
+ /* print raw data buffer to stdout */
+ void (*hex_print)(const uint8_t *buf, size_t buf_len);
+
+ /* print log message, currently only stdout is supported */
+ void (*log_msg)(const uint8_t *buf, size_t buf_len,
+ wrdp_log_level_e type, uint16_t flags);
+ /* print log message extended, currently only stdout is supported */
+ void (*log_msg_ex)(log_msg_info *i);
+} wrdp_core_cb_utils;
+
+typedef struct
+{
+ /* client<>server protocol functions: */
+ wrdp_core_cb_msgs *api_msgs;
+
+ /* Painting functions: */
+ wrdp_core_cb_paint *api_paint;
+
+ wrdp_core_cb_core *api_core;
+
+ wrdp_core_cb_clipboard *api_clipboard;
+
+ wrdp_core_cb_filetransfer *api_filetransfers;
+
+ wrdp_core_cb_utils *api_utils;
+
+} wrdp_core_exports;
diff --git a/src/core/include/webrdp_module_api.h b/src/core/include/webrdp_module_api.h
new file mode 100644
index 0000000..7f2d635
--- /dev/null
+++ b/src/core/include/webrdp_module_api.h
@@ -0,0 +1,109 @@
+#pragma once
+
+/* Short api usage brief...
+ * each backend module must export:
+ * 1. bool <backend_name>_create(wrdp_core_exports*, wrdp_backend_module*)
+ * which initialize passed wrdp_backend_module* instance
+ * with all required callbacks.
+ * wrdp_core_exports* ptr should be stored by backend,
+ * for example in "internals" structure.
+ * return "true" on success
+ */
+
+#include <stdint.h>
+
+#include "webrdp_api_shared_structures.h"
+
+//#include "webrdp_core_api.h"
+
+/* API used by backend modules defined here */
+
+typedef struct
+{
+ /* Init function exported by backend, this function should fully
+ * initialize backend.
+ * retrun false on failure
+ * required. */
+ bool (*init)(void *backend_internals);
+
+ /* Destroy backend function should deinitialize backend
+ * and free used memory
+ * required. */
+ void (*destroy)(void *backend_internals);
+
+ /* Pass task_info internal structure ptr to the backend.
+ * must be stored and suplied to core on need
+ * required. */
+ void (*set_task_info)(void *task_info, void *internals);
+
+ /* Backend settings related functions
+ * return false on failure.
+ * required. */
+ bool (*set_setting_str)(backend_setting_str *setting, void *internals);
+ bool (*set_setting_int)(backend_setting_int *setting, void *internals);
+} wrdp_backend_cb_module;
+
+typedef struct
+{
+ /* Backend ws-protocol handlers callbacks
+ * return false on failure
+ * at least one of the following is required*/
+ bool (*mouse)(ws_input_mouse input, void *internals);
+ bool (*kupdown)(ws_input_kupdown input, void *internals);
+ bool (*kpress)(ws_input_kpress input, void *internals);
+ bool (*kcomb)(ws_input_keycomb input, void *internals);
+ bool (*unicode)(ws_input_unicode input, void *internals);
+} wrdp_backend_cb_input;
+
+typedef struct
+{
+ /* DRAFT! */
+ /* TODO: */
+
+ bool (*request_data)(
+ const wrdp_backend_clipbrd_data_request *, void *backend_internals);
+ bool (*data_changed)(
+ const wrdp_backend_clipbrd_fmts *, void *backend_internals);
+ bool (*send_data)(
+ const wrdp_backend_clipbrd_data *, void *backend_internals);
+
+} wrdp_backend_cb_clipboard;
+
+typedef struct
+{
+ /* DRAFT! */
+ /* TODO: */
+
+ bool (*request)(
+ const wrdp_backend_ft_file_request *, void *backend_internals);
+ bool (*chunk)(const wrdp_backend_ft_chunk *, const uint8_t *data,
+ void *backend_internals);
+ bool (*finish)(const wrdp_backend_ft_status *, void *backend_internals);
+} wrdp_backend_cb_filetransfer;
+
+typedef struct
+{
+
+ /* Backend internal data, does not touched by core. */
+ void *backend_internals;
+
+ /* pointer to wrdp_thpool_task* used by some internals */
+ void *wrdp_thpool_task;
+
+ /* Variable indicate what task destruction is in progress,
+ * task should not use any of core exports anymore */
+ bool stopped;
+
+ /* Information about task used by core,
+ * must be passed to functions which require it */
+ void *task_info;
+
+ /* ws_session list */
+ void *sessions_list_head;
+
+ wrdp_backend_cb_module *callbacks_module;
+ wrdp_backend_cb_input *callbacks_input;
+ wrdp_backend_cb_clipboard *callbacks_clipbrd;
+ wrdp_backend_cb_filetransfer *callbacks_ft;
+
+} wrdp_backend_module;
diff --git a/src/core/json_helpers.c b/src/core/json_helpers.c
new file mode 100644
index 0000000..dfbef34
--- /dev/null
+++ b/src/core/json_helpers.c
@@ -0,0 +1,275 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <json.h>
+
+int64_t
+json_option_extract_int64(struct json_object_element_s *json_option)
+{
+ switch (json_option->value->type)
+ {
+ case json_type_null:
+ {
+ return 0;
+ }
+ break;
+ case json_type_false:
+ {
+ return 0;
+ }
+ break;
+ case json_type_true:
+ {
+ return 1;
+ }
+ break;
+ case json_type_number:
+ {
+ struct json_number_s *jsopt
+ = (struct json_number_s *)
+ json_option->value->payload;
+ char *out = malloc(jsopt->number_size + 1);
+ if (!out)
+ {
+ perror("malloc");
+ return 0;
+ }
+ int64_t ret = false;
+ strncpy(out, jsopt->number, jsopt->number_size);
+ out[jsopt->number_size] = 0;
+ ret = atoll(out);
+ free(out);
+ return ret;
+ }
+ break;
+ case json_type_string:
+ {
+ struct json_string_s *jsopt
+ = (struct json_string_s *)
+ json_option->value->payload;
+ char *out = malloc(jsopt->string_size + 1);
+ if (!out)
+ {
+ perror("malloc");
+ return 0;
+ }
+ int64_t ret = false;
+ strncpy(out, jsopt->string, jsopt->string_size);
+ out[jsopt->string_size] = 0;
+ ret = atoll(out);
+ free(out);
+ return ret;
+ }
+ break;
+ case json_type_object:
+ {
+ return 1;
+ }
+ break;
+ default:
+ {
+ return 0;
+ }
+ break;
+ }
+ return 0;
+}
+
+bool
+json_option_extract_bool(struct json_object_element_s *json_option)
+{
+ switch (json_option->value->type)
+ {
+ case json_type_null:
+ {
+ return false;
+ }
+ break;
+ case json_type_false:
+ {
+ return false;
+ }
+ break;
+ case json_type_true:
+ {
+ return true;
+ }
+ break;
+ case json_type_number:
+ {
+ struct json_number_s *jsopt
+ = (struct json_number_s *)
+ json_option->value->payload;
+ char *out = malloc(jsopt->number_size + 1);
+ if (!out)
+ {
+ perror("malloc");
+ return 0;
+ }
+ bool ret = false;
+ strncpy(out, jsopt->number, jsopt->number_size);
+ out[jsopt->number_size] = 0;
+ ret = atoi(out);
+ free(out);
+ return ret;
+ }
+ break;
+ case json_type_string:
+ {
+ struct json_string_s *jsopt
+ = (struct json_string_s *)
+ json_option->value->payload;
+ char *out = malloc(jsopt->string_size + 1);
+ if (!out)
+ {
+ perror("malloc");
+ return 0;
+ }
+ bool ret = false;
+ strncpy(out, jsopt->string, jsopt->string_size);
+ out[jsopt->string_size] = 0;
+ ret = atoi(out);
+ free(out);
+ return ret;
+ }
+ break;
+ case json_type_object:
+ {
+ return true;
+ }
+ break;
+ default:
+ {
+ return false;
+ }
+ break;
+ }
+ return false;
+}
+
+char *
+json_option_extract_string(
+ struct json_object_element_s *json_option, char *_out)
+{
+ char *out = 0;
+ if (_out)
+ {
+ out = _out;
+ }
+ switch (json_option->value->type)
+ {
+ case json_type_null:
+ {
+ if (!out)
+ {
+ out = malloc(strlen("null") + 1);
+ if (!out)
+ {
+ perror("malloc");
+ return 0;
+ }
+ }
+ strcpy(out, "null");
+ return out;
+ }
+ break;
+ case json_type_false:
+ {
+ if (!out)
+ {
+ out = malloc(strlen("false") + 1);
+ if (!out)
+ {
+ perror("malloc");
+ return 0;
+ }
+ }
+ strcpy(out, "false");
+ return out;
+ }
+ break;
+ case json_type_true:
+ {
+ if (!out)
+ {
+ out = malloc(strlen("true") + 1);
+ if (!out)
+ {
+ perror("malloc");
+ return 0;
+ }
+ }
+ strcpy(out, "true");
+ return out;
+ }
+ break;
+ case json_type_number:
+ {
+ struct json_number_s *jsopt
+ = (struct json_number_s *)
+ json_option->value->payload;
+ if (!out)
+ {
+ out = malloc(jsopt->number_size + 1);
+ if (!out)
+ {
+ perror("malloc");
+ return 0;
+ }
+ }
+ strncpy(out, jsopt->number, jsopt->number_size);
+ out[jsopt->number_size] = 0;
+ return out;
+ }
+ break;
+ case json_type_string:
+ {
+ struct json_string_s *jsopt
+ = (struct json_string_s *)
+ json_option->value->payload;
+ if (!out)
+ {
+ out = malloc(jsopt->string_size + 1);
+ if (!out)
+ {
+ perror("malloc");
+ return 0;
+ }
+ }
+ strncpy(out, jsopt->string, jsopt->string_size);
+ out[jsopt->string_size] = 0;
+ return out;
+ }
+ break;
+ case json_type_object:
+ {
+ if (!out)
+ {
+ out = malloc(strlen("object") + 1);
+ if (!out)
+ {
+ perror("malloc");
+ return 0;
+ }
+ }
+ strcpy(out, "object");
+ return out;
+ }
+ break;
+ default:
+ {
+ return out;
+ }
+ break;
+ }
+ return out;
+}
diff --git a/src/core/json_helpers.h b/src/core/json_helpers.h
new file mode 100644
index 0000000..953e56f
--- /dev/null
+++ b/src/core/json_helpers.h
@@ -0,0 +1,15 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+int64_t json_option_extract_int64(struct json_object_element_s *json_option);
+
+bool json_option_extract_bool(struct json_object_element_s *json_option);
+
+/* if out buffer is 0, caller must free memory */
+char *json_option_extract_string(
+ struct json_object_element_s *json_option, char *out);
diff --git a/src/core/log.c b/src/core/log.c
new file mode 100644
index 0000000..5671d90
--- /dev/null
+++ b/src/core/log.c
@@ -0,0 +1,164 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+
+#include "webrdp_core_api.h"
+
+#include "globals.h"
+
+#include "log.h"
+#include "utilities.h"
+
+#include "ws_session.h"
+#include "wrdp_thpool.h"
+
+/*
+ * sample:
+ * [D 190318 20:25:24 main.c:666]
+ */
+
+static void
+print_time()
+{
+ time_t t = time(0);
+ struct tm *ptm = localtime(&t);
+
+ if (!ptm)
+ printf("%ld", t);
+ else
+ printf("%02d-%02d %02d:%02d:%02d", ptm->tm_mon, ptm->tm_mday,
+ ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
+}
+
+static void
+binary_print(const uint8_t *buf, size_t buf_len)
+{
+ hex_print(buf, buf_len);
+}
+
+static void
+text_print(const uint8_t *buf, size_t buf_len)
+{
+ printf("%.*s", (int)buf_len, buf);
+}
+
+static void
+print_msg_level(wrdp_log_level_e *level)
+{
+ char l;
+ switch (*level)
+ {
+ case wrdp_log_level_debug:
+ {
+ l = 'D';
+ }
+ break;
+ case wrdp_log_level_warning:
+ {
+ l = 'W';
+ }
+ break;
+ case wrdp_log_level_error:
+ {
+ l = 'E';
+ }
+ break;
+ case wrdp_log_level_trace:
+ {
+ l = 'T';
+ }
+ break;
+ case wrdp_log_level_info:
+ default:
+ {
+ l = 'I';
+ }
+ break;
+ }
+ printf("%c", l);
+}
+
+static void
+print_sender_info(log_msg_info *msg_info)
+{
+ task_info *info = msg_info->task_info;
+ wrdp_thpool_task *t = msg_info->wrdp_thpool_task;
+ ws_session *s = msg_info->ws_session;
+ if (!t)
+ {
+ if (info && info->wrdp_thpool_task)
+ t = info->wrdp_thpool_task;
+ else if (s && s->wrdp_thpool_task)
+ t = s->wrdp_thpool_task;
+ else if (s && s->task_info)
+ {
+ task_info *i = s->task_info;
+ if (i->wrdp_thpool_task)
+ t = i->wrdp_thpool_task;
+ }
+ }
+ if (!info)
+ {
+ if (s && s->task_info)
+ info = s->task_info;
+ }
+ if (t)
+ printf(" task: %p", t);
+ if (info)
+ printf(" task_info: %p", info);
+ if (s)
+ printf(" ws_session: %p", s);
+}
+
+static void
+format_prefix(log_msg_info *msg_info)
+{
+ printf("[");
+ print_msg_level(&(msg_info->level));
+ printf(" ");
+ print_time();
+ print_sender_info(msg_info);
+ printf("] ");
+}
+
+void
+log_msg(
+ const uint8_t *buf, size_t buf_len, wrdp_log_level_e level, uint16_t flags)
+{
+ log_msg_info i = {0};
+ i.buf = buf;
+ i.buf_size = buf_len;
+ i.flags = flags;
+ i.level = level;
+ log_msg_ex(&i);
+}
+
+void
+log_msg_ex(log_msg_info *msg_info)
+{
+ size_t buf_size;
+ if (msg_info->level > g_globals.settings.log_level)
+ {
+ return;
+ }
+ buf_size = msg_info->buf_size;
+ if (!buf_size)
+ buf_size = strlen((const char *)msg_info->buf);
+ format_prefix(msg_info);
+ if (msg_info->flags & wrdp_log_flag_binary)
+ {
+ binary_print(msg_info->buf, buf_size);
+ }
+ else
+ {
+ text_print(msg_info->buf, buf_size);
+ }
+ printf("\n");
+}
diff --git a/src/core/log.h b/src/core/log.h
new file mode 100644
index 0000000..e8dde39
--- /dev/null
+++ b/src/core/log.h
@@ -0,0 +1,12 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+void log_msg(
+ const uint8_t *buf, size_t buf_len, wrdp_log_level_e type, uint16_t flags);
+
+void log_msg_ex(log_msg_info *msg_info);
diff --git a/src/core/main.c b/src/core/main.c
new file mode 100644
index 0000000..6572938
--- /dev/null
+++ b/src/core/main.c
@@ -0,0 +1,220 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <ev.h>
+
+#include <stdio.h>
+#include <execinfo.h>
+#include <libunwind.h>
+
+#include "cmdline.h"
+#include "config_file.h"
+#include "ev_loop.h"
+#include "exports.h"
+#include "globals.h"
+#include "thread_impl.h"
+#include "utilities.h"
+#include "wrdp_thpool.h"
+#include "ws_session.h"
+#include "remote_control.h"
+#include "curl_helpers.h"
+#include "thread_sync.h"
+
+globals g_globals;
+
+static void
+custom_thread_init(void *user_pool_data, wrdp_thpool_thread *t)
+{
+ per_thread_curl_init(user_pool_data, t);
+}
+
+static void
+init_internals()
+{
+ if (g_globals.settings.thread_count <= 0)
+ {
+ g_globals.settings.thread_count = sysconf(_SC_NPROCESSORS_ONLN);
+ }
+ if (g_globals.settings.thread_count <= 0)
+ {
+ g_globals.settings.thread_count = 2;
+ }
+ if (!init_exports())
+ {
+ printf("error: failed to initialize core exports\n");
+ exit(EXIT_FAILURE);
+ }
+ /* random seed */
+ srand(time(0) + 13);
+
+ LIST_INIT(&(g_globals.backends_head));
+
+ void *c = init_curl_global();
+ g_globals.thpool = wrdp_thpool_create(g_globals.settings.thread_count,
+ g_globals.settings.tasks_per_thread, custom_thread_init, 0, 0, 0,
+ pool_message_handler, thread_message_handler, EV_DEFAULT, c);
+}
+
+static void
+settings_load_default()
+{
+ memset(&g_globals, 0, sizeof(globals));
+ memset(&g_globals.settings, 0, sizeof(global_settings));
+ g_globals.settings.secret_key_verify[0] = 0;
+ g_globals.settings.secret_key_sign[0] = 0;
+ g_globals.settings.thread_count = -1;
+}
+
+static void
+settings_validate()
+{
+ bool failure = false;
+ if (!g_globals.settings.ws_port)
+ g_globals.settings.ws_port = 8080;
+ if (!g_globals.settings.ctl_port)
+ g_globals.settings.ctl_port = 13666;
+ if (!g_globals.settings.tasks_per_thread)
+ g_globals.settings.tasks_per_thread = 1024;
+ if (!g_globals.settings.secret_key_verify[0])
+ {
+ printf("\"secret_key_verify\" must be set\n");
+ failure = true;
+ }
+ if (!g_globals.settings.secret_key_sign[0])
+ {
+ strcpy(g_globals.settings.secret_key_sign,
+ g_globals.settings.secret_key_verify);
+ }
+ if (!g_globals.settings.ctl_ssl_cert)
+ {
+ printf("\"ctl_ssl_cert\" must be set\n");
+ failure = true;
+ }
+ if (!g_globals.settings.ctl_ssl_key)
+ {
+ printf("\"ctl_ssl_key\" must be set\n");
+ failure = true;
+ }
+ if (failure)
+ {
+ exit(EXIT_FAILURE);
+ }
+}
+
+void
+shutdown_core()
+{
+ free(g_globals.exports);
+}
+
+static void
+daemonize()
+{
+ pid_t p = fork();
+ if (p)
+ {
+ printf("child process forked, exiting parent\n");
+ exit(EXIT_SUCCESS);
+ }
+ /* TODO: */
+}
+
+/* https://stackoverflow.com/questions/77005/how-to-automatically
+ * -generate-a-stacktrace-when-my-program-crashes */
+/*static void
+sigsegv_handler(int sig) {
+ void *array[100];
+ size_t size;
+
+ // get void*'s for all entries on the stack
+ size = backtrace (array, 100);
+
+ // print out all the frames to stderr
+ fprintf(stderr, "Error: signal %d:\n", sig);
+ backtrace_symbols_fd (array, size, STDERR_FILENO);
+ exit (EXIT_FAILURE);
+} */
+
+#ifdef DEBUG
+/* https://github.com/cslarsen/libunwind-examples/blob/master/backtrace.cpp */
+static void
+sigsegv_handler(int sig)
+{
+ unw_cursor_t cursor;
+ unw_context_t context;
+
+ unw_getcontext(&context);
+ unw_init_local(&cursor, &context);
+
+ int n = 0;
+ while (unw_step(&cursor) > 0)
+ {
+ unw_word_t ip, sp, off;
+
+ unw_get_reg(&cursor, UNW_REG_IP, &ip);
+ unw_get_reg(&cursor, UNW_REG_SP, &sp);
+
+ char symbol[256] = {"<unknown>"};
+ char *name = symbol;
+
+ unw_get_proc_name(&cursor, symbol, sizeof(symbol), &off);
+
+ printf("#%-2d 0x%016" PRIxPTR " sp=0x%016" PRIxPTR
+ " %s + 0x%" PRIxPTR "\n",
+ ++n, ip, sp, name, off);
+
+ if (name != symbol)
+ free(name);
+ }
+ exit(EXIT_FAILURE);
+}
+
+/* this is required to generate gprof profile */
+static void
+sigusr2_handler(int sif)
+{
+ exit(EXIT_SUCCESS);
+}
+
+#endif
+
+int
+main(int argc, char **argv)
+{
+#ifdef DEBUG
+ signal(SIGSEGV, sigsegv_handler);
+ signal(SIGUSR2, sigusr2_handler);
+#endif
+ settings_load_default();
+ handle_cmdline_args(argc, argv);
+ if (g_globals.settings.daemon)
+ {
+ daemonize();
+ }
+ settings_validate();
+
+ init_internals();
+
+ {
+ int ws_tcp_sock = ws_server_init();
+ int ws_unix_sock = ws_server_init_unix();
+ int ctl_server_tcp_sock = ctl_server_init_tcp();
+ int ctl_server_unix_sock = ctl_server_init_unix();
+
+ //init_remote_control();
+
+ run_ev_loop_main(ws_tcp_sock, ws_unix_sock, ctl_server_tcp_sock,
+ ctl_server_unix_sock);
+ }
+
+ shutdown_core();
+ return 0;
+}
diff --git a/src/core/remote_control.c b/src/core/remote_control.c
new file mode 100644
index 0000000..90449d8
--- /dev/null
+++ b/src/core/remote_control.c
@@ -0,0 +1,435 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <openssl/err.h>
+#include <ev.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <json.h>
+
+#include "globals.h"
+#include "socket_helpers.h"
+#include "wrdp_thpool.h"
+#include "wrdp_thpool_internals.h"
+#include "remote_control.h"
+#include "json_helpers.h"
+
+#include "webrdp_core_api.h"
+#include "log.h"
+
+static int ctl_server_tcp_sock = -1, ctl_server_unix_sock = -1;
+static SSL_CTX *ctx;
+
+/* TODO: find out why this code affecting global certificate store */
+
+static void
+init_openssl()
+{
+ SSL_load_error_strings();
+ OpenSSL_add_ssl_algorithms();
+}
+
+static SSL_CTX *
+create_context()
+{
+ const SSL_METHOD *method = TLS_server_method();
+
+ ctx = SSL_CTX_new(method);
+ if (!ctx)
+ {
+ perror("Unable to create SSL context");
+ ERR_print_errors_fp(stderr);
+ exit(EXIT_FAILURE);
+ }
+
+ return ctx;
+}
+
+/* this is default behaviour, no need to override */
+/*static int
+verify_callback(int verify_ok, X509_STORE_CTX *s)
+{
+ return verify_ok;
+}*/
+
+static void
+configure_context(SSL_CTX *ctx)
+{
+ if (g_globals.settings.ctl_ssl_cafile
+ || g_globals.settings.ctl_ssl_capath)
+ {
+ if (!SSL_CTX_load_verify_locations(ctx,
+ g_globals.settings.ctl_ssl_cafile,
+ g_globals.settings.ctl_ssl_capath))
+ {
+ ERR_print_errors_fp(stderr);
+ exit(EXIT_FAILURE);
+ }
+ }
+ else
+ {
+ if (!SSL_CTX_set_default_verify_paths(ctx))
+ {
+ ERR_print_errors_fp(stderr);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ SSL_CTX_set_ecdh_auto(ctx, 1);
+
+ /*TODO: configurable file path*/
+ /* Set the key and cert */
+ if (SSL_CTX_use_certificate_file(
+ ctx, g_globals.settings.ctl_ssl_cert, SSL_FILETYPE_PEM)
+ <= 0)
+ {
+ ERR_print_errors_fp(stderr);
+ exit(EXIT_FAILURE);
+ }
+
+ if (SSL_CTX_use_PrivateKey_file(
+ ctx, g_globals.settings.ctl_ssl_key, SSL_FILETYPE_PEM)
+ <= 0)
+ {
+ ERR_print_errors_fp(stderr);
+ exit(EXIT_FAILURE);
+ }
+
+ SSL_CTX_set_verify(
+ ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, 0);
+}
+
+static void
+on_ctl_task_destroy(wrdp_thpool_task *task)
+{
+ ctl_task_info *info = task->userdata;
+ ctl_session *session = info->session;
+ {
+ const char *msg = "cleaning task slot";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_trace, 0);
+ }
+ ev_io_stop(task->thread->ev_th_loop, &(info->session->ev_con_fd_r));
+ if (ev_is_active(&(info->session->ev_con_fd_w)))
+ {
+ ev_io_stop(
+ task->thread->ev_th_loop, &(info->session->ev_con_fd_w));
+ }
+ if (session->ssl)
+ {
+ SSL_free(session->ssl);
+ }
+ close(info->session->connection_fd);
+ free(info->session);
+ free(info);
+}
+
+void
+ctl_destroy_task(wrdp_thpool_task *task)
+{
+ wrdp_thread_pool_destroy_task(task, on_ctl_task_destroy);
+}
+
+int
+ctl_server_init_tcp()
+{
+ if (g_globals.settings.ctl_port <= 0)
+ {
+ ctl_server_tcp_sock = -1;
+ return ctl_server_tcp_sock;
+ }
+ ctl_server_tcp_sock
+ = create_listen_socket_tcp(g_globals.settings.ctl_port);
+ if (ctl_server_tcp_sock != -1)
+ {
+ socket_make_non_block(ctl_server_tcp_sock);
+ }
+ return ctl_server_tcp_sock;
+}
+
+int
+ctl_server_init_unix()
+{
+ if (!g_globals.settings.ctl_socket_path
+ || !g_globals.settings.ctl_socket_path[0])
+ {
+ ctl_server_unix_sock = -1;
+ return ctl_server_unix_sock;
+ }
+ ctl_server_unix_sock
+ = create_listen_socket_unix(g_globals.settings.ctl_socket_path);
+ if (ctl_server_unix_sock != -1)
+ {
+ socket_make_non_block(ctl_server_unix_sock);
+ }
+ return ctl_server_unix_sock;
+}
+
+ctl_task_info *
+ctl_create_task(int connection_fd)
+{
+ /* TODO: handle errors */
+ ctl_task_info *info = 0;
+ ctl_session *session = 0;
+ int ssl_accept_ret = 0;
+ info = calloc(1, sizeof(ctl_task_info));
+ if (!info)
+ {
+ perror("calloc");
+ goto error;
+ }
+ session = calloc(1, sizeof(ctl_session));
+ if (!session)
+ {
+ perror("calloc");
+ goto error;
+ }
+ session->connection_fd = connection_fd;
+ info->session = session;
+ session->ssl = SSL_new(ctx);
+ SSL_set_fd(session->ssl, connection_fd);
+ ssl_accept_ret = SSL_accept(session->ssl);
+ if (ssl_accept_ret <= 0)
+ {
+ switch (SSL_get_error(session->ssl, ssl_accept_ret))
+ {
+ case SSL_ERROR_WANT_READ:
+ {
+ /* do nothing here, will be handled later */
+ }
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ {
+ /* TODO:
+ * 1. set write watcher
+ * 2. do SSL_write ?
+ */
+ }
+ break;
+ default:
+ goto error;
+ }
+ }
+ else
+ {
+ session->state = ctl_session_connected;
+ }
+ return info;
+error:
+ if (session)
+ {
+ if (session->ssl)
+ {
+ SSL_free(session->ssl);
+ }
+ free(session);
+ }
+ if (info)
+ {
+ free(info);
+ }
+ return 0;
+}
+
+typedef enum
+{
+ cmd_get_sessions,
+ cmd_kill
+} cmd_type;
+
+typedef struct
+{
+ cmd_type type;
+ char **sessions, *message;
+} ctl_cmd;
+
+static bool
+ctl_handle_json_option(struct json_object_element_s *json_option,
+ wrdp_thpool_task *task, ctl_cmd *cmd)
+{
+ switch (json_option->value->type)
+ {
+ case json_type_object:
+ {
+ }
+ break;
+ default:
+ {
+ const char *msg = "unsupported json setting type";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ return false;
+ }
+ break;
+ }
+ return true;
+}
+
+static bool
+ctl_handle_json_array_cmd(wrdp_thpool_task *task)
+{
+ ctl_task_info *info = task->userdata;
+ ctl_session *session = info->session;
+ struct json_parse_result_s res = {0};
+ struct json_value_s *root = 0;
+ ctl_cmd cmd;
+ root = json_parse_ex(session->read_buf, session->cmd_size,
+ json_parse_flags_allow_json5, 0, 0, &res);
+ if (!root)
+ {
+ const char *msg
+ = "Failed to parse remote control cmd data json";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ return false;
+ }
+ {
+ bool fail = false;
+ struct json_object_s *object
+ = (struct json_object_s *)root->payload;
+ struct json_object_element_s *option = object->start;
+ while (option)
+ {
+ fail = !ctl_handle_json_option(option, task, &cmd);
+ if (fail)
+ break;
+ option = option->next;
+ }
+ if (fail)
+ {
+ free(root);
+ return false;
+ }
+ }
+ free(root);
+ return true;
+}
+
+bool
+ctl_server_handle_data(void *taskdata)
+{
+ wrdp_thpool_task *task = taskdata;
+ ctl_task_info *info = task->userdata;
+ ctl_session *session = info->session;
+ switch (session->state)
+ {
+ case ctl_session_ssl_handshake:
+ {
+ int ssl_accept_ret = 0;
+ ssl_accept_ret = SSL_accept(session->ssl);
+ if (ssl_accept_ret <= 0)
+ {
+ /* ERR_print_errors_fp(stderr); */
+ int err = SSL_get_error(
+ session->ssl, ssl_accept_ret);
+ if (err != SSL_ERROR_WANT_READ
+ && err != SSL_ERROR_WANT_WRITE)
+ {
+ char buf[64];
+ snprintf(
+ buf, 63, "SSL_accept: %d", err);
+ log_msg((const uint8_t *)buf,
+ strlen(buf), wrdp_log_level_error,
+ 0);
+ return false;
+ }
+ }
+ else
+ {
+ session->state = ctl_session_connected;
+ }
+ }
+ break;
+ case ctl_session_connected:
+ {
+ if (!(session->cmd_size_known))
+ {
+ int32_t ret = SSL_read(session->ssl,
+ &(session->cmd_size) + session->read_size,
+ 4 - session->read_size);
+ if (ret <= 0)
+ {
+ int err
+ = SSL_get_error(session->ssl, ret);
+ if (err != SSL_ERROR_WANT_READ
+ && err != SSL_ERROR_WANT_WRITE)
+ {
+ char buf[64];
+ snprintf(buf, 63,
+ "SSL_read: %d", err);
+ log_msg((const uint8_t *)buf,
+ strlen(buf),
+ wrdp_log_level_error, 0);
+ return false;
+ }
+ }
+ session->read_size += ret;
+ if (session->read_size == 4)
+ {
+ session->cmd_size_known = true;
+ session->cmd_size
+ = ntohl(session->cmd_size);
+ session->read_buf
+ = malloc(session->cmd_size);
+ if (!session->read_buf)
+ {
+ perror("malloc");
+ return false;
+ }
+ /* reset read_size */
+ session->read_size = 0;
+ }
+ }
+ else
+ {
+ int32_t ret = SSL_read(session->ssl,
+ session->read_buf + session->read_size,
+ session->cmd_size - session->read_size);
+ if (ret <= 0)
+ {
+ int err
+ = SSL_get_error(session->ssl, ret);
+ if (err != SSL_ERROR_WANT_READ
+ && err != SSL_ERROR_WANT_WRITE)
+ {
+ char buf[64];
+ snprintf(buf, 63,
+ "SSL_read: %d", err);
+ log_msg((const uint8_t *)buf,
+ strlen(buf),
+ wrdp_log_level_error, 0);
+ return false;
+ }
+ }
+ session->read_size += ret;
+ if (session->read_size == session->cmd_size)
+ {
+ ctl_handle_json_array_cmd(task);
+ /* reset cmd_size_known */
+ session->cmd_size_known = false;
+ /* reset read_size, cmd_size */
+ session->cmd_size = session->read_size
+ = 0;
+ /* free read_buf */
+ free(session->read_buf);
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+void
+init_remote_control()
+{
+ init_openssl();
+ create_context();
+ configure_context(ctx);
+}
diff --git a/src/core/remote_control.h b/src/core/remote_control.h
new file mode 100644
index 0000000..0600a28
--- /dev/null
+++ b/src/core/remote_control.h
@@ -0,0 +1,36 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include <openssl/ssl.h>
+
+typedef enum
+{
+ ctl_session_ssl_handshake = 0,
+ ctl_session_connected
+} ctl_session_state;
+
+typedef struct ctl_session_s
+{
+ ev_io ev_con_fd_r, ev_con_fd_w;
+ int connection_fd;
+ SSL *ssl;
+ ctl_session_state state;
+
+ char *read_buf;
+ uint32_t cmd_size, read_size;
+ bool cmd_size_known;
+} ctl_session;
+
+#include "ctl_task.h"
+
+int ctl_server_init_tcp();
+int ctl_server_init_unix();
+void init_remote_control();
+ctl_task_info *ctl_create_task(int connection_fd);
+bool ctl_server_handle_data(void *taskdata);
+void ctl_destroy_task(wrdp_thpool_task *task);
diff --git a/src/core/socket_helpers.c b/src/core/socket_helpers.c
new file mode 100644
index 0000000..2d2cdbb
--- /dev/null
+++ b/src/core/socket_helpers.c
@@ -0,0 +1,143 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+
+#include <stdbool.h>
+#include "webrdp_core_api.h"
+#include "log.h"
+
+void
+socket_make_non_block(int sock)
+{
+ int flags, r;
+ while ((flags = fcntl(sock, F_GETFL, 0)) == -1 && errno == EINTR)
+ {
+ }
+ if (flags == -1)
+ {
+ perror("fcntl");
+ exit(EXIT_FAILURE);
+ }
+ while ((r = fcntl(sock, F_SETFL, flags | O_NONBLOCK)) == -1
+ && errno == EINTR)
+ {
+ }
+ if (r == -1)
+ {
+ perror("fcntl");
+ exit(EXIT_FAILURE);
+ }
+}
+
+int
+create_listen_socket_tcp(uint32_t port)
+{
+ /* TODO: ipv6 */
+ struct addrinfo hints, *res, *rp;
+ int sfd = -1;
+ int r;
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
+ {
+ char sport[10];
+ snprintf(sport, 9, "%d", port);
+ r = getaddrinfo(0, sport, &hints, &res);
+ }
+ if (r != 0)
+ {
+ uint8_t buf[64];
+ snprintf((char *)buf, 63, "getaddrinfo: %s", gai_strerror(r));
+ log_msg(
+ buf, strlen((const char *)buf), wrdp_log_level_error, 0);
+ exit(EXIT_FAILURE);
+ }
+ for (rp = res; rp; rp = rp->ai_next)
+ {
+ int val = 1;
+ sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (sfd == -1)
+ {
+ continue;
+ }
+ if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &val,
+ (socklen_t)sizeof(val))
+ == -1)
+ {
+ continue;
+ }
+ if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
+ {
+ break;
+ }
+ close(sfd);
+ }
+ freeaddrinfo(res);
+ if (listen(sfd, 16) == -1)
+ {
+ perror("listen");
+ close(sfd);
+ exit(EXIT_FAILURE);
+ }
+ return sfd;
+}
+
+int
+create_listen_socket_unix(const char *path)
+{
+ struct sockaddr_un saddr;
+ int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
+ bzero(&saddr, sizeof(struct sockaddr_un));
+ saddr.sun_family = AF_UNIX;
+ strcpy(saddr.sun_path, path);
+ /* this will delete file on disk, beware ) */
+ unlink(path);
+ if (bind(sfd, &saddr, sizeof(struct sockaddr_un)))
+ {
+ perror("bind");
+ exit(EXIT_FAILURE);
+ }
+ /* set permissive access to socket */
+ chmod(path, 0777);
+
+ if (listen(sfd, 16) == -1)
+ {
+ perror("listen");
+ close(sfd);
+ exit(EXIT_FAILURE);
+ }
+ return sfd;
+}
+
+int
+accept_new_connection(int socket)
+{
+ int fd = -1;
+ fd = accept(socket, NULL, NULL);
+ if (fd == -1 && errno != EINTR && errno != EAGAIN
+ && errno != EWOULDBLOCK)
+ {
+ perror("accept");
+ }
+ else
+ {
+ socket_make_non_block(fd);
+ }
+ return fd;
+}
diff --git a/src/core/socket_helpers.h b/src/core/socket_helpers.h
new file mode 100644
index 0000000..7eb04de
--- /dev/null
+++ b/src/core/socket_helpers.h
@@ -0,0 +1,12 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+void socket_make_non_block(int sock);
+int create_listen_socket_tcp(uint32_t port);
+int create_listen_socket_unix(const char *path);
+int accept_new_connection(int socket);
diff --git a/src/core/task.h b/src/core/task.h
new file mode 100644
index 0000000..6677739
--- /dev/null
+++ b/src/core/task.h
@@ -0,0 +1,59 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include <sys/queue.h>
+
+enum backend_setting_type_e
+{
+ setting_int,
+ setting_string
+};
+
+struct backend_setting_s
+{
+ union
+ {
+ backend_setting_int setting_int;
+ backend_setting_str setting_string;
+ };
+ enum backend_setting_type_e type;
+ SLIST_ENTRY(backend_setting_s) entries;
+};
+
+struct ws_session_list_entry_s
+{
+ ws_session *session;
+ ws_session_settings settings;
+ SLIST_ENTRY(ws_session_list_entry_s) entries;
+};
+
+typedef struct task_info_s
+{
+ /* name of backend handling this task */
+ char backend_name[64];
+
+ /* backend instance */
+ wrdp_backend_module *backend;
+
+ /* pointer to wrdp_thpool_task* used by some internals */
+ void *wrdp_thpool_task;
+
+ /* if true, all calls should return immediataely
+ * and do not touch any data */
+ bool stopped;
+
+ /* libev based session timer */
+ ev_timer *ev_timer_watcher;
+
+ /* task total run time, task idle time */
+ int64_t run_time, idle_time;
+
+ /* session related settings like timeouts */
+ ws_session_settings settings;
+
+} task_info;
diff --git a/src/core/thread_impl.c b/src/core/thread_impl.c
new file mode 100644
index 0000000..2be8ed0
--- /dev/null
+++ b/src/core/thread_impl.c
@@ -0,0 +1,387 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <ev.h>
+
+#include "wrdp_thpool.h"
+//required to work with thpool internals
+#include "wrdp_thpool_internals.h"
+
+//required to work with ws_server internals
+#include "ws_session.h"
+#include "ws_server_internals.h"
+
+#include <webrdp_module_api.h>
+
+#include "globals.h"
+#include "task.h"
+#include "remote_control.h"
+#include "ctl_task.h"
+
+#include "rdp_backend_api.h"
+
+#include "thread_sync.h"
+#include "backend_helpers.h"
+#include "curl_helpers.h"
+
+#include "curl_helpers.h"
+
+#include "log.h"
+
+#include <errno.h>
+#include "base64_url.h"
+
+void
+ws_session_init_cb(wrdp_thpool_task *task, void *userdata)
+{
+ ws_session *session = userdata;
+ task_info *info = session->task_info;
+ info->wrdp_thpool_task = task;
+ session->wrdp_thpool_task = task;
+}
+
+void
+task_destroy_timers(wrdp_thpool_task *task)
+{
+ task_info *info;
+ if (!task)
+ {
+ {
+ log_msg_info i = {0};
+ i.buf = (const uint8_t
+ *)"task_destroy_timers: task is null";
+ i.level = wrdp_log_level_trace;
+ log_msg_ex(&i);
+ }
+ return;
+ }
+ info = task->userdata;
+ {
+ log_msg_info i = {0};
+ i.buf = (const uint8_t *)"task_destroy_timers";
+ i.level = wrdp_log_level_trace;
+ i.task_info = info;
+ log_msg_ex(&i);
+ }
+ if (!info || !info->ev_timer_watcher)
+ {
+ return;
+ }
+ if (ev_is_active(info->ev_timer_watcher))
+ {
+ ev_timer_stop(task->thread->ev_th_loop, info->ev_timer_watcher);
+ }
+ free(info->ev_timer_watcher);
+ info->ev_timer_watcher = 0;
+}
+
+static void
+task_destroy_client_connection_impl(ws_session *session)
+{
+ if (session->session_state != ws_session_error)
+ session->session_state = ws_session_ended;
+ wrdp_thpool_task *task = session->wrdp_thpool_task;
+ {
+ log_msg_info i = {0};
+ i.buf = (const uint8_t *)"task_destroy_client_connection";
+ i.level = wrdp_log_level_trace;
+ i.ws_session = session;
+ log_msg_ex(&i);
+ }
+ if (task)
+ {
+ ev_io_stop(task->thread->ev_th_loop, &(session->ev_con_fd_r));
+ if (ev_is_active(&(session->ev_con_fd_w)))
+ {
+ ev_io_stop(
+ task->thread->ev_th_loop, &(session->ev_con_fd_w));
+ }
+ }
+ if (session->http_state == ws_server_state_ws_running
+ && session->wslay_ctx)
+ {
+ /* send all pending messages
+ * this may block ? */
+ wslay_event_shutdown_read(session->wslay_ctx);
+ wslay_event_queue_close(session->wslay_ctx, 0, 0, 0);
+ while (wslay_event_want_write(session->wslay_ctx)
+ && !wslay_event_get_close_received(session->wslay_ctx))
+ {
+ wslay_event_send(session->wslay_ctx);
+ }
+ wslay_event_shutdown_write(session->wslay_ctx);
+ wslay_event_context_free(session->wslay_ctx);
+ session->wslay_ctx = 0;
+ }
+ close(session->connection_fd);
+ free(session->sid_base64);
+}
+
+static void
+task_destroy_server_connection(wrdp_thpool_task *task)
+{
+ task_info *info = task->userdata;
+ {
+ log_msg_info i = {0};
+ i.buf = (const uint8_t *)"task_destroy_server_connection";
+ i.level = wrdp_log_level_trace;
+ i.task_info = info;
+ log_msg_ex(&i);
+ }
+ if (!info)
+ {
+ return;
+ }
+ /* TODO: implement backend self-destruction based on session timeout
+ * instead */
+ if (info->backend && info->backend->callbacks_module->destroy)
+ {
+ info->backend->callbacks_module->destroy(
+ info->backend->backend_internals);
+ info->backend->callbacks_module->destroy = 0;
+ }
+ if (info->backend)
+ {
+ backend_destroy(info->backend);
+ info->backend = 0;
+ }
+}
+
+void
+task_destroy_empty(wrdp_thpool_task *task)
+{
+ task_info *info = task->userdata;
+ {
+ log_msg_info i = {0};
+ i.buf = (const uint8_t *)"destroy empty task";
+ i.level = wrdp_log_level_trace;
+ i.task_info = info;
+ log_msg_ex(&i);
+ }
+ task_destroy_timers(task);
+ task_destroy_server_connection(task);
+ wrdp_thread_pool_destroy_task(task, 0);
+ free(info);
+}
+
+static void
+task_remove_session_from_list_and_destroy(void *session_)
+{
+ ws_session *session = session_;
+ task_info *info = session->task_info;
+ task_destroy_client_connection_impl(session);
+ if (!info)
+ {
+ /* in rare case this may be called when task_info not created
+ * yet */
+ return;
+ }
+ SLIST_HEAD(sessions_head, ws_session_list_entry_s) *sessions_list_head_p
+ = info->backend->sessions_list_head;
+ if (!SLIST_EMPTY(sessions_list_head_p))
+ {
+ for (struct ws_session_list_entry_s *s
+ = SLIST_FIRST(sessions_list_head_p);
+ s; s = SLIST_NEXT(s, entries))
+ {
+ if (s->session == session)
+ {
+ SLIST_REMOVE(sessions_list_head_p, s,
+ ws_session_list_entry_s, entries);
+ free(s);
+ break;
+ }
+ }
+ }
+ curl_list_session_destroy(session);
+
+ if (SLIST_EMPTY(sessions_list_head_p))
+ {
+ task_destroy_empty((wrdp_thpool_task *)info->wrdp_thpool_task);
+ }
+ else
+ {
+ wrdp_thread_pool_destroy_task(session->wrdp_thpool_task, 0);
+ }
+ free(session);
+}
+
+void
+task_destroy_client_connection(ws_session *session)
+{
+ if (session->token_base64)
+ {
+ uint8_t *data = 0;
+ size_t data_size
+ = curl_prepare_post_request_data(&data, session);
+ curl_request_info *request = curl_init_request(session,
+ curl_request_type_post, data, data_size, 0,
+ task_remove_session_from_list_and_destroy, session);
+ free(data);
+ curl_request(request);
+ }
+}
+
+static void
+on_task_destroy(wrdp_thpool_task *task)
+{
+ {
+ log_msg_info i = {0};
+ i.buf = (const uint8_t *)"cleaning task slot";
+ i.wrdp_thpool_task = task;
+ i.level = wrdp_log_level_debug;
+ log_msg_ex(&i);
+ }
+ task_destroy_timers(task);
+ task_info *info = task->userdata;
+ if (info)
+ {
+ SLIST_HEAD(sessions_head,
+ ws_session_list_entry_s) *sessions_list_head_p
+ = info->backend->sessions_list_head;
+ if (!SLIST_EMPTY(sessions_list_head_p))
+ {
+ for (struct ws_session_list_entry_s *s
+ = SLIST_FIRST(sessions_list_head_p);
+ s; s = SLIST_NEXT(s, entries))
+ {
+ if (!s->session)
+ continue;
+ if (s->session->session_state
+ != ws_session_error)
+ s->session->session_state
+ = ws_session_ended;
+ task_destroy_client_connection(s->session);
+ }
+ }
+ while (!SLIST_EMPTY(sessions_list_head_p))
+ SLIST_REMOVE_HEAD(sessions_list_head_p, entries);
+ }
+}
+
+void
+destroy_task(task_info *t_info)
+{
+ t_info->stopped = true;
+ if (t_info->backend)
+ {
+ t_info->backend->stopped = true;
+ }
+ /* TODO:
+ * eliminate possibility of small race condition here
+ * which lead to inconsistant task count in thread of thread pool
+ * for one second
+ */
+}
+
+void
+task_timeouts_check_cb(EV_P_ ev_timer *w, int revents)
+{
+
+ task_info *info = w->data;
+ if (!info)
+ {
+ return;
+ }
+ if (info->stopped)
+ {
+ w->data = 0;
+ on_task_destroy(info->wrdp_thpool_task);
+ return;
+ }
+ if (info->settings.session_idle_timeout
+ && (info->idle_time > info->settings.session_idle_timeout))
+ {
+ log_msg_info i = {0};
+ i.buf = (const uint8_t *)"Session idle timeout occured";
+ i.task_info = info;
+ i.level = wrdp_log_level_debug;
+ log_msg_ex(&i);
+ destroy_task(info);
+ }
+ if (info->settings.session_time_limit
+ && (info->run_time > info->settings.session_time_limit))
+ {
+ log_msg_info i = {0};
+ i.buf = (const uint8_t *)"Session timeout occured";
+ i.task_info = info;
+ i.level = wrdp_log_level_debug;
+ log_msg_ex(&i);
+ destroy_task(info);
+ }
+ info->run_time++;
+ info->idle_time++;
+}
+
+static void
+ws_ev_connection_readable_cb(struct ev_loop *loop, ev_io *w, int revents)
+{
+ ws_session *session = w->data;
+ if (!session)
+ {
+ return;
+ }
+ task_info *info = session->task_info;
+ if (info && info->stopped)
+ {
+ return;
+ }
+ if (!ws_server_handle_data(session))
+ {
+ w->data = 0;
+ if (session->session_state != ws_session_error)
+ session->session_state = ws_session_ended;
+ task_destroy_client_connection(session);
+ }
+}
+
+void
+ws_run_session(wrdp_thpool_task *task, void *_ws_session)
+{
+ ws_session *session = _ws_session;
+ ev_io *io = &(session->ev_con_fd_r);
+ ev_io_init(
+ io, ws_ev_connection_readable_cb, session->connection_fd, EV_READ);
+ session->ev_con_fd_r.data = session;
+ ev_io_start(task->thread->ev_th_loop, io);
+}
+
+void
+ws_stop_session(wrdp_thpool_task *current_task, void *_ws_session)
+{
+ ws_session *session = _ws_session;
+ ev_io *io = &(session->ev_con_fd_r);
+ if (ev_is_active(io))
+ {
+ ev_io_stop(current_task->thread->ev_th_loop, io);
+ }
+}
+
+static void
+ctl_ev_connection_readable_cb(struct ev_loop *loop, ev_io *w, int revents)
+{
+ if (!ctl_server_handle_data(w->data))
+ {
+ ctl_destroy_task(w->data);
+ }
+}
+
+void
+ctl_run_task(wrdp_thpool_task *task)
+{
+ ctl_task_info *info = task->userdata;
+ ctl_session *session = info->session;
+ ev_io *io = &(session->ev_con_fd_r);
+ ev_io_init(
+ io, ctl_ev_connection_readable_cb, session->connection_fd, EV_READ);
+ session->ev_con_fd_r.data = task;
+ ev_io_start(task->thread->ev_th_loop, io);
+}
diff --git a/src/core/thread_impl.h b/src/core/thread_impl.h
new file mode 100644
index 0000000..2034e74
--- /dev/null
+++ b/src/core/thread_impl.h
@@ -0,0 +1,23 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+void ws_run_session(wrdp_thpool_task *task, void *user_data);
+
+void ws_stop_session(wrdp_thpool_task *current_task, void *_ws_session);
+
+void ctl_run_task(wrdp_thpool_task *task, void *user_data);
+
+void task_destroy_client_connection(ws_session *session);
+
+void destroy_task(task_info *t_info);
+
+void ws_session_init_cb(wrdp_thpool_task *task, void *user_data);
+
+void task_timeouts_check_cb(EV_P_ ev_timer *w, int revents);
+
+void task_destroy_timers(wrdp_thpool_task *task);
diff --git a/src/core/thread_sync.c b/src/core/thread_sync.c
new file mode 100644
index 0000000..19b430a
--- /dev/null
+++ b/src/core/thread_sync.c
@@ -0,0 +1,75 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#include "globals.h"
+#include "thread_sync.h"
+#include "thread_impl.h"
+
+static void
+remove_ws_backend_from_list(user_pool_msg *pmsg)
+{
+ if (!LIST_EMPTY(&(g_globals.backends_head)))
+ {
+ struct backend_s *t1 = LIST_FIRST(&(g_globals.backends_head));
+ while (t1)
+ {
+ if (!t1->backend || t1->backend == pmsg->backend)
+ {
+ LIST_REMOVE(t1, entries);
+ free(t1);
+ break;
+ }
+ t1 = LIST_NEXT(t1, entries);
+ }
+ }
+
+ free(pmsg->backend);
+}
+
+void
+pool_message_handler(void *user_data)
+{
+ user_pool_msg *pmsg = user_data;
+ if (!pmsg)
+ {
+ return;
+ }
+ switch (pmsg->type)
+ {
+ case msg_type_destroy_ws_backend_info:
+ {
+ remove_ws_backend_from_list(pmsg);
+ }
+ break;
+ case msg_type_backend_created:
+ {
+ struct backend_s *t;
+ t = calloc(1, sizeof(struct backend_s));
+ if (!t)
+ {
+ perror("calloc");
+ free(pmsg);
+ return;
+ }
+ t->backend = pmsg->backend;
+ LIST_INSERT_HEAD(
+ &(g_globals.backends_head), t, entries);
+ }
+ break;
+ default:
+ break;
+ }
+ free(pmsg);
+}
+
+void
+thread_message_handler(void *user_data)
+{
+}
diff --git a/src/core/thread_sync.h b/src/core/thread_sync.h
new file mode 100644
index 0000000..226e610
--- /dev/null
+++ b/src/core/thread_sync.h
@@ -0,0 +1,23 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+typedef enum
+{
+ msg_type_destroy_ws_backend_info,
+ msg_type_backend_created
+} usr_pool_msg_type;
+
+typedef struct
+{
+ usr_pool_msg_type type;
+ wrdp_backend_module *backend;
+} user_pool_msg;
+
+void pool_message_handler(void *user_data);
+
+void thread_message_handler(void *user_data);
diff --git a/src/core/utilities.c b/src/core/utilities.c
new file mode 100644
index 0000000..969e36b
--- /dev/null
+++ b/src/core/utilities.c
@@ -0,0 +1,115 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/stat.h>
+
+#include <openssl/bio.h>
+#include <openssl/buffer.h>
+#include <openssl/evp.h>
+
+#include <openssl/sha.h>
+
+bool
+is_regular_file(const char *path)
+{
+ struct stat path_stat;
+ stat(path, &path_stat);
+ return S_ISREG(path_stat.st_mode);
+}
+
+bool
+is_directory(const char *path)
+{
+ struct stat path_stat;
+ stat(path, &path_stat);
+ return S_ISDIR(path_stat.st_mode);
+}
+
+bool
+sha1(uint8_t *dst, const uint8_t *src, size_t src_length)
+{
+ SHA_CTX ctx = {0};
+ if (!SHA1_Init(&ctx))
+ {
+ return false;
+ }
+ if (!SHA1_Update(&ctx, src, src_length))
+ {
+ return false;
+ }
+ if (!SHA1_Final(dst, &ctx))
+ {
+ return false;
+ }
+ return true;
+}
+
+void
+hex_print(uint8_t *buf, size_t buf_len)
+{
+ size_t p;
+ for (p = 0; p < buf_len; ++p)
+ {
+ if (!p)
+ {
+ printf("00: ");
+ }
+ if (p && !(p % 8))
+ {
+ printf("| ");
+ }
+ if (p && !(p % 16))
+ {
+ size_t pp = p - 16;
+ for (; pp < p; ++pp)
+ {
+ printf("%c", ((char *)(buf))[pp]);
+ }
+ printf("\n%lx: ", p);
+ }
+ printf("%x ", ((char *)(buf))[p]);
+ }
+}
+
+char
+random_ascii_character()
+{
+ /* use whole ascii table of printable characters */
+ char c;
+get_new_char:
+ c = ((rand() % 94) + 32);
+ while (!c)
+ goto get_new_char;
+ return c;
+}
+
+char *
+random_ascii_string(char *buf, const size_t len)
+{
+ size_t i = 0;
+ if (!buf)
+ {
+ return 0;
+ }
+ for (; i < len; ++i)
+ {
+ buf[i] = random_ascii_character();
+ }
+ buf[len] = 0;
+ return buf;
+}
+
+void
+random_bytes(uint8_t *buf, size_t buf_len)
+{
+ size_t i = 0;
+ for (; i < buf_len; ++i)
+ {
+ buf[i] = rand();
+ }
+}
diff --git a/src/core/utilities.h b/src/core/utilities.h
new file mode 100644
index 0000000..ee2c6eb
--- /dev/null
+++ b/src/core/utilities.h
@@ -0,0 +1,28 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+/* check if destination path is regular file */
+bool is_regular_file(const char *path);
+
+/* check if destination path is directory */
+bool is_directory(const char *path);
+
+/* calculate sha1 hash of src */
+bool sha1(uint8_t *dst, const uint8_t *src, size_t src_length);
+
+/* print hex representation of buf to stdout */
+void hex_print(const uint8_t *buf, size_t buf_len);
+
+/* get one random ascii character */
+char random_ascii_character();
+
+/* get random string of ascii characters with length of 'len'
+ * and store intopointed buffer */
+char *random_ascii_string(char *buf, const size_t buf_size);
+
+void random_bytes(uint8_t *buf, size_t buf_len);
diff --git a/src/core/wrdp_thpool.c b/src/core/wrdp_thpool.c
new file mode 100644
index 0000000..c1f8145
--- /dev/null
+++ b/src/core/wrdp_thpool.c
@@ -0,0 +1,770 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <ev.h>
+
+#include "wrdp_thpool.h"
+#include "wrdp_thpool_internals.h"
+
+#include "webrdp_core_api.h"
+#include "log.h"
+
+void
+wrdp_thpool_destroy(wrdp_thpool *pool)
+{
+ /* TODO: finish this */
+ /* TODO: destroy all tasks */
+ /* TODO: call per thread custom_thread_deinit in each worker thread */
+ /* NOTE: unfinished, unused */
+ uint32_t i;
+ if (!pool)
+ {
+ return;
+ }
+ if (pool->threads)
+ {
+ for (i = 0; i < pool->thread_count; ++i)
+ {
+ close(pool->threads[i].pipe_fds[0]);
+ close(pool->threads[i].pipe_fds[1]);
+ }
+ }
+ if (pool->custom_pool_destroy)
+ {
+ pool->custom_pool_destroy(pool->userdata);
+ }
+ free(pool);
+}
+
+static void *wrdp_thpool_worker_thread_loop(void *thread_);
+
+static void pipe_readable_cb(struct ev_loop *loop, ev_io *w, int revents);
+
+typedef enum
+{
+ pool_obj_pool,
+ pool_obj_thread
+} thpool_obj_type;
+
+typedef struct
+{
+ union
+ {
+ wrdp_thpool *pool;
+ wrdp_thpool_thread *thread;
+ };
+ thpool_obj_type receiver;
+} pool_receiver_ptr;
+
+typedef struct
+{
+ union
+ {
+ wrdp_thpool *pool;
+ wrdp_thpool_thread *thread;
+ };
+ thpool_obj_type sender;
+} pool_sender_ptr;
+
+wrdp_thpool *
+wrdp_thpool_create(uint16_t thread_count, uint64_t max_tasks_per_thread,
+ void (*custom_thread_init)(void *user_pool_data, wrdp_thpool_thread *t),
+ void (*custom_thread_deinit)(void *user_pool_data, wrdp_thpool_thread *t),
+ void (*custom_pool_create)(void *user_pool_data),
+ void (*custom_pool_destroy)(void *user_pool_data),
+ void (*pool_message_handler)(void *user_data),
+ void (*thread_message_handler)(void *user_data), struct ev_loop *loop,
+ void *user_pool_data)
+{
+ wrdp_thpool *pool = calloc(1, sizeof(wrdp_thpool));
+ if (!pool)
+ {
+ perror("calloc");
+ return 0;
+ }
+
+ pool->thread_count = thread_count;
+ pool->max_tasks = max_tasks_per_thread;
+ pool->custom_pool_destroy = custom_pool_destroy;
+ pool->custom_thread_deinit = custom_thread_deinit;
+ pool->custom_thread_init = custom_thread_init;
+ pool->userdata = user_pool_data;
+ pool->pool_message_handler = pool_message_handler;
+ pool->thread_message_handler = thread_message_handler;
+
+ if (custom_pool_create)
+ {
+ custom_pool_create(pool->userdata);
+ }
+
+ pool->tasks_per_thread = calloc(thread_count, sizeof(uint64_t));
+ if (!pool->tasks_per_thread)
+ {
+ perror("calloc");
+ goto error;
+ }
+
+ pool->threads = calloc(thread_count, sizeof(wrdp_thpool_thread));
+ if (!(pool->threads))
+ {
+ perror("calloc");
+ goto error;
+ }
+ if (pipe(pool->pipe_fds) == -1)
+ {
+ perror("pipe");
+ goto error;
+ }
+
+ /* allocate memory for threads, tasks, create threads */
+ {
+ uint32_t i;
+ for (i = 0; i < thread_count; ++i)
+ {
+ if (pipe(pool->threads[i].pipe_fds) == -1)
+ {
+ perror("pipe");
+ goto error;
+ }
+ pool->threads[i].thread_id = i;
+ pool->threads[i].tasks = calloc(
+ sizeof(wrdp_thpool_task *), max_tasks_per_thread);
+ if (!pool->threads[i].tasks)
+ {
+ perror("calloc");
+ goto error;
+ }
+ pool->threads[i].pool = pool;
+ if (pthread_create(&(pool->threads[i].thread), 0,
+ wrdp_thpool_worker_thread_loop,
+ &(pool->threads[i]))
+ != 0)
+ {
+ goto error;
+ }
+ }
+ }
+ /* attach pipe reed watcher to default event loop */
+ {
+ pool_receiver_ptr *p = calloc(1, sizeof(pool_receiver_ptr));
+ if (!p)
+ {
+ perror("calloc");
+ goto error;
+ }
+ p->receiver = pool_obj_pool;
+ p->pool = pool;
+ ev_io_init(&(pool->ev_pipe_readable), pipe_readable_cb,
+ pool->pipe_fds[0], EV_READ);
+ pool->ev_pipe_readable.data = p;
+ if (loop)
+ {
+ ev_io_start(loop, &(pool->ev_pipe_readable));
+ }
+ else
+ {
+ ev_io_start(EV_DEFAULT, &(pool->ev_pipe_readable));
+ }
+ }
+ return pool;
+error:
+ if (pool)
+ {
+ wrdp_thpool_destroy(pool);
+ }
+ return 0;
+}
+
+typedef enum
+{
+ thread_msg_task_count = 1,
+ thread_msg_push_task,
+ thread_msg_task_finished,
+ thread_msg_userdata
+} thread_msg_type;
+
+typedef struct
+{
+ thread_msg_type type;
+ union
+ {
+ wrdp_thpool_task *task;
+ uint64_t running_tasks;
+ void *user_data;
+ };
+ pool_sender_ptr sender;
+} thread_msg;
+
+static void
+send_msg(int write_fd, thread_msg *msg)
+{
+ size_t io_size = 0, left = 0, struct_size = sizeof(thread_msg);
+ left = struct_size;
+ while (left)
+ {
+ io_size
+ = write(write_fd, (char *)msg + (struct_size - left), left);
+ if (io_size == -1
+ && (errno != EAGAIN && errno != EWOULDBLOCK
+ && errno != EINTR))
+ {
+ const char *msg_ = "error: thpool pipe write failure";
+ perror("write");
+ log_msg((const uint8_t *)msg_, strlen(msg_),
+ wrdp_log_level_error, 0);
+ exit(EXIT_FAILURE);
+ }
+ else
+ {
+ left -= io_size;
+ }
+ }
+}
+
+bool
+wrdp_thpool_send_msg_to_thread(
+ wrdp_thpool *pool, uint32_t thread_id, void *user_data)
+{
+ size_t io_size = 0, left = 0, struct_size = sizeof(thread_msg);
+ thread_msg msg;
+ memset(&msg, 0, struct_size);
+ msg.user_data = user_data;
+ msg.type = thread_msg_userdata;
+ left = struct_size;
+ if (thread_id >= pool->thread_count)
+ {
+ return false;
+ }
+ while (left)
+ {
+ io_size = write(pool->threads[thread_id].pipe_fds[1],
+ (char *)&msg + (struct_size - left), left);
+ if (io_size == -1
+ && (errno != EAGAIN && errno != EWOULDBLOCK
+ && errno != EINTR))
+ {
+ const char *msg = "thpool pipe write failure";
+ perror("write");
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ exit(EXIT_FAILURE);
+ }
+ else
+ {
+ left -= io_size;
+ }
+ }
+ return true;
+}
+
+bool
+wrdp_thpool_send_msg_to_pool(wrdp_thpool *pool, void *user_data)
+{
+ size_t io_size = 0, left = 0, struct_size = sizeof(thread_msg);
+ thread_msg msg;
+ memset(&msg, 0, struct_size);
+ msg.user_data = user_data;
+ msg.type = thread_msg_userdata;
+ left = struct_size;
+ while (left)
+ {
+ io_size = write(pool->pipe_fds[1],
+ (char *)&msg + (struct_size - left), left);
+ if (io_size == -1
+ && (errno != EAGAIN && errno != EWOULDBLOCK
+ && errno != EINTR))
+ {
+ const char *msg = "thpool pipe write failure";
+ perror("write");
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ exit(EXIT_FAILURE);
+ }
+ else
+ {
+ left -= io_size;
+ }
+ }
+ return true;
+}
+
+static thread_msg *
+read_msg(int read_fd)
+{
+ size_t io_size = 0, struct_size = sizeof(thread_msg), left = 0;
+ void *buf = calloc(1, struct_size);
+ if (!buf)
+ {
+ perror("malloc");
+ exit(EXIT_FAILURE);
+ }
+ left = struct_size;
+ while (left)
+ {
+ io_size = read(read_fd, buf, left);
+ if (io_size == -1
+ && (errno != EAGAIN && errno != EWOULDBLOCK
+ && errno != EINTR))
+ {
+ const char *msg = "thpool pipe read failure";
+ perror("read");
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ exit(EXIT_FAILURE);
+ }
+ else
+ {
+ left -= io_size;
+ }
+ }
+ return buf;
+}
+
+static bool
+send_task_to_thread(wrdp_thpool *pool, wrdp_thpool_task *task)
+{
+ uint32_t i = 0, thread_id = 0, minimal_tasks = 0;
+
+ //find thread with minimal number of running tasks or no tasks at all
+ for (; i < pool->thread_count; ++i)
+ {
+ uint64_t running_tasks = pool->tasks_per_thread[i];
+ if (!running_tasks)
+ {
+ thread_id = i;
+ break;
+ }
+ if (!minimal_tasks)
+ {
+ minimal_tasks = running_tasks;
+ thread_id = i;
+ }
+ if (running_tasks < minimal_tasks)
+ {
+ minimal_tasks = running_tasks;
+ thread_id = i;
+ }
+ }
+ //all threads have maximum tasks
+ if (minimal_tasks >= pool->max_tasks)
+ {
+ return false;
+ }
+ {
+ thread_msg msg;
+ memset(&msg, 0, sizeof(thread_msg));
+ msg.type = thread_msg_push_task;
+ msg.task = task;
+ msg.sender.sender = pool_obj_pool;
+ msg.sender.pool = pool;
+ task->thread = &(pool->threads[thread_id]);
+ if (task->task_init_cb)
+ {
+ task->task_init_cb(task, task->userdata);
+ }
+ send_msg(pool->threads[thread_id].pipe_fds[1], &msg);
+ }
+
+ return true;
+}
+
+static bool
+send_task_to_thread_by_id(
+ wrdp_thpool *pool, wrdp_thpool_task *task, uint32_t thread_id)
+{
+ if (pool->tasks_per_thread[thread_id] >= pool->max_tasks)
+ {
+ return false;
+ }
+ else
+ {
+ thread_msg msg;
+ memset(&msg, 0, sizeof(thread_msg));
+ msg.type = thread_msg_push_task;
+ msg.task = task;
+ msg.sender.sender = pool_obj_pool;
+ msg.sender.pool = pool;
+ task->thread = &(pool->threads[thread_id]);
+ if (task->task_init_cb)
+ {
+ task->task_init_cb(task, task->userdata);
+ }
+ send_msg(pool->threads[thread_id].pipe_fds[1], &msg);
+ }
+ return true;
+}
+
+bool
+wrdp_thread_pool_add_task(wrdp_thpool *pool,
+ void (*run_task)(wrdp_thpool_task *task, void *userdata),
+ void (*task_init_cb)(wrdp_thpool_task *task, void *userdata),
+ void *userdata)
+{
+ wrdp_thpool_task *task = calloc(1, sizeof(wrdp_thpool_task));
+ if (!task)
+ {
+ perror("malloc");
+ return false;
+ }
+ task->userdata = userdata;
+ task->run_task = run_task;
+ task->task_init_cb = task_init_cb;
+ if (!send_task_to_thread(pool, task))
+ {
+ goto cleanup;
+ }
+ return true;
+cleanup:
+ if (task)
+ {
+ free(task);
+ }
+ return false;
+}
+
+bool
+wrdp_thread_pool_add_task_to_thread(wrdp_thpool *pool,
+ void (*run_task)(wrdp_thpool_task *task, void *userdata),
+ uint32_t thread_id,
+ void (*task_init_cb)(wrdp_thpool_task *task, void *userdata),
+ void *userdata)
+{
+ wrdp_thpool_task *task = calloc(1, sizeof(wrdp_thpool_task));
+ if (!task)
+ {
+ perror("malloc");
+ return false;
+ }
+ task->userdata = userdata;
+ task->run_task = run_task;
+ task->task_init_cb = task_init_cb;
+ if (!send_task_to_thread_by_id(pool, task, thread_id))
+ {
+ goto cleanup;
+ }
+ return true;
+cleanup:
+ if (task)
+ {
+ free(task);
+ }
+ return false;
+}
+
+bool
+wrdp_thread_pool_move_task_to_thread(wrdp_thpool *pool,
+ void (*run_task)(wrdp_thpool_task *task, void *userdata),
+ void (*stop_task)(wrdp_thpool_task *current_task, void *userdata),
+ uint32_t thread_id,
+ void (*task_init_cb)(wrdp_thpool_task *task, void *userdata),
+ wrdp_thpool_task *current_task, void *userdata)
+{
+ wrdp_thpool_task *task = calloc(1, sizeof(wrdp_thpool_task));
+ if (!task)
+ {
+ perror("malloc");
+ return false;
+ }
+ task->userdata = userdata;
+ task->run_task = run_task;
+ task->stop_task = stop_task;
+ task->task_init_cb = task_init_cb;
+ if (stop_task)
+ {
+ stop_task(current_task, userdata);
+ }
+ wrdp_thread_pool_destroy_task(current_task, 0);
+ if (!send_task_to_thread_by_id(pool, task, thread_id))
+ {
+ goto cleanup;
+ }
+ return true;
+cleanup:
+ if (task)
+ {
+ free(task);
+ }
+ return false;
+}
+
+static void
+pipe_readable_cb(struct ev_loop *loop, ev_io *w, int revents)
+{
+ pool_receiver_ptr *p = w->data;
+ thread_msg *in_msg = 0;
+ switch (p->receiver)
+ {
+ case pool_obj_thread:
+ {
+ in_msg = read_msg(p->thread->pipe_fds[0]);
+ if (!in_msg)
+ {
+ return;
+ }
+ switch (in_msg->type)
+ {
+ case thread_msg_push_task:
+ {
+ uint32_t i;
+ bool added = false;
+ for (i = 0;
+ i < p->thread->pool->max_tasks;
+ ++i)
+ {
+ if (p->thread->tasks[i])
+ {
+ continue;
+ }
+ thread_msg out_msg;
+ memset(&out_msg, 0,
+ sizeof(thread_msg));
+ p->thread->tasks[i]
+ = in_msg->task;
+ p->thread->running_task_count++;
+ if (!p->thread->tasks[i]
+ ->run_task)
+ {
+ /* TODO: error message
+ * to log */
+ break;
+ }
+ p->thread->tasks[i]->run_task(
+ p->thread->tasks[i],
+ (p->thread->tasks[i]
+ ->userdata));
+ out_msg.type
+ = thread_msg_task_count;
+ out_msg.running_tasks
+ = p->thread
+ ->running_task_count;
+ out_msg.sender.sender
+ = pool_obj_thread;
+ out_msg.sender.thread
+ = p->thread;
+ {
+ char buf[128];
+ log_msg_info mi = {0};
+ snprintf(buf, 127,
+ "Added new task to "
+ "thread"
+ " %d slot %d",
+ p->thread
+ ->thread_id,
+ i);
+ mi.buf = (uint8_t *)buf;
+ mi.level
+ = wrdp_log_level_trace;
+ mi.wrdp_thpool_task
+ = in_msg->task;
+ log_msg_ex(&mi);
+ }
+ send_msg(p->thread->pool
+ ->pipe_fds[1],
+ &out_msg);
+ added = true;
+ break;
+ }
+ if (!added)
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "Error: failed to add task "
+ "to"
+ " thread %d :"
+ "no free slots",
+ p->thread->thread_id);
+ log_msg((const uint8_t *)buf,
+ strlen(buf),
+ wrdp_log_level_error, 0);
+ }
+ }
+ break;
+ case thread_msg_task_finished:
+ {
+ thread_msg out_msg;
+ size_t i = 0;
+ bool task_found = false;
+ memset(&out_msg, 0, sizeof(thread_msg));
+ for (i = 0;
+ i < p->thread->pool->max_tasks;
+ ++i)
+ {
+ if (p->thread->tasks[i]
+ == in_msg->task)
+ {
+ task_found = true;
+ p->thread->tasks[i] = 0;
+ p->thread
+ ->running_task_count--;
+ out_msg.type
+ = thread_msg_task_count;
+ out_msg.running_tasks
+ = p->thread
+ ->running_task_count;
+ out_msg.sender.sender
+ = pool_obj_thread;
+ out_msg.sender.thread
+ = p->thread;
+ {
+ char buf[128];
+ log_msg_info mi
+ = {0};
+ snprintf(buf,
+ 127,
+ "Removed "
+ "task from "
+ "thread"
+ " %d slot "
+ "%zd",
+ p->thread
+ ->thread_id,
+ i);
+ mi.wrdp_thpool_task
+ = in_msg
+ ->task;
+ mi.level
+ = wrdp_log_level_trace;
+ mi.buf
+ = (uint8_t
+ *)
+ buf;
+ log_msg_ex(&mi);
+ }
+ free(p->thread
+ ->tasks[i]);
+ send_msg(
+ p->thread->pool
+ ->pipe_fds[1],
+ &out_msg);
+ break;
+ }
+ }
+ if (!task_found)
+ {
+ const char *msg_str
+ = "wrdp_thpool: "
+ "thread_msg_task_"
+ "finished: task not "
+ "found "
+ "in thread";
+ log_msg(
+ (const uint8_t *)msg_str,
+ strlen(msg_str),
+ wrdp_log_level_error, 0);
+ }
+ }
+ break;
+ case thread_msg_userdata:
+ {
+ if (p->thread->pool
+ ->thread_message_handler)
+ {
+ p->thread->pool
+ ->thread_message_handler(
+ in_msg->user_data);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ case pool_obj_pool:
+ {
+ in_msg = read_msg(p->pool->pipe_fds[0]);
+ if (!in_msg)
+ {
+ return;
+ }
+ switch (in_msg->type)
+ {
+ case thread_msg_task_count:
+ {
+ p->pool->tasks_per_thread
+ [in_msg->sender.thread->thread_id]
+ = in_msg->sender.thread
+ ->running_task_count;
+ }
+ break;
+ case thread_msg_userdata:
+ {
+ if (p->pool->pool_message_handler)
+ {
+ p->pool->pool_message_handler(
+ in_msg->user_data);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ default:
+ break;
+ }
+ if (in_msg)
+ {
+ free(in_msg);
+ }
+}
+
+static void *
+wrdp_thpool_worker_thread_loop(void *thread_)
+{
+ wrdp_thpool_thread *thread = thread_;
+ pool_receiver_ptr *p = calloc(1, sizeof(pool_receiver_ptr));
+ if (!p)
+ {
+ perror("calloc");
+ return 0;
+ }
+ p->receiver = pool_obj_thread;
+ p->thread = thread;
+ if (thread->pool->custom_thread_init)
+ {
+ thread->pool->custom_thread_init(
+ thread->pool->userdata, thread);
+ }
+ thread->ev_th_loop = ev_loop_new(EVFLAG_AUTO);
+ ev_io_init(&(thread->ev_pipe_readable), pipe_readable_cb,
+ thread->pipe_fds[0], EV_READ);
+ thread->ev_pipe_readable.data = p;
+ ev_io_start(thread->ev_th_loop, &(thread->ev_pipe_readable));
+ ev_run(thread->ev_th_loop, 0);
+ return 0;
+}
+
+void
+wrdp_thread_pool_destroy_task(
+ wrdp_thpool_task *task, void (*on_task_destroy)(wrdp_thpool_task *task))
+{
+ thread_msg msg;
+
+ /* TODO: this should never happen, but for now just crashfix hack */
+ if (!task->thread)
+ {
+ return;
+ }
+
+ memset(&msg, 0, sizeof(thread_msg));
+ if (on_task_destroy)
+ {
+ on_task_destroy(task);
+ }
+ msg.type = thread_msg_task_finished;
+ msg.task = task;
+ msg.sender.sender = pool_obj_thread;
+ msg.sender.thread = task->thread;
+ send_msg(task->thread->pipe_fds[1], &msg);
+}
diff --git a/src/core/wrdp_thpool.h b/src/core/wrdp_thpool.h
new file mode 100644
index 0000000..d6bbe38
--- /dev/null
+++ b/src/core/wrdp_thpool.h
@@ -0,0 +1,150 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <ev.h>
+
+struct wrdp_thpool_s;
+typedef struct wrdp_thpool_s wrdp_thpool;
+
+struct wrdp_thpool_thread_s;
+typedef struct wrdp_thpool_thread_s wrdp_thpool_thread;
+
+struct wrdp_thpool_task_s;
+typedef struct wrdp_thpool_task_s wrdp_thpool_task;
+
+/* initialize thread pool */
+
+/* function called in each thread to do additional initialization
+ * of user_pool_data
+void (*custom_thread_init) (void *user_pool_data);
+*/
+
+/* function called in each thread to do additional cleanup of
+ * user_pool_data
+void (*custom_thread_deinit) (void *user_pool_data);
+*/
+
+/* function called in wrdp_thpool_create to do additional initialization
+ * of user_pool_data
+void (*custom_pool_create) (void *user_pool_data);
+*/
+
+/* function called in wrdp_thpool_destroy to do additional cleanup of
+ * user_pool_data
+void (*custom_pool_destroy) (void *user_pool_data);
+*/
+/* function called on incomming mesdage with "void *user_data" directed to
+ * pool
+void (*pool_message_handler) (void *user_data);
+ */
+
+/* function called on incomming message with "void *user_data" directed to
+ * thrad
+void (*thread_message_handler) (void *user_data);
+ */
+
+/* struct ev_loop* loop
+ * it's possible to use specified ev_loop instead of EV_DEFAULT
+ */
+
+wrdp_thpool *wrdp_thpool_create(uint16_t thread_count,
+ uint64_t max_tasks_per_thread,
+ void (*custom_thread_init)(void *user_pool_data, wrdp_thpool_thread *t),
+ void (*custom_thread_deinit)(void *user_pool_data, wrdp_thpool_thread *t),
+ void (*custom_pool_create)(void *user_pool_data),
+ void (*custom_pool_destroy)(void *user_pool_data),
+ void (*pool_message_handler)(void *user_data),
+ void (*thread_message_handler)(void *user_data), struct ev_loop *loop,
+ void *user_pool_data);
+
+/* deinitialize thread pool */
+void wrdp_thpool_destroy(wrdp_thpool *pool);
+
+/*
+ * Add task to thread pool.
+ * task will be added to thread with minimal tasks count
+ * Not thread safe.
+ * Must be called from thread which created thread pool only.
+ *
+ * pool: fully initialized thread pool
+ * run_task: task entry point callback
+ * must not block but set libev watcher instead
+ *
+ * userdata: user specified data pointer
+ */
+bool wrdp_thread_pool_add_task(wrdp_thpool *pool,
+ void (*run_task)(wrdp_thpool_task *task, void *userdata),
+ void (*task_init_cb)(wrdp_thpool_task *task, void *userdata),
+ void *userdata);
+
+/*
+ * Add task to thread pool to specified thread.
+ * task will be added to specified thread
+ * Not thread safe.
+ * Must be called from thread which created thread pool only.
+ *
+ * pool: fully initialized thread pool
+ *
+ * thread_id: id of the thread to run task in
+ *
+ * run_task: task entry point callback
+ * must not block but set libev watcher instead
+ *
+ * userdata: user specified data pointer
+ */
+bool wrdp_thread_pool_add_task_to_thread(wrdp_thpool *pool,
+ void (*run_task)(wrdp_thpool_task *task, void *userdata),
+ uint32_t thread_id,
+ void (*task_init_cb)(wrdp_thpool_task *task, void *userdata),
+ void *userdata);
+
+/*
+ * Move task to specified thread.
+ * task will be moved to specified thread
+ *
+ * (technically new task in specified thread will
+ * be created, and current task will be deleted
+ * preserving task internal state)
+ *
+ * pool: fully initialized thread pool
+ *
+ * thread_id: id of the thread to run task in
+ *
+ * run_task: task entry point callback
+ * must not block but set libev watcher instead
+ *
+ * stop_task: callback function used to prepare task for ruining in new thread
+ *
+ * userdata: user specified data pointer
+ */
+bool wrdp_thread_pool_move_task_to_thread(wrdp_thpool *pool,
+ void (*run_task)(wrdp_thpool_task *task, void *userdata),
+ void (*stop_task)(wrdp_thpool_task *current_task, void *userdata),
+ uint32_t thread_id,
+ void (*task_init_cb)(wrdp_thpool_task *task, void *userdata),
+ wrdp_thpool_task *current_task, void *userdata);
+
+/*
+ * it is possible to send message with "void *user_data" to specified thread,
+ * or to pool main thread
+ * NOTE: custom message handler function must also be set during pool creation
+ */
+
+bool wrdp_thpool_send_msg_to_thread(
+ wrdp_thpool *pool, uint32_t thread_id, void *user_data);
+
+bool wrdp_thpool_send_msg_to_pool(wrdp_thpool *pool, void *user_data);
+
+/*
+ * Destroy runing task.
+ * Custom destroy is function ptr "void(*callable)(wrdp_thpool_task* task)".
+ */
+
+void wrdp_thread_pool_destroy_task(
+ wrdp_thpool_task *task, void (*on_task_destroy)(wrdp_thpool_task *task));
diff --git a/src/core/wrdp_thpool_internals.h b/src/core/wrdp_thpool_internals.h
new file mode 100644
index 0000000..b37e2d3
--- /dev/null
+++ b/src/core/wrdp_thpool_internals.h
@@ -0,0 +1,113 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include "wrdp_thpool.h"
+
+struct wrdp_thpool_task_s
+{
+
+ /* Pass any user specified data pointer. */
+ void *userdata;
+
+ //thread owning task
+ wrdp_thpool_thread *thread;
+
+ /* Task callbacks */
+
+ /* Task entry point callback */
+ void (*run_task)(wrdp_thpool_task *task, void *userdata);
+
+ /* callback used to stop task before moving to another thread */
+ void (*stop_task)(wrdp_thpool_task *task, void *userdata);
+
+ /* function called just before adding task to thread,
+ * may be used to do additional task initialization
+ * userdata is user specified data passed to "wrdp_thread_pool_add_task"
+ */
+ void (*task_init_cb)(wrdp_thpool_task *task, void *userdata);
+};
+
+struct wrdp_thpool_thread_s
+{
+
+ //per thread libev based event loop
+ struct ev_loop *ev_th_loop;
+
+ ev_io ev_pipe_readable;
+
+ pthread_t thread;
+
+ wrdp_thpool_task **tasks;
+
+ uint64_t running_task_count;
+ uint16_t thread_id;
+
+ int pipe_fds[2];
+
+ //pool owning thread
+ wrdp_thpool *pool;
+};
+
+struct wrdp_thpool_s
+{
+ wrdp_thpool_thread *threads;
+ /* worker threads count */
+ uint16_t thread_count;
+
+ /* dynamic variable holding number of threads were task count check is
+ * already done
+ */
+ uint16_t checked_threads_tasks;
+
+ /* maximum tasks per thread */
+ uint64_t max_tasks;
+
+ /* buffer to hold tasks count for each thread */
+ uint64_t *tasks_per_thread;
+
+ /* internal messageing pipe */
+ int pipe_fds[2];
+
+ ev_io ev_pipe_readable;
+
+ /* data assigned by user */
+ void *userdata;
+
+ /* additional api callbacks */
+
+ /* function called in each thread to do additional initialization
+ * of user_pool_data
+ */
+ void (*custom_thread_init)(void *user_pool_data, wrdp_thpool_thread *t);
+
+ /* function called in each thread to do additional cleanup of
+ * user_pool_data
+ */
+ void (*custom_thread_deinit)(
+ void *user_pool_data, wrdp_thpool_thread *t);
+
+ /* function called in wrdp_thpool_create to do additional initialization
+ * of user_pool_data
+ */
+ void (*custom_pool_create)(void *user_pool_data);
+
+ /* function called in wrdp_thpool_destroy to do additional cleanup of
+ * user_pool_data
+ */
+ void (*custom_pool_destroy)(void *user_pool_data);
+
+ /* function called on incomming mesdage with "void *user_data" directed
+ * to pool
+ */
+ void (*pool_message_handler)(void *user_data);
+
+ /* function called on incomming message with "void *user_data" directed
+ * to thread
+ */
+ void (*thread_message_handler)(void *user_data);
+};
diff --git a/src/core/ws_protocol.c b/src/core/ws_protocol.c
new file mode 100644
index 0000000..3bb8eba
--- /dev/null
+++ b/src/core/ws_protocol.c
@@ -0,0 +1,1206 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <ev.h>
+#include <json.h>
+#include <wslay/wslay.h>
+#include <curl/curl.h>
+
+#include <errno.h>
+#include "base64_url.h"
+
+#include <openssl/hmac.h>
+
+#include <webrdp_module_api.h>
+
+#include "wrdp_thpool.h"
+#include "wrdp_thpool_internals.h"
+#include "ws_session.h"
+#include "ws_protocol.h"
+#include "globals.h"
+#include "task.h"
+#include "thread_impl.h"
+#include "json_helpers.h"
+#include "curl_helpers.h"
+#include "backend_helpers.h"
+
+#include "utilities.h"
+
+#include "log.h"
+
+static bool
+token_check(ws_session *session)
+{
+ char *token = session->token_base64;
+ size_t token_len = strlen(token), raw_token_len = 0;
+ unsigned int raw_token_signature_len = 0;
+ /* used additional 2 bytes in buffer
+ * required for base64_url_decode */
+ uint8_t *raw_token = malloc(96 + 2), *raw_token_signature = 0;
+ if (!raw_token)
+ {
+ perror("malloc");
+ goto error;
+ }
+ {
+ char buf[128];
+ snprintf(buf, 127, "recieved token: %s", token);
+ log_msg(
+ (const uint8_t *)buf, strlen(buf), wrdp_log_level_trace, 0);
+ }
+ errno = base64_url_decode(
+ (uint8_t *)token, token_len, raw_token, token_len, &raw_token_len);
+ if (errno)
+ {
+ perror("token_check: base64_url_decode");
+ free(raw_token);
+ goto error;
+ }
+ if (raw_token_len != 96)
+ {
+ const char *msg = "token_check: raw_roken length != 96";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ free(raw_token);
+ goto error;
+ }
+ raw_token_signature = malloc(32);
+ if (!raw_token_signature)
+ {
+ perror("malloc");
+ free(raw_token);
+ goto error;
+ }
+ /* verify token */
+ {
+ HMAC_CTX *ctx = HMAC_CTX_new();
+ HMAC_Init_ex(ctx, g_globals.settings.secret_key_verify, 64,
+ EVP_sha256(), NULL);
+ HMAC_Update(ctx, raw_token, raw_token_len - 32);
+ HMAC_Final(ctx, raw_token_signature, &raw_token_signature_len);
+ HMAC_CTX_free(ctx);
+ if (raw_token_signature_len != 32)
+ {
+ const char *msg = "token signature validation failed"
+ " (incorrect result hash size)";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ free(raw_token);
+ free(raw_token_signature);
+ goto error;
+ }
+#ifdef DEBUG
+ {
+ log_msg((const uint8_t *)"old hash", strlen("old hash"),
+ wrdp_log_level_trace, wrdp_log_flag_binary);
+ log_msg(raw_token + 64, 32, wrdp_log_level_trace,
+ wrdp_log_flag_binary);
+ log_msg((const uint8_t *)"new hash", strlen("new hash"),
+ wrdp_log_level_trace, wrdp_log_flag_binary);
+ log_msg(raw_token_signature, 32, wrdp_log_level_trace,
+ wrdp_log_flag_binary);
+ }
+#endif
+ if (memcmp(raw_token + 64, raw_token_signature, 32))
+ {
+ const char *msg
+ = "token signature validation failed (result"
+ " hash differs)";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ free(raw_token);
+ free(raw_token_signature);
+ goto error;
+ }
+ }
+ /* extract and store base64-urlsafe encoded sid into session */
+ {
+ /* TODO: check if size calculation correct and optimal */
+ size_t sid_len = (4 * (64 / 3)) + 4 + 2, encoded_len = 0;
+ char *sid_base64 = calloc(sid_len, sizeof(char)), *ptr;
+ base64_url_encode(raw_token, 64, (uint8_t *)sid_base64, sid_len,
+ &encoded_len);
+ sid_base64[encoded_len] = 0;
+ ptr = realloc(sid_base64, encoded_len + 1);
+ if (!ptr)
+ {
+ //if realloc failed we still can use preveously
+ //allocated memory
+ perror("realloc");
+ }
+ else
+ {
+ sid_base64 = ptr;
+ }
+ session->sid_base64 = sid_base64;
+ }
+ /* reencrypt token */
+ {
+ HMAC_CTX *ctx = HMAC_CTX_new();
+ uint8_t resigned_raw_token[96] = {0};
+ size_t base64_len = 0;
+ HMAC_Init_ex(ctx, g_globals.settings.secret_key_sign, 64,
+ EVP_sha256(), NULL);
+ HMAC_Update(ctx, raw_token, raw_token_len - 32);
+ HMAC_Final(ctx, raw_token_signature, &raw_token_signature_len);
+ HMAC_CTX_free(ctx);
+ if (raw_token_signature_len != 32)
+ {
+ const char *msg
+ = "token signature generation failed (incorrect"
+ " result hash size)";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ free(raw_token);
+ free(raw_token_signature);
+ goto error;
+ }
+ memcpy(resigned_raw_token, raw_token, 64);
+ memcpy(resigned_raw_token + 64, raw_token_signature, 32);
+ errno = base64_url_encode(
+ resigned_raw_token, 96, (uint8_t *)token, 255, &base64_len);
+ if (errno)
+ {
+ free(raw_token);
+ free(raw_token_signature);
+ perror("token_verify: base64_url_encode");
+ goto error;
+ }
+ token[base64_len] = 0;
+ }
+ session->token_verified = true;
+ free(raw_token);
+ free(raw_token_signature);
+ return true;
+error:
+ session->session_state = ws_session_error;
+ free(session->token_base64);
+ session->token_base64 = 0;
+ free(session->backend_module_name);
+ session->backend_module_name = 0;
+ return false;
+}
+
+static bool
+handle_session_setting_element(
+ struct json_object_element_s *json_option, ws_session *session)
+{
+ task_info *info = session->task_info;
+ /* TODO: move timeouts to session ?
+ * cache timeouts settings until task_info created ?
+ */
+ /* if (!strncmp(json_option->name->string, "session_time_limit",
+ json_option->name->string_size))
+ {
+ info->settings.session_time_limit =
+ json_option_extract_int64 (json_option);
+ return true;
+ }
+ else if (!strncmp(json_option->name->string,
+ "session_idle_timeout", json_option->name->string_size))
+ {
+ info->settings.session_idle_timeout =
+ json_option_extract_int64 (json_option);
+ return true;
+ }
+ else */
+ if (!strncmp(json_option->name->string, "token",
+ json_option->name->string_size))
+ {
+ struct json_string_s *jsopt
+ = (struct json_string_s *)json_option->value->payload;
+ char *token_base64;
+ if (!jsopt->string_size)
+ {
+ const char *msg = "zero size token received";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_warning, 0);
+ return true;
+ }
+ token_base64 = malloc(jsopt->string_size + 1);
+ memcpy(token_base64, jsopt->string, jsopt->string_size);
+ token_base64[jsopt->string_size] = 0;
+ if (session->token_base64)
+ free(session->token_base64);
+ session->token_base64 = token_base64;
+ return true;
+ }
+ else if (!strncmp(json_option->name->string, "attach_sid",
+ json_option->name->string_size))
+ {
+ /* if (session->sid_base64)
+ {
+ free (session->sid_base64);
+ } */
+ struct json_string_s *jsopt
+ = (struct json_string_s *)json_option->value->payload;
+ char *attach_sid = malloc(jsopt->string_size + 1);
+ memcpy(attach_sid, jsopt->string, jsopt->string_size);
+ attach_sid[jsopt->string_size] = 0;
+ if (session->attach_sid_base64)
+ free(session->attach_sid_base64);
+ session->attach_sid_base64 = attach_sid;
+ return true;
+ }
+ else if (!strncmp(json_option->name->string, "proto",
+ json_option->name->string_size))
+ {
+ if (info && info->backend)
+ {
+ /* backend already created, possible by "proto" setting
+ * from web ui */
+ return true;
+ }
+ struct json_string_s *jsopt
+ = (struct json_string_s *)json_option->value->payload;
+ char *var = malloc(jsopt->string_size + 1);
+ memcpy(var, jsopt->string, jsopt->string_size);
+ var[jsopt->string_size] = 0;
+ if (session->backend_module_name)
+ free(session->backend_module_name);
+ session->backend_module_name = var;
+ return true;
+ }
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "handle_session_setting:"
+ " unhandled option: %.*s",
+ (int)(json_option->name->string_size),
+ json_option->name->string);
+ log_msg((const uint8_t *)buf, strlen(buf),
+ wrdp_log_level_warning, 0);
+ }
+ return false;
+}
+
+static bool
+handle_backend_setting_element(
+ struct json_object_element_s *json_option, ws_session *session)
+{
+ char option_name[json_option->name->string_size + 1];
+ strncpy(option_name, json_option->name->string,
+ json_option->name->string_size);
+ option_name[json_option->name->string_size] = 0;
+ switch (json_option->value->type)
+ {
+ case json_type_null:
+ case json_type_false:
+ {
+ if (!handle_backend_setting_int(
+ option_name, 0, session))
+ {
+ return false;
+ }
+ return true;
+ }
+ break;
+ case json_type_true:
+ {
+ if (!handle_backend_setting_int(
+ option_name, 1, session))
+ {
+ return false;
+ }
+ return true;
+ }
+ break;
+ case json_type_number:
+ {
+ int64_t num = json_option_extract_int64(json_option);
+ if (!handle_backend_setting_int(
+ option_name, num, session))
+ {
+ return false;
+ }
+ return true;
+ }
+ break;
+ case json_type_string:
+ {
+ struct json_string_s *jsopt
+ = (struct json_string_s *)
+ json_option->value->payload;
+ char value[jsopt->string_size + 1];
+ strncpy(value, jsopt->string, jsopt->string_size);
+ value[jsopt->string_size] = 0;
+
+ if (!strcmp(option_name, "dtsize"))
+ {
+ /* extract resolution width and height */
+ char *ptr = strchr(value, 'x');
+ if (!ptr)
+ return false;
+ char w[jsopt->string_size],
+ h[jsopt->string_size];
+ {
+ char *ptr2 = value;
+ int i;
+ for (i = 0; ptr2 != ptr; ++i, ++ptr2)
+ {
+ w[i] = ptr2[0];
+ }
+ w[i] = 0;
+ ptr++;
+ for (i = 0; ptr[i]; ++i)
+ {
+ h[i] = ptr[i];
+ }
+ h[i] = 0;
+ {
+ if (!handle_backend_setting_int(
+ "width", atoll(w),
+ session))
+ {
+ return false;
+ }
+ if (!handle_backend_setting_int(
+ "height", atoll(h),
+ session))
+ {
+ return false;
+ }
+ return true;
+ }
+ }
+ }
+ else
+ {
+ if (!handle_backend_setting_string(
+ option_name, value, session))
+ {
+ return false;
+ }
+ return true;
+ }
+ }
+ break;
+ default:
+ {
+ const char *msg = "handle_backend_setting:"
+ " unsupported json value type";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_warning, 0);
+ return true;
+ }
+ break;
+ }
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "handle_backend_setting:"
+ " unhandled option: %s",
+ option_name);
+ log_msg((const uint8_t *)buf, strlen(buf),
+ wrdp_log_level_warning, 0);
+ }
+ return true;
+}
+
+static void
+handle_json_session_option(
+ struct json_object_element_s *json_option, ws_session *session)
+{
+ switch (json_option->value->type)
+ {
+ case json_type_object:
+ {
+ if (memmem(json_option->name->string,
+ json_option->name->string_size,
+ "session_settings", strlen("session_settings")))
+ {
+ struct json_object_s *obj
+ = json_option->value->payload;
+ struct json_object_element_s *jsoption
+ = obj->start;
+ bool unhandled_option = false;
+ while (jsoption && !unhandled_option)
+ {
+ unhandled_option
+ = !handle_session_setting_element(
+ jsoption, session);
+ if (unhandled_option)
+ break;
+ jsoption = jsoption->next;
+ }
+ return;
+ }
+ }
+ break;
+ default:
+ {
+ const char *msg
+ = "handle_json_option: unsupported json value type"
+ " (not object)";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_warning, 0);
+ return;
+ }
+ break;
+ }
+}
+
+static void
+handle_json_session_options(struct json_value_s *root, ws_session *session)
+{
+ struct json_object_s *object = (struct json_object_s *)root->payload;
+ struct json_object_element_s *option = object->start;
+ while (option)
+ {
+ handle_json_session_option(option, session);
+ option = option->next;
+ }
+}
+
+static void
+handle_json_backend_option(
+ struct json_object_element_s *json_option, ws_session *session)
+{
+ switch (json_option->value->type)
+ {
+ case json_type_object:
+ {
+ if (memmem(json_option->name->string,
+ json_option->name->string_size,
+ "backend_settings", strlen("backend_settings")))
+ {
+ struct json_object_s *obj
+ = json_option->value->payload;
+ struct json_object_element_s *jsoption
+ = obj->start;
+ bool unhandled_option = false;
+ while (jsoption && !unhandled_option)
+ {
+ unhandled_option
+ = !handle_backend_setting_element(
+ jsoption, session);
+ if (unhandled_option)
+ break;
+ jsoption = jsoption->next;
+ }
+ }
+ }
+ break;
+ default:
+ {
+ const char *msg
+ = "handle_json_option: unsupported json value type"
+ " (not object)";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_warning, 0);
+ }
+ break;
+ }
+}
+
+static void
+handle_json_backend_options(struct json_value_s *root, ws_session *session)
+{
+ struct json_object_s *object = (struct json_object_s *)root->payload;
+ struct json_object_element_s *option = object->start;
+ while (option)
+ {
+ handle_json_backend_option(option, session);
+ option = option->next;
+ }
+}
+
+static bool
+ws_handle_json_settings_array(
+ char *json_buf, size_t json_buf_len, ws_session *session)
+{
+ struct json_parse_result_s res = {0};
+ struct json_value_s *root = json_parse_ex(
+ json_buf, json_buf_len, json_parse_flags_allow_json5, 0, 0, &res);
+#ifdef DEBUG
+ {
+ const size_t msg_len = json_buf_len + 256;
+ char msg[msg_len];
+ snprintf(msg, msg_len, "%s: %s\n", "received json data:\n",
+ json_buf);
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_trace, 0);
+ }
+#endif
+ if (!root)
+ {
+ const char *msg = "Failed to parse auth data json";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ return false;
+ }
+ /* handle json data
+ * assume what this is one level array
+ * of json options for backend or session*/
+ handle_json_session_options(root, session);
+ handle_json_backend_options(root, session);
+
+ if (!session->backend_module_name)
+ {
+ free(session->token_base64);
+ session->token_base64 = 0;
+ free(root);
+ return false;
+ }
+ if (!(session->token_verified) && session->token_base64)
+ {
+ if (!token_check(session))
+ {
+ free(root);
+ return false;
+ }
+ }
+ free(root);
+ return true;
+}
+
+bool
+ws_handle_token_reply_json(
+ uint8_t *_json_buf, ws_session *session, void *userdata)
+{
+ char *json_buf = (char *)_json_buf;
+ task_info *info = 0, *old_info = session->task_info;
+ if (!ws_handle_json_settings_array(json_buf, strlen(json_buf), session))
+ {
+ return false;
+ }
+ if (!backend_get(session->backend_module_name, session))
+ {
+ free(session->backend_module_name);
+ session->backend_module_name = 0;
+ return false;
+ }
+ else
+ {
+ backend_fill_settings(session);
+ }
+ free(session->attach_sid_base64);
+ session->attach_sid_base64 = 0;
+ info = session->task_info;
+
+ if (!info->backend)
+ {
+ const char *msg = "backend type(proto)setting does not set";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ return false;
+ }
+ if (info == old_info
+ && !info->backend->callbacks_module->init(
+ info->backend->backend_internals))
+ {
+ return false;
+ }
+ session->session_state = ws_session_started;
+ {
+ uint8_t *data = 0;
+ size_t data_size
+ = curl_prepare_post_request_data(&data, session);
+ curl_request_info *request = curl_init_request(
+ session, curl_request_type_post, data, data_size, 0, 0, 0);
+ free(data);
+ curl_request(request);
+ }
+ return true;
+}
+
+static bool
+validate_msg_size(
+ size_t msg_size, size_t expected_size, ws_input_codes msg_code)
+{
+ if (msg_size > expected_size)
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "ws_protocol: message size is: %ld, expected size"
+ " is: %ld for message type %d",
+ msg_size, expected_size, msg_code);
+ log_msg((const uint8_t *)buf, strlen(buf),
+ wrdp_log_level_warning, 0);
+ return true;
+ }
+ else if (msg_size < expected_size)
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "ws_protocol: message size is: %ld, expected size"
+ " is: %ld for message type %d",
+ msg_size, expected_size, msg_code);
+ log_msg(
+ (const uint8_t *)buf, strlen(buf), wrdp_log_level_error, 0);
+ return false;
+ }
+ return true;
+}
+
+static void
+generate_random_session_sid(ws_session *session)
+{
+ size_t sid_len = (4 * (64 / 3)) + 4 + 2, encoded_len = 0;
+ char *sid_base64 = calloc(sid_len, sizeof(char)), *ptr;
+ uint8_t random_data[64];
+ random_bytes(random_data, 64);
+ base64_url_encode(
+ random_data, 64, (uint8_t *)sid_base64, sid_len, &encoded_len);
+ sid_base64[encoded_len] = 0;
+ ptr = realloc(sid_base64, encoded_len + 1);
+ if (!ptr)
+ {
+ /* if realloc failed we still can use preveously allocated
+ * memory
+ */
+ perror("realloc");
+ }
+ else
+ {
+ sid_base64 = ptr;
+ }
+ session->sid_base64 = sid_base64;
+}
+
+bool
+ws_handle_message(
+ const struct wslay_event_on_msg_recv_arg *msg, ws_session *session)
+{
+ uint32_t hcode1 = 0;
+ task_info *info = session->task_info;
+ if (msg->msg_length < 4)
+ {
+ const char *msg = "Error: websocket message is too short";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ return false;
+ }
+ memcpy(&hcode1, msg->msg, 4);
+ if (session->session_state == ws_session_initial)
+ {
+ switch (hcode1)
+ {
+ case ws_in_credential_json:
+ {
+ size_t str_len = (msg->msg_length - 4) / 4;
+ char *str = malloc(str_len + 1);
+ if (!str)
+ {
+ perror("malloc");
+ return false;
+ }
+ {
+ size_t i, pos = 4;
+ for (i = 0; i < str_len; ++i, pos += 4)
+ {
+ str[i] = msg->msg[pos];
+ }
+ }
+ str[str_len] = 0;
+ if (!ws_handle_json_settings_array(
+ str, str_len, session))
+ {
+ free(str);
+ return false;
+ }
+ free(str);
+
+ /* backend and task_info created/set inside
+ * "ws_handle_json_settings_array", refresh
+ * pointer here */
+ info = session->task_info;
+
+ /* TODO: DEBUG BEGIN*/
+ //use_token = true;
+ /* DEBUG END */
+ if (!session->token_base64)
+ {
+ if (!info || !info->backend)
+ {
+ const char *msg
+ = "backend "
+ "type(proto)setting"
+ "does not set";
+ log_msg((const uint8_t *)msg,
+ strlen(msg),
+ wrdp_log_level_error, 0);
+ return false;
+ }
+ if (!info->backend->callbacks_module
+ ->init(
+ info->backend
+ ->backend_internals))
+ {
+ return false;
+ }
+ /* token is not used, we need to
+ * generate random sid */
+ generate_random_session_sid(session);
+ session->session_state
+ = ws_session_started;
+
+ if (session->token_base64)
+ {
+ uint8_t *data = 0;
+ size_t data_size
+ = curl_prepare_post_request_data(
+ &data, session);
+ curl_request_info *request
+ = curl_init_request(session,
+ curl_request_type_post,
+ data, data_size, 0, 0,
+ 0);
+ free(data);
+ curl_request(request);
+ }
+ }
+ else
+ {
+ curl_request_info *request
+ = curl_init_request(session,
+ curl_request_type_get, 0, 0,
+ ws_handle_token_reply_json, 0,
+ 0);
+ bool ok = curl_request(request);
+ if (!ok)
+ {
+ return false;
+ }
+ return true;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ else if (session->session_state == ws_session_started)
+ {
+ switch (hcode1)
+ {
+ case ws_in_specialcomb:
+ {
+ uint32_t *hcode2 = (uint32_t *)msg->msg + 1;
+ ws_input_keycomb comb;
+ if (!info->backend->callbacks_input->kcomb)
+ break;
+ if (!validate_msg_size(msg->msg_length - 4,
+ sizeof(uint32_t), ws_in_specialcomb))
+ {
+ return false;
+ }
+ switch (*hcode2)
+ {
+ case 0:
+ comb
+ = ws_keycomb_ctrlaltdel_press;
+ break;
+ case 1:
+ comb = ws_keycomb_alttab_press;
+ break;
+ case 2:
+ comb
+ = ws_keycomb_alttab_release;
+ break;
+ default:
+ /* this should never happen */
+ comb
+ = ws_keycomb_ctrlaltdel_press;
+ break;
+ }
+ if (!info->backend->callbacks_input->kcomb(
+ comb, info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ break;
+ case ws_in_mouse:
+ {
+ if (!info->backend->callbacks_input->mouse)
+ break;
+ typedef struct
+ {
+ uint32_t op;
+ uint32_t flags;
+ uint32_t x;
+ uint32_t y;
+ } wsmsg;
+ if (!validate_msg_size(msg->msg_length,
+ sizeof(wsmsg), ws_in_mouse))
+ {
+ return false;
+ }
+ const wsmsg *m = (const wsmsg *)msg->msg;
+ ws_input_mouse mi;
+ mi.flags = m->flags;
+ mi.x = m->x;
+ mi.y = m->y;
+ if (!info->backend->callbacks_input->mouse(
+ mi, info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ break;
+ case ws_in_kupdown:
+ {
+ if (!info->backend->callbacks_input->kupdown)
+ break;
+ typedef struct
+ {
+ uint32_t op;
+ uint32_t down;
+ uint32_t code;
+ } wsmsg;
+ if (!validate_msg_size(msg->msg_length,
+ sizeof(wsmsg), ws_in_kupdown))
+ {
+ return false;
+ }
+ const wsmsg *m = (const wsmsg *)msg->msg;
+ ws_input_kupdown mi;
+ mi.code = m->code;
+ mi.down = m->down;
+ if (!info->backend->callbacks_input->kupdown(
+ mi, info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ break;
+ case ws_in_kpress:
+ {
+ if (!info->backend->callbacks_input->kpress)
+ break;
+ typedef struct
+ {
+ uint32_t op;
+ uint32_t shiftstate;
+ uint32_t code;
+ } wsmsg;
+ if (!validate_msg_size(msg->msg_length,
+ sizeof(wsmsg), ws_in_kpress))
+ {
+ return false;
+ }
+ const wsmsg *m = (const wsmsg *)msg->msg;
+ ws_input_kpress mi;
+ mi.code = m->code;
+ mi.shiftstate = m->shiftstate;
+ if (!info->backend->callbacks_input->kpress(
+ mi, info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ break;
+ case ws_in_unicode:
+ {
+ if (!info->backend->callbacks_input->unicode)
+ break;
+ ws_input_unicode mu;
+ mu.length = msg->msg_length - 4;
+ mu.input = (const uint32_t *)msg->msg + 4;
+ if (!info->backend->callbacks_input->unicode(
+ mu, info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ break;
+ case ws_in_clipbrd_data_request:
+ {
+ if (!info->backend->callbacks_clipbrd
+ ->request_data)
+ {
+ const char *msg
+ = "wrdp_backend_cb_clipboard->"
+ "request_data"
+ " not set";
+ log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_warning,
+ 0);
+ break;
+ }
+ if (!validate_msg_size(msg->msg_length - 4,
+ sizeof(uint8_t),
+ ws_in_clipbrd_data_request))
+ {
+ return false;
+ }
+ wrdp_backend_clipbrd_data_request req;
+ req.format = *((uint8_t *)(msg->msg + 4));
+ if (!info->backend->callbacks_clipbrd
+ ->request_data(&req,
+ info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ break;
+ case ws_in_clipbrd_changed:
+ {
+ if (!info->backend->callbacks_clipbrd
+ ->data_changed)
+ {
+ const char *msg
+ = "wrdp_backend_cb_clipboard->"
+ "data_changed not set";
+ log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_warning,
+ 0);
+ break;
+ }
+ wrdp_backend_clipbrd_fmts fmts;
+ fmts.count = msg->msg_length - 4;
+ fmts.formats = (uint8_t *)msg->msg + 4;
+ if (!info->backend->callbacks_clipbrd
+ ->data_changed(&fmts,
+ info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ break;
+ case ws_in_clipbrd_data:
+ {
+ if (!info->backend->callbacks_clipbrd
+ ->send_data)
+ {
+ const char *msg = "wrdp_backend_cb_"
+ "clipboard->send_data"
+ " not set";
+ log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_warning,
+ 0);
+ break;
+ }
+ wrdp_backend_clipbrd_data data;
+ data.size = msg->msg_length - 4;
+ data.data = (uint8_t *)msg->msg + 4;
+ if (!info->backend->callbacks_clipbrd
+ ->send_data(&data,
+ info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ break;
+ case ws_in_ft_request:
+ {
+ if (!info->backend->callbacks_ft->request)
+ {
+ const char *msg
+ = "wrdp_backend_cb_filetransfer->"
+ "request not set";
+ log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_warning,
+ 0);
+ break;
+ }
+ if ((msg->msg_length - 4) < sizeof(uint16_t))
+ {
+ const char *msg
+ = "ws_protocol: ws_in_ft_request "
+ "message"
+ " is too small";
+ log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_error,
+ 0);
+ return false;
+ }
+ wrdp_backend_ft_file_request req;
+ if (!validate_msg_size(msg->msg_length - 4,
+ 4 + 8 + 8, ws_in_ft_request))
+ {
+ return false;
+ }
+ memcpy(&(req.file_id), msg->msg + 4, 4);
+ memcpy(&(req.req_size), msg->msg + 4 + 4, 8);
+ memcpy(&(req.file_offset), msg->msg + 4 + 4 + 8,
+ 8);
+ if (!info->backend->callbacks_ft->request(
+ &req, info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ break;
+ case ws_in_ft_chunk:
+ {
+ if (!info->backend->callbacks_ft->chunk)
+ {
+ const char *msg = "wrdp_backend_cb_"
+ "filetransfer->chunk"
+ " not set";
+ log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_warning,
+ 0);
+ break;
+ }
+ if ((msg->msg_length - 4)
+ < sizeof(wrdp_backend_ft_chunk))
+ {
+ const char *msg
+ = "ws_protocol: ws_in_ft_chunk "
+ "message"
+ " is too small";
+ log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_error,
+ 0);
+ return false;
+ }
+ const wrdp_backend_ft_chunk *c
+ = (wrdp_backend_ft_chunk *)(msg->msg + 4);
+ if (!validate_msg_size(msg->msg_length - 4,
+ sizeof(wrdp_backend_ft_chunk) + c->size,
+ ws_in_ft_chunk))
+ {
+ return false;
+ }
+ const uint8_t *data = (const uint8_t
+ *)(msg->msg + 4
+ + sizeof(wrdp_backend_ft_chunk));
+ if (!info->backend->callbacks_ft->chunk(c, data,
+ info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ break;
+ case ws_in_ft_finished:
+ {
+ if (!info->backend->callbacks_ft->finish)
+ {
+ const char *msg = "wrdp_backend_cb_"
+ "filetransfer->finish"
+ " not set";
+ log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_warning,
+ 0);
+ break;
+ }
+ if (!validate_msg_size(msg->msg_length - 4,
+ sizeof(wrdp_backend_ft_status),
+ ws_in_ft_finished))
+ {
+ return false;
+ }
+ const wrdp_backend_ft_status *f
+ = (wrdp_backend_ft_status *)(msg->msg + 4);
+ if (!info->backend->callbacks_ft->finish(
+ f, info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ break;
+ default:
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "protocol error, unsupported message code"
+ " %d",
+ hcode1);
+ log_msg((const uint8_t *)buf, strlen(buf),
+ wrdp_log_level_warning, 0);
+ return false;
+ }
+ break;
+ }
+ }
+ return true;
+}
+
+static void
+ev_con_w_cb(struct ev_loop *loop, ev_io *w, int revents)
+{
+ ws_session *s = w->data;
+ if (wslay_event_want_write(s->wslay_ctx))
+ {
+ wslay_event_send(s->wslay_ctx);
+ }
+ if (!wslay_event_want_write(s->wslay_ctx))
+ {
+ ev_io_stop(loop, w);
+ }
+}
+
+static void
+ws_send(const uint8_t *buf, size_t buf_size, uint8_t opcode, void *_ws_session)
+{
+ ws_session *session = _ws_session;
+ task_info *info = session->task_info;
+ wrdp_thpool_task *t = info->wrdp_thpool_task;
+ struct wslay_event_msg msgarg;
+ if (info->stopped)
+ {
+ return;
+ }
+ msgarg.opcode = opcode;
+ msgarg.msg = buf;
+ msgarg.msg_length = buf_size;
+ wslay_event_queue_msg(session->wslay_ctx, &msgarg);
+ if (wslay_event_want_write(session->wslay_ctx)
+ && !ev_is_active(&(session->ev_con_fd_w)))
+ {
+ ev_io *io = &(session->ev_con_fd_w);
+ ev_io_init(io, ev_con_w_cb, session->connection_fd, EV_WRITE);
+ io->data = session;
+ ev_io_start(t->thread->ev_th_loop, io);
+ }
+}
+
+static void
+ws_send_impl(
+ const uint8_t *buf, size_t buf_size, void *_task_info, uint8_t type)
+{
+ task_info *info = _task_info;
+ SLIST_HEAD(sessions_head, ws_session_list_entry_s) *sessions_list_head_p
+ = info->backend->sessions_list_head;
+ if (!SLIST_EMPTY(sessions_list_head_p))
+ {
+ for (struct ws_session_list_entry_s *s
+ = SLIST_FIRST(sessions_list_head_p);
+ s; s = SLIST_NEXT(s, entries))
+ {
+ if (s->session)
+ ws_send(buf, buf_size, type, s->session);
+ }
+ }
+}
+
+void
+ws_send_text(const uint8_t *buf, size_t buf_size, void *_task_info)
+{
+ ws_send_impl(buf, buf_size, _task_info, 1);
+}
+
+void
+ws_send_binary(const uint8_t *buf, size_t buf_size, void *_task_info)
+{
+ ws_send_impl(buf, buf_size, _task_info, 2);
+}
+
+uint8_t *
+ws_pack_msg(const uint8_t *buf, size_t buf_size, uint32_t msg_code)
+{
+ /* TODO: avoid copying data somehow */
+ uint8_t *msg = malloc(buf_size + 4);
+ if (!msg)
+ {
+ /* TODO: handle error */
+ perror("malloc");
+ return 0;
+ }
+ memcpy(msg, &msg_code, 4);
+ if (buf)
+ {
+ memcpy(msg + 4, buf, buf_size);
+ }
+ return msg;
+}
diff --git a/src/core/ws_protocol.h b/src/core/ws_protocol.h
new file mode 100644
index 0000000..d961fa2
--- /dev/null
+++ b/src/core/ws_protocol.h
@@ -0,0 +1,79 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+/**
+ * OP-Codes, sent from the (JavaScript)
+ * client to the server.
+ */
+typedef enum
+{
+ ws_in_mouse = 0, /* input is ws_input_mouse */
+ ws_in_kupdown, /* input is ws_input_kupdown */
+ ws_in_kpress, /* input is uint32_t code */
+ ws_in_specialcomb,
+ ws_in_credential_json,
+ ws_in_unicode, /* input is wchar_t* string stored into uint32_t array */
+
+ /* DRAFT */
+ ws_in_clipbrd_changed, /* message with new clipboard information */
+ ws_in_clipbrd_data, /* message with clipboard data in requested fromat
+ */
+ ws_in_clipbrd_data_request, /* message containing clipboarddata request
+ * of specified format */
+ ws_in_ft_request, /* request filetransfer */
+ ws_in_ft_chunk, /* filetransfer chunk from client */
+ ws_in_ft_finished, /* filetransfer from client finished */
+ /* last */
+ ws_in_unused
+} ws_input_codes;
+
+typedef enum
+{
+ ws_out_beginpaint = 0,
+ ws_out_endpaint,
+ ws_out_bitmap,
+ ws_out_opaquerect,
+ ws_out_setbounds,
+ ws_out_patblt,
+ ws_out_multi_opaquerect,
+ ws_out_scr_btl,
+ ws_out_ptr_new,
+ ws_out_ptr_free,
+ ws_out_ptr_set,
+ ws_out_ptr_set_null,
+ ws_out_ptr_set_default,
+ /* DRAFT! */
+ ws_out_clpbrd_changed,
+ ws_out_clpbrd_data,
+ ws_out_clpbrd_request_data,
+ ws_out_ft_request,
+ ws_out_ft_chunk,
+ ws_out_ft_finish,
+ /* last */
+ ws_out_last
+} ws_output_codes;
+
+bool ws_handle_message(
+ const struct wslay_event_on_msg_recv_arg *msg, ws_session *session);
+
+/* send text websocket message */
+void ws_send_text(const uint8_t *buf, size_t buf_size, void *_task_info);
+
+/* send binary websocket message */
+void ws_send_binary(const uint8_t *buf, size_t buf_size, void *_task_info);
+
+/* allocate new buffer of size msg_len + 4 bytes,
+ * prepend msg_code to msg_data
+ */
+uint8_t *ws_pack_msg(const uint8_t *buf, size_t buf_size, uint32_t msg_code);
+
+/* handle json response from external auth server */
+bool ws_handle_token_reply_json(
+ uint8_t *json_buf, ws_session *session, void *userdata);
diff --git a/src/core/ws_server_internals.h b/src/core/ws_server_internals.h
new file mode 100644
index 0000000..59842d3
--- /dev/null
+++ b/src/core/ws_server_internals.h
@@ -0,0 +1,7 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once \ No newline at end of file
diff --git a/src/core/ws_session.c b/src/core/ws_session.c
new file mode 100644
index 0000000..276ca9a
--- /dev/null
+++ b/src/core/ws_session.c
@@ -0,0 +1,636 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <fcntl.h>
+#include <netdb.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+/*
+ * HTTP and Websocket serialization/deserializtion headers
+ */
+
+#include <picohttpparser.h>
+#include <utils/base64.h>
+
+#include <webrdp_module_api.h>
+
+#include "globals.h"
+#include "ws_session.h"
+#include "ws_server_internals.h"
+#include "ws_protocol.h"
+#include "thread_impl.h"
+#include "utilities.h"
+#include "wrdp_thpool.h"
+#include "wrdp_thpool_internals.h"
+#include "task.h"
+#include "socket_helpers.h"
+
+#include "log.h"
+
+static int ws_server_socket = -1;
+static int ws_server_socket_unix = -1;
+
+int
+ws_server_init()
+{
+ if (g_globals.settings.ws_port <= 0)
+ {
+ ws_server_socket = -1;
+ return ws_server_socket;
+ }
+ ws_server_socket = create_listen_socket_tcp(g_globals.settings.ws_port);
+ if (ws_server_socket != -1)
+ {
+ socket_make_non_block(ws_server_socket);
+ }
+ return ws_server_socket;
+}
+
+int
+ws_server_init_unix()
+{
+ if (!g_globals.settings.ws_socket_path
+ || !g_globals.settings.ws_socket_path[0])
+ {
+ ws_server_socket_unix = -1;
+ return ws_server_socket_unix;
+ }
+ ws_server_socket_unix
+ = create_listen_socket_unix(g_globals.settings.ws_socket_path);
+
+ if (ws_server_socket_unix != -1)
+ {
+ socket_make_non_block(ws_server_socket_unix);
+ }
+ return ws_server_socket_unix;
+}
+
+static bool
+check_http_header_value_no_case(const struct phr_header *hdr, const char *value)
+{
+ if (hdr->value_len != strlen(value))
+ return false;
+ if (!strncasecmp(hdr->value, value, strlen(value)))
+ return true;
+ return false;
+}
+
+/*static bool
+check_http_header_value(const struct phr_header *hdr, const char *value)
+{
+ if (memmem(hdr->value, hdr->value_len, value, strlen(value)))
+ return true;
+ return false;
+}*/
+
+static struct phr_header *
+find_http_header(
+ struct phr_header *headers, size_t num_headers, const char *header_name)
+{
+ size_t i;
+ for (i = 0; i != num_headers; ++i)
+ {
+ if (!strncmp(headers[i].name, header_name, headers[i].name_len))
+ return &headers[i];
+ }
+ return 0;
+}
+
+#ifdef DEBUG
+
+static void
+print_http_header(const struct phr_header header, char *buf)
+{
+ char msg_buf[4096] = {0};
+ char name_buf[1024] = {0}, value_buf[3072] = {0};
+ memcpy(name_buf, header.name, header.name_len);
+ memcpy(value_buf, header.value, header.value_len);
+ snprintf(msg_buf, 4095, "\n%s: %s", name_buf, value_buf);
+ strcat(buf, msg_buf);
+}
+
+static void
+print_http_headers(struct phr_header *headers, size_t num_headers)
+{
+ size_t i;
+ char msg[81920] = {0};
+ strcpy(msg, "http headers:\n");
+ for (i = 0; i != num_headers; ++i)
+ {
+ print_http_header(headers[i], msg);
+ }
+ log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_trace, 0);
+}
+
+#endif
+
+typedef struct
+{
+ struct phr_header *hdr_key, *hdr_ext;
+ const char *path;
+ size_t path_len;
+} http_get_request_info;
+
+#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+
+static int
+create_accept_key(uint8_t *dst, const char *client_key, size_t *base64_len)
+{
+ uint8_t sha1buf[20], key_src[60];
+ memcpy(key_src, client_key, 24);
+ memcpy(key_src + 24, WS_GUID, 36);
+ if (!sha1(sha1buf, key_src, sizeof(key_src)))
+ return false;
+ return base64_encode(sha1buf, 20, dst, 60, base64_len);
+}
+
+typedef struct
+{
+ size_t pos;
+ /* Should not be touched by user */
+ ev_io *socket_fd_writeable;
+ bool can_write;
+} ws_s_a_s_i_internals;
+
+typedef struct
+{
+ /* initialized task info */
+ ws_session *session;
+ /* user allocated buffer filled with data for sending via socket
+ * freed by function */
+ void *buf;
+ /* buffer size */
+ size_t buf_size;
+ /* initialzed connection socket returned by accept
+ * set in non-blocking mode */
+ int socket_fd;
+
+ void (*write_done_cb)(ws_session *session, bool success);
+
+ ws_s_a_s_i_internals internals;
+} ws_server_async_send_info;
+
+static void
+ev_connection_writeable_cb(struct ev_loop *loop, ev_io *w, int revents)
+{
+ ws_server_async_send_info *w_info = w->data;
+ ssize_t len = 0;
+ if ((len = send(w_info->socket_fd, w_info->buf + w_info->internals.pos,
+ w_info->buf_size - w_info->internals.pos, MSG_DONTWAIT))
+ == -1)
+ {
+ if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK)
+ {
+ perror("send");
+ /* TODO: handle send error */
+ w_info->write_done_cb(w_info->session, false);
+ goto cleanup;
+ }
+ }
+ w_info->internals.pos += len;
+ if (w_info->internals.pos == w_info->buf_size)
+ {
+ /* Writing done.
+ * Doing cleanup. */
+ w_info->write_done_cb(w_info->session, true);
+ goto cleanup;
+ }
+ return;
+cleanup:
+ ev_io_stop(loop, w);
+ free(w); /* NOTE: this is "info->internals.socket_fd_writeable" */
+ free(w_info->buf);
+ free(w_info);
+}
+
+/* ws_server_async_send_info *info must be allocated by caller
+ * will be freed by function */
+static bool
+ws_server_async_send(ws_server_async_send_info *info)
+{
+ info->internals.socket_fd_writeable = calloc(1, sizeof(ev_io));
+ wrdp_thpool_task *task = info->session->wrdp_thpool_task;
+ if (!info->internals.socket_fd_writeable)
+ {
+ /* TODO: handle allocation error */
+ free(info->buf);
+ perror("calloc");
+ return false;
+ }
+ ev_io_init(info->internals.socket_fd_writeable,
+ ev_connection_writeable_cb, info->socket_fd, EV_WRITE);
+ info->internals.socket_fd_writeable->data = info;
+ ev_io_start(
+ task->thread->ev_th_loop, info->internals.socket_fd_writeable);
+ return true;
+}
+
+static ssize_t
+ws_send_callback(wslay_event_context_ptr ctx, const uint8_t *data, size_t len,
+ int flags, void *user_data)
+{
+ ws_session *session = user_data;
+ ssize_t r = 0;
+ int sflags = MSG_DONTWAIT;
+ r = send(session->connection_fd, data, len, sflags);
+ if (r == -1)
+ {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+ {
+ wslay_event_set_error(ctx, WSLAY_ERR_WOULDBLOCK);
+ }
+ else
+ {
+ wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE);
+ }
+ }
+ return r;
+}
+
+static ssize_t
+ws_recv_callback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len,
+ int flags, void *user_data)
+{
+ ws_session *session = user_data;
+ ssize_t r;
+ r = recv(session->connection_fd, buf, len, MSG_DONTWAIT);
+ if (r == -1)
+ {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+ {
+ wslay_event_set_error(ctx, WSLAY_ERR_WOULDBLOCK);
+ }
+ else
+ {
+ wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE);
+ task_destroy_client_connection(session);
+ }
+ }
+ else if (r == 0)
+ {
+ /* Unexpected EOF is also treated as an error */
+ wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE);
+ r = -1;
+ }
+ return r;
+}
+
+static void
+ws_on_msg_recv_callback(wslay_event_context_ptr ctx,
+ const struct wslay_event_on_msg_recv_arg *arg, void *_ws_session)
+{
+ if (!wslay_is_ctrl_frame(arg->opcode))
+ {
+ if (!ws_handle_message(arg, _ws_session))
+ {
+ log_msg_info i;
+ i.ws_session = _ws_session;
+ i.level = wrdp_log_level_warning;
+ i.buf = (const uint8_t
+ *)"Failed to handle websocket message.";
+ log_msg_ex(&i);
+ }
+ }
+}
+
+void
+ws_server_websocket_init(ws_session *session)
+{
+ struct wslay_event_callbacks callbacks = {ws_recv_callback,
+ ws_send_callback, NULL, NULL, NULL, NULL, ws_on_msg_recv_callback};
+ /* TODO: set TCP_NODELAY only for tcp socket, and not for unix */
+ /* int val = 1;
+
+ if (setsockopt (server->connection_fd,
+ IPPROTO_TCP,
+ TCP_NODELAY,
+ &val,
+ (socklen_t)sizeof (val)) == -1)
+ {
+ perror ("setsockopt: TCP_NODELAY");
+ thr_destroy_task (task);
+ return;
+ } */
+ wslay_event_context_server_init(
+ &(session->wslay_ctx), &callbacks, session);
+}
+
+static void
+ws_server_send_http_reply_w_cb(ws_session *session, bool success)
+{
+ if (!success)
+ {
+ log_msg_info i;
+ i.ws_session = session;
+ i.level = wrdp_log_level_warning;
+ i.buf = (const uint8_t *)"ws_session: Failed to send reply "
+ "with http protocol upgrade.";
+ log_msg_ex(&i);
+ /* TODO: handle error */
+ return;
+ }
+ session->http_state = ws_server_state_ws_running;
+ ws_server_websocket_init(session);
+}
+
+static void
+ws_server_send_http_reply(http_get_request_info info, ws_session *session)
+{
+ char accept_key[60] = {0}, res_header[255] = {0};
+ int header_len = 0;
+ size_t base64_len = 0 /*, written = 0*/;
+ create_accept_key(
+ (uint8_t *)accept_key, info.hdr_key->value, &base64_len);
+ header_len = snprintf(res_header, sizeof(res_header),
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: %.*s\r\n"
+ "\r\n",
+ (int)base64_len, accept_key);
+ {
+ ws_server_async_send_info *info
+ = calloc(1, sizeof(ws_server_async_send_info));
+ if (!info)
+ {
+ /* TODO: handle allocation error */
+ perror("calloc");
+ return;
+ }
+ info->buf = strdup(res_header);
+ info->buf_size = header_len;
+ info->socket_fd = session->connection_fd;
+ info->write_done_cb = ws_server_send_http_reply_w_cb;
+ info->session = session;
+ /* here is no memory leak, info is freed by ws_server_async_send
+ */
+ ws_server_async_send(info);
+ }
+}
+
+static bool
+ws_server_handle_http_get(const char *path, size_t path_len,
+ struct phr_header *headers, size_t num_headers, ws_session *session)
+{
+ struct phr_header *hdr_ws_key
+ = find_http_header(headers, num_headers, "Sec-WebSocket-Key"),
+ /* *hdr_connection = 0, */ *hdr_upgrade = 0, *hdr_ws_ext = 0;
+#ifdef DEBUG
+ print_http_headers(headers, num_headers);
+#endif
+ if (!hdr_ws_key)
+ {
+ log_msg_info i;
+ i.ws_session = session;
+ i.level = wrdp_log_level_warning;
+ i.buf = (const uint8_t *)"ws_session: error: Sec-WebSocket-Key "
+ "header not found";
+ log_msg_ex(&i);
+ return false;
+ }
+ if (hdr_ws_key->value_len != 24)
+ {
+ log_msg_info i;
+ i.ws_session = session;
+ i.level = wrdp_log_level_warning;
+ i.buf = (const uint8_t *)"ws_session: error: Sec-WebSocket-Key "
+ "header value length != 24";
+ log_msg_ex(&i);
+ return false;
+ }
+ /* hdr_connection = find_http_header (headers, num_headers,
+ "Connection"); if (!hdr_connection) return false; if
+ (!check_http_header_value (hdr_connection, "Upgrade")) return
+ false;*/
+ hdr_upgrade = find_http_header(headers, num_headers, "Upgrade");
+ if (!hdr_upgrade)
+ {
+ log_msg_info i;
+ i.ws_session = session;
+ i.level = wrdp_log_level_warning;
+ i.buf = (const uint8_t
+ *)"ws_session: error: Upgrade header not found";
+ log_msg_ex(&i);
+ return false;
+ }
+ if (!check_http_header_value_no_case(hdr_upgrade, "websocket"))
+ {
+ log_msg_info i;
+ i.ws_session = session;
+ i.level = wrdp_log_level_warning;
+ i.buf = (const uint8_t *)"ws_session: error: Upgrade header "
+ "value != websocket";
+ log_msg_ex(&i);
+ return false;
+ }
+ hdr_ws_ext = find_http_header(
+ headers, num_headers, "Sec-WebSocket-Extensions");
+ http_get_request_info info = {0};
+ info.hdr_key = hdr_ws_key;
+ info.hdr_ext = hdr_ws_ext;
+ info.path = path;
+ info.path_len = path_len;
+ ws_server_send_http_reply(info, session);
+
+ return true;
+}
+
+static bool
+ws_server_socket_read(void *taskdata)
+{
+ ws_session *session = taskdata;
+ ssize_t read_size = 0;
+
+ /* int err = 0;
+ socklen_t slen = sizeof (err);
+ getsockopt (socket, SOL_SOCKET, SO_ERROR, &err, &slen);
+ if(err)
+ return false; */
+ session->prev_read_size = session->read_size;
+ while (
+ (read_size = recv(session->connection_fd,
+ session->read_buf + session->read_size,
+ sizeof(session->read_buf) - session->read_size, MSG_DONTWAIT))
+ == -1
+ && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK))
+ {
+ }
+ if (read_size == -1)
+ {
+ perror("recv");
+ goto error;
+ }
+ if (!read_size)
+ {
+ const char *msg = "remote host closed connection";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_debug, 0);
+ goto cleanup;
+ }
+ session->read_size += read_size;
+ return true;
+error:
+{
+ const char *msg = "connection error occurs, destroying task";
+ log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+}
+cleanup:
+ return false;
+}
+
+static bool
+ws_server_handle_http_request(ws_session *session)
+{
+ char *method, *path;
+ int pret, minor_version;
+ const short header_count = 256;
+ struct phr_header headers[header_count];
+ size_t method_len, path_len, num_headers = header_count;
+#ifdef DEBUG
+ {
+ const char *msg_base = "RAW HTTP request:\n";
+ const size_t buf_size
+ = strlen(msg_base) + session->read_size + 1;
+ log_msg_info i = {0};
+ uint8_t *msg = malloc(buf_size);
+ strcpy((char *)msg, msg_base);
+ strcat((char *)msg, session->read_buf);
+ i.buf_size = buf_size;
+ i.ws_session = session;
+ i.buf = msg;
+ i.level = wrdp_log_level_trace;
+ log_msg_ex(&i);
+ free(msg);
+ }
+#endif
+
+ /* parse the request */
+ pret = phr_parse_request((const char *)session->read_buf,
+ session->read_size, (const char **)&method, &method_len,
+ (const char **)&path, &path_len, &minor_version, headers,
+ &num_headers, session->prev_read_size);
+ if (pret == 0)
+ {
+ if (session->read_size == sizeof(session->read_buf))
+ {
+ const char *msg = "HTTP request is too large";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ goto error;
+ }
+ return true; /* need more data */
+ }
+ else if (pret == -1)
+ {
+ const char *msg = "Failed to parse HTTP request";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ goto error;
+ }
+ /* Print request */
+ /* printf("request is %d bytes long\n", pret);
+ printf("method is %.*s\n", (int)method_len, method);
+ printf("path is %.*s\n", (int)path_len, path);
+ printf("HTTP version is 1.%d\n", minor_version);
+ printf("headers:\n");
+ {
+ int i;
+ for (i = 0; i != num_headers; ++i)
+ {
+ printf("%.*s: %.*s\n",
+ (int)headers[i].name_len, headers[i].name, (int)headers[i].value_len,
+ headers[i].value);
+ }
+ } */
+ session->prev_read_size = session->read_size = 0;
+ if (!strncmp(method, "GET", method_len))
+ {
+ if (!ws_server_handle_http_get(
+ path, path_len, headers, num_headers, session))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "Unsupported HTTP method %.*s, destroying task",
+ (int)method_len, method);
+ log_msg(
+ (const uint8_t *)buf, strlen(buf), wrdp_log_level_error, 0);
+ goto error;
+ }
+ /* reset buffer state */
+
+ return true;
+error:
+{
+ const char *msg = "HTTP error occurs, destroying task";
+ log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+}
+ return false;
+}
+
+bool
+ws_server_handle_data(ws_session *session)
+{
+ task_info *info;
+ info = session->task_info;
+ if (info && info->stopped)
+ {
+ return false;
+ }
+ switch (session->http_state)
+ {
+ case ws_server_state_http_handshake:
+ {
+ ws_server_socket_read(session);
+ if (!ws_server_handle_http_request(session))
+ {
+ return false;
+ }
+ }
+ break;
+ case ws_server_state_ws_running:
+ {
+ if (!wslay_event_want_read(session->wslay_ctx))
+ {
+ if (!wslay_event_get_close_received(
+ session->wslay_ctx))
+ {
+ const char *msg = "Unexpected data";
+ log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_error,
+ 0);
+ ws_server_socket_read(session);
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else if (wslay_event_recv(session->wslay_ctx))
+ {
+ return false;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return true;
+}
diff --git a/src/core/ws_session.h b/src/core/ws_session.h
new file mode 100644
index 0000000..0e8f95d
--- /dev/null
+++ b/src/core/ws_session.h
@@ -0,0 +1,59 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include <ev.h>
+#include <wslay/wslay.h>
+
+#include <sys/queue.h>
+
+typedef enum ws_server_state_e
+{
+ ws_server_state_http_handshake = 0,
+ ws_server_state_ws_running
+} ws_server_state;
+
+typedef enum
+{
+ ws_session_initial,
+ ws_session_approved,
+ ws_session_denied,
+ ws_session_started,
+ ws_session_ended,
+ ws_session_error
+} ws_session_state;
+
+typedef struct
+{
+ /* set session time limit and session idle timeout */
+ int64_t session_time_limit, session_idle_timeout;
+} ws_session_settings;
+
+typedef struct ws_session_s
+{
+ ws_server_state http_state;
+ ws_session_state session_state;
+ ev_io ev_con_fd_r, ev_con_fd_w;
+ int connection_fd;
+ char read_buf[2048], *sid_base64, *attach_sid_base64, *token_base64,
+ *backend_module_name;
+ size_t read_size, prev_read_size;
+ wslay_event_context_ptr wslay_ctx;
+ bool token_verified;
+ void *task_info, *wrdp_thpool_task, *curlm;
+
+ /* backend settings cache */
+ SLIST_HEAD(settings_head, backend_setting_s) backend_settings_head;
+
+ SLIST_HEAD(curl_head, curls_easy_s) curls_easy_head;
+} ws_session;
+
+int ws_server_init();
+
+int ws_server_init_unix();
+
+bool ws_server_handle_data(ws_session *session);
diff --git a/src/rdp/.clang-format b/src/rdp/.clang-format
new file mode 120000
index 0000000..2d11237
--- /dev/null
+++ b/src/rdp/.clang-format
@@ -0,0 +1 @@
+../../.clang-format \ No newline at end of file
diff --git a/src/rdp/CMakeLists.txt b/src/rdp/CMakeLists.txt
new file mode 100644
index 0000000..29e7b9a
--- /dev/null
+++ b/src/rdp/CMakeLists.txt
@@ -0,0 +1,82 @@
+# -*- CMakeLists.txt generated by CodeLite IDE. Do not edit by hand -*-
+
+cmake_minimum_required(VERSION 2.8.11)
+
+project(rdp)
+
+# Define some variables
+set(PROJECT_rdp_PATH "${CMAKE_CURRENT_LIST_DIR}")
+set(WORKSPACE_PATH "${CMAKE_CURRENT_LIST_DIR}/..")
+
+
+
+#{{{{ User Code 1
+# Place your code here
+#}}}}
+
+include_directories(
+ .
+ ./include
+ ../../3rdparty/FreeRDP/include
+ ../../3rdparty/FreeRDP/winpr
+ ../../3rdparty/FreeRDP/winpr/include
+ ../../3rdparty/FreeRDP/build/winpr/include
+ ../../3rdparty/FreeRDP/build/include
+ ../../3rdparty/libev
+ ../core/include
+)
+
+
+# Compiler options
+add_definitions(-Wall)
+add_definitions(-D_XOPEN_SOURCE=500)
+add_definitions(-D_POSIX_C_SOURCE=200112L)
+
+# Linker options
+
+
+if(WIN32)
+ # Resource options
+endif(WIN32)
+
+# Library path
+set(CMAKE_LDFLAGS "${CMAKE_LDFLAGS} -L. ")
+
+# Define the C sources
+set ( C_SRCS
+ ${CMAKE_CURRENT_LIST_DIR}/rdp_module.c
+ ${CMAKE_CURRENT_LIST_DIR}/rdp_impl.c
+ ${CMAKE_CURRENT_LIST_DIR}/rdp_png.c
+ ${CMAKE_CURRENT_LIST_DIR}/rdp_settings.c
+ ${CMAKE_CURRENT_LIST_DIR}/rdp_channels.c
+ ${CMAKE_CURRENT_LIST_DIR}/rdp_display_output.c
+ ${CMAKE_CURRENT_LIST_DIR}/rdp_clipboard.c
+ ${CMAKE_CURRENT_LIST_DIR}/rdp_ft.c
+ ${CMAKE_CURRENT_LIST_DIR}/rdp_user_input.c
+ ${CMAKE_CURRENT_LIST_DIR}/rdp_rail.c
+)
+
+set_source_files_properties(
+ ${C_SRCS} PROPERTIES COMPILE_FLAGS
+ " -Wall -D_XOPEN_SOURCE=500 -D_POSIX_C_SOURCE=200112L -fPIC")
+
+if(WIN32)
+ enable_language(RC)
+ set(CMAKE_RC_COMPILE_OBJECT
+ "<CMAKE_RC_COMPILER> ${RC_OPTIONS} -O coff -i <SOURCE> -o <OBJECT>")
+endif(WIN32)
+
+
+
+#{{{{ User Code 2
+# Place your code here
+#}}}}
+
+add_library(rdp ${RC_SRCS} ${CXX_SRCS} ${C_SRCS})
+
+
+
+#{{{{ User Code 3
+# Place your code here
+#}}}}
+
diff --git a/src/rdp/include/.clang-format b/src/rdp/include/.clang-format
new file mode 120000
index 0000000..3260daf
--- /dev/null
+++ b/src/rdp/include/.clang-format
@@ -0,0 +1 @@
+../../../.clang-format \ No newline at end of file
diff --git a/src/rdp/include/rdp_backend_api.h b/src/rdp/include/rdp_backend_api.h
new file mode 100644
index 0000000..4840765
--- /dev/null
+++ b/src/rdp/include/rdp_backend_api.h
@@ -0,0 +1,17 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+typedef enum
+{
+ rdp_conn_state_offline,
+ rdp_conn_state_connected
+} rdp_connection_state;
+
+bool rdp_create(wrdp_core_exports *core, wrdp_backend_module *module);
+
+void rdp_backend_destroy(void *backend);
diff --git a/src/rdp/rdp.project b/src/rdp/rdp.project
new file mode 100644
index 0000000..30925d4
--- /dev/null
+++ b/src/rdp/rdp.project
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CodeLite_Project Name="rdp" Version="11000" InternalType="Library">
+ <Plugins>
+ <Plugin Name="qmake">
+ <![CDATA[00010001N0005Debug000000000000]]>
+ </Plugin>
+ </Plugins>
+ <Description/>
+ <Dependencies/>
+ <VirtualDirectory Name="src">
+ <File Name="rdp_rail.h"/>
+ <File Name="rdp_rail.c"/>
+ <File Name="rdp_ft.h"/>
+ <File Name="rdp_ft.c"/>
+ <File Name="rdp_display_output.h"/>
+ <File Name="rdp_display_output.c"/>
+ <File Name="rdp_user_input.h"/>
+ <File Name="rdp_user_input.c"/>
+ <File Name="rdp_channels.h"/>
+ <File Name="rdp_channels.c"/>
+ <File Name="rdp_clipboard.h"/>
+ <File Name="rdp_clipboard.c"/>
+ <File Name="rdp_settings.h"/>
+ <File Name="rdp_settings.c"/>
+ <File Name="rdp_png.c"/>
+ <File Name="rdp_png.h"/>
+ <File Name="rdp_impl.c"/>
+ <File Name="rdp_impl.h"/>
+ <File Name="rdp_module.c"/>
+ <File Name="rdp_module.h"/>
+ </VirtualDirectory>
+ <VirtualDirectory Name="include">
+ <File Name="include/rdp_backend_api.h"/>
+ </VirtualDirectory>
+ <Settings Type="Static Library">
+ <GlobalSettings>
+ <Compiler Options="-fPIC" C_Options="-D_XOPEN_SOURCE=500;-D_POSIX_C_SOURCE=200112L" Assembler="">
+ <IncludePath Value="."/>
+ <IncludePath Value="./include"/>
+ <IncludePath Value="../../3rdparty/FreeRDP/include"/>
+ <IncludePath Value="../../3rdparty/libev"/>
+ <IncludePath Value="../../3rdparty/FreeRDP/winpr"/>
+ <IncludePath Value="../../3rdparty/FreeRDP/winpr/include"/>
+ <IncludePath Value="../../3rdparty/FreeRDP/build/winpr/include"/>
+ <IncludePath Value="../../3rdparty/FreeRDP/build/include"/>
+ <IncludePath Value="../core/include"/>
+ <IncludePath Value="/home/sss/.nix-profile/include"/>
+ </Compiler>
+ <Linker Options="">
+ <LibraryPath Value="."/>
+ </Linker>
+ <ResourceCompiler Options=""/>
+ </GlobalSettings>
+ <Configuration Name="Debug" CompilerType="clang" DebuggerType="GNU gdb debugger" Type="Static Library" BuildCmpWithGlobalSettings="append" BuildLnkWithGlobalSettings="append" BuildResWithGlobalSettings="append">
+ <Compiler Options="-g;-Wall;-fsanitize=address;-O0" C_Options="-pg;-g;-Wall;-DDEBUG;-fsanitize=address;-O0" Assembler="" Required="yes" PreCompiledHeader="" PCHInCommandLine="no" PCHFlags="" PCHFlagsPolicy="0"/>
+ <Linker Options="-pg" Required="yes"/>
+ <ResourceCompiler Options="" Required="no"/>
+ <General OutputFile="$(IntermediateDirectory)/$(ProjectName).a" IntermediateDirectory="./Debug" Command="" CommandArguments="" UseSeparateDebugArgs="no" DebugArguments="" WorkingDirectory="$(IntermediateDirectory)" PauseExecWhenProcTerminates="yes" IsGUIProgram="no" IsEnabled="yes"/>
+ <BuildSystem Name="Default"/>
+ <Environment EnvVarSetName="&lt;Use Defaults&gt;" DbgSetName="&lt;Use Defaults&gt;">
+ <![CDATA[]]>
+ </Environment>
+ <Debugger IsRemote="no" RemoteHostName="" RemoteHostPort="" DebuggerPath="" IsExtended="yes">
+ <DebuggerSearchPaths/>
+ <PostConnectCommands/>
+ <StartupCommands/>
+ </Debugger>
+ <PreBuild/>
+ <PostBuild/>
+ <CustomBuild Enabled="no">
+ <RebuildCommand/>
+ <CleanCommand/>
+ <BuildCommand/>
+ <PreprocessFileCommand/>
+ <SingleFileCommand/>
+ <MakefileGenerationCommand/>
+ <ThirdPartyToolName/>
+ <WorkingDirectory/>
+ </CustomBuild>
+ <AdditionalRules>
+ <CustomPostBuild/>
+ <CustomPreBuild/>
+ </AdditionalRules>
+ <Completion EnableCpp11="no" EnableCpp14="no">
+ <ClangCmpFlagsC/>
+ <ClangCmpFlags/>
+ <ClangPP/>
+ <SearchPaths/>
+ </Completion>
+ </Configuration>
+ <Configuration Name="Release" CompilerType="clang" DebuggerType="GNU gdb debugger" Type="Static Library" BuildCmpWithGlobalSettings="append" BuildLnkWithGlobalSettings="append" BuildResWithGlobalSettings="append">
+ <Compiler Options="-Wall;-fPIC" C_Options="-std=c99;-Wall;-D_XOPEN_SOURCE=500;-D_POSIX_C_SOURCE=200112L" Assembler="" Required="yes" PreCompiledHeader="" PCHInCommandLine="no" PCHFlags="" PCHFlagsPolicy="0">
+ <IncludePath Value="."/>
+ <IncludePath Value="./include"/>
+ <IncludePath Value="../../3rdparty/FreeRDP/include"/>
+ <IncludePath Value="../../3rdparty/FreeRDP/winpr/include"/>
+ <IncludePath Value="../../3rdparty/FreeRDP/build/winpr/include"/>
+ <IncludePath Value="../../3rdparty/FreeRDP/build/include"/>
+ <IncludePath Value="../core/include"/>
+ <Preprocessor Value="NDEBUG"/>
+ </Compiler>
+ <Linker Options="-O2" Required="yes"/>
+ <ResourceCompiler Options="" Required="no"/>
+ <General OutputFile="$(IntermediateDirectory)/$(ProjectName).a" IntermediateDirectory="./Release" Command="" CommandArguments="" UseSeparateDebugArgs="no" DebugArguments="" WorkingDirectory="$(IntermediateDirectory)" PauseExecWhenProcTerminates="yes" IsGUIProgram="no" IsEnabled="yes"/>
+ <BuildSystem Name="Default"/>
+ <Environment EnvVarSetName="&lt;Use Defaults&gt;" DbgSetName="&lt;Use Defaults&gt;">
+ <![CDATA[]]>
+ </Environment>
+ <Debugger IsRemote="no" RemoteHostName="" RemoteHostPort="" DebuggerPath="" IsExtended="yes">
+ <DebuggerSearchPaths/>
+ <PostConnectCommands/>
+ <StartupCommands/>
+ </Debugger>
+ <PreBuild/>
+ <PostBuild/>
+ <CustomBuild Enabled="no">
+ <RebuildCommand/>
+ <CleanCommand/>
+ <BuildCommand/>
+ <PreprocessFileCommand/>
+ <SingleFileCommand/>
+ <MakefileGenerationCommand/>
+ <ThirdPartyToolName/>
+ <WorkingDirectory/>
+ </CustomBuild>
+ <AdditionalRules>
+ <CustomPostBuild/>
+ <CustomPreBuild/>
+ </AdditionalRules>
+ <Completion EnableCpp11="no" EnableCpp14="no">
+ <ClangCmpFlagsC/>
+ <ClangCmpFlags/>
+ <ClangPP/>
+ <SearchPaths/>
+ </Completion>
+ </Configuration>
+ </Settings>
+</CodeLite_Project>
diff --git a/src/rdp/rdp_channels.c b/src/rdp/rdp_channels.c
new file mode 100644
index 0000000..a4d95eb
--- /dev/null
+++ b/src/rdp/rdp_channels.c
@@ -0,0 +1,39 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <string.h>
+#include "rdp_channels.h"
+#include "rdp_clipboard.h"
+#include "rdp_rail.h"
+
+void
+rdp_on_channel_connected(void *context, const ChannelConnectedEventArgs *e)
+{
+ if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
+ {
+ rdp_cliprdr_init(
+ context, (CliprdrClientContext *)e->pInterface);
+ }
+ else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0)
+ {
+ rdp_rail_init(context, (RailClientContext *)e->pInterface);
+ }
+}
+
+void
+rdp_on_channel_disconnected(
+ void *context, const ChannelDisconnectedEventArgs *e)
+{
+ if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
+ {
+ rdp_cliprdr_uninit(
+ context, (CliprdrClientContext *)e->pInterface);
+ }
+ else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0)
+ {
+ rdp_rail_uninit(context, (RailClientContext *)e->pInterface);
+ }
+}
diff --git a/src/rdp/rdp_channels.h b/src/rdp/rdp_channels.h
new file mode 100644
index 0000000..2d895fb
--- /dev/null
+++ b/src/rdp/rdp_channels.h
@@ -0,0 +1,17 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include <freerdp/client/channels.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/rail.h>
+
+void rdp_on_channel_connected(
+ void *context, const ChannelConnectedEventArgs *e);
+
+void rdp_on_channel_disconnected(
+ void *context, const ChannelDisconnectedEventArgs *e);
diff --git a/src/rdp/rdp_clipboard.c b/src/rdp/rdp_clipboard.c
new file mode 100644
index 0000000..e8ab882
--- /dev/null
+++ b/src/rdp/rdp_clipboard.c
@@ -0,0 +1,1524 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+/* this code mostly ported from XFreeRDP */
+
+#include <arpa/inet.h>
+#include <iconv.h>
+
+#include "rdp_clipboard.h"
+#include "rdp_ft.h"
+
+static UINT
+rdp_clip_ServerCapabilities(
+ CliprdrClientContext *context, const CLIPRDR_CAPABILITIES *capabilities)
+{
+ UINT32 i;
+ const CLIPRDR_CAPABILITY_SET *caps;
+ const CLIPRDR_GENERAL_CAPABILITY_SET *generalCaps;
+ const BYTE *capsPtr = (const BYTE *)capabilities->capabilitySets;
+ my_rdp_clipboard *clipboard = (my_rdp_clipboard *)context->custom;
+ clipboard->streams_supported = false;
+
+ for (i = 0; i < capabilities->cCapabilitiesSets; i++)
+ {
+ caps = (const CLIPRDR_CAPABILITY_SET *)capsPtr;
+
+ if (caps->capabilitySetType == CB_CAPSTYPE_GENERAL)
+ {
+ generalCaps
+ = (const CLIPRDR_GENERAL_CAPABILITY_SET *)caps;
+
+ if (generalCaps->generalFlags
+ & CB_STREAM_FILECLIP_ENABLED)
+ {
+ clipboard->streams_supported = true;
+ }
+ }
+
+ capsPtr += caps->capabilitySetLength;
+ }
+ {
+ const char *msg
+ = "rdp_module: cliprdr: server capabilities received";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_trace, 0);
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/* unused for now
+ * static UINT
+rdp_clip_ClientCapabilities(CliprdrClientContext* context,
+ CLIPRDR_CAPABILITIES* capabilities)
+{
+ return CHANNEL_RC_OK;
+}*/
+
+static UINT
+rdp_cliprdr_send_client_capabilities(my_rdp_clipboard *clipboard)
+{
+ CLIPRDR_CAPABILITIES capabilities;
+ CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet;
+ capabilities.cCapabilitiesSets = 1;
+ capabilities.capabilitySets
+ = (CLIPRDR_CAPABILITY_SET *)&(generalCapabilitySet);
+ generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL;
+ generalCapabilitySet.capabilitySetLength = 12;
+ generalCapabilitySet.version = CB_CAPS_VERSION_2;
+ generalCapabilitySet.generalFlags = CB_USE_LONG_FORMAT_NAMES;
+ /*|CB_CAN_LOCK_CLIPDATA; */
+
+ if (clipboard->streams_supported)
+ {
+ generalCapabilitySet.generalFlags |= CB_STREAM_FILECLIP_ENABLED
+ | CB_FILECLIP_NO_FILE_PATHS
+ | 0x00000020;
+ }
+
+ {
+ const char *msg
+ = "rdp_module: cliprdr: client capabilities sent";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_trace, 0);
+ }
+
+ return clipboard->clip_context->ClientCapabilities(
+ clipboard->clip_context, &capabilities);
+}
+
+static UINT
+rdp_cliprdr_send_client_format_list(my_rdp_clipboard *clipboard)
+{
+ const uint8_t format_count = 2;
+ CLIPRDR_FORMAT formats[format_count];
+ CLIPRDR_FORMAT_LIST formatList;
+ memset(formats, 0, sizeof(CLIPRDR_FORMAT) * format_count);
+ formats[0].formatId = CF_RAW;
+ formats[1].formatId = CF_TEXT;
+ formatList.common.msgFlags = CB_RESPONSE_OK;
+ formatList.numFormats = format_count;
+ formatList.formats = formats;
+
+ {
+ const char *msg
+ = "rdp_module: cliprdr: client format list sent";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_trace, 0);
+ }
+
+ return clipboard->clip_context->ClientFormatList(
+ clipboard->clip_context, &formatList);
+}
+
+static UINT
+rdp_clip_MonitorReady(
+ CliprdrClientContext *context, const CLIPRDR_MONITOR_READY *monitorReady)
+{
+ my_rdp_clipboard *clipboard = context->custom;
+ UINT ret;
+
+ if ((ret = rdp_cliprdr_send_client_capabilities(clipboard))
+ != CHANNEL_RC_OK)
+ return ret;
+
+ if ((ret = rdp_cliprdr_send_client_format_list(clipboard))
+ != CHANNEL_RC_OK)
+ return ret;
+
+ /* clipboard->sync = true; */
+ {
+ const char *msg
+ = "rdp_module: cliprdr: monitor ready message handled";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_trace, 0);
+ }
+ return CHANNEL_RC_OK;
+}
+
+/* unused for now
+ * static UINT
+rdp_clip_TempDirectory(CliprdrClientContext* context,
+ CLIPRDR_TEMP_DIRECTORY* tempDirectory)
+{
+ return CHANNEL_RC_OK;
+} */
+
+/* unused for now
+ * static UINT
+rdp_clip_ClientFormatList(CliprdrClientContext* context,
+ CLIPRDR_FORMAT_LIST* formatList)
+{
+ return CHANNEL_RC_OK;
+}*/
+
+static UINT
+rdp_cliprdr_send_client_format_list_response(
+ my_rdp_clipboard *clipboard, BOOL status)
+{
+ CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse;
+ formatListResponse.common.msgType = CB_FORMAT_LIST_RESPONSE;
+ formatListResponse.common.msgFlags
+ = status ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
+ formatListResponse.common.dataLen = 0;
+ return clipboard->clip_context->ClientFormatListResponse(
+ clipboard->clip_context, &formatListResponse);
+}
+
+static wrdp_enum_clip_format
+clip_format_from_string(const char *fmt)
+{
+ if (!strcmp(fmt, "FileGroupDescriptorW"))
+ {
+ return clip_format_file_list;
+ }
+ /*
+ * unused
+ else if (!strcmp(fmt, "FileContents"))
+ {
+ }
+ else if (!strcmp(fmt, "Preffered DropEffect"))
+ {
+ }
+ */
+ return clip_format_unsupported;
+}
+
+static wrdp_enum_clip_format
+clip_format_from_id(UINT32 id, my_rdp_clipboard *c)
+{
+ if (id == CF_TEXT || id == CF_OEMTEXT)
+ {
+ return clip_format_text;
+ }
+ else if (id == CF_RAW)
+ {
+ return clip_format_raw;
+ }
+ else if (id == CF_UNICODETEXT)
+ {
+ return clip_format_unicode;
+ }
+ else if (id == CB_FORMAT_TEXTURILIST)
+ {
+ return clip_format_file_list;
+ }
+ return clip_format_unsupported;
+}
+
+static UINT
+rdp_clip_ServerFormatList(
+ CliprdrClientContext *context, const CLIPRDR_FORMAT_LIST *formatList)
+{
+ UINT32 i;
+ uint8_t num_supported_fmts = 0, *out_fmts = 0, num_server_formats;
+ my_rdp_clipboard *clipboard = context->custom;
+ my_clip_format *server_formats = 0;
+ UINT ret;
+
+ if (clipboard->my_rdp_context->ft_to_server->is_runing)
+ {
+ const char *msg
+ = "rdp_module: clipboard: new clipboard formats list "
+ "during runing filetransfer";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_warning, 0);
+ /* return CHANNEL_RC_OK; */
+ }
+
+ num_server_formats = formatList->numFormats + 1; /* +1 for CF_RAW */
+
+ if (!(server_formats
+ = calloc(num_server_formats, sizeof(my_clip_format))))
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "failed to allocate %d my_clip_format structs",
+ num_server_formats);
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)buf, strlen(buf),
+ wrdp_log_level_error, 0);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if (clipboard->srv_fmts && clipboard->srv_fmts_count)
+ {
+ for (i = 0; i < clipboard->srv_fmts_count; ++i)
+ {
+ if (clipboard->srv_fmts[i].rdp_fmt.formatName)
+ {
+ free(clipboard->srv_fmts[i].rdp_fmt.formatName);
+ }
+ }
+ free(clipboard->srv_fmts);
+ }
+
+ clipboard->srv_fmts_count = formatList->numFormats;
+ for (i = 0; i < formatList->numFormats; i++)
+ {
+ CLIPRDR_FORMAT *format = &formatList->formats[i];
+ server_formats[i].rdp_fmt.formatId = format->formatId;
+ wrdp_enum_clip_format fmt
+ = clip_format_from_id(format->formatId, clipboard);
+ if (format->formatName)
+ {
+ server_formats[i].rdp_fmt.formatName
+ = strdup(format->formatName);
+ }
+ if (fmt == clip_format_unsupported && format->formatName)
+ {
+ fmt = clip_format_from_string(format->formatName);
+ }
+ if (fmt != clip_format_unsupported)
+ {
+ num_supported_fmts++;
+ }
+ server_formats[i].my_fmt = fmt;
+ }
+ /* CF_RAW is always implicitly supported by the server */
+ {
+ my_clip_format *format
+ = &server_formats[formatList->numFormats];
+ format->rdp_fmt.formatId = CF_RAW;
+ format->rdp_fmt.formatName = NULL;
+ format->my_fmt = clip_format_raw;
+ num_supported_fmts++;
+ }
+ {
+ out_fmts = calloc(num_supported_fmts, sizeof(uint8_t));
+ if (!out_fmts)
+ {
+ perror("calloc");
+ if (server_formats)
+ {
+ free(server_formats);
+ }
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ uint8_t pos = 0;
+ for (i = 0; i < num_supported_fmts; ++i)
+ {
+ if (server_formats[i].my_fmt == clip_format_unsupported)
+ {
+ continue;
+ }
+ out_fmts[pos] = server_formats[i].my_fmt;
+ pos++;
+ }
+ clipboard->my_rdp_context->my_internals->core->api_clipboard
+ ->clipboard_changed(out_fmts, num_supported_fmts,
+ clipboard->my_rdp_context->my_internals->task_info);
+ free(out_fmts);
+ clipboard->srv_fmts = server_formats;
+ }
+
+ {
+ const char *msg = "rdp_module: cliprdr: server format list"
+ " changed message received";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_trace, 0);
+ }
+
+ ret = rdp_cliprdr_send_client_format_list_response(clipboard, TRUE);
+ return ret;
+}
+
+/* unused for now
+ * static UINT
+rdp_clip_ClientFormatListResponse(CliprdrClientContext* context,
+ CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse)
+{
+ return CHANNEL_RC_OK;
+}*/
+
+static UINT
+rdp_clip_ServerFormatListResponse(CliprdrClientContext *context,
+ const CLIPRDR_FORMAT_LIST_RESPONSE *formatListResponse)
+{
+ return CHANNEL_RC_OK;
+}
+
+/* unused for now
+ * static UINT
+rdp_clip_ClientLockClipboardData(CliprdrClientContext* context,
+ CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData)
+{
+ return CHANNEL_RC_OK;
+}*/
+
+static UINT
+rdp_clip_ServerLockClipboardData(CliprdrClientContext *context,
+ const CLIPRDR_LOCK_CLIPBOARD_DATA *lockClipboardData)
+{
+ /* my_rdp_clipboard* clipboard = context->custom;
+ clipboard->my_rdp_context->ft_to_server->clip_data_id =
+ lockClipboardData->clipDataId; */
+ return CHANNEL_RC_OK;
+}
+
+/*
+ * unused
+static UINT
+rdp_clip_ClientUnlockClipboardData(CliprdrClientContext* context,
+ CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData)
+{
+ return CHANNEL_RC_OK;
+}
+*/
+
+static UINT
+rdp_clip_ServerUnlockClipboardData(CliprdrClientContext *context,
+ const CLIPRDR_UNLOCK_CLIPBOARD_DATA *unlockClipboardData)
+{
+ /* TODO: */
+ return CHANNEL_RC_OK;
+}
+
+/* unused for now
+ * static UINT
+rdp_clip_ClientFormatDataRequest(CliprdrClientContext* context,
+ CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest)
+{
+ return CHANNEL_RC_OK;
+}*/
+
+static UINT
+rdp_clip_ServerFormatDataRequest(CliprdrClientContext *context,
+ const CLIPRDR_FORMAT_DATA_REQUEST *formatDataRequest)
+{
+ my_rdp_clipboard *clipboard = context->custom;
+ wrdp_enum_clip_format fmt = clip_format_from_id(
+ formatDataRequest->requestedFormatId, clipboard);
+ clipboard->my_rdp_context->my_internals->core->api_clipboard
+ ->request_data(
+ fmt, clipboard->my_rdp_context->my_internals->task_info);
+
+ return CHANNEL_RC_OK;
+}
+
+/* unused for now
+ * static UINT
+rdp_clip_ClientFormatDataResponse(CliprdrClientContext* context,
+ CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse)
+{
+ return CHANNEL_RC_OK;
+}*/
+
+/*static UINT rdp_cliprdr_send_data_response(my_rdp_clipboard* clipboard,
+ BYTE* data, int size)
+{
+ CLIPRDR_FORMAT_DATA_RESPONSE response;
+ ZeroMemory(&response, sizeof(CLIPRDR_FORMAT_DATA_RESPONSE));
+ response.msgFlags = (data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
+ response.dataLen = size;
+ response.requestedFormatData = data;
+ return clipboard->clip_context->ClientFormatDataResponse(
+ clipboard->clip_context, &response);
+} */
+
+static UINT
+rdp_clip_ServerFormatDataResponse(CliprdrClientContext *context,
+ const CLIPRDR_FORMAT_DATA_RESPONSE *formatDataResponse)
+{
+ UINT32 size = formatDataResponse->common.dataLen;
+ const BYTE *data = formatDataResponse->requestedFormatData;
+ my_rdp_clipboard *clipboard = context->custom;
+
+ if (!data)
+ {
+ const char *msg
+ = "rdp_module: cliprdr: failed to get clipboard data";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ return CHANNEL_RC_OK;
+ }
+
+ switch (clipboard->cli_req_fmt_id.my_fmt)
+ {
+ case clip_format_file_list:
+ {
+ /* TODO: check this */
+ UINT error = NO_ERROR;
+ FILEDESCRIPTORW *file_array = 0;
+ UINT32 file_count = 0, pos = 0;
+ uint8_t *msg_struct = 0, *msg = 0;
+ wrdp_backend_ft_list_entry *list = 0;
+ size_t msg_size_result = 0;
+ error = cliprdr_parse_file_list(
+ data, size, &file_array, &file_count);
+ if (error)
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "failed to deserialize"
+ " CLIPRDR_FILELIST: 0x%08X",
+ error);
+ clipboard->my_rdp_context->my_internals->core
+ ->api_utils->log_msg((const uint8_t *)buf,
+ strlen(buf), wrdp_log_level_error, 0);
+ return CHANNEL_RC_OK;
+ }
+ msg_struct = calloc(
+ file_count, sizeof(wrdp_backend_ft_list_entry));
+ if (!msg_struct)
+ {
+ perror("calloc");
+ if (file_count && file_array)
+ {
+ free(file_array);
+ }
+ return CHANNEL_RC_OK;
+ }
+ msg_size_result
+ = (file_count * sizeof(wrdp_backend_ft_list_entry))
+ + 3;
+ char *filename_array[file_count];
+ {
+ int i = 0;
+ for (; i < file_count; ++i)
+ {
+ filename_array[i] = 0;
+ }
+ }
+ list = (wrdp_backend_ft_list_entry *)msg_struct;
+ for (; file_array && pos < file_count; ++pos)
+ {
+ list[pos].file_id = pos;
+ if (file_array[pos].cFileName[0])
+ {
+ iconv_t utf16_to_utf8
+ = iconv_open("UTF-8", "UTF-16LE");
+ char *ptr_w, *ptr_r,
+ *tmp_buf
+ = calloc(520, sizeof(char));
+ size_t name_len_utf16
+ = 260 * sizeof(WCHAR),
+ name_len_utf8 = 520,
+ name_len_result = 0;
+ if (!tmp_buf)
+ {
+ perror("calloc");
+ iconv_close(utf16_to_utf8);
+ free(msg_struct);
+ if (file_count)
+ {
+ int i = 0;
+ for (; (i < file_count)
+ && filename_array
+ [i];
+ ++i)
+ {
+ free(
+ filename_array
+ [i]);
+ }
+ free(file_array);
+ }
+ return CHANNEL_RC_OK;
+ }
+ ptr_w = tmp_buf;
+ ptr_r
+ = (char *)file_array[pos].cFileName;
+ while (name_len_utf16 && name_len_utf8)
+ {
+ iconv(utf16_to_utf8, &ptr_r,
+ &name_len_utf16, &ptr_w,
+ &name_len_utf8);
+ }
+#ifdef DEBUG
+ if (!name_len_utf8 && name_len_utf16)
+ {
+ log_msg_info i = {0};
+ i.level = wrdp_log_level_trace;
+ i.buf = (const uint8_t
+ *)"rdp_module: "
+ "cliprdr: iconv: not "
+ "sufficient space in "
+ "output buffer";
+ i.task_info
+ = clipboard->my_internals
+ ->task_info;
+ clipboard->my_internals->core
+ ->api_utils->log_msg_ex(&i);
+ }
+#endif
+ iconv(utf16_to_utf8, 0, 0, &ptr_w,
+ &name_len_utf8);
+ name_len_result = strlen(tmp_buf);
+ msg_size_result += name_len_result;
+ list[pos].filename_len
+ = name_len_result;
+ filename_array[pos] = strdup(tmp_buf);
+ iconv_close(utf16_to_utf8);
+ free(tmp_buf);
+ }
+ /* TODO: check/change endianness */
+ memcpy(&(list[pos].file_size),
+ &file_array[pos].nFileSizeLow, 4);
+ memcpy(((uint8_t *)&(list[pos].file_size)) + 4,
+ &file_array[pos].nFileSizeHigh, 4);
+ }
+ msg = malloc(msg_size_result);
+ if (!msg)
+ {
+ perror("malloc");
+ for (pos = 0; pos < file_count; ++pos)
+ {
+ free(filename_array[pos]);
+ }
+ free(msg_struct);
+ return CHANNEL_RC_OK;
+ }
+ memset(msg, 0, msg_size_result);
+ *((uint8_t *)msg) = clip_format_file_list;
+ memcpy(msg + 1, &file_count, 2);
+ {
+ size_t msg_pos = 3;
+ for (pos = 0; pos < file_count; ++pos)
+ {
+ memcpy(msg + msg_pos,
+ &list[pos].filename_len, 2);
+ msg_pos += 2;
+ memcpy(msg + msg_pos,
+ &list[pos].file_id, 4);
+ msg_pos += 4;
+ memcpy(msg + msg_pos,
+ &list[pos].file_size, 8);
+ msg_pos += 8;
+ /* TODO: looks like i have missed
+ * something ... "Null pointer passed as
+ * an argument to a 'nonnull' parameter"
+ * just added null ptr check for now */
+ if (filename_array[pos])
+ {
+ memcpy(msg + msg_pos,
+ filename_array[pos],
+ list[pos].filename_len);
+ free(filename_array[pos]);
+ }
+ msg_pos += list[pos].filename_len;
+ }
+ }
+ free(msg_struct);
+ clipboard->my_rdp_context->my_internals->core
+ ->api_clipboard->send_data((uint8_t *)msg,
+ msg_size_result,
+ clipboard->my_rdp_context->my_internals
+ ->task_info);
+ free(msg);
+ if (file_count && file_array)
+ {
+ free(file_array);
+ }
+ return CHANNEL_RC_OK;
+ }
+ break;
+ case clip_format_raw:
+ case clip_format_text:
+ {
+ size_t msg_len = size + 1;
+ uint8_t *msg = malloc(msg_len);
+ if (!msg)
+ {
+ perror("malloc");
+ return CHANNEL_RC_OK;
+ }
+ *(uint8_t *)msg = clip_format_text;
+ memcpy(msg + 1, data, size);
+ clipboard->my_rdp_context->my_internals->core
+ ->api_clipboard->send_data(msg, msg_len,
+ clipboard->my_rdp_context->my_internals
+ ->task_info);
+ free(msg);
+ }
+ break;
+ case clip_format_unicode:
+ {
+ iconv_t utf16_to_utf8 = iconv_open("UTF-8", "UTF-16LE");
+ size_t in_left = size, out_left = size;
+ uint8_t *tmp_buf = malloc(out_left), *tmp_buf_ptr;
+ uint8_t *msg = 0;
+ if (!tmp_buf)
+ {
+ perror("malloc");
+ return CHANNEL_RC_OK;
+ }
+ tmp_buf_ptr = tmp_buf;
+ /* wcstombs((char*)msg + 1, tmp_buf, text_len); */
+ while (in_left && out_left)
+ {
+ if (iconv(utf16_to_utf8, (char **)&data,
+ &in_left, (char **)&tmp_buf_ptr,
+ &out_left)
+ == (size_t)-1)
+ {
+ log_msg_info mi = {0};
+ mi.level = wrdp_log_level_warning;
+ mi.buf = (const uint8_t
+ *)"rdp_module: cliprdr: iconv: "
+ "failed to encode outgoing "
+ "buffer from utf8 to utf16le";
+ mi.task_info = clipboard->my_internals
+ ->task_info;
+ clipboard->my_internals->core->api_utils
+ ->log_msg_ex(&mi);
+ free(tmp_buf);
+ iconv_close(utf16_to_utf8);
+ return CHANNEL_RC_NO_BUFFER;
+ }
+ }
+#ifdef DEBUG
+ if (!out_left && in_left)
+ {
+ log_msg_info i = {0};
+ i.level = wrdp_log_level_trace;
+ i.buf = (const uint8_t
+ *)"rdp_module: cliprdr: iconv: not "
+ "sufficient space in output buffer";
+ i.task_info
+ = clipboard->my_internals->task_info;
+ clipboard->my_internals->core->api_utils
+ ->log_msg_ex(&i);
+ }
+#endif
+ iconv(utf16_to_utf8, 0, 0, (char **)&tmp_buf_ptr,
+ &out_left);
+ iconv_close(utf16_to_utf8);
+ msg = malloc(size - out_left + 1);
+ if (!msg)
+ {
+ perror("malloc");
+ return CHANNEL_RC_OK;
+ }
+ *(uint8_t *)msg = clip_format_unicode;
+ memcpy(msg + 1, tmp_buf, size - out_left);
+#ifdef DEBUG
+ {
+ log_msg_info i = {0};
+ size_t len = 1024;
+ char msg_buf[len];
+ snprintf(msg_buf, len - 1,
+ "rdp_module: cliprdr: sending encoded from "
+ "utf16le to utf8 text: %s",
+ tmp_buf);
+ i.level = wrdp_log_level_trace;
+ i.buf = (const uint8_t *)msg_buf;
+ i.task_info
+ = clipboard->my_internals->task_info;
+ clipboard->my_internals->core->api_utils
+ ->log_msg_ex(&i);
+ }
+#endif
+ free(tmp_buf);
+ clipboard->my_rdp_context->my_internals->core
+ ->api_clipboard->send_data(msg, size - out_left + 1,
+ clipboard->my_rdp_context->my_internals
+ ->task_info);
+ free(msg);
+ }
+ break;
+ default:
+ {
+ const char *msg = "rdp_module: cliprdr: unsupported"
+ " clipboardformat requested";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_warning, 0);
+ }
+ break;
+ }
+ return CHANNEL_RC_OK;
+}
+
+/* unused for now
+ * static UINT
+rdp_clip_ClientFileContentsRequest(CliprdrClientContext* context,
+ CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
+{
+ return CHANNEL_RC_OK;
+}*/
+
+static UINT
+rdp_clip_ServerFileContentsRequest(CliprdrClientContext *context,
+ const CLIPRDR_FILE_CONTENTS_REQUEST *fileContentsRequest)
+{
+ my_rdp_clipboard *clipboard = context->custom;
+ if (fileContentsRequest->dwFlags == FILECONTENTS_SIZE)
+ {
+ CLIPRDR_FILE_CONTENTS_RESPONSE resp;
+ if (fileContentsRequest->nPositionLow
+ || fileContentsRequest->nPositionHigh
+ || (fileContentsRequest->cbRequested != 0x00000008))
+ {
+ const char *msg
+ = "rdp_module: ft: ServerFileContentsRequest: "
+ "malformed request";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_warning, 0);
+ return CHANNEL_RC_OK;
+ }
+ memset(&resp, 0, sizeof(CLIPRDR_FILE_CONTENTS_RESPONSE));
+ resp.common.msgType = CB_FILECONTENTS_RESPONSE;
+ resp.common.msgFlags = CB_RESPONSE_OK;
+ resp.streamId = fileContentsRequest->streamId;
+ resp.cbRequested = sizeof(uint64_t);
+ resp.requestedData = (BYTE *)&(
+ clipboard
+ ->client_filelist_cache[fileContentsRequest->listIndex]
+ .file_size);
+ clipboard->clip_context->ClientFileContentsResponse(
+ clipboard->clip_context, &resp);
+ }
+ else if (fileContentsRequest->dwFlags == FILECONTENTS_RANGE)
+ {
+ wrdp_backend_ft_file_request req;
+ memset(&req, 0, sizeof(wrdp_backend_ft_list_entry));
+ /* TODO: better way to detect runing transfer */
+ clipboard->my_rdp_context->ft_to_server->is_runing = true;
+ req.file_id = fileContentsRequest->listIndex;
+ req.req_size = fileContentsRequest->cbRequested;
+ clipboard->my_rdp_context->ft_to_server->transfer_id
+ = fileContentsRequest->streamId;
+ memcpy(
+ &req.file_offset, &(fileContentsRequest->nPositionLow), 4);
+ memcpy(((uint8_t *)&req.file_offset) + 4,
+ &(fileContentsRequest->nPositionHigh), 4);
+ clipboard->my_rdp_context->my_internals->core->api_filetransfers
+ ->ft_request(&req,
+ clipboard->my_rdp_context->my_internals->task_info);
+ }
+ return CHANNEL_RC_OK;
+}
+
+/* unused for now
+ * static UINT
+rdp_clip_ClientFileContentsResponse(CliprdrClientContext* context,
+ CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse)
+{
+ return CHANNEL_RC_OK;
+} */
+
+static UINT
+rdp_clip_ServerFileContentsResponse(
+ CliprdrClientContext *context, const CLIPRDR_FILE_CONTENTS_RESPONSE *resp)
+{
+ /* TODO: somehow avoid memory copying here */
+ my_rdp_clipboard *clipboard = context->custom;
+ wrdp_backend_ft_chunk chunk;
+ if (resp->common.msgFlags & CB_RESPONSE_FAIL)
+ {
+ const char *msg = "rdp_module: ft: ServerFileContentsResponse: "
+ "CB_RESPONSE_FAIL";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ goto transfer_failed;
+ }
+ if (!resp->cbRequested
+ && (clipboard->my_rdp_context->ft_to_client->transferred
+ != clipboard->my_rdp_context->ft_to_client->file_size))
+ {
+ char msg[128];
+ snprintf(msg, 127,
+ "rdp_module: ft: chunk with zero size received on"
+ " position %lu of %lu",
+ clipboard->my_rdp_context->ft_to_client->transferred,
+ clipboard->my_rdp_context->ft_to_client->file_size);
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ goto transfer_failed;
+ }
+ else if (clipboard->my_rdp_context->ft_to_client->transfer_id
+ != resp->streamId)
+ {
+ const char *msg
+ = "rdp_module: clipboard: ft: transfer_id mismatch in "
+ "ServerFileContentsResponse";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ goto transfer_failed;
+ }
+ else if (resp->cbRequested)
+ {
+ chunk.transfer_id = resp->streamId;
+ chunk.size = resp->cbRequested;
+ clipboard->my_rdp_context->my_internals->core->api_filetransfers
+ ->ft_send_chunk(&chunk, (uint8_t *)(resp->requestedData),
+ clipboard->my_rdp_context->my_internals->task_info);
+ clipboard->my_rdp_context->ft_to_client->transferred
+ += resp->cbRequested;
+ }
+ /* request next file chunk */
+ if (clipboard->my_rdp_context->ft_to_client->transferred
+ < clipboard->my_rdp_context->ft_to_client->file_size)
+ {
+ my_rdp_context *c = clipboard->my_rdp_context;
+ CLIPRDR_FILE_CONTENTS_REQUEST file_req;
+ uint32_t chunk_len = 512000;
+ uint64_t left = 0;
+ memset(&file_req, 0, sizeof(CLIPRDR_FILE_CONTENTS_REQUEST));
+
+ left
+ = c->ft_to_client->file_size - c->ft_to_client->transferred;
+ if (left < chunk_len)
+ {
+ chunk_len = left;
+ }
+ file_req.cbRequested = chunk_len;
+ c->ft_to_client->transfer_id = rand();
+ file_req.streamId = c->ft_to_client->transfer_id;
+ file_req.listIndex = c->ft_to_client->file_id;
+ file_req.dwFlags = FILECONTENTS_RANGE;
+
+ /* TODO: check/change endianness */
+ memcpy(&(file_req.nPositionLow),
+ &(c->ft_to_client->transferred), 4);
+ memcpy(&(file_req.nPositionHigh),
+ (((uint8_t *)&(c->ft_to_client->transferred)) + 4), 4);
+ c->clipboard->clip_context->ClientFileContentsRequest(
+ c->clipboard->clip_context, &file_req);
+ }
+ else if (clipboard->my_rdp_context->ft_to_client->is_runing == true)
+ {
+ my_rdp_context *c = clipboard->my_rdp_context;
+ wrdp_backend_ft_status status;
+ {
+ char msg[128];
+ snprintf(msg, 127,
+ "rdp_module: ft: status: transfer finished,"
+ " transfered size: %lu",
+ clipboard->my_rdp_context->ft_to_client
+ ->transferred);
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_trace, 0);
+ }
+ memset(&status, 0, sizeof(wrdp_backend_ft_status));
+ status.status = ft_status_success;
+ status.file_id = c->ft_to_client->file_id;
+ status.transfer_id = c->ft_to_client->transfer_id;
+ c->my_internals->core->api_filetransfers->ft_finish(
+ &status, c->my_internals->task_info);
+ clipboard->my_rdp_context->ft_to_client->file_size = 0;
+ clipboard->my_rdp_context->ft_to_client->transfer_id = 0;
+ clipboard->my_rdp_context->ft_to_client->file_id = 0;
+ clipboard->my_rdp_context->ft_to_client->is_runing = false;
+ clipboard->my_rdp_context->ft_to_client->transferred = 0;
+ }
+ else
+ {
+ const char *msg = "rdp_module: clipboard: ft:"
+ " unexpected data chunk on finished transfer";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_warning, 0);
+ }
+ return CHANNEL_RC_OK;
+transfer_failed:
+{
+ my_rdp_context *c = clipboard->my_rdp_context;
+ wrdp_backend_ft_status status;
+ memset(&status, 0, sizeof(wrdp_backend_ft_status));
+ status.status = ft_status_failure;
+ status.file_id = c->ft_to_client->file_id;
+ status.transfer_id = c->ft_to_client->transfer_id;
+ c->my_internals->core->api_filetransfers->ft_finish(
+ &status, c->my_internals->task_info);
+ clipboard->my_rdp_context->ft_to_client->file_size = 0;
+ clipboard->my_rdp_context->ft_to_client->transfer_id = 0;
+ clipboard->my_rdp_context->ft_to_client->file_id = 0;
+ clipboard->my_rdp_context->ft_to_client->is_runing = false;
+ clipboard->my_rdp_context->ft_to_client->transferred = 0;
+}
+ return CHANNEL_RC_OK;
+}
+
+void
+rdp_cliprdr_init(my_rdp_context *ctx, CliprdrClientContext *cliprdr)
+{
+ ctx->clipboard->clip_context = cliprdr;
+ cliprdr->custom = (void *)ctx->clipboard;
+ ctx->clipboard->my_internals = ctx->my_internals;
+ cliprdr->MonitorReady = rdp_clip_MonitorReady;
+ cliprdr->ServerCapabilities = rdp_clip_ServerCapabilities;
+ cliprdr->ServerFormatList = rdp_clip_ServerFormatList;
+ cliprdr->ServerFormatListResponse = rdp_clip_ServerFormatListResponse;
+ cliprdr->ServerFormatDataRequest = rdp_clip_ServerFormatDataRequest;
+ cliprdr->ServerFormatDataResponse = rdp_clip_ServerFormatDataResponse;
+ cliprdr->ServerFileContentsRequest = rdp_clip_ServerFileContentsRequest;
+ cliprdr->ServerFileContentsResponse
+ = rdp_clip_ServerFileContentsResponse;
+ cliprdr->ServerLockClipboardData = rdp_clip_ServerLockClipboardData;
+ cliprdr->ServerUnlockClipboardData = rdp_clip_ServerUnlockClipboardData;
+}
+
+void
+rdp_cliprdr_uninit(my_rdp_context *ctx, CliprdrClientContext *cliprdr)
+{
+ /* do not need to do it here, done during backend destruction */
+ /* cliprdr->custom = NULL;
+
+ if (ctx->clipboard)
+ ctx->clipboard->clip_context = NULL;
+ ctx->clipboard = NULL; */
+}
+
+/* unused for now
+ * static UINT
+ClientRequestFileSize(wClipboardDelegate* delegate,
+ const wClipboardFileSizeRequest* request)
+{
+ return 0;
+}*/
+
+static UINT
+ClipboardFileSizeSuccess(wClipboardDelegate *delegate,
+ const wClipboardFileSizeRequest *request, UINT64 fileSize)
+{
+ CLIPRDR_FILE_CONTENTS_RESPONSE response;
+ my_rdp_clipboard *clipboard = delegate->custom;
+ ZeroMemory(&response, sizeof(response));
+ response.common.msgFlags = CB_RESPONSE_OK;
+ response.streamId = request->streamId;
+ response.cbRequested = sizeof(UINT64);
+ response.requestedData = (BYTE *)&fileSize;
+ return clipboard->clip_context->ClientFileContentsResponse(
+ clipboard->clip_context, &response);
+ // return 0;
+}
+static UINT
+ClipboardFileSizeFailure(wClipboardDelegate *delegate,
+ const wClipboardFileSizeRequest *request, UINT errorCode)
+{
+ CLIPRDR_FILE_CONTENTS_RESPONSE response;
+ my_rdp_clipboard *clipboard = delegate->custom;
+ ZeroMemory(&response, sizeof(response));
+ response.common.msgFlags = CB_RESPONSE_FAIL;
+ response.streamId = request->streamId;
+ return clipboard->clip_context->ClientFileContentsResponse(
+ clipboard->clip_context, &response);
+}
+
+/* unused for now
+ * static UINT
+ClientRequestFileRange(wClipboardDelegate* delegate,
+ const wClipboardFileRangeRequest* request)
+{
+ return 0;
+} */
+
+static UINT
+ClipboardFileRangeSuccess(wClipboardDelegate *delegate,
+ const wClipboardFileRangeRequest *request, const BYTE *data, UINT32 size)
+{
+ CLIPRDR_FILE_CONTENTS_RESPONSE response;
+ my_rdp_clipboard *clipboard = delegate->custom;
+ ZeroMemory(&response, sizeof(response));
+ response.common.msgFlags = CB_RESPONSE_OK;
+ response.streamId = request->streamId;
+ response.cbRequested = size;
+ response.requestedData = (BYTE *)data;
+ return clipboard->clip_context->ClientFileContentsResponse(
+ clipboard->clip_context, &response);
+}
+
+static UINT
+ClipboardFileRangeFailure(wClipboardDelegate *delegate,
+ const wClipboardFileRangeRequest *request, UINT errorCode)
+{
+ CLIPRDR_FILE_CONTENTS_RESPONSE response;
+ my_rdp_clipboard *clipboard = delegate->custom;
+ ZeroMemory(&response, sizeof(response));
+ response.common.msgFlags = CB_RESPONSE_FAIL;
+ response.streamId = request->streamId;
+ return clipboard->clip_context->ClientFileContentsResponse(
+ clipboard->clip_context, &response);
+}
+
+my_rdp_clipboard *
+rdp_clipboard_new(my_rdp_context *context)
+{
+ my_rdp_clipboard *clip = calloc(1, sizeof(my_rdp_clipboard));
+ if (!clip)
+ {
+ perror("calloc");
+ return 0;
+ }
+ clip->my_rdp_context = context;
+ clip->clipboard = ClipboardCreate();
+ clip->delegate = ClipboardGetDelegate(clip->clipboard);
+ clip->delegate->custom = clip;
+
+ clip->delegate->ClipboardFileSizeSuccess = ClipboardFileSizeSuccess;
+ clip->delegate->ClipboardFileSizeFailure = ClipboardFileSizeFailure;
+ clip->delegate->ClipboardFileRangeSuccess = ClipboardFileRangeSuccess;
+ clip->delegate->ClipboardFileRangeFailure = ClipboardFileRangeFailure;
+
+ context->clipboard = clip;
+
+ return clip;
+}
+
+static UINT
+cliprdr_send_data_request(my_rdp_clipboard *clipboard)
+{
+ CLIPRDR_FORMAT_DATA_REQUEST request;
+ ZeroMemory(&request, sizeof(CLIPRDR_FORMAT_DATA_REQUEST));
+ request.requestedFormatId = clipboard->cli_req_fmt_id.rdp_fmt.formatId;
+ return clipboard->clip_context->ClientFormatDataRequest(
+ clipboard->clip_context, &request);
+}
+
+static bool
+clip_api_handle_request_data(
+ const wrdp_backend_clipbrd_data_request *request, void *backend_internals)
+{
+ rdp_internals *i = backend_internals;
+ i->context->clipboard->cli_req_fmt_id.my_fmt = request->format;
+ switch (request->format)
+ {
+ case clip_format_raw:
+ {
+ /* i->context->clipboard->cli_req_fmt_id = CF_RAW;
+ * raw format does not work for some reason
+ */
+ i->context->clipboard->cli_req_fmt_id.rdp_fmt.formatId
+ = CF_TEXT;
+ }
+ break;
+ case clip_format_text:
+ {
+ i->context->clipboard->cli_req_fmt_id.rdp_fmt.formatId
+ = CF_TEXT;
+ }
+ break;
+ case clip_format_file_list:
+ {
+ /* 4.5.3 Format Data Request PDU
+ The following is an annotated dump of a Format
+ Data Request PDU (section 2.2.5.1). The format being
+ requested is the File List that was advertised
+ in section 4.5.1 (the advertised ID in the Format
+ List PDU was 49273).
+ */
+ uint8_t p;
+ bool format_found = false;
+ for (p = 0; p < i->context->clipboard->srv_fmts_count;
+ ++p)
+ {
+ if (!strcmp("FileGroupDescriptorW",
+ i->context->clipboard->srv_fmts[p]
+ .rdp_fmt.formatName))
+ {
+ i->context->clipboard->cli_req_fmt_id
+ .rdp_fmt.formatId
+ = i->context->clipboard->srv_fmts[p]
+ .rdp_fmt.formatId;
+ format_found = true;
+ break;
+ }
+ }
+ if (!format_found)
+ {
+ const char *msg = "rdp_module: cliprdr: "
+ "requested data format"
+ " id not found";
+ i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_warning, 0);
+ i->context->clipboard->cli_req_fmt_id.rdp_fmt
+ .formatId
+ = CF_RAW;
+ }
+ }
+ break;
+ case clip_format_unicode:
+ {
+ i->context->clipboard->cli_req_fmt_id.rdp_fmt.formatId
+ = CF_UNICODETEXT;
+ }
+ break;
+ default:
+ {
+ const char *msg
+ = "rdp_module: cliprdr: unsuported data format"
+ " requested";
+ i->core->api_utils->log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_warning, 0);
+ }
+ break;
+ }
+ cliprdr_send_data_request(i->context->clipboard);
+ return true;
+}
+
+static bool
+clip_api_handle_data_changed(
+ const wrdp_backend_clipbrd_fmts *fmts, void *backend_internals)
+{
+ rdp_internals *i = backend_internals;
+ my_rdp_clipboard *clip = i->context->clipboard;
+ uint8_t pos = 0;
+
+ CLIPRDR_FORMAT *formats = 0;
+ CLIPRDR_FORMAT_LIST formatList;
+
+ if (!fmts->count)
+ {
+ const char *msg = "received empty clipboard formats list";
+ i->core->api_utils->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_warning, 0);
+ return false;
+ }
+ formats = calloc(fmts->count, sizeof(CLIPRDR_FORMAT));
+ if (!formats)
+ {
+ perror("calloc");
+ return false;
+ }
+
+ for (; pos < fmts->count; ++pos)
+ {
+ switch ((wrdp_enum_clip_format)fmts->formats[pos])
+ {
+ case clip_format_raw:
+ {
+ formats[pos].formatId = CF_RAW;
+ }
+ break;
+ case clip_format_text:
+ {
+ formats[pos].formatId = CF_TEXT;
+ }
+ break;
+ case clip_format_unicode:
+ {
+ formats[pos].formatId = CF_UNICODETEXT;
+ }
+ break;
+ case clip_format_file_list:
+ {
+ formats[pos].formatId = CB_FORMAT_TEXTURILIST;
+ formats[pos].formatName
+ = "FileGroupDescriptorW";
+ }
+ break;
+ default:
+ {
+ formats[pos].formatId = CF_RAW;
+ }
+ break;
+ }
+ }
+ formatList.common.msgFlags = CB_RESPONSE_OK;
+ formatList.numFormats = fmts->count;
+ formatList.formats = formats;
+ /* TODO: check return value of ClientFormatList */
+ clip->clip_context->ClientFormatList(clip->clip_context, &formatList);
+ free(formats);
+
+ return true;
+}
+
+static bool
+clip_api_handle_send_data(
+ const wrdp_backend_clipbrd_data *data, void *backend_internals)
+{
+ rdp_internals *i = backend_internals;
+ my_rdp_clipboard *clip = i->context->clipboard;
+ CLIPRDR_FORMAT_DATA_RESPONSE response;
+
+ uint8_t type = data->data[0];
+ memset(&response, 0, sizeof(CLIPRDR_FORMAT_DATA_RESPONSE));
+ response.common.msgType = CB_FORMAT_DATA_RESPONSE;
+ response.common.msgFlags = CB_RESPONSE_FAIL;
+ response.common.dataLen = 0;
+ response.requestedFormatData = 0;
+#ifdef DEBUG
+ {
+ const char *msg
+ = "rdp_module: clipboard: from client: raw clipboard"
+ " data:";
+ i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_trace, 0);
+ i->core->api_utils->log_msg(data->data, data->size,
+ wrdp_log_level_trace, wrdp_log_flag_binary);
+ }
+#endif
+ switch (type)
+ {
+ case clip_format_raw:
+ case clip_format_text:
+ {
+ if (data->size > 1)
+ {
+ response.common.msgFlags = CB_RESPONSE_OK;
+ response.common.dataLen = data->size - 1;
+ response.requestedFormatData = data->data + 1;
+ }
+ clip->clip_context->ClientFormatDataResponse(
+ clip->clip_context, &response);
+ return true;
+ }
+ break;
+ case clip_format_unicode:
+ {
+ uint8_t *data_buf = 0;
+ BYTE *msg_buf = 0;
+ iconv_t utf8_to_utf16 = iconv_open("UTF-16LE", "UTF-8");
+ char *ptr_w, *ptr_r;
+ size_t text_len_utf16 = (data->size - 1) * 2,
+ text_len_utf8 = data->size - 1,
+ text_len_result = text_len_utf16;
+ data_buf = calloc(text_len_utf16, sizeof(uint8_t));
+ if (!data_buf)
+ {
+ perror("calloc");
+ return true;
+ }
+ ptr_w = (char *)data_buf;
+ ptr_r = (char *)(data->data + 1);
+ while (text_len_utf8 && text_len_utf16)
+ {
+ if (iconv(utf8_to_utf16, &ptr_r, &text_len_utf8,
+ &ptr_w, &text_len_utf16)
+ == (size_t)-1)
+ {
+ log_msg_info mi = {0};
+ free(data_buf);
+ clip->clip_context
+ ->ClientFormatDataResponse(
+ clip->clip_context, &response);
+ mi.level = wrdp_log_level_warning;
+ mi.buf = (const uint8_t
+ *)"rdp_module: cliprdr: iconv: "
+ "failed to encode outgoing "
+ "buffer from utf8 to utf16le";
+ mi.task_info = i->task_info;
+ i->core->api_utils->log_msg_ex(&mi);
+ iconv_close(utf8_to_utf16);
+ return true;
+ }
+ }
+#ifdef DEBUG
+ if (text_len_utf8 && !text_len_utf16)
+ {
+ log_msg_info mi = {0};
+ mi.level = wrdp_log_level_trace;
+ mi.buf = (const uint8_t
+ *)"rdp_module: cliprdr: iconv: not "
+ "sufficient space in output buffer";
+ mi.task_info = i->task_info;
+ i->core->api_utils->log_msg_ex(&mi);
+ }
+#endif
+ iconv(utf8_to_utf16, 0, 0, &ptr_w, &text_len_utf16);
+ text_len_result = text_len_result - text_len_utf16;
+ iconv_close(utf8_to_utf16);
+ if (text_len_result)
+ {
+ msg_buf = calloc(text_len_result, sizeof(BYTE));
+ memcpy(msg_buf, data_buf, text_len_result);
+ free(data_buf);
+ response.common.msgFlags = CB_RESPONSE_OK;
+ response.common.dataLen = text_len_result;
+ response.requestedFormatData = msg_buf;
+ }
+#ifdef DEBUG
+ if (msg_buf)
+ {
+ log_msg_info mi = {0};
+ mi.level = wrdp_log_level_trace;
+ mi.buf = (const uint8_t
+ *)"rdp_module: clipboard: to server:"
+ " utf16 encoded buffer";
+ mi.task_info = i->task_info;
+ i->core->api_utils->log_msg_ex(&mi);
+ mi.flags = wrdp_log_flag_binary;
+ mi.buf = msg_buf;
+ mi.buf_size = text_len_result;
+ i->core->api_utils->log_msg_ex(&mi);
+ }
+#endif
+ clip->clip_context->ClientFormatDataResponse(
+ clip->clip_context, &response);
+ if (msg_buf)
+ {
+ free(msg_buf);
+ }
+ return true;
+ }
+ break;
+ case clip_format_file_list:
+ {
+ uint16_t file_count;
+ bool broken_msg = false;
+ int _i;
+ uint64_t data_offset = 3;
+ FILEDESCRIPTORW *file_list = 0;
+ BYTE *format_data = 0;
+ UINT32 format_data_length = 0;
+ memcpy(&file_count, data->data + 1, 2);
+ if (!file_count)
+ {
+ const char *msg = "rdp_module: clipboard: ft: "
+ "empty file_list received";
+ i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_warning, 0);
+ return true;
+ }
+ if (clip->client_filelist_cache)
+ {
+ free(clip->client_filelist_cache);
+ }
+ clip->client_filelist_cache
+ = calloc(file_count, sizeof(file_list_cache_entry));
+ if (!clip->client_filelist_cache)
+ {
+ perror("calloc");
+ return false;
+ }
+ file_list = calloc(file_count, sizeof(FILEDESCRIPTOR));
+ if (!file_list)
+ {
+ perror("calloc");
+ return false;
+ }
+ for (_i = 0; _i < file_count; ++_i)
+ {
+ uint16_t filename_len;
+ uint32_t file_id;
+ uint64_t file_size;
+ char *filename, *ptr_w, *ptr_r;
+ iconv_t utf8_to_utf16
+ = iconv_open("UTF-16LE", "UTF-8");
+ size_t text_len_utf16 = 260 * sizeof(WCHAR),
+ text_len_utf8;
+ memcpy(
+ &filename_len, data->data + data_offset, 2);
+ data_offset += 2;
+ if (data_offset >= data->size)
+ {
+ broken_msg = true;
+ break;
+ }
+ text_len_utf8 = filename_len;
+ memcpy(&file_id, data->data + data_offset, 4);
+ clip->client_filelist_cache[_i].file_id
+ = file_id;
+ data_offset += 4;
+ if (data_offset >= data->size)
+ {
+ broken_msg = true;
+ break;
+ }
+ memcpy(&file_size, data->data + data_offset, 8);
+ clip->client_filelist_cache[_i].file_size
+ = file_size;
+ data_offset += 8;
+ if ((data_offset >= data->size)
+ && (_i < file_count))
+ {
+ broken_msg = true;
+ break;
+ }
+ filename = (char *)data->data + data_offset;
+ ptr_r = filename;
+ ptr_w = (char *)(file_list[_i].cFileName);
+ iconv(utf8_to_utf16, &ptr_r, &text_len_utf8,
+ &ptr_w, &text_len_utf16);
+ iconv(utf8_to_utf16, 0, 0, &ptr_w,
+ &text_len_utf16);
+ iconv_close(utf8_to_utf16);
+ data_offset += filename_len;
+ file_list[_i].dwFileAttributes
+ = FILE_ATTRIBUTE_NORMAL;
+ file_list[_i].dwFlags = FD_ATTRIBUTES
+ | FD_FILESIZE
+ | FD_SHOWPROGRESSUI;
+ memcpy(&(file_list[_i].nFileSizeLow),
+ &file_size, 4);
+ memcpy(&(file_list[_i].nFileSizeHigh),
+ ((uint8_t *)&file_size) + 4, 4);
+ }
+ if (broken_msg)
+ {
+ char msg[128];
+ snprintf(msg, 127,
+ "rdp_module: cliprdr: error: wrong file"
+ " list message size: %d, file count: %d\n",
+ data->size, file_count);
+ i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ }
+ else
+ {
+ UINT error = NO_ERROR;
+ error = cliprdr_serialize_file_list(file_list,
+ file_count, &format_data,
+ &format_data_length);
+ if (error)
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "failed to serialize "
+ "CLIPRDR_FILELIST:"
+ " 0x%08X",
+ error);
+ i->core->api_utils->log_msg(
+ (const uint8_t *)buf, strlen(buf),
+ wrdp_log_level_error, 0);
+ }
+ }
+ free(file_list);
+ if (format_data_length)
+ {
+ response.common.msgFlags = CB_RESPONSE_OK;
+ response.common.dataLen = format_data_length;
+ response.requestedFormatData = format_data;
+ }
+ clip->clip_context->ClientFormatDataResponse(
+ clip->clip_context, &response);
+
+ free(format_data);
+ return true;
+ }
+ break;
+ default:
+ {
+ response.common.msgFlags = CB_RESPONSE_FAIL;
+ response.common.dataLen = 0;
+ response.requestedFormatData = 0;
+ clip->clip_context->ClientFormatDataResponse(
+ clip->clip_context, &response);
+ return true;
+ }
+ break;
+ }
+ /* TODO: check return code from ClientFormatDataResponse */
+ return true;
+}
+
+void
+register_clipboard(wrdp_backend_module *backend)
+{
+ backend->callbacks_clipbrd->request_data = clip_api_handle_request_data;
+ backend->callbacks_clipbrd->send_data = clip_api_handle_send_data;
+ backend->callbacks_clipbrd->data_changed = clip_api_handle_data_changed;
+}
diff --git a/src/rdp/rdp_clipboard.h b/src/rdp/rdp_clipboard.h
new file mode 100644
index 0000000..1536a2c
--- /dev/null
+++ b/src/rdp/rdp_clipboard.h
@@ -0,0 +1,52 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "webrdp_core_api.h"
+#include "webrdp_module_api.h"
+#include "rdp_backend_api.h"
+#include "rdp_impl.h"
+
+typedef struct
+{
+ CLIPRDR_FORMAT rdp_fmt;
+ wrdp_enum_clip_format my_fmt;
+} my_clip_format;
+
+typedef struct
+{
+ uint32_t file_id;
+ uint64_t file_size;
+} file_list_cache_entry;
+
+struct my_rdp_clipboard_s
+{
+ wClipboard *clipboard;
+ CliprdrClientContext *clip_context;
+ my_rdp_context *my_rdp_context;
+ wClipboardDelegate *delegate;
+ bool streams_supported;
+ uint8_t *data_raw;
+ /* we need to cache server side format list to know dynamic format id */
+ my_clip_format *srv_fmts, cli_req_fmt_id, srv_req_fmt_id;
+ uint8_t srv_fmts_count;
+ file_list_cache_entry *client_filelist_cache;
+
+ rdp_internals *my_internals;
+};
+
+my_rdp_clipboard *rdp_clipboard_new(my_rdp_context *context);
+
+void rdp_cliprdr_init(my_rdp_context *ctx, CliprdrClientContext *cliprdr);
+
+void rdp_cliprdr_uninit(my_rdp_context *ctx, CliprdrClientContext *cliprdr);
+
+void register_clipboard(wrdp_backend_module *backend);
diff --git a/src/rdp/rdp_display_output.c b/src/rdp/rdp_display_output.c
new file mode 100644
index 0000000..16f3c5d
--- /dev/null
+++ b/src/rdp/rdp_display_output.c
@@ -0,0 +1,871 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <freerdp/client/cmdline.h>
+#include <freerdp/client/file.h>
+#include <freerdp/constants.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/log.h>
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#include <webrdp_core_api.h>
+#include <webrdp_module_api.h>
+
+#include "rdp_backend_api.h"
+#include "rdp_impl.h"
+#include "rdp_png.h"
+
+#define RGB_555_888(_r, _g, _b) \
+ _r = (_r << 3 & ~0x7) | (_r >> 2); \
+ _g = (_g << 3 & ~0x7) | (_g >> 2); \
+ _b = (_b << 3 & ~0x7) | (_b >> 2);
+#define RGB_565_888(_r, _g, _b) \
+ _r = (_r << 3 & ~0x7) | (_r >> 2); \
+ _g = (_g << 2 & ~0x3) | (_g >> 4); \
+ _b = (_b << 3 & ~0x7) | (_b >> 2);
+#define GetRGB_555(_r, _g, _b, _p) \
+ _r = (_p & 0x7C00) >> 10; \
+ _g = (_p & 0x3E0) >> 5; \
+ _b = (_p & 0x1F);
+#define GetRGB_565(_r, _g, _b, _p) \
+ _r = (_p & 0xF800) >> 11; \
+ _g = (_p & 0x7E0) >> 5; \
+ _b = (_p & 0x1F);
+#define GetRGB15(_r, _g, _b, _p) \
+ GetRGB_555(_r, _g, _b, _p); \
+ RGB_555_888(_r, _g, _b);
+#define GetRGB16(_r, _g, _b, _p) \
+ GetRGB_565(_r, _g, _b, _p); \
+ RGB_565_888(_r, _g, _b);
+#define RGB24(_r, _g, _b) ((_r << 16) | (_g << 8) | _b)
+#define GetRGB24(_r, _g, _b, _p) \
+ _r = (_p & 0xFF0000) >> 16; \
+ _g = (_p & 0xFF00) >> 8; \
+ _b = (_p & 0xFF);
+#define GetRGB32(_r, _g, _b, _p) \
+ _r = (_p & 0xFF0000) >> 16; \
+ _g = (_p & 0xFF00) >> 8; \
+ _b = (_p & 0xFF);
+#define ARGB32(_a, _r, _g, _b) (_a << 24) | (_r << 16) | (_g << 8) | _b
+#define RGB16(_r, _g, _b) \
+ (((_r >> 3) & 0x1F) << 11) | (((_g >> 2) & 0x3F) << 5) \
+ | ((_b >> 3) & 0x1F)
+#define RGB15(_r, _g, _b) \
+ (((_r >> 3) & 0x1F) << 10) | (((_g >> 3) & 0x1F) << 5) \
+ | ((_b >> 3) & 0x1F)
+#define GetARGB32(_a, _r, _g, _b, _p) \
+ _a = (_p & 0xFF000000) >> 24; \
+ _r = (_p & 0xFF0000) >> 16; \
+ _g = (_p & 0xFF00) >> 8; \
+ _b = (_p & 0xFF);
+
+HCLRCONV
+freerdp_clrconv_new(UINT32 flags)
+{
+ HCLRCONV clrconv;
+
+ clrconv = (CLRCONV *)calloc(1, sizeof(CLRCONV));
+
+ if (!clrconv)
+ {
+ perror("calloc");
+ return 0;
+ }
+
+ clrconv->alpha = (flags & CLRCONV_ALPHA) ? TRUE : FALSE;
+ clrconv->invert = (flags & CLRCONV_INVERT) ? TRUE : FALSE;
+ clrconv->rgb555 = (flags & CLRCONV_RGB555) ? TRUE : FALSE;
+
+ clrconv->palette = (rdpPalette *)calloc(1, sizeof(rdpPalette));
+
+ if (!clrconv->palette)
+ {
+ perror("calloc");
+ free(clrconv);
+ return 0;
+ }
+
+ return clrconv;
+}
+
+void
+freerdp_clrconv_free(HCLRCONV clrconv)
+{
+ if (clrconv)
+ {
+ free(clrconv->palette);
+ free(clrconv);
+ }
+}
+
+static int
+freerdp_get_pixel(BYTE *data, int x, int y, int width, int height, int bpp)
+{
+ int start;
+ int shift;
+ UINT16 *src16;
+ UINT32 *src32;
+ int red, green, blue;
+
+ switch (bpp)
+ {
+ case 1:
+ width = (width + 7) / 8;
+ start = (y * width) + x / 8;
+ shift = x % 8;
+ return (data[start] & (0x80 >> shift)) != 0;
+ case 8:
+ return data[y * width + x];
+ case 15:
+ case 16:
+ src16 = (UINT16 *)data;
+ return src16[y * width + x];
+ case 24:
+ data += y * width * 3;
+ data += x * 3;
+ red = data[0];
+ green = data[1];
+ blue = data[2];
+ return RGB24(red, green, blue);
+ case 32:
+ src32 = (UINT32 *)data;
+ return src32[y * width + x];
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static void
+freerdp_set_pixel(
+ BYTE *data, int x, int y, int width, int height, int bpp, int pixel)
+{
+ int start;
+ int shift;
+ int *dst32;
+
+ if (bpp == 1)
+ {
+ width = (width + 7) / 8;
+ start = (y * width) + x / 8;
+ shift = x % 8;
+ if (pixel)
+ data[start] = data[start] | (0x80 >> shift);
+ else
+ data[start] = data[start] & ~(0x80 >> shift);
+ }
+ else if (bpp == 32)
+ {
+ dst32 = (int *)data;
+ dst32[y * width + x] = pixel;
+ }
+}
+
+static void
+freerdp_color_split_rgb(UINT32 *color, int bpp, BYTE *red, BYTE *green,
+ BYTE *blue, BYTE *alpha, HCLRCONV clrconv)
+{
+ *red = *green = *blue = 0;
+ *alpha = (clrconv->alpha) ? 0xFF : 0x00;
+
+ switch (bpp)
+ {
+ case 32:
+ if (clrconv->alpha)
+ {
+ GetARGB32(*alpha, *red, *green, *blue, *color);
+ }
+ else
+ {
+ GetRGB32(*red, *green, *blue, *color);
+ }
+ break;
+
+ case 24:
+ GetRGB24(*red, *green, *blue, *color);
+ break;
+
+ case 16:
+ GetRGB16(*red, *green, *blue, *color);
+ break;
+
+ case 15:
+ GetRGB15(*red, *green, *blue, *color);
+ break;
+
+ case 8:
+ *color &= 0xFF;
+ *red = clrconv->palette->entries[*color].red;
+ *green = clrconv->palette->entries[*color].green;
+ *blue = clrconv->palette->entries[*color].blue;
+ break;
+
+ case 1:
+ if (*color != 0)
+ {
+ *red = 0xFF;
+ *green = 0xFF;
+ *blue = 0xFF;
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+freerdp_color_make_rgb(UINT32 *color, int bpp, BYTE *red, BYTE *green,
+ BYTE *blue, BYTE *alpha, HCLRCONV clrconv)
+{
+ switch (bpp)
+ {
+ case 32:
+ *color = ARGB32(*alpha, *red, *green, *blue);
+ break;
+
+ case 24:
+ *color = RGB24(*red, *green, *blue);
+ break;
+
+ case 16:
+ if (clrconv->rgb555)
+ {
+ *color = RGB15(*red, *green, *blue);
+ }
+ else
+ {
+ *color = RGB16(*red, *green, *blue);
+ }
+ break;
+
+ case 15:
+ *color = RGB15(*red, *green, *blue);
+ break;
+
+ case 8:
+ *color = RGB24(*red, *green, *blue);
+ break;
+
+ case 1:
+ if ((*red != 0) || (*green != 0) || (*blue != 0))
+ *color = 1;
+ break;
+
+ default:
+ break;
+ }
+}
+
+static UINT32
+freerdp_color_convert_rgb(
+ UINT32 srcColor, int srcBpp, int dstBpp, HCLRCONV clrconv)
+{
+ BYTE red = 0;
+ BYTE green = 0;
+ BYTE blue = 0;
+ BYTE alpha = 0xFF;
+ UINT32 dstColor = 0;
+
+ freerdp_color_split_rgb(
+ &srcColor, srcBpp, &red, &green, &blue, &alpha, clrconv);
+ freerdp_color_make_rgb(
+ &dstColor, dstBpp, &red, &green, &blue, &alpha, clrconv);
+
+ return dstColor;
+}
+
+static void
+freerdp_alpha_cursor_convert(BYTE *alphaData, BYTE *xorMask, BYTE *andMask,
+ int width, int height, int bpp, HCLRCONV clrconv)
+{
+ UINT32 xorPixel;
+ UINT32 andPixel;
+ UINT32 x, y, jj;
+
+ for (y = 0; y < height; y++)
+ {
+ jj = (bpp == 1) ? y : (height - 1) - y;
+
+ for (x = 0; x < width; x++)
+ {
+ xorPixel = freerdp_get_pixel(
+ xorMask, x, jj, width, height, bpp);
+ xorPixel = freerdp_color_convert_rgb(
+ xorPixel, bpp, 32, clrconv);
+ andPixel = freerdp_get_pixel(
+ andMask, x, jj, width, height, 1);
+
+ if (andPixel)
+ {
+ if ((xorPixel & 0xFFFFFF) == 0xFFFFFF)
+ {
+ /* use pattern (not solid black) for xor
+ * area */
+ xorPixel = (x & 1) == (y & 1);
+ xorPixel = xorPixel ? 0xFFFFFF : 0;
+ xorPixel |= 0xFF000000;
+ }
+ else if (xorPixel == 0xFF000000)
+ {
+ xorPixel = 0;
+ }
+ }
+
+ freerdp_set_pixel(
+ alphaData, x, y, width, height, 32, xorPixel);
+ }
+ }
+}
+
+static void
+freerdp_bitmap_flip(BYTE *src, BYTE *dst, int scanLineSz, int height)
+{
+ int i;
+
+ BYTE *bottomLine = dst + (scanLineSz * (height - 1));
+ BYTE *topLine = src;
+
+ /* Special processing if called for flip-in-place. */
+ if (src == dst)
+ {
+ /* Allocate a scanline buffer.
+ * (FIXME: malloc / xfree below should be replaced by "get/put
+ * scanline buffer from a pool/Q of fixed buffers" to reuse
+ * fixed size buffers (of max scanline size (or adaptative?) )
+ * -- would be much faster).
+ */
+ BYTE *tmpBfr = (BYTE *)winpr_aligned_malloc(scanLineSz, 16);
+ if (!tmpBfr)
+ {
+ return;
+ }
+ int half = height / 2;
+ /* Flip buffer in place by line permutations through the temp
+ * scan line buffer.
+ * Note that if height has an odd number of line, we don't need
+ * to move the center scanline anyway.
+ * Also note that in place flipping takes three memcpy() calls
+ * to process two scanlines while src to distinct dest would
+ * only requires two memcpy() calls for two scanlines.
+ */
+ height--;
+ for (i = 0; i < half; i++)
+ {
+ CopyMemory(tmpBfr, topLine, scanLineSz);
+ CopyMemory(topLine, bottomLine, scanLineSz);
+ CopyMemory(bottomLine, tmpBfr, scanLineSz);
+ topLine += scanLineSz;
+ bottomLine -= scanLineSz;
+ height--;
+ }
+ winpr_aligned_free(tmpBfr);
+ }
+ /* Flip from source buffer to destination buffer. */
+ else
+ {
+ for (i = 0; i < height; i++)
+ {
+ if (bottomLine && topLine && scanLineSz)
+ {
+ CopyMemory(bottomLine, topLine, scanLineSz);
+ topLine += scanLineSz;
+ bottomLine -= scanLineSz;
+ }
+ }
+ }
+}
+
+static BYTE *
+freerdp_image_flip(BYTE *srcData, BYTE *dstData, int width, int height, int bpp)
+{
+ int scanline;
+
+ scanline = width * ((bpp + 7) / 8);
+
+ if (!dstData)
+ dstData = (BYTE *)winpr_aligned_malloc(
+ width * height * ((bpp + 7) / 8), 16);
+
+ if (!dstData)
+ {
+ return NULL;
+ }
+
+ freerdp_bitmap_flip(srcData, dstData, scanline, height);
+
+ return dstData;
+}
+
+/* outgoing rdp protocol to client implementation */
+
+static BOOL
+Pointer_New(rdpContext *context, rdpPointer *pointer)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ HCLRCONV hclrconv = c->my_internals->clrconv;
+ size_t psize = pointer->width * pointer->height * 4;
+
+ my_rdp_pointer *p = (my_rdp_pointer *)pointer;
+ p->id = c->my_internals->m_ptrId++;
+ uint8_t *pixels = malloc(sizeof(uint8_t) * psize);
+ if (!pixels)
+ {
+ perror("malloc");
+ return FALSE;
+ }
+ memset(pixels, 0, psize);
+ if ((pointer->andMaskData != 0) && (pointer->xorMaskData != 0))
+ {
+ //XXX
+ freerdp_alpha_cursor_convert(pixels, pointer->xorMaskData,
+ pointer->andMaskData, pointer->width, pointer->height,
+ pointer->xorBpp, hclrconv);
+ }
+
+ //check if the cursor is fully transparent
+ bool transparent = TRUE;
+ for (int y = 0; y < pointer->height; y++)
+ {
+ for (int x = 0; x < pointer->width; x++)
+ {
+ if (pixels[0 + x * 4 + y * 4 * pointer->width] != 0)
+ {
+ transparent = FALSE;
+ }
+ }
+ }
+ if (transparent)
+ {
+ pixels[3] = 1;
+ }
+
+ rdp_png_buf png_buf;
+ memset(&png_buf, 0, sizeof(png_buf));
+ png_buf.buf_size
+ = (pointer->width * pointer->height * sizeof(uint32_t))
+ + 8 /* allocating fullsize 32but argb pixels + png header */;
+ png_buf.buf = malloc(png_buf.buf_size);
+ if (!png_buf.buf)
+ {
+ free(pixels);
+ perror("malloc");
+ return FALSE;
+ }
+ png_generate_from_argb(
+ pointer->width, pointer->height, pixels, &png_buf);
+
+ wrdp_core_display_cursor cur = {0x00, 0x00, 0x02, 0x00, 0x01, 0x00,
+ (unsigned char)pointer->width, (unsigned char)pointer->height, 0x00,
+ 0x00, (unsigned char)pointer->xPos,
+ (unsigned char)(pointer->xPos >> 8), (unsigned char)pointer->yPos,
+ (unsigned char)(pointer->yPos >> 8), (unsigned char)png_buf.written,
+ (unsigned char)(png_buf.written >> 8),
+ (unsigned char)(png_buf.written >> 16),
+ (unsigned char)(png_buf.written >> 24), 0x16, 0x00, 0x00, 0x00};
+
+ cur.data = png_buf.buf;
+ wrdp_core_display_cursor_info cursor_info
+ = {p->id, pointer->xPos, pointer->yPos, png_buf.written, &cur};
+
+ c->my_internals->core->api_paint->send_ptr_new(
+ &cursor_info, c->my_internals->task_info);
+ free(png_buf.buf);
+ free(pixels);
+ return TRUE;
+}
+
+static void
+Pointer_Free(rdpContext *context, rdpPointer *pointer)
+{
+ my_rdp_pointer *p = (my_rdp_pointer *)pointer;
+ my_rdp_context *c = (my_rdp_context *)context;
+ if (p->id)
+ {
+ c->my_internals->core->api_paint->send_ptr_free(
+ p->id, c->my_internals->task_info);
+ }
+}
+
+static BOOL
+Pointer_Set(rdpContext *context, rdpPointer *pointer)
+{
+ my_rdp_pointer *p = (my_rdp_pointer *)pointer;
+ my_rdp_context *c = (my_rdp_context *)context;
+ c->my_internals->core->api_paint->send_ptr_set(
+ p->id, c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+Pointer_SetNull(rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ c->my_internals->core->api_paint->send_ptr_set_null(
+ c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+Pointer_SetDefault(rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ c->my_internals->core->api_paint->send_ptr_set_default(
+ c->my_internals->task_info);
+ return TRUE;
+}
+
+void
+register_pointer(rdpPointer *p)
+{
+ p->New = Pointer_New;
+ p->Free = Pointer_Free;
+ p->Set = Pointer_Set;
+ p->SetNull = Pointer_SetNull;
+ p->SetDefault = Pointer_SetDefault;
+}
+
+static BOOL
+BeginPaint(rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ c->my_internals->core->api_paint->send_begin_paint(
+ c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+EndPaint(rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ c->my_internals->core->api_paint->send_end_paint(
+ c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+SetBounds(rdpContext *context, const rdpBounds *bounds)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ wrdp_core_display_bounds lB;
+ if (bounds)
+ {
+ lB.left = bounds->left;
+ lB.right = bounds->right + 1;
+ lB.top = bounds->top;
+ lB.bottom = bounds->bottom + 1;
+ }
+ else
+ {
+ memset(&lB, 0, sizeof(wrdp_core_display_bounds));
+ }
+ c->my_internals->core->api_paint->send_set_bounds(
+ &lB, c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+Synchronize(rdpContext *context)
+{
+ return TRUE;
+}
+
+static BOOL
+DesktopResize(rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ char buf[64];
+ snprintf(buf, 63, "R:%dx%d", context->settings->DesktopWidth,
+ context->settings->DesktopHeight);
+ c->my_internals->core->api_msgs->send_text_msg(
+ buf, c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+BitmapUpdate(rdpContext *context, const BITMAP_UPDATE *bitmap)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ int i;
+ BITMAP_DATA *bmd;
+ for (i = 0; i < (int)bitmap->number; i++)
+ {
+ bmd = &bitmap->rectangles[i];
+ wrdp_core_display_bmp bmp = {bmd->destLeft, bmd->destTop,
+ bmd->width, bmd->height, bmd->destRight - bmd->destLeft + 1,
+ bmd->destBottom - bmd->destTop + 1, bmd->bitsPerPixel,
+ bmd->compressed, bmd->bitmapLength};
+ if (!bmd->compressed)
+ {
+ freerdp_image_flip(bmd->bitmapDataStream,
+ bmd->bitmapDataStream, bmd->width, bmd->height,
+ bmd->bitsPerPixel);
+ }
+ c->my_internals->core->api_paint->send_bitmap(
+ &bmp, bmd->bitmapDataStream, c->my_internals->task_info);
+ }
+ return TRUE;
+}
+
+static BOOL
+Palette(rdpContext *c, const PALETTE_UPDATE *p)
+{
+ return TRUE;
+}
+
+static BOOL
+PlaySound(rdpContext *c, const PLAY_SOUND_UPDATE *s)
+{
+ return TRUE;
+}
+
+static BOOL
+RefreshRect(rdpContext *c, UINT8 hz, const RECTANGLE_16 *r)
+{
+ return TRUE;
+}
+
+static BOOL
+SuppressOutput(rdpContext *c, UINT8 hz, const RECTANGLE_16 *r)
+{
+ return TRUE;
+}
+
+/*static BOOL
+SurfaceCommand(rdpContext* c, wStream* s)
+{
+ return TRUE;
+}*/
+
+static BOOL
+SurfaceBits(rdpContext *c, const SURFACE_BITS_COMMAND *sbc)
+{
+ return TRUE;
+}
+
+/*static BOOL
+SurfaceFrameMarker(rdpContext* c, SURFACE_FRAME_MARKER* sfm)
+{
+ return TRUE;
+} */
+
+void
+RegisterUpdate(freerdp *rdp)
+{
+ rdpUpdate *u = rdp->context->update;
+ u->BeginPaint = BeginPaint;
+ u->EndPaint = EndPaint;
+ u->SetBounds = SetBounds;
+ u->Synchronize = Synchronize;
+ u->DesktopResize = DesktopResize;
+ u->BitmapUpdate = BitmapUpdate;
+ u->Palette = Palette;
+ u->PlaySound = PlaySound;
+ u->SurfaceBits = SurfaceBits;
+
+ u->RefreshRect = RefreshRect;
+ u->SuppressOutput = SuppressOutput;
+}
+
+static BOOL
+DstBlt(rdpContext *c, const DSTBLT_ORDER *_do)
+{
+ return TRUE;
+}
+
+static BOOL
+PatBlt(rdpContext *ctx, PATBLT_ORDER *po)
+{
+ my_rdp_context *c = (my_rdp_context *)ctx;
+ uint32_t rop3 = gdi_rop3_code(po->bRop);
+ if (GDI_BS_SOLID == po->brush.style)
+ {
+ wrdp_core_display_patblt_order patblt
+ = {po->nLeftRect, po->nTopRect, po->nWidth, po->nHeight,
+ FreeRDPConvertColor(po->foreColor, PIXEL_FORMAT_BGR16,
+ PIXEL_FORMAT_ABGR32,
+ NULL), //XXX
+ rop3};
+ c->my_internals->core->api_paint->send_pat_blt(
+ &patblt, c->my_internals->task_info);
+ }
+ return TRUE;
+}
+
+static BOOL
+ScrBlt(rdpContext *ctx, const SCRBLT_ORDER *sbo)
+{
+ my_rdp_context *c = (my_rdp_context *)ctx;
+ uint32_t rop3 = gdi_rop3_code(sbo->bRop);
+ wrdp_core_display_scr_blt scr_blt = {rop3, sbo->nLeftRect,
+ sbo->nTopRect, sbo->nWidth, sbo->nHeight, sbo->nXSrc, sbo->nYSrc};
+ c->my_internals->core->api_paint->send_scr_blt(
+ &scr_blt, c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+OpaqueRect(rdpContext *context, const OPAQUE_RECT_ORDER *oro)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ wrdp_core_display_opaque_rect_order oro2;
+ oro2.color = oro->color;
+ oro2.nHeight = oro->nHeight;
+ oro2.nLeftRect = oro->nLeftRect;
+ oro2.nTopRect = oro->nTopRect;
+ oro2.nWidth = oro->nWidth;
+ oro2.color = FreeRDPConvertColor(
+ oro2.color, PIXEL_FORMAT_BGR16, PIXEL_FORMAT_ABGR32, NULL);
+ c->my_internals->core->api_paint->send_opaque_rect_order(
+ &oro2, c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+DrawNineGrid(rdpContext *c, const DRAW_NINE_GRID_ORDER *dngo)
+{
+ return TRUE;
+}
+
+static BOOL
+MultiDstBlt(rdpContext *c, const MULTI_DSTBLT_ORDER *mdo)
+{
+ return TRUE;
+}
+
+static BOOL
+MultiPatBlt(rdpContext *c, const MULTI_PATBLT_ORDER *mpo)
+{
+ return TRUE;
+}
+
+static BOOL
+MultiScrBlt(rdpContext *c, const MULTI_SCRBLT_ORDER *mso)
+{
+ return TRUE;
+}
+
+static BOOL
+MultiOpaqueRect(rdpContext *context, const MULTI_OPAQUE_RECT_ORDER *moro)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ wrdp_core_display_m_opaque_rect mrect;
+ mrect.color = FreeRDPConvertColor(
+ moro->color, PIXEL_FORMAT_BGR16, PIXEL_FORMAT_ABGR32, NULL);
+ mrect.num_rect = moro->numRectangles;
+ mrect.rects = (wrdp_core_display_delta_rect *)(moro->rectangles);
+ c->my_internals->core->api_paint->send_multi_opaque_rect(
+ &mrect, c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+MultiDrawNineGrid(rdpContext *c, const MULTI_DRAW_NINE_GRID_ORDER *mdngo)
+{
+ return TRUE;
+}
+
+static BOOL
+LineTo(rdpContext *c, const LINE_TO_ORDER *lto)
+{
+ return TRUE;
+}
+
+static BOOL
+Polyline(rdpContext *c, const POLYLINE_ORDER *po)
+{
+ return TRUE;
+}
+
+static BOOL
+MemBlt(rdpContext *c, MEMBLT_ORDER *mo)
+{
+ return TRUE;
+}
+
+static BOOL
+Mem3Blt(rdpContext *c, MEM3BLT_ORDER *mo)
+{
+ return TRUE;
+}
+
+static BOOL
+SaveBitmap(rdpContext *c, const SAVE_BITMAP_ORDER *sbo)
+{
+ return TRUE;
+}
+
+static BOOL
+GlyphIndex(rdpContext *c, GLYPH_INDEX_ORDER *gio)
+{
+ return TRUE;
+}
+
+static BOOL
+FastIndex(rdpContext *c, const FAST_INDEX_ORDER *fio)
+{
+ return TRUE;
+}
+
+static BOOL
+FastGlyph(rdpContext *c, const FAST_GLYPH_ORDER *fgo)
+{
+ return TRUE;
+}
+
+static BOOL
+PolygonSC(rdpContext *c, const POLYGON_SC_ORDER *pso)
+{
+ return TRUE;
+}
+
+static BOOL
+PolygonCB(rdpContext *c, POLYGON_CB_ORDER *pco)
+{
+ return TRUE;
+}
+
+static BOOL
+EllipseSC(rdpContext *c, const ELLIPSE_SC_ORDER *eso)
+{
+ return TRUE;
+}
+
+static BOOL
+EllipseCB(rdpContext *c, const ELLIPSE_CB_ORDER *eco)
+{
+ return TRUE;
+}
+void
+RegisterPrimary(freerdp *rdp)
+{
+ rdpPrimaryUpdate *p = rdp->context->update->primary;
+ p->DstBlt = DstBlt;
+ p->PatBlt = PatBlt;
+ p->ScrBlt = ScrBlt;
+ p->OpaqueRect = OpaqueRect;
+ p->DrawNineGrid = DrawNineGrid;
+ p->MultiDstBlt = MultiDstBlt;
+ p->MultiPatBlt = MultiPatBlt;
+ p->MultiScrBlt = MultiScrBlt;
+ p->MultiOpaqueRect = MultiOpaqueRect;
+ p->MultiDrawNineGrid = MultiDrawNineGrid;
+ p->LineTo = LineTo;
+ p->Polyline = Polyline;
+ p->MemBlt = MemBlt;
+ p->Mem3Blt = Mem3Blt;
+ p->SaveBitmap = SaveBitmap;
+ p->GlyphIndex = GlyphIndex;
+ p->FastIndex = FastIndex;
+ p->FastGlyph = FastGlyph;
+ p->PolygonSC = PolygonSC;
+ p->PolygonCB = PolygonCB;
+ p->EllipseSC = EllipseSC;
+ p->EllipseCB = EllipseCB;
+}
diff --git a/src/rdp/rdp_display_output.h b/src/rdp/rdp_display_output.h
new file mode 100644
index 0000000..14ae7f3
--- /dev/null
+++ b/src/rdp/rdp_display_output.h
@@ -0,0 +1,17 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+void RegisterUpdate(freerdp *rdp);
+void RegisterPrimary(freerdp *rdp);
+
+void rdp_register_base(freerdp *r);
+
+HCLRCONV
+freerdp_clrconv_new(UINT32 flags);
+
+void freerdp_clrconv_free(HCLRCONV clrconv);
diff --git a/src/rdp/rdp_ft.c b/src/rdp/rdp_ft.c
new file mode 100644
index 0000000..0c5575e
--- /dev/null
+++ b/src/rdp/rdp_ft.c
@@ -0,0 +1,129 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stddef.h>
+#include <stdbool.h>
+
+#include "webrdp_module_api.h"
+#include "webrdp_core_api.h"
+#include "rdp_backend_api.h"
+#include "rdp_impl.h"
+#include "rdp_ft.h"
+#include "rdp_clipboard.h"
+
+static bool
+ft_api_handle_request(
+ const wrdp_backend_ft_file_request *request, void *backend_internals)
+{
+ rdp_internals *i = backend_internals;
+ CLIPRDR_FILE_CONTENTS_REQUEST file_req;
+ uint32_t chunk_len = 512000;
+ size_t left = 0;
+ if (i->context->ft_to_client->is_runing)
+ {
+ const char *msg
+ = "rdp_module: clipboard: filetransfers: transfer "
+ "already runing, cannot start new one";
+ i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ return true;
+ }
+ memset(&file_req, 0, sizeof(CLIPRDR_FILE_CONTENTS_REQUEST));
+ i->context->ft_to_client->transfer_id = rand();
+ i->context->ft_to_client->file_size = request->req_size;
+ i->context->ft_to_client->file_id = request->file_id;
+
+ i->context->ft_to_client->transferred = request->file_offset;
+ i->context->ft_to_client->is_runing = true;
+
+ left = i->context->ft_to_client->file_size
+ - i->context->ft_to_client->transferred;
+ if (left < chunk_len)
+ {
+ chunk_len = left;
+ }
+ file_req.cbRequested = chunk_len;
+ file_req.listIndex = request->file_id;
+ file_req.streamId = i->context->ft_to_client->transfer_id;
+ file_req.dwFlags = FILECONTENTS_RANGE;
+ /* file_req.haveClipDataId = TRUE;
+ file_req.clipDataId =
+ i->context->ft_to_client->clipboard_data_id; */
+
+ /* TODO: check/change endianness */
+ memcpy(&(file_req.nPositionLow),
+ &(i->context->ft_to_client->transferred), 4);
+ memcpy(&(file_req.nPositionHigh),
+ (((uint8_t *)&(i->context->ft_to_client->transferred)) + 4), 4);
+ i->context->clipboard->clip_context->ClientFileContentsRequest(
+ i->context->clipboard->clip_context, &file_req);
+
+ return true;
+}
+
+static bool
+ft_api_handle_chunk(const wrdp_backend_ft_chunk *chunk, const uint8_t *data,
+ void *backend_internals)
+{
+ rdp_internals *i = backend_internals;
+ CLIPRDR_FILE_CONTENTS_RESPONSE resp;
+ memset(&resp, 0, sizeof(CLIPRDR_FILE_CONTENTS_RESPONSE));
+ resp.cbRequested = chunk->size;
+ resp.streamId = i->context->ft_to_server->transfer_id;
+ resp.common.msgFlags = CB_RESPONSE_OK;
+ resp.common.msgType = CB_FILECONTENTS_RESPONSE;
+ resp.requestedData = data;
+ i->context->clipboard->clip_context->ClientFileContentsResponse(
+ i->context->clipboard->clip_context, &resp);
+ i->context->ft_to_server->transferred += chunk->size;
+ if (i->context->ft_to_server->transferred
+ == i->context->ft_to_server->file_size)
+ {
+ i->context->ft_to_server->is_runing = false;
+ i->context->ft_to_server->transfer_id = 0;
+ i->context->ft_to_server->file_id = 0;
+ i->context->ft_to_server->file_size = 0;
+ i->context->ft_to_server->transferred = 0;
+ }
+ return true;
+}
+
+static bool
+ft_api_handle_finish(
+ const wrdp_backend_ft_status *status, void *backend_internals)
+{
+ /* TODO: handle errors */
+ rdp_internals *i = backend_internals;
+ if (status->status != ft_status_success)
+ {
+ /* TODO: test it */
+ CLIPRDR_FILE_CONTENTS_RESPONSE resp;
+ memset(&resp, 0, sizeof(CLIPRDR_FILE_CONTENTS_RESPONSE));
+ const char *msg = "filetransfer finished with error";
+ i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ resp.common.msgType = CB_FILECONTENTS_RESPONSE;
+ resp.common.msgFlags = CB_RESPONSE_FAIL;
+ resp.streamId = i->context->ft_to_server->transfer_id;
+ resp.cbRequested = 0;
+ resp.requestedData = 0;
+ i->context->clipboard->clip_context->ClientFileContentsResponse(
+ i->context->clipboard->clip_context, &resp);
+ }
+ i->context->ft_to_server->is_runing = false;
+ i->context->ft_to_server->transfer_id = 0;
+ i->context->ft_to_server->file_size
+ = i->context->ft_to_server->transferred = 0;
+ return true;
+}
+
+void
+register_ft(wrdp_backend_module *backend)
+{
+ backend->callbacks_ft->chunk = ft_api_handle_chunk;
+ backend->callbacks_ft->finish = ft_api_handle_finish;
+ backend->callbacks_ft->request = ft_api_handle_request;
+}
diff --git a/src/rdp/rdp_ft.h b/src/rdp/rdp_ft.h
new file mode 100644
index 0000000..38b6a34
--- /dev/null
+++ b/src/rdp/rdp_ft.h
@@ -0,0 +1,17 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+struct my_rdp_ft_s
+{
+ uint32_t transfer_id, file_id;
+ bool is_runing;
+ uint64_t file_size, transferred;
+};
+
+void register_ft(wrdp_backend_module *backend);
+
diff --git a/src/rdp/rdp_impl.c b/src/rdp/rdp_impl.c
new file mode 100644
index 0000000..3aa47e2
--- /dev/null
+++ b/src/rdp/rdp_impl.c
@@ -0,0 +1,637 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <errno.h>
+#include <locale.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <sys/select.h>
+
+#include <freerdp/client/channels.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/cmdline.h>
+#include <freerdp/client/file.h>
+#include <freerdp/constants.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <freerdp/log.h>
+
+#include <webrdp_core_api.h>
+#include <webrdp_module_api.h>
+
+#include "rdp_backend_api.h"
+#include "rdp_impl.h"
+#include "rdp_display_output.h"
+#include "rdp_channels.h"
+#include "rdp_clipboard.h"
+#include "rdp_ft.h"
+#include "rdp_user_input.h"
+
+/* from winpr */
+
+typedef BOOL (*pcIsHandled)(HANDLE handle);
+typedef BOOL (*pcCloseHandle)(HANDLE handle);
+typedef int (*pcGetFd)(HANDLE handle);
+typedef DWORD (*pcCleanupHandle)(HANDLE handle);
+typedef BOOL (*pcReadFile)(PVOID Object, LPVOID lpBuffer,
+ DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead,
+ LPOVERLAPPED lpOverlapped);
+typedef BOOL (*pcReadFileEx)(HANDLE hFile, LPVOID lpBuffer,
+ DWORD nNumberOfBytesToRead, LPOVERLAPPED lpOverlapped,
+ LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
+typedef BOOL (*pcReadFileScatter)(HANDLE hFile,
+ FILE_SEGMENT_ELEMENT aSegmentArray[], DWORD nNumberOfBytesToRead,
+ LPDWORD lpReserved, LPOVERLAPPED lpOverlapped);
+typedef BOOL (*pcWriteFile)(PVOID Object, LPCVOID lpBuffer,
+ DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten,
+ LPOVERLAPPED lpOverlapped);
+typedef BOOL (*pcWriteFileEx)(HANDLE hFile, LPCVOID lpBuffer,
+ DWORD nNumberOfBytesToWrite, LPOVERLAPPED lpOverlapped,
+ LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
+typedef BOOL (*pcWriteFileGather)(HANDLE hFile,
+ FILE_SEGMENT_ELEMENT aSegmentArray[], DWORD nNumberOfBytesToWrite,
+ LPDWORD lpReserved, LPOVERLAPPED lpOverlapped);
+typedef DWORD (*pcGetFileSize)(HANDLE handle, LPDWORD lpFileSizeHigh);
+typedef BOOL (*pcFlushFileBuffers)(HANDLE hFile);
+typedef BOOL (*pcSetEndOfFile)(HANDLE handle);
+typedef DWORD (*pcSetFilePointer)(HANDLE handle, LONG lDistanceToMove,
+ PLONG lpDistanceToMoveHigh, DWORD dwMoveMethod);
+typedef BOOL (*pcSetFilePointerEx)(HANDLE hFile, LARGE_INTEGER liDistanceToMove,
+ PLARGE_INTEGER lpNewFilePointer, DWORD dwMoveMethod);
+typedef BOOL (*pcLockFile)(HANDLE hFile, DWORD dwFileOffsetLow,
+ DWORD dwFileOffsetHigh, DWORD nNumberOfBytesToLockLow,
+ DWORD nNumberOfBytesToLockHigh);
+typedef BOOL (*pcLockFileEx)(HANDLE hFile, DWORD dwFlags, DWORD dwReserved,
+ DWORD nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh,
+ LPOVERLAPPED lpOverlapped);
+typedef BOOL (*pcUnlockFile)(HANDLE hFile, DWORD dwFileOffsetLow,
+ DWORD dwFileOffsetHigh, DWORD nNumberOfBytesToUnlockLow,
+ DWORD nNumberOfBytesToUnlockHigh);
+typedef BOOL (*pcUnlockFileEx)(HANDLE hFile, DWORD dwReserved,
+ DWORD nNumberOfBytesToUnlockLow, DWORD nNumberOfBytesToUnlockHigh,
+ LPOVERLAPPED lpOverlapped);
+typedef BOOL (*pcSetFileTime)(HANDLE hFile, const FILETIME *lpCreationTime,
+ const FILETIME *lpLastAccessTime, const FILETIME *lpLastWriteTime);
+
+typedef struct _HANDLE_OPS
+{
+ pcIsHandled IsHandled;
+ pcCloseHandle CloseHandle;
+ pcGetFd GetFd;
+ pcCleanupHandle CleanupHandle;
+ pcReadFile ReadFile;
+ pcReadFileEx ReadFileEx;
+ pcReadFileScatter ReadFileScatter;
+ pcWriteFile WriteFile;
+ pcWriteFileEx WriteFileEx;
+ pcWriteFileGather WriteFileGather;
+ pcGetFileSize GetFileSize;
+ pcFlushFileBuffers FlushFileBuffers;
+ pcSetEndOfFile SetEndOfFile;
+ pcSetFilePointer SetFilePointer;
+ pcSetFilePointerEx SetFilePointerEx;
+ pcLockFile LockFile;
+ pcLockFileEx LockFileEx;
+ pcUnlockFile UnlockFile;
+ pcUnlockFileEx UnlockFileEx;
+ pcSetFileTime SetFileTime;
+} HANDLE_OPS;
+
+#define WINPR_HANDLE_DEF() \
+ ULONG Type; \
+ ULONG Mode; \
+ HANDLE_OPS *ops
+
+struct winpr_handle
+{
+ WINPR_HANDLE_DEF();
+};
+typedef struct winpr_handle WINPR_HANDLE;
+
+static INLINE BOOL
+winpr_Handle_GetInfo(HANDLE handle, ULONG *pType, WINPR_HANDLE **pObject)
+{
+ WINPR_HANDLE *wHandle;
+
+ if (handle == NULL || handle == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ wHandle = (WINPR_HANDLE *)handle;
+
+ *pType = wHandle->Type;
+ *pObject = handle;
+
+ return TRUE;
+}
+
+static INLINE int
+winpr_Handle_getFd(HANDLE handle)
+{
+ WINPR_HANDLE *hdl;
+ ULONG type;
+
+ if (!winpr_Handle_GetInfo(handle, &type, &hdl))
+ return -1;
+
+ if (!hdl || !hdl->ops || !hdl->ops->GetFd)
+ return -1;
+
+ return hdl->ops->GetFd(handle);
+}
+
+/* end from winpr */
+
+bool
+rdp_init_internals(void *internals)
+{
+ setlocale(LC_ALL, "");
+#ifdef DEBUG
+ setenv("WLOG_LEVEL", "DEBUG", 1);
+#else
+ setenv("WLOG_LEVEL", "ERROR", 1);
+#endif
+ rdp_internals *i = internals;
+ i->instance = freerdp_new();
+ if (!i->instance)
+ {
+ const char *msg
+ = "rdp_module: Failed to create FreeRDP instance";
+ i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ goto error;
+ }
+ rdp_register_base(i->instance);
+ i->instance->ContextSize = sizeof(my_rdp_context);
+ if (!freerdp_context_new(i->instance))
+ {
+ const char *msg
+ = "rdp_module: Failed to create FreeRDP context";
+ i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ goto error;
+ }
+ i->context = (my_rdp_context *)i->instance->context;
+ ((my_rdp_context *)i->instance->context)->my_internals = internals;
+ {
+ char **argv = malloc(sizeof(char *));
+ if (!argv)
+ {
+ perror("malloc");
+ goto error;
+ }
+ argv[0] = strdup("./core");
+ rdpSettings *settings = i->instance->context->settings;
+ freerdp_client_settings_parse_command_line(
+ settings, 1, argv, FALSE);
+ free(argv[0]);
+ free(argv);
+ if (!settings)
+ {
+ const char *msg
+ = "rdp_module: Failed to create FreeRDP settings";
+ i->core->api_utils->log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_error, 0);
+ goto error;
+ }
+ settings->ThreadingFlags = THREADING_FLAGS_DISABLE_THREADS;
+
+ settings->OsMajorType = OSMAJORTYPE_UNIX;
+ settings->OsMinorType = OSMINORTYPE_UNSPECIFIED;
+
+ /* unsupported */
+ settings->RemoteFxCodec = FALSE;
+ settings->NSCodec = FALSE;
+
+ /* turn on clipboard redirect */
+ settings->RedirectClipboard = TRUE;
+ }
+ i->context->ft_to_server = calloc(1, sizeof(my_rdp_ft));
+ if (!i->context->ft_to_server)
+ {
+ perror("calloc");
+ goto error;
+ }
+ i->context->ft_to_client = calloc(1, sizeof(my_rdp_ft));
+ if (!i->context->ft_to_client)
+ {
+ perror("calloc");
+ goto error;
+ }
+ freerdp_register_addin_provider(
+ freerdp_channels_load_static_addin_entry, 0);
+ i->settings = i->instance->context->settings;
+ //i->instance->settings = i->settings;
+ i->settings->instance = i->instance;
+ i->instance->context->settings = i->settings;
+ return true;
+error:
+ if (i->instance)
+ {
+ if (i->instance->context)
+ {
+ freerdp_context_free(i->instance);
+ }
+ freerdp_free(i->instance);
+ i->instance = 0;
+ }
+ if (i->context->ft_to_client)
+ {
+ free(i->context->ft_to_client);
+ }
+ if (i->context->ft_to_server)
+ {
+ free(i->context->ft_to_server);
+ }
+ {
+ const char *msg = "rdp_module: internal error";
+ i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ }
+ i->core->api_msgs->send_error_msg(
+ "rdp_module_internal_error", i->task_info);
+ return false;
+}
+
+static void
+rdp_check_all_fds(struct ev_loop *loop, ev_io *w)
+{
+ rdp_internals *i = w->data;
+ bool failure = false;
+ if (i->backend->stopped)
+ return;
+ if ((freerdp_check_event_handles(i->instance->context)) != TRUE)
+ {
+ uint32_t e;
+ e = freerdp_error_info(i->instance);
+ switch (e)
+ {
+ case 1:
+ case 2:
+ case 7:
+ case 9:
+ /* case 12: */
+ /* No really an error
+ * (Happens when you invoke Disconnect
+ * in Start-Menu) */
+ break;
+ case 5:
+ {
+ const char *msg
+ = "rdp_module: Another user connected to "
+ "the server,\nforcing the "
+ "disconnection of the "
+ "current connection.";
+ i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_debug, 0);
+ break;
+ }
+ default:
+ {
+ failure = true;
+ }
+ break;
+ }
+ if (failure)
+ {
+ char buf[512];
+ snprintf(buf, 511, "%s",
+ freerdp_get_last_error_name(
+ freerdp_get_last_error(i->instance->context)));
+ i->core->api_msgs->send_error_msg(buf, i->task_info);
+ i->core->api_utils->log_msg((const uint8_t *)buf,
+ strlen(buf), wrdp_log_level_error, 0);
+ goto cleanup;
+ }
+ }
+ if (freerdp_shall_disconnect_context(i->instance->context))
+ {
+ {
+ const char *msg = "rdp_module: disconnect, no error";
+ i->core->api_utils->log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_trace, 0);
+ }
+ freerdp_disconnect(i->instance);
+ i->conn_state = rdp_conn_state_offline;
+ i->core->api_msgs->send_error_msg(
+ "rdp_module_disconnect_no_error", i->task_info);
+ goto cleanup;
+ }
+ return;
+cleanup:
+ ev_io_stop(loop, w);
+ if (!failure)
+ {
+ i->core->api_msgs->send_termination_msg(i->task_info);
+ }
+ i->core->api_core->task_finished(true, i->task_info);
+}
+
+static void
+rdp_fd_readable_cb(struct ev_loop *loop, ev_io *w, int revents)
+{
+ rdp_check_all_fds(loop, w);
+}
+
+bool
+rdp_connect(void *internals)
+{
+ rdp_internals *_i = internals;
+#ifdef DEBUG
+ {
+ /* print settings before connect for debug */
+ char buf[512];
+ snprintf(buf, 511,
+ "rdp_module: settings:\n\tfntlm: %ld\n\t"
+ "height: %ld\n\thost: %s\n\tnomani: %ld\n\tnonla: %ld\n\t"
+ "notheme: %ld\n\tnotls: %ld\n\tnowallp: %ld\n\tnowdrag: "
+ "%ld\n\t"
+ "password: %s\n\tpcb: %s\n\tperf: %ld\n\tport: "
+ "%ld\n\tuser: %s\n\t"
+ "width: %ld",
+ _i->my_settings.fntlm.value, _i->my_settings.height.value,
+ _i->my_settings.host.value, _i->my_settings.nomani.value,
+ _i->my_settings.nonla.value, _i->my_settings.notheme.value,
+ _i->my_settings.notls.value, _i->my_settings.nowallp.value,
+ _i->my_settings.nowdrag.value,
+ _i->my_settings.password.value, _i->my_settings.pcb.value,
+ _i->my_settings.perf.value, _i->my_settings.port.value,
+ _i->my_settings.user.value, _i->my_settings.width.value);
+ _i->core->api_utils->log_msg(
+ (const uint8_t *)buf, strlen(buf), wrdp_log_level_trace, 0);
+ }
+#endif /* DEBUG */
+ if (!_i->my_settings.host.value || !_i->my_settings.host.value[0])
+ {
+ const char *msg = "rdp_module: host is not set";
+ _i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ _i->core->api_msgs->send_error_msg(
+ "rdp_module_param_error", _i->task_info);
+ return false;
+ }
+ if (!_i->my_settings.port.value)
+ {
+ const char *msg = "rdp_module: port is not set";
+ _i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ _i->core->api_msgs->send_error_msg(
+ "rdp_module_param_error", _i->task_info);
+ return false;
+ }
+ if (!_i->my_settings.user.value || !_i->my_settings.user.value[0])
+ {
+ const char *msg = "rdp_module: user is not set";
+ _i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ _i->core->api_msgs->send_error_msg(
+ "rdp_module_param_error", _i->task_info);
+ return false;
+ }
+ /* password can be optional ? */
+ if (freerdp_connect(_i->instance))
+ {
+ int fdcount = 0, i;
+ HANDLE *fds[256];
+ ZeroMemory(fds, sizeof(fds));
+
+ struct ev_loop *loop
+ = _i->core->api_core->get_libev_loop(_i->task_info);
+ fdcount = freerdp_get_event_handles(
+ _i->instance->context, (HANDLE *)fds, 256);
+ if (!fdcount)
+ {
+ const char *msg
+ = "Failed to get FreeRDP file descriptors";
+ _i->core->api_utils->log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_error, 0);
+ _i->core->api_msgs->send_error_msg(
+ "rdp_module_internal_error", _i->task_info);
+ return false;
+ }
+ _i->rdp_fd_count = fdcount;
+ for (i = 0; i < fdcount; ++i)
+ {
+ ULONG type;
+ WINPR_HANDLE *phandle = 0;
+ int fd = 0;
+ if (!winpr_Handle_GetInfo(fds[i], &type, &phandle))
+ {
+ /* TODO: assume what this is not fatal */
+ const char *msg = "winpr_Handle_GetInfo failed";
+ _i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ continue;
+ }
+ fd = winpr_Handle_getFd(phandle);
+
+ /* TODO: check type here, and set EV_WRITE if needed
+ * for now watch only for read available event*/
+ ev_io_init(
+ &(_i->rdp_fd[i]), rdp_fd_readable_cb, fd, EV_READ);
+ _i->rdp_fd[i].data = internals;
+ ev_io_start(loop, &(_i->rdp_fd[i]));
+ }
+ return true;
+ }
+ {
+ const char *msg = freerdp_get_last_error_name(
+ freerdp_get_last_error(_i->instance->context));
+ _i->core->api_msgs->send_error_msg(msg, _i->task_info);
+ }
+
+ return false;
+}
+
+static BOOL
+rdp_pre_connect(freerdp *instance)
+{
+ rdpSettings *settings = instance->context->settings;
+ my_rdp_context *c = (my_rdp_context *)instance->context;
+
+ RegisterUpdate(instance);
+ RegisterPrimary(instance);
+
+ /* looks like this settings reducing artifacts level */
+ settings->OrderSupport[NEG_PATBLT_INDEX] = FALSE;
+ settings->OrderSupport[NEG_SCRBLT_INDEX] = FALSE;
+
+ /* causing random disconnects */
+ settings->OrderSupport[NEG_ATEXTOUT_INDEX] = FALSE;
+ settings->OrderSupport[NEG_DRAWNINEGRID_INDEX] = FALSE;
+ settings->NoBitmapCompressionHeader = TRUE;
+ settings->BitmapCompressionDisabled = TRUE;
+ settings->BitmapCacheEnabled = FALSE;
+ settings->BrushSupportLevel = 0;
+
+ /* causing connection failure on recent versions of freerdp */
+ settings->OffscreenSupportLevel = 0;
+ settings->OffscreenCacheSize = 0;
+
+ /* following required at least on windows 7 */
+ settings->OrderSupport[NEG_MEMBLT_INDEX] = FALSE;
+ settings->OrderSupport[NEG_MEM3BLT_INDEX] = FALSE;
+
+ /* following required for windows 2012,2016 */
+ settings->FastPathOutput = 1;
+
+ /* testing */
+ /* wsgate settings */
+ // settings->ColorDepth = 16;
+ // settings->FrameAcknowledge = 1;
+ // settings->LargePointerFlag = 1;
+ // settings->BitmapCacheV3Enabled = FALSE;
+ // settings->BitmapCachePersistEnabled = FALSE;
+ // settings->OrderSupport[NEG_DSTBLT_INDEX] = TRUE;
+ // settings->OrderSupport[NEG_PATBLT_INDEX] = FALSE; // XXX
+ // settings->OrderSupport[NEG_SCRBLT_INDEX] = FALSE; // XXX
+ // settings->OrderSupport[NEG_OPAQUE_RECT_INDEX] = TRUE;
+ // settings->OrderSupport[NEG_MULTIDSTBLT_INDEX] = FALSE;
+ // settings->OrderSupport[NEG_MULTIPATBLT_INDEX] = FALSE;
+ // settings->OrderSupport[NEG_MULTISCRBLT_INDEX] = FALSE;
+ // settings->OrderSupport[NEG_MULTIOPAQUERECT_INDEX] = FALSE; //
+ //XXX settings->OrderSupport[NEG_MULTI_DRAWNINEGRID_INDEX] = FALSE;
+ // settings->OrderSupport[NEG_LINETO_INDEX] = TRUE;
+ // settings->OrderSupport[NEG_POLYLINE_INDEX] = TRUE;
+ // settings->OrderSupport[NEG_MEMBLT_V2_INDEX] = FALSE;
+ // settings->OrderSupport[NEG_MEM3BLT_V2_INDEX] = FALSE;
+ // settings->OrderSupport[NEG_SAVEBITMAP_INDEX] = FALSE;
+ // settings->OrderSupport[NEG_GLYPH_INDEX_INDEX] = TRUE;
+ // settings->OrderSupport[NEG_FAST_INDEX_INDEX] = TRUE;
+ // settings->OrderSupport[NEG_FAST_GLYPH_INDEX] = TRUE;
+ // settings->OrderSupport[NEG_POLYGON_SC_INDEX] = FALSE;
+ // settings->OrderSupport[NEG_POLYGON_CB_INDEX] = FALSE;
+ // settings->OrderSupport[NEG_ELLIPSE_SC_INDEX] = FALSE;
+ // settings->OrderSupport[NEG_ELLIPSE_CB_INDEX] = FALSE;
+ // settings->GlyphSupportLevel = GLYPH_SUPPORT_NONE;
+
+ PubSub_SubscribeChannelConnected(
+ instance->context->pubSub, rdp_on_channel_connected);
+ PubSub_SubscribeChannelDisconnected(
+ instance->context->pubSub, rdp_on_channel_disconnected);
+
+ freerdp_client_load_addins(instance->context->channels, settings);
+ instance->context->cache = cache_new(instance->context);
+
+ c->my_internals->clrconv
+ = freerdp_clrconv_new(CLRCONV_ALPHA | CLRCONV_INVERT);
+
+ if (!instance->context->cache)
+ {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL
+rdp_post_connect(freerdp *instance)
+{
+ my_rdp_context *c = (my_rdp_context *)instance->context;
+ my_rdp_clipboard *clip = 0;
+ rdpPointer p;
+ memset(&p, 0, sizeof(p));
+ p.size = sizeof(my_rdp_pointer);
+ register_pointer(&p);
+ graphics_register_pointer(instance->context->graphics, &p);
+ pointer_cache_register_callbacks(instance->context->update);
+
+ clip = rdp_clipboard_new(c);
+ if (!clip)
+ {
+ const char *msg = "rdp_module: \"rdp_clipboard_new\" failed";
+ c->my_internals->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ return FALSE;
+ }
+ c->clipboard = clip;
+
+ c->my_internals->conn_state = rdp_conn_state_connected;
+
+ {
+ const char *msg
+ = "rdp_module: \"C:RDP session connection started.\"";
+ c->my_internals->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_trace, 0);
+ }
+
+ c->my_internals->core->api_msgs->send_text_msg(
+ "C:RDP session connection started.", c->my_internals->task_info);
+
+ instance->context->update->DesktopResize(instance->context);
+
+ return TRUE;
+}
+
+static DWORD
+rdp_verify_certificate(freerdp *instance, const char *host, UINT16 port,
+ const char *common_name, const char *subject, const char *issuer,
+ const char *fingerprint, DWORD flags)
+{
+ my_rdp_context *c = (my_rdp_context *)instance->context;
+ char buf[512];
+ snprintf(buf, 511,
+ "Certificate details:\n\tSubject: %s\n\tIssuer:"
+ " %s\n\tThumbprint: %s\n"
+ "The above X.509 certificate could not be verified, possibly "
+ "because you do not have "
+ "the CA certificate in your certificate store, or the "
+ "certificate has expired. "
+ "Please look at the OpenSSL documentation on how to add a "
+ "private CA to the store.",
+ subject, issuer, fingerprint);
+ c->my_internals->core->api_utils->log_msg(
+ (const uint8_t *)buf, strlen(buf), wrdp_log_level_debug, 0);
+
+ return TRUE; /* ?? */
+}
+
+/*static int
+rdp_receive_channel_data (freerdp* instance,
+ UINT16 channelId,
+ BYTE* data,
+ int size,
+ int flags,
+ int total_size)
+{
+ return freerdp_channels_data (
+ instance, channelId, data, size, flags, total_size);
+}*/
+
+static BOOL
+rdp_context_new(freerdp *instance, rdpContext *context)
+{
+ return TRUE;
+}
+
+static void
+rdp_context_free(freerdp *instance, rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ if (context->cache)
+ {
+ cache_free(context->cache);
+ context->cache = NULL;
+ }
+ if (c->my_internals->clrconv)
+ {
+ freerdp_clrconv_free(c->my_internals->clrconv);
+ c->my_internals->clrconv = NULL;
+ }
+}
+
+void
+rdp_register_base(freerdp *r)
+{
+ r->PreConnect = rdp_pre_connect;
+ r->PostConnect = rdp_post_connect;
+ r->VerifyCertificateEx = rdp_verify_certificate;
+ // r->ReceiveChannelData = rdp_receive_channel_data;
+ r->ContextNew = rdp_context_new;
+ r->ContextFree = rdp_context_free;
+}
diff --git a/src/rdp/rdp_impl.h b/src/rdp/rdp_impl.h
new file mode 100644
index 0000000..0e561ed
--- /dev/null
+++ b/src/rdp/rdp_impl.h
@@ -0,0 +1,127 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include <ev.h>
+
+#include <freerdp/freerdp.h>
+#include <winpr/clipboard.h>
+#include <freerdp/client/cliprdr.h>
+
+#define CLRCONV_ALPHA 1
+#define CLRCONV_INVERT 2
+/* if defined RGB555 format is used when rendering with a 16-bit frame buffer
+ */
+#define CLRCONV_RGB555 4
+/* Supported Internal Buffer Formats */
+#define CLRBUF_16BPP 8
+#define CLRBUF_32BPP 32
+
+struct _CLRCONV
+{
+ int alpha;
+ int invert;
+ int rgb555;
+ rdpPalette *palette;
+};
+typedef struct _CLRCONV CLRCONV;
+typedef CLRCONV *HCLRCONV;
+
+/* #define TAG CLIENT_TAG ("RDP")*/
+
+typedef struct
+{
+ /* TODO: make variables list instead of static set */
+ backend_setting_str host, pcb, user, password,
+ /* new settings from freerdp header:*/
+ Domain, PasswordHash, AcceptedCert, ClientHostname, ClientProductId,
+ AlternateShell, ShellWorkingDirectory, ClientAddress, ClientDir,
+ DynamicDSTTimeZoneKeyName, RemoteAssistanceSessionId,
+ RemoteAssistancePassStub, RemoteAssistancePassword,
+ RemoteAssistanceRCTicket, AuthenticationServiceClass,
+ AllowedTlsCiphers, NtlmSamFile, PreconnectionBlob,
+ RedirectionAcceptedCert, KerberosKdc, KerberosRealm,
+ CertificateName, CertificateFile, PrivateKeyFile,
+ CertificateContent, PrivateKeyContent, WindowTitle, WmClass,
+ ComputerName, ConnectionFile, AssistanceFile, HomePath, ConfigPath,
+ CurrentPath, DumpRemoteFxFile, PlayRemoteFxFile, GatewayHostname,
+ GatewayUsername, GatewayPassword, GatewayDomain, GatewayAccessToken,
+ GatewayAcceptedCert, ProxyHostname, RemoteApplicationName,
+ RemoteApplicationIcon, RemoteApplicationProgram,
+ RemoteApplicationFile, RemoteApplicationGuid,
+ RemoteApplicationCmdLine, ImeFileName, DrivesToRedirect,
+ ActionScript;
+ backend_setting_int port, perf, fntlm, nowallp, nowdrag, nomani,
+ notheme, nonla, notls, width, height,
+ /* new settings from freerdp header:*/
+ ServerMode, WaitForOutputBufferFlush, MaxTimeInCheckLoop,
+ DesktopWidth, DesktopHeight, Workarea, Fullscreen, GrabKeyboard,
+ Decorations, RdpVersion, ColorDepth, ExtSecurity, NlaSecurity,
+ TlsSecurity, RdpSecurity, NegotiateSecurityLayer,
+ RestrictedAdminModeRequired, MstscCookieMode, CookieMaxLength,
+ ClientBuild, KeyboardType, KeyboardSubType, KeyboardFunctionKey,
+ KeyboardLayout, UseRdpSecurityLayer, SaltedChecksum, ServerPort,
+ GatewayPort, DesktopResize, ToggleFullscreen, Floatbar, DesktopPosX,
+ DesktopPosY, SoftwareGdi, UnmapButtons, PerformanceFlags,
+ AllowFontSmoothing, AllowDesktopComposition, DisableWallpaper,
+ DisableFullWindowDrag, DisableMenuAnims, DisableThemes,
+ ConnectionType, EncryptionMethods, EncryptionLevel, FIPSMode,
+ CompressionEnabled, LogonNotify, BrushSupportLevel,
+ RemoteApplicationMode, TcpAckTimeout;
+} my_rdp_settings;
+
+typedef struct
+{
+ rdpPointer pointer;
+ uint32_t id;
+} my_rdp_pointer;
+
+struct rdp_internals_s;
+typedef struct rdp_internals_s rdp_internals;
+
+struct my_rdp_clipboard_s;
+typedef struct my_rdp_clipboard_s my_rdp_clipboard;
+
+struct my_rdp_ft_s;
+typedef struct my_rdp_ft_s my_rdp_ft;
+
+typedef struct
+{
+ rdpContext context;
+ my_rdp_clipboard *clipboard;
+ my_rdp_ft *ft_to_client, *ft_to_server;
+ rdp_internals *my_internals;
+} my_rdp_context;
+
+struct rdp_internals_s
+{
+ freerdp *instance;
+ my_rdp_context *context;
+
+ rdpSettings *settings;
+
+ void *task_info;
+ wrdp_core_exports *core;
+ wrdp_backend_module *backend;
+
+ my_rdp_settings my_settings;
+ rdp_connection_state conn_state;
+
+ /* libev watchers */
+ /* TODO: dynamic allocation */
+ ev_io rdp_fd[32];
+ int rdp_fd_count;
+
+ HCLRCONV clrconv;
+ uint32_t m_ptrId;
+};
+
+bool rdp_init_internals(void *internals);
+
+bool rdp_connect(void *internals);
+
+bool rdp_init_settings(void *internals);
diff --git a/src/rdp/rdp_io.c b/src/rdp/rdp_io.c
new file mode 100644
index 0000000..5754997
--- /dev/null
+++ b/src/rdp/rdp_io.c
@@ -0,0 +1,1492 @@
+#include <freerdp/client/channels.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/cmdline.h>
+#include <freerdp/client/file.h>
+#include <freerdp/constants.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/log.h>
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#include <webrdp_core_api.h>
+#include <webrdp_module_api.h>
+
+#include "rdp_backend_api.h"
+#include "rdp_impl.h"
+#include "rdp_png.h"
+
+const UINT32 ASCII_TO_SCANCODE[256] = {
+ 0, /* 0 */
+ 0, /* 1 */
+ 0, /* 2 */
+ 0, /* 3 */
+ 0, /* 4 */
+ 0, /* 5 */
+ 0, /* 6 */
+ 0, /* 7 */
+ RDP_SCANCODE_BACKSPACE, /* 8 */
+ RDP_SCANCODE_TAB, /* 9 */
+ VK_KEY_D, /* 10 */
+ VK_KEY_F, /* 11 */
+ VK_KEY_H, /* 12 */
+ RDP_SCANCODE_RETURN, /* 13 */
+ RDP_SCANCODE_BACKSPACE, /* 14 */
+ VK_KEY_X, /* 15 */
+ RDP_SCANCODE_LSHIFT, /* 16 */
+ RDP_SCANCODE_LCONTROL, /* 17 */
+ RDP_SCANCODE_LMENU, /* 18 */
+ RDP_SCANCODE_PAUSE, /* 19 */
+ RDP_SCANCODE_CAPSLOCK, /* 20 */
+ VK_KEY_W, /* 21 */
+ VK_KEY_E, /* 22 */
+ VK_KEY_R, /* 23 */
+ VK_KEY_Y, /* 24 */
+ VK_KEY_A, /* 25 */
+ VK_KEY_1, /* 26 */
+ RDP_SCANCODE_ESCAPE, /* 27 */
+ VK_KEY_3, /* 28 */
+ VK_KEY_4, /* 29 */
+ VK_KEY_6, /* 30 */
+ VK_KEY_5, /* 31 */
+ RDP_SCANCODE_SPACE, /* 32 */
+ RDP_SCANCODE_PRIOR, /* 33 */
+ RDP_SCANCODE_NEXT, /* 34 */
+ RDP_SCANCODE_END, /* 35 */
+ RDP_SCANCODE_HOME, /* 36 */
+ RDP_SCANCODE_LEFT, /* 37 */
+ RDP_SCANCODE_UP, /* 38 */
+ RDP_SCANCODE_RIGHT, /* 39 */
+ RDP_SCANCODE_DOWN, /* 40 */
+ RDP_SCANCODE_KEY_0, /* 41 */
+ RDP_SCANCODE_MULTIPLY, /* 42 */
+ RDP_SCANCODE_ADD, /* 43 */
+ RDP_SCANCODE_PRINTSCREEN, /* 44 */
+ RDP_SCANCODE_INSERT, /* 45 */
+ RDP_SCANCODE_DELETE, /* 46 */
+ RDP_SCANCODE_DIVIDE, /* 47 */
+ RDP_SCANCODE_KEY_0, /* 48 */
+ RDP_SCANCODE_KEY_1, /* 49 */
+ RDP_SCANCODE_KEY_2, /* 50 */
+ RDP_SCANCODE_KEY_3, /* 51 */
+ RDP_SCANCODE_KEY_4, /* 52 */
+ RDP_SCANCODE_KEY_5, /* 53 */
+ RDP_SCANCODE_KEY_6, /* 54 */
+ RDP_SCANCODE_KEY_7, /* 55 */
+ RDP_SCANCODE_KEY_8, /* 56 */
+ RDP_SCANCODE_KEY_9, /* 57 */
+ RDP_SCANCODE_OEM_1, /* 58 */
+ RDP_SCANCODE_OEM_1, /* 59 */
+ RDP_SCANCODE_OEM_COMMA, /* 60 */
+ RDP_SCANCODE_OEM_PLUS, /* 61 */
+ RDP_SCANCODE_OEM_PERIOD, /* 62 */
+ RDP_SCANCODE_DIVIDE, /* 63 */
+ RDP_SCANCODE_KEY_2, /* 64 */
+ RDP_SCANCODE_KEY_A, /* 65 */
+ RDP_SCANCODE_KEY_B, /* 66 */
+ RDP_SCANCODE_KEY_C, /* 67 */
+ RDP_SCANCODE_KEY_D, /* 68 */
+ RDP_SCANCODE_KEY_E, /* 69 */
+ RDP_SCANCODE_KEY_F, /* 70 */
+ RDP_SCANCODE_KEY_G, /* 71 */
+ RDP_SCANCODE_KEY_H, /* 72 */
+ RDP_SCANCODE_KEY_I, /* 73 */
+ RDP_SCANCODE_KEY_J, /* 74 */
+ RDP_SCANCODE_KEY_K, /* 75 */
+ RDP_SCANCODE_KEY_L, /* 76 */
+ RDP_SCANCODE_KEY_M, /* 77 */
+ RDP_SCANCODE_KEY_N, /* 78 */
+ RDP_SCANCODE_KEY_O, /* 79 */
+ RDP_SCANCODE_KEY_P, /* 80 */
+ RDP_SCANCODE_KEY_Q, /* 81 */
+ RDP_SCANCODE_KEY_R, /* 82 */
+ RDP_SCANCODE_KEY_S, /* 83 */
+ RDP_SCANCODE_KEY_T, /* 84 */
+ RDP_SCANCODE_KEY_U, /* 85 */
+ RDP_SCANCODE_KEY_V, /* 86 */
+ RDP_SCANCODE_KEY_W, /* 87 */
+ RDP_SCANCODE_KEY_X, /* 88 */
+ RDP_SCANCODE_KEY_Y, /* 89 */
+ RDP_SCANCODE_KEY_Z, /* 90 */
+ RDP_SCANCODE_LWIN, /* 91 */
+ RDP_SCANCODE_RWIN, /* 92 */
+ RDP_SCANCODE_APPS, /* 93 */
+ RDP_SCANCODE_KEY_6, /* 94 */
+ RDP_SCANCODE_OEM_MINUS, /* 95 */
+ RDP_SCANCODE_NUMPAD0, /* 96 */
+ RDP_SCANCODE_NUMPAD1, /* 97 */
+ RDP_SCANCODE_NUMPAD2, /* 98 */
+ RDP_SCANCODE_NUMPAD3, /* 99 */
+ RDP_SCANCODE_NUMPAD4, /* 100 */
+ RDP_SCANCODE_NUMPAD5, /* 101 */
+ RDP_SCANCODE_NUMPAD6, /* 102 */
+ RDP_SCANCODE_NUMPAD7, /* 103 */
+ RDP_SCANCODE_NUMPAD8, /* 104 */
+ RDP_SCANCODE_NUMPAD9, /* 105 */
+ RDP_SCANCODE_MULTIPLY, /* 106 */
+ RDP_SCANCODE_ADD, /* 107 */
+ 0, /* 108 */
+ RDP_SCANCODE_SUBTRACT, /* 109 */
+ RDP_SCANCODE_DELETE, /* 110 */
+ RDP_SCANCODE_DIVIDE, /* 111 */
+ RDP_SCANCODE_F1, /* 112 */
+ RDP_SCANCODE_F2, /* 113 */
+ RDP_SCANCODE_F3, /* 114 */
+ RDP_SCANCODE_F4, /* 115 */
+ RDP_SCANCODE_F5, /* 116 */
+ RDP_SCANCODE_F6, /* 117 */
+ RDP_SCANCODE_F7, /* 118 */
+ RDP_SCANCODE_F8, /* 119 */
+ RDP_SCANCODE_F9, /* 120 */
+ RDP_SCANCODE_F10, /* 121 */
+ RDP_SCANCODE_F11, /* 122 */
+ RDP_SCANCODE_F12, /* 123 */
+ RDP_SCANCODE_OEM_5, /* 124 */
+ RDP_SCANCODE_OEM_6, /* 125 */
+ VK_F4, /* 126 */
+ VK_END, /* 127 */
+ VK_F2, /* 128 */
+ VK_NEXT, /* 129 */
+ VK_F1, /* 130 */
+ VK_LEFT, /* 131 */
+ VK_RIGHT, /* 132 */
+ VK_DOWN, /* 133 */
+ VK_UP, /* 134 */
+ 0, /* 135 */
+ 0, /* 136 */
+ 0, /* 137 */
+ 0, /* 138 */
+ 0, /* 139 */
+ 0, /* 140 */
+ 0, /* 141 */
+ 0, /* 142 */
+ 0, /* 143 */
+ RDP_SCANCODE_NUMLOCK, /* 144 */
+ RDP_SCANCODE_SCROLLLOCK, /* 145 */
+ 0, /* 146 */
+ 0, /* 147 */
+ 0, /* 148 */
+ 0, /* 149 */
+ 0, /* 150 */
+ 0, /* 151 */
+ 0, /* 152 */
+ 0, /* 153 */
+ 0, /* 154 */
+ 0, /* 155 */
+ 0, /* 156 */
+ 0, /* 157 */
+ 0, /* 158 */
+ 0, /* 159 */
+ 0, /* 160 */
+ 0, /* 161 */
+ 0, /* 162 */
+ 0, /* 163 */
+ 0, /* 164 */
+ 0, /* 165 */
+ 0, /* 166 */
+ 0, /* 167 */
+ 0, /* 168 */
+ 0, /* 169 */
+ 0, /* 170 */
+ 0, /* 171 */
+ 0, /* 172 */
+ RDP_SCANCODE_OEM_MINUS, /* 173 */
+ 0, /* 174 */
+ 0, /* 175 */
+ 0, /* 176 */
+ 0, /* 177 */
+ 0, /* 178 */
+ 0, /* 179 */
+ 0, /* 180 */
+ 0, /* 181 */
+ 0, /* 182 */
+ 0, /* 183 */
+ 0, /* 184 */
+ 0, /* 185 */
+ RDP_SCANCODE_OEM_1, /* 186 */
+ RDP_SCANCODE_OEM_PLUS, /* 187 */
+ RDP_SCANCODE_OEM_COMMA, /* 188 */
+ RDP_SCANCODE_OEM_MINUS, /* 189 */
+ RDP_SCANCODE_OEM_PERIOD, /* 190 */
+ RDP_SCANCODE_OEM_2, /* 191 */
+ RDP_SCANCODE_OEM_3, /* 192 */
+ 0, /* 193 */
+ 0, /* 194 */
+ 0, /* 195 */
+ 0, /* 196 */
+ 0, /* 197 */
+ 0, /* 198 */
+ 0, /* 199 */
+ 0, /* 200 */
+ 0, /* 201 */
+ 0, /* 202 */
+ 0, /* 203 */
+ 0, /* 204 */
+ 0, /* 205 */
+ 0, /* 206 */
+ 0, /* 207 */
+ 0, /* 208 */
+ 0, /* 209 */
+ 0, /* 210 */
+ 0, /* 211 */
+ 0, /* 212 */
+ 0, /* 213 */
+ 0, /* 214 */
+ 0, /* 215 */
+ 0, /* 216 */
+ 0, /* 217 */
+ 0, /* 218 */
+ RDP_SCANCODE_OEM_4, /* 219 */
+ RDP_SCANCODE_OEM_5, /* 220 */
+ RDP_SCANCODE_OEM_6, /* 221 */
+ RDP_SCANCODE_OEM_7, /* 222 */
+ 0, /* 223 */
+ 0, /* 224 */
+ 0, /* 225 */
+ 0, /* 226 */
+ 0, /* 227 */
+ 0, /* 228 */
+ 0, /* 229 */
+ 0, /* 230 */
+ 0, /* 231 */
+ 0, /* 232 */
+ 0, /* 233 */
+ 0, /* 234 */
+ 0, /* 235 */
+ 0, /* 236 */
+ 0, /* 237 */
+ 0, /* 238 */
+ 0, /* 239 */
+ 0, /* 240 */
+ 0, /* 241 */
+ 0, /* 242 */
+ 0, /* 243 */
+ 0, /* 244 */
+ 0, /* 245 */
+ 0, /* 246 */
+ 0, /* 247 */
+ 0, /* 248 */
+ 0, /* 249 */
+ 0, /* 250 */
+ 0, /* 251 */
+ 0, /* 252 */
+ 0, /* 253 */
+ 0, /* 254 */
+ 0 /* 255 */
+};
+
+#define RGB_555_888(_r, _g, _b) \
+ _r = (_r << 3 & ~0x7) | (_r >> 2); \
+ _g = (_g << 3 & ~0x7) | (_g >> 2); \
+ _b = (_b << 3 & ~0x7) | (_b >> 2);
+#define RGB_565_888(_r, _g, _b) \
+ _r = (_r << 3 & ~0x7) | (_r >> 2); \
+ _g = (_g << 2 & ~0x3) | (_g >> 4); \
+ _b = (_b << 3 & ~0x7) | (_b >> 2);
+#define GetRGB_555(_r, _g, _b, _p) \
+ _r = (_p & 0x7C00) >> 10; \
+ _g = (_p & 0x3E0) >> 5; \
+ _b = (_p & 0x1F);
+#define GetRGB_565(_r, _g, _b, _p) \
+ _r = (_p & 0xF800) >> 11; \
+ _g = (_p & 0x7E0) >> 5; \
+ _b = (_p & 0x1F);
+#define GetRGB15(_r, _g, _b, _p) \
+ GetRGB_555(_r, _g, _b, _p); \
+ RGB_555_888(_r, _g, _b);
+#define GetRGB16(_r, _g, _b, _p) \
+ GetRGB_565(_r, _g, _b, _p); \
+ RGB_565_888(_r, _g, _b);
+#define RGB24(_r, _g, _b) ((_r << 16) | (_g << 8) | _b)
+#define GetRGB24(_r, _g, _b, _p) \
+ _r = (_p & 0xFF0000) >> 16; \
+ _g = (_p & 0xFF00) >> 8; \
+ _b = (_p & 0xFF);
+#define GetRGB32(_r, _g, _b, _p) \
+ _r = (_p & 0xFF0000) >> 16; \
+ _g = (_p & 0xFF00) >> 8; \
+ _b = (_p & 0xFF);
+#define ARGB32(_a, _r, _g, _b) (_a << 24) | (_r << 16) | (_g << 8) | _b
+#define RGB16(_r, _g, _b) \
+ (((_r >> 3) & 0x1F) << 11) | (((_g >> 2) & 0x3F) << 5) \
+ | ((_b >> 3) & 0x1F)
+#define RGB15(_r, _g, _b) \
+ (((_r >> 3) & 0x1F) << 10) | (((_g >> 3) & 0x1F) << 5) \
+ | ((_b >> 3) & 0x1F)
+#define GetARGB32(_a, _r, _g, _b, _p) \
+ _a = (_p & 0xFF000000) >> 24; \
+ _r = (_p & 0xFF0000) >> 16; \
+ _g = (_p & 0xFF00) >> 8; \
+ _b = (_p & 0xFF);
+
+static HCLRCONV
+freerdp_clrconv_new(UINT32 flags)
+{
+ HCLRCONV clrconv;
+
+ clrconv = (CLRCONV *)calloc(1, sizeof(CLRCONV));
+
+ if (!clrconv)
+ return NULL;
+
+ clrconv->alpha = (flags & CLRCONV_ALPHA) ? TRUE : FALSE;
+ clrconv->invert = (flags & CLRCONV_INVERT) ? TRUE : FALSE;
+ clrconv->rgb555 = (flags & CLRCONV_RGB555) ? TRUE : FALSE;
+
+ clrconv->palette = (rdpPalette *)calloc(1, sizeof(rdpPalette));
+
+ if (!clrconv->palette)
+ {
+ free(clrconv);
+ return NULL;
+ }
+
+ return clrconv;
+}
+
+static void
+freerdp_clrconv_free(HCLRCONV clrconv)
+{
+ if (clrconv)
+ {
+ free(clrconv->palette);
+ free(clrconv);
+ }
+}
+
+static int
+freerdp_get_pixel(BYTE *data, int x, int y, int width, int height, int bpp)
+{
+ int start;
+ int shift;
+ UINT16 *src16;
+ UINT32 *src32;
+ int red, green, blue;
+
+ switch (bpp)
+ {
+ case 1:
+ width = (width + 7) / 8;
+ start = (y * width) + x / 8;
+ shift = x % 8;
+ return (data[start] & (0x80 >> shift)) != 0;
+ case 8:
+ return data[y * width + x];
+ case 15:
+ case 16:
+ src16 = (UINT16 *)data;
+ return src16[y * width + x];
+ case 24:
+ data += y * width * 3;
+ data += x * 3;
+ red = data[0];
+ green = data[1];
+ blue = data[2];
+ return RGB24(red, green, blue);
+ case 32:
+ src32 = (UINT32 *)data;
+ return src32[y * width + x];
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static void
+freerdp_set_pixel(
+ BYTE *data, int x, int y, int width, int height, int bpp, int pixel)
+{
+ int start;
+ int shift;
+ int *dst32;
+
+ if (bpp == 1)
+ {
+ width = (width + 7) / 8;
+ start = (y * width) + x / 8;
+ shift = x % 8;
+ if (pixel)
+ data[start] = data[start] | (0x80 >> shift);
+ else
+ data[start] = data[start] & ~(0x80 >> shift);
+ }
+ else if (bpp == 32)
+ {
+ dst32 = (int *)data;
+ dst32[y * width + x] = pixel;
+ }
+}
+
+static void
+freerdp_color_split_rgb(UINT32 *color, int bpp, BYTE *red, BYTE *green,
+ BYTE *blue, BYTE *alpha, HCLRCONV clrconv)
+{
+ *red = *green = *blue = 0;
+ *alpha = (clrconv->alpha) ? 0xFF : 0x00;
+
+ switch (bpp)
+ {
+ case 32:
+ if (clrconv->alpha)
+ {
+ GetARGB32(*alpha, *red, *green, *blue, *color);
+ }
+ else
+ {
+ GetRGB32(*red, *green, *blue, *color);
+ }
+ break;
+
+ case 24:
+ GetRGB24(*red, *green, *blue, *color);
+ break;
+
+ case 16:
+ GetRGB16(*red, *green, *blue, *color);
+ break;
+
+ case 15:
+ GetRGB15(*red, *green, *blue, *color);
+ break;
+
+ case 8:
+ *color &= 0xFF;
+ *red = clrconv->palette->entries[*color].red;
+ *green = clrconv->palette->entries[*color].green;
+ *blue = clrconv->palette->entries[*color].blue;
+ break;
+
+ case 1:
+ if (*color != 0)
+ {
+ *red = 0xFF;
+ *green = 0xFF;
+ *blue = 0xFF;
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+freerdp_color_make_rgb(UINT32 *color, int bpp, BYTE *red, BYTE *green,
+ BYTE *blue, BYTE *alpha, HCLRCONV clrconv)
+{
+ switch (bpp)
+ {
+ case 32:
+ *color = ARGB32(*alpha, *red, *green, *blue);
+ break;
+
+ case 24:
+ *color = RGB24(*red, *green, *blue);
+ break;
+
+ case 16:
+ if (clrconv->rgb555)
+ {
+ *color = RGB15(*red, *green, *blue);
+ }
+ else
+ {
+ *color = RGB16(*red, *green, *blue);
+ }
+ break;
+
+ case 15:
+ *color = RGB15(*red, *green, *blue);
+ break;
+
+ case 8:
+ *color = RGB24(*red, *green, *blue);
+ break;
+
+ case 1:
+ if ((*red != 0) || (*green != 0) || (*blue != 0))
+ *color = 1;
+ break;
+
+ default:
+ break;
+ }
+}
+
+static UINT32
+freerdp_color_convert_rgb(
+ UINT32 srcColor, int srcBpp, int dstBpp, HCLRCONV clrconv)
+{
+ BYTE red = 0;
+ BYTE green = 0;
+ BYTE blue = 0;
+ BYTE alpha = 0xFF;
+ UINT32 dstColor = 0;
+
+ freerdp_color_split_rgb(
+ &srcColor, srcBpp, &red, &green, &blue, &alpha, clrconv);
+ freerdp_color_make_rgb(
+ &dstColor, dstBpp, &red, &green, &blue, &alpha, clrconv);
+
+ return dstColor;
+}
+
+static void
+freerdp_alpha_cursor_convert(BYTE *alphaData, BYTE *xorMask, BYTE *andMask,
+ int width, int height, int bpp, HCLRCONV clrconv)
+{
+ UINT32 xorPixel;
+ UINT32 andPixel;
+ UINT32 x, y, jj;
+
+ for (y = 0; y < height; y++)
+ {
+ jj = (bpp == 1) ? y : (height - 1) - y;
+
+ for (x = 0; x < width; x++)
+ {
+ xorPixel = freerdp_get_pixel(
+ xorMask, x, jj, width, height, bpp);
+ xorPixel = freerdp_color_convert_rgb(
+ xorPixel, bpp, 32, clrconv);
+ andPixel = freerdp_get_pixel(
+ andMask, x, jj, width, height, 1);
+
+ if (andPixel)
+ {
+ if ((xorPixel & 0xFFFFFF) == 0xFFFFFF)
+ {
+ /* use pattern (not solid black) for xor
+ * area */
+ xorPixel = (x & 1) == (y & 1);
+ xorPixel = xorPixel ? 0xFFFFFF : 0;
+ xorPixel |= 0xFF000000;
+ }
+ else if (xorPixel == 0xFF000000)
+ {
+ xorPixel = 0;
+ }
+ }
+
+ freerdp_set_pixel(
+ alphaData, x, y, width, height, 32, xorPixel);
+ }
+ }
+}
+
+static void
+freerdp_bitmap_flip(BYTE *src, BYTE *dst, int scanLineSz, int height)
+{
+ int i;
+
+ BYTE *bottomLine = dst + (scanLineSz * (height - 1));
+ BYTE *topLine = src;
+
+ /* Special processing if called for flip-in-place. */
+ if (src == dst)
+ {
+ /* Allocate a scanline buffer.
+ * (FIXME: malloc / xfree below should be replaced by "get/put
+ * scanline buffer from a pool/Q of fixed buffers" to reuse
+ * fixed size buffers (of max scanline size (or adaptative?) )
+ * -- would be much faster).
+ */
+ BYTE *tmpBfr = (BYTE *)_aligned_malloc(scanLineSz, 16);
+ int half = height / 2;
+ /* Flip buffer in place by line permutations through the temp
+ * scan line buffer.
+ * Note that if height has an odd number of line, we don't need
+ * to move the center scanline anyway.
+ * Also note that in place flipping takes three memcpy() calls
+ * to process two scanlines while src to distinct dest would
+ * only requires two memcpy() calls for two scanlines.
+ */
+ height--;
+ for (i = 0; i < half; i++)
+ {
+ CopyMemory(tmpBfr, topLine, scanLineSz);
+ CopyMemory(topLine, bottomLine, scanLineSz);
+ CopyMemory(bottomLine, tmpBfr, scanLineSz);
+ topLine += scanLineSz;
+ bottomLine -= scanLineSz;
+ height--;
+ }
+ _aligned_free(tmpBfr);
+ }
+ /* Flip from source buffer to destination buffer. */
+ else
+ {
+
+ for (i = 0; i < height; i++)
+ {
+ CopyMemory(bottomLine, topLine, scanLineSz);
+ topLine += scanLineSz;
+ bottomLine -= scanLineSz;
+ }
+ }
+}
+
+static BYTE *
+freerdp_image_flip(BYTE *srcData, BYTE *dstData, int width, int height, int bpp)
+{
+ int scanline;
+
+ scanline = width * ((bpp + 7) / 8);
+
+ if (!dstData)
+ dstData = (BYTE *)_aligned_malloc(
+ width * height * ((bpp + 7) / 8), 16);
+
+ if (!dstData)
+ {
+ return NULL;
+ }
+
+ freerdp_bitmap_flip(srcData, dstData, scanline, height);
+
+ return dstData;
+}
+
+/* outgoing rdp protocol to client implementation */
+
+static BOOL
+Pointer_New(rdpContext *context, rdpPointer *pointer)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ HCLRCONV hclrconv = c->my_internals->clrconv;
+ size_t psize = pointer->width * pointer->height * 4;
+
+ my_rdp_pointer *p = (my_rdp_pointer *)pointer;
+ p->id = c->my_internals->m_ptrId++;
+ uint8_t *pixels = malloc(sizeof(uint8_t) * psize);
+ memset(pixels, 0, psize);
+ if ((pointer->andMaskData != 0) && (pointer->xorMaskData != 0))
+ {
+ //XXX
+ freerdp_alpha_cursor_convert(pixels, pointer->xorMaskData,
+ pointer->andMaskData, pointer->width, pointer->height,
+ pointer->xorBpp, hclrconv);
+ }
+
+ //check if the cursor is fully transparent
+ bool transparent = TRUE;
+ for (int y = 0; y < pointer->height; y++)
+ {
+ for (int x = 0; x < pointer->width; x++)
+ {
+ if (pixels[0 + x * 4 + y * 4 * pointer->width] != 0)
+ {
+ transparent = FALSE;
+ }
+ }
+ }
+ if (transparent)
+ {
+ pixels[3] = 1;
+ }
+
+ rdp_png_buf png_buf;
+ memset(&png_buf, 0, sizeof(png_buf));
+ png_buf.buf_size
+ = (pointer->width * pointer->height * sizeof(uint32_t))
+ + 8 /* allocating fullsize 32but argb pixels + png header */;
+ png_buf.buf = malloc(png_buf.buf_size);
+ png_generate_from_argb(
+ pointer->width, pointer->height, pixels, &png_buf);
+
+ struct
+ {
+ unsigned char resL; //2 bytes reserved must always be 0
+ unsigned char resH;
+ unsigned char imgTypeL; //2 bytes image type. 1 = ICO, 2 = CUR
+ unsigned char imgTypeH;
+ unsigned char nbImgL; //2 bytes number of images
+ unsigned char nbImgH;
+ unsigned char width; //1 byte image width in pixels
+ unsigned char height; //1 byte image height in pixels
+ unsigned char nbColors; //1 bytenumber of colors. 0 if not using
+ //a color pallete
+ unsigned char res; //1 byte reserved, should be 0
+ unsigned char xPosL; //2 bytes of hot spot x (for ICO, color
+ //planes, 0 or 1)
+ unsigned char xPosH;
+ unsigned char
+ yPosL; //2 bytes of hot spot y (for ICO bits per pixel)
+ unsigned char yPosH;
+ unsigned char sizeLL; //4 bytes of image size
+ unsigned char sizeLH;
+ unsigned char sizeHL;
+ unsigned char sizeHH;
+ unsigned char offsetLL; //4 bytes of offset of the data
+ unsigned char offsetLH;
+ unsigned char offsetHL;
+ unsigned char offsetHH;
+ } curHeader = {0x00, 0x00, 0x02, 0x00, 0x01, 0x00,
+ (unsigned char)pointer->width, (unsigned char)pointer->height, 0x00,
+ 0x00, (unsigned char)pointer->xPos,
+ (unsigned char)(pointer->xPos >> 8), (unsigned char)pointer->yPos,
+ (unsigned char)(pointer->yPos >> 8), (unsigned char)png_buf.written,
+ (unsigned char)(png_buf.written >> 8),
+ (unsigned char)(png_buf.written >> 16),
+ (unsigned char)(png_buf.written >> 24), 0x16, 0x00, 0x00, 0x00};
+
+ size_t cursor_buf_size = png_buf.written + sizeof(curHeader);
+ uint8_t *cursor_buf = malloc(cursor_buf_size);
+
+ memcpy(cursor_buf, &curHeader, sizeof(curHeader));
+ memcpy(cursor_buf + sizeof(curHeader), png_buf.buf, png_buf.written);
+ free(png_buf.buf);
+
+ /* rdp_cursor_add (p->id, cursor_buf, cursor_buf_size,
+ * c->my_internals); */
+
+ free(pixels);
+ struct
+ {
+ uint32_t id;
+ uint32_t hx;
+ uint32_t hy;
+ } tmp = {p->id, pointer->xPos, pointer->yPos};
+ size_t send_buf_size = sizeof(tmp) + cursor_buf_size;
+ uint8_t *send_buf = malloc(send_buf_size);
+ memcpy(send_buf, &tmp, sizeof(tmp));
+ memcpy(send_buf + sizeof(tmp), cursor_buf, cursor_buf_size);
+ free(cursor_buf);
+ c->my_internals->core->send_ptr_new(
+ send_buf, send_buf_size, c->my_internals->task_info);
+ return TRUE;
+}
+
+static void
+Pointer_Free(rdpContext *context, rdpPointer *pointer)
+{
+ my_rdp_pointer *p = (my_rdp_pointer *)pointer;
+ my_rdp_context *c = (my_rdp_context *)context;
+ if (p->id)
+ {
+ /* rdp_cursor_erase (p->id, c->my_internals); */
+ c->my_internals->core->send_ptr_free((uint8_t *)&(p->id),
+ sizeof(uint32_t), c->my_internals->task_info);
+ }
+}
+
+static BOOL
+Pointer_Set(rdpContext *context, const rdpPointer *pointer)
+{
+ my_rdp_pointer *p = (my_rdp_pointer *)pointer;
+ my_rdp_context *c = (my_rdp_context *)context;
+ c->my_internals->core->send_ptr_set(
+ (uint8_t *)&(p->id), sizeof(uint32_t), c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+Pointer_SetNull(rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ c->my_internals->core->send_ptr_set_null(c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+Pointer_SetDefault(rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ c->my_internals->core->send_ptr_set_default(c->my_internals->task_info);
+ return TRUE;
+}
+
+void
+register_pointer(rdpPointer *p)
+{
+ p->New = Pointer_New;
+ p->Free = Pointer_Free;
+ p->Set = Pointer_Set;
+ p->SetNull = Pointer_SetNull;
+ p->SetDefault = Pointer_SetDefault;
+}
+
+static BOOL
+rdp_context_new(freerdp *instance, rdpContext *context)
+{
+ return TRUE;
+}
+
+static void
+rdp_context_free(freerdp *instance, rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ if (context->cache)
+ {
+ cache_free(context->cache);
+ context->cache = NULL;
+ }
+ if (c->my_internals->clrconv)
+ {
+ freerdp_clrconv_free(c->my_internals->clrconv);
+ c->my_internals->clrconv = NULL;
+ }
+}
+
+static BOOL
+BeginPaint(rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ c->my_internals->core->send_begin_paint(c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+EndPaint(rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ c->my_internals->core->send_end_paint(c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+SetBounds(rdpContext *context, const rdpBounds *bounds)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ rdpBounds lB;
+ if (bounds)
+ {
+ memcpy(&lB, bounds, sizeof(rdpBounds));
+ lB.right++;
+ lB.bottom++;
+ }
+ else
+ {
+ memset(&lB, 0, sizeof(rdpBounds));
+ }
+ c->my_internals->core->send_set_bounds(
+ (uint8_t *)&lB, sizeof(lB), c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+Synchronize(rdpContext *context)
+{
+ return TRUE;
+}
+
+static BOOL
+DesktopResize(rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ char buf[64];
+ snprintf(buf, 63, "R:%dx%d", context->settings->DesktopWidth,
+ context->settings->DesktopHeight);
+ c->my_internals->core->send_text_msg(buf, c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+BitmapUpdate(rdpContext *context, const BITMAP_UPDATE *bitmap)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ int i;
+ BITMAP_DATA *bmd;
+ for (i = 0; i < (int)bitmap->number; i++)
+ {
+ bmd = &bitmap->rectangles[i];
+ struct
+ {
+ uint32_t x;
+ uint32_t y;
+ uint32_t w;
+ uint32_t h;
+ uint32_t dw;
+ uint32_t dh;
+ uint32_t bpp;
+ uint32_t cf;
+ uint32_t sz;
+ } wxbm = {bmd->destLeft, bmd->destTop, bmd->width, bmd->height,
+ bmd->destRight - bmd->destLeft + 1,
+ bmd->destBottom - bmd->destTop + 1, bmd->bitsPerPixel,
+ (uint32_t)bmd->compressed, bmd->bitmapLength};
+ if (!bmd->compressed)
+ {
+ freerdp_image_flip(bmd->bitmapDataStream,
+ bmd->bitmapDataStream, bmd->width, bmd->height,
+ bmd->bitsPerPixel);
+ }
+ size_t buf_size = sizeof(wxbm) + bmd->bitmapLength;
+ uint8_t *buf = malloc(buf_size);
+ memcpy(buf, &wxbm, sizeof(wxbm));
+ memcpy(buf + sizeof(wxbm), bmd->bitmapDataStream,
+ bmd->bitmapLength);
+ c->my_internals->core->send_bitmap(
+ buf, buf_size, c->my_internals->task_info);
+ free(buf);
+ }
+ return TRUE;
+}
+
+static BOOL
+Palette(rdpContext *c, const PALETTE_UPDATE *p)
+{
+ return TRUE;
+}
+
+static BOOL
+PlaySound(rdpContext *c, const PLAY_SOUND_UPDATE *s)
+{
+ return TRUE;
+}
+
+static BOOL
+RefreshRect(rdpContext *c, UINT8 hz, const RECTANGLE_16 *r)
+{
+ return TRUE;
+}
+
+static BOOL
+SuppressOutput(rdpContext *c, UINT8 hz, const RECTANGLE_16 *r)
+{
+ return TRUE;
+}
+
+/*static BOOL
+SurfaceCommand(rdpContext* c, wStream* s)
+{
+ return TRUE;
+}*/
+
+static BOOL
+SurfaceBits(rdpContext *c, const SURFACE_BITS_COMMAND *sbc)
+{
+ return TRUE;
+}
+
+/*static BOOL
+SurfaceFrameMarker(rdpContext* c, SURFACE_FRAME_MARKER* sfm)
+{
+ return TRUE;
+} */
+
+void
+RegisterUpdate(freerdp *rdp)
+{
+ rdp->update->BeginPaint = BeginPaint;
+ rdp->update->EndPaint = EndPaint;
+ rdp->update->SetBounds = SetBounds;
+ rdp->update->Synchronize = Synchronize;
+ rdp->update->DesktopResize = DesktopResize;
+ rdp->update->BitmapUpdate = BitmapUpdate;
+ rdp->update->Palette = Palette;
+ rdp->update->PlaySound = PlaySound;
+ rdp->update->SurfaceBits = SurfaceBits;
+
+ rdp->update->RefreshRect = RefreshRect;
+ rdp->update->SuppressOutput = SuppressOutput;
+}
+
+static BOOL
+DstBlt(rdpContext *c, const DSTBLT_ORDER *_do)
+{
+ return TRUE;
+}
+
+static BOOL
+PatBlt(rdpContext *ctx, PATBLT_ORDER *po)
+{
+ my_rdp_context *c = (my_rdp_context *)ctx;
+ uint32_t rop3 = gdi_rop3_code(po->bRop);
+ if (GDI_BS_SOLID == po->brush.style)
+ {
+ struct
+ {
+ int32_t x;
+ int32_t y;
+ int32_t w;
+ int32_t h;
+ uint32_t fg;
+ uint32_t rop;
+ } tmp = {po->nLeftRect, po->nTopRect, po->nWidth, po->nHeight,
+ ConvertColor(po->foreColor, PIXEL_FORMAT_BGR16,
+ PIXEL_FORMAT_ABGR32,
+ NULL), //XXX
+ rop3};
+ c->my_internals->core->send_pat_blt(
+ (uint8_t *)&tmp, sizeof(tmp), c->my_internals->task_info);
+ }
+ return TRUE;
+}
+
+static BOOL
+ScrBlt(rdpContext *ctx, const SCRBLT_ORDER *sbo)
+{
+ my_rdp_context *c = (my_rdp_context *)ctx;
+ uint32_t rop3 = gdi_rop3_code(sbo->bRop);
+ struct
+ {
+ uint32_t rop;
+ int32_t x;
+ int32_t y;
+ int32_t w;
+ int32_t h;
+ int32_t sx;
+ int32_t sy;
+ } tmp = {rop3, sbo->nLeftRect, sbo->nTopRect, sbo->nWidth, sbo->nHeight,
+ sbo->nXSrc, sbo->nYSrc};
+ c->my_internals->core->send_scr_blt(
+ (uint8_t *)&tmp, sizeof(tmp), c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+OpaqueRect(rdpContext *context, const OPAQUE_RECT_ORDER *oro)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ OPAQUE_RECT_ORDER oro2 = *oro;
+ oro2.color = ConvertColor(
+ oro2.color, PIXEL_FORMAT_BGR16, PIXEL_FORMAT_ABGR32, NULL);
+ c->my_internals->core->send_opaque_rect_order((uint8_t *)&oro2,
+ sizeof(OPAQUE_RECT_ORDER), c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+DrawNineGrid(rdpContext *c, const DRAW_NINE_GRID_ORDER *dngo)
+{
+ return TRUE;
+}
+
+static BOOL
+MultiDstBlt(rdpContext *c, const MULTI_DSTBLT_ORDER *mdo)
+{
+ return TRUE;
+}
+
+static BOOL
+MultiPatBlt(rdpContext *c, const MULTI_PATBLT_ORDER *mpo)
+{
+ return TRUE;
+}
+
+static BOOL
+MultiScrBlt(rdpContext *c, const MULTI_SCRBLT_ORDER *mso)
+{
+ return TRUE;
+}
+
+static BOOL
+MultiOpaqueRect(rdpContext *context, const MULTI_OPAQUE_RECT_ORDER *moro)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ uint32_t color = ConvertColor(
+ moro->color, PIXEL_FORMAT_BGR16, PIXEL_FORMAT_ABGR32, NULL);
+ uint32_t nr = moro->numRectangles;
+ size_t buf_size
+ = (sizeof(color) + sizeof(nr)) + (sizeof(DELTA_RECT) * nr);
+ uint8_t *buf = malloc(buf_size);
+ memcpy(buf, (uint8_t *)&color, sizeof(color));
+ memcpy(buf + sizeof(color), (uint8_t *)&nr, sizeof(nr));
+ memcpy(buf + (sizeof(color) + sizeof(color)),
+ (uint8_t *)&moro->rectangles[1], sizeof(DELTA_RECT) * nr);
+ c->my_internals->core->send_multi_opaque_rect(
+ buf, buf_size, c->my_internals->task_info);
+ free(buf);
+ return TRUE;
+}
+
+static BOOL
+MultiDrawNineGrid(rdpContext *c, const MULTI_DRAW_NINE_GRID_ORDER *mdngo)
+{
+ return TRUE;
+}
+
+static BOOL
+LineTo(rdpContext *c, const LINE_TO_ORDER *lto)
+{
+ return TRUE;
+}
+
+static BOOL
+Polyline(rdpContext *c, const POLYLINE_ORDER *po)
+{
+ return TRUE;
+}
+
+static BOOL
+MemBlt(rdpContext *c, MEMBLT_ORDER *mo)
+{
+ return TRUE;
+}
+
+static BOOL
+Mem3Blt(rdpContext *c, MEM3BLT_ORDER *mo)
+{
+ return TRUE;
+}
+
+static BOOL
+SaveBitmap(rdpContext *c, const SAVE_BITMAP_ORDER *sbo)
+{
+ return TRUE;
+}
+
+static BOOL
+GlyphIndex(rdpContext *c, GLYPH_INDEX_ORDER *gio)
+{
+ return TRUE;
+}
+
+static BOOL
+FastIndex(rdpContext *c, const FAST_INDEX_ORDER *fio)
+{
+ return TRUE;
+}
+
+static BOOL
+FastGlyph(rdpContext *c, const FAST_GLYPH_ORDER *fgo)
+{
+ return TRUE;
+}
+
+static BOOL
+PolygonSC(rdpContext *c, const POLYGON_SC_ORDER *pso)
+{
+ return TRUE;
+}
+
+static BOOL
+PolygonCB(rdpContext *c, POLYGON_CB_ORDER *pco)
+{
+ return TRUE;
+}
+
+static BOOL
+EllipseSC(rdpContext *c, const ELLIPSE_SC_ORDER *eso)
+{
+ return TRUE;
+}
+
+static BOOL
+EllipseCB(rdpContext *c, const ELLIPSE_CB_ORDER *eco)
+{
+ return TRUE;
+}
+void
+RegisterPrimary(freerdp *rdp)
+{
+ rdp->update->primary->DstBlt = DstBlt;
+ rdp->update->primary->PatBlt = PatBlt;
+ rdp->update->primary->ScrBlt = ScrBlt;
+ rdp->update->primary->OpaqueRect = OpaqueRect;
+ rdp->update->primary->DrawNineGrid = DrawNineGrid;
+ rdp->update->primary->MultiDstBlt = MultiDstBlt;
+ rdp->update->primary->MultiPatBlt = MultiPatBlt;
+ rdp->update->primary->MultiScrBlt = MultiScrBlt;
+ rdp->update->primary->MultiOpaqueRect = MultiOpaqueRect;
+ rdp->update->primary->MultiDrawNineGrid = MultiDrawNineGrid;
+ rdp->update->primary->LineTo = LineTo;
+ rdp->update->primary->Polyline = Polyline;
+ rdp->update->primary->MemBlt = MemBlt;
+ rdp->update->primary->Mem3Blt = Mem3Blt;
+ rdp->update->primary->SaveBitmap = SaveBitmap;
+ rdp->update->primary->GlyphIndex = GlyphIndex;
+ rdp->update->primary->FastIndex = FastIndex;
+ rdp->update->primary->FastGlyph = FastGlyph;
+ rdp->update->primary->PolygonSC = PolygonSC;
+ rdp->update->primary->PolygonCB = PolygonCB;
+ rdp->update->primary->EllipseSC = EllipseSC;
+ rdp->update->primary->EllipseCB = EllipseCB;
+}
+
+/* incomming input from client handlers */
+
+static bool
+rdp_backend_handle_input_mouse(ws_input_mouse input, void *internals)
+{
+ rdp_internals *_i = internals;
+ rdpInput *inp = _i->instance->input;
+ _i->core->reset_idle(_i->task_info);
+ inp->MouseEvent(inp, input.flags, input.x, input.y);
+ return true;
+}
+
+static bool
+rdp_backend_handle_input_kupdown(ws_input_kupdown input, void *internals)
+{
+ rdp_internals *_i = internals;
+ rdpInput *inp = _i->instance->input;
+ if (0 < input.code)
+ {
+ _i->core->reset_idle(_i->task_info);
+ /*log::info << "257 >> tcode: " << input.code << "\n"; */
+ /* make byte */
+ input.code = RDP_SCANCODE_CODE(input.code);
+ /* apply extended */
+ input.code = ASCII_TO_SCANCODE[input.code];
+ /* extract extended sepparatelly in tflag */
+ uint32_t tflag = RDP_SCANCODE_EXTENDED(input.code) ?
+ KBD_FLAGS_EXTENDED :
+ 0;
+ /* now make the call */
+ freerdp_input_send_keyboard_event(inp,
+ (input.down ? KBD_FLAGS_DOWN : KBD_FLAGS_RELEASE) | tflag,
+ input.code);
+ }
+ return true;
+}
+
+static bool
+rdp_backend_handle_input_kpress(ws_input_kpress input, void *internals)
+{
+ rdp_internals *_i = internals;
+ rdpInput *inp = _i->instance->input;
+ _i->core->reset_idle(_i->task_info);
+ if (0x20 < input.code)
+ {
+ if (input.shiftstate & 6)
+ {
+ //Control and or Alt: Must use scan-codes since
+ //unicode-event can't handle these
+ if (((64 < input.code) && (91 > input.code))
+ || ((96 < input.code) && (123 > input.code)))
+ {
+ input.code -= (input.shiftstate & 1) ? 0 : 32;
+ input.code
+ = GetVirtualScanCodeFromVirtualKeyCode(
+ input.code, 4);
+ if (0 < input.code)
+ {
+ freerdp_input_send_unicode_keyboard_event(
+ inp, KBD_FLAGS_DOWN,
+ ASCII_TO_SCANCODE[input.code]);
+ freerdp_input_send_unicode_keyboard_event(
+ inp, KBD_FLAGS_RELEASE,
+ ASCII_TO_SCANCODE[input.code]);
+ }
+ }
+ }
+ else
+ {
+ if (0 < input.code)
+ {
+ if (input.code == 96)
+ {
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_DOWN,
+ RDP_SCANCODE_LCONTROL);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_DOWN, RDP_SCANCODE_LMENU);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_DOWN,
+ RDP_SCANCODE_DELETE);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_RELEASE,
+ RDP_SCANCODE_LCONTROL);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_RELEASE,
+ RDP_SCANCODE_LMENU);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_RELEASE,
+ RDP_SCANCODE_DELETE);
+ }
+
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_DOWN,
+ ASCII_TO_SCANCODE[input.code]);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_RELEASE,
+ ASCII_TO_SCANCODE[input.code]);
+ }
+ }
+ }
+ else
+ {
+ if (0 < input.code)
+ {
+ input.code = RDP_SCANCODE_CODE(input.code);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, ASCII_TO_SCANCODE[input.code]);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_RELEASE, ASCII_TO_SCANCODE[input.code]);
+ }
+ }
+ return true;
+}
+
+static bool
+rdp_backend_handle_input_kcomb(ws_input_keycomb input, void *internals)
+{
+ rdp_internals *_i = internals;
+ rdpInput *inp = _i->instance->input;
+ _i->core->reset_idle(_i->task_info);
+ switch (input)
+ {
+ case ws_keycomb_ctrlaltdel_press:
+ {
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, RDP_SCANCODE_LCONTROL);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, RDP_SCANCODE_LMENU);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, RDP_SCANCODE_DELETE);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, RDP_SCANCODE_LCONTROL);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, RDP_SCANCODE_LMENU);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, RDP_SCANCODE_DELETE);
+ }
+ break;
+ case ws_keycomb_alttab_press:
+ {
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, RDP_SCANCODE_LMENU);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, RDP_SCANCODE_TAB);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, RDP_SCANCODE_TAB);
+ }
+ break;
+ case ws_keycomb_alttab_release:
+ {
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, RDP_SCANCODE_LMENU);
+ }
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool
+rdp_backend_handle_input_unicode(ws_input_unicode input, void *internals)
+{
+ rdp_internals *_i = internals;
+ rdpInput *inp = _i->instance->input;
+ int i;
+ _i->core->reset_idle(_i->task_info);
+ for (i = 0; i < input.length; ++i)
+ {
+ freerdp_input_send_unicode_keyboard_event(
+ inp, KBD_FLAGS_DOWN, (UINT16)input.input[i]);
+ freerdp_input_send_unicode_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, (UINT16)input.input[i]);
+ }
+ return true;
+}
+
+void
+register_input(wrdp_backend_module *backend)
+{
+ backend->backend_handle_input_kcomb = rdp_backend_handle_input_kcomb;
+ backend->backend_handle_input_kupdown
+ = rdp_backend_handle_input_kupdown;
+ backend->backend_handle_input_kpress = rdp_backend_handle_input_kpress;
+ backend->backend_handle_input_mouse = rdp_backend_handle_input_mouse;
+ backend->backend_handle_input_unicode
+ = rdp_backend_handle_input_unicode;
+}
+
+static BOOL
+rdp_pre_connect(freerdp *instance)
+{
+ rdpSettings *settings = instance->settings;
+ my_rdp_context *c = (my_rdp_context *)instance->context;
+
+ RegisterUpdate(instance);
+ RegisterPrimary(instance);
+
+ /* looks like this settings reducing artifacts level */
+ settings->OrderSupport[NEG_PATBLT_INDEX] = FALSE;
+ settings->OrderSupport[NEG_SCRBLT_INDEX] = FALSE;
+
+ /* causing random disconnects */
+ settings->OrderSupport[NEG_ATEXTOUT_INDEX] = FALSE;
+ settings->OrderSupport[NEG_DRAWNINEGRID_INDEX] = FALSE;
+ settings->NoBitmapCompressionHeader = TRUE;
+ settings->BitmapCompressionDisabled = TRUE;
+ settings->FastPathOutput = FALSE;
+ settings->BitmapCacheEnabled = FALSE;
+ settings->BrushSupportLevel = 0;
+
+ /* causing connection failure on recent versions of freerdp */
+ settings->OffscreenSupportLevel = 0;
+ settings->OffscreenCacheSize = 0;
+
+ /* following required at least on windows 7 */
+ settings->OrderSupport[NEG_MEMBLT_INDEX] = FALSE;
+ settings->OrderSupport[NEG_MEM3BLT_INDEX] = FALSE;
+
+ instance->context->cache = cache_new(settings);
+
+ c->my_internals->clrconv
+ = freerdp_clrconv_new(CLRCONV_ALPHA | CLRCONV_INVERT);
+
+ if (!instance->context->cache)
+ {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL
+rdp_post_connect(freerdp *instance)
+{
+ my_rdp_context *c = (my_rdp_context *)instance->context;
+ c->my_internals->conn_state = rdp_conn_state_connected;
+ rdpPointer p;
+ memset(&p, 0, sizeof(p));
+ p.size = sizeof(my_rdp_pointer);
+ register_pointer(&p);
+ graphics_register_pointer(instance->context->graphics, &p);
+ pointer_cache_register_callbacks(instance->update);
+
+ c->my_internals->core->send_text_msg(
+ "C:RDP session connection started.", c->my_internals->task_info);
+
+ instance->update->DesktopResize(instance->context);
+
+ return TRUE;
+}
+
+static DWORD
+rdp_verify_certificate(freerdp *instance, const char *common_name,
+ const char *subject, const char *issuer, const char *fingerprint,
+ BOOL host_mismatch)
+{
+ WLog_INFO(TAG, "Certificate details:");
+ WLog_INFO(TAG, "\tSubject: %s", subject);
+ WLog_INFO(TAG, "\tIssuer: %s", issuer);
+ WLog_INFO(TAG, "\tThumbprint: %s", fingerprint);
+ WLog_INFO(TAG,
+ "The above X.509 certificate could not be verified, possibly "
+ "because you do not have "
+ "the CA certificate in your certificate store, or the "
+ "certificate has expired. "
+ "Please look at the OpenSSL documentation on how to add a "
+ "private CA to the store.");
+
+ return TRUE; /* ?? */
+}
+
+/*static int
+rdp_receive_channel_data (freerdp* instance,
+ UINT16 channelId,
+ BYTE* data,
+ int size,
+ int flags,
+ int total_size)
+{
+ return freerdp_channels_data (
+ instance, channelId, data, size, flags, total_size);
+}*/
+
+void
+rdp_register_base(freerdp *r)
+{
+ r->PreConnect = rdp_pre_connect;
+ r->PostConnect = rdp_post_connect;
+ r->VerifyCertificate = rdp_verify_certificate;
+ // r->ReceiveChannelData = rdp_receive_channel_data;
+ r->ContextNew = rdp_context_new;
+ r->ContextFree = rdp_context_free;
+}
diff --git a/src/rdp/rdp_module.c b/src/rdp/rdp_module.c
new file mode 100644
index 0000000..a315325
--- /dev/null
+++ b/src/rdp/rdp_module.c
@@ -0,0 +1,168 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <unistd.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+
+#include <webrdp_core_api.h>
+#include <webrdp_module_api.h>
+
+#include "rdp_backend_api.h"
+#include "rdp_impl.h"
+#include "rdp_display_output.h"
+#include "rdp_user_input.h"
+#include "rdp_clipboard.h"
+#include "rdp_ft.h"
+#include "rdp_module.h"
+#include "rdp_settings.h"
+
+/* static void
+send_termination_message (rdp_internals* i)
+{
+ i->core->send_termination_msg (i->task_info);
+} */
+
+static bool
+rdp_init(void *internals)
+{
+ bool ret = true;
+ if (!rdp_init_internals(internals))
+ {
+ /* TODO: handle error */
+ return false;
+ }
+ if (!((rdp_internals *)internals)->instance)
+ {
+ /* TODO: handle error */
+ ret = false;
+ }
+ if (!rdp_init_settings(internals))
+ {
+ /* TODO: handle error */
+ ret = false;
+ }
+ if (!rdp_connect(internals))
+ {
+ /* TODO: handle error */
+ ret = false;
+ }
+ if (!ret)
+ {
+ rdp_internals *i = internals;
+ i->core->api_msgs->send_error_msg(
+ "rdp_module_internal_error", i->task_info);
+ /*send_termination_message (i);*/
+ i->core->api_core->task_finished(false, i->task_info);
+ }
+ return ret;
+}
+
+static void
+destroy_rdp(void *internals)
+{
+ rdp_internals *_i = internals;
+ /* stop all rdp fd's watchers */
+ int i;
+ for (i = 0; i < _i->rdp_fd_count; ++i)
+ {
+ if (ev_is_active(&(_i->rdp_fd[i])))
+ {
+ ev_io_stop(
+ _i->core->api_core->get_libev_loop(_i->task_info),
+ &(_i->rdp_fd[i]));
+ }
+ }
+ if (_i->conn_state == rdp_conn_state_connected)
+ {
+ freerdp_disconnect(_i->instance);
+ _i->conn_state = rdp_conn_state_offline;
+ }
+ _i->rdp_fd_count = 0;
+ if (_i->context)
+ {
+ if (_i->context->clipboard && _i->context->clipboard->srv_fmts
+ && _i->context->clipboard->srv_fmts_count)
+ {
+ for (i = 0; i < _i->context->clipboard->srv_fmts_count;
+ ++i)
+ {
+ if (_i->context->clipboard->srv_fmts[i]
+ .rdp_fmt.formatName)
+ {
+ free(_i->context->clipboard->srv_fmts[i]
+ .rdp_fmt.formatName);
+ }
+ }
+ free(_i->context->clipboard->srv_fmts);
+ }
+ if (_i->context->clipboard && _i->context->clipboard->clipboard)
+ {
+ ClipboardDestroy(_i->context->clipboard->clipboard);
+ }
+ if (_i->context->clipboard
+ && _i->context->clipboard->client_filelist_cache)
+ {
+ free(_i->context->clipboard->client_filelist_cache);
+ _i->context->clipboard->client_filelist_cache = 0;
+ }
+ free(_i->context->ft_to_client);
+ free(_i->context->ft_to_server);
+ }
+ if (_i->instance)
+ {
+ freerdp_context_free(_i->instance);
+ freerdp_free(_i->instance);
+ _i->instance = 0;
+ }
+ if (internals)
+ {
+ rdp_settings_free(internals);
+ internals = 0;
+ }
+}
+
+static void
+rdp_set_task_info(void *task_info, void *internals)
+{
+ rdp_internals *i = internals;
+ i->task_info = task_info;
+}
+
+bool
+rdp_create(wrdp_core_exports *core, wrdp_backend_module *module)
+{
+ wrdp_backend_module *backend = module;
+ if (!backend)
+ {
+ return false;
+ }
+ backend->callbacks_module->init = rdp_init;
+ backend->callbacks_module->destroy = destroy_rdp;
+ backend->callbacks_module->set_setting_int = rdp_set_setting_int;
+ backend->callbacks_module->set_setting_str = rdp_set_setting_str;
+ backend->callbacks_module->set_task_info = rdp_set_task_info;
+ register_input(backend);
+ register_clipboard(backend);
+ register_ft(backend);
+ backend->backend_internals = calloc(1, sizeof(rdp_internals));
+ if (!backend->backend_internals)
+ {
+ perror("calloc");
+ return false;
+ }
+ memset(&(((rdp_internals *)backend->backend_internals)->my_settings), 0,
+ sizeof(my_rdp_settings));
+ /* TODO: do we need to zero each option structure in settings ? */
+ {
+ rdp_internals *i = backend->backend_internals;
+ i->core = core;
+ i->backend = backend;
+ }
+
+ return true;
+}
diff --git a/src/rdp/rdp_module.h b/src/rdp/rdp_module.h
new file mode 100644
index 0000000..59842d3
--- /dev/null
+++ b/src/rdp/rdp_module.h
@@ -0,0 +1,7 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once \ No newline at end of file
diff --git a/src/rdp/rdp_png.c b/src/rdp/rdp_png.c
new file mode 100644
index 0000000..09f01a2
--- /dev/null
+++ b/src/rdp/rdp_png.c
@@ -0,0 +1,89 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include "rdp_png.h"
+#include <stdlib.h>
+#include <string.h>
+
+static void
+cbPngError(png_structp png, png_const_charp msg)
+{
+ printf("Png error: %s\n", (msg ? msg : "unknown error"));
+}
+
+static void
+cbPngWarn(png_structp png, png_const_charp msg)
+{
+ printf("Png warning: %s\n", (msg ? msg : "unknown error"));
+}
+
+static void
+cbPngWrite(png_structp png, png_bytep data, png_size_t len)
+{
+ rdp_png_buf *out_buf = (rdp_png_buf *)png_get_io_ptr(png);
+ uint8_t *ptr = out_buf->buf;
+ if (out_buf->written + len > out_buf->buf_size)
+ {
+ printf("error: rdp_png_buf is too small\n");
+ return;
+ }
+ ptr += out_buf->written;
+ memcpy(ptr, data, len);
+ out_buf->written += len;
+}
+
+static void
+cbPngFlush(png_structp png)
+{
+}
+
+bool
+png_generate_from_argb(
+ int width, int height, uint8_t *data, rdp_png_buf *out_buf)
+{
+ png_infop info_ptr;
+ png_structp png_ptr = png_create_write_struct(
+ PNG_LIBPNG_VER_STRING, 0, cbPngError, cbPngWarn);
+ if (!png_ptr)
+ {
+ printf("Could not allocate png_struct\n");
+ return false;
+ }
+ info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr)
+ {
+ png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
+ printf("Could not allocate png_info_struct\n");
+ return false;
+ }
+ png_set_write_fn(png_ptr, out_buf, cbPngWrite, cbPngFlush);
+ png_set_IHDR(png_ptr, info_ptr, width, height, 8,
+ PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+ png_bytep *rows = malloc(sizeof(png_bytep) * height);
+ if (!rows)
+ {
+ perror("malloc");
+ return false;
+ }
+ for (int i = 0; i < height; ++i)
+ {
+ rows[i] = (png_bytep)data;
+ data += width * 4;
+ }
+ png_set_rows(png_ptr, info_ptr, rows);
+ png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
+
+ free(rows);
+
+ return true;
+}
+
+void
+png_destroy(png_structp *p, png_infop *i)
+{
+ png_destroy_write_struct(p, i);
+}
diff --git a/src/rdp/rdp_png.h b/src/rdp/rdp_png.h
new file mode 100644
index 0000000..a3a3bfc
--- /dev/null
+++ b/src/rdp/rdp_png.h
@@ -0,0 +1,22 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include <png.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+typedef struct
+{
+ uint8_t *buf;
+ size_t buf_size, written;
+} rdp_png_buf;
+
+void png_destroy(png_structp *p, png_infop *i);
+
+bool png_generate_from_argb(
+ int width, int height, uint8_t *data, rdp_png_buf *out_buf);
diff --git a/src/rdp/rdp_rail.c b/src/rdp/rdp_rail.c
new file mode 100644
index 0000000..bde1950
--- /dev/null
+++ b/src/rdp/rdp_rail.c
@@ -0,0 +1,173 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "webrdp_core_api.h"
+#include "webrdp_module_api.h"
+#include "rdp_backend_api.h"
+#include "rdp_rail.h"
+
+static UINT
+RailServerSystemParam(
+ RailClientContext *context, const RAIL_SYSPARAM_ORDER *sysparam)
+{
+ return CHANNEL_RC_OK;
+}
+
+static UINT
+RailServerHandshake(
+ RailClientContext *context, const RAIL_HANDSHAKE_ORDER *handshake)
+{
+ return client_rail_server_start_cmd(context);
+}
+
+static UINT
+RailServerHandshakeEx(
+ RailClientContext *context, const RAIL_HANDSHAKE_EX_ORDER *handshakeEx)
+{
+ return client_rail_server_start_cmd(context);
+}
+
+static UINT
+RailServerLocalMoveSize(
+ RailClientContext *context, const RAIL_LOCALMOVESIZE_ORDER *localMoveSize)
+{
+ return CHANNEL_RC_OK;
+}
+static UINT
+RailServerMinMaxInfo(
+ RailClientContext *context, const RAIL_MINMAXINFO_ORDER *minMaxInfo)
+{
+ return CHANNEL_RC_OK;
+}
+
+static UINT
+RailServerLanguageBarInfo(
+ RailClientContext *context, const RAIL_LANGBAR_INFO_ORDER *langBarInfo)
+{
+ return CHANNEL_RC_OK;
+}
+static UINT
+RailServerExecuteResult(
+ RailClientContext *context, const RAIL_EXEC_RESULT_ORDER *execResult)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ if (execResult->execResult != RAIL_EXEC_S_OK)
+ {
+ const char *msg
+ = "rdp_module: rail: RailServerExecuteResult failure";
+ c->my_internals->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ }
+ return CHANNEL_RC_OK;
+}
+
+static UINT
+RailServerGetAppIdResponse(
+ RailClientContext *context, const RAIL_GET_APPID_RESP_ORDER *getAppIdResp)
+{
+ return CHANNEL_RC_OK;
+}
+
+static BOOL
+WindowCreate(rdpContext *context, const WINDOW_ORDER_INFO *orderInfo,
+ const WINDOW_STATE_ORDER *window_state)
+{
+ return TRUE;
+}
+static BOOL
+WindowUpdate(rdpContext *context, const WINDOW_ORDER_INFO *orderInfo,
+ const WINDOW_STATE_ORDER *window_state)
+{
+ return TRUE;
+}
+static BOOL
+WindowIcon(rdpContext *context, const WINDOW_ORDER_INFO *orderInfo,
+ const WINDOW_ICON_ORDER *window_icon)
+{
+ return TRUE;
+}
+static BOOL
+WindowCachedIcon(rdpContext *context, const WINDOW_ORDER_INFO *orderInfo,
+ const WINDOW_CACHED_ICON_ORDER *window_cached_icon)
+{
+ return TRUE;
+}
+static BOOL
+WindowDelete(rdpContext *context, const WINDOW_ORDER_INFO *orderInfo)
+{
+ return TRUE;
+}
+static BOOL
+NotifyIconCreate(rdpContext *context, const WINDOW_ORDER_INFO *orderInfo,
+ const NOTIFY_ICON_STATE_ORDER *notify_icon_state)
+{
+ return TRUE;
+}
+static BOOL
+NotifyIconUpdate(rdpContext *context, const WINDOW_ORDER_INFO *orderInfo,
+ const NOTIFY_ICON_STATE_ORDER *notify_icon_state)
+{
+ return TRUE;
+}
+static BOOL
+NotifyIconDelete(rdpContext *context, const WINDOW_ORDER_INFO *orderInfo)
+{
+ return TRUE;
+}
+static BOOL
+MonitoredDesktop(rdpContext *context, const WINDOW_ORDER_INFO *orderInfo,
+ const MONITORED_DESKTOP_ORDER *monitored_desktop)
+{
+ return TRUE;
+}
+static BOOL
+NonMonitoredDesktop(rdpContext *context, const WINDOW_ORDER_INFO *orderInfo)
+{
+ return TRUE;
+}
+
+static void
+rdp_rail_register_update_callbacks(rdpUpdate *update)
+{
+ rdpWindowUpdate *window = update->window;
+ window->WindowCreate = WindowCreate;
+ window->WindowUpdate = WindowUpdate;
+ window->WindowDelete = WindowDelete;
+ window->WindowIcon = WindowIcon;
+ window->WindowCachedIcon = WindowCachedIcon;
+ window->NotifyIconCreate = NotifyIconCreate;
+ window->NotifyIconUpdate = NotifyIconUpdate;
+ window->NotifyIconDelete = NotifyIconDelete;
+ window->MonitoredDesktop = MonitoredDesktop;
+ window->NonMonitoredDesktop = NonMonitoredDesktop;
+}
+
+void
+rdp_rail_init(my_rdp_context *context, RailClientContext *rail)
+{
+ if (!context || !rail)
+ return;
+
+ rdp_rail_register_update_callbacks(context->context.update);
+ rail->custom = (void *)context;
+ rail->ServerExecuteResult = RailServerExecuteResult;
+ rail->ServerSystemParam = RailServerSystemParam;
+ rail->ServerHandshake = RailServerHandshake;
+ rail->ServerHandshakeEx = RailServerHandshakeEx;
+ rail->ServerLocalMoveSize = RailServerLocalMoveSize;
+ rail->ServerMinMaxInfo = RailServerMinMaxInfo;
+ rail->ServerLanguageBarInfo = RailServerLanguageBarInfo;
+ rail->ServerGetAppIdResponse = RailServerGetAppIdResponse;
+}
+
+void
+rdp_rail_uninit(my_rdp_context *context, RailClientContext *rail)
+{
+}
diff --git a/src/rdp/rdp_rail.h b/src/rdp/rdp_rail.h
new file mode 100644
index 0000000..0d3d7ba
--- /dev/null
+++ b/src/rdp/rdp_rail.h
@@ -0,0 +1,14 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include "rdp_channels.h"
+#include "rdp_impl.h"
+
+void rdp_rail_init(my_rdp_context *context, RailClientContext *rail);
+
+void rdp_rail_uninit(my_rdp_context *context, RailClientContext *rail);
diff --git a/src/rdp/rdp_settings.c b/src/rdp/rdp_settings.c
new file mode 100644
index 0000000..ecb1dcc
--- /dev/null
+++ b/src/rdp/rdp_settings.c
@@ -0,0 +1,2278 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#include <webrdp_module_api.h>
+#include <webrdp_core_api.h>
+
+#include "rdp_backend_api.h"
+#include "rdp_impl.h"
+
+/* this variable used in integer settng name field to indicate used setting */
+static const char *setting_used = "1";
+
+bool
+rdp_set_setting_str(backend_setting_str *setting, void *internals)
+{
+ rdp_internals *i = internals;
+#ifdef DEBUG
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "rdp backend handling setting string: \"%s: %s\"",
+ setting->name, setting->value);
+ i->core->api_utils->log_msg(
+ (const uint8_t *)buf, strlen(buf), wrdp_log_level_trace, 0);
+ }
+#endif /* DEBUG */
+ if (!strcmp(setting->name, "host"))
+ {
+ free(i->my_settings.host.value);
+ i->my_settings.host.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "pcb"))
+ {
+ /*
+ free (i->my_settings.pcb.value);
+ i->my_settings.pcb.value = strdup (setting->value); */
+ return true;
+ }
+ else if (!strcmp(setting->name, "user") && setting->value)
+ {
+ free(i->my_settings.user.value);
+ i->my_settings.user.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "password") && setting->value)
+ {
+ free(i->my_settings.password.value);
+ i->my_settings.password.value = strdup(setting->value);
+ return true;
+ }
+
+ /* handle int settings passed as strings */
+ if (!strcmp(setting->name, "port"))
+ {
+ i->my_settings.port.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.port.value = atoll(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "width"))
+ {
+ i->my_settings.width.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.width.value = atoll(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "height"))
+ {
+ i->my_settings.height.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.height.value = atoll(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "perf"))
+ {
+ i->my_settings.perf.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.perf.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "fntlm"))
+ {
+ i->my_settings.fntlm.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.fntlm.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "nowallp"))
+ {
+ i->my_settings.nowallp.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.nowallp.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "nowdrag"))
+ {
+ i->my_settings.nowdrag.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.nowdrag.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "nomani"))
+ {
+ i->my_settings.nomani.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.nomani.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "notheme"))
+ {
+ i->my_settings.notheme.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.notheme.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "nonla"))
+ {
+ i->my_settings.nonla.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.nonla.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "notls"))
+ {
+ i->my_settings.notls.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.notls.value = atoi(setting->value);
+ }
+ return true;
+ }
+
+ /*new settings from freerdp header (strings) begin */
+ else if (!strcmp(setting->name, "Domain") && setting->value)
+ {
+ free(i->my_settings.Domain.value);
+ i->my_settings.Domain.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "PasswordHash") && setting->value)
+ {
+ free(i->my_settings.PasswordHash.value);
+ i->my_settings.PasswordHash.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "AcceptedCert") && setting->value)
+ {
+ free(i->my_settings.AcceptedCert.value);
+ i->my_settings.AcceptedCert.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "ClientHostname") && setting->value)
+ {
+ free(i->my_settings.ClientHostname.value);
+ i->my_settings.ClientHostname.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "ClientProductId") && setting->value)
+ {
+ free(i->my_settings.ClientProductId.value);
+ i->my_settings.ClientProductId.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "AlternateShell") && setting->value)
+ {
+ free(i->my_settings.AlternateShell.value);
+ i->my_settings.AlternateShell.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "ShellWorkingDirectory")
+ && setting->value)
+ {
+ free(i->my_settings.ShellWorkingDirectory.value);
+ i->my_settings.ShellWorkingDirectory.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "ClientAddress") && setting->value)
+ {
+ free(i->my_settings.ClientAddress.value);
+ i->my_settings.ClientAddress.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "ClientDir") && setting->value)
+ {
+ free(i->my_settings.ClientDir.value);
+ i->my_settings.ClientDir.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "DynamicDSTTimeZoneKeyName")
+ && setting->value)
+ {
+ free(i->my_settings.DynamicDSTTimeZoneKeyName.value);
+ i->my_settings.DynamicDSTTimeZoneKeyName.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteAssistanceSessionId")
+ && setting->value)
+ {
+ free(i->my_settings.RemoteAssistanceSessionId.value);
+ i->my_settings.RemoteAssistanceSessionId.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteAssistancePassStub")
+ && setting->value)
+ {
+ free(i->my_settings.RemoteAssistancePassStub.value);
+ i->my_settings.RemoteAssistancePassStub.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteAssistancePassword")
+ && setting->value)
+ {
+ free(i->my_settings.RemoteAssistancePassword.value);
+ i->my_settings.RemoteAssistancePassword.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteAssistanceRCTicket")
+ && setting->value)
+ {
+ free(i->my_settings.RemoteAssistanceRCTicket.value);
+ i->my_settings.RemoteAssistanceRCTicket.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "AuthenticationServiceClass")
+ && setting->value)
+ {
+ free(i->my_settings.AuthenticationServiceClass.value);
+ i->my_settings.AuthenticationServiceClass.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "AllowedTlsCiphers") && setting->value)
+ {
+ free(i->my_settings.AllowedTlsCiphers.value);
+ i->my_settings.AllowedTlsCiphers.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "NtlmSamFile") && setting->value)
+ {
+ free(i->my_settings.NtlmSamFile.value);
+ i->my_settings.NtlmSamFile.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "PreconnectionBlob") && setting->value)
+ {
+ free(i->my_settings.PreconnectionBlob.value);
+ i->my_settings.PreconnectionBlob.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "RedirectionAcceptedCert")
+ && setting->value)
+ {
+ free(i->my_settings.RedirectionAcceptedCert.value);
+ i->my_settings.RedirectionAcceptedCert.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "KerberosKdc") && setting->value)
+ {
+ free(i->my_settings.KerberosKdc.value);
+ i->my_settings.KerberosKdc.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "KerberosRealm") && setting->value)
+ {
+ free(i->my_settings.KerberosRealm.value);
+ i->my_settings.KerberosRealm.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "CertificateName") && setting->value)
+ {
+ free(i->my_settings.CertificateName.value);
+ i->my_settings.CertificateName.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "CertificateFile") && setting->value)
+ {
+ free(i->my_settings.CertificateFile.value);
+ i->my_settings.CertificateFile.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "PrivateKeyFile") && setting->value)
+ {
+ free(i->my_settings.PrivateKeyFile.value);
+ i->my_settings.PrivateKeyFile.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "CertificateContent") && setting->value)
+ {
+ free(i->my_settings.CertificateContent.value);
+ i->my_settings.CertificateContent.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "PrivateKeyContent") && setting->value)
+ {
+ free(i->my_settings.PrivateKeyContent.value);
+ i->my_settings.PrivateKeyContent.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "WindowTitle") && setting->value)
+ {
+ free(i->my_settings.WindowTitle.value);
+ i->my_settings.WindowTitle.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "WmClass") && setting->value)
+ {
+ free(i->my_settings.WmClass.value);
+ i->my_settings.WmClass.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "ComputerName") && setting->value)
+ {
+ free(i->my_settings.ComputerName.value);
+ i->my_settings.ComputerName.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "ConnectionFile") && setting->value)
+ {
+ free(i->my_settings.ConnectionFile.value);
+ i->my_settings.ConnectionFile.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "AssistanceFile") && setting->value)
+ {
+ free(i->my_settings.AssistanceFile.value);
+ i->my_settings.AssistanceFile.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "HomePath") && setting->value)
+ {
+ free(i->my_settings.HomePath.value);
+ i->my_settings.HomePath.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "ConfigPath") && setting->value)
+ {
+ free(i->my_settings.ConfigPath.value);
+ i->my_settings.ConfigPath.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "CurrentPath") && setting->value)
+ {
+ free(i->my_settings.CurrentPath.value);
+ i->my_settings.CurrentPath.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "DumpRemoteFxFile") && setting->value)
+ {
+ free(i->my_settings.DumpRemoteFxFile.value);
+ i->my_settings.DumpRemoteFxFile.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "PlayRemoteFxFile") && setting->value)
+ {
+ free(i->my_settings.PlayRemoteFxFile.value);
+ i->my_settings.PlayRemoteFxFile.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "GatewayHostname") && setting->value)
+ {
+ free(i->my_settings.GatewayHostname.value);
+ i->my_settings.GatewayHostname.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "GatewayUsername") && setting->value)
+ {
+ free(i->my_settings.GatewayUsername.value);
+ i->my_settings.GatewayUsername.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "GatewayPassword") && setting->value)
+ {
+ free(i->my_settings.GatewayPassword.value);
+ i->my_settings.GatewayPassword.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "GatewayDomain") && setting->value)
+ {
+ free(i->my_settings.GatewayDomain.value);
+ i->my_settings.GatewayDomain.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "GatewayAccessToken") && setting->value)
+ {
+ free(i->my_settings.GatewayAccessToken.value);
+ i->my_settings.GatewayAccessToken.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "GatewayAcceptedCert")
+ && setting->value)
+ {
+ free(i->my_settings.GatewayAcceptedCert.value);
+ i->my_settings.GatewayAcceptedCert.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "ProxyHostname") && setting->value)
+ {
+ free(i->my_settings.ProxyHostname.value);
+ i->my_settings.ProxyHostname.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteApplicationName")
+ && setting->value)
+ {
+ free(i->my_settings.RemoteApplicationName.value);
+ i->my_settings.RemoteApplicationName.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteApplicationIcon")
+ && setting->value)
+ {
+ free(i->my_settings.RemoteApplicationIcon.value);
+ i->my_settings.RemoteApplicationIcon.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteApplicationProgram")
+ && setting->value)
+ {
+ free(i->my_settings.RemoteApplicationProgram.value);
+ i->my_settings.RemoteApplicationProgram.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteApplicationFile")
+ && setting->value)
+ {
+ free(i->my_settings.RemoteApplicationFile.value);
+ i->my_settings.RemoteApplicationFile.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteApplicationGuid")
+ && setting->value)
+ {
+ free(i->my_settings.RemoteApplicationGuid.value);
+ i->my_settings.RemoteApplicationGuid.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteApplicationCmdLine")
+ && setting->value)
+ {
+ free(i->my_settings.RemoteApplicationCmdLine.value);
+ i->my_settings.RemoteApplicationCmdLine.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "ImeFileName") && setting->value)
+ {
+ free(i->my_settings.ImeFileName.value);
+ i->my_settings.ImeFileName.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "DrivesToRedirect") && setting->value)
+ {
+ free(i->my_settings.DrivesToRedirect.value);
+ i->my_settings.DrivesToRedirect.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "ActionScript") && setting->value)
+ {
+ free(i->my_settings.ActionScript.value);
+ i->my_settings.ActionScript.value = strdup(setting->value);
+ return true;
+ }
+ /*new settings from freerdp header (strings) end */
+
+ /* TODO: handle also integer settings passed as strings */
+
+ /* new settings from freerdp header (integers as string) begin */
+
+ else if (!strcmp(setting->name, "ServerMode"))
+ {
+ i->my_settings.ServerMode.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.ServerMode.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "WaitForOutputBufferFlush"))
+ {
+ i->my_settings.WaitForOutputBufferFlush.name
+ = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.WaitForOutputBufferFlush.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "MaxTimeInCheckLoop"))
+ {
+ i->my_settings.MaxTimeInCheckLoop.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.MaxTimeInCheckLoop.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "DesktopWidth"))
+ {
+ i->my_settings.DesktopWidth.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.DesktopWidth.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "DesktopHeight"))
+ {
+ i->my_settings.DesktopHeight.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.DesktopHeight.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "Workarea"))
+ {
+ i->my_settings.Workarea.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.Workarea.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "Fullscreen"))
+ {
+ i->my_settings.Fullscreen.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.Fullscreen.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "GrabKeyboard"))
+ {
+ i->my_settings.GrabKeyboard.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.GrabKeyboard.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "Decorations"))
+ {
+ i->my_settings.Decorations.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.Decorations.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "RdpVersion"))
+ {
+ i->my_settings.RdpVersion.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.RdpVersion.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "ColorDepth"))
+ {
+ i->my_settings.ColorDepth.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.ColorDepth.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "ExtSecurity"))
+ {
+ i->my_settings.ExtSecurity.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.ExtSecurity.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "NlaSecurity"))
+ {
+ i->my_settings.NlaSecurity.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.NlaSecurity.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "TlsSecurity"))
+ {
+ i->my_settings.TlsSecurity.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.TlsSecurity.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "RdpSecurity"))
+ {
+ i->my_settings.RdpSecurity.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.RdpSecurity.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "NegotiateSecurityLayer"))
+ {
+ i->my_settings.NegotiateSecurityLayer.name
+ = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.NegotiateSecurityLayer.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "RestrictedAdminModeRequired"))
+ {
+ i->my_settings.RestrictedAdminModeRequired.name
+ = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.RestrictedAdminModeRequired.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "MstscCookieMode"))
+ {
+ i->my_settings.MstscCookieMode.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.MstscCookieMode.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "CookieMaxLength"))
+ {
+ i->my_settings.CookieMaxLength.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.CookieMaxLength.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "ClientBuild"))
+ {
+ i->my_settings.ClientBuild.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.ClientBuild.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "KeyboardType"))
+ {
+ i->my_settings.KeyboardType.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.KeyboardType.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "KeyboardSubType"))
+ {
+ i->my_settings.KeyboardSubType.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.KeyboardSubType.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "KeyboardFunctionKey"))
+ {
+ i->my_settings.KeyboardFunctionKey.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.KeyboardFunctionKey.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "KeyboardLayout"))
+ {
+ i->my_settings.KeyboardLayout.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.KeyboardLayout.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "UseRdpSecurityLayer"))
+ {
+ i->my_settings.UseRdpSecurityLayer.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.UseRdpSecurityLayer.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "SaltedChecksum"))
+ {
+ i->my_settings.SaltedChecksum.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.SaltedChecksum.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "ServerPort"))
+ {
+ i->my_settings.ServerPort.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.ServerPort.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "GatewayPort"))
+ {
+ i->my_settings.GatewayPort.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.GatewayPort.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "DesktopResize"))
+ {
+ i->my_settings.DesktopResize.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.DesktopResize.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "ToggleFullscreen"))
+ {
+ i->my_settings.ToggleFullscreen.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.ToggleFullscreen.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "Floatbar"))
+ {
+ i->my_settings.Floatbar.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.Floatbar.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "DesktopPosX"))
+ {
+ i->my_settings.DesktopPosX.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.DesktopPosX.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "DesktopPosY"))
+ {
+ i->my_settings.DesktopPosY.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.DesktopPosY.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "SoftwareGdi"))
+ {
+ i->my_settings.SoftwareGdi.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.SoftwareGdi.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "UnmapButtons"))
+ {
+ i->my_settings.UnmapButtons.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.UnmapButtons.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "PerformanceFlags"))
+ {
+ i->my_settings.PerformanceFlags.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.PerformanceFlags.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "AllowFontSmoothing"))
+ {
+ i->my_settings.AllowFontSmoothing.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.AllowFontSmoothing.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "AllowDesktopComposition"))
+ {
+ i->my_settings.AllowDesktopComposition.name
+ = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.AllowDesktopComposition.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "DisableWallpaper"))
+ {
+ i->my_settings.DisableWallpaper.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.DisableWallpaper.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "DisableFullWindowDrag"))
+ {
+ i->my_settings.DisableFullWindowDrag.name
+ = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.DisableFullWindowDrag.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "DisableMenuAnims"))
+ {
+ i->my_settings.DisableMenuAnims.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.DisableMenuAnims.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "DisableThemes"))
+ {
+ i->my_settings.DisableThemes.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.DisableThemes.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "ConnectionType"))
+ {
+ i->my_settings.ConnectionType.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.ConnectionType.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "EncryptionMethods"))
+ {
+ i->my_settings.EncryptionMethods.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.EncryptionMethods.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "EncryptionLevel"))
+ {
+ i->my_settings.EncryptionLevel.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.EncryptionLevel.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "FIPSMode"))
+ {
+ i->my_settings.FIPSMode.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.FIPSMode.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "CompressionEnabled"))
+ {
+ i->my_settings.CompressionEnabled.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.CompressionEnabled.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "LogonNotify"))
+ {
+ i->my_settings.LogonNotify.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.LogonNotify.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "BrushSupportLevel"))
+ {
+ i->my_settings.BrushSupportLevel.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.BrushSupportLevel.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteApplicationMode"))
+ {
+ i->my_settings.RemoteApplicationMode.name
+ = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.RemoteApplicationMode.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "TcpAckTimeout"))
+ {
+ i->my_settings.TcpAckTimeout.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.TcpAckTimeout.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ /* new settings from freerdp header (integers as string) end */
+
+ /* error: unhandled setting */
+ {
+ char buf[128];
+ snprintf(buf, 127, "rdp_module: unknown setting string: %s",
+ setting->name);
+ i->core->api_utils->log_msg((const uint8_t *)buf, strlen(buf),
+ wrdp_log_level_warning, 0);
+ }
+ return false;
+}
+
+bool
+rdp_set_setting_int(backend_setting_int *setting, void *internals)
+{
+ rdp_internals *i = internals;
+#ifdef DEBUG
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "rdp backend handling setting int: \"%s: %ld\"",
+ setting->name, setting->value);
+ i->core->api_utils->log_msg(
+ (const uint8_t *)buf, strlen(buf), wrdp_log_level_trace, 0);
+ }
+#endif
+ if (!strcmp(setting->name, "port"))
+ {
+ i->my_settings.port.name = (char *)setting_used;
+ i->my_settings.port.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "width"))
+ {
+ i->my_settings.width.name = (char *)setting_used;
+ i->my_settings.width.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "height"))
+ {
+ i->my_settings.height.name = (char *)setting_used;
+ i->my_settings.height.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "perf"))
+ {
+ i->my_settings.perf.name = (char *)setting_used;
+ i->my_settings.perf.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "fntlm"))
+ {
+ i->my_settings.fntlm.name = (char *)setting_used;
+ i->my_settings.fntlm.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "nowallp"))
+ {
+ i->my_settings.nowallp.name = (char *)setting_used;
+ i->my_settings.nowallp.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "nowdrag"))
+ {
+ i->my_settings.nowdrag.name = (char *)setting_used;
+ i->my_settings.nowdrag.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "nomani"))
+ {
+ i->my_settings.nomani.name = (char *)setting_used;
+ i->my_settings.nomani.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "notheme"))
+ {
+ i->my_settings.notheme.name = (char *)setting_used;
+ i->my_settings.notheme.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "nonla"))
+ {
+ i->my_settings.nonla.name = (char *)setting_used;
+ i->my_settings.nonla.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "notls"))
+ {
+ i->my_settings.notls.name = (char *)setting_used;
+ i->my_settings.notls.value = setting->value;
+ return true;
+ }
+
+ /* new settings from freerdp header (integers) begin */
+ else if (!strcmp(setting->name, "ServerMode"))
+ {
+ i->my_settings.ServerMode.name = (char *)setting_used;
+ i->my_settings.ServerMode.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "WaitForOutputBufferFlush"))
+ {
+ i->my_settings.WaitForOutputBufferFlush.name
+ = (char *)setting_used;
+ i->my_settings.WaitForOutputBufferFlush.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "MaxTimeInCheckLoop"))
+ {
+ i->my_settings.MaxTimeInCheckLoop.name = (char *)setting_used;
+ i->my_settings.MaxTimeInCheckLoop.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "DesktopWidth"))
+ {
+ i->my_settings.DesktopWidth.name = (char *)setting_used;
+ i->my_settings.DesktopWidth.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "DesktopHeight"))
+ {
+ i->my_settings.DesktopHeight.name = (char *)setting_used;
+ i->my_settings.DesktopHeight.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "Workarea"))
+ {
+ i->my_settings.Workarea.name = (char *)setting_used;
+ i->my_settings.Workarea.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "Fullscreen"))
+ {
+ i->my_settings.Fullscreen.name = (char *)setting_used;
+ i->my_settings.Fullscreen.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "GrabKeyboard"))
+ {
+ i->my_settings.GrabKeyboard.name = (char *)setting_used;
+ i->my_settings.GrabKeyboard.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "Decorations"))
+ {
+ i->my_settings.Decorations.name = (char *)setting_used;
+ i->my_settings.Decorations.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "RdpVersion"))
+ {
+ i->my_settings.RdpVersion.name = (char *)setting_used;
+ i->my_settings.RdpVersion.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "ColorDepth"))
+ {
+ i->my_settings.ColorDepth.name = (char *)setting_used;
+ i->my_settings.ColorDepth.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "ExtSecurity"))
+ {
+ i->my_settings.ExtSecurity.name = (char *)setting_used;
+ i->my_settings.ExtSecurity.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "NlaSecurity"))
+ {
+ i->my_settings.NlaSecurity.name = (char *)setting_used;
+ i->my_settings.NlaSecurity.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "TlsSecurity"))
+ {
+ i->my_settings.TlsSecurity.name = (char *)setting_used;
+ i->my_settings.TlsSecurity.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "RdpSecurity"))
+ {
+ i->my_settings.RdpSecurity.name = (char *)setting_used;
+ i->my_settings.RdpSecurity.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "NegotiateSecurityLayer"))
+ {
+ i->my_settings.NegotiateSecurityLayer.name
+ = (char *)setting_used;
+ i->my_settings.NegotiateSecurityLayer.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "RestrictedAdminModeRequired"))
+ {
+ i->my_settings.RestrictedAdminModeRequired.name
+ = (char *)setting_used;
+ i->my_settings.RestrictedAdminModeRequired.value
+ = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "MstscCookieMode"))
+ {
+ i->my_settings.MstscCookieMode.name = (char *)setting_used;
+ i->my_settings.MstscCookieMode.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "CookieMaxLength"))
+ {
+ i->my_settings.CookieMaxLength.name = (char *)setting_used;
+ i->my_settings.CookieMaxLength.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "ClientBuild"))
+ {
+ i->my_settings.ClientBuild.name = (char *)setting_used;
+ i->my_settings.ClientBuild.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "KeyboardType"))
+ {
+ i->my_settings.KeyboardType.name = (char *)setting_used;
+ i->my_settings.KeyboardType.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "KeyboardSubType"))
+ {
+ i->my_settings.KeyboardSubType.name = (char *)setting_used;
+ i->my_settings.KeyboardSubType.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "KeyboardFunctionKey"))
+ {
+ i->my_settings.KeyboardFunctionKey.name = (char *)setting_used;
+ i->my_settings.KeyboardFunctionKey.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "KeyboardLayout"))
+ {
+ i->my_settings.KeyboardLayout.name = (char *)setting_used;
+ i->my_settings.KeyboardLayout.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "UseRdpSecurityLayer"))
+ {
+ i->my_settings.UseRdpSecurityLayer.name = (char *)setting_used;
+ i->my_settings.UseRdpSecurityLayer.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "SaltedChecksum"))
+ {
+ i->my_settings.SaltedChecksum.name = (char *)setting_used;
+ i->my_settings.SaltedChecksum.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "ServerPort"))
+ {
+ i->my_settings.ServerPort.name = (char *)setting_used;
+ i->my_settings.ServerPort.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "GatewayPort"))
+ {
+ i->my_settings.GatewayPort.name = (char *)setting_used;
+ i->my_settings.GatewayPort.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "DesktopResize"))
+ {
+ i->my_settings.DesktopResize.name = (char *)setting_used;
+ i->my_settings.DesktopResize.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "ToggleFullscreen"))
+ {
+ i->my_settings.ToggleFullscreen.name = (char *)setting_used;
+ i->my_settings.ToggleFullscreen.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "Floatbar"))
+ {
+ i->my_settings.Floatbar.name = (char *)setting_used;
+ i->my_settings.Floatbar.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "DesktopPosX"))
+ {
+ i->my_settings.DesktopPosX.name = (char *)setting_used;
+ i->my_settings.DesktopPosX.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "DesktopPosY"))
+ {
+ i->my_settings.DesktopPosY.name = (char *)setting_used;
+ i->my_settings.DesktopPosY.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "SoftwareGdi"))
+ {
+ i->my_settings.SoftwareGdi.name = (char *)setting_used;
+ i->my_settings.SoftwareGdi.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "UnmapButtons"))
+ {
+ i->my_settings.UnmapButtons.name = (char *)setting_used;
+ i->my_settings.UnmapButtons.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "PerformanceFlags"))
+ {
+ i->my_settings.PerformanceFlags.name = (char *)setting_used;
+ i->my_settings.PerformanceFlags.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "AllowFontSmoothing"))
+ {
+ i->my_settings.AllowFontSmoothing.name = (char *)setting_used;
+ i->my_settings.AllowFontSmoothing.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "AllowDesktopComposition"))
+ {
+ i->my_settings.AllowDesktopComposition.name
+ = (char *)setting_used;
+ i->my_settings.AllowDesktopComposition.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "DisableWallpaper"))
+ {
+ i->my_settings.DisableWallpaper.name = (char *)setting_used;
+ i->my_settings.DisableWallpaper.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "DisableFullWindowDrag"))
+ {
+ i->my_settings.DisableFullWindowDrag.name
+ = (char *)setting_used;
+ i->my_settings.DisableFullWindowDrag.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "DisableMenuAnims"))
+ {
+ i->my_settings.DisableMenuAnims.name = (char *)setting_used;
+ i->my_settings.DisableMenuAnims.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "DisableThemes"))
+ {
+ i->my_settings.DisableThemes.name = (char *)setting_used;
+ i->my_settings.DisableThemes.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "ConnectionType"))
+ {
+ i->my_settings.ConnectionType.name = (char *)setting_used;
+ i->my_settings.ConnectionType.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "EncryptionMethods"))
+ {
+ i->my_settings.EncryptionMethods.name = (char *)setting_used;
+ i->my_settings.EncryptionMethods.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "EncryptionLevel"))
+ {
+ i->my_settings.EncryptionLevel.name = (char *)setting_used;
+ i->my_settings.EncryptionLevel.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "FIPSMode"))
+ {
+ i->my_settings.FIPSMode.name = (char *)setting_used;
+ i->my_settings.FIPSMode.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "CompressionEnabled"))
+ {
+ i->my_settings.CompressionEnabled.name = (char *)setting_used;
+ i->my_settings.CompressionEnabled.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "LogonNotify"))
+ {
+ i->my_settings.LogonNotify.name = (char *)setting_used;
+ i->my_settings.LogonNotify.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "BrushSupportLevel"))
+ {
+ i->my_settings.BrushSupportLevel.name = (char *)setting_used;
+ i->my_settings.BrushSupportLevel.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteApplicationMode"))
+ {
+ i->my_settings.RemoteApplicationMode.name
+ = (char *)setting_used;
+ i->my_settings.RemoteApplicationMode.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "TcpAckTimeout"))
+ {
+ i->my_settings.TcpAckTimeout.name = (char *)setting_used;
+ i->my_settings.TcpAckTimeout.value = setting->value;
+ return true;
+ }
+
+ /* new settings from freerdp header (integers) end */
+
+ /* error: unhandled setting */
+ {
+ char buf[128];
+ snprintf(buf, 127, "rdp_module: unknown setting int: %s",
+ setting->name);
+ i->core->api_utils->log_msg((const uint8_t *)buf, strlen(buf),
+ wrdp_log_level_warning, 0);
+ }
+ return false;
+}
+
+void
+rdp_settings_free(rdp_internals *internals)
+{
+ rdp_internals *i = internals;
+ if (i->my_settings.host.value)
+ {
+ free((void *)i->my_settings.host.value);
+ }
+ if (i->my_settings.pcb.value)
+ {
+ free((void *)i->my_settings.pcb.value);
+ }
+ if (i->my_settings.user.value)
+ {
+ free((void *)i->my_settings.user.value);
+ }
+ if (i->my_settings.password.value)
+ {
+ free((void *)i->my_settings.password.value);
+ }
+ /* new */
+ if (i->my_settings.Domain.value)
+ {
+ free((void *)i->my_settings.Domain.value);
+ }
+ if (i->my_settings.PasswordHash.value)
+ {
+ free((void *)i->my_settings.PasswordHash.value);
+ }
+ if (i->my_settings.AcceptedCert.value)
+ {
+ free((void *)i->my_settings.AcceptedCert.value);
+ }
+ if (i->my_settings.ClientHostname.value)
+ {
+ free((void *)i->my_settings.ClientHostname.value);
+ }
+ if (i->my_settings.ClientProductId.value)
+ {
+ free((void *)i->my_settings.ClientProductId.value);
+ }
+ if (i->my_settings.AlternateShell.value)
+ {
+ free((void *)i->my_settings.AlternateShell.value);
+ }
+ if (i->my_settings.ShellWorkingDirectory.value)
+ {
+ free((void *)i->my_settings.ShellWorkingDirectory.value);
+ }
+ if (i->my_settings.ClientAddress.value)
+ {
+ free((void *)i->my_settings.ClientAddress.value);
+ }
+ if (i->my_settings.ClientDir.value)
+ {
+ free((void *)i->my_settings.ClientDir.value);
+ }
+ if (i->my_settings.DynamicDSTTimeZoneKeyName.value)
+ {
+ free((void *)i->my_settings.DynamicDSTTimeZoneKeyName.value);
+ }
+ if (i->my_settings.RemoteAssistanceSessionId.value)
+ {
+ free((void *)i->my_settings.RemoteAssistanceSessionId.value);
+ }
+ if (i->my_settings.RemoteAssistancePassStub.value)
+ {
+ free((void *)i->my_settings.RemoteAssistancePassStub.value);
+ }
+ if (i->my_settings.RemoteAssistancePassword.value)
+ {
+ free((void *)i->my_settings.RemoteAssistancePassword.value);
+ }
+ if (i->my_settings.RemoteAssistanceRCTicket.value)
+ {
+ free((void *)i->my_settings.RemoteAssistanceRCTicket.value);
+ }
+ if (i->my_settings.AuthenticationServiceClass.value)
+ {
+ free((void *)i->my_settings.AuthenticationServiceClass.value);
+ }
+ if (i->my_settings.AllowedTlsCiphers.value)
+ {
+ free((void *)i->my_settings.AllowedTlsCiphers.value);
+ }
+ if (i->my_settings.NtlmSamFile.value)
+ {
+ free((void *)i->my_settings.NtlmSamFile.value);
+ }
+ if (i->my_settings.PreconnectionBlob.value)
+ {
+ free((void *)i->my_settings.PreconnectionBlob.value);
+ }
+ if (i->my_settings.RedirectionAcceptedCert.value)
+ {
+ free((void *)i->my_settings.RedirectionAcceptedCert.value);
+ }
+ if (i->my_settings.KerberosKdc.value)
+ {
+ free((void *)i->my_settings.KerberosKdc.value);
+ }
+ if (i->my_settings.KerberosRealm.value)
+ {
+ free((void *)i->my_settings.KerberosRealm.value);
+ }
+ if (i->my_settings.CertificateName.value)
+ {
+ free((void *)i->my_settings.CertificateName.value);
+ }
+ if (i->my_settings.CertificateFile.value)
+ {
+ free((void *)i->my_settings.CertificateFile.value);
+ }
+ if (i->my_settings.PrivateKeyFile.value)
+ {
+ free((void *)i->my_settings.PrivateKeyFile.value);
+ }
+ if (i->my_settings.CertificateContent.value)
+ {
+ free((void *)i->my_settings.CertificateContent.value);
+ }
+ if (i->my_settings.PrivateKeyContent.value)
+ {
+ free((void *)i->my_settings.PrivateKeyContent.value);
+ }
+ if (i->my_settings.WindowTitle.value)
+ {
+ free((void *)i->my_settings.WindowTitle.value);
+ }
+ if (i->my_settings.WmClass.value)
+ {
+ free((void *)i->my_settings.WmClass.value);
+ }
+ if (i->my_settings.ComputerName.value)
+ {
+ free((void *)i->my_settings.ComputerName.value);
+ }
+ if (i->my_settings.ConnectionFile.value)
+ {
+ free((void *)i->my_settings.ConnectionFile.value);
+ }
+ if (i->my_settings.AssistanceFile.value)
+ {
+ free((void *)i->my_settings.AssistanceFile.value);
+ }
+ if (i->my_settings.HomePath.value)
+ {
+ free((void *)i->my_settings.HomePath.value);
+ }
+ if (i->my_settings.ConfigPath.value)
+ {
+ free((void *)i->my_settings.ConfigPath.value);
+ }
+ if (i->my_settings.CurrentPath.value)
+ {
+ free((void *)i->my_settings.CurrentPath.value);
+ }
+ if (i->my_settings.DumpRemoteFxFile.value)
+ {
+ free((void *)i->my_settings.DumpRemoteFxFile.value);
+ }
+ if (i->my_settings.PlayRemoteFxFile.value)
+ {
+ free((void *)i->my_settings.PlayRemoteFxFile.value);
+ }
+ if (i->my_settings.GatewayHostname.value)
+ {
+ free((void *)i->my_settings.GatewayHostname.value);
+ }
+ if (i->my_settings.GatewayUsername.value)
+ {
+ free((void *)i->my_settings.GatewayUsername.value);
+ }
+ if (i->my_settings.GatewayPassword.value)
+ {
+ free((void *)i->my_settings.GatewayPassword.value);
+ }
+ if (i->my_settings.GatewayDomain.value)
+ {
+ free((void *)i->my_settings.GatewayDomain.value);
+ }
+ if (i->my_settings.GatewayAccessToken.value)
+ {
+ free((void *)i->my_settings.GatewayAccessToken.value);
+ }
+ if (i->my_settings.GatewayAcceptedCert.value)
+ {
+ free((void *)i->my_settings.GatewayAcceptedCert.value);
+ }
+ if (i->my_settings.ProxyHostname.value)
+ {
+ free((void *)i->my_settings.ProxyHostname.value);
+ }
+ if (i->my_settings.RemoteApplicationName.value)
+ {
+ free((void *)i->my_settings.RemoteApplicationName.value);
+ }
+ if (i->my_settings.RemoteApplicationIcon.value)
+ {
+ free((void *)i->my_settings.RemoteApplicationIcon.value);
+ }
+ if (i->my_settings.RemoteApplicationProgram.value)
+ {
+ free((void *)i->my_settings.RemoteApplicationProgram.value);
+ }
+ if (i->my_settings.RemoteApplicationFile.value)
+ {
+ free((void *)i->my_settings.RemoteApplicationFile.value);
+ }
+ if (i->my_settings.RemoteApplicationGuid.value)
+ {
+ free((void *)i->my_settings.RemoteApplicationGuid.value);
+ }
+ if (i->my_settings.RemoteApplicationCmdLine.value)
+ {
+ free((void *)i->my_settings.RemoteApplicationCmdLine.value);
+ }
+ if (i->my_settings.ImeFileName.value)
+ {
+ free((void *)i->my_settings.ImeFileName.value);
+ }
+ if (i->my_settings.DrivesToRedirect.value)
+ {
+ free((void *)i->my_settings.DrivesToRedirect.value);
+ }
+ if (i->my_settings.ActionScript.value)
+ {
+ free((void *)i->my_settings.ActionScript.value);
+ }
+}
+
+bool
+rdp_init_settings(void *internals)
+{
+ rdp_internals *i = internals;
+ rdpSettings *s = i->instance->context->settings;
+
+ /* via are not server here ) */
+ s->ServerMode = FALSE;
+
+ s->TlsSecurity = !(i->my_settings.notls.value);
+ if (i->my_settings.host.value && i->my_settings.host.value[0])
+ {
+ s->ServerHostname = strdup((char *)i->my_settings.host.value);
+ }
+ else
+ {
+ const char *msg = "rdp_module: server hostname does not set.";
+ i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ return FALSE;
+ }
+ if (i->my_settings.port.name && i->my_settings.port.name[0] == '1')
+ {
+ s->ServerPort = i->my_settings.port.value;
+ }
+ if (i->my_settings.user.value && i->my_settings.user.value[0])
+ {
+ s->Username = strdup((char *)i->my_settings.user.value);
+ }
+ if (i->my_settings.password.value && i->my_settings.password.value[0])
+ {
+ s->Password = strdup((char *)i->my_settings.password.value);
+ }
+ if (!i->my_settings.user.value && !i->my_settings.password.value)
+ {
+ /* TODO: disable authentication */
+ }
+ if (i->my_settings.nowallp.name
+ && i->my_settings.nowallp.name[0] == '1')
+ {
+ s->DisableWallpaper = i->my_settings.nowallp.value;
+ s->PerformanceFlags |= PERF_DISABLE_WALLPAPER;
+ }
+ if (i->my_settings.nowdrag.name
+ && i->my_settings.nowdrag.name[0] == '1')
+ {
+ s->DisableFullWindowDrag = i->my_settings.nowdrag.value;
+ s->PerformanceFlags |= PERF_DISABLE_FULLWINDOWDRAG;
+ }
+ if (i->my_settings.nomani.name && i->my_settings.nomani.name[0] == '1')
+ {
+ s->DisableMenuAnims = i->my_settings.nomani.value;
+ s->PerformanceFlags |= PERF_DISABLE_MENUANIMATIONS;
+ }
+ if (i->my_settings.notheme.name
+ && i->my_settings.notheme.name[0] == '1')
+ {
+ s->DisableThemes = i->my_settings.notheme.value;
+ s->PerformanceFlags |= PERF_DISABLE_THEMING;
+ }
+ if (i->my_settings.nonla.name && i->my_settings.nonla.name[0] == '1')
+ {
+ s->NlaSecurity = !(i->my_settings.nonla.value);
+ }
+ if (i->my_settings.width.name && i->my_settings.width.name[0] == '1')
+ {
+ s->DesktopWidth = i->my_settings.width.value;
+ }
+ if (i->my_settings.height.name && i->my_settings.height.name[0] == '1')
+ {
+ s->DesktopHeight = i->my_settings.height.value;
+ }
+ if (i->my_settings.pcb.value && i->my_settings.pcb.value[0])
+ {
+ s->SendPreconnectionPdu = TRUE;
+ s->PreconnectionBlob = strdup(i->my_settings.pcb.value);
+ }
+
+ s->IgnoreCertificate = TRUE;
+
+ if (i->my_settings.perf.name && i->my_settings.perf.name[0] == '1')
+ {
+ switch (i->my_settings.perf.value)
+ {
+ case 0:
+ //LAN
+ s->PerformanceFlags = PERF_FLAG_NONE;
+ s->ConnectionType = CONNECTION_TYPE_LAN;
+ s->AllowFontSmoothing = TRUE;
+ break;
+ case 1:
+ //Broadband
+ s->PerformanceFlags = PERF_DISABLE_WALLPAPER;
+ s->ConnectionType
+ = CONNECTION_TYPE_BROADBAND_HIGH;
+ break;
+ case 2:
+ //Modem
+ s->PerformanceFlags
+ = PERF_DISABLE_WALLPAPER
+ | PERF_DISABLE_FULLWINDOWDRAG
+ | PERF_DISABLE_MENUANIMATIONS
+ | PERF_DISABLE_THEMING;
+ s->ConnectionType = CONNECTION_TYPE_MODEM;
+ break;
+ }
+ }
+
+ /* new settings from freerdp header (strings) begin */
+
+ if (i->my_settings.Domain.value)
+ {
+ s->Domain = strdup(i->my_settings.Domain.value);
+ }
+ if (i->my_settings.PasswordHash.value)
+ {
+ s->PasswordHash = strdup(i->my_settings.PasswordHash.value);
+ }
+ if (i->my_settings.AcceptedCert.value)
+ {
+ s->AcceptedCert = strdup(i->my_settings.AcceptedCert.value);
+ }
+ if (i->my_settings.ClientHostname.value)
+ {
+ s->ClientHostname = strdup(i->my_settings.ClientHostname.value);
+ }
+ if (i->my_settings.ClientProductId.value)
+ {
+ s->ClientProductId
+ = strdup(i->my_settings.ClientProductId.value);
+ }
+ if (i->my_settings.AlternateShell.value)
+ {
+ s->AlternateShell = strdup(i->my_settings.AlternateShell.value);
+ }
+ if (i->my_settings.ShellWorkingDirectory.value)
+ {
+ s->ShellWorkingDirectory
+ = strdup(i->my_settings.ShellWorkingDirectory.value);
+ }
+ if (i->my_settings.ClientAddress.value)
+ {
+ s->ClientAddress = strdup(i->my_settings.ClientAddress.value);
+ }
+ if (i->my_settings.ClientDir.value)
+ {
+ s->ClientDir = strdup(i->my_settings.ClientDir.value);
+ }
+ if (i->my_settings.DynamicDSTTimeZoneKeyName.value)
+ {
+ s->DynamicDSTTimeZoneKeyName
+ = strdup(i->my_settings.DynamicDSTTimeZoneKeyName.value);
+ }
+ if (i->my_settings.RemoteAssistanceSessionId.value)
+ {
+ s->RemoteAssistanceSessionId
+ = strdup(i->my_settings.RemoteAssistanceSessionId.value);
+ }
+ if (i->my_settings.RemoteAssistancePassStub.value)
+ {
+ s->RemoteAssistancePassStub
+ = strdup(i->my_settings.RemoteAssistancePassStub.value);
+ }
+ if (i->my_settings.RemoteAssistancePassword.value)
+ {
+ s->RemoteAssistancePassword
+ = strdup(i->my_settings.RemoteAssistancePassword.value);
+ }
+ if (i->my_settings.RemoteAssistanceRCTicket.value)
+ {
+ s->RemoteAssistanceRCTicket
+ = strdup(i->my_settings.RemoteAssistanceRCTicket.value);
+ }
+ if (i->my_settings.AuthenticationServiceClass.value)
+ {
+ s->AuthenticationServiceClass
+ = strdup(i->my_settings.AuthenticationServiceClass.value);
+ }
+ if (i->my_settings.AllowedTlsCiphers.value)
+ {
+ s->AllowedTlsCiphers
+ = strdup(i->my_settings.AllowedTlsCiphers.value);
+ }
+ if (i->my_settings.NtlmSamFile.value)
+ {
+ s->NtlmSamFile = strdup(i->my_settings.NtlmSamFile.value);
+ }
+ if (i->my_settings.PreconnectionBlob.value)
+ {
+ s->PreconnectionBlob
+ = strdup(i->my_settings.PreconnectionBlob.value);
+ }
+ if (i->my_settings.RedirectionAcceptedCert.value)
+ {
+ s->RedirectionAcceptedCert
+ = strdup(i->my_settings.RedirectionAcceptedCert.value);
+ }
+ if (i->my_settings.KerberosKdc.value)
+ {
+ s->KerberosKdc = strdup(i->my_settings.KerberosKdc.value);
+ }
+ if (i->my_settings.KerberosRealm.value)
+ {
+ s->KerberosRealm = strdup(i->my_settings.KerberosRealm.value);
+ }
+ if (i->my_settings.CertificateName.value)
+ {
+ s->CertificateName
+ = strdup(i->my_settings.CertificateName.value);
+ }
+ if (i->my_settings.CertificateFile.value)
+ {
+ s->CertificateFile
+ = strdup(i->my_settings.CertificateFile.value);
+ }
+ if (i->my_settings.PrivateKeyFile.value)
+ {
+ s->PrivateKeyFile = strdup(i->my_settings.PrivateKeyFile.value);
+ }
+ if (i->my_settings.CertificateContent.value)
+ {
+ s->CertificateContent
+ = strdup(i->my_settings.CertificateContent.value);
+ }
+ if (i->my_settings.PrivateKeyContent.value)
+ {
+ s->PrivateKeyContent
+ = strdup(i->my_settings.PrivateKeyContent.value);
+ }
+ if (i->my_settings.WindowTitle.value)
+ {
+ s->WindowTitle = strdup(i->my_settings.WindowTitle.value);
+ }
+ if (i->my_settings.WmClass.value)
+ {
+ s->WmClass = strdup(i->my_settings.WmClass.value);
+ }
+ if (i->my_settings.ComputerName.value)
+ {
+ s->ComputerName = strdup(i->my_settings.ComputerName.value);
+ }
+ if (i->my_settings.ConnectionFile.value)
+ {
+ s->ConnectionFile = strdup(i->my_settings.ConnectionFile.value);
+ }
+ if (i->my_settings.AssistanceFile.value)
+ {
+ s->AssistanceFile = strdup(i->my_settings.AssistanceFile.value);
+ }
+ if (i->my_settings.HomePath.value)
+ {
+ s->HomePath = strdup(i->my_settings.HomePath.value);
+ }
+ if (i->my_settings.ConfigPath.value)
+ {
+ s->ConfigPath = strdup(i->my_settings.ConfigPath.value);
+ }
+ if (i->my_settings.CurrentPath.value)
+ {
+ s->CurrentPath = strdup(i->my_settings.CurrentPath.value);
+ }
+ if (i->my_settings.DumpRemoteFxFile.value)
+ {
+ s->DumpRemoteFxFile
+ = strdup(i->my_settings.DumpRemoteFxFile.value);
+ }
+ if (i->my_settings.PlayRemoteFxFile.value)
+ {
+ s->PlayRemoteFxFile
+ = strdup(i->my_settings.PlayRemoteFxFile.value);
+ }
+ if (i->my_settings.GatewayHostname.value)
+ {
+ s->GatewayHostname
+ = strdup(i->my_settings.GatewayHostname.value);
+ }
+ if (i->my_settings.GatewayUsername.value)
+ {
+ s->GatewayUsername
+ = strdup(i->my_settings.GatewayUsername.value);
+ }
+ if (i->my_settings.GatewayPassword.value)
+ {
+ s->GatewayPassword
+ = strdup(i->my_settings.GatewayPassword.value);
+ }
+ if (i->my_settings.GatewayDomain.value)
+ {
+ s->GatewayDomain = strdup(i->my_settings.GatewayDomain.value);
+ }
+ if (i->my_settings.GatewayAccessToken.value)
+ {
+ s->GatewayAccessToken
+ = strdup(i->my_settings.GatewayAccessToken.value);
+ }
+ if (i->my_settings.GatewayAcceptedCert.value)
+ {
+ s->GatewayAcceptedCert
+ = strdup(i->my_settings.GatewayAcceptedCert.value);
+ }
+ if (i->my_settings.ProxyHostname.value)
+ {
+ s->ProxyHostname = strdup(i->my_settings.ProxyHostname.value);
+ }
+ if (i->my_settings.RemoteApplicationName.value)
+ {
+ s->RemoteApplicationName
+ = strdup(i->my_settings.RemoteApplicationName.value);
+ }
+ if (i->my_settings.RemoteApplicationIcon.value)
+ {
+ s->RemoteApplicationIcon
+ = strdup(i->my_settings.RemoteApplicationIcon.value);
+ }
+ if (i->my_settings.RemoteApplicationProgram.value)
+ {
+ s->RemoteApplicationProgram
+ = strdup(i->my_settings.RemoteApplicationProgram.value);
+ }
+ if (i->my_settings.RemoteApplicationFile.value)
+ {
+ s->RemoteApplicationFile
+ = strdup(i->my_settings.RemoteApplicationFile.value);
+ }
+ if (i->my_settings.RemoteApplicationGuid.value)
+ {
+ s->RemoteApplicationGuid
+ = strdup(i->my_settings.RemoteApplicationGuid.value);
+ }
+ if (i->my_settings.RemoteApplicationCmdLine.value)
+ {
+ s->RemoteApplicationCmdLine
+ = strdup(i->my_settings.RemoteApplicationCmdLine.value);
+ }
+ if (i->my_settings.ImeFileName.value)
+ {
+ s->ImeFileName = strdup(i->my_settings.ImeFileName.value);
+ }
+ if (i->my_settings.DrivesToRedirect.value)
+ {
+ s->DrivesToRedirect
+ = strdup(i->my_settings.DrivesToRedirect.value);
+ }
+ if (i->my_settings.ActionScript.value)
+ {
+ s->ActionScript = strdup(i->my_settings.ActionScript.value);
+ }
+
+ /* new settings from freerdp header (strings) end */
+
+ /* new settings from freerdp header (integers) begin */
+
+ if (i->my_settings.ServerMode.name
+ && i->my_settings.ServerMode.name[0] == '1')
+ {
+ s->ServerMode = i->my_settings.ServerMode.value;
+ }
+ if (i->my_settings.WaitForOutputBufferFlush.name
+ && i->my_settings.WaitForOutputBufferFlush.name[0] == '1')
+ {
+ s->WaitForOutputBufferFlush
+ = i->my_settings.WaitForOutputBufferFlush.value;
+ }
+ if (i->my_settings.MaxTimeInCheckLoop.name
+ && i->my_settings.MaxTimeInCheckLoop.name[0] == '1')
+ {
+ s->MaxTimeInCheckLoop = i->my_settings.MaxTimeInCheckLoop.value;
+ }
+ if (i->my_settings.DesktopWidth.name
+ && i->my_settings.DesktopWidth.name[0] == '1')
+ {
+ s->DesktopWidth = i->my_settings.DesktopWidth.value;
+ }
+ if (i->my_settings.Workarea.name
+ && i->my_settings.Workarea.name[0] == '1')
+ {
+ s->Workarea = i->my_settings.Workarea.value;
+ }
+ if (i->my_settings.Fullscreen.name
+ && i->my_settings.Fullscreen.name[0] == '1')
+ {
+ s->Fullscreen = i->my_settings.Fullscreen.value;
+ }
+ if (i->my_settings.GrabKeyboard.name
+ && i->my_settings.GrabKeyboard.name[0] == '1')
+ {
+ s->GrabKeyboard = i->my_settings.GrabKeyboard.value;
+ }
+ if (i->my_settings.Decorations.name
+ && i->my_settings.Decorations.name[0] == '1')
+ {
+ s->Decorations = i->my_settings.Decorations.value;
+ }
+ if (i->my_settings.RdpVersion.name
+ && i->my_settings.RdpVersion.name[0] == '1')
+ {
+ s->RdpVersion = i->my_settings.RdpVersion.value;
+ }
+ if (i->my_settings.ColorDepth.name
+ && i->my_settings.ColorDepth.name[0] == '1')
+ {
+ s->ColorDepth = i->my_settings.ColorDepth.value;
+ }
+ if (i->my_settings.ExtSecurity.name
+ && i->my_settings.ExtSecurity.name[0] == '1')
+ {
+ s->ExtSecurity = i->my_settings.ExtSecurity.value;
+ }
+ if (i->my_settings.NlaSecurity.name
+ && i->my_settings.NlaSecurity.name[0] == '1')
+ {
+ s->NlaSecurity = i->my_settings.NlaSecurity.value;
+ }
+ if (i->my_settings.TlsSecurity.name
+ && i->my_settings.TlsSecurity.name[0] == '1')
+ {
+ s->TlsSecurity = i->my_settings.TlsSecurity.value;
+ }
+ if (i->my_settings.RdpSecurity.name
+ && i->my_settings.RdpSecurity.name[0] == '1')
+ {
+ s->RdpSecurity = i->my_settings.RdpSecurity.value;
+ }
+ if (i->my_settings.NegotiateSecurityLayer.name
+ && i->my_settings.NegotiateSecurityLayer.name[0] == '1')
+ {
+ s->NegotiateSecurityLayer
+ = i->my_settings.NegotiateSecurityLayer.value;
+ }
+ if (i->my_settings.RestrictedAdminModeRequired.name
+ && i->my_settings.RestrictedAdminModeRequired.name[0] == '1')
+ {
+ s->RestrictedAdminModeRequired
+ = i->my_settings.RestrictedAdminModeRequired.value;
+ }
+ if (i->my_settings.MstscCookieMode.name
+ && i->my_settings.MstscCookieMode.name[0] == '1')
+ {
+ s->MstscCookieMode = i->my_settings.MstscCookieMode.value;
+ }
+ if (i->my_settings.CookieMaxLength.name
+ && i->my_settings.CookieMaxLength.name[0] == '1')
+ {
+ s->CookieMaxLength = i->my_settings.CookieMaxLength.value;
+ }
+ if (i->my_settings.ClientBuild.name
+ && i->my_settings.ClientBuild.name[0] == '1')
+ {
+ s->ClientBuild = i->my_settings.ClientBuild.value;
+ }
+ if (i->my_settings.KeyboardType.name
+ && i->my_settings.KeyboardType.name[0] == '1')
+ {
+ s->KeyboardType = i->my_settings.KeyboardType.value;
+ }
+ if (i->my_settings.KeyboardSubType.name
+ && i->my_settings.KeyboardSubType.name[0] == '1')
+ {
+ s->KeyboardSubType = i->my_settings.KeyboardSubType.value;
+ }
+ if (i->my_settings.KeyboardFunctionKey.name
+ && i->my_settings.KeyboardFunctionKey.name[0] == '1')
+ {
+ s->KeyboardFunctionKey
+ = i->my_settings.KeyboardFunctionKey.value;
+ }
+ if (i->my_settings.KeyboardLayout.name
+ && i->my_settings.KeyboardLayout.name[0] == '1')
+ {
+ s->KeyboardLayout = i->my_settings.KeyboardLayout.value;
+ }
+ if (i->my_settings.UseRdpSecurityLayer.name
+ && i->my_settings.UseRdpSecurityLayer.name[0] == '1')
+ {
+ s->UseRdpSecurityLayer
+ = i->my_settings.UseRdpSecurityLayer.value;
+ }
+ if (i->my_settings.SaltedChecksum.name
+ && i->my_settings.SaltedChecksum.name[0] == '1')
+ {
+ s->SaltedChecksum = i->my_settings.SaltedChecksum.value;
+ }
+ if (i->my_settings.ServerPort.name
+ && i->my_settings.ServerPort.name[0] == '1')
+ {
+ s->ServerPort = i->my_settings.ServerPort.value;
+ }
+ if (i->my_settings.GatewayPort.name
+ && i->my_settings.GatewayPort.name[0] == '1')
+ {
+ s->GatewayPort = i->my_settings.GatewayPort.value;
+ }
+ if (i->my_settings.DesktopResize.name
+ && i->my_settings.DesktopResize.name[0] == '1')
+ {
+ s->DesktopResize = i->my_settings.DesktopResize.value;
+ }
+ if (i->my_settings.ToggleFullscreen.name
+ && i->my_settings.ToggleFullscreen.name[0] == '1')
+ {
+ s->ToggleFullscreen = i->my_settings.ToggleFullscreen.value;
+ }
+ if (i->my_settings.Floatbar.name
+ && i->my_settings.Floatbar.name[0] == '1')
+ {
+ s->Floatbar = i->my_settings.Floatbar.value;
+ }
+ if (i->my_settings.DesktopPosX.name
+ && i->my_settings.DesktopPosX.name[0] == '1')
+ {
+ s->DesktopPosX = i->my_settings.DesktopPosX.value;
+ }
+ if (i->my_settings.DesktopPosY.name
+ && i->my_settings.DesktopPosY.name[0] == '1')
+ {
+ s->DesktopPosY = i->my_settings.DesktopPosY.value;
+ }
+ if (i->my_settings.SoftwareGdi.name
+ && i->my_settings.SoftwareGdi.name[0] == '1')
+ {
+ s->SoftwareGdi = i->my_settings.SoftwareGdi.value;
+ }
+ if (i->my_settings.UnmapButtons.name
+ && i->my_settings.UnmapButtons.name[0] == '1')
+ {
+ s->UnmapButtons = i->my_settings.UnmapButtons.value;
+ }
+ if (i->my_settings.PerformanceFlags.name
+ && i->my_settings.PerformanceFlags.name[0] == '1')
+ {
+ s->PerformanceFlags = i->my_settings.PerformanceFlags.value;
+ }
+ if (i->my_settings.AllowFontSmoothing.name
+ && i->my_settings.AllowFontSmoothing.name[0] == '1')
+ {
+ s->AllowFontSmoothing = i->my_settings.AllowFontSmoothing.value;
+ }
+ if (i->my_settings.AllowDesktopComposition.name
+ && i->my_settings.AllowDesktopComposition.name[0] == '1')
+ {
+ s->AllowDesktopComposition
+ = i->my_settings.AllowDesktopComposition.value;
+ }
+ if (i->my_settings.DisableWallpaper.name
+ && i->my_settings.DisableWallpaper.name[0] == '1')
+ {
+ s->DisableWallpaper = i->my_settings.DisableWallpaper.value;
+ }
+ if (i->my_settings.DisableFullWindowDrag.name
+ && i->my_settings.DisableFullWindowDrag.name[0] == '1')
+ {
+ s->DisableFullWindowDrag
+ = i->my_settings.DisableFullWindowDrag.value;
+ }
+ if (i->my_settings.DisableMenuAnims.name
+ && i->my_settings.DisableMenuAnims.name[0] == '1')
+ {
+ s->DisableMenuAnims = i->my_settings.DisableMenuAnims.value;
+ }
+ if (i->my_settings.DisableThemes.name
+ && i->my_settings.DisableThemes.name[0] == '1')
+ {
+ s->DisableThemes = i->my_settings.DisableThemes.value;
+ }
+ if (i->my_settings.ConnectionType.name
+ && i->my_settings.ConnectionType.name[0] == '1')
+ {
+ s->ConnectionType = i->my_settings.ConnectionType.value;
+ }
+ if (i->my_settings.EncryptionMethods.name
+ && i->my_settings.EncryptionMethods.name[0] == '1')
+ {
+ s->EncryptionMethods = i->my_settings.EncryptionMethods.value;
+ }
+ if (i->my_settings.EncryptionLevel.name
+ && i->my_settings.EncryptionLevel.name[0] == '1')
+ {
+ s->EncryptionLevel = i->my_settings.EncryptionLevel.value;
+ }
+ if (i->my_settings.FIPSMode.name
+ && i->my_settings.FIPSMode.name[0] == '1')
+ {
+ s->FIPSMode = i->my_settings.FIPSMode.value;
+ }
+ if (i->my_settings.CompressionEnabled.name
+ && i->my_settings.CompressionEnabled.name[0] == '1')
+ {
+ s->CompressionEnabled = i->my_settings.CompressionEnabled.value;
+ }
+ if (i->my_settings.LogonNotify.name
+ && i->my_settings.LogonNotify.name[0] == '1')
+ {
+ s->LogonNotify = i->my_settings.LogonNotify.value;
+ }
+ if (i->my_settings.BrushSupportLevel.name
+ && i->my_settings.BrushSupportLevel.name[0] == '1')
+ {
+ s->BrushSupportLevel = i->my_settings.BrushSupportLevel.value;
+ }
+ if (i->my_settings.RemoteApplicationMode.name
+ && i->my_settings.RemoteApplicationMode.name[0] == '1')
+ {
+ s->RemoteApplicationMode
+ = i->my_settings.RemoteApplicationMode.value;
+ }
+ if (i->my_settings.TcpAckTimeout.name
+ && i->my_settings.TcpAckTimeout.name[0] == '1')
+ {
+ s->TcpAckTimeout = i->my_settings.TcpAckTimeout.value;
+ }
+ /* new settings from freerdp header (integers) end */
+
+ /* TODO: handle rest of options:
+ * fntlm
+ */
+
+ return true;
+}
diff --git a/src/rdp/rdp_settings.h b/src/rdp/rdp_settings.h
new file mode 100644
index 0000000..3fca325
--- /dev/null
+++ b/src/rdp/rdp_settings.h
@@ -0,0 +1,15 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+bool rdp_set_setting_str(backend_setting_str *setting, void *internals);
+
+bool rdp_set_setting_int(backend_setting_int *setting, void *internals);
+
+void rdp_settings_free(rdp_internals *internals);
+
+bool rdp_init_settings(void *internals);
diff --git a/src/rdp/rdp_user_input.c b/src/rdp/rdp_user_input.c
new file mode 100644
index 0000000..c79e5b1
--- /dev/null
+++ b/src/rdp/rdp_user_input.c
@@ -0,0 +1,458 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "webrdp_core_api.h"
+#include "webrdp_module_api.h"
+
+#include "rdp_backend_api.h"
+#include "rdp_impl.h"
+
+const UINT32 ASCII_TO_SCANCODE[256] = {
+ 0, /* 0 */
+ 0, /* 1 */
+ 0, /* 2 */
+ 0, /* 3 */
+ 0, /* 4 */
+ 0, /* 5 */
+ 0, /* 6 */
+ 0, /* 7 */
+ RDP_SCANCODE_BACKSPACE, /* 8 */
+ RDP_SCANCODE_TAB, /* 9 */
+ VK_KEY_D, /* 10 */
+ VK_KEY_F, /* 11 */
+ VK_KEY_H, /* 12 */
+ RDP_SCANCODE_RETURN, /* 13 */
+ RDP_SCANCODE_BACKSPACE, /* 14 */
+ VK_KEY_X, /* 15 */
+ RDP_SCANCODE_LSHIFT, /* 16 */
+ RDP_SCANCODE_LCONTROL, /* 17 */
+ RDP_SCANCODE_LMENU, /* 18 */
+ RDP_SCANCODE_PAUSE, /* 19 */
+ RDP_SCANCODE_CAPSLOCK, /* 20 */
+ VK_KEY_W, /* 21 */
+ VK_KEY_E, /* 22 */
+ VK_KEY_R, /* 23 */
+ VK_KEY_Y, /* 24 */
+ VK_KEY_A, /* 25 */
+ VK_KEY_1, /* 26 */
+ RDP_SCANCODE_ESCAPE, /* 27 */
+ VK_KEY_3, /* 28 */
+ VK_KEY_4, /* 29 */
+ VK_KEY_6, /* 30 */
+ VK_KEY_5, /* 31 */
+ RDP_SCANCODE_SPACE, /* 32 */
+ RDP_SCANCODE_PRIOR, /* 33 */
+ RDP_SCANCODE_NEXT, /* 34 */
+ RDP_SCANCODE_END, /* 35 */
+ RDP_SCANCODE_HOME, /* 36 */
+ RDP_SCANCODE_LEFT, /* 37 */
+ RDP_SCANCODE_UP, /* 38 */
+ RDP_SCANCODE_RIGHT, /* 39 */
+ RDP_SCANCODE_DOWN, /* 40 */
+ RDP_SCANCODE_KEY_0, /* 41 */
+ RDP_SCANCODE_MULTIPLY, /* 42 */
+ RDP_SCANCODE_ADD, /* 43 */
+ RDP_SCANCODE_PRINTSCREEN, /* 44 */
+ RDP_SCANCODE_INSERT, /* 45 */
+ RDP_SCANCODE_DELETE, /* 46 */
+ RDP_SCANCODE_DIVIDE, /* 47 */
+ RDP_SCANCODE_KEY_0, /* 48 */
+ RDP_SCANCODE_KEY_1, /* 49 */
+ RDP_SCANCODE_KEY_2, /* 50 */
+ RDP_SCANCODE_KEY_3, /* 51 */
+ RDP_SCANCODE_KEY_4, /* 52 */
+ RDP_SCANCODE_KEY_5, /* 53 */
+ RDP_SCANCODE_KEY_6, /* 54 */
+ RDP_SCANCODE_KEY_7, /* 55 */
+ RDP_SCANCODE_KEY_8, /* 56 */
+ RDP_SCANCODE_KEY_9, /* 57 */
+ RDP_SCANCODE_OEM_1, /* 58 */
+ RDP_SCANCODE_OEM_1, /* 59 */
+ RDP_SCANCODE_OEM_COMMA, /* 60 */
+ RDP_SCANCODE_OEM_PLUS, /* 61 */
+ RDP_SCANCODE_OEM_PERIOD, /* 62 */
+ RDP_SCANCODE_DIVIDE, /* 63 */
+ RDP_SCANCODE_KEY_2, /* 64 */
+ RDP_SCANCODE_KEY_A, /* 65 */
+ RDP_SCANCODE_KEY_B, /* 66 */
+ RDP_SCANCODE_KEY_C, /* 67 */
+ RDP_SCANCODE_KEY_D, /* 68 */
+ RDP_SCANCODE_KEY_E, /* 69 */
+ RDP_SCANCODE_KEY_F, /* 70 */
+ RDP_SCANCODE_KEY_G, /* 71 */
+ RDP_SCANCODE_KEY_H, /* 72 */
+ RDP_SCANCODE_KEY_I, /* 73 */
+ RDP_SCANCODE_KEY_J, /* 74 */
+ RDP_SCANCODE_KEY_K, /* 75 */
+ RDP_SCANCODE_KEY_L, /* 76 */
+ RDP_SCANCODE_KEY_M, /* 77 */
+ RDP_SCANCODE_KEY_N, /* 78 */
+ RDP_SCANCODE_KEY_O, /* 79 */
+ RDP_SCANCODE_KEY_P, /* 80 */
+ RDP_SCANCODE_KEY_Q, /* 81 */
+ RDP_SCANCODE_KEY_R, /* 82 */
+ RDP_SCANCODE_KEY_S, /* 83 */
+ RDP_SCANCODE_KEY_T, /* 84 */
+ RDP_SCANCODE_KEY_U, /* 85 */
+ RDP_SCANCODE_KEY_V, /* 86 */
+ RDP_SCANCODE_KEY_W, /* 87 */
+ RDP_SCANCODE_KEY_X, /* 88 */
+ RDP_SCANCODE_KEY_Y, /* 89 */
+ RDP_SCANCODE_KEY_Z, /* 90 */
+ RDP_SCANCODE_LWIN, /* 91 */
+ RDP_SCANCODE_RWIN, /* 92 */
+ RDP_SCANCODE_APPS, /* 93 */
+ RDP_SCANCODE_KEY_6, /* 94 */
+ RDP_SCANCODE_OEM_MINUS, /* 95 */
+ RDP_SCANCODE_NUMPAD0, /* 96 */
+ RDP_SCANCODE_NUMPAD1, /* 97 */
+ RDP_SCANCODE_NUMPAD2, /* 98 */
+ RDP_SCANCODE_NUMPAD3, /* 99 */
+ RDP_SCANCODE_NUMPAD4, /* 100 */
+ RDP_SCANCODE_NUMPAD5, /* 101 */
+ RDP_SCANCODE_NUMPAD6, /* 102 */
+ RDP_SCANCODE_NUMPAD7, /* 103 */
+ RDP_SCANCODE_NUMPAD8, /* 104 */
+ RDP_SCANCODE_NUMPAD9, /* 105 */
+ RDP_SCANCODE_MULTIPLY, /* 106 */
+ RDP_SCANCODE_ADD, /* 107 */
+ 0, /* 108 */
+ RDP_SCANCODE_SUBTRACT, /* 109 */
+ RDP_SCANCODE_DELETE, /* 110 */
+ RDP_SCANCODE_DIVIDE, /* 111 */
+ RDP_SCANCODE_F1, /* 112 */
+ RDP_SCANCODE_F2, /* 113 */
+ RDP_SCANCODE_F3, /* 114 */
+ RDP_SCANCODE_F4, /* 115 */
+ RDP_SCANCODE_F5, /* 116 */
+ RDP_SCANCODE_F6, /* 117 */
+ RDP_SCANCODE_F7, /* 118 */
+ RDP_SCANCODE_F8, /* 119 */
+ RDP_SCANCODE_F9, /* 120 */
+ RDP_SCANCODE_F10, /* 121 */
+ RDP_SCANCODE_F11, /* 122 */
+ RDP_SCANCODE_F12, /* 123 */
+ RDP_SCANCODE_OEM_5, /* 124 */
+ RDP_SCANCODE_OEM_6, /* 125 */
+ VK_F4, /* 126 */
+ VK_END, /* 127 */
+ VK_F2, /* 128 */
+ VK_NEXT, /* 129 */
+ VK_F1, /* 130 */
+ VK_LEFT, /* 131 */
+ VK_RIGHT, /* 132 */
+ VK_DOWN, /* 133 */
+ VK_UP, /* 134 */
+ 0, /* 135 */
+ 0, /* 136 */
+ 0, /* 137 */
+ 0, /* 138 */
+ 0, /* 139 */
+ 0, /* 140 */
+ 0, /* 141 */
+ 0, /* 142 */
+ 0, /* 143 */
+ RDP_SCANCODE_NUMLOCK, /* 144 */
+ RDP_SCANCODE_SCROLLLOCK, /* 145 */
+ 0, /* 146 */
+ 0, /* 147 */
+ 0, /* 148 */
+ 0, /* 149 */
+ 0, /* 150 */
+ 0, /* 151 */
+ 0, /* 152 */
+ 0, /* 153 */
+ 0, /* 154 */
+ 0, /* 155 */
+ 0, /* 156 */
+ 0, /* 157 */
+ 0, /* 158 */
+ 0, /* 159 */
+ 0, /* 160 */
+ 0, /* 161 */
+ 0, /* 162 */
+ 0, /* 163 */
+ 0, /* 164 */
+ 0, /* 165 */
+ 0, /* 166 */
+ 0, /* 167 */
+ 0, /* 168 */
+ 0, /* 169 */
+ 0, /* 170 */
+ 0, /* 171 */
+ 0, /* 172 */
+ RDP_SCANCODE_OEM_MINUS, /* 173 */
+ 0, /* 174 */
+ 0, /* 175 */
+ 0, /* 176 */
+ 0, /* 177 */
+ 0, /* 178 */
+ 0, /* 179 */
+ 0, /* 180 */
+ 0, /* 181 */
+ 0, /* 182 */
+ 0, /* 183 */
+ 0, /* 184 */
+ 0, /* 185 */
+ RDP_SCANCODE_OEM_1, /* 186 */
+ RDP_SCANCODE_OEM_PLUS, /* 187 */
+ RDP_SCANCODE_OEM_COMMA, /* 188 */
+ RDP_SCANCODE_OEM_MINUS, /* 189 */
+ RDP_SCANCODE_OEM_PERIOD, /* 190 */
+ RDP_SCANCODE_OEM_2, /* 191 */
+ RDP_SCANCODE_OEM_3, /* 192 */
+ 0, /* 193 */
+ 0, /* 194 */
+ 0, /* 195 */
+ 0, /* 196 */
+ 0, /* 197 */
+ 0, /* 198 */
+ 0, /* 199 */
+ 0, /* 200 */
+ 0, /* 201 */
+ 0, /* 202 */
+ 0, /* 203 */
+ 0, /* 204 */
+ 0, /* 205 */
+ 0, /* 206 */
+ 0, /* 207 */
+ 0, /* 208 */
+ 0, /* 209 */
+ 0, /* 210 */
+ 0, /* 211 */
+ 0, /* 212 */
+ 0, /* 213 */
+ 0, /* 214 */
+ 0, /* 215 */
+ 0, /* 216 */
+ 0, /* 217 */
+ 0, /* 218 */
+ RDP_SCANCODE_OEM_4, /* 219 */
+ RDP_SCANCODE_OEM_5, /* 220 */
+ RDP_SCANCODE_OEM_6, /* 221 */
+ RDP_SCANCODE_OEM_7, /* 222 */
+ 0, /* 223 */
+ 0, /* 224 */
+ 0, /* 225 */
+ 0, /* 226 */
+ 0, /* 227 */
+ 0, /* 228 */
+ 0, /* 229 */
+ 0, /* 230 */
+ 0, /* 231 */
+ 0, /* 232 */
+ 0, /* 233 */
+ 0, /* 234 */
+ 0, /* 235 */
+ 0, /* 236 */
+ 0, /* 237 */
+ 0, /* 238 */
+ 0, /* 239 */
+ 0, /* 240 */
+ 0, /* 241 */
+ 0, /* 242 */
+ 0, /* 243 */
+ 0, /* 244 */
+ 0, /* 245 */
+ 0, /* 246 */
+ 0, /* 247 */
+ 0, /* 248 */
+ 0, /* 249 */
+ 0, /* 250 */
+ 0, /* 251 */
+ 0, /* 252 */
+ 0, /* 253 */
+ 0, /* 254 */
+ 0 /* 255 */
+};
+
+static bool
+rdp_backend_handle_input_mouse(ws_input_mouse input, void *internals)
+{
+ rdp_internals *_i = internals;
+ rdpInput *inp = _i->instance->context->input;
+ _i->core->api_core->reset_idle(_i->task_info);
+ inp->MouseEvent(inp, input.flags, input.x, input.y);
+ return true;
+}
+
+static bool
+rdp_backend_handle_input_kupdown(ws_input_kupdown input, void *internals)
+{
+ rdp_internals *_i = internals;
+ rdpInput *inp = _i->instance->context->input;
+ if (0 < input.code)
+ {
+ _i->core->api_core->reset_idle(_i->task_info);
+ /* make byte */
+ input.code = RDP_SCANCODE_CODE(input.code);
+ /* apply extended */
+ input.code = ASCII_TO_SCANCODE[input.code];
+ /* extract extended sepparatelly in tflag */
+ uint32_t tflag = RDP_SCANCODE_EXTENDED(input.code) ?
+ KBD_FLAGS_EXTENDED :
+ 0;
+ freerdp_input_send_keyboard_event(inp,
+ (input.down ? KBD_FLAGS_DOWN : KBD_FLAGS_RELEASE) | tflag,
+ input.code);
+ }
+ return true;
+}
+
+static bool
+rdp_backend_handle_input_kpress(ws_input_kpress input, void *internals)
+{
+ rdp_internals *_i = internals;
+ rdpInput *inp = _i->instance->context->input;
+ _i->core->api_core->reset_idle(_i->task_info);
+ if (0x20 < input.code)
+ {
+ if (input.shiftstate & 6)
+ {
+ //Control and or Alt: Must use scan-codes since
+ //unicode-event can't handle these
+ if (((64 < input.code) && (91 > input.code))
+ || ((96 < input.code) && (123 > input.code)))
+ {
+ input.code -= (input.shiftstate & 1) ? 0 : 32;
+ input.code
+ = GetVirtualScanCodeFromVirtualKeyCode(
+ input.code, 4);
+ if (0 < input.code)
+ {
+ freerdp_input_send_unicode_keyboard_event(
+ inp, KBD_FLAGS_DOWN,
+ ASCII_TO_SCANCODE[input.code]);
+ freerdp_input_send_unicode_keyboard_event(
+ inp, KBD_FLAGS_RELEASE,
+ ASCII_TO_SCANCODE[input.code]);
+ }
+ }
+ }
+ else
+ {
+ if (0 < input.code)
+ {
+ if (input.code == 96)
+ {
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_DOWN,
+ RDP_SCANCODE_LCONTROL);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_DOWN, RDP_SCANCODE_LMENU);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_DOWN,
+ (UINT8)RDP_SCANCODE_DELETE);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_RELEASE,
+ RDP_SCANCODE_LCONTROL);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_RELEASE,
+ RDP_SCANCODE_LMENU);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_RELEASE,
+ (UINT8)RDP_SCANCODE_DELETE);
+ }
+
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_DOWN,
+ ASCII_TO_SCANCODE[input.code]);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_RELEASE,
+ ASCII_TO_SCANCODE[input.code]);
+ }
+ }
+ }
+ else
+ {
+ if (0 < input.code)
+ {
+ input.code = RDP_SCANCODE_CODE(input.code);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, ASCII_TO_SCANCODE[input.code]);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_RELEASE, ASCII_TO_SCANCODE[input.code]);
+ }
+ }
+ return true;
+}
+
+static bool
+rdp_backend_handle_input_kcomb(ws_input_keycomb input, void *internals)
+{
+ rdp_internals *_i = internals;
+ rdpInput *inp = _i->instance->context->input;
+ _i->core->api_core->reset_idle(_i->task_info);
+ switch (input)
+ {
+ case ws_keycomb_ctrlaltdel_press:
+ {
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, RDP_SCANCODE_LCONTROL);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, RDP_SCANCODE_LMENU);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, (UINT8)RDP_SCANCODE_DELETE);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, RDP_SCANCODE_LCONTROL);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, RDP_SCANCODE_LMENU);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, (UINT8)RDP_SCANCODE_DELETE);
+ }
+ break;
+ case ws_keycomb_alttab_press:
+ {
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, RDP_SCANCODE_LMENU);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, RDP_SCANCODE_TAB);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, RDP_SCANCODE_TAB);
+ }
+ break;
+ case ws_keycomb_alttab_release:
+ {
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, RDP_SCANCODE_LMENU);
+ }
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool
+rdp_backend_handle_input_unicode(ws_input_unicode input, void *internals)
+{
+ rdp_internals *_i = internals;
+ rdpInput *inp = _i->instance->context->input;
+ int i;
+ _i->core->api_core->reset_idle(_i->task_info);
+ for (i = 0; i < input.length; ++i)
+ {
+ freerdp_input_send_unicode_keyboard_event(
+ inp, KBD_FLAGS_DOWN, (UINT16)input.input[i]);
+ freerdp_input_send_unicode_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, (UINT16)input.input[i]);
+ }
+ return true;
+}
+
+void
+register_input(wrdp_backend_module *backend)
+{
+ backend->callbacks_input->kcomb = rdp_backend_handle_input_kcomb;
+ backend->callbacks_input->kupdown = rdp_backend_handle_input_kupdown;
+ backend->callbacks_input->kpress = rdp_backend_handle_input_kpress;
+ backend->callbacks_input->mouse = rdp_backend_handle_input_mouse;
+ backend->callbacks_input->unicode = rdp_backend_handle_input_unicode;
+}
diff --git a/src/rdp/rdp_user_input.h b/src/rdp/rdp_user_input.h
new file mode 100644
index 0000000..c7851e4
--- /dev/null
+++ b/src/rdp/rdp_user_input.h
@@ -0,0 +1,10 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+void register_input(wrdp_backend_module *backend);
+void register_pointer(rdpPointer *p);
diff --git a/src/tools/keygen.c b/src/tools/keygen.c
new file mode 100644
index 0000000..8e12f15
--- /dev/null
+++ b/src/tools/keygen.c
@@ -0,0 +1,57 @@
+/*-
+ * Copyright (c) 2022 Alexandr Gluzskiy <sss@sss.chaoslab.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "../core/base64_url.h"
+
+const size_t key_size = 64;
+
+int
+main()
+{
+ uint32_t buf[key_size / 4];
+ char out_buf[key_size * 3];
+ size_t out_len = 0;
+ srand((unsigned int)buf);
+ //TODO: use srand
+ // size_t pos;
+ for (size_t pos = 0; pos < key_size / 4; ++pos)
+ {
+ buf[pos] = rand();
+ }
+ base64_url_encode(
+ (uint8_t *)buf, key_size, out_buf, key_size * 3, &out_len);
+ out_buf[out_len] = 0;
+
+ printf("%s\n", out_buf);
+ return 0;
+}
diff --git a/www/FreeRDP_Logo.png b/www/FreeRDP_Logo.png
new file mode 100644
index 0000000..3b2d02f
--- /dev/null
+++ b/www/FreeRDP_Logo.png
Binary files differ
diff --git a/www/c_default.png b/www/c_default.png
new file mode 100644
index 0000000..2699d92
--- /dev/null
+++ b/www/c_default.png
Binary files differ
diff --git a/www/c_none.png b/www/c_none.png
new file mode 100644
index 0000000..7dd2104
--- /dev/null
+++ b/www/c_none.png
Binary files differ
diff --git a/www/css/style-debug.css b/www/css/style-debug.css
new file mode 100644
index 0000000..4dd11a0
--- /dev/null
+++ b/www/css/style-debug.css
@@ -0,0 +1,537 @@
+/*
+ * HTML5 Boilerplate
+ *
+ * What follows is the result of much research on cross-browser styling.
+ * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal,
+ * Kroc Camen, and the H5BP dev community and team.
+ *
+ * Detailed information about this CSS: h5bp.com/css
+ *
+ * ==|== normalize ==========================================================
+ */
+
+
+/* =============================================================================
+ HTML5 display definitions
+ ========================================================================== */
+
+article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; }
+audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; }
+audio:not([controls]) { display: none; }
+[hidden] { display: none; }
+
+/* =============================================================================
+ Base
+ ========================================================================== */
+
+/*
+ * 1. Correct text resizing oddly in IE6/7 when body font-size is set using em units
+ * 2. Prevent iOS text size adjust on device orientation change, without disabling user zoom: h5bp.com/g
+ */
+
+html { font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
+
+html, button, input, select, textarea { font-family: sans-serif; color: #222; }
+
+body { margin: 0; font-size: 1em; line-height: 1.4;
+}
+
+/*
+ * Remove text-shadow in selection highlight: h5bp.com/i
+ * These selection declarations have to be separate
+ * Also: hot pink! (or customize the background color to match your design)
+ */
+
+::-moz-selection { background: #fe57a1; color: #fff; text-shadow: none; }
+::selection { background: #fe57a1; color: #fff; text-shadow: none; }
+
+
+/* =============================================================================
+ Links
+ ========================================================================== */
+
+a { color: #00e; }
+a:visited { color: #551a8b; }
+a:hover { color: #06e; }
+a:focus { outline: thin dotted; }
+
+/* Improve readability when focused and hovered in all browsers: h5bp.com/h */
+a:hover, a:active { outline: 0; }
+
+
+/* =============================================================================
+ Typography
+ ========================================================================== */
+
+abbr[title] { border-bottom: 1px dotted; }
+
+b, strong { font-weight: bold; }
+
+blockquote { margin: 1em 40px; }
+
+dfn { font-style: italic; }
+
+hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; }
+
+ins { background: #ff9; color: #000; text-decoration: none; }
+
+mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; }
+
+/* Redeclare monospace font family: h5bp.com/j */
+pre, code, kbd, samp { font-family: monospace, serif; _font-family: 'courier new', monospace; font-size: 1em; }
+
+/* Improve readability of pre-formatted text in all browsers */
+pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; }
+
+q { quotes: none; }
+q:before, q:after { content: ""; content: none; }
+
+small { font-size: 85%; }
+
+/* Position subscript and superscript content without affecting line-height: h5bp.com/k */
+sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
+sup { top: -0.5em; }
+sub { bottom: -0.25em; }
+
+
+/* =============================================================================
+ Lists
+ ========================================================================== */
+
+ul, ol { margin: 1em 0; padding: 0 0 0 40px; }
+dd { margin: 0 0 0 40px; }
+nav ul, nav ol { list-style: none; list-style-image: none; margin: 0; padding: 0; }
+
+
+/* =============================================================================
+ Embedded content
+ ========================================================================== */
+
+/*
+ * 1. Improve image quality when scaled in IE7: h5bp.com/d
+ * 2. Remove the gap between images and borders on image containers: h5bp.com/i/440
+ */
+
+img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; }
+
+/*
+ * Correct overflow not hidden in IE9
+ */
+
+svg:not(:root) { overflow: hidden; }
+
+
+/* =============================================================================
+ Figures
+ ========================================================================== */
+
+figure { margin: 0; }
+
+
+/* =============================================================================
+ Forms
+ ========================================================================== */
+
+form { margin: 0; }
+fieldset { border: 0; margin: 0; padding: 0; }
+
+/* Indicate that 'label' will shift focus to the associated form element */
+label { cursor: pointer; }
+
+/*
+ * 1. Correct color not inheriting in IE6/7/8/9
+ * 2. Correct alignment displayed oddly in IE6/7
+ */
+
+legend { border: 0; *margin-left: -7px; padding: 0; white-space: normal; }
+
+/*
+ * 1. Correct font-size not inheriting in all browsers
+ * 2. Remove margins in FF3/4 S5 Chrome
+ * 3. Define consistent vertical alignment display in all browsers
+ */
+
+button, input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; }
+
+/*
+ * 1. Define line-height as normal to match FF3/4 (set using !important in the UA stylesheet)
+ */
+
+button, input { line-height: normal; }
+
+/*
+ * 1. Display hand cursor for clickable form elements
+ * 2. Allow styling of clickable form elements in iOS
+ * 3. Correct inner spacing displayed oddly in IE7 (doesn't effect IE6)
+ */
+
+button, input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; *overflow: visible; }
+
+/*
+ * Re-set default cursor for disabled elements
+ */
+
+button[disabled], input[disabled] { cursor: default; }
+
+/*
+ * Consistent box sizing and appearance
+ */
+
+input[type="checkbox"], input[type="radio"] { box-sizing: border-box; padding: 0; *width: 13px; *height: 13px; }
+input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; }
+input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; }
+
+/*
+ * Remove inner padding and border in FF3/4: h5bp.com/l
+ */
+
+button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; }
+
+/*
+ * 1. Remove default vertical scrollbar in IE6/7/8/9
+ * 2. Allow only vertical resizing
+ */
+
+textarea { overflow: auto; vertical-align: top; resize: vertical; }
+
+/* Colors for form validity */
+input:valid, textarea:valid { }
+input:invalid, textarea:invalid { background-color: #f0dddd; }
+
+
+/* =============================================================================
+ Tables
+ ========================================================================== */
+
+table { border-collapse: collapse; border-spacing: 0;}
+td { vertical-align: top; }
+
+
+/* =============================================================================
+ Chrome Frame Prompt
+ ========================================================================== */
+
+.chromeframe { margin: 0.2em 0; background: #ccc; color: black; padding: 0.2em 0; }
+
+
+/* ==|== primary styles =====================================================
+ Author:
+ ========================================================================== */
+
+html {
+ width: 100%;
+ height: 100%;
+}
+body {
+ font-family: Verdana,Arial,Helvetica,sans-serif;
+ font-size: 10pt;
+ width: 100%;
+ height: 100%;
+ margin:0;
+
+}
+fieldset {
+ border: 1px solid #CCCCCC;
+ margin: 0 10px 10px;
+ padding: 5px;
+ text-align: left;
+}
+input:focus, select:focus, textarea:focus {
+ background-color: #FFFFDD;
+}
+input, select {
+ border: 1px solid silver;
+ font-size: 10pt;
+}
+legend {
+ font-family: 'Quicksand', sans-serif;
+ color: #3B445E;
+ font-size: 20px;
+ font-weight: normal;
+ letter-spacing: normal;
+}
+table td {
+ vertical-align: middle;
+ padding: 5px;
+}
+table td.buttons {
+ font: 12px/20px "Arial", Arial, Helvetica, sans-serif;
+ margin-top: 20px;
+ text-align: right;
+}
+table td.key {
+ font-family:Arial;
+ font-size:13px;
+ font-style:normal;
+ background-color: #F6F6F6;
+ border-bottom: 1px solid #E9E9E9;
+ border-right: 1px solid #E9E9E9;
+ color: #3B445E;
+ font-weight: bold;
+ text-align: right;
+ white-space: nowrap;
+ width: 230px;
+}
+
+.dialog {
+ background-color: white;
+}
+.tab-wrapper
+{
+ padding: 5px 5px;
+ margin: 0;
+ border: 1px solid #eee;
+ clear: both;
+}
+.tab-menu
+{
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ /* overflow: auto; */
+}
+.tab-menu li
+{
+ margin: 0;
+ padding: 0;
+ float: left;
+ line-height: 1.5em;
+}
+.tab-menu li a
+{
+ background-color: #DDD;
+ text-decoration: none;
+ padding: 5px;
+ font-weight: bold;
+ color: black;
+ border: 2px solid #fff;
+ margin-top: 2px;
+}
+.tab-menu li a:hover,
+.tab-menu li a:focus
+{
+ background-color: #eee;
+ border: 2px solid #000;
+ color: black;
+ /* your effect here */
+}
+.tab-menu li.tab-selected a
+{
+ background-color: #FFF;
+ text-decoration: none;
+ color: black;
+ /* your effect here */
+ border: 2px solid #eee;
+}
+.tab-menu li.tab-selected a:hover
+.tab-menu li.tab-selected a:focus
+{
+ border: 2px solid #eee;
+}
+
+.about {
+ border: 1px solid #000;
+ margin: 10px;
+ text-align: center;
+ font-size: 14pt;
+}
+
+.error {
+ color: #FFF;
+ background-color: #F00;
+ font-size: 14pt;
+ font-weight: bold;
+}
+
+label.big {
+ font-size: 14pt;
+ font-weight: bold;
+}
+
+#mousedialog {
+ margin: 10px !important;
+ padding: 10px !important;
+}
+
+#screen {
+ border: 0;
+ display: block;
+ position: absolute;
+ padding: 0;
+}
+
+/* ====== extra commands bar ================================================
+ ========================================================================== */
+
+#extracommands ul
+{
+ list-style-type: none;
+ display: inline;
+ cursor: default;
+}
+
+#extracommands ul li
+{
+ display: inline-block;
+ margin-top: 4px;
+ margin-right: 16px;
+ font-size: 13px;
+ text-decoration: none;
+ text-align: center;
+ color: #a1a1a1;
+
+ cursor: hand;
+ cursor:pointer;
+
+ /*unselectable*/
+ -webkit-user-select: none; /* Chrome/Safari */
+ -moz-user-select: none; /* Firefox */
+ -ms-user-select: none; /* IE10+ */
+
+ /* Rules below not implemented in browsers yet */
+ -o-user-select: none;
+ user-select: none;
+
+ /*color change on hover*/
+ -moz-transition-property: color; /* FF4+ */
+ -moz-transition-duration: 0.5s;
+ -webkit-transition-property: color; /* Saf3.2+, Chrome */
+ -webkit-transition-duration: 0.5s;
+ -o-transition-property: color; /* Opera 10.5+ */
+ -o-transition-duration: 0.5s;
+ -ms-transition-property: color; /* IE10? */
+ -ms-transition-duration: 0.5s;
+ transition-property: color; /* Standard */
+ transition-duration: 0.5s;
+}
+
+#extracommands ul li:hover
+{
+ color:#717171;
+ cursor:hand;
+}
+
+.extracommandshold
+{
+ background-color: #d7d7d7;
+}
+
+/* ==|== media queries ======================================================
+ EXAMPLE Media Query for Responsive Design.
+ This example overrides the primary ('mobile first') styles
+ Modify as content requires.
+ ========================================================================== */
+
+@media only screen and (min-width: 35em) {
+ /* Style adjustments for viewports that meet the condition */
+}
+
+#cbdcontent {
+ visibility: hidden;
+ max-width: 600px;
+ background-color: rgb(241, 241, 241);
+ color: black;
+ text-align: left;
+ border-radius: 1px;
+ border: black;
+ border-style: solid;
+ border-width: thin;
+ padding: 5px 0;
+ margin-top: 28px;
+ /* Position the tooltip */
+ position: absolute;
+ font-size: 16px;
+ font-weight: normal !important;
+ z-index: 1;
+}
+
+#clipboard:hover #cbdcontent {
+ visibility: visible;
+}
+
+
+
+/* ==|== non-semantic helper classes ========================================
+ Please define your styles before this section.
+ ========================================================================== */
+
+/* For image replacement */
+.ir { border: 0; font: 0/0 a; text-shadow: none; color: transparent; background-color: transparent; }
+
+/* Hide from both screenreaders and browsers: h5bp.com/u */
+.hidden { display: none !important; visibility: hidden; }
+
+/* Hide only visually, but have it available for screenreaders: h5bp.com/v */
+.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
+
+/* Extends the .visuallyhidden class to allow the element to be focusable when navigated to via the keyboard: h5bp.com/p */
+.visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; }
+
+/* Hide visually and from screenreaders, but maintain layout */
+.invisible { visibility: hidden; }
+
+/* Contain floats: h5bp.com/q */
+.clearfix:before, .clearfix:after { content: ""; display: table; }
+.clearfix:after { clear: both; }
+.clearfix { *zoom: 1; }
+
+
+
+/* ==|== print styles =======================================================
+ Print styles.
+ Inlined to avoid required HTTP connection: h5bp.com/r
+ ========================================================================== */
+
+@media print {
+ * { background: transparent !important; color: black !important; box-shadow:none !important; text-shadow: none !important; filter:none !important; -ms-filter: none !important; } /* Black prints faster: h5bp.com/s */
+ a, a:visited { text-decoration: underline; }
+ a[href]:after { content: " (" attr(href) ")"; }
+ abbr[title]:after { content: " (" attr(title) ")"; }
+ .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } /* Don't show links for images, or javascript/internal links */
+ pre, blockquote { border: 1px solid #999; page-break-inside: avoid; }
+ thead { display: table-header-group; } /* h5bp.com/t */
+ tr, img { page-break-inside: avoid; }
+ img { max-width: 100% !important; }
+ @page { margin: 0.5cm; }
+ p, h2, h3 { orphans: 3; widows: 3; }
+ h2, h3 { page-break-after: avoid; }
+}
+
+
+/* Pop up message box
+ */
+
+.popupwrapper {
+ background-color: transparent;
+ width: 50%;
+ margin: auto;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ position: absolute;
+}
+
+.popupmessage {
+ border-style: solid;
+ border-radius: 15px;
+ border-width: 0;
+ padding: 15px;
+ width: auto;
+ height: auto;
+ z-index: 1234;
+ position: relative;
+ font-family: sans-serif;
+ text-align: center;
+}
+
+#drop_zone {
+ border: 2px dashed #bbb;
+ border-radius: 5px;
+ #padding: 25px;
+ text-align: center;
+ font: 40pt bold;
+ color: #bbb;
+ width: auto;
+ height: auto;
+}
diff --git a/www/css/vkb-debug.css b/www/css/vkb-debug.css
new file mode 100644
index 0000000..c6d2434
--- /dev/null
+++ b/www/css/vkb-debug.css
@@ -0,0 +1,282 @@
+#keyboardInputMaster {
+ position:absolute;
+ font:normal 11px Arial,sans-serif;
+ border-top:1px solid #eeeeee;
+ border-right:1px solid #888888;
+ border-bottom:1px solid #444444;
+ border-left:1px solid #cccccc;
+ -webkit-border-radius:0.6em;
+ -moz-border-radius:0.6em;
+ border-radius:0.6em;
+ -webkit-box-shadow:0px 2px 10px #444444;
+ -moz-box-shadow:0px 2px 10px #444444;
+ box-shadow:0px 2px 10px #444444;
+ opacity:0.95;
+ filter:alpha(opacity=95);
+ background-color:#dddddd;
+ text-align:left;
+ z-index:1000000;
+ width:auto;
+ height:auto;
+ min-width:0;
+ min-height:0;
+ margin:0px;
+ padding:0px;
+ line-height:normal;
+ -moz-user-select:none;
+ cursor:default;
+}
+#keyboardInputMaster * {
+ position:static;
+ color:#000000;
+ background:transparent;
+ font:normal 11px Arial,sans-serif;
+ width:auto;
+ height:auto;
+ min-width:0;
+ min-height:0;
+ margin:0px;
+ padding:0px;
+ border:0px none;
+ outline:0px;
+ vertical-align:baseline;
+ line-height:1.3em;
+}
+#keyboardInputMaster table {
+ table-layout:auto;
+}
+#keyboardInputMaster.keyboardInputSize1,
+#keyboardInputMaster.keyboardInputSize1 * {
+ font-size:9px;
+}
+#keyboardInputMaster.keyboardInputSize3,
+#keyboardInputMaster.keyboardInputSize3 * {
+ font-size:13px;
+}
+#keyboardInputMaster.keyboardInputSize4,
+#keyboardInputMaster.keyboardInputSize4 * {
+ font-size:16px;
+}
+#keyboardInputMaster.keyboardInputSize5,
+#keyboardInputMaster.keyboardInputSize5 * {
+ font-size:20px;
+}
+
+#keyboardInputMaster thead tr th {
+ padding:0.3em 0.3em 0.1em 0.3em;
+ background-color:#999999;
+ white-space:nowrap;
+ text-align:right;
+ -webkit-border-radius:0.6em 0.6em 0px 0px;
+ -moz-border-radius:0.6em 0.6em 0px 0px;
+ border-radius:0.6em 0.6em 0px 0px;
+}
+#keyboardInputMaster thead tr th div {
+ float:left;
+ font-size:130% !important;
+ height:1.3em;
+ font-weight:bold;
+ position:relative;
+ z-index:1;
+ margin-right:0.5em;
+ cursor:pointer;
+ background-color:transparent;
+}
+#keyboardInputMaster thead tr th div ol {
+ position:absolute;
+ left:0px;
+ top:90%;
+ list-style-type:none;
+ height:9.4em;
+ overflow-y:auto;
+ overflow-x:hidden;
+ background-color:#f6f6f6;
+ border:1px solid #999999;
+ display:none;
+ text-align:left;
+ width:12em;
+}
+#keyboardInputMaster thead tr th div ol li {
+ padding:0.2em 0.4em;
+ cursor:pointer;
+ white-space:nowrap;
+ width:12em;
+}
+#keyboardInputMaster thead tr th div ol li.selected {
+ background-color:#ffffcc;
+}
+#keyboardInputMaster thead tr th div ol li:hover,
+#keyboardInputMaster thead tr th div ol li.hover {
+ background-color:#dddddd;
+}
+#keyboardInputMaster thead tr th span,
+#keyboardInputMaster thead tr th strong,
+#keyboardInputMaster thead tr th small,
+#keyboardInputMaster thead tr th big {
+ display:inline-block;
+ padding:0px 0.4em;
+ height:1.4em;
+ line-height:1.4em;
+ border-top:1px solid #e5e5e5;
+ border-right:1px solid #5d5d5d;
+ border-bottom:1px solid #5d5d5d;
+ border-left:1px solid #e5e5e5;
+ background-color:#cccccc;
+ cursor:pointer;
+ margin:0px 0px 0px 0.3em;
+ -webkit-border-radius:0.3em;
+ -moz-border-radius:0.3em;
+ border-radius:0.3em;
+ vertical-align:middle;
+ -webkit-transition:background-color .15s ease-in-out;
+ -o-transition:background-color .15s ease-in-out;
+ transition:background-color .15s ease-in-out;
+}
+#keyboardInputMaster thead tr th strong {
+ font-weight:bold;
+}
+#keyboardInputMaster thead tr th small {
+ -webkit-border-radius:0.3em 0px 0px 0.3em;
+ -moz-border-radius:0.3em 0px 0px 0.3em;
+ border-radius:0.3em 0px 0px 0.3em;
+ border-right:1px solid #aaaaaa;
+ padding:0px 0.2em 0px 0.3em;
+}
+#keyboardInputMaster thead tr th big {
+ -webkit-border-radius:0px 0.3em 0.3em 0px;
+ -moz-border-radius:0px 0.3em 0.3em 0px;
+ border-radius:0px 0.3em 0.3em 0px;
+ border-left:0px none;
+ margin:0px;
+ padding:0px 0.3em 0px 0.2em;
+}
+#keyboardInputMaster thead tr th span:hover,
+#keyboardInputMaster thead tr th span.hover,
+#keyboardInputMaster thead tr th strong:hover,
+#keyboardInputMaster thead tr th strong.hover,
+#keyboardInputMaster thead tr th small:hover,
+#keyboardInputMaster thead tr th small.hover,
+#keyboardInputMaster thead tr th big:hover,
+#keyboardInputMaster thead tr th big.hover {
+ background-color:#dddddd;
+}
+
+#keyboardInputMaster tbody tr td {
+ text-align:left;
+ padding:0.2em 0.3em 0.3em 0.3em;
+ vertical-align:top;
+}
+#keyboardInputMaster tbody tr td div {
+ text-align:center;
+ position:relative;
+ zoom:1;
+}
+#keyboardInputMaster tbody tr td table {
+ white-space:nowrap;
+ width:100%;
+ border-collapse:separate;
+ border-spacing:0px;
+}
+#keyboardInputMaster tbody tr td#keyboardInputNumpad table {
+ margin-left:0.2em;
+ width:auto;
+}
+#keyboardInputMaster tbody tr td#keyboardInputCursor table {
+ margin-left:0.2em;
+ width:auto;
+}
+#keyboardInputMaster tbody tr td table.keyboardInputCenter {
+ width:auto;
+ margin:0px auto;
+}
+#keyboardInputMaster tbody tr td table tbody tr td {
+ text-align: center;
+ vertical-align:middle;
+ padding:0px 0.45em;
+ white-space:pre;
+ height:1.8em;
+ font-family:'Lucida Console','Arial Unicode MS',monospace;
+ border-top:1px solid #e5e5e5;
+ border-right:1px solid #5d5d5d;
+ border-bottom:1px solid #5d5d5d;
+ border-left:1px solid #e5e5e5;
+ background-color:#eeeeee;
+ cursor:default;
+ min-width:0.75em;
+ -webkit-border-radius:0.2em;
+ -moz-border-radius:0.2em;
+ border-radius:0.2em;
+ -webkit-transition:background-color .15s ease-in-out;
+ -o-transition:background-color .15s ease-in-out;
+ transition:background-color .15s ease-in-out;
+}
+#keyboardInputMaster tbody tr td table tbody tr td.last {
+ width:99%;
+}
+#keyboardInputMaster tbody tr td table tbody tr td.space {
+ padding:0px 4em;
+}
+#keyboardInputMaster tbody tr td table tbody tr td.deadkey {
+ background-color:#ccccdd;
+}
+#keyboardInputMaster tbody tr td table tbody tr td.target {
+ background-color:#ddddcc;
+}
+#keyboardInputMaster tbody tr td table tbody tr td:hover,
+#keyboardInputMaster tbody tr td table tbody tr td.hover {
+ border-top:1px solid #d5d5d5;
+ border-right:1px solid #555555;
+ border-bottom:1px solid #555555;
+ border-left:1px solid #d5d5d5;
+ background-color:#cccccc;
+}
+#keyboardInputMaster thead tr th span.pressed,
+#keyboardInputMaster tbody tr td table tbody tr td:active,
+#keyboardInputMaster tbody tr td table tbody tr td.pressed {
+ border-top:1px solid #555555 !important;
+ border-right:1px solid #d5d5d5;
+ border-bottom:1px solid #d5d5d5;
+ border-left:1px solid #555555;
+ background-color:#cccccc;
+}
+#keyboardInputMaster tbody tr td table tbody tr td.none,
+#keyboardInputMaster tbody tr td table tbody tr td.none:hover,
+#keyboardInputMaster tbody tr td table tbody tr td.none:active {
+ border: 1px none !important;
+ background-color:#dddddd;
+}
+#keyboardInputMaster thead tr th span:active,
+
+#keyboardInputMaster tbody tr td table tbody tr td small {
+ display:block;
+ text-align:center;
+ font-size:0.6em !important;
+ line-height:1.1em;
+}
+
+#keyboardInputMaster tbody tr td div label {
+ position:absolute;
+ bottom:0.2em;
+ left:0.3em;
+}
+#keyboardInputMaster tbody tr td div label input {
+ background-color:#f6f6f6;
+ vertical-align:middle;
+ font-size:inherit;
+ width:1.1em;
+ height:1.1em;
+}
+#keyboardInputMaster tbody tr td div var {
+ position:absolute;
+ bottom:0px;
+ right:3px;
+ font-weight:bold;
+ font-style:italic;
+ color:#444444;
+}
+
+.keyboardInputInitiator {
+ margin:0px 3px;
+ vertical-align:middle;
+ cursor:pointer;
+}
diff --git a/www/favicon.ico b/www/favicon.ico
new file mode 100755
index 0000000..b22818f
--- /dev/null
+++ b/www/favicon.ico
Binary files differ
diff --git a/www/images/cbd_empty.png b/www/images/cbd_empty.png
new file mode 100644
index 0000000..fdae100
--- /dev/null
+++ b/www/images/cbd_empty.png
Binary files differ
diff --git a/www/images/cbd_empty_out.png b/www/images/cbd_empty_out.png
new file mode 100644
index 0000000..2dc7f6c
--- /dev/null
+++ b/www/images/cbd_empty_out.png
Binary files differ
diff --git a/www/images/cbd_files.png b/www/images/cbd_files.png
new file mode 100644
index 0000000..c2c39f4
--- /dev/null
+++ b/www/images/cbd_files.png
Binary files differ
diff --git a/www/images/cbd_text.png b/www/images/cbd_text.png
new file mode 100644
index 0000000..d2e303b
--- /dev/null
+++ b/www/images/cbd_text.png
Binary files differ
diff --git a/www/images/cbd_wait.png b/www/images/cbd_wait.png
new file mode 100644
index 0000000..9b1cc75
--- /dev/null
+++ b/www/images/cbd_wait.png
Binary files differ
diff --git a/www/images/clipboard_in.png b/www/images/clipboard_in.png
new file mode 100644
index 0000000..13a96ab
--- /dev/null
+++ b/www/images/clipboard_in.png
Binary files differ
diff --git a/www/images/clipboard_out.png b/www/images/clipboard_out.png
new file mode 100644
index 0000000..461c160
--- /dev/null
+++ b/www/images/clipboard_out.png
Binary files differ
diff --git a/www/images/loading.gif b/www/images/loading.gif
new file mode 100644
index 0000000..4c1e38c
--- /dev/null
+++ b/www/images/loading.gif
Binary files differ
diff --git a/www/index.html b/www/index.html
new file mode 100644
index 0000000..ef3db73
--- /dev/null
+++ b/www/index.html
@@ -0,0 +1,339 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+ <title>Portal RDP</title>
+ <meta name="robots" content="nofollow" />
+ <meta name="description" content="A HTML5 based RDP client">
+ <meta name="viewport" content="width=device-width height=device-height user-scalable=no">
+ <meta name="cursor-event-mode" content="native">
+ <meta name="touch-event-mode" content="pure-with-mouse-conversion">
+ <link rel="stylesheet" href="css/style-debug.css">
+ <link rel="stylesheet" href="css/vkb-debug.css">
+ <link rel="icon" href="favicon.ico" type="image/x-icon" />
+ <script src="js/modernizr-debug.js"></script>
+ <!-- <script src="js/mootools-debug.js"></script> -->
+ <script src="js/mootools-1.6.0.js"></script>
+ <script src="js/webrdp-ext.js"></script>
+ <script src="js/simpletabs-debug.js"></script>
+ <script src="js/webrdp-debug.js"></script>
+ <script src="js/webrdp-log.js"></script>
+ <script src="js/file-transfer.js"></script>
+ <script src="js/vkb-debug.js"></script>
+ <script src="js/popupdeck.js"></script>
+ <script src="js/rdp-start.js"></script>
+ <script src="js/clipboard.js"></script>
+ <!--<script language="javascript" type="text/javascript" src="js/rdpevents%JSDEBUG%.js"></script>-->
+ <script language="javascript" type="text/javascript">
+ var wsBase;
+ var rdp;
+
+ if(window.location.protocol.indexOf("https") != -1)
+ wsBase = "wss://";
+ else
+ wsBase = "ws://";
+ wsBase += window.location.hostname + ":" + window.location.port + "/wrdp";
+
+ var RIMtablet = navigator.appVersion && (-1 != navigator.appVersion.indexOf('RIM Tablet'));
+ var mhx = 100;
+ var mhy = 100;
+ var dragX = 0;
+ var dragY = 0;
+ var inDrag = false;
+ var rdp = null;
+ var vkbd = null;
+ var embedded = (location.search.substring(1).length > 1);
+
+ var externalConnection = false;
+
+ function initBody(){
+ initPopUpDeck();
+ }
+
+ window.addEventListener("beforeunload", function() {
+ if ($('maindialog').hasClass('invisible')){
+ var ans = confirm("Закончить сессию?");
+ if (ans) {
+ rdp.Disconnect();
+ }
+ }
+ }, false);
+ var lables = {
+ "cbd_empty": "Пусто",
+ "cbd_raw": "Текст",
+ "cbd_text": "Текст",
+ "cbd_file": "Файл",
+ "cpd_select_file": "Загрузить",
+ "cpd_failed_set_text": "Недостаточно прав для установки локального буфера обмена, пожалуйста скопируйте текст вручную под сначком буфера обмена.",
+ "file_lager_then_2g": "Файл больше двух гигабайт",
+ "file_read_failed": "Не удалось прочитать файл",
+ "files_in_transfer": "Один или несколько файлов находятся в процессе передачи",
+ "drop_here": "Положить файлы в буфер обмена\nПоддерживаются файлы до 2Гб\n(<a href='https://support.microsoft.com/en-us/help/2258090/copying-files-larger-than-2-gb-over-a-remote-desktop-services-or-termi'>See microsoft support</a>)",
+ "size_names": ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
+ };
+ // $.getJSON("/static/lables.json", function(json) {
+ // lables = json;
+ // });
+ window.addEvent('domready', function() {
+
+ var querystring = window.location.href.slice(window.location.href.indexOf('?'))
+
+ $('dtsize').addEvent('change', OnDesktopSize);
+ var tabs = new SimpleTabs('rdpdialog',{selector:'h4'});
+ OnDesktopSize();
+ if (RIMtablet) {
+ // Set default performance flags to modem
+ $('perf').value = '2';
+ }
+ window.addEvent('resize', OnDesktopSize);
+ // Special handling of webkit nightly builds
+ var webkitOK = false;
+ var wkVA = RegExp("( AppleWebKit/)([^ ]+)").exec(navigator.userAgent);
+ if (wkVA && (wkVA.length > 2)) {
+ if (wkVA[2].indexOf('+') != -1) {
+ webkitOK = true;
+ }
+ }
+ var wsOK = RIMtablet || webkitOK ||
+ (Browser.name == "firefox" && (Browser.version >= 11.0)) ||
+ (Browser.name == "chrome" && (Browser.version >= 17)) ||
+ (Browser.name == "safari" && (Browser.version >= 6))
+ if(externalConnection == true)
+ {
+ RDPStart();
+ vkbd = new webrdp.vkbd({
+ version:false,
+ sizeswitch:false,
+ numpadtoggle:false
+ });
+ }
+ if (wsOK) {
+ if(querystring.length > 2)
+ {
+ showDialog(false);
+ if (embedded){
+ $('disconnect').setStyles({'visibility':'hidden'});
+ }
+ var urlParams;
+ (window.onpopstate = function () {
+ var match,
+ pl = /\+/g, // Regex for replacing addition symbol with a space
+ search = /([^&=]+)=?([^&]*)/g,
+ decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); },
+ query = window.location.search.substring(1);
+
+ urlParams = {};
+ while (match = search.exec(query))
+ urlParams[decode(match[1])] = decode(match[2]);
+ })();
+ RDPStart(wsBase + querystring, urlParams["title"]);
+ }
+ else
+ {
+ $('rdpconnect').addEvent('click', function(){RDPStart();});
+ showDialog(true);
+ }
+ vkbd = new webrdp.vkbd({
+ version:false,
+ sizeswitch:false,
+ numpadtoggle:false
+ });
+ } else {
+ alert('Sorry!\nYour Browser (' + Browser.name + ' ' + Browser.version
+ + ') does not yet\nprovide the required HTML5 features '
+ + 'for this application.\n');
+ }
+ });
+ </script>
+ </head>
+ <body id="wrapper" onload="initBody()">
+ <div id="extracommands" style="height:28px; background-color:#f1f1f1; padding-left:10px; cursor:default; visibility:hidden;">
+<!-- <input id="config" style="display: none;" onchange="rdp.cbd.ClipoboardSendFilelist(this.files);" type="file">
+ <input type="submit" onclick="document.getElementById('config').click()" value="Загрузить"> -->
+
+ <div id="cbdcontent" class="tab-menu"></div>
+ <div id="clipboardin" style="float:left;margin-right:10px;height:28px;font-size:medium;">
+ <img style="height:100%;width:auto;" src="images/cbd_empty.png" title="In">
+ </div>
+ <div id="clipboardout" style="float:left;margin-right:10px;height:28px;font-size:medium;">
+ <img style="height:100%;width:auto;" src="images/cbd_empty.png" title="Out">
+ </div>
+ Комбинация клавиш:
+ <ul>
+
+ <li id="ctrlaltdelete">
+ Ctrl+Alt+Delete
+ </li>
+ <li id="alttab">
+ Alt+Tab
+ </li>
+ <li id="keyboardlanguage">
+ Multilanguage keyboard
+ </li>
+ <li>
+
+ </li>
+ </ul>
+ <button id="disconnect" type="button" style="float:right; margin-right:40px; margin-top:2px">Выход</button>
+ </div>
+ <div id="IMEhelper" style="z-index:1111;visibility:hidden;height:auto;width:auto;display:inline;position:absolute;background-color:#ffffff;"></div>
+ <canvas id="screen">
+ <p class="error">Браузер не поддерживает canvas tag.</p>
+ </canvas>
+ <div id="dvLoading" style="position:fixed; left:45%; top:45%; visibility:hidden;"><img src="images/loading.gif"></div>
+ <noscript><p class="error">Включите JavaScript.</p></noscript>
+ <table width="400px" id="maindialog" class="invisible">
+ <tr><td>
+ <div style="margin-left:auto; margin-right:auto; width:60%; position:relative;">
+ </a></div></img>
+ </td></tr>
+ <tr class="rdp_connect_dialog">
+ <td>
+ <div id="rdpdialog" class="dialog">
+ <h4 title="Main connection settings">General</h4>
+ <div>
+ <form onkeypress="if (event.keyCode == 13) { document.getElementById('rdpconnect').focus();document.getElementById('rdpconnect').click(); return event.preventDefault();}">
+ <fieldset>
+ <legend>Session parameters</legend>
+ <table>
+ <tr>
+ <td class="key"><label for="host">Hostname:</label></td>
+ <td><input id="rdphost" type="text" name="host" value="192.168.14.8"/></td>
+ </tr>
+ <tr>
+ <td class="key"><label for="user">User:</label></td>
+ <td><input id="rdpuser" type="text" name="user" value="admin"/></td>
+ </tr>
+ <tr>
+ <td class="key"><label for="password">Password:</label></td>
+ <td><input id="rdppass" type="password" name="password" value="Xx123456" autocomplete="off"/></td>
+ </tr>
+ <tr>
+ <td class="key"><label for="dtsize">Desktop size:</label></td>
+ <td>
+ <select id="dtsize" name="dtsize">
+ <option>640x480</option>
+ <option>800x600</option>
+ <option>1024x768</option>
+ <option>1280x960</option>
+ <option>1280x1024</option>
+ <option value="auto" selected>Available Area</option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td class="buttons" colspan="2"><input id="rdpconnect" type="button" value="Connect" /></td>
+ </tr>
+ </table>
+ </fieldset>
+ </form>
+ </div>
+ <h4 title="Advanced session parameters">Advanced</h4>
+ <div>
+ <form onsubmit="event.preventDefault(); return false;">
+ <fieldset>
+ <legend>Advanced session parameters</legend>
+ <table>
+ <tr>
+ <td class="key"><label for="port">Port:</label></td>
+ <td><input id="rdpport" name="port" type="text" size="6" maxlen="5" value="3389" /></td>
+ </tr>
+ <tr>
+ <td class="key"><label for="pcb">PCB (vmID):</label></td>
+ <td><input id="rdppcb" type="text" name="pcb" value=""/></td>
+ </tr>
+ <tr>
+ <td class="key"><label for="perf">Performance:</label></td>
+ <td>
+ <select id="perf" name="perf">
+ <option value="0" selected>LAN</option>
+ <option value="1">Broadband</option>
+ <option value="2">Modem</option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td class="key"><label for="nowallp">Disable wallpaper:</label></td>
+ <td><input id="nowallp" name="nowallp" type="checkbox"/></td>
+ </tr>
+ <tr>
+ <td class="key"><label for="nowdrag">Disable full window drag:</label></td>
+ <td><input id="nowdrag" name="nowdrag" type="checkbox"/></td>
+ </tr>
+ <tr>
+ <td class="key"><label for="nomani">Disable menu animation:</label></td>
+ <td><input id="nomani" name="nomani" type="checkbox"/></td>
+ </tr>
+ <tr>
+ <td class="key"><label for="notheme">Disable theming:</label></td>
+ <td><input id="notheme" name="notheme" type="checkbox"/></td>
+ </tr>
+ <tr>
+ <td class="key"><label for="nonla">Disable network level authentication:</label></td>
+ <td><input id="nonla" name="nonla" type="checkbox"/></td>
+ </tr>
+ <tr>
+ <td class="key"><label for="notls">Disable TLS:</label></td>
+ <td><input id="notls" name="notls" type="checkbox"/></td>
+ </tr>
+ <tr>
+ <td class="key"><label for="fntlm">Force NTLM auth:</label></td>
+ <td>
+ <select id="fntlm" name="fntlm">
+ <option value="0" selected>disabled</option>
+ <option value="1">NTLM v1</option>
+ <option value="2">NTLM v2</option>
+ </select>
+ </td>
+ </tr>
+ </table>
+ </fieldset>
+ </form>
+ <br />
+ </div>
+ <h4 title="Show version info">About</h4>
+ <div class="about">
+ <h2>Portal WebRDP</h2>
+ </div>
+ </div>
+ </td>
+ </tr>
+ </table>
+ <table width="240px" id="mousehelper" class="invisible">
+ <tr>
+ <td>
+ <div id="mousedialog" class="dialog">
+ <form>
+ <fieldset>
+ <legend>Click properties</legend>
+ <table>
+ <tr>
+ <td class="key"><label class="big" for="rclick">Right</label></td>
+ <td><input id="rclick" name="rclick" type="checkbox" /></td>
+ </tr>
+ <tr>
+ <td class="key"><label class="big" for="mclick">Middle</label></td>
+ <td><input id="mclick" name="mclick" type="checkbox" /></td>
+ </tr>
+ <tr>
+ <td class="key"><label class="big" for="aclick">Alt</label></td>
+ <td><input id="aclick" name="aclick" type="checkbox" /></td>
+ </tr>
+ <tr>
+ <td class="key"><label class="big" for="sclick">Shift</label></td>
+ <td><input id="sclick" name="sclick" type="checkbox" /></td>
+ </tr>
+ <tr>
+ <td class="key"><label class="big" for="cclick">Control</label></td>
+ <td><input id="cclick" name="cclick" type="checkbox" /></td>
+ </tr>
+ </table>
+ </fieldset>
+ </form>
+ </div>
+ </td>
+ </tr>
+ </table>
+ </body>
+</html>
diff --git a/www/js/clipboard.js b/www/js/clipboard.js
new file mode 100644
index 0000000..77ea154
--- /dev/null
+++ b/www/js/clipboard.js
@@ -0,0 +1,535 @@
+'use strict';
+/** BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>, smake <smake at ya dot ru>.
+ *
+ */
+
+/**
+ * Main clipboard class, that dispatch clipboard events and handle their status
+ */
+
+
+class ClipboardHandler {
+ constructor(rdp) {
+ this.rdp = rdp;
+ this.sock = this.rdp.sock;
+ this.filelistIn = [];
+ this.filelistOut = [];
+ this.textIn = null;
+ this.textOut = null;
+ this.update();
+ return this;
+ }
+
+ get istransferring() {
+ /*
+ * check for any file is currently transferring
+ * returns strings "in", "out" or bool false
+ */
+ if (this.filelistOut.length > 0) {
+ for (var i = 0, f; f = this.filelistOut[i]; i++) {
+ if (f.isTransferring){
+ return "out";
+ }
+ }
+ }
+ if (this.filelistIn.length > 0) {
+ for (var i = 0, f; f = this.filelistIn[i]; i++) {
+ if (f.isTransferring){
+ return "in";
+ }
+ }
+ }
+ return false;
+ }
+
+ ClipoboardFileGet(fid) {
+ var ft = this.filelistIn[fid];
+ if (!ft) {
+ return;
+ }
+ if (this.istransferring) {
+ console.log("Transfer in progress, please await");
+ return;
+ }
+ if (this.sock.readyState == this.sock.OPEN) {
+ this.rdp.log.debug('requesting file transfer', 1);
+ var buf = ft.nextChunkRequest;
+ // console.log(buf.toHexdump());
+ this.sock.send(buf);
+ };
+
+ }
+
+ ClipoboardSendFilelist(files) {
+ if (!files.length) {
+ console.log("There in now escape(files)")
+ return;
+ }
+ if (this.istransferring) {
+ var msg = lables.files_in_transfer;
+ this.rdp.fireEvent('alert', msg, 2);
+ console.log("Transfer in progress, please await");
+ return;
+ }
+ this.filelistOut = [];
+ this.filelistIn = [];
+ for (var i = 0, f; f = files[i]; i++) {
+ /*
+ * Bug in microsoft RDP server
+ * https://support.microsoft.com/en-us/help/2258090/copying-files-larger-than-2-gb-over-a-remote-desktop-services-or-termi
+ */
+ if (f.size < 2147483647) {
+ var j = this.filelistOut.length
+ var ft_entry = new FileEntry(this.rdp,f,j);
+ this.filelistOut[j] = ft_entry;
+ this.checkFileReadable(ft_entry);
+ } else {
+ var msg = f.name + ": " + lables.file_lager_then_2g;
+ this.rdp.fireEvent('alert', msg, 2);
+ }
+ }
+
+ if (this.filelistOut.length == 0) {
+ // popUpMessage
+ console.log("There in now escape(files)")
+ return;
+ }
+
+ if (this.sock.readyState == this.sock.OPEN) {
+ this.rdp.log.debug('updating remote clipbord status', 1);
+ var buf = new ArrayBuffer(6);
+ var c = new Uint32Array(buf, 0, 1);
+ var count = new Uint8Array(buf, 4, 1);
+ var fmt = new Uint8Array(buf, 5, 1);
+ c[0] = 6; // ws_in_clipbrd_changed
+ count[0] = this.filelistOut.length;
+ fmt[0] = 3;
+ // console.log(buf.toHexdump());
+ this.sock.send(buf);
+ };
+ this.update();
+
+ }
+
+ getFile() {
+ if (this.rdp.sock.readyState == this.rdp.sock.OPEN) {
+ this.rdp.log.debug('requesting file transfer', 1);
+ var buf = this.nextChunkRequest;
+ console.log(buf.toHexdump());
+ this.rdp.sock.send(buf);
+ };
+ }
+
+ resetUi() {
+ var cbdinimg = document.querySelector("div#clipboardin > img");
+ cbdinimg.style.backgroundColor = "#eee";
+ cbdinimg.src = "images/cbd_empty.png";
+
+ var new_element = cbdinimg.cloneNode(true);
+ cbdinimg.parentNode.replaceChild(new_element, cbdinimg);
+ var cbdinimg = new_element;
+
+ var cbdoutimg = document.querySelector("div#clipboardout > img");
+ cbdoutimg.style.backgroundColor = "#eee";
+ cbdoutimg.src = "images/cbd_empty_out.png";
+
+ var new_element = cbdoutimg.cloneNode(true);
+ cbdoutimg.parentNode.replaceChild(new_element, cbdoutimg);
+ var cbdoutimg = new_element;
+
+ var pos = this.rdp.canvas.getPosition();
+ var size = this.rdp.canvas.getSize();
+
+ var cbdcontent = document.getElementById('cbdcontent');
+ cbdcontent.innerHTML = "";
+ cbdcontent.setStyle('max-height', size.y);
+ cbdcontent.setStyle('overflow-y', "auto");
+ cbdcontent.style.visibility = "hidden";
+ }
+
+ update() {
+ /*
+ * Re-draw
+ */
+
+ this.resetUi();
+ var cbdinimg = document.querySelector("div#clipboardin > img");
+ var cbdoutimg = document.querySelector("div#clipboardout > img");
+ var cbdcontent = document.getElementById('cbdcontent');
+ cbdcontent.addEventListener("mouseleave", function(evt) {
+ evt.target.style.visibility = "hidden";
+ });
+
+
+ cbdoutimg.cbd = this;
+ cbdoutimg.addEventListener("click", function(evt) {
+ var cbdcontent = document.getElementById('cbdcontent');
+ var cbd = evt.target.cbd;
+ if (cbd.istransferring) {
+ return;
+ }
+ cbd.filelistIn = [];
+ cbd.filelistOut = [];
+ cbd.textIn = null;
+ cbd.textOut = null;
+ cbd.update();
+ navigator.clipboard.readText().then(text => {
+ rdp.cbd.textOut = text;
+ rdp.cbd.update();
+ }).catch(err => {
+ cbdcontent.style.visibility = "visible";
+ var input = document.createElement('input');
+ cbdcontent.appendChild(input);
+ input.cbd = cbd;
+ input.addEventListener("paste", function(evt) {
+ var cbd = evt.target.cbd;
+ cbd.textOut = "" + evt.clipboardData.getData("text");
+ cbd.update();
+ });
+ input.focus();
+ console.error('Failed to read clipboard contents: ', err);
+ });
+
+ });
+
+ if (this.filelistIn.length > 0) {
+ if (this.istransferring == "in") {
+ cbdinimg.src = "images/cbd_wait.png";
+ } else {
+ cbdinimg.src = "images/cbd_files.png";
+ }
+
+ this.iconAddListners(cbdinimg);
+
+ for (var i =0; i < this.filelistIn.length; i++) {
+ var file = this.filelistIn[i];
+ cbdcontent.innerHTML += file.uiElement;
+ }
+
+ return;
+ } else if (this.filelistOut.length > 0) {
+ if (this.istransferring == "out") {
+ cbdoutimg.src = "images/cbd_wait.png";
+ } else {
+ cbdoutimg.src = "images/cbd_files.png";
+ }
+
+ this.iconAddListners(cbdoutimg);
+
+ var html = "";
+ for (var i =0; i < this.filelistOut.length; i++) {
+ var file = this.filelistOut[i];
+ cbdcontent.innerHTML += file.uiElement
+ }
+ } else if (this.textIn) {
+ cbdinimg.src = "images/cbd_text.png";
+ cbdoutimg.src = "images/cbd_empty_out.png";
+
+ this.iconAddListners(cbdinimg);
+
+ cbdcontent.innerHTML = '<input type="text" value="' + this.textIn + '">';
+ } else if (this.textOut) {
+ cbdinimg.src = "images/cbd_empty.png";
+ cbdoutimg.src = "images/cbd_text.png";
+
+ this.iconAddListners(cbdoutimg);
+ if (this.sock.readyState == this.sock.OPEN) {
+ this.rdp.log.debug('updating remote clipbord status', 1);
+ var buf = new ArrayBuffer(6);
+ var c = new Uint32Array(buf, 0, 1);
+ var count = new Uint8Array(buf, 4, 1);
+ var fmt = new Uint8Array(buf, 5, 1);
+ c[0] = 6; // ws_in_clipbrd_changed
+ count[0] = 1;
+ fmt[0] = 2;
+ // console.log(buf.toHexdump());
+ this.sock.send(buf);
+ };
+
+ cbdcontent.innerHTML = '<input type="text" value="' + this.textOut + '">';
+ } else {
+ cbdinimg.src = "images/cbd_empty.png";
+ cbdoutimg.src = "images/cbd_empty_out.png";
+ }
+
+ }
+
+ iconAddListners (obj) {
+ var cbdcontent = document.getElementById('cbdcontent');
+ obj.addEventListener("mouseover", function(evt) {
+ var cbdcontent = document.getElementById('cbdcontent');
+ cbdcontent.style.visibility = "visible";
+ evt.target.style.backgroundColor = "darkgreen";
+ });
+ obj.addEventListener("mouseout", function(evt) {
+ var fl = document.getElementById('cbdcontent');
+ evt.target.style.backgroundColor = "#eee";
+ });
+
+ }
+
+ ClipbrdInfoParse(fmts) {
+ var clipboardimg = document.querySelector("div#clipboardin > img");
+ if (this.istransferring) {
+ console.log("Transfer in progress, please await");
+ return;
+ }
+ var fmt = 0;
+ if (fmts.length > 1) {
+ for (var i = 0; i < fmts.length; ++i) {
+ if (fmts[i] == 3) {
+ fmt = 3;
+ }
+ }
+ }
+ clipboardimg.style.backgroundColor = "darkgreen";
+ switch (fmt) {
+ case 0: // clip_format_raw
+ case 1: // clip_format_text
+ case 2: // clip_format_utf8
+ if (this.sock.readyState == this.sock.OPEN) {
+ this.rdp.log.debug('getting raw buffer', 1);
+ var buf = new ArrayBuffer(5);
+ var c = new Uint32Array(buf, 0, 1);
+ var t = new Uint8Array(buf, 4, 1);
+ c[0] = 8; // ws_in_clipbrd_data_request
+ t[0] = 2;
+ this.sock.send(buf);
+ clipboardimg.src = "images/cbd_text.png";
+ setTimeout(function () {
+ clipboardimg.style.backgroundColor = "#eee";
+ }, 100);
+ };
+ break;
+ case 3: // clip_format_FileGroupDescriptorW
+ if (this.sock.readyState == this.sock.OPEN) {
+ this.rdp.log.debug('getting file list', 1);
+ var buf = new ArrayBuffer(5);
+ var c = new Uint32Array(buf, 0, 1);
+ var t = new Uint8Array(buf, 4, 1);
+ c[0] = 8; // ws_in_clipbrd_data_request
+ t[0] = 3;
+ this.sock.send(buf);
+ clipboardimg.src = "images/cbd_files.png";
+ setTimeout(function () {
+ clipboardimg.style.backgroundColor = "#eee";
+ }, 100);
+ };
+ break;
+ default:
+ this.innerHTML = lables.cbd_empty;
+ break;
+ }
+ return true;
+ }
+
+ sendClipboardInfo (type) {
+ if (this.istransferring) {
+ console.log("Transfer in progress, please await");
+ return;
+ }
+ switch(type)
+ {
+ case 0:
+ case 1:
+ case 2:
+ {
+ if (this.sock.readyState != this.sock.OPEN) {
+ break;
+ }
+ // if (!this.textOut) {
+ // return;
+ // }
+ var text = new TextEncoder().encode(this.textOut);
+
+ // var buf = new ArrayBuffer(4 + 1 + 4 + text.byteLength);
+ var buf = new ArrayBuffer(4 + 1 + text.byteLength);
+ var bufArr = new Uint8Array(buf);
+ var c = new Uint32Array(buf, 0, 1);
+ var type = new Uint8Array(buf, 4, 1);
+ // var size = new Uint8Array(buf, 5, 1);
+ var data = new Uint8Array(buf, 5);
+ var tmpSize = new Uint32Array(1);
+ c[0] = 7; // ws_in_clipbrd_data
+ type[0] = 2;
+ tmpSize[0] = text.byteLength
+ // size.set(tmpSize);
+ data.set(text);
+ console.log(buf.toHexdump());
+ this.sock.send(buf);
+ }
+ break;
+ case 3:
+ {
+
+ if (this.sock.readyState != this.sock.OPEN) {
+ break;
+ }
+ if(this.filelistOut.length == 0) {
+ console.log("no files in buffer");
+ break;
+ }
+ var ftlenght = 0;
+ for (var i =0; i < this.filelistOut.length; i++) {
+ var f = this.filelistOut[i];
+ ftlenght += f.raw_ft.length;
+ };
+
+ var ftOffset = 4 + 2 + 1;
+ var buf = new ArrayBuffer(ftOffset + ftlenght);
+ var bufArr = new Uint8Array(buf);
+ var c = new Uint32Array(buf, 0, 1);
+ var type = new Uint8Array(buf, 4, 1);
+ var count = new Uint16Array(1);
+ c[0] = 7; // ws_in_clipbrd_data
+ type[0] = 3;
+ count[0] = this.filelistOut.length;
+
+ // because of buf migth be not multiple Uint16Array, which throw exeption in js
+ bufArr.set(new Uint8Array(count.buffer), 4 + 1);
+
+ for (var i =0; i < this.filelistOut.length; i++) {
+ var f = this.filelistOut[i];
+ bufArr.set(f.raw_ft, ftOffset);
+ ftOffset += f.raw_ft.length;
+ };
+
+ // console.log(buf.toHexdump());
+ this.sock.send(buf);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ reciveClipboardInfo(data) {
+ /* type is:
+ * typedef enum
+ * {
+ * clip_format_unsupported = -1,
+ * clip_format_raw,
+ * clip_format_text,
+ * clip_format_unicode,
+ * clip_format_file_list
+ * }wrdp_enum_clip_format;
+ * NOTE: must be kept in sync with core
+ */
+ if (this.istransferring) {
+ console.log("Transfer in progress, please await");
+ return;
+ }
+ var type = new Uint8Array(data, 4, 1);
+ this.filelistIn = [];
+ this.filelistOut = [];
+ this.textIn = null;
+ this.textOut = null;
+ switch(type[0])
+ {
+ case 0:
+ case 1:
+ {
+ /* text is most probably in local rdp server
+ * endocing */
+ var buf = new Uint8Array(data, 5);
+ //var text = new TextDecoder("utf-8").decode(buf);
+ //Viva la hardcoding encoding
+ var text = new TextDecoder("windows-1251").decode(buf.filter(v => v != 0));
+ this.textIn = text;
+ navigator.clipboard.writeText(text).then(function() {
+ /* clipboard successfully set */
+ // console.log("buffer setted")
+ return;
+ }, function() {
+ /* clipboard write failed */
+ var msg = lables.files_in_transfer;
+ this.rdp.fireEvent('alert', msg, 2);
+ console.log("buffer not setted")
+ return;
+ });
+ this.update();
+ }
+ break;
+ case 2:
+ {
+ var buf = new Uint8Array(data, 5);
+ var text = new TextDecoder("utf-8").decode(buf.filter(v => v != 0));
+ this.textIn = text;
+ navigator.clipboard.writeText(text).then(function() {
+ /* clipboard successfully set */
+ // console.log("buffer setted");
+ return;
+ }, function() {
+ /* clipboard write failed */
+ console.log("buffer not setted")
+ return;
+ });
+ this.update();
+ }
+ break;
+ case 3:
+ {
+ /*
+ * typedef struct
+ * {
+ * uint16_t filename_len;
+ * uint32_t file_id;
+ * uint64_t file_size, file_offset;
+ * }wrdp_backend_ft_list_entry;
+ * NOTE: must be kept in sync with core
+ * NOTE: file_offset is omited here
+ */
+ var count = new Uint16Array(data.slice(5,7));
+ var list = data.slice(7);
+ var pos = 0;
+ for (var i = 0; i < count[0]; ++i)
+ {
+ var name_len = new Uint16Array(list.slice(pos, pos + 2));
+ var entry_size = 2 + 4 + 8 + name_len[0]
+ var id = new Uint32Array(list.slice(pos + 2, pos + 2 + 4))[0];
+ var ft_entry = new Uint8Array(list.slice(pos, pos + entry_size));
+ /* "name" in utf8 encoding */
+ this.filelistIn[id] = new FileEntry(this.rdp, ft_entry);
+ pos += entry_size;
+ this.update();
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ }
+
+ checkFileReadable(fe) {
+ var offset = 0;
+ var req_size = 8;
+
+ var blob = fe.file.slice(offset, offset + req_size);
+ var reader = new FileReader();
+ reader.fe = fe;
+ reader.onerror = function(e) {
+ var file = e.target.fe;
+ var msg = file.name + ": " + e.target.error.message;
+ file.rdp.cbd.filelistOut.pop(file);
+ file.rdp.fireEvent('alert', msg);
+ file.rdp.cbd.update();
+ if (file.rdp.sock.readyState == file.rdp.sock.OPEN) {
+ file.rdp.log.debug('updating remote clipbord status', 1);
+ var buf = new ArrayBuffer(6);
+ var c = new Uint32Array(buf, 0, 1);
+ var count = new Uint8Array(buf, 4, 1);
+ var fmt = new Uint8Array(buf, 5, 1);
+ c[0] = 6; // ws_in_clipbrd_changed
+ count[0] = file.rdp.cbd.filelistOut.length;
+ fmt[0] = 3;
+ // console.log(buf.toHexdump());
+ file.rdp.sock.send(buf);
+ };
+ };
+ reader.readAsArrayBuffer(blob);
+ }
+
+}
diff --git a/www/js/file-transfer.js b/www/js/file-transfer.js
new file mode 100644
index 0000000..e5def90
--- /dev/null
+++ b/www/js/file-transfer.js
@@ -0,0 +1,365 @@
+'use strict';
+/** BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>, smake <smake at ya dot ru>.
+ *
+ */
+
+/**
+ * File handle files in clipboard and dispath events on transferring
+ */
+
+
+class FileEntry {
+ constructor(rdp, raw_ft, fileId) {
+ if (raw_ft instanceof File) {
+ this.file = raw_ft;
+ var file = this.file;
+ var filename = new TextEncoder().encode(file.name);
+ var raw_ft = new Uint8Array(
+ new ArrayBuffer(2 + 4 + 8 + filename.length)
+ );
+ var name_len = new Uint16Array(1);
+ var id = new Uint32Array(1);
+ var size = new BigUint64Array(1);
+ name_len[0] = filename.length;
+ id[0] = fileId;
+ size[0] = BigInt(file.size);
+ raw_ft.set(new Uint8Array(name_len.buffer));
+ raw_ft.set(new Uint8Array(id.buffer), 2);
+ raw_ft.set(new Uint8Array(size.buffer), 2 + 4);
+ raw_ft.set(filename, 2 + 4 + 8);
+
+ }
+ this.raw_ft = raw_ft;
+ this.rdp = rdp;
+ this.status = 0;
+ this.name_len = new Uint16Array(this.raw_ft)[0];
+ this.id = new Uint32Array(this.raw_ft.slice(2, 2 + 4))[0];
+ this.size = new BigUint64Array(this.raw_ft.slice(6, 14).buffer)[0];
+ this.log = true;
+ this.transfer_id = null;
+ this.chunkSize = BigInt(8192);
+ this.offset = BigInt(0);
+ this.chunks = [];
+ this.lastChunkTime = null;
+ this.lastOffset = BigInt(0);
+ this.timout = 120;
+ this.refreshTimeout = 3;
+ return this;
+ }
+
+ get fullname() {
+ var fullname = new Uint8Array(this.raw_ft.slice(14, 14 + this.name_len));
+ fullname = new TextDecoder("utf-8").decode(fullname.filter(v => v != 0));
+ return fullname;
+ }
+
+ get isTransferring() {
+ if (this.status == 2) {
+ var now = new Date();
+ var timeleft = Math.floor((now - this.lastChunkTime)/100);
+ if (!this.lastChunkTime) {
+ return true;
+ }
+ if ( timeleft > this.timout ) {
+ this.SendFtFinish();
+ return false;
+ } else {
+ return true;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ get name() {
+ var name = this.fullname.split('/').pop();
+ name = name.split('\\').pop();
+ return name;
+ }
+
+ get nextChunkRequest() {
+ if (this.offset >= this.size) {
+ this.status = 0;
+ return;
+ }
+ this.status = 2; //transfer in progress
+ var buf = new ArrayBuffer(4 + 4 + 8 + 8);
+ var c = new Uint32Array(buf, 0, 1);
+ c[0] = 0x09; // ws_in_ft_request
+ var file_id = new Uint32Array(buf, 4, 1);
+ var req_size = new BigUint64Array(buf, 4 + 4, 1);
+ var file_offset = new BigUint64Array(buf, 4 + 4 + 8, 1);
+ file_id[0] = this.id;
+ req_size[0] = this.size;
+ file_offset[0] = this.offset;
+
+ return buf
+ }
+
+ get progress () {
+ if (this.status == 2) {
+ return '(' + this.chunks.length + "/" + this.size / this.chunkSize + ')';
+ } else {
+ return "";
+ }
+ }
+
+ get percents () {
+ if (this.status == 2) {
+ return Math.floor((this.chunks.length / parseInt(this.size / this.chunkSize)) * 100);
+ } else {
+ return "";
+ }
+ }
+
+ get sizeHumanReadable() {
+ var bytes = parseInt(this.size);
+ return this.sizeParse(this.size);
+ }
+
+ get speed () {
+ if (this.status == 2) {
+ var bytes = parseInt(this.offset - this.lastOffset);
+ return this.sizeParse(Math.floor(bytes / this.refreshTimeout));
+ } else {
+ return "";
+ }
+ }
+
+ get uiElement () {
+ var html = "";
+ if (!this.file) {
+ if ( this.size < BigInt(2147483647) ){
+ html += '<li class="tab-menu" onclick="rdp.cbd.ClipoboardFileGet('+this.id+')">'
+ html += '<a>'
+ } else {
+ html += '<li class="tab-menu">';
+ html += '<a style="background-color: #555">(' + lables.file_lager_then_2g +') '
+ }
+ } else {
+ html += '<li class="tab-menu">'
+ html += '<a>'
+ }
+ if (this.status == 2) {
+ html += this.id + ': ' + this.name + ': ' + this.sizeHumanReadable + " " + this.percents + "% (" + this.speed + "/sec)" +'</li>'
+ } else {
+ html += this.id + ': ' + this.name + ': ' + this.sizeHumanReadable + '</li>'
+ }
+ html += '</a></li>'
+ return html;
+
+ }
+
+ newChunk(data) {
+ var size = BigInt(new Uint32Array(data,0,3)[2]);
+ this.transfer_id = new Uint32Array(data,0,3)[1];
+ if (this.log) {
+ var f = this;
+ setTimeout(function () {
+ f.log = true;
+ }, this.refreshTimeout * 1000);
+ // console.log(f.name + ": " + f.progress);
+ f.log = false;
+ this.rdp.cbd.update();
+ f.lastOffset = f.offset;
+ }
+ if (this.status == 2 && new Uint32Array(data,0,3)[2] != 0 ) {
+ if (this.offset + size >= this.size) {
+ this.status = 0;
+ this.lastChunkTime = new Date();
+ this.chunks.push(data.slice(12));
+ this.offset += size;
+ this.pushFileDownload();
+ } else {
+ this.lastChunkTime = new Date();
+ this.chunks.push(data.slice(12));
+ this.offset += size;
+ }
+ }
+ }
+
+ sendChunk(req_size, offset) {
+ if (!this.transfer_id) {
+ this.transfer_id = (Math.random() * 4294967292).round(0);
+ }
+ this.status = 2;
+
+ var offset = parseInt(offset);
+ var req_size = parseInt(req_size);
+
+ var blob = this.file.slice(offset, offset + req_size);
+ var reader = new FileReader();
+ reader.fe = this;
+ reader.onload = function(e) {
+ var file = e.target.fe;
+ var sock = e.target.fe.rdp.sock;
+ var c = e.target.result;
+ var buf = new ArrayBuffer(4 + 4 + 4 + c.byteLength);
+ var cmd = new Uint32Array(buf, 0, 3);
+ var transfer_id = new Uint32Array(buf, 0, 3);
+ var size = new Uint32Array(buf, 0, 3);
+ var data = new Uint8Array(buf)
+ cmd[0] = 0x0a; // ws_in_ft_chunk
+ transfer_id[1] = file.transfer_id;
+ size[2] = c.byteLength;
+ data.set(new Uint8Array(c), 12);
+ // console.log("Sending chunk:" + (buf.byteLength - 12));
+ // console.log(buf.toHexdump());
+ file.lastChunkTime = new Date();
+ sock.send(buf);
+ file.offset += BigInt(c.byteLength);
+ file.lastOffset = file.offset;
+ if (file.offset >= file.size) {
+ file.SendFtFinish();
+ }
+ };
+ reader.onerror = function(e) {
+ var file = e.target.fe;
+ var msg = file.name + ": " + e.target.error.message;
+ file.rdp.fireEvent('alert', msg);
+ file.SendFtFinish();
+ };
+ reader.readAsArrayBuffer(blob);
+ }
+
+ SendFtFinish() {
+ if (this.offset >= this.size) {
+ this.status = 0;
+ } else {
+ this.status = 1;
+ }
+ var buf = new ArrayBuffer(4 + 4 + 4 + 1);
+ var cmd = new Uint32Array(buf, 0, 1);
+ var id = new Uint32Array(buf, 4, 1);
+ var transfer_id = new Uint32Array(buf, 8, 1);
+ var status = new Uint8Array(buf, 12, 1);
+ cmd[0] = 0x0b; // ws_in_ft_finished
+ id[0] = this.id;
+ transfer_id[0] = this.transfer_id;
+ status[0] = this.status;
+
+ // console.log(buf.toHexdump());
+ this.rdp.sock.send(buf);
+ return;
+ }
+
+ sizeParse(bytes) {
+ var bytes = parseInt(bytes);
+ var decimals = null;
+ if(bytes == 0) return '0 Bytes';
+ var k = 1024;
+ var dm = decimals <= 0 ? 0 : decimals || 2;
+ var sizes = lables.size_names;
+ var i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
+ }
+
+ pushFileDownload () {
+ var blob = new Blob(this.chunks)
+ var url = window.URL.createObjectURL(blob);
+ var a = document.createElement("a");
+ document.body.appendChild(a);
+ a.style = "display: none";
+ a.href = url;
+ a.download = this.name;
+ a.click();
+ window.URL.revokeObjectURL(url);
+ this.chunks = [];
+ }
+
+ getFile() {
+ if (this.rdp.sock.readyState == this.rdp.sock.OPEN) {
+ this.log.debug('requesting file transfer', 1);
+ var buf = this.nextChunkRequest;
+ // console.log(buf.toHexdump());
+ this.rdp.sock.send(buf);
+ };
+ }
+
+ // startSending(offset) {
+ // if (!this.transfer_id) {
+ // this.transfer_id = (Math.random() * 4294967292).round(0);
+ // }
+ // this.status = 3;
+
+ // var offset = parseInt(0);
+ // var chunkSize = parseInt(8);
+ // var blob = this.file.slice(offset, offset + chunkSize);
+ // var reader = new FileReader();
+ // reader.fe = this;
+ // reader.onload = function(e) {
+ // var file = e.target.fe;
+ // var sock = e.target.fe.rdp.sock;
+ // var c = e.target.result;
+ // var buf = new ArrayBuffer(4 + 4 + 4 + c.byteLength);
+ // var cmd = new Uint32Array(buf, 0, 3);
+ // var transfer_id = new Uint32Array(buf, 0, 3);
+ // var size = new Uint32Array(buf, 0, 3);
+ // var data = new Uint8Array(buf)
+ // cmd[0] = 0x0a; // ws_in_ft_chunk
+ // transfer_id[1] = file.transfer_id;
+ // size[2] = c.byteLength;
+ // data.set(new Uint8Array(c), 12);
+ // console.log("Sending chunk:" + (buf.byteLength - 12));
+ // // console.log(buf.toHexdump());
+ // sock.send(buf);
+ // file.offset += BigInt(c.byteLength);
+ // file.sendNextChunk();
+ // };
+ // reader.readAsArrayBuffer(blob);
+ // }
+
+
+ // sendNextChunk() {
+ // if (this.status != 3) {
+ // return;
+ // }
+ // if (this.offset >= this.size) {
+ // this.status = 0;
+ // var buf = new ArrayBuffer(4 + 4 + 4 + 1);
+ // var cmd = new Uint32Array(buf, 0, 1);
+ // var id = new Uint32Array(buf, 4, 1);
+ // var transfer_id = new Uint32Array(buf, 8, 1);
+ // var status = new Uint8Array(buf, 9, 1);
+ // cmd[0] = 0x0b; // ws_in_ft_finished
+ // id[0] = this.id;
+ // transfer_id[0] = this.transfer_id;
+ // status[0] = 0;
+
+ // console.log(buf.toHexdump());
+ // this.rdp.sock.send(buf);
+ // return;
+ // }
+ // var offset = parseInt(this.offset);
+ // var chunkSize = parseInt(this.chunkSize);
+ // var blob = this.file.slice(offset, offset + chunkSize);
+ // var reader = new FileReader();
+ // reader.fe = this;
+ // reader.onload = function(e) {
+ // var file = e.target.fe;
+ // var sock = e.target.fe.rdp.sock;
+ // var c = e.target.result;
+ // var buf = new ArrayBuffer(4 + 4 + 4 + c.byteLength);
+ // var cmd = new Uint32Array(buf, 0, 3);
+ // var transfer_id = new Uint32Array(buf, 0, 3);
+ // var size = new Uint32Array(buf, 0, 3);
+ // var data = new Uint8Array(buf)
+ // cmd[0] = 0x0a; // ws_in_ft_chunk
+ // transfer_id[1] = file.transfer_id;
+ // size[2] = c.byteLength;
+ // data.set(new Uint8Array(c), 12);
+ // console.log("Sending chunk:" + (buf.byteLength - 12));
+ // // console.log(buf.toHexdump());
+ // sock.send(buf);
+ // file.offset += BigInt(c.byteLength);
+ // file.sendNextChunk();
+ // };
+ // reader.readAsArrayBuffer(blob);
+ // }
+}
+
+
+
+
diff --git a/www/js/modernizr-debug.js b/www/js/modernizr-debug.js
new file mode 100644
index 0000000..f344bac
--- /dev/null
+++ b/www/js/modernizr-debug.js
@@ -0,0 +1,441 @@
+/* Modernizr 2.5.3 (Custom Build) | MIT & BSD
+ * Build: http://www.modernizr.com/download/#-canvas-canvastext-input-inputtypes-websockets-webworkers-shiv-cssclasses-addtest-prefixed-testprop-testallprops-hasevent-domprefixes-load
+ */
+;
+
+
+
+window.Modernizr = (function( window, document, undefined ) {
+
+ var version = '2.5.3',
+
+ Modernizr = {},
+
+ enableClasses = true,
+
+ docElement = document.documentElement,
+
+ mod = 'modernizr',
+ modElem = document.createElement(mod),
+ mStyle = modElem.style,
+
+ inputElem = document.createElement('input') ,
+
+ smile = ':)',
+
+ toString = {}.toString, omPrefixes = 'Webkit Moz O ms',
+
+ cssomPrefixes = omPrefixes.split(' '),
+
+ domPrefixes = omPrefixes.toLowerCase().split(' '),
+
+
+ tests = {},
+ inputs = {},
+ attrs = {},
+
+ classes = [],
+
+ slice = classes.slice,
+
+ featureName,
+
+ isEventSupported = (function() {
+
+ var TAGNAMES = {
+ 'select': 'input', 'change': 'input',
+ 'submit': 'form', 'reset': 'form',
+ 'error': 'img', 'load': 'img', 'abort': 'img'
+ };
+
+ function isEventSupported( eventName, element ) {
+
+ element = element || document.createElement(TAGNAMES[eventName] || 'div');
+ eventName = 'on' + eventName;
+
+ var isSupported = eventName in element;
+
+ if ( !isSupported ) {
+ if ( !element.setAttribute ) {
+ element = document.createElement('div');
+ }
+ if ( element.setAttribute && element.removeAttribute ) {
+ element.setAttribute(eventName, '');
+ isSupported = is(element[eventName], 'function');
+
+ if ( !is(element[eventName], 'undefined') ) {
+ element[eventName] = undefined;
+ }
+ element.removeAttribute(eventName);
+ }
+ }
+
+ element = null;
+ return isSupported;
+ }
+ return isEventSupported;
+ })(),
+
+
+ _hasOwnProperty = ({}).hasOwnProperty, hasOwnProperty;
+
+ if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) {
+ hasOwnProperty = function (object, property) {
+ return _hasOwnProperty.call(object, property);
+ };
+ }
+ else {
+ hasOwnProperty = function (object, property) {
+ return ((property in object) && is(object.constructor.prototype[property], 'undefined'));
+ };
+ }
+
+
+ if (!Function.prototype.bind) {
+ Function.prototype.bind = function bind(that) {
+
+ var target = this;
+
+ if (typeof target != "function") {
+ throw new TypeError();
+ }
+
+ var args = slice.call(arguments, 1),
+ bound = function () {
+
+ if (this instanceof bound) {
+
+ var F = function(){};
+ F.prototype = target.prototype;
+ var self = new F;
+
+ var result = target.apply(
+ self,
+ args.concat(slice.call(arguments))
+ );
+ if (Object(result) === result) {
+ return result;
+ }
+ return self;
+
+ } else {
+
+ return target.apply(
+ that,
+ args.concat(slice.call(arguments))
+ );
+
+ }
+
+ };
+
+ return bound;
+ };
+ }
+
+ function setCss( str ) {
+ mStyle.cssText = str;
+ }
+
+ function setCssAll( str1, str2 ) {
+ return setCss(prefixes.join(str1 + ';') + ( str2 || '' ));
+ }
+
+ function is( obj, type ) {
+ return typeof obj === type;
+ }
+
+ function contains( str, substr ) {
+ return !!~('' + str).indexOf(substr);
+ }
+
+ function testProps( props, prefixed ) {
+ for ( var i in props ) {
+ if ( mStyle[ props[i] ] !== undefined ) {
+ return prefixed == 'pfx' ? props[i] : true;
+ }
+ }
+ return false;
+ }
+
+ function testDOMProps( props, obj, elem ) {
+ for ( var i in props ) {
+ var item = obj[props[i]];
+ if ( item !== undefined) {
+
+ if (elem === false) return props[i];
+
+ if (is(item, 'function')){
+ return item.bind(elem || obj);
+ }
+
+ return item;
+ }
+ }
+ return false;
+ }
+
+ function testPropsAll( prop, prefixed, elem ) {
+
+ var ucProp = prop.charAt(0).toUpperCase() + prop.substr(1),
+ props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' ');
+
+ if(is(prefixed, "string") || is(prefixed, "undefined")) {
+ return testProps(props, prefixed);
+
+ } else {
+ props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' ');
+ return testDOMProps(props, prefixed, elem);
+ }
+ } tests['canvas'] = function() {
+ var elem = document.createElement('canvas');
+ return !!(elem.getContext && elem.getContext('2d'));
+ };
+
+ tests['canvastext'] = function() {
+ return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function'));
+ };
+
+
+
+ tests['websockets'] = function() {
+ for ( var i = -1, len = cssomPrefixes.length; ++i < len; ){
+ if ( window[cssomPrefixes[i] + 'WebSocket'] ){
+ return true;
+ }
+ }
+ return 'WebSocket' in window;
+ };
+ tests['webworkers'] = function() {
+ return !!window.Worker;
+ }; function webforms() {
+ Modernizr['input'] = (function( props ) {
+ for ( var i = 0, len = props.length; i < len; i++ ) {
+ attrs[ props[i] ] = !!(props[i] in inputElem);
+ }
+ if (attrs.list){
+ attrs.list = !!(document.createElement('datalist') && window.HTMLDataListElement);
+ }
+ return attrs;
+ })('autocomplete autofocus list placeholder max min multiple pattern required step'.split(' '));
+ Modernizr['inputtypes'] = (function(props) {
+
+ for ( var i = 0, bool, inputElemType, defaultView, len = props.length; i < len; i++ ) {
+
+ inputElem.setAttribute('type', inputElemType = props[i]);
+ bool = inputElem.type !== 'text';
+
+ if ( bool ) {
+
+ inputElem.value = smile;
+ inputElem.style.cssText = 'position:absolute;visibility:hidden;';
+
+ if ( /^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined ) {
+
+ docElement.appendChild(inputElem);
+ defaultView = document.defaultView;
+
+ bool = defaultView.getComputedStyle &&
+ defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' &&
+ (inputElem.offsetHeight !== 0);
+
+ docElement.removeChild(inputElem);
+
+ } else if ( /^(search|tel)$/.test(inputElemType) ){
+ } else if ( /^(url|email)$/.test(inputElemType) ) {
+ bool = inputElem.checkValidity && inputElem.checkValidity() === false;
+
+ } else if ( /^color$/.test(inputElemType) ) {
+ docElement.appendChild(inputElem);
+ docElement.offsetWidth;
+ bool = inputElem.value != smile;
+ docElement.removeChild(inputElem);
+
+ } else {
+ bool = inputElem.value != smile;
+ }
+ }
+
+ inputs[ props[i] ] = !!bool;
+ }
+ return inputs;
+ })('search tel url email datetime date month week time datetime-local number range color'.split(' '));
+ }
+ for ( var feature in tests ) {
+ if ( hasOwnProperty(tests, feature) ) {
+ featureName = feature.toLowerCase();
+ Modernizr[featureName] = tests[feature]();
+
+ classes.push((Modernizr[featureName] ? '' : 'no-') + featureName);
+ }
+ }
+
+ Modernizr.input || webforms();
+
+
+ Modernizr.addTest = function ( feature, test ) {
+ if ( typeof feature == 'object' ) {
+ for ( var key in feature ) {
+ if ( hasOwnProperty( feature, key ) ) {
+ Modernizr.addTest( key, feature[ key ] );
+ }
+ }
+ } else {
+
+ feature = feature.toLowerCase();
+
+ if ( Modernizr[feature] !== undefined ) {
+ return Modernizr;
+ }
+
+ test = typeof test == 'function' ? test() : test;
+
+ docElement.className += ' ' + (test ? '' : 'no-') + feature;
+ Modernizr[feature] = test;
+
+ }
+
+ return Modernizr;
+ };
+
+
+ setCss('');
+ modElem = inputElem = null;
+
+ ;(function(window, document) {
+
+ var options = window.html5 || {};
+
+ var reSkip = /^<|^(?:button|form|map|select|textarea)$/i;
+
+ var supportsHtml5Styles;
+
+ var supportsUnknownElements;
+
+ (function() {
+ var a = document.createElement('a');
+
+ a.innerHTML = '<xyz></xyz>';
+
+ supportsHtml5Styles = ('hidden' in a);
+ supportsUnknownElements = a.childNodes.length == 1 || (function() {
+ try {
+ (document.createElement)('a');
+ } catch(e) {
+ return true;
+ }
+ var frag = document.createDocumentFragment();
+ return (
+ typeof frag.cloneNode == 'undefined' ||
+ typeof frag.createDocumentFragment == 'undefined' ||
+ typeof frag.createElement == 'undefined'
+ );
+ }());
+
+ }()); function addStyleSheet(ownerDocument, cssText) {
+ var p = ownerDocument.createElement('p'),
+ parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement;
+
+ p.innerHTML = 'x<style>' + cssText + '</style>';
+ return parent.insertBefore(p.lastChild, parent.firstChild);
+ }
+
+ function getElements() {
+ var elements = html5.elements;
+ return typeof elements == 'string' ? elements.split(' ') : elements;
+ }
+
+ function shivMethods(ownerDocument) {
+ var cache = {},
+ docCreateElement = ownerDocument.createElement,
+ docCreateFragment = ownerDocument.createDocumentFragment,
+ frag = docCreateFragment();
+
+
+ ownerDocument.createElement = function(nodeName) {
+ var node = (cache[nodeName] || (cache[nodeName] = docCreateElement(nodeName))).cloneNode();
+ return html5.shivMethods && node.canHaveChildren && !reSkip.test(nodeName) ? frag.appendChild(node) : node;
+ };
+
+ ownerDocument.createDocumentFragment = Function('h,f', 'return function(){' +
+ 'var n=f.cloneNode(),c=n.createElement;' +
+ 'h.shivMethods&&(' +
+ getElements().join().replace(/\w+/g, function(nodeName) {
+ cache[nodeName] = docCreateElement(nodeName);
+ frag.createElement(nodeName);
+ return 'c("' + nodeName + '")';
+ }) +
+ ');return n}'
+ )(html5, frag);
+ } function shivDocument(ownerDocument) {
+ var shived;
+ if (ownerDocument.documentShived) {
+ return ownerDocument;
+ }
+ if (html5.shivCSS && !supportsHtml5Styles) {
+ shived = !!addStyleSheet(ownerDocument,
+ 'article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}' +
+ 'audio{display:none}' +
+ 'canvas,video{display:inline-block;*display:inline;*zoom:1}' +
+ '[hidden]{display:none}audio[controls]{display:inline-block;*display:inline;*zoom:1}' +
+ 'mark{background:#FF0;color:#000}'
+ );
+ }
+ if (!supportsUnknownElements) {
+ shived = !shivMethods(ownerDocument);
+ }
+ if (shived) {
+ ownerDocument.documentShived = shived;
+ }
+ return ownerDocument;
+ } var html5 = {
+
+ 'elements': options.elements || 'abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video',
+
+ 'shivCSS': !(options.shivCSS === false),
+
+ 'shivMethods': !(options.shivMethods === false),
+
+ 'type': 'default',
+ 'shivDocument': shivDocument
+ }; window.html5 = html5;
+
+ shivDocument(document);
+
+ }(this, document));
+
+ Modernizr._version = version;
+
+ Modernizr._domPrefixes = domPrefixes;
+ Modernizr._cssomPrefixes = cssomPrefixes;
+
+
+ Modernizr.hasEvent = isEventSupported;
+
+ Modernizr.testProp = function(prop){
+ return testProps([prop]);
+ };
+
+ Modernizr.testAllProps = testPropsAll;
+
+
+ Modernizr.prefixed = function(prop, obj, elem){
+ if(!obj) {
+ return testPropsAll(prop, 'pfx');
+ } else {
+ return testPropsAll(prop, obj, elem);
+ }
+ };
+
+
+ docElement.className = docElement.className.replace(/(^|\s)no-js(\s|$)/, '$1$2') +
+
+ (enableClasses ? ' js ' + classes.join(' ') : '');
+
+ return Modernizr;
+
+})(this, this.document);
+/*yepnope1.5.3|WTFPL*/
+(function(a,b,c){function d(a){return o.call(a)=="[object Function]"}function e(a){return typeof a=="string"}function f(){}function g(a){return!a||a=="loaded"||a=="complete"||a=="uninitialized"}function h(){var a=p.shift();q=1,a?a.t?m(function(){(a.t=="c"?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){a!="img"&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l={},o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};y[c]===1&&(r=1,y[c]=[],l=b.createElement(a)),a=="object"?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),a!="img"&&(r||y[c]===2?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i(b=="c"?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),p.length==1&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&o.call(a.opera)=="[object Opera]",l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return o.call(a)=="[object Array]"},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f<d;f++)g=a[f].split("="),(e=z[g.shift()])&&(c=e(c,g));for(f=0;f<b;f++)c=x[f](c);return c}function g(a,e,f,g,i){var j=b(a),l=j.autoCallback;j.url.split(".").pop().split("?").shift(),j.bypass||(e&&(e=d(e)?e:e[a]||e[g]||e[a.split("/").pop().split("?")[0]]||h),j.instead?j.instead(a,e,f,g,i):(y[j.url]?j.noexec=!0:y[j.url]=1,f.load(j.url,j.forceCSS||!j.forceJS&&"css"==j.url.split(".").pop().split("?").shift()?"c":c,j.noexec,j.attrs,j.timeout),(d(e)||d(l))&&f.load(function(){k(),e&&e(j.origUrl,i,g),l&&l(j.origUrl,i,g),y[j.url]=2})))}function i(a,b){function c(a,c){if(a){if(e(a))c||(j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}),g(a,j,b,0,h);else if(Object(a)===a)for(n in m=function(){var b=0,c;for(c in a)a.hasOwnProperty(c)&&b++;return b}(),a)a.hasOwnProperty(n)&&(!c&&!--m&&(d(j)?j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}:j[n]=function(a){return function(){var b=[].slice.call(arguments);a&&a.apply(this,b),l()}}(k[n])),g(a[n],j,b,n,h))}else!c&&l()}var h=!!a.test,i=a.load||a.both,j=a.callback||f,k=j,l=a.complete||f,m,n;c(h?a.yep:a.nope,!!i),i&&c(i)}var j,l,m=this.yepnope.loader;if(e(a))g(a,0,m,0);else if(w(a))for(j=0;j<a.length;j++)l=a[j],e(l)?g(l,0,m,0):w(l)?B(l):Object(l)===l&&i(l,m);else Object(a)===a&&i(a,m)},B.addPrefix=function(a,b){z[a]=b},B.addFilter=function(a){x.push(a)},B.errorTimeout=1e4,b.readyState==null&&b.addEventListener&&(b.readyState="loading",b.addEventListener("DOMContentLoaded",A=function(){b.removeEventListener("DOMContentLoaded",A,0),b.readyState="complete"},0)),a.yepnope=k(),a.yepnope.executeStack=h,a.yepnope.injectJs=function(a,c,d,e,i,j){var k=b.createElement("script"),l,o,e=e||B.errorTimeout;k.src=a;for(o in d)k.setAttribute(o,d[o]);c=j?h:c||f,k.onreadystatechange=k.onload=function(){!l&&g(k.readyState)&&(l=1,c(),k.onload=k.onreadystatechange=null)},m(function(){l||(l=1,c(1))},e),i?k.onload():n.parentNode.insertBefore(k,n)},a.yepnope.injectCss=function(a,c,d,e,g,i){var e=b.createElement("link"),j,c=i?h:c||f;e.href=a,e.rel="stylesheet",e.type="text/css";for(j in d)e.setAttribute(j,d[j]);g||(n.parentNode.insertBefore(e,n),m(c,0))}})(this,document);
+Modernizr.load=function(){yepnope.apply(window,[].slice.call(arguments,0));};
+; \ No newline at end of file
diff --git a/www/js/mootools-1.6.0.js b/www/js/mootools-1.6.0.js
new file mode 100644
index 0000000..f35c54f
--- /dev/null
+++ b/www/js/mootools-1.6.0.js
@@ -0,0 +1,6382 @@
+/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2018 [Valerio Proietti](https://mootools.net/).*/
+/*!
+Web Build: https://mootools.net/core/builder/e426a9ae7167c5807b173d5deff673fc
+*/
+/*
+---
+
+name: Core
+
+description: The heart of MooTools.
+
+license: MIT-style license.
+
+copyright: Copyright (c) 2006-2015 [Valerio Proietti](https://github.com/kamicane/).
+
+authors: The MooTools production team (http://mootools.net/developers/)
+
+inspiration:
+ - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)
+ - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php)
+
+provides: [Core, MooTools, Type, typeOf, instanceOf, Native]
+
+...
+*/
+/*! MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](https://github.com/kamicane/).*/
+(function(){
+
+this.MooTools = {
+ version: '1.6.0',
+ build: '529422872adfff401b901b8b6c7ca5114ee95e2b'
+};
+
+// typeOf, instanceOf
+
+var typeOf = this.typeOf = function(item){
+ if (item == null) return 'null';
+ if (item.$family != null) return item.$family();
+
+ if (item.nodeName){
+ if (item.nodeType == 1) return 'element';
+ if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace';
+ } else if (typeof item.length == 'number'){
+ if ('callee' in item) return 'arguments';
+ if ('item' in item) return 'collection';
+ }
+
+ return typeof item;
+};
+
+var instanceOf = this.instanceOf = function(item, object){
+ if (item == null) return false;
+ var constructor = item.$constructor || item.constructor;
+ while (constructor){
+ if (constructor === object) return true;
+ constructor = constructor.parent;
+ }
+ /*<ltIE8>*/
+ if (!item.hasOwnProperty) return false;
+ /*</ltIE8>*/
+ return item instanceof object;
+};
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+/*<ltIE8>*/
+var enumerables = true;
+for (var i in {toString: 1}) enumerables = null;
+if (enumerables) enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'constructor'];
+function forEachObjectEnumberableKey(object, fn, bind){
+ if (enumerables) for (var i = enumerables.length; i--;){
+ var k = enumerables[i];
+ // signature has key-value, so overloadSetter can directly pass the
+ // method function, without swapping arguments.
+ if (hasOwnProperty.call(object, k)) fn.call(bind, k, object[k]);
+ }
+}
+/*</ltIE8>*/
+
+// Function overloading
+
+var Function = this.Function;
+
+Function.prototype.overloadSetter = function(usePlural){
+ var self = this;
+ return function(a, b){
+ if (a == null) return this;
+ if (usePlural || typeof a != 'string'){
+ for (var k in a) self.call(this, k, a[k]);
+ /*<ltIE8>*/
+ forEachObjectEnumberableKey(a, self, this);
+ /*</ltIE8>*/
+ } else {
+ self.call(this, a, b);
+ }
+ return this;
+ };
+};
+
+Function.prototype.overloadGetter = function(usePlural){
+ var self = this;
+ return function(a){
+ var args, result;
+ if (typeof a != 'string') args = a;
+ else if (arguments.length > 1) args = arguments;
+ else if (usePlural) args = [a];
+ if (args){
+ result = {};
+ for (var i = 0; i < args.length; i++) result[args[i]] = self.call(this, args[i]);
+ } else {
+ result = self.call(this, a);
+ }
+ return result;
+ };
+};
+
+Function.prototype.extend = function(key, value){
+ this[key] = value;
+}.overloadSetter();
+
+Function.prototype.implement = function(key, value){
+ this.prototype[key] = value;
+}.overloadSetter();
+
+// From
+
+var slice = Array.prototype.slice;
+
+Array.convert = function(item){
+ if (item == null) return [];
+ return (Type.isEnumerable(item) && typeof item != 'string') ? (typeOf(item) == 'array') ? item : slice.call(item) : [item];
+};
+
+Function.convert = function(item){
+ return (typeOf(item) == 'function') ? item : function(){
+ return item;
+ };
+};
+
+
+Number.convert = function(item){
+ var number = parseFloat(item);
+ return isFinite(number) ? number : null;
+};
+
+String.convert = function(item){
+ return item + '';
+};
+
+
+
+Function.from = Function.convert;
+Number.from = Number.convert;
+String.from = String.convert;
+
+// hide, protect
+
+Function.implement({
+
+ hide: function(){
+ this.$hidden = true;
+ return this;
+ },
+
+ protect: function(){
+ this.$protected = true;
+ return this;
+ }
+
+});
+
+// Type
+
+var Type = this.Type = function(name, object){
+ if (name){
+ var lower = name.toLowerCase();
+ var typeCheck = function(item){
+ return (typeOf(item) == lower);
+ };
+
+ Type['is' + name] = typeCheck;
+ if (object != null){
+ object.prototype.$family = (function(){
+ return lower;
+ }).hide();
+
+ }
+ }
+
+ if (object == null) return null;
+
+ object.extend(this);
+ object.$constructor = Type;
+ object.prototype.$constructor = object;
+
+ return object;
+};
+
+var toString = Object.prototype.toString;
+
+Type.isEnumerable = function(item){
+ return (item != null && typeof item.length == 'number' && toString.call(item) != '[object Function]' );
+};
+
+var hooks = {};
+
+var hooksOf = function(object){
+ var type = typeOf(object.prototype);
+ return hooks[type] || (hooks[type] = []);
+};
+
+var implement = function(name, method){
+ if (method && method.$hidden) return;
+
+ var hooks = hooksOf(this);
+
+ for (var i = 0; i < hooks.length; i++){
+ var hook = hooks[i];
+ if (typeOf(hook) == 'type') implement.call(hook, name, method);
+ else hook.call(this, name, method);
+ }
+
+ var previous = this.prototype[name];
+ if (previous == null || !previous.$protected) this.prototype[name] = method;
+
+ if (this[name] == null && typeOf(method) == 'function') extend.call(this, name, function(item){
+ return method.apply(item, slice.call(arguments, 1));
+ });
+};
+
+var extend = function(name, method){
+ if (method && method.$hidden) return;
+ var previous = this[name];
+ if (previous == null || !previous.$protected) this[name] = method;
+};
+
+Type.implement({
+
+ implement: implement.overloadSetter(),
+
+ extend: extend.overloadSetter(),
+
+ alias: function(name, existing){
+ implement.call(this, name, this.prototype[existing]);
+ }.overloadSetter(),
+
+ mirror: function(hook){
+ hooksOf(this).push(hook);
+ return this;
+ }
+
+});
+
+new Type('Type', Type);
+
+// Default Types
+
+var force = function(name, object, methods){
+ var isType = (object != Object),
+ prototype = object.prototype;
+
+ if (isType) object = new Type(name, object);
+
+ for (var i = 0, l = methods.length; i < l; i++){
+ var key = methods[i],
+ generic = object[key],
+ proto = prototype[key];
+
+ if (generic) generic.protect();
+ if (isType && proto) object.implement(key, proto.protect());
+ }
+
+ if (isType){
+ var methodsEnumerable = prototype.propertyIsEnumerable(methods[0]);
+ object.forEachMethod = function(fn){
+ if (!methodsEnumerable) for (var i = 0, l = methods.length; i < l; i++){
+ fn.call(prototype, prototype[methods[i]], methods[i]);
+ }
+ for (var key in prototype) fn.call(prototype, prototype[key], key);
+ };
+ }
+
+ return force;
+};
+
+force('String', String, [
+ 'charAt', 'charCodeAt', 'concat', 'contains', 'indexOf', 'lastIndexOf', 'match', 'quote', 'replace', 'search',
+ 'slice', 'split', 'substr', 'substring', 'trim', 'toLowerCase', 'toUpperCase'
+])('Array', Array, [
+ 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice',
+ 'indexOf', 'lastIndexOf', 'filter', 'forEach', 'every', 'map', 'some', 'reduce', 'reduceRight', 'contains'
+])('Number', Number, [
+ 'toExponential', 'toFixed', 'toLocaleString', 'toPrecision'
+])('Function', Function, [
+ 'apply', 'call', 'bind'
+])('RegExp', RegExp, [
+ 'exec', 'test'
+])('Object', Object, [
+ 'create', 'defineProperty', 'defineProperties', 'keys',
+ 'getPrototypeOf', 'getOwnPropertyDescriptor', 'getOwnPropertyNames',
+ 'preventExtensions', 'isExtensible', 'seal', 'isSealed', 'freeze', 'isFrozen'
+])('Date', Date, ['now']);
+
+Object.extend = extend.overloadSetter();
+
+Date.extend('now', function(){
+ return +(new Date);
+});
+
+new Type('Boolean', Boolean);
+
+// fixes NaN returning as Number
+
+Number.prototype.$family = function(){
+ return isFinite(this) ? 'number' : 'null';
+}.hide();
+
+// Number.random
+
+Number.extend('random', function(min, max){
+ return Math.floor(Math.random() * (max - min + 1) + min);
+});
+
+// forEach, each, keys
+
+Array.implement({
+
+ /*<!ES5>*/
+ forEach: function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (i in this) fn.call(bind, this[i], i, this);
+ }
+ },
+ /*</!ES5>*/
+
+ each: function(fn, bind){
+ Array.forEach(this, fn, bind);
+ return this;
+ }
+
+});
+
+Object.extend({
+
+ keys: function(object){
+ var keys = [];
+ for (var k in object){
+ if (hasOwnProperty.call(object, k)) keys.push(k);
+ }
+ /*<ltIE8>*/
+ forEachObjectEnumberableKey(object, function(k){
+ keys.push(k);
+ });
+ /*</ltIE8>*/
+ return keys;
+ },
+
+ forEach: function(object, fn, bind){
+ Object.keys(object).forEach(function(key){
+ fn.call(bind, object[key], key, object);
+ });
+ }
+
+});
+
+Object.each = Object.forEach;
+
+
+// Array & Object cloning, Object merging and appending
+
+var cloneOf = function(item){
+ switch (typeOf(item)){
+ case 'array': return item.clone();
+ case 'object': return Object.clone(item);
+ default: return item;
+ }
+};
+
+Array.implement('clone', function(){
+ var i = this.length, clone = new Array(i);
+ while (i--) clone[i] = cloneOf(this[i]);
+ return clone;
+});
+
+var mergeOne = function(source, key, current){
+ switch (typeOf(current)){
+ case 'object':
+ if (typeOf(source[key]) == 'object') Object.merge(source[key], current);
+ else source[key] = Object.clone(current);
+ break;
+ case 'array': source[key] = current.clone(); break;
+ default: source[key] = current;
+ }
+ return source;
+};
+
+Object.extend({
+
+ merge: function(source, k, v){
+ if (typeOf(k) == 'string') return mergeOne(source, k, v);
+ for (var i = 1, l = arguments.length; i < l; i++){
+ var object = arguments[i];
+ for (var key in object) mergeOne(source, key, object[key]);
+ }
+ return source;
+ },
+
+ clone: function(object){
+ var clone = {};
+ for (var key in object) clone[key] = cloneOf(object[key]);
+ return clone;
+ },
+
+ append: function(original){
+ for (var i = 1, l = arguments.length; i < l; i++){
+ var extended = arguments[i] || {};
+ for (var key in extended) original[key] = extended[key];
+ }
+ return original;
+ }
+
+});
+
+// Object-less types
+
+['Object', 'WhiteSpace', 'TextNode', 'Collection', 'Arguments'].each(function(name){
+ new Type(name);
+});
+
+// Unique ID
+
+var UID = Date.now();
+
+String.extend('uniqueID', function(){
+ return (UID++).toString(36);
+});
+
+
+
+})();
+
+/*
+---
+
+name: Array
+
+description: Contains Array Prototypes like each, contains, and erase.
+
+license: MIT-style license.
+
+requires: [Type]
+
+provides: Array
+
+...
+*/
+
+Array.implement({
+
+ /*<!ES5>*/
+ every: function(fn, bind){
+ for (var i = 0, l = this.length >>> 0; i < l; i++){
+ if ((i in this) && !fn.call(bind, this[i], i, this)) return false;
+ }
+ return true;
+ },
+
+ filter: function(fn, bind){
+ var results = [];
+ for (var value, i = 0, l = this.length >>> 0; i < l; i++) if (i in this){
+ value = this[i];
+ if (fn.call(bind, value, i, this)) results.push(value);
+ }
+ return results;
+ },
+
+ indexOf: function(item, from){
+ var length = this.length >>> 0;
+ for (var i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++){
+ if (this[i] === item) return i;
+ }
+ return -1;
+ },
+
+ map: function(fn, bind){
+ var length = this.length >>> 0, results = Array(length);
+ for (var i = 0; i < length; i++){
+ if (i in this) results[i] = fn.call(bind, this[i], i, this);
+ }
+ return results;
+ },
+
+ some: function(fn, bind){
+ for (var i = 0, l = this.length >>> 0; i < l; i++){
+ if ((i in this) && fn.call(bind, this[i], i, this)) return true;
+ }
+ return false;
+ },
+ /*</!ES5>*/
+
+ clean: function(){
+ return this.filter(function(item){
+ return item != null;
+ });
+ },
+
+ invoke: function(methodName){
+ var args = Array.slice(arguments, 1);
+ return this.map(function(item){
+ return item[methodName].apply(item, args);
+ });
+ },
+
+ associate: function(keys){
+ var obj = {}, length = Math.min(this.length, keys.length);
+ for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
+ return obj;
+ },
+
+ link: function(object){
+ var result = {};
+ for (var i = 0, l = this.length; i < l; i++){
+ for (var key in object){
+ if (object[key](this[i])){
+ result[key] = this[i];
+ delete object[key];
+ break;
+ }
+ }
+ }
+ return result;
+ },
+
+ contains: function(item, from){
+ return this.indexOf(item, from) != -1;
+ },
+
+ append: function(array){
+ this.push.apply(this, array);
+ return this;
+ },
+
+ getLast: function(){
+ return (this.length) ? this[this.length - 1] : null;
+ },
+
+ getRandom: function(){
+ return (this.length) ? this[Number.random(0, this.length - 1)] : null;
+ },
+
+ include: function(item){
+ if (!this.contains(item)) this.push(item);
+ return this;
+ },
+
+ combine: function(array){
+ for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
+ return this;
+ },
+
+ erase: function(item){
+ for (var i = this.length; i--;){
+ if (this[i] === item) this.splice(i, 1);
+ }
+ return this;
+ },
+
+ empty: function(){
+ this.length = 0;
+ return this;
+ },
+
+ flatten: function(){
+ var array = [];
+ for (var i = 0, l = this.length; i < l; i++){
+ var type = typeOf(this[i]);
+ if (type == 'null') continue;
+ array = array.concat((type == 'array' || type == 'collection' || type == 'arguments' || instanceOf(this[i], Array)) ? Array.flatten(this[i]) : this[i]);
+ }
+ return array;
+ },
+
+ pick: function(){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (this[i] != null) return this[i];
+ }
+ return null;
+ },
+
+ hexToRgb: function(array){
+ if (this.length != 3) return null;
+ var rgb = this.map(function(value){
+ if (value.length == 1) value += value;
+ return parseInt(value, 16);
+ });
+ return (array) ? rgb : 'rgb(' + rgb + ')';
+ },
+
+ rgbToHex: function(array){
+ if (this.length < 3) return null;
+ if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
+ var hex = [];
+ for (var i = 0; i < 3; i++){
+ var bit = (this[i] - 0).toString(16);
+ hex.push((bit.length == 1) ? '0' + bit : bit);
+ }
+ return (array) ? hex : '#' + hex.join('');
+ }
+
+});
+
+
+
+/*
+---
+
+name: Function
+
+description: Contains Function Prototypes like create, bind, pass, and delay.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Function
+
+...
+*/
+
+Function.extend({
+
+ attempt: function(){
+ for (var i = 0, l = arguments.length; i < l; i++){
+ try {
+ return arguments[i]();
+ } catch (e){}
+ }
+ return null;
+ }
+
+});
+
+Function.implement({
+
+ attempt: function(args, bind){
+ try {
+ return this.apply(bind, Array.convert(args));
+ } catch (e){}
+
+ return null;
+ },
+
+ /*<!ES5-bind>*/
+ bind: function(that){
+ var self = this,
+ args = arguments.length > 1 ? Array.slice(arguments, 1) : null,
+ F = function(){};
+
+ var bound = function(){
+ var context = that, length = arguments.length;
+ if (this instanceof bound){
+ F.prototype = self.prototype;
+ context = new F;
+ }
+ var result = (!args && !length)
+ ? self.call(context)
+ : self.apply(context, args && length ? args.concat(Array.slice(arguments)) : args || arguments);
+ return context == that ? result : context;
+ };
+ return bound;
+ },
+ /*</!ES5-bind>*/
+
+ pass: function(args, bind){
+ var self = this;
+ if (args != null) args = Array.convert(args);
+ return function(){
+ return self.apply(bind, args || arguments);
+ };
+ },
+
+ delay: function(delay, bind, args){
+ return setTimeout(this.pass((args == null ? [] : args), bind), delay);
+ },
+
+ periodical: function(periodical, bind, args){
+ return setInterval(this.pass((args == null ? [] : args), bind), periodical);
+ }
+
+});
+
+
+
+/*
+---
+
+name: Number
+
+description: Contains Number Prototypes like limit, round, times, and ceil.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Number
+
+...
+*/
+
+Number.implement({
+
+ limit: function(min, max){
+ return Math.min(max, Math.max(min, this));
+ },
+
+ round: function(precision){
+ precision = Math.pow(10, precision || 0).toFixed(precision < 0 ? -precision : 0);
+ return Math.round(this * precision) / precision;
+ },
+
+ times: function(fn, bind){
+ for (var i = 0; i < this; i++) fn.call(bind, i, this);
+ },
+
+ toFloat: function(){
+ return parseFloat(this);
+ },
+
+ toInt: function(base){
+ return parseInt(this, base || 10);
+ }
+
+});
+
+Number.alias('each', 'times');
+
+(function(math){
+
+var methods = {};
+
+math.each(function(name){
+ if (!Number[name]) methods[name] = function(){
+ return Math[name].apply(null, [this].concat(Array.convert(arguments)));
+ };
+});
+
+Number.implement(methods);
+
+})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']);
+
+/*
+---
+
+name: String
+
+description: Contains String Prototypes like camelCase, capitalize, test, and toInt.
+
+license: MIT-style license.
+
+requires: [Type, Array]
+
+provides: String
+
+...
+*/
+
+String.implement({
+
+ //<!ES6>
+ contains: function(string, index){
+ return (index ? String(this).slice(index) : String(this)).indexOf(string) > -1;
+ },
+ //</!ES6>
+
+ test: function(regex, params){
+ return ((typeOf(regex) == 'regexp') ? regex : new RegExp('' + regex, params)).test(this);
+ },
+
+ trim: function(){
+ return String(this).replace(/^\s+|\s+$/g, '');
+ },
+
+ clean: function(){
+ return String(this).replace(/\s+/g, ' ').trim();
+ },
+
+ camelCase: function(){
+ return String(this).replace(/-\D/g, function(match){
+ return match.charAt(1).toUpperCase();
+ });
+ },
+
+ hyphenate: function(){
+ return String(this).replace(/[A-Z]/g, function(match){
+ return ('-' + match.charAt(0).toLowerCase());
+ });
+ },
+
+ capitalize: function(){
+ return String(this).replace(/\b[a-z]/g, function(match){
+ return match.toUpperCase();
+ });
+ },
+
+ escapeRegExp: function(){
+ return String(this).replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
+ },
+
+ toInt: function(base){
+ return parseInt(this, base || 10);
+ },
+
+ toFloat: function(){
+ return parseFloat(this);
+ },
+
+ hexToRgb: function(array){
+ var hex = String(this).match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
+ return (hex) ? hex.slice(1).hexToRgb(array) : null;
+ },
+
+ rgbToHex: function(array){
+ var rgb = String(this).match(/\d{1,3}/g);
+ return (rgb) ? rgb.rgbToHex(array) : null;
+ },
+
+ substitute: function(object, regexp){
+ return String(this).replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){
+ if (match.charAt(0) == '\\') return match.slice(1);
+ return (object[name] != null) ? object[name] : '';
+ });
+ }
+
+});
+
+
+
+/*
+---
+
+name: Browser
+
+description: The Browser Object. Contains Browser initialization, Window and Document, and the Browser Hash.
+
+license: MIT-style license.
+
+requires: [Array, Function, Number, String]
+
+provides: [Browser, Window, Document]
+
+...
+*/
+
+(function(){
+
+var document = this.document;
+var window = document.window = this;
+
+var parse = function(ua, platform){
+ ua = ua.toLowerCase();
+ platform = (platform ? platform.toLowerCase() : '');
+
+ // chrome is included in the edge UA, so need to check for edge first,
+ // before checking if it's chrome.
+ var UA = ua.match(/(edge)[\s\/:]([\w\d\.]+)/);
+ if (!UA){
+ UA = ua.match(/(opera|ie|firefox|chrome|trident|crios|version)[\s\/:]([\w\d\.]+)?.*?(safari|(?:rv[\s\/:]|version[\s\/:])([\w\d\.]+)|$)/) || [null, 'unknown', 0];
+ }
+
+ if (UA[1] == 'trident'){
+ UA[1] = 'ie';
+ if (UA[4]) UA[2] = UA[4];
+ } else if (UA[1] == 'crios'){
+ UA[1] = 'chrome';
+ }
+
+ platform = ua.match(/ip(?:ad|od|hone)/) ? 'ios' : (ua.match(/(?:webos|android)/) || ua.match(/mac|win|linux/) || ['other'])[0];
+ if (platform == 'win') platform = 'windows';
+
+ return {
+ extend: Function.prototype.extend,
+ name: (UA[1] == 'version') ? UA[3] : UA[1],
+ version: parseFloat((UA[1] == 'opera' && UA[4]) ? UA[4] : UA[2]),
+ platform: platform
+ };
+};
+
+var Browser = this.Browser = parse(navigator.userAgent, navigator.platform);
+
+if (Browser.name == 'ie' && document.documentMode){
+ Browser.version = document.documentMode;
+}
+
+Browser.extend({
+ Features: {
+ xpath: !!(document.evaluate),
+ air: !!(window.runtime),
+ query: !!(document.querySelector),
+ json: !!(window.JSON)
+ },
+ parseUA: parse
+});
+
+
+
+// Request
+
+Browser.Request = (function(){
+
+ var XMLHTTP = function(){
+ return new XMLHttpRequest();
+ };
+
+ var MSXML2 = function(){
+ return new ActiveXObject('MSXML2.XMLHTTP');
+ };
+
+ var MSXML = function(){
+ return new ActiveXObject('Microsoft.XMLHTTP');
+ };
+
+ return Function.attempt(function(){
+ XMLHTTP();
+ return XMLHTTP;
+ }, function(){
+ MSXML2();
+ return MSXML2;
+ }, function(){
+ MSXML();
+ return MSXML;
+ });
+
+})();
+
+Browser.Features.xhr = !!(Browser.Request);
+
+
+
+// String scripts
+
+Browser.exec = function(text){
+ if (!text) return text;
+ if (window.execScript){
+ window.execScript(text);
+ } else {
+ var script = document.createElement('script');
+ script.setAttribute('type', 'text/javascript');
+ script.text = text;
+ document.head.appendChild(script);
+ document.head.removeChild(script);
+ }
+ return text;
+};
+
+String.implement('stripScripts', function(exec){
+ var scripts = '';
+ var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(all, code){
+ scripts += code + '\n';
+ return '';
+ });
+ if (exec === true) Browser.exec(scripts);
+ else if (typeOf(exec) == 'function') exec(scripts, text);
+ return text;
+});
+
+// Window, Document
+
+Browser.extend({
+ Document: this.Document,
+ Window: this.Window,
+ Element: this.Element,
+ Event: this.Event
+});
+
+this.Window = this.$constructor = new Type('Window', function(){});
+
+this.$family = Function.convert('window').hide();
+
+Window.mirror(function(name, method){
+ window[name] = method;
+});
+
+this.Document = document.$constructor = new Type('Document', function(){});
+
+document.$family = Function.convert('document').hide();
+
+Document.mirror(function(name, method){
+ document[name] = method;
+});
+
+document.html = document.documentElement;
+if (!document.head) document.head = document.getElementsByTagName('head')[0];
+
+if (document.execCommand) try {
+ document.execCommand('BackgroundImageCache', false, true);
+} catch (e){}
+
+/*<ltIE9>*/
+if (this.attachEvent && !this.addEventListener){
+ var unloadEvent = function(){
+ this.detachEvent('onunload', unloadEvent);
+ document.head = document.html = document.window = null;
+ window = this.Window = document = null;
+ };
+ this.attachEvent('onunload', unloadEvent);
+}
+
+// IE fails on collections and <select>.options (refers to <select>)
+var arrayFrom = Array.convert;
+try {
+ arrayFrom(document.html.childNodes);
+} catch (e){
+ Array.convert = function(item){
+ if (typeof item != 'string' && Type.isEnumerable(item) && typeOf(item) != 'array'){
+ var i = item.length, array = new Array(i);
+ while (i--) array[i] = item[i];
+ return array;
+ }
+ return arrayFrom(item);
+ };
+
+ var prototype = Array.prototype,
+ slice = prototype.slice;
+ ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice'].each(function(name){
+ var method = prototype[name];
+ Array[name] = function(item){
+ return method.apply(Array.convert(item), slice.call(arguments, 1));
+ };
+ });
+}
+/*</ltIE9>*/
+
+
+
+})();
+
+/*
+---
+
+name: Class
+
+description: Contains the Class Function for easily creating, extending, and implementing reusable Classes.
+
+license: MIT-style license.
+
+requires: [Array, String, Function, Number]
+
+provides: Class
+
+...
+*/
+
+(function(){
+
+var Class = this.Class = new Type('Class', function(params){
+ if (instanceOf(params, Function)) params = {initialize: params};
+
+ var newClass = function(){
+ reset(this);
+ if (newClass.$prototyping) return this;
+ this.$caller = null;
+ this.$family = null;
+ var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
+ this.$caller = this.caller = null;
+ return value;
+ }.extend(this).implement(params);
+
+ newClass.$constructor = Class;
+ newClass.prototype.$constructor = newClass;
+ newClass.prototype.parent = parent;
+
+ return newClass;
+});
+
+var parent = function(){
+ if (!this.$caller) throw new Error('The method "parent" cannot be called.');
+ var name = this.$caller.$name,
+ parent = this.$caller.$owner.parent,
+ previous = (parent) ? parent.prototype[name] : null;
+ if (!previous) throw new Error('The method "' + name + '" has no parent.');
+ return previous.apply(this, arguments);
+};
+
+var reset = function(object){
+ for (var key in object){
+ var value = object[key];
+ switch (typeOf(value)){
+ case 'object':
+ var F = function(){};
+ F.prototype = value;
+ object[key] = reset(new F);
+ break;
+ case 'array': object[key] = value.clone(); break;
+ }
+ }
+ return object;
+};
+
+var wrap = function(self, key, method){
+ if (method.$origin) method = method.$origin;
+ var wrapper = function(){
+ if (method.$protected && this.$caller == null) throw new Error('The method "' + key + '" cannot be called.');
+ var caller = this.caller, current = this.$caller;
+ this.caller = current; this.$caller = wrapper;
+ var result = method.apply(this, arguments);
+ this.$caller = current; this.caller = caller;
+ return result;
+ }.extend({$owner: self, $origin: method, $name: key});
+ return wrapper;
+};
+
+var implement = function(key, value, retain){
+ if (Class.Mutators.hasOwnProperty(key)){
+ value = Class.Mutators[key].call(this, value);
+ if (value == null) return this;
+ }
+
+ if (typeOf(value) == 'function'){
+ if (value.$hidden) return this;
+ this.prototype[key] = (retain) ? value : wrap(this, key, value);
+ } else {
+ Object.merge(this.prototype, key, value);
+ }
+
+ return this;
+};
+
+var getInstance = function(klass){
+ klass.$prototyping = true;
+ var proto = new klass;
+ delete klass.$prototyping;
+ return proto;
+};
+
+Class.implement('implement', implement.overloadSetter());
+
+Class.Mutators = {
+
+ Extends: function(parent){
+ this.parent = parent;
+ this.prototype = getInstance(parent);
+ },
+
+ Implements: function(items){
+ Array.convert(items).each(function(item){
+ var instance = new item;
+ for (var key in instance) implement.call(this, key, instance[key], true);
+ }, this);
+ }
+};
+
+})();
+
+/*
+---
+
+name: Class.Extras
+
+description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks.
+
+license: MIT-style license.
+
+requires: Class
+
+provides: [Class.Extras, Chain, Events, Options]
+
+...
+*/
+
+(function(){
+
+this.Chain = new Class({
+
+ $chain: [],
+
+ chain: function(){
+ this.$chain.append(Array.flatten(arguments));
+ return this;
+ },
+
+ callChain: function(){
+ return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false;
+ },
+
+ clearChain: function(){
+ this.$chain.empty();
+ return this;
+ }
+
+});
+
+var removeOn = function(string){
+ return string.replace(/^on([A-Z])/, function(full, first){
+ return first.toLowerCase();
+ });
+};
+
+this.Events = new Class({
+
+ $events: {},
+
+ addEvent: function(type, fn, internal){
+ type = removeOn(type);
+
+
+
+ this.$events[type] = (this.$events[type] || []).include(fn);
+ if (internal) fn.internal = true;
+ return this;
+ },
+
+ addEvents: function(events){
+ for (var type in events) this.addEvent(type, events[type]);
+ return this;
+ },
+
+ fireEvent: function(type, args, delay){
+ type = removeOn(type);
+ var events = this.$events[type];
+ if (!events) return this;
+ args = Array.convert(args);
+ events.each(function(fn){
+ if (delay) fn.delay(delay, this, args);
+ else fn.apply(this, args);
+ }, this);
+ return this;
+ },
+
+ removeEvent: function(type, fn){
+ type = removeOn(type);
+ var events = this.$events[type];
+ if (events && !fn.internal){
+ var index = events.indexOf(fn);
+ if (index != -1) delete events[index];
+ }
+ return this;
+ },
+
+ removeEvents: function(events){
+ var type;
+ if (typeOf(events) == 'object'){
+ for (type in events) this.removeEvent(type, events[type]);
+ return this;
+ }
+ if (events) events = removeOn(events);
+ for (type in this.$events){
+ if (events && events != type) continue;
+ var fns = this.$events[type];
+ for (var i = fns.length; i--;) if (i in fns){
+ this.removeEvent(type, fns[i]);
+ }
+ }
+ return this;
+ }
+
+});
+
+this.Options = new Class({
+
+ setOptions: function(){
+ var options = this.options = Object.merge.apply(null, [{}, this.options].append(arguments));
+ if (this.addEvent) for (var option in options){
+ if (typeOf(options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue;
+ this.addEvent(option, options[option]);
+ delete options[option];
+ }
+ return this;
+ }
+
+});
+
+})();
+
+/*
+---
+
+name: Class.Thenable
+
+description: Contains a Utility Class that can be implemented into your own Classes to make them "thenable".
+
+license: MIT-style license.
+
+requires: Class
+
+provides: [Class.Thenable]
+
+...
+*/
+
+(function(){
+
+var STATE_PENDING = 0,
+ STATE_FULFILLED = 1,
+ STATE_REJECTED = 2;
+
+var Thenable = Class.Thenable = new Class({
+
+ $thenableState: STATE_PENDING,
+ $thenableResult: null,
+ $thenableReactions: [],
+
+ resolve: function(value){
+ resolve(this, value);
+ return this;
+ },
+
+ reject: function(reason){
+ reject(this, reason);
+ return this;
+ },
+
+ getThenableState: function(){
+ switch (this.$thenableState){
+ case STATE_PENDING:
+ return 'pending';
+
+ case STATE_FULFILLED:
+ return 'fulfilled';
+
+ case STATE_REJECTED:
+ return 'rejected';
+ }
+ },
+
+ resetThenable: function(reason){
+ reject(this, reason);
+ reset(this);
+ return this;
+ },
+
+ then: function(onFulfilled, onRejected){
+ if (typeof onFulfilled !== 'function') onFulfilled = 'Identity';
+ if (typeof onRejected !== 'function') onRejected = 'Thrower';
+
+ var thenable = new Thenable();
+
+ this.$thenableReactions.push({
+ thenable: thenable,
+ fulfillHandler: onFulfilled,
+ rejectHandler: onRejected
+ });
+
+ if (this.$thenableState !== STATE_PENDING){
+ react(this);
+ }
+
+ return thenable;
+ },
+
+ 'catch': function(onRejected){
+ return this.then(null, onRejected);
+ }
+
+});
+
+Thenable.extend({
+ resolve: function(value){
+ var thenable;
+ if (value instanceof Thenable){
+ thenable = value;
+ } else {
+ thenable = new Thenable();
+ resolve(thenable, value);
+ }
+ return thenable;
+ },
+ reject: function(reason){
+ var thenable = new Thenable();
+ reject(thenable, reason);
+ return thenable;
+ }
+});
+
+// Private functions
+
+function resolve(thenable, value){
+ if (thenable.$thenableState === STATE_PENDING){
+ if (thenable === value){
+ reject(thenable, new TypeError('Tried to resolve a thenable with itself.'));
+ } else if (value && (typeof value === 'object' || typeof value === 'function')){
+ var then;
+ try {
+ then = value.then;
+ } catch (exception){
+ reject(thenable, exception);
+ }
+ if (typeof then === 'function'){
+ var resolved = false;
+ defer(function(){
+ try {
+ then.call(
+ value,
+ function(nextValue){
+ if (!resolved){
+ resolved = true;
+ resolve(thenable, nextValue);
+ }
+ },
+ function(reason){
+ if (!resolved){
+ resolved = true;
+ reject(thenable, reason);
+ }
+ }
+ );
+ } catch (exception){
+ if (!resolved){
+ resolved = true;
+ reject(thenable, exception);
+ }
+ }
+ });
+ } else {
+ fulfill(thenable, value);
+ }
+ } else {
+ fulfill(thenable, value);
+ }
+ }
+}
+
+function fulfill(thenable, value){
+ if (thenable.$thenableState === STATE_PENDING){
+ thenable.$thenableResult = value;
+ thenable.$thenableState = STATE_FULFILLED;
+
+ react(thenable);
+ }
+}
+
+function reject(thenable, reason){
+ if (thenable.$thenableState === STATE_PENDING){
+ thenable.$thenableResult = reason;
+ thenable.$thenableState = STATE_REJECTED;
+
+ react(thenable);
+ }
+}
+
+function reset(thenable){
+ if (thenable.$thenableState !== STATE_PENDING){
+ thenable.$thenableResult = null;
+ thenable.$thenableState = STATE_PENDING;
+ }
+}
+
+function react(thenable){
+ var state = thenable.$thenableState,
+ result = thenable.$thenableResult,
+ reactions = thenable.$thenableReactions,
+ type;
+
+ if (state === STATE_FULFILLED){
+ thenable.$thenableReactions = [];
+ type = 'fulfillHandler';
+ } else if (state == STATE_REJECTED){
+ thenable.$thenableReactions = [];
+ type = 'rejectHandler';
+ }
+
+ if (type){
+ defer(handle.pass([result, reactions, type]));
+ }
+}
+
+function handle(result, reactions, type){
+ for (var i = 0, l = reactions.length; i < l; ++i){
+ var reaction = reactions[i],
+ handler = reaction[type];
+
+ if (handler === 'Identity'){
+ resolve(reaction.thenable, result);
+ } else if (handler === 'Thrower'){
+ reject(reaction.thenable, result);
+ } else {
+ try {
+ resolve(reaction.thenable, handler(result));
+ } catch (exception){
+ reject(reaction.thenable, exception);
+ }
+ }
+ }
+}
+
+var defer;
+if (typeof process !== 'undefined' && typeof process.nextTick === 'function'){
+ defer = process.nextTick;
+} else if (typeof setImmediate !== 'undefined'){
+ defer = setImmediate;
+} else {
+ defer = function(fn){
+ setTimeout(fn, 0);
+ };
+}
+
+})();
+
+/*
+---
+
+name: Object
+
+description: Object generic methods
+
+license: MIT-style license.
+
+requires: Type
+
+provides: [Object, Hash]
+
+...
+*/
+
+(function(){
+
+Object.extend({
+
+ subset: function(object, keys){
+ var results = {};
+ for (var i = 0, l = keys.length; i < l; i++){
+ var k = keys[i];
+ if (k in object) results[k] = object[k];
+ }
+ return results;
+ },
+
+ map: function(object, fn, bind){
+ var results = {};
+ var keys = Object.keys(object);
+ for (var i = 0; i < keys.length; i++){
+ var key = keys[i];
+ results[key] = fn.call(bind, object[key], key, object);
+ }
+ return results;
+ },
+
+ filter: function(object, fn, bind){
+ var results = {};
+ var keys = Object.keys(object);
+ for (var i = 0; i < keys.length; i++){
+ var key = keys[i], value = object[key];
+ if (fn.call(bind, value, key, object)) results[key] = value;
+ }
+ return results;
+ },
+
+ every: function(object, fn, bind){
+ var keys = Object.keys(object);
+ for (var i = 0; i < keys.length; i++){
+ var key = keys[i];
+ if (!fn.call(bind, object[key], key)) return false;
+ }
+ return true;
+ },
+
+ some: function(object, fn, bind){
+ var keys = Object.keys(object);
+ for (var i = 0; i < keys.length; i++){
+ var key = keys[i];
+ if (fn.call(bind, object[key], key)) return true;
+ }
+ return false;
+ },
+
+ values: function(object){
+ var values = [];
+ var keys = Object.keys(object);
+ for (var i = 0; i < keys.length; i++){
+ var k = keys[i];
+ values.push(object[k]);
+ }
+ return values;
+ },
+
+ getLength: function(object){
+ return Object.keys(object).length;
+ },
+
+ keyOf: function(object, value){
+ var keys = Object.keys(object);
+ for (var i = 0; i < keys.length; i++){
+ var key = keys[i];
+ if (object[key] === value) return key;
+ }
+ return null;
+ },
+
+ contains: function(object, value){
+ return Object.keyOf(object, value) != null;
+ },
+
+ toQueryString: function(object, base){
+ var queryString = [];
+
+ Object.each(object, function(value, key){
+ if (base) key = base + '[' + key + ']';
+ var result;
+ switch (typeOf(value)){
+ case 'object': result = Object.toQueryString(value, key); break;
+ case 'array':
+ var qs = {};
+ value.each(function(val, i){
+ qs[i] = val;
+ });
+ result = Object.toQueryString(qs, key);
+ break;
+ default: result = key + '=' + encodeURIComponent(value);
+ }
+ if (value != null) queryString.push(result);
+ });
+
+ return queryString.join('&');
+ }
+
+});
+
+})();
+
+
+
+/*
+---
+name: Slick.Parser
+description: Standalone CSS3 Selector parser
+provides: Slick.Parser
+...
+*/
+
+;(function(){
+
+var parsed,
+ separatorIndex,
+ combinatorIndex,
+ reversed,
+ cache = {},
+ reverseCache = {},
+ reUnescape = /\\/g;
+
+var parse = function(expression, isReversed){
+ if (expression == null) return null;
+ if (expression.Slick === true) return expression;
+ expression = ('' + expression).replace(/^\s+|\s+$/g, '');
+ reversed = !!isReversed;
+ var currentCache = (reversed) ? reverseCache : cache;
+ if (currentCache[expression]) return currentCache[expression];
+ parsed = {
+ Slick: true,
+ expressions: [],
+ raw: expression,
+ reverse: function(){
+ return parse(this.raw, true);
+ }
+ };
+ separatorIndex = -1;
+ while (expression != (expression = expression.replace(regexp, parser)));
+ parsed.length = parsed.expressions.length;
+ return currentCache[parsed.raw] = (reversed) ? reverse(parsed) : parsed;
+};
+
+var reverseCombinator = function(combinator){
+ if (combinator === '!') return ' ';
+ else if (combinator === ' ') return '!';
+ else if ((/^!/).test(combinator)) return combinator.replace(/^!/, '');
+ else return '!' + combinator;
+};
+
+var reverse = function(expression){
+ var expressions = expression.expressions;
+ for (var i = 0; i < expressions.length; i++){
+ var exp = expressions[i];
+ var last = {parts: [], tag: '*', combinator: reverseCombinator(exp[0].combinator)};
+
+ for (var j = 0; j < exp.length; j++){
+ var cexp = exp[j];
+ if (!cexp.reverseCombinator) cexp.reverseCombinator = ' ';
+ cexp.combinator = cexp.reverseCombinator;
+ delete cexp.reverseCombinator;
+ }
+
+ exp.reverse().push(last);
+ }
+ return expression;
+};
+
+var escapeRegExp = function(string){// Credit: XRegExp 0.6.1 (c) 2007-2008 Steven Levithan <http://stevenlevithan.com/regex/xregexp/> MIT License
+ return string.replace(/[-[\]{}()*+?.\\^$|,#\s]/g, function(match){
+ return '\\' + match;
+ });
+};
+
+var regexp = new RegExp(
+/*
+#!/usr/bin/env ruby
+puts "\t\t" + DATA.read.gsub(/\(\?x\)|\s+#.*$|\s+|\\$|\\n/,'')
+__END__
+ "(?x)^(?:\
+ \\s* ( , ) \\s* # Separator \n\
+ | \\s* ( <combinator>+ ) \\s* # Combinator \n\
+ | ( \\s+ ) # CombinatorChildren \n\
+ | ( <unicode>+ | \\* ) # Tag \n\
+ | \\# ( <unicode>+ ) # ID \n\
+ | \\. ( <unicode>+ ) # ClassName \n\
+ | # Attribute \n\
+ \\[ \
+ \\s* (<unicode1>+) (?: \
+ \\s* ([*^$!~|]?=) (?: \
+ \\s* (?:\
+ ([\"']?)(.*?)\\9 \
+ )\
+ ) \
+ )? \\s* \
+ \\](?!\\]) \n\
+ | :+ ( <unicode>+ )(?:\
+ \\( (?:\
+ (?:([\"'])([^\\12]*)\\12)|((?:\\([^)]+\\)|[^()]*)+)\
+ ) \\)\
+ )?\
+ )"
+*/
+ "^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)"
+ .replace(/<combinator>/, '[' + escapeRegExp('>+~`!@$%^&={}\\;</') + ']')
+ .replace(/<unicode>/g, '(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
+ .replace(/<unicode1>/g, '(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
+);
+
+function parser(
+ rawMatch,
+
+ separator,
+ combinator,
+ combinatorChildren,
+
+ tagName,
+ id,
+ className,
+
+ attributeKey,
+ attributeOperator,
+ attributeQuote,
+ attributeValue,
+
+ pseudoMarker,
+ pseudoClass,
+ pseudoQuote,
+ pseudoClassQuotedValue,
+ pseudoClassValue
+){
+ if (separator || separatorIndex === -1){
+ parsed.expressions[++separatorIndex] = [];
+ combinatorIndex = -1;
+ if (separator) return '';
+ }
+
+ if (combinator || combinatorChildren || combinatorIndex === -1){
+ combinator = combinator || ' ';
+ var currentSeparator = parsed.expressions[separatorIndex];
+ if (reversed && currentSeparator[combinatorIndex])
+ currentSeparator[combinatorIndex].reverseCombinator = reverseCombinator(combinator);
+ currentSeparator[++combinatorIndex] = {combinator: combinator, tag: '*'};
+ }
+
+ var currentParsed = parsed.expressions[separatorIndex][combinatorIndex];
+
+ if (tagName){
+ currentParsed.tag = tagName.replace(reUnescape, '');
+
+ } else if (id){
+ currentParsed.id = id.replace(reUnescape, '');
+
+ } else if (className){
+ className = className.replace(reUnescape, '');
+
+ if (!currentParsed.classList) currentParsed.classList = [];
+ if (!currentParsed.classes) currentParsed.classes = [];
+ currentParsed.classList.push(className);
+ currentParsed.classes.push({
+ value: className,
+ regexp: new RegExp('(^|\\s)' + escapeRegExp(className) + '(\\s|$)')
+ });
+
+ } else if (pseudoClass){
+ pseudoClassValue = pseudoClassValue || pseudoClassQuotedValue;
+ pseudoClassValue = pseudoClassValue ? pseudoClassValue.replace(reUnescape, '') : null;
+
+ if (!currentParsed.pseudos) currentParsed.pseudos = [];
+ currentParsed.pseudos.push({
+ key: pseudoClass.replace(reUnescape, ''),
+ value: pseudoClassValue,
+ type: pseudoMarker.length == 1 ? 'class' : 'element'
+ });
+
+ } else if (attributeKey){
+ attributeKey = attributeKey.replace(reUnescape, '');
+ attributeValue = (attributeValue || '').replace(reUnescape, '');
+
+ var test, regexp;
+
+ switch (attributeOperator){
+ case '^=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) ); break;
+ case '$=' : regexp = new RegExp( escapeRegExp(attributeValue) +'$' ); break;
+ case '~=' : regexp = new RegExp( '(^|\\s)'+ escapeRegExp(attributeValue) +'(\\s|$)' ); break;
+ case '|=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) +'(-|$)' ); break;
+ case '=' : test = function(value){
+ return attributeValue == value;
+ }; break;
+ case '*=' : test = function(value){
+ return value && value.indexOf(attributeValue) > -1;
+ }; break;
+ case '!=' : test = function(value){
+ return attributeValue != value;
+ }; break;
+ default : test = function(value){
+ return !!value;
+ };
+ }
+
+ if (attributeValue == '' && (/^[*$^]=$/).test(attributeOperator)) test = function(){
+ return false;
+ };
+
+ if (!test) test = function(value){
+ return value && regexp.test(value);
+ };
+
+ if (!currentParsed.attributes) currentParsed.attributes = [];
+ currentParsed.attributes.push({
+ key: attributeKey,
+ operator: attributeOperator,
+ value: attributeValue,
+ test: test
+ });
+
+ }
+
+ return '';
+};
+
+// Slick NS
+
+var Slick = (this.Slick || {});
+
+Slick.parse = function(expression){
+ return parse(expression);
+};
+
+Slick.escapeRegExp = escapeRegExp;
+
+if (!this.Slick) this.Slick = Slick;
+
+}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this);
+
+/*
+---
+name: Slick.Finder
+description: The new, superfast css selector engine.
+provides: Slick.Finder
+requires: Slick.Parser
+...
+*/
+
+;(function(){
+
+var local = {},
+ featuresCache = {},
+ toString = Object.prototype.toString;
+
+// Feature / Bug detection
+
+local.isNativeCode = function(fn){
+ return (/\{\s*\[native code\]\s*\}/).test('' + fn);
+};
+
+local.isXML = function(document){
+ return (!!document.xmlVersion) || (!!document.xml) || (toString.call(document) == '[object XMLDocument]') ||
+ (document.nodeType == 9 && document.documentElement.nodeName != 'HTML');
+};
+
+local.setDocument = function(document){
+
+ // convert elements / window arguments to document. if document cannot be extrapolated, the function returns.
+ var nodeType = document.nodeType;
+ if (nodeType == 9); // document
+ else if (nodeType) document = document.ownerDocument; // node
+ else if (document.navigator) document = document.document; // window
+ else return;
+
+ // check if it's the old document
+
+ if (this.document === document) return;
+ this.document = document;
+
+ // check if we have done feature detection on this document before
+
+ var root = document.documentElement,
+ rootUid = this.getUIDXML(root),
+ features = featuresCache[rootUid],
+ feature;
+
+ if (features){
+ for (feature in features){
+ this[feature] = features[feature];
+ }
+ return;
+ }
+
+ features = featuresCache[rootUid] = {};
+
+ features.root = root;
+ features.isXMLDocument = this.isXML(document);
+
+ features.brokenStarGEBTN
+ = features.starSelectsClosedQSA
+ = features.idGetsName
+ = features.brokenMixedCaseQSA
+ = features.brokenGEBCN
+ = features.brokenCheckedQSA
+ = features.brokenEmptyAttributeQSA
+ = features.isHTMLDocument
+ = features.nativeMatchesSelector
+ = false;
+
+ var starSelectsClosed, starSelectsComments,
+ brokenSecondClassNameGEBCN, cachedGetElementsByClassName,
+ brokenFormAttributeGetter;
+
+ var selected, id = 'slick_uniqueid';
+ var testNode = document.createElement('div');
+
+ var testRoot = document.body || document.getElementsByTagName('body')[0] || root;
+ testRoot.appendChild(testNode);
+
+ // on non-HTML documents innerHTML and getElementsById doesnt work properly
+ try {
+ testNode.innerHTML = '<a id="'+id+'"></a>';
+ features.isHTMLDocument = !!document.getElementById(id);
+ } catch (e){}
+
+ if (features.isHTMLDocument){
+
+ testNode.style.display = 'none';
+
+ // IE returns comment nodes for getElementsByTagName('*') for some documents
+ testNode.appendChild(document.createComment(''));
+ starSelectsComments = (testNode.getElementsByTagName('*').length > 1);
+
+ // IE returns closed nodes (EG:"</foo>") for getElementsByTagName('*') for some documents
+ try {
+ testNode.innerHTML = 'foo</foo>';
+ selected = testNode.getElementsByTagName('*');
+ starSelectsClosed = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
+ } catch (e){};
+
+ features.brokenStarGEBTN = starSelectsComments || starSelectsClosed;
+
+ // IE returns elements with the name instead of just id for getElementsById for some documents
+ try {
+ testNode.innerHTML = '<a name="'+ id +'"></a><b id="'+ id +'"></b>';
+ features.idGetsName = document.getElementById(id) === testNode.firstChild;
+ } catch (e){}
+
+ if (testNode.getElementsByClassName){
+
+ // Safari 3.2 getElementsByClassName caches results
+ try {
+ testNode.innerHTML = '<a class="f"></a><a class="b"></a>';
+ testNode.getElementsByClassName('b').length;
+ testNode.firstChild.className = 'b';
+ cachedGetElementsByClassName = (testNode.getElementsByClassName('b').length != 2);
+ } catch (e){};
+
+ // Opera 9.6 getElementsByClassName doesnt detects the class if its not the first one
+ try {
+ testNode.innerHTML = '<a class="a"></a><a class="f b a"></a>';
+ brokenSecondClassNameGEBCN = (testNode.getElementsByClassName('a').length != 2);
+ } catch (e){}
+
+ features.brokenGEBCN = cachedGetElementsByClassName || brokenSecondClassNameGEBCN;
+ }
+
+ if (testNode.querySelectorAll){
+ // IE 8 returns closed nodes (EG:"</foo>") for querySelectorAll('*') for some documents
+ try {
+ testNode.innerHTML = 'foo</foo>';
+ selected = testNode.querySelectorAll('*');
+ features.starSelectsClosedQSA = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
+ } catch (e){}
+
+ // Safari 3.2 querySelectorAll doesnt work with mixedcase on quirksmode
+ try {
+ testNode.innerHTML = '<a class="MiX"></a>';
+ features.brokenMixedCaseQSA = !testNode.querySelectorAll('.MiX').length;
+ } catch (e){}
+
+ // Webkit and Opera dont return selected options on querySelectorAll
+ try {
+ testNode.innerHTML = '<select><option selected="selected">a</option></select>';
+ features.brokenCheckedQSA = (testNode.querySelectorAll(':checked').length == 0);
+ } catch (e){};
+
+ // IE returns incorrect results for attr[*^$]="" selectors on querySelectorAll
+ try {
+ testNode.innerHTML = '<a class=""></a>';
+ features.brokenEmptyAttributeQSA = (testNode.querySelectorAll('[class*=""]').length != 0);
+ } catch (e){}
+
+ }
+
+ // IE6-7, if a form has an input of id x, form.getAttribute(x) returns a reference to the input
+ try {
+ testNode.innerHTML = '<form action="s"><input id="action"/></form>';
+ brokenFormAttributeGetter = (testNode.firstChild.getAttribute('action') != 's');
+ } catch (e){}
+
+ // native matchesSelector function
+
+ features.nativeMatchesSelector = root.matches || /*root.msMatchesSelector ||*/ root.mozMatchesSelector || root.webkitMatchesSelector;
+ if (features.nativeMatchesSelector) try {
+ // if matchesSelector trows errors on incorrect sintaxes we can use it
+ features.nativeMatchesSelector.call(root, ':slick');
+ features.nativeMatchesSelector = null;
+ } catch (e){}
+
+ }
+
+ try {
+ root.slick_expando = 1;
+ delete root.slick_expando;
+ features.getUID = this.getUIDHTML;
+ } catch (e){
+ features.getUID = this.getUIDXML;
+ }
+
+ testRoot.removeChild(testNode);
+ testNode = selected = testRoot = null;
+
+ // getAttribute
+
+ features.getAttribute = (features.isHTMLDocument && brokenFormAttributeGetter) ? function(node, name){
+ var method = this.attributeGetters[name];
+ if (method) return method.call(node);
+ var attributeNode = node.getAttributeNode(name);
+ return (attributeNode) ? attributeNode.nodeValue : null;
+ } : function(node, name){
+ var method = this.attributeGetters[name];
+ return (method) ? method.call(node) : node.getAttribute(name);
+ };
+
+ // hasAttribute
+
+ features.hasAttribute = (root && this.isNativeCode(root.hasAttribute)) ? function(node, attribute){
+ return node.hasAttribute(attribute);
+ } : function(node, attribute){
+ node = node.getAttributeNode(attribute);
+ return !!(node && (node.specified || node.nodeValue));
+ };
+
+ // contains
+ // FIXME: Add specs: local.contains should be different for xml and html documents?
+ var nativeRootContains = root && this.isNativeCode(root.contains),
+ nativeDocumentContains = document && this.isNativeCode(document.contains);
+
+ features.contains = (nativeRootContains && nativeDocumentContains) ? function(context, node){
+ return context.contains(node);
+ } : (nativeRootContains && !nativeDocumentContains) ? function(context, node){
+ // IE8 does not have .contains on document.
+ return context === node || ((context === document) ? document.documentElement : context).contains(node);
+ } : (root && root.compareDocumentPosition) ? function(context, node){
+ return context === node || !!(context.compareDocumentPosition(node) & 16);
+ } : function(context, node){
+ if (node) do {
+ if (node === context) return true;
+ } while ((node = node.parentNode));
+ return false;
+ };
+
+ // document order sorting
+ // credits to Sizzle (http://sizzlejs.com/)
+
+ features.documentSorter = (root.compareDocumentPosition) ? function(a, b){
+ if (!a.compareDocumentPosition || !b.compareDocumentPosition) return 0;
+ return a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
+ } : ('sourceIndex' in root) ? function(a, b){
+ if (!a.sourceIndex || !b.sourceIndex) return 0;
+ return a.sourceIndex - b.sourceIndex;
+ } : (document.createRange) ? function(a, b){
+ if (!a.ownerDocument || !b.ownerDocument) return 0;
+ var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
+ aRange.setStart(a, 0);
+ aRange.setEnd(a, 0);
+ bRange.setStart(b, 0);
+ bRange.setEnd(b, 0);
+ return aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
+ } : null;
+
+ root = null;
+
+ for (feature in features){
+ this[feature] = features[feature];
+ }
+};
+
+// Main Method
+
+var reSimpleSelector = /^([#.]?)((?:[\w-]+|\*))$/,
+ reEmptyAttribute = /\[.+[*$^]=(?:""|'')?\]/,
+ qsaFailExpCache = {};
+
+local.search = function(context, expression, append, first){
+
+ var found = this.found = (first) ? null : (append || []);
+
+ if (!context) return found;
+ else if (context.navigator) context = context.document; // Convert the node from a window to a document
+ else if (!context.nodeType) return found;
+
+ // setup
+
+ var parsed, i, node, nodes,
+ uniques = this.uniques = {},
+ hasOthers = !!(append && append.length),
+ contextIsDocument = (context.nodeType == 9);
+
+ if (this.document !== (contextIsDocument ? context : context.ownerDocument)) this.setDocument(context);
+
+ // avoid duplicating items already in the append array
+ if (hasOthers) for (i = found.length; i--;) uniques[this.getUID(found[i])] = true;
+
+ // expression checks
+
+ if (typeof expression == 'string'){ // expression is a string
+
+ /*<simple-selectors-override>*/
+ var simpleSelector = expression.match(reSimpleSelector);
+ simpleSelectors: if (simpleSelector){
+
+ var symbol = simpleSelector[1],
+ name = simpleSelector[2];
+
+ if (!symbol){
+
+ if (name == '*' && this.brokenStarGEBTN) break simpleSelectors;
+ nodes = context.getElementsByTagName(name);
+ if (first) return nodes[0] || null;
+ for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+
+ } else if (symbol == '#'){
+
+ if (!this.isHTMLDocument || !contextIsDocument) break simpleSelectors;
+ node = context.getElementById(name);
+ if (!node) return found;
+ if (this.idGetsName && node.getAttributeNode('id').nodeValue != name) break simpleSelectors;
+ if (first) return node || null;
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+
+ } else if (symbol == '.'){
+
+ if (!this.isHTMLDocument || ((!context.getElementsByClassName || this.brokenGEBCN) && context.querySelectorAll)) break simpleSelectors;
+ if (context.getElementsByClassName && !this.brokenGEBCN){
+ nodes = context.getElementsByClassName(name);
+ if (first) return nodes[0] || null;
+ for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+ } else {
+ var matchClass = new RegExp('(^|\\s)'+ Slick.escapeRegExp(name) +'(\\s|$)');
+ nodes = context.getElementsByTagName('*');
+ for (i = 0; node = nodes[i++];){
+ className = node.className;
+ if (!(className && matchClass.test(className))) continue;
+ if (first) return node;
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+ }
+
+ }
+
+ if (hasOthers) this.sort(found);
+ return (first) ? null : found;
+
+ }
+ /*</simple-selectors-override>*/
+
+ /*<query-selector-override>*/
+ querySelector: if (context.querySelectorAll){
+
+ if (!this.isHTMLDocument
+ || qsaFailExpCache[expression]
+ //TODO: only skip when expression is actually mixed case
+ || this.brokenMixedCaseQSA
+ || (this.brokenCheckedQSA && expression.indexOf(':checked') > -1)
+ || (this.brokenEmptyAttributeQSA && reEmptyAttribute.test(expression))
+ || (!contextIsDocument //Abort when !contextIsDocument and...
+ // there are multiple expressions in the selector
+ // since we currently only fix non-document rooted QSA for single expression selectors
+ && expression.indexOf(',') > -1
+ )
+ || Slick.disableQSA
+ ) break querySelector;
+
+ var _expression = expression, _context = context, currentId;
+ if (!contextIsDocument){
+ // non-document rooted QSA
+ // credits to Andrew Dupont
+ currentId = _context.getAttribute('id'), slickid = 'slickid__';
+ _context.setAttribute('id', slickid);
+ _expression = '#' + slickid + ' ' + _expression;
+ context = _context.parentNode;
+ }
+
+ try {
+ if (first) return context.querySelector(_expression) || null;
+ else nodes = context.querySelectorAll(_expression);
+ } catch (e){
+ qsaFailExpCache[expression] = 1;
+ break querySelector;
+ } finally {
+ if (!contextIsDocument){
+ if (currentId) _context.setAttribute('id', currentId);
+ else _context.removeAttribute('id');
+ context = _context;
+ }
+ }
+
+ if (this.starSelectsClosedQSA) for (i = 0; node = nodes[i++];){
+ if (node.nodeName > '@' && !(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ } else for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+
+ if (hasOthers) this.sort(found);
+ return found;
+
+ }
+ /*</query-selector-override>*/
+
+ parsed = this.Slick.parse(expression);
+ if (!parsed.length) return found;
+ } else if (expression == null){ // there is no expression
+ return found;
+ } else if (expression.Slick){ // expression is a parsed Slick object
+ parsed = expression;
+ } else if (this.contains(context.documentElement || context, expression)){ // expression is a node
+ (found) ? found.push(expression) : found = expression;
+ return found;
+ } else { // other junk
+ return found;
+ }
+
+ /*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
+
+ // cache elements for the nth selectors
+
+ this.posNTH = {};
+ this.posNTHLast = {};
+ this.posNTHType = {};
+ this.posNTHTypeLast = {};
+
+ /*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
+
+ // if append is null and there is only a single selector with one expression use pushArray, else use pushUID
+ this.push = (!hasOthers && (first || (parsed.length == 1 && parsed.expressions[0].length == 1))) ? this.pushArray : this.pushUID;
+
+ if (found == null) found = [];
+
+ // default engine
+
+ var j, m, n;
+ var combinator, tag, id, classList, classes, attributes, pseudos;
+ var currentItems, currentExpression, currentBit, lastBit, expressions = parsed.expressions;
+
+ search: for (i = 0; (currentExpression = expressions[i]); i++) for (j = 0; (currentBit = currentExpression[j]); j++){
+
+ combinator = 'combinator:' + currentBit.combinator;
+ if (!this[combinator]) continue search;
+
+ tag = (this.isXMLDocument) ? currentBit.tag : currentBit.tag.toUpperCase();
+ id = currentBit.id;
+ classList = currentBit.classList;
+ classes = currentBit.classes;
+ attributes = currentBit.attributes;
+ pseudos = currentBit.pseudos;
+ lastBit = (j === (currentExpression.length - 1));
+
+ this.bitUniques = {};
+
+ if (lastBit){
+ this.uniques = uniques;
+ this.found = found;
+ } else {
+ this.uniques = {};
+ this.found = [];
+ }
+
+ if (j === 0){
+ this[combinator](context, tag, id, classes, attributes, pseudos, classList);
+ if (first && lastBit && found.length) break search;
+ } else {
+ if (first && lastBit) for (m = 0, n = currentItems.length; m < n; m++){
+ this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
+ if (found.length) break search;
+ } else for (m = 0, n = currentItems.length; m < n; m++) this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
+ }
+
+ currentItems = this.found;
+ }
+
+ // should sort if there are nodes in append and if you pass multiple expressions.
+ if (hasOthers || (parsed.expressions.length > 1)) this.sort(found);
+
+ return (first) ? (found[0] || null) : found;
+};
+
+// Utils
+
+local.uidx = 1;
+local.uidk = 'slick-uniqueid';
+
+local.getUIDXML = function(node){
+ var uid = node.getAttribute(this.uidk);
+ if (!uid){
+ uid = this.uidx++;
+ node.setAttribute(this.uidk, uid);
+ }
+ return uid;
+};
+
+local.getUIDHTML = function(node){
+ return node.uniqueNumber || (node.uniqueNumber = this.uidx++);
+};
+
+// sort based on the setDocument documentSorter method.
+
+local.sort = function(results){
+ if (!this.documentSorter) return results;
+ results.sort(this.documentSorter);
+ return results;
+};
+
+/*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
+
+local.cacheNTH = {};
+
+local.matchNTH = /^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/;
+
+local.parseNTHArgument = function(argument){
+ var parsed = argument.match(this.matchNTH);
+ if (!parsed) return false;
+ var special = parsed[2] || false;
+ var a = parsed[1] || 1;
+ if (a == '-') a = -1;
+ var b = +parsed[3] || 0;
+ parsed =
+ (special == 'n') ? {a: a, b: b} :
+ (special == 'odd') ? {a: 2, b: 1} :
+ (special == 'even') ? {a: 2, b: 0} : {a: 0, b: a};
+
+ return (this.cacheNTH[argument] = parsed);
+};
+
+local.createNTHPseudo = function(child, sibling, positions, ofType){
+ return function(node, argument){
+ var uid = this.getUID(node);
+ if (!this[positions][uid]){
+ var parent = node.parentNode;
+ if (!parent) return false;
+ var el = parent[child], count = 1;
+ if (ofType){
+ var nodeName = node.nodeName;
+ do {
+ if (el.nodeName != nodeName) continue;
+ this[positions][this.getUID(el)] = count++;
+ } while ((el = el[sibling]));
+ } else {
+ do {
+ if (el.nodeType != 1) continue;
+ this[positions][this.getUID(el)] = count++;
+ } while ((el = el[sibling]));
+ }
+ }
+ argument = argument || 'n';
+ var parsed = this.cacheNTH[argument] || this.parseNTHArgument(argument);
+ if (!parsed) return false;
+ var a = parsed.a, b = parsed.b, pos = this[positions][uid];
+ if (a == 0) return b == pos;
+ if (a > 0){
+ if (pos < b) return false;
+ } else {
+ if (b < pos) return false;
+ }
+ return ((pos - b) % a) == 0;
+ };
+};
+
+/*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
+
+local.pushArray = function(node, tag, id, classes, attributes, pseudos){
+ if (this.matchSelector(node, tag, id, classes, attributes, pseudos)) this.found.push(node);
+};
+
+local.pushUID = function(node, tag, id, classes, attributes, pseudos){
+ var uid = this.getUID(node);
+ if (!this.uniques[uid] && this.matchSelector(node, tag, id, classes, attributes, pseudos)){
+ this.uniques[uid] = true;
+ this.found.push(node);
+ }
+};
+
+local.matchNode = function(node, selector){
+ if (this.isHTMLDocument && this.nativeMatchesSelector){
+ try {
+ return this.nativeMatchesSelector.call(node, selector.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g, '[$1="$2"]'));
+ } catch (matchError){}
+ }
+
+ var parsed = this.Slick.parse(selector);
+ if (!parsed) return true;
+
+ // simple (single) selectors
+ var expressions = parsed.expressions, simpleExpCounter = 0, i, currentExpression;
+ for (i = 0; (currentExpression = expressions[i]); i++){
+ if (currentExpression.length == 1){
+ var exp = currentExpression[0];
+ if (this.matchSelector(node, (this.isXMLDocument) ? exp.tag : exp.tag.toUpperCase(), exp.id, exp.classes, exp.attributes, exp.pseudos)) return true;
+ simpleExpCounter++;
+ }
+ }
+
+ if (simpleExpCounter == parsed.length) return false;
+
+ var nodes = this.search(this.document, parsed), item;
+ for (i = 0; item = nodes[i++];){
+ if (item === node) return true;
+ }
+ return false;
+};
+
+local.matchPseudo = function(node, name, argument){
+ var pseudoName = 'pseudo:' + name;
+ if (this[pseudoName]) return this[pseudoName](node, argument);
+ var attribute = this.getAttribute(node, name);
+ return (argument) ? argument == attribute : !!attribute;
+};
+
+local.matchSelector = function(node, tag, id, classes, attributes, pseudos){
+ if (tag){
+ var nodeName = (this.isXMLDocument) ? node.nodeName : node.nodeName.toUpperCase();
+ if (tag == '*'){
+ if (nodeName < '@') return false; // Fix for comment nodes and closed nodes
+ } else {
+ if (nodeName != tag) return false;
+ }
+ }
+
+ if (id && node.getAttribute('id') != id) return false;
+
+ var i, part, cls;
+ if (classes) for (i = classes.length; i--;){
+ cls = this.getAttribute(node, 'class');
+ if (!(cls && classes[i].regexp.test(cls))) return false;
+ }
+ if (attributes) for (i = attributes.length; i--;){
+ part = attributes[i];
+ if (part.operator ? !part.test(this.getAttribute(node, part.key)) : !this.hasAttribute(node, part.key)) return false;
+ }
+ if (pseudos) for (i = pseudos.length; i--;){
+ part = pseudos[i];
+ if (!this.matchPseudo(node, part.key, part.value)) return false;
+ }
+ return true;
+};
+
+var combinators = {
+
+ ' ': function(node, tag, id, classes, attributes, pseudos, classList){ // all child nodes, any level
+
+ var i, item, children;
+
+ if (this.isHTMLDocument){
+ getById: if (id){
+ item = this.document.getElementById(id);
+ if ((!item && node.all) || (this.idGetsName && item && item.getAttributeNode('id').nodeValue != id)){
+ // all[id] returns all the elements with that name or id inside node
+ // if theres just one it will return the element, else it will be a collection
+ children = node.all[id];
+ if (!children) return;
+ if (!children[0]) children = [children];
+ for (i = 0; item = children[i++];){
+ var idNode = item.getAttributeNode('id');
+ if (idNode && idNode.nodeValue == id){
+ this.push(item, tag, null, classes, attributes, pseudos);
+ break;
+ }
+ }
+ return;
+ }
+ if (!item){
+ // if the context is in the dom we return, else we will try GEBTN, breaking the getById label
+ if (this.contains(this.root, node)) return;
+ else break getById;
+ } else if (this.document !== node && !this.contains(node, item)) return;
+ this.push(item, tag, null, classes, attributes, pseudos);
+ return;
+ }
+ getByClass: if (classes && node.getElementsByClassName && !this.brokenGEBCN){
+ children = node.getElementsByClassName(classList.join(' '));
+ if (!(children && children.length)) break getByClass;
+ for (i = 0; item = children[i++];) this.push(item, tag, id, null, attributes, pseudos);
+ return;
+ }
+ }
+ getByTag: {
+ children = node.getElementsByTagName(tag);
+ if (!(children && children.length)) break getByTag;
+ if (!this.brokenStarGEBTN) tag = null;
+ for (i = 0; item = children[i++];) this.push(item, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '>': function(node, tag, id, classes, attributes, pseudos){ // direct children
+ if ((node = node.firstChild)) do {
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ } while ((node = node.nextSibling));
+ },
+
+ '+': function(node, tag, id, classes, attributes, pseudos){ // next sibling
+ while ((node = node.nextSibling)) if (node.nodeType == 1){
+ this.push(node, tag, id, classes, attributes, pseudos);
+ break;
+ }
+ },
+
+ '^': function(node, tag, id, classes, attributes, pseudos){ // first child
+ node = node.firstChild;
+ if (node){
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ else this['combinator:+'](node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '~': function(node, tag, id, classes, attributes, pseudos){ // next siblings
+ while ((node = node.nextSibling)){
+ if (node.nodeType != 1) continue;
+ var uid = this.getUID(node);
+ if (this.bitUniques[uid]) break;
+ this.bitUniques[uid] = true;
+ this.push(node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '++': function(node, tag, id, classes, attributes, pseudos){ // next sibling and previous sibling
+ this['combinator:+'](node, tag, id, classes, attributes, pseudos);
+ this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
+ },
+
+ '~~': function(node, tag, id, classes, attributes, pseudos){ // next siblings and previous siblings
+ this['combinator:~'](node, tag, id, classes, attributes, pseudos);
+ this['combinator:!~'](node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!': function(node, tag, id, classes, attributes, pseudos){ // all parent nodes up to document
+ while ((node = node.parentNode)) if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!>': function(node, tag, id, classes, attributes, pseudos){ // direct parent (one level)
+ node = node.parentNode;
+ if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!+': function(node, tag, id, classes, attributes, pseudos){ // previous sibling
+ while ((node = node.previousSibling)) if (node.nodeType == 1){
+ this.push(node, tag, id, classes, attributes, pseudos);
+ break;
+ }
+ },
+
+ '!^': function(node, tag, id, classes, attributes, pseudos){ // last child
+ node = node.lastChild;
+ if (node){
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ else this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '!~': function(node, tag, id, classes, attributes, pseudos){ // previous siblings
+ while ((node = node.previousSibling)){
+ if (node.nodeType != 1) continue;
+ var uid = this.getUID(node);
+ if (this.bitUniques[uid]) break;
+ this.bitUniques[uid] = true;
+ this.push(node, tag, id, classes, attributes, pseudos);
+ }
+ }
+
+};
+
+for (var c in combinators) local['combinator:' + c] = combinators[c];
+
+var pseudos = {
+
+ /*<pseudo-selectors>*/
+
+ 'empty': function(node){
+ var child = node.firstChild;
+ return !(child && child.nodeType == 1) && !(node.innerText || node.textContent || '').length;
+ },
+
+ 'not': function(node, expression){
+ return !this.matchNode(node, expression);
+ },
+
+ 'contains': function(node, text){
+ return (node.innerText || node.textContent || '').indexOf(text) > -1;
+ },
+
+ 'first-child': function(node){
+ while ((node = node.previousSibling)) if (node.nodeType == 1) return false;
+ return true;
+ },
+
+ 'last-child': function(node){
+ while ((node = node.nextSibling)) if (node.nodeType == 1) return false;
+ return true;
+ },
+
+ 'only-child': function(node){
+ var prev = node;
+ while ((prev = prev.previousSibling)) if (prev.nodeType == 1) return false;
+ var next = node;
+ while ((next = next.nextSibling)) if (next.nodeType == 1) return false;
+ return true;
+ },
+
+ /*<nth-pseudo-selectors>*/
+
+ 'nth-child': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTH'),
+
+ 'nth-last-child': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHLast'),
+
+ 'nth-of-type': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTHType', true),
+
+ 'nth-last-of-type': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHTypeLast', true),
+
+ 'index': function(node, index){
+ return this['pseudo:nth-child'](node, '' + (index + 1));
+ },
+
+ 'even': function(node){
+ return this['pseudo:nth-child'](node, '2n');
+ },
+
+ 'odd': function(node){
+ return this['pseudo:nth-child'](node, '2n+1');
+ },
+
+ /*</nth-pseudo-selectors>*/
+
+ /*<of-type-pseudo-selectors>*/
+
+ 'first-of-type': function(node){
+ var nodeName = node.nodeName;
+ while ((node = node.previousSibling)) if (node.nodeName == nodeName) return false;
+ return true;
+ },
+
+ 'last-of-type': function(node){
+ var nodeName = node.nodeName;
+ while ((node = node.nextSibling)) if (node.nodeName == nodeName) return false;
+ return true;
+ },
+
+ 'only-of-type': function(node){
+ var prev = node, nodeName = node.nodeName;
+ while ((prev = prev.previousSibling)) if (prev.nodeName == nodeName) return false;
+ var next = node;
+ while ((next = next.nextSibling)) if (next.nodeName == nodeName) return false;
+ return true;
+ },
+
+ /*</of-type-pseudo-selectors>*/
+
+ // custom pseudos
+
+ 'enabled': function(node){
+ return !node.disabled;
+ },
+
+ 'disabled': function(node){
+ return node.disabled;
+ },
+
+ 'checked': function(node){
+ return node.checked || node.selected;
+ },
+
+ 'focus': function(node){
+ return this.isHTMLDocument && this.document.activeElement === node && (node.href || node.type || this.hasAttribute(node, 'tabindex'));
+ },
+
+ 'root': function(node){
+ return (node === this.root);
+ },
+
+ 'selected': function(node){
+ return node.selected;
+ }
+
+ /*</pseudo-selectors>*/
+};
+
+for (var p in pseudos) local['pseudo:' + p] = pseudos[p];
+
+// attributes methods
+
+var attributeGetters = local.attributeGetters = {
+
+ 'for': function(){
+ return ('htmlFor' in this) ? this.htmlFor : this.getAttribute('for');
+ },
+
+ 'href': function(){
+ return ('href' in this) ? this.getAttribute('href', 2) : this.getAttribute('href');
+ },
+
+ 'style': function(){
+ return (this.style) ? this.style.cssText : this.getAttribute('style');
+ },
+
+ 'tabindex': function(){
+ var attributeNode = this.getAttributeNode('tabindex');
+ return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
+ },
+
+ 'type': function(){
+ return this.getAttribute('type');
+ },
+
+ 'maxlength': function(){
+ var attributeNode = this.getAttributeNode('maxLength');
+ return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
+ }
+
+};
+
+attributeGetters.MAXLENGTH = attributeGetters.maxLength = attributeGetters.maxlength;
+
+// Slick
+
+var Slick = local.Slick = (this.Slick || {});
+
+Slick.version = '1.1.7';
+
+// Slick finder
+
+Slick.search = function(context, expression, append){
+ return local.search(context, expression, append);
+};
+
+Slick.find = function(context, expression){
+ return local.search(context, expression, null, true);
+};
+
+// Slick containment checker
+
+Slick.contains = function(container, node){
+ local.setDocument(container);
+ return local.contains(container, node);
+};
+
+// Slick attribute getter
+
+Slick.getAttribute = function(node, name){
+ local.setDocument(node);
+ return local.getAttribute(node, name);
+};
+
+Slick.hasAttribute = function(node, name){
+ local.setDocument(node);
+ return local.hasAttribute(node, name);
+};
+
+// Slick matcher
+
+Slick.match = function(node, selector){
+ if (!(node && selector)) return false;
+ if (!selector || selector === node) return true;
+ local.setDocument(node);
+ return local.matchNode(node, selector);
+};
+
+// Slick attribute accessor
+
+Slick.defineAttributeGetter = function(name, fn){
+ local.attributeGetters[name] = fn;
+ return this;
+};
+
+Slick.lookupAttributeGetter = function(name){
+ return local.attributeGetters[name];
+};
+
+// Slick pseudo accessor
+
+Slick.definePseudo = function(name, fn){
+ local['pseudo:' + name] = function(node, argument){
+ return fn.call(node, argument);
+ };
+ return this;
+};
+
+Slick.lookupPseudo = function(name){
+ var pseudo = local['pseudo:' + name];
+ if (pseudo) return function(argument){
+ return pseudo.call(this, argument);
+ };
+ return null;
+};
+
+// Slick overrides accessor
+
+Slick.override = function(regexp, fn){
+ local.override(regexp, fn);
+ return this;
+};
+
+Slick.isXML = local.isXML;
+
+Slick.uidOf = function(node){
+ return local.getUIDHTML(node);
+};
+
+if (!this.Slick) this.Slick = Slick;
+
+}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this);
+
+/*
+---
+
+name: Element
+
+description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements.
+
+license: MIT-style license.
+
+requires: [Window, Document, Array, String, Function, Object, Number, Slick.Parser, Slick.Finder]
+
+provides: [Element, Elements, $, $$, IFrame, Selectors]
+
+...
+*/
+
+var Element = this.Element = function(tag, props){
+ var konstructor = Element.Constructors[tag];
+ if (konstructor) return konstructor(props);
+ if (typeof tag != 'string') return document.id(tag).set(props);
+
+ if (!props) props = {};
+
+ if (!(/^[\w-]+$/).test(tag)){
+ var parsed = Slick.parse(tag).expressions[0][0];
+ tag = (parsed.tag == '*') ? 'div' : parsed.tag;
+ if (parsed.id && props.id == null) props.id = parsed.id;
+
+ var attributes = parsed.attributes;
+ if (attributes) for (var attr, i = 0, l = attributes.length; i < l; i++){
+ attr = attributes[i];
+ if (props[attr.key] != null) continue;
+
+ if (attr.value != null && attr.operator == '=') props[attr.key] = attr.value;
+ else if (!attr.value && !attr.operator) props[attr.key] = true;
+ }
+
+ if (parsed.classList && props['class'] == null) props['class'] = parsed.classList.join(' ');
+ }
+
+ return document.newElement(tag, props);
+};
+
+
+if (Browser.Element){
+ Element.prototype = Browser.Element.prototype;
+ // IE8 and IE9 require the wrapping.
+ Element.prototype._fireEvent = (function(fireEvent){
+ return function(type, event){
+ return fireEvent.call(this, type, event);
+ };
+ })(Element.prototype.fireEvent);
+}
+
+new Type('Element', Element).mirror(function(name){
+ if (Array.prototype[name]) return;
+
+ var obj = {};
+ obj[name] = function(){
+ var results = [], args = arguments, elements = true;
+ for (var i = 0, l = this.length; i < l; i++){
+ var element = this[i], result = results[i] = element[name].apply(element, args);
+ elements = (elements && typeOf(result) == 'element');
+ }
+ return (elements) ? new Elements(results) : results;
+ };
+
+ Elements.implement(obj);
+});
+
+if (!Browser.Element){
+ Element.parent = Object;
+
+ Element.Prototype = {
+ '$constructor': Element,
+ '$family': Function.convert('element').hide()
+ };
+
+ Element.mirror(function(name, method){
+ Element.Prototype[name] = method;
+ });
+}
+
+Element.Constructors = {};
+
+
+
+var IFrame = new Type('IFrame', function(){
+ var params = Array.link(arguments, {
+ properties: Type.isObject,
+ iframe: function(obj){
+ return (obj != null);
+ }
+ });
+
+ var props = params.properties || {}, iframe;
+ if (params.iframe) iframe = document.id(params.iframe);
+ var onload = props.onload || function(){};
+ delete props.onload;
+ props.id = props.name = [props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + String.uniqueID()].pick();
+ iframe = new Element(iframe || 'iframe', props);
+
+ var onLoad = function(){
+ onload.call(iframe.contentWindow);
+ };
+
+ if (window.frames[props.id]) onLoad();
+ else iframe.addListener('load', onLoad);
+ return iframe;
+});
+
+var Elements = this.Elements = function(nodes){
+ if (nodes && nodes.length){
+ var uniques = {}, node;
+ for (var i = 0; node = nodes[i++];){
+ var uid = Slick.uidOf(node);
+ if (!uniques[uid]){
+ uniques[uid] = true;
+ this.push(node);
+ }
+ }
+ }
+};
+
+Elements.prototype = {length: 0};
+Elements.parent = Array;
+
+new Type('Elements', Elements).implement({
+
+ filter: function(filter, bind){
+ if (!filter) return this;
+ return new Elements(Array.filter(this, (typeOf(filter) == 'string') ? function(item){
+ return item.match(filter);
+ } : filter, bind));
+ }.protect(),
+
+ push: function(){
+ var length = this.length;
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = document.id(arguments[i]);
+ if (item) this[length++] = item;
+ }
+ return (this.length = length);
+ }.protect(),
+
+ unshift: function(){
+ var items = [];
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = document.id(arguments[i]);
+ if (item) items.push(item);
+ }
+ return Array.prototype.unshift.apply(this, items);
+ }.protect(),
+
+ concat: function(){
+ var newElements = new Elements(this);
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = arguments[i];
+ if (Type.isEnumerable(item)) newElements.append(item);
+ else newElements.push(item);
+ }
+ return newElements;
+ }.protect(),
+
+ append: function(collection){
+ for (var i = 0, l = collection.length; i < l; i++) this.push(collection[i]);
+ return this;
+ }.protect(),
+
+ empty: function(){
+ while (this.length) delete this[--this.length];
+ return this;
+ }.protect()
+
+});
+
+
+
+(function(){
+
+// FF, IE
+var splice = Array.prototype.splice, object = {'0': 0, '1': 1, length: 2};
+
+splice.call(object, 1, 1);
+if (object[1] == 1) Elements.implement('splice', function(){
+ var length = this.length;
+ var result = splice.apply(this, arguments);
+ while (length >= this.length) delete this[length--];
+ return result;
+}.protect());
+
+Array.forEachMethod(function(method, name){
+ Elements.implement(name, method);
+});
+
+Array.mirror(Elements);
+
+/*<ltIE8>*/
+var createElementAcceptsHTML;
+try {
+ createElementAcceptsHTML = (document.createElement('<input name=x>').name == 'x');
+} catch (e){}
+
+var escapeQuotes = function(html){
+ return ('' + html).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
+};
+/*</ltIE8>*/
+
+/*<ltIE9>*/
+// #2479 - IE8 Cannot set HTML of style element
+var canChangeStyleHTML = (function(){
+ var div = document.createElement('style'),
+ flag = false;
+ try {
+ div.innerHTML = '#justTesing{margin: 0px;}';
+ flag = !!div.innerHTML;
+ } catch (e){}
+ return flag;
+})();
+/*</ltIE9>*/
+
+Document.implement({
+
+ newElement: function(tag, props){
+ if (props){
+ if (props.checked != null) props.defaultChecked = props.checked;
+ if ((props.type == 'checkbox' || props.type == 'radio') && props.value == null) props.value = 'on';
+ /*<ltIE9>*/ // IE needs the type to be set before changing content of style element
+ if (!canChangeStyleHTML && tag == 'style'){
+ var styleElement = document.createElement('style');
+ styleElement.setAttribute('type', 'text/css');
+ if (props.type) delete props.type;
+ return this.id(styleElement).set(props);
+ }
+ /*</ltIE9>*/
+ /*<ltIE8>*/// Fix for readonly name and type properties in IE < 8
+ if (createElementAcceptsHTML){
+ tag = '<' + tag;
+ if (props.name) tag += ' name="' + escapeQuotes(props.name) + '"';
+ if (props.type) tag += ' type="' + escapeQuotes(props.type) + '"';
+ tag += '>';
+ delete props.name;
+ delete props.type;
+ }
+ /*</ltIE8>*/
+ }
+ return this.id(this.createElement(tag)).set(props);
+ }
+
+});
+
+})();
+
+(function(){
+
+Slick.uidOf(window);
+Slick.uidOf(document);
+
+Document.implement({
+
+ newTextNode: function(text){
+ return this.createTextNode(text);
+ },
+
+ getDocument: function(){
+ return this;
+ },
+
+ getWindow: function(){
+ return this.window;
+ },
+
+ id: (function(){
+
+ var types = {
+
+ string: function(id, nocash, doc){
+ id = Slick.find(doc, '#' + id.replace(/(\W)/g, '\\$1'));
+ return (id) ? types.element(id, nocash) : null;
+ },
+
+ element: function(el, nocash){
+ Slick.uidOf(el);
+ if (!nocash && !el.$family && !(/^(?:object|embed)$/i).test(el.tagName)){
+ var fireEvent = el.fireEvent;
+ // wrapping needed in IE7, or else crash
+ el._fireEvent = function(type, event){
+ return fireEvent(type, event);
+ };
+ Object.append(el, Element.Prototype);
+ }
+ return el;
+ },
+
+ object: function(obj, nocash, doc){
+ if (obj.toElement) return types.element(obj.toElement(doc), nocash);
+ return null;
+ }
+
+ };
+
+ types.textnode = types.whitespace = types.window = types.document = function(zero){
+ return zero;
+ };
+
+ return function(el, nocash, doc){
+ if (el && el.$family && el.uniqueNumber) return el;
+ var type = typeOf(el);
+ return (types[type]) ? types[type](el, nocash, doc || document) : null;
+ };
+
+ })()
+
+});
+
+if (window.$ == null) Window.implement('$', function(el, nc){
+ return document.id(el, nc, this.document);
+});
+
+Window.implement({
+
+ getDocument: function(){
+ return this.document;
+ },
+
+ getWindow: function(){
+ return this;
+ }
+
+});
+
+[Document, Element].invoke('implement', {
+
+ getElements: function(expression){
+ return Slick.search(this, expression, new Elements);
+ },
+
+ getElement: function(expression){
+ return document.id(Slick.find(this, expression));
+ }
+
+});
+
+var contains = {contains: function(element){
+ return Slick.contains(this, element);
+}};
+
+if (!document.contains) Document.implement(contains);
+if (!document.createElement('div').contains) Element.implement(contains);
+
+
+
+// tree walking
+
+var injectCombinator = function(expression, combinator){
+ if (!expression) return combinator;
+
+ expression = Object.clone(Slick.parse(expression));
+
+ var expressions = expression.expressions;
+ for (var i = expressions.length; i--;)
+ expressions[i][0].combinator = combinator;
+
+ return expression;
+};
+
+Object.forEach({
+ getNext: '~',
+ getPrevious: '!~',
+ getParent: '!'
+}, function(combinator, method){
+ Element.implement(method, function(expression){
+ return this.getElement(injectCombinator(expression, combinator));
+ });
+});
+
+Object.forEach({
+ getAllNext: '~',
+ getAllPrevious: '!~',
+ getSiblings: '~~',
+ getChildren: '>',
+ getParents: '!'
+}, function(combinator, method){
+ Element.implement(method, function(expression){
+ return this.getElements(injectCombinator(expression, combinator));
+ });
+});
+
+Element.implement({
+
+ getFirst: function(expression){
+ return document.id(Slick.search(this, injectCombinator(expression, '>'))[0]);
+ },
+
+ getLast: function(expression){
+ return document.id(Slick.search(this, injectCombinator(expression, '>')).getLast());
+ },
+
+ getWindow: function(){
+ return this.ownerDocument.window;
+ },
+
+ getDocument: function(){
+ return this.ownerDocument;
+ },
+
+ getElementById: function(id){
+ return document.id(Slick.find(this, '#' + ('' + id).replace(/(\W)/g, '\\$1')));
+ },
+
+ match: function(expression){
+ return !expression || Slick.match(this, expression);
+ }
+
+});
+
+
+
+if (window.$$ == null) Window.implement('$$', function(selector){
+ if (arguments.length == 1){
+ if (typeof selector == 'string') return Slick.search(this.document, selector, new Elements);
+ else if (Type.isEnumerable(selector)) return new Elements(selector);
+ }
+ return new Elements(arguments);
+});
+
+// Inserters
+
+var inserters = {
+
+ before: function(context, element){
+ var parent = element.parentNode;
+ if (parent) parent.insertBefore(context, element);
+ },
+
+ after: function(context, element){
+ var parent = element.parentNode;
+ if (parent) parent.insertBefore(context, element.nextSibling);
+ },
+
+ bottom: function(context, element){
+ element.appendChild(context);
+ },
+
+ top: function(context, element){
+ element.insertBefore(context, element.firstChild);
+ }
+
+};
+
+inserters.inside = inserters.bottom;
+
+
+
+// getProperty / setProperty
+
+var propertyGetters = {}, propertySetters = {};
+
+// properties
+
+var properties = {};
+Array.forEach([
+ 'type', 'value', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan',
+ 'frameBorder', 'rowSpan', 'tabIndex', 'useMap'
+], function(property){
+ properties[property.toLowerCase()] = property;
+});
+
+properties.html = 'innerHTML';
+properties.text = (document.createElement('div').textContent == null) ? 'innerText': 'textContent';
+
+Object.forEach(properties, function(real, key){
+ propertySetters[key] = function(node, value){
+ node[real] = value;
+ };
+ propertyGetters[key] = function(node){
+ return node[real];
+ };
+});
+
+/*<ltIE9>*/
+propertySetters.text = (function(){
+ return function(node, value){
+ if (node.get('tag') == 'style') node.set('html', value);
+ else node[properties.text] = value;
+ };
+})(propertySetters.text);
+
+propertyGetters.text = (function(getter){
+ return function(node){
+ return (node.get('tag') == 'style') ? node.innerHTML : getter(node);
+ };
+})(propertyGetters.text);
+/*</ltIE9>*/
+
+// Booleans
+
+var bools = [
+ 'compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked',
+ 'disabled', 'readOnly', 'multiple', 'selected', 'noresize',
+ 'defer', 'defaultChecked', 'autofocus', 'controls', 'autoplay',
+ 'loop'
+];
+
+var booleans = {};
+Array.forEach(bools, function(bool){
+ var lower = bool.toLowerCase();
+ booleans[lower] = bool;
+ propertySetters[lower] = function(node, value){
+ node[bool] = !!value;
+ };
+ propertyGetters[lower] = function(node){
+ return !!node[bool];
+ };
+});
+
+// Special cases
+
+Object.append(propertySetters, {
+
+ 'class': function(node, value){
+ ('className' in node) ? node.className = (value || '') : node.setAttribute('class', value);
+ },
+
+ 'for': function(node, value){
+ ('htmlFor' in node) ? node.htmlFor = value : node.setAttribute('for', value);
+ },
+
+ 'style': function(node, value){
+ (node.style) ? node.style.cssText = value : node.setAttribute('style', value);
+ },
+
+ 'value': function(node, value){
+ node.value = (value != null) ? value : '';
+ }
+
+});
+
+propertyGetters['class'] = function(node){
+ return ('className' in node) ? node.className || null : node.getAttribute('class');
+};
+
+/* <webkit> */
+var el = document.createElement('button');
+// IE sets type as readonly and throws
+try { el.type = 'button'; } catch (e){}
+if (el.type != 'button') propertySetters.type = function(node, value){
+ node.setAttribute('type', value);
+};
+el = null;
+/* </webkit> */
+
+/*<IE>*/
+
+/*<ltIE9>*/
+// #2479 - IE8 Cannot set HTML of style element
+var canChangeStyleHTML = (function(){
+ var div = document.createElement('style'),
+ flag = false;
+ try {
+ div.innerHTML = '#justTesing{margin: 0px;}';
+ flag = !!div.innerHTML;
+ } catch (e){}
+ return flag;
+})();
+/*</ltIE9>*/
+
+var input = document.createElement('input'), volatileInputValue, html5InputSupport;
+
+// #2178
+input.value = 't';
+input.type = 'submit';
+volatileInputValue = input.value != 't';
+
+// #2443 - IE throws "Invalid Argument" when trying to use html5 input types
+try {
+ input.value = '';
+ input.type = 'email';
+ html5InputSupport = input.type == 'email';
+} catch (e){}
+
+input = null;
+
+if (volatileInputValue || !html5InputSupport) propertySetters.type = function(node, type){
+ try {
+ var value = node.value;
+ node.type = type;
+ node.value = value;
+ } catch (e){}
+};
+/*</IE>*/
+
+/* getProperty, setProperty */
+
+/* <ltIE9> */
+var pollutesGetAttribute = (function(div){
+ div.random = 'attribute';
+ return (div.getAttribute('random') == 'attribute');
+})(document.createElement('div'));
+
+var hasCloneBug = (function(test){
+ test.innerHTML = '<object><param name="should_fix" value="the unknown" /></object>';
+ return test.cloneNode(true).firstChild.childNodes.length != 1;
+})(document.createElement('div'));
+/* </ltIE9> */
+
+var hasClassList = !!document.createElement('div').classList;
+
+var classes = function(className){
+ var classNames = (className || '').clean().split(' '), uniques = {};
+ return classNames.filter(function(className){
+ if (className !== '' && !uniques[className]) return uniques[className] = className;
+ });
+};
+
+var addToClassList = function(name){
+ this.classList.add(name);
+};
+
+var removeFromClassList = function(name){
+ this.classList.remove(name);
+};
+
+Element.implement({
+
+ setProperty: function(name, value){
+ var setter = propertySetters[name.toLowerCase()];
+ if (setter){
+ setter(this, value);
+ } else {
+ /* <ltIE9> */
+ var attributeWhiteList;
+ if (pollutesGetAttribute) attributeWhiteList = this.retrieve('$attributeWhiteList', {});
+ /* </ltIE9> */
+
+ if (value == null){
+ this.removeAttribute(name);
+ /* <ltIE9> */
+ if (pollutesGetAttribute) delete attributeWhiteList[name];
+ /* </ltIE9> */
+ } else {
+ this.setAttribute(name, '' + value);
+ /* <ltIE9> */
+ if (pollutesGetAttribute) attributeWhiteList[name] = true;
+ /* </ltIE9> */
+ }
+ }
+ return this;
+ },
+
+ setProperties: function(attributes){
+ for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
+ return this;
+ },
+
+ getProperty: function(name){
+ var getter = propertyGetters[name.toLowerCase()];
+ if (getter) return getter(this);
+ /* <ltIE9> */
+ if (pollutesGetAttribute){
+ var attr = this.getAttributeNode(name), attributeWhiteList = this.retrieve('$attributeWhiteList', {});
+ if (!attr) return null;
+ if (attr.expando && !attributeWhiteList[name]){
+ var outer = this.outerHTML;
+ // segment by the opening tag and find mention of attribute name
+ if (outer.substr(0, outer.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(name) < 0) return null;
+ attributeWhiteList[name] = true;
+ }
+ }
+ /* </ltIE9> */
+ var result = Slick.getAttribute(this, name);
+ return (!result && !Slick.hasAttribute(this, name)) ? null : result;
+ },
+
+ getProperties: function(){
+ var args = Array.convert(arguments);
+ return args.map(this.getProperty, this).associate(args);
+ },
+
+ removeProperty: function(name){
+ return this.setProperty(name, null);
+ },
+
+ removeProperties: function(){
+ Array.each(arguments, this.removeProperty, this);
+ return this;
+ },
+
+ set: function(prop, value){
+ var property = Element.Properties[prop];
+ (property && property.set) ? property.set.call(this, value) : this.setProperty(prop, value);
+ }.overloadSetter(),
+
+ get: function(prop){
+ var property = Element.Properties[prop];
+ return (property && property.get) ? property.get.apply(this) : this.getProperty(prop);
+ }.overloadGetter(),
+
+ erase: function(prop){
+ var property = Element.Properties[prop];
+ (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop);
+ return this;
+ },
+
+ hasClass: hasClassList ? function(className){
+ return this.classList.contains(className);
+ } : function(className){
+ return classes(this.className).contains(className);
+ },
+
+ addClass: hasClassList ? function(className){
+ classes(className).forEach(addToClassList, this);
+ return this;
+ } : function(className){
+ this.className = classes(className + ' ' + this.className).join(' ');
+ return this;
+ },
+
+ removeClass: hasClassList ? function(className){
+ classes(className).forEach(removeFromClassList, this);
+ return this;
+ } : function(className){
+ var classNames = classes(this.className);
+ classes(className).forEach(classNames.erase, classNames);
+ this.className = classNames.join(' ');
+ return this;
+ },
+
+ toggleClass: function(className, force){
+ if (force == null) force = !this.hasClass(className);
+ return (force) ? this.addClass(className) : this.removeClass(className);
+ },
+
+ adopt: function(){
+ var parent = this, fragment, elements = Array.flatten(arguments), length = elements.length;
+ if (length > 1) parent = fragment = document.createDocumentFragment();
+
+ for (var i = 0; i < length; i++){
+ var element = document.id(elements[i], true);
+ if (element) parent.appendChild(element);
+ }
+
+ if (fragment) this.appendChild(fragment);
+
+ return this;
+ },
+
+ appendText: function(text, where){
+ return this.grab(this.getDocument().newTextNode(text), where);
+ },
+
+ grab: function(el, where){
+ inserters[where || 'bottom'](document.id(el, true), this);
+ return this;
+ },
+
+ inject: function(el, where){
+ inserters[where || 'bottom'](this, document.id(el, true));
+ return this;
+ },
+
+ replaces: function(el){
+ el = document.id(el, true);
+ el.parentNode.replaceChild(this, el);
+ return this;
+ },
+
+ wraps: function(el, where){
+ el = document.id(el, true);
+ return this.replaces(el).grab(el, where);
+ },
+
+ getSelected: function(){
+ this.selectedIndex; // Safari 3.2.1
+ return new Elements(Array.convert(this.options).filter(function(option){
+ return option.selected;
+ }));
+ },
+
+ toQueryString: function(){
+ var queryString = [];
+ this.getElements('input, select, textarea').each(function(el){
+ var type = el.type;
+ if (!el.name || el.disabled || type == 'submit' || type == 'reset' || type == 'file' || type == 'image') return;
+
+ var value = (el.get('tag') == 'select') ? el.getSelected().map(function(opt){
+ // IE
+ return document.id(opt).get('value');
+ }) : ((type == 'radio' || type == 'checkbox') && !el.checked) ? null : el.get('value');
+
+ Array.convert(value).each(function(val){
+ if (typeof val != 'undefined') queryString.push(encodeURIComponent(el.name) + '=' + encodeURIComponent(val));
+ });
+ });
+ return queryString.join('&');
+ }
+
+});
+
+
+// appendHTML
+
+var appendInserters = {
+ before: 'beforeBegin',
+ after: 'afterEnd',
+ bottom: 'beforeEnd',
+ top: 'afterBegin',
+ inside: 'beforeEnd'
+};
+
+Element.implement('appendHTML', ('insertAdjacentHTML' in document.createElement('div')) ? function(html, where){
+ this.insertAdjacentHTML(appendInserters[where || 'bottom'], html);
+ return this;
+} : function(html, where){
+ var temp = new Element('div', {html: html}),
+ children = temp.childNodes,
+ fragment = temp.firstChild;
+
+ if (!fragment) return this;
+ if (children.length > 1){
+ fragment = document.createDocumentFragment();
+ for (var i = 0, l = children.length; i < l; i++){
+ fragment.appendChild(children[i]);
+ }
+ }
+
+ inserters[where || 'bottom'](fragment, this);
+ return this;
+});
+
+var collected = {}, storage = {};
+
+var get = function(uid){
+ return (storage[uid] || (storage[uid] = {}));
+};
+
+var clean = function(item){
+ var uid = item.uniqueNumber;
+ if (item.removeEvents) item.removeEvents();
+ if (item.clearAttributes) item.clearAttributes();
+ if (uid != null){
+ delete collected[uid];
+ delete storage[uid];
+ }
+ return item;
+};
+
+var formProps = {input: 'checked', option: 'selected', textarea: 'value'};
+
+Element.implement({
+
+ destroy: function(){
+ var children = clean(this).getElementsByTagName('*');
+ Array.each(children, clean);
+ Element.dispose(this);
+ return null;
+ },
+
+ empty: function(){
+ Array.convert(this.childNodes).each(Element.dispose);
+ return this;
+ },
+
+ dispose: function(){
+ return (this.parentNode) ? this.parentNode.removeChild(this) : this;
+ },
+
+ clone: function(contents, keepid){
+ contents = contents !== false;
+ var clone = this.cloneNode(contents), ce = [clone], te = [this], i;
+
+ if (contents){
+ ce.append(Array.convert(clone.getElementsByTagName('*')));
+ te.append(Array.convert(this.getElementsByTagName('*')));
+ }
+
+ for (i = ce.length; i--;){
+ var node = ce[i], element = te[i];
+ if (!keepid) node.removeAttribute('id');
+ /*<ltIE9>*/
+ if (node.clearAttributes){
+ node.clearAttributes();
+ node.mergeAttributes(element);
+ node.removeAttribute('uniqueNumber');
+ if (node.options){
+ var no = node.options, eo = element.options;
+ for (var j = no.length; j--;) no[j].selected = eo[j].selected;
+ }
+ }
+ /*</ltIE9>*/
+ var prop = formProps[element.tagName.toLowerCase()];
+ if (prop && element[prop]) node[prop] = element[prop];
+ }
+
+ /*<ltIE9>*/
+ if (hasCloneBug){
+ var co = clone.getElementsByTagName('object'), to = this.getElementsByTagName('object');
+ for (i = co.length; i--;) co[i].outerHTML = to[i].outerHTML;
+ }
+ /*</ltIE9>*/
+ return document.id(clone);
+ }
+
+});
+
+[Element, Window, Document].invoke('implement', {
+
+ addListener: function(type, fn){
+ if (window.attachEvent && !window.addEventListener){
+ collected[Slick.uidOf(this)] = this;
+ }
+ if (this.addEventListener) this.addEventListener(type, fn, !!arguments[2]);
+ else this.attachEvent('on' + type, fn);
+ return this;
+ },
+
+ removeListener: function(type, fn){
+ if (this.removeEventListener) this.removeEventListener(type, fn, !!arguments[2]);
+ else this.detachEvent('on' + type, fn);
+ return this;
+ },
+
+ retrieve: function(property, dflt){
+ var storage = get(Slick.uidOf(this)), prop = storage[property];
+ if (dflt != null && prop == null) prop = storage[property] = dflt;
+ return prop != null ? prop : null;
+ },
+
+ store: function(property, value){
+ var storage = get(Slick.uidOf(this));
+ storage[property] = value;
+ return this;
+ },
+
+ eliminate: function(property){
+ var storage = get(Slick.uidOf(this));
+ delete storage[property];
+ return this;
+ }
+
+});
+
+/*<ltIE9>*/
+if (window.attachEvent && !window.addEventListener){
+ var gc = function(){
+ Object.each(collected, clean);
+ if (window.CollectGarbage) CollectGarbage();
+ window.removeListener('unload', gc);
+ };
+ window.addListener('unload', gc);
+}
+/*</ltIE9>*/
+
+Element.Properties = {};
+
+
+
+Element.Properties.style = {
+
+ set: function(style){
+ this.style.cssText = style;
+ },
+
+ get: function(){
+ return this.style.cssText;
+ },
+
+ erase: function(){
+ this.style.cssText = '';
+ }
+
+};
+
+Element.Properties.tag = {
+
+ get: function(){
+ return this.tagName.toLowerCase();
+ }
+
+};
+
+Element.Properties.html = {
+
+ set: function(html){
+ if (html == null) html = '';
+ else if (typeOf(html) == 'array') html = html.join('');
+
+ /*<ltIE9>*/
+ if (this.styleSheet && !canChangeStyleHTML) this.styleSheet.cssText = html;
+ else /*</ltIE9>*/this.innerHTML = html;
+ },
+ erase: function(){
+ this.set('html', '');
+ }
+
+};
+
+var supportsHTML5Elements = true, supportsTableInnerHTML = true, supportsTRInnerHTML = true;
+
+/*<ltIE9>*/
+// technique by jdbarlett - http://jdbartlett.com/innershiv/
+var div = document.createElement('div');
+var fragment;
+div.innerHTML = '<nav></nav>';
+supportsHTML5Elements = (div.childNodes.length == 1);
+if (!supportsHTML5Elements){
+ var tags = 'abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video'.split(' ');
+ fragment = document.createDocumentFragment(), l = tags.length;
+ while (l--) fragment.createElement(tags[l]);
+}
+div = null;
+/*</ltIE9>*/
+
+/*<IE>*/
+supportsTableInnerHTML = Function.attempt(function(){
+ var table = document.createElement('table');
+ table.innerHTML = '<tr><td></td></tr>';
+ return true;
+});
+
+/*<ltFF4>*/
+var tr = document.createElement('tr'), html = '<td></td>';
+tr.innerHTML = html;
+supportsTRInnerHTML = (tr.innerHTML == html);
+tr = null;
+/*</ltFF4>*/
+
+if (!supportsTableInnerHTML || !supportsTRInnerHTML || !supportsHTML5Elements){
+
+ Element.Properties.html.set = (function(set){
+
+ var translations = {
+ table: [1, '<table>', '</table>'],
+ select: [1, '<select>', '</select>'],
+ tbody: [2, '<table><tbody>', '</tbody></table>'],
+ tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
+ };
+
+ translations.thead = translations.tfoot = translations.tbody;
+
+ return function(html){
+
+ /*<ltIE9>*/
+ if (this.styleSheet) return set.call(this, html);
+ /*</ltIE9>*/
+ var wrap = translations[this.get('tag')];
+ if (!wrap && !supportsHTML5Elements) wrap = [0, '', ''];
+ if (!wrap) return set.call(this, html);
+
+ var level = wrap[0], wrapper = document.createElement('div'), target = wrapper;
+ if (!supportsHTML5Elements) fragment.appendChild(wrapper);
+ wrapper.innerHTML = [wrap[1], html, wrap[2]].flatten().join('');
+ while (level--) target = target.firstChild;
+ this.empty().adopt(target.childNodes);
+ if (!supportsHTML5Elements) fragment.removeChild(wrapper);
+ wrapper = null;
+ };
+
+ })(Element.Properties.html.set);
+}
+/*</IE>*/
+
+/*<ltIE9>*/
+var testForm = document.createElement('form');
+testForm.innerHTML = '<select><option>s</option></select>';
+
+if (testForm.firstChild.value != 's') Element.Properties.value = {
+
+ set: function(value){
+ var tag = this.get('tag');
+ if (tag != 'select') return this.setProperty('value', value);
+ var options = this.getElements('option');
+ value = String(value);
+ for (var i = 0; i < options.length; i++){
+ var option = options[i],
+ attr = option.getAttributeNode('value'),
+ optionValue = (attr && attr.specified) ? option.value : option.get('text');
+ if (optionValue === value) return option.selected = true;
+ }
+ },
+
+ get: function(){
+ var option = this, tag = option.get('tag');
+
+ if (tag != 'select' && tag != 'option') return this.getProperty('value');
+
+ if (tag == 'select' && !(option = option.getSelected()[0])) return '';
+
+ var attr = option.getAttributeNode('value');
+ return (attr && attr.specified) ? option.value : option.get('text');
+ }
+
+};
+testForm = null;
+/*</ltIE9>*/
+
+/*<IE>*/
+if (document.createElement('div').getAttributeNode('id')) Element.Properties.id = {
+ set: function(id){
+ this.id = this.getAttributeNode('id').value = id;
+ },
+ get: function(){
+ return this.id || null;
+ },
+ erase: function(){
+ this.id = this.getAttributeNode('id').value = '';
+ }
+};
+/*</IE>*/
+
+})();
+
+/*
+---
+
+name: Event
+
+description: Contains the Event Type, to make the event object cross-browser.
+
+license: MIT-style license.
+
+requires: [Window, Document, Array, Function, String, Object]
+
+provides: Event
+
+...
+*/
+
+(function(){
+
+var _keys = {};
+var normalizeWheelSpeed = function(event){
+ var normalized;
+ if (event.wheelDelta){
+ normalized = event.wheelDelta % 120 == 0 ? event.wheelDelta / 120 : event.wheelDelta / 12;
+ } else {
+ var rawAmount = event.deltaY || event.detail || 0;
+ normalized = -(rawAmount % 3 == 0 ? rawAmount / 3 : rawAmount * 10);
+ }
+ return normalized;
+};
+
+var DOMEvent = this.DOMEvent = new Type('DOMEvent', function(event, win){
+ if (!win) win = window;
+ event = event || win.event;
+ if (event.$extended) return event;
+ this.event = event;
+ this.$extended = true;
+ this.shift = event.shiftKey;
+ this.control = event.ctrlKey;
+ this.alt = event.altKey;
+ this.meta = event.metaKey;
+ var type = this.type = event.type;
+ var target = event.target || event.srcElement;
+ while (target && target.nodeType == 3) target = target.parentNode;
+ this.target = document.id(target);
+
+ if (type.indexOf('key') == 0){
+ var code = this.code = (event.which || event.keyCode);
+ if (!this.shift || type != 'keypress') this.key = _keys[code];
+ if (type == 'keydown' || type == 'keyup'){
+ if (code > 111 && code < 124) this.key = 'f' + (code - 111);
+ else if (code > 95 && code < 106) this.key = code - 96;
+ }
+ if (this.key == null) this.key = String.fromCharCode(code).toLowerCase();
+ } else if (type == 'click' || type == 'dblclick' || type == 'contextmenu' || type == 'wheel' || type == 'DOMMouseScroll' || type.indexOf('mouse') == 0){
+ var doc = win.document;
+ doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+ this.page = {
+ x: (event.pageX != null) ? event.pageX : event.clientX + doc.scrollLeft,
+ y: (event.pageY != null) ? event.pageY : event.clientY + doc.scrollTop
+ };
+ this.client = {
+ x: (event.pageX != null) ? event.pageX - win.pageXOffset : event.clientX,
+ y: (event.pageY != null) ? event.pageY - win.pageYOffset : event.clientY
+ };
+ if (type == 'DOMMouseScroll' || type == 'wheel' || type == 'mousewheel') this.wheel = normalizeWheelSpeed(event);
+ this.rightClick = (event.which == 3 || event.button == 2);
+ if (type == 'mouseover' || type == 'mouseout' || type == 'mouseenter' || type == 'mouseleave'){
+ var overTarget = type == 'mouseover' || type == 'mouseenter';
+ var related = event.relatedTarget || event[(overTarget ? 'from' : 'to') + 'Element'];
+ while (related && related.nodeType == 3) related = related.parentNode;
+ this.relatedTarget = document.id(related);
+ }
+ } else if (type.indexOf('touch') == 0 || type.indexOf('gesture') == 0){
+ this.rotation = event.rotation;
+ this.scale = event.scale;
+ this.targetTouches = event.targetTouches;
+ this.changedTouches = event.changedTouches;
+ var touches = this.touches = event.touches;
+ if (touches && touches[0]){
+ var touch = touches[0];
+ this.page = {x: touch.pageX, y: touch.pageY};
+ this.client = {x: touch.clientX, y: touch.clientY};
+ }
+ }
+
+ if (!this.client) this.client = {};
+ if (!this.page) this.page = {};
+});
+
+DOMEvent.implement({
+
+ stop: function(){
+ return this.preventDefault().stopPropagation();
+ },
+
+ stopPropagation: function(){
+ if (this.event.stopPropagation) this.event.stopPropagation();
+ else this.event.cancelBubble = true;
+ return this;
+ },
+
+ preventDefault: function(){
+ if (this.event.preventDefault) this.event.preventDefault();
+ else this.event.returnValue = false;
+ return this;
+ }
+
+});
+
+DOMEvent.defineKey = function(code, key){
+ _keys[code] = key;
+ return this;
+};
+
+DOMEvent.defineKeys = DOMEvent.defineKey.overloadSetter(true);
+
+DOMEvent.defineKeys({
+ '38': 'up', '40': 'down', '37': 'left', '39': 'right',
+ '27': 'esc', '32': 'space', '8': 'backspace', '9': 'tab',
+ '46': 'delete', '13': 'enter'
+});
+
+})();
+
+
+
+
+
+/*
+---
+
+name: Element.Event
+
+description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events, if necessary.
+
+license: MIT-style license.
+
+requires: [Element, Event]
+
+provides: Element.Event
+
+...
+*/
+
+(function(){
+
+Element.Properties.events = {set: function(events){
+ this.addEvents(events);
+}};
+
+[Element, Window, Document].invoke('implement', {
+
+ addEvent: function(type, fn){
+ var events = this.retrieve('events', {});
+ if (!events[type]) events[type] = {keys: [], values: []};
+ if (events[type].keys.contains(fn)) return this;
+ events[type].keys.push(fn);
+ var realType = type,
+ custom = Element.Events[type],
+ condition = fn,
+ self = this;
+ if (custom){
+ if (custom.onAdd) custom.onAdd.call(this, fn, type);
+ if (custom.condition){
+ condition = function(event){
+ if (custom.condition.call(this, event, type)) return fn.call(this, event);
+ return true;
+ };
+ }
+ if (custom.base) realType = Function.convert(custom.base).call(this, type);
+ }
+ var defn = function(){
+ return fn.call(self);
+ };
+ var nativeEvent = Element.NativeEvents[realType];
+ if (nativeEvent){
+ if (nativeEvent == 2){
+ defn = function(event){
+ event = new DOMEvent(event, self.getWindow());
+ if (condition.call(self, event) === false) event.stop();
+ };
+ }
+ this.addListener(realType, defn, arguments[2]);
+ }
+ events[type].values.push(defn);
+ return this;
+ },
+
+ removeEvent: function(type, fn){
+ var events = this.retrieve('events');
+ if (!events || !events[type]) return this;
+ var list = events[type];
+ var index = list.keys.indexOf(fn);
+ if (index == -1) return this;
+ var value = list.values[index];
+ delete list.keys[index];
+ delete list.values[index];
+ var custom = Element.Events[type];
+ if (custom){
+ if (custom.onRemove) custom.onRemove.call(this, fn, type);
+ if (custom.base) type = Function.convert(custom.base).call(this, type);
+ }
+ return (Element.NativeEvents[type]) ? this.removeListener(type, value, arguments[2]) : this;
+ },
+
+ addEvents: function(events){
+ for (var event in events) this.addEvent(event, events[event]);
+ return this;
+ },
+
+ removeEvents: function(events){
+ var type;
+ if (typeOf(events) == 'object'){
+ for (type in events) this.removeEvent(type, events[type]);
+ return this;
+ }
+ var attached = this.retrieve('events');
+ if (!attached) return this;
+ if (!events){
+ for (type in attached) this.removeEvents(type);
+ this.eliminate('events');
+ } else if (attached[events]){
+ attached[events].keys.each(function(fn){
+ this.removeEvent(events, fn);
+ }, this);
+ delete attached[events];
+ }
+ return this;
+ },
+
+ fireEvent: function(type, args, delay){
+ var events = this.retrieve('events');
+ if (!events || !events[type]) return this;
+ args = Array.convert(args);
+
+ events[type].keys.each(function(fn){
+ if (delay) fn.delay(delay, this, args);
+ else fn.apply(this, args);
+ }, this);
+ return this;
+ },
+
+ cloneEvents: function(from, type){
+ from = document.id(from);
+ var events = from.retrieve('events');
+ if (!events) return this;
+ if (!type){
+ for (var eventType in events) this.cloneEvents(from, eventType);
+ } else if (events[type]){
+ events[type].keys.each(function(fn){
+ this.addEvent(type, fn);
+ }, this);
+ }
+ return this;
+ }
+
+});
+
+Element.NativeEvents = {
+ click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons
+ wheel: 2, mousewheel: 2, DOMMouseScroll: 2, //mouse wheel
+ mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement
+ keydown: 2, keypress: 2, keyup: 2, //keyboard
+ orientationchange: 2, // mobile
+ touchstart: 2, touchmove: 2, touchend: 2, touchcancel: 2, // touch
+ gesturestart: 2, gesturechange: 2, gestureend: 2, // gesture
+ focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, paste: 2, input: 2, //form elements
+ load: 2, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window
+ hashchange: 1, popstate: 2, pageshow: 2, pagehide: 2, // history
+ error: 1, abort: 1, scroll: 1, message: 2 //misc
+};
+
+Element.Events = {
+ mousewheel: {
+ base: 'onwheel' in document ? 'wheel' : 'onmousewheel' in document ? 'mousewheel' : 'DOMMouseScroll'
+ }
+};
+
+var check = function(event){
+ var related = event.relatedTarget;
+ if (related == null) return true;
+ if (!related) return false;
+ return (related != this && related.prefix != 'xul' && typeOf(this) != 'document' && !this.contains(related));
+};
+
+if ('onmouseenter' in document.documentElement){
+ Element.NativeEvents.mouseenter = Element.NativeEvents.mouseleave = 2;
+ Element.MouseenterCheck = check;
+} else {
+ Element.Events.mouseenter = {
+ base: 'mouseover',
+ condition: check
+ };
+
+ Element.Events.mouseleave = {
+ base: 'mouseout',
+ condition: check
+ };
+}
+
+/*<ltIE9>*/
+if (!window.addEventListener){
+ Element.NativeEvents.propertychange = 2;
+ Element.Events.change = {
+ base: function(){
+ var type = this.type;
+ return (this.get('tag') == 'input' && (type == 'radio' || type == 'checkbox')) ? 'propertychange' : 'change';
+ },
+ condition: function(event){
+ return event.type != 'propertychange' || event.event.propertyName == 'checked';
+ }
+ };
+}
+/*</ltIE9>*/
+
+
+
+})();
+
+/*
+---
+
+name: Element.Delegation
+
+description: Extends the Element native object to include the delegate method for more efficient event management.
+
+license: MIT-style license.
+
+requires: [Element.Event]
+
+provides: [Element.Delegation]
+
+...
+*/
+
+(function(){
+
+var eventListenerSupport = !!window.addEventListener;
+
+Element.NativeEvents.focusin = Element.NativeEvents.focusout = 2;
+
+var bubbleUp = function(self, match, fn, event, target){
+ while (target && target != self){
+ if (match(target, event)) return fn.call(target, event, target);
+ target = document.id(target.parentNode);
+ }
+};
+
+var map = {
+ mouseenter: {
+ base: 'mouseover',
+ condition: Element.MouseenterCheck
+ },
+ mouseleave: {
+ base: 'mouseout',
+ condition: Element.MouseenterCheck
+ },
+ focus: {
+ base: 'focus' + (eventListenerSupport ? '' : 'in'),
+ capture: true
+ },
+ blur: {
+ base: eventListenerSupport ? 'blur' : 'focusout',
+ capture: true
+ }
+};
+
+/*<ltIE9>*/
+var _key = '$delegation:';
+var formObserver = function(type){
+
+ return {
+
+ base: 'focusin',
+
+ remove: function(self, uid){
+ var list = self.retrieve(_key + type + 'listeners', {})[uid];
+ if (list && list.forms) for (var i = list.forms.length; i--;){
+ // the form may have been destroyed, so it won't have the
+ // removeEvent method anymore. In that case the event was
+ // removed as well.
+ if (list.forms[i].removeEvent) list.forms[i].removeEvent(type, list.fns[i]);
+ }
+ },
+
+ listen: function(self, match, fn, event, target, uid){
+ var form = (target.get('tag') == 'form') ? target : event.target.getParent('form');
+ if (!form) return;
+
+ var listeners = self.retrieve(_key + type + 'listeners', {}),
+ listener = listeners[uid] || {forms: [], fns: []},
+ forms = listener.forms, fns = listener.fns;
+
+ if (forms.indexOf(form) != -1) return;
+ forms.push(form);
+
+ var _fn = function(event){
+ bubbleUp(self, match, fn, event, target);
+ };
+ form.addEvent(type, _fn);
+ fns.push(_fn);
+
+ listeners[uid] = listener;
+ self.store(_key + type + 'listeners', listeners);
+ }
+ };
+};
+
+var inputObserver = function(type){
+ return {
+ base: 'focusin',
+ listen: function(self, match, fn, event, target){
+ var events = {blur: function(){
+ this.removeEvents(events);
+ }};
+ events[type] = function(event){
+ bubbleUp(self, match, fn, event, target);
+ };
+ event.target.addEvents(events);
+ }
+ };
+};
+
+if (!eventListenerSupport) Object.append(map, {
+ submit: formObserver('submit'),
+ reset: formObserver('reset'),
+ change: inputObserver('change'),
+ select: inputObserver('select')
+});
+/*</ltIE9>*/
+
+var proto = Element.prototype,
+ addEvent = proto.addEvent,
+ removeEvent = proto.removeEvent;
+
+var relay = function(old, method){
+ return function(type, fn, useCapture){
+ if (type.indexOf(':relay') == -1) return old.call(this, type, fn, useCapture);
+ var parsed = Slick.parse(type).expressions[0][0];
+ if (parsed.pseudos[0].key != 'relay') return old.call(this, type, fn, useCapture);
+ var newType = parsed.tag;
+ parsed.pseudos.slice(1).each(function(pseudo){
+ newType += ':' + pseudo.key + (pseudo.value ? '(' + pseudo.value + ')' : '');
+ });
+ old.call(this, type, fn);
+ return method.call(this, newType, parsed.pseudos[0].value, fn);
+ };
+};
+
+var delegation = {
+
+ addEvent: function(type, match, fn){
+ var storage = this.retrieve('$delegates', {}), stored = storage[type];
+ if (stored) for (var _uid in stored){
+ if (stored[_uid].fn == fn && stored[_uid].match == match) return this;
+ }
+
+ var _type = type, _match = match, _fn = fn, _map = map[type] || {};
+ type = _map.base || _type;
+
+ match = function(target){
+ return Slick.match(target, _match);
+ };
+
+ var elementEvent = Element.Events[_type];
+ if (_map.condition || elementEvent && elementEvent.condition){
+ var __match = match, condition = _map.condition || elementEvent.condition;
+ match = function(target, event){
+ return __match(target, event) && condition.call(target, event, type);
+ };
+ }
+
+ var self = this, uid = String.uniqueID();
+ var delegator = _map.listen ? function(event, target){
+ if (!target && event && event.target) target = event.target;
+ if (target) _map.listen(self, match, fn, event, target, uid);
+ } : function(event, target){
+ if (!target && event && event.target) target = event.target;
+ if (target) bubbleUp(self, match, fn, event, target);
+ };
+
+ if (!stored) stored = {};
+ stored[uid] = {
+ match: _match,
+ fn: _fn,
+ delegator: delegator
+ };
+ storage[_type] = stored;
+ return addEvent.call(this, type, delegator, _map.capture);
+ },
+
+ removeEvent: function(type, match, fn, _uid){
+ var storage = this.retrieve('$delegates', {}), stored = storage[type];
+ if (!stored) return this;
+
+ if (_uid){
+ var _type = type, delegator = stored[_uid].delegator, _map = map[type] || {};
+ type = _map.base || _type;
+ if (_map.remove) _map.remove(this, _uid);
+ delete stored[_uid];
+ storage[_type] = stored;
+ return removeEvent.call(this, type, delegator, _map.capture);
+ }
+
+ var __uid, s;
+ if (fn) for (__uid in stored){
+ s = stored[__uid];
+ if (s.match == match && s.fn == fn) return delegation.removeEvent.call(this, type, match, fn, __uid);
+ } else for (__uid in stored){
+ s = stored[__uid];
+ if (s.match == match) delegation.removeEvent.call(this, type, match, s.fn, __uid);
+ }
+ return this;
+ }
+
+};
+
+[Element, Window, Document].invoke('implement', {
+ addEvent: relay(addEvent, delegation.addEvent),
+ removeEvent: relay(removeEvent, delegation.removeEvent)
+});
+
+})();
+
+/*
+---
+
+name: Element.Style
+
+description: Contains methods for interacting with the styles of Elements in a fashionable way.
+
+license: MIT-style license.
+
+requires: Element
+
+provides: Element.Style
+
+...
+*/
+
+(function(){
+
+var html = document.html, el;
+
+//<ltIE9>
+// Check for oldIE, which does not remove styles when they're set to null
+el = document.createElement('div');
+el.style.color = 'red';
+el.style.color = null;
+var doesNotRemoveStyles = el.style.color == 'red';
+
+// check for oldIE, which returns border* shorthand styles in the wrong order (color-width-style instead of width-style-color)
+var border = '1px solid #123abc';
+el.style.border = border;
+var returnsBordersInWrongOrder = el.style.border != border;
+el = null;
+//</ltIE9>
+
+var hasGetComputedStyle = !!window.getComputedStyle,
+ supportBorderRadius = document.createElement('div').style.borderRadius != null;
+
+Element.Properties.styles = {set: function(styles){
+ this.setStyles(styles);
+}};
+
+var hasOpacity = (html.style.opacity != null),
+ hasFilter = (html.style.filter != null),
+ reAlpha = /alpha\(opacity=([\d.]+)\)/i;
+
+var setVisibility = function(element, opacity){
+ element.store('$opacity', opacity);
+ element.style.visibility = opacity > 0 || opacity == null ? 'visible' : 'hidden';
+};
+
+//<ltIE9>
+var setFilter = function(element, regexp, value){
+ var style = element.style,
+ filter = style.filter || element.getComputedStyle('filter') || '';
+ style.filter = (regexp.test(filter) ? filter.replace(regexp, value) : filter + ' ' + value).trim();
+ if (!style.filter) style.removeAttribute('filter');
+};
+//</ltIE9>
+
+var setOpacity = (hasOpacity ? function(element, opacity){
+ element.style.opacity = opacity;
+} : (hasFilter ? function(element, opacity){
+ if (!element.currentStyle || !element.currentStyle.hasLayout) element.style.zoom = 1;
+ if (opacity == null || opacity == 1){
+ setFilter(element, reAlpha, '');
+ if (opacity == 1 && getOpacity(element) != 1) setFilter(element, reAlpha, 'alpha(opacity=100)');
+ } else {
+ setFilter(element, reAlpha, 'alpha(opacity=' + (opacity * 100).limit(0, 100).round() + ')');
+ }
+} : setVisibility));
+
+var getOpacity = (hasOpacity ? function(element){
+ var opacity = element.style.opacity || element.getComputedStyle('opacity');
+ return (opacity == '') ? 1 : opacity.toFloat();
+} : (hasFilter ? function(element){
+ var filter = (element.style.filter || element.getComputedStyle('filter')),
+ opacity;
+ if (filter) opacity = filter.match(reAlpha);
+ return (opacity == null || filter == null) ? 1 : (opacity[1] / 100);
+} : function(element){
+ var opacity = element.retrieve('$opacity');
+ if (opacity == null) opacity = (element.style.visibility == 'hidden' ? 0 : 1);
+ return opacity;
+}));
+
+var floatName = (html.style.cssFloat == null) ? 'styleFloat' : 'cssFloat',
+ namedPositions = {left: '0%', top: '0%', center: '50%', right: '100%', bottom: '100%'},
+ hasBackgroundPositionXY = (html.style.backgroundPositionX != null),
+ prefixPattern = /^-(ms)-/;
+
+var camelCase = function(property){
+ return property.replace(prefixPattern, '$1-').camelCase();
+};
+
+//<ltIE9>
+var removeStyle = function(style, property){
+ if (property == 'backgroundPosition'){
+ style.removeAttribute(property + 'X');
+ property += 'Y';
+ }
+ style.removeAttribute(property);
+};
+//</ltIE9>
+
+Element.implement({
+
+ getComputedStyle: function(property){
+ if (!hasGetComputedStyle && this.currentStyle) return this.currentStyle[camelCase(property)];
+ var defaultView = Element.getDocument(this).defaultView,
+ computed = defaultView ? defaultView.getComputedStyle(this, null) : null;
+ return (computed) ? computed.getPropertyValue((property == floatName) ? 'float' : property.hyphenate()) : '';
+ },
+
+ setStyle: function(property, value){
+ if (property == 'opacity'){
+ if (value != null) value = parseFloat(value);
+ setOpacity(this, value);
+ return this;
+ }
+ property = camelCase(property == 'float' ? floatName : property);
+ if (typeOf(value) != 'string'){
+ var map = (Element.Styles[property] || '@').split(' ');
+ value = Array.convert(value).map(function(val, i){
+ if (!map[i]) return '';
+ return (typeOf(val) == 'number') ? map[i].replace('@', Math.round(val)) : val;
+ }).join(' ');
+ } else if (value == String(Number(value))){
+ value = Math.round(value);
+ }
+ this.style[property] = value;
+ //<ltIE9>
+ if ((value == '' || value == null) && doesNotRemoveStyles && this.style.removeAttribute){
+ removeStyle(this.style, property);
+ }
+ //</ltIE9>
+ return this;
+ },
+
+ getStyle: function(property){
+ if (property == 'opacity') return getOpacity(this);
+ property = camelCase(property == 'float' ? floatName : property);
+ if (supportBorderRadius && property.indexOf('borderRadius') != -1){
+ return ['borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius'].map(function(corner){
+ return this.style[corner] || '0px';
+ }, this).join(' ');
+ }
+ var result = this.style[property];
+ if (!result || property == 'zIndex'){
+ if (Element.ShortStyles.hasOwnProperty(property)){
+ result = [];
+ for (var s in Element.ShortStyles[property]) result.push(this.getStyle(s));
+ return result.join(' ');
+ }
+ result = this.getComputedStyle(property);
+ }
+ if (hasBackgroundPositionXY && /^backgroundPosition[XY]?$/.test(property)){
+ return result.replace(/(top|right|bottom|left)/g, function(position){
+ return namedPositions[position];
+ }) || '0px';
+ }
+ if (!result && property == 'backgroundPosition') return '0px 0px';
+ if (result){
+ result = String(result);
+ var color = result.match(/rgba?\([\d\s,]+\)/);
+ if (color) result = result.replace(color[0], color[0].rgbToHex());
+ }
+ if (!hasGetComputedStyle && !this.style[property]){
+ if ((/^(height|width)$/).test(property) && !(/px$/.test(result))){
+ var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0;
+ values.each(function(value){
+ size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt();
+ }, this);
+ return this['offset' + property.capitalize()] - size + 'px';
+ }
+ if ((/^border(.+)Width|margin|padding/).test(property) && isNaN(parseFloat(result))){
+ return '0px';
+ }
+ }
+ //<ltIE9>
+ if (returnsBordersInWrongOrder && /^border(Top|Right|Bottom|Left)?$/.test(property) && /^#/.test(result)){
+ return result.replace(/^(.+)\s(.+)\s(.+)$/, '$2 $3 $1');
+ }
+ //</ltIE9>
+
+ return result;
+ },
+
+ setStyles: function(styles){
+ for (var style in styles) this.setStyle(style, styles[style]);
+ return this;
+ },
+
+ getStyles: function(){
+ var result = {};
+ Array.flatten(arguments).each(function(key){
+ result[key] = this.getStyle(key);
+ }, this);
+ return result;
+ }
+
+});
+
+Element.Styles = {
+ left: '@px', top: '@px', bottom: '@px', right: '@px',
+ width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px',
+ backgroundColor: 'rgb(@, @, @)', backgroundSize: '@px', backgroundPosition: '@px @px', color: 'rgb(@, @, @)',
+ fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)',
+ margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)',
+ borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)',
+ zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@', borderRadius: '@px @px @px @px'
+};
+
+
+
+
+
+Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}};
+
+['Top', 'Right', 'Bottom', 'Left'].each(function(direction){
+ var Short = Element.ShortStyles;
+ var All = Element.Styles;
+ ['margin', 'padding'].each(function(style){
+ var sd = style + direction;
+ Short[style][sd] = All[sd] = '@px';
+ });
+ var bd = 'border' + direction;
+ Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)';
+ var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color';
+ Short[bd] = {};
+ Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px';
+ Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@';
+ Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)';
+});
+
+if (hasBackgroundPositionXY) Element.ShortStyles.backgroundPosition = {backgroundPositionX: '@', backgroundPositionY: '@'};
+})();
+
+/*
+---
+
+name: Element.Dimensions
+
+description: Contains methods to work with size, scroll, or positioning of Elements and the window object.
+
+license: MIT-style license.
+
+credits:
+ - Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html).
+ - Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html).
+
+requires: [Element, Element.Style]
+
+provides: [Element.Dimensions]
+
+...
+*/
+
+(function(){
+
+var element = document.createElement('div'),
+ child = document.createElement('div');
+element.style.height = '0';
+element.appendChild(child);
+var brokenOffsetParent = (child.offsetParent === element);
+element = child = null;
+
+var heightComponents = ['height', 'paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth'],
+ widthComponents = ['width', 'paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth'];
+
+var svgCalculateSize = function(el){
+
+ var gCS = window.getComputedStyle(el),
+ bounds = {x: 0, y: 0};
+
+ heightComponents.each(function(css){
+ bounds.y += parseFloat(gCS[css]);
+ });
+ widthComponents.each(function(css){
+ bounds.x += parseFloat(gCS[css]);
+ });
+ return bounds;
+};
+
+var isOffset = function(el){
+ return styleString(el, 'position') != 'static' || isBody(el);
+};
+
+var isOffsetStatic = function(el){
+ return isOffset(el) || (/^(?:table|td|th)$/i).test(el.tagName);
+};
+
+Element.implement({
+
+ scrollTo: function(x, y){
+ if (isBody(this)){
+ this.getWindow().scrollTo(x, y);
+ } else {
+ this.scrollLeft = x;
+ this.scrollTop = y;
+ }
+ return this;
+ },
+
+ getSize: function(){
+ if (isBody(this)) return this.getWindow().getSize();
+
+ //<ltIE9>
+ // This if clause is because IE8- cannot calculate getBoundingClientRect of elements with visibility hidden.
+ if (!window.getComputedStyle) return {x: this.offsetWidth, y: this.offsetHeight};
+ //</ltIE9>
+
+ // This svg section under, calling `svgCalculateSize()`, can be removed when FF fixed the svg size bug.
+ // Bug info: https://bugzilla.mozilla.org/show_bug.cgi?id=530985
+ if (this.get('tag') == 'svg') return svgCalculateSize(this);
+
+ try {
+ var bounds = this.getBoundingClientRect();
+ return {x: bounds.width, y: bounds.height};
+ } catch (e){
+ return {x: 0, y: 0};
+ }
+ },
+
+ getScrollSize: function(){
+ if (isBody(this)) return this.getWindow().getScrollSize();
+ return {x: this.scrollWidth, y: this.scrollHeight};
+ },
+
+ getScroll: function(){
+ if (isBody(this)) return this.getWindow().getScroll();
+ return {x: this.scrollLeft, y: this.scrollTop};
+ },
+
+ getScrolls: function(){
+ var element = this.parentNode, position = {x: 0, y: 0};
+ while (element && !isBody(element)){
+ position.x += element.scrollLeft;
+ position.y += element.scrollTop;
+ element = element.parentNode;
+ }
+ return position;
+ },
+
+ getOffsetParent: brokenOffsetParent ? function(){
+ var element = this;
+ if (isBody(element) || styleString(element, 'position') == 'fixed') return null;
+
+ var isOffsetCheck = (styleString(element, 'position') == 'static') ? isOffsetStatic : isOffset;
+ while ((element = element.parentNode)){
+ if (isOffsetCheck(element)) return element;
+ }
+ return null;
+ } : function(){
+ var element = this;
+ if (isBody(element) || styleString(element, 'position') == 'fixed') return null;
+
+ try {
+ return element.offsetParent;
+ } catch (e){}
+ return null;
+ },
+
+ getOffsets: function(){
+ var hasGetBoundingClientRect = this.getBoundingClientRect;
+
+ if (hasGetBoundingClientRect){
+ var bound = this.getBoundingClientRect(),
+ html = document.id(this.getDocument().documentElement),
+ htmlScroll = html.getScroll(),
+ elemScrolls = this.getScrolls(),
+ isFixed = (styleString(this, 'position') == 'fixed');
+
+ return {
+ x: bound.left.toFloat() + elemScrolls.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft,
+ y: bound.top.toFloat() + elemScrolls.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop
+ };
+ }
+
+ var element = this, position = {x: 0, y: 0};
+ if (isBody(this)) return position;
+
+ while (element && !isBody(element)){
+ position.x += element.offsetLeft;
+ position.y += element.offsetTop;
+
+ element = element.offsetParent;
+ }
+
+ return position;
+ },
+
+ getPosition: function(relative){
+ var offset = this.getOffsets(),
+ scroll = this.getScrolls();
+ var position = {
+ x: offset.x - scroll.x,
+ y: offset.y - scroll.y
+ };
+
+ if (relative && (relative = document.id(relative))){
+ var relativePosition = relative.getPosition();
+ return {x: position.x - relativePosition.x - leftBorder(relative), y: position.y - relativePosition.y - topBorder(relative)};
+ }
+ return position;
+ },
+
+ getCoordinates: function(element){
+ if (isBody(this)) return this.getWindow().getCoordinates();
+ var position = this.getPosition(element),
+ size = this.getSize();
+ var obj = {
+ left: position.x,
+ top: position.y,
+ width: size.x,
+ height: size.y
+ };
+ obj.right = obj.left + obj.width;
+ obj.bottom = obj.top + obj.height;
+ return obj;
+ },
+
+ computePosition: function(obj){
+ return {
+ left: obj.x - styleNumber(this, 'margin-left'),
+ top: obj.y - styleNumber(this, 'margin-top')
+ };
+ },
+
+ setPosition: function(obj){
+ return this.setStyles(this.computePosition(obj));
+ }
+
+});
+
+
+[Document, Window].invoke('implement', {
+
+ getSize: function(){
+ var doc = getCompatElement(this);
+ return {x: doc.clientWidth, y: doc.clientHeight};
+ },
+
+ getScroll: function(){
+ var win = this.getWindow(), doc = getCompatElement(this);
+ return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop};
+ },
+
+ getScrollSize: function(){
+ var doc = getCompatElement(this),
+ min = this.getSize(),
+ body = this.getDocument().body;
+
+ return {x: Math.max(doc.scrollWidth, body.scrollWidth, min.x), y: Math.max(doc.scrollHeight, body.scrollHeight, min.y)};
+ },
+
+ getPosition: function(){
+ return {x: 0, y: 0};
+ },
+
+ getCoordinates: function(){
+ var size = this.getSize();
+ return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x};
+ }
+
+});
+
+// private methods
+
+var styleString = Element.getComputedStyle;
+
+function styleNumber(element, style){
+ return styleString(element, style).toInt() || 0;
+}
+
+
+
+function topBorder(element){
+ return styleNumber(element, 'border-top-width');
+}
+
+function leftBorder(element){
+ return styleNumber(element, 'border-left-width');
+}
+
+function isBody(element){
+ return (/^(?:body|html)$/i).test(element.tagName);
+}
+
+function getCompatElement(element){
+ var doc = element.getDocument();
+ return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+}
+
+})();
+
+//aliases
+Element.alias({position: 'setPosition'}); //compatability
+
+[Window, Document, Element].invoke('implement', {
+
+ getHeight: function(){
+ return this.getSize().y;
+ },
+
+ getWidth: function(){
+ return this.getSize().x;
+ },
+
+ getScrollTop: function(){
+ return this.getScroll().y;
+ },
+
+ getScrollLeft: function(){
+ return this.getScroll().x;
+ },
+
+ getScrollHeight: function(){
+ return this.getScrollSize().y;
+ },
+
+ getScrollWidth: function(){
+ return this.getScrollSize().x;
+ },
+
+ getTop: function(){
+ return this.getPosition().y;
+ },
+
+ getLeft: function(){
+ return this.getPosition().x;
+ }
+
+});
+
+/*
+---
+
+name: Fx
+
+description: Contains the basic animation logic to be extended by all other Fx Classes.
+
+license: MIT-style license.
+
+requires: [Chain, Events, Options, Class.Thenable]
+
+provides: Fx
+
+...
+*/
+
+(function(){
+
+var Fx = this.Fx = new Class({
+
+ Implements: [Chain, Events, Options, Class.Thenable],
+
+ options: {
+ /*
+ onStart: nil,
+ onCancel: nil,
+ onComplete: nil,
+ */
+ fps: 60,
+ unit: false,
+ duration: 500,
+ frames: null,
+ frameSkip: true,
+ link: 'ignore'
+ },
+
+ initialize: function(options){
+ this.subject = this.subject || this;
+ this.setOptions(options);
+ },
+
+ getTransition: function(){
+ return function(p){
+ return -(Math.cos(Math.PI * p) - 1) / 2;
+ };
+ },
+
+ step: function(now){
+ if (this.options.frameSkip){
+ var diff = (this.time != null) ? (now - this.time) : 0, frames = diff / this.frameInterval;
+ this.time = now;
+ this.frame += frames;
+ } else {
+ this.frame++;
+ }
+
+ if (this.frame < this.frames){
+ var delta = this.transition(this.frame / this.frames);
+ this.set(this.compute(this.from, this.to, delta));
+ } else {
+ this.frame = this.frames;
+ this.set(this.compute(this.from, this.to, 1));
+ this.stop();
+ }
+ },
+
+ set: function(now){
+ return now;
+ },
+
+ compute: function(from, to, delta){
+ return Fx.compute(from, to, delta);
+ },
+
+ check: function(){
+ if (!this.isRunning()) return true;
+ switch (this.options.link){
+ case 'cancel': this.cancel(); return true;
+ case 'chain': this.chain(this.caller.pass(arguments, this)); return false;
+ }
+ return false;
+ },
+
+ start: function(from, to){
+ if (!this.check(from, to)) return this;
+ this.from = from;
+ this.to = to;
+ this.frame = (this.options.frameSkip) ? 0 : -1;
+ this.time = null;
+ this.transition = this.getTransition();
+ var frames = this.options.frames, fps = this.options.fps, duration = this.options.duration;
+ this.duration = Fx.Durations[duration] || duration.toInt();
+ this.frameInterval = 1000 / fps;
+ this.frames = frames || Math.round(this.duration / this.frameInterval);
+ if (this.getThenableState() !== 'pending'){
+ this.resetThenable(this.subject);
+ }
+ this.fireEvent('start', this.subject);
+ pushInstance.call(this, fps);
+ return this;
+ },
+
+ stop: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ if (this.frames == this.frame){
+ this.fireEvent('complete', this.subject);
+ if (!this.callChain()) this.fireEvent('chainComplete', this.subject);
+ } else {
+ this.fireEvent('stop', this.subject);
+ }
+ this.resolve(this.subject === this ? null : this.subject);
+ }
+ return this;
+ },
+
+ cancel: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ this.frame = this.frames;
+ this.fireEvent('cancel', this.subject).clearChain();
+ this.reject(this.subject);
+ }
+ return this;
+ },
+
+ pause: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ }
+ return this;
+ },
+
+ resume: function(){
+ if (this.isPaused()) pushInstance.call(this, this.options.fps);
+ return this;
+ },
+
+ isRunning: function(){
+ var list = instances[this.options.fps];
+ return list && list.contains(this);
+ },
+
+ isPaused: function(){
+ return (this.frame < this.frames) && !this.isRunning();
+ }
+
+});
+
+Fx.compute = function(from, to, delta){
+ return (to - from) * delta + from;
+};
+
+Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000};
+
+// global timers
+
+var instances = {}, timers = {};
+
+var loop = function(){
+ var now = Date.now();
+ for (var i = this.length; i--;){
+ var instance = this[i];
+ if (instance) instance.step(now);
+ }
+};
+
+var pushInstance = function(fps){
+ var list = instances[fps] || (instances[fps] = []);
+ list.push(this);
+ if (!timers[fps]) timers[fps] = loop.periodical(Math.round(1000 / fps), list);
+};
+
+var pullInstance = function(fps){
+ var list = instances[fps];
+ if (list){
+ list.erase(this);
+ if (!list.length && timers[fps]){
+ delete instances[fps];
+ timers[fps] = clearInterval(timers[fps]);
+ }
+ }
+};
+
+})();
+
+/*
+---
+
+name: Fx.CSS
+
+description: Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements.
+
+license: MIT-style license.
+
+requires: [Fx, Element.Style]
+
+provides: Fx.CSS
+
+...
+*/
+
+Fx.CSS = new Class({
+
+ Extends: Fx,
+
+ //prepares the base from/to object
+
+ prepare: function(element, property, values){
+ values = Array.convert(values);
+ var from = values[0], to = values[1];
+ if (to == null){
+ to = from;
+ from = element.getStyle(property);
+ var unit = this.options.unit;
+ // adapted from: https://github.com/ryanmorr/fx/blob/master/fx.js#L299
+ if (unit && from && typeof from == 'string' && from.slice(-unit.length) != unit && parseFloat(from) != 0){
+ element.setStyle(property, to + unit);
+ var value = element.getComputedStyle(property);
+ // IE and Opera support pixelLeft or pixelWidth
+ if (!(/px$/.test(value))){
+ value = element.style[('pixel-' + property).camelCase()];
+ if (value == null){
+ // adapted from Dean Edwards' http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+ var left = element.style.left;
+ element.style.left = to + unit;
+ value = element.style.pixelLeft;
+ element.style.left = left;
+ }
+ }
+ from = (to || 1) / (parseFloat(value) || 1) * (parseFloat(from) || 0);
+ element.setStyle(property, from + unit);
+ }
+ }
+ return {from: this.parse(from), to: this.parse(to)};
+ },
+
+ //parses a value into an array
+
+ parse: function(value){
+ value = Function.convert(value)();
+ value = (typeof value == 'string') ? value.split(' ') : Array.convert(value);
+ return value.map(function(val){
+ val = String(val);
+ var found = false;
+ Object.each(Fx.CSS.Parsers, function(parser){
+ if (found) return;
+ var parsed = parser.parse(val);
+ if (parsed || parsed === 0) found = {value: parsed, parser: parser};
+ });
+ found = found || {value: val, parser: Fx.CSS.Parsers.String};
+ return found;
+ });
+ },
+
+ //computes by a from and to prepared objects, using their parsers.
+
+ compute: function(from, to, delta){
+ var computed = [];
+ (Math.min(from.length, to.length)).times(function(i){
+ computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser});
+ });
+ computed.$family = Function.convert('fx:css:value');
+ return computed;
+ },
+
+ //serves the value as settable
+
+ serve: function(value, unit){
+ if (typeOf(value) != 'fx:css:value') value = this.parse(value);
+ var returned = [];
+ value.each(function(bit){
+ returned = returned.concat(bit.parser.serve(bit.value, unit));
+ });
+ return returned;
+ },
+
+ //renders the change to an element
+
+ render: function(element, property, value, unit){
+ element.setStyle(property, this.serve(value, unit));
+ },
+
+ //searches inside the page css to find the values for a selector
+
+ search: function(selector){
+ if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector];
+ var to = {}, selectorTest = new RegExp('^' + selector.escapeRegExp() + '$');
+
+ var searchStyles = function(rules){
+ Array.each(rules, function(rule){
+ if (rule.media){
+ searchStyles(rule.rules || rule.cssRules);
+ return;
+ }
+ if (!rule.style) return;
+ var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){
+ return m.toLowerCase();
+ }) : null;
+ if (!selectorText || !selectorTest.test(selectorText)) return;
+ Object.each(Element.Styles, function(value, style){
+ if (!rule.style[style] || Element.ShortStyles[style]) return;
+ value = String(rule.style[style]);
+ to[style] = ((/^rgb/).test(value)) ? value.rgbToHex() : value;
+ });
+ });
+ };
+
+ Array.each(document.styleSheets, function(sheet){
+ var href = sheet.href;
+ if (href && href.indexOf('://') > -1 && href.indexOf(document.domain) == -1) return;
+ var rules = sheet.rules || sheet.cssRules;
+ searchStyles(rules);
+ });
+ return Fx.CSS.Cache[selector] = to;
+ }
+
+});
+
+Fx.CSS.Cache = {};
+
+Fx.CSS.Parsers = {
+
+ Color: {
+ parse: function(value){
+ if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true);
+ return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false;
+ },
+ compute: function(from, to, delta){
+ return from.map(function(value, i){
+ return Math.round(Fx.compute(from[i], to[i], delta));
+ });
+ },
+ serve: function(value){
+ return value.map(Number);
+ }
+ },
+
+ Number: {
+ parse: parseFloat,
+ compute: Fx.compute,
+ serve: function(value, unit){
+ return (unit) ? value + unit : value;
+ }
+ },
+
+ String: {
+ parse: Function.convert(false),
+ compute: function(zero, one){
+ return one;
+ },
+ serve: function(zero){
+ return zero;
+ }
+ }
+
+};
+
+
+
+/*
+---
+
+name: Fx.Morph
+
+description: Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules.
+
+license: MIT-style license.
+
+requires: Fx.CSS
+
+provides: Fx.Morph
+
+...
+*/
+
+Fx.Morph = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+ },
+
+ set: function(now){
+ if (typeof now == 'string') now = this.search(now);
+ for (var p in now) this.render(this.element, p, now[p], this.options.unit);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ var now = {};
+ for (var p in from) now[p] = this.parent(from[p], to[p], delta);
+ return now;
+ },
+
+ start: function(properties){
+ if (!this.check(properties)) return this;
+ if (typeof properties == 'string') properties = this.search(properties);
+ var from = {}, to = {};
+ for (var p in properties){
+ var parsed = this.prepare(this.element, p, properties[p]);
+ from[p] = parsed.from;
+ to[p] = parsed.to;
+ }
+ return this.parent(from, to);
+ }
+
+});
+
+Element.Properties.morph = {
+
+ set: function(options){
+ this.get('morph').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var morph = this.retrieve('morph');
+ if (!morph){
+ morph = new Fx.Morph(this, {link: 'cancel'});
+ this.store('morph', morph);
+ }
+ return morph;
+ }
+
+};
+
+Element.implement({
+
+ morph: function(props){
+ this.get('morph').start(props);
+ return this;
+ }
+
+});
+
+/*
+---
+
+name: Fx.Transitions
+
+description: Contains a set of advanced transitions to be used with any of the Fx Classes.
+
+license: MIT-style license.
+
+credits:
+ - Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified and optimized to be used with MooTools.
+
+requires: Fx
+
+provides: Fx.Transitions
+
+...
+*/
+
+Fx.implement({
+
+ getTransition: function(){
+ var trans = this.options.transition || Fx.Transitions.Sine.easeInOut;
+ if (typeof trans == 'string'){
+ var data = trans.split(':');
+ trans = Fx.Transitions;
+ trans = trans[data[0]] || trans[data[0].capitalize()];
+ if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')];
+ }
+ return trans;
+ }
+
+});
+
+Fx.Transition = function(transition, params){
+ params = Array.convert(params);
+ var easeIn = function(pos){
+ return transition(pos, params);
+ };
+ return Object.append(easeIn, {
+ easeIn: easeIn,
+ easeOut: function(pos){
+ return 1 - transition(1 - pos, params);
+ },
+ easeInOut: function(pos){
+ return (pos <= 0.5 ? transition(2 * pos, params) : (2 - transition(2 * (1 - pos), params))) / 2;
+ }
+ });
+};
+
+Fx.Transitions = {
+
+ linear: function(zero){
+ return zero;
+ }
+
+};
+
+
+
+Fx.Transitions.extend = function(transitions){
+ for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]);
+};
+
+Fx.Transitions.extend({
+
+ Pow: function(p, x){
+ return Math.pow(p, x && x[0] || 6);
+ },
+
+ Expo: function(p){
+ return Math.pow(2, 8 * (p - 1));
+ },
+
+ Circ: function(p){
+ return 1 - Math.sin(Math.acos(p));
+ },
+
+ Sine: function(p){
+ return 1 - Math.cos(p * Math.PI / 2);
+ },
+
+ Back: function(p, x){
+ x = x && x[0] || 1.618;
+ return Math.pow(p, 2) * ((x + 1) * p - x);
+ },
+
+ Bounce: function(p){
+ var value;
+ for (var a = 0, b = 1; 1; a += b, b /= 2){
+ if (p >= (7 - 4 * a) / 11){
+ value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
+ break;
+ }
+ }
+ return value;
+ },
+
+ Elastic: function(p, x){
+ return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x && x[0] || 1) / 3);
+ }
+
+});
+
+['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){
+ Fx.Transitions[transition] = new Fx.Transition(function(p){
+ return Math.pow(p, i + 2);
+ });
+});
+
+/*
+---
+
+name: Fx.Tween
+
+description: Formerly Fx.Style, effect to transition any CSS property for an element.
+
+license: MIT-style license.
+
+requires: Fx.CSS
+
+provides: [Fx.Tween, Element.fade, Element.highlight]
+
+...
+*/
+
+Fx.Tween = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+ },
+
+ set: function(property, now){
+ if (arguments.length == 1){
+ now = property;
+ property = this.property || this.options.property;
+ }
+ this.render(this.element, property, now, this.options.unit);
+ return this;
+ },
+
+ start: function(property, from, to){
+ if (!this.check(property, from, to)) return this;
+ var args = Array.flatten(arguments);
+ this.property = this.options.property || args.shift();
+ var parsed = this.prepare(this.element, this.property, args);
+ return this.parent(parsed.from, parsed.to);
+ }
+
+});
+
+Element.Properties.tween = {
+
+ set: function(options){
+ this.get('tween').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var tween = this.retrieve('tween');
+ if (!tween){
+ tween = new Fx.Tween(this, {link: 'cancel'});
+ this.store('tween', tween);
+ }
+ return tween;
+ }
+
+};
+
+Element.implement({
+
+ tween: function(property, from, to){
+ this.get('tween').start(property, from, to);
+ return this;
+ },
+
+ fade: function(){
+ var fade = this.get('tween'), method, args = ['opacity'].append(arguments), toggle;
+ if (args[1] == null) args[1] = 'toggle';
+ switch (args[1]){
+ case 'in': method = 'start'; args[1] = 1; break;
+ case 'out': method = 'start'; args[1] = 0; break;
+ case 'show': method = 'set'; args[1] = 1; break;
+ case 'hide': method = 'set'; args[1] = 0; break;
+ case 'toggle':
+ var flag = this.retrieve('fade:flag', this.getStyle('opacity') == 1);
+ method = 'start';
+ args[1] = flag ? 0 : 1;
+ this.store('fade:flag', !flag);
+ toggle = true;
+ break;
+ default: method = 'start';
+ }
+ if (!toggle) this.eliminate('fade:flag');
+ fade[method].apply(fade, args);
+ var to = args[args.length - 1];
+
+ if (method == 'set'){
+ this.setStyle('visibility', to == 0 ? 'hidden' : 'visible');
+ } else if (to != 0){
+ if (fade.$chain.length){
+ fade.chain(function(){
+ this.element.setStyle('visibility', 'visible');
+ this.callChain();
+ });
+ } else {
+ this.setStyle('visibility', 'visible');
+ }
+ } else {
+ fade.chain(function(){
+ if (this.element.getStyle('opacity')) return;
+ this.element.setStyle('visibility', 'hidden');
+ this.callChain();
+ });
+ }
+
+ return this;
+ },
+
+ highlight: function(start, end){
+ if (!end){
+ end = this.retrieve('highlight:original', this.getStyle('background-color'));
+ end = (end == 'transparent') ? '#fff' : end;
+ }
+ var tween = this.get('tween');
+ tween.start('background-color', start || '#ffff88', end).chain(function(){
+ this.setStyle('background-color', this.retrieve('highlight:original'));
+ tween.callChain();
+ }.bind(this));
+ return this;
+ }
+
+});
+
+/*
+---
+
+name: Request
+
+description: Powerful all purpose Request Class. Uses XMLHTTPRequest.
+
+license: MIT-style license.
+
+requires: [Object, Element, Chain, Events, Options, Class.Thenable, Browser]
+
+provides: Request
+
+...
+*/
+
+(function(){
+
+var empty = function(){},
+ progressSupport = ('onprogress' in new Browser.Request);
+
+var Request = this.Request = new Class({
+
+ Implements: [Chain, Events, Options, Class.Thenable],
+
+ options: {/*
+ onRequest: function(){},
+ onLoadstart: function(event, xhr){},
+ onProgress: function(event, xhr){},
+ onComplete: function(){},
+ onCancel: function(){},
+ onSuccess: function(responseText, responseXML){},
+ onFailure: function(xhr){},
+ onException: function(headerName, value){},
+ onTimeout: function(){},
+ user: '',
+ password: '',
+ withCredentials: false,*/
+ url: '',
+ data: '',
+ headers: {
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+ },
+ async: true,
+ format: false,
+ method: 'post',
+ link: 'ignore',
+ isSuccess: null,
+ emulation: true,
+ urlEncoded: true,
+ encoding: 'utf-8',
+ evalScripts: false,
+ evalResponse: false,
+ timeout: 0,
+ noCache: false
+ },
+
+ initialize: function(options){
+ this.xhr = new Browser.Request();
+ this.setOptions(options);
+ this.headers = this.options.headers;
+ },
+
+ onStateChange: function(){
+ var xhr = this.xhr;
+ if (xhr.readyState != 4 || !this.running) return;
+ this.running = false;
+ this.status = 0;
+ Function.attempt(function(){
+ var status = xhr.status;
+ this.status = (status == 1223) ? 204 : status;
+ }.bind(this));
+ xhr.onreadystatechange = empty;
+ if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
+ if (this.timer){
+ clearTimeout(this.timer);
+ delete this.timer;
+ }
+
+ this.response = {text: this.xhr.responseText || '', xml: this.xhr.responseXML};
+ if (this.options.isSuccess.call(this, this.status))
+ this.success(this.response.text, this.response.xml);
+ else
+ this.failure();
+ },
+
+ isSuccess: function(){
+ var status = this.status;
+ return (status >= 200 && status < 300);
+ },
+
+ isRunning: function(){
+ return !!this.running;
+ },
+
+ processScripts: function(text){
+ if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return Browser.exec(text);
+ return text.stripScripts(this.options.evalScripts);
+ },
+
+ success: function(text, xml){
+ this.onSuccess(this.processScripts(text), xml);
+ this.resolve({text: text, xml: xml});
+ },
+
+ onSuccess: function(){
+ this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain();
+ },
+
+ failure: function(){
+ this.onFailure();
+ this.reject({reason: 'failure', xhr: this.xhr});
+ },
+
+ onFailure: function(){
+ this.fireEvent('complete').fireEvent('failure', this.xhr);
+ },
+
+ loadstart: function(event){
+ this.fireEvent('loadstart', [event, this.xhr]);
+ },
+
+ progress: function(event){
+ this.fireEvent('progress', [event, this.xhr]);
+ },
+
+ timeout: function(){
+ this.fireEvent('timeout', this.xhr);
+ this.reject({reason: 'timeout', xhr: this.xhr});
+ },
+
+ setHeader: function(name, value){
+ this.headers[name] = value;
+ return this;
+ },
+
+ getHeader: function(name){
+ return Function.attempt(function(){
+ return this.xhr.getResponseHeader(name);
+ }.bind(this));
+ },
+
+ check: function(){
+ if (!this.running) return true;
+ switch (this.options.link){
+ case 'cancel': this.cancel(); return true;
+ case 'chain': this.chain(this.caller.pass(arguments, this)); return false;
+ }
+ return false;
+ },
+
+ send: function(options){
+ if (!this.check(options)) return this;
+
+ this.options.isSuccess = this.options.isSuccess || this.isSuccess;
+ this.running = true;
+
+ var type = typeOf(options);
+ if (type == 'string' || type == 'element') options = {data: options};
+
+ var old = this.options;
+ options = Object.append({data: old.data, url: old.url, method: old.method}, options);
+ var data = options.data, url = String(options.url), method = options.method.toLowerCase();
+
+ switch (typeOf(data)){
+ case 'element': data = document.id(data).toQueryString(); break;
+ case 'object': case 'hash': data = Object.toQueryString(data);
+ }
+
+ if (this.options.format){
+ var format = 'format=' + this.options.format;
+ data = (data) ? format + '&' + data : format;
+ }
+
+ if (this.options.emulation && !['get', 'post'].contains(method)){
+ var _method = '_method=' + method;
+ data = (data) ? _method + '&' + data : _method;
+ method = 'post';
+ }
+
+ if (this.options.urlEncoded && ['post', 'put'].contains(method)){
+ var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
+ this.headers['Content-type'] = 'application/x-www-form-urlencoded' + encoding;
+ }
+
+ if (!url) url = document.location.pathname;
+
+ var trimPosition = url.lastIndexOf('/');
+ if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition);
+
+ if (this.options.noCache)
+ url += (url.indexOf('?') > -1 ? '&' : '?') + String.uniqueID();
+
+ if (data && (method == 'get' || method == 'delete')){
+ url += (url.indexOf('?') > -1 ? '&' : '?') + data;
+ data = null;
+ }
+
+ var xhr = this.xhr;
+ if (progressSupport){
+ xhr.onloadstart = this.loadstart.bind(this);
+ xhr.onprogress = this.progress.bind(this);
+ }
+
+ xhr.open(method.toUpperCase(), url, this.options.async, this.options.user, this.options.password);
+ if ((this.options.withCredentials) && 'withCredentials' in xhr) xhr.withCredentials = true;
+
+ xhr.onreadystatechange = this.onStateChange.bind(this);
+
+ Object.each(this.headers, function(value, key){
+ try {
+ xhr.setRequestHeader(key, value);
+ } catch (e){
+ this.fireEvent('exception', [key, value]);
+ this.reject({reason: 'exception', xhr: xhr, exception: e});
+ }
+ }, this);
+
+ if (this.getThenableState() !== 'pending'){
+ this.resetThenable({reason: 'send'});
+ }
+ this.fireEvent('request');
+ xhr.send(data);
+ if (!this.options.async) this.onStateChange();
+ else if (this.options.timeout) this.timer = this.timeout.delay(this.options.timeout, this);
+ return this;
+ },
+
+ cancel: function(){
+ if (!this.running) return this;
+ this.running = false;
+ var xhr = this.xhr;
+ xhr.abort();
+ if (this.timer){
+ clearTimeout(this.timer);
+ delete this.timer;
+ }
+ xhr.onreadystatechange = empty;
+ if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
+ this.xhr = new Browser.Request();
+ this.fireEvent('cancel');
+ this.reject({reason: 'cancel', xhr: xhr});
+ return this;
+ }
+
+});
+
+var methods = {};
+['get', 'post', 'put', 'delete', 'patch', 'head', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'].each(function(method){
+ methods[method] = function(data){
+ var object = {
+ method: method
+ };
+ if (data != null) object.data = data;
+ return this.send(object);
+ };
+});
+
+Request.implement(methods);
+
+Element.Properties.send = {
+
+ set: function(options){
+ var send = this.get('send').cancel();
+ send.setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var send = this.retrieve('send');
+ if (!send){
+ send = new Request({
+ data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action')
+ });
+ this.store('send', send);
+ }
+ return send;
+ }
+
+};
+
+Element.implement({
+
+ send: function(url){
+ var sender = this.get('send');
+ sender.send({data: this, url: url || sender.options.url});
+ return this;
+ }
+
+});
+
+})();
+
+/*
+---
+
+name: Request.HTML
+
+description: Extends the basic Request Class with additional methods for interacting with HTML responses.
+
+license: MIT-style license.
+
+requires: [Element, Request]
+
+provides: Request.HTML
+
+...
+*/
+
+Request.HTML = new Class({
+
+ Extends: Request,
+
+ options: {
+ update: false,
+ append: false,
+ evalScripts: true,
+ filter: false,
+ headers: {
+ Accept: 'text/html, application/xml, text/xml, */*'
+ }
+ },
+
+ success: function(text){
+ var options = this.options, response = this.response;
+
+ response.html = text.stripScripts(function(script){
+ response.javascript = script;
+ });
+
+ var match = response.html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
+ if (match) response.html = match[1];
+ var temp = new Element('div').set('html', response.html);
+
+ response.tree = temp.childNodes;
+ response.elements = temp.getElements(options.filter || '*');
+
+ if (options.filter) response.tree = response.elements;
+ if (options.update){
+ var update = document.id(options.update).empty();
+ if (options.filter) update.adopt(response.elements);
+ else update.set('html', response.html);
+ } else if (options.append){
+ var append = document.id(options.append);
+ if (options.filter) response.elements.reverse().inject(append);
+ else append.adopt(temp.getChildren());
+ }
+ if (options.evalScripts) Browser.exec(response.javascript);
+
+ this.onSuccess(response.tree, response.elements, response.html, response.javascript);
+ this.resolve({tree: response.tree, elements: response.elements, html: response.html, javascript: response.javascript});
+ }
+
+});
+
+Element.Properties.load = {
+
+ set: function(options){
+ var load = this.get('load').cancel();
+ load.setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var load = this.retrieve('load');
+ if (!load){
+ load = new Request.HTML({data: this, link: 'cancel', update: this, method: 'get'});
+ this.store('load', load);
+ }
+ return load;
+ }
+
+};
+
+Element.implement({
+
+ load: function(){
+ this.get('load').send(Array.link(arguments, {data: Type.isObject, url: Type.isString}));
+ return this;
+ }
+
+});
+
+/*
+---
+
+name: JSON
+
+description: JSON encoder and decoder.
+
+license: MIT-style license.
+
+SeeAlso: <http://www.json.org/>
+
+requires: [Array, String, Number, Function]
+
+provides: JSON
+
+...
+*/
+
+if (typeof JSON == 'undefined') this.JSON = {};
+
+
+
+(function(){
+
+var special = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'};
+
+var escape = function(chr){
+ return special[chr] || '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).slice(-4);
+};
+
+JSON.validate = function(string){
+ string = string.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+ replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+ replace(/(?:^|:|,)(?:\s*\[)+/g, '');
+
+ return (/^[\],:{}\s]*$/).test(string);
+};
+
+JSON.encode = JSON.stringify ? function(obj){
+ return JSON.stringify(obj);
+} : function(obj){
+ if (obj && obj.toJSON) obj = obj.toJSON();
+
+ switch (typeOf(obj)){
+ case 'string':
+ return '"' + obj.replace(/[\x00-\x1f\\"]/g, escape) + '"';
+ case 'array':
+ return '[' + obj.map(JSON.encode).clean() + ']';
+ case 'object': case 'hash':
+ var string = [];
+ Object.each(obj, function(value, key){
+ var json = JSON.encode(value);
+ if (json) string.push(JSON.encode(key) + ':' + json);
+ });
+ return '{' + string + '}';
+ case 'number': case 'boolean': return '' + obj;
+ case 'null': return 'null';
+ }
+
+ return null;
+};
+
+JSON.secure = true;
+
+
+JSON.decode = function(string, secure){
+ if (!string || typeOf(string) != 'string') return null;
+
+ if (secure == null) secure = JSON.secure;
+ if (secure){
+ if (JSON.parse) return JSON.parse(string);
+ if (!JSON.validate(string)) throw new Error('JSON could not decode the input; security is enabled and the value is not secure.');
+ }
+
+ return eval('(' + string + ')');
+};
+
+})();
+
+/*
+---
+
+name: Request.JSON
+
+description: Extends the basic Request Class with additional methods for sending and receiving JSON data.
+
+license: MIT-style license.
+
+requires: [Request, JSON]
+
+provides: Request.JSON
+
+...
+*/
+
+Request.JSON = new Class({
+
+ Extends: Request,
+
+ options: {
+ /*onError: function(text, error){},*/
+ secure: true
+ },
+
+ initialize: function(options){
+ this.parent(options);
+ Object.append(this.headers, {
+ 'Accept': 'application/json',
+ 'X-Request': 'JSON'
+ });
+ },
+
+ success: function(text){
+ var json;
+ try {
+ json = this.response.json = JSON.decode(text, this.options.secure);
+ } catch (error){
+ this.fireEvent('error', [text, error]);
+ return;
+ }
+ if (json == null){
+ this.failure();
+ } else {
+ this.onSuccess(json, text);
+ this.resolve({json: json, text: text});
+ }
+ }
+
+});
+
+/*
+---
+
+name: Cookie
+
+description: Class for creating, reading, and deleting browser Cookies.
+
+license: MIT-style license.
+
+credits:
+ - Based on the functions by Peter-Paul Koch (http://quirksmode.org).
+
+requires: [Options, Browser]
+
+provides: Cookie
+
+...
+*/
+
+var Cookie = new Class({
+
+ Implements: Options,
+
+ options: {
+ path: '/',
+ domain: false,
+ duration: false,
+ secure: false,
+ document: document,
+ encode: true,
+ httpOnly: false
+ },
+
+ initialize: function(key, options){
+ this.key = key;
+ this.setOptions(options);
+ },
+
+ write: function(value){
+ if (this.options.encode) value = encodeURIComponent(value);
+ if (this.options.domain) value += '; domain=' + this.options.domain;
+ if (this.options.path) value += '; path=' + this.options.path;
+ if (this.options.duration){
+ var date = new Date();
+ date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000);
+ value += '; expires=' + date.toGMTString();
+ }
+ if (this.options.secure) value += '; secure';
+ if (this.options.httpOnly) value += '; HttpOnly';
+ this.options.document.cookie = this.key + '=' + value;
+ return this;
+ },
+
+ read: function(){
+ var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)');
+ return (value) ? decodeURIComponent(value[1]) : null;
+ },
+
+ dispose: function(){
+ new Cookie(this.key, Object.merge({}, this.options, {duration: -1})).write('');
+ return this;
+ }
+
+});
+
+Cookie.write = function(key, value, options){
+ return new Cookie(key, options).write(value);
+};
+
+Cookie.read = function(key){
+ return new Cookie(key).read();
+};
+
+Cookie.dispose = function(key, options){
+ return new Cookie(key, options).dispose();
+};
+
+/*
+---
+
+name: DOMReady
+
+description: Contains the custom event domready.
+
+license: MIT-style license.
+
+requires: [Browser, Element, Element.Event]
+
+provides: [DOMReady, DomReady]
+
+...
+*/
+
+(function(window, document){
+
+var ready,
+ loaded,
+ checks = [],
+ shouldPoll,
+ timer,
+ testElement = document.createElement('div');
+
+var domready = function(){
+ clearTimeout(timer);
+ if (!ready){
+ Browser.loaded = ready = true;
+ document.removeListener('DOMContentLoaded', domready).removeListener('readystatechange', check);
+ document.fireEvent('domready');
+ window.fireEvent('domready');
+ }
+ // cleanup scope vars
+ document = window = testElement = null;
+};
+
+var check = function(){
+ for (var i = checks.length; i--;) if (checks[i]()){
+ domready();
+ return true;
+ }
+ return false;
+};
+
+var poll = function(){
+ clearTimeout(timer);
+ if (!check()) timer = setTimeout(poll, 10);
+};
+
+document.addListener('DOMContentLoaded', domready);
+
+/*<ltIE8>*/
+// doScroll technique by Diego Perini http://javascript.nwbox.com/IEContentLoaded/
+// testElement.doScroll() throws when the DOM is not ready, only in the top window
+var doScrollWorks = function(){
+ try {
+ testElement.doScroll();
+ return true;
+ } catch (e){}
+ return false;
+};
+// If doScroll works already, it can't be used to determine domready
+// e.g. in an iframe
+if (testElement.doScroll && !doScrollWorks()){
+ checks.push(doScrollWorks);
+ shouldPoll = true;
+}
+/*</ltIE8>*/
+
+if (document.readyState) checks.push(function(){
+ var state = document.readyState;
+ return (state == 'loaded' || state == 'complete');
+});
+
+if ('onreadystatechange' in document) document.addListener('readystatechange', check);
+else shouldPoll = true;
+
+if (shouldPoll) poll();
+
+Element.Events.domready = {
+ onAdd: function(fn){
+ if (ready) fn.call(this);
+ }
+};
+
+// Make sure that domready fires before load
+Element.Events.load = {
+ base: 'load',
+ onAdd: function(fn){
+ if (loaded && this == window) fn.call(this);
+ },
+ condition: function(){
+ if (this == window){
+ domready();
+ delete Element.Events.load;
+ }
+ return true;
+ }
+};
+
+// This is based on the custom load event
+window.addEvent('load', function(){
+ loaded = true;
+});
+
+})(window, document);
diff --git a/www/js/mootools-debug.js b/www/js/mootools-debug.js
new file mode 100644
index 0000000..26e8850
--- /dev/null
+++ b/www/js/mootools-debug.js
@@ -0,0 +1,5180 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/core/1f9082c332d19034561f804233cd5b90
+
+packager build:
+ - packager build Core/Event Core/Browser Core/Class Core/Class.Extras Core/Element.Style Core/Element.Event Core/Element.Delegation Core/Element.Dimensions Core/JSON Core/DOMReady
+
+...
+*/
+
+/*
+---
+
+name: Core
+
+description: The heart of MooTools.
+
+license: MIT-style license.
+
+copyright: Copyright (c) 2006-2012 [Valerio Proietti](http://mad4milk.net/).
+
+authors: The MooTools production team (http://mootools.net/developers/)
+
+inspiration:
+ - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)
+ - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php)
+
+provides: [Core, MooTools, Type, typeOf, instanceOf, Native]
+
+...
+*/
+
+(function(){
+
+this.MooTools = {
+ version: '1.4.5',
+ build: 'ab8ea8824dc3b24b6666867a2c4ed58ebb762cf0'
+};
+
+// typeOf, instanceOf
+
+var typeOf = this.typeOf = function(item){
+ if (item == null) return 'null';
+ if (item.$family != null) return item.$family();
+
+ if (item.nodeName){
+ if (item.nodeType == 1) return 'element';
+ if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace';
+ } else if (typeof item.length == 'number'){
+ if (item.callee) return 'arguments';
+ if ('item' in item) return 'collection';
+ }
+
+ return typeof item;
+};
+
+var instanceOf = this.instanceOf = function(item, object){
+ if (item == null) return false;
+ var constructor = item.$constructor || item.constructor;
+ while (constructor){
+ if (constructor === object) return true;
+ constructor = constructor.parent;
+ }
+ /*<ltIE8>*/
+ if (!item.hasOwnProperty) return false;
+ /*</ltIE8>*/
+ return item instanceof object;
+};
+
+// Function overloading
+
+var Function = this.Function;
+
+var enumerables = true;
+for (var i in {toString: 1}) enumerables = null;
+if (enumerables) enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'constructor'];
+
+Function.prototype.overloadSetter = function(usePlural){
+ var self = this;
+ return function(a, b){
+ if (a == null) return this;
+ if (usePlural || typeof a != 'string'){
+ for (var k in a) self.call(this, k, a[k]);
+ if (enumerables) for (var i = enumerables.length; i--;){
+ k = enumerables[i];
+ if (a.hasOwnProperty(k)) self.call(this, k, a[k]);
+ }
+ } else {
+ self.call(this, a, b);
+ }
+ return this;
+ };
+};
+
+Function.prototype.overloadGetter = function(usePlural){
+ var self = this;
+ return function(a){
+ var args, result;
+ if (typeof a != 'string') args = a;
+ else if (arguments.length > 1) args = arguments;
+ else if (usePlural) args = [a];
+ if (args){
+ result = {};
+ for (var i = 0; i < args.length; i++) result[args[i]] = self.call(this, args[i]);
+ } else {
+ result = self.call(this, a);
+ }
+ return result;
+ };
+};
+
+Function.prototype.extend = function(key, value){
+ this[key] = value;
+}.overloadSetter();
+
+Function.prototype.implement = function(key, value){
+ this.prototype[key] = value;
+}.overloadSetter();
+
+// From
+
+var slice = Array.prototype.slice;
+
+Function.from = function(item){
+ return (typeOf(item) == 'function') ? item : function(){
+ return item;
+ };
+};
+
+Array.from = function(item){
+ if (item == null) return [];
+ return (Type.isEnumerable(item) && typeof item != 'string') ? (typeOf(item) == 'array') ? item : slice.call(item) : [item];
+};
+
+Number.from = function(item){
+ var number = parseFloat(item);
+ return isFinite(number) ? number : null;
+};
+
+String.from = function(item){
+ return item + '';
+};
+
+// hide, protect
+
+Function.implement({
+
+ hide: function(){
+ this.$hidden = true;
+ return this;
+ },
+
+ protect: function(){
+ this.$protected = true;
+ return this;
+ }
+
+});
+
+// Type
+
+var Type = this.Type = function(name, object){
+ if (name){
+ var lower = name.toLowerCase();
+ var typeCheck = function(item){
+ return (typeOf(item) == lower);
+ };
+
+ Type['is' + name] = typeCheck;
+ if (object != null){
+ object.prototype.$family = (function(){
+ return lower;
+ }).hide();
+
+ }
+ }
+
+ if (object == null) return null;
+
+ object.extend(this);
+ object.$constructor = Type;
+ object.prototype.$constructor = object;
+
+ return object;
+};
+
+var toString = Object.prototype.toString;
+
+Type.isEnumerable = function(item){
+ return (item != null && typeof item.length == 'number' && toString.call(item) != '[object Function]' );
+};
+
+var hooks = {};
+
+var hooksOf = function(object){
+ var type = typeOf(object.prototype);
+ return hooks[type] || (hooks[type] = []);
+};
+
+var implement = function(name, method){
+ if (method && method.$hidden) return;
+
+ var hooks = hooksOf(this);
+
+ for (var i = 0; i < hooks.length; i++){
+ var hook = hooks[i];
+ if (typeOf(hook) == 'type') implement.call(hook, name, method);
+ else hook.call(this, name, method);
+ }
+
+ var previous = this.prototype[name];
+ if (previous == null || !previous.$protected) this.prototype[name] = method;
+
+ if (this[name] == null && typeOf(method) == 'function') extend.call(this, name, function(item){
+ return method.apply(item, slice.call(arguments, 1));
+ });
+};
+
+var extend = function(name, method){
+ if (method && method.$hidden) return;
+ var previous = this[name];
+ if (previous == null || !previous.$protected) this[name] = method;
+};
+
+Type.implement({
+
+ implement: implement.overloadSetter(),
+
+ extend: extend.overloadSetter(),
+
+ alias: function(name, existing){
+ implement.call(this, name, this.prototype[existing]);
+ }.overloadSetter(),
+
+ mirror: function(hook){
+ hooksOf(this).push(hook);
+ return this;
+ }
+
+});
+
+new Type('Type', Type);
+
+// Default Types
+
+var force = function(name, object, methods){
+ var isType = (object != Object),
+ prototype = object.prototype;
+
+ if (isType) object = new Type(name, object);
+
+ for (var i = 0, l = methods.length; i < l; i++){
+ var key = methods[i],
+ generic = object[key],
+ proto = prototype[key];
+
+ if (generic) generic.protect();
+ if (isType && proto) object.implement(key, proto.protect());
+ }
+
+ if (isType){
+ var methodsEnumerable = prototype.propertyIsEnumerable(methods[0]);
+ object.forEachMethod = function(fn){
+ if (!methodsEnumerable) for (var i = 0, l = methods.length; i < l; i++){
+ fn.call(prototype, prototype[methods[i]], methods[i]);
+ }
+ for (var key in prototype) fn.call(prototype, prototype[key], key)
+ };
+ }
+
+ return force;
+};
+
+force('String', String, [
+ 'charAt', 'charCodeAt', 'concat', 'indexOf', 'lastIndexOf', 'match', 'quote', 'replace', 'search',
+ 'slice', 'split', 'substr', 'substring', 'trim', 'toLowerCase', 'toUpperCase'
+])('Array', Array, [
+ 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice',
+ 'indexOf', 'lastIndexOf', 'filter', 'forEach', 'every', 'map', 'some', 'reduce', 'reduceRight'
+])('Number', Number, [
+ 'toExponential', 'toFixed', 'toLocaleString', 'toPrecision'
+])('Function', Function, [
+ 'apply', 'call', 'bind'
+])('RegExp', RegExp, [
+ 'exec', 'test'
+])('Object', Object, [
+ 'create', 'defineProperty', 'defineProperties', 'keys',
+ 'getPrototypeOf', 'getOwnPropertyDescriptor', 'getOwnPropertyNames',
+ 'preventExtensions', 'isExtensible', 'seal', 'isSealed', 'freeze', 'isFrozen'
+])('Date', Date, ['now']);
+
+Object.extend = extend.overloadSetter();
+
+Date.extend('now', function(){
+ return +(new Date);
+});
+
+new Type('Boolean', Boolean);
+
+// fixes NaN returning as Number
+
+Number.prototype.$family = function(){
+ return isFinite(this) ? 'number' : 'null';
+}.hide();
+
+// Number.random
+
+Number.extend('random', function(min, max){
+ return Math.floor(Math.random() * (max - min + 1) + min);
+});
+
+// forEach, each
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+Object.extend('forEach', function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) fn.call(bind, object[key], key, object);
+ }
+});
+
+Object.each = Object.forEach;
+
+Array.implement({
+
+ forEach: function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (i in this) fn.call(bind, this[i], i, this);
+ }
+ },
+
+ each: function(fn, bind){
+ Array.forEach(this, fn, bind);
+ return this;
+ }
+
+});
+
+// Array & Object cloning, Object merging and appending
+
+var cloneOf = function(item){
+ switch (typeOf(item)){
+ case 'array': return item.clone();
+ case 'object': return Object.clone(item);
+ default: return item;
+ }
+};
+
+Array.implement('clone', function(){
+ var i = this.length, clone = new Array(i);
+ while (i--) clone[i] = cloneOf(this[i]);
+ return clone;
+});
+
+var mergeOne = function(source, key, current){
+ switch (typeOf(current)){
+ case 'object':
+ if (typeOf(source[key]) == 'object') Object.merge(source[key], current);
+ else source[key] = Object.clone(current);
+ break;
+ case 'array': source[key] = current.clone(); break;
+ default: source[key] = current;
+ }
+ return source;
+};
+
+Object.extend({
+
+ merge: function(source, k, v){
+ if (typeOf(k) == 'string') return mergeOne(source, k, v);
+ for (var i = 1, l = arguments.length; i < l; i++){
+ var object = arguments[i];
+ for (var key in object) mergeOne(source, key, object[key]);
+ }
+ return source;
+ },
+
+ clone: function(object){
+ var clone = {};
+ for (var key in object) clone[key] = cloneOf(object[key]);
+ return clone;
+ },
+
+ append: function(original){
+ for (var i = 1, l = arguments.length; i < l; i++){
+ var extended = arguments[i] || {};
+ for (var key in extended) original[key] = extended[key];
+ }
+ return original;
+ }
+
+});
+
+// Object-less types
+
+['Object', 'WhiteSpace', 'TextNode', 'Collection', 'Arguments'].each(function(name){
+ new Type(name);
+});
+
+// Unique ID
+
+var UID = Date.now();
+
+String.extend('uniqueID', function(){
+ return (UID++).toString(36);
+});
+
+
+
+})();
+
+
+/*
+---
+
+name: Array
+
+description: Contains Array Prototypes like each, contains, and erase.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Array
+
+...
+*/
+
+Array.implement({
+
+ /*<!ES5>*/
+ every: function(fn, bind){
+ for (var i = 0, l = this.length >>> 0; i < l; i++){
+ if ((i in this) && !fn.call(bind, this[i], i, this)) return false;
+ }
+ return true;
+ },
+
+ filter: function(fn, bind){
+ var results = [];
+ for (var value, i = 0, l = this.length >>> 0; i < l; i++) if (i in this){
+ value = this[i];
+ if (fn.call(bind, value, i, this)) results.push(value);
+ }
+ return results;
+ },
+
+ indexOf: function(item, from){
+ var length = this.length >>> 0;
+ for (var i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++){
+ if (this[i] === item) return i;
+ }
+ return -1;
+ },
+
+ map: function(fn, bind){
+ var length = this.length >>> 0, results = Array(length);
+ for (var i = 0; i < length; i++){
+ if (i in this) results[i] = fn.call(bind, this[i], i, this);
+ }
+ return results;
+ },
+
+ some: function(fn, bind){
+ for (var i = 0, l = this.length >>> 0; i < l; i++){
+ if ((i in this) && fn.call(bind, this[i], i, this)) return true;
+ }
+ return false;
+ },
+ /*</!ES5>*/
+
+ clean: function(){
+ return this.filter(function(item){
+ return item != null;
+ });
+ },
+
+ invoke: function(methodName){
+ var args = Array.slice(arguments, 1);
+ return this.map(function(item){
+ return item[methodName].apply(item, args);
+ });
+ },
+
+ associate: function(keys){
+ var obj = {}, length = Math.min(this.length, keys.length);
+ for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
+ return obj;
+ },
+
+ link: function(object){
+ var result = {};
+ for (var i = 0, l = this.length; i < l; i++){
+ for (var key in object){
+ if (object[key](this[i])){
+ result[key] = this[i];
+ delete object[key];
+ break;
+ }
+ }
+ }
+ return result;
+ },
+
+ contains: function(item, from){
+ return this.indexOf(item, from) != -1;
+ },
+
+ append: function(array){
+ this.push.apply(this, array);
+ return this;
+ },
+
+ getLast: function(){
+ return (this.length) ? this[this.length - 1] : null;
+ },
+
+ getRandom: function(){
+ return (this.length) ? this[Number.random(0, this.length - 1)] : null;
+ },
+
+ include: function(item){
+ if (!this.contains(item)) this.push(item);
+ return this;
+ },
+
+ combine: function(array){
+ for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
+ return this;
+ },
+
+ erase: function(item){
+ for (var i = this.length; i--;){
+ if (this[i] === item) this.splice(i, 1);
+ }
+ return this;
+ },
+
+ empty: function(){
+ this.length = 0;
+ return this;
+ },
+
+ flatten: function(){
+ var array = [];
+ for (var i = 0, l = this.length; i < l; i++){
+ var type = typeOf(this[i]);
+ if (type == 'null') continue;
+ array = array.concat((type == 'array' || type == 'collection' || type == 'arguments' || instanceOf(this[i], Array)) ? Array.flatten(this[i]) : this[i]);
+ }
+ return array;
+ },
+
+ pick: function(){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (this[i] != null) return this[i];
+ }
+ return null;
+ },
+
+ hexToRgb: function(array){
+ if (this.length != 3) return null;
+ var rgb = this.map(function(value){
+ if (value.length == 1) value += value;
+ return value.toInt(16);
+ });
+ return (array) ? rgb : 'rgb(' + rgb + ')';
+ },
+
+ rgbToHex: function(array){
+ if (this.length < 3) return null;
+ if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
+ var hex = [];
+ for (var i = 0; i < 3; i++){
+ var bit = (this[i] - 0).toString(16);
+ hex.push((bit.length == 1) ? '0' + bit : bit);
+ }
+ return (array) ? hex : '#' + hex.join('');
+ }
+
+});
+
+
+
+
+/*
+---
+
+name: Function
+
+description: Contains Function Prototypes like create, bind, pass, and delay.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Function
+
+...
+*/
+
+Function.extend({
+
+ attempt: function(){
+ for (var i = 0, l = arguments.length; i < l; i++){
+ try {
+ return arguments[i]();
+ } catch (e){}
+ }
+ return null;
+ }
+
+});
+
+Function.implement({
+
+ attempt: function(args, bind){
+ try {
+ return this.apply(bind, Array.from(args));
+ } catch (e){}
+
+ return null;
+ },
+
+ /*<!ES5-bind>*/
+ bind: function(that){
+ var self = this,
+ args = arguments.length > 1 ? Array.slice(arguments, 1) : null,
+ F = function(){};
+
+ var bound = function(){
+ var context = that, length = arguments.length;
+ if (this instanceof bound){
+ F.prototype = self.prototype;
+ context = new F;
+ }
+ var result = (!args && !length)
+ ? self.call(context)
+ : self.apply(context, args && length ? args.concat(Array.slice(arguments)) : args || arguments);
+ return context == that ? result : context;
+ };
+ return bound;
+ },
+ /*</!ES5-bind>*/
+
+ pass: function(args, bind){
+ var self = this;
+ if (args != null) args = Array.from(args);
+ return function(){
+ return self.apply(bind, args || arguments);
+ };
+ },
+
+ delay: function(delay, bind, args){
+ return setTimeout(this.pass((args == null ? [] : args), bind), delay);
+ },
+
+ periodical: function(periodical, bind, args){
+ return setInterval(this.pass((args == null ? [] : args), bind), periodical);
+ }
+
+});
+
+
+
+
+/*
+---
+
+name: Number
+
+description: Contains Number Prototypes like limit, round, times, and ceil.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Number
+
+...
+*/
+
+Number.implement({
+
+ limit: function(min, max){
+ return Math.min(max, Math.max(min, this));
+ },
+
+ round: function(precision){
+ precision = Math.pow(10, precision || 0).toFixed(precision < 0 ? -precision : 0);
+ return Math.round(this * precision) / precision;
+ },
+
+ times: function(fn, bind){
+ for (var i = 0; i < this; i++) fn.call(bind, i, this);
+ },
+
+ toFloat: function(){
+ return parseFloat(this);
+ },
+
+ toInt: function(base){
+ return parseInt(this, base || 10);
+ }
+
+});
+
+Number.alias('each', 'times');
+
+(function(math){
+ var methods = {};
+ math.each(function(name){
+ if (!Number[name]) methods[name] = function(){
+ return Math[name].apply(null, [this].concat(Array.from(arguments)));
+ };
+ });
+ Number.implement(methods);
+})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']);
+
+
+/*
+---
+
+name: String
+
+description: Contains String Prototypes like camelCase, capitalize, test, and toInt.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: String
+
+...
+*/
+
+String.implement({
+
+ test: function(regex, params){
+ return ((typeOf(regex) == 'regexp') ? regex : new RegExp('' + regex, params)).test(this);
+ },
+
+ contains: function(string, separator){
+ return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : String(this).indexOf(string) > -1;
+ },
+
+ trim: function(){
+ return String(this).replace(/^\s+|\s+$/g, '');
+ },
+
+ clean: function(){
+ return String(this).replace(/\s+/g, ' ').trim();
+ },
+
+ camelCase: function(){
+ return String(this).replace(/-\D/g, function(match){
+ return match.charAt(1).toUpperCase();
+ });
+ },
+
+ hyphenate: function(){
+ return String(this).replace(/[A-Z]/g, function(match){
+ return ('-' + match.charAt(0).toLowerCase());
+ });
+ },
+
+ capitalize: function(){
+ return String(this).replace(/\b[a-z]/g, function(match){
+ return match.toUpperCase();
+ });
+ },
+
+ escapeRegExp: function(){
+ return String(this).replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
+ },
+
+ toInt: function(base){
+ return parseInt(this, base || 10);
+ },
+
+ toFloat: function(){
+ return parseFloat(this);
+ },
+
+ hexToRgb: function(array){
+ var hex = String(this).match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
+ return (hex) ? hex.slice(1).hexToRgb(array) : null;
+ },
+
+ rgbToHex: function(array){
+ var rgb = String(this).match(/\d{1,3}/g);
+ return (rgb) ? rgb.rgbToHex(array) : null;
+ },
+
+ substitute: function(object, regexp){
+ return String(this).replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){
+ if (match.charAt(0) == '\\') return match.slice(1);
+ return (object[name] != null) ? object[name] : '';
+ });
+ }
+
+});
+
+
+/*
+---
+
+name: Browser
+
+description: The Browser Object. Contains Browser initialization, Window and Document, and the Browser Hash.
+
+license: MIT-style license.
+
+requires: [Array, Function, Number, String]
+
+provides: [Browser, Window, Document]
+
+...
+*/
+
+(function(){
+
+var document = this.document;
+var window = document.window = this;
+
+var ua = navigator.userAgent.toLowerCase(),
+ platform = navigator.platform.toLowerCase(),
+ UA = ua.match(/(opera|ie|trident|firefox|chrome|version)[\s\/:]([\w\d\.]+)?.*?(safari|version[\s\/:]([\w\d\.]+)|$)/) || [null, 'unknown', 0],
+ mode = (UA[1] == 'ie' || UA[1] == 'trident') && document.documentMode;
+
+var Browser = this.Browser = {
+
+ extend: Function.prototype.extend,
+
+ name: (UA[1] == 'version') ? UA[3] : (UA[1] == 'trident' ? 'ie' : UA[1]),
+
+ version: mode || parseFloat((UA[1] == 'opera' && UA[4]) ? UA[4] : UA[2]),
+
+ Platform: {
+ name: ua.match(/ip(?:ad|od|hone)/) ? 'ios' : (ua.match(/(?:webos|android)/) || platform.match(/mac|win|linux/) || ['other'])[0]
+ },
+
+ Features: {
+ xpath: !!(document.evaluate),
+ air: !!(window.runtime),
+ query: !!(document.querySelector),
+ json: !!(window.JSON)
+ },
+
+ Plugins: {}
+
+};
+
+Browser[Browser.name] = true;
+Browser[Browser.name + parseInt(Browser.version, 10)] = true;
+Browser.Platform[Browser.Platform.name] = true;
+
+// Request
+
+Browser.Request = (function(){
+
+ var XMLHTTP = function(){
+ return new XMLHttpRequest();
+ };
+
+ var MSXML2 = function(){
+ return new ActiveXObject('MSXML2.XMLHTTP');
+ };
+
+ var MSXML = function(){
+ return new ActiveXObject('Microsoft.XMLHTTP');
+ };
+
+ return Function.attempt(function(){
+ XMLHTTP();
+ return XMLHTTP;
+ }, function(){
+ MSXML2();
+ return MSXML2;
+ }, function(){
+ MSXML();
+ return MSXML;
+ });
+
+})();
+
+Browser.Features.xhr = !!(Browser.Request);
+
+// Flash detection
+
+var version = (Function.attempt(function(){
+ return navigator.plugins['Shockwave Flash'].description;
+}, function(){
+ return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
+}) || '0 r0').match(/\d+/g);
+
+Browser.Plugins.Flash = {
+ version: Number(version[0] || '0.' + version[1]) || 0,
+ build: Number(version[2]) || 0
+};
+
+// String scripts
+
+Browser.exec = function(text){
+ if (!text) return text;
+ if (window.execScript){
+ window.execScript(text);
+ } else {
+ var script = document.createElement('script');
+ script.setAttribute('type', 'text/javascript');
+ script.text = text;
+ document.head.appendChild(script);
+ document.head.removeChild(script);
+ }
+ return text;
+};
+
+String.implement('stripScripts', function(exec){
+ var scripts = '';
+ var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(all, code){
+ scripts += code + '\n';
+ return '';
+ });
+ if (exec === true) Browser.exec(scripts);
+ else if (typeOf(exec) == 'function') exec(scripts, text);
+ return text;
+});
+
+// Window, Document
+
+Browser.extend({
+ Document: this.Document,
+ Window: this.Window,
+ Element: this.Element,
+ Event: this.Event
+});
+
+this.Window = this.$constructor = new Type('Window', function(){});
+
+this.$family = Function.from('window').hide();
+
+Window.mirror(function(name, method){
+ window[name] = method;
+});
+
+this.Document = document.$constructor = new Type('Document', function(){});
+
+document.$family = Function.from('document').hide();
+
+Document.mirror(function(name, method){
+ document[name] = method;
+});
+
+document.html = document.documentElement;
+if (!document.head) document.head = document.getElementsByTagName('head')[0];
+
+if (document.execCommand) try {
+ document.execCommand("BackgroundImageCache", false, true);
+} catch (e){}
+
+/*<ltIE9>*/
+if (this.attachEvent && !this.addEventListener){
+ var unloadEvent = function(){
+ this.detachEvent('onunload', unloadEvent);
+ document.head = document.html = document.window = null;
+ };
+ this.attachEvent('onunload', unloadEvent);
+}
+
+// IE fails on collections and <select>.options (refers to <select>)
+var arrayFrom = Array.from;
+try {
+ arrayFrom(document.html.childNodes);
+} catch(e){
+ Array.from = function(item){
+ if (typeof item != 'string' && Type.isEnumerable(item) && typeOf(item) != 'array'){
+ var i = item.length, array = new Array(i);
+ while (i--) array[i] = item[i];
+ return array;
+ }
+ return arrayFrom(item);
+ };
+
+ var prototype = Array.prototype,
+ slice = prototype.slice;
+ ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice'].each(function(name){
+ var method = prototype[name];
+ Array[name] = function(item){
+ return method.apply(Array.from(item), slice.call(arguments, 1));
+ };
+ });
+}
+/*</ltIE9>*/
+
+
+
+})();
+
+
+/*
+---
+
+name: Object
+
+description: Object generic methods
+
+license: MIT-style license.
+
+requires: Type
+
+provides: [Object, Hash]
+
+...
+*/
+
+(function(){
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+Object.extend({
+
+ subset: function(object, keys){
+ var results = {};
+ for (var i = 0, l = keys.length; i < l; i++){
+ var k = keys[i];
+ if (k in object) results[k] = object[k];
+ }
+ return results;
+ },
+
+ map: function(object, fn, bind){
+ var results = {};
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) results[key] = fn.call(bind, object[key], key, object);
+ }
+ return results;
+ },
+
+ filter: function(object, fn, bind){
+ var results = {};
+ for (var key in object){
+ var value = object[key];
+ if (hasOwnProperty.call(object, key) && fn.call(bind, value, key, object)) results[key] = value;
+ }
+ return results;
+ },
+
+ every: function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && !fn.call(bind, object[key], key)) return false;
+ }
+ return true;
+ },
+
+ some: function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && fn.call(bind, object[key], key)) return true;
+ }
+ return false;
+ },
+
+ keys: function(object){
+ var keys = [];
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) keys.push(key);
+ }
+ return keys;
+ },
+
+ values: function(object){
+ var values = [];
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) values.push(object[key]);
+ }
+ return values;
+ },
+
+ getLength: function(object){
+ return Object.keys(object).length;
+ },
+
+ keyOf: function(object, value){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && object[key] === value) return key;
+ }
+ return null;
+ },
+
+ contains: function(object, value){
+ return Object.keyOf(object, value) != null;
+ },
+
+ toQueryString: function(object, base){
+ var queryString = [];
+
+ Object.each(object, function(value, key){
+ if (base) key = base + '[' + key + ']';
+ var result;
+ switch (typeOf(value)){
+ case 'object': result = Object.toQueryString(value, key); break;
+ case 'array':
+ var qs = {};
+ value.each(function(val, i){
+ qs[i] = val;
+ });
+ result = Object.toQueryString(qs, key);
+ break;
+ default: result = key + '=' + encodeURIComponent(value);
+ }
+ if (value != null) queryString.push(result);
+ });
+
+ return queryString.join('&');
+ }
+
+});
+
+})();
+
+
+
+
+/*
+---
+
+name: Event
+
+description: Contains the Event Type, to make the event object cross-browser.
+
+license: MIT-style license.
+
+requires: [Window, Document, Array, Function, String, Object]
+
+provides: Event
+
+...
+*/
+
+(function() {
+
+var _keys = {};
+
+var DOMEvent = this.DOMEvent = new Type('DOMEvent', function(event, win){
+ if (!win) win = window;
+ event = event || win.event;
+ if (event.$extended) return event;
+ this.event = event;
+ this.$extended = true;
+ this.shift = event.shiftKey;
+ this.control = event.ctrlKey;
+ this.alt = event.altKey;
+ this.meta = event.metaKey;
+ var type = this.type = event.type;
+ var target = event.target || event.srcElement;
+ while (target && target.nodeType == 3) target = target.parentNode;
+ this.target = document.id(target);
+
+ if (type.indexOf('key') == 0){
+ var code = this.code = (event.which || event.keyCode);
+ this.key = _keys[code];
+ if (type == 'keydown'){
+ if (code > 111 && code < 124) this.key = 'f' + (code - 111);
+ else if (code > 95 && code < 106) this.key = code - 96;
+ }
+ if (this.key == null) this.key = String.fromCharCode(code).toLowerCase();
+ } else if (type == 'click' || type == 'dblclick' || type == 'contextmenu' || type == 'DOMMouseScroll' || type.indexOf('mouse') == 0){
+ var doc = win.document;
+ doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+ this.page = {
+ x: (event.pageX != null) ? event.pageX : event.clientX + doc.scrollLeft,
+ y: (event.pageY != null) ? event.pageY : event.clientY + doc.scrollTop
+ };
+ this.client = {
+ x: (event.pageX != null) ? event.pageX - win.pageXOffset : event.clientX,
+ y: (event.pageY != null) ? event.pageY - win.pageYOffset : event.clientY
+ };
+ if (type == 'DOMMouseScroll' || type == 'mousewheel')
+ this.wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
+
+ this.rightClick = (event.which == 3 || event.button == 2);
+ if (type == 'mouseover' || type == 'mouseout'){
+ var related = event.relatedTarget || event[(type == 'mouseover' ? 'from' : 'to') + 'Element'];
+ while (related && related.nodeType == 3) related = related.parentNode;
+ this.relatedTarget = document.id(related);
+ }
+ } else if (type.indexOf('touch') == 0 || type.indexOf('gesture') == 0){
+ this.rotation = event.rotation;
+ this.scale = event.scale;
+ this.targetTouches = event.targetTouches;
+ this.changedTouches = event.changedTouches;
+ var touches = this.touches = event.touches;
+ if (touches && touches[0]){
+ var touch = touches[0];
+ this.page = {x: touch.pageX, y: touch.pageY};
+ this.client = {x: touch.clientX, y: touch.clientY};
+ }
+ }
+
+ if (!this.client) this.client = {};
+ if (!this.page) this.page = {};
+});
+
+DOMEvent.implement({
+
+ stop: function(){
+ return this.preventDefault().stopPropagation();
+ },
+
+ stopPropagation: function(){
+ if (this.event.stopPropagation) this.event.stopPropagation();
+ else this.event.cancelBubble = true;
+ return this;
+ },
+
+ preventDefault: function(){
+ if (this.event.preventDefault) this.event.preventDefault();
+ else this.event.returnValue = false;
+ return this;
+ }
+
+});
+
+DOMEvent.defineKey = function(code, key){
+ _keys[code] = key;
+ return this;
+};
+
+DOMEvent.defineKeys = DOMEvent.defineKey.overloadSetter(true);
+
+DOMEvent.defineKeys({
+ '38': 'up', '40': 'down', '37': 'left', '39': 'right',
+ '27': 'esc', '32': 'space', '8': 'backspace', '9': 'tab',
+ '46': 'delete', '13': 'enter'
+});
+
+})();
+
+
+
+
+
+
+/*
+---
+
+name: Class
+
+description: Contains the Class Function for easily creating, extending, and implementing reusable Classes.
+
+license: MIT-style license.
+
+requires: [Array, String, Function, Number]
+
+provides: Class
+
+...
+*/
+
+(function(){
+
+var Class = this.Class = new Type('Class', function(params){
+ if (instanceOf(params, Function)) params = {initialize: params};
+
+ var newClass = function(){
+ reset(this);
+ if (newClass.$prototyping) return this;
+ this.$caller = null;
+ var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
+ this.$caller = this.caller = null;
+ return value;
+ }.extend(this).implement(params);
+
+ newClass.$constructor = Class;
+ newClass.prototype.$constructor = newClass;
+ newClass.prototype.parent = parent;
+
+ return newClass;
+});
+
+var parent = function(){
+ if (!this.$caller) throw new Error('The method "parent" cannot be called.');
+ var name = this.$caller.$name,
+ parent = this.$caller.$owner.parent,
+ previous = (parent) ? parent.prototype[name] : null;
+ if (!previous) throw new Error('The method "' + name + '" has no parent.');
+ return previous.apply(this, arguments);
+};
+
+var reset = function(object){
+ for (var key in object){
+ var value = object[key];
+ switch (typeOf(value)){
+ case 'object':
+ var F = function(){};
+ F.prototype = value;
+ object[key] = reset(new F);
+ break;
+ case 'array': object[key] = value.clone(); break;
+ }
+ }
+ return object;
+};
+
+var wrap = function(self, key, method){
+ if (method.$origin) method = method.$origin;
+ var wrapper = function(){
+ if (method.$protected && this.$caller == null) throw new Error('The method "' + key + '" cannot be called.');
+ var caller = this.caller, current = this.$caller;
+ this.caller = current; this.$caller = wrapper;
+ var result = method.apply(this, arguments);
+ this.$caller = current; this.caller = caller;
+ return result;
+ }.extend({$owner: self, $origin: method, $name: key});
+ return wrapper;
+};
+
+var implement = function(key, value, retain){
+ if (Class.Mutators.hasOwnProperty(key)){
+ value = Class.Mutators[key].call(this, value);
+ if (value == null) return this;
+ }
+
+ if (typeOf(value) == 'function'){
+ if (value.$hidden) return this;
+ this.prototype[key] = (retain) ? value : wrap(this, key, value);
+ } else {
+ Object.merge(this.prototype, key, value);
+ }
+
+ return this;
+};
+
+var getInstance = function(klass){
+ klass.$prototyping = true;
+ var proto = new klass;
+ delete klass.$prototyping;
+ return proto;
+};
+
+Class.implement('implement', implement.overloadSetter());
+
+Class.Mutators = {
+
+ Extends: function(parent){
+ this.parent = parent;
+ this.prototype = getInstance(parent);
+ },
+
+ Implements: function(items){
+ Array.from(items).each(function(item){
+ var instance = new item;
+ for (var key in instance) implement.call(this, key, instance[key], true);
+ }, this);
+ }
+};
+
+})();
+
+
+/*
+---
+
+name: Class.Extras
+
+description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks.
+
+license: MIT-style license.
+
+requires: Class
+
+provides: [Class.Extras, Chain, Events, Options]
+
+...
+*/
+
+(function(){
+
+this.Chain = new Class({
+
+ $chain: [],
+
+ chain: function(){
+ this.$chain.append(Array.flatten(arguments));
+ return this;
+ },
+
+ callChain: function(){
+ return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false;
+ },
+
+ clearChain: function(){
+ this.$chain.empty();
+ return this;
+ }
+
+});
+
+var removeOn = function(string){
+ return string.replace(/^on([A-Z])/, function(full, first){
+ return first.toLowerCase();
+ });
+};
+
+this.Events = new Class({
+
+ $events: {},
+
+ addEvent: function(type, fn, internal){
+ type = removeOn(type);
+
+
+
+ this.$events[type] = (this.$events[type] || []).include(fn);
+ if (internal) fn.internal = true;
+ return this;
+ },
+
+ addEvents: function(events){
+ for (var type in events) this.addEvent(type, events[type]);
+ return this;
+ },
+
+ fireEvent: function(type, args, delay){
+ type = removeOn(type);
+ var events = this.$events[type];
+ if (!events) return this;
+ args = Array.from(args);
+ events.each(function(fn){
+ if (delay) fn.delay(delay, this, args);
+ else fn.apply(this, args);
+ }, this);
+ return this;
+ },
+
+ removeEvent: function(type, fn){
+ type = removeOn(type);
+ var events = this.$events[type];
+ if (events && !fn.internal){
+ var index = events.indexOf(fn);
+ if (index != -1) delete events[index];
+ }
+ return this;
+ },
+
+ removeEvents: function(events){
+ var type;
+ if (typeOf(events) == 'object'){
+ for (type in events) this.removeEvent(type, events[type]);
+ return this;
+ }
+ if (events) events = removeOn(events);
+ for (type in this.$events){
+ if (events && events != type) continue;
+ var fns = this.$events[type];
+ for (var i = fns.length; i--;) if (i in fns){
+ this.removeEvent(type, fns[i]);
+ }
+ }
+ return this;
+ }
+
+});
+
+this.Options = new Class({
+
+ setOptions: function(){
+ var options = this.options = Object.merge.apply(null, [{}, this.options].append(arguments));
+ if (this.addEvent) for (var option in options){
+ if (typeOf(options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue;
+ this.addEvent(option, options[option]);
+ delete options[option];
+ }
+ return this;
+ }
+
+});
+
+})();
+
+
+/*
+---
+name: Slick.Parser
+description: Standalone CSS3 Selector parser
+provides: Slick.Parser
+...
+*/
+
+;(function(){
+
+var parsed,
+ separatorIndex,
+ combinatorIndex,
+ reversed,
+ cache = {},
+ reverseCache = {},
+ reUnescape = /\\/g;
+
+var parse = function(expression, isReversed){
+ if (expression == null) return null;
+ if (expression.Slick === true) return expression;
+ expression = ('' + expression).replace(/^\s+|\s+$/g, '');
+ reversed = !!isReversed;
+ var currentCache = (reversed) ? reverseCache : cache;
+ if (currentCache[expression]) return currentCache[expression];
+ parsed = {
+ Slick: true,
+ expressions: [],
+ raw: expression,
+ reverse: function(){
+ return parse(this.raw, true);
+ }
+ };
+ separatorIndex = -1;
+ while (expression != (expression = expression.replace(regexp, parser)));
+ parsed.length = parsed.expressions.length;
+ return currentCache[parsed.raw] = (reversed) ? reverse(parsed) : parsed;
+};
+
+var reverseCombinator = function(combinator){
+ if (combinator === '!') return ' ';
+ else if (combinator === ' ') return '!';
+ else if ((/^!/).test(combinator)) return combinator.replace(/^!/, '');
+ else return '!' + combinator;
+};
+
+var reverse = function(expression){
+ var expressions = expression.expressions;
+ for (var i = 0; i < expressions.length; i++){
+ var exp = expressions[i];
+ var last = {parts: [], tag: '*', combinator: reverseCombinator(exp[0].combinator)};
+
+ for (var j = 0; j < exp.length; j++){
+ var cexp = exp[j];
+ if (!cexp.reverseCombinator) cexp.reverseCombinator = ' ';
+ cexp.combinator = cexp.reverseCombinator;
+ delete cexp.reverseCombinator;
+ }
+
+ exp.reverse().push(last);
+ }
+ return expression;
+};
+
+var escapeRegExp = function(string){// Credit: XRegExp 0.6.1 (c) 2007-2008 Steven Levithan <http://stevenlevithan.com/regex/xregexp/> MIT License
+ return string.replace(/[-[\]{}()*+?.\\^$|,#\s]/g, function(match){
+ return '\\' + match;
+ });
+};
+
+var regexp = new RegExp(
+/*
+#!/usr/bin/env ruby
+puts "\t\t" + DATA.read.gsub(/\(\?x\)|\s+#.*$|\s+|\\$|\\n/,'')
+__END__
+ "(?x)^(?:\
+ \\s* ( , ) \\s* # Separator \n\
+ | \\s* ( <combinator>+ ) \\s* # Combinator \n\
+ | ( \\s+ ) # CombinatorChildren \n\
+ | ( <unicode>+ | \\* ) # Tag \n\
+ | \\# ( <unicode>+ ) # ID \n\
+ | \\. ( <unicode>+ ) # ClassName \n\
+ | # Attribute \n\
+ \\[ \
+ \\s* (<unicode1>+) (?: \
+ \\s* ([*^$!~|]?=) (?: \
+ \\s* (?:\
+ ([\"']?)(.*?)\\9 \
+ )\
+ ) \
+ )? \\s* \
+ \\](?!\\]) \n\
+ | :+ ( <unicode>+ )(?:\
+ \\( (?:\
+ (?:([\"'])([^\\12]*)\\12)|((?:\\([^)]+\\)|[^()]*)+)\
+ ) \\)\
+ )?\
+ )"
+*/
+ "^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)"
+ .replace(/<combinator>/, '[' + escapeRegExp(">+~`!@$%^&={}\\;</") + ']')
+ .replace(/<unicode>/g, '(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
+ .replace(/<unicode1>/g, '(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
+);
+
+function parser(
+ rawMatch,
+
+ separator,
+ combinator,
+ combinatorChildren,
+
+ tagName,
+ id,
+ className,
+
+ attributeKey,
+ attributeOperator,
+ attributeQuote,
+ attributeValue,
+
+ pseudoMarker,
+ pseudoClass,
+ pseudoQuote,
+ pseudoClassQuotedValue,
+ pseudoClassValue
+){
+ if (separator || separatorIndex === -1){
+ parsed.expressions[++separatorIndex] = [];
+ combinatorIndex = -1;
+ if (separator) return '';
+ }
+
+ if (combinator || combinatorChildren || combinatorIndex === -1){
+ combinator = combinator || ' ';
+ var currentSeparator = parsed.expressions[separatorIndex];
+ if (reversed && currentSeparator[combinatorIndex])
+ currentSeparator[combinatorIndex].reverseCombinator = reverseCombinator(combinator);
+ currentSeparator[++combinatorIndex] = {combinator: combinator, tag: '*'};
+ }
+
+ var currentParsed = parsed.expressions[separatorIndex][combinatorIndex];
+
+ if (tagName){
+ currentParsed.tag = tagName.replace(reUnescape, '');
+
+ } else if (id){
+ currentParsed.id = id.replace(reUnescape, '');
+
+ } else if (className){
+ className = className.replace(reUnescape, '');
+
+ if (!currentParsed.classList) currentParsed.classList = [];
+ if (!currentParsed.classes) currentParsed.classes = [];
+ currentParsed.classList.push(className);
+ currentParsed.classes.push({
+ value: className,
+ regexp: new RegExp('(^|\\s)' + escapeRegExp(className) + '(\\s|$)')
+ });
+
+ } else if (pseudoClass){
+ pseudoClassValue = pseudoClassValue || pseudoClassQuotedValue;
+ pseudoClassValue = pseudoClassValue ? pseudoClassValue.replace(reUnescape, '') : null;
+
+ if (!currentParsed.pseudos) currentParsed.pseudos = [];
+ currentParsed.pseudos.push({
+ key: pseudoClass.replace(reUnescape, ''),
+ value: pseudoClassValue,
+ type: pseudoMarker.length == 1 ? 'class' : 'element'
+ });
+
+ } else if (attributeKey){
+ attributeKey = attributeKey.replace(reUnescape, '');
+ attributeValue = (attributeValue || '').replace(reUnescape, '');
+
+ var test, regexp;
+
+ switch (attributeOperator){
+ case '^=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) ); break;
+ case '$=' : regexp = new RegExp( escapeRegExp(attributeValue) +'$' ); break;
+ case '~=' : regexp = new RegExp( '(^|\\s)'+ escapeRegExp(attributeValue) +'(\\s|$)' ); break;
+ case '|=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) +'(-|$)' ); break;
+ case '=' : test = function(value){
+ return attributeValue == value;
+ }; break;
+ case '*=' : test = function(value){
+ return value && value.indexOf(attributeValue) > -1;
+ }; break;
+ case '!=' : test = function(value){
+ return attributeValue != value;
+ }; break;
+ default : test = function(value){
+ return !!value;
+ };
+ }
+
+ if (attributeValue == '' && (/^[*$^]=$/).test(attributeOperator)) test = function(){
+ return false;
+ };
+
+ if (!test) test = function(value){
+ return value && regexp.test(value);
+ };
+
+ if (!currentParsed.attributes) currentParsed.attributes = [];
+ currentParsed.attributes.push({
+ key: attributeKey,
+ operator: attributeOperator,
+ value: attributeValue,
+ test: test
+ });
+
+ }
+
+ return '';
+};
+
+// Slick NS
+
+var Slick = (this.Slick || {});
+
+Slick.parse = function(expression){
+ return parse(expression);
+};
+
+Slick.escapeRegExp = escapeRegExp;
+
+if (!this.Slick) this.Slick = Slick;
+
+}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this);
+
+
+/*
+---
+name: Slick.Finder
+description: The new, superfast css selector engine.
+provides: Slick.Finder
+requires: Slick.Parser
+...
+*/
+
+;(function(){
+
+var local = {},
+ featuresCache = {},
+ toString = Object.prototype.toString;
+
+// Feature / Bug detection
+
+local.isNativeCode = function(fn){
+ return (/\{\s*\[native code\]\s*\}/).test('' + fn);
+};
+
+local.isXML = function(document){
+ return (!!document.xmlVersion) || (!!document.xml) || (toString.call(document) == '[object XMLDocument]') ||
+ (document.nodeType == 9 && document.documentElement.nodeName != 'HTML');
+};
+
+local.setDocument = function(document){
+
+ // convert elements / window arguments to document. if document cannot be extrapolated, the function returns.
+ var nodeType = document.nodeType;
+ if (nodeType == 9); // document
+ else if (nodeType) document = document.ownerDocument; // node
+ else if (document.navigator) document = document.document; // window
+ else return;
+
+ // check if it's the old document
+
+ if (this.document === document) return;
+ this.document = document;
+
+ // check if we have done feature detection on this document before
+
+ var root = document.documentElement,
+ rootUid = this.getUIDXML(root),
+ features = featuresCache[rootUid],
+ feature;
+
+ if (features){
+ for (feature in features){
+ this[feature] = features[feature];
+ }
+ return;
+ }
+
+ features = featuresCache[rootUid] = {};
+
+ features.root = root;
+ features.isXMLDocument = this.isXML(document);
+
+ features.brokenStarGEBTN
+ = features.starSelectsClosedQSA
+ = features.idGetsName
+ = features.brokenMixedCaseQSA
+ = features.brokenGEBCN
+ = features.brokenCheckedQSA
+ = features.brokenEmptyAttributeQSA
+ = features.isHTMLDocument
+ = features.nativeMatchesSelector
+ = false;
+
+ var starSelectsClosed, starSelectsComments,
+ brokenSecondClassNameGEBCN, cachedGetElementsByClassName,
+ brokenFormAttributeGetter;
+
+ var selected, id = 'slick_uniqueid';
+ var testNode = document.createElement('div');
+
+ var testRoot = document.body || document.getElementsByTagName('body')[0] || root;
+ testRoot.appendChild(testNode);
+
+ // on non-HTML documents innerHTML and getElementsById doesnt work properly
+ try {
+ testNode.innerHTML = '<a id="'+id+'"></a>';
+ features.isHTMLDocument = !!document.getElementById(id);
+ } catch(e){};
+
+ if (features.isHTMLDocument){
+
+ testNode.style.display = 'none';
+
+ // IE returns comment nodes for getElementsByTagName('*') for some documents
+ testNode.appendChild(document.createComment(''));
+ starSelectsComments = (testNode.getElementsByTagName('*').length > 1);
+
+ // IE returns closed nodes (EG:"</foo>") for getElementsByTagName('*') for some documents
+ try {
+ testNode.innerHTML = 'foo</foo>';
+ selected = testNode.getElementsByTagName('*');
+ starSelectsClosed = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
+ } catch(e){};
+
+ features.brokenStarGEBTN = starSelectsComments || starSelectsClosed;
+
+ // IE returns elements with the name instead of just id for getElementsById for some documents
+ try {
+ testNode.innerHTML = '<a name="'+ id +'"></a><b id="'+ id +'"></b>';
+ features.idGetsName = document.getElementById(id) === testNode.firstChild;
+ } catch(e){};
+
+ if (testNode.getElementsByClassName){
+
+ // Safari 3.2 getElementsByClassName caches results
+ try {
+ testNode.innerHTML = '<a class="f"></a><a class="b"></a>';
+ testNode.getElementsByClassName('b').length;
+ testNode.firstChild.className = 'b';
+ cachedGetElementsByClassName = (testNode.getElementsByClassName('b').length != 2);
+ } catch(e){};
+
+ // Opera 9.6 getElementsByClassName doesnt detects the class if its not the first one
+ try {
+ testNode.innerHTML = '<a class="a"></a><a class="f b a"></a>';
+ brokenSecondClassNameGEBCN = (testNode.getElementsByClassName('a').length != 2);
+ } catch(e){};
+
+ features.brokenGEBCN = cachedGetElementsByClassName || brokenSecondClassNameGEBCN;
+ }
+
+ if (testNode.querySelectorAll){
+ // IE 8 returns closed nodes (EG:"</foo>") for querySelectorAll('*') for some documents
+ try {
+ testNode.innerHTML = 'foo</foo>';
+ selected = testNode.querySelectorAll('*');
+ features.starSelectsClosedQSA = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
+ } catch(e){};
+
+ // Safari 3.2 querySelectorAll doesnt work with mixedcase on quirksmode
+ try {
+ testNode.innerHTML = '<a class="MiX"></a>';
+ features.brokenMixedCaseQSA = !testNode.querySelectorAll('.MiX').length;
+ } catch(e){};
+
+ // Webkit and Opera dont return selected options on querySelectorAll
+ try {
+ testNode.innerHTML = '<select><option selected="selected">a</option></select>';
+ features.brokenCheckedQSA = (testNode.querySelectorAll(':checked').length == 0);
+ } catch(e){};
+
+ // IE returns incorrect results for attr[*^$]="" selectors on querySelectorAll
+ try {
+ testNode.innerHTML = '<a class=""></a>';
+ features.brokenEmptyAttributeQSA = (testNode.querySelectorAll('[class*=""]').length != 0);
+ } catch(e){};
+
+ }
+
+ // IE6-7, if a form has an input of id x, form.getAttribute(x) returns a reference to the input
+ try {
+ testNode.innerHTML = '<form action="s"><input id="action"/></form>';
+ brokenFormAttributeGetter = (testNode.firstChild.getAttribute('action') != 's');
+ } catch(e){};
+
+ // native matchesSelector function
+
+ features.nativeMatchesSelector = root.matchesSelector || /*root.msMatchesSelector ||*/ root.mozMatchesSelector || root.webkitMatchesSelector;
+ if (features.nativeMatchesSelector) try {
+ // if matchesSelector trows errors on incorrect sintaxes we can use it
+ features.nativeMatchesSelector.call(root, ':slick');
+ features.nativeMatchesSelector = null;
+ } catch(e){};
+
+ }
+
+ try {
+ root.slick_expando = 1;
+ delete root.slick_expando;
+ features.getUID = this.getUIDHTML;
+ } catch(e) {
+ features.getUID = this.getUIDXML;
+ }
+
+ testRoot.removeChild(testNode);
+ testNode = selected = testRoot = null;
+
+ // getAttribute
+
+ features.getAttribute = (features.isHTMLDocument && brokenFormAttributeGetter) ? function(node, name){
+ var method = this.attributeGetters[name];
+ if (method) return method.call(node);
+ var attributeNode = node.getAttributeNode(name);
+ return (attributeNode) ? attributeNode.nodeValue : null;
+ } : function(node, name){
+ var method = this.attributeGetters[name];
+ return (method) ? method.call(node) : node.getAttribute(name);
+ };
+
+ // hasAttribute
+
+ features.hasAttribute = (root && this.isNativeCode(root.hasAttribute)) ? function(node, attribute) {
+ return node.hasAttribute(attribute);
+ } : function(node, attribute) {
+ node = node.getAttributeNode(attribute);
+ return !!(node && (node.specified || node.nodeValue));
+ };
+
+ // contains
+ // FIXME: Add specs: local.contains should be different for xml and html documents?
+ var nativeRootContains = root && this.isNativeCode(root.contains),
+ nativeDocumentContains = document && this.isNativeCode(document.contains);
+
+ features.contains = (nativeRootContains && nativeDocumentContains) ? function(context, node){
+ return context.contains(node);
+ } : (nativeRootContains && !nativeDocumentContains) ? function(context, node){
+ // IE8 does not have .contains on document.
+ return context === node || ((context === document) ? document.documentElement : context).contains(node);
+ } : (root && root.compareDocumentPosition) ? function(context, node){
+ return context === node || !!(context.compareDocumentPosition(node) & 16);
+ } : function(context, node){
+ if (node) do {
+ if (node === context) return true;
+ } while ((node = node.parentNode));
+ return false;
+ };
+
+ // document order sorting
+ // credits to Sizzle (http://sizzlejs.com/)
+
+ features.documentSorter = (root.compareDocumentPosition) ? function(a, b){
+ if (!a.compareDocumentPosition || !b.compareDocumentPosition) return 0;
+ return a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
+ } : ('sourceIndex' in root) ? function(a, b){
+ if (!a.sourceIndex || !b.sourceIndex) return 0;
+ return a.sourceIndex - b.sourceIndex;
+ } : (document.createRange) ? function(a, b){
+ if (!a.ownerDocument || !b.ownerDocument) return 0;
+ var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
+ aRange.setStart(a, 0);
+ aRange.setEnd(a, 0);
+ bRange.setStart(b, 0);
+ bRange.setEnd(b, 0);
+ return aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
+ } : null ;
+
+ root = null;
+
+ for (feature in features){
+ this[feature] = features[feature];
+ }
+};
+
+// Main Method
+
+var reSimpleSelector = /^([#.]?)((?:[\w-]+|\*))$/,
+ reEmptyAttribute = /\[.+[*$^]=(?:""|'')?\]/,
+ qsaFailExpCache = {};
+
+local.search = function(context, expression, append, first){
+
+ var found = this.found = (first) ? null : (append || []);
+
+ if (!context) return found;
+ else if (context.navigator) context = context.document; // Convert the node from a window to a document
+ else if (!context.nodeType) return found;
+
+ // setup
+
+ var parsed, i,
+ uniques = this.uniques = {},
+ hasOthers = !!(append && append.length),
+ contextIsDocument = (context.nodeType == 9);
+
+ if (this.document !== (contextIsDocument ? context : context.ownerDocument)) this.setDocument(context);
+
+ // avoid duplicating items already in the append array
+ if (hasOthers) for (i = found.length; i--;) uniques[this.getUID(found[i])] = true;
+
+ // expression checks
+
+ if (typeof expression == 'string'){ // expression is a string
+
+ /*<simple-selectors-override>*/
+ var simpleSelector = expression.match(reSimpleSelector);
+ simpleSelectors: if (simpleSelector) {
+
+ var symbol = simpleSelector[1],
+ name = simpleSelector[2],
+ node, nodes;
+
+ if (!symbol){
+
+ if (name == '*' && this.brokenStarGEBTN) break simpleSelectors;
+ nodes = context.getElementsByTagName(name);
+ if (first) return nodes[0] || null;
+ for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+
+ } else if (symbol == '#'){
+
+ if (!this.isHTMLDocument || !contextIsDocument) break simpleSelectors;
+ node = context.getElementById(name);
+ if (!node) return found;
+ if (this.idGetsName && node.getAttributeNode('id').nodeValue != name) break simpleSelectors;
+ if (first) return node || null;
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+
+ } else if (symbol == '.'){
+
+ if (!this.isHTMLDocument || ((!context.getElementsByClassName || this.brokenGEBCN) && context.querySelectorAll)) break simpleSelectors;
+ if (context.getElementsByClassName && !this.brokenGEBCN){
+ nodes = context.getElementsByClassName(name);
+ if (first) return nodes[0] || null;
+ for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+ } else {
+ var matchClass = new RegExp('(^|\\s)'+ Slick.escapeRegExp(name) +'(\\s|$)');
+ nodes = context.getElementsByTagName('*');
+ for (i = 0; node = nodes[i++];){
+ className = node.className;
+ if (!(className && matchClass.test(className))) continue;
+ if (first) return node;
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+ }
+
+ }
+
+ if (hasOthers) this.sort(found);
+ return (first) ? null : found;
+
+ }
+ /*</simple-selectors-override>*/
+
+ /*<query-selector-override>*/
+ querySelector: if (context.querySelectorAll) {
+
+ if (!this.isHTMLDocument
+ || qsaFailExpCache[expression]
+ //TODO: only skip when expression is actually mixed case
+ || this.brokenMixedCaseQSA
+ || (this.brokenCheckedQSA && expression.indexOf(':checked') > -1)
+ || (this.brokenEmptyAttributeQSA && reEmptyAttribute.test(expression))
+ || (!contextIsDocument //Abort when !contextIsDocument and...
+ // there are multiple expressions in the selector
+ // since we currently only fix non-document rooted QSA for single expression selectors
+ && expression.indexOf(',') > -1
+ )
+ || Slick.disableQSA
+ ) break querySelector;
+
+ var _expression = expression, _context = context;
+ if (!contextIsDocument){
+ // non-document rooted QSA
+ // credits to Andrew Dupont
+ var currentId = _context.getAttribute('id'), slickid = 'slickid__';
+ _context.setAttribute('id', slickid);
+ _expression = '#' + slickid + ' ' + _expression;
+ context = _context.parentNode;
+ }
+
+ try {
+ if (first) return context.querySelector(_expression) || null;
+ else nodes = context.querySelectorAll(_expression);
+ } catch(e) {
+ qsaFailExpCache[expression] = 1;
+ break querySelector;
+ } finally {
+ if (!contextIsDocument){
+ if (currentId) _context.setAttribute('id', currentId);
+ else _context.removeAttribute('id');
+ context = _context;
+ }
+ }
+
+ if (this.starSelectsClosedQSA) for (i = 0; node = nodes[i++];){
+ if (node.nodeName > '@' && !(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ } else for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+
+ if (hasOthers) this.sort(found);
+ return found;
+
+ }
+ /*</query-selector-override>*/
+
+ parsed = this.Slick.parse(expression);
+ if (!parsed.length) return found;
+ } else if (expression == null){ // there is no expression
+ return found;
+ } else if (expression.Slick){ // expression is a parsed Slick object
+ parsed = expression;
+ } else if (this.contains(context.documentElement || context, expression)){ // expression is a node
+ (found) ? found.push(expression) : found = expression;
+ return found;
+ } else { // other junk
+ return found;
+ }
+
+ /*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
+
+ // cache elements for the nth selectors
+
+ this.posNTH = {};
+ this.posNTHLast = {};
+ this.posNTHType = {};
+ this.posNTHTypeLast = {};
+
+ /*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
+
+ // if append is null and there is only a single selector with one expression use pushArray, else use pushUID
+ this.push = (!hasOthers && (first || (parsed.length == 1 && parsed.expressions[0].length == 1))) ? this.pushArray : this.pushUID;
+
+ if (found == null) found = [];
+
+ // default engine
+
+ var j, m, n;
+ var combinator, tag, id, classList, classes, attributes, pseudos;
+ var currentItems, currentExpression, currentBit, lastBit, expressions = parsed.expressions;
+
+ search: for (i = 0; (currentExpression = expressions[i]); i++) for (j = 0; (currentBit = currentExpression[j]); j++){
+
+ combinator = 'combinator:' + currentBit.combinator;
+ if (!this[combinator]) continue search;
+
+ tag = (this.isXMLDocument) ? currentBit.tag : currentBit.tag.toUpperCase();
+ id = currentBit.id;
+ classList = currentBit.classList;
+ classes = currentBit.classes;
+ attributes = currentBit.attributes;
+ pseudos = currentBit.pseudos;
+ lastBit = (j === (currentExpression.length - 1));
+
+ this.bitUniques = {};
+
+ if (lastBit){
+ this.uniques = uniques;
+ this.found = found;
+ } else {
+ this.uniques = {};
+ this.found = [];
+ }
+
+ if (j === 0){
+ this[combinator](context, tag, id, classes, attributes, pseudos, classList);
+ if (first && lastBit && found.length) break search;
+ } else {
+ if (first && lastBit) for (m = 0, n = currentItems.length; m < n; m++){
+ this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
+ if (found.length) break search;
+ } else for (m = 0, n = currentItems.length; m < n; m++) this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
+ }
+
+ currentItems = this.found;
+ }
+
+ // should sort if there are nodes in append and if you pass multiple expressions.
+ if (hasOthers || (parsed.expressions.length > 1)) this.sort(found);
+
+ return (first) ? (found[0] || null) : found;
+};
+
+// Utils
+
+local.uidx = 1;
+local.uidk = 'slick-uniqueid';
+
+local.getUIDXML = function(node){
+ var uid = node.getAttribute(this.uidk);
+ if (!uid){
+ uid = this.uidx++;
+ node.setAttribute(this.uidk, uid);
+ }
+ return uid;
+};
+
+local.getUIDHTML = function(node){
+ return node.uniqueNumber || (node.uniqueNumber = this.uidx++);
+};
+
+// sort based on the setDocument documentSorter method.
+
+local.sort = function(results){
+ if (!this.documentSorter) return results;
+ results.sort(this.documentSorter);
+ return results;
+};
+
+/*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
+
+local.cacheNTH = {};
+
+local.matchNTH = /^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/;
+
+local.parseNTHArgument = function(argument){
+ var parsed = argument.match(this.matchNTH);
+ if (!parsed) return false;
+ var special = parsed[2] || false;
+ var a = parsed[1] || 1;
+ if (a == '-') a = -1;
+ var b = +parsed[3] || 0;
+ parsed =
+ (special == 'n') ? {a: a, b: b} :
+ (special == 'odd') ? {a: 2, b: 1} :
+ (special == 'even') ? {a: 2, b: 0} : {a: 0, b: a};
+
+ return (this.cacheNTH[argument] = parsed);
+};
+
+local.createNTHPseudo = function(child, sibling, positions, ofType){
+ return function(node, argument){
+ var uid = this.getUID(node);
+ if (!this[positions][uid]){
+ var parent = node.parentNode;
+ if (!parent) return false;
+ var el = parent[child], count = 1;
+ if (ofType){
+ var nodeName = node.nodeName;
+ do {
+ if (el.nodeName != nodeName) continue;
+ this[positions][this.getUID(el)] = count++;
+ } while ((el = el[sibling]));
+ } else {
+ do {
+ if (el.nodeType != 1) continue;
+ this[positions][this.getUID(el)] = count++;
+ } while ((el = el[sibling]));
+ }
+ }
+ argument = argument || 'n';
+ var parsed = this.cacheNTH[argument] || this.parseNTHArgument(argument);
+ if (!parsed) return false;
+ var a = parsed.a, b = parsed.b, pos = this[positions][uid];
+ if (a == 0) return b == pos;
+ if (a > 0){
+ if (pos < b) return false;
+ } else {
+ if (b < pos) return false;
+ }
+ return ((pos - b) % a) == 0;
+ };
+};
+
+/*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
+
+local.pushArray = function(node, tag, id, classes, attributes, pseudos){
+ if (this.matchSelector(node, tag, id, classes, attributes, pseudos)) this.found.push(node);
+};
+
+local.pushUID = function(node, tag, id, classes, attributes, pseudos){
+ var uid = this.getUID(node);
+ if (!this.uniques[uid] && this.matchSelector(node, tag, id, classes, attributes, pseudos)){
+ this.uniques[uid] = true;
+ this.found.push(node);
+ }
+};
+
+local.matchNode = function(node, selector){
+ if (this.isHTMLDocument && this.nativeMatchesSelector){
+ try {
+ return this.nativeMatchesSelector.call(node, selector.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g, '[$1="$2"]'));
+ } catch(matchError) {}
+ }
+
+ var parsed = this.Slick.parse(selector);
+ if (!parsed) return true;
+
+ // simple (single) selectors
+ var expressions = parsed.expressions, simpleExpCounter = 0, i;
+ for (i = 0; (currentExpression = expressions[i]); i++){
+ if (currentExpression.length == 1){
+ var exp = currentExpression[0];
+ if (this.matchSelector(node, (this.isXMLDocument) ? exp.tag : exp.tag.toUpperCase(), exp.id, exp.classes, exp.attributes, exp.pseudos)) return true;
+ simpleExpCounter++;
+ }
+ }
+
+ if (simpleExpCounter == parsed.length) return false;
+
+ var nodes = this.search(this.document, parsed), item;
+ for (i = 0; item = nodes[i++];){
+ if (item === node) return true;
+ }
+ return false;
+};
+
+local.matchPseudo = function(node, name, argument){
+ var pseudoName = 'pseudo:' + name;
+ if (this[pseudoName]) return this[pseudoName](node, argument);
+ var attribute = this.getAttribute(node, name);
+ return (argument) ? argument == attribute : !!attribute;
+};
+
+local.matchSelector = function(node, tag, id, classes, attributes, pseudos){
+ if (tag){
+ var nodeName = (this.isXMLDocument) ? node.nodeName : node.nodeName.toUpperCase();
+ if (tag == '*'){
+ if (nodeName < '@') return false; // Fix for comment nodes and closed nodes
+ } else {
+ if (nodeName != tag) return false;
+ }
+ }
+
+ if (id && node.getAttribute('id') != id) return false;
+
+ var i, part, cls;
+ if (classes) for (i = classes.length; i--;){
+ cls = this.getAttribute(node, 'class');
+ if (!(cls && classes[i].regexp.test(cls))) return false;
+ }
+ if (attributes) for (i = attributes.length; i--;){
+ part = attributes[i];
+ if (part.operator ? !part.test(this.getAttribute(node, part.key)) : !this.hasAttribute(node, part.key)) return false;
+ }
+ if (pseudos) for (i = pseudos.length; i--;){
+ part = pseudos[i];
+ if (!this.matchPseudo(node, part.key, part.value)) return false;
+ }
+ return true;
+};
+
+var combinators = {
+
+ ' ': function(node, tag, id, classes, attributes, pseudos, classList){ // all child nodes, any level
+
+ var i, item, children;
+
+ if (this.isHTMLDocument){
+ getById: if (id){
+ item = this.document.getElementById(id);
+ if ((!item && node.all) || (this.idGetsName && item && item.getAttributeNode('id').nodeValue != id)){
+ // all[id] returns all the elements with that name or id inside node
+ // if theres just one it will return the element, else it will be a collection
+ children = node.all[id];
+ if (!children) return;
+ if (!children[0]) children = [children];
+ for (i = 0; item = children[i++];){
+ var idNode = item.getAttributeNode('id');
+ if (idNode && idNode.nodeValue == id){
+ this.push(item, tag, null, classes, attributes, pseudos);
+ break;
+ }
+ }
+ return;
+ }
+ if (!item){
+ // if the context is in the dom we return, else we will try GEBTN, breaking the getById label
+ if (this.contains(this.root, node)) return;
+ else break getById;
+ } else if (this.document !== node && !this.contains(node, item)) return;
+ this.push(item, tag, null, classes, attributes, pseudos);
+ return;
+ }
+ getByClass: if (classes && node.getElementsByClassName && !this.brokenGEBCN){
+ children = node.getElementsByClassName(classList.join(' '));
+ if (!(children && children.length)) break getByClass;
+ for (i = 0; item = children[i++];) this.push(item, tag, id, null, attributes, pseudos);
+ return;
+ }
+ }
+ getByTag: {
+ children = node.getElementsByTagName(tag);
+ if (!(children && children.length)) break getByTag;
+ if (!this.brokenStarGEBTN) tag = null;
+ for (i = 0; item = children[i++];) this.push(item, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '>': function(node, tag, id, classes, attributes, pseudos){ // direct children
+ if ((node = node.firstChild)) do {
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ } while ((node = node.nextSibling));
+ },
+
+ '+': function(node, tag, id, classes, attributes, pseudos){ // next sibling
+ while ((node = node.nextSibling)) if (node.nodeType == 1){
+ this.push(node, tag, id, classes, attributes, pseudos);
+ break;
+ }
+ },
+
+ '^': function(node, tag, id, classes, attributes, pseudos){ // first child
+ node = node.firstChild;
+ if (node){
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ else this['combinator:+'](node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '~': function(node, tag, id, classes, attributes, pseudos){ // next siblings
+ while ((node = node.nextSibling)){
+ if (node.nodeType != 1) continue;
+ var uid = this.getUID(node);
+ if (this.bitUniques[uid]) break;
+ this.bitUniques[uid] = true;
+ this.push(node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '++': function(node, tag, id, classes, attributes, pseudos){ // next sibling and previous sibling
+ this['combinator:+'](node, tag, id, classes, attributes, pseudos);
+ this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
+ },
+
+ '~~': function(node, tag, id, classes, attributes, pseudos){ // next siblings and previous siblings
+ this['combinator:~'](node, tag, id, classes, attributes, pseudos);
+ this['combinator:!~'](node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!': function(node, tag, id, classes, attributes, pseudos){ // all parent nodes up to document
+ while ((node = node.parentNode)) if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!>': function(node, tag, id, classes, attributes, pseudos){ // direct parent (one level)
+ node = node.parentNode;
+ if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!+': function(node, tag, id, classes, attributes, pseudos){ // previous sibling
+ while ((node = node.previousSibling)) if (node.nodeType == 1){
+ this.push(node, tag, id, classes, attributes, pseudos);
+ break;
+ }
+ },
+
+ '!^': function(node, tag, id, classes, attributes, pseudos){ // last child
+ node = node.lastChild;
+ if (node){
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ else this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '!~': function(node, tag, id, classes, attributes, pseudos){ // previous siblings
+ while ((node = node.previousSibling)){
+ if (node.nodeType != 1) continue;
+ var uid = this.getUID(node);
+ if (this.bitUniques[uid]) break;
+ this.bitUniques[uid] = true;
+ this.push(node, tag, id, classes, attributes, pseudos);
+ }
+ }
+
+};
+
+for (var c in combinators) local['combinator:' + c] = combinators[c];
+
+var pseudos = {
+
+ /*<pseudo-selectors>*/
+
+ 'empty': function(node){
+ var child = node.firstChild;
+ return !(child && child.nodeType == 1) && !(node.innerText || node.textContent || '').length;
+ },
+
+ 'not': function(node, expression){
+ return !this.matchNode(node, expression);
+ },
+
+ 'contains': function(node, text){
+ return (node.innerText || node.textContent || '').indexOf(text) > -1;
+ },
+
+ 'first-child': function(node){
+ while ((node = node.previousSibling)) if (node.nodeType == 1) return false;
+ return true;
+ },
+
+ 'last-child': function(node){
+ while ((node = node.nextSibling)) if (node.nodeType == 1) return false;
+ return true;
+ },
+
+ 'only-child': function(node){
+ var prev = node;
+ while ((prev = prev.previousSibling)) if (prev.nodeType == 1) return false;
+ var next = node;
+ while ((next = next.nextSibling)) if (next.nodeType == 1) return false;
+ return true;
+ },
+
+ /*<nth-pseudo-selectors>*/
+
+ 'nth-child': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTH'),
+
+ 'nth-last-child': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHLast'),
+
+ 'nth-of-type': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTHType', true),
+
+ 'nth-last-of-type': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHTypeLast', true),
+
+ 'index': function(node, index){
+ return this['pseudo:nth-child'](node, '' + (index + 1));
+ },
+
+ 'even': function(node){
+ return this['pseudo:nth-child'](node, '2n');
+ },
+
+ 'odd': function(node){
+ return this['pseudo:nth-child'](node, '2n+1');
+ },
+
+ /*</nth-pseudo-selectors>*/
+
+ /*<of-type-pseudo-selectors>*/
+
+ 'first-of-type': function(node){
+ var nodeName = node.nodeName;
+ while ((node = node.previousSibling)) if (node.nodeName == nodeName) return false;
+ return true;
+ },
+
+ 'last-of-type': function(node){
+ var nodeName = node.nodeName;
+ while ((node = node.nextSibling)) if (node.nodeName == nodeName) return false;
+ return true;
+ },
+
+ 'only-of-type': function(node){
+ var prev = node, nodeName = node.nodeName;
+ while ((prev = prev.previousSibling)) if (prev.nodeName == nodeName) return false;
+ var next = node;
+ while ((next = next.nextSibling)) if (next.nodeName == nodeName) return false;
+ return true;
+ },
+
+ /*</of-type-pseudo-selectors>*/
+
+ // custom pseudos
+
+ 'enabled': function(node){
+ return !node.disabled;
+ },
+
+ 'disabled': function(node){
+ return node.disabled;
+ },
+
+ 'checked': function(node){
+ return node.checked || node.selected;
+ },
+
+ 'focus': function(node){
+ return this.isHTMLDocument && this.document.activeElement === node && (node.href || node.type || this.hasAttribute(node, 'tabindex'));
+ },
+
+ 'root': function(node){
+ return (node === this.root);
+ },
+
+ 'selected': function(node){
+ return node.selected;
+ }
+
+ /*</pseudo-selectors>*/
+};
+
+for (var p in pseudos) local['pseudo:' + p] = pseudos[p];
+
+// attributes methods
+
+var attributeGetters = local.attributeGetters = {
+
+ 'for': function(){
+ return ('htmlFor' in this) ? this.htmlFor : this.getAttribute('for');
+ },
+
+ 'href': function(){
+ return ('href' in this) ? this.getAttribute('href', 2) : this.getAttribute('href');
+ },
+
+ 'style': function(){
+ return (this.style) ? this.style.cssText : this.getAttribute('style');
+ },
+
+ 'tabindex': function(){
+ var attributeNode = this.getAttributeNode('tabindex');
+ return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
+ },
+
+ 'type': function(){
+ return this.getAttribute('type');
+ },
+
+ 'maxlength': function(){
+ var attributeNode = this.getAttributeNode('maxLength');
+ return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
+ }
+
+};
+
+attributeGetters.MAXLENGTH = attributeGetters.maxLength = attributeGetters.maxlength;
+
+// Slick
+
+var Slick = local.Slick = (this.Slick || {});
+
+Slick.version = '1.1.7';
+
+// Slick finder
+
+Slick.search = function(context, expression, append){
+ return local.search(context, expression, append);
+};
+
+Slick.find = function(context, expression){
+ return local.search(context, expression, null, true);
+};
+
+// Slick containment checker
+
+Slick.contains = function(container, node){
+ local.setDocument(container);
+ return local.contains(container, node);
+};
+
+// Slick attribute getter
+
+Slick.getAttribute = function(node, name){
+ local.setDocument(node);
+ return local.getAttribute(node, name);
+};
+
+Slick.hasAttribute = function(node, name){
+ local.setDocument(node);
+ return local.hasAttribute(node, name);
+};
+
+// Slick matcher
+
+Slick.match = function(node, selector){
+ if (!(node && selector)) return false;
+ if (!selector || selector === node) return true;
+ local.setDocument(node);
+ return local.matchNode(node, selector);
+};
+
+// Slick attribute accessor
+
+Slick.defineAttributeGetter = function(name, fn){
+ local.attributeGetters[name] = fn;
+ return this;
+};
+
+Slick.lookupAttributeGetter = function(name){
+ return local.attributeGetters[name];
+};
+
+// Slick pseudo accessor
+
+Slick.definePseudo = function(name, fn){
+ local['pseudo:' + name] = function(node, argument){
+ return fn.call(node, argument);
+ };
+ return this;
+};
+
+Slick.lookupPseudo = function(name){
+ var pseudo = local['pseudo:' + name];
+ if (pseudo) return function(argument){
+ return pseudo.call(this, argument);
+ };
+ return null;
+};
+
+// Slick overrides accessor
+
+Slick.override = function(regexp, fn){
+ local.override(regexp, fn);
+ return this;
+};
+
+Slick.isXML = local.isXML;
+
+Slick.uidOf = function(node){
+ return local.getUIDHTML(node);
+};
+
+if (!this.Slick) this.Slick = Slick;
+
+}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this);
+
+
+/*
+---
+
+name: Element
+
+description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements.
+
+license: MIT-style license.
+
+requires: [Window, Document, Array, String, Function, Object, Number, Slick.Parser, Slick.Finder]
+
+provides: [Element, Elements, $, $$, Iframe, Selectors]
+
+...
+*/
+
+var Element = function(tag, props){
+ var konstructor = Element.Constructors[tag];
+ if (konstructor) return konstructor(props);
+ if (typeof tag != 'string') return document.id(tag).set(props);
+
+ if (!props) props = {};
+
+ if (!(/^[\w-]+$/).test(tag)){
+ var parsed = Slick.parse(tag).expressions[0][0];
+ tag = (parsed.tag == '*') ? 'div' : parsed.tag;
+ if (parsed.id && props.id == null) props.id = parsed.id;
+
+ var attributes = parsed.attributes;
+ if (attributes) for (var attr, i = 0, l = attributes.length; i < l; i++){
+ attr = attributes[i];
+ if (props[attr.key] != null) continue;
+
+ if (attr.value != null && attr.operator == '=') props[attr.key] = attr.value;
+ else if (!attr.value && !attr.operator) props[attr.key] = true;
+ }
+
+ if (parsed.classList && props['class'] == null) props['class'] = parsed.classList.join(' ');
+ }
+
+ return document.newElement(tag, props);
+};
+
+
+if (Browser.Element){
+ Element.prototype = Browser.Element.prototype;
+ // IE8 and IE9 require the wrapping.
+ Element.prototype._fireEvent = (function(fireEvent){
+ return function(type, event){
+ return fireEvent.call(this, type, event);
+ };
+ })(Element.prototype.fireEvent);
+}
+
+new Type('Element', Element).mirror(function(name){
+ if (Array.prototype[name]) return;
+
+ var obj = {};
+ obj[name] = function(){
+ var results = [], args = arguments, elements = true;
+ for (var i = 0, l = this.length; i < l; i++){
+ var element = this[i], result = results[i] = element[name].apply(element, args);
+ elements = (elements && typeOf(result) == 'element');
+ }
+ return (elements) ? new Elements(results) : results;
+ };
+
+ Elements.implement(obj);
+});
+
+if (!Browser.Element){
+ Element.parent = Object;
+
+ Element.Prototype = {
+ '$constructor': Element,
+ '$family': Function.from('element').hide()
+ };
+
+ Element.mirror(function(name, method){
+ Element.Prototype[name] = method;
+ });
+}
+
+Element.Constructors = {};
+
+
+
+var IFrame = new Type('IFrame', function(){
+ var params = Array.link(arguments, {
+ properties: Type.isObject,
+ iframe: function(obj){
+ return (obj != null);
+ }
+ });
+
+ var props = params.properties || {}, iframe;
+ if (params.iframe) iframe = document.id(params.iframe);
+ var onload = props.onload || function(){};
+ delete props.onload;
+ props.id = props.name = [props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + String.uniqueID()].pick();
+ iframe = new Element(iframe || 'iframe', props);
+
+ var onLoad = function(){
+ onload.call(iframe.contentWindow);
+ };
+
+ if (window.frames[props.id]) onLoad();
+ else iframe.addListener('load', onLoad);
+ return iframe;
+});
+
+var Elements = this.Elements = function(nodes){
+ if (nodes && nodes.length){
+ var uniques = {}, node;
+ for (var i = 0; node = nodes[i++];){
+ var uid = Slick.uidOf(node);
+ if (!uniques[uid]){
+ uniques[uid] = true;
+ this.push(node);
+ }
+ }
+ }
+};
+
+Elements.prototype = {length: 0};
+Elements.parent = Array;
+
+new Type('Elements', Elements).implement({
+
+ filter: function(filter, bind){
+ if (!filter) return this;
+ return new Elements(Array.filter(this, (typeOf(filter) == 'string') ? function(item){
+ return item.match(filter);
+ } : filter, bind));
+ }.protect(),
+
+ push: function(){
+ var length = this.length;
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = document.id(arguments[i]);
+ if (item) this[length++] = item;
+ }
+ return (this.length = length);
+ }.protect(),
+
+ unshift: function(){
+ var items = [];
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = document.id(arguments[i]);
+ if (item) items.push(item);
+ }
+ return Array.prototype.unshift.apply(this, items);
+ }.protect(),
+
+ concat: function(){
+ var newElements = new Elements(this);
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = arguments[i];
+ if (Type.isEnumerable(item)) newElements.append(item);
+ else newElements.push(item);
+ }
+ return newElements;
+ }.protect(),
+
+ append: function(collection){
+ for (var i = 0, l = collection.length; i < l; i++) this.push(collection[i]);
+ return this;
+ }.protect(),
+
+ empty: function(){
+ while (this.length) delete this[--this.length];
+ return this;
+ }.protect()
+
+});
+
+
+
+(function(){
+
+// FF, IE
+var splice = Array.prototype.splice, object = {'0': 0, '1': 1, length: 2};
+
+splice.call(object, 1, 1);
+if (object[1] == 1) Elements.implement('splice', function(){
+ var length = this.length;
+ var result = splice.apply(this, arguments);
+ while (length >= this.length) delete this[length--];
+ return result;
+}.protect());
+
+Array.forEachMethod(function(method, name){
+ Elements.implement(name, method);
+});
+
+Array.mirror(Elements);
+
+/*<ltIE8>*/
+var createElementAcceptsHTML;
+try {
+ createElementAcceptsHTML = (document.createElement('<input name=x>').name == 'x');
+} catch (e){}
+
+var escapeQuotes = function(html){
+ return ('' + html).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
+};
+/*</ltIE8>*/
+
+Document.implement({
+
+ newElement: function(tag, props){
+ if (props && props.checked != null) props.defaultChecked = props.checked;
+ /*<ltIE8>*/// Fix for readonly name and type properties in IE < 8
+ if (createElementAcceptsHTML && props){
+ tag = '<' + tag;
+ if (props.name) tag += ' name="' + escapeQuotes(props.name) + '"';
+ if (props.type) tag += ' type="' + escapeQuotes(props.type) + '"';
+ tag += '>';
+ delete props.name;
+ delete props.type;
+ }
+ /*</ltIE8>*/
+ return this.id(this.createElement(tag)).set(props);
+ }
+
+});
+
+})();
+
+(function(){
+
+Slick.uidOf(window);
+Slick.uidOf(document);
+
+Document.implement({
+
+ newTextNode: function(text){
+ return this.createTextNode(text);
+ },
+
+ getDocument: function(){
+ return this;
+ },
+
+ getWindow: function(){
+ return this.window;
+ },
+
+ id: (function(){
+
+ var types = {
+
+ string: function(id, nocash, doc){
+ id = Slick.find(doc, '#' + id.replace(/(\W)/g, '\\$1'));
+ return (id) ? types.element(id, nocash) : null;
+ },
+
+ element: function(el, nocash){
+ Slick.uidOf(el);
+ if (!nocash && !el.$family && !(/^(?:object|embed)$/i).test(el.tagName)){
+ var fireEvent = el.fireEvent;
+ // wrapping needed in IE7, or else crash
+ el._fireEvent = function(type, event){
+ return fireEvent(type, event);
+ };
+ Object.append(el, Element.Prototype);
+ }
+ return el;
+ },
+
+ object: function(obj, nocash, doc){
+ if (obj.toElement) return types.element(obj.toElement(doc), nocash);
+ return null;
+ }
+
+ };
+
+ types.textnode = types.whitespace = types.window = types.document = function(zero){
+ return zero;
+ };
+
+ return function(el, nocash, doc){
+ if (el && el.$family && el.uniqueNumber) return el;
+ var type = typeOf(el);
+ return (types[type]) ? types[type](el, nocash, doc || document) : null;
+ };
+
+ })()
+
+});
+
+if (window.$ == null) Window.implement('$', function(el, nc){
+ return document.id(el, nc, this.document);
+});
+
+Window.implement({
+
+ getDocument: function(){
+ return this.document;
+ },
+
+ getWindow: function(){
+ return this;
+ }
+
+});
+
+[Document, Element].invoke('implement', {
+
+ getElements: function(expression){
+ return Slick.search(this, expression, new Elements);
+ },
+
+ getElement: function(expression){
+ return document.id(Slick.find(this, expression));
+ }
+
+});
+
+var contains = {contains: function(element){
+ return Slick.contains(this, element);
+}};
+
+if (!document.contains) Document.implement(contains);
+if (!document.createElement('div').contains) Element.implement(contains);
+
+
+
+// tree walking
+
+var injectCombinator = function(expression, combinator){
+ if (!expression) return combinator;
+
+ expression = Object.clone(Slick.parse(expression));
+
+ var expressions = expression.expressions;
+ for (var i = expressions.length; i--;)
+ expressions[i][0].combinator = combinator;
+
+ return expression;
+};
+
+Object.forEach({
+ getNext: '~',
+ getPrevious: '!~',
+ getParent: '!'
+}, function(combinator, method){
+ Element.implement(method, function(expression){
+ return this.getElement(injectCombinator(expression, combinator));
+ });
+});
+
+Object.forEach({
+ getAllNext: '~',
+ getAllPrevious: '!~',
+ getSiblings: '~~',
+ getChildren: '>',
+ getParents: '!'
+}, function(combinator, method){
+ Element.implement(method, function(expression){
+ return this.getElements(injectCombinator(expression, combinator));
+ });
+});
+
+Element.implement({
+
+ getFirst: function(expression){
+ return document.id(Slick.search(this, injectCombinator(expression, '>'))[0]);
+ },
+
+ getLast: function(expression){
+ return document.id(Slick.search(this, injectCombinator(expression, '>')).getLast());
+ },
+
+ getWindow: function(){
+ return this.ownerDocument.window;
+ },
+
+ getDocument: function(){
+ return this.ownerDocument;
+ },
+
+ getElementById: function(id){
+ return document.id(Slick.find(this, '#' + ('' + id).replace(/(\W)/g, '\\$1')));
+ },
+
+ match: function(expression){
+ return !expression || Slick.match(this, expression);
+ }
+
+});
+
+
+
+if (window.$$ == null) Window.implement('$$', function(selector){
+ if (arguments.length == 1){
+ if (typeof selector == 'string') return Slick.search(this.document, selector, new Elements);
+ else if (Type.isEnumerable(selector)) return new Elements(selector);
+ }
+ return new Elements(arguments);
+});
+
+// Inserters
+
+var inserters = {
+
+ before: function(context, element){
+ var parent = element.parentNode;
+ if (parent) parent.insertBefore(context, element);
+ },
+
+ after: function(context, element){
+ var parent = element.parentNode;
+ if (parent) parent.insertBefore(context, element.nextSibling);
+ },
+
+ bottom: function(context, element){
+ element.appendChild(context);
+ },
+
+ top: function(context, element){
+ element.insertBefore(context, element.firstChild);
+ }
+
+};
+
+inserters.inside = inserters.bottom;
+
+
+
+// getProperty / setProperty
+
+var propertyGetters = {}, propertySetters = {};
+
+// properties
+
+var properties = {};
+Array.forEach([
+ 'type', 'value', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan',
+ 'frameBorder', 'rowSpan', 'tabIndex', 'useMap'
+], function(property){
+ properties[property.toLowerCase()] = property;
+});
+
+properties.html = 'innerHTML';
+properties.text = (document.createElement('div').textContent == null) ? 'innerText': 'textContent';
+
+Object.forEach(properties, function(real, key){
+ propertySetters[key] = function(node, value){
+ node[real] = value;
+ };
+ propertyGetters[key] = function(node){
+ return node[real];
+ };
+});
+
+// Booleans
+
+var bools = [
+ 'compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked',
+ 'disabled', 'readOnly', 'multiple', 'selected', 'noresize',
+ 'defer', 'defaultChecked', 'autofocus', 'controls', 'autoplay',
+ 'loop'
+];
+
+var booleans = {};
+Array.forEach(bools, function(bool){
+ var lower = bool.toLowerCase();
+ booleans[lower] = bool;
+ propertySetters[lower] = function(node, value){
+ node[bool] = !!value;
+ };
+ propertyGetters[lower] = function(node){
+ return !!node[bool];
+ };
+});
+
+// Special cases
+
+Object.append(propertySetters, {
+
+ 'class': function(node, value){
+ ('className' in node) ? node.className = (value || '') : node.setAttribute('class', value);
+ },
+
+ 'for': function(node, value){
+ ('htmlFor' in node) ? node.htmlFor = value : node.setAttribute('for', value);
+ },
+
+ 'style': function(node, value){
+ (node.style) ? node.style.cssText = value : node.setAttribute('style', value);
+ },
+
+ 'value': function(node, value){
+ node.value = (value != null) ? value : '';
+ }
+
+});
+
+propertyGetters['class'] = function(node){
+ return ('className' in node) ? node.className || null : node.getAttribute('class');
+};
+
+/* <webkit> */
+var el = document.createElement('button');
+// IE sets type as readonly and throws
+try { el.type = 'button'; } catch(e){}
+if (el.type != 'button') propertySetters.type = function(node, value){
+ node.setAttribute('type', value);
+};
+el = null;
+/* </webkit> */
+
+/*<IE>*/
+var input = document.createElement('input');
+input.value = 't';
+input.type = 'submit';
+if (input.value != 't') propertySetters.type = function(node, type){
+ var value = node.value;
+ node.type = type;
+ node.value = value;
+};
+input = null;
+/*</IE>*/
+
+/* getProperty, setProperty */
+
+/* <ltIE9> */
+var pollutesGetAttribute = (function(div){
+ div.random = 'attribute';
+ return (div.getAttribute('random') == 'attribute');
+})(document.createElement('div'));
+
+/* <ltIE9> */
+
+Element.implement({
+
+ setProperty: function(name, value){
+ var setter = propertySetters[name.toLowerCase()];
+ if (setter){
+ setter(this, value);
+ } else {
+ /* <ltIE9> */
+ if (pollutesGetAttribute) var attributeWhiteList = this.retrieve('$attributeWhiteList', {});
+ /* </ltIE9> */
+
+ if (value == null){
+ this.removeAttribute(name);
+ /* <ltIE9> */
+ if (pollutesGetAttribute) delete attributeWhiteList[name];
+ /* </ltIE9> */
+ } else {
+ this.setAttribute(name, '' + value);
+ /* <ltIE9> */
+ if (pollutesGetAttribute) attributeWhiteList[name] = true;
+ /* </ltIE9> */
+ }
+ }
+ return this;
+ },
+
+ setProperties: function(attributes){
+ for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
+ return this;
+ },
+
+ getProperty: function(name){
+ var getter = propertyGetters[name.toLowerCase()];
+ if (getter) return getter(this);
+ /* <ltIE9> */
+ if (pollutesGetAttribute){
+ var attr = this.getAttributeNode(name), attributeWhiteList = this.retrieve('$attributeWhiteList', {});
+ if (!attr) return null;
+ if (attr.expando && !attributeWhiteList[name]){
+ var outer = this.outerHTML;
+ // segment by the opening tag and find mention of attribute name
+ if (outer.substr(0, outer.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(name) < 0) return null;
+ attributeWhiteList[name] = true;
+ }
+ }
+ /* </ltIE9> */
+ var result = Slick.getAttribute(this, name);
+ return (!result && !Slick.hasAttribute(this, name)) ? null : result;
+ },
+
+ getProperties: function(){
+ var args = Array.from(arguments);
+ return args.map(this.getProperty, this).associate(args);
+ },
+
+ removeProperty: function(name){
+ return this.setProperty(name, null);
+ },
+
+ removeProperties: function(){
+ Array.each(arguments, this.removeProperty, this);
+ return this;
+ },
+
+ set: function(prop, value){
+ var property = Element.Properties[prop];
+ (property && property.set) ? property.set.call(this, value) : this.setProperty(prop, value);
+ }.overloadSetter(),
+
+ get: function(prop){
+ var property = Element.Properties[prop];
+ return (property && property.get) ? property.get.apply(this) : this.getProperty(prop);
+ }.overloadGetter(),
+
+ erase: function(prop){
+ var property = Element.Properties[prop];
+ (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop);
+ return this;
+ },
+
+ hasClass: function(className){
+ return this.className.clean().contains(className, ' ');
+ },
+
+ addClass: function(className){
+ if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean();
+ return this;
+ },
+
+ removeClass: function(className){
+ this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1');
+ return this;
+ },
+
+ toggleClass: function(className, force){
+ if (force == null) force = !this.hasClass(className);
+ return (force) ? this.addClass(className) : this.removeClass(className);
+ },
+
+ adopt: function(){
+ var parent = this, fragment, elements = Array.flatten(arguments), length = elements.length;
+ if (length > 1) parent = fragment = document.createDocumentFragment();
+
+ for (var i = 0; i < length; i++){
+ var element = document.id(elements[i], true);
+ if (element) parent.appendChild(element);
+ }
+
+ if (fragment) this.appendChild(fragment);
+
+ return this;
+ },
+
+ appendText: function(text, where){
+ return this.grab(this.getDocument().newTextNode(text), where);
+ },
+
+ grab: function(el, where){
+ inserters[where || 'bottom'](document.id(el, true), this);
+ return this;
+ },
+
+ inject: function(el, where){
+ inserters[where || 'bottom'](this, document.id(el, true));
+ return this;
+ },
+
+ replaces: function(el){
+ el = document.id(el, true);
+ el.parentNode.replaceChild(this, el);
+ return this;
+ },
+
+ wraps: function(el, where){
+ el = document.id(el, true);
+ return this.replaces(el).grab(el, where);
+ },
+
+ getSelected: function(){
+ this.selectedIndex; // Safari 3.2.1
+ return new Elements(Array.from(this.options).filter(function(option){
+ return option.selected;
+ }));
+ },
+
+ toQueryString: function(){
+ var queryString = [];
+ this.getElements('input, select, textarea').each(function(el){
+ var type = el.type;
+ if (!el.name || el.disabled || type == 'submit' || type == 'reset' || type == 'file' || type == 'image') return;
+
+ var value = (el.get('tag') == 'select') ? el.getSelected().map(function(opt){
+ // IE
+ return document.id(opt).get('value');
+ }) : ((type == 'radio' || type == 'checkbox') && !el.checked) ? null : el.get('value');
+
+ Array.from(value).each(function(val){
+ if (typeof val != 'undefined') queryString.push(encodeURIComponent(el.name) + '=' + encodeURIComponent(val));
+ });
+ });
+ return queryString.join('&');
+ }
+
+});
+
+var collected = {}, storage = {};
+
+var get = function(uid){
+ return (storage[uid] || (storage[uid] = {}));
+};
+
+var clean = function(item){
+ var uid = item.uniqueNumber;
+ if (item.removeEvents) item.removeEvents();
+ if (item.clearAttributes) item.clearAttributes();
+ if (uid != null){
+ delete collected[uid];
+ delete storage[uid];
+ }
+ return item;
+};
+
+var formProps = {input: 'checked', option: 'selected', textarea: 'value'};
+
+Element.implement({
+
+ destroy: function(){
+ var children = clean(this).getElementsByTagName('*');
+ Array.each(children, clean);
+ Element.dispose(this);
+ return null;
+ },
+
+ empty: function(){
+ Array.from(this.childNodes).each(Element.dispose);
+ return this;
+ },
+
+ dispose: function(){
+ return (this.parentNode) ? this.parentNode.removeChild(this) : this;
+ },
+
+ clone: function(contents, keepid){
+ contents = contents !== false;
+ var clone = this.cloneNode(contents), ce = [clone], te = [this], i;
+
+ if (contents){
+ ce.append(Array.from(clone.getElementsByTagName('*')));
+ te.append(Array.from(this.getElementsByTagName('*')));
+ }
+
+ for (i = ce.length; i--;){
+ var node = ce[i], element = te[i];
+ if (!keepid) node.removeAttribute('id');
+ /*<ltIE9>*/
+ if (node.clearAttributes){
+ node.clearAttributes();
+ node.mergeAttributes(element);
+ node.removeAttribute('uniqueNumber');
+ if (node.options){
+ var no = node.options, eo = element.options;
+ for (var j = no.length; j--;) no[j].selected = eo[j].selected;
+ }
+ }
+ /*</ltIE9>*/
+ var prop = formProps[element.tagName.toLowerCase()];
+ if (prop && element[prop]) node[prop] = element[prop];
+ }
+
+ /*<ltIE9>*/
+ if (Browser.ie){
+ var co = clone.getElementsByTagName('object'), to = this.getElementsByTagName('object');
+ for (i = co.length; i--;) co[i].outerHTML = to[i].outerHTML;
+ }
+ /*</ltIE9>*/
+ return document.id(clone);
+ }
+
+});
+
+[Element, Window, Document].invoke('implement', {
+
+ addListener: function(type, fn){
+ if (type == 'unload'){
+ var old = fn, self = this;
+ fn = function(){
+ self.removeListener('unload', fn);
+ old();
+ };
+ } else {
+ collected[Slick.uidOf(this)] = this;
+ }
+ if (this.addEventListener) this.addEventListener(type, fn, !!arguments[2]);
+ else this.attachEvent('on' + type, fn);
+ return this;
+ },
+
+ removeListener: function(type, fn){
+ if (this.removeEventListener) this.removeEventListener(type, fn, !!arguments[2]);
+ else this.detachEvent('on' + type, fn);
+ return this;
+ },
+
+ retrieve: function(property, dflt){
+ var storage = get(Slick.uidOf(this)), prop = storage[property];
+ if (dflt != null && prop == null) prop = storage[property] = dflt;
+ return prop != null ? prop : null;
+ },
+
+ store: function(property, value){
+ var storage = get(Slick.uidOf(this));
+ storage[property] = value;
+ return this;
+ },
+
+ eliminate: function(property){
+ var storage = get(Slick.uidOf(this));
+ delete storage[property];
+ return this;
+ }
+
+});
+
+/*<ltIE9>*/
+if (window.attachEvent && !window.addEventListener) window.addListener('unload', function(){
+ Object.each(collected, clean);
+ if (window.CollectGarbage) CollectGarbage();
+});
+/*</ltIE9>*/
+
+Element.Properties = {};
+
+
+
+Element.Properties.style = {
+
+ set: function(style){
+ this.style.cssText = style;
+ },
+
+ get: function(){
+ return this.style.cssText;
+ },
+
+ erase: function(){
+ this.style.cssText = '';
+ }
+
+};
+
+Element.Properties.tag = {
+
+ get: function(){
+ return this.tagName.toLowerCase();
+ }
+
+};
+
+Element.Properties.html = {
+
+ set: function(html){
+ if (html == null) html = '';
+ else if (typeOf(html) == 'array') html = html.join('');
+ this.innerHTML = html;
+ },
+
+ erase: function(){
+ this.innerHTML = '';
+ }
+
+};
+
+/*<ltIE9>*/
+// technique by jdbarlett - http://jdbartlett.com/innershiv/
+var div = document.createElement('div');
+div.innerHTML = '<nav></nav>';
+var supportsHTML5Elements = (div.childNodes.length == 1);
+if (!supportsHTML5Elements){
+ var tags = 'abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video'.split(' '),
+ fragment = document.createDocumentFragment(), l = tags.length;
+ while (l--) fragment.createElement(tags[l]);
+}
+div = null;
+/*</ltIE9>*/
+
+/*<IE>*/
+var supportsTableInnerHTML = Function.attempt(function(){
+ var table = document.createElement('table');
+ table.innerHTML = '<tr><td></td></tr>';
+ return true;
+});
+
+/*<ltFF4>*/
+var tr = document.createElement('tr'), html = '<td></td>';
+tr.innerHTML = html;
+var supportsTRInnerHTML = (tr.innerHTML == html);
+tr = null;
+/*</ltFF4>*/
+
+if (!supportsTableInnerHTML || !supportsTRInnerHTML || !supportsHTML5Elements){
+
+ Element.Properties.html.set = (function(set){
+
+ var translations = {
+ table: [1, '<table>', '</table>'],
+ select: [1, '<select>', '</select>'],
+ tbody: [2, '<table><tbody>', '</tbody></table>'],
+ tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
+ };
+
+ translations.thead = translations.tfoot = translations.tbody;
+
+ return function(html){
+ var wrap = translations[this.get('tag')];
+ if (!wrap && !supportsHTML5Elements) wrap = [0, '', ''];
+ if (!wrap) return set.call(this, html);
+
+ var level = wrap[0], wrapper = document.createElement('div'), target = wrapper;
+ if (!supportsHTML5Elements) fragment.appendChild(wrapper);
+ wrapper.innerHTML = [wrap[1], html, wrap[2]].flatten().join('');
+ while (level--) target = target.firstChild;
+ this.empty().adopt(target.childNodes);
+ if (!supportsHTML5Elements) fragment.removeChild(wrapper);
+ wrapper = null;
+ };
+
+ })(Element.Properties.html.set);
+}
+/*</IE>*/
+
+/*<ltIE9>*/
+var testForm = document.createElement('form');
+testForm.innerHTML = '<select><option>s</option></select>';
+
+if (testForm.firstChild.value != 's') Element.Properties.value = {
+
+ set: function(value){
+ var tag = this.get('tag');
+ if (tag != 'select') return this.setProperty('value', value);
+ var options = this.getElements('option');
+ for (var i = 0; i < options.length; i++){
+ var option = options[i],
+ attr = option.getAttributeNode('value'),
+ optionValue = (attr && attr.specified) ? option.value : option.get('text');
+ if (optionValue == value) return option.selected = true;
+ }
+ },
+
+ get: function(){
+ var option = this, tag = option.get('tag');
+
+ if (tag != 'select' && tag != 'option') return this.getProperty('value');
+
+ if (tag == 'select' && !(option = option.getSelected()[0])) return '';
+
+ var attr = option.getAttributeNode('value');
+ return (attr && attr.specified) ? option.value : option.get('text');
+ }
+
+};
+testForm = null;
+/*</ltIE9>*/
+
+/*<IE>*/
+if (document.createElement('div').getAttributeNode('id')) Element.Properties.id = {
+ set: function(id){
+ this.id = this.getAttributeNode('id').value = id;
+ },
+ get: function(){
+ return this.id || null;
+ },
+ erase: function(){
+ this.id = this.getAttributeNode('id').value = '';
+ }
+};
+/*</IE>*/
+
+})();
+
+
+/*
+---
+
+name: Element.Style
+
+description: Contains methods for interacting with the styles of Elements in a fashionable way.
+
+license: MIT-style license.
+
+requires: Element
+
+provides: Element.Style
+
+...
+*/
+
+(function(){
+
+var html = document.html;
+
+//<ltIE9>
+// Check for oldIE, which does not remove styles when they're set to null
+var el = document.createElement('div');
+el.style.color = 'red';
+el.style.color = null;
+var doesNotRemoveStyles = el.style.color == 'red';
+el = null;
+//</ltIE9>
+
+Element.Properties.styles = {set: function(styles){
+ this.setStyles(styles);
+}};
+
+var hasOpacity = (html.style.opacity != null),
+ hasFilter = (html.style.filter != null),
+ reAlpha = /alpha\(opacity=([\d.]+)\)/i;
+
+var setVisibility = function(element, opacity){
+ element.store('$opacity', opacity);
+ element.style.visibility = opacity > 0 || opacity == null ? 'visible' : 'hidden';
+};
+
+var setOpacity = (hasOpacity ? function(element, opacity){
+ element.style.opacity = opacity;
+} : (hasFilter ? function(element, opacity){
+ var style = element.style;
+ if (!element.currentStyle || !element.currentStyle.hasLayout) style.zoom = 1;
+ if (opacity == null || opacity == 1) opacity = '';
+ else opacity = 'alpha(opacity=' + (opacity * 100).limit(0, 100).round() + ')';
+ var filter = style.filter || element.getComputedStyle('filter') || '';
+ style.filter = reAlpha.test(filter) ? filter.replace(reAlpha, opacity) : filter + opacity;
+ if (!style.filter) style.removeAttribute('filter');
+} : setVisibility));
+
+var getOpacity = (hasOpacity ? function(element){
+ var opacity = element.style.opacity || element.getComputedStyle('opacity');
+ return (opacity == '') ? 1 : opacity.toFloat();
+} : (hasFilter ? function(element){
+ var filter = (element.style.filter || element.getComputedStyle('filter')),
+ opacity;
+ if (filter) opacity = filter.match(reAlpha);
+ return (opacity == null || filter == null) ? 1 : (opacity[1] / 100);
+} : function(element){
+ var opacity = element.retrieve('$opacity');
+ if (opacity == null) opacity = (element.style.visibility == 'hidden' ? 0 : 1);
+ return opacity;
+}));
+
+var floatName = (html.style.cssFloat == null) ? 'styleFloat' : 'cssFloat';
+
+Element.implement({
+
+ getComputedStyle: function(property){
+ if (this.currentStyle) return this.currentStyle[property.camelCase()];
+ var defaultView = Element.getDocument(this).defaultView,
+ computed = defaultView ? defaultView.getComputedStyle(this, null) : null;
+ return (computed) ? computed.getPropertyValue((property == floatName) ? 'float' : property.hyphenate()) : null;
+ },
+
+ setStyle: function(property, value){
+ if (property == 'opacity'){
+ if (value != null) value = parseFloat(value);
+ setOpacity(this, value);
+ return this;
+ }
+ property = (property == 'float' ? floatName : property).camelCase();
+ if (typeOf(value) != 'string'){
+ var map = (Element.Styles[property] || '@').split(' ');
+ value = Array.from(value).map(function(val, i){
+ if (!map[i]) return '';
+ return (typeOf(val) == 'number') ? map[i].replace('@', Math.round(val)) : val;
+ }).join(' ');
+ } else if (value == String(Number(value))){
+ value = Math.round(value);
+ }
+ this.style[property] = value;
+ //<ltIE9>
+ if ((value == '' || value == null) && doesNotRemoveStyles && this.style.removeAttribute){
+ this.style.removeAttribute(property);
+ }
+ //</ltIE9>
+ return this;
+ },
+
+ getStyle: function(property){
+ if (property == 'opacity') return getOpacity(this);
+ property = (property == 'float' ? floatName : property).camelCase();
+ var result = this.style[property];
+ if (!result || property == 'zIndex'){
+ result = [];
+ for (var style in Element.ShortStyles){
+ if (property != style) continue;
+ for (var s in Element.ShortStyles[style]) result.push(this.getStyle(s));
+ return result.join(' ');
+ }
+ result = this.getComputedStyle(property);
+ }
+ if (result){
+ result = String(result);
+ var color = result.match(/rgba?\([\d\s,]+\)/);
+ if (color) result = result.replace(color[0], color[0].rgbToHex());
+ }
+ if (Browser.opera || Browser.ie){
+ if ((/^(height|width)$/).test(property) && !(/px$/.test(result))){
+ var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0;
+ values.each(function(value){
+ size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt();
+ }, this);
+ return this['offset' + property.capitalize()] - size + 'px';
+ }
+ if (Browser.ie && (/^border(.+)Width|margin|padding/).test(property) && isNaN(parseFloat(result))){
+ return '0px';
+ }
+ }
+ return result;
+ },
+
+ setStyles: function(styles){
+ for (var style in styles) this.setStyle(style, styles[style]);
+ return this;
+ },
+
+ getStyles: function(){
+ var result = {};
+ Array.flatten(arguments).each(function(key){
+ result[key] = this.getStyle(key);
+ }, this);
+ return result;
+ }
+
+});
+
+Element.Styles = {
+ left: '@px', top: '@px', bottom: '@px', right: '@px',
+ width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px',
+ backgroundColor: 'rgb(@, @, @)', backgroundPosition: '@px @px', color: 'rgb(@, @, @)',
+ fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)',
+ margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)',
+ borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)',
+ zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@'
+};
+
+
+
+
+
+Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}};
+
+['Top', 'Right', 'Bottom', 'Left'].each(function(direction){
+ var Short = Element.ShortStyles;
+ var All = Element.Styles;
+ ['margin', 'padding'].each(function(style){
+ var sd = style + direction;
+ Short[style][sd] = All[sd] = '@px';
+ });
+ var bd = 'border' + direction;
+ Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)';
+ var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color';
+ Short[bd] = {};
+ Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px';
+ Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@';
+ Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)';
+});
+
+})();
+
+
+/*
+---
+
+name: Element.Event
+
+description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events, if necessary.
+
+license: MIT-style license.
+
+requires: [Element, Event]
+
+provides: Element.Event
+
+...
+*/
+
+(function(){
+
+Element.Properties.events = {set: function(events){
+ this.addEvents(events);
+}};
+
+[Element, Window, Document].invoke('implement', {
+
+ addEvent: function(type, fn){
+ var events = this.retrieve('events', {});
+ if (!events[type]) events[type] = {keys: [], values: []};
+ if (events[type].keys.contains(fn)) return this;
+ events[type].keys.push(fn);
+ var realType = type,
+ custom = Element.Events[type],
+ condition = fn,
+ self = this;
+ if (custom){
+ if (custom.onAdd) custom.onAdd.call(this, fn, type);
+ if (custom.condition){
+ condition = function(event){
+ if (custom.condition.call(this, event, type)) return fn.call(this, event);
+ return true;
+ };
+ }
+ if (custom.base) realType = Function.from(custom.base).call(this, type);
+ }
+ var defn = function(){
+ return fn.call(self);
+ };
+ var nativeEvent = Element.NativeEvents[realType];
+ if (nativeEvent){
+ if (nativeEvent == 2){
+ defn = function(event){
+ event = new DOMEvent(event, self.getWindow());
+ if (condition.call(self, event) === false) event.stop();
+ };
+ }
+ this.addListener(realType, defn, arguments[2]);
+ }
+ events[type].values.push(defn);
+ return this;
+ },
+
+ removeEvent: function(type, fn){
+ var events = this.retrieve('events');
+ if (!events || !events[type]) return this;
+ var list = events[type];
+ var index = list.keys.indexOf(fn);
+ if (index == -1) return this;
+ var value = list.values[index];
+ delete list.keys[index];
+ delete list.values[index];
+ var custom = Element.Events[type];
+ if (custom){
+ if (custom.onRemove) custom.onRemove.call(this, fn, type);
+ if (custom.base) type = Function.from(custom.base).call(this, type);
+ }
+ return (Element.NativeEvents[type]) ? this.removeListener(type, value, arguments[2]) : this;
+ },
+
+ addEvents: function(events){
+ for (var event in events) this.addEvent(event, events[event]);
+ return this;
+ },
+
+ removeEvents: function(events){
+ var type;
+ if (typeOf(events) == 'object'){
+ for (type in events) this.removeEvent(type, events[type]);
+ return this;
+ }
+ var attached = this.retrieve('events');
+ if (!attached) return this;
+ if (!events){
+ for (type in attached) this.removeEvents(type);
+ this.eliminate('events');
+ } else if (attached[events]){
+ attached[events].keys.each(function(fn){
+ this.removeEvent(events, fn);
+ }, this);
+ delete attached[events];
+ }
+ return this;
+ },
+
+ fireEvent: function(type, args, delay){
+ var events = this.retrieve('events');
+ if (!events || !events[type]) return this;
+ args = Array.from(args);
+
+ events[type].keys.each(function(fn){
+ if (delay) fn.delay(delay, this, args);
+ else fn.apply(this, args);
+ }, this);
+ return this;
+ },
+
+ cloneEvents: function(from, type){
+ from = document.id(from);
+ var events = from.retrieve('events');
+ if (!events) return this;
+ if (!type){
+ for (var eventType in events) this.cloneEvents(from, eventType);
+ } else if (events[type]){
+ events[type].keys.each(function(fn){
+ this.addEvent(type, fn);
+ }, this);
+ }
+ return this;
+ }
+
+});
+
+Element.NativeEvents = {
+ click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons
+ mousewheel: 2, DOMMouseScroll: 2, //mouse wheel
+ mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement
+ keydown: 2, keypress: 2, keyup: 2, //keyboard
+ orientationchange: 2, // mobile
+ touchstart: 2, touchmove: 2, touchend: 2, touchcancel: 2, // touch
+ gesturestart: 2, gesturechange: 2, gestureend: 2, // gesture
+ focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, paste: 2, input: 2, //form elements
+ load: 2, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window
+ error: 1, abort: 1, scroll: 1 //misc
+};
+
+Element.Events = {mousewheel: {
+ base: (Browser.firefox) ? 'DOMMouseScroll' : 'mousewheel'
+}};
+
+if ('onmouseenter' in document.documentElement){
+ Element.NativeEvents.mouseenter = Element.NativeEvents.mouseleave = 2;
+} else {
+ var check = function(event){
+ var related = event.relatedTarget;
+ if (related == null) return true;
+ if (!related) return false;
+ return (related != this && related.prefix != 'xul' && typeOf(this) != 'document' && !this.contains(related));
+ };
+
+ Element.Events.mouseenter = {
+ base: 'mouseover',
+ condition: check
+ };
+
+ Element.Events.mouseleave = {
+ base: 'mouseout',
+ condition: check
+ };
+}
+
+/*<ltIE9>*/
+if (!window.addEventListener){
+ Element.NativeEvents.propertychange = 2;
+ Element.Events.change = {
+ base: function(){
+ var type = this.type;
+ return (this.get('tag') == 'input' && (type == 'radio' || type == 'checkbox')) ? 'propertychange' : 'change'
+ },
+ condition: function(event){
+ return this.type != 'radio' || (event.event.propertyName == 'checked' && this.checked);
+ }
+ }
+}
+/*</ltIE9>*/
+
+
+
+})();
+
+
+/*
+---
+
+name: Element.Delegation
+
+description: Extends the Element native object to include the delegate method for more efficient event management.
+
+license: MIT-style license.
+
+requires: [Element.Event]
+
+provides: [Element.Delegation]
+
+...
+*/
+
+(function(){
+
+var eventListenerSupport = !!window.addEventListener;
+
+Element.NativeEvents.focusin = Element.NativeEvents.focusout = 2;
+
+var bubbleUp = function(self, match, fn, event, target){
+ while (target && target != self){
+ if (match(target, event)) return fn.call(target, event, target);
+ target = document.id(target.parentNode);
+ }
+};
+
+var map = {
+ mouseenter: {
+ base: 'mouseover'
+ },
+ mouseleave: {
+ base: 'mouseout'
+ },
+ focus: {
+ base: 'focus' + (eventListenerSupport ? '' : 'in'),
+ capture: true
+ },
+ blur: {
+ base: eventListenerSupport ? 'blur' : 'focusout',
+ capture: true
+ }
+};
+
+/*<ltIE9>*/
+var _key = '$delegation:';
+var formObserver = function(type){
+
+ return {
+
+ base: 'focusin',
+
+ remove: function(self, uid){
+ var list = self.retrieve(_key + type + 'listeners', {})[uid];
+ if (list && list.forms) for (var i = list.forms.length; i--;){
+ list.forms[i].removeEvent(type, list.fns[i]);
+ }
+ },
+
+ listen: function(self, match, fn, event, target, uid){
+ var form = (target.get('tag') == 'form') ? target : event.target.getParent('form');
+ if (!form) return;
+
+ var listeners = self.retrieve(_key + type + 'listeners', {}),
+ listener = listeners[uid] || {forms: [], fns: []},
+ forms = listener.forms, fns = listener.fns;
+
+ if (forms.indexOf(form) != -1) return;
+ forms.push(form);
+
+ var _fn = function(event){
+ bubbleUp(self, match, fn, event, target);
+ };
+ form.addEvent(type, _fn);
+ fns.push(_fn);
+
+ listeners[uid] = listener;
+ self.store(_key + type + 'listeners', listeners);
+ }
+ };
+};
+
+var inputObserver = function(type){
+ return {
+ base: 'focusin',
+ listen: function(self, match, fn, event, target){
+ var events = {blur: function(){
+ this.removeEvents(events);
+ }};
+ events[type] = function(event){
+ bubbleUp(self, match, fn, event, target);
+ };
+ event.target.addEvents(events);
+ }
+ };
+};
+
+if (!eventListenerSupport) Object.append(map, {
+ submit: formObserver('submit'),
+ reset: formObserver('reset'),
+ change: inputObserver('change'),
+ select: inputObserver('select')
+});
+/*</ltIE9>*/
+
+var proto = Element.prototype,
+ addEvent = proto.addEvent,
+ removeEvent = proto.removeEvent;
+
+var relay = function(old, method){
+ return function(type, fn, useCapture){
+ if (type.indexOf(':relay') == -1) return old.call(this, type, fn, useCapture);
+ var parsed = Slick.parse(type).expressions[0][0];
+ if (parsed.pseudos[0].key != 'relay') return old.call(this, type, fn, useCapture);
+ var newType = parsed.tag;
+ parsed.pseudos.slice(1).each(function(pseudo){
+ newType += ':' + pseudo.key + (pseudo.value ? '(' + pseudo.value + ')' : '');
+ });
+ old.call(this, type, fn);
+ return method.call(this, newType, parsed.pseudos[0].value, fn);
+ };
+};
+
+var delegation = {
+
+ addEvent: function(type, match, fn){
+ var storage = this.retrieve('$delegates', {}), stored = storage[type];
+ if (stored) for (var _uid in stored){
+ if (stored[_uid].fn == fn && stored[_uid].match == match) return this;
+ }
+
+ var _type = type, _match = match, _fn = fn, _map = map[type] || {};
+ type = _map.base || _type;
+
+ match = function(target){
+ return Slick.match(target, _match);
+ };
+
+ var elementEvent = Element.Events[_type];
+ if (elementEvent && elementEvent.condition){
+ var __match = match, condition = elementEvent.condition;
+ match = function(target, event){
+ return __match(target, event) && condition.call(target, event, type);
+ };
+ }
+
+ var self = this, uid = String.uniqueID();
+ var delegator = _map.listen ? function(event, target){
+ if (!target && event && event.target) target = event.target;
+ if (target) _map.listen(self, match, fn, event, target, uid);
+ } : function(event, target){
+ if (!target && event && event.target) target = event.target;
+ if (target) bubbleUp(self, match, fn, event, target);
+ };
+
+ if (!stored) stored = {};
+ stored[uid] = {
+ match: _match,
+ fn: _fn,
+ delegator: delegator
+ };
+ storage[_type] = stored;
+ return addEvent.call(this, type, delegator, _map.capture);
+ },
+
+ removeEvent: function(type, match, fn, _uid){
+ var storage = this.retrieve('$delegates', {}), stored = storage[type];
+ if (!stored) return this;
+
+ if (_uid){
+ var _type = type, delegator = stored[_uid].delegator, _map = map[type] || {};
+ type = _map.base || _type;
+ if (_map.remove) _map.remove(this, _uid);
+ delete stored[_uid];
+ storage[_type] = stored;
+ return removeEvent.call(this, type, delegator);
+ }
+
+ var __uid, s;
+ if (fn) for (__uid in stored){
+ s = stored[__uid];
+ if (s.match == match && s.fn == fn) return delegation.removeEvent.call(this, type, match, fn, __uid);
+ } else for (__uid in stored){
+ s = stored[__uid];
+ if (s.match == match) delegation.removeEvent.call(this, type, match, s.fn, __uid);
+ }
+ return this;
+ }
+
+};
+
+[Element, Window, Document].invoke('implement', {
+ addEvent: relay(addEvent, delegation.addEvent),
+ removeEvent: relay(removeEvent, delegation.removeEvent)
+});
+
+})();
+
+
+/*
+---
+
+name: Element.Dimensions
+
+description: Contains methods to work with size, scroll, or positioning of Elements and the window object.
+
+license: MIT-style license.
+
+credits:
+ - Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html).
+ - Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html).
+
+requires: [Element, Element.Style]
+
+provides: [Element.Dimensions]
+
+...
+*/
+
+(function(){
+
+var element = document.createElement('div'),
+ child = document.createElement('div');
+element.style.height = '0';
+element.appendChild(child);
+var brokenOffsetParent = (child.offsetParent === element);
+element = child = null;
+
+var isOffset = function(el){
+ return styleString(el, 'position') != 'static' || isBody(el);
+};
+
+var isOffsetStatic = function(el){
+ return isOffset(el) || (/^(?:table|td|th)$/i).test(el.tagName);
+};
+
+Element.implement({
+
+ scrollTo: function(x, y){
+ if (isBody(this)){
+ this.getWindow().scrollTo(x, y);
+ } else {
+ this.scrollLeft = x;
+ this.scrollTop = y;
+ }
+ return this;
+ },
+
+ getSize: function(){
+ if (isBody(this)) return this.getWindow().getSize();
+ return {x: this.offsetWidth, y: this.offsetHeight};
+ },
+
+ getScrollSize: function(){
+ if (isBody(this)) return this.getWindow().getScrollSize();
+ return {x: this.scrollWidth, y: this.scrollHeight};
+ },
+
+ getScroll: function(){
+ if (isBody(this)) return this.getWindow().getScroll();
+ return {x: this.scrollLeft, y: this.scrollTop};
+ },
+
+ getScrolls: function(){
+ var element = this.parentNode, position = {x: 0, y: 0};
+ while (element && !isBody(element)){
+ position.x += element.scrollLeft;
+ position.y += element.scrollTop;
+ element = element.parentNode;
+ }
+ return position;
+ },
+
+ getOffsetParent: brokenOffsetParent ? function(){
+ var element = this;
+ if (isBody(element) || styleString(element, 'position') == 'fixed') return null;
+
+ var isOffsetCheck = (styleString(element, 'position') == 'static') ? isOffsetStatic : isOffset;
+ while ((element = element.parentNode)){
+ if (isOffsetCheck(element)) return element;
+ }
+ return null;
+ } : function(){
+ var element = this;
+ if (isBody(element) || styleString(element, 'position') == 'fixed') return null;
+
+ try {
+ return element.offsetParent;
+ } catch(e) {}
+ return null;
+ },
+
+ getOffsets: function(){
+ if (this.getBoundingClientRect && !Browser.Platform.ios){
+ var bound = this.getBoundingClientRect(),
+ html = document.id(this.getDocument().documentElement),
+ htmlScroll = html.getScroll(),
+ elemScrolls = this.getScrolls(),
+ isFixed = (styleString(this, 'position') == 'fixed');
+
+ return {
+ x: bound.left.toInt() + elemScrolls.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft,
+ y: bound.top.toInt() + elemScrolls.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop
+ };
+ }
+
+ var element = this, position = {x: 0, y: 0};
+ if (isBody(this)) return position;
+
+ while (element && !isBody(element)){
+ position.x += element.offsetLeft;
+ position.y += element.offsetTop;
+
+ if (Browser.firefox){
+ if (!borderBox(element)){
+ position.x += leftBorder(element);
+ position.y += topBorder(element);
+ }
+ var parent = element.parentNode;
+ if (parent && styleString(parent, 'overflow') != 'visible'){
+ position.x += leftBorder(parent);
+ position.y += topBorder(parent);
+ }
+ } else if (element != this && Browser.safari){
+ position.x += leftBorder(element);
+ position.y += topBorder(element);
+ }
+
+ element = element.offsetParent;
+ }
+ if (Browser.firefox && !borderBox(this)){
+ position.x -= leftBorder(this);
+ position.y -= topBorder(this);
+ }
+ return position;
+ },
+
+ getPosition: function(relative){
+ var offset = this.getOffsets(),
+ scroll = this.getScrolls();
+ var position = {
+ x: offset.x - scroll.x,
+ y: offset.y - scroll.y
+ };
+
+ if (relative && (relative = document.id(relative))){
+ var relativePosition = relative.getPosition();
+ return {x: position.x - relativePosition.x - leftBorder(relative), y: position.y - relativePosition.y - topBorder(relative)};
+ }
+ return position;
+ },
+
+ getCoordinates: function(element){
+ if (isBody(this)) return this.getWindow().getCoordinates();
+ var position = this.getPosition(element),
+ size = this.getSize();
+ var obj = {
+ left: position.x,
+ top: position.y,
+ width: size.x,
+ height: size.y
+ };
+ obj.right = obj.left + obj.width;
+ obj.bottom = obj.top + obj.height;
+ return obj;
+ },
+
+ computePosition: function(obj){
+ return {
+ left: obj.x - styleNumber(this, 'margin-left'),
+ top: obj.y - styleNumber(this, 'margin-top')
+ };
+ },
+
+ setPosition: function(obj){
+ return this.setStyles(this.computePosition(obj));
+ }
+
+});
+
+
+[Document, Window].invoke('implement', {
+
+ getSize: function(){
+ var doc = getCompatElement(this);
+ return {x: doc.clientWidth, y: doc.clientHeight};
+ },
+
+ getScroll: function(){
+ var win = this.getWindow(), doc = getCompatElement(this);
+ return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop};
+ },
+
+ getScrollSize: function(){
+ var doc = getCompatElement(this),
+ min = this.getSize(),
+ body = this.getDocument().body;
+
+ return {x: Math.max(doc.scrollWidth, body.scrollWidth, min.x), y: Math.max(doc.scrollHeight, body.scrollHeight, min.y)};
+ },
+
+ getPosition: function(){
+ return {x: 0, y: 0};
+ },
+
+ getCoordinates: function(){
+ var size = this.getSize();
+ return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x};
+ }
+
+});
+
+// private methods
+
+var styleString = Element.getComputedStyle;
+
+function styleNumber(element, style){
+ return styleString(element, style).toInt() || 0;
+}
+
+function borderBox(element){
+ return styleString(element, '-moz-box-sizing') == 'border-box';
+}
+
+function topBorder(element){
+ return styleNumber(element, 'border-top-width');
+}
+
+function leftBorder(element){
+ return styleNumber(element, 'border-left-width');
+}
+
+function isBody(element){
+ return (/^(?:body|html)$/i).test(element.tagName);
+}
+
+function getCompatElement(element){
+ var doc = element.getDocument();
+ return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+}
+
+})();
+
+//aliases
+Element.alias({position: 'setPosition'}); //compatability
+
+[Window, Document, Element].invoke('implement', {
+
+ getHeight: function(){
+ return this.getSize().y;
+ },
+
+ getWidth: function(){
+ return this.getSize().x;
+ },
+
+ getScrollTop: function(){
+ return this.getScroll().y;
+ },
+
+ getScrollLeft: function(){
+ return this.getScroll().x;
+ },
+
+ getScrollHeight: function(){
+ return this.getScrollSize().y;
+ },
+
+ getScrollWidth: function(){
+ return this.getScrollSize().x;
+ },
+
+ getTop: function(){
+ return this.getPosition().y;
+ },
+
+ getLeft: function(){
+ return this.getPosition().x;
+ }
+
+});
+
+
+/*
+---
+
+name: JSON
+
+description: JSON encoder and decoder.
+
+license: MIT-style license.
+
+SeeAlso: <http://www.json.org/>
+
+requires: [Array, String, Number, Function]
+
+provides: JSON
+
+...
+*/
+
+if (typeof JSON == 'undefined') this.JSON = {};
+
+
+
+(function(){
+
+var special = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'};
+
+var escape = function(chr){
+ return special[chr] || '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).slice(-4);
+};
+
+JSON.validate = function(string){
+ string = string.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+ replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+ replace(/(?:^|:|,)(?:\s*\[)+/g, '');
+
+ return (/^[\],:{}\s]*$/).test(string);
+};
+
+JSON.encode = JSON.stringify ? function(obj){
+ return JSON.stringify(obj);
+} : function(obj){
+ if (obj && obj.toJSON) obj = obj.toJSON();
+
+ switch (typeOf(obj)){
+ case 'string':
+ return '"' + obj.replace(/[\x00-\x1f\\"]/g, escape) + '"';
+ case 'array':
+ return '[' + obj.map(JSON.encode).clean() + ']';
+ case 'object': case 'hash':
+ var string = [];
+ Object.each(obj, function(value, key){
+ var json = JSON.encode(value);
+ if (json) string.push(JSON.encode(key) + ':' + json);
+ });
+ return '{' + string + '}';
+ case 'number': case 'boolean': return '' + obj;
+ case 'null': return 'null';
+ }
+
+ return null;
+};
+
+JSON.decode = function(string, secure){
+ if (!string || typeOf(string) != 'string') return null;
+
+ if (secure || JSON.secure){
+ if (JSON.parse) return JSON.parse(string);
+ if (!JSON.validate(string)) throw new Error('JSON could not decode the input; security is enabled and the value is not secure.');
+ }
+
+ return eval('(' + string + ')');
+};
+
+})();
+
+/*
+---
+
+name: DOMReady
+
+description: Contains the custom event domready.
+
+license: MIT-style license.
+
+requires: [Browser, Element, Element.Event]
+
+provides: [DOMReady, DomReady]
+
+...
+*/
+
+(function(window, document){
+
+var ready,
+ loaded,
+ checks = [],
+ shouldPoll,
+ timer,
+ testElement = document.createElement('div');
+
+var domready = function(){
+ clearTimeout(timer);
+ if (ready) return;
+ Browser.loaded = ready = true;
+ document.removeListener('DOMContentLoaded', domready).removeListener('readystatechange', check);
+
+ document.fireEvent('domready');
+ window.fireEvent('domready');
+};
+
+var check = function(){
+ for (var i = checks.length; i--;) if (checks[i]()){
+ domready();
+ return true;
+ }
+ return false;
+};
+
+var poll = function(){
+ clearTimeout(timer);
+ if (!check()) timer = setTimeout(poll, 10);
+};
+
+document.addListener('DOMContentLoaded', domready);
+
+/*<ltIE8>*/
+// doScroll technique by Diego Perini http://javascript.nwbox.com/IEContentLoaded/
+// testElement.doScroll() throws when the DOM is not ready, only in the top window
+var doScrollWorks = function(){
+ try {
+ testElement.doScroll();
+ return true;
+ } catch (e){}
+ return false;
+};
+// If doScroll works already, it can't be used to determine domready
+// e.g. in an iframe
+if (testElement.doScroll && !doScrollWorks()){
+ checks.push(doScrollWorks);
+ shouldPoll = true;
+}
+/*</ltIE8>*/
+
+if (document.readyState) checks.push(function(){
+ var state = document.readyState;
+ return (state == 'loaded' || state == 'complete');
+});
+
+if ('onreadystatechange' in document) document.addListener('readystatechange', check);
+else shouldPoll = true;
+
+if (shouldPoll) poll();
+
+Element.Events.domready = {
+ onAdd: function(fn){
+ if (ready) fn.call(this);
+ }
+};
+
+// Make sure that domready fires before load
+Element.Events.load = {
+ base: 'load',
+ onAdd: function(fn){
+ if (loaded && this == window) fn.call(this);
+ },
+ condition: function(){
+ if (this == window){
+ domready();
+ delete Element.Events.load;
+ }
+ return true;
+ }
+};
+
+// This is based on the custom load event
+window.addEvent('load', function(){
+ loaded = true;
+});
+
+})(window, document);
+
+/*
+---
+
+script: Base64.js
+
+description: String methods for encoding and decoding Base64 data
+
+license: MIT-style license.
+
+authors: Ryan Florence (http://ryanflorence.com), webtoolkit.info
+
+requires:
+ - core:1.2.4: [String]
+
+provides: [String.toBase64, String.decodeBase64]
+
+...
+*/
+
+
+(function(){
+
+ // Base64 string methods taken from http://www.webtoolkit.info/
+ var Base64 = {
+
+ _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
+
+ encode : function (input) {
+ var output = "";
+ var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
+ var i = 0;
+ input = Base64._utf8_encode(input);
+ while (i < input.length) {
+ chr1 = input.charCodeAt(i++);
+ chr2 = input.charCodeAt(i++);
+ chr3 = input.charCodeAt(i++);
+ enc1 = chr1 >> 2;
+ enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
+ enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
+ enc4 = chr3 & 63;
+ if (isNaN(chr2)) {
+ enc3 = enc4 = 64;
+ } else if (isNaN(chr3)) {
+ enc4 = 64;
+ };
+ output = output +
+ this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
+ this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
+ };
+ return output;
+ },
+
+ decode : function (input) {
+ var output = "";
+ var chr1, chr2, chr3;
+ var enc1, enc2, enc3, enc4;
+ var i = 0;
+ input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
+ while (i < input.length) {
+ enc1 = this._keyStr.indexOf(input.charAt(i++));
+ enc2 = this._keyStr.indexOf(input.charAt(i++));
+ enc3 = this._keyStr.indexOf(input.charAt(i++));
+ enc4 = this._keyStr.indexOf(input.charAt(i++));
+ chr1 = (enc1 << 2) | (enc2 >> 4);
+ chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
+ chr3 = ((enc3 & 3) << 6) | enc4;
+ output = output + String.fromCharCode(chr1);
+ if (enc3 != 64) {
+ output = output + String.fromCharCode(chr2);
+ };
+ if (enc4 != 64) {
+ output = output + String.fromCharCode(chr3);
+ };
+ };
+ output = Base64._utf8_decode(output);
+ return output;
+ },
+
+ // private method for UTF-8 encoding
+ _utf8_encode : function (string) {
+ string = string.replace(/\r\n/g,"\n");
+ var utftext = "";
+ for (var n = 0; n < string.length; n++) {
+ var c = string.charCodeAt(n);
+ if (c < 128) {
+ utftext += String.fromCharCode(c);
+ } else if((c > 127) && (c < 2048)) {
+ utftext += String.fromCharCode((c >> 6) | 192);
+ utftext += String.fromCharCode((c & 63) | 128);
+ } else {
+ utftext += String.fromCharCode((c >> 12) | 224);
+ utftext += String.fromCharCode(((c >> 6) & 63) | 128);
+ utftext += String.fromCharCode((c & 63) | 128);
+ };
+
+ };
+ return utftext;
+ },
+
+
+ _utf8_decode : function (utftext) {
+ var string = "";
+ var i = 0;
+ var c = c1 = c2 = 0;
+ while ( i < utftext.length ) {
+ c = utftext.charCodeAt(i);
+ if (c < 128) {
+ string += String.fromCharCode(c);
+ i++;
+ } else if((c > 191) && (c < 224)) {
+ c2 = utftext.charCodeAt(i+1);
+ string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
+ i += 2;
+ } else {
+ c2 = utftext.charCodeAt(i+1);
+ c3 = utftext.charCodeAt(i+2);
+ string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
+ i += 3;
+ };
+ };
+ return string;
+ }
+
+ };
+
+ String.implement({
+ toBase64: function(){
+ return Base64.encode(this);
+ },
+
+ decodeBase64: function(){
+ return Base64.decode(this);
+ }
+ });
+
+})();
+
+/*
+---
+
+name: Request
+
+description: Powerful all purpose Request Class. Uses XMLHTTPRequest.
+
+license: MIT-style license.
+
+requires: [Object, Element, Chain, Events, Options, Browser]
+
+provides: Request
+
+...
+*/
+
+(function(){
+
+var empty = function(){},
+ progressSupport = ('onprogress' in new Browser.Request);
+
+var Request = this.Request = new Class({
+
+ Implements: [Chain, Events, Options],
+
+ options: {/*
+ onRequest: function(){},
+ onLoadstart: function(event, xhr){},
+ onProgress: function(event, xhr){},
+ onComplete: function(){},
+ onCancel: function(){},
+ onSuccess: function(responseText, responseXML){},
+ onFailure: function(xhr){},
+ onException: function(headerName, value){},
+ onTimeout: function(){},
+ user: '',
+ password: '',*/
+ url: '',
+ data: '',
+ headers: {
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+ },
+ async: true,
+ format: false,
+ method: 'post',
+ link: 'ignore',
+ isSuccess: null,
+ emulation: true,
+ urlEncoded: true,
+ encoding: 'utf-8',
+ evalScripts: false,
+ evalResponse: false,
+ timeout: 0,
+ noCache: false
+ },
+
+ initialize: function(options){
+ this.xhr = new Browser.Request();
+ this.setOptions(options);
+ this.headers = this.options.headers;
+ },
+
+ onStateChange: function(){
+ var xhr = this.xhr;
+ if (xhr.readyState != 4 || !this.running) return;
+ this.running = false;
+ this.status = 0;
+ Function.attempt(function(){
+ var status = xhr.status;
+ this.status = (status == 1223) ? 204 : status;
+ }.bind(this));
+ xhr.onreadystatechange = empty;
+ if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
+ clearTimeout(this.timer);
+
+ this.response = {text: this.xhr.responseText || '', xml: this.xhr.responseXML};
+ if (this.options.isSuccess.call(this, this.status))
+ this.success(this.response.text, this.response.xml);
+ else
+ this.failure();
+ },
+
+ isSuccess: function(){
+ var status = this.status;
+ return (status >= 200 && status < 300);
+ },
+
+ isRunning: function(){
+ return !!this.running;
+ },
+
+ processScripts: function(text){
+ if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return Browser.exec(text);
+ return text.stripScripts(this.options.evalScripts);
+ },
+
+ success: function(text, xml){
+ this.onSuccess(this.processScripts(text), xml);
+ },
+
+ onSuccess: function(){
+ this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain();
+ },
+
+ failure: function(){
+ this.onFailure();
+ },
+
+ onFailure: function(){
+ this.fireEvent('complete').fireEvent('failure', this.xhr);
+ },
+
+ loadstart: function(event){
+ this.fireEvent('loadstart', [event, this.xhr]);
+ },
+
+ progress: function(event){
+ this.fireEvent('progress', [event, this.xhr]);
+ },
+
+ timeout: function(){
+ this.fireEvent('timeout', this.xhr);
+ },
+
+ setHeader: function(name, value){
+ this.headers[name] = value;
+ return this;
+ },
+
+ getHeader: function(name){
+ return Function.attempt(function(){
+ return this.xhr.getResponseHeader(name);
+ }.bind(this));
+ },
+
+ check: function(){
+ if (!this.running) return true;
+ switch (this.options.link){
+ case 'cancel': this.cancel(); return true;
+ case 'chain': this.chain(this.caller.pass(arguments, this)); return false;
+ }
+ return false;
+ },
+
+ send: function(options){
+ if (!this.check(options)) return this;
+
+ this.options.isSuccess = this.options.isSuccess || this.isSuccess;
+ this.running = true;
+
+ var type = typeOf(options);
+ if (type == 'string' || type == 'element') options = {data: options};
+
+ var old = this.options;
+ options = Object.append({data: old.data, url: old.url, method: old.method}, options);
+ var data = options.data, url = String(options.url), method = options.method.toLowerCase();
+
+ switch (typeOf(data)){
+ case 'element': data = document.id(data).toQueryString(); break;
+ case 'object': case 'hash': data = Object.toQueryString(data);
+ }
+
+ if (this.options.format){
+ var format = 'format=' + this.options.format;
+ data = (data) ? format + '&' + data : format;
+ }
+
+ if (this.options.emulation && !['get', 'post'].contains(method)){
+ var _method = '_method=' + method;
+ data = (data) ? _method + '&' + data : _method;
+ method = 'post';
+ }
+
+ if (this.options.urlEncoded && ['post', 'put'].contains(method)){
+ var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
+ this.headers['Content-type'] = 'application/x-www-form-urlencoded' + encoding;
+ }
+
+ if (!url) url = document.location.pathname;
+
+ var trimPosition = url.lastIndexOf('/');
+ if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition);
+
+ if (this.options.noCache)
+ url += (url.contains('?') ? '&' : '?') + String.uniqueID();
+
+ if (data && method == 'get'){
+ url += (url.contains('?') ? '&' : '?') + data;
+ data = null;
+ }
+
+ var xhr = this.xhr;
+ if (progressSupport){
+ xhr.onloadstart = this.loadstart.bind(this);
+ xhr.onprogress = this.progress.bind(this);
+ }
+
+ xhr.open(method.toUpperCase(), url, this.options.async, this.options.user, this.options.password);
+ if (this.options.user && 'withCredentials' in xhr) xhr.withCredentials = true;
+
+ xhr.onreadystatechange = this.onStateChange.bind(this);
+
+ Object.each(this.headers, function(value, key){
+ try {
+ xhr.setRequestHeader(key, value);
+ } catch (e){
+ this.fireEvent('exception', [key, value]);
+ }
+ }, this);
+
+ this.fireEvent('request');
+ xhr.send(data);
+ if (!this.options.async) this.onStateChange();
+ else if (this.options.timeout) this.timer = this.timeout.delay(this.options.timeout, this);
+ return this;
+ },
+
+ cancel: function(){
+ if (!this.running) return this;
+ this.running = false;
+ var xhr = this.xhr;
+ xhr.abort();
+ clearTimeout(this.timer);
+ xhr.onreadystatechange = empty;
+ if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
+ this.xhr = new Browser.Request();
+ this.fireEvent('cancel');
+ return this;
+ }
+
+});
+
+var methods = {};
+['get', 'post', 'put', 'delete', 'GET', 'POST', 'PUT', 'DELETE'].each(function(method){
+ methods[method] = function(data){
+ var object = {
+ method: method
+ };
+ if (data != null) object.data = data;
+ return this.send(object);
+ };
+});
+
+Request.implement(methods);
+
+Element.Properties.send = {
+
+ set: function(options){
+ var send = this.get('send').cancel();
+ send.setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var send = this.retrieve('send');
+ if (!send){
+ send = new Request({
+ data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action')
+ });
+ this.store('send', send);
+ }
+ return send;
+ }
+
+};
+
+Element.implement({
+
+ send: function(url){
+ var sender = this.get('send');
+ sender.send({data: this, url: url || sender.options.url});
+ return this;
+ }
+
+});
+
+})();
+
+/*
+---
+
+name: Request.JSON
+
+description: Extends the basic Request Class with additional methods for sending and receiving JSON data.
+
+license: MIT-style license.
+
+requires: [Request, JSON]
+
+provides: Request.JSON
+
+...
+*/
+
+Request.JSON = new Class({
+
+ Extends: Request,
+
+ options: {
+ /*onError: function(text, error){},*/
+ secure: true
+ },
+
+ initialize: function(options){
+ this.parent(options);
+ Object.append(this.headers, {
+ 'Accept': 'application/json',
+ 'X-Request': 'JSON'
+ });
+ },
+
+ success: function(text){
+ var json;
+ try {
+ json = this.response.json = JSON.decode(text, this.options.secure);
+ } catch (error){
+ this.fireEvent('error', [text, error]);
+ return;
+ }
+ if (json == null) this.onFailure();
+ else this.onSuccess(json, text);
+ }
+
+});
+
diff --git a/www/js/popupdeck.js b/www/js/popupdeck.js
new file mode 100644
index 0000000..70ab5c0
--- /dev/null
+++ b/www/js/popupdeck.js
@@ -0,0 +1,96 @@
+'use strict';
+
+/** BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>, smake <smake at ya dot ru>.
+ *
+ */
+
+//pop up message procedure
+var popUpDeck = null;
+var popUpElements = [];
+function initPopUpDeck(){
+ popUpDeck = document.createElement('div');
+ document.body.appendChild(popUpDeck);
+
+ popUpDeck.set('class', 'popupwrapper');
+ document.querySelector('.popupwrapper').style.display = 'none';
+}
+
+function cleanPopUpDeck(){
+ for(var i=0; i<popUpElements.length; i++){
+ popUpElements[i].removeEvents();
+ popUpElements[i].destroy();
+ document.querySelector('.popupwrapper').style.display = 'none';
+ }
+}
+
+function popUpMessage(type, msg, timeout, callback, center){
+ var newMessage = document.createElement('div');
+ popUpDeck.appendChild(newMessage);
+ document.querySelector('.popupwrapper').style.display = 'block';
+
+ newMessage.set('class', 'popupmessage');
+ newMessage.set('text', msg);
+ popUpDeck.addEvent('mouseleave',
+ function(){
+ if(callback)
+ callback();
+ newMessage.destroy();
+ newMessage = null;
+ document.querySelector('.popupwrapper').style.display = 'none';
+ });
+
+ var color = {
+ r: 255,
+ g: 255,
+ b: 255
+ };
+
+ if(type=='error'){
+ color.r = 247;
+ color.g = 203;
+ color.b = 30;
+ }else
+ if(type=='message'){
+ color.r = 107;
+ color.g = 180;
+ color.b = 229;
+ }else
+ if(type=='critical'){
+ color.r = 255;
+ color.g = 0;
+ color.b = 0;
+ }
+
+ if(center){
+ newMessage.setStyle('position','absolute');
+ newMessage.setStyle('top', document.body.offsetHeight/2);
+ newMessage.setStyle('z-index', '1235');
+ }
+ newMessage.setStyle('background-color','rgba(' + color.r
+ + ',' + color.g
+ + ',' + color.b
+ + ', 0.8)' );
+
+ if(timeout){
+ window.setTimeout(
+ function(){
+ if(newMessage){
+ if(callback)
+ callback();
+ newMessage.destroy();
+ document.querySelector('.popupwrapper').style.display = 'none';
+ }
+ },
+ timeout*5000);
+ }
+
+ popUpElements.push(newMessage);
+
+ return newMessage;
+}
+
+function noInstancePopUp(){
+ popUpMessage('critical', "This instance seems to be not working. Try to enter the console again.", 0, noInstancePopUp, true);
+}
diff --git a/www/js/rdp-start.js b/www/js/rdp-start.js
new file mode 100644
index 0000000..ee509e1
--- /dev/null
+++ b/www/js/rdp-start.js
@@ -0,0 +1,262 @@
+'use strict';
+
+/** BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>, smake <smake at ya dot ru>.
+ *
+ */
+
+function RDPStart(uri, title){
+ uri = wsBase
+ document.querySelector('.rdp_connect_dialog').style.display = 'none';
+ if (title === undefined) {
+ // title = "Portal rdp session " + $('rdphost').value.trim();
+ title = "Portal remote session";
+ }
+ if(!embedded){
+ $('dvLoading').setStyles({'visibility':'visible'});
+ }
+ var controls = document.querySelector('#extracommands');
+ rdp = new webrdp.RDP(uri, $('screen'), controls, !RIMtablet, RIMtablet, vkbd);
+
+ rdp.addEvent('alert', function(msg) {
+ popUpMessage('error', msg, 5);
+ });
+ rdp.addEvent('connected', function() {
+ cleanPopUpDeck();
+ document.title = title;
+ var button = $("rdpconnect");
+ button.removeEvents();
+ window.removeEvent('resize', OnDesktopSize);
+ button.value = 'Disconnect';
+ button.addEvent('click', rdp.Disconnect.bind(rdp));
+ window.addEvent("beforeunload", rdp.Disconnect.bind(rdp));
+ });
+ rdp.addEvent('disconnected', function() {
+ showDialog(true);
+ if(embedded){
+ $('maindialog').addClass('invisible');
+ noInstancePopUp()
+ }
+ var button = $("rdpconnect");
+ button.removeEvents();
+ button.value = 'Connect';
+ button.addEvent('click', function(){RDPStart();});
+ OnDesktopSize();
+ window.addEvent('resize', OnDesktopSize);
+ if(!embedded)
+ {
+ document.querySelector('.rdp_connect_dialog').style.display = 'block';
+ }
+ });
+ rdp.addEvent('mouserelease', ResetRdpMouseFlags);
+ rdp.addEvent('touch2', function() {
+ ShowMouseHelper($('mousehelper').hasClass('invisible'));
+ });
+ rdp.addEvent('touch3', function() {
+ vkbd.toggle();
+ });
+ rdp.addEvent('touch4', function() {
+ if (confirm('Are you sure you want to disconnect?')) {
+ rdp.Disconnect();
+ }
+ });
+ showDialog(false);
+ // rdp.log.loglevel = "debug";
+ rdp.Run();
+}
+
+function SetRdpMouseFlags() {
+ var mf = {
+ 'r': $('rclick').checked,
+ 'm': $('mclick').checked,
+ 'a': $('aclick').checked,
+ 's': $('sclick').checked,
+ 'c': $('cclick').checked,
+ };
+ rdp.SetArtificialMouseFlags(mf);
+}
+function ResetRdpMouseFlags() {
+ $('rclick').checked = false;
+ $('mclick').checked = false;
+ $('aclick').checked = false;
+ $('sclick').checked = false;
+ $('cclick').checked = false;
+ rdp.SetArtificialMouseFlags(null);
+}
+function ShowMouseHelper(show) {
+ var mh = $('mousehelper');
+ inDrag = false;
+ if (show) {
+ mh.setStyles({'position':'absolute','top':mhy,'left':mhx,'z-index':999});
+ mh.addEvent('mousedown',DragStart);
+ $('rclick').addEvent('change', SetRdpMouseFlags);
+ $('mclick').addEvent('change', SetRdpMouseFlags);
+ $('aclick').addEvent('change', SetRdpMouseFlags);
+ $('sclick').addEvent('change', SetRdpMouseFlags);
+ $('cclick').addEvent('change', SetRdpMouseFlags);
+ mh.removeClass('invisible');
+ } else {
+ mh.removeEvents();
+ mh.addClass('invisible');
+ $('rclick').removeEvents();
+ $('mclick').removeEvents();
+ $('aclick').removeEvents();
+ $('sclick').removeEvents();
+ $('cclick').removeEvents();
+ }
+}
+
+function OnDesktopSize() {
+ ResizeCanvas($('dtsize').value);
+ DrawLogo();
+}
+
+function DragStart(evt) {
+ var mh = $('mousehelper');
+ if (!mh.hasClass('invisible')) {
+ inDrag = true;
+ dragX = evt.page.x;
+ dragY = evt.page.y;
+ window.addEvent('mouseup',DragEnd);
+ window.addEvent('touchmove',DragMove);
+ }
+}
+function DragEnd(evt) {
+ inDrag = false;
+ var mh = $('mousehelper');
+ window.removeEvent('touchmove',DragMove);
+ window.removeEvent('mouseup',DragEnd);
+}
+function DragMove(evt) {
+ if (inDrag) {
+ var dx = evt.page.x - dragX;
+ var dy = evt.page.y - dragY;
+ dragX = evt.page.x;
+ dragY = evt.page.y;
+ var mh = $('mousehelper');
+ if (!mh.hasClass('invisible')) {
+ mhx += dx;
+ mhy += dy;
+ mh.setStyles({'top':mhy,'left':mhx});
+ }
+ }
+}
+
+function DrawLogo() {
+ var logo = new Element('img', {'src': 'c_none.png'});
+ logo.addEvent('load', function() {
+ var screen = document.getElementById('screen');
+ var scaleWCoeficient = 0.5;
+ var scaleHCoeficient = 0.5;
+ var iw = this.width * scaleWCoeficient;
+ var ih = this.height * scaleHCoeficient;
+ var scale = (screen.height - 20) / ih;
+ screen.getContext('2d').drawImage(
+ this, 10, 10, Math.round(iw * scale), Math.round(ih * scale)
+ );
+ }.bind(logo));
+}
+
+function ResizeCanvas(sz) {
+ var w, h;
+ if (sz == 'auto') {
+ w = window.getCoordinates().width;
+ h = window.getCoordinates().height;
+ if (RIMtablet) {
+ // Toplevel bar not removable
+ h -= 31;
+ }
+ if (w % 2) {
+ w -= 1;
+ }
+ } else {
+ var sza = sz.split('x');
+ var w = sza[0];
+ var h = sza[1];
+ }
+ $('screen').width = w - 10;
+ $('screen').height = h - 50;
+ $('screen').style["margin"] = "0 auto";
+}
+
+var sendDisconnect = function() {
+if (confirm('Закончить сессию ?')) {
+ $('extracommands').setStyles({'visibility':'hidden'});
+ rdp.Disconnect();
+ window.close();
+}
+}
+
+var altTabOn = false;
+function altTabEvent(){
+ if(altTabOn){
+ altTabOn = false;
+ rdp.SendKey(2);//alt+tab release
+ $('alttab').removeClass('extracommandshold');
+ }
+ else{
+ altTabOn = true;
+ rdp.SendKey(1);//alt+tab
+ $('alttab').addClass('extracommandshold');
+ }
+}
+
+function showDialog(show) {
+ if (show) {
+ ShowMouseHelper(false);
+ var dlg = $('maindialog');
+ var x = Math.round((window.getCoordinates().width - dlg.getCoordinates().width) / 2) + 'px';
+ var y = Math.round((window.getCoordinates().height - dlg.getCoordinates().height) / 2) + 'px';
+ $('extracommands').setStyles(
+ {
+ 'visibility':'hidden'
+ });
+ $('dvLoading').setStyles(
+ {
+ 'visibility':'hidden'
+ });
+ DrawLogo();
+ dlg.setStyles({
+ 'position': 'absolute',
+ 'top': y,
+ 'left': x,
+ 'z-index': 999
+ }).removeClass('invisible');
+ } else {
+ $('maindialog').addClass('invisible');
+ $('extracommands').setStyles(
+ {
+ 'visibility':'visible'
+ });
+ $('ctrlaltdelete').addEvent('click', function(){ rdp.SendKey(0); });
+ $('alttab').addEvent('click', altTabEvent);
+ $('disconnect').addEvent('click', sendDisconnect);
+ }
+}
+
+function settingsGetJSON(){
+ var screen = document.getElementById('screen');
+ return {
+ "backend_settings": {
+ "host" : $('rdphost').value.trim()
+ ,"port" : parseInt($('rdpport').value.trim())
+ ,"pcb" : $('rdppcb').value.trim()
+ ,"user" : $('rdpuser').value.trim()
+ ,"password" : $('rdppass').value
+ ,"perf" : parseInt($('perf').value.trim())
+ ,"fntlm" : parseInt($('fntlm').value.trim())
+ ,"nowallp": parseInt($('nowallp').checked ? '1' : '0')
+ ,"nowdrag": parseInt($('nowdrag').checked ? '1' : '0')
+ ,"nomani" : parseInt($('nomani').checked ? '1' : '0')
+ ,"notheme": parseInt($('notheme').checked ? '1' : '0')
+ ,"nonla" : parseInt($('nonla').checked ? '1' : '0')
+ ,"notls" : parseInt($('notls').checked ? '1' : '0')
+ ,"dtsize" : screen.width + 'x' + screen.height
+ }
+ ,"session_settings": {
+ "token" : location.search.substring(1)
+ ,"proto" : "rdp"
+ }
+ };
+}
diff --git a/www/js/simpletabs-debug.js b/www/js/simpletabs-debug.js
new file mode 100644
index 0000000..33a5e4a
--- /dev/null
+++ b/www/js/simpletabs-debug.js
@@ -0,0 +1,120 @@
+/**
+ * SimpleTabs - Unobtrusive Tabs with Ajax
+ *
+ * @example
+ *
+ * var tabs = new SimpleTabs($('tab-element'), {
+ * selector: 'h2.tab-tab'
+ * });
+ *
+ * @version 1.0
+ *
+ * @license MIT License
+ * @author Harald Kirschner <mail [at] digitarald.de>
+ * @copyright 2007 Author
+ */
+var SimpleTabs = new Class({
+
+ Implements: [Events, Options],
+
+ /**
+ * Options
+ */
+ options: {
+ show: 0,
+ selector: '.tab-tab',
+ classWrapper: 'tab-wrapper',
+ classMenu: 'tab-menu',
+ classContainer: 'tab-container',
+ onSelect: function(toggle, container, index) {
+ toggle.addClass('tab-selected');
+ container.setStyle('display', '');
+ },
+ onDeselect: function(toggle, container, index) {
+ toggle.removeClass('tab-selected');
+ container.setStyle('display', 'none');
+ },
+ onAdded: Class.empty,
+ getContent: null
+ },
+
+ /**
+ * Constructor
+ *
+ * @param {Element} The parent Element that holds the tab elements
+ * @param {Object} Options
+ */
+ initialize: function(element, options) {
+ this.element = $(element);
+ this.setOptions(options);
+ this.selected = null;
+ this.build();
+ },
+
+ build: function() {
+ this.tabs = [];
+ this.menu = new Element('ul', {'class': this.options.classMenu});
+ this.wrapper = new Element('div', {'class': this.options.classWrapper});
+
+ this.element.getElements(this.options.selector).each(function(el) {
+ var content = el.get('href') || (this.options.getContent ? this.options.getContent.call(this, el) : el.getNext());
+ this.addTab(el.innerHTML, el.title || el.innerHTML, content);
+ }, this);
+ this.element.empty().adopt(this.menu, this.wrapper);
+
+ if (this.tabs.length) this.select(this.options.show);
+ },
+
+ /**
+ * Add a new tab at the end of the tab menu
+ *
+ * @param {String} inner Text
+ * @param {String} Title
+ * @param {Element|String} Content Element or URL for Ajax
+ */
+ addTab: function(text, title, content) {
+ var grab = $(content);
+ var container = (grab || new Element('div'))
+ .setStyle('display', 'none')
+ .addClass(this.options.classContainer)
+ .inject(this.wrapper);
+ var pos = this.tabs.length;
+ var evt = (this.options.hover) ? 'mouseenter' : 'click';
+ var tab = {
+ container: container,
+ toggle: new Element('li').grab(new Element('a', {
+ href: '#',
+ title: title
+ }).grab(
+ new Element('span', {html: text})
+ )).addEvent(evt,function(e){this.onClick(e,pos);}.bind(this)).inject(this.menu)
+ };
+ this.tabs.push(tab);
+ return this.fireEvent('onAdded', [tab.toggle, tab.container, pos]);
+ },
+
+ onClick: function(evt, index) {
+ this.select(index);
+ return false;
+ },
+
+ /**
+ * Select the tab via tab-index
+ *
+ * @param {Number} Tab-index
+ */
+ select: function(index) {
+ if (this.selected === index || !this.tabs[index]) return this;
+ var tab = this.tabs[index];
+ var params = [tab.toggle, tab.container, index];
+ if (this.selected !== null) {
+ var current = this.tabs[this.selected];
+ params.append([current.toggle, current.container, this.selected]);
+ this.fireEvent('onDeselect', [current.toggle, current.container, this.selected]);
+ }
+ this.fireEvent('onSelect', params);
+ this.selected = index;
+ return this;
+ }
+
+});
diff --git a/www/js/vkb-debug.js b/www/js/vkb-debug.js
new file mode 100644
index 0000000..97b3ef3
--- /dev/null
+++ b/www/js/vkb-debug.js
@@ -0,0 +1,750 @@
+/*
+ * This code is based on:
+ * HTML Virtual Keyboard Interface Script - v1.49
+ * Copyright (c) 2011 - GreyWyvern
+ * - Licensed for free distribution under the BSDL
+ * http://www.opensource.org/licenses/bsd-license.php
+ * Found at: http://www.greywyvern.com/code/javascript/keyboard
+ *
+ * Extensive modfications and mootoolization:
+ * Copyright (c) 2012 Fritz Elfert
+ */
+
+var webrdp = webrdp || {}
+
+webrdp.vkbd = new Class({
+ Implements: [Options, Events],
+ options: {
+ target: null,
+ version: true, // Show the version
+ deadcheck: true, // Show dead keys checkbox
+ deadkeys: false, // Turn dead keys on by default
+ numpadtoggle: true, // Show numpad toggle
+ numpad: true, // Show number pad by default
+ layouts: ['en_US','de_DE'], // Keyboard layouts to load
+ deflayout: 'de_DE', // Default keyboard layout
+ size: 5, // Initial size
+ sizeswitch: true, // Show size-adjust controls
+ hoverclick: 0, // Click a key after n ms (0 = off)
+ clicksound: '/vkbclick.ogg'
+ },
+ initialize: function(options) {
+ this.setOptions(options);
+ this.VERSION = '1.49';
+ this.posX = 100;
+ this.posY = 100;
+ this.dragging = false;
+ this.VKI_shift = this.VKI_shiftlock = false;
+ this.VKI_altgr = this.VKI_altgrlock = false;
+ this.VKI_alt = this.VKI_altlock = false;
+ this.VKI_ctrl = this.VKI_ctrllock = false;
+ this.VKI_dead = false; // Flag: dead key active
+ this.VKI_kt = 'US International';
+ this.VKI_size = this.options.size;
+ this.VKI_keyCenter = 3;
+ /* ***** i18n text strings ************************************* */
+ this.VKI_i18n = {
+ '00': 'Display Number Pad',
+ '01': 'Display virtual keyboard interface',
+ '02': 'Select keyboard layout',
+ '03': 'Dead keys',
+ '04': 'On',
+ '05': 'Off',
+ '06': 'Close the keyboard',
+ '07': 'Clear',
+ '08': 'Clear this input',
+ '09': 'Version',
+ '10': 'Decrease keyboard size',
+ '11': 'Increase keyboard size'
+ };
+
+ // Load layouts
+ this.VKI_layout = {};
+ this.options.layouts.each(function(n) {
+ new Request.JSON({
+ 'url': 'js/vkbl-' + n + '.json',
+ 'method': 'get',
+ 'async': false,
+ 'onSuccess': function(obj) {
+ this.VKI_layout[obj.displayname] = obj;
+ if (n == this.options.deflayout) {
+ this.VKI_kt = obj.displayname;
+ }
+ }.bind(this)
+ }).addEvent('error', function(txt, err) {
+ console.debug('err:', err);
+ console.debug('txt:', txt);
+ }).send();
+ }, this);
+ /*
+ if (this.options.clicksound) {
+ this.audio = new Audio(this.options.clicksound);
+ }
+ */
+ this.VKI_deadkey = {};
+ this.VKI_deadkey['"'] = this.VKI_deadkey['\u00a8'] = this.VKI_deadkey['\u309B'] = { // Umlaut / Diaeresis / Greek Dialytika / Hiragana/Katakana Voiced Sound Mark
+ 'a': '\u00e4', 'e': '\u00eb', 'i': '\u00ef', 'o': '\u00f6', 'u': '\u00fc', 'y': '\u00ff', '\u03b9': '\u03ca', '\u03c5': '\u03cb',
+ '\u016B': '\u01D6', '\u00FA': '\u01D8', '\u01D4': '\u01DA', '\u00F9': '\u01DC', 'A': '\u00c4', 'E': '\u00cb', 'I': '\u00cf',
+ 'O': '\u00d6', 'U': '\u00dc', 'Y': '\u0178', '\u0399': '\u03aa', '\u03a5': '\u03ab', '\u016A': '\u01D5', '\u00DA': '\u01D7',
+ '\u01D3': '\u01D9', '\u00D9': '\u01DB', '\u304b': '\u304c', '\u304d': '\u304e', '\u304f': '\u3050', '\u3051': '\u3052',
+ '\u3053': '\u3054', '\u305f': '\u3060', '\u3061': '\u3062', '\u3064': '\u3065', '\u3066': '\u3067', '\u3068': '\u3069',
+ '\u3055': '\u3056', '\u3057': '\u3058', '\u3059': '\u305a', '\u305b': '\u305c', '\u305d': '\u305e', '\u306f': '\u3070',
+ '\u3072': '\u3073', '\u3075': '\u3076', '\u3078': '\u3079', '\u307b': '\u307c', '\u30ab': '\u30ac', '\u30ad': '\u30ae',
+ '\u30af': '\u30b0', '\u30b1': '\u30b2', '\u30b3': '\u30b4', '\u30bf': '\u30c0', '\u30c1': '\u30c2', '\u30c4': '\u30c5',
+ '\u30c6': '\u30c7', '\u30c8': '\u30c9', '\u30b5': '\u30b6', '\u30b7': '\u30b8', '\u30b9': '\u30ba', '\u30bb': '\u30bc',
+ '\u30bd': '\u30be', '\u30cf': '\u30d0', '\u30d2': '\u30d3', '\u30d5': '\u30d6', '\u30d8': '\u30d9', '\u30db': '\u30dc'
+ };
+ this.VKI_deadkey['~'] = { // Tilde / Stroke
+ 'a': '\u00e3', 'l': '\u0142', 'n': '\u00f1', 'o': '\u00f5',
+ 'A': '\u00c3', 'L': '\u0141', 'N': '\u00d1', 'O': '\u00d5'
+ };
+ this.VKI_deadkey['^'] = { // Circumflex
+ 'a': '\u00e2', 'e': '\u00ea', 'i': '\u00ee', 'o': '\u00f4', 'u': '\u00fb', 'w': '\u0175', 'y': '\u0177',
+ 'A': '\u00c2', 'E': '\u00ca', 'I': '\u00ce', 'O': '\u00d4', 'U': '\u00db', 'W': '\u0174', 'Y': '\u0176'
+ };
+ this.VKI_deadkey['\u02c7'] = { // Baltic caron
+ 'c': '\u010D', 'd': '\u010f', 'e': '\u011b', 's': '\u0161', 'l': '\u013e', 'n': '\u0148', 'r': '\u0159', 't': '\u0165',
+ 'u': '\u01d4', 'z': '\u017E', '\u00fc': '\u01da', 'C': '\u010C', 'D': '\u010e', 'E': '\u011a', 'S': '\u0160',
+ 'L': '\u013d', 'N': '\u0147', 'R': '\u0158', 'T': '\u0164', 'U': '\u01d3', 'Z': '\u017D', '\u00dc': '\u01d9'
+ };
+ this.VKI_deadkey['\u02d8'] = { // Romanian and Turkish breve
+ 'a': '\u0103', 'g': '\u011f',
+ 'A': '\u0102', 'G': '\u011e'
+ };
+ this.VKI_deadkey['-'] = this.VKI_deadkey['\u00af'] = { // Macron
+ 'a': '\u0101', 'e': '\u0113', 'i': '\u012b', 'o': '\u014d', 'u': '\u016B', 'y': '\u0233', '\u00fc': '\u01d6',
+ 'A': '\u0100', 'E': '\u0112', 'I': '\u012a', 'O': '\u014c', 'U': '\u016A', 'Y': '\u0232', '\u00dc': '\u01d5'
+ };
+ this.VKI_deadkey['`'] = { // Grave
+ 'a': '\u00e0', 'e': '\u00e8', 'i': '\u00ec', 'o': '\u00f2', 'u': '\u00f9', '\u00fc': '\u01dc',
+ 'A': '\u00c0', 'E': '\u00c8', 'I': '\u00cc', 'O': '\u00d2', 'U': '\u00d9', '\u00dc': '\u01db'
+ };
+ this.VKI_deadkey["'"] = this.VKI_deadkey['\u00b4'] = this.VKI_deadkey['\u0384'] = { // Acute / Greek Tonos
+ 'a': '\u00e1', 'e': '\u00e9', 'i': '\u00ed', 'o': '\u00f3', 'u': '\u00fa', 'y': '\u00fd', '\u03b1': '\u03ac',
+ '\u03b5': '\u03ad', '\u03b7': '\u03ae', '\u03b9': '\u03af', '\u03bf': '\u03cc', '\u03c5': '\u03cd', '\u03c9': '\u03ce',
+ '\u00fc': '\u01d8', 'A': '\u00c1', 'E': '\u00c9', 'I': '\u00cd', 'O': '\u00d3', 'U': '\u00da', 'Y': '\u00dd',
+ '\u0391': '\u0386', '\u0395': '\u0388', '\u0397': '\u0389', '\u0399': '\u038a', '\u039f': '\u038c', '\u03a5': '\u038e',
+ '\u03a9': '\u038f', '\u00dc': '\u01d7'
+ };
+ this.VKI_deadkey['\u02dd'] = { // Hungarian Double Acute Accent
+ 'o': '\u0151', 'u': '\u0171',
+ 'O': '\u0150', 'U': '\u0170'
+ };
+ this.VKI_deadkey['\u0385'] = { // Greek Dialytika + Tonos
+ '\u03b9': '\u0390', '\u03c5': '\u03b0'
+ };
+ this.VKI_deadkey['\u00b0'] = this.VKI_deadkey['\u00ba'] = { // Ring
+ 'a': '\u00e5', 'u': '\u016f',
+ 'A': '\u00c5', 'U': '\u016e'
+ };
+ this.VKI_deadkey['\u02DB'] = { // Ogonek
+ 'a': '\u0106', 'e': '\u0119', 'i': '\u012f', 'o': '\u01eb', 'u': '\u0173', 'y': '\u0177',
+ 'A': '\u0105', 'E': '\u0118', 'I': '\u012e', 'O': '\u01ea', 'U': '\u0172', 'Y': '\u0176'
+ };
+ this.VKI_deadkey['\u02D9'] = { // Dot-above
+ 'c': '\u010B', 'e': '\u0117', 'g': '\u0121', 'z': '\u017C',
+ 'C': '\u010A', 'E': '\u0116', 'G': '\u0120', 'Z': '\u017B'
+ };
+ this.VKI_deadkey['\u00B8'] = this.VKI_deadkey['\u201a'] = { // Cedilla
+ 'c': '\u00e7', 's': '\u015F',
+ 'C': '\u00c7', 'S': '\u015E'
+ };
+ this.VKI_deadkey[','] = { // Comma
+ 's': '\u0219', 't': '\u021B',
+ 'S': '\u0218', 'T': '\u021A'
+ };
+ this.VKI_deadkey['\u3002'] = { // Hiragana/Katakana Point
+ '\u306f': '\u3071', '\u3072': '\u3074', '\u3075': '\u3077', '\u3078': '\u307a', '\u307b': '\u307d',
+ '\u30cf': '\u30d1', '\u30d2': '\u30d4', '\u30d5': '\u30d7', '\u30d8': '\u30da', '\u30db': '\u30dd'
+ };
+ this.VKI_symbol = {
+ '\u00a0': 'NB\nSP', '\u200b': 'ZW\nSP', '\u200c': 'ZW\nNJ', '\u200d': 'ZW\nJ'
+ };
+ this.VKI_numpad = [
+ [['$'], ['\u00a3'], ['\u20ac'], ['\u00a5']],
+ [['7'], ['8'], ['9'], ['/']],
+ [['4'], ['5'], ['6'], ['*']],
+ [['1'], ['2'], ['3'], ['-']],
+ [['0'], ['.'], ['='], ['+']]
+ ];
+ this.cblock = [
+ [['Prt', 0x2c],['Slk', 0],['\u231b', 0x13]],
+ [['Ins', 0x2d],['\u21f1', 0x24],['\u21d1', 0x21]],
+ [['Del', 0x2e],['\u21f2', 0x23],['\u21d3', 0x23]],
+ [[''],['\u2191', 0x26],['']],
+ [['\u2190', 0x25],['\u2193', 0x28],['\u2192', 0x27]],
+ ];
+ this.fnblock = [
+ ['Esc', 0x1b], ['F1', 0x70], ['F2', 0x71], ['F3', 0x72], ['F4', 0x73], ['F5', 0x74],
+ ['F6', 0x75], ['F7', 0x76], ['F8', 0x77], ['F9', 0x78], ['F10', 0x79], ['F11', 0x7a],
+ ['F12', 0x7b]
+ ];
+ if (!this.VKI_layout[this.VKI_kt]) {
+ throw 'No layout named "' + this.VKI_kt + '"';
+ }
+ this.VKI_keyboard = new Element('table', {
+ 'id': 'keyboardInputMaster',
+ 'dir': 'ltr',
+ 'cellspacing': 0,
+ 'class': 'hidden',
+ 'styles': {
+ 'position': 'absolute',
+ 'left': this.posX,
+ 'top': this.posY,
+ 'z-index': 999
+ },
+ 'events': {
+ 'click': function(e) { e.stopPropagation(); },
+ 'selectstart': function(e) { e.stopPropagation(); }
+ }
+ }).inject(document.body);
+ this.VKI_langCode = {};
+ var thead = new Element('thead', {
+ 'events': {
+ 'mousedown': this.dragStart.bind(this),
+ }
+ }).inject(this.VKI_keyboard);
+ var tr = new Element('tr').inject(thead);
+ var th = new Element('th', {'colspan':3}).inject(tr);
+
+ var nlayouts = 0;
+ Object.each(this.VKI_layout, function(item) { if ('object' == typeof(item)) { nlayouts++; } });
+ // Build layout selector if more than one layouts
+ if (nlayouts > 1) {
+ this.kbSelect = new Element('div', {
+ 'html': this.VKI_kt,
+ 'title': this.VKI_i18n['02']
+ }).addEvent('click', function(e) {
+ var ol = e.target.getElement('ol');
+ if (!ol.style.display) {
+ ol.setStyle('display','block');
+ var scr = 0;
+ ol.getElements('li').each(function(li) {
+ if (this.VKI_kt == li.firstChild.nodeValue) {
+ li.addClass('selected');
+ scr = li.offsetTop - li.offsetHeight * 2;
+ } else {
+ li.removeClass('selected');
+ }
+ });
+ setTimeout(function() { ol.scrollTop = scr; }, 0);
+ } else {
+ ol.setStyle('display','');
+ }
+ }.bind(this)).appendText(' \u25be').inject(th);
+ var ol = new Element('ol').inject(this.kbSelect);
+ for (ktype in this.VKI_layout) {
+ if (typeof this.VKI_layout[ktype] == 'object') {
+ if (!this.VKI_layout[ktype].lang) {
+ this.VKI_layout[ktype].lang = [];
+ }
+ for (var x = 0; x < this.VKI_layout[ktype].lang.length; x++) {
+ this.VKI_langCode[this.VKI_layout[ktype].lang[x].toLowerCase().replace(/-/g, '_')] = ktype;
+ }
+ var li = new Element('li', {
+ 'title': this.VKI_layout[ktype].name,
+ 'html': ktype
+ }).inject(ol).addEvent('click', function(e) {
+ e.stopPropagation();
+ var el = e.target;
+ el.parentNode.setStyle('display','');
+ this.VKI_kt = this.kbSelect.firstChild.nodeValue = el.get('text');
+ this.VKI_buildKeys();
+ }.bind(this));
+ }
+ }
+ }
+ // Sort the layout selector alphabetically
+ this.VKI_langCode.index = [];
+ for (prop in this.VKI_langCode) {
+ if (prop != 'index' && typeof this.VKI_langCode[prop] == 'string') {
+ this.VKI_langCode.index.push(prop);
+ }
+ }
+ this.VKI_langCode.index.sort();
+ this.VKI_langCode.index.reverse();
+
+ // Build Number-pad toggle-button
+ if (this.options.numpadtoggle) {
+ new Element('span', {
+ 'html': '#',
+ 'title': this.VKI_i18n['00']
+ }).addEvent('click', function() {
+ this.kbNumpad.toggleClass('hidden');
+ }.bind(this)).inject(th);
+ }
+
+ // Build SizeUp and SizeDown buttons
+ if (this.options.sizeswitch) {
+ new Element('small', {
+ 'html': '\u21d3',
+ 'title': this.VKI_i18n['10']
+ }).addEvent('click', function() {
+ this.VKI_size--;
+ this.VKI_kbsize();
+ }.bind(this)).inject(th);
+ new Element('big', {
+ 'html': '\u21d1',
+ 'title': this.VKI_i18n['11']
+ }).addEvent('click', function() {
+ this.VKI_size++;
+ this.VKI_kbsize();
+ }.bind(this)).inject(th);
+ }
+
+ // Build Clear button
+ if (this.options.target) {
+ new Element('span', {
+ 'html': this.VKI_i18n['07'],
+ 'title': this.VKI_i18n['08']
+ }).addEvent('click', function() {
+ this.options.target.value = '';
+ this.options.target.focus();
+ }.bind(this)).inject(th);
+ }
+
+ // Build Close box
+ var closeBox = new Element('strong', {
+ 'html': 'X',
+ 'title': this.VKI_i18n['06']
+ }).addEvent('click', function() {
+ this.hide();
+ }.bind(this)).inject(th);
+
+ var tbody = new Element('tbody').inject(this.VKI_keyboard);
+ var tr = new Element('tr').inject(tbody);
+ var td = new Element('td').inject(tr);
+ var div = new Element('div').inject(td);
+
+ // Build deadKey checkbox
+ if (this.options.deadcheck) {
+ var label = new Element('label').inject(div);
+ this.deadCheckbox = new Element('input', {
+ 'type': 'checkbox',
+ 'title': this.VKI_i18n['03'] + ': ' + (this.options.deadkeys ? this.VKI_i18n['04'] : this.VKI_i18n['05']),
+ 'defaultchecked': this.options.deadkeys,
+ 'checked': this.options.deadkeys
+ }).addEvent('click', function(e) {
+ el = e.target;
+ el.set('title', this.VKI_i18n['03'] + ': ' + ((el.checked) ? this.VKI_i18n['04'] : this.VKI_i18n['05']));
+ this.modify('');
+ }.bind(this)).inject(label);
+ } else {
+ this.deadCheckbox = new Element('input', {
+ 'type': 'checkbox',
+ 'defaultchecked': this.options.deadkeys,
+ 'checked': this.options.deadkeys
+ });
+ }
+
+ // Build Version display
+ if (this.options.version) {
+ new Element('var', {
+ 'html': 'v' + this.VERSION,
+ 'title': this.VKI_i18n['09'] + ' ' + this.VERSION
+ }).inject(div);
+ }
+
+ // Build cursor block
+ this.kbCursor = new Element('td', {
+ 'id': 'keyboardInputCursor'
+ }).inject(tr);
+ var ctable = new Element('table', {
+ 'cellspacing': '0'
+ }).inject(this.kbCursor);
+ var ctbody = new Element('tbody').inject(ctable);
+ this.cblock.each(function(row) {
+ ctr = new Element('tr').inject(ctbody);
+ row.each(function(col) {
+ this.VKI_stdEvents(new Element('td', {
+ 'html': col[0],
+ 'class': (col[0].length ? '' : 'none'),
+ 'char': col[1],
+ 'events': {
+ 'click': (col[1] ? this.kclick2.bind(this) : this.dummy)
+ }
+ }).inject(ctr));
+ }.bind(this));
+ }.bind(this));
+
+ // Build NumPad
+ this.kbNumpad = new Element('td', {
+ 'id': 'keyboardInputNumpad',
+ 'class': (this.options.numpad ? '' : 'hidden')
+ }).inject(tr);
+ var ntable = new Element('table', {
+ 'cellspacing': '0'
+ }).inject(this.kbNumpad);
+ var ntbody = new Element('tbody').inject(ntable);
+ for (var x = 0; x < this.VKI_numpad.length; x++) {
+ var ntr = new Element('tr').inject(ntbody);
+ for (var y = 0; y < this.VKI_numpad[x].length; y++) {
+ this.VKI_stdEvents(new Element('td', {
+ 'html': this.VKI_numpad[x][y],
+ 'events': {
+ 'click': this.kclick.bind(this)
+ }
+ }).inject(ntr));
+ }
+ }
+
+ // Build normal keys
+ this.VKI_buildKeys();
+ this.VKI_keyboard.unselectable = 'on';
+ this.VKI_kbsize();
+ },
+ dummy: function() {
+ },
+ VKI_stdEvents: function(elem) {
+ if ('td' == elem.get('tag')) {
+ // elem.addEvent('dblclick', function(e) { e.PreventDefault(); });
+ if (this.options.hoverclick) {
+ elem.clid = 0;
+ elem.addEvents({
+ 'mouseover': function(e) {
+ var el = e.target;
+ clearTimeout(el.clid);
+ el.clid = function() {
+ this.fireEvent('click', this, 0);
+ }.delay(this.options.hoverclick);
+ }.bind(this),
+ 'mouseout': function() { clearTimeout(this.clid); }.bind(elem),
+ 'mousedown': function() { clearTimeout(this.clid); }.bind(elem),
+ 'mouseup': function() { clearTimeout(this.clid); }.bind(elem)
+ });
+ }
+ }
+ },
+ VKI_kbsize: function(e) {
+ this.VKI_size = Math.min(5, Math.max(1, this.VKI_size));
+ for (var i = 0; i < 6; i++) {
+ this.VKI_keyboard.removeClass('keyboardInputSize' + i);
+ }
+ if (this.VKI_size != 2) {
+ this.VKI_keyboard.addClass('keyboardInputSize' + this.VKI_size);
+ }
+ },
+ kdown: function(evt) {
+ },
+ kup: function(evt) {
+ },
+ kclick: function(evt) {
+ var done = false, character = '\xa0', el = evt.target;
+ if (el.firstChild.nodeName.toLowerCase() != 'small') {
+ if ((character = el.firstChild.nodeValue) == '\xa0') {
+ return;
+ }
+ } else {
+ character = el.firstChild.get('char');
+ }
+ if (this.deadCheckbox.checked && this.VKI_dead) {
+ if (this.VKI_dead != character) {
+ if (character != ' ') {
+ if (this.VKI_deadkey[this.VKI_dead][character]) {
+ this.kpress(this.VKI_deadkey[this.VKI_dead][character]);
+ done = true;
+ }
+ } else {
+ this.kpress(this.VKI_dead);
+ done = true;
+ }
+ } else {
+ done = true;
+ }
+ }
+ this.VKI_dead = false;
+
+ if (!done) {
+ if (this.deadCheckbox.checked && this.VKI_deadkey[character]) {
+ this.VKI_dead = character;
+ el.addClass('dead');
+ if (this.VKI_shift) {
+ this.modify('Shift');
+ }
+ if (this.VKI_alt) {
+ this.modify('Alt');
+ }
+ if (this.VKI_altgr) {
+ this.modify('AltGr');
+ }
+ if (this.VKI_ctrl) {
+ this.modify('Ctrl');
+ }
+ } else {
+ this.kpress(character);
+ }
+ }
+ this.modify('');
+ },
+ kclick2: function(evt) {
+ var c = evt.target.get('char');
+ if (c) {
+ this.kpress(c, true);
+ this.VKI_dead = false;
+ this.modify('');
+ }
+ },
+ VKI_buildKeys: function() {
+ this.VKI_shift = this.VKI_shiftlock = this.VKI_altgr = this.VKI_altgrlock = this.VKI_dead = false;
+ var container = this.VKI_keyboard.tBodies[0].getElement('div');
+ container.getElements('table').each(function(el) { el.destroy(); });
+ for (var x = 0, hasDeadKeys = false, lyt; lyt = this.VKI_layout[this.VKI_kt].keys[x++];) {
+ var table = new Element('table',{
+ 'cellspacing': '0'
+ }).inject(container);
+ if (lyt.length <= this.VKI_keyCenter) {
+ table.addClass('keyboardInputCenter');
+ }
+ var tbody = new Element('tbody').inject(table);
+ var tr = new Element('tr').inject(tbody);
+ for (var y = 0, lkey; lkey = lyt[y++];) {
+ var td = new Element('td').inject(tr);
+ if (this.VKI_symbol[lkey[0]]) {
+ var text = this.VKI_symbol[lkey[0]].split('\n');
+ var small = new Element('small', {
+ 'char': lkey[0]
+ }).inject(td);
+ for (var z = 0; z < text.length; z++) {
+ if (z) {
+ new Element('br').inject(small);
+ }
+ small.appendText(text[z]);
+ }
+ } else {
+ td.appendText(lkey[0] || '\xa0');
+ }
+
+ if (this.deadCheckbox.checked) {
+ for (key in this.VKI_deadkey) {
+ if (key === lkey[0]) {
+ td.addClass('deadkey');
+ break;
+ }
+ }
+ }
+ if (lyt.length > this.VKI_keyCenter && y == lyt.length) {
+ td.addClass('last');
+ }
+ if (lkey[0] == ' ' || lkey[1] == ' ') {
+ td.addClass('space');
+ }
+
+ switch (lkey[1]) {
+ case 'Caps':
+ case 'Ctrl':
+ case 'Shift':
+ case 'Alt':
+ case 'AltGr':
+ case 'AltLk':
+ td.addEvent('click', function(type) {
+ // XXX
+ this.modify(type);
+ }.bind(this, lkey[1]));
+ break;
+ case 'Tab':
+ td.addEvent('click', function(e) {
+ e.preventDefault();
+ this.kpress('\t');
+ }.bind(this));
+ break;
+ case 'Bksp':
+ td.addEvent('click', function(e) {
+ e.preventDefault();
+ this.kpress('\b');
+ }.bind(this));
+ break;
+ case 'Enter':
+ td.addEvent('click', function(e) {
+ e.preventDefault();
+ this.kpress('\r');
+ }.bind(this));
+ break;
+ default:
+ td.addEvent('click', this.kclick.bind(this));
+ break;
+ }
+ this.VKI_stdEvents(td);
+ for (var z = 0; z < 4; z++) {
+ if (this.VKI_deadkey[lkey[z] = lkey[z] || '']) {
+ hasDeadKeys = true;
+ }
+ }
+ }
+ }
+ // Hide deadkey checkbox if layout has no deadkeys
+ if (this.options.deadcheck) {
+ this.deadCheckbox.setStyle('display', hasDeadKeys ? 'inline' : 'none');
+ }
+ },
+ modify: function(type) {
+ switch (type) {
+ case 'Alt':
+ this.VKI_alt = !this.VKI_alt;
+ break;
+ case 'AltGr':
+ this.VKI_altgr = !this.VKI_altgr;
+ break;
+ case 'AltLk':
+ this.VKI_altgr = 0;
+ this.VKI_altgrlock = !this.VKI_altgrlock;
+ break;
+ case 'Caps':
+ this.VKI_shift = 0;
+ this.VKI_shiftlock = !this.VKI_shiftlock;
+ break;
+ case 'Ctrl':
+ this.VKI_ctrl = !this.VKI_ctrl;
+ break;
+ case 'Shift':
+ this.VKI_shift = !this.VKI_shift;
+ break;
+ }
+ var vchar = 0;
+ if (!this.VKI_shift != !this.VKI_shiftlock) {
+ vchar += 1;
+ }
+ if (!this.VKI_altgr != !this.VKI_altgrlock) {
+ vchar += 2;
+ }
+ var tables = this.VKI_keyboard.tBodies[0].getElement('div').getElements('table');
+ for (var x = 0; x < tables.length; x++) {
+ var tds = tables[x].getElements('td');
+ for (var y = 0; y < tds.length; y++) {
+ var classes = {}, lkey = this.VKI_layout[this.VKI_kt].keys[x][y];
+ switch (lkey[1]) {
+ case 'Alt':
+ if (this.VKI_alt) {
+ classes.pressed = 1;
+ }
+ break;
+ case 'AltGr':
+ if (this.VKI_altgr) {
+ classes.pressed = 1;
+ }
+ break;
+ case 'AltLk':
+ if (this.VKI_altgrlock) {
+ classes.pressed = 1;
+ }
+ break;
+ case 'Shift':
+ if (this.VKI_shift) {
+ classes.pressed = 1;
+ }
+ break;
+ case 'Caps':
+ if (this.VKI_shiftlock) {
+ classes.pressed = 1;
+ }
+ break;
+ case 'Ctrl':
+ if (this.VKI_ctrl) {
+ classes.pressed = 1;
+ }
+ break;
+ case 'Tab':
+ case 'Enter':
+ case 'Bksp':
+ break;
+ default:
+ if (type) {
+ tds[y].removeChild(tds[y].firstChild);
+ if (this.VKI_symbol[lkey[vchar]]) {
+ var text = this.VKI_symbol[lkey[vchar]].split('\n');
+ var small = new Element('small', {
+ 'char': lkey[vchar]
+ }).inject(tds[y]);
+ for (var z = 0; z < text.length; z++) {
+ if (z) {
+ new Element('br').inject(small);
+ }
+ small.appendText(text[z]);
+ }
+ } else {
+ tds[y].appendText(lkey[vchar] || '\xa0');
+ }
+ }
+ if (this.deadCheckbox.checked) {
+ var character =
+ tds[y].firstChild.nodeValue || tds[y].firstChild.className;
+ if (this.VKI_dead) {
+ if (character == this.VKI_dead) {
+ classes.pressed = 1;
+ }
+ if (this.VKI_deadkey[this.VKI_dead][character]) {
+ classes.target = 1;
+ }
+ }
+ if (this.VKI_deadkey[character]) {
+ classes.deadkey = 1;
+ }
+ }
+ break;
+ }
+ if (y == tds.length - 1 && tds.length > this.VKI_keyCenter) {
+ classes.last = 1;
+ }
+ if (lkey[0] == ' ' || lkey[1] == ' ') {
+ classes.space = 1;
+ }
+ tds[y].removeClass('pressed').removeClass('target').removeClass('deadkey').removeClass('last').removeClass('space');
+ Object.each(classes, function(i,k) { tds[y].addClass(k); });
+ }
+ }
+ },
+ kpress: function(text, special) {
+ var e = {
+ code: text.charCodeAt(0),
+ shift: this.VKI_shift,
+ control: this.VKI_ctrl,
+ alt: this.VKI_alt,
+ meta: this.VKI_altgr,
+ special: special
+ };
+ this.fireEvent('vkpress', e);
+ if (this.VKI_alt) {
+ this.modify("Alt");
+ }
+ if (this.VKI_altgr) {
+ this.modify("AltGr");
+ }
+ if (this.VKI_ctrl) {
+ this.modify("Ctrl");
+ }
+ if (this.VKI_shift) {
+ this.modify("Shift");
+ }
+ },
+ show: function() {
+ this.VKI_keyboard.removeClass('hidden');
+ },
+ hide: function() {
+ this.VKI_keyboard.addClass('hidden');
+ },
+ toggle: function() {
+ this.VKI_keyboard.toggleClass('hidden');
+ },
+ dragEnd: function(evt) {
+ this.dragging = false;
+ window.removeEvent('mouseup', this.dragEnd.bind(this));
+ window.removeEvent('mousemove', this.dragMove.bind(this));
+ window.removeEvent('touchmove', this.dragMove.bind(this));
+ },
+ dragStart: function(evt) {
+ this.dragX = evt.page.x;
+ this.dragY = evt.page.y;
+ this.dragging = true;
+ window.addEvent('mouseup', this.dragEnd.bind(this));
+ window.addEvent('mousemove', this.dragMove.bind(this));
+ window.addEvent('touchmove', this.dragMove.bind(this));
+ },
+ dragMove: function(evt) {
+ if (this.dragging) {
+ this.posX += evt.page.x - this.dragX;
+ this.posY += evt.page.y - this.dragY;
+ this.dragX = evt.page.x;
+ this.dragY = evt.page.y;
+ this.VKI_keyboard.setStyles({'top':this.posY,'left':this.posX});
+ }
+ }
+});
diff --git a/www/js/vkbl-de_DE.json b/www/js/vkbl-de_DE.json
new file mode 100644
index 0000000..f65cef7
--- /dev/null
+++ b/www/js/vkbl-de_DE.json
@@ -0,0 +1,8 @@
+ { "name": "German", "displayname":"Deutsch", "keys": [
+ [["^", "\u00b0"], ["1", "!"], ["2", "%x22", "\u00b2"], ["3", "\u00a7", "\u00b3"], ["4", "$"], ["5", "%"], ["6", "&"], ["7", "/", "{"], ["8", "(", "["], ["9", ")", "]"], ["0", "=", "}"], ["\u00df", "?", "\\"], ["\u00b4", "`"], ["Bksp", "Bksp"]],
+ [["Tab", "Tab"], ["q", "Q", "@"], ["w", "W"], ["e", "E", "\u20ac"], ["r", "R"], ["t", "T"], ["z", "Z"], ["u", "U"], ["i", "I"], ["o", "O"], ["p", "P"], ["\u00fc", "\u00dc"], ["+", "*", "~"], ["#", "'"]],
+ [["Caps", "Caps"], ["a", "A"], ["s", "S"], ["d", "D"], ["f", "F"], ["g", "G"], ["h", "H"], ["j", "J"], ["k", "K"], ["l", "L"], ["\u00f6", "\u00d6"], ["\u00e4", "\u00c4"], ["Enter", "Enter"]],
+ [["Shift", "Shift"], ["<", ">", "\u00a6"], ["y", "Y"], ["x", "X"], ["c", "C"], ["v", "V"], ["b", "B"], ["n", "N"], ["m", "M", "\u00b5"], [",", ";"], [".", ":"], ["-", "_"], ["Ctrl", "Ctrl"]],
+ [["Alt", "Alt"], [" ", " ", " ", " "], ["AltGr", "AltGr"]]
+ ], "lang": ["de"]
+ }
diff --git a/www/js/vkbl-en_US.json b/www/js/vkbl-en_US.json
new file mode 100644
index 0000000..7e989f9
--- /dev/null
+++ b/www/js/vkbl-en_US.json
@@ -0,0 +1,12 @@
+{
+ "name":"US International",
+ "displayname":"US International",
+ "keys": [
+ [["`", "~"], ["1", "!", "\u00a1", "\u00b9"], ["2", "@", "\u00b2"], ["3", "#", "\u00b3"], ["4", "$", "\u00a4", "\u00a3"], ["5", "%", "\u20ac"], ["6", "^", "\u00bc"], ["7", "&", "\u00bd"], ["8", "*", "\u00be"], ["9", "(", "\u2018"], ["0", ")", "\u2019"], ["-", "_", "\u00a5"], ["=", "+", "\u00d7", "\u00f7"], ["Bksp", "Bksp"]],
+ [["Tab", "Tab"], ["q", "Q", "\u00e4", "\u00c4"], ["w", "W", "\u00e5", "\u00c5"], ["e", "E", "\u00e9", "\u00c9"], ["r", "R", "\u00ae"], ["t", "T", "\u00fe", "\u00de"], ["y", "Y", "\u00fc", "\u00dc"], ["u", "U", "\u00fa", "\u00da"], ["i", "I", "\u00ed", "\u00cd"], ["o", "O", "\u00f3", "\u00d3"], ["p", "P", "\u00f6", "\u00d6"], ["[", "{", "\u00ab"], ["]", "}", "\u00bb"], ["\\", "|", "\u00ac", "\u00a6"]],
+ [["Caps", "Caps"], ["a", "A", "\u00e1", "\u00c1"], ["s", "S", "\u00df", "\u00a7"], ["d", "D", "\u00f0", "\u00d0"], ["f", "F"], ["g", "G"], ["h", "H"], ["j", "J"], ["k", "K"], ["l", "L", "\u00f8", "\u00d8"], [";", ":", "\u00b6", "\u00b0"], ["'", "%x22", "\u00b4", "\u00a8"], ["Enter", "Enter"]],
+ [["Shift", "Shift"], ["z", "Z", "\u00e6", "\u00c6"], ["x", "X"], ["c", "C", "\u00a9", "\u00a2"], ["v", "V"], ["b", "B"], ["n", "N", "\u00f1", "\u00d1"], ["m", "M", "\u00b5"], [",", "<", "\u00e7", "\u00c7"], [".", ">"], ["/", "?", "\u00bf"], ["Shift", "Shift"]],
+ [[" ", " ", " ", " "], ["Alt", "Alt"]]
+ ],
+ "lang": ["en"]
+}
diff --git a/www/js/webrdp-Log.js b/www/js/webrdp-Log.js
new file mode 100644
index 0000000..455a736
--- /dev/null
+++ b/www/js/webrdp-Log.js
@@ -0,0 +1,134 @@
+/** BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, smake <smake at ya dot ru>.
+ *
+ */
+
+webrdp.o2s = function(obj, depth) {
+ depth = depth || [];
+ if (depth.contains(obj)) {
+ return '{SELF}';
+ }
+ switch (typeof(obj)) {
+ case 'undefined':
+ return 'undefined';
+ case 'string':
+ return '"' + obj.replace(/[\x00-\x1f\\"]/g, escape) + '"';
+ case 'array':
+ var string = [];
+ depth.push(obj);
+ for (var i = 0; i < obj.length; ++i) {
+ string.push(webrdp.o2s(obj[i], depth));
+ }
+ depth.pop();
+ return '[' + string + ']';
+ case 'object':
+ case 'hash':
+ var string = [];
+ depth.push(obj);
+ var isE = (obj instanceof UIEvent);
+ Object.each(obj, function(v, k) {
+ if (v instanceof HTMLElement) {
+ string.push(k + '={HTMLElement}');
+ } else if (isE && (('layerX' == k) || ('layerY' == k) ('view' == k))) {
+ string.push(k + '=!0');
+ } else {
+ try {
+ var vstr = webrdp.o2s(v, depth);
+ if (vstr) {
+ string.push(k + '=' + vstr);
+ }
+ } catch (error) {
+ string.push(k + '=??E??');
+ }
+ }
+ });
+ depth.pop();
+ return '{' + string + '}';
+ case 'number':
+ case 'boolean':
+ return '' + obj;
+ case 'null':
+ return 'null';
+ }
+ return null;
+};
+
+webrdp.Log = new Class({
+ initialize: function() {
+ this.ws = null;
+ this.history = [];
+ this.loglevel = null;
+ },
+ _p: function(pfx, a) {
+ var line = '';
+ var i;
+ for (i = 0; i < a.length; ++i) {
+ switch (typeof(a[i])) {
+ case 'string':
+ case 'number':
+ case 'boolean':
+ case 'null':
+ line += a[i] + ' ';
+ break;
+ default:
+ line += webrdp.o2s(a[i]) + ' ';
+ break;
+ }
+ }
+ if (0 < line.length) {
+ this.ws.send(pfx + line);
+ }
+ },
+ drop: function() {
+ },
+ debug: function() {
+ if (this.loglevel == "debug") {
+ var entry = {
+ "date": new Date(),
+ "logtype": arguments.callee.name,
+ "direction": arguments[0],
+ "data": arguments[1]
+ }
+ this.history.push(entry);
+ }
+ },
+ info: function() {
+ if (this.ws) {
+ var a = Array.prototype.slice.call(arguments);
+ a.unshift('I:');
+ this._p.apply(this, a);
+ }
+ },
+ warn: function() {
+ if (this.ws) {
+ var a = Array.prototype.slice.call(arguments);
+ a.unshift('W:');
+ this._p.apply(this, a);
+ }
+ },
+ err: function() {
+ if (this.ws) {
+ var a = Array.prototype.slice.call(arguments);
+ a.unshift('E:');
+ this._p.apply(this, a);
+ }
+ },
+ show: function(count) {
+ if (!count) {
+ var count = 30;
+ }
+ var first = this.history.length - count > 0 ? this.history.length - count : 0;
+ for (var i = first; i < this.history.length; i++) {
+ var h = this.history[i];
+ var header = '[' + h.date.toLocaleTimeString('ru') + '.' + h.date.getMilliseconds() + ']';
+ var header = '[' + h.date.toLocaleTimeString('ru') + '.' + h.date.getMilliseconds() + ']';
+ header += '[' + h.logtype + '] ' + h.direction + ':';
+ console.log(header);
+ console.hex(h.data);
+ }
+ },
+ setWS: function(_ws) {
+ this.ws = _ws;
+ }
+});
diff --git a/www/js/webrdp-debug.js b/www/js/webrdp-debug.js
new file mode 100644
index 0000000..12c7e52
--- /dev/null
+++ b/www/js/webrdp-debug.js
@@ -0,0 +1,1707 @@
+/** BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 wsgate devs, NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+var webrdp = webrdp || {}
+
+webrdp.hasconsole = (typeof console !== 'undefined' && 'debug' in console && 'info' in console && 'warn' in console && 'error' in console);
+
+webrdp.WSrunner = new Class( {
+ Implements: Events,
+ initialize: function(url) {
+ this.url = url;
+ this.log = new webrdp.Log();
+ },
+ Run: function() {
+ try {
+ this.sock = new WebSocket(this.url);
+ } catch (err) { }
+ this.sock.binaryType = 'arraybuffer';
+ this.sock.onopen = this.onWSopen.bind(this);
+ this.sock.onclose = this.onWSclose.bind(this);
+ this.sock.onmessage = this.onWSmsg.bind(this);
+ this.sock.onerror = this.onWSerr.bind(this);
+ this.sock.sendd = this.sock.send;
+ this.sock.send = (data) => {
+ this.log.debug('wsout', data);
+ this.sock.sendd(data);
+ }
+
+ }
+});
+
+webrdp.RDP = new Class( {
+ Extends: webrdp.WSrunner,
+ initialize: function(url, canvas, controls, cssCursor, useTouch, vkbd) {
+ this.canvas = canvas;
+ this.cctx = canvas.getContext('2d');
+ this.cctx.strokeStyle = 'rgba(255,255,255,0)';
+ this.cctx.FillStyle = 'rgba(255,255,255,0)';
+ this.bstore = new Element('canvas', {
+ 'width':this.canvas.width,
+ 'height':this.canvas.height,
+ });
+ this.bctx = this.bstore.getContext('2d');
+ this.aMF = 0;
+ this.Tcool = true;
+ this.pTe = null;
+ this.ccnt = 0;
+ this.clx = 0;
+ this.cly = 0;
+ this.clw = 0;
+ this.clh = 0;
+ this.mX = 0;
+ this.mY = 0;
+ this.chx = 10;
+ this.chy = 10;
+ this.modkeys = [144, ];
+ this.cursors = new Array();
+ this.sid = null;
+ this.open = false;
+ this.cssC = cssCursor;
+ this.uT = useTouch;
+ this.controls = controls;
+ if (!cssCursor) {
+ this.cI = new Element('img', {
+ 'src': '/c_default.png',
+ 'styles': {
+ 'position': 'absolute',
+ 'z-index': 998,
+ 'left': this.mX - this.chx,
+ 'top': this.mY - this.chy
+ }
+ }).inject(document.body);
+ }
+ if (vkbd) {
+ vkbd.addEvent('vkpress', this.onKv.bind(this));
+ }
+ //browser identiying variables
+ this.msie = window.navigator.userAgent.indexOf('MSIE ');
+ this.trident = window.navigator.userAgent.indexOf('Trident/');
+ this.parent(url);
+ //add the toggle function to the keyboard language button
+ $('keyboardlanguage').addEvent('click', this.ToggleLanguageButton.bind(this));
+ },
+ Disconnect: function() {
+ this._reset();
+ },
+ SendKey: function(comb) {
+ //code 0 : ctrl+alt+delete
+ //code 1 : alt+tab
+ //code 2 : alt+tab release
+
+ if (this.sock.readyState == this.sock.OPEN) {
+ this.log.debug('send special combination', comb);
+ buf = new ArrayBuffer(12);
+ a = new Uint32Array(buf);
+ a[0] = 3; // WSOP_CS_SPECIALCOMB
+ a[1] = comb;
+ this.sock.send(buf);
+ };
+ },
+ SendCredentials: function() {
+ var infoJSONstring = JSON.stringify(settingsGetJSON());
+ var len = infoJSONstring.length;
+ var buf = new ArrayBuffer((len + 1)*4); // 4 bytes for each char
+ var bufView = new Uint32Array(buf);
+ bufView[0] = 4; // WSOP_CS_CREDENTIAL_JSON
+ for(var i = 0; i<len; i++){
+ bufView[i+1] = infoJSONstring.charCodeAt(i);
+ }
+ this.sock.send(buf);
+ },
+ /**
+ * Multilanguage mode
+ */
+ useIME: false,
+ ToggleLanguageButton: function(){
+ if(this.useIME){
+ this.useIME = false;
+ $('keyboardlanguage').removeClass('extracommandshold');
+ }else{
+ this.useIME = true;
+ $('keyboardlanguage').addClass('extracommandshold');
+ }
+ },
+ /**
+ * Used when the special input method is on
+ */
+ IMEon: false,
+ /**
+ * The textarea element object
+ */
+ textAreaInput: null,
+ /**
+ * This function adds a textarea element on top of the canvas for the purpose of keyboard input
+ */
+ SetupCanvas: function(){
+ if(this.textAreaInput)return;
+
+ var pos = this.canvas.getPosition();
+ var size = this.canvas.getSize();
+
+ this.textAreaInput = document.createElement('textarea');
+ this.textAreaInput.rdp = this;
+
+ this.textAreaInput.set('id', 'textareainput');
+
+ this.textAreaInput.setStyle('width', size.x);
+ this.textAreaInput.setStyle('height', size.y);
+ this.textAreaInput.setStyle('position', 'absolute');
+ this.textAreaInput.setStyle('opacity', 0);
+ this.textAreaInput.setStyle('resize', 'none');
+ this.textAreaInput.setStyle('cursor', 'default');
+ this.canvas.setStyle('cursor', 'none');
+
+ this.textAreaInput.setPosition(pos);
+
+ this.textAreaInput.addEvent('keydown', this.KeyDownEvent.bind(this));
+ this.textAreaInput.addEvent('keypress', this.KeyPressEvent.bind(this));
+ this.textAreaInput.addEvent('keypress', this.KeyPressEvent.bind(this));
+ this.textAreaInput.addEvent('keyup', this.KeyUpEvent.bind(this));
+ this.textAreaInput.addEvent('copy', function(evt){
+ if (evt.preventDefault) evt.preventDefault();
+ if (evt.stopPropagation) evt.stopPropagation();
+ });
+ this.textAreaInput.addEvent('paste', function(evt){
+ if (evt.preventDefault) evt.preventDefault();
+ if (evt.stopPropagation) evt.stopPropagation();
+ // alert("pasted");
+ });
+
+
+ document.body.appendChild(this.textAreaInput);
+
+ //start the IME helper refresh
+ refreshIMEhelper();
+
+ //make sure the textarea is always on focus
+ this.textAreaInput.focus();
+ this.textAreaInput.addEvent('blur', function(){
+ setTimeout(function(){
+ if($('textareainput')){
+ var cbdcontent = document.getElementById('cbdcontent');
+ if (cbdcontent.style.visibility != "visible") {
+ $('textareainput').focus();
+ }
+ }
+ },20);
+ });
+
+ },
+ /**
+ * Creating clipboard object and setting drag`n`drop area
+ */
+ SetupDragnDrop: function(){
+ this.cbd = new ClipboardHandler(this);
+ var area = this.textAreaInput;
+ area.addEventListener("dragenter", function(evt) {
+ var drop_zone = document.createElement('div');
+ document.body.appendChild(drop_zone);
+ drop_zone.rdp = evt.target.rdp;
+
+ var html = lables.drop_here;
+ drop_zone.innerHTML = html;
+
+ var pos = event.target.getPosition();
+ // var size = event.target.getSize();
+
+ drop_zone.set('id', 'drop_zone');
+ // drop_zone.setStyle('z-index', '1000000');
+ drop_zone.setStyle('position', 'absolute');
+ drop_zone.setStyle('position', 'absolute');
+ drop_zone.setStyle('resize', 'none');
+ drop_zone.setPosition(pos);
+ drop_zone.setStyle('width', event.target.style.width);
+ drop_zone.setStyle('height', event.target.style.height);
+
+ drop_zone.addEventListener("dragover", function(evt) {
+ evt.stopPropagation();
+ evt.preventDefault();
+ evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
+ }, false);
+
+ drop_zone.addEventListener("drop", function(evt) {
+ var cbd = evt.target.rdp.cbd;
+ evt.stopPropagation();
+ evt.preventDefault();
+ cbd.ClipoboardSendFilelist(evt.dataTransfer.files);
+ var drop_zone = document.getElementById('drop_zone');
+ document.body.removeChild(drop_zone);
+ }, false);
+
+ drop_zone.addEventListener("dragleave", function(evt) {
+ var drop_zone = document.getElementById('drop_zone');
+ document.body.removeChild(drop_zone);
+ }, false);
+
+ }, false);
+ },
+ /**
+ * Returns true when a non character is pressed
+ */
+ FunctionalKey: function(key){
+ return (key != 32 && ((key <= 46) || (91 <= key && key <= 145)));
+ },
+ /**
+ * Takes the contents of the textarea and sends them to the server
+ */
+ DumpTextArea: function(key){
+ if(key==13||key==0)
+ if(this.IMEon){
+ var textinput = this.textAreaInput.get("value");
+ if(textinput!=""){
+ if(textinput[0]=='\n'){
+ textinput = textinput.substring(1);
+ }
+ this.SendUnicodeString(textinput);
+ this.textAreaInput.set("value","");
+ }
+ this.IMEon=false;
+ }
+ }, /**
+ * Position cursor image
+ */
+ cP: function() {
+ this.cI.setStyles({'left': this.mX - this.chx, 'top': this.mY - this.chy});
+ },
+ /**
+ * Check, if a given point is inside the clipping region.
+ */
+ _ckclp: function(x, y) {
+ if (this.clw || this.clh) {
+ return (
+ (x >= this.clx) &&
+ (x <= (this.clx + this.clw)) &&
+ (y >= this.cly) &&
+ (y <= (this.cly + this.clh))
+ );
+ }
+ // No clipping region
+ return true;
+ },
+ /**
+ * Main message loop.
+ */
+ _pmsg: function(data) { // process a binary RDP message from our queue
+ // 0-12 code is WSGATE protocol with a little tuning
+ // 13-XXX codes is our own protocol draft implementation
+ var op, hdr, count, rects, bmdata, rgba, compressed, i, offs, x, y, sx, sy, w, h, dw, dh, bpp, color, len;
+ op = new Uint32Array(data, 0, 1);
+ switch (op[0]) {
+ case 0x0:
+ // BeginPaint
+ // this.log.debug('BeginPaint');
+ this._ctxS();
+ break;
+ case 0x01:
+ // EndPaint
+ // this.log.debug('EndPaint');
+ this._ctxR();
+ break;
+ case 0x02:
+ // Single bitmap
+ //
+ // 0 uint32 Destination X
+ // 1 uint32 Destination Y
+ // 2 uint32 Width
+ // 3 uint32 Height
+ // 4 uint32 Destination Width
+ // 5 uint32 Destination Height
+ // 6 uint32 Bits per Pixel
+ // 7 uint32 Flag: Compressed
+ // 8 uint32 DataSize
+ //
+ hdr = new Uint32Array(data, 4, 9);
+ bmdata = new Uint8Array(data, 40);
+ x = hdr[0];
+ y = hdr[1];
+ w = hdr[2];
+ h = hdr[3];
+ dw = hdr[4];
+ dh = hdr[5];
+ bpp = hdr[6];
+ compressed = (hdr[7] != 0);
+ len = hdr[8];
+ if ((bpp == 16) || (bpp == 15)) {
+ if (this._ckclp(x, y) && this._ckclp(x + dw, y + dh)) {
+ // this.log.debug('BMi:',(compressed ? ' C ' : ' U '),' x=',x,'y=',y,' w=',w,' h=',h,' l=',len);
+ var outB = this.cctx.createImageData(w, h);
+ if (compressed) {
+ webrdp.dRLE16_RGBA(bmdata, len, w, outB.data);
+ webrdp.flipV(outB.data, w, h);
+ } else {
+ webrdp.dRGB162RGBA(bmdata, len, outB.data);
+ }
+ this.cctx.putImageData(outB, x, y, 0, 0, dw, dh);
+ } else {
+ // this.log.debug('BMc:',(compressed ? ' C ' : ' U '),' x=',x,'y=',y,' w=',w,' h=',h,' bpp=',bpp);
+ // putImageData ignores the clipping region, so we must
+ // clip ourselves: We first paint into a second canvas,
+ // then use drawImage (which honors clipping).
+
+ var outB = this.bctx.createImageData(w, h);
+ if (compressed) {
+ webrdp.dRLE16_RGBA(bmdata, len, w, outB.data);
+ webrdp.flipV(outB.data, w, h);
+ } else {
+ webrdp.dRGB162RGBA(bmdata, len, outB.data);
+ }
+ this.bctx.putImageData(outB, 0, 0, 0, 0, dw, dh);
+ this.cctx.drawImage(this.bstore, 0, 0, dw, dh, x, y, dw, dh);
+ }
+ } else {
+ this.log.warn('BPP <> 15/16 not yet implemented');
+ }
+ break;
+ case 0x03:
+ // Primary: OPAQUE_RECT_ORDER
+ // x, y , w, h, color
+ hdr = new Int32Array(data, 4, 4);
+ rgba = new Uint8Array(data, 20, 4);
+ // this.log.debug('Fill:',hdr[0], hdr[1], hdr[2], hdr[3], this._c2s(rgba));
+ this.cctx.fillStyle = this._c2s(rgba);
+ this.cctx.fillRect(hdr[0], hdr[1], hdr[2], hdr[3]);
+ break;
+ case 0x04:
+ // SetBounds
+ // left, top, right, bottom
+ hdr = new Int32Array(data, 4, 4);
+ this._cR(hdr[0], hdr[1], hdr[2] - hdr[0], hdr[3] - hdr[1], true);
+ break;
+ case 0x05:
+ // PatBlt
+ if (28 == data.byteLength) {
+ // Solid brush style
+ // x, y, width, height, fgcolor, rop3
+ hdr = new Int32Array(data, 4, 4);
+ x = hdr[0];
+ y = hdr[1];
+ w = hdr[2];
+ h = hdr[3];
+ rgba = new Uint8Array(data, 20, 4);
+ this._ctxS();
+ this._cR(x, y, w, h, false);
+ if (this._sROP(new Uint32Array(data, 24, 1)[0])) {
+ this.cctx.fillStyle = this._c2s(rgba);
+ this.cctx.fillRect(x, y, w, h);
+ }
+ this._ctxR();
+ } else {
+ this.log.warn('PatBlt: Patterned brush not yet implemented');
+ }
+ break;
+ case 0x06:
+ // Multi Opaque rect
+ // color, nrects
+ // rect1.x,rect1.y,rect1.w,rect1.h ... rectn.x,rectn.y,rectn.w,rectn.h
+ rgba = new Uint8Array(data, 4, 4);
+ count = new Uint32Array(data, 8, 1);
+ rects = new Uint32Array(data, 12, count[0] * 4);
+ // this.log.debug('MultiFill: ', count[0], " ", this._c2s(rgba));
+ this.cctx.fillStyle = this._c2s(rgba);
+ offs = 0;
+ // var c = this._c2s(rgba);
+ for (i = 0; i < count[0]; ++i) {
+ this.cctx.fillRect(rects[offs], rects[offs+1], rects[offs+2], rects[offs+3]);
+ // this._fR(rects[offs], rects[offs+1], rects[offs+2], rects[offs+3], c);
+ offs += 4;
+ }
+ break;
+ case 0x07:
+ // ScrBlt
+ // rop3, x, y, w, h, sx, sy
+ hdr = new Int32Array(data, 8, 6);
+ x = hdr[0];
+ y = hdr[1];
+ w = hdr[2];
+ h = hdr[3];
+ sx = hdr[4];
+ sy = hdr[5];
+ if ((w > 0) && (h > 0)) {
+ if (this._sROP(new Uint32Array(data, 4, 1)[0])) {
+ if (this._ckclp(x, y) && this._ckclp(x + w, y + h)) {
+ // No clipping necessary
+ this.cctx.putImageData(this.cctx.getImageData(sx, sy, w, h), x, y);
+ } else {
+ // Clipping necessary
+ this.bctx.putImageData(this.cctx.getImageData(sx, sy, w, h), 0, 0);
+ this.cctx.drawImage(this.bstore, 0, 0, w, h, x, y, w, h);
+ }
+ }
+ } else {
+ this.log.warn('ScrBlt: width and/or height is zero');
+ }
+ break;
+ case 0x08:
+ // PTR_NEW
+ // id, xhot, yhot
+ hdr = new Uint32Array(data, 4, 3);
+ var inline_cursor_base64 = btoa(String.fromCharCode.apply(null, new Uint8Array(data, 16)));
+ if (this.cssC) {
+ this.cursors[hdr[0]] = (this.msie > 0 || this.trident > 0) ? 'url(/cur/' + this.sid + '/' + hdr[0] + '), none' : //IE is not suporting given hot spots
+ 'url(data:image/vnd.microsoft.icon;base64,' + inline_cursor_base64 + ') ' + hdr[1] + ' ' + hdr[2] + ',default';
+ } else {
+ this.cursors[hdr[0]] = (this.msie > 0 || this.trident > 0) ? { u: '/cur/' + this.sid + '/' + hdr[0] } :
+ { u: inline_cursor_base64, x: hdr[1], y: hdr[2] };
+ }
+ break;
+ case 0x09:
+ // PTR_FREE
+ // id
+ this.cursors[new Uint32Array(data, 4, 1)[0]] = undefined;
+ break;
+ case 0x0a:
+ // PTR_SET
+ // id
+ // this.log.debug('PS:', this.cursors[new Uint32Array(data, 4, 1)[0]]);
+ if (this.cssC) {
+ if(this.textAreaInput)
+ this.textAreaInput.setStyle('cursor', this.cursors[new Uint32Array(data, 4, 1)[0]]);
+ } else {
+ var cobj = this.cursors[new Uint32Array(data, 4, 1)[0]];
+ this.chx = cobj.x;
+ this.chy = cobj.y;
+ this.cI.src = cobj.u;
+ }
+ break;
+ case 0x0b:
+ // PTR_SETNULL
+ if (this.cssC) {
+ if(this.textAreaInput)
+ this.textAreaInput.setStyle('cursor', 'none');
+ } else {
+ this.cI.src = '/c_none.png';
+ }
+ break;
+ case 0x0c:
+ // PTR_SETDEFAULT
+ if (this.cssC) {
+ if(this.textAreaInput)
+ this.textAreaInput.setStyle('cursor', 'default');
+ } else {
+ this.chx = 10;
+ this.chy = 10;
+ this.cI.src = '/c_default.png';
+ }
+ break;
+
+ // our draft protocol implementation follows
+ case 0x0d:
+ /* clipboard_send_formats_available
+ * this message contain list of clipboard format on server
+ * available for fetching
+ *
+ * typedef enum
+ * {
+ * clip_format_unsupported = -1,
+ * clip_format_raw,
+ * clip_format_text,
+ * clip_format_FileGroupDescriptorW
+ * }wrdp_enum_clip_format;
+ * NOTE: "wrdp_enum_clip_format" must be kept in sync with
+ * core api
+ *
+ */
+ {
+ // console.log(data.toHexdump());
+ var fmts = new Uint8Array(data, 4);
+ this.cbd.ClipbrdInfoParse(fmts);
+ }
+ break;
+ case 0x0e:
+ // clipboard_send_data
+ // this message contain actual clipboard data
+ // NOTE: not all clipboard formats will support data
+ // fetching
+ {
+ // console.log(data.toHexdump());
+ this.cbd.reciveClipboardInfo(data);
+ }
+ break;
+ case 0x0f:
+ // clipboard_request_data
+ // local clipboard data requested
+ {
+ // console.log(data.toHexdump());
+ var type = new Uint8Array(data, 4, 1);
+ this.cbd.sendClipboardInfo(type[0]);
+ }
+ break;
+ case 0x10:
+ /* ft_request
+ * this is filetransfer request
+ * must contain file id from cached list
+ */
+ {
+ // console.log(data.toHexdump());
+ var file_id = new Uint32Array(data, 4, 1)[0];
+ var req_size = new BigUint64Array(data, 4 + 4, 1)[0];
+ var file_offset = new BigUint64Array(data, 4 + 4 + 8, 1)[0];
+ file = this.cbd.filelistOut[file_id];
+ if (!file) {
+ console.log("Can`t find file for transfer")
+ return;
+ }
+
+ file.sendChunk(req_size, file_offset);
+ }
+ break;
+ case 0x11:
+ // ft_chunk
+ // this message contain transfer id and chunk of file data
+ {
+ // console.log(data.toHexdump())
+ var transfer_id = new Uint32Array(data,0,2)[1];
+ var file = null;
+ this.cbd.filelistIn.forEach(function(f) {
+ if (transfer_id == f.transfer_id) {
+ file = f
+ }
+ });
+ if (!file) {
+ this.cbd.filelistIn.forEach(function(f) {
+ if (f.status == 2) {
+ file = f
+ }
+ });
+ }
+ if (file) {
+ file.newChunk(data);
+ }
+ }
+ break;
+ case 0x12:
+ // ft_finish
+ // this message indicate what filetransfer is finished
+ {
+ // console.log(data.toHexdump());
+ this.cbd.update();
+ }
+ break;
+ default:
+ this.log.warn('Unknown BINRESP: ', data.byteLength);
+ }
+ },
+ _cR: function(x, y, w, h, save) {
+ if (save) {
+ this.clx = x;
+ this.cly = y;
+ this.clw = w;
+ this.clh = h;
+ }
+ // Replace clipping region, NO intersection.
+ this.cctx.beginPath();
+ this.cctx.rect(0, 0, this.canvas.width, this.canvas.height);
+ this.cctx.clip();
+ if (x == y == 0) {
+ // All zero means: reset to full canvas size
+ if ((w == h == 0) || ((w == this.canvas.width) && (h == this.canvas.height))) {
+ return;
+ }
+ }
+ // New clipping region
+ this.cctx.beginPath();
+ this.cctx.rect(x, y, w, h);
+ this.cctx.clip();
+ },
+ _fR: function(x, y, w, h, color) {
+ return;
+ if ((w < 2) || (h < 2)) {
+ this.cctx.strokeStyle = color;
+ this.cctx.beginPath();
+ this.cctx.moveTo(x, y);
+ if (w > h) {
+ this.cctx.lineWidth = h;
+ this.cctx.lineTo(x + w, y);
+ } else {
+ this.cctx.lineWidth = w;
+ this.cctx.lineTo(x, y + h);
+ }
+ this.cctx.stroke();
+ } else {
+ this.cctx.fillStyle = color;
+ this.cctx.fillRect(x, y, w, h);
+ }
+ },
+ _sROP: function(rop) {
+ switch (rop) {
+ case 0x005A0049:
+ // GDI_PATINVERT: D = P ^ D
+ this.cctx.globalCompositeOperation = 'xor';
+ return true;
+ break;
+ case 0x00F00021:
+ // GDI_PATCOPY: D = P
+ this.cctx.globalCompositeOperation = 'copy';
+ return true;
+ break;
+ case 0x00CC0020:
+ // GDI_SRCCOPY: D = S
+ this.cctx.globalCompositeOperation = 'source-over';
+ return true;
+ break;
+ default:
+ this.log.warn('Unsupported raster op: ', rop.toString(16));
+ break;
+ }
+ return false;
+ /*
+ case 0x00EE0086:
+ // GDI_SRCPAINT: D = S | D
+ break;
+ case 0x008800C6:
+ // GDI_SRCAND: D = S & D
+ break;
+ case 0x00660046:
+ // GDI_SRCINVERT: D = S ^ D
+ break;
+ case 0x00440328:
+ // GDI_SRCERASE: D = S & ~D
+ break;
+ case 0x00330008:
+ // GDI_NOTSRCCOPY: D = ~S
+ break;
+ case 0x001100A6:
+ // GDI_NOTSRCERASE: D = ~S & ~D
+ break;
+ case 0x00C000CA:
+ // GDI_MERGECOPY: D = S & P
+ break;
+ case 0x00BB0226:
+ // GDI_MERGEPAINT: D = ~S | D
+ break;
+ case 0x00FB0A09:
+ // GDI_PATPAINT: D = D | (P | ~S)
+ break;
+ case 0x00550009:
+ // GDI_DSTINVERT: D = ~D
+ break;
+ case 0x00000042:
+ // GDI_BLACKNESS: D = 0
+ break;
+ case 0x00FF0062:
+ // GDI_WHITENESS: D = 1
+ break;
+ case 0x00E20746:
+ // GDI_DSPDxax: D = (S & P) | (~S & D)
+ break;
+ case 0x00B8074A:
+ // GDI_PSDPxax: D = (S & D) | (~S & P)
+ break;
+ case 0x000C0324:
+ // GDI_SPna: D = S & ~P
+ break;
+ case 0x00220326:
+ // GDI_DSna D = D & ~S
+ break;
+ case 0x00220326:
+ // GDI_DSna: D = D & ~S
+ break;
+ case 0x00A000C9:
+ // GDI_DPa: D = D & P
+ break;
+ case 0x00A50065:
+ // GDI_PDxn: D = D ^ ~P
+ break;
+ */
+ },
+ /**
+ * Reset our state to disconnected
+ */
+ _reset: function() {
+ this.log.setWS(null);
+ this.fireEvent('disconnected');
+ if (this.sock.readyState == this.sock.OPEN) {
+ this.sock.close();
+ }
+ this.clx = 0;
+ this.cly = 0;
+ this.clw = 0;
+ this.clh = 0;
+ this.canvas.removeEvents();
+ document.removeEvents();
+ var drop_zone = document.getElementById('drop_zone');
+ if (drop_zone) {
+ document.body.removeChild(drop_zone);
+ }
+
+ try{
+ this.textAreaInput.remove();
+ }
+ catch(err){
+ }
+ this.textAreaInput = null;
+ while (this.ccnt > 0) {
+ this.cctx.restore();
+ this.ccnt -= 1;
+ }
+ this.cctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
+ document.title = document.title.replace(/:.*/, ': offline');
+ if (this.cssC) {
+ this.canvas.setStyle('cursor','default');
+ } else {
+ this.cI.src = '/c_default.png';
+ }
+ if (!this.cssC) {
+ this.cI.removeEvents();
+ this.cI.destroy();
+ }
+ },
+ fT: function() {
+ delete this.fTid;
+ if (this.pT) {
+ this.fireEvent('touch' + this.pT);
+ this.pT = 0;
+ return;
+ }
+ if (this.pTe) {
+ this.onMd(this.pTe);
+ this.pTe = null;
+ }
+ },
+ cT: function() {
+ this.log.debug('cT');
+ this.Tcool = true;
+ },
+ /**
+ * Event handler for touch start
+ */
+ onTs: function(evt) {
+ var tn = evt.targetTouches.length;
+ this.log.debug('Ts:', tn);
+ switch (tn) {
+ default:
+ break;
+ case 1:
+ this.pTe = evt;
+ evt.preventDefault();
+ if ('number' == typeof(this.fTid)) {
+ clearTimeout(this.fTid);
+ }
+ this.fTid = this.fT.delay(50, this);
+ break;
+ case 2:
+ this.pT = 2;
+ this.Tcool = false;
+ evt.preventDefault();
+ if ('number' == typeof(this.fTid)) {
+ clearTimeout(this.fTid);
+ }
+ this.cT.delay(500, this)
+ this.fTid = this.fT.delay(50, this);
+ break;
+ case 3:
+ this.pT = 3;
+ this.Tcool = false;
+ evt.preventDefault();
+ if ('number' == typeof(this.fTid)) {
+ clearTimeout(this.fTid);
+ }
+ this.cT.delay(500, this)
+ this.fTid = this.fT.delay(50, this);
+ break;
+ case 4:
+ this.pT = 4;
+ this.Tcool = false;
+ evt.preventDefault();
+ if ('number' == typeof(this.fTid)) {
+ clearTimeout(this.fTid);
+ }
+ this.cT.delay(500, this)
+ this.fTid = this.fT.delay(50, this);
+ break;
+ }
+ return true;
+ },
+ /**
+ * Event handler for touch start
+ */
+ onTe: function(evt) {
+ if ((0 == evt.targetTouches.length) && this.Tcool) {
+ evt.preventDefault();
+ this.onMu(evt, evt.changedTouches[0].pageX, evt.changedTouches[0].pageY);
+ }
+ },
+ /**
+ * Event handler for touch move
+ */
+ onTm: function(evt) {
+ // this.log.debug('Tm:', evt);
+ if (1 == evt.targetTouches.length) {
+ this.onMm(evt);
+ }
+ },
+ /**
+ * Event handler for mouse move events
+ */
+ onMm: function(evt) {
+ var buf, a, x, y;
+ evt.preventDefault();
+ x = (this.msie > 0 || this.trident > 0) ? evt.event.layerX - evt.event.currentTarget.offsetLeft : evt.event.layerX;
+ y = (this.msie > 0 || this.trident > 0) ? evt.event.layerY - evt.event.currentTarget.offsetTop : evt.event.layerY;
+ if (!this.cssC) {
+ this.mX = x;
+ this.mY = y;
+ this.cP();
+ }
+ // this.log.debug('mM x: ', x, ' y: ', y);
+ if (this.sock.readyState == this.sock.OPEN) {
+ buf = new ArrayBuffer(16);
+ a = new Uint32Array(buf);
+ a[0] = 0; // WSOP_CS_MOUSE
+ a[1] = 0x0800; // PTR_FLAGS_MOVE
+ a[2] = x;
+ a[3] = y;
+ this.sock.send(buf);
+ }
+ },
+ /**
+ * Event handler for mouse down events
+ */
+ onMd: function(evt) {
+ var buf, a, x, y, which;
+ this.textAreaInput.focus();
+ this.cbd.update();
+ if (this.Tcool) {
+ if(evt.preventDefault) evt.preventDefault();
+ if(evt.stopPropagation) evt.stopPropagation();
+ if (evt.rightClick && evt.control && evt.alt) {
+ this.fireEvent('touch3');
+ return;
+ }
+ x = (this.msie > 0 || this.trident > 0) ? evt.event.layerX - evt.event.currentTarget.offsetLeft : evt.event.layerX;
+ y = (this.msie > 0 || this.trident > 0) ? evt.event.layerY - evt.event.currentTarget.offsetTop : evt.event.layerY;
+ which = this._mB(evt);
+ //this.log.debug('mD b: ', which, ' x: ', x, ' y: ', y);
+ if (this.sock.readyState == this.sock.OPEN) {
+ buf = new ArrayBuffer(16);
+ a = new Uint32Array(buf);
+ a[0] = 0; // WSOP_CS_MOUSE
+ a[1] = 0x8000 | which;
+ a[2] = x;
+ a[3] = y;
+ this.sock.send(buf);
+ this.mouseDownStatus[which] = true;
+ }
+ }
+ },
+ /**
+ * Event handler for mouse up events
+ */
+ onMu: function(evt, x, y) {
+ var buf, a, x, y, which;
+ if (this.Tcool) {
+ evt.preventDefault();
+ x = (this.msie > 0 || this.trident > 0) ? evt.event.layerX - evt.event.currentTarget.offsetLeft : evt.event.layerX;
+ y = (this.msie > 0 || this.trident > 0) ? evt.event.layerY - evt.event.currentTarget.offsetTop : evt.event.layerY;
+ which = this._mB(evt);
+ this.log.debug('mU b: ', which, ' x: ', x, ' y: ', y);
+ if (this.aMF) {
+ this.fireEvent('mouserelease');
+ }
+ if (this.sock.readyState == this.sock.OPEN) {
+ buf = new ArrayBuffer(16);
+ a = new Uint32Array(buf);
+ a[0] = 0; // WSOP_CS_MOUSE
+ a[1] = which;
+ a[2] = x;
+ a[3] = y;
+ this.sock.send(buf);
+ this.mouseDownStatus[which] = false;
+ }
+ }
+ },
+ /**
+ * Event handler for mouse wheel events
+ */
+ onMw: function(evt) {
+ var buf, a, x, y;
+ evt.preventDefault();
+ x = (this.msie > 0 || this.trident > 0) ? evt.event.layerX - evt.event.currentTarget.offsetLeft : evt.event.layerX;
+ y = (this.msie > 0 || this.trident > 0) ? evt.event.layerY - evt.event.currentTarget.offsetTop : evt.event.layerY;
+ // this.log.debug('mW d: ', evt.wheel, ' x: ', x, ' y: ', y);
+ if (this.sock.readyState == this.sock.OPEN) {
+ buf = new ArrayBuffer(16);
+ a = new Uint32Array(buf);
+ a[0] = 0; // WSOP_CS_MOUSE
+ a[1] = 0x200 | ((evt.wheel > 0) ? 0x087 : 0x188);
+ a[2] = 0;
+ a[3] = 0;
+ this.sock.send(buf);
+ }
+ },
+ /**
+ * Field used to keep the states of the sent mouse events
+ */
+ mouseDownStatus: {},
+ /**
+ * Event handler for mouse leaving the canvas area
+ * used to send mouse release for any unsent mouse releases
+ */
+ onMouseLeave: function(evt){
+ for(var button in this.mouseDownStatus){
+ if(this.mouseDownStatus[button]){
+ var x = (this.msie > 0 || this.trident > 0) ? evt.event.layerX - evt.event.currentTarget.offsetLeft : evt.event.layerX;
+ var y = (this.msie > 0 || this.trident > 0) ? evt.event.layerY - evt.event.currentTarget.offsetTop : evt.event.layerY;
+ var maxX = $('textareainput').getStyle('width').toInt();
+ var maxY = $('textareainput').getStyle('height').toInt();
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+ if (x > maxX) x = maxX;
+ if (y > maxY) y = maxY;
+ var which = button;
+ if (this.sock.readyState == this.sock.OPEN) {
+ buf = new ArrayBuffer(16);
+ a = new Uint32Array(buf);
+ a[0] = 0; // WSOP_CS_MOUSE
+ a[1] = which;
+ a[2] = x;
+ a[3] = y;
+ this.sock.send(buf);
+ this.mouseDownStatus[which] = false;
+ }
+ }
+ }
+ },
+ /**
+ * Event handler for sending array of keys to be pressed
+ */
+ sendKeys: function(codes) {
+ // var myStringArray = ["Hello","World"];
+ for (var i = 0; i < codes.length; i++) {
+ alert(codes[i]);
+ if(this.sock.readyState == this.sock.OPEN) {
+ buf = new ArrayBuffer(12);
+
+ }
+ }
+ },
+ /**
+ * Sends a unicode char or string
+ */
+ SendUnicodeString: function(str){
+ var len = str.length;
+ buf = new ArrayBuffer(4 * len + 4);
+ a = new Uint32Array(buf);
+ a[0] = 5; // WSOP_CS_UNICODE
+ for(var i = 0; i<len; i++){
+ a[i+1] = str.charCodeAt(i);
+ }
+ this.sock.send(buf);
+ },
+ /**
+ * Sends a scancode key event
+ * down = 1
+ * up = 0
+ */
+ SendKeyUpDown: function(key, upDown){
+ if (this.sock.readyState == this.sock.OPEN) {
+ buf = new ArrayBuffer(12);
+ a = new Uint32Array(buf);
+ a[0] = 1; // WSOP_CS_KUPDOWN
+ a[1] = upDown;
+ a[2] = key;
+ this.sock.send(buf);
+ }
+ },
+ KeyDownEvent: function(evt){
+ if(!this.useIME){
+ this.SendKeyUpDown(evt.code, 1);
+ }else{
+ if(evt.code==229||evt.code==0)this.IMEon=true;
+ //send key presses only when IME is off
+ if(!this.IMEon){
+ if(this.FunctionalKey(evt.code)){
+ if(evt.preventDefault) evt.preventDefault();
+ if(evt.stopPropagation) evt.stopPropagation();
+ this.SendKeyUpDown(evt.code, 1);
+ }
+ }
+ }
+ },
+ KeyUpEvent: function(evt){
+ if(!this.useIME){
+ this.SendKeyUpDown(evt.code, 0);
+ this.textAreaInput.set("value","");
+ }else{
+ if(!this.IMEon)
+ if(this.FunctionalKey(evt.code)){
+ if(evt.preventDefault) evt.preventDefault();
+ if(evt.stopPropagation) evt.stopPropagation();
+ this.SendKeyUpDown(evt.code, 0);
+ }
+ this.DumpTextArea(evt.code);
+ }
+ },
+ /**
+ * Sends unicode to the server
+ */
+ KeyPressEvent: function(evt){
+ if(this.useIME)
+ if(!this.IMEon){
+ this.SendUnicodeString(String.fromCharCode(evt.code));
+ $('textareainput').set("value","");
+ if(evt.preventDefault) evt.preventDefault();
+ if(evt.stopPropagation) evt.stopPropagation();
+ }
+ this.DumpTextArea(evt.code);
+ },
+ /**
+ * Event handler for key down events
+ */
+ onKd: function(evt) {
+ var a, buf;
+ this.log.debug('kD code: ', evt.code, ' ', evt);
+ evt.preventDefault();
+ // this.log.debug('kD code: ', evt.code, ' ', evt);
+ if (this.sock.readyState == this.sock.OPEN) {
+ buf = new ArrayBuffer(12);
+ a = new Uint32Array(buf);
+ a[0] = 1; // WSOP_CS_KUPDOWN
+ a[1] = 1; // down
+ a[2] = evt.code;
+ this.sock.send(buf);
+ }
+ },
+ /**
+ * Event handler for key up events
+ */
+ onKu: function(evt) {
+ var a, buf;
+ evt.preventDefault();
+ this.log.debug('ku code: ', evt.code, ' ', evt);
+ if (this.sock.readyState == this.sock.OPEN) {
+ buf = new ArrayBuffer(12);
+ a = new Uint32Array(buf);
+ a[0] = 1; // WSOP_CS_KUPDOWN
+ a[1] = 0; // up
+ a[2] = evt.code;
+ this.sock.send(buf);
+ }
+ },
+ /**
+ * Event handler for virtual keyboard
+ */
+ onKv: function(evt) {
+ var a, buf;
+ if (this.sock.readyState == this.sock.OPEN) {
+ // this.log.debug('kP code: ', evt.code);
+ buf = new ArrayBuffer(12);
+ a = new Uint32Array(buf);
+ if (evt.special) {
+ a[0] = 1; // WSOP_CS_KUPDOWN
+ a[1] = 1; // down
+ a[2] = evt.code;
+ this.sock.send(buf);
+ a[0] = 1; // WSOP_CS_KUPDOWN
+ a[1] = 0; // up
+ a[2] = evt.code;
+ } else {
+ a[0] = 2; // WSOP_CS_KPRESS
+ a[1] = (evt.shift ? 1 : 0)|(evt.control ? 2 : 0)|(evt.alt ? 4 : 0)|(evt.meta ? 8 : 0);
+ a[2] = evt.code;
+ }
+ this.sock.send(buf);
+ }
+ },
+ /**
+ * Event handler for key pressed events
+ Obsv: not used anymore. Will be removed after checking it's dependants.
+ */
+ onKp: function(evt) {
+ return;
+ },
+ /**
+ * Event handler for WebSocket RX events
+ */
+ onWSmsg: function(evt) {
+ //hide the loading image when the actual streaming starts
+ if ($('dvLoading').getStyle("visibility") !== "hidden") {
+ $('dvLoading').setStyles({ 'visibility': 'hidden' });
+ }
+ switch (typeof(evt.data)) {
+ // We use text messages for alerts and debugging ...
+ case 'string':
+ // this.log.debug(evt.data);
+ switch (evt.data.substr(0,2)) {
+ case "T:":
+ this._reset();
+ break;
+ case "0:":
+ //handle control message here
+ break;
+ case "E:":
+ var msg = evt.data.substring(2);
+ if(msg.substring(0, 2)=='E:'){
+ embedded = true;
+ msg = msg.substring(2);
+ }
+ this.log.err(msg);
+ this.fireEvent('alert', msg);
+ this._reset();
+ break;
+ case 'I:':
+ this.log.info(evt.data.substring(2));
+ break;
+ case 'W:':
+ this.log.warn(evt.data.substring(2));
+ break;
+ case 'D:':
+ this.log.debug(evt.data.substring(2));
+ break;
+ case 'S:':
+ this.sid = evt.data.substring(2);
+ break;
+ case 'R:':
+ //resolution changed
+ resolution=evt.data.substr(2).split('x');
+ $('screen').width=resolution[0];
+ $('screen').height=resolution[1];
+ this.bstore.width=resolution[0];
+ this.bstore.height=resolution[1];
+ $('textareainput').setStyle('width', resolution[0]+'px');
+ $('textareainput').setStyle('height', resolution[1]+'px');
+ break;
+ case 'C:':
+ var msg = evt.data.substr(2);
+ if(msg.substr(0, 2) == 'E:'){
+ msg = msg.substr(2);
+ embedded = true;
+ }
+ if(msg == "RDP session connection started."){
+ //the connection worked so we can set the cookies
+ //settingsSet();
+ return;
+ }
+ break;
+ }
+ break;
+ // ... and binary messages for the actual RDP stuff.
+ case 'object':
+ this.log.debug('wsin', evt.data);
+ this._pmsg(evt.data);
+ break;
+ }
+
+ },
+ /**
+ * Event handler for WebSocket connect events
+ */
+ onWSopen: function(evt) {
+ this.open = true;
+ this.log.setWS(this.sock);
+ //add the textarea on top of the canvas
+ this.SetupCanvas();
+ this.SetupDragnDrop();
+ // Add listeners for the various input events
+ this.textAreaInput.addEvent('mousemove', this.onMm.bind(this));
+ this.textAreaInput.addEvent('mousedown', this.onMd.bind(this));
+ this.textAreaInput.addEvent('mouseup', this.onMu.bind(this));
+ this.textAreaInput.addEvent('mousewheel', this.onMw.bind(this));
+ this.textAreaInput.addEvent('mouseleave', this.onMouseLeave.bind(this));
+ // Disable the browser's context menu
+ this.textAreaInput.addEvent('contextmenu', function(e) {e.stop();});
+ // For touch devices
+ if (this.uT) {
+ this.textAreaInput.addEvent('touchstart', this.onTs.bind(this));
+ this.textAreaInput.addEvent('touchend', this.onTe.bind(this));
+ this.textAreaInput.addEvent('touchmove', this.onTm.bind(this));
+ }
+ if (!this.cssC) {
+ // Same events on pointer image
+ this.cI.addEvent('mousemove', this.onMm.bind(this));
+ this.cI.addEvent('mousedown', this.onMd.bind(this));
+ this.cI.addEvent('mouseup', this.onMu.bind(this));
+ this.cI.addEvent('mousewheel', this.onMw.bind(this));
+ this.cI.addEvent('contextmenu', function(e) {e.stop();});
+ if (this.uT) {
+ this.cI.addEvent('touchstart', this.onTs.bind(this));
+ this.cI.addEvent('touchend', this.onTe.bind(this));
+ this.cI.addEvent('touchmove', this.onTm.bind(this));
+ }
+ }
+ this.fireEvent('connected');
+ this.SendCredentials();
+ },
+ /**
+ * Event handler for WebSocket disconnect events
+ */
+ onWSclose: function(evt) {
+ /*if (Browser.name == 'chrome') {
+ // Current chrome is buggy in that it does not
+ // fire WebSockets error events, so we use the
+ // wasClean flag in the close event.
+ if ((!evt.wasClean) && (!this.open)) {
+ this.fireEvent('alert', 'Could not connect to WebSockets gateway');
+ }
+ }*/
+ //this.open = false;
+ this._reset();
+ },
+ /**
+ * Event handler for WebSocket error events
+ */
+ onWSerr: function (evt) {
+ this.open = false;
+ switch (this.sock.readyState) {
+ case this.sock.CONNECTING:
+ this.fireEvent('alert', 'Could not connect to WebSockets gateway');
+ break;
+ }
+ this._reset();
+ },
+ /**
+ * Convert a color value contained in an uint8 array into an rgba expression
+ * that can be used to parameterize the canvas.
+ */
+ _c2s: function(c) {
+ return 'rgba' + '(' + c[0] + ',' + c[1] + ',' + c[2] + ',' + ((0.0 + c[3]) / 255) + ')';
+ },
+ /**
+ * Save the canvas state and remember this in our object.
+ */
+ _ctxS: function() {
+ this.cctx.save();
+ this.ccnt += 1;
+ },
+ /**
+ * Restore the canvas state and remember this in our object.
+ */
+ _ctxR: function() {
+ this.cctx.restore();
+ this.ccnt -= 1;
+ },
+ /**
+ * Convert the button information of a mouse event into
+ * RDP-like flags.
+ */
+ _mB: function(evt) {
+ if (this.aMF) {
+ return this.aMF;
+ }
+ var bidx;
+ if ('event' in evt && 'button' in evt.event) {
+ bidx = evt.event.button;
+ } else {
+ bidx = evt.rightClick ? 2 : 0;
+ }
+ switch (bidx) {
+ case 0:
+ return 0x1000; // left button
+ case 1:
+ return 0x4000; // middle button
+ case 2:
+ return 0x2000; // right button
+ }
+ return 0x1000;
+ },
+ SetArtificialMouseFlags: function(mf) {
+ if (null == mf) {
+ this.aMF = 0;
+ return;
+ }
+ this.aMF = 0x1000; // left button
+ if (mf.r) {
+ this.aMF = 0x2000; // right
+ }
+ if (mf.m) {
+ this.aMF = 0x4000; // middle
+ }
+ }
+});
+webrdp.copyRGBA = function(inA, inI, outA, outI) {
+ if ('subarray' in inA) {
+ outA.set(inA.subarray(inI, inI + 4), outI);
+ } else {
+ outA[outI++] = inA[inI++];
+ outA[outI++] = inA[inI++];
+ outA[outI++] = inA[inI++];
+ outA[outI] = inA[inI];
+ }
+}
+webrdp.xorbufRGBAPel16 = function(inA, inI, outA, outI, pel) {
+ var pelR = (pel & 0xF800) >> 11;
+ var pelG = (pel & 0x7E0) >> 5;
+ var pelB = pel & 0x1F;
+ // 656 -> 888
+ pelR = (pelR << 3 & ~0x7) | (pelR >> 2);
+ pelG = (pelG << 2 & ~0x3) | (pelG >> 4);
+ pelB = (pelB << 3 & ~0x7) | (pelB >> 2);
+
+ outA[outI++] = inA[inI++] ^ pelR;
+ outA[outI++] = inA[inI++] ^ pelG;
+ outA[outI++] = inA[inI] ^ pelB;
+ outA[outI] = 255; // alpha
+}
+webrdp.buf2RGBA = function(inA, inI, outA, outI) {
+ var pel = inA[inI] | (inA[inI + 1] << 8);
+ var pelR = (pel & 0xF800) >> 11;
+ var pelG = (pel & 0x7E0) >> 5;
+ var pelB = pel & 0x1F;
+ // 656 -> 888
+ pelR = (pelR << 3 & ~0x7) | (pelR >> 2);
+ pelG = (pelG << 2 & ~0x3) | (pelG >> 4);
+ pelB = (pelB << 3 & ~0x7) | (pelB >> 2);
+
+ outA[outI++] = pelR;
+ outA[outI++] = pelG;
+ outA[outI++] = pelB;
+ outA[outI] = 255; // alpha
+}
+webrdp.pel2RGBA = function (pel, outA, outI) {
+ var pelR = (pel & 0xF800) >> 11;
+ var pelG = (pel & 0x7E0) >> 5;
+ var pelB = pel & 0x1F;
+ // 656 -> 888
+ pelR = (pelR << 3 & ~0x7) | (pelR >> 2);
+ pelG = (pelG << 2 & ~0x3) | (pelG >> 4);
+ pelB = (pelB << 3 & ~0x7) | (pelB >> 2);
+
+ outA[outI++] = pelR;
+ outA[outI++] = pelG;
+ outA[outI++] = pelB;
+ outA[outI] = 255; // alpha
+}
+
+webrdp.flipV = function(inA, width, height) {
+ var sll = width * 4;
+ var half = height / 2;
+ var lbot = sll * (height - 1);
+ var ltop = 0;
+ var tmp = new Uint8Array(sll);
+ var i, j;
+ if ('subarray' in inA) {
+ for (i = 0; i < half ; ++i) {
+ tmp.set(inA.subarray(ltop, ltop + sll));
+ inA.set(inA.subarray(lbot, lbot + sll), ltop);
+ inA.set(tmp, lbot);
+ ltop += sll;
+ lbot -= sll;
+ }
+ } else {
+ for (i = 0; i < half ; ++i) {
+ for (j = 0; j < sll; ++j) {
+ tmp[j] = inA[ltop + j];
+ inA[ltop + j] = inA[lbot + j];
+ inA[lbot + j] = tmp[j];
+ }
+ ltop += sll;
+ lbot -= sll;
+ }
+ }
+}
+
+webrdp.dRGB162RGBA = function(inA, inLength, outA) {
+ var inI = 0;
+ var outI = 0;
+ while (inI < inLength) {
+ webrdp.buf2RGBA(inA, inI, outA, outI);
+ inI += 2;
+ outI += 4;
+ }
+}
+
+webrdp.ExtractCodeId = function(bOrderHdr) {
+ var code;
+ switch (bOrderHdr) {
+ case 0xF0:
+ case 0xF1:
+ case 0xF6:
+ case 0xF8:
+ case 0xF3:
+ case 0xF2:
+ case 0xF7:
+ case 0xF4:
+ case 0xF9:
+ case 0xFA:
+ case 0xFD:
+ case 0xFE:
+ return bOrderHdr;
+ }
+ code = bOrderHdr >> 5;
+ switch (code) {
+ case 0x00:
+ case 0x01:
+ case 0x03:
+ case 0x02:
+ case 0x04:
+ return code;
+ }
+ return bOrderHdr >> 4;
+}
+webrdp.ExtractRunLength = function(code, inA, inI, advance) {
+ var runLength = 0;
+ var ladvance = 1;
+ switch (code) {
+ case 0x02:
+ runLength = inA[inI] & 0x1F;
+ if (0 == runLength) {
+ runLength = inA[inI + 1] + 1;
+ ladvance += 1;
+ } else {
+ runLength *= 8;
+ }
+ break;
+ case 0x0D:
+ runLength = inA[inI] & 0x0F;
+ if (0 == runLength) {
+ runLength = inA[inI + 1] + 1;
+ ladvance += 1;
+ } else {
+ runLength *= 8;
+ }
+ break;
+ case 0x00:
+ case 0x01:
+ case 0x03:
+ case 0x04:
+ runLength = inA[inI] & 0x1F;
+ if (0 == runLength) {
+ runLength = inA[inI + 1] + 32;
+ ladvance += 1;
+ }
+ break;
+ case 0x0C:
+ case 0x0E:
+ runLength = inA[inI] & 0x0F;
+ if (0 == runLength) {
+ runLength = inA[inI + 1] + 16;
+ ladvance += 1;
+ }
+ break;
+ case 0xF0:
+ case 0xF1:
+ case 0xF6:
+ case 0xF8:
+ case 0xF3:
+ case 0xF2:
+ case 0xF7:
+ case 0xF4:
+ runLength = inA[inI + 1] | (inA[inI + 2] << 8);
+ ladvance += 2;
+ break;
+ }
+ advance.val = ladvance;
+ return runLength;
+}
+
+webrdp.WriteFgBgImage16toRGBA = function(outA, outI, rowDelta, bitmask, fgPel, cBits) {
+ var cmpMask = 0x01;
+
+ while (cBits-- > 0) {
+ if (bitmask & cmpMask) {
+ webrdp.xorbufRGBAPel16(outA, outI - rowDelta, outA, outI, fgPel);
+ } else {
+ webrdp.copyRGBA(outA, outI - rowDelta, outA, outI);
+ }
+ outI += 4;
+ cmpMask <<= 1;
+ }
+ return outI;
+}
+
+webrdp.WriteFirstLineFgBgImage16toRGBA = function(outA, outI, bitmask, fgPel, cBits) {
+ var cmpMask = 0x01;
+
+ while (cBits-- > 0) {
+ if (bitmask & cmpMask) {
+ webrdp.pel2RGBA(fgPel, outA, outI);
+ } else {
+ webrdp.pel2RGBA(0, outA, outI);
+ }
+ outI += 4;
+ cmpMask <<= 1;
+ }
+ return outI;
+}
+
+webrdp.dRLE16_RGBA = function(inA, inLength, width, outA) {
+ var runLength;
+ var code, pixelA, pixelB, bitmask;
+ var inI = 0;
+ var outI = 0;
+ var fInsertFgPel = false;
+ var fFirstLine = true;
+ var fgPel = 0xFFFFFF;
+ var rowDelta = width * 4;
+ var advance = {val: 0};
+
+ while (inI < inLength) {
+ if (fFirstLine) {
+ if (outI >= rowDelta) {
+ fFirstLine = false;
+ fInsertFgPel = false;
+ }
+ }
+ code = webrdp.ExtractCodeId(inA[inI]);
+ if (code == 0x00 || code == 0xF0) {
+ runLength = webrdp.ExtractRunLength(code, inA, inI, advance);
+ inI += advance.val;
+ if (fFirstLine) {
+ if (fInsertFgPel) {
+ webrdp.pel2RGBA(fgPel, outA, outI);
+ outI += 4;
+ runLength -= 1;
+ }
+ while (runLength > 0) {
+ webrdp.pel2RGBA(0, outA, outI);
+ runLength -= 1;
+ outI += 4;
+ }
+ } else {
+ if (fInsertFgPel) {
+ webrdp.xorbufRGBAPel16(outA, outI - rowDelta, outA, outI, fgPel);
+ outI += 4;
+ runLength -= 1;
+ }
+ while (runLength > 0) {
+ webrdp.copyRGBA(outA, outI - rowDelta, outA, outI);
+ runLength -= 1;
+ outI += 4;
+ }
+ }
+ fInsertFgPel = true;
+ continue;
+ }
+ fInsertFgPel = false;
+ switch (code) {
+ case 0x01:
+ case 0xF1:
+ case 0x0C:
+ case 0xF6:
+ runLength = webrdp.ExtractRunLength(code, inA, inI, advance);
+ inI += advance.val;
+ if (code == 0x0C || code == 0xF6) {
+ fgPel = inA[inI] | (inA[inI + 1] << 8);
+ inI += 2;
+ }
+ if (fFirstLine) {
+ while (runLength > 0) {
+ webrdp.pel2RGBA(fgPel, outA, outI);
+ runLength -= 1;
+ outI += 4;
+ }
+ } else {
+ while (runLength > 0) {
+ webrdp.xorbufRGBAPel16(outA, outI - rowDelta, outA, outI, fgPel);
+ runLength -= 1;
+ outI += 4;
+ }
+ }
+ break;
+ case 0x0E:
+ case 0xF8:
+ runLength = webrdp.ExtractRunLength(code, inA, inI, advance);
+ inI += advance.val;
+ pixelA = inA[inI] | (inA[inI + 1] << 8);
+ inI += 2;
+ pixelB = inA[inI] | (inA[inI + 1] << 8);
+ inI += 2;
+ while (runLength > 0) {
+ webrdp.pel2RGBA(pixelA, outA, outI);
+ outI += 4;
+ webrdp.pel2RGBA(pixelB, outA, outI);
+ outI += 4;
+ runLength -= 1;
+ }
+ break;
+ case 0x03:
+ case 0xF3:
+ runLength = webrdp.ExtractRunLength(code, inA, inI, advance);
+ inI += advance.val;
+ pixelA = inA[inI] | (inA[inI + 1] << 8);
+ inI += 2;
+ while (runLength > 0) {
+ webrdp.pel2RGBA(pixelA, outA, outI);
+ outI += 4;
+ runLength -= 1;
+ }
+ break;
+ case 0x02:
+ case 0xF2:
+ case 0x0D:
+ case 0xF7:
+ runLength = webrdp.ExtractRunLength(code, inA, inI, advance);
+ inI += advance.val;
+ if (code == 0x0D || code == 0xF7) {
+ fgPel = inA[inI] | (inA[inI + 1] << 8);
+ inI += 2;
+ }
+ if (fFirstLine) {
+ while (runLength >= 8) {
+ bitmask = inA[inI++];
+ outI = webrdp.WriteFirstLineFgBgImage16toRGBA(outA, outI, bitmask, fgPel, 8);
+ runLength -= 8;
+ }
+ } else {
+ while (runLength >= 8) {
+ bitmask = inA[inI++];
+ outI = webrdp.WriteFgBgImage16toRGBA(outA, outI, rowDelta, bitmask, fgPel, 8);
+ runLength -= 8;
+ }
+ }
+ if (runLength > 0) {
+ bitmask = inA[inI++];
+ if (fFirstLine) {
+ outI = webrdp.WriteFirstLineFgBgImage16toRGBA(outA, outI, bitmask, fgPel, runLength);
+ } else {
+ outI = webrdp.WriteFgBgImage16toRGBA(outA, outI, rowDelta, bitmask, fgPel, runLength);
+ }
+ }
+ break;
+ case 0x04:
+ case 0xF4:
+ runLength = webrdp.ExtractRunLength(code, inA, inI, advance);
+ inI += advance.val;
+ while (runLength > 0) {
+ webrdp.pel2RGBA(inA[inI] | (inA[inI + 1] << 8), outA, outI);
+ inI += 2;
+ outI += 4;
+ runLength -= 1;
+ }
+ break;
+ case 0xF9:
+ inI += 1;
+ if (fFirstLine) {
+ outI = webrdp.WriteFirstLineFgBgImage16toRGBA(outA, outI, 0x03, fgPel, 8);
+ } else {
+ outI = webrdp.WriteFgBgImage16toRGBA(outA, outI, rowDelta, 0x03, fgPel, 8);
+ }
+ break;
+ case 0xFA:
+ inI += 1;
+ if (fFirstLine) {
+ outI = webrdp.WriteFirstLineFgBgImage16toRGBA(outA, outI, 0x05, fgPel, 8);
+ } else {
+ outI = webrdp.WriteFgBgImage16toRGBA(outA, outI, rowDelta, 0x05, fgPel, 8);
+ }
+ break;
+ case 0xFD:
+ inI += 1;
+ webrdp.pel2RGBA(0xFFFF, outA, outI);
+ outI += 4;
+ break;
+ case 0xFE:
+ inI += 1;
+ webrdp.pel2RGBA(0, outA, outI);
+ outI += 4;
+ break;
+ }
+ }
+}
+/**
+ * Continuos refresh for the IMEhelper
+ */
+function refreshIMEhelper(){
+ setTimeout(this.refreshIMEhelper,20);
+ //IME helper div
+ if(rdp.IMEon){
+ $('IMEhelper').setStyle('visibility','visible');
+ $('IMEhelper').set('html',$('textareainput').get('value'));
+ }else{
+ $('IMEhelper').setStyle('visibility','hidden');
+ }
+}
diff --git a/www/js/webrdp-ext.js b/www/js/webrdp-ext.js
new file mode 100644
index 0000000..9a96b23
--- /dev/null
+++ b/www/js/webrdp-ext.js
@@ -0,0 +1,19 @@
+/** BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, smake <smake at ya dot ru>.
+ *
+ */
+
+ArrayBuffer.prototype.toHexdump = function () {
+ return new Uint8Array(this).reduce(
+ (p, c, i, a) => p + (
+ i % 16 === 0 ? i.toString(16).padStart(6, 0) + ' ' : ' '
+ ) + c.toString(16).padStart(2, 0) + (
+ i === a.length - 1 || i % 16 === 15 ?
+ ' '.repeat((15 - i % 16) * 3) + Array.from(a).splice(i - i % 16, 16).reduce((r, v) => r + (
+ v > 31 && v < 127 || v > 159 ? String.fromCharCode(v) : '.'
+ ), ' ') + '\n' : ''
+ ), ''
+ );
+
+}
diff --git a/www/js/webrdp-log.js b/www/js/webrdp-log.js
new file mode 100644
index 0000000..62f17f6
--- /dev/null
+++ b/www/js/webrdp-log.js
@@ -0,0 +1,147 @@
+/** BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, smake <smake at ya dot ru>.
+ *
+ */
+
+webrdp.o2s = function(obj, depth) {
+ depth = depth || [];
+ if (depth.contains(obj)) {
+ return '{SELF}';
+ }
+ switch (typeof(obj)) {
+ case 'undefined':
+ return 'undefined';
+ case 'string':
+ return '"' + obj.replace(/[\x00-\x1f\\"]/g, escape) + '"';
+ case 'array':
+ var string = [];
+ depth.push(obj);
+ for (var i = 0; i < obj.length; ++i) {
+ string.push(webrdp.o2s(obj[i], depth));
+ }
+ depth.pop();
+ return '[' + string + ']';
+ case 'object':
+ case 'hash':
+ var string = [];
+ depth.push(obj);
+ var isE = (obj instanceof UIEvent);
+ Object.each(obj, function(v, k) {
+ if (v instanceof HTMLElement) {
+ string.push(k + '={HTMLElement}');
+ } else if (isE && (('layerX' == k) || ('layerY' == k) ('view' == k))) {
+ string.push(k + '=!0');
+ } else {
+ try {
+ var vstr = webrdp.o2s(v, depth);
+ if (vstr) {
+ string.push(k + '=' + vstr);
+ }
+ } catch (error) {
+ string.push(k + '=??E??');
+ }
+ }
+ });
+ depth.pop();
+ return '{' + string + '}';
+ case 'number':
+ case 'boolean':
+ return '' + obj;
+ case 'null':
+ return 'null';
+ }
+ return null;
+};
+
+webrdp.Log = new Class({
+ initialize: function() {
+ this.ws = null;
+ this.history = [];
+ this.loglevel = null;
+ this.debuglevel = 0;
+ },
+ _p: function(pfx, a) {
+ var line = '';
+ var i;
+ for (i = 0; i < a.length; ++i) {
+ switch (typeof(a[i])) {
+ case 'string':
+ case 'number':
+ case 'boolean':
+ case 'null':
+ line += a[i] + ' ';
+ break;
+ default:
+ line += webrdp.o2s(a[i]) + ' ';
+ break;
+ }
+ }
+ if (0 < line.length) {
+ this.ws.send(pfx + line);
+ }
+ },
+ drop: function() {
+ },
+ debug: function() {
+ if (this.loglevel == "debug") {
+ var entry = {
+ "date": new Date(),
+ "logtype": arguments.callee.name,
+ "direction": arguments[0],
+ "data": arguments[1]
+ }
+ this.history.push(entry);
+ }
+ },
+ info: function() {
+ if (this.ws) {
+ var a = Array.prototype.slice.call(arguments);
+ a.unshift('I:');
+ this._p.apply(this, a);
+ }
+ },
+ warn: function() {
+ if (this.ws) {
+ var a = Array.prototype.slice.call(arguments);
+ a.unshift('W:');
+ this._p.apply(this, a);
+ }
+ },
+ err: function() {
+ if (this.ws) {
+ var a = Array.prototype.slice.call(arguments);
+ a.unshift('E:');
+ this._p.apply(this, a);
+ }
+ },
+ show: function(count) {
+ if (!count) {
+ var count = 30;
+ }
+ var first = this.history.length - count > 0 ? this.history.length - count : 0;
+ for (var i = first; i < this.history.length; i++) {
+ var h = this.history[i];
+ var header = '[' + h.date.toLocaleTimeString('ru') + '.' + h.date.getMilliseconds() + ']';
+ header += '[' + h.logtype + '] ' + h.direction + ':';
+ console.log(header);
+ console.hex(h.data);
+ }
+ },
+ setWS: function(_ws) {
+ this.ws = _ws;
+ }
+});
+
+
+
+/**
+ * For debugging
+ */
+
+console.hex = (d) => console.log((Object(d).buffer instanceof ArrayBuffer ? new Uint8Array(d.buffer) :
+typeof d === 'string' ? (new TextEncoder('utf-8')).encode(d) :
+new Uint8ClampedArray(d)).reduce((p, c, i, a) => p + (i % 16 === 0 ? i.toString(16).padStart(6, 0) + ' ' : ' ') +
+c.toString(16).padStart(2, 0) + (i === a.length - 1 || i % 16 === 15 ?
+' '.repeat((15 - i % 16) * 3) + Array.from(a).splice(i - i % 16, 16).reduce((r, v) =>
+r + (v > 31 && v < 127 || v > 159 ? String.fromCharCode(v) : '.'), ' ') + '\n' : ''), ''));