Adding keyboard shortcut for direct access + Fixes for Linux clipboard reading (#1479)

* feat: add ctrl+d keyboard shortcut to open direct access

* fix: make sure direct access promt only get shown once

* fix: linux clipboard acess on non x11 systems

xclip not present on all linux machines: using the electron mechanism instead
This commit is contained in:
kubectl
2025-11-11 05:16:23 +01:00
committed by GitHub
parent dda3d2dda9
commit 56b13ae4cb
5 changed files with 57 additions and 23 deletions

View File

@@ -11,6 +11,7 @@ const {
nativeImage nativeImage
} = require('electron'); } = require('electron');
const { spawn, spawnSync } = require('child_process'); const { spawn, spawnSync } = require('child_process');
const { clipboard } = require('electron');
const fs = require('fs'); const fs = require('fs');
const https = require('https'); const https = require('https');
@@ -262,6 +263,9 @@ ipcMain.handle(
ipcMain.handle('app:getArch', () => { ipcMain.handle('app:getArch', () => {
return process.arch.toString(); return process.arch.toString();
}); });
ipcMain.handle('app:getClipboardText', () => {
return clipboard.readText();
});
ipcMain.handle('app:getNoUpdater', () => { ipcMain.handle('app:getNoUpdater', () => {
return noUpdater; return noUpdater;

View File

@@ -23,6 +23,7 @@ const validChannels = ['launch-command'];
contextBridge.exposeInMainWorld('electron', { contextBridge.exposeInMainWorld('electron', {
getArch: () => ipcRenderer.invoke('app:getArch'), getArch: () => ipcRenderer.invoke('app:getArch'),
getClipboardText: () => ipcRenderer.invoke('app:getClipboardText'),
getNoUpdater: () => ipcRenderer.invoke('app:getNoUpdater'), getNoUpdater: () => ipcRenderer.invoke('app:getNoUpdater'),
setTrayIconNotification: (notify) => setTrayIconNotification: (notify) =>
ipcRenderer.invoke('app:setTrayIconNotification', notify), ipcRenderer.invoke('app:setTrayIconNotification', notify),

View File

@@ -198,7 +198,7 @@
</template> </template>
<script setup> <script setup>
import { computed, onMounted, ref, watch } from 'vue'; import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@@ -374,6 +374,13 @@
openExternalLink('https://github.com/vrcx-team/VRCX'); openExternalLink('https://github.com/vrcx-team/VRCX');
}; };
function handleKeydown(e) {
if (e.ctrlKey && e.key === 'd') {
e.preventDefault();
directAccessPaste();
}
}
const supportLinks = { const supportLinks = {
wiki: 'https://github.com/vrcx-team/VRCX/wiki', wiki: 'https://github.com/vrcx-team/VRCX/wiki',
github: 'https://github.com/vrcx-team/VRCX', github: 'https://github.com/vrcx-team/VRCX',
@@ -451,6 +458,11 @@
if (!sentryErrorReporting.value) return; if (!sentryErrorReporting.value) return;
const feedback = Sentry.getFeedback(); const feedback = Sentry.getFeedback();
feedback?.attachTo(document.getElementById('feedback')); feedback?.attachTo(document.getElementById('feedback'));
window.addEventListener('keydown', handleKeydown);
});
onUnmounted(() => {
window.removeEventListener('keydown', handleKeydown);
}); });
</script> </script>

View File

@@ -31,6 +31,8 @@ export const useSearchStore = defineStore('Search', () => {
const quickSearchItems = ref([]); const quickSearchItems = ref([]);
const friendsListSearch = ref(''); const friendsListSearch = ref('');
const directAccessPrompt = ref(null);
const stringComparer = computed(() => const stringComparer = computed(() =>
Intl.Collator(appearanceSettingsStore.appLanguage.replace('_', '-'), { Intl.Collator(appearanceSettingsStore.appLanguage.replace('_', '-'), {
usage: 'search', usage: 'search',
@@ -217,12 +219,21 @@ export const useSearchStore = defineStore('Search', () => {
return results; return results;
} }
function directAccessPaste() { async function directAccessPaste() {
AppApi.GetClipboard().then((clipboard) => { let cbText = '';
if (!directAccessParse(clipboard.trim())) { if (LINUX) {
promptOmniDirectDialog(); cbText = await window.electron.getClipboardText();
} } else {
}); cbText = await AppApi.GetClipboard().catch((e) => {
console.log(e);
return '';
});
}
let trimemd = cbText.trim();
if (!directAccessParse(trimemd)) {
promptOmniDirectDialog();
}
} }
function directAccessParse(input) { function directAccessParse(input) {
@@ -335,8 +346,10 @@ export const useSearchStore = defineStore('Search', () => {
return false; return false;
} }
function promptOmniDirectDialog() { async function promptOmniDirectDialog() {
ElMessageBox.prompt( if (directAccessPrompt.value) return;
directAccessPrompt.value = ElMessageBox.prompt(
t('prompt.direct_access_omni.description'), t('prompt.direct_access_omni.description'),
t('prompt.direct_access_omni.header'), t('prompt.direct_access_omni.header'),
{ {
@@ -346,21 +359,24 @@ export const useSearchStore = defineStore('Search', () => {
inputPattern: /\S+/, inputPattern: /\S+/,
inputErrorMessage: t('prompt.direct_access_omni.input_error') inputErrorMessage: t('prompt.direct_access_omni.input_error')
} }
) );
.then(({ value, action }) => {
if (action === 'confirm' && value) { try {
const input = value.trim(); const { value, action } = await directAccessPrompt.value;
if (!directAccessParse(input)) {
ElMessage({ if (action === 'confirm' && value) {
message: t( const input = value.trim();
'prompt.direct_access_omni.message.error' if (!directAccessParse(input)) {
), ElMessage({
type: 'error' message: t('prompt.direct_access_omni.message.error'),
}); type: 'error'
} });
} }
}) }
.catch(() => {}); } catch {
} finally {
directAccessPrompt.value = null;
}
} }
function showGroupDialogShortCode(shortCode) { function showGroupDialogShortCode(shortCode) {

View File

@@ -36,6 +36,7 @@ declare global {
}; };
electron: { electron: {
getArch: () => Promise<string>; getArch: () => Promise<string>;
getClipboardText: () => Promise<string>;
getNoUpdater: () => Promise<boolean>; getNoUpdater: () => Promise<boolean>;
setTrayIconNotification: (notify: boolean) => Promise<void>; setTrayIconNotification: (notify: boolean) => Promise<void>;
openFileDialog: () => Promise<string>; openFileDialog: () => Promise<string>;