Skip to content

Commit

Permalink
Allow capturing the cursor.
Browse files Browse the repository at this point in the history
  • Loading branch information
H-M-H committed Jun 11, 2020
1 parent f118dbf commit 27d9aa1
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 70 deletions.
1 change: 1 addition & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,5 @@ fn linux() {
println!("cargo:rustc-link-lib=X11");
println!("cargo:rustc-link-lib=Xext");
println!("cargo:rustc-link-lib=Xrandr");
println!("cargo:rustc-link-lib=Xfixes");
}
63 changes: 57 additions & 6 deletions lib/linux/xcapture.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,26 @@
#include <X11/Xutil.h>

#include <X11/extensions/XShm.h>
#include <X11/extensions/Xfixes.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#include <stdint.h>
#include <stdio.h>

#include "../error.h"
#include "xhelper.h"

int clamp(int x, int lb, int ub)
{
if (x < lb)
return lb;
if (x > ub)
return ub;
return x;
}

struct CaptureContext
{
Capturable cap;
Expand Down Expand Up @@ -79,7 +88,7 @@ void stop_capture(CaptureContext* ctx, Error* err)
free(ctx);
}

void capture_sceen(CaptureContext* ctx, struct Image* img, Error* err)
void capture_sceen(CaptureContext* ctx, struct Image* img, int capture_cursor, Error* err)
{
Window root = DefaultRootWindow(ctx->cap.disp);
int x, y;
Expand All @@ -106,8 +115,8 @@ void capture_sceen(CaptureContext* ctx, struct Image* img, Error* err)
Window* active_window;
unsigned long size;

active_window = (Window*)get_property(
ctx->cap.disp, root, XA_WINDOW, "_NET_ACTIVE_WINDOW", &size, err);
active_window =
(Window*)get_property(ctx->cap.disp, root, XA_WINDOW, "_NET_ACTIVE_WINDOW", &size, err);
if (*active_window == ctx->cap.c.winfo.win)
{
// cap window within its root so menus are visible as strictly speaking menus do not
Expand All @@ -118,8 +127,7 @@ void capture_sceen(CaptureContext* ctx, struct Image* img, Error* err)
{
// ... but only if it is the active window as we might be recording the wrong thing
// otherwise. If it is not active just record the window itself.
XShmGetImage(
ctx->cap.disp, ctx->cap.c.winfo.win, ctx->ximg, 0, 0, 0x00ffffff);
XShmGetImage(ctx->cap.disp, ctx->cap.c.winfo.win, ctx->ximg, 0, 0, 0x00ffffff);
}
free(active_window);
break;
Expand All @@ -128,6 +136,49 @@ void capture_sceen(CaptureContext* ctx, struct Image* img, Error* err)
XShmGetImage(ctx->cap.disp, root, ctx->ximg, x, y, 0x00ffffff);
break;
}

if (capture_cursor)
{
XFixesCursorImage* cursor_img = XFixesGetCursorImage(ctx->cap.disp);
uint32_t* data = (uint32_t*)ctx->ximg->data;

// coordinates of cursor inside ximg
int x0 = cursor_img->x - cursor_img->xhot - x;
int y0 = cursor_img->y - cursor_img->yhot - y;

// clamp part of cursor image to draw to the part of the cursor that is inside
// the captured area
int i0 = clamp(0, -x0, width - x0);
int i1 = clamp(cursor_img->width, -x0, width - x0);
int j0 = clamp(0, -y0, height - y0);
int j1 = clamp(cursor_img->height, -y0, height - y0);
// paint cursor image into captured image
for (int j = j0; j < j1; ++j)
for (int i = i0; i < i1; ++i)
{
uint32_t c_pixel = cursor_img->pixels[j * cursor_img->width + i];
unsigned char a = (c_pixel & 0xff000000) >> 24;
if (a)
{
uint32_t d_pixel = data[(j + y0) * width + i + x0];

unsigned char c1 = (c_pixel & 0x00ff0000) >> 16;
unsigned char c2 = (c_pixel & 0x0000ff00) >> 8;
unsigned char c3 = (c_pixel & 0x000000ff) >> 0;
unsigned char d1 = (d_pixel & 0x00ff0000) >> 16;
unsigned char d2 = (d_pixel & 0x0000ff00) >> 8;
unsigned char d3 = (d_pixel & 0x000000ff) >> 0;
// colors from the cursor image are premultiplied with the alpha channel
unsigned char f1 = c1 + d1 * (255 - a) / 255;
unsigned char f2 = c2 + d2 * (255 - a) / 255;
unsigned char f3 = c3 + d3 * (255 - a) / 255;
data[(j + y0) * width + i + x0] = (f1 << 16) | (f2 << 8) | (f3 << 0);
}
}

XFree(cursor_img);
}

img->width = ctx->ximg->width;
img->height = ctx->ximg->height;
img->data = ctx->ximg->data;
Expand Down
142 changes: 84 additions & 58 deletions src/gui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,11 @@ pub fn run(log_receiver: mpsc::Receiver<String>) {
.with_label("Start");

let mut check_stylus = CheckButton::default()
.with_pos(430, 30 + (padding + height))
.with_pos(430, padding + height - 10)
.with_size(width, 2 * height);
#[cfg(target_os = "linux")]
{
check_stylus.set_label("Stylus/Pen support\nRequires access to\n/dev/uinput");
check_stylus.set_label("Stylus/Pen support\n(Requires access to\n/dev/uinput)");
check_stylus.set_checked(true);
}
#[cfg(not(target_os = "linux"))]
Expand All @@ -100,21 +100,30 @@ pub fn run(log_receiver: mpsc::Receiver<String>) {
let mut check_faster_screencapture = CheckButton::default()
.with_size(width, 2 * height)
.below_of(&check_stylus, 2 * padding);

#[allow(unused_mut)]
let mut check_capture_cursor = CheckButton::default()
.with_size(width, height)
.below_of(&check_faster_screencapture, padding)
.with_label("Capture Cursor");

#[cfg(target_os = "linux")]
{
check_capture_cursor.set_checked(false);
check_faster_screencapture.set_checked(true);
check_faster_screencapture.set_label("Faster screencapture");
}
#[cfg(not(target_os = "linux"))]
{
check_faster_screencapture.set_label("Faster screencapture\n(works only on\nLinux)");
check_faster_screencapture.deactivate();
check_capture_cursor.deactivate();
}

#[cfg(target_os = "linux")]
let label_capturable_choice = Frame::default()
.with_size(width, height)
.below_of(&check_faster_screencapture, padding)
.below_of(&check_capture_cursor, padding)
.with_label("Capture:");

#[cfg(not(target_os = "linux"))]
Expand All @@ -130,7 +139,7 @@ pub fn run(log_receiver: mpsc::Receiver<String>) {
#[cfg(not(target_os = "linux"))]
choice_capturable.deactivate();

let mut but_update_capturables = Button::default()
let but_update_capturables = Button::default()
.with_size(width, height)
.below_of(&choice_capturable, padding)
.with_label("Refresh");
Expand All @@ -153,8 +162,10 @@ pub fn run(log_receiver: mpsc::Receiver<String>) {
wind.show();

let but_toggle_ref = Rc::new(RefCell::new(but_toggle));
let but_update_capturables_ref = Rc::new(RefCell::new(but_update_capturables));
let choice_capturable_ref = Rc::new(RefCell::new(choice_capturable));
let check_faster_screencapture_ref = Rc::new(RefCell::new(check_faster_screencapture));
let check_capture_cursor_ref = Rc::new(RefCell::new(check_capture_cursor));
let output_server_addr = Arc::new(Mutex::new(output_server_addr));
let output = Arc::new(Mutex::new(output));

Expand Down Expand Up @@ -196,49 +207,53 @@ pub fn run(log_receiver: mpsc::Receiver<String>) {

{
let choice_capturable_ref = choice_capturable_ref.clone();
but_update_capturables.set_callback(Box::new(move || {
let mut choice_capturable = choice_capturable_ref.borrow_mut();
choice_capturable.clear();
let capturables = x11_context.capturables().unwrap();
{
let mut current_capturable = current_capturable.borrow_mut();
if current_capturable.is_none() {
let first_capturable = capturables[0].clone();
current_capturable.replace(first_capturable);
but_update_capturables_ref
.borrow_mut()
.set_callback(Box::new(move || {
let mut choice_capturable = choice_capturable_ref.borrow_mut();
choice_capturable.clear();
let capturables = x11_context.capturables().unwrap();
{
let mut current_capturable = current_capturable.borrow_mut();
if current_capturable.is_none() {
let first_capturable = capturables[0].clone();
current_capturable.replace(first_capturable);
}
}
}
for c in capturables {
let current_capturable = current_capturable.clone();
let chars = c
.name()
.replace("\\", "\\\\")
.replace("/", "\\/")
.replace("_", "\\_")
.replace("&", "\\&");
let chars = chars.chars();
let mut name = String::new();
for (i, c) in chars.enumerate() {
if i >= 32 {
name.push_str("...");
break;
for c in capturables {
let current_capturable = current_capturable.clone();
let chars = c
.name()
.replace("\\", "\\\\")
.replace("/", "\\/")
.replace("_", "\\_")
.replace("&", "\\&");
let chars = chars.chars();
let mut name = String::new();
for (i, c) in chars.enumerate() {
if i >= 32 {
name.push_str("...");
break;
}
name.push(c);
}
name.push(c);
choice_capturable.add(
&name,
Shortcut::None,
MenuFlag::Normal,
Box::new(move || {
current_capturable.replace(Some(c.clone()));
}),
);
}
choice_capturable.add(
&name,
Shortcut::None,
MenuFlag::Normal,
Box::new(move || {
current_capturable.replace(Some(c.clone()));
}),
);
}
}));
}));
}

but_update_capturables.do_callback();
but_update_capturables_ref.borrow_mut().do_callback();

let check_faster_screencapture_ref = check_faster_screencapture_ref.clone();
let check_capture_cursor_ref = check_capture_cursor_ref.clone();
let but_update_capturables_ref = but_update_capturables_ref.clone();

check_faster_screencapture_ref
.clone()
Expand All @@ -248,10 +263,12 @@ pub fn run(log_receiver: mpsc::Receiver<String>) {
let mut choice_capturable = choice_capturable_ref.borrow_mut();
if checked {
choice_capturable.deactivate();
but_update_capturables.deactivate();
but_update_capturables_ref.borrow_mut().deactivate();
check_capture_cursor_ref.borrow_mut().deactivate();
} else {
choice_capturable.activate();
but_update_capturables.activate();
but_update_capturables_ref.borrow_mut().activate();
check_capture_cursor_ref.borrow_mut().activate();
}
}));
}
Expand Down Expand Up @@ -285,22 +302,31 @@ pub fn run(log_receiver: mpsc::Receiver<String>) {
let (sender_gui2ws_tmp, receiver_gui2ws) = mpsc::channel();
sender_gui2ws = Some(sender_gui2ws_tmp);
#[cfg(target_os = "linux")]
crate::websocket::run(
sender_ws2gui.clone(),
receiver_gui2ws,
SocketAddr::new(bind_addr, ws_pointer_port),
SocketAddr::new(bind_addr, ws_video_port),
password,
screen_update_interval,
check_stylus.is_checked(),
check_faster_screencapture_ref.borrow().is_checked(),
current_capturable
.clone()
.borrow()
.as_ref()
.unwrap()
.clone(),
);
{
let faster_screencapture =
check_faster_screencapture_ref.borrow().is_checked();
if !faster_screencapture {
current_capturable.replace(None);
but_update_capturables_ref.borrow_mut().do_callback();
}
crate::websocket::run(
sender_ws2gui.clone(),
receiver_gui2ws,
SocketAddr::new(bind_addr, ws_pointer_port),
SocketAddr::new(bind_addr, ws_video_port),
password,
screen_update_interval,
check_stylus.is_checked(),
faster_screencapture,
current_capturable
.clone()
.borrow()
.as_ref()
.unwrap()
.clone(),
check_capture_cursor_ref.borrow().is_checked(),
);
}
#[cfg(not(target_os = "linux"))]
crate::websocket::run(
sender_ws2gui.clone(),
Expand Down
10 changes: 6 additions & 4 deletions src/screen_capture/linux.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::os::raw::{c_uint, c_void};
use std::os::raw::{c_uint, c_void, c_int};
use std::slice::from_raw_parts;

use crate::cerror::CError;
Expand All @@ -7,7 +7,7 @@ use crate::x11helper::Capturable;

extern "C" {
fn start_capture(handle: *const c_void, ctx: *mut c_void, err: *mut CError) -> *mut c_void;
fn capture_sceen(handle: *mut c_void, img: *mut CImage, err: *mut CError);
fn capture_sceen(handle: *mut c_void, img: *mut CImage, capture_cursor: c_int, err: *mut CError);
fn stop_capture(handle: *mut c_void, err: *mut CError);
}

Expand Down Expand Up @@ -39,10 +39,11 @@ impl CImage {
pub struct ScreenCaptureX11 {
handle: *mut c_void,
img: CImage,
capture_cursor: bool,
}

impl ScreenCaptureX11 {
pub fn new(mut capture: Capturable) -> Result<Self, CError> {
pub fn new(mut capture: Capturable, capture_cursor: bool) -> Result<Self, CError> {
let mut err = CError::new();
fltk::app::lock().unwrap();
let handle = unsafe { start_capture(capture.handle(), std::ptr::null_mut(), &mut err) };
Expand All @@ -53,6 +54,7 @@ impl ScreenCaptureX11 {
return Ok(Self {
handle,
img: CImage::new(),
capture_cursor
});
}
}
Expand All @@ -74,7 +76,7 @@ impl ScreenCapture for ScreenCaptureX11 {
let mut err = CError::new();
fltk::app::lock().unwrap();
unsafe {
capture_sceen(self.handle, &mut self.img, &mut err);
capture_sceen(self.handle, &mut self.img, self.capture_cursor.into(), &mut err);
}
fltk::app::unlock();
}
Expand Down
Loading

0 comments on commit 27d9aa1

Please sign in to comment.