|
# frozen_string_literal: true
|
|
|
|
# Copyright (C) 2020 Fitmap, Shenzhen, Guangdong, China
|
|
# mailto:Fitmap <fitmap@qq.com>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
require 'fiddle/import'
|
|
|
|
# Clipboard only for Windows, can use both in x86 and x64
|
|
# size_t and handles must use void* (NOT uint) in Fiddle to fit the pointer size of x86 or x64
|
|
# if use size_t as return value, you need add to_i to it.
|
|
module Clipboard
|
|
module_function
|
|
|
|
extend Fiddle::Importer
|
|
include Fiddle::CParser
|
|
|
|
dlload 'kernel32', 'user32'
|
|
|
|
# HWND GetClipboardOwner();
|
|
extern 'void* GetClipboardOwner()'
|
|
|
|
# HWND GetClipboardViewer();
|
|
extern 'void* GetClipboardViewer()'
|
|
|
|
# HWND GetDesktopWindow();
|
|
extern 'void* GetDesktopWindow()'
|
|
|
|
# BOOL OpenClipboard(
|
|
# _In_opt_ HWND hWndNewOwner
|
|
# );
|
|
extern 'int OpenClipboard(void*)'
|
|
|
|
# BOOL CloseClipboard();
|
|
extern 'int CloseClipboard()'
|
|
|
|
# BOOL EmptyClipboard();
|
|
extern 'int EmptyClipboard()'
|
|
|
|
# Predefined Clipboard Formats
|
|
CF_UNICODETEXT = 13
|
|
|
|
# HANDLE GetClipboardData(
|
|
# _In_ UINT uFormat
|
|
# );
|
|
extern 'void* GetClipboardData(uint)'
|
|
|
|
# HANDLE SetClipboardData(
|
|
# _In_ UINT uFormat,
|
|
# _In_opt_ HANDLE hMem
|
|
# );
|
|
extern 'void* SetClipboardData(uint, void*)'
|
|
|
|
# Global Memory Flags
|
|
GMEM_FIXED = 0x0000 # Don't use GMEM_MOVEABLE = 0x0002(need GlobalLock and GlobalUnlock)
|
|
|
|
# HGLOBAL GlobalAlloc(
|
|
# _In_ UINT uFlags,
|
|
# _In_ SIZE_T dwBytes
|
|
# );
|
|
extern 'void* GlobalAlloc(uint, void*)'
|
|
|
|
# SIZE_T GlobalSize(
|
|
# _In_ HGLOBAL hMem
|
|
# );
|
|
extern 'void* GlobalSize(void*)'
|
|
|
|
# void RtlCopyMemory(
|
|
# _In_ PVOID Destination,
|
|
# _In_ const VOID *Source,
|
|
# _In_ SIZE_T Length
|
|
# );
|
|
extern 'void RtlCopyMemory(void*, const void*, void*)'
|
|
|
|
# DWORD GetLastError();
|
|
extern 'uint GetLastError()'
|
|
|
|
def show(_str)
|
|
# puts _str # uncomment only when debug
|
|
nil
|
|
end
|
|
|
|
def clip_hwnd
|
|
hwnd = GetClipboardOwner()
|
|
show("#{func} GetClipboardOwner fail! Error=#{GetLastError()}") if hwnd.null?
|
|
hwnd.null? ? nil : hwnd
|
|
end
|
|
|
|
def view_hwnd
|
|
hwnd = GetClipboardViewer()
|
|
show("#{func} GetClipboardViewer fail! Error=#{GetLastError()}") if hwnd.null?
|
|
hwnd.null? ? nil : hwnd
|
|
end
|
|
|
|
def desk_hwnd
|
|
GetDesktopWindow()
|
|
end
|
|
|
|
def find_hwnd
|
|
clip_hwnd || view_hwnd || desk_hwnd
|
|
end
|
|
|
|
# Sometimes OpenClipboard will fail without any error, so we need retry
|
|
def open_cb(func)
|
|
999.times do
|
|
hwnd = find_hwnd
|
|
next unless hwnd
|
|
|
|
break unless OpenClipboard(hwnd).zero?
|
|
|
|
show("#{func} OpenClipboard fail! Error=#{GetLastError()}")
|
|
end
|
|
end
|
|
|
|
def empty_cb(func)
|
|
999.times do
|
|
break unless EmptyClipboard().zero?
|
|
|
|
show("#{func} EmptyClipboard fail! Error=#{GetLastError()}")
|
|
end
|
|
end
|
|
|
|
def close_cb(func)
|
|
999.times do
|
|
break unless CloseClipboard().zero?
|
|
|
|
show("#{func} CloseClipboard fail! Error=#{GetLastError()}")
|
|
end
|
|
end
|
|
|
|
def move_it(text)
|
|
text_16le = "#{text}\u0000".encode(Encoding::UTF_16LE) # must append \u0000 for text
|
|
len = text_16le.bytesize
|
|
hmem = GlobalAlloc(GMEM_FIXED, len)
|
|
show("#{__callee__} GlobalAlloc fail! Error=#{GetLastError()}") if hmem.null?
|
|
RtlCopyMemory(hmem, text_16le, len)
|
|
hmem
|
|
end
|
|
|
|
def save_it(hmem)
|
|
hndl = SetClipboardData(CF_UNICODETEXT, hmem)
|
|
show("#{__callee__} SetClipboardData Error=#{GetLastError()} hmem=#{hmem.to_i} != hndl=#{hndl.to_i}") if hndl != hmem
|
|
end
|
|
|
|
def copy(text)
|
|
open_cb(__callee__)
|
|
empty_cb(__callee__)
|
|
hmem = move_it(text)
|
|
save_it(hmem)
|
|
close_cb(__callee__)
|
|
end
|
|
|
|
def clear
|
|
open_cb(__callee__)
|
|
empty_cb(__callee__)
|
|
close_cb(__callee__)
|
|
end
|
|
|
|
def load_it(hmem)
|
|
len = GlobalSize(hmem).to_i
|
|
show("#{__callee__} GlobalSize Error=#{GetLastError()} len=#{len}") if len.zero?
|
|
len -= 2 # needn't copy tail \u0000
|
|
text = ' '.encode(Encoding::UTF_16LE) * (len / 2)
|
|
RtlCopyMemory(text, hmem, len)
|
|
text.encode(Encoding::UTF_8)
|
|
end
|
|
|
|
def paste
|
|
open_cb(__callee__)
|
|
hmem = GetClipboardData(CF_UNICODETEXT)
|
|
text = hmem.null? ? '' : load_it(hmem)
|
|
close_cb(__callee__)
|
|
text
|
|
end
|
|
end
|