Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 22 additions & 13 deletions Client/Linux/Makefile
Original file line number Diff line number Diff line change
@@ -1,36 +1,45 @@
OBJS=main.o socket.o gui.o discover.o h264.o
DEBUG=-O
DEBUG=-O3
CFLAGS=$(DEBUG) -Wall -Werror -I../Common/x264 -I../Common/ffmpeg -I../Common/Socket
LIBRARIES:= -L../Common/x264 -lx264 -L../Common/ffmpeg/libswscale -lswscale -L../Common/ffmpeg/libavutil -lavutil -lX11 -lXrandr -lXfixes -lpthread
LIBRARIES:= -L../Common/x264 -lx264 -L../Common/ffmpeg/libswscale -lswscale -L../Common/ffmpeg/libavutil -lavutil -lX11 -lxcb -lxcb-randr -lxcb-xfixes -lpthread

screencast: $(OBJS)
cp ../Common/x264/libx264.so.* .
cp ../Common/ffmpeg/libswscale/libswscale.so.* .
cp ../Common/ffmpeg/libavutil/libavutil.so.* .
$(CC) $(DEBUG) -o $@ $(OBJS) $(FRAMEWORKS) -Wl,-rpath=./:/opt/promys $(LIBRARIES)

socket.o: ../Common/Socket/socket.c
$(CC) -c -o $@ $(CFLAGS) $<

install: screencast
cp ../Common/x264/libx264.so.* .
cp ../Common/ffmpeg/libswscale/libswscale.so.* .
cp ../Common/ffmpeg/libavutil/libavutil.so.* .
tgz: screencast
strip screencast lib*.so.*
tar czf promys.tgz screencast lib*.so.*

package: screencast
deb: dist
mkdir -p dist/DEBIAN
cp deb-control dist/DEBIAN/control
echo Installed-Size: `du -sk dist | awk -- '{print $$1}'` >> dist/DEBIAN/control
dpkg-deb -b dist promys.deb
rm -rf dist

rpm: dist
rpmbuild --quiet -bb --buildroot ${PWD}/dist promys.spec
cp ${HOME}/rpmbuild/RPMS/`uname -m`/promy*.rpm promys.rpm

dist: screencast FRC
mkdir -p dist/opt/promys
cp ../Common/x264/libx264.so.* dist/opt/promys
cp ../Common/ffmpeg/libswscale/libswscale.so.* dist/opt/promys
cp ../Common/ffmpeg/libavutil/libavutil.so.* dist/opt/promys
strip screencast
cp screencast dist/opt/promys
strip dist/opt/promys/screencast dist/opt/promys/lib*
cp promys.png dist/opt/promys
mkdir -p dist/DEBIAN
cp deb-control dist/DEBIAN/control
echo Installed-Size: `du -sk dist | awk -- '{print $$1}'` >> dist/DEBIAN/control
mkdir -p dist/usr/share/applications
cp promys.desktop dist/usr/share/applications
dpkg-deb -b dist promys.deb
rm -rf dist

FRC:

clean:
-rm -f *.o lib*.so.* screencast
-rm -rf dist
8 changes: 6 additions & 2 deletions Client/Linux/gui.c
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,12 @@ gui_init(int *go) {
BlackPixel(dpy, screen), WhitePixel(dpy, screen));
gc = XCreateGC(dpy, win, 0, 0);
XSelectInput(dpy, win, ExposureMask);
font = XLoadFont(dpy, "-*-fixed-*-*-*-*-18-*-*-*-*-*-*-*");
font_info = XQueryFont(dpy, font);

font_info = XLoadQueryFont(dpy, "-*-fixed-*-*-*-*-18-*-*-*-*-*-*-*");
if (font_info == NULL) {
font_info = XLoadQueryFont(dpy, "fixed");
}
font = font_info->fid;

XSetFont(dpy, gc, font);

Expand Down
229 changes: 147 additions & 82 deletions Client/Linux/main.c
Original file line number Diff line number Diff line change
@@ -1,29 +1,120 @@
/*
* Copyright (c) 2018, Olivier DEBON
* Valentin DEBON (XCB version)
* All rights reserved.
* Please checkout LICENSE file.
*/
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/Xrandr.h>
#include <X11/extensions/Xfixes.h>
#include <xcb/xcb.h>
#include <xcb/randr.h>
#include <xcb/xfixes.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include "gui.h"
#include "h264.h"
#include "discover.h"
#include "socket.h"

