Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9111131
Make live view usable on touch devices
masnwilliams Oct 9, 2026
52d6c14
Keep the keyboard button above the soft keyboard and ignore stale cur…
masnwilliams Oct 9, 2026
facde55
Measure the keyboard inset against the player box
masnwilliams Oct 9, 2026
7a77aa8
Keep the touch keyboard control off the stream
masnwilliams Oct 9, 2026
0838a38
Place the keyboard control in pixels from its measured size
masnwilliams Oct 9, 2026
bc5599b
Reuse a known cursor shape when a tap lands on the same spot
masnwilliams Oct 9, 2026
90ead0c
Only bind a cursor shape to a pointer position it can belong to
masnwilliams Oct 9, 2026
b978a25
Treat any text cursor after a tap as the keyboard signal
masnwilliams Oct 9, 2026
4e938a0
Keep the keyboard when a tap lands on text under an unchanged I-beam
masnwilliams Oct 10, 2026
77dd1eb
Raise the keyboard on iOS when the text cursor arrives after the tap
masnwilliams Oct 13, 2026
f4fc300
Drop a pending tap keyboard decision when a new touch begins
masnwilliams Oct 13, 2026
3dc5b39
Merge branch 'main' into hypeship/live-view-touch-input
masnwilliams Oct 13, 2026
194695e
Make touch ownership and keyboard decisions explicit in the live view
masnwilliams Oct 14, 2026
faa28e0
Release the mouse button when a drag is cancelled
masnwilliams Oct 14, 2026
dc352a9
Type soft keyboard capitals from the remote's Shift and Caps Lock state
masnwilliams Oct 14, 2026
fb56f3d
Turn the remote Caps Lock off when the soft keyboard opens
masnwilliams Oct 14, 2026
eef3025
Merge branch 'main' into hypeship/live-view-touch-input
masnwilliams Oct 14, 2026
1562e45
Keep the video's scale above the soft keyboard, add a paste control, …
masnwilliams Oct 14, 2026
a13955c
Measure the keyboard pan from the player's layout position
masnwilliams Oct 14, 2026
9fe86cf
Keep a possibly stale I-beam from raising the keyboard, and keep curs…
masnwilliams Oct 14, 2026
4e7a9b7
Limit the stale cursor window to taps still waiting for their cursor
masnwilliams Oct 14, 2026
04193d2
Drop on-page chips, close the keyboard after a submit, stop parking t…
masnwilliams Oct 14, 2026
9f13cae
Keep the clipboard sync from replacing a pending Copy write
masnwilliams Oct 14, 2026
5d6e96e
Add an extra keys row, paste through the remote clipboard, offer Copy…
masnwilliams Oct 14, 2026
8903604
Close the soft keyboard before opening the clipboard panel on touch
masnwilliams Oct 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion images/chromium-headful/client/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="viewport" content="width=device-width,initial-scale=1.0,viewport-fit=cover">
<title>Kernel</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTEiIGhlaWdodD0iMTM5IiB2aWV3Qm94PSIwIDAgMTExIDEzOSIgZmlsbD0ibm9uZSI+CjxwYXRoIGQ9Ik0xMS4xNzUzIDBDMTQuMjY3MiAwIDE2Ljg5NDIgMS4wOTc2IDE5LjA1NDggMy4yOTMxMUMyMS4yNTI2IDUuNDUxNCAyMi4zNTEzIDguMDc1MTYgMjIuMzUxMyAxMS4xNjM3VjE2LjU1NjJDMjIuMzUxMyAxNy4yMjYxIDIzLjIwMzUgMTcuNTExMSAyMy42MDY0IDE2Ljk3NTlMMzMuMDI0NCA0LjQ2NTc2QzM0Ljg0OTggMi4wMDk3NSAzNy4yODk4IDAuNTc2OTU2IDQwLjM0NDUgMC4xNjc2MTlDNDMuMzk5IC0wLjI3ODgzIDQ2LjE3NDUgMC40MDk1NSA0OC42NzAzIDIuMjMyODhDNTEuMTI4NyA0LjA5MzQ0IDUyLjU2MjcgNi41NDk0MiA1Mi45NzI1IDkuNjAwNjRDNTMuNDE5NSAxMi42NTIxIDUyLjcxMTkgMTUuNDA2NCA1MC44NDkzIDE3Ljg2MjRMNDEuMTM5IDMwLjgxMjdDNDAuNzY3MyAzMS4zMDg0IDQwLjc2NyAzMS45OTAxIDQxLjEzODMgMzIuNDg2MUw1My41ODcxIDQ5LjEyMDdDNTUuNDQ5NyA1MS41NzY3IDU2LjE1OCA1NC4zMzAzIDU1LjcxMSA1Ny4zODE3QzU1LjMwMTIgNjAuNDMzMSA1My44NjY2IDYyLjg4ODkgNTEuNDA4MSA2NC43NDk1QzQ4LjkxMjIgNjYuNTcyOSA0Ni4xMzY5IDY3LjI2MTMgNDMuMDgyMyA2Ni44MTQ4QzQwLjAyNzcgNjYuNDA1NCAzNy41ODc2IDY0Ljk3MzEgMzUuNzYyMiA2Mi41MTczTDIzLjYwNTcgNDYuNDI3MkMyMy4yMDIzIDQ1Ljg5MzIgMjIuMzUxMyA0Ni4xNzgzIDIyLjM1MTMgNDYuODQ3NlY1NS44MTg2QzIyLjM1MTMgNTguOTA3MiAyMS4yNTI2IDYxLjU0OTYgMTkuMDU0OCA2My43NDUxQzE2Ljg5NDIgNjUuOTAzNCAxNC4yNjcyIDY2Ljk4MjQgMTEuMTc1MyA2Ni45ODI0QzguMDgzNTMgNjYuOTgyMyA1LjQzODQ0IDY1LjkwMzQgMy4yNDA2NCA2My43NDUxQzEuMDgwMDYgNjEuNTQ5NiAwIDU4LjkwNzIgMCA1NS44MTg2VjExLjE2MzdDMS4wOTI0OGUtMDUgOC4wNzUxNCAxLjA4MDA2IDUuNDUxNDEgMy4yNDA2NCAzLjI5MzExQzUuNDM4NDQgMS4wOTc2NiA4LjA4MzUzIDguMzA0OTFlLTA1IDExLjE3NTMgMFoiIGZpbGw9IiM4MUIzMDAiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik04Ni41OTE4IDAuNTU4MDVDOTIuNzc1NiAwLjU1ODA1NiA5OC4wNDY4IDIuNzM1MzEgMTAyLjQwNSA3LjA4OTA4QzEwNi43NjQgMTEuNDQyOSAxMDguOTQzIDE2LjcwODMgMTA4Ljk0MyAyMi44ODU1QzEwOC45NDMgMjkuMDYyOCAxMDYuNzY0IDM0LjMyODggMTAyLjQwNSAzOC42ODI2QzEwMi4yNDggMzguODQwMSAxMDIuMDkgMzguOTkzNiAxMDEuOTMyIDM5LjE0MzJDMTAxLjM5NiAzOS42NTE4IDEwMS4yOTkgNDAuNDgzNiAxMDEuNzQyIDQxLjA3NDlMMTA3Ljc3IDQ5LjEyMDdDMTA5LjYzMiA1MS41NzY3IDExMC4zNCA1NC4zMzA0IDEwOS44OTMgNTcuMzgxN0MxMDkuNDgzIDYwLjQzMyAxMDguMDQ5IDYyLjg4ODkgMTA1LjU5MSA2NC43NDk1QzEwMy4wOTUgNjYuNTcyOCAxMDAuMzIgNjcuMjYxMiA5Ny4yNjQ5IDY2LjgxNDhDOTQuMjEwNCA2Ni40MDU0IDkxLjc3MDIgNjQuOTczMSA4OS45NDQ5IDYyLjUxNzNMODIuMzA2NiA0OS4yMTI3QzgxLjk1MDYgNDguNTkzMiA4MS4wMDM5IDQ4Ljg0NTYgODEuMDAzOCA0OS41NjAyVjU1LjgxODZDODEuMDAzOCA1OC45MDczIDc5LjkwNTIgNjEuNTQ5NiA3Ny43MDczIDYzLjc0NTFDNzUuNTQ2NyA2NS45MDM0IDcyLjkyMDQgNjYuOTgyMyA2OS44Mjg1IDY2Ljk4MjRDNjYuNzM2NiA2Ni45ODI0IDY0LjA5MTcgNjUuOTAzMyA2MS44OTM4IDYzLjc0NTFDNTkuNzMzMiA2MS41NDk2IDU4LjY1MjUgNTguOTA3MyA1OC42NTI1IDU1LjgxODZWMTEuNzIxOEM1OC42NTI1IDguNjMzMjEgNTkuNzMzMiA2LjAwOTQ1IDYxLjg5MzggMy44NTExNkM2NC4wOTE3IDEuNjU1NzggNjYuNzM2NyAwLjU1ODA1MyA2OS44Mjg1IDAuNTU4MDVIODYuNTkxOFpNODMuNTI4MyAxOC4xNDExQzgxLjAyMzYgMTguMTQxMSA3OC45OTMgMjAuMTcxNiA3OC45OTMgMjIuNjc2M0M3OC45OTMgMjUuMTgxMSA4MS4wMjM2IDI3LjIxMTYgODMuNTI4MyAyNy4yMTE2Qzg2LjAzMzEgMjcuMjExNiA4OC4wNjM2IDI1LjE4MTEgODguMDYzNiAyMi42NzYzQzg4LjA2MzYgMjAuMTcxNiA4Ni4wMzMxIDE4LjE0MTEgODMuNTI4MyAxOC4xNDExWiIgZmlsbD0iIzgxQjMwMCIvPgo8cGF0aCBkPSJNNTAuMjkwNiA3MS41MTc2QzUzLjM4MjMgNzEuNTE3NyA1Ni4wMDgyIDcyLjYxNTUgNTguMTY4NyA3NC44MTA3QzYwLjM2NjYgNzYuOTY5MSA2MS40NjU5IDc5LjU5MjcgNjEuNDY1OSA4Mi42ODE0VjEyNy4zMzZDNjEuNDY1OSAxMzAuNDI1IDYwLjM2NjYgMTMzLjA2NyA1OC4xNjg3IDEzNS4yNjNDNTYuMDA4MiAxMzcuNDIxIDUzLjM4MjMgMTM4LjUgNTAuMjkwNiAxMzguNUM0Ny4xOTg4IDEzOC41IDQ0LjU3MjQgMTM3LjQwMiA0Mi40MTE4IDEzNS4yMDdMMjMuNTczIDExMy43NjdDMjMuMTQ4MyAxMTMuMjg0IDIyLjM1MTUgMTEzLjU4NSAyMi4zNTEzIDExNC4yMjhWMTI3LjMzNkMyMi4zNTEzIDEzMC40MjUgMjEuMjUyIDEzMy4wNjcgMTkuMDU0MSAxMzUuMjYzQzE2Ljg5MzUgMTM3LjQyMSAxNC4yNjcyIDEzOC41IDExLjE3NTMgMTM4LjVDOC4wODM0NyAxMzguNSA1LjQzODQ4IDEzNy40MjEgMy4yNDA2NCAxMzUuMjYzQzEuMDgwMDQgMTMzLjA2NyAwIDEzMC40MjUgMCAxMjcuMzM2VjgyLjY4MTRDMS4wOTI0OWUtMDUgNzkuNTkyNyAxLjA4MDAzIDc2Ljk2OTEgMy4yNDA2NCA3NC44MTA3QzUuNDM4NDggNzIuNjE1MyA4LjA4MzQ2IDcxLjUxNzcgMTEuMTc1MyA3MS41MTc2QzE0LjI2NzEgNzEuNTE3NiAxNi45MTIxIDcyLjU5NjcgMTkuMTEgNzQuNzU0OUwzNy44OTIyIDk2LjE4OTdDMzguMzE2NyA5Ni42NzQgMzkuMTE0NiA5Ni4zNzM4IDM5LjExNDYgOTUuNzI5OFY4Mi42ODE0QzM5LjExNDYgNzkuNTkyNyA0MC4xOTUzIDc2Ljk2OTEgNDIuMzU1OSA3NC44MTA3QzQ0LjU1MzggNzIuNjE1NCA0Ny4xOTg4IDcxLjUxNzYgNTAuMjkwNiA3MS41MTc2WiIgZmlsbD0iIzgxQjMwMCIvPgo8cGF0aCBkPSJNNzYuNzMyOSA3MS41MTc2Qzc5LjgyNDcgNzEuNTE3NiA4Mi40NTExIDcyLjYxNTQgODQuNjExNyA3NC44MTA3Qzg2LjgwOTYgNzYuOTY5MSA4Ny45MDg5IDc5LjU5MjcgODcuOTA4OSA4Mi42ODE0VjExNC4yMTlDODcuOTA5IDExNC45OSA4OC41MzM3IDExNS42MTQgODkuMzA0NCAxMTUuNjE0SDk5LjA4NDlDMTAyLjE3NyAxMTUuNjE1IDEwNC44MDMgMTE2LjcxMiAxMDYuOTY0IDExOC45MDhDMTA5LjE2MSAxMjEuMDY2IDExMC4yNiAxMjMuNjkgMTEwLjI2IDEyNi43NzhDMTEwLjI2IDEyOS44NjcgMTA5LjE2MiAxMzIuNTA5IDEwNi45NjQgMTM0LjcwNUMxMDQuODAzIDEzNi44NjMgMTAyLjE3NyAxMzcuOTQyIDk5LjA4NDkgMTM3Ljk0Mkg3Ni43MzI5QzczLjY0MTMgMTM3Ljk0MiA3MC45OTY3IDEzNi44NjMgNjguNzk4OSAxMzQuNzA1QzY2LjYzODMgMTMyLjUwOSA2NS41NTc2IDEyOS44NjcgNjUuNTU3NiAxMjYuNzc4VjgyLjY4MTRDNjUuNTU3NiA3OS41OTI3IDY2LjYzODMgNzYuOTY5MSA2OC43OTg5IDc0LjgxMDdDNzAuOTk2NyA3Mi42MTU1IDczLjY0MTMgNzEuNTE3NyA3Ni43MzI5IDcxLjUxNzZaIiBmaWxsPSIjODFCMzAwIi8+CjxwYXRoIGQ9Ik0xMDEuNTkzIDcxLjUxNzZDMTA2LjM4IDcxLjUxNzYgMTEwLjI2IDc1LjM5MzggMTEwLjI2IDgwLjE3NTJDMTEwLjI2IDg0Ljk1NjggMTA2LjM4IDg4LjgzMjkgMTAxLjU5MyA4OC44MzI5Qzk2LjgwNjUgODguODMyOCA5Mi45MjY2IDg0Ljk1NjcgOTIuOTI2NiA4MC4xNzUyQzkyLjkyNjcgNzUuMzkzOCA5Ni44MDY1IDcxLjUxNzcgMTAxLjU5MyA3MS41MTc2WiIgZmlsbD0iIzgxQjMwMCIvPgo8L3N2Zz4K">
<link rel="manifest" href="site.webmanifest">
Expand Down
5 changes: 4 additions & 1 deletion images/chromium-headful/client/src/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,12 @@
bottom: 0;
max-width: 100vw;
max-height: 100vh;
max-height: 100dvh;
flex-direction: row;
display: flex;

