1
0
Fork 0
mirror of https://github.com/albfan/miraclecast.git synced 2025-02-12 16:11:54 +00:00

UIBC support

Basic support for UIBC (single mouse events and key events)

 - Option --uibc on miracle-sinkctl to enable it
 - Option --log-journal-level to see player execution on journalctl
 - Controller for uibc miracle-uibcctl
 - Player based on PyGtk to receive and communicate mouse and keyevents

closes #57
This commit is contained in:
albfan 2016-07-02 15:35:58 +02:00
parent 4637409d0b
commit 1e69dd6dae
21 changed files with 1258 additions and 79 deletions

1
.gitignore vendored
View file

@ -26,6 +26,7 @@ miracle-dhcp
miracle-sinkctl miracle-sinkctl
miracle-wifictl miracle-wifictl
miracle-wifid miracle-wifid
miracle-uibcctl
miraclectl miraclectl
miracled miracled
stamp-h1 stamp-h1

View file

@ -86,7 +86,7 @@ See there was interface changes on systemd 219, if you are below that version, u
### Arch linux ### 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 $ export _systemd_git_kdbus=--enable-kdbus
@ -142,7 +142,13 @@ Steps to use it as peer:
> psp-scan > 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 ## Autocompletion

View file

@ -35,6 +35,7 @@ AC_PROG_AWK
LT_PREREQ(2.2) LT_PREREQ(2.2)
LT_INIT LT_INIT
LT_LIB_M
AC_ARG_ENABLE([rely-udev], 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])) 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/dhcp/Makefile
src/shared/Makefile src/shared/Makefile
src/wifi/Makefile src/wifi/Makefile
src/uibc/Makefile
test/Makefile]) test/Makefile])
AC_OUTPUT AC_OUTPUT

View file

@ -1,7 +1,7 @@
########### install files ############### ########### install files ###############
install(FILES miracle-gst.sh DESTINATION bin) install(FILES miracle-gst gstplayer DESTINATION bin)

View file

@ -1,2 +1,2 @@
bin_SCRIPTS = miracle-gst.sh bin_SCRIPTS = miracle-gst gstplayer uibc-viewer
EXTRA_DIST = wpa.conf EXTRA_DIST = wpa.conf

219
res/gstplayer Executable file
View file

@ -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):
#<type>,<count>,<id>,<x>,<y>
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()

100
res/miracle-gst Executable file
View file

@ -0,0 +1,100 @@
#!/bin/bash
function help {
local scriptname="$(basename $0)"
cat >&2 <<EOF
$scriptname [options]
play rtp stream
Options:
-r Resolution
-s <Width>x<height> Scale
-d <level> Log level for gst
-p <port> 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}

View file

@ -35,7 +35,7 @@ Try installing packages "gst-plugins-bad, gst-plugins-base, gst-plugins-base-lib
If that fails too, try: If that fails too, try:
$ vlc rtp://@1991 $ vlc rtp://@:1991
EOF EOF
else else

8
res/uibc-viewer Executable file
View file

@ -0,0 +1,8 @@
#!/bin/bash
IP=$1
shift
UIBC_PORT=$1
shift
gstplayer $@ | miracle-uibcctl $IP $UIBC_PORT --daemon

View file

@ -1,9 +1,10 @@
set(CMAKE_C_FLAGS "-std=gnu11") set(CMAKE_C_FLAGS "-std=gnu11 ${CMAKE_C_FLAGS}")
add_subdirectory(shared) add_subdirectory(shared)
add_subdirectory(wifi) add_subdirectory(wifi)
add_subdirectory(dhcp) add_subdirectory(dhcp)
add_subdirectory(ctl) add_subdirectory(ctl)
add_subdirectory(uibc)
set(miracled_SRCS miracled.h miracled.c) set(miracled_SRCS miracled.h miracled.c)
add_executable(miracled ${miracled_SRCS}) add_executable(miracled ${miracled_SRCS})

View file

