diff --git a/mobile/src/assets/fonts/LICENSE-NotoSansSymbols b/mobile/src/assets/fonts/LICENSE-NotoSansSymbols new file mode 100644 index 00000000..7c95767f --- /dev/null +++ b/mobile/src/assets/fonts/LICENSE-NotoSansSymbols @@ -0,0 +1,93 @@ +Copyright 2022 The Noto Project Authors (https://github.com/notofonts/symbols) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/mobile/src/assets/fonts/NotoEmoji-subset.woff2 b/mobile/src/assets/fonts/NotoEmoji-subset.woff2 new file mode 100644 index 00000000..fcf6e426 Binary files /dev/null and b/mobile/src/assets/fonts/NotoEmoji-subset.woff2 differ diff --git a/mobile/src/assets/fonts/NotoSansSymbols-subset.woff2 b/mobile/src/assets/fonts/NotoSansSymbols-subset.woff2 new file mode 100644 index 00000000..0c9853b7 Binary files /dev/null and b/mobile/src/assets/fonts/NotoSansSymbols-subset.woff2 differ diff --git a/mobile/src/assets/fonts/NotoSansSymbols2-subset.woff2 b/mobile/src/assets/fonts/NotoSansSymbols2-subset.woff2 new file mode 100644 index 00000000..c4d62b38 Binary files /dev/null and b/mobile/src/assets/fonts/NotoSansSymbols2-subset.woff2 differ diff --git a/mobile/src/main.ts b/mobile/src/main.ts index c0d6d80c..40c0b4d6 100644 --- a/mobile/src/main.ts +++ b/mobile/src/main.ts @@ -2414,7 +2414,7 @@ function terminalScreen(host: Host, place: Place, pane: PaneView, title: string, cols: 80, rows: 24, fontSize: readablePx(), - fontFamily: 'Hack, "Symbols Nerd Font Mono", Menlo, ui-monospace, monospace', + fontFamily: 'Hack, "Symbols Nerd Font Mono", "Noto Sans Symbols", "Noto Sans Symbols 2", "Noto Emoji", Menlo, ui-monospace, monospace', scrollback: 5000, cursorBlink: false, theme: terminalTheme(), @@ -2750,23 +2750,35 @@ function terminalScreen(host: Host, place: Place, pane: PaneView, title: string, field.value = ""; edited(); }; - // Return sends; Shift-Return, on a keyboard that has it, starts a line. + // Return sends; Shift-Return starts a line, on a keyboard with a real + // Shift. The phone's own keyboard says Shift whenever it has armed a + // capital — at the start, after a full stop — so there it is ignored. + const hardKeys = matchMedia("(any-pointer: fine)"); let newLine = false; field.addEventListener("keydown", (e) => { - newLine = e.key === "Enter" && e.shiftKey; + const shifted = e.shiftKey && hardKeys.matches; + newLine = e.key === "Enter" && shifted; // Enter that confirms an IME's candidate is the IME's, not a send. - if (e.key !== "Enter" || e.shiftKey || e.isComposing || e.keyCode === 229) return; + if (e.key !== "Enter" || shifted || e.isComposing || e.keyCode === 229) return; e.preventDefault(); void submit(); }); - // A Return that comes as a line break with no key behind it — the - // on-screen keyboard can deliver it that way — sends too. - field.addEventListener("beforeinput", (e) => { + // A Return that comes as text with no key behind it sends too. The + // on-screen keyboard delivers it that way — as a line break, or, with + // corrections on, as a typed newline once it has settled the word — + // and not always cancellably, so the newline is taken back out. + field.addEventListener("input", (e) => { const wasShift = newLine; newLine = false; - if (e.isComposing || wasShift) return; - if (e.inputType !== "insertLineBreak" && e.inputType !== "insertParagraph") return; - e.preventDefault(); + const typed = e as InputEvent; + const newline = + typed.inputType === "insertLineBreak" || typed.inputType === "insertParagraph" || typed.data === "\n"; + if (!newline || typed.isComposing || wasShift) return; + const at = field.selectionStart; + if (field.value[at - 1] === "\n") { + field.value = field.value.slice(0, at - 1) + field.value.slice(at); + edited(); + } void submit(); }); sendKey.onpointerdown = (e) => e.preventDefault(); @@ -3462,6 +3474,9 @@ function terminalScreen(host: Host, place: Place, pane: PaneView, title: string, Promise.allSettled([ document.fonts.load("12px Hack"), document.fonts.load('12px "Symbols Nerd Font Mono"', "\ue0a0"), + document.fonts.load('12px "Noto Sans Symbols"', "\u23bf"), + document.fonts.load('12px "Noto Sans Symbols 2"', "\u23fa"), + document.fonts.load('12px "Noto Emoji"', "\u23f0"), ]).finally(() => { if (!alive) return; term.open(screenEl); diff --git a/mobile/src/style.css b/mobile/src/style.css index b3de2e21..77cce7f2 100644 --- a/mobile/src/style.css +++ b/mobile/src/style.css @@ -25,6 +25,29 @@ font-display: block; } +/* What agents and TUIs draw their marks with — Claude Code's ⏺ and ⎿, ✔, + ⚠, braille spinners — which the phone's own fonts leave as empty boxes + in a canvas. Noto's symbols, cut down to those blocks. */ +@font-face { + font-family: "Noto Sans Symbols"; + src: url("./assets/fonts/NotoSansSymbols-subset.woff2") format("woff2"); + font-display: block; +} + +@font-face { + font-family: "Noto Sans Symbols 2"; + src: url("./assets/fonts/NotoSansSymbols2-subset.woff2") format("woff2"); + font-display: block; +} + +/* The few of those that are emoji by default (⏰ ✅ ❌ ✨), in outline, as + text: a canvas leaves them as boxes rather than reach for colour ones. */ +@font-face { + font-family: "Noto Emoji"; + src: url("./assets/fonts/NotoEmoji-subset.woff2") format("woff2"); + font-display: block; +} + :root { --canvas: #f3f3f1; --cell: #fcfcfb; @@ -59,7 +82,7 @@ --done: #22c55e; --ui: -apple-system, BlinkMacSystemFont, "SF Pro Text", "PingFang SC", Roboto, "Segoe UI", system-ui, sans-serif; - --mono: Hack, "Symbols Nerd Font Mono", "SF Mono", Menlo, ui-monospace, monospace; + --mono: Hack, "Symbols Nerd Font Mono", "Noto Sans Symbols", "Noto Sans Symbols 2", "Noto Emoji", "SF Mono", Menlo, ui-monospace, monospace; --ease: cubic-bezier(0.32, 0.72, 0, 1); /* The home indicator's gap; none while the keyboard is up, which covers