kael_share 0.1.2

Share services for Kael
Documentation
use anyhow::{Result, anyhow};

use crate::{
    ReceiverCallback, ShareFileType, ShareResult, ShareSheet, ShareType,
    platform::PlatformShareReceiver,
};

pub(crate) async fn show(sheet: &ShareSheet) -> Result<ShareResult> {
    if !sheet.attachment_paths()?.is_empty() {
        return Err(anyhow!(
            "Windows file and image sharing still needs DataTransferManager integration"
        ));
    }

    if !sheet.is_excluded(ShareType::Mail) {
        if let Some(uri) = sheet.mailto_uri() {
            if open_with_shell(&uri)? {
                return Ok(ShareResult::Completed {
                    activity_type: ShareType::Mail.activity_name().to_string(),
                });
            }
        }
    }

    if !sheet.is_excluded(ShareType::Clipboard) {
        if let Some(body) = sheet.body_text() {
            if copy_to_clipboard(&body)? {
                return Ok(ShareResult::Completed {
                    activity_type: ShareType::Clipboard.activity_name().to_string(),
                });
            }
        }
    }

    if let Some(url) = sheet.all_urls().first() {
        if open_with_shell(url)? {
            return Ok(ShareResult::Completed {
                activity_type: "open".to_string(),
            });
        }
    }

    Ok(ShareResult::Cancelled)
}

pub(crate) fn register_receiver(
    _file_types: &[ShareFileType],
    _callback: ReceiverCallback,
) -> Result<PlatformShareReceiver> {
    Err(anyhow!(
        "share receiver registration is not implemented yet on Windows"
    ))
}

pub(crate) fn support() -> crate::PlatformShareSupport {
    crate::PlatformShareSupport {
        mail: true,
        messages: false,
        airdrop: false,
        clipboard: true,
        social: false,
        print: false,
        receiver_registration: false,
    }
}

fn copy_to_clipboard(text: &str) -> Result<bool> {
    use windows::Win32::{
        Foundation::{HANDLE, HWND},
        System::{
            DataExchange::{CloseClipboard, EmptyClipboard, OpenClipboard, SetClipboardData},
            Memory::{GMEM_MOVEABLE, GlobalAlloc, GlobalLock, GlobalUnlock},
        },
    };

    const CF_UNICODETEXT: u32 = 13;
    let wide: Vec<u16> = text.encode_utf16().chain(Some(0)).collect();

    unsafe {
        OpenClipboard(HWND::default())?;

        if let Err(err) = EmptyClipboard() {
            let _ = CloseClipboard();
            return Err(err.into());
        }

        let allocation = match GlobalAlloc(GMEM_MOVEABLE, std::mem::size_of_val(wide.as_slice())) {
            Ok(alloc) => alloc,
            Err(_) => {
                let _ = CloseClipboard();
                return Ok(false);
            }
        };

        let target = GlobalLock(allocation) as *mut u16;
        if target.is_null() {
            let _ = CloseClipboard();
            return Ok(false);
        }

        target.copy_from_nonoverlapping(wide.as_ptr(), wide.len());
        let _ = GlobalUnlock(allocation);
        SetClipboardData(CF_UNICODETEXT, HANDLE(allocation.0))?;
        CloseClipboard()?;
    }

    Ok(true)
}

fn open_with_shell(target: &str) -> Result<bool> {
    use windows::{
        Win32::{
            Foundation::HWND, UI::Shell::ShellExecuteW, UI::WindowsAndMessaging::SW_SHOWNORMAL,
        },
        core::PCWSTR,
    };

    let operation: Vec<u16> = "open".encode_utf16().chain(Some(0)).collect();
    let target: Vec<u16> = target.encode_utf16().chain(Some(0)).collect();

    unsafe {
        let instance = ShellExecuteW(
            HWND::default(),
            PCWSTR::from_raw(operation.as_ptr()),
            PCWSTR::from_raw(target.as_ptr()),
            PCWSTR::null(),
            PCWSTR::null(),
            SW_SHOWNORMAL,
        );
        Ok(instance.0 as usize > 32)
    }
}