MS Windows Clipboard Woes

Mindwatering Incorporated

Author: Tripp W Black

Created: 07/20/2024 at 02:49 PM

 

Category:
Notes Developer Tips
LotusScript, Bugs/Workarounds

Issue 1:
With the 64-bit Notes 12.0.x client, after upgrading to MS Windows 11, the LotusScript UI front-end copy to clipboard are no longer working and hanging notes (spinning forever cursor wheel). If you create a VM with only two processor cores, the issue generally is not seen with the first copy-and-paste. If you increase the amount of text from several lines to 20 to 30 lines and perform the Lotusscript UI call, the hang potential increases. If you have a VM or workstation with lots of processors (e.g. 8 or more), the hang issue occurs 100% of the time at the second use.

The issue cannot be reproduced on MS Windows 10. It is a MS Windows 11 specific bug.

Workaround:
HCL had a sample 64-bit workaround of using the MS native DLLs directly for 64-bit. Based on their sample, we created this GetCopyWinClipboard library code included at the bottom of this page. We did NOT verify the 32-bit functionality because we no longer have 32bit MS Windows clients which to test.


Issue 2:
Clipboard does not work after days running and sleep/hibernation/screensaver.
(Not in an issue in MS Win NT, Issue persists in Windows XP until FP 3, and Windows 7 through 11)

- Workaround 1:
The very old Shift+Insert copy and paste still work in XP and in early MS Win 7.


- Workaround 1:
Disable all Screensavers and Sleep/Hibernation. Maybe not great for the energy bill, but at least the operating system clipboard works.


- Workaround 2:
In Windows 11, disable the "Connected Devices Platform Service)
Computer Management --> Services --> Connected Devices Platform Service --> Start-up Type change from Automatic (Delayed) to Disabled.

(source: answers.microsoft.com forum 4d89fe8-0336-48ba-9296-0adcecddbd98. Various sites have lifted the content, which HCL found.)

Note:
This service is used with "smart devices" to sync data to those devices, and the MS cloud.


- Workaround 3:
Replace the stock LotusScript uiDoc.Copy calls with the 32-bit and 64-bit MS Windows clipboard libraries.

WARNING:
- 2025/7 update & 2026/03 update:
- This stopped working in Windows 11 64-bit around Notes 14.0.1, or 14.0 FP.x. Based on what we can see it broke for multiple clients w/in a few months ago and the issue has been "worsening".
Since most of the Notes clients are across multiple versions (12.0.1 FPx through 14.5 FP1), it appears to be Notes specific bug, but a new incompatibility in the Clipboard library these Windows cPointer calls/locations. Worse is that this code will work perfectly a few times, and then start copying to clipboard completely different contents, and subsequently on the next try or two, the Notes client will crash. After restarting the Notes client, the calls will work again for a time or few, and then crash the client again. Manually clearing the clipboard contents after the issue does not help, these cPointers will still pull non-current clipboard contents which supposedly no longer exist since the clipboard contents were emptied/cleared.
- Thanks to others and Glen U. at atnotes.de for providing back in March through May 2025 the following new workaround.

Copy to Clipboard Code Snippet

(Options)
Use "GetCopyWinClipboard"


Click
...
   Dim strToClip as String					' working string of collected string content joined together with Chr$(10)
