diff --git a/.gitignore b/.gitignore index 5e1f1b3..089ec5d 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ miracle-dhcp miracle-sinkctl miracle-wifictl miracle-wifid +miracle-uibcctl miraclectl miracled stamp-h1 diff --git a/README.md b/README.md index a829c2f..9f91f59 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ See there was interface changes on systemd 219, if you are below that version, u ### Arch linux -Use existing [AUR package](https://aur.archlinux.org/packages/miraclecast/). Remember to enable kdus to systemd-git dependency +Use existing [AUR package](https://aur.archlinux.org/packages/miraclecast/). Remember to enable kdus to systemd-git dependency if you are below 221 systemd. $ export _systemd_git_kdbus=--enable-kdbus @@ -142,7 +142,13 @@ Steps to use it as peer: > psp-scan - 5. Apart from list, or show info with peer <mac> there's nothing useful here by now. + 5. Apart from list, or show info with peer <mac> there's nothing useful here by now. For a Q&D see [Using as peer](https://github.com/albfan/miraclecast/issues/4) + +## UIBC + +> The User Input Back Channel (UIBC) is an optional WFD feature that when implemented facilitates communication of user inputs to a User Interface, present at the WFD Sink, to the WFD Source. + +To use it just add `--uibc` on `miracle-sinkctl` startup. Single mouse events and key events are implemented. ## Autocompletion diff --git a/configure.ac b/configure.ac index 97ba376..a2f3738 100644 --- a/configure.ac +++ b/configure.ac @@ -35,6 +35,7 @@ AC_PROG_AWK LT_PREREQ(2.2) LT_INIT +LT_LIB_M AC_ARG_ENABLE([rely-udev], AS_HELP_STRING([--enable-rely-udev], [Use tagged device with miraclecast]), AC_DEFINE([RELY_UDEV], [], [Rely on udev to find miraclecast device])) @@ -70,6 +71,7 @@ AC_CONFIG_FILES([Makefile src/dhcp/Makefile src/shared/Makefile src/wifi/Makefile + src/uibc/Makefile test/Makefile]) AC_OUTPUT diff --git a/res/CMakeLists.txt b/res/CMakeLists.txt index 2065690..da37a71 100644 --- a/res/CMakeLists.txt +++ b/res/CMakeLists.txt @@ -1,7 +1,7 @@ ########### install files ############### -install(FILES miracle-gst.sh DESTINATION bin) +install(FILES miracle-gst gstplayer DESTINATION bin) diff --git a/res/Makefile.am b/res/Makefile.am index 51f71fb..56eea69 100644 --- a/res/Makefile.am +++ b/res/Makefile.am @@ -1,2 +1,2 @@ -bin_SCRIPTS = miracle-gst.sh +bin_SCRIPTS = miracle-gst gstplayer uibc-viewer EXTRA_DIST = wpa.conf diff --git a/res/gstplayer b/res/gstplayer new file mode 100755 index 0000000..b1d3c08 --- /dev/null +++ b/res/gstplayer @@ -0,0 +1,219 @@ +#!/usr/bin/python3 -u + +import gi +import sys +import argparse + +gi.require_version('Gst', '1.0') +gi.require_version('Gtk', '3.0') +gi.require_version('GstVideo', '1.0') + +from gi.repository import GObject, Gst, Gtk, Gdk, GLib + +# Needed for window.get_xid(), xvimagesink.set_window_handle(), respectively: +from gi.repository import GdkX11, GstVideo + +GObject.threads_init() +Gst.init(None) + +class Player(object): + def __init__(self, **kwargs): + + scale = kwargs.get("scale") + #scale = "1080x1920" + if not scale: + self.width = 800 + self.height = 600 + else: + split = scale.split("x") + self.width = int(split[0]) + self.height = int(split[1]) + + port = kwargs.get("port") + + uri = kwargs.get("uri") + + self.window = Gtk.Window() + self.window.set_name('eco') + self.window.connect('destroy', self.quit) + + title = kwargs.get("title") + if title: + self.window.set_title(title) + + self.window.set_default_size(self.width, self.height) + + self.drawingarea = Gtk.DrawingArea() + self.window.add(self.drawingarea) + + self.drawingarea.set_size_request(self.width,self.height) + self.drawingarea.add_events(Gdk.EventMask.BUTTON_PRESS_MASK|Gdk.EventMask.BUTTON_RELEASE_MASK) + self.drawingarea.connect('button-press-event', self.on_mouse_pressed) + self.drawingarea.connect('button-release-event', self.on_mouse_pressed) + self.drawingarea.add_events(Gdk.EventMask.KEY_PRESS_MASK) + self.window.connect('key-press-event', self.on_key_pressed) + + audio = kwargs.get("audio") + + self.playbin = None + + #Create GStreamer pipeline + if uri is not None: + self.pipeline = Gst.Pipeline() + + # Create GStreamer elements + self.playbin = Gst.ElementFactory.make('playbin', "source") + + if not uri.startswith("http://") or not uri.startswith("http://") or not uri.startswith("file://"): + if not uri.startswith("/"): + import os + uri = os.path.abspath(uri) + uri = "file://"+uri + + # Set properties + self.playbin.set_property('uri', uri) + + + # Add playbin to the pipeline + self.pipeline.add(self.playbin) + else: + gstcommand = "udpsrc port="+str(port)+" caps=\"application/x-rtp, media=video\" ! rtpjitterbuffer latency=100 ! rtpmp2tdepay ! tsdemux " + + if audio: + gstcommand += "name=demuxer demuxer. " + + gstcommand += "! queue max-size-buffers=0 max-size-time=0 ! h264parse ! avdec_h264 ! videoconvert ! " + + if scale: + gstcommand += "videoscale method=1 ! video/x-raw,width="+str(self.width)+",height="+str(self.height)+" ! " + + gstcommand += "autovideosink " + + if audio: + gstcommand += "demuxer. ! queue max-size-buffers=0 max-size-time=0 ! aacparse ! avdec_aac ! audioconvert ! audioresample ! autoaudiosink " + + self.pipeline = Gst.parse_launch(gstcommand) + + + # Create bus to get events from GStreamer pipeline + self.bus = self.pipeline.get_bus() + self.bus.add_signal_watch() + self.bus.connect('message::eos', self.on_eos) + self.bus.connect('message::error', self.on_error) + + # This is needed to make the video output in our DrawingArea: + self.bus.enable_sync_message_emission() + self.bus.connect('sync-message::element', self.on_sync_message) + self.bus.connect('message', self.on_message) + + self.success = False + + def on_message(self, bus, message): + if self.playbin: + videoPad = self.playbin.emit("get-video-pad", 0) + if videoPad and not self.success: + videoPadCapabilities = videoPad.get_current_caps() + if videoPadCapabilities: + (self.success, self.videoWidth) = \ + videoPadCapabilities.get_structure(0).get_int("width") + (self.success, self.videoHeight) = \ + videoPadCapabilities.get_structure(0).get_int("height") + if self.success: + print("{0} {1}".format(self.videoWidth, self.videoHeight)) + self.drawingarea.set_size_request(self.videoWidth, self.videoHeight) + + def on_mouse_pressed(self, widget, event): + #,,,, + if event.type == Gdk.EventType.BUTTON_PRESS: + type = 0 + else: + type = 1 + + width = self.drawingarea.get_allocation().width + height = self.drawingarea.get_allocation().height + half_area_width = width / 2 + half_area_height = height / 2 + + half_def_width = self.width / 2 + half_def_height = self.height / 2 + + min_hor_pos = half_area_width - half_def_width + max_hor_pos = half_area_width + half_def_width + min_ver_pos = half_area_height - half_def_height + max_ver_pos = half_area_height + half_def_height + + pos_event_x = event.x + pos_event_y = event.y + + + #print('{0} {1} {2} {3} {4} {5}'.format(min_hor_pos, pos_event_x, max_hor_pos, min_ver_pos, pos_event_y,max_ver_pos)) + if min_hor_pos <= pos_event_x <= max_hor_pos and min_ver_pos <= pos_event_y <= max_ver_pos: + uibc_x = int(pos_event_x - (half_area_width - half_def_width)) + #print ('{0} {1} {2} {3}'.format(uibc_x, pos_event_x, half_area_width,half_def_width)) + uibc_y = int(pos_event_y - (half_area_height - half_def_height)) + #print ('{0} {1} {2} {3}'.format(uibc_y, pos_event_y, half_area_height,half_def_height)) + print('{0},1,0,{1},{2}'.format(type, uibc_x , uibc_y)) + + def on_key_pressed(self, widget, event): + #print(Gdk.keyval_name(event.keyval)) + print("3,0x%04X,0x0000" % event.keyval) + # print(event.state) + # if event.state & Gdk.ModifierType.LOCK_MASK == Gdk.ModifierType.LOCK_MASK: + # print("caps lock") + # print(Gtk.accelerator_get_label(event.keyval, event.state)) + + def run(self): + self.window.show_all() + # You need to get the XID after window.show_all(). You shouldn't get it + # in the on_sync_message() handler because threading issues will cause + # segfaults there. + self.xid = self.drawingarea.get_property('window').get_xid() + self.pipeline.set_state(Gst.State.PLAYING) + Gtk.main() + + + def quit(self, window): + self.pipeline.set_state(Gst.State.NULL) + Gtk.main_quit() + + def on_sync_message(self, bus, msg): + if msg.get_structure().get_name() == 'prepare-window-handle': + print(self.drawingarea.get_allocation()) + #msg.src.set_property("force-aspect-radio", True) + msg.src.set_window_handle(self.xid) + + def on_eos(self, bus, msg): + print('on_eos(): seeking to start of video') + self.pipeline.seek_simple( + Gst.Format.TIME, + Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, + 0 + ) + + def on_error(self, bus, msg): + print('on_error():', msg.parse_error()) + + +if __name__ == '__main__': + + parser = argparse.ArgumentParser() + parser.add_argument("uri", nargs="?", help="Uri to play") + parser.add_argument("-v", "--version", help="Show package version") + parser.add_argument("--log-level", metavar="lvl", help="Maximum level for log messages") + parser.add_argument("-p", "--port", type=int, default=1991, help="Port for rtsp") + parser.add_argument("-a", "--audio", dest="audio", action="store_true", help="Enable audio support") + parser.add_argument("-s", "--scale", metavar="WxH", help="Scale to resolution") + parser.add_argument("-d", "--debug", help="Debug") + parser.add_argument("--uibc", help="Enable UIBC") + parser.add_argument("--title", help="set player title") + parser.add_argument("--res", metavar="n,n,n", help="Supported resolutions masks (CEA, VESA, HH)") + # res + # " default CEA %08X\n" + # " default VESA %08X\n" + # " default HH %08X\n" + parser.add_argument("-r", "--resolution", help="Resolution") + parser.set_defaults(audio=True) + args = parser.parse_args() + + p = Player(**vars(args)) + p.run() diff --git a/res/miracle-gst b/res/miracle-gst new file mode 100755 index 0000000..9a2a4db --- /dev/null +++ b/res/miracle-gst @@ -0,0 +1,100 @@ +#!/bin/bash + +function help { + local scriptname="$(basename $0)" + cat >&2 <x Scale + -d Log level for gst + -p Port for stream + -a Enables audio + -h Show this help + +Examples: + + # play stream on port 7236 + $ $scriptname -p 7236 + # play stream with resolution 800x600 + $ $scriptname -s 800x600 + # play stream with audio + $ $scriptname -a + # play stream with debug level 3 + $ $scriptname -d 3 + +EOF +} + +DEBUG='0' +AUDIO='0' +SCALE='0' + +while getopts "r:d:as:p:h" optname + do + case "$optname" in + "h") + help + exit 0 + ;; + "d") + DEBUG=`echo ${OPTARG} | tr -d ' '` + ;; + "r") + RESOLUTION=`echo ${OPTARG} | tr -d ' '` + ;; + "a") + AUDIO='1' + ;; + "p") + PORT=`echo ${OPTARG} | tr -d ' '` + ;; + "s") + SCALE='1' + WIDTH=`echo ${OPTARG} | tr -d ' ' | cut -dx -f 1` + HEIGHT=`echo ${OPTARG} | tr -d ' ' | cut -dx -f 2` + ;; + "?") + echo "Unknown option $OPTARG" + ;; + *) + echo "Unknown parameter $OPTARG" + help + exit 1 + ;; + esac + done + +RUN="/usr/bin/gst-launch-1.0 -v " +if [ $DEBUG != '0' ] +then + RUN+="--gst-debug=${DEBUG} " +fi + +RUN+="udpsrc port=$PORT caps=\"application/x-rtp, media=video\" ! rtpjitterbuffer latency=100 ! rtpmp2tdepay ! tsdemux " + +if [ $AUDIO == '1' ] +then + RUN+="name=demuxer demuxer. " +fi + +RUN+="! queue max-size-buffers=0 max-size-time=0 ! h264parse ! avdec_h264 ! videoconvert ! " + +if [ $SCALE == '1' ] +then + RUN+="videoscale method=1 ! video/x-raw,width=${WIDTH},height=${HEIGHT} ! " +fi + +RUN+="autovideosink " + +if [ $AUDIO == '1' ] +then + RUN+="demuxer. ! queue max-size-buffers=0 max-size-time=0 ! aacparse ! avdec_aac ! audioconvert ! audioresample ! autoaudiosink " +fi + +echo "running: $RUN" +exec ${RUN} diff --git a/res/test-viewer.sh b/res/test-viewer.sh index ab1fa99..38bd83a 100755 --- a/res/test-viewer.sh +++ b/res/test-viewer.sh @@ -35,7 +35,7 @@ Try installing packages "gst-plugins-bad, gst-plugins-base, gst-plugins-base-lib If that fails too, try: -$ vlc rtp://@1991 +$ vlc rtp://@:1991 EOF else diff --git a/res/uibc-viewer b/res/uibc-viewer new file mode 100755 index 0000000..40365c9 --- /dev/null +++ b/res/uibc-viewer @@ -0,0 +1,8 @@ +#!/bin/bash + +IP=$1 +shift +UIBC_PORT=$1 +shift + +gstplayer $@ | miracle-uibcctl $IP $UIBC_PORT --daemon diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5b8363c..96fe570 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,9 +1,10 @@ -set(CMAKE_C_FLAGS "-std=gnu11") +set(CMAKE_C_FLAGS "-std=gnu11 ${CMAKE_C_FLAGS}") add_subdirectory(shared) add_subdirectory(wifi) add_subdirectory(dhcp) add_subdirectory(ctl) +add_subdirectory(uibc) set(miracled_SRCS miracled.h miracled.c) add_executable(miracled ${miracled_SRCS}) diff --git a/src/Makefile.am b/src/Makefile.am index f10e22b..3a3ead6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,5 @@ include $(top_srcdir)/common.am -SUBDIRS = shared wifi dhcp ctl +SUBDIRS = shared wifi dhcp ctl uibc bin_PROGRAMS = miracled diff --git a/src/ctl/CMakeLists.txt b/src/ctl/CMakeLists.txt index 727a2d6..d6d98e4 100644 --- a/src/ctl/CMakeLists.txt +++ b/src/ctl/CMakeLists.txt @@ -24,11 +24,11 @@ target_link_libraries(miracle-wifictl miracle-shared) set(miracle-sinkctl_SRCS ctl.h ctl-cli.c + ctl-sink.h ctl-sink.c ctl-wifi.c sinkctl.c wfd.c) - add_executable(miracle-sinkctl ${miracle-sinkctl_SRCS}) diff --git a/src/ctl/Makefile.am b/src/ctl/Makefile.am index 9f88336..f02d052 100644 --- a/src/ctl/Makefile.am +++ b/src/ctl/Makefile.am @@ -17,6 +17,7 @@ miracle_wifictl_LDADD = \ miracle_sinkctl_SOURCES = \ ctl.h \ ctl-cli.c \ + ctl-sink.h \ ctl-sink.c \ ctl-wifi.c \ wfd.c \ diff --git a/src/ctl/ctl-sink.c b/src/ctl/ctl-sink.c index 3a5cf5d..e771f52 100644 --- a/src/ctl/ctl-sink.c +++ b/src/ctl/ctl-sink.c @@ -17,52 +17,8 @@ * along with MiracleCast; If not, see . */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "ctl.h" -#include "rtsp.h" -#include "shl_macro.h" -#include "shl_util.h" -#include "wfd.h" +#include "ctl-sink.h" -struct ctl_sink { - sd_event *event; - - char *target; - char *session; - char *url; - char *uibc_setting; - struct sockaddr_storage addr; - size_t addr_size; - int fd; - sd_event_source *fd_source; - - struct rtsp *rtsp; - - bool connected : 1; - bool hup : 1; - - uint32_t resolutions_cea; - uint32_t resolutions_vesa; - uint32_t resolutions_hh; - - int hres; - int vres; -}; -static const int DEFAULT_UIBC_PORT = 7239; -static const int DEFAULT_RSTP_PORT = 1991; /* * RTSP Session */ @@ -158,7 +114,7 @@ static void sink_handle_get_parameter(struct ctl_sink *s, if (rtsp_message_read(m, "{<>}", "wfd_client_rtp_ports") >= 0) { char wfd_client_rtp_ports[128]; sprintf(wfd_client_rtp_ports, - "wfd_client_rtp_ports: RTP/AVP/UDP;unicast %d 0 mode=play", DEFAULT_RSTP_PORT); + "wfd_client_rtp_ports: RTP/AVP/UDP;unicast %d 0 mode=play", rstp_port); r = rtsp_message_append(rep, "{&}", wfd_client_rtp_ports); if (r < 0) @@ -166,12 +122,12 @@ static void sink_handle_get_parameter(struct ctl_sink *s, } /* wfd_uibc_capability */ - if (rtsp_message_read(m, "{<>}", "wfd_uibc_capability") >= 0) { + if (rtsp_message_read(m, "{<>}", "wfd_uibc_capability") >= 0 && uibc) { char wfd_uibc_capability[512]; sprintf(wfd_uibc_capability, "wfd_uibc_capability: input_category_list=GENERIC;" - "generic_cap_list=Mouse,SingleTouch,Keyboard,Camera;" - "hidc_cap_list=none;port=%d", DEFAULT_UIBC_PORT); + "generic_cap_list=Mouse,SingleTouch;" + "hidc_cap_list=none;port=none"); r = rtsp_message_append(rep, "{&}", wfd_uibc_capability); if (r < 0) return cli_vERR(r); @@ -243,7 +199,7 @@ static int sink_set_format(struct ctl_sink *s, if (hres && vres) { s->hres = hres; s->vres = vres; - ctl_fn_sink_resolution_set(s, hres, vres); + ctl_fn_sink_resolution_set(s); return 0; } } @@ -257,6 +213,7 @@ static void sink_handle_set_parameter(struct ctl_sink *s, _rtsp_message_unref_ struct rtsp_message *rep = NULL; const char *trigger; const char *url; + const char *uibc_config; const char *uibc_setting; char *nu; unsigned int cea_res, vesa_res, hh_res; @@ -290,6 +247,30 @@ static void sink_handle_set_parameter(struct ctl_sink *s, } } + /* M4 (or any other) can pass presentation URLs */ + r = rtsp_message_read(m, "{}", "wfd_uibc_capability", &uibc_config); + if (r >= 0) { + if (!s->uibc_config || strcmp(s->uibc_config, uibc_config)) { + nu = strdup(uibc_config); + if (!nu) + return cli_vENOMEM(); + + free(s->uibc_config); + s->uibc_config = nu; + + char* token = strtok(uibc_config, ";"); + + while (token) { + if (sscanf(token, "port=%d", &uibc_port)) { + break; + } + token = strtok(0, ";"); + } + + cli_debug("Got URL: %s\n", s->url); + } + } + /* M4 (or any other) can pass presentation URLs */ r = rtsp_message_read(m, "{}", "wfd_uibc_setting", &uibc_setting); if (r >= 0) { @@ -330,9 +311,9 @@ static void sink_handle_set_parameter(struct ctl_sink *s, if (r < 0) return cli_vERR(r); - r = rtsp_message_append(rep, "", - "Transport", - "RTP/AVP/UDP;unicast;client_port=1991"); + char rtsp_setup[128]; + sprintf(rtsp_setup, "RTP/AVP/UDP;unicast;client_port=%d", rstp_port); + r = rtsp_message_append(rep, "", "Transport", rtsp_setup); if (r < 0) return cli_vERR(r); diff --git a/src/ctl/ctl-sink.h b/src/ctl/ctl-sink.h new file mode 100644 index 0000000..69d0532 --- /dev/null +++ b/src/ctl/ctl-sink.h @@ -0,0 +1,72 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * MiracleCast is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * MiracleCast is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with MiracleCast; If not, see . + */ + +#ifndef CTL_SINK_H +#define CTL_SINK_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ctl.h" + +#include "rtsp.h" +#include "shl_macro.h" +#include "shl_util.h" +#include "wfd.h" + +extern int rstp_port; +extern bool uibc; +extern int uibc_port; + +struct ctl_sink { + sd_event *event; + + char *target; + char *session; + char *url; + char *uibc_config; + char *uibc_setting; + struct sockaddr_storage addr; + size_t addr_size; + int fd; + sd_event_source *fd_source; + + struct rtsp *rtsp; + + bool connected : 1; + bool hup : 1; + + uint32_t resolutions_cea; + uint32_t resolutions_vesa; + uint32_t resolutions_hh; + + int hres; + int vres; +}; + +#endif /* CTL_SINK_H */ diff --git a/src/ctl/ctl.h b/src/ctl/ctl.h index 8062f0c..077cc49 100644 --- a/src/ctl/ctl.h +++ b/src/ctl/ctl.h @@ -241,7 +241,7 @@ void ctl_fn_link_free(struct ctl_link *l); void ctl_fn_sink_connected(struct ctl_sink *s); void ctl_fn_sink_disconnected(struct ctl_sink *s); -void ctl_fn_sink_resolution_set(struct ctl_sink *s, int hres, int vres); +void ctl_fn_sink_resolution_set(struct ctl_sink *s); void cli_fn_help(void); diff --git a/src/ctl/sinkctl.c b/src/ctl/sinkctl.c index e71ea7c..e594a50 100644 --- a/src/ctl/sinkctl.c +++ b/src/ctl/sinkctl.c @@ -34,6 +34,7 @@ #include #include #include "ctl.h" +#include "ctl-sink.h" #include "wfd.h" #include "shl_macro.h" #include "shl_util.h" @@ -53,8 +54,15 @@ static struct ctl_link *running_link; static struct ctl_peer *running_peer; static struct ctl_peer *pending_peer; +void launch_player(struct ctl_sink *s); + char *gst_scale_res; int gst_audio_en = 1; +static const int DEFAULT_RSTP_PORT = 1991; +bool uibc; +int rstp_port; +int uibc_port; + unsigned int wfd_supported_res_cea = 0x0000001f; /* up to 720x576 */ unsigned int wfd_supported_res_vesa = 0x00000003; /* up to 800x600 */ unsigned int wfd_supported_res_hh = 0x00000000; /* not supported */ @@ -335,12 +343,10 @@ static const struct cli_cmd cli_cmds[] = { { }, }; -static void spawn_gst(int hres, int vres) +static void spawn_gst(struct ctl_sink *s) { - char *argv[64]; - char resolution[64]; pid_t pid; - int fd_journal, i; + int fd_journal; sigset_t mask; if (sink_pid > 0) @@ -357,7 +363,7 @@ static void spawn_gst(int hres, int vres) /* redirect stdout/stderr to journal */ fd_journal = sd_journal_stream_fd("miracle-sinkctl-gst", - LOG_INFO, + LOG_DEBUG, false); if (fd_journal >= 0) { /* dup journal-fd to stdout and stderr */ @@ -368,8 +374,34 @@ static void spawn_gst(int hres, int vres) dup2(2, 1); } - i = 0; - argv[i++] = (char*) "/miracle-gst.sh"; + launch_player(s); + _exit(1); + } else { + sink_pid = pid; + } +} + +void launch_player(struct ctl_sink *s) { + char *argv[64]; + char resolution[64]; + char port[64]; + char uibc_portStr[64]; + int i = 0; + char* player; + if (uibc) { +// player = "gstplayer"; + player = "uibc-viewer"; + } else { + player = "miracle-gst"; + } + + argv[i++] = player; + if (uibc) { + argv[i++] = s->target; + sprintf(uibc_portStr, "%d", uibc_port); + argv[i++] = uibc_portStr; +// argv[i++] = "--daemon"; + } if (cli_max_sev >= 7) argv[i++] = "-d 3"; if (gst_audio_en) @@ -378,18 +410,55 @@ static void spawn_gst(int hres, int vres) argv[i++] = "-s"; argv[i++] = gst_scale_res; } - if (hres && vres) { - sprintf(resolution, "%dx%d", hres, vres); + argv[i++] = "-p"; + sprintf(port, "%d", rstp_port); + argv[i++] = port; + + if (s->hres && s->vres) { + sprintf(resolution, "%dx%d", s->hres, s->vres); argv[i++] = "-r"; argv[i++] = resolution; } + argv[i] = NULL; - execve(argv[0], argv, environ); - _exit(1); - } else { - sink_pid = pid; + i = 0; + int size = 0; + while (argv[i]) { + size += strlen(argv[i++] + 1); } + + char* player_command = malloc(size); + i = 0; + strcpy(player_command, argv[i++]); + while (argv[i]) { + strcat(player_command, " "); + strcat(player_command, argv[i++]); + } + log_debug("player command: %s", player_command); + //free(player_command); + if (execvpe(argv[0], argv, environ) < 0) { + cli_debug("stream player failed (%d): %m", errno); + int i = 0; + cli_debug("printing environment: "); + while (environ[i]) { + cli_debug("%s", environ[i++]); + } + } +} + +void launch_uibc_daemon(int port) { + char *argv[64]; + char portStr[64]; + int i = 0; + argv[i++] = "miracle-uibcctl"; + argv[i++] = "localhost"; + sprintf(portStr, "%d", port); + argv[i++] = portStr; + argv[i] = NULL; + + cli_debug("uibc daemon: %s", argv[0]); + execvpe(argv[0], argv, environ); } static void kill_gst(void) @@ -418,11 +487,11 @@ void ctl_fn_sink_disconnected(struct ctl_sink *s) } } -void ctl_fn_sink_resolution_set(struct ctl_sink *s, int hres, int vres) +void ctl_fn_sink_resolution_set(struct ctl_sink *s) { - cli_printf("SINK set resolution %dx%d\n", hres, vres); + cli_printf("SINK set resolution %dx%d\n", s->hres, s->vres); if (sink_connected) - spawn_gst(hres, vres); + spawn_gst(s); } void ctl_fn_peer_new(struct ctl_peer *p) @@ -604,14 +673,17 @@ void cli_fn_help() " -h --help Show this help\n" " --version Show package version\n" " --log-level Maximum level for log messages\n" + " --log-journal-level Maximum level for journal log messages\n" " --audio <0/1> Enable audio support (default %d)\n" " --scale WxH Scale to resolution\n" + " --port Port for rtsp (default %d)\n" + " --uibc Enables UIBC\n" " --res Supported resolutions masks (CEA, VESA, HH)\n" " default CEA %08X\n" " default VESA %08X\n" " default HH %08X\n" "\n" - , program_invocation_short_name, gst_audio_en, + , program_invocation_short_name, gst_audio_en, DEFAULT_RSTP_PORT, wfd_supported_res_cea, wfd_supported_res_vesa, wfd_supported_res_hh ); wfd_print_resolutions(); @@ -680,21 +752,30 @@ static int parse_argv(int argc, char *argv[]) enum { ARG_VERSION = 0x100, ARG_LOG_LEVEL, + ARG_JOURNAL_LEVEL, ARG_AUDIO, ARG_SCALE, ARG_RES, + ARG_PORT, + ARG_UIBC, }; static const struct option options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, ARG_VERSION }, { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, + { "log-journal-level", required_argument, NULL, ARG_JOURNAL_LEVEL }, { "audio", required_argument, NULL, ARG_AUDIO }, { "scale", required_argument, NULL, ARG_SCALE }, { "res", required_argument, NULL, ARG_RES }, + { "port", required_argument, NULL, ARG_PORT }, + { "uibc", no_argument, NULL, ARG_UIBC }, {} }; int c; + uibc = false; + rstp_port = DEFAULT_RSTP_PORT; + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { switch (c) { case 'h': @@ -705,6 +786,9 @@ static int parse_argv(int argc, char *argv[]) case ARG_LOG_LEVEL: cli_max_sev = log_parse_arg(optarg); break; + case ARG_JOURNAL_LEVEL: + log_max_sev = log_parse_arg(optarg); + break; case ARG_AUDIO: gst_audio_en = atoi(optarg); break; @@ -717,6 +801,12 @@ static int parse_argv(int argc, char *argv[]) &wfd_supported_res_vesa, &wfd_supported_res_hh); break; + case ARG_PORT: + rstp_port = atoi(optarg); + break; + case ARG_UIBC: + uibc = true; + break; case '?': return -EINVAL; } diff --git a/src/uibc/CMakeLists.txt b/src/uibc/CMakeLists.txt new file mode 100644 index 0000000..ac81904 --- /dev/null +++ b/src/uibc/CMakeLists.txt @@ -0,0 +1,10 @@ +set(miracle-uibcctl_SRCS miracle-uibcctl.h + miracle-uibcctl.c) + +add_executable(miracle-uibcctl ${miracle-uibcctl_SRCS}) +target_link_libraries(miracle-uibcctl PRIVATE miracle-shared) +target_link_libraries(miracle-uibcctl PUBLIC m) + +install(TARGETS miracle-uibcctl DESTINATION bin) + +INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/shared) diff --git a/src/uibc/Makefile.am b/src/uibc/Makefile.am new file mode 100644 index 0000000..7829d8c --- /dev/null +++ b/src/uibc/Makefile.am @@ -0,0 +1,16 @@ +include $(top_srcdir)/common.am + +bin_PROGRAMS = miracle-uibcctl + +miracle_uibcctl_SOURCES = \ + miracle-uibcctl.h \ + miracle-uibcctl.c + +miracle_uibctcl_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + $(DEPS_CFLAGS) + +miracle_uibcctl_LDADD = \ + ../shared/libmiracle-shared.la \ + $(LIBM) + diff --git a/src/uibc/miracle-uibcctl.c b/src/uibc/miracle-uibcctl.c new file mode 100644 index 0000000..39bc42c --- /dev/null +++ b/src/uibc/miracle-uibcctl.c @@ -0,0 +1,624 @@ + +#include "miracle-uibcctl.h" + +int main(int argc, char *argv[]) { + //TODO: Add miracle TUI interface + //TODO: Add parsearg + //--daemon (read stdin) + + int portno; + struct hostent *server; + int sockfd; + struct sockaddr_in serv_addr; + char buffer[256]; + int r; + + log_max_sev = LOG_INFO; + + if (argc < 3) { + fprintf(stderr, "Usage:\n"); + fprintf(stderr, " %s \n", argv[0]); + return EXIT_FAILURE; + } + + server = gethostbyname(argv[1]); + portno = atoi(argv[2]); + + log_info("server %s port %d", argv[1], portno); + + if (server == NULL) { + fprintf(stderr,"ERROR, no such host\n"); + exit(0); + } + + sockfd = socket(AF_INET, SOCK_STREAM, 0); + + if (sockfd < 0) { + perror("ERROR opening socket"); + return EXIT_FAILURE; + } + + bzero((char *) &serv_addr, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + bcopy(server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length); + serv_addr.sin_port = htons(portno); + + if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) { + perror("ERROR connecting"); + return EXIT_FAILURE; + } + + bool daemon = true; + while(1) { + if (!daemon) { + printf("enter event ,,,,: "); + } + bzero(buffer, 256); + fgets(buffer, 255, stdin); + if (buffer == NULL) { + break; + } + if (daemon) { + printf("input: %s", buffer); + } + char type = buffer[0]; + UibcMessage uibcmessage; + if (type == '0' || type == '1') { + uibcmessage = buildUibcMessage(GENERIC_TOUCH_DOWN, buffer, 1, 1); + } else if (type == '3' || type == '4') { + uibcmessage = buildUibcMessage(GENERIC_KEY_DOWN, buffer, 1, 1); + } else { + printf("unknow event type: %s", buffer); + continue; + } + + r = sendUibcMessage(&uibcmessage, sockfd); + if (r) { + return r; + } + } + + close(sockfd); + return EXIT_SUCCESS; +} + +const char *int2binary(int x, int padding) +{ + char *b; + int min_padding = x > 0 ? floor(log2(x)) : 0; + if (padding < min_padding) + { + padding = min_padding; + } + + b = (char *)malloc (sizeof (char *) * padding + 1); + strcpy(b, ""); + + int z; + for (z = pow(2,padding); z > 0; z >>= 1) + { + strcat(b, ((x & z) == z) ? "1" : "0"); + } + + return b; +} + +int sendUibcMessage(UibcMessage* uibcmessage, int sockfd) { + ssize_t n; + + printf("sending %d bytes\n", uibcmessage->m_PacketDataLen); + + n = write(sockfd, uibcmessage->m_PacketData , uibcmessage->m_PacketDataLen); + + if (n < 0) { + perror("ERROR writing to socket"); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +UibcMessage buildUibcMessage(MessageType type, + const char* inEventDesc, + double widthRatio, + double heightRatio) { + UibcMessage uibcmessage; + uibcmessage.m_PacketData = NULL; + uibcmessage.m_PacketDataLen = 0; + uibcmessage.m_DataValid = false; + + switch (type) { + case GENERIC_TOUCH_DOWN: + case GENERIC_TOUCH_UP: + case GENERIC_TOUCH_MOVE: + getUIBCGenericTouchPacket(inEventDesc, + &uibcmessage, + widthRatio, + heightRatio); + break; + + case GENERIC_KEY_DOWN: + case GENERIC_KEY_UP: + getUIBCGenericKeyPacket(inEventDesc, &uibcmessage); + break; + + case GENERIC_ZOOM: + getUIBCGenericZoomPacket(inEventDesc, &uibcmessage); + break; + + case GENERIC_VERTICAL_SCROLL: + case GENERIC_HORIZONTAL_SCROLL: + getUIBCGenericScalePacket(inEventDesc, &uibcmessage); + break; + + case GENERIC_ROTATE: + getUIBCGenericRotatePacket(inEventDesc, &uibcmessage); + break; + }; + return uibcmessage; +} + + +// format: "typeId, number of pointers, pointer Id1, X coordnate, Y coordinate, pointer Id2, X coordnate, Y coordnate,..." +void getUIBCGenericTouchPacket(const char *inEventDesc, + UibcMessage* uibcmessage, + double widthRatio, + double heightRatio) { + log_info("getUIBCGenericTouchPacket (%s)", inEventDesc); + char* outData; + int32_t typeId = 0; + int32_t numberOfPointers = 0; + size_t uibcBodyLen = 0; + int32_t genericPacketLen = 0; + int32_t temp; + size_t size; + + char** splitedStr = str_split((char*)inEventDesc, ",", &size); + + if ((int)size - 5 < 0 || ((int)size - 5) % 3 != 0) { + log_error("getUIBCGenericTouchPacket (%s)", "bad input event"); + return; + } + int offset_split = 0; + typeId = atoi(*(splitedStr + offset_split++)); + numberOfPointers = atoi(*(splitedStr + offset_split++)); + + genericPacketLen = numberOfPointers * 5 + 1; + uibcBodyLen = genericPacketLen + 7; // Generic header length = 7 + //Padding to even number + uibcBodyLen = (uibcBodyLen % 2 == 0) ? uibcBodyLen : (uibcBodyLen + 1); + + outData = (char*)malloc(uibcBodyLen); + // UIBC header Octets + int offset_header = 0; + //Version (3 bits),T (1 bit),Reserved(8 bits),Input Category (4 bits) + outData[offset_header++] = 0x00; // 000 0 0000 + outData[offset_header++] = 0x00; // 0000 0000 + + //Length(16 bits) + outData[offset_header++] = (uibcBodyLen >> 8) & 0xFF; // first 8 bytes + outData[offset_header++] = uibcBodyLen & 0xFF; // last 8 bytes + + //Generic Input Body Format + outData[offset_header++] = typeId & 0xFF; // Tyoe ID, 1 octet + + // Length, 2 octets + outData[offset_header++] = (genericPacketLen >> 8) & 0xFF; // first 8 bytes + outData[offset_header++] = genericPacketLen & 0xFF; //last 8 bytes + + // Number of pointers, 1 octet + outData[offset_header++] = numberOfPointers & 0xFF; + + int offset = 0; + log_info("getUIBCGenericTouchPacket numberOfPointers=[%d]\n", numberOfPointers); + for (int i = 0; i < numberOfPointers; i++) { + int splitoffset = offset_split + (i * 3); + temp = atoi(*(splitedStr + splitoffset++)); + offset = offset_header + ( i * 5); + outData[offset++] = temp & 0xFF; + log_info("getUIBCGenericTouchPacket PointerId=[%d]\n", temp); + + temp = atoi(*(splitedStr + splitoffset++)); + temp = (int32_t)((double)temp / widthRatio); + log_info("getUIBCGenericTouchPacket X-coordinate=[%d]\n", temp); + outData[offset++] = (temp >> 8) & 0xFF; + outData[offset++] = temp & 0xFF; + + temp = atoi(*(splitedStr + splitoffset++)); + temp = (int32_t)((double)temp / heightRatio); + log_info("getUIBCGenericTouchPacket Y-coordinate=[%d]\n", temp); + outData[offset++] = (temp >> 8) & 0xFF; + outData[offset++] = temp & 0xFF; + } + + while (offset <= uibcBodyLen) { + outData[offset++] = 0x00; + } + + for (int i = 0; i < size; i++) { + free(*(splitedStr + i)); + } + free(splitedStr); + + binarydump(outData, uibcBodyLen); + uibcmessage->m_DataValid = true; + uibcmessage->m_PacketData = outData; + uibcmessage->m_PacketDataLen = uibcBodyLen; +} + +void hexdump(void *_data, size_t len) +{ + unsigned char *data = _data; + int count; + + int line = 15; + for (count = 0; count < len; count++) { + if ((count & line) == 0) { + fprintf(stderr,"%04zu: ", count); + } + fprintf(stderr,"%02x ", *data); + data++; + if ((count & line) == line) { + fprintf(stderr,"\n"); + } + } + if ((count & line) != 0) { + fprintf(stderr,"\n"); + } +} + +void binarydump(void *_data, size_t len) +{ + unsigned char *data = _data; + int count; + + int line = 7; + for (count = 0; count < len; count++) { + if ((count & line) == 0) { + fprintf(stderr,"%04zu: ", count); + } + fprintf(stderr,"%s ", int2binary(*data, 8)); + data++; + if ((count & line) == line) { + fprintf(stderr,"\n"); + } + } + if ((count & line) != 0) { + fprintf(stderr,"\n"); + } +} +// format: "typeId, Key code 1(0x00), Key code 2(0x00)" +void getUIBCGenericKeyPacket(const char *inEventDesc, + UibcMessage* uibcmessage) { + log_info("getUIBCGenericKeyPacket (%s)", inEventDesc); + char* outData = uibcmessage->m_PacketData; + int32_t typeId = 0; + int32_t uibcBodyLen = 0; + int32_t genericPacketLen = 0; + int32_t temp = 0; + size_t size; + char** splitedStr = str_split((char*)inEventDesc, ",", &size); + + if (size > 0) { + + if (((int)size) % 3 != 0) { + log_error("getUIBCGenericKeyPacket (%s)", "bad input event"); + return; + } + int i; + for (i = 0; i < size; i++) { + log_info("getUIBCGenericKeyPacket splitedStr tokens=[%s]\n", *(splitedStr + i)); + + switch (i) { + case 0: { + typeId = atoi(*(splitedStr + i)); + log_info("getUIBCGenericKeyPacket typeId=[%d]\n", typeId); + genericPacketLen = 5; + uibcBodyLen = genericPacketLen + 7; // Generic header legth = 7 + outData = (char*)malloc(uibcBodyLen + 1); + // UIBC header + outData[0] = 0x00; //Version (3 bits),T (1 bit),Reserved(8 bits),Input Category (4 bits) + outData[1] = 0x00; //Version (3 bits),T (1 bit),Reserved(8 bits),Input Category (4 bits) + outData[2] = (uibcBodyLen >> 8) & 0xFF; //Length(16 bits) + outData[3] = uibcBodyLen & 0xFF; //Length(16 bits) + //Generic Input Body Format + outData[4] = typeId & 0xFF; // Tyoe ID, 1 octet + outData[5] = (genericPacketLen >> 8) & 0xFF; // Length, 2 octets + outData[6] = genericPacketLen & 0xFF; // Length, 2 octets + outData[7] = 0x00; // reserved + break; + } + case 1: { + sscanf(*(splitedStr + i), " 0x%04X", &temp); + if (temp == 0) { + outData[8] = 0x00; + outData[9] = 0x00; + } + log_info("getUIBCGenericKeyPacket key code 1=[%d]\n", temp); + outData[8] = (temp >> 8) & 0xFF; + outData[9] = temp & 0xFF; + + break; + } + case 2: { + sscanf(*(splitedStr + i), " 0x%04X", &temp); + if (temp == 0) { + outData[10] = 0x00; + outData[11] = 0x00; + } + log_info("getUIBCGenericKeyPacket key code 2=[%d]\n", temp); + outData[10] = (temp >> 8) & 0xFF; + outData[11] = temp & 0xFF; + + break; + } + default: { + } + break; + } + free(*(splitedStr + i)); + } + } + free(splitedStr); + + binarydump(outData, uibcBodyLen); + uibcmessage->m_DataValid = true; + uibcmessage->m_PacketData = outData; + uibcmessage->m_PacketDataLen = uibcBodyLen; +} + +// format: "typeId, X coordnate, Y coordnate, integer part, fraction part" +void getUIBCGenericZoomPacket(const char *inEventDesc, UibcMessage* uibcmessage) { + log_info("getUIBCGenericZoomPacket (%s)", inEventDesc); + char* outData = uibcmessage->m_PacketData; + int32_t typeId; + int32_t uibcBodyLen, genericPacketLen; + int32_t xCoord, yCoord, integerPart, FractionPart; + size_t size; + + char** splitedStr = str_split((char*)inEventDesc, ",", &size); + + if (splitedStr) { + int i; + for (i = 0; * (splitedStr + i); i++) { + //log_info("getUIBCGenericZoomPacket splitedStr tokens=[%s]\n", *(splitedStr + i)); + + switch (i) { + case 0: { + typeId = atoi(*(splitedStr + i)); + //log_info("getUIBCGenericZoomPacket typeId=[%d]\n", typeId); + + genericPacketLen = 6; + uibcBodyLen = genericPacketLen + 7; // Generic herder leh = 7 + outData = (char*)malloc(uibcBodyLen + 1); + // UIBC header + outData[0] = 0x00; //Version (3 bits),T (1 bit),Reserved(8 bits),Input Category (4 bits) + outData[1] = 0x00; //Version (3 bits),T (1 bit),Reserved(8 bits),Input Category (4 bits) + outData[2] = (uibcBodyLen >> 8) & 0xFF; //Length(16 bits) + outData[3] = uibcBodyLen & 0xFF; //Length(16 bits) + //Generic Input Body Format + outData[4] = typeId & 0xFF; // Tyoe ID, 1 octet + outData[5] = (genericPacketLen >> 8) & 0xFF; // Length, 2 octets + outData[6] = genericPacketLen & 0xFF; // Length, 2 octets + break; + } + + case 1: { + xCoord = atoi(*(splitedStr + i)); + outData[7] = (xCoord >> 8) & 0xFF; + outData[8] = xCoord & 0xFF; + log_info("getUIBCGenericZoomPacket xCoord=[%d]\n", xCoord); + break; + } + case 2: { + yCoord = atoi(*(splitedStr + i)); + log_info("getUIBCGenericZoomPacket yCoord=[%d]\n", yCoord); + break; + } + case 3: { + integerPart = atoi(*(splitedStr + i)); + outData[11] = integerPart & 0xFF; + //log_info("getUIBCGenericZoomPacket integerPart=[%d]\n", integerPart); + break; + } + case 4: { + FractionPart = atoi(*(splitedStr + i)); + outData[12] = FractionPart & 0xFF; + //log_info("getUIBCGenericZoomPacket FractionPart=[%d]\n", FractionPart); + + break; + } + default: { + break; + } + } + + free(*(splitedStr + i)); + } + free(splitedStr); + } + //hexdump(outData, uibcBodyLen); + uibcmessage->m_DataValid = true; + uibcmessage->m_PacketDataLen = uibcBodyLen; +} + +// format: "typeId, unit, direction, amount to scroll" +void getUIBCGenericScalePacket(const char *inEventDesc, UibcMessage* uibcmessage) { + log_info("getUIBCGenericScalePacket (%s)", inEventDesc); + char* outData = uibcmessage->m_PacketData; + int32_t typeId; + int32_t uibcBodyLen, genericPacketLen; + int32_t temp; + size_t size; + char** splitedStr = str_split((char*)inEventDesc, ",", &size); + + if (splitedStr) { + int i; + for (i = 0; * (splitedStr + i); i++) { + //log_info("getUIBCGenericScalePacket splitedStr tokens=[%s]\n", *(splitedStr + i)); + + switch (i) { + case 0: { + typeId = atoi(*(splitedStr + i)); + //log_info("getUIBCGenericScalePacket typeId=[%d]\n", typeId); + genericPacketLen = 2; + uibcBodyLen = genericPacketLen + 7; // Generic herder leh = 7 + outData = (char*)malloc(uibcBodyLen + 1); + // UIBC header + outData[0] = 0x00; //Version (3 bits),T (1 bit),Reserved(8 bits),Input Category (4 bits) + outData[1] = 0x00; //Version (3 bits),T (1 bit),Reserved(8 bits),Input Category (4 bits) + outData[2] = (uibcBodyLen >> 8) & 0xFF; //Length(16 bits) + outData[3] = uibcBodyLen & 0xFF; //Length(16 bits) + //Generic Input Body Format + outData[4] = typeId & 0xFF; // Tyoe ID, 1 octet + outData[5] = (genericPacketLen >> 8) & 0xFF; // Length, 2 octets + outData[6] = genericPacketLen & 0xFF; // Length, 2 octets + outData[7] = 0x00; // Clear the byte + outData[8] = 0x00; // Clear the byte + /* + B15B14; Scroll Unit Indication bits. + 0b00; the unit is a pixel (normalized with respect to the WFD Source display resolution that is conveyed in an RTSP M4 request message). + 0b01; the unit is a mouse notch (where the application is responsible for representing the number of pixels per notch). + 0b10-0b11; Reserved. + + B13; Scroll Direction Indication bit. + 0b0; Scrolling to the right. Scrolling to the right means the displayed content being shifted to the left from a user perspective. + 0b1; Scrolling to the left. Scrolling to the left means the displayed content being shifted to the right from a user perspective. + +B12:B0; Number of Scroll bits. +Number of units for a Horizontal scroll. +*/ + break; + } + case 1: { + temp = atoi(*(splitedStr + i)); + //log_info("getUIBCGenericScalePacket unit=[%d]\n", temp); + outData[7] = (temp >> 8) & 0xFF; + break; + } + case 2: { + temp = atoi(*(splitedStr + i)); + //log_info("getUIBCGenericScalePacket direction=[%d]\n", temp); + outData[7] |= ((temp >> 10) & 0xFF); + break; + + } + case 3: { + temp = atoi(*(splitedStr + i)); + //log_info("getUIBCGenericScalePacket amount to scroll=[%d]\n", temp); + outData[7] |= ((temp >> 12) & 0xFF); + outData[8] = temp & 0xFF; + + break; + } + default: { + break; + } + } + + free(*(splitedStr + i)); + } + + free(splitedStr); + } + //hexdump(outData, uibcBodyLen); + uibcmessage->m_DataValid = true; + uibcmessage->m_PacketDataLen = uibcBodyLen; +} + +// format: "typeId, integer part, fraction part" +void getUIBCGenericRotatePacket(const char * inEventDesc, UibcMessage* uibcmessage) { + log_info("getUIBCGenericRotatePacket (%s)", inEventDesc); + char* outData = uibcmessage->m_PacketData; + int32_t typeId; + int32_t uibcBodyLen, genericPacketLen; + int32_t integerPart, FractionPart; + size_t size; + + char** splitedStr = str_split((char*)inEventDesc, ",", &size); + + if (splitedStr) { + int i; + for (i = 0; * (splitedStr + i); i++) { + //log_info("getUIBCGenericRotatePacket splitedStr tokens=[%s]\n", *(splitedStr + i)); + + switch (i) { + case 0: { + typeId = atoi(*(splitedStr + i)); + //log_info("getUIBCGenericRotatePacket typeId=[%d]\n", typeId); + genericPacketLen = 2; + uibcBodyLen = genericPacketLen + 7; // Generic herder leh = 7 + outData = (char*)malloc(uibcBodyLen + 1); + // UIBC header + outData[0] = 0x00; //Version (3 bits),T (1 bit),Reserved(8 bits),Input Category (4 bits) + outData[1] = 0x00; //Version (3 bits),T (1 bit),Reserved(8 bits),Input Category (4 bits) + outData[2] = (uibcBodyLen >> 8) & 0xFF; //Length(16 bits) + outData[3] = uibcBodyLen & 0xFF; //Length(16 bits) + //Generic Input Body Format + outData[4] = typeId & 0xFF; // Tyoe ID, 1 octet + outData[5] = (genericPacketLen >> 8) & 0xFF; // Length, 2 octets + outData[6] = genericPacketLen & 0xFF; // Length, 2 octets + break; + } + case 1: { + integerPart = atoi(*(splitedStr + i)); + outData[7] = integerPart & 0xFF; + //log_info("getUIBCGenericRotatePacket integerPart=[%d]\n", integerPart); + break; + } + case 2: { + FractionPart = atoi(*(splitedStr + i)); + outData[8] = FractionPart & 0xFF; + //log_info("getUIBCGenericRotatePacket FractionPart=[%d]\n", FractionPart); + + break; + } + default: { + break; + } + } + free(*(splitedStr + i)); + } + + free(splitedStr); + } + //hexdump(outData, uibcBodyLen); + uibcmessage->m_DataValid = true; + uibcmessage->m_PacketDataLen = uibcBodyLen; +} + + +char** str_split(char* pStr, const char* pDelim, size_t* size) { + char** result = 0; + size_t count = 0; + char* tmp = pStr; + char* tmpStr = NULL; + char* last_comma = 0; + + asprintf(&tmpStr, "%s", pStr); + + /* Count how many elements will be extracted. */ + while (*tmp) { + if (*pDelim == *tmp) { + count++; + last_comma = tmp; + } + tmp++; + } + + /* Add space for trailing token. */ + count += last_comma < (pStr + strlen(pStr) - 1) ? 1 : 0; + + result = (char**)malloc(sizeof(char*) * count); + + *size = count; + + tmp = tmpStr = strdup(pStr); + size_t idx = 0; + char* token; + while ((token = strsep(&tmp, pDelim)) != NULL) { + * (result + idx++) = strdup(token); + } + free(tmpStr); + return result; +} diff --git a/src/uibc/miracle-uibcctl.h b/src/uibc/miracle-uibcctl.h new file mode 100644 index 0000000..1398011 --- /dev/null +++ b/src/uibc/miracle-uibcctl.h @@ -0,0 +1,48 @@ + +#ifndef MIRACLE_UIBCCTL_H +#define MIRACLE_UIBCCTL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "shl_log.h" + +typedef enum { + GENERIC_TOUCH_DOWN = 0, + GENERIC_TOUCH_UP, + GENERIC_TOUCH_MOVE, + GENERIC_KEY_DOWN, + GENERIC_KEY_UP, + GENERIC_ZOOM, + GENERIC_VERTICAL_SCROLL, + GENERIC_HORIZONTAL_SCROLL, + GENERIC_ROTATE +} MessageType; + +typedef struct { + char* m_PacketData; + size_t m_PacketDataLen; + bool m_DataValid; +} UibcMessage; + +UibcMessage buildUibcMessage(MessageType type, const char* inEventDesc, double widthRatio, double heightRatio); + +static char** str_split(char* pStr, const char* pDelim, size_t* size); + +void getUIBCGenericTouchPacket(const char *inEventDesc, UibcMessage* uibcmessage, double widthRatio, double heightRatio); +void getUIBCGenericKeyPacket(const char *inEventDesc, UibcMessage* uibcmessage); +void getUIBCGenericZoomPacket(const char *inEventDesc,UibcMessage* uibcmessage); +void getUIBCGenericScalePacket(const char *inEventDesc, UibcMessage* uibcmessage); +void getUIBCGenericRotatePacket(const char *inEventDesc, UibcMessage* uibcmessage); + +void hexdump(void *_data, size_t len); +void binarydump(void *_data, size_t len); + +int sendUibcMessage(UibcMessage* uibcmessage, int sockfd); +#endif