@ -1,5 +1,5 @@
include $(top_srcdir)/common.am include $(top_srcdir)/common.am
SUBDIRS = shared wifi dhcp ctl SUBDIRS = shared wifi dhcp ctl uibc
bin_PROGRAMS = miracled bin_PROGRAMS = miracled

View file

@ -24,11 +24,11 @@ target_link_libraries(miracle-wifictl miracle-shared)
set(miracle-sinkctl_SRCS ctl.h set(miracle-sinkctl_SRCS ctl.h
ctl-cli.c ctl-cli.c
ctl-sink.h
ctl-sink.c ctl-sink.c
ctl-wifi.c ctl-wifi.c
sinkctl.c sinkctl.c
wfd.c) wfd.c)
add_executable(miracle-sinkctl ${miracle-sinkctl_SRCS}) add_executable(miracle-sinkctl ${miracle-sinkctl_SRCS})

View file

@ -17,6 +17,7 @@ miracle_wifictl_LDADD = \
miracle_sinkctl_SOURCES = \ miracle_sinkctl_SOURCES = \
ctl.h \ ctl.h \
ctl-cli.c \ ctl-cli.c \
ctl-sink.h \
ctl-sink.c \ ctl-sink.c \
ctl-wifi.c \ ctl-wifi.c \
wfd.c \ wfd.c \

View file

