diff --git a/main.py b/main.py index 53569d1..66d93f9 100644 --- a/main.py +++ b/main.py @@ -12,7 +12,6 @@ from modules.dock.main import Dock from modules.switcher import ApplicationSwitcher from modules.osd import OSD from modules.launcher import Launcher -from services.screencapture import ScreenCapture gi.require_version("GLib", "2.0") @@ -49,7 +48,7 @@ if __name__ == "__main__": switcher = ApplicationSwitcher() osd = OSD() launcher = Launcher() - sc = ScreenCapture() + # Set corners visibility based on config corners_visible = config.get("corners_visible", True) corners.set_visible(corners_visible) diff --git a/scripts/screen-capture.sh b/scripts/screen-capture.sh new file mode 100755 index 0000000..0005d07 --- /dev/null +++ b/scripts/screen-capture.sh @@ -0,0 +1,647 @@ +#!/bin/env bash + +# Script name +SCRIPT_NAME=$(basename "$0") + +# Function to display usage +usage() { + cat < [options] + +Commands: + screenshot Take a screenshot + Targets: + selection - Screenshot selected area + eDP-1 - Screenshot eDP-1 display + HDMI-A-1 - Screenshot HDMI-A-1 display + both - Screenshot both displays + + record Start/stop recording (with audio) + Targets: + selection - Record selected area + eDP-1 - Record eDP-1 display + HDMI-A-1 - Record HDMI-A-1 display + stop - Stop current recording + + record-noaudio Start/stop recording (no audio) + Targets: + selection - Record selected area without audio + eDP-1 - Record eDP-1 display without audio + HDMI-A-1 - Record HDMI-A-1 display without audio + stop - Stop current recording + + record-hq Start/stop high-quality recording (for YouTube) + Targets: + selection - Record selected area in high quality + eDP-1 - Record eDP-1 display in high quality + HDMI-A-1 - Record HDMI-A-1 display in high quality + stop - Stop current recording + + record-gif Start/stop GIF recording + Targets: + selection - Record selected area as GIF + eDP-1 - Record eDP-1 display as GIF + HDMI-A-1 - Record HDMI-A-1 display as GIF + stop - Stop current recording + + status Check if recording is active (exit 0 if recording, 1 if not) + + convert Convert recordings + Formats: + webm - Convert MKV files to WebM + iphone - Convert MKV files for iPhone + youtube - Convert MKV files for YouTube (high quality) + gif - Convert MKV/MP4 files to GIF + +Examples: + $SCRIPT_NAME screenshot selection + $SCRIPT_NAME record eDP-1 + $SCRIPT_NAME record-noaudio selection + $SCRIPT_NAME record-hq eDP-1 + $SCRIPT_NAME record-gif selection + $SCRIPT_NAME record stop + $SCRIPT_NAME convert gif + +EOF + exit 1 +} + +# Check if no arguments provided +if [ $# -eq 0 ]; then + usage +fi + +# Function to send screenshot notification with action buttons +send_screenshot_notification() { + local full_path="$1" + local save_dir=$(dirname "$full_path") + + ACTION=$(notify-send -a "Modus" -i "$full_path" "Screenshot saved" "in $full_path" \ + -A "view=View" -A "edit=Edit" -A "open=Open Folder") + + case "$ACTION" in + view) xdg-open "$full_path" ;; + edit) swappy -f "$full_path" ;; + open) xdg-open "$save_dir" ;; + esac +} + +# Function to send recording notification with action buttons +send_recording_notification() { + local full_path="$1" + local save_dir=$(dirname "$full_path") + + ACTION=$(notify-send -a "Modus" -i "camera-video-symbolic" "Recording saved" "in $full_path" \ + -A "view=View" -A "open=Open Folder") + + case "$ACTION" in + view) xdg-open "$full_path" ;; + open) xdg-open "$save_dir" ;; + esac +} + +wf-recorder_check() { + if pgrep -x "wf-recorder" >/dev/null; then + pkill -INT -x wf-recorder + # Get the recording file path and send notification with actions + if [ -f /tmp/recording.txt ]; then + recording_file=$(cat /tmp/recording.txt) + wl-copy <"$recording_file" + send_recording_notification "$recording_file" + else + notify-send "Recording stopped" "wf-recorder process terminated" + fi + exit 0 + fi +} + +# Function to record with standard settings +record_video() { + local output_file="$1" + shift + + wf-recorder "$@" -f "$output_file" -c libvpx-vp9 --pixel-format yuv420p -F "eq=brightness=0.12:contrast=1.1" +} + +# Function to record without audio +record_video_noaudio() { + local output_file="$1" + shift + + wf-recorder "$@" -f "$output_file" -c libvpx-vp9 --pixel-format yuv420p -F "eq=brightness=0.12:contrast=1.1" --no-audio +} + +record_high_quality() { + local output_file="$1" + shift + + # High quality settings for YouTube uploads + # - h264_vaapi for hardware encoding (if available) or libx264 for software + # - yuv420p pixel format for maximum compatibility + # - High bitrate (8000k) for quality + # - GOP size of 30 for better seeking + # - Preset 'slow' for better compression efficiency + # - CRF 18 for high quality (lower = better quality, 0-51 scale) + # - Audio at 192k bitrate + # - 60 FPS for smooth motion + # - No color filters to maintain original colors + + # Check if VAAPI hardware encoding is available + if vainfo &>/dev/null && wf-recorder --help | grep -q "h264_vaapi"; then + # Use hardware encoding for better performance + wf-recorder "$@" -f "$output_file" \ + -c h264_vaapi \ + -p "preset=slow" \ + -p "crf=18" \ + -r 60 \ + -b 8000000 \ + -B 192000 \ + --pixel-format yuv420p \ + -g 30 + else + # Fallback to software encoding + wf-recorder "$@" -f "$output_file" \ + -c libx264 \ + -p "preset=slow" \ + -p "crf=18" \ + -r 60 \ + -b 8000000 \ + -B 192000 \ + --pixel-format yuv420p \ + -g 30 + fi +} + +record_gif() { + local output_file="$1" + shift + + # Record temporary video first (MKV format for better quality) + local temp_video="/tmp/gif_recording_$(date +%s).mkv" + echo "$temp_video" >/tmp/gif_temp_video.txt + + # GIF-optimized recording settings: + # - Lower framerate (15 fps) for smaller file size + # - No audio recording + # - Standard codec for compatibility + wf-recorder "$@" -f "$temp_video" \ + -c libvpx-vp9 \ + -r 15 \ + --pixel-format yuv420p \ + --no-audio + + # After recording stops, convert to GIF + if [ -f "$temp_video" ]; then + notify-send "Converting to GIF" "Processing recording..." + + # Create high-quality GIF with optimized palette + # Using ffmpeg with palette generation for better colors + ffmpeg -i "$temp_video" \ + -vf "fps=15,scale=iw:-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=128:stats_mode=diff[p];[s1][p]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle" \ + -loop 0 \ + "$output_file" 2>/tmp/gif_conversion.log + + if [ $? -eq 0 ]; then + # Clean up temp file + rm -f "$temp_video" + rm -f /tmp/gif_temp_video.txt + + # Copy to clipboard and send notification with actions + wl-copy <"$output_file" + send_recording_notification "$output_file" + else + error=$(cat /tmp/gif_conversion.log | tail -n 5) + notify-send "GIF Conversion Failed" "Error: $error" + rm -f "$temp_video" + rm -f /tmp/gif_temp_video.txt + fi + fi +} + +# Parse command +COMMAND="$1" +TARGET="$2" + +# Set up file paths +IMG="${HOME}/Pictures/Screenshots/$(date +%Y-%m-%d_%H-%m-%s).png" +VID="${HOME}/Videos/Recordings/$(date +%Y-%m-%d_%H-%m-%s).mkv" + +case "$COMMAND" in +"screenshot") + case "$TARGET" in + "selection") + grim -g "$(slurp)" "$IMG" + wl-copy <"$IMG" + send_screenshot_notification "$IMG" + ;; + "eDP-1") + grim -c -o eDP-1 "$IMG" + wl-copy <"$IMG" + send_screenshot_notification "$IMG" + ;; + "HDMI-A-1") + grim -c -o HDMI-A-1 "$IMG" + wl-copy <"$IMG" + send_screenshot_notification "$IMG" + ;; + "both") + grim -c -o eDP-1 "${IMG//.png/-eDP-1.png}" + grim -c -o HDMI-A-1 "${IMG//.png/-HDMI-A-1.png}" + montage "${IMG//.png/-eDP-1.png}" "${IMG//.png/-HDMI-A-1.png}" -tile 2x1 -geometry +0+0 "$IMG" + wl-copy <"$IMG" + rm "${IMG//.png/-eDP-1.png}" "${IMG//.png/-HDMI-A-1.png}" + send_screenshot_notification "$IMG" + ;; + *) + echo "Error: Invalid screenshot target '$TARGET'" + usage + ;; + esac + ;; + +"record") + case "$TARGET" in + "stop") + wf-recorder_check + ;; + "selection") + wf-recorder_check + echo "$VID" >/tmp/recording.txt + record_video "$VID" -g "$(slurp)" + ;; + "eDP-1") + wf-recorder_check + echo "$VID" >/tmp/recording.txt + record_video "$VID" -a -o eDP-1 + ;; + "HDMI-A-1") + wf-recorder_check + echo "$VID" >/tmp/recording.txt + record_video "$VID" -a -o HDMI-A-1 + ;; + *) + echo "Error: Invalid record target '$TARGET'" + usage + ;; + esac + ;; + +"record-noaudio") + case "$TARGET" in + "stop") + wf-recorder_check + ;; + "selection") + wf-recorder_check + echo "$VID" >/tmp/recording.txt + record_video_noaudio "$VID" -g "$(slurp)" + ;; + "eDP-1") + wf-recorder_check + echo "$VID" >/tmp/recording.txt + record_video_noaudio "$VID" -o eDP-1 + ;; + "HDMI-A-1") + wf-recorder_check + echo "$VID" >/tmp/recording.txt + record_video_noaudio "$VID" -o HDMI-A-1 + ;; + *) + echo "Error: Invalid record-noaudio target '$TARGET'" + usage + ;; + esac + ;; + +"record-hq") + # Change file extension to mp4 for high quality recordings + VID_HQ="${HOME}/Videos/Recordings/$(date +%Y-%m-%d_%H-%m-%s)-hq.mp4" + + case "$TARGET" in + "stop") + wf-recorder_check + ;; + "selection") + wf-recorder_check + echo "$VID_HQ" >/tmp/recording.txt + notify-send "High Quality Recording" "Starting YouTube-quality recording..." + record_high_quality "$VID_HQ" -g "$(slurp)" + ;; + "eDP-1") + wf-recorder_check + echo "$VID_HQ" >/tmp/recording.txt + notify-send "High Quality Recording" "Starting YouTube-quality recording on eDP-1..." + record_high_quality "$VID_HQ" -a -o eDP-1 + ;; + "HDMI-A-1") + wf-recorder_check + echo "$VID_HQ" >/tmp/recording.txt + notify-send "High Quality Recording" "Starting YouTube-quality recording on HDMI-A-1..." + record_high_quality "$VID_HQ" -a -o HDMI-A-1 + ;; + *) + echo "Error: Invalid record-hq target '$TARGET'" + usage + ;; + esac + ;; + +"record-gif") + # GIF files go to a specific location + GIF="${HOME}/Videos/Recordings/$(date +%Y-%m-%d_%H-%m-%s).gif" + + case "$TARGET" in + "stop") + wf-recorder_check + ;; + "selection") + wf-recorder_check + echo "$GIF" >/tmp/recording.txt + notify-send "GIF Recording" "Starting GIF recording (15 FPS)..." + record_gif "$GIF" -g "$(slurp)" + ;; + "eDP-1") + wf-recorder_check + echo "$GIF" >/tmp/recording.txt + notify-send "GIF Recording" "Starting GIF recording on eDP-1..." + record_gif "$GIF" -o eDP-1 + ;; + "HDMI-A-1") + wf-recorder_check + echo "$GIF" >/tmp/recording.txt + notify-send "GIF Recording" "Starting GIF recording on HDMI-A-1..." + record_gif "$GIF" -o HDMI-A-1 + ;; + *) + echo "Error: Invalid record-gif target '$TARGET'" + usage + ;; + esac + ;; + +"status") + # Check if wf-recorder is running + if pgrep -x "wf-recorder" >/dev/null; then + echo "true" + exit 0 + else + echo "false" + exit 0 + fi + ;; + +"convert") + case "$TARGET" in + "webm") + # Check if ffmpeg is installed + if ! command -v ffmpeg >/dev/null 2>&1; then + notify-send "Error" "ffmpeg is not installed. Please install it to use this feature." + exit 1 + fi + + RECORDING_DIR="${HOME}/Videos/Recordings" + CONVERTED=0 + TOTAL=0 + + for mkv_file in "${RECORDING_DIR}"/*.mkv; do + if [ -f "$mkv_file" ]; then + TOTAL=$((TOTAL + 1)) + webm_file="${mkv_file%.mkv}.webm" + + # Check if webm version doesn't already exist + if [ ! -f "$webm_file" ]; then + # Simpler ffmpeg command with basic settings + ffmpeg -y -i "$mkv_file" -c:v libvpx -b:v 1M -c:a libvorbis "$webm_file" 2>/tmp/ffmpeg_error.log + + if [ $? -eq 0 ]; then + CONVERTED=$((CONVERTED + 1)) + notify-send "Converted to WebM" "$(basename "$mkv_file")" + else + error=$(cat /tmp/ffmpeg_error.log | tail -n 5) + notify-send "Conversion Failed" "Error: $error" + fi + fi + fi + done + + if [ $TOTAL -eq 0 ]; then + notify-send "WebM Conversion" "No MKV files found in Recordings folder" + else + notify-send "WebM Conversion Complete" "Converted $CONVERTED out of $TOTAL MKV files" + fi + ;; + + "iphone") + # Check if ffmpeg is installed + if ! command -v ffmpeg >/dev/null 2>&1; then + notify-send "Error" "ffmpeg is not installed. Please install it to use this feature." + exit 1 + fi + + RECORDING_DIR="${HOME}/Videos/Recordings" + CONVERTED=0 + SKIPPED_IPHONE=0 + SKIPPED_EXISTING=0 + TOTAL_FILES=0 + + for mkv_file in "${RECORDING_DIR}"/*.mkv; do + if [ -f "$mkv_file" ]; then + TOTAL_FILES=$((TOTAL_FILES + 1)) + base_filename=$(basename "$mkv_file") + + # Skip files with "iphone" in the filename + if [[ $base_filename == *"iphone"* ]]; then + SKIPPED_IPHONE=$((SKIPPED_IPHONE + 1)) + continue + fi + + iphone_file="${mkv_file%.mkv}-iphone.mp4" + + # Check if iPhone version doesn't already exist + if [ ! -f "$iphone_file" ]; then + # Simpler ffmpeg command for iPhone compatibility + ffmpeg -y -i "$mkv_file" -vcodec h264 -acodec aac "$iphone_file" 2>/tmp/ffmpeg_error.log + + if [ $? -eq 0 ]; then + CONVERTED=$((CONVERTED + 1)) + notify-send "Converted for iPhone" "$(basename "$mkv_file")" + else + error=$(cat /tmp/ffmpeg_error.log | tail -n 5) + notify-send "Conversion Failed" "Error: $error" + fi + else + SKIPPED_EXISTING=$((SKIPPED_EXISTING + 1)) + fi + fi + done + + if [ $TOTAL_FILES -eq 0 ]; then + notify-send "iPhone Conversion" "No MKV files found in Recordings folder" + else + notify-send "iPhone Conversion Complete" "Converted: $CONVERTED files +Skipped (already iPhone): $SKIPPED_IPHONE files +Skipped (has iPhone version): $SKIPPED_EXISTING files +Total files checked: $TOTAL_FILES" + fi + ;; + + "youtube") + # Check if ffmpeg is installed + if ! command -v ffmpeg >/dev/null 2>&1; then + notify-send "Error" "ffmpeg is not installed. Please install it to use this feature." + exit 1 + fi + + RECORDING_DIR="${HOME}/Videos/Recordings" + CONVERTED=0 + SKIPPED_YOUTUBE=0 + SKIPPED_EXISTING=0 + TOTAL_FILES=0 + + # Process both MKV and MP4 files + for video_file in "${RECORDING_DIR}"/*.{mkv,mp4}; do + if [ -f "$video_file" ]; then + TOTAL_FILES=$((TOTAL_FILES + 1)) + base_filename=$(basename "$video_file") + + # Skip files already marked as YouTube uploads + if [[ $base_filename == *"youtube"* ]]; then + SKIPPED_YOUTUBE=$((SKIPPED_YOUTUBE + 1)) + continue + fi + + # Create YouTube optimized filename + youtube_file="${video_file%.*}-youtube.mp4" + + # Check if YouTube version doesn't already exist + if [ ! -f "$youtube_file" ]; then + notify-send "Converting for YouTube" "Processing: $(basename "$video_file")" + + # YouTube recommended settings: + # - H.264 codec with High profile + # - 1080p or source resolution + # - 60fps or source framerate + # - High bitrate for quality (8-12 Mbps for 1080p60) + # - AAC audio at 384kbps + # - yuv420p pixel format for compatibility + # - Keyframe interval of 2 seconds (GOP) + # - No filters to preserve original colors + + ffmpeg -y -i "$video_file" \ + -c:v libx264 \ + -profile:v high \ + -preset slow \ + -crf 18 \ + -pix_fmt yuv420p \ + -c:a aac \ + -b:a 384k \ + -movflags +faststart \ + "$youtube_file" 2>/tmp/ffmpeg_error.log + + if [ $? -eq 0 ]; then + CONVERTED=$((CONVERTED + 1)) + file_size=$(du -h "$youtube_file" | cut -f1) + notify-send "YouTube Conversion Success" "$(basename "$video_file") → $(basename "$youtube_file") ($file_size)" + else + error=$(cat /tmp/ffmpeg_error.log | tail -n 5) + notify-send "YouTube Conversion Failed" "Error converting $(basename "$video_file"): $error" + fi + else + SKIPPED_EXISTING=$((SKIPPED_EXISTING + 1)) + fi + fi + done + + if [ $TOTAL_FILES -eq 0 ]; then + notify-send "YouTube Conversion" "No video files found in Recordings folder" + else + notify-send "YouTube Conversion Complete" "Converted: $CONVERTED files +Skipped (already YouTube): $SKIPPED_YOUTUBE files +Skipped (has YouTube version): $SKIPPED_EXISTING files +Total files checked: $TOTAL_FILES" + fi + ;; + + "gif") + # Check if ffmpeg is installed + if ! command -v ffmpeg >/dev/null 2>&1; then + notify-send "Error" "ffmpeg is not installed. Please install it to use this feature." + exit 1 + fi + + RECORDING_DIR="${HOME}/Videos/Recordings" + CONVERTED=0 + SKIPPED_GIF=0 + SKIPPED_EXISTING=0 + TOTAL_FILES=0 + + # Process both MKV and MP4 files + for video_file in "${RECORDING_DIR}"/*.{mkv,mp4}; do + if [ -f "$video_file" ]; then + TOTAL_FILES=$((TOTAL_FILES + 1)) + base_filename=$(basename "$video_file") + + # Skip files already GIFs + if [[ $base_filename == *.gif ]]; then + SKIPPED_GIF=$((SKIPPED_GIF + 1)) + continue + fi + + # Create GIF filename + gif_file="${video_file%.*}.gif" + + # Check if GIF version doesn't already exist + if [ ! -f "$gif_file" ]; then + notify-send "Converting to GIF" "Processing: $(basename "$video_file")" + + # Get video dimensions for scaling + width=$(ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=s=x:p=0 "$video_file") + + # Scale down if wider than 800px to keep file size reasonable + if [ "$width" -gt 800 ]; then + scale_filter="scale=800:-1:flags=lanczos," + else + scale_filter="" + fi + + # Create high-quality GIF with optimized palette + ffmpeg -i "$video_file" \ + -vf "${scale_filter}fps=15,split[s0][s1];[s0]palettegen=max_colors=256:stats_mode=diff[p];[s1][p]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle" \ + -loop 0 \ + "$gif_file" 2>/tmp/ffmpeg_error.log + + if [ $? -eq 0 ]; then + CONVERTED=$((CONVERTED + 1)) + send_recording_notification "$gif_file" + else + error=$(cat /tmp/ffmpeg_error.log | tail -n 5) + notify-send "GIF Conversion Failed" "Error converting $(basename "$video_file"): $error" + fi + else + SKIPPED_EXISTING=$((SKIPPED_EXISTING + 1)) + fi + fi + done + + if [ $TOTAL_FILES -eq 0 ]; then + notify-send "GIF Conversion" "No video files found in Recordings folder" + else + notify-send "GIF Conversion Complete" "Converted: $CONVERTED files +Skipped (already GIF): $SKIPPED_GIF files +Skipped (has GIF version): $SKIPPED_EXISTING files +Total files checked: $TOTAL_FILES" + fi + ;; + + *) + echo "Error: Invalid convert format '$TARGET'" + usage + ;; + esac + ;; + +*) + echo "Error: Invalid command '$COMMAND'" + usage + ;; +esac diff --git a/services/screencapture.py b/services/screencapture.py deleted file mode 100644 index 7ae596b..0000000 --- a/services/screencapture.py +++ /dev/null @@ -1,157 +0,0 @@ -import datetime -import os -from fabric.core.service import Property, Service, Signal -from fabric.utils import exec_shell_command, exec_shell_command_async -from gi.repository import Gio, GLib -from loguru import logger - - -class ScreenCapture(Service): - @Signal - def recording(self, value: bool) -> None: ... - - def __init__(self, **kwargs): - self.screenshot_path = GLib.get_home_dir() + "/Pictures/Screenshots/" - self.screenrecord_path = GLib.get_home_dir() + "/Videos/Screencasting/" - os.makedirs(self.screenshot_path, exist_ok=True) - os.makedirs(self.screenrecord_path, exist_ok=True) - - super().__init__(**kwargs) - - def screenshot(self, fullscreen=False, save_copy=True): - time = datetime.datetime.today().strftime("%Y-%m-%d_%H-%M-%S") - file_path = self.screenshot_path + str(time) + ".png" - # hyprshot: --mode [window|output|region], --stdout, --clipboard, --save-path - if save_copy: - command = [ - "hyprshot", - "--mode", - "output" if fullscreen else "region", - "--clipboard", - "--save-path", - file_path, - ] - else: - command = [ - "hyprshot", - "--mode", - "output" if fullscreen else "region", - "--clipboard" - ] - try: - exec_shell_command_async( - " ".join(command), - lambda *_: self.send_screenshot_notification( - file_path=file_path if save_copy else None, - ), - ) - - except Exception as e: - logger.error(f"[SCREENSHOT] Failed to run command: {command}") - - def screencast_start(self, fullscreen=False, audio=False): - if self.is_recording: - logger.error( - "[SCREENRECORD] Another instance of wf-recorder is already running" - ) - return - time = datetime.datetime.today().strftime("%Y-%m-%d_%H-%M-%S") - file_path = self.screenrecord_path + str(time) + ".mp4" - self._current_screencast_path = file_path - area = "" if fullscreen else f"-g '{exec_shell_command('slurp')}'" - audio_flag = "--audio --no-mic" if audio else "" - command = f"wf-recorder --file={file_path} --pixel-format yuv420p {audio_flag} {area}" - exec_shell_command_async(command) - self.emit("recording", True) - - def screencast_stop(self): - exec_shell_command_async("killall -INT wf-recorder") - self.emit("recording", False) - self.send_screencast_notification(self._current_screencast_path) - - def send_screencast_notification(self, file_path): - # TODO: Generate a thumbnail - # exec_shell_command_async(f"totem-video-thumbnailer {file_path} {FABRIC_CACHE}") - - cmd = ["notify-send"] - cmd.extend( - [ - "-A", - "files=Show in Files", - "-A", - "view=View", - "-i", - "camera-video-symbolic", - "-a", - "Fabric Screenshot Utility", - "Screencast Saved", - f"Saved Screencast at {file_path}", - ] - ) - - proc: Gio.Subprocess = Gio.Subprocess.new(cmd, Gio.SubprocessFlags.STDOUT_PIPE) - - def do_callback(process: Gio.Subprocess, task: Gio.Task): - try: - _, stdout, stderr = process.communicate_utf8_finish(task) - except Exception: - logger.error( - f"[SCREENCAST] Failed read notification action with error {stderr}" - ) - return - - match stdout.strip("\n"): - case "files": - exec_shell_command_async(f"xdg-open {self.screenrecord_path}") - case "view": - exec_shell_command_async(f"xdg-open {file_path}") - - proc.communicate_utf8_async(None, None, do_callback) - - def send_screenshot_notification(self, file_path=None): - cmd = ["notify-send"] - cmd.extend( - [ - "-A", - "files=Show in Files", - "-A", - "view=View", - "-A", - "edit=Edit", - "-i", - "camera-photo-symbolic", - "-a", - "Fabric Screenshot Utility", - "-h", - f"STRING:image-path:{file_path}", - "Screenshot Saved", - f"Saved Screenshot at {file_path}", - ] - if file_path - else ["Screenshot Sent to Clipboard"] - ) - - proc: Gio.Subprocess = Gio.Subprocess.new(cmd, Gio.SubprocessFlags.STDOUT_PIPE) - - def do_callback(process: Gio.Subprocess, task: Gio.Task): - try: - _, stdout, stderr = process.communicate_utf8_finish(task) - except Exception: - logger.error( - f"[SCREENSHOT] Failed read notification action with error {stderr}" - ) - return - - match stdout.strip("\n"): - case "files": - exec_shell_command_async(f"xdg-open {self.screenshot_path}") - case "view": - exec_shell_command_async(f"xdg-open {file_path}") - case "edit": - exec_shell_command_async(f"swappy -f {file_path}") - - proc.communicate_utf8_async(None, None, do_callback) - - @Property(bool, "readable", default_value=False) - def is_recording(self): - return False if len(exec_shell_command("pidof wf-recorder")) == 0 else True