wdext 0.1.0

A DbgEng wrapper framework
// SPDX-FileCopyrightText: 2026 takubokudori
// SPDX-License-Identifier: MIT OR Apache-2.0
//! IDebugOutputCallbacks

use crate::*;
use std::{cell::Cell, rc::Rc};
use windows::Win32::Foundation::{E_INVALIDARG, E_NOTIMPL, E_POINTER};
use windows_core::{PCSTR, PCWSTR, implement};
use windy::{WStr, traits::ToWString};

enum_flags! {
    /// [DEBUG_OUTCB_XXX](https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/debug-outcb-xxx)
    pub enum DebugOutCbKind: u32 {
        Text = DEBUG_OUTCB_TEXT,
        Dml = DEBUG_OUTCB_DML,
        ExplicitFlush = DEBUG_OUTCB_EXPLICIT_FLUSH,
    }
}

bitflags::bitflags! {
    /// [DEBUG_OUTCBI_XXX](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/dbgeng/nf-dbgeng-idebugoutputcallbacks2-getinterestmask)
    #[derive(Debug, Copy, Clone, Eq, PartialEq)]
    pub struct DebugOutCbiFlags: u32 {
        const ExplicitFlush = DEBUG_OUTCBI_EXPLICIT_FLUSH;
        const Text = DEBUG_OUTCBI_TEXT;
        const Dml = DEBUG_OUTCBI_DML;
        const AnyFormat = DEBUG_OUTCBI_ANY_FORMAT;
    }
}