...

	strToClip = Join(fullnmLst(), Chr$(10) & "") 
	
	
	Select Case s.Platform
		
	Case "Windows/64"
		' ms win11 is crashing w/high CPU copy-to-clipboard, switching to 64-bit ptr C-API method
		strFromClip = GetClipboard()
		Msgbox "Overwriting existing contents: " & strFromClip
		Call SetClipboard(strToClip)
		Msgbox "Copied to 64-bit MS Windows: " & strToClip
		
	Case Else
		' use stock UI objects
		Print "Composing document vi UI objects . . ."	
		' create/open Temporary form
		Set uiDoc = w.ComposeDocument("","","ToClp")
		Sleep 1
		If (uiDoc Is Nothing) Then
		' skip, failure
			Msgbox "Failure creating doc to paste value",, "Aborted"
			Exit Sub
		End If
		Print "Copying to field for copy-and-paste..."
		' copy the value to the field	
		Call uidoc.FieldAppendText("FldToClpBd",strToClip)
		Sleep 1
		Print "Selecting text for clipboard..."
		'copy the value to the clipboard	
		Call uidoc.GotoField( "FldToClpBd" )
		Call uidoc.SelectAll
		Print "Copying to clipboard..."
		Call uidoc.Copy
		Print "Closing temporary form..."
		Sleep 1
		' clear form
		Call uiDoc.Close(True)
		Set uidoc = Nothing
		' done UI
	End Select
...


Click twistie to view library contents:
Hide details for GetCopyWinClipboard LotusScript Library:GetCopyWinClipboard LotusScript Library:

GetCopyWinClipboard LotusScript Library:

%REM
	Library GetCopyWinClipboard
	Created Jun 3, 2024 by MW Admin/Mindwatering
	Description: Used w/32-bit or w/64-bit copy to clipboard
%END REM
Option Public
Option Declare


'dataformat ID for ANSI text with ending null (\0). CR(13)/ LF(10) are for end of line.
Const CF_UNICODETEXT = 13
Const OS_TRANSLATE_UNICODE_TO_LMBCS = 23
Private Const CF_TEXT = 1
Private Const GMEM_MOVABLE = &H2&
Private Const GMEM_DDESHARE = &H2000&

'** 32-bit API calls
Declare Function OpenClipboard Lib "user32.dll" (ByVal hwnd As Long) As Long
Declare Function CloseClipboard Lib "user32.dll" () As Long
Declare Function GetClipboardData Lib "user32.dll" (ByVal wFormat As Long) As Long
Declare Function GlobalLock Lib "kernel32" (ByVal hMem As Long) As Long
Declare Function GlobalUnlock Lib "kernel32" (ByVal hMem As Long) As Long
Declare Function GlobalSize Lib "kernel32" (ByVal hMem As Long) As Long
Declare Function OSTranslateFromPtr Lib "nnotes.dll" Alias "OSTranslate" (ByVal mode As Integer,ByVal strIn As Long,ByVal lenIn As Integer,ByVal strOut As LMBCS String, ByVal lenOut As Integer ) As Integer

Declare Private Function GlobalAlloc Lib "kernel32" (ByVal wFlags As Long, ByVal dwBytes As Long) As Long
Declare Private Function GlobalFree Lib "kernel32" (ByVal hMem As Long) As Long
Declare Private Function EmptyClipboard Lib "user32" () As Long
Declare Private Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal strDest As Any, _
ByVal lpSource As Any, ByVal Length As Any)
Declare Private Function SetClipboardData Lib "user32" (ByVal wFormat As Long, ByVal hData As Long) As Long

'** 64-bit API calls
Declare Function OpenClipboard_64 Lib "user32.dll" Alias "OpenClipboard" (ByVal hwnd As Double) As Long '** hwnd is a window handle
Declare Function GetClipboardData_64 Lib "user32.dll" Alias "GetClipboardData" (ByVal wFormat As Long) As Double '** returns a memory handle
Declare Function CloseClipboard_64 Lib "user32.dll" Alias "CloseClipboard" () As Long
Declare Function GlobalLock_64 Lib "kernel32.dll" Alias "GlobalLock" (ByVal hMem As Double) As Double '** hMem is a memory handle, returns a pointer
Declare Function GlobalUnlock_64 Lib "kernel32.dll" Alias "GlobalUnlock" (ByVal hMem As Double) As Long '** hMem is a memory handle, returns a BOOL
Declare Function GlobalSize_64 Lib "kernel32.dll" Alias "GlobalSize" (ByVal hMem As Double) As Long '** hMem is a memory handle, returns a size
Declare Function OSTranslateFromPtr_64 Lib "nnotes.dll" Alias "OSTranslate" ( ByVal mode As Integer, ByVal strIn As Double, _ '** strIn is a string pointer
ByVal lenIn As Integer, _
ByVal strOut As LMBCS String, _
ByVal lenOut As Integer ) As Integer

