add invert, more help text, ...
choose if pointer is visible to make dithering more stable
This commit is contained in:
		
							parent
							
								
									83baf7b419
								
							
						
					
					
						commit
						b770607893
					
				
					 4 changed files with 125 additions and 59 deletions
				
			
		
							
								
								
									
										44
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										44
									
								
								README.md
									
										
									
									
									
								
							|  | @ -57,11 +57,29 @@ Options: | |||
| Usage: servicepoint-cli stream <COMMAND> | ||||
| 
 | ||||
| Commands: | ||||
|   stdin    | ||||
|   screen   | ||||
|   stdin   Pipe text to the display, example: `journalctl | servicepoint-cli stream stdin` | ||||
|   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) | ||||
| ``` | ||||
| 
 | ||||
| #### Screen | ||||
| 
 | ||||
| ``` | ||||
| Usage: servicepoint-cli stream screen [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 | ||||
| ``` | ||||
| 
 | ||||
|  | @ -71,26 +89,22 @@ Options: | |||
| Usage: servicepoint-cli brightness <COMMAND> | ||||
| 
 | ||||
| Commands: | ||||
|   reset  [aliases: r] | ||||
|   set    [aliases: s] | ||||
|   min     | ||||
|   max     | ||||
|   help   Print this message or the help of the given subcommand(s) | ||||
| 
 | ||||
| Options: | ||||
|   -h, --help  Print help | ||||
|   max   Reset brightness to the default (max) level [aliases: r, reset] | ||||
|   set   Set one brightness for the whole screen [aliases: s] | ||||
|   min   Set brightness to lowest possible level. | ||||
|   help  Print this message or the help of the given subcommand(s) | ||||
| ``` | ||||
| 
 | ||||
| ### Pixels | ||||
| 
 | ||||
| ``` | ||||
| Usage: servicepoint-cli pixels <COMMAND> | ||||
| 
 | ||||
| Commands: | ||||
|   reset  [aliases: r] | ||||
|   help   Print this message or the help of the given subcommand(s) | ||||
| 
 | ||||
| Options: | ||||
|   -h, --help  Print help | ||||
|   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) | ||||
| ``` | ||||
| 
 | ||||
| ## Contributing | ||||
|  |  | |||
							
								
								
									
										59
									
								
								src/cli.rs
									
										
									
									
									
								
							
							
						
						
									
										59
									
								
								src/cli.rs
									
										
									
									
									
								
							|  | @ -1,7 +1,9 @@ | |||
| use crate::stream_window::StreamScreenOptions; | ||||
| 
 | ||||
| #[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 { | ||||
|     #[arg(
 | ||||
|         short, | ||||
|  | @ -26,7 +28,7 @@ pub struct Cli { | |||
| 
 | ||||
| #[derive(clap::Parser, std::fmt::Debug)] | ||||
| pub enum Mode { | ||||
|     #[command(visible_alias = "r")] | ||||
|     #[command(visible_alias = "r", about = "Reset both pixels and brightness")] | ||||
|     ResetEverything, | ||||
|     #[command(visible_alias = "p")] | ||||
|     Pixels { | ||||
|  | @ -42,25 +44,40 @@ pub enum Mode { | |||
|     Stream { | ||||
|         #[clap(subcommand)] | ||||
|         stream_command: StreamCommand, | ||||
|     } | ||||
|     }, | ||||
| } | ||||
| 
 | ||||
| #[derive(clap::Parser, std::fmt::Debug)] | ||||
| #[clap(about = "Commands for manipulating pixels")] | ||||
| pub enum PixelCommand { | ||||
|     #[command(visible_alias = "r")] | ||||
|     Reset, | ||||
|     #[command(
 | ||||
|         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)] | ||||
| #[clap(about = "Commands for manipulating the brightness")] | ||||
| pub enum BrightnessCommand { | ||||
|     #[command(visible_alias = "r")] | ||||
|     Reset, | ||||
|     #[command(visible_alias = "s")] | ||||
|     #[command(
 | ||||
|         visible_alias = "r", | ||||
|         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 { | ||||
|         #[arg()] | ||||
|         brightness: u8, | ||||
|     }, | ||||
|     #[command(about = "Set brightness to lowest possible level.")] | ||||
|     Min, | ||||
|     Max, | ||||
| } | ||||
| 
 | ||||
| #[derive(clap::ValueEnum, Clone, Debug)] | ||||
|  | @ -71,13 +88,33 @@ pub enum Protocol { | |||
| } | ||||
| 
 | ||||
| #[derive(clap::Parser, std::fmt::Debug)] | ||||
| #[clap(about = "Continuously send data to the display")] | ||||
| pub enum StreamCommand { | ||||
|     #[clap(
 | ||||
|         about = "Pipe text to the display, example: `journalctl | servicepoint-cli stream stdin`" | ||||
|     )] | ||||
|     Stdin { | ||||
|         #[arg(long, short, default_value_t = false)] | ||||
|         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 { | ||||
|         #[command(flatten)] | ||||
|         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_window::stream_window; | ||||
| 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) { | ||||
|     match mode { | ||||
|  | @ -21,15 +21,30 @@ pub fn execute_mode(mode: Mode, connection: Connection) { | |||
| 
 | ||||
| fn pixels(connection: &Connection, pixel_command: PixelCommand) { | ||||
|     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) { | ||||
|     match brightness_command { | ||||
|         BrightnessCommand::Reset => brightness_reset(connection), | ||||
|         BrightnessCommand::Max => brightness_reset(connection), | ||||
|         BrightnessCommand::Min => brightness_set(connection, Brightness::MIN), | ||||
|         BrightnessCommand::Max => brightness_set(connection, Brightness::MAX), | ||||
|         BrightnessCommand::Set { brightness } => { | ||||
|             brightness_set(connection, Brightness::saturating_from(brightness)) | ||||
|         } | ||||
|  |  | |||
|  | @ -1,8 +1,9 @@ | |||
| use crate::cli::StreamScreenOptions; | ||||
| use image::{ | ||||
|     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::{ | ||||
|     capturer::{Capturer, Options}, | ||||
|     frame::convert_bgra_to_rgb, | ||||
|  | @ -13,34 +14,19 @@ use servicepoint::{ | |||
| }; | ||||
| 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) { | ||||
|     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, | ||||
|         None => return, | ||||
|     }; | ||||
| 
 | ||||
|     let mut bitmap = Bitmap::new(PIXEL_WIDTH, PIXEL_HEIGHT); | ||||
|     info!("now starting to stream images"); | ||||
|     loop { | ||||
|         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 !options.no_dither { | ||||
|             dither(&mut frame, &BiLevel); | ||||
|         } | ||||
| 
 | ||||
|         let frame = get_next_frame(&capturer, options.no_dither); | ||||
|         for (mut dest, src) in bitmap.iter_mut().zip(frame.pixels()) { | ||||
|             *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() { | ||||
|         error!("platform not supported by scap"); | ||||
|         return None; | ||||
|  | @ -71,11 +74,8 @@ fn start_capture() -> Option<Capturer> { | |||
| 
 | ||||
|     let mut capturer = Capturer::build(Options { | ||||
|         fps: FRAME_PACING.div_duration_f32(Duration::from_secs(1)) as u32, | ||||
|         target: None, | ||||
|         show_cursor: true, | ||||
|         show_highlight: true, | ||||
|         excluded_targets: None, | ||||
|         output_type: scap::frame::FrameType::BGR0, | ||||
|         show_cursor: options.pointer, | ||||
|         output_type: scap::frame::FrameType::BGR0, // this is more like a suggestion
 | ||||
|         ..Default::default() | ||||
|     }) | ||||
|     .expect("failed to create screen capture"); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Vinzenz Schroeter
						Vinzenz Schroeter