add invert, more help text, ...
choose if pointer is visible to make dithering more stable
This commit is contained in:
parent
83baf7b419
commit
b770607893
40
README.md
40
README.md
|
@ -57,11 +57,29 @@ Options:
|
||||||
Usage: servicepoint-cli stream <COMMAND>
|
Usage: servicepoint-cli stream <COMMAND>
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
stdin
|
stdin Pipe text to the display, example: `journalctl | servicepoint-cli stream stdin`
|
||||||
screen
|
screen Stream the default source to the display. On Linux Wayland, this pops up a screen or window chooser, but it also may directly start streaming your main screen.
|
||||||
help Print this message or the help of the given subcommand(s)
|
help Print this message or the help of the given subcommand(s)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Screen
|
||||||
|
|
||||||
|
```
|
||||||
|
Usage: servicepoint-cli stream screen [OPTIONS]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
-n, --no-dither Disable dithering
|
||||||
|
-p, --pointer Show mouse pointer in video feed
|
||||||
|
-h, --help Print help
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Stdin
|
||||||
|
|
||||||
|
```
|
||||||
|
Usage: servicepoint-cli stream stdin [OPTIONS]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-s, --slow
|
||||||
-h, --help Print help
|
-h, --help Print help
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -71,26 +89,22 @@ Options:
|
||||||
Usage: servicepoint-cli brightness <COMMAND>
|
Usage: servicepoint-cli brightness <COMMAND>
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
reset [aliases: r]
|
max Reset brightness to the default (max) level [aliases: r, reset]
|
||||||
set [aliases: s]
|
set Set one brightness for the whole screen [aliases: s]
|
||||||
min
|
min Set brightness to lowest possible level.
|
||||||
max
|
|
||||||
help Print this message or the help of the given subcommand(s)
|
help Print this message or the help of the given subcommand(s)
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help Print help
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Pixels
|
### Pixels
|
||||||
|
|
||||||
```
|
```
|
||||||
Usage: servicepoint-cli pixels <COMMAND>
|
Usage: servicepoint-cli pixels <COMMAND>
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
reset [aliases: r]
|
off Reset all pixels to the default (off) state [aliases: r, reset]
|
||||||
|
invert Invert the state of all pixels [aliases: i]
|
||||||
|
on Set all pixels to the on state
|
||||||
help Print this message or the help of the given subcommand(s)
|
help Print this message or the help of the given subcommand(s)
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help Print help
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
59
src/cli.rs
59
src/cli.rs
|
@ -1,7 +1,9 @@
|
||||||
use crate::stream_window::StreamScreenOptions;
|
|
||||||
|
|
||||||
#[derive(clap::Parser, std::fmt::Debug)]
|
#[derive(clap::Parser, std::fmt::Debug)]
|
||||||
#[clap(version, arg_required_else_help = true)]
|
#[clap(
|
||||||
|
version,
|
||||||
|
arg_required_else_help = true,
|
||||||
|
about = "A command line interface for the ServicePoint display."
|
||||||
|
)]
|
||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
#[arg(
|
#[arg(
|
||||||
short,
|
short,
|
||||||
|
@ -26,7 +28,7 @@ pub struct Cli {
|
||||||
|
|
||||||
#[derive(clap::Parser, std::fmt::Debug)]
|
#[derive(clap::Parser, std::fmt::Debug)]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
#[command(visible_alias = "r")]
|
#[command(visible_alias = "r", about = "Reset both pixels and brightness")]
|
||||||
ResetEverything,
|
ResetEverything,
|
||||||
#[command(visible_alias = "p")]
|
#[command(visible_alias = "p")]
|
||||||
Pixels {
|
Pixels {
|
||||||
|
@ -42,25 +44,40 @@ pub enum Mode {
|
||||||
Stream {
|
Stream {
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
stream_command: StreamCommand,
|
stream_command: StreamCommand,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::Parser, std::fmt::Debug)]
|
#[derive(clap::Parser, std::fmt::Debug)]
|
||||||
|
#[clap(about = "Commands for manipulating pixels")]
|
||||||
pub enum PixelCommand {
|
pub enum PixelCommand {
|
||||||
#[command(visible_alias = "r")]
|
#[command(
|
||||||
Reset,
|
visible_alias = "r",
|
||||||
|
visible_alias = "reset",
|
||||||
|
about = "Reset all pixels to the default (off) state"
|
||||||
|
)]
|
||||||
|
Off,
|
||||||
|
#[command(visible_alias = "i", about = "Invert the state of all pixels")]
|
||||||
|
Invert,
|
||||||
|
#[command(about = "Set all pixels to the on state")]
|
||||||
|
On,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::Parser, std::fmt::Debug)]
|
#[derive(clap::Parser, std::fmt::Debug)]
|
||||||
|
#[clap(about = "Commands for manipulating the brightness")]
|
||||||
pub enum BrightnessCommand {
|
pub enum BrightnessCommand {
|
||||||
#[command(visible_alias = "r")]
|
#[command(
|
||||||
Reset,
|
visible_alias = "r",
|
||||||
#[command(visible_alias = "s")]
|
visible_alias = "reset",
|
||||||
|
about = "Reset brightness to the default (max) level"
|
||||||
|
)]
|
||||||
|
Max,
|
||||||
|
#[command(visible_alias = "s", about = "Set one brightness for the whole screen")]
|
||||||
Set {
|
Set {
|
||||||
|
#[arg()]
|
||||||
brightness: u8,
|
brightness: u8,
|
||||||
},
|
},
|
||||||
|
#[command(about = "Set brightness to lowest possible level.")]
|
||||||
Min,
|
Min,
|
||||||
Max,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::ValueEnum, Clone, Debug)]
|
#[derive(clap::ValueEnum, Clone, Debug)]
|
||||||
|
@ -71,13 +88,33 @@ pub enum Protocol {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::Parser, std::fmt::Debug)]
|
#[derive(clap::Parser, std::fmt::Debug)]
|
||||||
|
#[clap(about = "Continuously send data to the display")]
|
||||||
pub enum StreamCommand {
|
pub enum StreamCommand {
|
||||||
|
#[clap(
|
||||||
|
about = "Pipe text to the display, example: `journalctl | servicepoint-cli stream stdin`"
|
||||||
|
)]
|
||||||
Stdin {
|
Stdin {
|
||||||
#[arg(long, short, default_value_t = false)]
|
#[arg(long, short, default_value_t = false)]
|
||||||
slow: bool,
|
slow: bool,
|
||||||
},
|
},
|
||||||
|
#[clap(about = "Stream the default source to the display. \
|
||||||
|
On Linux Wayland, this pops up a screen or window chooser, but it also may directly start streaming your main screen.")]
|
||||||
Screen {
|
Screen {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
options: StreamScreenOptions,
|
options: StreamScreenOptions,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(clap::Parser, std::fmt::Debug, Clone)]
|
||||||
|
pub struct StreamScreenOptions {
|
||||||
|
#[arg(long, short, default_value_t = false, help = "Disable dithering")]
|
||||||
|
pub no_dither: bool,
|
||||||
|
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
short,
|
||||||
|
default_value_t = false,
|
||||||
|
help = "Show mouse pointer in video feed"
|
||||||
|
)]
|
||||||
|
pub pointer: bool,
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::cli::{BrightnessCommand, Mode, PixelCommand, StreamCommand};
|
||||||
use crate::stream_stdin::stream_stdin;
|
use crate::stream_stdin::stream_stdin;
|
||||||
use crate::stream_window::stream_window;
|
use crate::stream_window::stream_window;
|
||||||
use log::info;
|
use log::info;
|
||||||
use servicepoint::{Brightness, Command, Connection};
|
use servicepoint::{BitVec, Brightness, Command, CompressionCode, Connection, PIXEL_COUNT};
|
||||||
|
|
||||||
pub fn execute_mode(mode: Mode, connection: Connection) {
|
pub fn execute_mode(mode: Mode, connection: Connection) {
|
||||||
match mode {
|
match mode {
|
||||||
|
@ -21,15 +21,30 @@ pub fn execute_mode(mode: Mode, connection: Connection) {
|
||||||
|
|
||||||
fn pixels(connection: &Connection, pixel_command: PixelCommand) {
|
fn pixels(connection: &Connection, pixel_command: PixelCommand) {
|
||||||
match pixel_command {
|
match pixel_command {
|
||||||
PixelCommand::Reset => pixels_reset(connection),
|
PixelCommand::Off => pixels_reset(connection),
|
||||||
|
PixelCommand::Invert => pixels_invert(connection),
|
||||||
|
PixelCommand::On => pixels_on(connection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pixels_on(connection: &Connection) {
|
||||||
|
let mask = BitVec::repeat(true, PIXEL_COUNT);
|
||||||
|
connection
|
||||||
|
.send(Command::BitmapLinearXor(0, mask, CompressionCode::Lzma))
|
||||||
|
.expect("could not send command")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pixels_invert(connection: &Connection) {
|
||||||
|
let mask = BitVec::repeat(true, PIXEL_COUNT);
|
||||||
|
connection
|
||||||
|
.send(Command::BitmapLinearXor(0, mask, CompressionCode::Lzma))
|
||||||
|
.expect("could not send command")
|
||||||
|
}
|
||||||
|
|
||||||
fn brightness(connection: &Connection, brightness_command: BrightnessCommand) {
|
fn brightness(connection: &Connection, brightness_command: BrightnessCommand) {
|
||||||
match brightness_command {
|
match brightness_command {
|
||||||
BrightnessCommand::Reset => brightness_reset(connection),
|
BrightnessCommand::Max => brightness_reset(connection),
|
||||||
BrightnessCommand::Min => brightness_set(connection, Brightness::MIN),
|
BrightnessCommand::Min => brightness_set(connection, Brightness::MIN),
|
||||||
BrightnessCommand::Max => brightness_set(connection, Brightness::MAX),
|
|
||||||
BrightnessCommand::Set { brightness } => {
|
BrightnessCommand::Set { brightness } => {
|
||||||
brightness_set(connection, Brightness::saturating_from(brightness))
|
brightness_set(connection, Brightness::saturating_from(brightness))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
use crate::cli::StreamScreenOptions;
|
||||||
use image::{
|
use image::{
|
||||||
imageops::{dither, resize, BiLevel, FilterType},
|
imageops::{dither, resize, BiLevel, FilterType},
|
||||||
DynamicImage, ImageBuffer, Rgb, Rgba,
|
DynamicImage, ImageBuffer, Luma, Rgb, Rgba,
|
||||||
};
|
};
|
||||||
use log::{error, warn};
|
use log::{error, info, warn};
|
||||||
use scap::{
|
use scap::{
|
||||||
capturer::{Capturer, Options},
|
capturer::{Capturer, Options},
|
||||||
frame::convert_bgra_to_rgb,
|
frame::convert_bgra_to_rgb,
|
||||||
|
@ -13,34 +14,19 @@ use servicepoint::{
|
||||||
};
|
};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[derive(clap::Parser, std::fmt::Debug, Clone)]
|
|
||||||
pub struct StreamScreenOptions {
|
|
||||||
#[arg(long, short, default_value_t = false)]
|
|
||||||
pub no_dither: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stream_window(connection: &Connection, options: StreamScreenOptions) {
|
pub fn stream_window(connection: &Connection, options: StreamScreenOptions) {
|
||||||
let capturer = match start_capture() {
|
info!("Starting capture with options: {:?}", options);
|
||||||
|
warn!("this implementation does not drop any frames - set a lower fps or disable dithering if your computer cannot keep up.");
|
||||||
|
|
||||||
|
let capturer = match start_capture(&options) {
|
||||||
Some(value) => value,
|
Some(value) => value,
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut bitmap = Bitmap::new(PIXEL_WIDTH, PIXEL_HEIGHT);
|
let mut bitmap = Bitmap::new(PIXEL_WIDTH, PIXEL_HEIGHT);
|
||||||
|
info!("now starting to stream images");
|
||||||
loop {
|
loop {
|
||||||
let frame = capturer.get_next_frame().expect("failed to capture frame");
|
let frame = get_next_frame(&capturer, options.no_dither);
|
||||||
let frame = frame_to_image(frame);
|
|
||||||
let frame = frame.grayscale().to_luma8();
|
|
||||||
let mut frame = resize(
|
|
||||||
&frame,
|
|
||||||
PIXEL_WIDTH as u32,
|
|
||||||
PIXEL_HEIGHT as u32,
|
|
||||||
FilterType::Nearest,
|
|
||||||
);
|
|
||||||
|
|
||||||
if !options.no_dither {
|
|
||||||
dither(&mut frame, &BiLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (mut dest, src) in bitmap.iter_mut().zip(frame.pixels()) {
|
for (mut dest, src) in bitmap.iter_mut().zip(frame.pixels()) {
|
||||||
*dest = src.0[0] > u8::MAX / 2;
|
*dest = src.0[0] > u8::MAX / 2;
|
||||||
}
|
}
|
||||||
|
@ -55,7 +41,24 @@ pub fn stream_window(connection: &Connection, options: StreamScreenOptions) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_capture() -> Option<Capturer> {
|
fn get_next_frame(capturer: &Capturer, no_dither: bool) -> ImageBuffer<Luma<u8>, Vec<u8>> {
|
||||||
|
let frame = capturer.get_next_frame().expect("failed to capture frame");
|
||||||
|
let frame = frame_to_image(frame);
|
||||||
|
let frame = frame.grayscale().to_luma8();
|
||||||
|
let mut frame = resize(
|
||||||
|
&frame,
|
||||||
|
PIXEL_WIDTH as u32,
|
||||||
|
PIXEL_HEIGHT as u32,
|
||||||
|
FilterType::Nearest,
|
||||||
|
);
|
||||||
|
|
||||||
|
if !no_dither {
|
||||||
|
dither(&mut frame, &BiLevel);
|
||||||
|
}
|
||||||
|
frame
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_capture(options: &StreamScreenOptions) -> Option<Capturer> {
|
||||||
if !scap::is_supported() {
|
if !scap::is_supported() {
|
||||||
error!("platform not supported by scap");
|
error!("platform not supported by scap");
|
||||||
return None;
|
return None;
|
||||||
|
@ -71,11 +74,8 @@ fn start_capture() -> Option<Capturer> {
|
||||||
|
|
||||||
let mut capturer = Capturer::build(Options {
|
let mut capturer = Capturer::build(Options {
|
||||||
fps: FRAME_PACING.div_duration_f32(Duration::from_secs(1)) as u32,
|
fps: FRAME_PACING.div_duration_f32(Duration::from_secs(1)) as u32,
|
||||||
target: None,
|
show_cursor: options.pointer,
|
||||||
show_cursor: true,
|
output_type: scap::frame::FrameType::BGR0, // this is more like a suggestion
|
||||||
show_highlight: true,
|
|
||||||
excluded_targets: None,
|
|
||||||
output_type: scap::frame::FrameType::BGR0,
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.expect("failed to create screen capture");
|
.expect("failed to create screen capture");
|
||||||
|
|
Loading…
Reference in a new issue