nativeshell 0.1.16

NativeShell Rust package
Documentation
use core::slice;
use std::{ffi::CStr, mem::size_of, ptr::null_mut, u32};

use widestring::WideCStr;
use windows::{
    core::GUID,
    Win32::{
        Foundation::POINT,
        System::{
            Com::{IDataObject, DVASPECT_CONTENT, FORMATETC, STGMEDIUM, TYMED, TYMED_HGLOBAL},
            Memory::{GlobalLock, GlobalSize, GlobalUnlock},
            Ole::{
                ReleaseStgMedium, DROPEFFECT_COPY, DROPEFFECT_LINK, DROPEFFECT_MOVE,
                DROPEFFECT_NONE,
            },
        },
        UI::Shell::DROPFILES,
    },
};

use crate::shell::api_model::DragEffect;

use super::util::as_u8_slice;

use byte_slice_cast::*;

#[allow(non_upper_case_globals)]
pub const CLSID_DragDropHelper: GUID = GUID::from_values(
    0x4657278a,
    0x411b,
    0x11d2,
    [0x83, 0x9a, 0x0, 0xc0, 0x4f, 0xd9, 0x18, 0xd0],
);

pub fn convert_drop_effect_mask(mask: u32) -> Vec<DragEffect> {
    let mut res = Vec::new();

    if mask & DROPEFFECT_COPY == DROPEFFECT_COPY {
        res.push(DragEffect::Copy);
    }
    if mask & DROPEFFECT_MOVE == DROPEFFECT_MOVE {
        res.push(DragEffect::Move);
    }
    if mask & DROPEFFECT_LINK == DROPEFFECT_LINK {
        res.push(DragEffect::Link);
    }
    res
}

pub fn convert_drag_effect(effect: &DragEffect) -> u32 {
    match effect {
        DragEffect::None => DROPEFFECT_NONE,
        DragEffect::Copy => DROPEFFECT_COPY,
        DragEffect::Link => DROPEFFECT_LINK,
        DragEffect::Move => DROPEFFECT_MOVE,
    }
}

pub fn convert_drag_effects(effects: &[DragEffect]) -> u32 {
    let mut res: u32 = 0;
    for e in effects {
        res |= convert_drag_effect(e);
    }
    res
}

pub struct DataUtil {}

impl DataUtil {
    pub fn get_data(object: IDataObject, format: u32) -> windows::core::Result<Vec<u8>> {
        let mut format = Self::get_format(format);

        unsafe {
            let mut medium = object.GetData(&mut format as *mut _)?;

            let size = GlobalSize(medium.Anonymous.hGlobal);
            let data = GlobalLock(medium.Anonymous.hGlobal);

            let v = slice::from_raw_parts(data as *const u8, size);
            let res: Vec<u8> = v.into();

            GlobalUnlock(medium.Anonymous.hGlobal);

            ReleaseStgMedium(&mut medium as *mut STGMEDIUM);

            Ok(res)
        }
    }

    pub fn has_data(object: IDataObject, format: u32) -> bool {
        let mut format = Self::get_format(format);
        unsafe { object.QueryGetData(&mut format as *mut _).is_ok() }
    }

    pub fn extract_files(buffer: Vec<u8>) -> Vec<String> {
        let files: &DROPFILES = unsafe { &*(buffer.as_ptr() as *const DROPFILES) };

        let mut res = Vec::new();
        if { files.fWide }.as_bool() {
            let data = buffer.as_slice()[files.pFiles as usize..]
                .as_slice_of::<u16>()
                .unwrap();
            let mut offset = 0;
            loop {
                let str = WideCStr::from_slice_truncate(&data[offset..]).unwrap();
                if str.is_empty() {
                    break;
                }
                res.push(str.to_string_lossy());
                offset += str.len() + 1;
            }
        } else {
            let data = &buffer.as_slice()[files.pFiles as usize..];
            let mut offset = 0;
            loop {
                let str = CStr::from_bytes_with_nul(&data[offset..]).unwrap();
                let bytes = str.to_bytes();
                if bytes.is_empty() {
                    break;
                }
                res.push(str.to_string_lossy().into());
                offset += bytes.len();
            }
        }
        res
    }

    pub fn extract_url_w(buffer: &[u8]) -> String {
        let data = buffer.as_slice_of::<u16>().unwrap();
        let str = WideCStr::from_slice_truncate(data).unwrap();
        str.to_string_lossy()
    }

    pub fn extract_url(buffer: &[u8]) -> String {
        let str = CStr::from_bytes_with_nul(buffer).unwrap();
        str.to_string_lossy().into()
    }

    pub fn bundle_files(files: &[String]) -> Vec<u8> {
        let mut res = Vec::new();

        let drop_files = DROPFILES {
            pFiles: size_of::<DROPFILES>() as u32,
            pt: POINT { x: 0, y: 0 },
            fNC: false.into(),
            fWide: true.into(),
        };

        let drop_files = unsafe { as_u8_slice(&drop_files) };
        res.extend_from_slice(drop_files);

        for f in files {
            let mut wide: Vec<u16> = f.encode_utf16().collect();
            wide.push(0);
            res.extend_from_slice(wide.as_byte_slice());
        }
        res.extend_from_slice(&[0, 0]);

        res
    }

    pub fn get_format(format: u32) -> FORMATETC {
        Self::get_format_with_tymed(format, TYMED_HGLOBAL)
    }

    pub fn get_format_with_tymed(format: u32, tymed: TYMED) -> FORMATETC {
        FORMATETC {
            cfFormat: format as u16,
            ptd: null_mut(),
            dwAspect: DVASPECT_CONTENT.0 as u32,
            lindex: -1,
            tymed: tymed.0 as u32,
        }
    }
}