do not use ref where not needed.

This commit is contained in:
Miroslav Šedivý 2024-03-17 16:05:02 +01:00
parent 93125abfd7
commit 3a96d2611e
3 changed files with 199 additions and 200 deletions

View File

@ -38,16 +38,16 @@ const props = defineProps<{
}>() }>()
const overlay = ref<HTMLCanvasElement | null>(null) const overlay = ref<HTMLCanvasElement | null>(null)
const ctx = ref<CanvasRenderingContext2D | null>(null)
const canvasScale = ref(window.devicePixelRatio)
let ctx: CanvasRenderingContext2D | null = null
let canvasScale = window.devicePixelRatio
let unsubscribePixelRatioChange = null as (() => void) | null let unsubscribePixelRatioChange = null as (() => void) | null
onMounted(() => { onMounted(() => {
// get canvas overlay context // get canvas overlay context
const canvas = overlay.value const canvas = overlay.value
if (canvas != null) { if (canvas != null) {
ctx.value = canvas.getContext('2d') ctx = canvas.getContext('2d')
// synchronize intrinsic with extrinsic dimensions // synchronize intrinsic with extrinsic dimensions
const { width, height } = canvas.getBoundingClientRect() const { width, height } = canvas.getBoundingClientRect()
@ -58,7 +58,7 @@ onMounted(() => {
onPixelRatioChange() onPixelRatioChange()
// store last drawing points // store last drawing points
last_points.value = {} last_points = {}
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
@ -79,7 +79,7 @@ function onPixelRatioChange() {
media.removeEventListener('change', onPixelRatioChange) media.removeEventListener('change', onPixelRatioChange)
} }
canvasScale.value = window.devicePixelRatio canvasScale = window.devicePixelRatio
onCanvasSizeChange(props.canvasSize) onCanvasSizeChange(props.canvasSize)
} }
@ -91,42 +91,42 @@ function onCanvasSizeChange({ width, height }: Dimension) {
watch(() => props.canvasSize, onCanvasSizeChange) watch(() => props.canvasSize, onCanvasSizeChange)
function canvasResize({ width, height }: Dimension) { function canvasResize({ width, height }: Dimension) {
overlay.value!.width = width * canvasScale.value overlay.value!.width = width * canvasScale
overlay.value!.height = height * canvasScale.value overlay.value!.height = height * canvasScale
ctx.value?.setTransform(canvasScale.value, 0, 0, canvasScale.value, 0, 0) ctx?.setTransform(canvasScale, 0, 0, canvasScale, 0, 0)
} }
// start as undefined to prevent jumping // start as undefined to prevent jumping
const last_animation_time = ref<number>(0) let last_animation_time = 0
// current animation progress (0-1) // current animation progress (0-1)
const percent = ref<number>(0) let percent = 0
// points to be animated for each session // points to be animated for each session
const points = ref<SessionCursors[]>([]) let points: SessionCursors[] = []
// last points coordinates for each session // last points coordinates for each session
const last_points = ref<Record<string, Cursor>>({}) let last_points: Record<string, Cursor> = {}
function canvasAnimateFrame(now: number = NaN) { function canvasAnimateFrame(now: number = NaN) {
// request another frame // request another frame
if (percent.value <= 1) window.requestAnimationFrame(canvasAnimateFrame) if (percent <= 1) window.requestAnimationFrame(canvasAnimateFrame)
// calc elapsed time since last loop // calc elapsed time since last loop
const elapsed = now - last_animation_time.value const elapsed = now - last_animation_time
// skip if fps is set and elapsed time is less than fps // skip if fps is set and elapsed time is less than fps
if (props.fps > 0 && elapsed < 1000 / props.fps) return if (props.fps > 0 && elapsed < 1000 / props.fps) return
// calc current animation progress // calc current animation progress
const delta = elapsed / POS_INTERVAL_MS const delta = elapsed / POS_INTERVAL_MS
last_animation_time.value = now last_animation_time = now
// skip very first delta to prevent jumping // skip very first delta to prevent jumping
if (isNaN(delta)) return if (isNaN(delta)) return
// set the animation position // set the animation position
percent.value += delta percent += delta
// draw points for current frame // draw points for current frame
canvasDrawPoints(percent.value) canvasDrawPoints(percent)
} }
function canvasDrawPoints(percent: number = 1) { function canvasDrawPoints(percent: number = 1) {
@ -134,7 +134,7 @@ function canvasDrawPoints(percent: number = 1) {
canvasClear() canvasClear()
// draw current position // draw current position
for (const p of points.value) { for (const p of points) {
const { x, y } = getMovementXYatPercent(p.cursors, percent) const { x, y } = getMovementXYatPercent(p.cursors, percent)
canvasDrawCursor(x, y, p.id) canvasDrawCursor(x, y, p.id)
} }
@ -147,7 +147,7 @@ function canvasUpdateCursors() {
let unchanged = 0 let unchanged = 0
// create points for animation // create points for animation
points.value = [] points = []
for (const { id, cursors } of props.cursors) { for (const { id, cursors } of props.cursors) {
if ( if (
// if there are no positions // if there are no positions
@ -166,8 +166,8 @@ function canvasUpdateCursors() {
// add last cursor position to cursors (if available) // add last cursor position to cursors (if available)
let pos = { id } as SessionCursors let pos = { id } as SessionCursors
if (id in last_points.value) { if (id in last_points) {
const last_point = last_points.value[id] const last_point = last_points[id]
// if cursor did not move considerably // if cursor did not move considerably
if ( if (
@ -191,14 +191,14 @@ function canvasUpdateCursors() {
} }
new_last_points[id] = new_last_point new_last_points[id] = new_last_point
points.value.push(pos) points.push(pos)
} }
// apply new last points // apply new last points
last_points.value = new_last_points last_points = new_last_points
// no cursors to animate // no cursors to animate
if (points.value.length == 0) { if (points.length == 0) {
canvasClear() canvasClear()
return return
} }
@ -211,8 +211,8 @@ function canvasUpdateCursors() {
} }
// start animation if not running // start animation if not running
const p = percent.value const p = percent
percent.value = 0 percent = 0
if (p > 1 || !p) { if (p > 1 || !p) {
canvasAnimateFrame() canvasAnimateFrame()
} }
@ -222,17 +222,19 @@ watch(() => props.hostId, canvasUpdateCursors)
watch(() => props.cursors, canvasUpdateCursors) watch(() => props.cursors, canvasUpdateCursors)
function canvasDrawCursor(x: number, y: number, id: string) { function canvasDrawCursor(x: number, y: number, id: string) {
if (!ctx) return
// get intrinsic dimensions // get intrinsic dimensions
const { width, height } = props.canvasSize const { width, height } = props.canvasSize
x = Math.round((x / props.screenSize.width) * width) x = Math.round((x / props.screenSize.width) * width)
y = Math.round((y / props.screenSize.height) * height) y = Math.round((y / props.screenSize.height) * height)
// reset transformation, X and Y will be 0 again // reset transformation, X and Y will be 0 again
ctx.value!.setTransform(canvasScale.value, 0, 0, canvasScale.value, 0, 0) ctx.setTransform(canvasScale, 0, 0, canvasScale, 0, 0)
// use custom draw function, if available // use custom draw function, if available
if (props.cursorDraw) { if (props.cursorDraw) {
props.cursorDraw(ctx.value!, x, y, id) props.cursorDraw(ctx, x, y, id)
return return
} }
@ -240,23 +242,25 @@ function canvasDrawCursor(x: number, y: number, id: string) {
const cursorTag = props.sessions[id]?.profile.name || '' const cursorTag = props.sessions[id]?.profile.name || ''
// draw inactive cursor tag // draw inactive cursor tag
ctx.value!.font = '14px Arial, sans-serif' ctx.font = '14px Arial, sans-serif'
ctx.value!.textBaseline = 'top' ctx.textBaseline = 'top'
ctx.value!.shadowColor = 'black' ctx.shadowColor = 'black'
ctx.value!.shadowBlur = 2 ctx.shadowBlur = 2
ctx.value!.lineWidth = 2 ctx.lineWidth = 2
ctx.value!.fillStyle = 'black' ctx.fillStyle = 'black'
ctx.value!.strokeText(cursorTag, x, y) ctx.strokeText(cursorTag, x, y)
ctx.value!.shadowBlur = 0 ctx.shadowBlur = 0
ctx.value!.fillStyle = 'white' ctx.fillStyle = 'white'
ctx.value!.fillText(cursorTag, x, y) ctx.fillText(cursorTag, x, y)
} }
function canvasClear() { function canvasClear() {
if (!ctx) return
// reset transformation, X and Y will be 0 again // reset transformation, X and Y will be 0 again
ctx.value?.setTransform(canvasScale.value, 0, 0, canvasScale.value, 0, 0) ctx.setTransform(canvasScale, 0, 0, canvasScale, 0, 0)
const { width, height } = props.canvasSize const { width, height } = props.canvasSize
ctx.value?.clearRect(0, 0, width, height) ctx.clearRect(0, 0, width, height)
} }
</script> </script>

View File

@ -266,7 +266,7 @@ const events = new NekoMessages(connection, state)
// Public methods // Public methods
///////////////////////////// /////////////////////////////
function setUrl(url: string) { function setUrl(url?: string) {
if (!url) { if (!url) {
url = location.href url = location.href
} }
@ -302,7 +302,7 @@ function setUrl(url: string) {
} }
watch(() => props.server, (url) => { watch(() => props.server, (url) => {
url && setUrl(url) setUrl(url)
}, { immediate: true }) }, { immediate: true })
async function authenticate(token?: string) { async function authenticate(token?: string) {
@ -641,15 +641,15 @@ function onScreenSyncChange() {
watch(() => state.screen.sync.enabled, onScreenSyncChange) watch(() => state.screen.sync.enabled, onScreenSyncChange)
const syncScreenSizeTimeout = ref(0) let syncScreenSizeTimeout = 0
function syncScreenSize() { function syncScreenSize() {
if (syncScreenSizeTimeout.value) { if (syncScreenSizeTimeout) {
window.clearTimeout(syncScreenSizeTimeout.value) window.clearTimeout(syncScreenSizeTimeout)
} }
syncScreenSizeTimeout.value = window.setTimeout(() => { syncScreenSizeTimeout = window.setTimeout(() => {
const multiplier = state.screen.sync.multiplier || window.devicePixelRatio const multiplier = state.screen.sync.multiplier || window.devicePixelRatio
syncScreenSizeTimeout.value = 0 syncScreenSizeTimeout = 0
const { offsetWidth, offsetHeight } = component.value! const { offsetWidth, offsetHeight } = component.value!
setScreenSize( setScreenSize(
Math.round(offsetWidth * multiplier), Math.round(offsetWidth * multiplier),

View File

@ -76,15 +76,15 @@ const INACTIVE_CURSOR_INTERVAL = 1000 / 4 // in ms, 4fps
const overlay = ref<HTMLCanvasElement | null>(null) const overlay = ref<HTMLCanvasElement | null>(null)
const textarea = ref<HTMLTextAreaElement | null>(null) const textarea = ref<HTMLTextAreaElement | null>(null)
const ctx = ref<CanvasRenderingContext2D | null>(null)
const canvasScale = ref(window.devicePixelRatio) let ctx: CanvasRenderingContext2D | null = null
let canvasScale = window.devicePixelRatio
const keyboard = ref<KeyboardInterface | null>(null) let keyboard: KeyboardInterface = NewKeyboard()
const gestureHandler = ref<GestureHandler | null>(null) let gestureHandler: GestureHandler = new GestureHandlerInit()
const textInput = ref('')
const focused = ref(false) const focused = ref(false)
const textInput = ref('')
// props and emits // props and emits
@ -124,10 +124,7 @@ onMounted(() => {
window.addEventListener('mouseup', onMouseUp, true) window.addEventListener('mouseup', onMouseUp, true)
// get canvas overlay context // get canvas overlay context
const _ctx = overlay.value?.getContext('2d') ctx = overlay.value!.getContext('2d')
if (_ctx != null) {
ctx.value = _ctx
}
// synchronize intrinsic with extrinsic dimensions // synchronize intrinsic with extrinsic dimensions
const { width, height } = overlay.value?.getBoundingClientRect() || { width: 0, height: 0 } const { width, height } = overlay.value?.getBoundingClientRect() || { width: 0, height: 0 }
@ -140,8 +137,7 @@ onMounted(() => {
let noKeyUp = {} as Record<number, boolean> let noKeyUp = {} as Record<number, boolean>
// Initialize Keyboard // Initialize Keyboard
keyboard.value = NewKeyboard() keyboard.onkeydown = (key: number) => {
keyboard.value.onkeydown = (key: number) => {
key = keySymsRemap(key) key = keySymsRemap(key)
if (!props.isControling) { if (!props.isControling) {
@ -151,7 +147,7 @@ onMounted(() => {
// ctrl+v is aborted // ctrl+v is aborted
if (ctrlKey != 0 && key == KeyTable.XK_v) { if (ctrlKey != 0 && key == KeyTable.XK_v) {
keyboard.value!.release(ctrlKey) keyboard!.release(ctrlKey)
noKeyUp[key] = true noKeyUp[key] = true
return true return true
} }
@ -163,7 +159,7 @@ onMounted(() => {
props.control.keyDown(key) props.control.keyDown(key)
return isCtrlKey return isCtrlKey
} }
keyboard.value.onkeyup = (key: number) => { keyboard.onkeyup = (key: number) => {
key = keySymsRemap(key) key = keySymsRemap(key)
if (key in noKeyUp) { if (key in noKeyUp) {
@ -176,10 +172,7 @@ onMounted(() => {
props.control.keyUp(key) props.control.keyUp(key)
} }
keyboard.value.listenTo(textarea.value!) keyboard.listenTo(textarea.value!)
// Initialize GestureHandler
gestureHandler.value = new GestureHandlerInit()
// bind touch handler using @Watch on supportedTouchEvents // bind touch handler using @Watch on supportedTouchEvents
// because we need to know if touch events are supported // because we need to know if touch events are supported
@ -196,10 +189,7 @@ onMounted(() => {
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('mouseup', onMouseUp, true) window.removeEventListener('mouseup', onMouseUp, true)
keyboard.removeListener()
if (keyboard.value) {
keyboard.value.removeListener()
}
// unbind touch handler // unbind touch handler
unbindTouchHandler() unbindTouchHandler()
@ -216,8 +206,8 @@ onBeforeUnmount(() => {
clearInactiveCursorInterval() clearInactiveCursorInterval()
// stop pixel ratio change listener // stop pixel ratio change listener
if (unsubscribePixelRatioChange.value) { if (unsubscribePixelRatioChange) {
unsubscribePixelRatioChange.value() unsubscribePixelRatioChange()
} }
}) })
@ -280,23 +270,23 @@ function onTouchHandler(ev: TouchEvent) {
// //
function bindGestureHandler() { function bindGestureHandler() {
gestureHandler.value?.attach(textarea.value!) gestureHandler.attach(textarea.value!)
textarea.value?.addEventListener('gesturestart', onGestureHandler) textarea.value?.addEventListener('gesturestart', onGestureHandler)
textarea.value?.addEventListener('gesturemove', onGestureHandler) textarea.value?.addEventListener('gesturemove', onGestureHandler)
textarea.value?.addEventListener('gestureend', onGestureHandler) textarea.value?.addEventListener('gestureend', onGestureHandler)
} }
function unbindGestureHandler() { function unbindGestureHandler() {
gestureHandler.value?.detach() gestureHandler.detach()
textarea.value?.removeEventListener('gesturestart', onGestureHandler) textarea.value?.removeEventListener('gesturestart', onGestureHandler)
textarea.value?.removeEventListener('gesturemove', onGestureHandler) textarea.value?.removeEventListener('gesturemove', onGestureHandler)
textarea.value?.removeEventListener('gestureend', onGestureHandler) textarea.value?.removeEventListener('gestureend', onGestureHandler)
} }
const gestureLastTapTime = ref<number | null>(null) let gestureLastTapTime: number | null = null
const gestureFirstDoubleTapEv = ref<any | null>(null) let gestureFirstDoubleTapEv: any | null = null
const gestureLastMagnitudeX = ref(0) let gestureLastMagnitudeX = 0
const gestureLastMagnitudeY = ref(0) let gestureLastMagnitudeY = 0
function _handleTapEvent(ev: any, code: number) { function _handleTapEvent(ev: any, code: number) {
let pos = getMousePos(ev.detail.clientX, ev.detail.clientY) let pos = getMousePos(ev.detail.clientX, ev.detail.clientY)
@ -305,23 +295,23 @@ function _handleTapEvent(ev: any, code: number) {
// hit the same spot, so slightly adjust coordinates // hit the same spot, so slightly adjust coordinates
if ( if (
gestureLastTapTime.value !== null && gestureLastTapTime !== null &&
Date.now() - gestureLastTapTime.value < DOUBLE_TAP_TIMEOUT && Date.now() - gestureLastTapTime < DOUBLE_TAP_TIMEOUT &&
gestureFirstDoubleTapEv.value?.detail.type === ev.detail.type gestureFirstDoubleTapEv?.detail.type === ev.detail.type
) { ) {
const dx = gestureFirstDoubleTapEv.value.detail.clientX - ev.detail.clientX const dx = gestureFirstDoubleTapEv.detail.clientX - ev.detail.clientX
const dy = gestureFirstDoubleTapEv.value.detail.clientY - ev.detail.clientY const dy = gestureFirstDoubleTapEv.detail.clientY - ev.detail.clientY
const distance = Math.hypot(dx, dy) const distance = Math.hypot(dx, dy)
if (distance < DOUBLE_TAP_THRESHOLD) { if (distance < DOUBLE_TAP_THRESHOLD) {
pos = getMousePos(gestureFirstDoubleTapEv.value.detail.clientX, gestureFirstDoubleTapEv.value.detail.clientY) pos = getMousePos(gestureFirstDoubleTapEv.detail.clientX, gestureFirstDoubleTapEv.detail.clientY)
} else { } else {
gestureFirstDoubleTapEv.value = ev gestureFirstDoubleTapEv = ev
} }
} else { } else {
gestureFirstDoubleTapEv.value = ev gestureFirstDoubleTapEv = ev
} }
gestureLastTapTime.value = Date.now() gestureLastTapTime = Date.now()
props.control.buttonDown(code, pos) props.control.buttonDown(code, pos)
props.control.buttonUp(code, pos) props.control.buttonUp(code, pos)
@ -361,12 +351,12 @@ function onGestureHandler(ev: any) {
break break
case 'twodrag': case 'twodrag':
gestureLastMagnitudeX.value = ev.detail.magnitudeX gestureLastMagnitudeX = ev.detail.magnitudeX
gestureLastMagnitudeY.value = ev.detail.magnitudeY gestureLastMagnitudeY = ev.detail.magnitudeY
props.control.move(pos) props.control.move(pos)
break break
case 'pinch': case 'pinch':
gestureLastMagnitudeX.value = Math.hypot(ev.detail.magnitudeX, ev.detail.magnitudeY) gestureLastMagnitudeX = Math.hypot(ev.detail.magnitudeX, ev.detail.magnitudeY)
props.control.move(pos) props.control.move(pos)
break break
} }
@ -387,21 +377,21 @@ function onGestureHandler(ev: any) {
// We don't know if the mouse was moved so we need to move it // We don't know if the mouse was moved so we need to move it
// every update. // every update.
props.control.move(pos) props.control.move(pos)
while (ev.detail.magnitudeY - gestureLastMagnitudeY.value > GESTURE_SCRLSENS) { while (ev.detail.magnitudeY - gestureLastMagnitudeY > GESTURE_SCRLSENS) {
props.control.scroll({ delta_x: 0, delta_y: 1 }) props.control.scroll({ delta_x: 0, delta_y: 1 })
gestureLastMagnitudeY.value += GESTURE_SCRLSENS gestureLastMagnitudeY += GESTURE_SCRLSENS
} }
while (ev.detail.magnitudeY - gestureLastMagnitudeY.value < -GESTURE_SCRLSENS) { while (ev.detail.magnitudeY - gestureLastMagnitudeY < -GESTURE_SCRLSENS) {
props.control.scroll({ delta_x: 0, delta_y: -1 }) props.control.scroll({ delta_x: 0, delta_y: -1 })
gestureLastMagnitudeY.value -= GESTURE_SCRLSENS gestureLastMagnitudeY -= GESTURE_SCRLSENS
} }
while (ev.detail.magnitudeX - gestureLastMagnitudeX.value > GESTURE_SCRLSENS) { while (ev.detail.magnitudeX - gestureLastMagnitudeX > GESTURE_SCRLSENS) {
props.control.scroll({ delta_x: 1, delta_y: 0 }) props.control.scroll({ delta_x: 1, delta_y: 0 })
gestureLastMagnitudeX.value += GESTURE_SCRLSENS gestureLastMagnitudeX+= GESTURE_SCRLSENS
} }
while (ev.detail.magnitudeX - gestureLastMagnitudeX.value < -GESTURE_SCRLSENS) { while (ev.detail.magnitudeX - gestureLastMagnitudeX < -GESTURE_SCRLSENS) {
props.control.scroll({ delta_x: -1, delta_y: 0 }) props.control.scroll({ delta_x: -1, delta_y: 0 })
gestureLastMagnitudeX.value -= GESTURE_SCRLSENS gestureLastMagnitudeX-= GESTURE_SCRLSENS
} }
break break
case 'pinch': case 'pinch':
@ -410,14 +400,14 @@ function onGestureHandler(ev: any) {
// every update. // every update.
props.control.move(pos) props.control.move(pos)
magnitude = Math.hypot(ev.detail.magnitudeX, ev.detail.magnitudeY) magnitude = Math.hypot(ev.detail.magnitudeX, ev.detail.magnitudeY)
if (Math.abs(magnitude - gestureLastMagnitudeX.value) > GESTURE_ZOOMSENS) { if (Math.abs(magnitude - gestureLastMagnitudeX) > GESTURE_ZOOMSENS) {
while (magnitude - gestureLastMagnitudeX.value > GESTURE_ZOOMSENS) { while (magnitude - gestureLastMagnitudeX > GESTURE_ZOOMSENS) {
props.control.scroll({ delta_x: 0, delta_y: 1, control_key: true }) props.control.scroll({ delta_x: 0, delta_y: 1, control_key: true })
gestureLastMagnitudeX.value += GESTURE_ZOOMSENS gestureLastMagnitudeX+= GESTURE_ZOOMSENS
} }
while (magnitude - gestureLastMagnitudeX.value < -GESTURE_ZOOMSENS) { while (magnitude - gestureLastMagnitudeX < -GESTURE_ZOOMSENS) {
props.control.scroll({ delta_x: 0, delta_y: -1, control_key: true }) props.control.scroll({ delta_x: 0, delta_y: -1, control_key: true })
gestureLastMagnitudeX.value -= GESTURE_ZOOMSENS gestureLastMagnitudeX-= GESTURE_ZOOMSENS
} }
} }
break break
@ -500,12 +490,12 @@ function sendMousePos(e: MouseEvent) {
if (props.webrtc.connected) { if (props.webrtc.connected) {
props.webrtc.send('mousemove', pos) props.webrtc.send('mousemove', pos)
} // otherwise, no events are sent } // otherwise, no events are sent
cursorPosition.value = pos cursorPosition = pos
} }
const wheelX = ref(0) let wheelX = 0
const wheelY = ref(0) let wheelY = 0
const wheelTimeStamp = ref(0) let wheelTimeStamp = 0
// negative sensitivity can be acheived using increased step value // negative sensitivity can be acheived using increased step value
const wheelStep = computed(() => { const wheelStep = computed(() => {
@ -550,12 +540,12 @@ function onWheel(e: WheelEvent) {
} }
// when the last scroll was more than 250ms ago // when the last scroll was more than 250ms ago
const firstScroll = e.timeStamp - wheelTimeStamp.value > 250 const firstScroll = e.timeStamp - wheelTimeStamp > 250
if (firstScroll) { if (firstScroll) {
wheelX.value = 0 wheelX = 0
wheelY.value = 0 wheelY = 0
wheelTimeStamp.value = e.timeStamp wheelTimeStamp = e.timeStamp
} }
let dx = e.deltaX let dx = e.deltaX
@ -566,32 +556,32 @@ function onWheel(e: WheelEvent) {
dy *= WHEEL_LINE_HEIGHT dy *= WHEEL_LINE_HEIGHT
} }
wheelX.value += dx wheelX += dx
wheelY.value += dy wheelY += dy
let x = 0 let x = 0
if (Math.abs(wheelX.value) >= wheelStep.value || firstScroll) { if (Math.abs(wheelX) >= wheelStep.value || firstScroll) {
if (wheelX.value < 0) { if (wheelX < 0) {
x = wheelSensitivity.value * -1 x = wheelSensitivity.value * -1
} else if (wheelX.value > 0) { } else if (wheelX > 0) {
x = wheelSensitivity.value x = wheelSensitivity.value
} }
if (!firstScroll) { if (!firstScroll) {
wheelX.value = 0 wheelX = 0
} }
} }
let y = 0 let y = 0
if (Math.abs(wheelY.value) >= wheelStep.value || firstScroll) { if (Math.abs(wheelY) >= wheelStep.value || firstScroll) {
if (wheelY.value < 0) { if (wheelY < 0) {
y = wheelSensitivity.value * -1 y = wheelSensitivity.value * -1
} else if (wheelY.value > 0) { } else if (wheelY > 0) {
y = wheelSensitivity.value y = wheelSensitivity.value
} }
if (!firstScroll) { if (!firstScroll) {
wheelY.value = 0 wheelY = 0
} }
} }
@ -606,12 +596,12 @@ function onWheel(e: WheelEvent) {
}) })
} }
const lastMouseMove = ref(0) let lastMouseMove = 0
function onMouseMove(e: MouseEvent) { function onMouseMove(e: MouseEvent) {
// throttle mousemove events // throttle mousemove events
if (e.timeStamp - lastMouseMove.value < MOUSE_MOVE_THROTTLE) return if (e.timeStamp - lastMouseMove < MOUSE_MOVE_THROTTLE) return
lastMouseMove.value = e.timeStamp lastMouseMove = e.timeStamp
if (props.isControling) { if (props.isControling) {
sendMousePos(e) sendMousePos(e)
@ -622,10 +612,10 @@ function onMouseMove(e: MouseEvent) {
} }
} }
const isMouseDown = ref(false) let isMouseDown = false
function onMouseDown(e: MouseEvent) { function onMouseDown(e: MouseEvent) {
isMouseDown.value = true isMouseDown = true
if (!props.isControling) { if (!props.isControling) {
implicitControlRequest(e) implicitControlRequest(e)
@ -639,8 +629,8 @@ function onMouseDown(e: MouseEvent) {
function onMouseUp(e: MouseEvent) { function onMouseUp(e: MouseEvent) {
// only if we are the one who started the mouse down // only if we are the one who started the mouse down
if (!isMouseDown.value) return if (!isMouseDown) return
isMouseDown.value = false isMouseDown = false
if (!props.isControling) { if (!props.isControling) {
implicitControlRequest(e) implicitControlRequest(e)
@ -668,7 +658,7 @@ function onMouseEnter(e: MouseEvent) {
function onMouseLeave(e: MouseEvent) { function onMouseLeave(e: MouseEvent) {
if (props.isControling) { if (props.isControling) {
// save current keyboard modifiers state // save current keyboard modifiers state
keyboardModifiers.value = getModifierState(e) keyboardModifiers = getModifierState(e)
} }
focused.value = false focused.value = false
@ -703,13 +693,13 @@ async function onDrop(e: DragEvent) {
// inactive cursor position // inactive cursor position
// //
const inactiveCursorInterval = ref<number | null>(null) let inactiveCursorInterval: number | null = null
const inactiveCursorPosition = ref<CursorPosition | null>(null) let inactiveCursorPosition: CursorPosition | null = null
function clearInactiveCursorInterval() { function clearInactiveCursorInterval() {
if (inactiveCursorInterval.value) { if (inactiveCursorInterval) {
window.clearInterval(inactiveCursorInterval.value) window.clearInterval(inactiveCursorInterval)
inactiveCursorInterval.value = null inactiveCursorInterval = null
} }
} }
@ -718,7 +708,7 @@ function restartInactiveCursorInterval() {
clearInactiveCursorInterval() clearInactiveCursorInterval()
if (props.inactiveCursors && focused.value && !props.isControling) { if (props.inactiveCursors && focused.value && !props.isControling) {
inactiveCursorInterval.value = window.setInterval(sendInactiveMousePos, INACTIVE_CURSOR_INTERVAL) inactiveCursorInterval = window.setInterval(sendInactiveMousePos, INACTIVE_CURSOR_INTERVAL)
} }
} }
@ -728,14 +718,14 @@ watch(() => props.isControling, restartInactiveCursorInterval)
function saveInactiveMousePos(e: MouseEvent) { function saveInactiveMousePos(e: MouseEvent) {
const pos = getMousePos(e.clientX, e.clientY) const pos = getMousePos(e.clientX, e.clientY)
inactiveCursorPosition.value = pos inactiveCursorPosition = pos
} }
function sendInactiveMousePos() { function sendInactiveMousePos() {
if (inactiveCursorPosition.value && props.webrtc.connected) { if (inactiveCursorPosition && props.webrtc.connected) {
// not using NekoControl here, because inactive cursors are // not using NekoControl here, because inactive cursors are
// treated differently than moving the mouse while controling // treated differently than moving the mouse while controling
props.webrtc.send('mousemove', inactiveCursorPosition.value) props.webrtc.send('mousemove', inactiveCursorPosition)
} // if webrtc is not connected, we don't need to send anything } // if webrtc is not connected, we don't need to send anything
} }
@ -743,12 +733,12 @@ function sendInactiveMousePos() {
// keyboard modifiers // keyboard modifiers
// //
const keyboardModifiers = ref<KeyboardModifiers | null>(null) let keyboardModifiers: KeyboardModifiers | null = null
function updateKeyboardModifiers(e: MouseEvent) { function updateKeyboardModifiers(e: MouseEvent) {
const mods = getModifierState(e) const mods = getModifierState(e)
const newMods = Object.values(mods).join() const newMods = Object.values(mods).join()
const oldMods = Object.values(keyboardModifiers.value || {}).join() const oldMods = Object.values(keyboardModifiers || {}).join()
// update keyboard modifiers only if they changed // update keyboard modifiers only if they changed
if (newMods !== oldMods) { if (newMods !== oldMods) {
@ -762,25 +752,26 @@ function updateKeyboardModifiers(e: MouseEvent) {
const cursorImage = ref<CursorImage | null>(null) const cursorImage = ref<CursorImage | null>(null)
const cursorElement = new Image() const cursorElement = new Image()
const cursorPosition = ref<CursorPosition | null>(null)
const cursorLastTime = ref(0)
const canvasRequestedFrame = ref(false)
const canvasRenderTimeout = ref<number | null>(null)
const unsubscribePixelRatioChange = ref<(() => void) | null>(null) let cursorPosition: CursorPosition | null = null
let cursorLastTime = 0
let canvasRequestedFrame = false
let canvasRenderTimeout: number | null = null
let unsubscribePixelRatioChange: (() => void) | null = null
function onPixelRatioChange() { function onPixelRatioChange() {
if (unsubscribePixelRatioChange.value) { if (unsubscribePixelRatioChange) {
unsubscribePixelRatioChange.value() unsubscribePixelRatioChange()
} }
const media = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`) const media = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`)
media.addEventListener('change', onPixelRatioChange) media.addEventListener('change', onPixelRatioChange)
unsubscribePixelRatioChange.value = () => { unsubscribePixelRatioChange = () => {
media.removeEventListener('change', onPixelRatioChange) media.removeEventListener('change', onPixelRatioChange)
} }
canvasScale.value = window.devicePixelRatio canvasScale = window.devicePixelRatio
onCanvasSizeChange(props.canvasSize) onCanvasSizeChange(props.canvasSize)
} }
@ -793,7 +784,7 @@ watch(() => props.canvasSize, onCanvasSizeChange)
function onCursorPosition(data: CursorPosition) { function onCursorPosition(data: CursorPosition) {
if (!props.isControling) { if (!props.isControling) {
cursorPosition.value = data cursorPosition = data
canvasRequestRedraw() canvasRequestRedraw()
} }
} }
@ -807,30 +798,32 @@ function onCursorImage(data: CursorImage) {
} }
function canvasResize({ width, height }: Dimension) { function canvasResize({ width, height }: Dimension) {
overlay.value!.width = width * canvasScale.value if (!ctx || !overlay.value) return
overlay.value!.height = height * canvasScale.value
ctx.value!.setTransform(canvasScale.value, 0, 0, canvasScale.value, 0, 0) overlay.value.width = width * canvasScale
overlay.value.height = height * canvasScale
ctx.setTransform(canvasScale, 0, 0, canvasScale, 0, 0)
} }
function canvasRequestRedraw() { function canvasRequestRedraw() {
if (canvasRequestedFrame.value) return if (canvasRequestedFrame) return
if (props.fps > 0) { if (props.fps > 0) {
if (canvasRenderTimeout.value) { if (canvasRenderTimeout) {
window.clearTimeout(canvasRenderTimeout.value) window.clearTimeout(canvasRenderTimeout)
canvasRenderTimeout.value = null canvasRenderTimeout = null
} }
const now = Date.now() const now = Date.now()
if (now - cursorLastTime.value < 1000 / props.fps) { if (now - cursorLastTime < 1000 / props.fps) {
canvasRenderTimeout.value = window.setTimeout(canvasRequestRedraw, 1000 / props.fps) canvasRenderTimeout = window.setTimeout(canvasRequestRedraw, 1000 / props.fps)
return return
} }
cursorLastTime.value = now cursorLastTime = now
} }
canvasRequestedFrame.value = true canvasRequestedFrame = true
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
if (props.isControling) { if (props.isControling) {
canvasClear() canvasClear()
@ -838,7 +831,7 @@ function canvasRequestRedraw() {
canvasRedraw() canvasRedraw()
} }
canvasRequestedFrame.value = false canvasRequestedFrame = false
}) })
} }
@ -846,7 +839,7 @@ watch(() => props.hostId, canvasRequestRedraw)
watch(() => props.cursorDraw, canvasRequestRedraw) watch(() => props.cursorDraw, canvasRequestRedraw)
function canvasRedraw() { function canvasRedraw() {
if (!cursorPosition.value || !props.screenSize || !cursorImage.value) return if (!ctx || !cursorPosition || !props.screenSize || !cursorImage.value) return
// clear drawings // clear drawings
canvasClear() canvasClear()
@ -858,20 +851,20 @@ function canvasRedraw() {
const { width, height } = props.canvasSize const { width, height } = props.canvasSize
// reset transformation, X and Y will be 0 again // reset transformation, X and Y will be 0 again
ctx.value!.setTransform(canvasScale.value, 0, 0, canvasScale.value, 0, 0) ctx.setTransform(canvasScale, 0, 0, canvasScale, 0, 0)
// get cursor position // get cursor position
let x = Math.round((cursorPosition.value.x / props.screenSize.width) * width) let x = Math.round((cursorPosition.x / props.screenSize.width) * width)
let y = Math.round((cursorPosition.value.y / props.screenSize.height) * height) let y = Math.round((cursorPosition.y / props.screenSize.height) * height)
// use custom draw function, if available // use custom draw function, if available
if (props.cursorDraw) { if (props.cursorDraw) {
props.cursorDraw(ctx.value!, x, y, cursorElement, cursorImage.value, props.hostId) props.cursorDraw(ctx, x, y, cursorElement, cursorImage.value, props.hostId)
return return
} }
// draw cursor image // draw cursor image
ctx.value!.drawImage( ctx.drawImage(
cursorElement, cursorElement,
x - cursorImage.value.x, x - cursorImage.value.x,
y - cursorImage.value.y, y - cursorImage.value.y,
@ -885,63 +878,65 @@ function canvasRedraw() {
x += cursorImage.value.width x += cursorImage.value.width
y += cursorImage.value.height y += cursorImage.value.height
ctx.value!.font = '14px Arial, sans-serif' ctx.font = '14px Arial, sans-serif'
ctx.value!.textBaseline = 'top' ctx.textBaseline = 'top'
ctx.value!.shadowColor = 'black' ctx.shadowColor = 'black'
ctx.value!.shadowBlur = 2 ctx.shadowBlur = 2
ctx.value!.lineWidth = 2 ctx.lineWidth = 2
ctx.value!.fillStyle = 'black' ctx.fillStyle = 'black'
ctx.value!.strokeText(cursorTag, x, y) ctx.strokeText(cursorTag, x, y)
ctx.value!.shadowBlur = 0 ctx.shadowBlur = 0
ctx.value!.fillStyle = 'white' ctx.fillStyle = 'white'
ctx.value!.fillText(cursorTag, x, y) ctx.fillText(cursorTag, x, y)
} }
} }
function canvasClear() { function canvasClear() {
if (!ctx) return
// reset transformation, X and Y will be 0 again // reset transformation, X and Y will be 0 again
ctx.value!.setTransform(canvasScale.value, 0, 0, canvasScale.value, 0, 0) ctx.setTransform(canvasScale, 0, 0, canvasScale, 0, 0)
const { width, height } = props.canvasSize const { width, height } = props.canvasSize
ctx.value!.clearRect(0, 0, width, height) ctx.clearRect(0, 0, width, height)
} }
// //
// implicit hosting // implicit hosting
// //
const reqMouseDown = ref<MouseEvent | null>(null) let reqMouseDown: MouseEvent | null = null
const reqMouseUp = ref<MouseEvent | null>(null) let reqMouseUp: MouseEvent | null = null
function onControlChange(isControling: boolean) { function onControlChange(isControling: boolean) {
keyboardModifiers.value = null keyboardModifiers = null
if (isControling && reqMouseDown.value) { if (isControling && reqMouseDown) {
updateKeyboardModifiers(reqMouseDown.value) updateKeyboardModifiers(reqMouseDown)
onMouseDown(reqMouseDown.value) onMouseDown(reqMouseDown)
} }
if (isControling && reqMouseUp.value) { if (isControling && reqMouseUp) {
onMouseUp(reqMouseUp.value) onMouseUp(reqMouseUp)
} }
canvasRequestRedraw() canvasRequestRedraw()
reqMouseDown.value = null reqMouseDown = null
reqMouseUp.value = null reqMouseUp = null
} }
watch(() => props.isControling, onControlChange) watch(() => props.isControling, onControlChange)
function implicitControlRequest(e: MouseEvent) { function implicitControlRequest(e: MouseEvent) {
if (props.implicitControl && e.type === 'mousedown') { if (props.implicitControl && e.type === 'mousedown') {
reqMouseDown.value = e reqMouseDown = e
reqMouseUp.value = null reqMouseUp = null
props.control.request() props.control.request()
} }
if (props.implicitControl && e.type === 'mouseup') { if (props.implicitControl && e.type === 'mouseup') {
reqMouseUp.value = e reqMouseUp = e
} }
} }
@ -956,15 +951,15 @@ function implicitControlRelease() {
// mobile keyboard // mobile keyboard
// //
const kbdShow = ref(false) let kbdShow = false
const kbdOpen = ref(false) let kbdOpen = false
function mobileKeyboardShow() { function mobileKeyboardShow() {
// skip if not a touch device // skip if not a touch device
if (!props.hasMobileKeyboard) return if (!props.hasMobileKeyboard) return
kbdShow.value = true kbdShow = true
kbdOpen.value = false kbdOpen = false
textarea.value!.focus() textarea.value!.focus()
window.visualViewport?.addEventListener('resize', onVisualViewportResize) window.visualViewport?.addEventListener('resize', onVisualViewportResize)
@ -975,8 +970,8 @@ function mobileKeyboardHide() {
// skip if not a touch device // skip if not a touch device
if (!props.hasMobileKeyboard) return if (!props.hasMobileKeyboard) return
kbdShow.value = false kbdShow = false
kbdOpen.value = false kbdOpen = false
emit('mobileKeyboardOpen', false) emit('mobileKeyboardOpen', false)
window.visualViewport?.removeEventListener('resize', onVisualViewportResize) window.visualViewport?.removeEventListener('resize', onVisualViewportResize)
@ -986,10 +981,10 @@ function mobileKeyboardHide() {
// visual viewport resize event is fired when keyboard is opened or closed // visual viewport resize event is fired when keyboard is opened or closed
// android does not blur textarea when keyboard is closed, so we need to do it manually // android does not blur textarea when keyboard is closed, so we need to do it manually
function onVisualViewportResize() { function onVisualViewportResize() {
if (!kbdShow.value) return if (!kbdShow) return
if (!kbdOpen.value) { if (!kbdOpen) {
kbdOpen.value = true kbdOpen = true
} else { } else {
mobileKeyboardHide() mobileKeyboardHide()
} }