feat: linux screenshots
This commit is contained in:
Binary file not shown.
@ -22,6 +22,7 @@
|
||||
"@tauri-apps/plugin-dialog": "~2",
|
||||
"@tauri-apps/plugin-fs": "~2",
|
||||
"@tauri-apps/plugin-http": "~2",
|
||||
"@tauri-apps/plugin-log": "~2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"clsx": "^2.1.1",
|
||||
"fuse.js": "^7.1.0",
|
||||
|
1
frontend/src-tauri/Cargo.lock
generated
1
frontend/src-tauri/Cargo.lock
generated
@ -9,6 +9,7 @@ dependencies = [
|
||||
"base64 0.21.7",
|
||||
"chrono",
|
||||
"cocoa",
|
||||
"log",
|
||||
"notify",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -27,6 +27,7 @@ tokio = { version = "1.36.0", features = ["full"] }
|
||||
tauri-plugin-store = "2.0.0-beta.12"
|
||||
tauri-plugin-http = "2.0.0-beta.12"
|
||||
chrono = "0.4"
|
||||
log = "0.4"
|
||||
tauri-plugin-log = "2"
|
||||
tauri-plugin-sharetarget = "0.1.6"
|
||||
tauri-plugin-fs = "2"
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::screenshot::take_area_screenshot;
|
||||
use crate::state::SharedWatcherState;
|
||||
use crate::utils::process_png_file;
|
||||
use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
|
||||
@ -69,3 +70,8 @@ pub async fn handle_selected_folder(
|
||||
|
||||
Ok(format!("Now watching directory: {}", path))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn take_screenshot(app: AppHandle) -> Result<String, String> {
|
||||
take_area_screenshot(&app)
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ pub fn desktop() {
|
||||
let watcher_state = new_shared_watcher_state();
|
||||
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_log::Builder::new().build())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_store::Builder::new().build())
|
||||
.plugin(tauri_plugin_http::init())
|
||||
@ -24,6 +25,7 @@ pub fn desktop() {
|
||||
.manage(watcher_state)
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
commands::handle_selected_folder,
|
||||
commands::take_screenshot,
|
||||
shortcut::change_shortcut,
|
||||
shortcut::unregister_shortcut,
|
||||
shortcut::get_current_shortcut,
|
||||
|
@ -1,8 +1,55 @@
|
||||
use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
use std::process::{Command, Output};
|
||||
use std::{fs, path::PathBuf};
|
||||
use tauri::{AppHandle, Emitter, Runtime};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn screenshot(path: &PathBuf) -> Result<Output, String> {
|
||||
Command::new("screencapture")
|
||||
.arg("-i") // interactive selection
|
||||
.arg("-x") // don't play sound
|
||||
.arg("-o") // don't show cursor
|
||||
.arg("-r") // don't add shadow
|
||||
.arg(path.to_str().unwrap())
|
||||
.output()
|
||||
.map_err(|e| format!("Failed to execute screencapture: {}", e))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn screenshot(path: &PathBuf) -> Result<Output, String> {
|
||||
let slurp_output = Command::new("slurp")
|
||||
.output()
|
||||
.map_err(|e| format!("Failed to execute screencapture: {}", e))?;
|
||||
|
||||
if !slurp_output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&slurp_output.stderr);
|
||||
if slurp_output.status.code() == Some(1) && stderr.is_empty() {
|
||||
log::warn!("slurp cancelled by user.");
|
||||
return Err("Screenshot cancelled by user.".to_string());
|
||||
}
|
||||
return Err(format!(
|
||||
"slurp failed. Status: {:?}, Stderr: {}",
|
||||
slurp_output.status, stderr
|
||||
));
|
||||
}
|
||||
|
||||
let geometry = String::from_utf8(slurp_output.stdout)
|
||||
.map_err(|e| format!("slurp output is not valid UTF-8: {}", e))?
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
if geometry.is_empty() {
|
||||
return Err("slurp succeeded but returned empty geometry".to_string());
|
||||
}
|
||||
|
||||
Command::new("grim")
|
||||
.arg("-g")
|
||||
.arg(&geometry)
|
||||
.arg(path.to_str().unwrap())
|
||||
.output()
|
||||
.map_err(|e| format!("Failed to execute screencapture: {}", e))
|
||||
}
|
||||
|
||||
/// Takes a screenshot of a selected area and returns the image data as base64
|
||||
pub fn take_area_screenshot<R: Runtime>(app: &AppHandle<R>) -> Result<String, String> {
|
||||
// Create a temporary file path
|
||||
@ -10,15 +57,10 @@ pub fn take_area_screenshot<R: Runtime>(app: &AppHandle<R>) -> Result<String, St
|
||||
let timestamp = chrono::Local::now().format("%Y%m%d_%H%M%S");
|
||||
let temp_file = temp_dir.join(format!("haystack_screenshot_{}.png", timestamp));
|
||||
|
||||
// Use screencapture command with -i flag for interactive selection
|
||||
let output = Command::new("screencapture")
|
||||
.arg("-i") // interactive selection
|
||||
.arg("-x") // don't play sound
|
||||
.arg("-o") // don't show cursor
|
||||
.arg("-r") // don't add shadow
|
||||
.arg(temp_file.to_str().unwrap())
|
||||
.output()
|
||||
.map_err(|e| format!("Failed to execute screencapture: {}", e))?;
|
||||
log::info!("{}", temp_file.display());
|
||||
|
||||
let output = screenshot(&temp_file)?;
|
||||
log::info!("0");
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(format!(
|
||||
@ -27,10 +69,14 @@ pub fn take_area_screenshot<R: Runtime>(app: &AppHandle<R>) -> Result<String, St
|
||||
));
|
||||
}
|
||||
|
||||
log::info!("1");
|
||||
|
||||
// Read the captured image
|
||||
let contents =
|
||||
fs::read(&temp_file).map_err(|e| format!("Failed to read screenshot file: {}", e))?;
|
||||
|
||||
log::info!("2");
|
||||
|
||||
// Convert to base64
|
||||
let base64_string = BASE64.encode(&contents);
|
||||
|
||||
|
BIN
frontend/src-tauri/~/tmp/bruhhhhhhhhhh
Normal file
BIN
frontend/src-tauri/~/tmp/bruhhhhhhhhhh
Normal file
Binary file not shown.
After Width: | Height: | Size: 72 B |
@ -9,6 +9,7 @@ import { ImageViewer } from "./components/ImageViewer";
|
||||
import { ShareTarget } from "./components/share-target/ShareTarget";
|
||||
import type { sendImage } from "./network";
|
||||
import { ImageStatus } from "./components/image-status/ImageStatus";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
export const App = () => {
|
||||
const [processingImage, setProcessingImage] =
|
||||
@ -25,34 +26,41 @@ export const App = () => {
|
||||
});
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
let listener: PluginListener;
|
||||
const setupListener = async () => {
|
||||
listener = await listenForShareEvents(
|
||||
async (intent: ShareEvent) => {
|
||||
const contents = await readFile(intent.stream).catch(
|
||||
(error: Error) => {
|
||||
console.warn("fetching shared content failed:");
|
||||
throw error;
|
||||
},
|
||||
);
|
||||
setFile(
|
||||
new File([contents], intent.name ?? "no-name", {
|
||||
type: intent.content_type,
|
||||
}),
|
||||
);
|
||||
setLogs((l) => [...l, intent.uri]);
|
||||
},
|
||||
);
|
||||
};
|
||||
setupListener();
|
||||
return () => {
|
||||
listener?.unregister();
|
||||
};
|
||||
});
|
||||
// createEffect(() => {
|
||||
// let listener: PluginListener;
|
||||
// const setupListener = async () => {
|
||||
// listener = await listenForShareEvents(
|
||||
// async (intent: ShareEvent) => {
|
||||
// const contents = await readFile(intent.stream ?? "").catch(
|
||||
// (error: Error) => {
|
||||
// console.warn("fetching shared content failed:");
|
||||
// throw error;
|
||||
// },
|
||||
// );
|
||||
// setFile(
|
||||
// new File([contents], intent.name ?? "no-name", {
|
||||
// type: intent.content_type,
|
||||
// }),
|
||||
// );
|
||||
// setLogs((l) => [...l, intent.uri]);
|
||||
// },
|
||||
// );
|
||||
// };
|
||||
// setupListener();
|
||||
// return () => {
|
||||
// listener?.unregister();
|
||||
// };
|
||||
// });
|
||||
|
||||
const onTakeScreenshot = () => {
|
||||
invoke("take_screenshot");
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<button type="button" onClick={onTakeScreenshot}>
|
||||
Take Screenshot [wayland :(]
|
||||
</button>
|
||||
<ImageViewer onSendImage={setProcessingImage} />
|
||||
<ImageStatus processingImage={processingImage} />
|
||||
<ShareTarget />
|
||||
|
Reference in New Issue
Block a user