1

Тема: AHK v2: Класс для работы с историей буфера обмена Windows 10

Частичная реализация класса Clipboard из Windows UWP для AHK v2. Реализованы методы, нужные для работы с историей буфера обмена Windows 10. Доступные свойства и методы собраны в класс ClipboardHistory, по их названию должно быть понятно, что они делают. Остальные классы являются вспомогательными. Примеры использования в коде.

#Requires AutoHotkey v2.0.2 ; and OS >= Windows 10

if !ClipboardHistory.HistoryEnabled {
    if MsgBox('Clipboard history disabled, want to enable?', 'Clipboard History', 'YN') = 'No'
        ExitApp
    ClipboardHistory.HistoryEnabled := true
}
MsgBox 'Clipboard history enabled'
MsgBox ClipboardHistory.GetHistoryItemText(1), 'First item text'
MsgBox ClipboardHistory.DeleteHistoryItem(1), 'Boolean success status'   ; deletes the first history item and returns boolean success status
MsgBox ClipboardHistory.GetHistoryItemText(1), 'First item text'
MsgBox ClipboardHistory.PutHistoryItemIntoClipboard(2), 'Success status' ; puts the second history item into Clipboard and returns succsess status
MsgBox A_Clipboard, 'Clipboard content'

formats := ''
for fmt in ClipboardHistory.GetAvailableFormats(2) { ; available formats of the second history item
    formats .= fmt . '`n'
}
MsgBox formats, 'Available Formats'

historyItemCount := ClipboardHistory.Count
Loop historyItemCount {
    MsgBox ClipboardHistory.GetHistoryItemText(A_Index), 'Item ' . A_Index . ' of ' . historyItemCount, 'T.7'
}

class ClipboardHistory
{
; properties
    static HistoryEnabled {
        get => CBH_API.HistoryEnabled
        set => CBH_API.HistoryEnabled := value ; boolean
    }
    static Count => CBH_API.GetClipboardHistoryItems()

; methods
    static ClearHistory()                     => CBH_API.ClearHistory()
    static DeleteHistoryItem(index)           => CBH_API.DeleteHistoryItem(index) ; 1 based index
    static GetHistoryItemText(index)          => CBH_API.GetHistoryItemText(index)
    static PutHistoryItemIntoClipboard(index) => CBH_API.PutHistoryItemIntoClipboard(index)
    static GetAvailableFormats(index)         => CBH_API.GetAvailableFormats(index)
}

class CBH_API
{
    /*
        https://is.gd/bYyogJ     Clipboard Class (MSDN)
        https://is.gd/2z2Y9G     Windows.ApplicationModel.DataTransfer.0.h (GitHub)
        https://is.gd/T4Lb7b     asyncinfo.h (GitHub)
    */
    static HistoryEnabled {
        get => IClipboardStatics2.IsHistoryEnabled
        set => RegWrite(!!value, 'REG_DWORD', 'HKCU\SOFTWARE\Microsoft\Clipboard', 'EnableClipboardHistory')
    }

    static ClearHistory() => IClipboardStatics2.ClearHistory()

    static DeleteHistoryItem(index) { ; 1 based
        if !pIClipboardHistoryItem := this.GetClipboardHistoryItemByIndex(index)
            return false
        bool := IClipboardStatics2.DeleteItemFromHistory(pIClipboardHistoryItem)
        ObjRelease(pIClipboardHistoryItem)
        return bool
    }

    static GetHistoryItemText(index) {
        if !DataPackageView := this.GetDataPackageView(index)
            return
        ReadOnlyList := IReadOnlyList(DataPackageView.AvailableFormats)
        textFound := false
        Loop ReadOnlyList.Count
            HSTRING := ReadOnlyList.Item[A_Index - 1]
        until WrtString(HSTRING).GetText() = 'Text' && textFound := true
        if !textFound
            return
        HSTRING := this.Await(DataPackageView.GetTextAsync())
        return WrtString(HSTRING).GetText()
    }

    static GetAvailableFormats(index) {
        if !DataPackageView := this.GetDataPackageView(index)
            return
        ReadOnlyList := IReadOnlyList(DataPackageView.AvailableFormats)
        AvailableFormats := []
        Loop ReadOnlyList.Count {
            HSTRING := ReadOnlyList.Item[A_Index - 1]
            AvailableFormats.Push(WrtString(HSTRING).GetText())
        }
        return AvailableFormats
    }

    static GetDataPackageView(index) {
        if !pIClipboardHistoryItem := this.GetClipboardHistoryItemByIndex(index)
            return
        pIDataPackageView := IClipboardHistoryItem(pIClipboardHistoryItem).Content
        return IDataPackageView(pIDataPackageView)
    }

    static PutHistoryItemIntoClipboard(index) {
        static SetHistoryItemAsContentStatus := ['Success', 'AccessDenied', 'ItemDeleted']
        if !pIClipboardHistoryItem := this.GetClipboardHistoryItemByIndex(index)
            return
        status := IClipboardStatics2.SetHistoryItemAsContent(pIClipboardHistoryItem)
        ObjRelease(pIClipboardHistoryItem)
        return SetHistoryItemAsContentStatus[status + 1]
    }

    static GetClipboardHistoryItemByIndex(index) { ; 1 based
        count := this.GetClipboardHistoryItems(&ReadOnlyList)
        if (count < index) {
            MsgBox 'Index "' . index . '" exceeds items count!'
            return
        }
        return pIClipboardHistoryItem := ReadOnlyList.Item[index - 1]
    }

