├── .gitignore ├── CGLTexImageIOSurface2D.md ├── LICENSE ├── README.md ├── arch-install-errata.md ├── arch-stripped-base-packages.txt ├── async-gpu-downloads.md ├── base64-embedding ├── airhorn.html ├── honk.html └── toBase64.html ├── colors ├── color-quads-16-127-235.html ├── color_quads.html ├── from-coeffs.html ├── icc-profile-editor.html └── matrices.js ├── content-script-injection ├── browser-content-script-extensions.md ├── content-script.js ├── content_script_embed.py ├── injected-src.js ├── injected-text.js ├── manifest.json └── test.html ├── count-loop.html ├── count-spins.html ├── cpp ├── CopyRingBuffer.h ├── IntRange.h ├── includes-list.py ├── includes-shell.py └── narrow_cast.cpp ├── d3d_stream_output_same_buffer_disjoint.cpp ├── depot_tools-on-arch.md ├── end-script-parsing-poc.html ├── fake-wake-lock.html ├── fake-wake-lock.js ├── gen-index.py ├── git-server-install.txt ├── gl ├── egl │ ├── egl.h │ ├── eglext.h │ ├── eglplatform.h │ └── khrplatform.h ├── gl │ ├── glcorearb.h │ ├── glext.h │ ├── glxext.h │ └── wglext.h ├── gles │ ├── gl2ext.h │ ├── gl32.h │ └── gl3platform.h ├── gles30_state_tables.cpp.md └── search-headers.html ├── greasemonkey-embed.html ├── https_server.py ├── index.html ├── js-for-range-benchmark.html ├── json-loader.html ├── md-minutes-to-details.html ├── misc.md ├── murmurhash3.js ├── parse-lang.js ├── pgo-clang.md ├── polyfills.md ├── pyroboros.html ├── remoted-readback.md ├── replace.py ├── set-python.sh ├── ssh-auto-lock.py ├── ssh-auto-lock.py.md ├── toast.ps1 └── toast.ps1 ├── voice-guesser.html ├── webgl-ext-packs.md ├── webgl-from-worker ├── index.html └── worker.js ├── webgl-one-to-many └── index.html ├── webgl-style.md ├── webgl ├── InL7Il5.mp4 ├── best-practices.md ├── ccw-point.html ├── device-pixel-presnap.html ├── device-pixel-tester.html ├── fingerprint-v1.html ├── fingerprint.html ├── low-power.html ├── many-triangles.html ├── ping-pong.html ├── screen-quad.html ├── testcase-srgb-average.html ├── video-upload.html ├── viewport-scissor.html ├── webgl-clipspace.html ├── webgl-feature-levels.html ├── webgl-feature-levels.js └── webgl-v1.1.js ├── webgpu ├── adapter-report.html ├── example-wgsl-preprocess-and-map-errors.html └── wgsl-boids-ptr-compiled.html └── webtorrent.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | server.pem 107 | -------------------------------------------------------------------------------- /CGLTexImageIOSurface2D.md: -------------------------------------------------------------------------------- 1 | 2 | Must use GL_TEXTURE_RECTANGLE. 3 | 4 | Warning: For IOSurfaceGetPixelFormat() -> '2vuy', IOSurfaceGetPlaneCount() -> 0! 5 | 6 | 7 | iosurf format: 2vuy (0x32767579) 8 | 8a51 80/128 8a1f/85bb 9 | GL_RGB_RAW_422_APPLE: 10008 10 | GL_RGB_422_APPLE: 0 11 | GL_RGB: 10008 12 | GL_RGB_RAW_422_APPLE non-rev: 10008 13 | GL_RGB_422_APPLE non-rev: 0 14 | GL_RGB non-rev: 10008 15 | 16 | https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_rgb_422.txt 17 | This adds GL_RGB_RAW_422_APPLE to `internalFormat`, but that yields an error. 18 | However, RGB/RGB_422_APPLE/UNSIGNED_SHORT_8_8_[REV_]APPLE works. 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 jdashg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # snippets 2 | Snippets 3 | -------------------------------------------------------------------------------- /arch-install-errata.md: -------------------------------------------------------------------------------- 1 | # During setup 2 | 3 | ## Live CD copy-on-write partition too small 4 | 5 | *(c/o https://www.ostechnix.com/adjust-size-root-partition-live-arch-linux/)* 6 | 7 | ### Option 1: At boot 8 | 9 | When you boot the Arch Linux livecd, press e or hit tab key to edit the kernel parameters. 10 | Go to the end of the line that says “…. linux=… initrd=….” something like that and append cow_spacesize=16G at the end to get 16GB size root partition or whatever space left from RAM. 11 | 12 | ### Option 2: Live 13 | 14 | ``` 15 | # mount -o remount,size=16G /run/archiso/cowspace 16 | ``` 17 | 18 | ## mirrorlist 19 | 20 | ``` 21 | # pacman -S reflector 22 | # pushd /etc/pacman.d 23 | # cp mirrorlist mirrorlist.bak 24 | # reflector --protocol https --country United\ States --score 10 > mirrorlist 25 | ``` 26 | 27 | ### If using syslinux for the bootloader 28 | syslinux can't use ext4 with the 64bit feature, which is now the default in mkfs.ext4. 29 | Instead, create without the 64bit feature: 30 | 31 | ~~~ 32 | $ mkfs.ext4 -O \^64bit /dev/sda1 33 | ~~~ 34 | 35 | ### Use fs labels instead of uuids 36 | 37 | ~~~ 38 | $ e2label /dev/sda1 foo 39 | $ genfstab -L /mnt > /mnt/etc/fstab 40 | ~~~ 41 | 42 | ## Bootloader: syslinux 43 | 44 | ~~~ 45 | $ pacman -S syslinux 46 | $ cp /usr/lib/syslinux/bios/*.c32 /boot/syslinux/ 47 | $ extlinux --install /boot/syslinux/ 48 | ~~~ 49 | 50 | ### Edit /boot/syslinux/syslinux.cfg 51 | Use: `APPEND root=LABEL=foo rw` 52 | 53 | For non-stop boots, comment out all `UI` entries and use `PROMPT 0`. 54 | 55 | ### altmbr 56 | An alternative MBR which Syslinux provides is: altmbr.bin. 57 | This MBR does not scan for bootable partitions; instead, the last byte of the MBR is set to a value indicating which partition to boot from. 58 | Here is an example of how altmbr.bin can be copied into position: 59 | 60 | ~~~ 61 | $ printf '\x5' | cat /usr/lib/syslinux/bios/altmbr.bin - | dd bs=440 count=1 iflag=fullblock of=/dev/sda 62 | ~~~ 63 | 64 | In this case, a single byte of value 5 (hexadecimal) is appended to the contents of altmbr.bin and the resulting 440 bytes are written to the MBR on device sda. 65 | Syslinux was installed on the first logical partition (/dev/sda5) of the disk. 66 | 67 | # After first boot 68 | 69 | ## No ethernet on first start, so setup networkd. (netctl is no longer installed by default :( ) 70 | 71 | ~~~ 72 | $ systemctl start systemd-networkd 73 | $ systemctl start systemd-resolved 74 | ~~~ 75 | 76 | ## Create a user 77 | 78 | ~~~ 79 | useradd -m -G wheel ((-m creates ~)) 80 | ~~~ 81 | 82 | 83 | ## sudo 84 | Install, and in `/etc/sudoers`, uncomment `%wheel` entry. 85 | 86 | ## GUI 87 | 88 | ~~~ 89 | $ pacman -S xfce4 90 | $ pacman -S xterm ((not installed by default!)) 91 | $ echo 'exec startxfce4' > ~/.xinitrc 92 | $ startx ((when desired)) 93 | ~~~ 94 | 95 | ### Lockscreen 96 | 97 | ~~~ 98 | $ pacman -S slock 99 | ~~~ 100 | 101 | ## Disable terminal beep 102 | 103 | ~~~ 104 | $ echo 'set bell-style none' > ~/.inputrc 105 | $ bash ((to refresh)) 106 | ~~~ 107 | -------------------------------------------------------------------------------- /arch-stripped-base-packages.txt: -------------------------------------------------------------------------------- 1 | Removable: 2 | man-db 3 | groff 4 | texinfo 5 | s-nail 6 | reiserfsprogs 7 | xfsprogs 8 | man-pages 9 | gettext 10 | jfsutils 11 | mdadm 12 | libpipeline 13 | pcmciautils 14 | sed 15 | tar 16 | 17 | 18 | stuck: 19 | perl: openssl: coreutils 20 | sqlite: gnupg: gpgme: pacman 21 | cryptsetup: systemd 22 | gawk: mkinitcpio 23 | grep: mkinitcpio 24 | 25 | 26 | My minimal choices: (pacman -Qet(t)) 27 | diffutils 28 | inetutils 29 | iputils 30 | licenses 31 | linux 32 | logrotate 33 | lvm2 34 | nano 35 | netctl 36 | pacman 37 | pciutils 38 | psmisc 39 | syslinux 40 | usbutils 41 | vim 42 | which 43 | -------------------------------------------------------------------------------- /async-gpu-downloads.md: -------------------------------------------------------------------------------- 1 | # Async downloads in blocking APIs 2 | 3 | ## Conceptually, we want to: 4 | 5 | - Enqueue writes to a GPU-side buffer 6 | - Download the result asynchronously 7 | - Map and operate on the result only once it's ready, without stalling the pipeline 8 | 9 | ## Abstract approach 10 | 11 | - Enqueue writes to a GPU-side buffer 12 | - Enqueue a copy from that buffer to a CPU-side buffer 13 | - Enqueue a fence to establish when the copy is complete 14 | - Wait/poll for the fence to complete 15 | - Map the CPU buffer that we copied into 16 | - Since the fence has been passed, this should be immediate 17 | - ... 18 | - Unmap 19 | 20 | ## OpenGL ES 3+ example class 21 | 22 | ~~~ 23 | class AsyncReadback { 24 | GLsizeiptr mSize; 25 | GLuint mBuffer; 26 | GLuint mFence; 27 | 28 | public: 29 | AsyncReadback(GLenum srcTarget, GLintptr readOffset, GLsizeiptr size) { 30 | mSize = size; 31 | glGenBuffers(1, &mBuffer); 32 | glBindBuffer(GL_COPY_WRITE_BUFFER, mBuffer); 33 | glBufferData(GL_COPY_WRITE_BUFFER, size, nullptr, GL_STREAM_READ); 34 | glCopyBufferSubData(srcTarget, GL_COPY_WRITE_BUFFER, readOffset, 0, size); 35 | mFence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); 36 | // NB: This isn't guaranteed to become signaled unless you glFlush! (or similar) 37 | } 38 | 39 | ~AsyncReadback() { 40 | glDeleteSync(mFence); 41 | glDeleteBuffers(1, &mBuffer); 42 | } 43 | 44 | const void* TryMap(GLenum target) { 45 | if (mFence) { 46 | GLenum status; 47 | glGetSynciv(mFence, GL_SYNC_STATUS, 1, nullptr, (GLint*)&status); 48 | if (status != GL_SIGNALED) 49 | return nullptr; 50 | glDeleteSync(mFence); 51 | mFence = 0; 52 | } 53 | glBindBuffer(target, mBuffer); 54 | return (const void*)glMapBufferRange(target, 0, mSize, GL_MAP_READ_BIT); 55 | } 56 | 57 | void Unmap(GLenum target) { 58 | glBindBuffer(target, mBuffer); 59 | glUnmapBuffer(target); 60 | } 61 | }; 62 | ~~~ 63 | 64 | ## Takeaways 65 | 66 | - Blocking APIs can support async behavior without callbacks or other sophisticated async mechanisms 67 | - Making the copy explicit leads to predictable behavior 68 | - This is the approach provided in extant native APIs 69 | - Some APIs do allow for synchronizing/stalling/blocking reads 70 | - This notably *does* includes WebGL (glReadPixels, glFinish) for historical reasons 71 | 72 | ### Notes 73 | 74 | All references to GPU-side and CPU-side can be replaced by the more generic remote/non-mappable and local/mappable. 75 | This approach is not non-UMA or discrete-GPU specific. 76 | A multithreaded driver/implementation (especially a cross-process one) would benefit from this even on a UMA device. 77 | 78 | In WebGL, we can warn when an app messes this up and incurs a pipeline stall. 79 | This warning was implemented in Firefox in [bug 1425488](https://bugzilla.mozilla.org/show_bug.cgi?id=1425488). 80 | -------------------------------------------------------------------------------- /base64-embedding/toBase64.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /colors/color-quads-16-127-235.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
Width: 8 |
Height: 9 |
Text: 10 |
11 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /colors/color_quads.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | color_quads.html (2022-07-15) 21 | 22 | 23 |
24 | Canvas Width: 25 |
Canvas Height: 26 |
Canvas Colorspace: 27 |
Canvas Context Type: 31 |
Canvas Context Options: 32 | 33 |
34 |
OuterTopLeft: 35 |
OuterTopRight: 36 |
OuterBottomLeft: 37 |
OuterBottomRight: 38 |
39 |
InnerTopLeft: 40 |
InnerTopRight: 41 |
InnerBottomLeft: 42 |
InnerBottomRight: 43 |
44 |
45 |
46 |
47 | 232 | 233 | 234 | -------------------------------------------------------------------------------- /colors/matrices.js: -------------------------------------------------------------------------------- 1 | 2 | function vecDot(a, b) { 3 | var ret = 0; 4 | if (a.length !== b.length) throw new Error(`${a.length} !== ${b.length}`); 5 | for (var i = 0; i < a.length; i++) { 6 | ret += a[i]*b[i]; 7 | } 8 | return ret; 9 | } 10 | 11 | function matRow(m, row) { 12 | return m[row]; 13 | } 14 | 15 | function matCol(m, col) { 16 | let ret = []; 17 | for (var i = 0; i < m.length; i++) { 18 | ret.push(m[i][col]); 19 | } 20 | return ret; 21 | } 22 | 23 | function matMul(a, b) { 24 | const ret = []; 25 | for (let y = 0; y < a.length; y++) { 26 | const row = []; 27 | for (let x = 0; x < b[0].length; x++) { 28 | const val = vecDot(matRow(a, y), matCol(b, x)); 29 | row.push(val); 30 | } 31 | ret.push(row); 32 | } 33 | return ret; 34 | } 35 | 36 | function matMulVec(a, b) { 37 | b = [b]; 38 | b = matTrans(b); 39 | return matMul(a, b); 40 | } 41 | 42 | function matAdd(a, b) { 43 | if (a.length !== b.length) throw new Error(`${a.length} !== ${b.length}`); 44 | const ret = []; 45 | for (let y = 0; y < a.length; y++) { 46 | if (a[y].length !== b[y].length) throw new Error(`${a[y].length} !== ${b[y].length}`); 47 | const row = []; 48 | for (let x = 0; x < a[y].length; x++) { 49 | const val = a[y][x] + b[y][x]; 50 | row.push(val); 51 | } 52 | ret.push(row); 53 | } 54 | return ret; 55 | } 56 | 57 | function matNeg(a) { 58 | const ret = []; 59 | for (let y = 0; y < a.length; y++) { 60 | const row = []; 61 | for (let x = 0; x < a[y].length; x++) { 62 | const val = -a[y][x]; 63 | row.push(val); 64 | } 65 | ret.push(row); 66 | } 67 | return ret; 68 | } 69 | 70 | function matDet(m) { 71 | if (m.length === 1) { 72 | if (m[0].length !== m.length) throw new Error(`${m[0].length} !== ${m.length}`); 73 | return m[0][0]; 74 | } 75 | 76 | let ret = 0; 77 | for (let x = 0; x < m.length; x++) { 78 | if (m[x].length !== m.length) throw new Error(`${m[x].length} !== ${m.length}`); 79 | const cofact = matCofactor(m, x, 0); 80 | ret += m[0][x]*cofact; 81 | } 82 | return ret; 83 | } 84 | 85 | function matString(m, precision) { 86 | precision = precision || 5; 87 | const rows = []; 88 | for (let i = 0; i < m.length; i++) { 89 | if (m[i].length !== m[0].length) throw new Error(`${m[i].length} !== ${m[0].length}`); 90 | const row = matRow(m, i); 91 | const format = function(x) { 92 | let str = x.toFixed(precision); 93 | if (str[0] != '-') 94 | str = ' ' + str; 95 | return str; 96 | }; 97 | const rowStrs = row.map(format); 98 | const rowStr = rowStrs.join(','); 99 | rows.push(rowStr); 100 | } 101 | const rowsStr = '[' + rows.join(',\n ') + ' ]'; 102 | return rowsStr; 103 | } 104 | 105 | function matTrans(m) { 106 | const n = m[0].length; 107 | const ret = []; 108 | for (let i = 0; i < n; i++) { 109 | ret.push(matCol(m, i)); 110 | } 111 | return ret; 112 | } 113 | 114 | function matMinorMat(m, X, Y) { 115 | if (m[0].length !== m.length) throw new Error(`${m[0].length} !== ${m.length}`); 116 | const n = m[0].length; 117 | const ret = []; 118 | for (let y = 0; y < n; y++) { 119 | if (y == Y) 120 | continue; 121 | 122 | const row = []; 123 | for (let x = 0; x < n; x++) { 124 | if (x == X) 125 | continue; 126 | 127 | row.push(m[y][x]); 128 | } 129 | ret.push(row); 130 | } 131 | return ret; 132 | } 133 | 134 | function matCofactor(m, X, Y) { 135 | if (m[0].length !== m.length) throw new Error(`${m[0].length} !== ${m.length}`); 136 | const minorMat = matMinorMat(m, X, Y); 137 | const minor = matDet(minorMat); 138 | let cofactor = minor; 139 | if ((X+Y) % 2 == 1) { 140 | cofactor *= -1; 141 | } 142 | return cofactor; 143 | } 144 | 145 | function matScale(m, k) { 146 | const ret = []; 147 | for (let y = 0; y < m.length; y++) { 148 | const row = []; 149 | for (let x = 0; x < m[y].length; x++) { 150 | row.push(m[y][x] * k); 151 | } 152 | ret.push(row); 153 | } 154 | return ret; 155 | } 156 | 157 | function matComatrix(m) { 158 | if (m[0].length !== m.length) throw new Error(`${m[0].length} !== ${m.length}`); 159 | const n = m[0].length; 160 | const ret = []; 161 | for (let y = 0; y < n; y++) { 162 | const row = []; 163 | for (let x = 0; x < n; x++) { 164 | row.push(matCofactor(m, x, y)); 165 | } 166 | ret.push(row); 167 | } 168 | return ret; 169 | } 170 | 171 | function matInv(A) { 172 | const rows = A.length; 173 | const cols = A[0].length; 174 | if (cols === rows + 1) { 175 | // Affine inverse. 176 | let b = matCol(A, cols-1); 177 | const B = A.map(r => r.slice(0, rows)); 178 | const ret = matInv(B); 179 | const bPrime = matMulVec(ret, b); 180 | for (let y = 0; y < rows; y++) { 181 | ret[y].push(-bPrime[y]); 182 | } 183 | return ret; 184 | } 185 | 186 | const det = matDet(A); 187 | //console.log('matDet', det); 188 | const comat = matComatrix(A); 189 | //console.log('comat', matString(comat)); 190 | const adj = matTrans(comat); 191 | //console.log('adj', matString(adj)); 192 | const inv = matScale(adj, 1/det); 193 | //console.log('inv', matString(inv)); 194 | return inv; 195 | } 196 | 197 | function matIdent(m_rows, n_cols) { 198 | n_cols = n_cols || m_rows; 199 | 200 | const ret = []; 201 | for (let y = 0; y < m_rows; y++) { 202 | const row = []; 203 | for (let x = 0; x < n_cols; x++) { 204 | let val = 0; 205 | if (x == y) { 206 | val = 1; 207 | } 208 | row.push(val); 209 | } 210 | ret.push(row); 211 | } 212 | return ret; 213 | } 214 | 215 | function matResized(A, m_rows, n_cols) { 216 | const ret = matIdent(m_rows, n_cols); 217 | for (let y = 0; y < Math.min(A.length, ret.length); y++) { 218 | for (let x = 0; x < Math.min(A[y].length, ret[y].length); x++) { 219 | ret[y][x] = A[y][x]; 220 | } 221 | } 222 | return ret; 223 | } 224 | -------------------------------------------------------------------------------- /content-script-injection/browser-content-script-extensions.md: -------------------------------------------------------------------------------- 1 | # Temporary Browser Extensions via Content Scripts 2 | 3 | ## Example `manifest.json` 4 | 5 | An example: 6 | ``` 7 | { 8 | "manifest_version": 2, 9 | "name": "untitled", 10 | "version": "0.5", 11 | 12 | "content_scripts": [ 13 | { 14 | "matches": ["https://*/*", "http://*/*", "file://*/*"], 15 | "js": ["my-content-script.js"], 16 | "run_at": "document_start", 17 | "all_frames": true 18 | } 19 | ] 20 | } 21 | ``` 22 | 23 | ## Loading in Firefox 24 | 25 | 1. Go to `about:debugging` 26 | 2. Select your running instance of the browser by clicking "This Nightly" 27 | * Or if you have and want to use another instance (e.g. on your phone) you can 28 | select that instead 29 | 3. Under "Temporary Extensions", click "Load Temporary Add-on..." 30 | 4. Select `path/to/ext/manifest.json` file 31 | 32 | There's a "Reload" button for when you changed anything (including included 33 | files) in the extension. 34 | 35 | ## Loading in Chrome 36 | 37 | 1. Go to `chrome://extensions` 38 | 2. Enable "Developer mode" if not yet enabled 39 | 3. "Load unpacked" 40 | 4. Select `path/to/ext/` **directory** 41 | 42 | Chrome is a little pickier than Firefox. For example, Chrome won't load an 43 | extension if there's a `__pycache__` directory in the extension directory. 44 | Extension directories for Chrome need to be "clean". 45 | 46 | # Interacting with webpages 47 | 48 | Content scripts will be run before anything in the page has loaded. 49 | This is *before* any other (non-extension) script runs in the page. 50 | However, content-scripts can't communicate with scripts in webpages. 51 | (and importantly for security, vice-versa) 52 | 53 | If you want to interact with the webpage, you'll need to inject scripts into the host page's DOM. 54 | 55 | ## Loading external files breaks early script execution 56 | 57 | Incurring even hidden microtask breaks (such as doing synchronous XHR!) 58 | will cause any code after the break to execute late. 59 | 60 | Load `manifest.json` for "Content Script Injection Test" and load `test.html` to 61 | see this `console.log` output: 62 | ``` 63 | begin content-script.js content-script.js:1:9 64 | begin injected_inline_text test.html:1:9 65 | request.open content-script.js:22:9 66 | begin test.html 8 | 9 | 10 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /count-loop.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /count-spins.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /cpp/CopyRingBuffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct CopyRingBuffer 4 | { 5 | uint8_t* const _data; 6 | const uint64_t _data_len; 7 | 8 | void Read(uint64_t offset, uint8_t* dest, uint64_t dest_size) const; 9 | void Write(uint64_t offset, const uint8_t* src, uint64_t src_size); 10 | }; 11 | 12 | //#include "CopyRingBuffer.h" 13 | 14 | #include 15 | #include 16 | 17 | #define UNLIKELY(X) (X) 18 | 19 | void 20 | CopyRingBuffer::Read(const uint64_t offset, uint8_t* const dest, 21 | const uint64_t dest_size) const 22 | { 23 | auto pos = offset; 24 | while (UNLIKELY( pos >= _data_len)) { 25 | pos -= _data_len; 26 | } 27 | 28 | auto itr = dest; 29 | auto bytes_left = dest_size; 30 | while (bytes_left) { 31 | const auto chunk_size = std::min(bytes_left, _data_len - pos); 32 | memcpy(itr, _data + pos, chunk_size); 33 | pos = 0; 34 | bytes_left -= chunk_size; 35 | itr += chunk_size; 36 | } 37 | } 38 | 39 | void 40 | CopyRingBuffer::Write(const uint64_t offset, const uint8_t* const src, 41 | const uint64_t src_size) 42 | { 43 | auto pos = offset; 44 | while (UNLIKELY( pos >= _data_len)) { 45 | pos -= _data_len; 46 | } 47 | 48 | auto itr = src; 49 | auto bytes_left = src_size; 50 | while (bytes_left) { 51 | const auto chunk_size = std::min(bytes_left, _data_len - pos); 52 | memcpy(_data + pos, itr, chunk_size); 53 | pos = 0; 54 | bytes_left -= chunk_size; 55 | itr += chunk_size; 56 | } 57 | } 58 | 59 | #undef UNLIKELY 60 | -------------------------------------------------------------------------------- /cpp/IntRange.h: -------------------------------------------------------------------------------- 1 | #ifndef INT_RANGE_H_ 2 | #define INT_RANGE_H_ 3 | 4 | template 5 | class IntRangeT final { 6 | const T mBegin; 7 | const T mEnd; 8 | 9 | public: 10 | IntRangeT(const T begin, const T end) : mBegin(begin), mEnd(end) { } 11 | 12 | struct IntIter final { 13 | T val; 14 | 15 | bool operator!=(const IntIter& rhs) const { return val != rhs.val; } 16 | T operator*() const { return val; } 17 | 18 | IntIter& operator++() { 19 | ++val; 20 | return *this; 21 | } 22 | }; 23 | 24 | auto begin() const { return IntIter{ mBegin }; } 25 | auto end() const { return IntIter{ mEnd }; } 26 | }; 27 | 28 | template 29 | inline IntRangeT IntRange(const T begin, const U end) { 30 | return IntRangeT(begin, end); 31 | } 32 | 33 | template 34 | inline IntRangeT IntRange(const T end) { 35 | return IntRange(0, end); 36 | } 37 | 38 | #endif // INT_RANGE_H_ 39 | -------------------------------------------------------------------------------- /cpp/includes-list.py: -------------------------------------------------------------------------------- 1 | #! python3 2 | 3 | from pathlib import * 4 | from typing import * 5 | import sys 6 | 7 | ''' 8 | def template(foo): 9 | pass 10 | ''' 11 | class MozBuild_Files(object): 12 | def __init__(self, path_glob, eg_srcdir_resolver_js=None): 13 | pass 14 | def __enter__(self): 15 | pass 16 | def __exit__(self, type, value, traceback): 17 | pass 18 | 19 | SRCDIR = Path('.') 20 | 21 | CONFIG: dict[str,Any] = { 22 | "OS_ARCH": "WINNT", 23 | 'CC_TYPE': 'clang', 24 | 'MOZ_WIDGET_TOOLKIT': '', 25 | 'SKIA_INCLUDES': [], 26 | 'LIBFUZZER_FLAGS': [], 27 | 'FUZZING': False, 28 | 'ENABLE_CLANG_PLUGIN': False, 29 | 'JS_STANDALONE': False, 30 | 'MOZ_BUILD_APP': 'browser', 31 | 'COMPILE_ENVIRONMENT': True, 32 | 'JS_HAS_INTL_API': True, 33 | 'BUILD_CTYPES': True, 34 | 'MOZ_OVERRIDE_GKRUST': False, 35 | 'ENABLE_TESTS': True, 36 | 'MOZ_SANDBOX': True, 37 | 'MOZ_USING_WASM_SANDBOXING': True, 38 | 'MOZ_UPDATER': False, 39 | 'MOZ_AUTH_EXTENSION': False, 40 | 'MOZ_WEBRTC': True, 41 | 'MOZ_UNIVERSALCHARDET': False, 42 | 'ACCESSIBILITY': False, 43 | 'MOZ_JPROF': False, 44 | 'MOZ_PREF_EXTENSIONS': False, 45 | 'ENABLE_WEBDRIVER': False, 46 | 'MOZ_GECKODRIVER': False, 47 | 'MOZ_WMF_CDM': False, 48 | 'MOZ_MEMORY': False, 49 | 'MOZ_BRANDING_DIRECTORY': False, 50 | 'MOZ_OVERRIDE_CARGO_CONFIG': False, 51 | 'ENABLE_WEBDRIVER': False, 52 | 'INTEL_ARCHITECTURE': True, 53 | 'SSE2_FLAGS': [], 54 | 'SSSE3_FLAGS': [], 55 | 'TARGET_CPU': 'x86_64', 56 | 'BUILD_ARM_NEON': False, 57 | } 58 | 59 | def new_mozbuild_globals(p: Path): 60 | mbg = { 61 | 'Files': MozBuild_Files, 62 | 'CONFIG': dict(CONFIG), 63 | 'MOCHITEST_MANIFESTS': [], 64 | 'MOCHITEST_CHROME_MANIFESTS': [], 65 | 'EXPORTS': ListTreeNode('EXPORTS'), 66 | 'UNIFIED_SOURCES': ListTreeNode('UNIFIED_SOURCES'), 67 | 'SOURCES': ListTreeNode('SOURCES'), 68 | 'LOCAL_INCLUDES': [], 69 | 'IPDL_SOURCES': [], 70 | 'TEST_DIRS': [], 71 | 'OS_LIBS': [], 72 | 'DEFINES': {}, 73 | 'USE_LIBS': [], 74 | 'CXXFLAGS': [], 75 | 'DIRS': [], 76 | 'SCHEDULES': ListTreeNode('SCHEDULES'), 77 | 'CONFIGURE_SUBST_FILES': ListTreeNode('CONFIGURE_SUBST_FILES'), 78 | 'CONFIGURE_DEFINE_FILES': ListTreeNode('CONFIGURE_DEFINE_FILES'), 79 | 'GENERATED_FILES': ListTreeNode('GENERATED_FILES'), 80 | 'PYTHON_UNITTEST_MANIFESTS': ListTreeNode('PYTHON_UNITTEST_MANIFESTS'), 81 | 'OBJDIR_PP_FILES': ListTreeNode('OBJDIR_PP_FILES'), 82 | 'SPHINX_TREES': ListTreeNode('SPHINX_TREES'), 83 | 'TOPSRCDIR': str(SRCDIR), 84 | '': p.parent, 85 | '__builtins__': {}, 86 | } 87 | 88 | def mozbuild_include(pathname: str): 89 | print(f'+mozbuild_include({pathname})') 90 | if pathname.startswith('/'): 91 | pinc = SRCDIR / pathname[1:] 92 | else: 93 | pinc = mbg[''] / pathname 94 | 95 | cwd_was = mbg[''] 96 | mbg[''] = pinc.parent 97 | exec(pinc.read_text(), mbg) 98 | mbg[''] = cwd_was 99 | print(f'-mozbuild_include({pathname})') 100 | 101 | mbg['include'] = mozbuild_include 102 | return mbg 103 | 104 | 105 | class GlobalState(): 106 | def __init__(self): 107 | self.repr_level = 0 108 | 109 | G = GlobalState() 110 | 111 | class ListTreeNode: 112 | name: str 113 | children: dict[str,'ListTreeNode'] 114 | contents: dict[str,'ListTreeNode'] 115 | 116 | def __init__(self, name): 117 | self.name = name 118 | self.children = {} 119 | self.contents = {} 120 | 121 | def __getattr__(self, k): 122 | print(f'__getattr__({k})') 123 | if k not in self.children: 124 | self.children[k] = ListTreeNode(k) 125 | return self.children[k] 126 | 127 | def __getitem__(self, k): 128 | print(f'__getitem__({k})') 129 | if k not in self.contents: 130 | self.contents[k] = ListTreeNode(k) 131 | return self.contents[k] 132 | 133 | def __setitem__(self, k, v): 134 | print(f'__setitem__({k}, {v})') 135 | self.contents[k] = v 136 | 137 | def __iadd__(self, names: list[str]): 138 | for x in names: 139 | self.contents[x] = ListTreeNode(x) 140 | return self 141 | 142 | def __repr__(self): 143 | INDENT = ' ' 144 | NL = f'\n{INDENT*G.repr_level}' 145 | ret = '' 146 | #ret = '\n' 147 | ret += NL + self.name 148 | if self.contents or self.children: 149 | ret += ': ' 150 | G.repr_level += 1 151 | if self.contents: 152 | ret += '[' + repr(list(self.contents.values()))[1:-1] + NL + ']' 153 | if self.children: 154 | ret += '{' + repr(list(self.children.values()))[1:-1] + NL + '}' 155 | G.repr_level -= 1 156 | return ret 157 | 158 | 159 | def read_mozbuild(path: Path): 160 | mbg = new_mozbuild_globals(path) 161 | #print(repr(mbg)) 162 | res = exec(path.read_text(), mbg) 163 | #print(repr(mbg)) 164 | del mbg['Files'] 165 | del mbg['CONFIG'] 166 | try: 167 | del mbg['BUG_COMPONENT'] 168 | except KeyError: 169 | pass 170 | del mbg[''] 171 | del mbg['include'] 172 | del mbg['__builtins__'] 173 | print(repr(mbg)) 174 | return mbg 175 | 176 | import re 177 | 178 | RE_INCLUDE = re.compile(' *# *include +([<"])(.+?)[>"]') 179 | 180 | INCLUDE_NODE_BY_PATH = {} 181 | 182 | class IncludeNode: 183 | def __init__(self, path: Path): 184 | self.path = path 185 | self.included_by = set() 186 | self.includes = set() 187 | 188 | def include_node_by_path(p: Path): 189 | k = str(p) 190 | if k not in INCLUDE_NODE_BY_PATH: 191 | INCLUDE_NODE_BY_PATH[k] = IncludeNode(p) 192 | return INCLUDE_NODE_BY_PATH[k] 193 | 194 | def parse_includes(cpp: Path): 195 | node = include_node_by_path(cpp) 196 | print(f'parse_includes({cpp}):') 197 | for line in cpp.read_text().split('\n'): 198 | m = RE_INCLUDE.match(line) 199 | if not m: 200 | continue 201 | path_text = m[2] 202 | print(' '*3 + path_text) 203 | 204 | if __name__ == '__main__': 205 | (_, mozbuild_spath) = sys.argv 206 | p = Path(mozbuild_spath) 207 | mb = read_mozbuild(p) 208 | cpps = [cpp for cpp in mb['SOURCES'].contents] 209 | cpps += [cpp for cpp in mb['UNIFIED_SOURCES'].contents] 210 | for cpp in cpps: 211 | cppp = p.parent / cpp 212 | parse_includes(cppp) 213 | -------------------------------------------------------------------------------- /cpp/narrow_cast.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include // std::numeric_limits 4 | #include 5 | 6 | template 7 | To narrow_cast_t(const From val) { 8 | assert(val >= std::numeric_limits::min() && 9 | val <= std::numeric_limits::max()); 10 | return static_cast(val); 11 | } 12 | 13 | namespace detail { 14 | 15 | template 16 | class NarrowCastClass final { 17 | const T val; 18 | 19 | public: 20 | explicit NarrowCastClass(const T rhs) : val(rhs) {} 21 | 22 | template 23 | operator U() const { 24 | return narrow_cast_t(val); 25 | } 26 | }; 27 | 28 | } // namespace detail 29 | 30 | template 31 | inline detail::NarrowCastClass narrow_cast(const T val) { 32 | return detail::NarrowCastClass(val); 33 | } 34 | 35 | void foo(int8_t) {} 36 | char bar(int64_t x) { 37 | return narrow_cast(x); 38 | } 39 | 40 | int main() { 41 | uint64_t x = 300; 42 | uint32_t y = narrow_cast(x); 43 | printf("u32: %u\n", y); 44 | bar(x); 45 | foo(x); 46 | uint8_t z = narrow_cast(x); 47 | printf("u8: %u\n", z); 48 | return 0; 49 | } 50 | 51 | -------------------------------------------------------------------------------- /d3d_stream_output_same_buffer_disjoint.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define ASSERT(X) \ 7 | if (!(X)) { \ 8 | *(int*)3 = 42; \ 9 | } 10 | 11 | constexpr UINT DISJOINT_SO_RANGES = 2; 12 | 13 | const std::string vs_src(R"( 14 | struct vs_in 15 | { 16 | float2 xy : POSITION; 17 | }; 18 | 19 | struct vs_out 20 | { 21 | float4 pos : SV_POSITION; 22 | float2 xy : POSITION; 23 | }; 24 | 25 | //-------------------------------------------------------------------------------------- 26 | // Vertex Shader 27 | //-------------------------------------------------------------------------------------- 28 | vs_out Main(vs_in input) 29 | { 30 | vs_out output; 31 | output.pos = float4(0,0,0,1); 32 | output.xy = input.xy * 3; 33 | return output; 34 | } 35 | )"); 36 | 37 | int 38 | main(const int argc, const char* const argv[]) 39 | { 40 | HRESULT hr; 41 | 42 | UINT flags = D3D11_CREATE_DEVICE_DEBUG; 43 | const std::vector feature_levels({ 44 | //D3D_FEATURE_LEVEL_11_1, 45 | D3D_FEATURE_LEVEL_11_0, 46 | }); 47 | ID3D11Device* device = nullptr; 48 | D3D_FEATURE_LEVEL feature_level; 49 | ID3D11DeviceContext* context = nullptr; 50 | hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_REFERENCE, nullptr, 51 | flags, feature_levels.data(), feature_levels.size(), 52 | D3D11_SDK_VERSION, &device, &feature_level, &context); 53 | ASSERT(device); 54 | 55 | // -- 56 | 57 | ID3DBlob* vs_blob = nullptr; 58 | ID3DBlob* err_blob = nullptr; 59 | hr = D3DCompile(vs_src.data(), vs_src.size(), "vs_src", nullptr, nullptr, "Main", "vs_5_0", 60 | D3DCOMPILE_ENABLE_STRICTNESS, 0, &vs_blob, &err_blob); 61 | const char* err_str = nullptr; 62 | if (err_blob) { 63 | err_str = (const char*)err_blob->GetBufferPointer(); 64 | printf("%s", err_str); 65 | } 66 | ASSERT(vs_blob); 67 | const auto vs_bytes_ptr = vs_blob->GetBufferPointer(); 68 | const auto vs_bytes_size = vs_blob->GetBufferSize(); 69 | 70 | ID3D11VertexShader* vs_shader = nullptr; 71 | hr = device->CreateVertexShader(vs_bytes_ptr, vs_bytes_size, 72 | nullptr, &vs_shader); 73 | ASSERT(vs_shader); 74 | 75 | ID3D11GeometryShader* gs_shader = nullptr; 76 | { 77 | const D3D11_SO_DECLARATION_ENTRY descs[] { 78 | { 0, "POSITION", 0, 0, 1, 0 }, 79 | { 0, "POSITION", 0, 1, 1, 1 }, 80 | }; 81 | const UINT strides[2] { 4, 4 }; 82 | hr = device->CreateGeometryShaderWithStreamOutput(vs_bytes_ptr, vs_bytes_size, 83 | descs, DISJOINT_SO_RANGES, strides, DISJOINT_SO_RANGES, 84 | D3D11_SO_NO_RASTERIZED_STREAM, nullptr, &gs_shader); 85 | ASSERT(gs_shader); 86 | } 87 | 88 | // -- 89 | 90 | const float in_data[]{ 91 | 0.5, 1, 2, 3, 92 | }; 93 | 94 | ID3D11Buffer* in_buffer = nullptr; 95 | { 96 | const D3D11_BUFFER_DESC desc{ 97 | sizeof(in_data), D3D11_USAGE_DEFAULT, 98 | D3D11_BIND_VERTEX_BUFFER, 99 | 0, 0, 100 | 0 101 | }; 102 | const D3D11_SUBRESOURCE_DATA data{ in_data }; 103 | hr = device->CreateBuffer(&desc, &data, &in_buffer); 104 | ASSERT(in_buffer); 105 | } 106 | 107 | ID3D11Buffer* out_buffer = nullptr; 108 | { 109 | const D3D11_BUFFER_DESC desc{ 110 | sizeof(in_data), D3D11_USAGE_DEFAULT, 111 | D3D11_BIND_STREAM_OUTPUT, 112 | 0, 0, 113 | 0 114 | }; 115 | hr = device->CreateBuffer(&desc, nullptr, &out_buffer); 116 | ASSERT(out_buffer); 117 | } 118 | 119 | ID3D11Buffer* out_buffer2 = nullptr; 120 | { 121 | const D3D11_BUFFER_DESC desc{ 122 | sizeof(in_data), D3D11_USAGE_DEFAULT, 123 | D3D11_BIND_STREAM_OUTPUT, 124 | 0, 0, 125 | 0 126 | }; 127 | hr = device->CreateBuffer(&desc, nullptr, &out_buffer2); 128 | ASSERT(out_buffer2); 129 | } 130 | 131 | ID3D11Buffer* read_buffer = nullptr; 132 | { 133 | const D3D11_BUFFER_DESC desc{ 134 | sizeof(in_data), D3D11_USAGE_STAGING, 135 | 0, 136 | D3D11_CPU_ACCESS_READ, 0, 137 | 0 138 | }; 139 | hr = device->CreateBuffer(&desc, nullptr, &read_buffer); 140 | ASSERT(read_buffer); 141 | } 142 | 143 | // -- 144 | 145 | context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST); 146 | context->VSSetShader(vs_shader, nullptr, 0); 147 | context->GSSetShader(gs_shader, nullptr, 0); 148 | 149 | ID3D11InputLayout* input_layout = nullptr; 150 | { 151 | const D3D11_INPUT_ELEMENT_DESC desc{ 152 | "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, 153 | D3D11_INPUT_PER_VERTEX_DATA, 0 154 | }; 155 | hr = device->CreateInputLayout(&desc, 1, vs_bytes_ptr, vs_bytes_size, 156 | &input_layout); 157 | ASSERT(input_layout); 158 | } 159 | context->IASetInputLayout(input_layout); 160 | 161 | { 162 | const UINT strides[]{ 8 }; 163 | const UINT offsets[]{ 0 }; 164 | context->IASetVertexBuffers(0, 1, &in_buffer, strides, offsets); 165 | } 166 | { 167 | //ID3D11Buffer* const buffers[]{ out_buffer, out_buffer2 }; 168 | ID3D11Buffer* const buffers[]{ out_buffer, out_buffer }; 169 | const UINT offsets[]{ 0, 8 }; 170 | context->SOSetTargets(DISJOINT_SO_RANGES, buffers, offsets); 171 | } 172 | context->Draw(1, 0); 173 | 174 | // -- 175 | 176 | context->CopyResource(read_buffer, out_buffer); 177 | 178 | D3D11_MAPPED_SUBRESOURCE mapped{}; 179 | hr = context->Map(read_buffer, 0, D3D11_MAP_READ, 0, &mapped); 180 | ASSERT(mapped.pData); 181 | 182 | const auto out_data = (const float*)mapped.pData; 183 | ASSERT(out_data[0] == in_data[0] * 3.0f); 184 | 185 | context->Unmap(read_buffer, 0); 186 | 187 | // -- 188 | 189 | printf("OK\n"); 190 | return 0; 191 | } 192 | -------------------------------------------------------------------------------- /depot_tools-on-arch.md: -------------------------------------------------------------------------------- 1 | ## `python` Considered Harmful 2 | 3 | Arch uses python->python3, not python2. 4 | This is fine according to PEP 394, but naturally not everyone is compliant. 5 | 6 | ## gn 7 | 8 | We can use the correct python with `gn` with `gn --script-executable=python2`. 9 | Theoretically this can go in the .gn file also, but since the official python.org Windows 10 | packages are presently incompatible PEP 394, it's not a slam-dunk. 11 | 12 | ### clang missing libtinfo.so.5 13 | Arch uses ncurses-6, so we don't have this lib from ncurses-5. 14 | We can't use system clang yet, because (at least) ANGLE wants clang-6, and only clang-5 15 | has been released. 16 | 17 | You will need: 18 | https://aur.archlinux.org/packages/libtinfo5/ 19 | 20 | ## depot_tools 21 | 22 | Issue: 23 | https://bugs.chromium.org/p/chromium/issues/detail?id=777069 24 | 25 | Potential cset: 26 | https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/746142 27 | -------------------------------------------------------------------------------- /end-script-parsing-poc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | '; 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /fake-wake-lock.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

13 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /fake-wake-lock.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright and related rights waived via CC0: 3 | https://creativecommons.org/publicdomain/zero/1.0/ 4 | 5 | By: Kelsey Gilbert 6 | */ 7 | 8 | 'use strict'; 9 | 10 | if (!navigator.fakeWakeLock) { 11 | const TRACE = false; 12 | const WARN_ON_GC_EARLY = true; 13 | 14 | // The browser will hold its own wakelock if there's a video in the document 15 | // that: 16 | // * is playing 17 | // * has an audio track 18 | // * (it may be hidden and muted) 19 | // So we take any arbitrary with-sound video, and resize it to 2x2, and trim 20 | // it to as few frames as possible: 21 | // ffmpeg.exe -i MOV_0714.mp4 -vf scale=2:2 -t 00:00:00.01 -crf 63 MOV_0714.2x2.1f.webm 22 | // Then we base64-encode the resulting file so we can embed it: 23 | // https://kdashg.github.io/misc/base64-embedding/toBase64.html 24 | // Then all that's left is to dress it up in a polyfill of the real wakeLock API. 25 | const MOV_0714_2X2_1F_WEBM = `\ 26 | data:video/webm;base64,GkXfo59ChoEBQveBAULygQRC84E\ 27 | IQoKEd2VibUKHgQRChYECGFOAZwEAAAAAAAV0EU2bdLpNu4tTq\ 28 | 4QVSalmU6yBoU27i1OrhBZUrmtTrIHWTbuMU6uEElTDZ1OsggG\ 29 | fTbuMU6uEHFO7a1OsggVd7AEAAAAAAABZAAAAAAAAAAAAAAAAA\ 30 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ 31 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ 32 | AAVSalmsCrXsYMPQkBNgIxMYXZmNjAuNC4xMDFXQYxMYXZmNjA\ 33 | uNC4xMDFEiYhAQIAAAAAAABZUrmtAw64BAAAAAAAAWNeBAXPFi\ 34 | MwVWwqmQIAPnIEAIrWcg2VuZ4aFVl9WUDmDgQEj44OEAf0iiuC\ 35 | ssIECuoECmoECVLCBBFS6gQJVsJhVuoEGVbGBAVW7gQFVuYEBV\ 36 | beBAVW4gQKuAQAAAAAAAFnXgQJzxYiMJm8s4hYsPZyBACK1nIN\ 37 | lbmeGhkFfT1BVU1aqg2MuoFa7hATEtACDgQLhkZ+BArWIQOdwA\ 38 | AAAAABiZIEgY6KTT3B1c0hlYWQBAjgBgLsAAAAAABJUw2dBx3N\ 39 | zQI5jwIBnyJVFo4tNQUpPUl9CUkFORESHhG1wNDJnyJRFo41NS\ 40 | U5PUl9WRVJTSU9ORIeBMGfIn0WjkUNPTVBBVElCTEVfQlJBTkR\ 41 | TRIeIaXNvbW1wNDJnyJtFo5NDT00uQU5EUk9JRC5WRVJTSU9OR\ 42 | IeCMTFnyJlFo4dFTkNPREVSRIeMTGF2ZjYwLjQuMTAxc3NAmGP\ 43 | Ai2PFiMwVWwqmQIAPZ8idRaOMSEFORExFUl9OQU1FRIeLVmlkZ\ 44 | W9IYW5kbGVnyJtFo4lWRU5ET1JfSUREh4xbMF1bMF1bMF1bMF1\ 45 | nyKRFo4dFTkNPREVSRIeXTGF2YzYwLjkuMTAwIGxpYnZweC12c\ 46 | DlnyKJFo4hEVVJBVElPTkSHlDAwOjAwOjAwLjAzMzAwMDAwMAA\ 47 | Ac3NAlWPAi2PFiIwmbyziFiw9Z8idRaOMSEFORExFUl9OQU1FR\ 48 | IeLU291bmRIYW5kbGVnyJtFo4lWRU5ET1JfSUREh4xbMF1bMF1\ 49 | bMF1bMF1nyKFFo4dFTkNPREVSRIeUTGF2YzYwLjkuMTAwIGxpY\ 50 | m9wdXNnyKJFo4hEVVJBVElPTkSHlDAwOjAwOjAwLjAyMDAwMDA\ 51 | wMAAAH0O2dUHr54EDoEHGoUG9ggAAAPx/MXOZ6Mo3HS0Oibvxy\ 52 | T1xSuYiBQm+ktA2GKfv/NEIgBLgSlX3DCWyf33T98ZPFFX6DVY\ 53 | mTnuxl1yy2CDoDF+1BiExXTk/cyqdb58Y5fJytgu//derMaJ8f\ 54 | /7Y931/DftW18GkFg4+uOtHv5soI0v7uT9fHaWhZr1whieoAIp\ 55 | OaO6ypv+t3DaeEPLGt6pG/DzZSEPIphmj5YPWk8JJfEFjrkB9A\ 56 | evIpw3VX4jRrliZnlb4yxvECvzV7fVmDcwgvXOOGdnVGaNgAAH\ 57 | CUosp/JlPYyurv3qU+Y7AL5SGDSvnwKuuy6u8EzuUYKYm1vC1G\ 58 | 0qgKcYWHhGj3ii4YeFI7nCBbV2p0pSWimsLNWmQmi98nMk/6jn\ 59 | B+jUMcVZu/0CX+uWEQPwqqAujcsI2tj8W2oyDtY35pIUPTEEAR\ 60 | ji4mbVEZBHZrOtE96ENi3q57fzJ9LmZ/H1WrEyLrP1+nK7sAv9\ 61 | LAmqarefytyBdehUVKMhS/yq2rsTJn4enjmUfgzzZZV6+6771G\ 62 | +X3OcCVJAi8Qy5fmq4lwJbuPMsWlCSj0mFUAyB9a2J5q0RIZWq\ 63 | tW4Rcv3v3TAaI+Ia6yXWigzVn4KOdgf/9gIJJg0JAABAAFgA4J\ 64 | BwZcAAAICAAEb7wbwAcU7trkruQs4EAt4v3gQHxggNs8IIBzA=\ 65 | =`; // 1951 chars. 66 | 67 | const on_finalize = new FinalizationRegistry(fn => fn()); 68 | 69 | // - 70 | 71 | function FakeWakeLock() { 72 | this.request = async function request(type) { 73 | type = type || 'screen'; 74 | if (type != 'screen') throw type; 75 | // Skipping a ton of validation I don't care about... 76 | return new FakeWakeLockSentinel(type); 77 | }; 78 | } 79 | 80 | // - 81 | 82 | let next_lock_id = 1; 83 | 84 | function FakeWakeLockSentinel(type) { 85 | const id = `FakeWakeLockSentinel#${next_lock_id}`; 86 | next_lock_id += 1; 87 | 88 | if (TRACE) { 89 | console.trace(`[${id}] acquired`); 90 | } 91 | 92 | let e_video = document.createElement('video'); 93 | e_video.id = `${id}-video`; 94 | e_video.hidden = true; // Wild that Firefox doesn't care about this. 95 | e_video.muted = true; 96 | e_video.loop = true; 97 | e_video.src = MOV_0714_2X2_1F_WEBM; 98 | e_video.play(); // ignore the promise 99 | 100 | document.body.appendChild(e_video); 101 | 102 | function release_video(why) { 103 | why.defined; 104 | if (!e_video) return; 105 | 106 | const text = `[${id}] (releasing video via ${why})`; 107 | if (WARN_ON_GC_EARLY && why == 'gc') { 108 | console.warn(text, {WARN_ON_GC_EARLY}); 109 | } else if (TRACE) { 110 | console.trace(text, {TRACE}); 111 | } 112 | 113 | document.body.removeChild(e_video); 114 | e_video = null; 115 | } 116 | on_finalize.register(this, () => release_video('gc')); 117 | 118 | this.released = false; 119 | this.type = type; 120 | this.onrelease = undefined; 121 | 122 | // The browser has a form of promise pruning, where it can figure 123 | // out that there's no way to access something anymore. 124 | // The problem is that it's figuring out that we're not going to 125 | // call release(), so we get finalized before we call onrelease. 126 | // * The caller wants the screen to stay locked until the browser 127 | // "randomly" releases the lock. (e.g. visibility change) 128 | // * We want to leave the screen locked until the caller's lock gets 129 | // GC'd. 130 | // Maybe a better way than using onrelease, is for the caller to 131 | // just poll .released, or even manually call release() itself 132 | // periodically. 133 | // So that's the recommendation: 134 | async function wake_forever(refresh_every_n_seconds = 10) { 135 | while (true) { 136 | try { 137 | const lock = await navigator.wakeLock.request(); 138 | await new Promise(go => { 139 | setTimeout(go, refresh_every_n_seconds*1000); 140 | }); 141 | await lock.release(); 142 | } catch (e) {} 143 | } 144 | } 145 | 146 | 147 | this.release = async function release() { 148 | if (!this.released) { 149 | if (TRACE) { 150 | console.trace(`[${id}] released`); 151 | } 152 | release_video('release()'); 153 | this.released = true; 154 | if (this.onrelease) { 155 | this.onrelease(); 156 | } 157 | } 158 | }; 159 | } 160 | 161 | // - 162 | 163 | navigator.fakeWakeLock = new FakeWakeLock(); 164 | 165 | if (!navigator.wakeLock) { 166 | navigator.wakeLock = navigator.fakeWakeLock; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /gen-index.py: -------------------------------------------------------------------------------- 1 | #! python3 2 | import os 3 | import re 4 | import string 5 | 6 | assert __name__ == '__main__' 7 | 8 | class Node(object): 9 | kTagNameRE = re.compile('<([^ >]+)') 10 | 11 | def __init__(self, before, close_tags=True): 12 | self.before = before 13 | 14 | self.after = '' 15 | if close_tags: 16 | match = self.kTagNameRE.match(before) 17 | if match: 18 | self.after = ''.format(match.group(1)) 19 | 20 | self.sub = [] 21 | return 22 | 23 | def add_node(self, child): 24 | self.sub.append(child) 25 | return child 26 | 27 | def add(self, before, close_tags=True): 28 | return self.add_node(Node(before, close_tags)) 29 | 30 | def to_lines(self, indent=0): 31 | sub_indent = indent 32 | if self.before: 33 | sub_indent += 1 34 | 35 | sub_lines = [] 36 | for x in self.sub: 37 | sub_lines += x.to_lines(sub_indent) 38 | 39 | if not self.before: 40 | return sub_lines 41 | 42 | ret = [' '*4*indent + self.before] 43 | if len(sub_lines) <= 1: 44 | if sub_lines: 45 | ret[0] += sub_lines[0].lstrip() 46 | ret[0] += self.after 47 | else: 48 | ret += sub_lines 49 | if self.after: 50 | ret.append(' '*4*indent + self.after) 51 | 52 | return ret 53 | 54 | 55 | body = Node('') 56 | src_url = 'https://github.com/jdashg/misc/blob/master/gen-index.py' 57 | body.add("

Gen-Index:".format(src_url)) 58 | dir_list = body.add('
    ') 59 | 60 | def should_add_to_index(name): 61 | try: 62 | (name, ext) = name.rsplit('.', 1) 63 | except ValueError: 64 | return False 65 | 66 | return ext in ('html', 'md', 'txt') 67 | 68 | for (cur, dirs, files) in os.walk('./'): 69 | dirs[:] = sorted(filter(lambda x: x[0] != '.', dirs)) 70 | files = sorted(filter(lambda x: x[0] != '.', files)) 71 | 72 | htmls = filter(should_add_to_index, files) 73 | htmls = list(htmls) 74 | if not htmls: 75 | continue 76 | 77 | clean_path = cur[len('./'):].replace(os.sep, '/') 78 | 79 | dir_item = dir_list.add('
  • {}/

    '.format(clean_path), False) 80 | file_list = dir_item.add('
      ') 81 | for x in htmls: 82 | file_item = file_list.add('
    • ', False) 83 | url = x 84 | if clean_path: 85 | url = clean_path + '/' + url 86 | print(url) 87 | 88 | try: 89 | (name, ext) = url.rsplit('.', 1) 90 | if ext == 'md': 91 | url = '{}.html'.format(name) 92 | except ValueError: 93 | pass 94 | 95 | file_item.add("{}".format(url, x)) 96 | 97 | index_template = string.Template(''' 98 | 99 | 100 | 101 | Gen-Index 102 | 103 | 104 | 112 | $body 113 | 114 | 115 | ''') 116 | 117 | body_text = '\n'.join(body.to_lines(2)) 118 | text = index_template.substitute(body=body_text) 119 | 120 | with open('index.html', 'wb') as f: 121 | f.write(text.encode()) 122 | -------------------------------------------------------------------------------- /git-server-install.txt: -------------------------------------------------------------------------------- 1 | For arch. 2 | 3 | > sudo su # keep it simple 4 | > pacman -S git 5 | > cd 6 | > usermod -d `pwd` git 7 | > usermod -s /usr/bin/git-shell # Disallow shell access. 8 | > mkdir .ssh 9 | > touch .ssh/authorized_keys # public keys go in here 10 | > git init --bare # by convention, bare repos (like for servers, no working directory) end in .git 11 | > chown -R git:git . 12 | 13 | Now on a dev machine: 14 | > git add remote git@: 15 | or 16 | > git clone git@: 17 | 18 | For example: 19 | : /mnt/data/git-home 20 | : foo.git 21 | : origin 22 | 23 | FWIW, the git-remote string is @:. 24 | -------------------------------------------------------------------------------- /gl/egl/eglplatform.h: -------------------------------------------------------------------------------- 1 | #ifndef __eglplatform_h_ 2 | #define __eglplatform_h_ 3 | 4 | /* 5 | ** Copyright (c) 2007-2016 The Khronos Group Inc. 6 | ** 7 | ** Permission is hereby granted, free of charge, to any person obtaining a 8 | ** copy of this software and/or associated documentation files (the 9 | ** "Materials"), to deal in the Materials without restriction, including 10 | ** without limitation the rights to use, copy, modify, merge, publish, 11 | ** distribute, sublicense, and/or sell copies of the Materials, and to 12 | ** permit persons to whom the Materials are furnished to do so, subject to 13 | ** the following conditions: 14 | ** 15 | ** The above copyright notice and this permission notice shall be included 16 | ** in all copies or substantial portions of the Materials. 17 | ** 18 | ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. 25 | */ 26 | 27 | /* Platform-specific types and definitions for egl.h 28 | * $Revision: 30994 $ on $Date: 2015-04-30 13:36:48 -0700 (Thu, 30 Apr 2015) $ 29 | * 30 | * Adopters may modify khrplatform.h and this file to suit their platform. 31 | * You are encouraged to submit all modifications to the Khronos group so that 32 | * they can be included in future versions of this file. Please submit changes 33 | * by sending them to the public Khronos Bugzilla (http://khronos.org/bugzilla) 34 | * by filing a bug against product "EGL" component "Registry". 35 | */ 36 | 37 | #include 38 | 39 | /* Macros used in EGL function prototype declarations. 40 | * 41 | * EGL functions should be prototyped as: 42 | * 43 | * EGLAPI return-type EGLAPIENTRY eglFunction(arguments); 44 | * typedef return-type (EXPAPIENTRYP PFNEGLFUNCTIONPROC) (arguments); 45 | * 46 | * KHRONOS_APICALL and KHRONOS_APIENTRY are defined in KHR/khrplatform.h 47 | */ 48 | 49 | #ifndef EGLAPI 50 | #define EGLAPI KHRONOS_APICALL 51 | #endif 52 | 53 | #ifndef EGLAPIENTRY 54 | #define EGLAPIENTRY KHRONOS_APIENTRY 55 | #endif 56 | #define EGLAPIENTRYP EGLAPIENTRY* 57 | 58 | /* The types NativeDisplayType, NativeWindowType, and NativePixmapType 59 | * are aliases of window-system-dependent types, such as X Display * or 60 | * Windows Device Context. They must be defined in platform-specific 61 | * code below. The EGL-prefixed versions of Native*Type are the same 62 | * types, renamed in EGL 1.3 so all types in the API start with "EGL". 63 | * 64 | * Khronos STRONGLY RECOMMENDS that you use the default definitions 65 | * provided below, since these changes affect both binary and source 66 | * portability of applications using EGL running on different EGL 67 | * implementations. 68 | */ 69 | 70 | #if defined(_WIN32) || defined(__VC32__) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) /* Win32 and WinCE */ 71 | #ifndef WIN32_LEAN_AND_MEAN 72 | #define WIN32_LEAN_AND_MEAN 1 73 | #endif 74 | #include 75 | 76 | typedef HDC EGLNativeDisplayType; 77 | typedef HBITMAP EGLNativePixmapType; 78 | typedef HWND EGLNativeWindowType; 79 | 80 | #elif defined(__APPLE__) || defined(__WINSCW__) || defined(__SYMBIAN32__) /* Symbian */ 81 | 82 | typedef int EGLNativeDisplayType; 83 | typedef void *EGLNativeWindowType; 84 | typedef void *EGLNativePixmapType; 85 | 86 | #elif defined(__ANDROID__) || defined(ANDROID) 87 | 88 | struct ANativeWindow; 89 | struct egl_native_pixmap_t; 90 | 91 | typedef struct ANativeWindow* EGLNativeWindowType; 92 | typedef struct egl_native_pixmap_t* EGLNativePixmapType; 93 | typedef void* EGLNativeDisplayType; 94 | 95 | #elif defined(USE_OZONE) 96 | 97 | typedef intptr_t EGLNativeDisplayType; 98 | typedef intptr_t EGLNativeWindowType; 99 | typedef intptr_t EGLNativePixmapType; 100 | 101 | #elif defined(__unix__) 102 | 103 | /* X11 (tentative) */ 104 | #include 105 | #include 106 | 107 | typedef Display *EGLNativeDisplayType; 108 | typedef Pixmap EGLNativePixmapType; 109 | typedef Window EGLNativeWindowType; 110 | 111 | #else 112 | #error "Platform not recognized" 113 | #endif 114 | 115 | /* EGL 1.2 types, renamed for consistency in EGL 1.3 */ 116 | typedef EGLNativeDisplayType NativeDisplayType; 117 | typedef EGLNativePixmapType NativePixmapType; 118 | typedef EGLNativeWindowType NativeWindowType; 119 | 120 | 121 | /* Define EGLint. This must be a signed integral type large enough to contain 122 | * all legal attribute names and values passed into and out of EGL, whether 123 | * their type is boolean, bitmask, enumerant (symbolic constant), integer, 124 | * handle, or other. While in general a 32-bit integer will suffice, if 125 | * handles are 64 bit types, then EGLint should be defined as a signed 64-bit 126 | * integer type. 127 | */ 128 | typedef khronos_int32_t EGLint; 129 | 130 | 131 | /* C++ / C typecast macros for special EGL handle values */ 132 | #if defined(__cplusplus) 133 | #define EGL_CAST(type, value) (static_cast(value)) 134 | #else 135 | #define EGL_CAST(type, value) ((type) (value)) 136 | #endif 137 | 138 | #endif /* __eglplatform_h */ 139 | -------------------------------------------------------------------------------- /gl/gles/gl3platform.h: -------------------------------------------------------------------------------- 1 | #ifndef __gl3platform_h_ 2 | #define __gl3platform_h_ 3 | 4 | /* 5 | ** Copyright (c) 2017 The Khronos Group Inc. 6 | ** 7 | ** Licensed under the Apache License, Version 2.0 (the "License"); 8 | ** you may not use this file except in compliance with the License. 9 | ** You may obtain a copy of the License at 10 | ** 11 | ** http://www.apache.org/licenses/LICENSE-2.0 12 | ** 13 | ** Unless required by applicable law or agreed to in writing, software 14 | ** distributed under the License is distributed on an "AS IS" BASIS, 15 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | ** See the License for the specific language governing permissions and 17 | ** limitations under the License. 18 | */ 19 | 20 | /* Platform-specific types and definitions for OpenGL ES 3.X gl3.h 21 | * 22 | * Adopters may modify khrplatform.h and this file to suit their platform. 23 | * Please contribute modifications back to Khronos as pull requests on the 24 | * public github repository: 25 | * https://github.com/KhronosGroup/OpenGL-Registry 26 | */ 27 | 28 | #include 29 | 30 | #ifndef GL_APICALL 31 | #define GL_APICALL KHRONOS_APICALL 32 | #endif 33 | 34 | #ifndef GL_APIENTRY 35 | #define GL_APIENTRY KHRONOS_APIENTRY 36 | #endif 37 | 38 | #endif /* __gl3platform_h_ */ 39 | -------------------------------------------------------------------------------- /gl/search-headers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 18 |
      19 | 20 |
      21 | Regex: Flags: 22 |
      23 |
      24 | 252 | 253 | 254 | -------------------------------------------------------------------------------- /greasemonkey-embed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
      8 | Header: 9 |
      10 | 17 |
      18 | Source: 19 |
      20 | 24 |
      25 | 26 | 28 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /https_server.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | assert __name__ == '__main__' 3 | print(__file__) 4 | import http.server 5 | import pathlib 6 | import ssl 7 | 8 | import argparse 9 | 10 | parser = argparse.ArgumentParser() 11 | parser.add_argument('--bind', '-b', default='localhost', metavar='ADDRESS', 12 | help='Specify alternate bind address ' 13 | '[default: all interfaces]') 14 | parser.add_argument('--cache', '-c', action='store', 15 | default=0, type=int, 16 | help='Allow caching [default: 0]') 17 | parser.add_argument('port', action='store', 18 | default=4443, type=int, 19 | nargs='?', 20 | help='Specify alternate port [default: 4443]') 21 | args = parser.parse_args() 22 | 23 | 24 | class CustomRequestHandler(http.server.SimpleHTTPRequestHandler): 25 | def end_headers(self): 26 | if not args.cache: 27 | self.send_header("Cache-Control", "no-cache, no-store, must-revalidate") 28 | self.send_header("Pragma", "no-cache") 29 | self.send_header("Expires", "0") 30 | super().end_headers() 31 | 32 | CERT_FILE = str(pathlib.PurePath(__file__).with_name('server.pem')) 33 | 34 | httpd = http.server.ThreadingHTTPServer(('', 4443), CustomRequestHandler) 35 | try: 36 | httpd.socket = ssl.wrap_socket(httpd.socket, certfile=CERT_FILE, server_side=True) 37 | except FileNotFoundError: 38 | print(f'''{CERT_FILE} not found. 39 | Try `openssl req -new -x509 -keyout server.pem -out {CERT_FILE} -days 365 -nodes`''') 40 | exit(1) 41 | print(f'Serving at {httpd.socket.getsockname()}...') 42 | httpd.serve_forever() 43 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Gen-Index 6 | 7 | 8 | 16 |

      Gen-Index:

      17 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /js-for-range-benchmark.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
      Width (default 1000): 8 |
      Height (default 1000): 9 |
      Points (default 100): 10 |
      Reps (default 10): 11 |
      12 |
      
       13 |     
      246 |   
      247 | 
      248 | 
      
      
      --------------------------------------------------------------------------------
      /json-loader.html:
      --------------------------------------------------------------------------------
       1 | 
       2 | 
       3 |    
       4 |       
       5 |    
       6 |    
       7 | 
      `window. = JSON.parse();`
      10 | Status: - 11 | 12 |
      13 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /murmurhash3.js: -------------------------------------------------------------------------------- 1 | MurmurHash3_x86_32 = (() => { 2 | function SHR32(x, n) { 3 | x >>= 1; 4 | x &= 0x7fffffff; 5 | x >>= n-1; 6 | return x; 7 | } 8 | 9 | function ROTL32(x, r) { 10 | return (x << r) | SHR32(x, (32 - r)); 11 | } 12 | 13 | function MUL32(a,b) { return Math.imul(a,b); } 14 | 15 | function fmix32(h) { 16 | h ^= SHR32(h, 16); 17 | h = MUL32(h,0x85ebca6b); 18 | h ^= SHR32(h, 13); 19 | h = MUL32(h,0xc2b2ae35); 20 | h ^= SHR32(h, 16); 21 | 22 | return h; 23 | } 24 | 25 | return function(data, seed=0) { 26 | if (typeof data == 'string') { 27 | const te = new TextEncoder(); 28 | data = te.encode(data).buffer; 29 | } 30 | if (!(data instanceof ArrayBuffer)) throw new Error("Bad `data` type."); 31 | const len = data.byteLength; 32 | const nblocks = (len / 4)|0; 33 | 34 | let h1 = seed|0; 35 | 36 | const c1 = 0xcc9e2d51; 37 | const c2 = 0x1b873593; 38 | 39 | //---------- 40 | // body 41 | 42 | const blocks = new Uint32Array(data, 0, nblocks); 43 | 44 | for (let k1 of blocks) 45 | { 46 | k1 = MUL32(k1,c1); 47 | k1 = ROTL32(k1,15); 48 | k1 = MUL32(k1,c2); 49 | 50 | h1 ^= k1; 51 | h1 = ROTL32(h1,13); 52 | h1 = (MUL32(h1,5)+0xe6546b64)|0; 53 | } 54 | 55 | //---------- 56 | // tail 57 | 58 | const tail = new Uint8Array(data, nblocks*4); 59 | 60 | let k1 = 0; 61 | 62 | if (tail.length >= 3) { k1 ^= tail[2] << 16; } 63 | if (tail.length >= 2) { k1 ^= tail[1] << 8; } 64 | if (tail.length >= 1) { 65 | k1 ^= tail[0]; 66 | k1 = MUL32(k1,c1); 67 | k1 = ROTL32(k1,15); 68 | k1 = MUL32(k1,c2); 69 | h1 ^= k1; 70 | } 71 | 72 | //---------- 73 | // finalization 74 | 75 | h1 ^= len; 76 | 77 | h1 = fmix32(h1); 78 | 79 | if (h1 < 0) { 80 | h1 += 0x100000000; // Back to [0,UINT32_MAX]. 81 | } 82 | return h1; 83 | } ; 84 | })(); 85 | -------------------------------------------------------------------------------- /parse-lang.js: -------------------------------------------------------------------------------- 1 | 2 | function fnLess(a, b) { return a < b; } 3 | 4 | function bisect(x, arr, fnLess=fnLess) { 5 | let left = 0; 6 | let right = arr.length; 7 | while (left != right) { 8 | const mid = ((left + right) / 2)|0; 9 | if (fnLess(x, arr[mid])) { 10 | right = mid; 11 | } else { 12 | left = mid; 13 | } 14 | } 15 | return fnLess(x, arr[left]) ? left : left+1; 16 | } 17 | 18 | class LineMap { 19 | constructor() { 20 | this._lineStartPositions = []; 21 | this.addNextLineStart(0); 22 | } 23 | 24 | get lineCount() { 25 | return this._lineStartPositions.length; 26 | } 27 | 28 | startPosForLine(lineNum) { 29 | const lineId = lineNum - 1; 30 | return this._lineStartPositions[lineId]; 31 | } 32 | 33 | addNextLineStart(pos) { 34 | this._lineStartPositions.push(pos); 35 | } 36 | 37 | lineNumForPos(pos) { 38 | const lineId = bisect(x, this._lineStartPositions); 39 | const lineNum = lineId + 1; 40 | return lineNum; 41 | } 42 | } 43 | 44 | // -- 45 | 46 | /* 47 | 48 | class Tokenizer { 49 | constructor() 50 | 51 | function codePointLength(str) { 52 | let codePoints = 0; 53 | for (let c of str) { 54 | codePoints += 1; 55 | } 56 | return codePoints; 57 | } 58 | 59 | function substringUntil(str, offset, endStr) { 60 | let endPos = str.indexOf(endStr, offset); 61 | if (endPos == -1) { 62 | endPos = str.length; 63 | } 64 | return str.substring(offset, endPos); 65 | } 66 | 67 | function substringIncluding(str, offset, endStr) { 68 | const endPos = str.indexOf(endStr, offset); 69 | if (endPos == -1) 70 | throw null; 71 | return str.substring(offset, endPos + endStr.length); 72 | } 73 | 74 | function repeatChar(c, n) { 75 | let ret = ''; 76 | for (let i = 0; i < n; i++) { 77 | ret += c; 78 | } 79 | return ret; 80 | } 81 | 82 | class ExProblemAt { 83 | constructor(src, lineMap, offset) { 84 | this.lineNum = lineMap.lineNumForPos(offset); 85 | const lineStart = lineMap.startPosForLine(this.lineNum); 86 | this.linePos = codePointLength(text.substring(lineStart, offset)); 87 | 88 | this.problemLine = substringUntil(text, lineStart, '\n'); 89 | this.problemPointerLine = repeatChar('-', this.linePos) + '^'; 90 | } 91 | } 92 | 93 | 94 | 95 | class ExUnmatchedTokenStart extends ExProblemAt { 96 | constructor(src, lineMap, offset, startStr, endStr) { 97 | super(src, lineMap, offset); 98 | 99 | const lineNumStr = '[L' + this.lineNum + ']'; 100 | this.message = [ 101 | '[L${this.lineNum}:${this.linePos}] "${startStr}" has no matching "${endStr}".', 102 | lineNumStr + this.problemLine, 103 | repeatChar(' ', lineNumStr.length) + this.problemPointerLine, 104 | ].join('\n'); 105 | } 106 | 107 | toString() { 108 | return this.message; 109 | } 110 | } 111 | */ 112 | 113 | class ExBadToken { 114 | constructor(src, startPos, endPos=-1) { 115 | if (endPos == -1) { 116 | endPos = src.length; 117 | } 118 | this.badToken = src.substring(startPos, endPos); 119 | } 120 | 121 | toString() { 122 | return 'ExBadToken: "${this.badToken}"'; 123 | } 124 | } 125 | 126 | class TokenizePass { 127 | constructor(src) { 128 | this._src = src; 129 | this._lineMap = new LineMap(); 130 | } 131 | 132 | get src() { return this._src; } 133 | get lineMap() { return this._lineMap; } 134 | 135 | badToken(startPos, endPos=-1) { 136 | return new ExBadToken(this._src, startPos, endPos); 137 | } 138 | 139 | findMatchingQuote(startPos) { 140 | const quoteChar = this._src[startPos]; 141 | for (let i = startPos+1; i < this._src.length; i++) { 142 | const cur = this._src[i]; 143 | if (cur == '\\') { 144 | i++; 145 | continue; 146 | } 147 | if (cur == quoteChar) 148 | return i; 149 | } 150 | return -1; 151 | } 152 | 153 | substringUntil(startPos, endStr) { 154 | let endPos = this._src.indexOf(endStr, startPos); 155 | if (endPos == -1) { 156 | endPos = this._src.length; 157 | } 158 | return this._src.substring(startPos, endPos); 159 | } 160 | 161 | tokenizeWith(fnNextToken) { 162 | let curPos = 0; 163 | const tokens = []; 164 | while (curPos < this._src.length) { 165 | const [consumed, emitted] = fnNextToken(this, curPos); 166 | if (emitted) { 167 | tokens.push(emitted); 168 | } 169 | for (let i in consumed) { 170 | curPos += 1; 171 | const c = consumed[i]; 172 | if (c == '\n') { 173 | this._lineMap.addNextLineStart(curPos); 174 | } 175 | } 176 | } 177 | return tokens; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /pgo-clang.md: -------------------------------------------------------------------------------- 1 | # Use GCC to PGO Clang 2 | 3 | Clang is faster to compile than GCC, but It Is Said that GCC PGOs better than Clang. 4 | So let's build a monster. 5 | 6 | ## Methodology 7 | 8 | I'm building a DEBUG version of this cset: 9 | 10 | ~~~ 11 | commit 6fa252d9b66aa88294aeef1f97160411f2f6f85f 12 | Merge: bd1a9cc986c6 1688c0df862d 13 | Author: Ciure Andrei 14 | Date: Fri Mar 30 01:06:18 2018 +0300 15 | 16 | Merge inbound to mozilla-central. a=merge 17 | ~~~ 18 | 19 | All build times are for `./mach build` after running `./mach configure` untimed beforehand. 20 | 21 | The machine is a 2xE5-2670, so 2x2x8=32 hardware threads at 2.60GHz base. 22 | I'm running everything with the default of -j32. 23 | GCC version: 7.3.1 24 | LLVM version: 6.0.0 25 | 26 | I only run one trial of each config, but the error bars on builds seem to be less than 27 | +/-5s, so I'm not too concerned. 28 | 29 | ## Install GCC 30 | 31 | There's no need to build GCC yourself. 32 | 33 | ## Clone and checkout 34 | 35 | ~~~ 36 | git clone https://git.llvm.org/git/llvm.git/ 37 | git checkout origin/release_60 38 | 39 | cd llvm/tools 40 | git clone https://git.llvm.org/git/clang.git/ 41 | git checkout origin/release_60 42 | 43 | cd ../.. 44 | ~~~ 45 | 46 | 47 | ## Build with -fprofile-generate 48 | 49 | ~~~ 50 | cd llvm-obj-inst 51 | 52 | prof="-fprofile-generate=`pwd`/fprofiles" 53 | flags="-march=native -O3 $prof" 54 | CFLAGS="$flags" CXXFLAGS="$flags" LDFLAGS="$prof" cmake -G Ninja -DCMAKE_BUILD_TYPE=Release ../llvm 55 | 56 | cmake --build . 57 | ~~~ 58 | 59 | This takes a while even at -j32: 60 | 61 | ~~~ 62 | real 14m36.881s 63 | user 433m33.101s 64 | sys 17m14.185s 65 | ~~~ 66 | 67 | 68 | ## Compile the thing you care about 69 | 70 | We add these to our gecko .mozconfig: 71 | 72 | ~~~ 73 | export CC=/path/to/llvm-obj-inst/bin/clang 74 | export CXX=/path/to/llvm-obj-inst/bin/clang++ 75 | ac_add_options --disable-warnings-as-errors 76 | ~~~ 77 | 78 | Optional: 79 | 80 | ~~~ 81 | ac_add_options --disable-tests 82 | ~~~ 83 | 84 | `./mach build`: 18m09s (+6m50s from non-pgo) 85 | 86 | 87 | ## Build with -fprofile-use 88 | 89 | ~~~ 90 | cd pgo-llvm 91 | 92 | prof="-fprofile-use=`pwd`/fprofiles" 93 | flags="-march=native -O3 $prof" 94 | CFLAGS="$flags" CXXFLAGS="$flags" LDFLAGS="$prof" cmake -G Ninja -DCMAKE_BUILD_TYPE=Release ../llvm 95 | 96 | cmake --build . 97 | ~~~ 98 | 99 | This was faster for me: 100 | 101 | ~~~ 102 | real 10m58.746s 103 | user 327m20.485s 104 | sys 14m47.751s 105 | ~~~ 106 | 107 | 108 | ## Switch over! 109 | 110 | ~~~ 111 | export CC=/path/to/pgo-llvm/bin/clang 112 | export CXX=/path/to/pgo-llvm/bin/clang++ 113 | ac_add_options --disable-warnings-as-errors 114 | ~~~ 115 | 116 | `./mach build`: 10m30s (vs 12m24s for my system clang) 117 | Note that at these times, linking dominates the build time. (about 5m alone) 118 | It looks like lld defaults to using --threads, but we do spend most of the time pegging a 119 | single core. 120 | -30s isn't really worth it at 10m total, but might be more attractive if we could get link 121 | time down. 122 | 123 | 124 | ## Establish baseline build times (optional) 125 | 126 | To take a baseline, it's useful to see what we'd get without PGO or other options. 127 | 128 | ~~~ 129 | CFLAGS="$flags" CXXFLAGS="$flags" cmake -G Ninja -DCMAKE_BUILD_TYPE=Release ../llvm 130 | cmake --build . 131 | ~~~ 132 | 133 | - Arch Linux `clang` 6.0.0-1: 12m24s 134 | - `flags=''`: 12m00s (-24s from system binary) 135 | - `flags='-O3'`: 12m02s (+2s from no-flags) 136 | - `flags='-march=native'`: 12m05s (+5s from no-flags) 137 | - `flags='-O3 -march=native'`: 11m55s (-5s from no-flags) 138 | - With PGO: 10m30s (-1m30s from no-flags, -1m54s from system binary) 139 | 140 | My guess is that no-flags clang builds with good enough optimization flags that it's not 141 | worth supplying your own flags. 142 | The spread in times here for different flags are really close to the error bars. 143 | 144 | ## Conclusion 145 | 146 | It's probably worth it if you clobber a lot, particularly on a low-core-count machine. 147 | 148 | If that's too much work, just building 'default clang' from source seems to help. 149 | -------------------------------------------------------------------------------- /polyfills.md: -------------------------------------------------------------------------------- 1 | ## JS 2 | 3 | ### `window.setImmediate` 4 | 5 | ``` 6 | (function() { 7 | if (window.setImmediate) return; 8 | 9 | let next_id = 1; 10 | let func_by_id = {}; 11 | const KEY = 'window.setImmediate'; 12 | function handler(e) { 13 | if (e.data !== KEY) return; 14 | e.stopImmediatePropagation(); 15 | 16 | for (const func of Object.values(func_by_id)) { 17 | (async function() { 18 | func(); 19 | })(); 20 | } 21 | func_by_id = {}; 22 | } 23 | window.addEventListener('message', handler, true); 24 | 25 | window.setImmediate = function(func) { 26 | const id = next_id; 27 | next_id += 1; 28 | func_by_id[id] = func; 29 | window.postMessage(KEY, '*'); 30 | return id; 31 | }; 32 | 33 | window.clearImmediate = function(id) { 34 | func_by_id[id] = undefined; 35 | }; 36 | })(); 37 | 38 | function next_event_loop() { 39 | return new Promise((res, rej) => { 40 | setImmediate(() => { 41 | res(); 42 | }); 43 | }); 44 | } 45 | ``` 46 | -------------------------------------------------------------------------------- /remoted-readback.md: -------------------------------------------------------------------------------- 1 | With reasonable optimizations, readback from GLBuffers can be efficient even on remoting implementations. 2 | 3 | ## GLServer 4 | 5 | ~~~ 6 | func GLServer.ReadPixels(PBO x): 7 | glReadPixels(x.id) 8 | BufferDataModified(x) 9 | 10 | 11 | func GLServer.BufferDataModified(x): 12 | switch x.usage: 13 | case DYNAMIC_READ: 14 | case STATIC_READ: 15 | case STREAM_READ: 16 | break 17 | default: 18 | return 19 | 20 | self.nextSyncPointToEnqueue.callbacks.add(new DeferredReadback(x)) 21 | 22 | 23 | func GLServer.FenceSync(): 24 | x = new FenceSync(glFenceSync()) 25 | 26 | self.nextSyncPointToEnqueue.callbacks.add(new NotifyClientThatFencePassed(x)) 27 | 28 | x.syncPoint = self.nextSyncPointToEnqueue 29 | self.nextSyncPointToEnqueue = new SyncPoint() 30 | x.syncPoint.next = self.nextSyncPointToEnqueue 31 | 32 | return x 33 | 34 | 35 | func GLServer.PollSync(x, block): 36 | if x.isPassed: 37 | return true 38 | 39 | while self.lastPassedSyncPoint.next: 40 | id = self.lastPassedSyncPoint.next.id 41 | if block: 42 | glWaitSync(id) 43 | else if !glPollSync(id): 44 | return false 45 | 46 | self.lastPassedSyncPoint = self.lastPassedSyncPoint.next 47 | self.lastPassedSyncPoint.isPassed = true 48 | for y in self.lastPassedSyncPoint.callbacks: 49 | y.DoCallback() 50 | 51 | if self.lastPassedSyncPoint == x: 52 | return true 53 | ASSERT_UNREACHABLE() 54 | ~~~ 55 | 56 | ## GLClient 57 | 58 | ~~~ 59 | func GLClient.GetBufferSubData(x, dest, len): 60 | switch x.usage: 61 | case DYNAMIC_READ: 62 | case STATIC_READ: 63 | case STREAM_READ: 64 | if x.hasShmemAlready: 65 | memcpy(dest, x.GetShmem(), len) 66 | return 67 | Warning("Asking for readback without waiting to the command to complete is bad.") 68 | break 69 | 70 | default: 71 | Warning("Asking for readback from a non-_READ-usage buffer is bad.") 72 | break 73 | 74 | x.SynchronousReadbackIntoShmem() 75 | memcpy(dest, x.GetShmem(), len) 76 | return 77 | ~~~ -------------------------------------------------------------------------------- /replace.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import re 4 | import sys 5 | 6 | (_,*FLAGS,PATTERN,REPLACEMENT,PATH) = sys.argv 7 | 8 | FLAGS = set(FLAGS) 9 | for flag in FLAGS: 10 | assert flag.startswith('-'), flag 11 | 12 | # Default to dry-run 13 | DRY = '--write' not in FLAGS 14 | 15 | try: 16 | with open(PATH,'rb') as f: 17 | b = f.read() 18 | s = b.decode() 19 | except UnicodeDecodeError as e: 20 | start_byte = e.start 21 | line_num = 1 22 | line_start = 0 23 | for i in range(start_byte): 24 | if b[i] == b'\n'[0]: 25 | line_num += 1 26 | line_start = i+1 27 | next_newline = b.find(b'\n', e.end) 28 | sys.stderr.write(f'\nUnicodeDecodeError: {PATH}:{line_num}:\n') 29 | 30 | offset_on_line = start_byte-line_start 31 | offset_on_printed_line = 2 + offset_on_line 32 | sys.stderr.write(b[line_start:next_newline] + '\n') 33 | sys.stderr.write(' '*offset_on_printed_line + '^\n') 34 | raise 35 | 36 | (s2, count) = re.subn(PATTERN, REPLACEMENT, s) 37 | 38 | if not count: 39 | sys.stderr.write(f'[{PATH}]\tNo change.\n') 40 | exit(0) 41 | 42 | b = s2.encode() 43 | 44 | if DRY: 45 | sys.stderr.write(f'[{PATH}]\tWould write {len(b)} bytes...\n') 46 | if '--show' in FLAGS: 47 | sys.stdout.buffer.write(b + b'\n') 48 | exit(0) 49 | 50 | if count: 51 | sys.stderr.write(f'[{PATH}]\tWriting {len(b)} bytes...\n') 52 | with open(PATH, 'wb') as f: 53 | f.write(b) 54 | 55 | exit(0) 56 | -------------------------------------------------------------------------------- /set-python.sh: -------------------------------------------------------------------------------- 1 | # Switch between python2 and python3 easily. (particularly useful for depot_tools on Arch) 2 | ls -al /usr/bin/python 3 | sudo ln -s -f /usr/bin/$@ /usr/bin/python 4 | ls -al /usr/bin/python 5 | 6 | -------------------------------------------------------------------------------- /ssh-auto-lock.py: -------------------------------------------------------------------------------- 1 | #! python3 2 | # Kelsey Gilbert 2025-03-31 CC0: https://creativecommons.org/public-domain/cc0/ 3 | 4 | import importlib 5 | import subprocess 6 | import sys 7 | import time 8 | 9 | assert sys.platform == 'win32', f'Not supported: {sys.platform}' 10 | 11 | # - 12 | 13 | WINDOW_TITLE = 'ssh-auto-lock.py' 14 | VERBOSE = '-v' in sys.argv 15 | 16 | # - 17 | 18 | def pip_install(package_name): 19 | install_args = [sys.executable] + f'-m pip install {package_name}'.split(' ') 20 | yn = input(f'pip-install "{package_name}"? (y/N) ') 21 | yes = yn != '' and yn in 'Yy' 22 | print(f'("{yn}" => {yes})') 23 | if not yes: 24 | return False 25 | print(f'> {" ".join(install_args)}') 26 | subprocess.run(install_args) 27 | 28 | import importlib 29 | importlib.invalidate_caches() 30 | return True 31 | 32 | # - 33 | 34 | while True: 35 | try: 36 | import win32gui 37 | import win32api 38 | import win32con 39 | import win32ts 40 | break 41 | except ModuleNotFoundError as e: 42 | print(f'Module "{e.name}" not found.') 43 | if not pip_install('pywin32'): 44 | raise 45 | try: 46 | import win32gui 47 | except ModuleNotFoundError as e: 48 | print(f'Module "{e.name}" still not found!') 49 | print(f'`importlib.invalidate_caches()` is probably still broken:', 50 | 'https://stackoverflow.com/questions/77354992/pythons-importlib-invalidate-caches-doesnt-seem-to-work-in-my-case') 51 | print(f'Rerun the command and it should work though!') 52 | exit(1) 53 | continue 54 | 55 | # - 56 | 57 | THIS_MODULE = win32api.GetModuleHandle(None) 58 | 59 | # - 60 | 61 | def print2(*args): 62 | return print(f'[{time.ctime()}]', *args) 63 | 64 | # - 65 | 66 | def RegisterClass(name, fn_wnd_proc, hinst=THIS_MODULE): 67 | wc = win32gui.WNDCLASS() 68 | wc.hInstance = hinst 69 | wc.lpszClassName = name 70 | wc.lpfnWndProc = fn_wnd_proc 71 | return win32gui.RegisterClass(wc) 72 | 73 | 74 | class CreateWindow: 75 | def __init__(self, wclass, title='', hinst=THIS_MODULE, style=0, x=0, y=0, w=0, h=0, parent=0, menu=0): 76 | self.hwnd = win32gui.CreateWindow(wclass, title, 77 | style, x, y, w, h, parent, menu, hinst, None) 78 | #win32gui.UpdateWindow(self.hwnd) 79 | 80 | def close(self): 81 | if not self.hwnd: 82 | return 83 | win32gui.DestroyWindow(self.hwnd) 84 | self.hwnd = None 85 | 86 | def __enter__(self): 87 | return self 88 | 89 | def __exit__(self, exc_type, exc_value, traceback): 90 | self.close() 91 | 92 | # - 93 | 94 | WM_NAME_BY_ID = { 95 | win32con.WM_CREATE: 'WM_CREATE', 96 | win32con.WM_GETMINMAXINFO: 'WM_GETMINMAXINFO', 97 | win32con.WM_NCCREATE: 'WM_NCCREATE', 98 | win32con.WM_CLOSE: 'WM_CLOSE', 99 | win32con.WM_DESTROY: 'WM_DESTROY', 100 | win32con.WM_QUERYENDSESSION: 'WM_QUERYENDSESSION', 101 | 0x2b1: 'WM_WTSSESSION_CHANGE', 102 | } 103 | 104 | EVENT_BY_WTS_SESSION_WPARAM = { 105 | 0x7: 'WTS_SESSION_LOCK', 106 | 0x8: 'WTS_SESSION_UNLOCK', 107 | } 108 | 109 | # - 110 | 111 | def on_WTS_SESSION_LOCK(): 112 | print2(f'Session lock detected. Clearing ssh-agent:') 113 | cmd = 'ssh-add -D' 114 | print2(f'> {cmd}') 115 | subprocess.run(cmd.split(' ')) 116 | 117 | 118 | def on_WTSSESSION_CHANGE(hwnd, event, session_id): 119 | try: 120 | event = EVENT_BY_WTS_SESSION_WPARAM[event] 121 | except KeyError: 122 | pass 123 | if VERBOSE: 124 | print2(f'') 125 | 126 | if event == 'WTS_SESSION_LOCK': 127 | on_WTS_SESSION_LOCK() 128 | 129 | 130 | def WNDPROC(hwnd, msg, wparam, lparam): 131 | try: 132 | msg = WM_NAME_BY_ID[msg] 133 | except KeyError: 134 | # You might be able to find it here: https://github.com/mhammond/pywin32/blob/main/win32/Lib/win32con.py 135 | #print(f'Warning: Unknown WNDPROC msg: {msg}') 136 | pass 137 | if VERBOSE: 138 | print2(f'') 139 | 140 | if msg == 'WM_CLOSE': 141 | WINDOW.close() 142 | elif msg == 'WM_DESTROY': 143 | win32gui.PostQuitMessage(0) 144 | elif msg == 'WM_QUERYENDSESSION': 145 | return True 146 | 147 | # Thank you @grawity: https://superuser.com/a/264973 148 | if msg == 'WM_WTSSESSION_CHANGE': 149 | on_WTSSESSION_CHANGE(hwnd, wparam, lparam) 150 | 151 | return 0 152 | 153 | # - 154 | 155 | WCLASS = RegisterClass('ssh-auto-lock.py: hidden window', WNDPROC) 156 | 157 | with CreateWindow(WCLASS, title=WINDOW_TITLE) as WINDOW: 158 | win32ts.WTSRegisterSessionNotification(WINDOW.hwnd, win32ts.NOTIFY_FOR_ALL_SESSIONS) 159 | print2('Waiting for session change...') 160 | try: 161 | #win32gui.PumpMessages() # Seems uninterruptable! 162 | while win32gui.PumpWaitingMessages() == 0: 163 | continue 164 | except KeyboardInterrupt: 165 | WINDOW.close() 166 | -------------------------------------------------------------------------------- /ssh-auto-lock.py.md: -------------------------------------------------------------------------------- 1 | # `ssh-auto-lock.py` 2 | 3 | > *For now only supports Windows, sorry!* 4 | 5 | While left running, detects when your session/screen gets locked, and calls `ssh-add -D` to clear `ssh-agent`'s keys. 6 | 7 | **Download: [ssh-auto-lock.py](https://kdashg.github.io/misc/ssh-auto-lock.py)** 8 | 9 | **Source: [github.com/kdashg/misc/blob/tip/ssh-auto-lock.py](https://github.com/kdashg/misc/blob/tip/ssh-auto-lock.py)** 10 | 11 | --- 12 | 13 | I use `ssh_config`'s `AddKeysToAgent yes` to automatically `ssh-add` them back in as-needed. 14 | 15 | > *In the future, it might be cool to use `ssh-add -x` to lock/unlock the agent until user enters their/a password again. 16 | But this is all I need.* 17 | 18 | ### Requires: 19 | * `python`, e.g. `winget install python` 20 | 21 | ### Use: 22 | In its own terminal/cmd window, run `py ssh-auto-lock.py`, and leave it running. That's it. 23 | You can use `ctrl-c` in that window to close it. 24 | 25 | ### License: CC0 26 | As of 2025-03-31: [https://creativecommons.org/public-domain/cc0/](https://creativecommons.org/public-domain/cc0/) 27 | 28 | ### Thanks: 29 | * **[@grawity](https://superuser.com/users/1686/grawity)**, for [their answer to "Running python script on when user unlocks windows machine"](https://superuser.com/a/264973). 30 | -------------------------------------------------------------------------------- /toast.ps1/toast.ps1: -------------------------------------------------------------------------------- 1 | # E.g. powershell -file toast.ps1 "A Toast" "...to toast!" > /dev/null 2 | # (If you don't use -file, you might not be able to quote strings with spaces) 3 | [reflection.assembly]::loadwithpartialname("System.Windows.Forms") 4 | [reflection.assembly]::loadwithpartialname("System.Drawing") 5 | $notify = new-object system.windows.forms.notifyicon 6 | $notify.icon = [System.Drawing.SystemIcons]::Information 7 | $notify.visible = $true 8 | $title = "Toast!" 9 | $text = "[toast.ps1 by kdashg]" 10 | if ( $args.count -ge 1 ) { 11 | $title = $args[0] 12 | } 13 | if ( $args.count -ge 2 ) { 14 | $text = $args[1] 15 | } 16 | $notify.showballoontip(10,$title,$text,[system.windows.forms.tooltipicon]::None) 17 | -------------------------------------------------------------------------------- /voice-guesser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

      [click to start]

      8 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /webgl-ext-packs.md: -------------------------------------------------------------------------------- 1 | Sourced from [webglstats.com]. 2 | 3 | # WebGL 1 4 | 5 | ## WEBGL_extension_pack1_1 6 | - blend_minmax: 88%/96% (99% w/o ie+edge) 7 | - debug_renderer_info: 100%/100% 8 | - element_index_uint: 100%/93% (88% Chrome Android) 9 | - instance_arrays: 99%/92% (86% Chrome Android) 10 | - lose_context: 100%/100% w/o ie+edge 11 | - standard_derivatives: 100%/100% 12 | - vertex_array_object: 100%/100% w/o ie+edge 13 | 14 | ### Maybes 15 | - depth_texture: 90%/84% 16 | - 96% w/o ie 17 | - 71% android 18 | - 100% iOS 19 | - draw_buffer: 67%/1% 20 | - Chrome Windows/macOS/Linux: 71%/95%/47% 21 | - Chromium Linux: 79% 22 | - Firefox Windows/macOS/Linux: 92%/99%/97% 23 | - Exists on Firefox Android (percentage unknown) 24 | - frag_depth: 87%/0% 25 | - 92% w/o ie 26 | - 100% Firefox Desktop 27 | - 89% Chrome Desktop? 28 | - 0% both Chrome Android and Safari iOS 29 | - Exists on Firefox Android (percentage unknown) 30 | - texture_filter_anisotropic: 99%/74% (only 52% Chrome Android) 31 | - texture_float: 99%/75% 32 | - 53% Chrome Android 33 | - texture_float_linear: 96%/60% 34 | - 26% Chrome Android 35 | - texture_half_float: 93%/74% 36 | - 99% w/o ie 37 | - 53% Chrome Android 38 | - texture_half_float_linear: 92%/73% 39 | - 98% w/o ie 40 | - 99% w/o ie+edge 41 | - 51% Chrome Android 42 | 43 | # WebGL 2 44 | 45 | ## WEBGL_extension_pack2_1? 46 | 47 | Likely not enough common extensions to be worth it. 48 | 49 | - color_buffer_float: 100%/84% 50 | - 84% Chrome Android 51 | - disjoint_timer_query: 92%/86% 52 | - texture_filter_anisotropic: 99%/45% 53 | - 45% Chrome Android (?) 54 | - texture_float_linear: 100%/40% 55 | - 40% Chrome Android (?) -------------------------------------------------------------------------------- /webgl-from-worker/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
      9 |
      10 | 11 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /webgl-from-worker/worker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Basically RPC. 4 | 5 | var kIsWorker = !(self instanceof Window); 6 | if (kIsWorker) { 7 | self.onmessage = function(e) { 8 | var pair = e.data; 9 | var funcName = pair[0]; 10 | var args = pair[1]; 11 | 12 | var func = self[funcName]; 13 | if (func === undefined) 14 | throw 'Bad funcName: ' + funcName; 15 | 16 | func.apply(self, args); 17 | }; 18 | } 19 | 20 | //////////////////// 21 | 22 | var kVertShaderSource = [ 23 | 'attribute vec3 aPos;', 24 | 'attribute vec4 aColor;', 25 | '', 26 | 'varying vec4 vColor;', 27 | '', 28 | 'void main(void) {', 29 | ' gl_PointSize = 64.0;', 30 | ' gl_Position = vec4(aPos, 1.0);', 31 | ' vColor = aColor;', 32 | '}', 33 | ].join('\n'); 34 | 35 | var kFragShaderSource = [ 36 | 'precision mediump float;', 37 | '', 38 | 'varying vec4 vColor;', 39 | '', 40 | 'void main(void) {', 41 | ' gl_FragColor = vColor;', 42 | '}', 43 | ].join('\n'); 44 | 45 | //////////////////// 46 | 47 | var g_gl; 48 | var g_prog; 49 | 50 | //////////////////// 51 | 52 | function BuildProgram(gl, vertSource, fragSource) { 53 | var vs = gl.createShader(gl.VERTEX_SHADER); 54 | gl.shaderSource(vs, kVertShaderSource); 55 | gl.compileShader(vs); 56 | 57 | var fs = gl.createShader(gl.FRAGMENT_SHADER); 58 | gl.shaderSource(fs, kFragShaderSource); 59 | gl.compileShader(fs); 60 | 61 | var prog = gl.createProgram(); 62 | gl.attachShader(prog, vs); 63 | gl.attachShader(prog, fs); 64 | 65 | gl.linkProgram(prog); 66 | 67 | var success = gl.getProgramParameter(prog, gl.LINK_STATUS); 68 | if (success) 69 | return prog; 70 | 71 | console.log('Error linking program: ' + gl.getProgramInfoLog(prog)); 72 | console.log('\nVert shader log: ' + gl.getShaderInfoLog(vs)); 73 | console.log('\nFrag shader log: ' + gl.getShaderInfoLog(fs)); 74 | return null; 75 | } 76 | 77 | 78 | function DrawPoint(gl, prog, pos3, color4) { 79 | gl.vertexAttrib3fv(prog.aPos, pos3); 80 | gl.vertexAttrib4fv(prog.aColor, color4); 81 | 82 | gl.drawArrays(gl.POINTS, 0, 1); 83 | } 84 | 85 | //////////////////// 86 | 87 | function InitCanvas(canvas) { 88 | var gl = canvas.getContext('webgl'); 89 | if (!gl) 90 | throw 'Failed to get a webgl context.'; 91 | 92 | var prog = BuildProgram(gl, kVertShaderSource, kFragShaderSource); 93 | if (!prog) 94 | throw 'BuildProgram failed.'; 95 | 96 | prog.aPos = gl.getAttribLocation(prog, "aPos"); 97 | prog.aColor = gl.getAttribLocation(prog, "aColor"); 98 | 99 | g_gl = gl; 100 | g_prog = prog; 101 | } 102 | 103 | function RenderFrame(blueNormX, blueNormY) { 104 | var gl = g_gl; 105 | var prog = g_prog; 106 | 107 | var bgColor = kIsWorker ? [0.0, 0.3, 0.0] 108 | : [0.3, 0.0, 0.0]; 109 | gl.clearColor(bgColor[0], bgColor[1], bgColor[2], 1.0); 110 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 111 | 112 | gl.enable(gl.DEPTH_TEST); 113 | 114 | gl.useProgram(prog); 115 | gl.disableVertexAttribArray(prog.aPos); 116 | gl.disableVertexAttribArray(prog.aColor); 117 | 118 | DrawPoint(gl, prog, [-0.1, -0.1, -0.1], [1.0, 0.0, 0.0, 1.0]); 119 | DrawPoint(gl, prog, [ 0.1, 0.1, 0.1], [0.0, 1.0, 0.0, 1.0]); 120 | 121 | var blueClipX = 2.0*blueNormX - 1.0; 122 | var blueClipY = -(2.0*blueNormY - 1.0); // Browser is origin-top-left, we're origin-bottom-left. 123 | 124 | DrawPoint(gl, prog, [blueClipX, blueClipY, 0.0], [0.0, 0.0, 1.0, 1.0]); 125 | 126 | if ('commit' in gl) 127 | gl.commit(); 128 | } 129 | -------------------------------------------------------------------------------- /webgl-one-to-many/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 34 | 35 | 36 |
      37 |
      38 | Top: 39 |
      40 | 41 | 42 |
      43 | 44 | Front: 45 |
      46 | 47 | 48 |
      49 | 50 | Left: 51 |
      52 | 53 |
      54 |
      55 | Free: 56 |
      57 | 58 |
      59 |
      60 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /webgl/InL7Il5.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kdashg/misc/ff6029a0ef866e1461cda190fcd463b32755ec21/webgl/InL7Il5.mp4 -------------------------------------------------------------------------------- /webgl/best-practices.md: -------------------------------------------------------------------------------- 1 | *(these will often apply to OpenGL as well)* 2 | 3 | ## Avoid invalidating FBOs' state 4 | Almost any change to an FBO's state will invalidate its framebuffer 5 | completeness. 6 | Set up your hot framebuffers ahead of time. 7 | 8 | ## Delete objects eagerly 9 | Don't wait for the GC/CC to realize objects are orphaned and destroy them. 10 | Implementations track the liveness of objects, so 'deleting' them at the API level only 11 | releases the handle that refers to the actual object. (conceptually releasing the handle's 12 | ref-pointer to the object) 13 | Only once the object is unused in the implementation is it actually freed. 14 | For example, if you never want to access your shaders directly again, just delete their 15 | handles after attaching them to a program. 16 | 17 | ## Flush when expecting results (like queries or rendering frame completion) 18 | Flush tells the implementation to push all pending commands out for execution, not leaving 19 | any around in a buffer, waiting for more commands to execute. 20 | 21 | For example, it is possible for the following to never complete without context loss: 22 | ~~~ 23 | sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); 24 | glClientWaitSync(sync, 0, GL_TIMEOUT_IGNORED); 25 | ~~~ 26 | 27 | WebGL doesn't have a SwapBuffers call by default, so a flush can help fill the gap, as 28 | well. 29 | -------------------------------------------------------------------------------- /webgl/ccw-point.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /webgl/device-pixel-presnap.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Max frames: 8 |
      Workaround: macOS+Chrome non-devicePixelRatio-multiple snapping issue. 9 |
      10 |
      
       11 |     
      12 | 13 |
      14 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /webgl/device-pixel-tester.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
      8 |

      e.style.left: , .top 9 |

      e.width: , .height: 10 |

      canvas.width: , .height: 11 |

      
      12 |     
      13 |     
      94 |   
      95 | 
      96 | 
      
      
      --------------------------------------------------------------------------------
      /webgl/fingerprint-v1.html:
      --------------------------------------------------------------------------------
        1 | 
        2 | 
        3 | 
        4 | 
        5 | 
        6 | 
        7 | 

      Please submit your fingerprinting results: Google Form

      8 |

      JSON text data:

      9 |
       10 | ...
       11 | 
      12 |

      Canvases:

      13 |
      14 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /webgl/fingerprint.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

      Expectations:

      8 | (default antialias:false) 9 |
      10 | Intel 630 on Windows: 85a84610938fb26a59069cfc6070f9fbbbb8a9e5
      11 | * 4xAA: dbc7de02fdc7be37a132c0072feae36f2e9f6ca2
      12 | * 8xAA: 0acba529af42da4a22afc40892994bd7bb9c6a68
      13 | 
      14 |
      15 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /webgl/low-power.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 28 | 29 | -------------------------------------------------------------------------------- /webgl/many-triangles.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 35 | 45 | 46 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /webgl/ping-pong.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 179 | 180 | 181 | -------------------------------------------------------------------------------- /webgl/screen-quad.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 28 | 29 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /webgl/testcase-srgb-average.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
      10 | 11 | 12 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /webgl/video-upload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

      Video upload sources

      8 |
        9 |
      • 10 |
      • 11 |
      • 12 |
          13 |
        14 |
      15 |
      Duplicate uploads (cruelty): 16 |
      Score: - 17 |
      18 | 273 | 274 | 275 | -------------------------------------------------------------------------------- /webgl/viewport-scissor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 28 | 29 |
      Scissor xMax: 30 |
      Viewport xMax: 31 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /webgl/webgl-clipspace.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 15 | 24 | 25 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /webgl/webgl-feature-levels.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 36 |
      37 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /webgl/webgl-v1.1.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | // WebGL 1 has universal support for the following: 3 | // * ANGLE_instanced_arrays 4 | // * EXT_blend_minmax 5 | // * OES_element_index_uint 6 | // * OES_standard_derivatives 7 | // * OES_vertex_array_object 8 | // * WEBGL_debug_renderer_info 9 | // * WEBGL_lose_context 10 | // Include this library to modify WebGL 1.0 into 1.1 by including these extensions. 11 | 12 | if (WebGLRenderingContext.prototype.drawArraysInstanced) return; 13 | 14 | // - 15 | 16 | let prev = WebGLRenderingContext.prototype.getParameter; 17 | WebGLRenderingContext.prototype.getParameter = function(pname) { 18 | if (pname == this.VERSION) 19 | return 'WebGL 1.1'; 20 | 21 | if (pname == this.SHADING_LANGUAGE_VERSION) 22 | return 'WebGL GLSL ES 1.1'; 23 | 24 | return prev.apply(this, arguments); 25 | } 26 | 27 | const SHADER_PREAMBLE = ` 28 | #extension GL_OES_standard_derivatives : enable 29 | #line 1 30 | `; 31 | 32 | prev = WebGLRenderingContext.prototype.shaderSource; 33 | WebGLRenderingContext.prototype.shaderSource = function(shader, source) { 34 | const modified = SHADER_PREAMBLE + source; 35 | return prev.call(this, shader, modified); 36 | } 37 | 38 | // These need to indirect through the current context's valid extensions, since context 39 | // loss detaches/invalidates all extensions. (except for WEBGL_lose_context) 40 | 41 | WebGLRenderingContext.prototype.drawArraysInstanced = function() { 42 | this._ANGLE_instanced_arrays.drawArraysInstancedANGLE(...arguments); 43 | } 44 | WebGLRenderingContext.prototype.drawElementsInstanced = function() { 45 | this._ANGLE_instanced_arrays.drawElementsInstancedANGLE(...arguments); 46 | } 47 | WebGLRenderingContext.prototype.vertexAttribDivisor = function() { 48 | this._ANGLE_instanced_arrays.vertexAttribDivisorANGLE(...arguments); 49 | } 50 | 51 | WebGLRenderingContext.prototype.createVertexArray = function() { 52 | return this._OES_vertex_array_object.createVertexArrayOES(...arguments); 53 | } 54 | WebGLRenderingContext.prototype.deleteVertexArray = function() { 55 | this._OES_vertex_array_object.deleteVertexArrayOES(...arguments); 56 | } 57 | WebGLRenderingContext.prototype.isVertexArray = function() { 58 | return this._OES_vertex_array_object.isVertexArrayOES(...arguments); 59 | } 60 | WebGLRenderingContext.prototype.bindVertexArray = function() { 61 | this._OES_vertex_array_object.bindVertexArrayOES(...arguments); 62 | } 63 | 64 | // Handle these the same way, even though this extension isn't lost. 65 | WebGLRenderingContext.prototype.loseContext = function() { 66 | this._WEBGL_lose_context.loseContext(...arguments); 67 | } 68 | WebGLRenderingContext.prototype.restoreContext = function() { 69 | this._WEBGL_lose_context.restoreContext(...arguments); 70 | } 71 | 72 | function init_exts(gl) { 73 | const reset_exts = () => { 74 | let ext; 75 | 76 | ext = gl._ANGLE_instanced_arrays = gl.getExtension('ANGLE_instanced_arrays'); 77 | gl.VERTEX_ATTRIB_ARRAY_DIVISOR = ext.VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE; 78 | 79 | // - 80 | 81 | ext = gl.getExtension('EXT_blend_minmax'); 82 | gl.MIN = ext.MIN_EXT; 83 | gl.MAX = ext.MAX_EXT; 84 | 85 | // - 86 | 87 | gl.getExtension('OES_element_index_uint'); 88 | 89 | // - 90 | 91 | ext = gl.getExtension('OES_standard_derivatives'); 92 | gl.FRAGMENT_SHADER_DERIVATIVE_HINT = ext.FRAGMENT_SHADER_DERIVATIVE_HINT_OES; 93 | 94 | // - 95 | 96 | ext = gl._OES_vertex_array_object = gl.getExtension('OES_vertex_array_object'); 97 | gl.VERTEX_ARRAY_BINDING = ext.VERTEX_ARRAY_BINDING; 98 | 99 | // - 100 | 101 | ext = gl.getExtension('WEBGL_debug_renderer_info'); 102 | gl.UNMASKED_VENDOR = ext.UNMASKED_VENDOR_WEBGL; 103 | gl.UNMASKED_RENDERER = ext.UNMASKED_RENDERER_WEBGL; 104 | 105 | // - 106 | 107 | gl._WEBGL_lose_context = gl.getExtension('WEBGL_lose_context'); 108 | }; 109 | reset_exts(); 110 | gl.canvas.addEventListener('webglcontextrestored', reset_exts); 111 | }; 112 | 113 | prev = HTMLCanvasElement.prototype.getContext; 114 | HTMLCanvasElement.prototype.getContext = function() { 115 | const ret = prev.apply(this, arguments); 116 | 117 | if (ret instanceof WebGLRenderingContext && !ret._WEBGL_lose_context) { 118 | init_exts(ret); 119 | } 120 | 121 | return ret; 122 | } 123 | })(); 124 | -------------------------------------------------------------------------------- /webgpu/adapter-report.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
      
       8 |         
      92 |     
      93 | 
      94 | 
      
      
      --------------------------------------------------------------------------------
      /webgpu/example-wgsl-preprocess-and-map-errors.html:
      --------------------------------------------------------------------------------
        1 | 
        2 | 
        3 | 
        4 | 
        5 | 
        6 | 
        7 | 
        8 | #e_main:
        9 | 
      10 |
      11 | 12 | 13 |
      14 |
      15 | WGSL translated error log: 16 |
      17 |
      
       18 | 
      167 | 
      168 | 
      169 | 
      
      
      --------------------------------------------------------------------------------
      /webgpu/wgsl-boids-ptr-compiled.html:
      --------------------------------------------------------------------------------
        1 | 
        2 | 
        3 | 
        4 | 
        5 | 
        6 |   
      7 |
      
        8 |     
      
        9 |     
      
       10 |     
      
       11 |   
      12 | 263 | 264 | 265 | -------------------------------------------------------------------------------- /webtorrent.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | 23 | 24 |

      Basic UI for https://webtorrent.io

      25 | NB: In the browser, WebTorrent can only download torrents that are seeded by a WebRTC-capable torrent client. 26 |
      Magnet URI, torrent URL, or info hash hex string: 27 |
      28 |
      29 |
      30 | 275 | 276 | 277 | --------------------------------------------------------------------------------