static inline unsigned char
color_select(unsigned char dst, unsigned char src_pre, unsigned char alpha) {
unsigned short val;
static void
desktop_properties(xcb_connection_t *connection, int screen_number, xcb_window_t *root, int16_t *x, int16_t *y, uint16_t *width, uint16_t *height) {
xcb_screen_iterator_t iterator;

val = (dst * (~alpha & 255)) + src_pre * 255;
return val >> 8;
// First, we find the root window
iterator = xcb_setup_roots_iterator(xcb_get_setup(connection));

while (iterator.rem && screen_number) {
screen_number--;
xcb_screen_next(&iterator);
}

if (screen_number != 0) {
fprintf(stderr, "Cannot determine root window\n");
exit(EXIT_FAILURE);
}

*root = iterator.data->root;

// We find randr resources associated to root
xcb_randr_get_screen_resources_reply_t *screen_resources;

screen_resources = xcb_randr_get_screen_resources_reply(connection, xcb_randr_get_screen_resources(connection, *root), NULL);

if(screen_resources == NULL) {
fprintf(stderr, "Unable to get screen resources\n");
exit(EXIT_FAILURE);
}

// Listing its crtcs, we take the first viewport for display
xcb_randr_crtc_t *crtcs;
xcb_randr_get_crtc_info_reply_t *info;

crtcs = xcb_randr_get_screen_resources_crtcs(screen_resources);
info = xcb_randr_get_crtc_info_reply(connection, xcb_randr_get_crtc_info(connection, crtcs[0], XCB_TIME_CURRENT_TIME), NULL);

*x = info->x;
*y = info->y;
*width = info->width;
*height = info->height;

free(info);
free(screen_resources);
}

static inline uint8_t
color_select(uint8_t dst, uint8_t src_pre, uint8_t alpha) {
uint16_t val;

val = (dst * (~alpha & 255)) + src_pre * 255;

return val >> 8;
}

static void
draw_cursor(xcb_connection_t *connection, xcb_window_t root, uint8_t *data, uint16_t height, size_t stride) {
xcb_query_pointer_reply_t *query_pointer;

query_pointer = xcb_query_pointer_reply(connection, xcb_query_pointer(connection, root), NULL);

if (query_pointer != NULL && query_pointer->same_screen == 1) {
xcb_xfixes_get_cursor_image_reply_t *cursor_image;

cursor_image = xcb_xfixes_get_cursor_image_reply(connection, xcb_xfixes_get_cursor_image(connection), NULL);

if(cursor_image != NULL) {
uint8_t *dst, *low, *high;
uint32_t *src;

dst = data
+ ((cursor_image->y - cursor_image->yhot) * stride)
+ (cursor_image->x - cursor_image->xhot) * 4;

src = xcb_xfixes_get_cursor_image_cursor_image(cursor_image);

low = data;
high = data + (height - 1) * stride;

int l,c;

for (l=0; l < cursor_image->height; l++) {
for (c=0; c < cursor_image->width*4; c+=4) {
if (dst >= low && dst < high) {
dst[0+c] = color_select(dst[0+c], *src, (*src)>>24);
dst[1+c] = color_select(dst[1+c], (*src)>>8, (*src)>>24);
dst[2+c] = color_select(dst[2+c], (*src)>>16, (*src)>>24);
}
src++;
}
dst += stride;
}

free(cursor_image);
}
}

free(query_pointer);
}

int
Expand Down Expand Up @@ -52,122 +143,96 @@ main(int argc, char **argv) {

gettimeofday(&start, NULL);

// Determine desktop size
size_t width, height;
// Connection to X11
xcb_connection_t *connection;
int screen_number;

Display *dpy;
int screen;
XImage *image;
Window root;
connection = xcb_connect(NULL, &screen_number);

dpy = XOpenDisplay(getenv("DISPLAY"));
if (dpy == NULL) {
printf("Can't open display.\n");
exit(1);
}
screen = DefaultScreen(dpy);
root = RootWindow(dpy, screen);
width = DisplayWidth (dpy, screen);
height = DisplayHeight (dpy, screen);
if (xcb_connection_has_error(connection) > 0) {
fprintf(stderr, "Cannot connect to X11\n");
exit(EXIT_FAILURE);
}

// If multiple monitors, use Xrandr to figure out the central left most one
XRRMonitorInfo *monitors;
// XFixes query version, required by protocol or "undefined behavior"
xcb_xfixes_query_version_reply_t *version;

int nbMonitors = 0;
version = xcb_xfixes_query_version_reply(connection, xcb_xfixes_query_version(connection, XCB_XFIXES_MAJOR_VERSION, XCB_XFIXES_MINOR_VERSION), NULL);

monitors = XRRGetMonitors(dpy, root, True, &nbMonitors);
// Determine desktop geometry
xcb_window_t root;
int16_t x, y;
uint16_t width, height;

if (nbMonitors) {
width = monitors[0].width;
height = monitors[0].height;
}
desktop_properties(connection, screen_number, &root, &x, &y, &width, &height);

// Make a first capture to figure out stride
image = XGetImage(dpy, root, 0, 0, width, height, AllPlanes, ZPixmap);
xcb_get_image_cookie_t image_cookie;
xcb_get_image_reply_t *image;

image_cookie = xcb_get_image(connection, XCB_IMAGE_FORMAT_Z_PIXMAP, root, x, y, width, height, ~0);
image = xcb_get_image_reply(connection, image_cookie, NULL);

if (image == NULL) {
fprintf(stderr, "Unable to get root image\n");
exit(EXIT_FAILURE);
}

size_t stride = xcb_get_image_data_length(image) / height;

// Init encoder with correct sizes
h264_init(width, height, image->bytes_per_line);
h264_init(width, height, stride);

// Inform we're broadcasting thru UI
showMessage("Broadcasting...");

// Proceed til user ends it up
while(go) {
while (go) {
unsigned char *packet;
size_t packet_size;
uint8_t *data = xcb_get_image_data(image);

const unsigned char *data = (const unsigned char *)image->data;
draw_cursor(connection, root, data, height, stride);

packet = h264_encode(data, &packet_size);

XDestroyImage(image);
free(image);

if (packet) {
if (socket_send(packet, packet_size) < 0) break; // Peer likely has disconnected
if (socket_send(packet, packet_size) < 0) {
printf("Error %d (%s)\n", errno, strerror(errno));
break; // Peer likely has disconnected
}
}

// Request next capture
image_cookie = xcb_get_image(connection, XCB_IMAGE_FORMAT_Z_PIXMAP, root, x, y, width, height, ~0);

// We're trying to maintain a 20i/s pace
// taking into account all possible delays (capture, encoding and network)

gettimeofday(&stop, NULL);

#define DELAY_US 50000

long delay;

delay = (stop.tv_sec - start.tv_sec)*1000000;
delay += (stop.tv_usec - start.tv_usec);
if (delay < DELAY_US) {
usleep(DELAY_US - delay);
usleep(DELAY_US - delay);
}

gettimeofday(&start, NULL);

// Capture a new image
image = XGetImage(dpy, root, 0, 0, width, height, AllPlanes, ZPixmap);

// Capture cursor
Window root, child;
int root_x, root_y, win_x, win_y;
unsigned int mask;

// Use XQueryPointer to make sure we're on main monitor
if (XQueryPointer(dpy, DefaultRootWindow(dpy), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask)) {
XFixesCursorImage *cursorImage;

cursorImage = XFixesGetCursorImage(dpy);

if (cursorImage) {
unsigned char *dst, *low, *high;
unsigned long *src;

dst = (unsigned char*)image->data + ((cursorImage->y - cursorImage->yhot) * image->bytes_per_line) + (cursorImage->x - cursorImage->xhot)*4;
src = cursorImage->pixels;

low = (unsigned char*)image->data;
high = (unsigned char*)image->data + (image->height - 1) * image->bytes_per_line;

int l,c;

for(l=0; l < cursorImage->height; l++) {
for(c=0; c < cursorImage->width*4; c+=4) {
if (dst >= low && dst < high) {
dst[0+c] = color_select(dst[0+c], *src, (*src)>>24);
dst[1+c] = color_select(dst[1+c], (*src)>>8, (*src)>>24);
dst[2+c] = color_select(dst[2+c], (*src)>>16, (*src)>>24);
}
src++;
}
dst += image->bytes_per_line;
}
XFree(cursorImage);
}
}
image = xcb_get_image_reply(connection, image_cookie, NULL);
}

h264_close();

socket_close();

XCloseDisplay(dpy);
free(image);
free(version);
xcb_disconnect(connection);
}
21 changes: 21 additions & 0 deletions Client/Linux/promys.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Summary: Promys - Project My Screen
Name: promys
Version: 1.2
Release: 1
Vendor: Olivier DEBON <olivier@debon.net>
Group: X11
License: BSD3
%description
Linux client to cast desktop to a Promys device
%prep
%build
%install
cd `dirname %{buildroot}`
make dist
%files
/opt/promys/libavutil.so.56
/opt/promys/libswscale.so.5
/opt/promys/libx264.so.155
/opt/promys/promys.png
/opt/promys/screencast
/usr/share/applications/promys.desktop