Declare Private Function GlobalAlloc_64 Lib "kernel32" Alias "GlobalAlloc" (ByVal wFlags As Long, ByVal dwBytes As Long) As Double ' returns a memory handle
Declare Private Function GlobalFree_64 Lib "kernel32" Alias "GlobalFree" (ByVal hMem As Double) As Double 'hmem is a memory handle, returns a pointer
Declare Private Function EmptyClipboard_64 Lib "user32" Alias "EmptyClipboard" () As Long
Declare Private Sub MoveMemory_64 Lib "kernel32" Alias "RtlMoveMemory" (ByVal strDest As Double, _
ByVal lpSource As String, ByVal Length As Long)'lpSource is a pointers
Declare Private Function SetClipboardData_64 Lib "user32" Alias "SetClipboardData" (ByVal wFormat As Long, ByVal hData As Double) As Double 'hData is a handle and it returns a handle

Sub Initialize
	' WARNING - Make sure you set the pointer in order for MoveMemory to not crash Notes
End Sub



%REM
	Function GetClipboard
	Description: Retrieves the current clipboard contents for use by the calling code
%END REM
Function GetClipboard() As String
	Dim session As New NotesSession
	If (session.Platform = "Windows/64") Then
		GetClipboard = GetClipboard64()
		Exit Function
	End If
	Dim glbHandle As Long
	Dim cbPointer As Long
	Dim cbPointerLen As Long
	Dim cbString As String
	If OpenClipboard(0) Then
		glbHandle = GetClipboardData(CF_UNICODETEXT)
		cbPointer = GlobalLock(glbHandle)
		cbPointerLen = GlobalSize(glbHandle)
		cbString = Space(cbPointerLen)
		Call OSTranslateFromPtr( OS_TRANSLATE_UNICODE_TO_LMBCS, _
		cbPointer, cbPointerLen, cbString, cbPointerLen )
		cbString = StrLeft(cbString, Chr(0))
		Call GlobalUnlock(glbHandle)
		Call CloseClipboard()
	End If
	GetClipboard = cbString
End Function


Function GetClipboard64() As String
	Dim session As New NotesSession
	session.UseDoubleAsPointer = True
	Dim glbHandle_64 As Double
	Dim cbPointer_64 As Double
	Dim cbPointerLen As Long
	Dim cbString As String
	If OpenClipboard_64(0) Then
		glbHandle_64 = GetClipboardData_64(CF_UNICODETEXT)
		cbPointer_64 = GlobalLock_64(glbHandle_64)
		cbPointerLen = GlobalSize_64(glbHandle_64)
		cbString = Space(cbPointerLen)
		Call OSTranslateFromPtr_64( OS_TRANSLATE_UNICODE_TO_LMBCS, _
		cbPointer_64, cbPointerLen, cbString, cbPointerLen )
		cbString = StrLeft(cbString, Chr(0))
		Call GlobalUnlock_64(glbHandle_64)
		Call CloseClipboard_64()
	End If
	GetClipboard64 = cbString
	session.UseDoubleAsPointer = False
End Function
%REM
	Sub SetClibboard
	Description: Set the clipboard contents for transfer to another program
