Safari has security restrictions that display a paste bubble button when you access the clipboard. This can interfere with syncing content from your local client to a cloud phone. This topic describes how to use the Web SDK to paste text from your local clipboard into the Safari browser.
Solution overview
Add a clipboard button to the local client. Clicking this button opens a bubble input box.
Enter the content that you want to send to the cloud phone in the bubble input box.
Click the Paste Now button in the bubble input box to sync the content to the cloud phone's clipboard and input box.
Implementation steps
Cloud phone parameter configuration
Set
readClipboardDataByUserto `true`. This prevents the software development kit (SDK) from automatically reading the clipboard.Set
useCustomImeto `true` to enable the local input method editor (IME). The local IME must be enabled to sync content to the cloud phone's input box.
// 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) => {},
}
);Build the bubble input box
You can build a clipboard button in the client. When clicked, this button opens a dialog box that interacts with the clipboard. For reference, see the following code:
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);
}
}Notes
The new dialog box must handle all keyboard events to prevent them from being captured by other desktop elements. If other elements capture these events, normal key presses will not register correctly in the dialog box. For reference, see the following configuration:
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);