.neko-main {
min-width: 360px;
min-width: 0;
max-width: 100%;
flex-grow: 1;
flex-direction: column;
Expand Down Expand Up @@ -144,10 +145,12 @@

.neko-main {
height: 100vh;
height: 100dvh;
}

.neko-menu {
height: 100vh;
height: 100dvh;
width: 100% !important;
}
}
Expand Down
73 changes: 69 additions & 4 deletions images/chromium-headful/client/src/components/clipboard.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
<template>
<div class="clipboard" v-if="opened" @click="$event.stopPropagation()">
<div class="clipboard" :class="{ touch }" v-if="opened" @click="$event.stopPropagation()">
<p v-if="touch && hint" class="hint">{{ hint }}</p>
<textarea ref="textarea" v-model="clipboard" @focus="$event.target.select()" />
<div v-if="touch" class="actions">
<button @click.stop.prevent="send">Paste into page</button>
<button @click.stop.prevent="close">Close</button>
</div>
</div>
</template>

Expand Down Expand Up @@ -32,19 +37,67 @@
background: $text-normal;
}
}

// on touch devices it sits at the top, clear of the soft keyboard and the controls
&.touch {
top: calc(56px + env(safe-area-inset-top));
bottom: auto;
left: 50%;
right: auto;
z-index: 4;
width: calc(100% - 24px);
height: auto;
max-height: none;
transform: translateX(-50%);
Comment thread
cursor[bot] marked this conversation as resolved.
box-sizing: border-box;

textarea {
display: block;
height: 96px;
font-size: 16px;
user-select: text;
-webkit-user-select: text;
}

.hint {
margin: 0 0 4px;
color: $text-normal;
font-size: 13px;
}

.actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 4px;

button {
min-height: 36px;
padding: 0 12px;
border: 0;
border-radius: 18px;
background: rgba($color: #fff, $alpha: 0.15);
color: $text-normal;
font-weight: 600;
}
}
}
}
</style>

<script lang="ts">
import { Component, Ref, Vue } from 'vue-property-decorator'
import { Component, Prop, Ref, Vue } from 'vue-property-decorator'

@Component({
name: 'neko-clipboard',
})
export default class extends Vue {
@Ref('textarea') readonly _textarea!: HTMLTextAreaElement
// the fallback when a touch device's clipboard cannot be read or written
@Prop(Boolean) readonly touch!: boolean

private opened: boolean = false
private hint = ''
private typing?: number

get clipboard() {
Expand All @@ -62,10 +115,22 @@
this.typing = window.setTimeout(() => this.$accessor.remote.sendClipboard(this.clipboard), 500)
}

open() {
open(hint = '') {
this.opened = true
this.hint = hint
document.body.addEventListener('click', this.close)
window.setTimeout(() => this._textarea.focus(), 0)
// focusing it would raise the soft keyboard over it
if (!this.touch) window.setTimeout(() => this._textarea.focus(), 0)
}

// the text goes to the remote clipboard as typed; this pastes it into the page
send() {
if (this.typing) {
clearTimeout(this.typing)
this.typing = undefined
}
this.$emit('paste', this.clipboard)
this.close()
}

close() {
Expand Down
Loading
Loading