feat: init commit.
4
src-tauri/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
3273
src-tauri/Cargo.lock
generated
Normal file
30
src-tauri/Cargo.toml
Normal file
@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "test-demo"
|
||||
version = "0.0.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
license = ""
|
||||
repository = ""
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1.2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "1.2", features = ["shell-open"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
webp = "0.2.2"
|
||||
base64 = "0.21.0"
|
||||
core-graphics = "0.22.3"
|
||||
display-info = "0.4.1"
|
||||
png = "0.17.7"
|
||||
anyhow = "1.0.69"
|
||||
tracing = "0.1.37"
|
||||
|
||||
[features]
|
||||
# this feature is used for production builds or when `devPath` points to the filesystem
|
||||
# DO NOT REMOVE!!
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
3
src-tauri/build.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
BIN
src-tauri/icons/128x128.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
src-tauri/icons/128x128@2x.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
src-tauri/icons/32x32.png
Normal file
After Width: | Height: | Size: 974 B |
BIN
src-tauri/icons/Square107x107Logo.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
src-tauri/icons/Square142x142Logo.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
src-tauri/icons/Square150x150Logo.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
src-tauri/icons/Square284x284Logo.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
src-tauri/icons/Square30x30Logo.png
Normal file
After Width: | Height: | Size: 903 B |
BIN
src-tauri/icons/Square310x310Logo.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
src-tauri/icons/Square44x44Logo.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
src-tauri/icons/Square71x71Logo.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
src-tauri/icons/Square89x89Logo.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
src-tauri/icons/StoreLogo.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
src-tauri/icons/icon.icns
Normal file
BIN
src-tauri/icons/icon.ico
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
src-tauri/icons/icon.png
Normal file
After Width: | Height: | Size: 14 KiB |
148
src-tauri/src/main.rs
Normal file
@ -0,0 +1,148 @@
|
||||
// Prevents additional console window on WiOk(ndows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
mod take_screenshot_loop;
|
||||
|
||||
use base64::Engine;
|
||||
use core_graphics::display::{
|
||||
kCGNullWindowID, kCGWindowImageDefault, kCGWindowListOptionOnScreenOnly, CGDisplay,
|
||||
};
|
||||
use display_info::DisplayInfo;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::to_string;
|
||||
use tracing::error;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(remote = "DisplayInfo")]
|
||||
struct DisplayInfoDef {
|
||||
pub id: u32,
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub rotation: f32,
|
||||
pub scale_factor: f32,
|
||||
pub is_primary: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct DisplayInfoWrapper<'a>(#[serde(with = "DisplayInfoDef")] &'a DisplayInfo);
|
||||
|
||||
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
|
||||
#[tauri::command]
|
||||
fn greet(name: &str) -> String {
|
||||
format!("Hello, {}! You've been greeted from Rust!", name)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn list_display_info() -> Result<String, String> {
|
||||
let displays = display_info::DisplayInfo::all().map_err(|e| {
|
||||
error!("can not list display info: {}", e);
|
||||
e.to_string()
|
||||
})?;
|
||||
let displays: Vec<DisplayInfoWrapper> =
|
||||
displays.iter().map(|v| DisplayInfoWrapper(v)).collect();
|
||||
let json_str = to_string(&displays).map_err(|e| {
|
||||
error!("can not list display info: {}", e);
|
||||
e.to_string()
|
||||
})?;
|
||||
Ok(json_str)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn take_screenshot(display_id: u32, scale_factor: f32) -> Result<String, String> {
|
||||
let exec = || {
|
||||
println!("take_screenshot");
|
||||
let start_at = std::time::Instant::now();
|
||||
|
||||
let cg_display = CGDisplay::new(display_id);
|
||||
let cg_image = CGDisplay::screenshot(
|
||||
cg_display.bounds(),
|
||||
kCGWindowListOptionOnScreenOnly,
|
||||
kCGNullWindowID,
|
||||
kCGWindowImageDefault,
|
||||
)
|
||||
.ok_or_else(|| anyhow::anyhow!("Display#{}: take screenshot failed", display_id))?;
|
||||
println!("take screenshot took {}ms", start_at.elapsed().as_millis());
|
||||
|
||||
let buffer = cg_image.data();
|
||||
let bytes_per_row = cg_image.bytes_per_row() as f32;
|
||||
|
||||
let height = cg_image.height();
|
||||
let width = cg_image.width();
|
||||
|
||||
let image_height = (height as f32 / scale_factor) as u32;
|
||||
let image_width = (width as f32 / scale_factor) as u32;
|
||||
|
||||
println!(
|
||||
"raw image: {}x{}, output image: {}x{}",
|
||||
width, height, image_width, image_height
|
||||
);
|
||||
// // from bitmap vec
|
||||
let mut image_buffer = vec![0u8; (image_width * image_height * 3) as usize];
|
||||
|
||||
for y in 0..image_height {
|
||||
for x in 0..image_width {
|
||||
let offset =
|
||||
(((y as f32) * bytes_per_row + (x as f32) * 4.0) * scale_factor) as usize;
|
||||
let b = buffer[offset];
|
||||
let g = buffer[offset + 1];
|
||||
let r = buffer[offset + 2];
|
||||
let a = buffer[offset + 3];
|
||||
let offset = (y * image_width + x) as usize;
|
||||
image_buffer[offset * 3] = r;
|
||||
image_buffer[offset * 3 + 1] = g;
|
||||
image_buffer[offset * 3 + 2] = b;
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"convert to image buffer took {}ms",
|
||||
start_at.elapsed().as_millis()
|
||||
);
|
||||
|
||||
// to png image
|
||||
// let mut image_png = Vec::new();
|
||||
// let mut encoder = png::Encoder::new(&mut image_png, image_width, image_height);
|
||||
// encoder.set_color(png::ColorType::Rgb);
|
||||
// encoder.set_depth(png::BitDepth::Eight);
|
||||
|
||||
// let mut writer = encoder
|
||||
// .write_header()
|
||||
// .map_err(|e| anyhow::anyhow!("png: {}", anyhow::anyhow!(e.to_string())))?;
|
||||
// writer
|
||||
// .write_image_data(&image_buffer)
|
||||
// .map_err(|e| anyhow::anyhow!("png: {}", anyhow::anyhow!(e.to_string())))?;
|
||||
// writer
|
||||
// .finish()
|
||||
// .map_err(|e| anyhow::anyhow!("png: {}", anyhow::anyhow!(e.to_string())))?;
|
||||
// println!("encode to png took {}ms", start_at.elapsed().as_millis());
|
||||
let image_webp =
|
||||
webp::Encoder::from_rgb(&image_buffer, image_width, image_height).encode(90f32);
|
||||
// // base64 image
|
||||
let mut image_base64 = String::new();
|
||||
image_base64.push_str("data:image/webp;base64,");
|
||||
let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(&*image_webp);
|
||||
image_base64.push_str(encoded.as_str());
|
||||
|
||||
println!("took {}ms", start_at.elapsed().as_millis());
|
||||
println!("image_base64: {}", image_base64.len());
|
||||
|
||||
Ok(image_base64)
|
||||
};
|
||||
|
||||
exec().map_err(|e: anyhow::Error| {
|
||||
println!("error: {}", e);
|
||||
e.to_string()
|
||||
})
|
||||
}
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
greet,
|
||||
take_screenshot,
|
||||
list_display_info
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
0
src-tauri/src/take_screenshot_loop.rs
Normal file
49
src-tauri/tauri.conf.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm dev",
|
||||
"beforeBuildCommand": "pnpm build",
|
||||
"devPath": "http://localhost:1420",
|
||||
"distDir": "../dist",
|
||||
"withGlobalTauri": false
|
||||
},
|
||||
"package": {
|
||||
"productName": "test-demo",
|
||||
"version": "0.0.0"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"all": false,
|
||||
"shell": {
|
||||
"all": false,
|
||||
"open": true
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"identifier": "cc.ivanli.take-screenshot-test-demo",
|
||||
"targets": "all"
|
||||
},
|
||||
"security": {
|
||||
"csp": null
|
||||
},
|
||||
"updater": {
|
||||
"active": false
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
"fullscreen": false,
|
||||
"resizable": true,
|
||||
"title": "test-demo",
|
||||
"width": 800,
|
||||
"height": 600
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|