@ -17,52 +17,8 @@
* along with MiracleCast; If not, see <http://www.gnu.org/licenses/>. * along with MiracleCast; If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <arpa/inet.h> #include "ctl-sink.h"
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <poll.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <systemd/sd-event.h>
#include <time.h>
#include <unistd.h>
#include "ctl.h"
#include "rtsp.h"
#include "shl_macro.h"
#include "shl_util.h"
#include "wfd.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 * 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) { if (rtsp_message_read(m, "{<>}", "wfd_client_rtp_ports") >= 0) {
char wfd_client_rtp_ports[128]; char wfd_client_rtp_ports[128];
sprintf(wfd_client_rtp_ports, 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, "{&}", r = rtsp_message_append(rep, "{&}",
wfd_client_rtp_ports); wfd_client_rtp_ports);
if (r < 0) if (r < 0)
@ -166,12 +122,12 @@ static void sink_handle_get_parameter(struct ctl_sink *s,
} }
/* wfd_uibc_capability */ /* 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]; char wfd_uibc_capability[512];
sprintf(wfd_uibc_capability, sprintf(wfd_uibc_capability,
"wfd_uibc_capability: input_category_list=GENERIC;" "wfd_uibc_capability: input_category_list=GENERIC;"
"generic_cap_list=Mouse,SingleTouch,Keyboard,Camera;" "generic_cap_list=Mouse,SingleTouch;"
"hidc_cap_list=none;port=%d", DEFAULT_UIBC_PORT); "hidc_cap_list=none;port=none");
r = rtsp_message_append(rep, "{&}", wfd_uibc_capability); r = rtsp_message_append(rep, "{&}", wfd_uibc_capability);
if (r < 0) if (r < 0)
return cli_vERR(r); return cli_vERR(r);
@ -243,7 +199,7 @@ static int sink_set_format(struct ctl_sink *s,
if (hres && vres) { if (hres && vres) {
s->hres = hres; s->hres = hres;
s->vres = vres; s->vres = vres;
ctl_fn_sink_resolution_set(s, hres, vres); ctl_fn_sink_resolution_set(s);
return 0; return 0;
} }
} }
@ -257,6 +213,7 @@ static void sink_handle_set_parameter(struct ctl_sink *s,
_rtsp_message_unref_ struct rtsp_message *rep = NULL; _rtsp_message_unref_ struct rtsp_message *rep = NULL;
const char *trigger; const char *trigger;
const char *url; const char *url;
const char *uibc_config;
const char *uibc_setting; const char *uibc_setting;
char *nu; char *nu;
unsigned int cea_res, vesa_res, hh_res; 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, "{<s>}", "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 */ /* M4 (or any other) can pass presentation URLs */
r = rtsp_message_read(m, "{<s>}", "wfd_uibc_setting", &uibc_setting); r = rtsp_message_read(m, "{<s>}", "wfd_uibc_setting", &uibc_setting);
if (r >= 0) { if (r >= 0) {
@ -330,9 +311,9 @@ static void sink_handle_set_parameter(struct ctl_sink *s,
if (r < 0) if (r < 0)
return cli_vERR(r); return cli_vERR(r);
r = rtsp_message_append(rep, "<s>", char rtsp_setup[128];
"Transport", sprintf(rtsp_setup, "RTP/AVP/UDP;unicast;client_port=%d", rstp_port);
"RTP/AVP/UDP;unicast;client_port=1991"); r = rtsp_message_append(rep, "<s>", "Transport", rtsp_setup);
if (r < 0) if (r < 0)
return cli_vERR(r); return cli_vERR(r);

72
src/ctl/ctl-sink.h Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef CTL_SINK_H
#define CTL_SINK_H
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <poll.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <systemd/sd-event.h>
#include <time.h>
#include <unistd.h>
#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 */

View file

@ -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_connected(struct ctl_sink *s);
void ctl_fn_sink_disconnected(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); void cli_fn_help(void);

View file

@ -34,6 +34,7 @@
#include <time.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
#include "ctl.h" #include "ctl.h"
#include "ctl-sink.h"
#include "wfd.h" #include "wfd.h"
#include "shl_macro.h" #include "shl_macro.h"
#include "shl_util.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 *running_peer;
static struct ctl_peer *pending_peer; static struct ctl_peer *pending_peer;
void launch_player(struct ctl_sink *s);
char *gst_scale_res; char *gst_scale_res;
int gst_audio_en = 1; 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_cea = 0x0000001f; /* up to 720x576 */
unsigned int wfd_supported_res_vesa = 0x00000003; /* up to 800x600 */ unsigned int wfd_supported_res_vesa = 0x00000003; /* up to 800x600 */
unsigned int wfd_supported_res_hh = 0x00000000; /* not supported */ 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; pid_t pid;
int fd_journal, i; int fd_journal;
sigset_t mask; sigset_t mask;
if (sink_pid > 0) if (sink_pid > 0)
@ -357,7 +363,7 @@ static void spawn_gst(int hres, int vres)
/* redirect stdout/stderr to journal */ /* redirect stdout/stderr to journal */
fd_journal = sd_journal_stream_fd("miracle-sinkctl-gst", fd_journal = sd_journal_stream_fd("miracle-sinkctl-gst",
LOG_INFO, LOG_DEBUG,
false); false);
if (fd_journal >= 0) { if (fd_journal >= 0) {
/* dup journal-fd to stdout and stderr */ /* dup journal-fd to stdout and stderr */
@ -368,8 +374,34 @@ static void spawn_gst(int hres, int vres)
dup2(2, 1); dup2(2, 1);
} }
i = 0; launch_player(s);
argv[i++] = (char*) "/miracle-gst.sh"; _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) if (cli_max_sev >= 7)
argv[i++] = "-d 3"; argv[i++] = "-d 3";
if (gst_audio_en) if (gst_audio_en)
@ -378,18 +410,55 @@ static void spawn_gst(int hres, int vres)
argv[i++] = "-s"; argv[i++] = "-s";
argv[i++] = gst_scale_res; argv[i++] = gst_scale_res;
} }
if (hres && vres) { argv[i++] = "-p";
sprintf(resolution, "%dx%d", hres, vres); 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++] = "-r";
argv[i++] = resolution; argv[i++] = resolution;
} }
argv[i] = NULL; argv[i] = NULL;
execve(argv[0], argv, environ); i = 0;
_exit(1); int size = 0;
} else { while (argv[i]) {
sink_pid = pid; 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) 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) if (sink_connected)
spawn_gst(hres, vres); spawn_gst(s);
} }
void ctl_fn_peer_new(struct ctl_peer *p) void ctl_fn_peer_new(struct ctl_peer *p)
@ -604,14 +673,17 @@ void cli_fn_help()
" -h --help Show this help\n" " -h --help Show this help\n"
" --version Show package version\n" " --version Show package version\n"
" --log-level <lvl> Maximum level for log messages\n" " --log-level <lvl> Maximum level for log messages\n"
" --log-journal-level <lvl> Maximum level for journal log messages\n"
" --audio <0/1> Enable audio support (default %d)\n" " --audio <0/1> Enable audio support (default %d)\n"
" --scale WxH Scale to resolution\n" " --scale WxH Scale to resolution\n"
" --port <port> Port for rtsp (default %d)\n"
" --uibc Enables UIBC\n"
" --res <n,n,n> Supported resolutions masks (CEA, VESA, HH)\n" " --res <n,n,n> Supported resolutions masks (CEA, VESA, HH)\n"
" default CEA %08X\n" " default CEA %08X\n"
" default VESA %08X\n" " default VESA %08X\n"
" default HH %08X\n" " default HH %08X\n"
"\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_supported_res_cea, wfd_supported_res_vesa, wfd_supported_res_hh
); );
wfd_print_resolutions(); wfd_print_resolutions();
@ -680,21 +752,30 @@ static int parse_argv(int argc, char *argv[])
enum { enum {
ARG_VERSION = 0x100, ARG_VERSION = 0x100,
ARG_LOG_LEVEL, ARG_LOG_LEVEL,
ARG_JOURNAL_LEVEL,
ARG_AUDIO, ARG_AUDIO,
ARG_SCALE, ARG_SCALE,
ARG_RES, ARG_RES,
ARG_PORT,
ARG_UIBC,
}; };
static const struct option options[] = { static const struct option options[] = {
{ "help", no_argument, NULL, 'h' }, { "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION }, { "version", no_argument, NULL, ARG_VERSION },
{ "log-level", required_argument, NULL, ARG_LOG_LEVEL }, { "log-level", required_argument, NULL, ARG_LOG_LEVEL },
{ "log-journal-level", required_argument, NULL, ARG_JOURNAL_LEVEL },
{ "audio", required_argument, NULL, ARG_AUDIO }, { "audio", required_argument, NULL, ARG_AUDIO },
{ "scale", required_argument, NULL, ARG_SCALE }, { "scale", required_argument, NULL, ARG_SCALE },
{ "res", required_argument, NULL, ARG_RES }, { "res", required_argument, NULL, ARG_RES },
{ "port", required_argument, NULL, ARG_PORT },
{ "uibc", no_argument, NULL, ARG_UIBC },
{} {}
}; };
int c; int c;
uibc = false;
rstp_port = DEFAULT_RSTP_PORT;
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
switch (c) { switch (c) {
case 'h': case 'h':
@ -705,6 +786,9 @@ static int parse_argv(int argc, char *argv[])
case ARG_LOG_LEVEL: case ARG_LOG_LEVEL:
cli_max_sev = log_parse_arg(optarg); cli_max_sev = log_parse_arg(optarg);
break; break;
case ARG_JOURNAL_LEVEL:
log_max_sev = log_parse_arg(optarg);
break;
case ARG_AUDIO: case ARG_AUDIO:
gst_audio_en = atoi(optarg); gst_audio_en = atoi(optarg);
break; break;
@ -717,6 +801,12 @@ static int parse_argv(int argc, char *argv[])
&wfd_supported_res_vesa, &wfd_supported_res_vesa,
&wfd_supported_res_hh); &wfd_supported_res_hh);
break; break;
case ARG_PORT:
rstp_port = atoi(optarg);
break;
case ARG_UIBC:
uibc = true;
break;
case '?': case '?':
return -EINVAL; return -EINVAL;
} }

10
src/uibc/CMakeLists.txt Normal file
View file

@ -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)

16
src/uibc/Makefile.am Normal file
View file

@ -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)

624
src/uibc/miracle-uibcctl.c Normal file
View file

@ -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 <hostname> <port>\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 <type>,<count>,<id>,<x>,<y>: ");
}
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;
}

View file

@ -0,0 +1,48 @@
#ifndef MIRACLE_UIBCCTL_H
#define MIRACLE_UIBCCTL_H
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdlib.h>
#include <netdb.h>
#include<arpa/inet.h>
#include <math.h>
#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