    static GetClipboardHistoryItems(&ReadOnlyList := '') {
        pIClipboardHistoryItemsResult := this.Await(IClipboardStatics2.GetHistoryItemsAsync())
        ClipboardHistoryItemsResult := IClipboardHistoryItemsResult(pIClipboardHistoryItemsResult)
        pIReadOnlyList := ClipboardHistoryItemsResult.Items
        ReadOnlyList := IReadOnlyList(pIReadOnlyList)
        return ReadOnlyList.Count
    }

    static Await(pIAsyncOperation) {
        static AsyncStatus := ['Started', 'Completed', 'Canceled', 'Error']
        AsyncOperation := IAsyncOperation(pIAsyncOperation)
        pIAsyncInfo := AsyncOperation.QueryIAsyncInfo()
        AsyncInfo := IAsyncInfo(pIAsyncInfo)
        Loop {
            Sleep 10
            status := AsyncStatus[AsyncInfo.Status + 1]
        } until status != 'Started'
        if (status != 'Completed')
            throw OSError('AsyncInfo error, status: "' . status . '"', A_ThisFunc)
        return AsyncOperation.GetResults()
    }
}

class IClipboardStatics2
{
    static __New() {
        static IID_IClipboardStatics2 := '{D2AC1B6A-D29F-554B-B303-F0452345FE02}', VT_UNKNOWN := 0xD
        if !(A_OSVersion ~= '^10\.')
            throw OSError('This class requires Windows 10 or later!', A_ThisFunc)
        
        WrtStr := WrtString('Windows.ApplicationModel.DataTransfer.Clipboard')
        DllCall('Ole32\CLSIDFromString', 'Str', IID_IClipboardStatics2, 'Ptr', CLSID := Buffer(16))
        this.comObj := ComValue(VT_UNKNOWN, WrtStr.GetFactory(CLSID))
        this.ptr := this.comObj.ptr
    }
    static GetHistoryItemsAsync() => (ComCall(6, this, 'UIntP', &pIAsyncOperation := 0), pIAsyncOperation)

    static ClearHistory() => (ComCall(7, this, 'UIntP', &res := 0), res)

    static DeleteItemFromHistory(pIClipboardHistoryItem) => (ComCall(8, this, 'Ptr', pIClipboardHistoryItem, 'UIntP', &res := 0), res)

    static SetHistoryItemAsContent(pIClipboardHistoryItem) => (ComCall(9, this, 'Ptr', pIClipboardHistoryItem, 'UIntP', &res := 0), res)

    static IsHistoryEnabled => (ComCall(10, this, 'UIntP', &res := 0), res)
}

class IClipboardHistoryItemsResult extends _InterfaceBase
{
    Items => (ComCall(7, this, 'PtrP', &pIReadOnlyList := 0), pIReadOnlyList)
}

class IClipboardHistoryItem extends _InterfaceBase
{
    Content => (ComCall(8, this, 'PtrP', &pIDataPackageView := 0), pIDataPackageView)
}

class IDataPackageView extends _InterfaceBase
{
    AvailableFormats => (ComCall(9, this, 'PtrP', &pIReadOnlyList := 0), pIReadOnlyList)

    GetTextAsync() => (ComCall(12, this, 'UIntP', &pIAsyncOperation := 0), pIAsyncOperation)
}

class IReadOnlyList extends _InterfaceBase
{
    Item[index] => (ComCall(6, this, 'Int', index, 'PtrP', &pItem := 0), pItem)

    Count => (ComCall(7, this, 'UIntP', &count := 0), count)
}

class IAsyncOperation extends _InterfaceBase
{
    QueryIAsyncInfo() => ComObjQuery(this, IID_IAsyncInfo := '{00000036-0000-0000-C000-000000000046}')

    GetResults() => (ComCall(8, this, 'PtrP', &pResult := 0), pResult)
}

class IAsyncInfo extends _InterfaceBase
{
    Status => (ComCall(7, this, 'PtrP', &status := 0), status)
}

class _InterfaceBase
{
    __New(ptr) {
        this.comObj := ComValue(VT_UNKNOWN := 0xD, ptr)
        this.ptr := this.comObj.ptr
    }
}

class WrtString
{
    __New(stringOrHandle) {
        if Type(stringOrHandle) = 'Integer'
            this.ptr := stringOrHandle
        else {
            DllCall('Combase\WindowsCreateString', 'WStr', stringOrHandle, 'UInt', StrLen(stringOrHandle), 'PtrP', &HSTRING := 0)
            this.ptr := HSTRING
        }
    }
    __Delete() {
        DllCall('Combase\WindowsDeleteString', 'Ptr', this)
    }
    GetText() {
        buf := DllCall('Combase\WindowsGetStringRawBuffer', 'Ptr', this, 'UIntP', &len := 0, 'Ptr')
        return StrGet(buf, len, 'UTF-16')
    }
    GetFactory(riid) {
        hr := DllCall('Combase\RoGetActivationFactory', 'Ptr', this, 'Ptr', riid, 'PtrP', &pInterface := 0)
        if (hr != 0)
            throw OSError(WrtString.SysError(hr), A_ThisFunc)
        return pInterface
    }
    static SysError(nError := '') {
        static flags := (FORMAT_MESSAGE_ALLOCATE_BUFFER := 0x100) | (FORMAT_MESSAGE_FROM_SYSTEM := 0x1000)
        (nError = '' && nError := A_LastError)
        DllCall('FormatMessage', 'UInt', flags, 'UInt', 0, 'UInt', nError, 'UInt', 0, 'PtrP', &pBuf := 0, 'UInt', 128)
        err := (str := StrGet(pBuf)) = '' ? nError : str
        DllCall('LocalFree', 'Ptr', pBuf)
        return err
    }
}

Связанная тема AHK: UWP, Clipboard Class, в ней выложен вариант для v1 (но с меньшей функциональностью).

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder