Due to Safari's security restrictions, accessing clipboard content programmatically triggers a native paste confirmation pop-up. This disrupts the user experience when trying to synchronize the local client's clipboard with the Cloud Phone's clipboard. This topic explains how to implement a custom solution using the Web SDK to seamlessly paste locally copied text into a Cloud Phone session running in Safari.
Solution overview
Add a custom clipboard button to your local client's UI. Clicking this button opens a pop-up input box.
The user pastes the content they want to send to the Cloud Phone into this pop-up input box.
Clicking a "Paste Now" button within the pop-up synchronizes the content to both the Cloud Phone's clipboard and its active input field.
Implementation steps
Cloud Phone SDK parameter configuration
Set
readClipboardDataByUserto true: Enable this parameter to prevent the SDK from attempting to read the clipboard automatically, giving you control over the process.Set
useCustomImeto true: Enable the local Input Method Editor (IME). This is required to programmatically insert the content into the Cloud Phone's active input field.
// Check if the browser is Safari
function isSafari() {
return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
}
// Main control configuration. Disable automatic clipboard reading by the SDK and enable the local IME.
connConfig = {..., readClipboardDataByUser:isSafari(), useCustomIme:true};
var appInfo = {
...,
connConfig: connConfig,
...
};
var sessionParam = {
...,
appInfo: appInfo,
....
};
var wuyingSdk = Wuying.WebSDK;
session = wuyingSdk.createSession('appstream', sessionParam);
// Thumbnail configuration. Enable the local IME.
this.thumbnail = new window.Wuying.ThumbnailSDK({
...,
connectionConfig: {
useCustomIme: true,
},
},
{
onConnected: (data) => {
this.thumbnail.session.getLocalConfig().setClipboardEnabled(true);
},
onDisConnected: (data) => {},
onThumbnailData: (url) => {},
}
);Create the pop-up input box
In your client application, create a clipboard button that, when clicked, displays a dialog box designed to handle clipboard interaction. You can refer to the following code sample:
function showInput() {
showCustomDialog();
// Click the modal overlay to close the dialog box
document.querySelector('.modal-overlay').addEventListener('click', function (e) {
if (e.target === this) {
closeModal();
}
});
// Press the ESC key to close the dialog box
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') {
closeModal();
}
});
}
function closeModal() {
console.log('closeModal');
const overlay = document.querySelector('.modal-overlay');
if (overlay) {
overlay.style.display = 'none';
overlay.remove();
}
}
function submitContent() {
const content = document.querySelector('.input-field').value.trim();
if (!content) {
alert('Please enter content');
return;
}
// Sync content to the cloud phone
sendMsgToCloudPhoneOperation(content);
closeModal();
}
function showCustomDialog() {
// Create the modal background
const overlay = document.createElement('div');
overlay.className = 'modal-overlay';
// Ensure the modal captures all events and is displayed at the highest level
overlay.style.pointerEvents = 'all';
overlay.style.zIndex = '9999';
// Prevent events from propagating to underlying layers
const stopPropagation = function(e) {
e.stopPropagation();
};
overlay.addEventListener('contextmenu', stopPropagation, true);
// Add event listeners to intercept all keyboard events
overlay.addEventListener('keydown', stopPropagation, true);
overlay.addEventListener('keyup', stopPropagation, true);
overlay.addEventListener('keypress', stopPropagation, true);
overlay.innerHTML = `
<div class="modal">
<div class="modal-header">
<h2 class="modal-title">Clipboard</h2>
<button class="close-btn" onclick="closeModal()">×</button>
</div>
<div class="input-container">
<textarea
class="input-field"
placeholder="Enter content"
maxlength="500"
></textarea>
</div>
<button class="submit-btn" onclick="submitContent()">
Paste Now
</button>
</div>
`;
document.body.appendChild(overlay);
// Focus the textarea when the modal opens to ensure keyboard events are captured
setTimeout(() => {
const textarea = overlay.querySelector('.input-field');
if (textarea) {
textarea.focus();
}
}, 100);
}Send the copied text to the cloud phone
Trigger the sync task
function submitContent() {
const content = document.querySelector('.input-field').value.trim();
if (!content) {
alert('Please enter content');
return;
}
// Sync content to the cloud phone
sendMsgToCloudPhoneOperation(content);
closeModal();
}Sync the content to the cloud phone
function sendMsgToCloudPhoneOperation(msg) {
// Main control
if (session) {
session.setClipboardModule('sendClipboardDataToRemote', msg)
}
// Thumbnail
for (const [key, value] of thumbnailSDKMap) {
console.log('thumbnail sendMsgToCloudPhoneOperation ', key, msg);
value.thumbnail.session.getClipboardModule().sendClipboardDataToRemote(msg);
}
}Usage notes
The newly created dialog box must take full control of all keyboard events to prevent them from being captured by other UI elements on the page. Otherwise, standard key presses (like typing) will not be correctly handled within the dialog. Refer to the following configuration example:
overlay.addEventListener('contextmenu', stopPropagation, true);
// Add event listeners to intercept all keyboard events
overlay.addEventListener('keydown', stopPropagation, true);
overlay.addEventListener('keyup', stopPropagation, true);
overlay.addEventListener('keypress', stopPropagation, true);