%END REM
Sub SetClipboard(cbstr As String)
	Dim lSize As Long
	Dim hMem As Long
	Dim pMemory As Long
	Dim temp As Variant
	Dim session As New NotesSession
	If (session.Platform = "Windows/64") Then
		Call SetClipboard64(cbstr)
		Exit Sub
	End If
	
	lSize = Len(cbstr)+1
	hMem = GlobalAlloc(GMEM_MOVABLE Or GMEM_DDESHARE, lSize)
	If hMem = 0 Or IsNull(hMem) Then Exit Sub
	pMemory = GlobalLock(hMem)
	If pMemory = 0 Or IsNull(pMemory) Then 
		GlobalFree(hMem)
		Exit Sub
	End If
	Call MoveMemory(pMemory, cbstr, lSize)
	Call GlobalUnlock(hMem)
	If (OpenClipboard(0&) <> 0) Then
		If (EmptyClipboard() <> 0) Then
			temp = SetClipboardData(CF_TEXT, hMem)
		End If
		temp = CloseClipboard()
	End If
	GlobalFree(hMem)
End Sub
Sub SetClipboard64(cbstr As String)
	Dim session As New NotesSession
	session.UseDoubleAsPointer = True
	
	' Dim lSize As Double 	' changed to and from per HCL
	Dim lSize As Long
	Dim hMem As Double
	Dim pMemory As Double
	Dim temp As Double
	
	lSize = Len(cbstr)+1
	hMem = GlobalAlloc_64(GMEM_MOVABLE Or GMEM_DDESHARE, lSize)
	If hMem = 0 Or IsNull(hMem) Then Exit Sub
	pMemory = GlobalLock_64(hMem)
	If pMemory = 0 Or IsNull(pMemory) Then 
		GlobalFree_64(hMem)
		Exit Sub
	End If
	Call MoveMemory_64(pMemory, cbstr, lSize)
	Call GlobalUnlock_64(hMem)
	If (OpenClipboard_64(0&) <> 0) Then
		If (EmptyClipboard_64() <> 0) Then
			temp = SetClipboardData_64(CF_TEXT, hMem)
		End If
		temp = CloseClipboard_64()
	End If
	GlobalFree_64(hMem)
	session.UseDoubleAsPointer = False
End Sub




- Workaround 4:
2025/10 - New code we are trying that is working with a client so far. The new MS Windows objects are part of the MSXML2 and WinHTTP libraries.
It is working with one client w/Notes R9.0.1, R10.0 R12.0.2, and R14.0.1. Just like workaround 3, this code is for MS Windows only.

Step1: Add the following 3 items (2 subs and 1 function) to the GetCopyWinClipBoard LotusScript library of Workaround 3 above:

Sub ObjClearClipBoardText()
' notes:
' htmlfile is part of the MSXML2 and the WinHTTP libraries. In VB, the calls would be:
' Dim HTMLDoc As Object 'MSHTML.HTMLDocument
' Set HTMLDoc = CreateObject("htmlfile")
Dim obj As Variant
Set obj = CreateObject("htmlfile")
Call obj.ParentWindow.ClipboardData.clearData("Text")
End Sub

Sub ObjSetClipboardText(ByVal incomingTxt As Variant)
Dim obj As Variant
Set obj = CreateObject("htmlfile")
Call obj.ParentWindow.ClipboardData.SetData("Text", incomingTxt)
End Sub

Function ObjGetClipBoardText() As String
Dim obj As Variant

On Error GoTo FErrorHandler

Set obj = CreateObject("htmlfile")
ObjGetClipBoardText = obj.ParentWindow.ClipboardData.getData("Text")
Exit Function

FErrorHandler:
Print "(GetCopyWinClipboard - ObjGetClipBoardText) Unexpected failure: " & CStr(Err) & ", " & Error$ & ", on line: " & CStr(Erl) & "."
Resume Next
End Function


Step2: Update the if/then case section outlined in Workaround 3 and try the new code:
...
Case "Windows/64"
' ms win11 is crashing w/high CPU copy-to-clipboard, switching to 64-bit ptr C-API method
strFromClip = ObjGetClipBoardText()
' Msgbox "Overwriting existing contents: " & strFromClip
Call ObjSetClipboardText(strToClip)
' Msgbox "Copied to 64-bit MS Windows: " & strToClip
...




previous page

×