bitflags::bitflags! {
    #[derive(Debug, Copy, Clone, Eq, PartialEq)]
    pub struct DebugOutCbfFlags: u32 {
        const CombinedExplicitFlush = DEBUG_OUTCBF_COMBINED_EXPLICIT_FLUSH;
        const DmlHasTags = DEBUG_OUTCBF_DML_HAS_TAGS;
        const DmlHasSpecialCharacters = DEBUG_OUTCBF_DML_HAS_SPECIAL_CHARACTERS;
    }
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub enum DebugOutCbText {
    ExplicitFlush,
    Text(String),
    Dml(String),
}

impl DebugOutCbText {
    pub(crate) fn from_arg(
        which: u32,
        text: &PCWSTR,
    ) -> windows::core::Result<Self> {
        let kind = DebugOutCbKind::try_from(which)
            .map_err(|_| windows::core::Error::from(E_INVALIDARG))?;
        Ok(match kind {
            DebugOutCbKind::ExplicitFlush => Self::ExplicitFlush,
            DebugOutCbKind::Text => {
                if text.is_null() {
                    return Err(E_POINTER.into());
                }
                Self::Text(unsafe { WStr::from_raw(text.0).to_string() })
            }
            DebugOutCbKind::Dml => {
                if text.is_null() {
                    return Err(E_POINTER.into());
                }
                Self::Dml(unsafe { WStr::from_raw(text.0).to_string() })
            }
        })
    }

    pub fn kind(&self) -> DebugOutCbKind {
        match self {
            Self::ExplicitFlush => DebugOutCbKind::ExplicitFlush,
            Self::Text(_) => DebugOutCbKind::Text,
            Self::Dml(_) => DebugOutCbKind::Dml,
        }
    }

    pub fn get_text(&self) -> Option<&str> {
        match self {
            DebugOutCbText::ExplicitFlush => None,
            DebugOutCbText::Text(s) | DebugOutCbText::Dml(s) => Some(s),
        }
    }
}

/// Trait for IDebugOutputCallbacks.
#[allow(unused_variables)]
pub trait DebugOutputCallbacksHandler {
    /*
    // IDebugOutputCallbacks2 does not call this function.
    fn output(
        &self,
        mask: DebugOutputFlags,
        text: String,
    ) -> windows::core::Result<()> {
        Ok(())
    }
    */

    // IDebugOutputCallbacks2
    fn get_interest_mask(&self) -> windows::core::Result<DebugOutCbiFlags> {
        Ok(DebugOutCbiFlags::all())
    }

    fn output2(
        &self,
        flags: DebugOutCbfFlags,
        arg: DebugOutputFlags,
        text: &DebugOutCbText,
    ) -> windows::core::Result<()> {
        Ok(())
    }
}

impl_debug_interface!(
    DebugOutputCallbacks,
    DebugOutputCallbacksRef,
    IDebugOutputCallbacks2,
    IDebugOutputCallbacks
);

impl From<DebugOutputCallbacks> for IDebugOutputCallbacks {
    fn from(value: DebugOutputCallbacks) -> Self { value.0.cast().unwrap() }
}

impl From<DebugOutputCallbacks> for IDebugOutputCallbacks2 {
    fn from(value: DebugOutputCallbacks) -> Self { value.0 }
}

impl DebugOutputCallbacksHandler for DebugOutputCallbacks {
    /*
    fn output(
        &self,
        mask: DebugOutputFlags,
        text: String,
    ) -> windows::core::Result<()> {
        unsafe {
            let text = text.to_astring_lossy();
            self.0.Output(mask.bits(), PCSTR(text.as_u8_ptr()))
        }
    }
    */

    fn get_interest_mask(&self) -> windows::core::Result<DebugOutCbiFlags> {
        unsafe {
            Ok(DebugOutCbiFlags::from_bits_retain(
                self.0.GetInterestMask()?,
            ))
        }
    }

    fn output2(
        &self,
        flags: DebugOutCbfFlags,
        arg: DebugOutputFlags,
        text: &DebugOutCbText,
    ) -> windows::core::Result<()> {
        let which = text.kind();
        let text = text.get_text().map(|x| x.to_wstring());
        let text_ptr = text.as_ref().map_or(std::ptr::null(), |x| x.as_ptr());
        unsafe {
            self.0.Output2(
                which as u32,
                flags.bits(),
                arg.bits() as u64,
                PCWSTR(text_ptr),
            )
        }
    }
}

#[implement(IDebugOutputCallbacks, IDebugOutputCallbacks2)]
pub struct DebugOutputCallbacksAdapter {
    pub callbacks: Rc<dyn DebugOutputCallbacksHandler>,
    poisoned: Cell<bool>,
}

impl DebugOutputCallbacksAdapter {
    pub fn new(callbacks: Rc<dyn DebugOutputCallbacksHandler>) -> Self {
        Self {
            callbacks,
            poisoned: Cell::new(false),
        }
    }

    pub fn as_handler(&self) -> &dyn DebugOutputCallbacksHandler {
        self.callbacks.as_ref()
    }

    pub fn into_callbacks(self) -> DebugOutputCallbacks {
        let interface: IDebugOutputCallbacks2 = self.into();
        interface.into()
    }

    impl_callbacks_catch_unwind!();
}

#[allow(non_snake_case)]
impl IDebugOutputCallbacks_Impl for DebugOutputCallbacksAdapter_Impl {
    fn Output(&self, _mask: u32, _text: &PCSTR) -> windows::core::Result<()> {
        Err(E_NOTIMPL.into())
        /*
        self.0
            .output(DebugOutputFlags::from_bits_retain(mask), unsafe {
                AStr::from_raw(text.0).to_string_lossy()
            })
        */
    }
}

#[allow(non_snake_case)]
impl IDebugOutputCallbacks2_Impl for DebugOutputCallbacksAdapter_Impl {
    fn Output(&self, _mask: u32, _text: &PCSTR) -> windows::core::Result<()> {
        Err(E_NOTIMPL.into())
    }

    fn GetInterestMask(&self) -> windows::core::Result<u32> {
        self.__catch_unwind(|| Ok(self.callbacks.get_interest_mask()?.bits()))
    }

    fn Output2(
        &self,
        which: u32,
        flags: u32,
        arg: u64,
        text: &PCWSTR,
    ) -> windows::core::Result<()> {
        self.__catch_unwind(|| {
            let text = DebugOutCbText::from_arg(which, text)?;
            let arg = u32::try_from(arg)
                .map_err(|_| windows::core::Error::from(E_INVALIDARG))?;
            self.callbacks.output2(
                DebugOutCbfFlags::from_bits_retain(flags),
                DebugOutputFlags::from_bits_retain(arg),
                &text,
            )
        })
    }
}

impl From<Rc<dyn DebugOutputCallbacksHandler>> for DebugOutputCallbacksAdapter {
    fn from(value: Rc<dyn DebugOutputCallbacksHandler>) -> Self {
        Self {
            callbacks: value,
            poisoned: Cell::new(false),
        }
    }
}

#[allow(unused_variables)]
pub trait DebugOutputCallbacksWideHandler {
    fn output(
        &self,
        mask: DebugOutputFlags,
        text: String,
    ) -> windows::core::Result<()> {
        Ok(())
    }
}

impl_debug_interface!(
    DebugOutputCallbacksWide,
    DebugOutputCallbacksWideRef,
    IDebugOutputCallbacksWide
);

impl DebugOutputCallbacksWideHandler for DebugOutputCallbacksWide {
    fn output(
        &self,
        mask: DebugOutputFlags,
        text: String,
    ) -> windows::core::Result<()> {
        let text = text.to_wstring();
        unsafe { self.0.Output(mask.bits(), PCWSTR(text.as_ptr())) }
    }
}

#[implement(IDebugOutputCallbacksWide)]
pub struct DebugOutputCallbacksWideAdapter {
    pub callbacks: Rc<dyn DebugOutputCallbacksWideHandler>,
    poisoned: Cell<bool>,
}

impl DebugOutputCallbacksWideAdapter {
    pub fn new(callbacks: Rc<dyn DebugOutputCallbacksWideHandler>) -> Self {
        Self {
            callbacks,
            poisoned: Cell::new(false),
        }
    }

    pub fn as_handler(&self) -> &dyn DebugOutputCallbacksWideHandler {
        self.callbacks.as_ref()
    }

    pub fn into_callbacks(self) -> DebugOutputCallbacksWide {
        self.into_interface().into()
    }

    pub fn into_interface(self) -> IDebugOutputCallbacksWide { self.into() }

    impl_callbacks_catch_unwind!();
}

#[allow(non_snake_case)]
impl IDebugOutputCallbacksWide_Impl for DebugOutputCallbacksWideAdapter_Impl {
    fn Output(&self, mask: u32, text: &PCWSTR) -> windows::core::Result<()> {
        self.__catch_unwind(|| {
            if text.is_null() {
                return Err(E_POINTER.into());
            }
            self.callbacks
                .output(DebugOutputFlags::from_bits_retain(mask), unsafe {
                    WStr::from_raw(text.0).to_string()
                })
        })
    }
}

impl<T: DebugOutputCallbacksWideHandler + 'static> From<Rc<T>>
    for DebugOutputCallbacksWideAdapter
{
    fn from(value: Rc<T>) -> Self {
        Self {
            callbacks: value,
            poisoned: Cell::new(false),
        }
    }
}