Project

General

Profile

Bug #17934 » clipboard.Rb

fitmap (Justin Peal), 06/03/2021 10:12 AM

 
# 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
(1-1/2)